From 1ff19bf38fd927a4349e06b911cf708ff661e5fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Paul=20Nicou=C3=A9?= Date: Fri, 29 Oct 2021 18:05:46 +0200 Subject: [PATCH] Initial commit --- .editorconfig | 22 + .gitignore | 43 + .htaccess | 57 + README.md | 34 + composer.json | 34 + composer.lock | 1003 ++ content/error/error.txt | 1 + content/home/home.txt | 1 + content/site.txt | 1 + index.php | 5 + kirby/.editorconfig | 15 + kirby/SECURITY.md | 3 + kirby/assets/whoops.css | 83 + kirby/bootstrap.php | 35 + kirby/cacert.pem | 3174 +++++ kirby/composer.json | 77 + kirby/composer.lock | 733 + kirby/config/aliases.php | 62 + kirby/config/api/authentication.php | 24 + kirby/config/api/collections.php | 72 + kirby/config/api/models.php | 20 + kirby/config/api/models/File.php | 166 + kirby/config/api/models/FileBlueprint.php | 26 + kirby/config/api/models/FileVersion.php | 83 + kirby/config/api/models/Language.php | 44 + kirby/config/api/models/Page.php | 157 + kirby/config/api/models/PageBlueprint.php | 35 + kirby/config/api/models/Role.php | 31 + kirby/config/api/models/Site.php | 72 + kirby/config/api/models/SiteBlueprint.php | 26 + kirby/config/api/models/System.php | 136 + kirby/config/api/models/Translation.php | 34 + kirby/config/api/models/User.php | 108 + kirby/config/api/models/UserBlueprint.php | 26 + kirby/config/api/routes.php | 26 + kirby/config/api/routes/auth.php | 108 + kirby/config/api/routes/files.php | 123 + kirby/config/api/routes/languages.php | 46 + kirby/config/api/routes/lock.php | 99 + kirby/config/api/routes/pages.php | 127 + kirby/config/api/routes/roles.php | 28 + kirby/config/api/routes/site.php | 110 + kirby/config/api/routes/system.php | 79 + kirby/config/api/routes/translations.php | 24 + kirby/config/api/routes/users.php | 160 + kirby/config/blocks/code/code.php | 2 + kirby/config/blocks/code/code.yml | 59 + kirby/config/blocks/gallery/gallery.php | 10 + kirby/config/blocks/gallery/gallery.yml | 15 + kirby/config/blocks/heading/heading.php | 2 + kirby/config/blocks/heading/heading.yml | 24 + kirby/config/blocks/image/image.php | 35 + kirby/config/blocks/image/image.yml | 59 + kirby/config/blocks/list/list.php | 2 + kirby/config/blocks/list/list.yml | 8 + kirby/config/blocks/markdown/markdown.php | 2 + kirby/config/blocks/markdown/markdown.yml | 11 + kirby/config/blocks/quote/quote.php | 9 + kirby/config/blocks/quote/quote.yml | 17 + kirby/config/blocks/table/table.yml | 3 + kirby/config/blocks/text/text.php | 2 + kirby/config/blocks/text/text.yml | 9 + kirby/config/blocks/video/video.php | 9 + kirby/config/blocks/video/video.yml | 12 + kirby/config/blueprints.php | 26 + kirby/config/blueprints/blocks/code.yml | 56 + kirby/config/blueprints/blocks/heading.yml | 21 + kirby/config/blueprints/blocks/image.yml | 16 + kirby/config/blueprints/blocks/quote.yml | 12 + kirby/config/blueprints/blocks/table.yml | 25 + kirby/config/blueprints/blocks/text.yml | 5 + kirby/config/blueprints/blocks/video.yml | 8 + kirby/config/blueprints/files/default.yml | 2 + kirby/config/blueprints/pages/default.yml | 3 + kirby/config/blueprints/site.yml | 7 + kirby/config/components.php | 378 + kirby/config/fields.php | 32 + kirby/config/fields/checkboxes.php | 61 + kirby/config/fields/date.php | 179 + kirby/config/fields/email.php | 40 + kirby/config/fields/files.php | 138 + kirby/config/fields/gap.php | 5 + kirby/config/fields/headline.php | 26 + kirby/config/fields/hidden.php | 3 + kirby/config/fields/info.php | 44 + kirby/config/fields/line.php | 5 + kirby/config/fields/list.php | 17 + kirby/config/fields/mixins/datetime.php | 28 + kirby/config/fields/mixins/filepicker.php | 14 + kirby/config/fields/mixins/min.php | 22 + kirby/config/fields/mixins/options.php | 48 + kirby/config/fields/mixins/pagepicker.php | 14 + kirby/config/fields/mixins/picker.php | 78 + kirby/config/fields/mixins/upload.php | 72 + kirby/config/fields/mixins/userpicker.php | 13 + kirby/config/fields/multiselect.php | 32 + kirby/config/fields/number.php | 48 + kirby/config/fields/pages.php | 117 + kirby/config/fields/radio.php | 29 + kirby/config/fields/range.php | 24 + kirby/config/fields/select.php | 24 + kirby/config/fields/structure.php | 193 + kirby/config/fields/tags.php | 103 + kirby/config/fields/tel.php | 27 + kirby/config/fields/text.php | 103 + kirby/config/fields/textarea.php | 123 + kirby/config/fields/time.php | 151 + kirby/config/fields/toggle.php | 66 + kirby/config/fields/url.php | 41 + kirby/config/fields/users.php | 97 + kirby/config/fields/writer.php | 33 + kirby/config/helpers.php | 953 ++ kirby/config/methods.php | 606 + kirby/config/presets/files.php | 24 + kirby/config/presets/page.php | 72 + kirby/config/presets/pages.php | 57 + kirby/config/roots.php | 93 + kirby/config/routes.php | 151 + kirby/config/sections/fields.php | 56 + kirby/config/sections/files.php | 251 + kirby/config/sections/info.php | 35 + kirby/config/sections/mixins/empty.php | 21 + kirby/config/sections/mixins/headline.php | 23 + kirby/config/sections/mixins/help.php | 23 + kirby/config/sections/mixins/layout.php | 12 + kirby/config/sections/mixins/max.php | 28 + kirby/config/sections/mixins/min.php | 21 + kirby/config/sections/mixins/pagination.php | 36 + kirby/config/sections/mixins/parent.php | 43 + kirby/config/sections/pages.php | 308 + kirby/config/setup.php | 36 + kirby/config/snippets.php | 17 + kirby/config/tags.php | 353 + kirby/config/templates.php | 8 + kirby/config/templates/emails/auth/login.php | 8 + .../templates/emails/auth/password-reset.php | 8 + kirby/config/urls.php | 33 + .../parsedown-extra/ParsedownExtra.php | 627 + kirby/dependencies/parsedown/Parsedown.php | 1822 +++ kirby/i18n/rules/LICENSE | 9 + kirby/i18n/rules/ar.json | 30 + kirby/i18n/rules/az.json | 16 + kirby/i18n/rules/bg.json | 65 + kirby/i18n/rules/cs.json | 20 + kirby/i18n/rules/da.json | 10 + kirby/i18n/rules/de.json | 9 + kirby/i18n/rules/el.json | 111 + kirby/i18n/rules/eo.json | 14 + kirby/i18n/rules/et.json | 14 + kirby/i18n/rules/fa.json | 36 + kirby/i18n/rules/fi.json | 6 + kirby/i18n/rules/fr.json | 34 + kirby/i18n/rules/hi.json | 66 + kirby/i18n/rules/hr.json | 12 + kirby/i18n/rules/hu.json | 20 + kirby/i18n/rules/hy.json | 79 + kirby/i18n/rules/it.json | 13 + kirby/i18n/rules/ja.json | 182 + kirby/i18n/rules/ka.json | 35 + kirby/i18n/rules/ko.json | 11174 ++++++++++++++++ kirby/i18n/rules/lt.json | 20 + kirby/i18n/rules/lv.json | 18 + kirby/i18n/rules/mk.json | 64 + kirby/i18n/rules/my.json | 121 + kirby/i18n/rules/nb.json | 8 + kirby/i18n/rules/pl.json | 20 + kirby/i18n/rules/pt_BR.json | 187 + kirby/i18n/rules/rm.json | 16 + kirby/i18n/rules/ru.json | 68 + kirby/i18n/rules/sr.json | 72 + kirby/i18n/rules/sv_SE.json | 8 + kirby/i18n/rules/tr.json | 14 + kirby/i18n/rules/uk.json | 10 + kirby/i18n/rules/vi.json | 135 + kirby/i18n/rules/zh.json | 6937 ++++++++++ kirby/i18n/translations/bg.json | 527 + kirby/i18n/translations/ca.json | 527 + kirby/i18n/translations/cs.json | 527 + kirby/i18n/translations/da.json | 527 + kirby/i18n/translations/de.json | 527 + kirby/i18n/translations/el.json | 527 + kirby/i18n/translations/en.json | 527 + kirby/i18n/translations/es_419.json | 527 + kirby/i18n/translations/es_ES.json | 527 + kirby/i18n/translations/fa.json | 527 + kirby/i18n/translations/fi.json | 527 + kirby/i18n/translations/fr.json | 527 + kirby/i18n/translations/hu.json | 527 + kirby/i18n/translations/id.json | 527 + kirby/i18n/translations/it.json | 527 + kirby/i18n/translations/ko.json | 527 + kirby/i18n/translations/lt.json | 527 + kirby/i18n/translations/nb.json | 527 + kirby/i18n/translations/nl.json | 527 + kirby/i18n/translations/pl.json | 527 + kirby/i18n/translations/pt_BR.json | 527 + kirby/i18n/translations/pt_PT.json | 527 + kirby/i18n/translations/ru.json | 527 + kirby/i18n/translations/sk.json | 527 + kirby/i18n/translations/sv_SE.json | 527 + kirby/i18n/translations/tr.json | 527 + kirby/kirby.pub | 9 + kirby/panel/dist/apple-touch-icon.png | Bin 0 -> 4115 bytes kirby/panel/dist/css/app.css | 1 + kirby/panel/dist/favicon.png | Bin 0 -> 539 bytes kirby/panel/dist/favicon.svg | 9 + kirby/panel/dist/img/icons.svg | 449 + kirby/panel/dist/js/app.js | 1 + kirby/panel/dist/js/plugins.js | 86 + kirby/panel/dist/js/vendor.js | 30 + kirby/router.php | 12 + kirby/src/Api/Api.php | 835 ++ kirby/src/Api/Collection.php | 178 + kirby/src/Api/Model.php | 248 + kirby/src/Cache/ApcuCache.php | 86 + kirby/src/Cache/Cache.php | 242 + kirby/src/Cache/FileCache.php | 234 + kirby/src/Cache/MemCached.php | 99 + kirby/src/Cache/MemoryCache.php | 82 + kirby/src/Cache/NullCache.php | 69 + kirby/src/Cache/Value.php | 152 + kirby/src/Cms/Api.php | 317 + kirby/src/Cms/App.php | 1554 +++ kirby/src/Cms/AppCaches.php | 137 + kirby/src/Cms/AppErrors.php | 189 + kirby/src/Cms/AppPlugins.php | 816 ++ kirby/src/Cms/AppTranslations.php | 234 + kirby/src/Cms/AppUsers.php | 143 + kirby/src/Cms/Asset.php | 126 + kirby/src/Cms/Auth.php | 872 ++ kirby/src/Cms/Auth/Challenge.php | 63 + kirby/src/Cms/Auth/EmailChallenge.php | 76 + kirby/src/Cms/Auth/Status.php | 219 + kirby/src/Cms/Block.php | 240 + kirby/src/Cms/BlockConverter.php | 272 + kirby/src/Cms/Blocks.php | 154 + kirby/src/Cms/Blueprint.php | 800 ++ kirby/src/Cms/Collection.php | 340 + kirby/src/Cms/Collections.php | 141 + kirby/src/Cms/Content.php | 266 + kirby/src/Cms/ContentLock.php | 232 + kirby/src/Cms/ContentLocks.php | 228 + kirby/src/Cms/ContentTranslation.php | 242 + kirby/src/Cms/Dir.php | 180 + kirby/src/Cms/Email.php | 255 + kirby/src/Cms/Event.php | 290 + kirby/src/Cms/Field.php | 257 + kirby/src/Cms/Fieldset.php | 212 + kirby/src/Cms/Fieldsets.php | 100 + kirby/src/Cms/File.php | 809 ++ kirby/src/Cms/FileActions.php | 305 + kirby/src/Cms/FileBlueprint.php | 181 + kirby/src/Cms/FileFoundation.php | 248 + kirby/src/Cms/FileModifications.php | 202 + kirby/src/Cms/FilePermissions.php | 17 + kirby/src/Cms/FilePicker.php | 74 + kirby/src/Cms/FileRules.php | 302 + kirby/src/Cms/FileVersion.php | 143 + kirby/src/Cms/Filename.php | 303 + kirby/src/Cms/Files.php | 145 + kirby/src/Cms/Form.php | 130 + kirby/src/Cms/HasChildren.php | 241 + kirby/src/Cms/HasFiles.php | 226 + kirby/src/Cms/HasMethods.php | 80 + kirby/src/Cms/HasSiblings.php | 182 + kirby/src/Cms/Html.php | 30 + kirby/src/Cms/Ingredients.php | 95 + kirby/src/Cms/Item.php | 137 + kirby/src/Cms/Items.php | 97 + kirby/src/Cms/KirbyTag.php | 64 + kirby/src/Cms/KirbyTags.php | 45 + kirby/src/Cms/Language.php | 684 + kirby/src/Cms/LanguageRouter.php | 135 + kirby/src/Cms/LanguageRoutes.php | 155 + kirby/src/Cms/LanguageRules.php | 98 + kirby/src/Cms/Languages.php | 99 + kirby/src/Cms/Layout.php | 120 + kirby/src/Cms/LayoutColumn.php | 122 + kirby/src/Cms/LayoutColumns.php | 18 + kirby/src/Cms/Layouts.php | 66 + kirby/src/Cms/Media.php | 172 + kirby/src/Cms/Model.php | 109 + kirby/src/Cms/ModelPermissions.php | 116 + kirby/src/Cms/ModelWithContent.php | 804 ++ kirby/src/Cms/Nest.php | 48 + kirby/src/Cms/NestCollection.php | 33 + kirby/src/Cms/NestObject.php | 43 + kirby/src/Cms/Page.php | 1579 +++ kirby/src/Cms/PageActions.php | 862 ++ kirby/src/Cms/PageBlueprint.php | 209 + kirby/src/Cms/PagePermissions.php | 80 + kirby/src/Cms/PagePicker.php | 265 + kirby/src/Cms/PageRules.php | 432 + kirby/src/Cms/PageSiblings.php | 140 + kirby/src/Cms/Pages.php | 515 + kirby/src/Cms/Pagination.php | 179 + kirby/src/Cms/Panel.php | 139 + kirby/src/Cms/PanelPlugins.php | 108 + kirby/src/Cms/Permissions.php | 230 + kirby/src/Cms/Picker.php | 176 + kirby/src/Cms/Plugin.php | 158 + kirby/src/Cms/PluginAssets.php | 84 + kirby/src/Cms/R.php | 25 + kirby/src/Cms/Responder.php | 313 + kirby/src/Cms/Response.php | 30 + kirby/src/Cms/Role.php | 232 + kirby/src/Cms/Roles.php | 139 + kirby/src/Cms/S.php | 25 + kirby/src/Cms/Search.php | 62 + kirby/src/Cms/Section.php | 107 + kirby/src/Cms/Site.php | 678 + kirby/src/Cms/SiteActions.php | 98 + kirby/src/Cms/SiteBlueprint.php | 60 + kirby/src/Cms/SitePermissions.php | 17 + kirby/src/Cms/SiteRules.php | 58 + kirby/src/Cms/Structure.php | 64 + kirby/src/Cms/StructureObject.php | 209 + kirby/src/Cms/System.php | 545 + kirby/src/Cms/Template.php | 201 + kirby/src/Cms/Translation.php | 195 + kirby/src/Cms/Translations.php | 78 + kirby/src/Cms/Url.php | 69 + kirby/src/Cms/User.php | 959 ++ kirby/src/Cms/UserActions.php | 354 + kirby/src/Cms/UserBlueprint.php | 47 + kirby/src/Cms/UserPermissions.php | 67 + kirby/src/Cms/UserPicker.php | 69 + kirby/src/Cms/UserRules.php | 365 + kirby/src/Cms/Users.php | 140 + kirby/src/Cms/Visitor.php | 25 + kirby/src/Data/Data.php | 127 + kirby/src/Data/Handler.php | 66 + kirby/src/Data/Json.php | 57 + kirby/src/Data/PHP.php | 94 + kirby/src/Data/Txt.php | 133 + kirby/src/Data/Xml.php | 64 + kirby/src/Data/Yaml.php | 77 + kirby/src/Database/Database.php | 664 + kirby/src/Database/Db.php | 284 + kirby/src/Database/Query.php | 1070 ++ kirby/src/Database/Sql.php | 955 ++ kirby/src/Database/Sql/Mysql.php | 59 + kirby/src/Database/Sql/Sqlite.php | 143 + kirby/src/Email/Body.php | 85 + kirby/src/Email/Email.php | 472 + kirby/src/Email/PHPMailer.php | 113 + .../src/Exception/BadMethodCallException.php | 21 + kirby/src/Exception/DuplicateException.php | 21 + kirby/src/Exception/ErrorPageException.php | 21 + kirby/src/Exception/Exception.php | 221 + .../Exception/InvalidArgumentException.php | 21 + kirby/src/Exception/LogicException.php | 20 + kirby/src/Exception/NotFoundException.php | 20 + kirby/src/Exception/PermissionException.php | 21 + kirby/src/Form/Field.php | 506 + kirby/src/Form/Field/BlocksField.php | 274 + kirby/src/Form/Field/LayoutField.php | 233 + kirby/src/Form/FieldClass.php | 638 + kirby/src/Form/Fields.php | 57 + kirby/src/Form/Form.php | 277 + kirby/src/Form/Mixin/EmptyState.php | 18 + kirby/src/Form/Mixin/Max.php | 18 + kirby/src/Form/Mixin/Min.php | 18 + kirby/src/Form/Options.php | 207 + kirby/src/Form/OptionsApi.php | 247 + kirby/src/Form/OptionsQuery.php | 291 + kirby/src/Form/Validations.php | 294 + kirby/src/Http/Cookie.php | 209 + .../Http/Exceptions/NextRouteException.php | 16 + kirby/src/Http/Header.php | 316 + kirby/src/Http/Idn.php | 64 + kirby/src/Http/Params.php | 155 + kirby/src/Http/Path.php | 47 + kirby/src/Http/Query.php | 58 + kirby/src/Http/Remote.php | 404 + kirby/src/Http/Request.php | 413 + kirby/src/Http/Request/Auth/BasicAuth.php | 78 + kirby/src/Http/Request/Auth/BearerAuth.php | 54 + kirby/src/Http/Request/Body.php | 129 + kirby/src/Http/Request/Data.php | 84 + kirby/src/Http/Request/Files.php | 73 + kirby/src/Http/Request/Query.php | 98 + kirby/src/Http/Response.php | 317 + kirby/src/Http/Route.php | 230 + kirby/src/Http/Router.php | 169 + kirby/src/Http/Server.php | 169 + kirby/src/Http/Uri.php | 563 + kirby/src/Http/Url.php | 287 + kirby/src/Http/Visitor.php | 252 + kirby/src/Image/Camera.php | 93 + kirby/src/Image/Darkroom.php | 145 + kirby/src/Image/Darkroom/GdLib.php | 109 + kirby/src/Image/Darkroom/ImageMagick.php | 228 + kirby/src/Image/Dimensions.php | 430 + kirby/src/Image/Exif.php | 296 + kirby/src/Image/Image.php | 339 + kirby/src/Image/Location.php | 136 + kirby/src/Parsley/Element.php | 100 + kirby/src/Parsley/Inline.php | 72 + kirby/src/Parsley/Parsley.php | 232 + kirby/src/Parsley/Schema.php | 11 + kirby/src/Parsley/Schema/Blocks.php | 317 + kirby/src/Parsley/Schema/Plain.php | 40 + kirby/src/Sane/Handler.php | 51 + kirby/src/Sane/Sane.php | 129 + kirby/src/Sane/Svg.php | 486 + kirby/src/Sane/Svgz.php | 39 + kirby/src/Sane/Xml.php | 339 + kirby/src/Session/AutoSession.php | 171 + kirby/src/Session/FileSessionStore.php | 481 + kirby/src/Session/Session.php | 779 ++ kirby/src/Session/SessionData.php | 255 + kirby/src/Session/SessionStore.php | 110 + kirby/src/Session/Sessions.php | 288 + kirby/src/Text/KirbyTag.php | 149 + kirby/src/Text/KirbyTags.php | 56 + kirby/src/Text/Markdown.php | 79 + kirby/src/Text/SmartyPants.php | 129 + kirby/src/Toolkit/A.php | 718 + kirby/src/Toolkit/Collection.php | 1463 ++ kirby/src/Toolkit/Component.php | 290 + kirby/src/Toolkit/Config.php | 21 + kirby/src/Toolkit/Controller.php | 66 + kirby/src/Toolkit/Dir.php | 444 + kirby/src/Toolkit/Escape.php | 162 + kirby/src/Toolkit/F.php | 865 ++ kirby/src/Toolkit/Facade.php | 36 + kirby/src/Toolkit/File.php | 358 + kirby/src/Toolkit/Html.php | 573 + kirby/src/Toolkit/I18n.php | 308 + kirby/src/Toolkit/Iterator.php | 181 + kirby/src/Toolkit/Locale.php | 183 + kirby/src/Toolkit/Mime.php | 343 + kirby/src/Toolkit/Obj.php | 106 + kirby/src/Toolkit/Pagination.php | 488 + kirby/src/Toolkit/Properties.php | 151 + kirby/src/Toolkit/Query.php | 244 + kirby/src/Toolkit/Silo.php | 73 + kirby/src/Toolkit/Str.php | 1176 ++ kirby/src/Toolkit/Tpl.php | 49 + kirby/src/Toolkit/V.php | 527 + kirby/src/Toolkit/View.php | 136 + kirby/src/Toolkit/Xml.php | 433 + kirby/vendor/autoload.php | 7 + kirby/vendor/claviska/simpleimage/LICENSE.md | 7 + .../vendor/claviska/simpleimage/composer.json | 22 + .../simpleimage/src/claviska/SimpleImage.php | 2151 +++ kirby/vendor/composer/ClassLoader.php | 481 + kirby/vendor/composer/InstalledVersions.php | 337 + kirby/vendor/composer/LICENSE | 21 + kirby/vendor/composer/autoload_classmap.php | 298 + kirby/vendor/composer/autoload_files.php | 14 + kirby/vendor/composer/autoload_namespaces.php | 11 + kirby/vendor/composer/autoload_psr4.php | 18 + kirby/vendor/composer/autoload_real.php | 73 + kirby/vendor/composer/autoload_static.php | 406 + kirby/vendor/composer/installed.json | 752 ++ kirby/vendor/composer/installed.php | 143 + kirby/vendor/filp/whoops/LICENSE.md | 19 + kirby/vendor/filp/whoops/composer.json | 45 + .../src/Whoops/Exception/ErrorException.php | 17 + .../whoops/src/Whoops/Exception/Formatter.php | 74 + .../whoops/src/Whoops/Exception/Frame.php | 295 + .../src/Whoops/Exception/FrameCollection.php | 203 + .../whoops/src/Whoops/Exception/Inspector.php | 323 + .../src/Whoops/Handler/CallbackHandler.php | 52 + .../whoops/src/Whoops/Handler/Handler.php | 95 + .../src/Whoops/Handler/HandlerInterface.php | 36 + .../Whoops/Handler/JsonResponseHandler.php | 88 + .../src/Whoops/Handler/PlainTextHandler.php | 359 + .../src/Whoops/Handler/PrettyPageHandler.php | 830 ++ .../src/Whoops/Handler/XmlResponseHandler.php | 107 + .../src/Whoops/Resources/css/whoops.base.css | 604 + .../src/Whoops/Resources/js/clipboard.min.js | 7 + .../src/Whoops/Resources/js/prettify.min.js | 28 + .../src/Whoops/Resources/js/whoops.base.js | 210 + .../src/Whoops/Resources/js/zepto.min.js | 2 + .../Resources/views/env_details.html.php | 42 + .../Resources/views/frame_code.html.php | 63 + .../Resources/views/frame_list.html.php | 17 + .../Resources/views/frames_container.html.php | 3 + .../views/frames_description.html.php | 14 + .../Whoops/Resources/views/header.html.php | 96 + .../Resources/views/header_outer.html.php | 3 + .../Whoops/Resources/views/layout.html.php | 33 + .../Resources/views/panel_details.html.php | 2 + .../views/panel_details_outer.html.php | 3 + .../Resources/views/panel_left.html.php | 4 + .../Resources/views/panel_left_outer.html.php | 3 + kirby/vendor/filp/whoops/src/Whoops/Run.php | 545 + .../filp/whoops/src/Whoops/RunInterface.php | 140 + .../src/Whoops/Util/HtmlDumperOutput.php | 36 + .../filp/whoops/src/Whoops/Util/Misc.php | 77 + .../whoops/src/Whoops/Util/SystemFacade.php | 144 + .../whoops/src/Whoops/Util/TemplateHelper.php | 352 + .../laminas/laminas-escaper/COPYRIGHT.md | 1 + .../vendor/laminas/laminas-escaper/LICENSE.md | 26 + .../laminas/laminas-escaper/composer.json | 61 + .../laminas/laminas-escaper/src/Escaper.php | 391 + .../src/Exception/ExceptionInterface.php | 13 + .../Exception/InvalidArgumentException.php | 17 + .../src/Exception/RuntimeException.php | 17 + .../laminas-zendframework-bridge/COPYRIGHT.md | 1 + .../laminas-zendframework-bridge/LICENSE.md | 26 + .../composer.json | 61 + .../config/replacements.php | 372 + .../src/Autoloader.php | 166 + .../src/ConfigPostProcessor.php | 426 + .../src/Module.php | 48 + .../src/Replacements.php | 40 + .../src/RewriteRules.php | 73 + .../src/autoload.php | 3 + kirby/vendor/league/color-extractor/LICENSE | 21 + .../league/color-extractor/composer.json | 32 + .../src/League/ColorExtractor/Color.php | 51 + .../League/ColorExtractor/ColorExtractor.php | 275 + .../src/League/ColorExtractor/Palette.php | 126 + .../vendor/michelf/php-smartypants/License.md | 36 + .../Michelf/SmartyPants.inc.php | 9 + .../php-smartypants/Michelf/SmartyPants.php | 560 + .../Michelf/SmartyPantsTypographer.inc.php | 10 + .../Michelf/SmartyPantsTypographer.php | 486 + .../michelf/php-smartypants/composer.json | 26 + kirby/vendor/mustangostang/spyc/COPYING | 21 + kirby/vendor/mustangostang/spyc/Spyc.php | 1186 ++ kirby/vendor/mustangostang/spyc/composer.json | 30 + kirby/vendor/phpmailer/phpmailer/LICENSE | 502 + .../vendor/phpmailer/phpmailer/composer.json | 65 + .../phpmailer/phpmailer/get_oauth_token.php | 146 + .../phpmailer/language/phpmailer.lang-af.php | 26 + .../phpmailer/language/phpmailer.lang-ar.php | 27 + .../phpmailer/language/phpmailer.lang-az.php | 27 + .../phpmailer/language/phpmailer.lang-ba.php | 27 + .../phpmailer/language/phpmailer.lang-be.php | 27 + .../phpmailer/language/phpmailer.lang-bg.php | 27 + .../phpmailer/language/phpmailer.lang-ca.php | 27 + .../phpmailer/language/phpmailer.lang-ch.php | 27 + .../phpmailer/language/phpmailer.lang-cs.php | 28 + .../phpmailer/language/phpmailer.lang-da.php | 29 + .../phpmailer/language/phpmailer.lang-de.php | 28 + .../phpmailer/language/phpmailer.lang-el.php | 26 + .../phpmailer/language/phpmailer.lang-eo.php | 26 + .../phpmailer/language/phpmailer.lang-es.php | 27 + .../phpmailer/language/phpmailer.lang-et.php | 28 + .../phpmailer/language/phpmailer.lang-fa.php | 28 + .../phpmailer/language/phpmailer.lang-fi.php | 28 + .../phpmailer/language/phpmailer.lang-fo.php | 27 + .../phpmailer/language/phpmailer.lang-fr.php | 32 + .../phpmailer/language/phpmailer.lang-gl.php | 27 + .../phpmailer/language/phpmailer.lang-he.php | 27 + .../phpmailer/language/phpmailer.lang-hi.php | 27 + .../phpmailer/language/phpmailer.lang-hr.php | 27 + .../phpmailer/language/phpmailer.lang-hu.php | 27 + .../phpmailer/language/phpmailer.lang-hy.php | 27 + .../phpmailer/language/phpmailer.lang-id.php | 31 + .../phpmailer/language/phpmailer.lang-it.php | 28 + .../phpmailer/language/phpmailer.lang-ja.php | 28 + .../phpmailer/language/phpmailer.lang-ka.php | 27 + .../phpmailer/language/phpmailer.lang-ko.php | 27 + .../phpmailer/language/phpmailer.lang-lt.php | 27 + .../phpmailer/language/phpmailer.lang-lv.php | 27 + .../phpmailer/language/phpmailer.lang-mg.php | 27 + .../phpmailer/language/phpmailer.lang-ms.php | 27 + .../phpmailer/language/phpmailer.lang-nb.php | 26 + .../phpmailer/language/phpmailer.lang-nl.php | 29 + .../phpmailer/language/phpmailer.lang-pl.php | 27 + .../phpmailer/language/phpmailer.lang-pt.php | 27 + .../language/phpmailer.lang-pt_br.php | 30 + .../phpmailer/language/phpmailer.lang-ro.php | 27 + .../phpmailer/language/phpmailer.lang-ru.php | 28 + .../phpmailer/language/phpmailer.lang-sk.php | 30 + .../phpmailer/language/phpmailer.lang-sl.php | 31 + .../phpmailer/language/phpmailer.lang-sr.php | 28 + .../language/phpmailer.lang-sr_latn.php | 28 + .../phpmailer/language/phpmailer.lang-sv.php | 27 + .../phpmailer/language/phpmailer.lang-tl.php | 28 + .../phpmailer/language/phpmailer.lang-tr.php | 31 + .../phpmailer/language/phpmailer.lang-uk.php | 28 + .../phpmailer/language/phpmailer.lang-vi.php | 27 + .../phpmailer/language/phpmailer.lang-zh.php | 29 + .../language/phpmailer.lang-zh_cn.php | 29 + .../phpmailer/phpmailer/src/Exception.php | 40 + .../vendor/phpmailer/phpmailer/src/OAuth.php | 139 + .../phpmailer/phpmailer/src/PHPMailer.php | 4970 +++++++ kirby/vendor/phpmailer/phpmailer/src/POP3.php | 448 + kirby/vendor/phpmailer/phpmailer/src/SMTP.php | 1456 ++ kirby/vendor/psr/log/LICENSE | 19 + .../vendor/psr/log/Psr/Log/AbstractLogger.php | 128 + .../log/Psr/Log/InvalidArgumentException.php | 7 + kirby/vendor/psr/log/Psr/Log/LogLevel.php | 18 + .../psr/log/Psr/Log/LoggerAwareInterface.php | 18 + .../psr/log/Psr/Log/LoggerAwareTrait.php | 26 + .../psr/log/Psr/Log/LoggerInterface.php | 125 + kirby/vendor/psr/log/Psr/Log/LoggerTrait.php | 142 + kirby/vendor/psr/log/Psr/Log/NullLogger.php | 30 + kirby/vendor/psr/log/composer.json | 26 + .../vendor/symfony/polyfill-mbstring/LICENSE | 19 + .../symfony/polyfill-mbstring/Mbstring.php | 869 ++ .../Resources/unidata/lowerCase.php | 1397 ++ .../Resources/unidata/titleCaseRegexp.php | 5 + .../Resources/unidata/upperCase.php | 1489 ++ .../symfony/polyfill-mbstring/bootstrap.php | 147 + .../symfony/polyfill-mbstring/bootstrap80.php | 143 + .../symfony/polyfill-mbstring/composer.json | 38 + kirby/vendor/true/punycode/LICENSE | 19 + kirby/vendor/true/punycode/composer.json | 26 + .../Exception/DomainOutOfBoundsException.php | 13 + .../Exception/LabelOutOfBoundsException.php | 13 + .../src/Exception/OutOfBoundsException.php | 13 + kirby/vendor/true/punycode/src/Punycode.php | 360 + kirby/views/fatal.php | 11 + kirby/views/panel.php | 60 + kirby/views/php.php | 11 + kirby/views/snippets/footer.php | 2 + kirby/views/snippets/header.php | 39 + media/index.html | 0 site/accounts/index.html | 0 site/blueprints/pages/default.yml | 8 + site/blueprints/site.yml | 3 + site/cache/index.html | 0 site/config/config.php | 7 + site/plugins/kirby-twig/LICENSE.md | 9 + site/plugins/kirby-twig/README.md | 102 + site/plugins/kirby-twig/composer.json | 29 + site/plugins/kirby-twig/composer.lock | 287 + site/plugins/kirby-twig/index.php | 46 + .../kirby-twig/src/classes/Environment.php | 425 + .../kirby-twig/src/classes/Functions.php | 15 + .../plugins/kirby-twig/src/classes/Plugin.php | 47 + .../kirby-twig/src/classes/Template.php | 117 + site/plugins/kirby-twig/src/classes/Tests.php | 50 + site/plugins/kirby-twig/src/errorpage.php | 126 + site/plugins/kirby-twig/src/helpers.php | 15 + site/plugins/kirby-twig/vendor/autoload.php | 7 + .../vendor/composer/ClassLoader.php | 445 + .../vendor/composer/autoload_classmap.php | 194 + .../vendor/composer/autoload_files.php | 11 + .../vendor/composer/autoload_namespaces.php | 9 + .../vendor/composer/autoload_psr4.php | 14 + .../vendor/composer/autoload_real.php | 73 + .../vendor/composer/autoload_static.php | 254 + .../vendor/symfony/polyfill-ctype/Ctype.php | 227 + .../symfony/polyfill-ctype/bootstrap.php | 50 + .../symfony/polyfill-ctype/bootstrap80.php | 46 + .../symfony/polyfill-mbstring/Mbstring.php | 869 ++ .../Resources/unidata/lowerCase.php | 1397 ++ .../Resources/unidata/titleCaseRegexp.php | 5 + .../Resources/unidata/upperCase.php | 1414 ++ .../symfony/polyfill-mbstring/bootstrap.php | 147 + .../symfony/polyfill-mbstring/bootstrap80.php | 143 + .../kirby-twig/vendor/twig/twig/CHANGELOG | 1598 +++ .../kirby-twig/vendor/twig/twig/README.rst | 24 + .../vendor/twig/twig/drupal_test.sh | 50 + .../twig/twig/src/Cache/CacheInterface.php | 46 + .../twig/twig/src/Cache/FilesystemCache.php | 87 + .../vendor/twig/twig/src/Cache/NullCache.php | 38 + .../vendor/twig/twig/src/Compiler.php | 214 + .../vendor/twig/twig/src/Environment.php | 814 ++ .../vendor/twig/twig/src/Error/Error.php | 227 + .../twig/twig/src/Error/LoaderError.php | 21 + .../twig/twig/src/Error/RuntimeError.php | 22 + .../twig/twig/src/Error/SyntaxError.php | 46 + .../vendor/twig/twig/src/ExpressionParser.php | 823 ++ .../twig/src/Extension/AbstractExtension.php | 45 + .../twig/twig/src/Extension/CoreExtension.php | 1628 +++ .../twig/src/Extension/DebugExtension.php | 64 + .../twig/src/Extension/EscaperExtension.php | 421 + .../twig/src/Extension/ExtensionInterface.php | 68 + .../twig/src/Extension/GlobalsInterface.php | 25 + .../twig/src/Extension/OptimizerExtension.php | 29 + .../twig/src/Extension/ProfilerExtension.php | 52 + .../Extension/RuntimeExtensionInterface.php | 19 + .../twig/src/Extension/SandboxExtension.php | 123 + .../twig/src/Extension/StagingExtension.php | 100 + .../src/Extension/StringLoaderExtension.php | 42 + .../vendor/twig/twig/src/ExtensionSet.php | 463 + .../src/FileExtensionEscapingStrategy.php | 60 + .../kirby-twig/vendor/twig/twig/src/Lexer.php | 501 + .../twig/twig/src/Loader/ArrayLoader.php | 78 + .../twig/twig/src/Loader/ChainLoader.php | 119 + .../twig/twig/src/Loader/FilesystemLoader.php | 283 + .../twig/twig/src/Loader/LoaderInterface.php | 49 + .../vendor/twig/twig/src/Markup.php | 47 + .../twig/twig/src/Node/AutoEscapeNode.php | 38 + .../vendor/twig/twig/src/Node/BlockNode.php | 44 + .../twig/twig/src/Node/BlockReferenceNode.php | 36 + .../vendor/twig/twig/src/Node/BodyNode.php | 21 + .../twig/src/Node/CheckSecurityCallNode.php | 28 + .../twig/twig/src/Node/CheckSecurityNode.php | 88 + .../twig/twig/src/Node/CheckToStringNode.php | 45 + .../twig/twig/src/Node/DeprecatedNode.php | 53 + .../vendor/twig/twig/src/Node/DoNode.php | 38 + .../vendor/twig/twig/src/Node/EmbedNode.php | 48 + .../Node/Expression/AbstractExpression.php | 24 + .../src/Node/Expression/ArrayExpression.php | 85 + .../Expression/ArrowFunctionExpression.php | 64 + .../Node/Expression/AssignNameExpression.php | 27 + .../Node/Expression/Binary/AbstractBinary.php | 42 + .../src/Node/Expression/Binary/AddBinary.php | 23 + .../src/Node/Expression/Binary/AndBinary.php | 23 + .../Expression/Binary/BitwiseAndBinary.php | 23 + .../Expression/Binary/BitwiseOrBinary.php | 23 + .../Expression/Binary/BitwiseXorBinary.php | 23 + .../Node/Expression/Binary/ConcatBinary.php | 23 + .../src/Node/Expression/Binary/DivBinary.php | 23 + .../Node/Expression/Binary/EndsWithBinary.php | 35 + .../Node/Expression/Binary/EqualBinary.php | 39 + .../Node/Expression/Binary/FloorDivBinary.php | 29 + .../Node/Expression/Binary/GreaterBinary.php | 39 + .../Expression/Binary/GreaterEqualBinary.php | 39 + .../src/Node/Expression/Binary/InBinary.php | 33 + .../src/Node/Expression/Binary/LessBinary.php | 39 + .../Expression/Binary/LessEqualBinary.php | 39 + .../Node/Expression/Binary/MatchesBinary.php | 33 + .../src/Node/Expression/Binary/ModBinary.php | 23 + .../src/Node/Expression/Binary/MulBinary.php | 23 + .../Node/Expression/Binary/NotEqualBinary.php | 39 + .../Node/Expression/Binary/NotInBinary.php | 33 + .../src/Node/Expression/Binary/OrBinary.php | 23 + .../Node/Expression/Binary/PowerBinary.php | 22 + .../Node/Expression/Binary/RangeBinary.php | 33 + .../Expression/Binary/SpaceshipBinary.php | 22 + .../Expression/Binary/StartsWithBinary.php | 35 + .../src/Node/Expression/Binary/SubBinary.php | 23 + .../Expression/BlockReferenceExpression.php | 86 + .../src/Node/Expression/CallExpression.php | 320 + .../Node/Expression/ConditionalExpression.php | 36 + .../Node/Expression/ConstantExpression.php | 28 + .../Node/Expression/Filter/DefaultFilter.php | 52 + .../src/Node/Expression/FilterExpression.php | 40 + .../Node/Expression/FunctionExpression.php | 43 + .../src/Node/Expression/GetAttrExpression.php | 87 + .../twig/src/Node/Expression/InlinePrint.php | 35 + .../Node/Expression/MethodCallExpression.php | 62 + .../src/Node/Expression/NameExpression.php | 97 + .../Expression/NullCoalesceExpression.php | 60 + .../src/Node/Expression/ParentExpression.php | 46 + .../Node/Expression/TempNameExpression.php | 31 + .../src/Node/Expression/Test/ConstantTest.php | 49 + .../src/Node/Expression/Test/DefinedTest.php | 74 + .../Node/Expression/Test/DivisiblebyTest.php | 36 + .../src/Node/Expression/Test/EvenTest.php | 35 + .../src/Node/Expression/Test/NullTest.php | 34 + .../twig/src/Node/Expression/Test/OddTest.php | 35 + .../src/Node/Expression/Test/SameasTest.php | 34 + .../src/Node/Expression/TestExpression.php | 42 + .../Node/Expression/Unary/AbstractUnary.php | 34 + .../src/Node/Expression/Unary/NegUnary.php | 23 + .../src/Node/Expression/Unary/NotUnary.php | 23 + .../src/Node/Expression/Unary/PosUnary.php | 23 + .../Node/Expression/VariadicExpression.php | 24 + .../vendor/twig/twig/src/Node/FlushNode.php | 35 + .../vendor/twig/twig/src/Node/ForLoopNode.php | 49 + .../vendor/twig/twig/src/Node/ForNode.php | 107 + .../vendor/twig/twig/src/Node/IfNode.php | 70 + .../vendor/twig/twig/src/Node/ImportNode.php | 63 + .../vendor/twig/twig/src/Node/IncludeNode.php | 106 + .../vendor/twig/twig/src/Node/MacroNode.php | 113 + .../vendor/twig/twig/src/Node/ModuleNode.php | 464 + .../vendor/twig/twig/src/Node/Node.php | 178 + .../twig/src/Node/NodeCaptureInterface.php | 21 + .../twig/src/Node/NodeOutputInterface.php | 21 + .../vendor/twig/twig/src/Node/PrintNode.php | 39 + .../vendor/twig/twig/src/Node/SandboxNode.php | 52 + .../vendor/twig/twig/src/Node/SetNode.php | 105 + .../vendor/twig/twig/src/Node/TextNode.php | 38 + .../vendor/twig/twig/src/Node/WithNode.php | 70 + .../vendor/twig/twig/src/NodeTraverser.php | 76 + .../src/NodeVisitor/AbstractNodeVisitor.php | 49 + .../src/NodeVisitor/EscaperNodeVisitor.php | 208 + .../MacroAutoImportNodeVisitor.php | 74 + .../src/NodeVisitor/NodeVisitorInterface.php | 46 + .../src/NodeVisitor/OptimizerNodeVisitor.php | 217 + .../NodeVisitor/SafeAnalysisNodeVisitor.php | 160 + .../src/NodeVisitor/SandboxNodeVisitor.php | 136 + .../vendor/twig/twig/src/Parser.php | 349 + .../twig/src/Profiler/Dumper/BaseDumper.php | 63 + .../src/Profiler/Dumper/BlackfireDumper.php | 72 + .../twig/src/Profiler/Dumper/HtmlDumper.php | 47 + .../twig/src/Profiler/Dumper/TextDumper.php | 35 + .../src/Profiler/Node/EnterProfileNode.php | 42 + .../src/Profiler/Node/LeaveProfileNode.php | 36 + .../NodeVisitor/ProfilerNodeVisitor.php | 76 + .../vendor/twig/twig/src/Profiler/Profile.php | 181 + .../RuntimeLoader/ContainerRuntimeLoader.php | 37 + .../RuntimeLoader/FactoryRuntimeLoader.php | 41 + .../RuntimeLoader/RuntimeLoaderInterface.php | 27 + .../twig/twig/src/Sandbox/SecurityError.php | 23 + .../Sandbox/SecurityNotAllowedFilterError.php | 33 + .../SecurityNotAllowedFunctionError.php | 33 + .../Sandbox/SecurityNotAllowedMethodError.php | 40 + .../SecurityNotAllowedPropertyError.php | 40 + .../Sandbox/SecurityNotAllowedTagError.php | 33 + .../twig/twig/src/Sandbox/SecurityPolicy.php | 126 + .../src/Sandbox/SecurityPolicyInterface.php | 35 + .../vendor/twig/twig/src/Source.php | 51 + .../vendor/twig/twig/src/Template.php | 422 + .../vendor/twig/twig/src/TemplateWrapper.php | 109 + .../twig/src/Test/IntegrationTestCase.php | 265 + .../twig/twig/src/Test/NodeTestCase.php | 65 + .../kirby-twig/vendor/twig/twig/src/Token.php | 178 + .../src/TokenParser/AbstractTokenParser.php | 32 + .../twig/src/TokenParser/ApplyTokenParser.php | 60 + .../src/TokenParser/AutoEscapeTokenParser.php | 58 + .../twig/src/TokenParser/BlockTokenParser.php | 78 + .../src/TokenParser/DeprecatedTokenParser.php | 43 + .../twig/src/TokenParser/DoTokenParser.php | 38 + .../twig/src/TokenParser/EmbedTokenParser.php | 73 + .../src/TokenParser/ExtendsTokenParser.php | 52 + .../twig/src/TokenParser/FlushTokenParser.php | 38 + .../twig/src/TokenParser/ForTokenParser.php | 79 + .../twig/src/TokenParser/FromTokenParser.php | 66 + .../twig/src/TokenParser/IfTokenParser.php | 89 + .../src/TokenParser/ImportTokenParser.php | 44 + .../src/TokenParser/IncludeTokenParser.php | 69 + .../twig/src/TokenParser/MacroTokenParser.php | 66 + .../src/TokenParser/SandboxTokenParser.php | 66 + .../twig/src/TokenParser/SetTokenParser.php | 73 + .../src/TokenParser/TokenParserInterface.php | 46 + .../twig/src/TokenParser/UseTokenParser.php | 73 + .../twig/src/TokenParser/WithTokenParser.php | 56 + .../vendor/twig/twig/src/TokenStream.php | 132 + .../vendor/twig/twig/src/TwigFilter.php | 134 + .../vendor/twig/twig/src/TwigFunction.php | 122 + .../vendor/twig/twig/src/TwigTest.php | 100 + .../twig/src/Util/DeprecationCollector.php | 77 + .../twig/src/Util/TemplateDirIterator.php | 28 + site/sessions/index.html | 0 site/snippets/index.html | 0 site/templates/default.php | 1 + 830 files changed, 159212 insertions(+) create mode 100644 .editorconfig create mode 100644 .gitignore create mode 100644 .htaccess create mode 100644 README.md create mode 100644 composer.json create mode 100644 composer.lock create mode 100644 content/error/error.txt create mode 100644 content/home/home.txt create mode 100644 content/site.txt create mode 100644 index.php create mode 100644 kirby/.editorconfig create mode 100644 kirby/SECURITY.md create mode 100644 kirby/assets/whoops.css create mode 100644 kirby/bootstrap.php create mode 100644 kirby/cacert.pem create mode 100644 kirby/composer.json create mode 100644 kirby/composer.lock create mode 100644 kirby/config/aliases.php create mode 100644 kirby/config/api/authentication.php create mode 100644 kirby/config/api/collections.php create mode 100644 kirby/config/api/models.php create mode 100644 kirby/config/api/models/File.php create mode 100644 kirby/config/api/models/FileBlueprint.php create mode 100644 kirby/config/api/models/FileVersion.php create mode 100644 kirby/config/api/models/Language.php create mode 100644 kirby/config/api/models/Page.php create mode 100644 kirby/config/api/models/PageBlueprint.php create mode 100644 kirby/config/api/models/Role.php create mode 100644 kirby/config/api/models/Site.php create mode 100644 kirby/config/api/models/SiteBlueprint.php create mode 100644 kirby/config/api/models/System.php create mode 100644 kirby/config/api/models/Translation.php create mode 100644 kirby/config/api/models/User.php create mode 100644 kirby/config/api/models/UserBlueprint.php create mode 100644 kirby/config/api/routes.php create mode 100644 kirby/config/api/routes/auth.php create mode 100644 kirby/config/api/routes/files.php create mode 100644 kirby/config/api/routes/languages.php create mode 100644 kirby/config/api/routes/lock.php create mode 100644 kirby/config/api/routes/pages.php create mode 100644 kirby/config/api/routes/roles.php create mode 100644 kirby/config/api/routes/site.php create mode 100644 kirby/config/api/routes/system.php create mode 100644 kirby/config/api/routes/translations.php create mode 100644 kirby/config/api/routes/users.php create mode 100644 kirby/config/blocks/code/code.php create mode 100644 kirby/config/blocks/code/code.yml create mode 100644 kirby/config/blocks/gallery/gallery.php create mode 100644 kirby/config/blocks/gallery/gallery.yml create mode 100644 kirby/config/blocks/heading/heading.php create mode 100644 kirby/config/blocks/heading/heading.yml create mode 100644 kirby/config/blocks/image/image.php create mode 100644 kirby/config/blocks/image/image.yml create mode 100644 kirby/config/blocks/list/list.php create mode 100644 kirby/config/blocks/list/list.yml create mode 100644 kirby/config/blocks/markdown/markdown.php create mode 100644 kirby/config/blocks/markdown/markdown.yml create mode 100644 kirby/config/blocks/quote/quote.php create mode 100644 kirby/config/blocks/quote/quote.yml create mode 100644 kirby/config/blocks/table/table.yml create mode 100644 kirby/config/blocks/text/text.php create mode 100644 kirby/config/blocks/text/text.yml create mode 100644 kirby/config/blocks/video/video.php create mode 100644 kirby/config/blocks/video/video.yml create mode 100644 kirby/config/blueprints.php create mode 100644 kirby/config/blueprints/blocks/code.yml create mode 100644 kirby/config/blueprints/blocks/heading.yml create mode 100644 kirby/config/blueprints/blocks/image.yml create mode 100644 kirby/config/blueprints/blocks/quote.yml create mode 100644 kirby/config/blueprints/blocks/table.yml create mode 100644 kirby/config/blueprints/blocks/text.yml create mode 100644 kirby/config/blueprints/blocks/video.yml create mode 100644 kirby/config/blueprints/files/default.yml create mode 100644 kirby/config/blueprints/pages/default.yml create mode 100644 kirby/config/blueprints/site.yml create mode 100644 kirby/config/components.php create mode 100644 kirby/config/fields.php create mode 100644 kirby/config/fields/checkboxes.php create mode 100644 kirby/config/fields/date.php create mode 100644 kirby/config/fields/email.php create mode 100644 kirby/config/fields/files.php create mode 100644 kirby/config/fields/gap.php create mode 100644 kirby/config/fields/headline.php create mode 100644 kirby/config/fields/hidden.php create mode 100644 kirby/config/fields/info.php create mode 100644 kirby/config/fields/line.php create mode 100644 kirby/config/fields/list.php create mode 100644 kirby/config/fields/mixins/datetime.php create mode 100644 kirby/config/fields/mixins/filepicker.php create mode 100644 kirby/config/fields/mixins/min.php create mode 100644 kirby/config/fields/mixins/options.php create mode 100644 kirby/config/fields/mixins/pagepicker.php create mode 100644 kirby/config/fields/mixins/picker.php create mode 100644 kirby/config/fields/mixins/upload.php create mode 100644 kirby/config/fields/mixins/userpicker.php create mode 100644 kirby/config/fields/multiselect.php create mode 100644 kirby/config/fields/number.php create mode 100644 kirby/config/fields/pages.php create mode 100644 kirby/config/fields/radio.php create mode 100644 kirby/config/fields/range.php create mode 100644 kirby/config/fields/select.php create mode 100644 kirby/config/fields/structure.php create mode 100644 kirby/config/fields/tags.php create mode 100644 kirby/config/fields/tel.php create mode 100644 kirby/config/fields/text.php create mode 100644 kirby/config/fields/textarea.php create mode 100644 kirby/config/fields/time.php create mode 100644 kirby/config/fields/toggle.php create mode 100644 kirby/config/fields/url.php create mode 100644 kirby/config/fields/users.php create mode 100644 kirby/config/fields/writer.php create mode 100644 kirby/config/helpers.php create mode 100644 kirby/config/methods.php create mode 100644 kirby/config/presets/files.php create mode 100644 kirby/config/presets/page.php create mode 100644 kirby/config/presets/pages.php create mode 100644 kirby/config/roots.php create mode 100644 kirby/config/routes.php create mode 100644 kirby/config/sections/fields.php create mode 100644 kirby/config/sections/files.php create mode 100644 kirby/config/sections/info.php create mode 100644 kirby/config/sections/mixins/empty.php create mode 100644 kirby/config/sections/mixins/headline.php create mode 100644 kirby/config/sections/mixins/help.php create mode 100644 kirby/config/sections/mixins/layout.php create mode 100644 kirby/config/sections/mixins/max.php create mode 100644 kirby/config/sections/mixins/min.php create mode 100644 kirby/config/sections/mixins/pagination.php create mode 100644 kirby/config/sections/mixins/parent.php create mode 100644 kirby/config/sections/pages.php create mode 100644 kirby/config/setup.php create mode 100644 kirby/config/snippets.php create mode 100644 kirby/config/tags.php create mode 100644 kirby/config/templates.php create mode 100644 kirby/config/templates/emails/auth/login.php create mode 100644 kirby/config/templates/emails/auth/password-reset.php create mode 100644 kirby/config/urls.php create mode 100644 kirby/dependencies/parsedown-extra/ParsedownExtra.php create mode 100644 kirby/dependencies/parsedown/Parsedown.php create mode 100644 kirby/i18n/rules/LICENSE create mode 100644 kirby/i18n/rules/ar.json create mode 100644 kirby/i18n/rules/az.json create mode 100644 kirby/i18n/rules/bg.json create mode 100644 kirby/i18n/rules/cs.json create mode 100644 kirby/i18n/rules/da.json create mode 100644 kirby/i18n/rules/de.json create mode 100644 kirby/i18n/rules/el.json create mode 100644 kirby/i18n/rules/eo.json create mode 100644 kirby/i18n/rules/et.json create mode 100644 kirby/i18n/rules/fa.json create mode 100644 kirby/i18n/rules/fi.json create mode 100644 kirby/i18n/rules/fr.json create mode 100644 kirby/i18n/rules/hi.json create mode 100644 kirby/i18n/rules/hr.json create mode 100644 kirby/i18n/rules/hu.json create mode 100644 kirby/i18n/rules/hy.json create mode 100644 kirby/i18n/rules/it.json create mode 100644 kirby/i18n/rules/ja.json create mode 100644 kirby/i18n/rules/ka.json create mode 100644 kirby/i18n/rules/ko.json create mode 100644 kirby/i18n/rules/lt.json create mode 100644 kirby/i18n/rules/lv.json create mode 100644 kirby/i18n/rules/mk.json create mode 100644 kirby/i18n/rules/my.json create mode 100644 kirby/i18n/rules/nb.json create mode 100644 kirby/i18n/rules/pl.json create mode 100644 kirby/i18n/rules/pt_BR.json create mode 100644 kirby/i18n/rules/rm.json create mode 100644 kirby/i18n/rules/ru.json create mode 100644 kirby/i18n/rules/sr.json create mode 100644 kirby/i18n/rules/sv_SE.json create mode 100644 kirby/i18n/rules/tr.json create mode 100644 kirby/i18n/rules/uk.json create mode 100644 kirby/i18n/rules/vi.json create mode 100644 kirby/i18n/rules/zh.json create mode 100644 kirby/i18n/translations/bg.json create mode 100644 kirby/i18n/translations/ca.json create mode 100644 kirby/i18n/translations/cs.json create mode 100644 kirby/i18n/translations/da.json create mode 100644 kirby/i18n/translations/de.json create mode 100644 kirby/i18n/translations/el.json create mode 100644 kirby/i18n/translations/en.json create mode 100644 kirby/i18n/translations/es_419.json create mode 100644 kirby/i18n/translations/es_ES.json create mode 100644 kirby/i18n/translations/fa.json create mode 100644 kirby/i18n/translations/fi.json create mode 100644 kirby/i18n/translations/fr.json create mode 100644 kirby/i18n/translations/hu.json create mode 100644 kirby/i18n/translations/id.json create mode 100644 kirby/i18n/translations/it.json create mode 100644 kirby/i18n/translations/ko.json create mode 100644 kirby/i18n/translations/lt.json create mode 100644 kirby/i18n/translations/nb.json create mode 100644 kirby/i18n/translations/nl.json create mode 100644 kirby/i18n/translations/pl.json create mode 100644 kirby/i18n/translations/pt_BR.json create mode 100644 kirby/i18n/translations/pt_PT.json create mode 100644 kirby/i18n/translations/ru.json create mode 100644 kirby/i18n/translations/sk.json create mode 100644 kirby/i18n/translations/sv_SE.json create mode 100644 kirby/i18n/translations/tr.json create mode 100644 kirby/kirby.pub create mode 100644 kirby/panel/dist/apple-touch-icon.png create mode 100644 kirby/panel/dist/css/app.css create mode 100644 kirby/panel/dist/favicon.png create mode 100644 kirby/panel/dist/favicon.svg create mode 100644 kirby/panel/dist/img/icons.svg create mode 100644 kirby/panel/dist/js/app.js create mode 100644 kirby/panel/dist/js/plugins.js create mode 100644 kirby/panel/dist/js/vendor.js create mode 100644 kirby/router.php create mode 100644 kirby/src/Api/Api.php create mode 100644 kirby/src/Api/Collection.php create mode 100644 kirby/src/Api/Model.php create mode 100644 kirby/src/Cache/ApcuCache.php create mode 100644 kirby/src/Cache/Cache.php create mode 100644 kirby/src/Cache/FileCache.php create mode 100644 kirby/src/Cache/MemCached.php create mode 100644 kirby/src/Cache/MemoryCache.php create mode 100644 kirby/src/Cache/NullCache.php create mode 100644 kirby/src/Cache/Value.php create mode 100644 kirby/src/Cms/Api.php create mode 100644 kirby/src/Cms/App.php create mode 100644 kirby/src/Cms/AppCaches.php create mode 100644 kirby/src/Cms/AppErrors.php create mode 100644 kirby/src/Cms/AppPlugins.php create mode 100644 kirby/src/Cms/AppTranslations.php create mode 100644 kirby/src/Cms/AppUsers.php create mode 100644 kirby/src/Cms/Asset.php create mode 100644 kirby/src/Cms/Auth.php create mode 100644 kirby/src/Cms/Auth/Challenge.php create mode 100644 kirby/src/Cms/Auth/EmailChallenge.php create mode 100644 kirby/src/Cms/Auth/Status.php create mode 100644 kirby/src/Cms/Block.php create mode 100644 kirby/src/Cms/BlockConverter.php create mode 100644 kirby/src/Cms/Blocks.php create mode 100644 kirby/src/Cms/Blueprint.php create mode 100644 kirby/src/Cms/Collection.php create mode 100644 kirby/src/Cms/Collections.php create mode 100644 kirby/src/Cms/Content.php create mode 100644 kirby/src/Cms/ContentLock.php create mode 100644 kirby/src/Cms/ContentLocks.php create mode 100644 kirby/src/Cms/ContentTranslation.php create mode 100644 kirby/src/Cms/Dir.php create mode 100644 kirby/src/Cms/Email.php create mode 100644 kirby/src/Cms/Event.php create mode 100644 kirby/src/Cms/Field.php create mode 100644 kirby/src/Cms/Fieldset.php create mode 100644 kirby/src/Cms/Fieldsets.php create mode 100644 kirby/src/Cms/File.php create mode 100644 kirby/src/Cms/FileActions.php create mode 100644 kirby/src/Cms/FileBlueprint.php create mode 100644 kirby/src/Cms/FileFoundation.php create mode 100644 kirby/src/Cms/FileModifications.php create mode 100644 kirby/src/Cms/FilePermissions.php create mode 100644 kirby/src/Cms/FilePicker.php create mode 100644 kirby/src/Cms/FileRules.php create mode 100644 kirby/src/Cms/FileVersion.php create mode 100644 kirby/src/Cms/Filename.php create mode 100644 kirby/src/Cms/Files.php create mode 100644 kirby/src/Cms/Form.php create mode 100644 kirby/src/Cms/HasChildren.php create mode 100644 kirby/src/Cms/HasFiles.php create mode 100644 kirby/src/Cms/HasMethods.php create mode 100644 kirby/src/Cms/HasSiblings.php create mode 100644 kirby/src/Cms/Html.php create mode 100644 kirby/src/Cms/Ingredients.php create mode 100644 kirby/src/Cms/Item.php create mode 100644 kirby/src/Cms/Items.php create mode 100644 kirby/src/Cms/KirbyTag.php create mode 100644 kirby/src/Cms/KirbyTags.php create mode 100644 kirby/src/Cms/Language.php create mode 100644 kirby/src/Cms/LanguageRouter.php create mode 100644 kirby/src/Cms/LanguageRoutes.php create mode 100644 kirby/src/Cms/LanguageRules.php create mode 100644 kirby/src/Cms/Languages.php create mode 100644 kirby/src/Cms/Layout.php create mode 100644 kirby/src/Cms/LayoutColumn.php create mode 100644 kirby/src/Cms/LayoutColumns.php create mode 100644 kirby/src/Cms/Layouts.php create mode 100644 kirby/src/Cms/Media.php create mode 100644 kirby/src/Cms/Model.php create mode 100644 kirby/src/Cms/ModelPermissions.php create mode 100644 kirby/src/Cms/ModelWithContent.php create mode 100644 kirby/src/Cms/Nest.php create mode 100644 kirby/src/Cms/NestCollection.php create mode 100644 kirby/src/Cms/NestObject.php create mode 100644 kirby/src/Cms/Page.php create mode 100644 kirby/src/Cms/PageActions.php create mode 100644 kirby/src/Cms/PageBlueprint.php create mode 100644 kirby/src/Cms/PagePermissions.php create mode 100644 kirby/src/Cms/PagePicker.php create mode 100644 kirby/src/Cms/PageRules.php create mode 100644 kirby/src/Cms/PageSiblings.php create mode 100644 kirby/src/Cms/Pages.php create mode 100644 kirby/src/Cms/Pagination.php create mode 100644 kirby/src/Cms/Panel.php create mode 100644 kirby/src/Cms/PanelPlugins.php create mode 100644 kirby/src/Cms/Permissions.php create mode 100644 kirby/src/Cms/Picker.php create mode 100644 kirby/src/Cms/Plugin.php create mode 100644 kirby/src/Cms/PluginAssets.php create mode 100644 kirby/src/Cms/R.php create mode 100644 kirby/src/Cms/Responder.php create mode 100644 kirby/src/Cms/Response.php create mode 100644 kirby/src/Cms/Role.php create mode 100644 kirby/src/Cms/Roles.php create mode 100644 kirby/src/Cms/S.php create mode 100644 kirby/src/Cms/Search.php create mode 100644 kirby/src/Cms/Section.php create mode 100644 kirby/src/Cms/Site.php create mode 100644 kirby/src/Cms/SiteActions.php create mode 100644 kirby/src/Cms/SiteBlueprint.php create mode 100644 kirby/src/Cms/SitePermissions.php create mode 100644 kirby/src/Cms/SiteRules.php create mode 100644 kirby/src/Cms/Structure.php create mode 100644 kirby/src/Cms/StructureObject.php create mode 100644 kirby/src/Cms/System.php create mode 100644 kirby/src/Cms/Template.php create mode 100644 kirby/src/Cms/Translation.php create mode 100644 kirby/src/Cms/Translations.php create mode 100644 kirby/src/Cms/Url.php create mode 100644 kirby/src/Cms/User.php create mode 100644 kirby/src/Cms/UserActions.php create mode 100644 kirby/src/Cms/UserBlueprint.php create mode 100644 kirby/src/Cms/UserPermissions.php create mode 100644 kirby/src/Cms/UserPicker.php create mode 100644 kirby/src/Cms/UserRules.php create mode 100644 kirby/src/Cms/Users.php create mode 100644 kirby/src/Cms/Visitor.php create mode 100644 kirby/src/Data/Data.php create mode 100644 kirby/src/Data/Handler.php create mode 100644 kirby/src/Data/Json.php create mode 100644 kirby/src/Data/PHP.php create mode 100644 kirby/src/Data/Txt.php create mode 100644 kirby/src/Data/Xml.php create mode 100644 kirby/src/Data/Yaml.php create mode 100644 kirby/src/Database/Database.php create mode 100644 kirby/src/Database/Db.php create mode 100644 kirby/src/Database/Query.php create mode 100644 kirby/src/Database/Sql.php create mode 100644 kirby/src/Database/Sql/Mysql.php create mode 100644 kirby/src/Database/Sql/Sqlite.php create mode 100644 kirby/src/Email/Body.php create mode 100644 kirby/src/Email/Email.php create mode 100644 kirby/src/Email/PHPMailer.php create mode 100644 kirby/src/Exception/BadMethodCallException.php create mode 100644 kirby/src/Exception/DuplicateException.php create mode 100644 kirby/src/Exception/ErrorPageException.php create mode 100644 kirby/src/Exception/Exception.php create mode 100644 kirby/src/Exception/InvalidArgumentException.php create mode 100644 kirby/src/Exception/LogicException.php create mode 100644 kirby/src/Exception/NotFoundException.php create mode 100644 kirby/src/Exception/PermissionException.php create mode 100644 kirby/src/Form/Field.php create mode 100644 kirby/src/Form/Field/BlocksField.php create mode 100644 kirby/src/Form/Field/LayoutField.php create mode 100644 kirby/src/Form/FieldClass.php create mode 100644 kirby/src/Form/Fields.php create mode 100644 kirby/src/Form/Form.php create mode 100644 kirby/src/Form/Mixin/EmptyState.php create mode 100644 kirby/src/Form/Mixin/Max.php create mode 100644 kirby/src/Form/Mixin/Min.php create mode 100644 kirby/src/Form/Options.php create mode 100644 kirby/src/Form/OptionsApi.php create mode 100644 kirby/src/Form/OptionsQuery.php create mode 100644 kirby/src/Form/Validations.php create mode 100644 kirby/src/Http/Cookie.php create mode 100644 kirby/src/Http/Exceptions/NextRouteException.php create mode 100644 kirby/src/Http/Header.php create mode 100644 kirby/src/Http/Idn.php create mode 100644 kirby/src/Http/Params.php create mode 100644 kirby/src/Http/Path.php create mode 100644 kirby/src/Http/Query.php create mode 100644 kirby/src/Http/Remote.php create mode 100644 kirby/src/Http/Request.php create mode 100644 kirby/src/Http/Request/Auth/BasicAuth.php create mode 100644 kirby/src/Http/Request/Auth/BearerAuth.php create mode 100644 kirby/src/Http/Request/Body.php create mode 100644 kirby/src/Http/Request/Data.php create mode 100644 kirby/src/Http/Request/Files.php create mode 100644 kirby/src/Http/Request/Query.php create mode 100644 kirby/src/Http/Response.php create mode 100644 kirby/src/Http/Route.php create mode 100644 kirby/src/Http/Router.php create mode 100644 kirby/src/Http/Server.php create mode 100644 kirby/src/Http/Uri.php create mode 100644 kirby/src/Http/Url.php create mode 100644 kirby/src/Http/Visitor.php create mode 100644 kirby/src/Image/Camera.php create mode 100644 kirby/src/Image/Darkroom.php create mode 100644 kirby/src/Image/Darkroom/GdLib.php create mode 100644 kirby/src/Image/Darkroom/ImageMagick.php create mode 100644 kirby/src/Image/Dimensions.php create mode 100644 kirby/src/Image/Exif.php create mode 100644 kirby/src/Image/Image.php create mode 100644 kirby/src/Image/Location.php create mode 100644 kirby/src/Parsley/Element.php create mode 100644 kirby/src/Parsley/Inline.php create mode 100644 kirby/src/Parsley/Parsley.php create mode 100644 kirby/src/Parsley/Schema.php create mode 100644 kirby/src/Parsley/Schema/Blocks.php create mode 100644 kirby/src/Parsley/Schema/Plain.php create mode 100644 kirby/src/Sane/Handler.php create mode 100644 kirby/src/Sane/Sane.php create mode 100644 kirby/src/Sane/Svg.php create mode 100644 kirby/src/Sane/Svgz.php create mode 100644 kirby/src/Sane/Xml.php create mode 100644 kirby/src/Session/AutoSession.php create mode 100644 kirby/src/Session/FileSessionStore.php create mode 100644 kirby/src/Session/Session.php create mode 100644 kirby/src/Session/SessionData.php create mode 100644 kirby/src/Session/SessionStore.php create mode 100644 kirby/src/Session/Sessions.php create mode 100644 kirby/src/Text/KirbyTag.php create mode 100644 kirby/src/Text/KirbyTags.php create mode 100644 kirby/src/Text/Markdown.php create mode 100644 kirby/src/Text/SmartyPants.php create mode 100644 kirby/src/Toolkit/A.php create mode 100644 kirby/src/Toolkit/Collection.php create mode 100644 kirby/src/Toolkit/Component.php create mode 100644 kirby/src/Toolkit/Config.php create mode 100644 kirby/src/Toolkit/Controller.php create mode 100644 kirby/src/Toolkit/Dir.php create mode 100644 kirby/src/Toolkit/Escape.php create mode 100644 kirby/src/Toolkit/F.php create mode 100644 kirby/src/Toolkit/Facade.php create mode 100644 kirby/src/Toolkit/File.php create mode 100644 kirby/src/Toolkit/Html.php create mode 100644 kirby/src/Toolkit/I18n.php create mode 100644 kirby/src/Toolkit/Iterator.php create mode 100644 kirby/src/Toolkit/Locale.php create mode 100644 kirby/src/Toolkit/Mime.php create mode 100644 kirby/src/Toolkit/Obj.php create mode 100644 kirby/src/Toolkit/Pagination.php create mode 100644 kirby/src/Toolkit/Properties.php create mode 100644 kirby/src/Toolkit/Query.php create mode 100644 kirby/src/Toolkit/Silo.php create mode 100644 kirby/src/Toolkit/Str.php create mode 100644 kirby/src/Toolkit/Tpl.php create mode 100644 kirby/src/Toolkit/V.php create mode 100644 kirby/src/Toolkit/View.php create mode 100644 kirby/src/Toolkit/Xml.php create mode 100644 kirby/vendor/autoload.php create mode 100644 kirby/vendor/claviska/simpleimage/LICENSE.md create mode 100644 kirby/vendor/claviska/simpleimage/composer.json create mode 100644 kirby/vendor/claviska/simpleimage/src/claviska/SimpleImage.php create mode 100644 kirby/vendor/composer/ClassLoader.php create mode 100644 kirby/vendor/composer/InstalledVersions.php create mode 100644 kirby/vendor/composer/LICENSE create mode 100644 kirby/vendor/composer/autoload_classmap.php create mode 100644 kirby/vendor/composer/autoload_files.php create mode 100644 kirby/vendor/composer/autoload_namespaces.php create mode 100644 kirby/vendor/composer/autoload_psr4.php create mode 100644 kirby/vendor/composer/autoload_real.php create mode 100644 kirby/vendor/composer/autoload_static.php create mode 100644 kirby/vendor/composer/installed.json create mode 100644 kirby/vendor/composer/installed.php create mode 100644 kirby/vendor/filp/whoops/LICENSE.md create mode 100644 kirby/vendor/filp/whoops/composer.json create mode 100644 kirby/vendor/filp/whoops/src/Whoops/Exception/ErrorException.php create mode 100644 kirby/vendor/filp/whoops/src/Whoops/Exception/Formatter.php create mode 100644 kirby/vendor/filp/whoops/src/Whoops/Exception/Frame.php create mode 100644 kirby/vendor/filp/whoops/src/Whoops/Exception/FrameCollection.php create mode 100644 kirby/vendor/filp/whoops/src/Whoops/Exception/Inspector.php create mode 100644 kirby/vendor/filp/whoops/src/Whoops/Handler/CallbackHandler.php create mode 100644 kirby/vendor/filp/whoops/src/Whoops/Handler/Handler.php create mode 100644 kirby/vendor/filp/whoops/src/Whoops/Handler/HandlerInterface.php create mode 100644 kirby/vendor/filp/whoops/src/Whoops/Handler/JsonResponseHandler.php create mode 100644 kirby/vendor/filp/whoops/src/Whoops/Handler/PlainTextHandler.php create mode 100644 kirby/vendor/filp/whoops/src/Whoops/Handler/PrettyPageHandler.php create mode 100644 kirby/vendor/filp/whoops/src/Whoops/Handler/XmlResponseHandler.php create mode 100644 kirby/vendor/filp/whoops/src/Whoops/Resources/css/whoops.base.css create mode 100644 kirby/vendor/filp/whoops/src/Whoops/Resources/js/clipboard.min.js create mode 100644 kirby/vendor/filp/whoops/src/Whoops/Resources/js/prettify.min.js create mode 100644 kirby/vendor/filp/whoops/src/Whoops/Resources/js/whoops.base.js create mode 100644 kirby/vendor/filp/whoops/src/Whoops/Resources/js/zepto.min.js create mode 100644 kirby/vendor/filp/whoops/src/Whoops/Resources/views/env_details.html.php create mode 100644 kirby/vendor/filp/whoops/src/Whoops/Resources/views/frame_code.html.php create mode 100644 kirby/vendor/filp/whoops/src/Whoops/Resources/views/frame_list.html.php create mode 100644 kirby/vendor/filp/whoops/src/Whoops/Resources/views/frames_container.html.php create mode 100644 kirby/vendor/filp/whoops/src/Whoops/Resources/views/frames_description.html.php create mode 100644 kirby/vendor/filp/whoops/src/Whoops/Resources/views/header.html.php create mode 100644 kirby/vendor/filp/whoops/src/Whoops/Resources/views/header_outer.html.php create mode 100644 kirby/vendor/filp/whoops/src/Whoops/Resources/views/layout.html.php create mode 100644 kirby/vendor/filp/whoops/src/Whoops/Resources/views/panel_details.html.php create mode 100644 kirby/vendor/filp/whoops/src/Whoops/Resources/views/panel_details_outer.html.php create mode 100644 kirby/vendor/filp/whoops/src/Whoops/Resources/views/panel_left.html.php create mode 100644 kirby/vendor/filp/whoops/src/Whoops/Resources/views/panel_left_outer.html.php create mode 100644 kirby/vendor/filp/whoops/src/Whoops/Run.php create mode 100644 kirby/vendor/filp/whoops/src/Whoops/RunInterface.php create mode 100644 kirby/vendor/filp/whoops/src/Whoops/Util/HtmlDumperOutput.php create mode 100644 kirby/vendor/filp/whoops/src/Whoops/Util/Misc.php create mode 100644 kirby/vendor/filp/whoops/src/Whoops/Util/SystemFacade.php create mode 100644 kirby/vendor/filp/whoops/src/Whoops/Util/TemplateHelper.php create mode 100644 kirby/vendor/laminas/laminas-escaper/COPYRIGHT.md create mode 100644 kirby/vendor/laminas/laminas-escaper/LICENSE.md create mode 100644 kirby/vendor/laminas/laminas-escaper/composer.json create mode 100644 kirby/vendor/laminas/laminas-escaper/src/Escaper.php create mode 100644 kirby/vendor/laminas/laminas-escaper/src/Exception/ExceptionInterface.php create mode 100644 kirby/vendor/laminas/laminas-escaper/src/Exception/InvalidArgumentException.php create mode 100644 kirby/vendor/laminas/laminas-escaper/src/Exception/RuntimeException.php create mode 100644 kirby/vendor/laminas/laminas-zendframework-bridge/COPYRIGHT.md create mode 100644 kirby/vendor/laminas/laminas-zendframework-bridge/LICENSE.md create mode 100644 kirby/vendor/laminas/laminas-zendframework-bridge/composer.json create mode 100644 kirby/vendor/laminas/laminas-zendframework-bridge/config/replacements.php create mode 100644 kirby/vendor/laminas/laminas-zendframework-bridge/src/Autoloader.php create mode 100644 kirby/vendor/laminas/laminas-zendframework-bridge/src/ConfigPostProcessor.php create mode 100644 kirby/vendor/laminas/laminas-zendframework-bridge/src/Module.php create mode 100644 kirby/vendor/laminas/laminas-zendframework-bridge/src/Replacements.php create mode 100644 kirby/vendor/laminas/laminas-zendframework-bridge/src/RewriteRules.php create mode 100644 kirby/vendor/laminas/laminas-zendframework-bridge/src/autoload.php create mode 100644 kirby/vendor/league/color-extractor/LICENSE create mode 100644 kirby/vendor/league/color-extractor/composer.json create mode 100644 kirby/vendor/league/color-extractor/src/League/ColorExtractor/Color.php create mode 100644 kirby/vendor/league/color-extractor/src/League/ColorExtractor/ColorExtractor.php create mode 100644 kirby/vendor/league/color-extractor/src/League/ColorExtractor/Palette.php create mode 100644 kirby/vendor/michelf/php-smartypants/License.md create mode 100644 kirby/vendor/michelf/php-smartypants/Michelf/SmartyPants.inc.php create mode 100644 kirby/vendor/michelf/php-smartypants/Michelf/SmartyPants.php create mode 100644 kirby/vendor/michelf/php-smartypants/Michelf/SmartyPantsTypographer.inc.php create mode 100644 kirby/vendor/michelf/php-smartypants/Michelf/SmartyPantsTypographer.php create mode 100644 kirby/vendor/michelf/php-smartypants/composer.json create mode 100644 kirby/vendor/mustangostang/spyc/COPYING create mode 100644 kirby/vendor/mustangostang/spyc/Spyc.php create mode 100644 kirby/vendor/mustangostang/spyc/composer.json create mode 100644 kirby/vendor/phpmailer/phpmailer/LICENSE create mode 100644 kirby/vendor/phpmailer/phpmailer/composer.json create mode 100644 kirby/vendor/phpmailer/phpmailer/get_oauth_token.php create mode 100644 kirby/vendor/phpmailer/phpmailer/language/phpmailer.lang-af.php create mode 100644 kirby/vendor/phpmailer/phpmailer/language/phpmailer.lang-ar.php create mode 100644 kirby/vendor/phpmailer/phpmailer/language/phpmailer.lang-az.php create mode 100644 kirby/vendor/phpmailer/phpmailer/language/phpmailer.lang-ba.php create mode 100644 kirby/vendor/phpmailer/phpmailer/language/phpmailer.lang-be.php create mode 100644 kirby/vendor/phpmailer/phpmailer/language/phpmailer.lang-bg.php create mode 100644 kirby/vendor/phpmailer/phpmailer/language/phpmailer.lang-ca.php create mode 100644 kirby/vendor/phpmailer/phpmailer/language/phpmailer.lang-ch.php create mode 100644 kirby/vendor/phpmailer/phpmailer/language/phpmailer.lang-cs.php create mode 100644 kirby/vendor/phpmailer/phpmailer/language/phpmailer.lang-da.php create mode 100644 kirby/vendor/phpmailer/phpmailer/language/phpmailer.lang-de.php create mode 100644 kirby/vendor/phpmailer/phpmailer/language/phpmailer.lang-el.php create mode 100644 kirby/vendor/phpmailer/phpmailer/language/phpmailer.lang-eo.php create mode 100644 kirby/vendor/phpmailer/phpmailer/language/phpmailer.lang-es.php create mode 100644 kirby/vendor/phpmailer/phpmailer/language/phpmailer.lang-et.php create mode 100644 kirby/vendor/phpmailer/phpmailer/language/phpmailer.lang-fa.php create mode 100644 kirby/vendor/phpmailer/phpmailer/language/phpmailer.lang-fi.php create mode 100644 kirby/vendor/phpmailer/phpmailer/language/phpmailer.lang-fo.php create mode 100644 kirby/vendor/phpmailer/phpmailer/language/phpmailer.lang-fr.php create mode 100644 kirby/vendor/phpmailer/phpmailer/language/phpmailer.lang-gl.php create mode 100644 kirby/vendor/phpmailer/phpmailer/language/phpmailer.lang-he.php create mode 100644 kirby/vendor/phpmailer/phpmailer/language/phpmailer.lang-hi.php create mode 100644 kirby/vendor/phpmailer/phpmailer/language/phpmailer.lang-hr.php create mode 100644 kirby/vendor/phpmailer/phpmailer/language/phpmailer.lang-hu.php create mode 100644 kirby/vendor/phpmailer/phpmailer/language/phpmailer.lang-hy.php create mode 100644 kirby/vendor/phpmailer/phpmailer/language/phpmailer.lang-id.php create mode 100644 kirby/vendor/phpmailer/phpmailer/language/phpmailer.lang-it.php create mode 100644 kirby/vendor/phpmailer/phpmailer/language/phpmailer.lang-ja.php create mode 100644 kirby/vendor/phpmailer/phpmailer/language/phpmailer.lang-ka.php create mode 100644 kirby/vendor/phpmailer/phpmailer/language/phpmailer.lang-ko.php create mode 100644 kirby/vendor/phpmailer/phpmailer/language/phpmailer.lang-lt.php create mode 100644 kirby/vendor/phpmailer/phpmailer/language/phpmailer.lang-lv.php create mode 100644 kirby/vendor/phpmailer/phpmailer/language/phpmailer.lang-mg.php create mode 100644 kirby/vendor/phpmailer/phpmailer/language/phpmailer.lang-ms.php create mode 100644 kirby/vendor/phpmailer/phpmailer/language/phpmailer.lang-nb.php create mode 100644 kirby/vendor/phpmailer/phpmailer/language/phpmailer.lang-nl.php create mode 100644 kirby/vendor/phpmailer/phpmailer/language/phpmailer.lang-pl.php create mode 100644 kirby/vendor/phpmailer/phpmailer/language/phpmailer.lang-pt.php create mode 100644 kirby/vendor/phpmailer/phpmailer/language/phpmailer.lang-pt_br.php create mode 100644 kirby/vendor/phpmailer/phpmailer/language/phpmailer.lang-ro.php create mode 100644 kirby/vendor/phpmailer/phpmailer/language/phpmailer.lang-ru.php create mode 100644 kirby/vendor/phpmailer/phpmailer/language/phpmailer.lang-sk.php create mode 100644 kirby/vendor/phpmailer/phpmailer/language/phpmailer.lang-sl.php create mode 100644 kirby/vendor/phpmailer/phpmailer/language/phpmailer.lang-sr.php create mode 100644 kirby/vendor/phpmailer/phpmailer/language/phpmailer.lang-sr_latn.php create mode 100644 kirby/vendor/phpmailer/phpmailer/language/phpmailer.lang-sv.php create mode 100644 kirby/vendor/phpmailer/phpmailer/language/phpmailer.lang-tl.php create mode 100644 kirby/vendor/phpmailer/phpmailer/language/phpmailer.lang-tr.php create mode 100644 kirby/vendor/phpmailer/phpmailer/language/phpmailer.lang-uk.php create mode 100644 kirby/vendor/phpmailer/phpmailer/language/phpmailer.lang-vi.php create mode 100644 kirby/vendor/phpmailer/phpmailer/language/phpmailer.lang-zh.php create mode 100644 kirby/vendor/phpmailer/phpmailer/language/phpmailer.lang-zh_cn.php create mode 100644 kirby/vendor/phpmailer/phpmailer/src/Exception.php create mode 100644 kirby/vendor/phpmailer/phpmailer/src/OAuth.php create mode 100644 kirby/vendor/phpmailer/phpmailer/src/PHPMailer.php create mode 100644 kirby/vendor/phpmailer/phpmailer/src/POP3.php create mode 100644 kirby/vendor/phpmailer/phpmailer/src/SMTP.php create mode 100644 kirby/vendor/psr/log/LICENSE create mode 100644 kirby/vendor/psr/log/Psr/Log/AbstractLogger.php create mode 100644 kirby/vendor/psr/log/Psr/Log/InvalidArgumentException.php create mode 100644 kirby/vendor/psr/log/Psr/Log/LogLevel.php create mode 100644 kirby/vendor/psr/log/Psr/Log/LoggerAwareInterface.php create mode 100644 kirby/vendor/psr/log/Psr/Log/LoggerAwareTrait.php create mode 100644 kirby/vendor/psr/log/Psr/Log/LoggerInterface.php create mode 100644 kirby/vendor/psr/log/Psr/Log/LoggerTrait.php create mode 100644 kirby/vendor/psr/log/Psr/Log/NullLogger.php create mode 100644 kirby/vendor/psr/log/composer.json create mode 100644 kirby/vendor/symfony/polyfill-mbstring/LICENSE create mode 100644 kirby/vendor/symfony/polyfill-mbstring/Mbstring.php create mode 100644 kirby/vendor/symfony/polyfill-mbstring/Resources/unidata/lowerCase.php create mode 100644 kirby/vendor/symfony/polyfill-mbstring/Resources/unidata/titleCaseRegexp.php create mode 100644 kirby/vendor/symfony/polyfill-mbstring/Resources/unidata/upperCase.php create mode 100644 kirby/vendor/symfony/polyfill-mbstring/bootstrap.php create mode 100644 kirby/vendor/symfony/polyfill-mbstring/bootstrap80.php create mode 100644 kirby/vendor/symfony/polyfill-mbstring/composer.json create mode 100644 kirby/vendor/true/punycode/LICENSE create mode 100644 kirby/vendor/true/punycode/composer.json create mode 100644 kirby/vendor/true/punycode/src/Exception/DomainOutOfBoundsException.php create mode 100644 kirby/vendor/true/punycode/src/Exception/LabelOutOfBoundsException.php create mode 100644 kirby/vendor/true/punycode/src/Exception/OutOfBoundsException.php create mode 100644 kirby/vendor/true/punycode/src/Punycode.php create mode 100644 kirby/views/fatal.php create mode 100644 kirby/views/panel.php create mode 100644 kirby/views/php.php create mode 100644 kirby/views/snippets/footer.php create mode 100644 kirby/views/snippets/header.php create mode 100644 media/index.html create mode 100644 site/accounts/index.html create mode 100644 site/blueprints/pages/default.yml create mode 100644 site/blueprints/site.yml create mode 100644 site/cache/index.html create mode 100644 site/config/config.php create mode 100644 site/plugins/kirby-twig/LICENSE.md create mode 100644 site/plugins/kirby-twig/README.md create mode 100644 site/plugins/kirby-twig/composer.json create mode 100644 site/plugins/kirby-twig/composer.lock create mode 100644 site/plugins/kirby-twig/index.php create mode 100755 site/plugins/kirby-twig/src/classes/Environment.php create mode 100644 site/plugins/kirby-twig/src/classes/Functions.php create mode 100644 site/plugins/kirby-twig/src/classes/Plugin.php create mode 100644 site/plugins/kirby-twig/src/classes/Template.php create mode 100644 site/plugins/kirby-twig/src/classes/Tests.php create mode 100644 site/plugins/kirby-twig/src/errorpage.php create mode 100644 site/plugins/kirby-twig/src/helpers.php create mode 100644 site/plugins/kirby-twig/vendor/autoload.php create mode 100644 site/plugins/kirby-twig/vendor/composer/ClassLoader.php create mode 100644 site/plugins/kirby-twig/vendor/composer/autoload_classmap.php create mode 100644 site/plugins/kirby-twig/vendor/composer/autoload_files.php create mode 100644 site/plugins/kirby-twig/vendor/composer/autoload_namespaces.php create mode 100644 site/plugins/kirby-twig/vendor/composer/autoload_psr4.php create mode 100644 site/plugins/kirby-twig/vendor/composer/autoload_real.php create mode 100644 site/plugins/kirby-twig/vendor/composer/autoload_static.php create mode 100644 site/plugins/kirby-twig/vendor/symfony/polyfill-ctype/Ctype.php create mode 100644 site/plugins/kirby-twig/vendor/symfony/polyfill-ctype/bootstrap.php create mode 100644 site/plugins/kirby-twig/vendor/symfony/polyfill-ctype/bootstrap80.php create mode 100644 site/plugins/kirby-twig/vendor/symfony/polyfill-mbstring/Mbstring.php create mode 100644 site/plugins/kirby-twig/vendor/symfony/polyfill-mbstring/Resources/unidata/lowerCase.php create mode 100644 site/plugins/kirby-twig/vendor/symfony/polyfill-mbstring/Resources/unidata/titleCaseRegexp.php create mode 100644 site/plugins/kirby-twig/vendor/symfony/polyfill-mbstring/Resources/unidata/upperCase.php create mode 100644 site/plugins/kirby-twig/vendor/symfony/polyfill-mbstring/bootstrap.php create mode 100644 site/plugins/kirby-twig/vendor/symfony/polyfill-mbstring/bootstrap80.php create mode 100644 site/plugins/kirby-twig/vendor/twig/twig/CHANGELOG create mode 100644 site/plugins/kirby-twig/vendor/twig/twig/README.rst create mode 100755 site/plugins/kirby-twig/vendor/twig/twig/drupal_test.sh create mode 100644 site/plugins/kirby-twig/vendor/twig/twig/src/Cache/CacheInterface.php create mode 100644 site/plugins/kirby-twig/vendor/twig/twig/src/Cache/FilesystemCache.php create mode 100644 site/plugins/kirby-twig/vendor/twig/twig/src/Cache/NullCache.php create mode 100644 site/plugins/kirby-twig/vendor/twig/twig/src/Compiler.php create mode 100644 site/plugins/kirby-twig/vendor/twig/twig/src/Environment.php create mode 100644 site/plugins/kirby-twig/vendor/twig/twig/src/Error/Error.php create mode 100644 site/plugins/kirby-twig/vendor/twig/twig/src/Error/LoaderError.php create mode 100644 site/plugins/kirby-twig/vendor/twig/twig/src/Error/RuntimeError.php create mode 100644 site/plugins/kirby-twig/vendor/twig/twig/src/Error/SyntaxError.php create mode 100644 site/plugins/kirby-twig/vendor/twig/twig/src/ExpressionParser.php create mode 100644 site/plugins/kirby-twig/vendor/twig/twig/src/Extension/AbstractExtension.php create mode 100644 site/plugins/kirby-twig/vendor/twig/twig/src/Extension/CoreExtension.php create mode 100644 site/plugins/kirby-twig/vendor/twig/twig/src/Extension/DebugExtension.php create mode 100644 site/plugins/kirby-twig/vendor/twig/twig/src/Extension/EscaperExtension.php create mode 100644 site/plugins/kirby-twig/vendor/twig/twig/src/Extension/ExtensionInterface.php create mode 100644 site/plugins/kirby-twig/vendor/twig/twig/src/Extension/GlobalsInterface.php create mode 100644 site/plugins/kirby-twig/vendor/twig/twig/src/Extension/OptimizerExtension.php create mode 100644 site/plugins/kirby-twig/vendor/twig/twig/src/Extension/ProfilerExtension.php create mode 100644 site/plugins/kirby-twig/vendor/twig/twig/src/Extension/RuntimeExtensionInterface.php create mode 100644 site/plugins/kirby-twig/vendor/twig/twig/src/Extension/SandboxExtension.php create mode 100644 site/plugins/kirby-twig/vendor/twig/twig/src/Extension/StagingExtension.php create mode 100644 site/plugins/kirby-twig/vendor/twig/twig/src/Extension/StringLoaderExtension.php create mode 100644 site/plugins/kirby-twig/vendor/twig/twig/src/ExtensionSet.php create mode 100644 site/plugins/kirby-twig/vendor/twig/twig/src/FileExtensionEscapingStrategy.php create mode 100644 site/plugins/kirby-twig/vendor/twig/twig/src/Lexer.php create mode 100644 site/plugins/kirby-twig/vendor/twig/twig/src/Loader/ArrayLoader.php create mode 100644 site/plugins/kirby-twig/vendor/twig/twig/src/Loader/ChainLoader.php create mode 100644 site/plugins/kirby-twig/vendor/twig/twig/src/Loader/FilesystemLoader.php create mode 100644 site/plugins/kirby-twig/vendor/twig/twig/src/Loader/LoaderInterface.php create mode 100644 site/plugins/kirby-twig/vendor/twig/twig/src/Markup.php create mode 100644 site/plugins/kirby-twig/vendor/twig/twig/src/Node/AutoEscapeNode.php create mode 100644 site/plugins/kirby-twig/vendor/twig/twig/src/Node/BlockNode.php create mode 100644 site/plugins/kirby-twig/vendor/twig/twig/src/Node/BlockReferenceNode.php create mode 100644 site/plugins/kirby-twig/vendor/twig/twig/src/Node/BodyNode.php create mode 100644 site/plugins/kirby-twig/vendor/twig/twig/src/Node/CheckSecurityCallNode.php create mode 100644 site/plugins/kirby-twig/vendor/twig/twig/src/Node/CheckSecurityNode.php create mode 100644 site/plugins/kirby-twig/vendor/twig/twig/src/Node/CheckToStringNode.php create mode 100644 site/plugins/kirby-twig/vendor/twig/twig/src/Node/DeprecatedNode.php create mode 100644 site/plugins/kirby-twig/vendor/twig/twig/src/Node/DoNode.php create mode 100644 site/plugins/kirby-twig/vendor/twig/twig/src/Node/EmbedNode.php create mode 100644 site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/AbstractExpression.php create mode 100644 site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/ArrayExpression.php create mode 100644 site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/ArrowFunctionExpression.php create mode 100644 site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/AssignNameExpression.php create mode 100644 site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/Binary/AbstractBinary.php create mode 100644 site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/Binary/AddBinary.php create mode 100644 site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/Binary/AndBinary.php create mode 100644 site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/Binary/BitwiseAndBinary.php create mode 100644 site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/Binary/BitwiseOrBinary.php create mode 100644 site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/Binary/BitwiseXorBinary.php create mode 100644 site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/Binary/ConcatBinary.php create mode 100644 site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/Binary/DivBinary.php create mode 100644 site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/Binary/EndsWithBinary.php create mode 100644 site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/Binary/EqualBinary.php create mode 100644 site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/Binary/FloorDivBinary.php create mode 100644 site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/Binary/GreaterBinary.php create mode 100644 site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/Binary/GreaterEqualBinary.php create mode 100644 site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/Binary/InBinary.php create mode 100644 site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/Binary/LessBinary.php create mode 100644 site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/Binary/LessEqualBinary.php create mode 100644 site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/Binary/MatchesBinary.php create mode 100644 site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/Binary/ModBinary.php create mode 100644 site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/Binary/MulBinary.php create mode 100644 site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/Binary/NotEqualBinary.php create mode 100644 site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/Binary/NotInBinary.php create mode 100644 site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/Binary/OrBinary.php create mode 100644 site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/Binary/PowerBinary.php create mode 100644 site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/Binary/RangeBinary.php create mode 100644 site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/Binary/SpaceshipBinary.php create mode 100644 site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/Binary/StartsWithBinary.php create mode 100644 site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/Binary/SubBinary.php create mode 100644 site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/BlockReferenceExpression.php create mode 100644 site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/CallExpression.php create mode 100644 site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/ConditionalExpression.php create mode 100644 site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/ConstantExpression.php create mode 100644 site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/Filter/DefaultFilter.php create mode 100644 site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/FilterExpression.php create mode 100644 site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/FunctionExpression.php create mode 100644 site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/GetAttrExpression.php create mode 100644 site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/InlinePrint.php create mode 100644 site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/MethodCallExpression.php create mode 100644 site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/NameExpression.php create mode 100644 site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/NullCoalesceExpression.php create mode 100644 site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/ParentExpression.php create mode 100644 site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/TempNameExpression.php create mode 100644 site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/Test/ConstantTest.php create mode 100644 site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/Test/DefinedTest.php create mode 100644 site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/Test/DivisiblebyTest.php create mode 100644 site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/Test/EvenTest.php create mode 100644 site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/Test/NullTest.php create mode 100644 site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/Test/OddTest.php create mode 100644 site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/Test/SameasTest.php create mode 100644 site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/TestExpression.php create mode 100644 site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/Unary/AbstractUnary.php create mode 100644 site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/Unary/NegUnary.php create mode 100644 site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/Unary/NotUnary.php create mode 100644 site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/Unary/PosUnary.php create mode 100644 site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/VariadicExpression.php create mode 100644 site/plugins/kirby-twig/vendor/twig/twig/src/Node/FlushNode.php create mode 100644 site/plugins/kirby-twig/vendor/twig/twig/src/Node/ForLoopNode.php create mode 100644 site/plugins/kirby-twig/vendor/twig/twig/src/Node/ForNode.php create mode 100644 site/plugins/kirby-twig/vendor/twig/twig/src/Node/IfNode.php create mode 100644 site/plugins/kirby-twig/vendor/twig/twig/src/Node/ImportNode.php create mode 100644 site/plugins/kirby-twig/vendor/twig/twig/src/Node/IncludeNode.php create mode 100644 site/plugins/kirby-twig/vendor/twig/twig/src/Node/MacroNode.php create mode 100644 site/plugins/kirby-twig/vendor/twig/twig/src/Node/ModuleNode.php create mode 100644 site/plugins/kirby-twig/vendor/twig/twig/src/Node/Node.php create mode 100644 site/plugins/kirby-twig/vendor/twig/twig/src/Node/NodeCaptureInterface.php create mode 100644 site/plugins/kirby-twig/vendor/twig/twig/src/Node/NodeOutputInterface.php create mode 100644 site/plugins/kirby-twig/vendor/twig/twig/src/Node/PrintNode.php create mode 100644 site/plugins/kirby-twig/vendor/twig/twig/src/Node/SandboxNode.php create mode 100644 site/plugins/kirby-twig/vendor/twig/twig/src/Node/SetNode.php create mode 100644 site/plugins/kirby-twig/vendor/twig/twig/src/Node/TextNode.php create mode 100644 site/plugins/kirby-twig/vendor/twig/twig/src/Node/WithNode.php create mode 100644 site/plugins/kirby-twig/vendor/twig/twig/src/NodeTraverser.php create mode 100644 site/plugins/kirby-twig/vendor/twig/twig/src/NodeVisitor/AbstractNodeVisitor.php create mode 100644 site/plugins/kirby-twig/vendor/twig/twig/src/NodeVisitor/EscaperNodeVisitor.php create mode 100644 site/plugins/kirby-twig/vendor/twig/twig/src/NodeVisitor/MacroAutoImportNodeVisitor.php create mode 100644 site/plugins/kirby-twig/vendor/twig/twig/src/NodeVisitor/NodeVisitorInterface.php create mode 100644 site/plugins/kirby-twig/vendor/twig/twig/src/NodeVisitor/OptimizerNodeVisitor.php create mode 100644 site/plugins/kirby-twig/vendor/twig/twig/src/NodeVisitor/SafeAnalysisNodeVisitor.php create mode 100644 site/plugins/kirby-twig/vendor/twig/twig/src/NodeVisitor/SandboxNodeVisitor.php create mode 100644 site/plugins/kirby-twig/vendor/twig/twig/src/Parser.php create mode 100644 site/plugins/kirby-twig/vendor/twig/twig/src/Profiler/Dumper/BaseDumper.php create mode 100644 site/plugins/kirby-twig/vendor/twig/twig/src/Profiler/Dumper/BlackfireDumper.php create mode 100644 site/plugins/kirby-twig/vendor/twig/twig/src/Profiler/Dumper/HtmlDumper.php create mode 100644 site/plugins/kirby-twig/vendor/twig/twig/src/Profiler/Dumper/TextDumper.php create mode 100644 site/plugins/kirby-twig/vendor/twig/twig/src/Profiler/Node/EnterProfileNode.php create mode 100644 site/plugins/kirby-twig/vendor/twig/twig/src/Profiler/Node/LeaveProfileNode.php create mode 100644 site/plugins/kirby-twig/vendor/twig/twig/src/Profiler/NodeVisitor/ProfilerNodeVisitor.php create mode 100644 site/plugins/kirby-twig/vendor/twig/twig/src/Profiler/Profile.php create mode 100644 site/plugins/kirby-twig/vendor/twig/twig/src/RuntimeLoader/ContainerRuntimeLoader.php create mode 100644 site/plugins/kirby-twig/vendor/twig/twig/src/RuntimeLoader/FactoryRuntimeLoader.php create mode 100644 site/plugins/kirby-twig/vendor/twig/twig/src/RuntimeLoader/RuntimeLoaderInterface.php create mode 100644 site/plugins/kirby-twig/vendor/twig/twig/src/Sandbox/SecurityError.php create mode 100644 site/plugins/kirby-twig/vendor/twig/twig/src/Sandbox/SecurityNotAllowedFilterError.php create mode 100644 site/plugins/kirby-twig/vendor/twig/twig/src/Sandbox/SecurityNotAllowedFunctionError.php create mode 100644 site/plugins/kirby-twig/vendor/twig/twig/src/Sandbox/SecurityNotAllowedMethodError.php create mode 100644 site/plugins/kirby-twig/vendor/twig/twig/src/Sandbox/SecurityNotAllowedPropertyError.php create mode 100644 site/plugins/kirby-twig/vendor/twig/twig/src/Sandbox/SecurityNotAllowedTagError.php create mode 100644 site/plugins/kirby-twig/vendor/twig/twig/src/Sandbox/SecurityPolicy.php create mode 100644 site/plugins/kirby-twig/vendor/twig/twig/src/Sandbox/SecurityPolicyInterface.php create mode 100644 site/plugins/kirby-twig/vendor/twig/twig/src/Source.php create mode 100644 site/plugins/kirby-twig/vendor/twig/twig/src/Template.php create mode 100644 site/plugins/kirby-twig/vendor/twig/twig/src/TemplateWrapper.php create mode 100644 site/plugins/kirby-twig/vendor/twig/twig/src/Test/IntegrationTestCase.php create mode 100644 site/plugins/kirby-twig/vendor/twig/twig/src/Test/NodeTestCase.php create mode 100644 site/plugins/kirby-twig/vendor/twig/twig/src/Token.php create mode 100644 site/plugins/kirby-twig/vendor/twig/twig/src/TokenParser/AbstractTokenParser.php create mode 100644 site/plugins/kirby-twig/vendor/twig/twig/src/TokenParser/ApplyTokenParser.php create mode 100644 site/plugins/kirby-twig/vendor/twig/twig/src/TokenParser/AutoEscapeTokenParser.php create mode 100644 site/plugins/kirby-twig/vendor/twig/twig/src/TokenParser/BlockTokenParser.php create mode 100644 site/plugins/kirby-twig/vendor/twig/twig/src/TokenParser/DeprecatedTokenParser.php create mode 100644 site/plugins/kirby-twig/vendor/twig/twig/src/TokenParser/DoTokenParser.php create mode 100644 site/plugins/kirby-twig/vendor/twig/twig/src/TokenParser/EmbedTokenParser.php create mode 100644 site/plugins/kirby-twig/vendor/twig/twig/src/TokenParser/ExtendsTokenParser.php create mode 100644 site/plugins/kirby-twig/vendor/twig/twig/src/TokenParser/FlushTokenParser.php create mode 100644 site/plugins/kirby-twig/vendor/twig/twig/src/TokenParser/ForTokenParser.php create mode 100644 site/plugins/kirby-twig/vendor/twig/twig/src/TokenParser/FromTokenParser.php create mode 100644 site/plugins/kirby-twig/vendor/twig/twig/src/TokenParser/IfTokenParser.php create mode 100644 site/plugins/kirby-twig/vendor/twig/twig/src/TokenParser/ImportTokenParser.php create mode 100644 site/plugins/kirby-twig/vendor/twig/twig/src/TokenParser/IncludeTokenParser.php create mode 100644 site/plugins/kirby-twig/vendor/twig/twig/src/TokenParser/MacroTokenParser.php create mode 100644 site/plugins/kirby-twig/vendor/twig/twig/src/TokenParser/SandboxTokenParser.php create mode 100644 site/plugins/kirby-twig/vendor/twig/twig/src/TokenParser/SetTokenParser.php create mode 100644 site/plugins/kirby-twig/vendor/twig/twig/src/TokenParser/TokenParserInterface.php create mode 100644 site/plugins/kirby-twig/vendor/twig/twig/src/TokenParser/UseTokenParser.php create mode 100644 site/plugins/kirby-twig/vendor/twig/twig/src/TokenParser/WithTokenParser.php create mode 100644 site/plugins/kirby-twig/vendor/twig/twig/src/TokenStream.php create mode 100644 site/plugins/kirby-twig/vendor/twig/twig/src/TwigFilter.php create mode 100644 site/plugins/kirby-twig/vendor/twig/twig/src/TwigFunction.php create mode 100644 site/plugins/kirby-twig/vendor/twig/twig/src/TwigTest.php create mode 100644 site/plugins/kirby-twig/vendor/twig/twig/src/Util/DeprecationCollector.php create mode 100644 site/plugins/kirby-twig/vendor/twig/twig/src/Util/TemplateDirIterator.php create mode 100644 site/sessions/index.html create mode 100644 site/snippets/index.html create mode 100644 site/templates/default.php diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..d2d7f0f --- /dev/null +++ b/.editorconfig @@ -0,0 +1,22 @@ +[*.{css,scss,less,js,json,ts,sass,html,hbs,mustache,phtml,html.twig,md,yml}] +charset = utf-8 +indent_style = space +indent_size = 2 +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true +insert_final_newline = true + +[*.md] +indent_size = 4 +trim_trailing_whitespace = false + +[site/templates/**.php] +indent_size = 2 + +[site/snippets/**.php] +indent_size = 2 + +[package.json,.{babelrc,editorconfig,eslintrc,lintstagedrc,stylelintrc}] +indent_style = space +indent_size = 2 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3780f27 --- /dev/null +++ b/.gitignore @@ -0,0 +1,43 @@ +# -------------------------------------------------- +# GENERAL +# -------------------------------------------------- + +# Dependencies +/vendor/ + +# Editors +*.sublime-project +*.sublime-workspace +/.vscode +/.idea + +# System files +Icon +.DS_Store + +# Temporary files +/media/* +!/media/index.html + +# -------------------------------------------------- +# SECURITY +# -------------------------------------------------- + +# Accounts +/site/accounts/* +!/site/accounts/index.html + +# Cache Files +/site/cache/* +!/site/cache/index.html + +# Configuration files +/site/config/config.xiaowang.fr.php +/site/config/config.xiaowang.test.php + +# License +/site/config/.license + +# Sessions +/site/sessions/* +!/site/sessions/index.html diff --git a/.htaccess b/.htaccess new file mode 100644 index 0000000..370133c --- /dev/null +++ b/.htaccess @@ -0,0 +1,57 @@ +# Kirby .htaccess +# revision 2020-06-15 + +# rewrite rules + + +# enable awesome urls. i.e.: +# http://yourdomain.com/about-us/team +RewriteEngine on + +# make sure to set the RewriteBase correctly +# if you are running the site in a subfolder; +# otherwise links or the entire site will break. +# +# If your homepage is http://yourdomain.com/mysite, +# set the RewriteBase to: +# +# RewriteBase /mysite + +# In some environments it's necessary to +# set the RewriteBase to: +# +# RewriteBase / + +# block files and folders beginning with a dot, such as .git +# except for the .well-known folder, which is used for Let's Encrypt and security.txt +RewriteRule (^|/)\.(?!well-known\/) index.php [L] + +# block all files in the content folder from being accessed directly +RewriteRule ^content/(.*) index.php [L] + +# block all files in the site folder from being accessed directly +RewriteRule ^site/(.*) index.php [L] + +# block direct access to Kirby and the Panel sources +RewriteRule ^kirby/(.*) index.php [L] + +# make site links work +RewriteCond %{REQUEST_FILENAME} !-f +RewriteCond %{REQUEST_FILENAME} !-d +RewriteRule ^(.*) index.php [L] + + + +# pass the Authorization header to PHP +SetEnvIf Authorization "(.*)" HTTP_AUTHORIZATION=$1 + +# compress text file responses + +AddOutputFilterByType DEFLATE text/plain +AddOutputFilterByType DEFLATE text/html +AddOutputFilterByType DEFLATE text/css +AddOutputFilterByType DEFLATE text/javascript +AddOutputFilterByType DEFLATE application/json +AddOutputFilterByType DEFLATE application/javascript +AddOutputFilterByType DEFLATE application/x-javascript + diff --git a/README.md b/README.md new file mode 100644 index 0000000..7f0cb69 --- /dev/null +++ b/README.md @@ -0,0 +1,34 @@ + + + +**Kirby: the CMS that adapts to any project, loved by developers and editors alike.** +The Plainkit is a minimal Kirby setup with the basics you need to start a project from scratch. It is the ideal choice if you are already familiar with Kirby and want to start step-by-step. + +You can learn more about Kirby at [getkirby.com](https://getkirby.com). + +### Try Kirby for free +You can try Kirby and the Plainkit on your local machine or on a test server as long as you need to make sure it is the right tool for your next project. … and when you’re convinced, [buy your license](https://getkirby.com/buy). + +### Get going +Read our guide on [how to get started with Kirby](https://getkirby.com/docs/guide/quickstart). + +You can [download the latest version](https://github.com/getkirby/plainkit/archive/main.zip) of the Plainkit. +If you are familiar with Git, you can clone Kirby's Plainkit repository from Github. + + git clone https://github.com/getkirby/plainkit.git + +## What's Kirby? +- **[getkirby.com](https://getkirby.com)** – Get to know the CMS. +- **[Try it](https://getkirby.com/try)** – Take a test ride with our online demo. Or download one of our kits to get started. +- **[Documentation](https://getkirby.com/docs/guide)** – Read the official guide, reference and cookbook recipes. +- **[Issues](https://github.com/getkirby/kirby/issues)** – Report bugs and other problems. +- **[Feedback](https://feedback.getkirby.com)** – You have an idea for Kirby? Share it. +- **[Forum](https://forum.getkirby.com)** – Whenever you get stuck, don't hesitate to reach out for questions and support. +- **[Discord](https://chat.getkirby.com)** – Hang out and meet the community. +- **[Twitter](https://twitter.com/getkirby)** – Spread the word. +- **[Instagram](https://www.instagram.com/getkirby/)** – Share your creations: #madewithkirby. + +--- + +© 2009-2020 Bastian Allgeier (Bastian Allgeier GmbH) +[getkirby.com](https://getkirby.com) · [License agreement](https://getkirby.com/license) diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..3d821fa --- /dev/null +++ b/composer.json @@ -0,0 +1,34 @@ +{ + "name": "getkirby/plainkit", + "description": "Kirby Plainkit", + "type": "project", + "keywords": ["kirby", "cms", "starterkit"], + "homepage": "https://getkirby.com", + "authors": [ + { + "name": "Bastian Allgeier", + "email": "bastian@getkirby.com", + "homepage": "https://getkirby.com" + } + ], + "support": { + "email": "support@getkirby.com", + "issues": "https://github.com/getkirby/starterkit/issues", + "forum": "https://forum.getkirby.com", + "source": "https://github.com/getkirby/starterkit" + }, + "require": { + "php": ">=7.3.0 <8.1.0", + "getkirby/cms": "^3.5", + "amteich/kirby-twig": "^4.1" + }, + "scripts": { + "start": [ + "Composer\\Config::disableProcessTimeout", + "@php -S localhost:8000 kirby/router.php" + ] + }, + "config": { + "optimize-autoloader": true + } +} diff --git a/composer.lock b/composer.lock new file mode 100644 index 0000000..3078c2b --- /dev/null +++ b/composer.lock @@ -0,0 +1,1003 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "114f3df5841d7c1e6c499a6bbcc2e380", + "packages": [ + { + "name": "amteich/kirby-twig", + "version": "4.1.4", + "source": { + "type": "git", + "url": "https://github.com/amteich/kirby-twig.git", + "reference": "d4d69a254dc5a904a0f6f5ec3cbe7e12394c0cf8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/amteich/kirby-twig/zipball/d4d69a254dc5a904a0f6f5ec3cbe7e12394c0cf8", + "reference": "d4d69a254dc5a904a0f6f5ec3cbe7e12394c0cf8", + "shasum": "" + }, + "require": { + "getkirby/composer-installer": "^1.1", + "twig/twig": "^3.0" + }, + "type": "kirby-plugin", + "autoload": { + "psr-4": { + "amteich\\Twig\\": "src/classes/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Christian Zehetner", + "email": "christian@am-teich.com" + }, + { + "name": "Florens Verschelde", + "email": "florens@fvsch.com" + } + ], + "description": "Twig templating support for Kirby CMS", + "support": { + "issues": "https://github.com/amteich/kirby-twig/issues", + "source": "https://github.com/amteich/kirby-twig/tree/4.1.4" + }, + "time": "2021-08-27T06:57:14+00:00" + }, + { + "name": "claviska/simpleimage", + "version": "3.6.3", + "source": { + "type": "git", + "url": "https://github.com/claviska/SimpleImage.git", + "reference": "21b6f4bf4ef1927158b3e29bd0c2d99c6681c750" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/claviska/SimpleImage/zipball/21b6f4bf4ef1927158b3e29bd0c2d99c6681c750", + "reference": "21b6f4bf4ef1927158b3e29bd0c2d99c6681c750", + "shasum": "" + }, + "require": { + "ext-gd": "*", + "league/color-extractor": "0.3.*", + "php": ">=5.6.0" + }, + "type": "library", + "autoload": { + "psr-0": { + "claviska": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Cory LaViska", + "homepage": "http://www.abeautifulsite.net/", + "role": "Developer" + } + ], + "description": "A PHP class that makes working with images as simple as possible.", + "support": { + "issues": "https://github.com/claviska/SimpleImage/issues", + "source": "https://github.com/claviska/SimpleImage/tree/3.6.3" + }, + "funding": [ + { + "url": "https://github.com/claviska", + "type": "github" + } + ], + "time": "2021-04-20T12:18:18+00:00" + }, + { + "name": "filp/whoops", + "version": "2.12.1", + "source": { + "type": "git", + "url": "https://github.com/filp/whoops.git", + "reference": "c13c0be93cff50f88bbd70827d993026821914dd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/filp/whoops/zipball/c13c0be93cff50f88bbd70827d993026821914dd", + "reference": "c13c0be93cff50f88bbd70827d993026821914dd", + "shasum": "" + }, + "require": { + "php": "^5.5.9 || ^7.0 || ^8.0", + "psr/log": "^1.0.1" + }, + "require-dev": { + "mockery/mockery": "^0.9 || ^1.0", + "phpunit/phpunit": "^4.8.36 || ^5.7.27 || ^6.5.14 || ^7.5.20 || ^8.5.8 || ^9.3.3", + "symfony/var-dumper": "^2.6 || ^3.0 || ^4.0 || ^5.0" + }, + "suggest": { + "symfony/var-dumper": "Pretty print complex values better with var-dumper available", + "whoops/soap": "Formats errors as SOAP responses" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.7-dev" + } + }, + "autoload": { + "psr-4": { + "Whoops\\": "src/Whoops/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Filipe Dobreira", + "homepage": "https://github.com/filp", + "role": "Developer" + } + ], + "description": "php error handling for cool kids", + "homepage": "https://filp.github.io/whoops/", + "keywords": [ + "error", + "exception", + "handling", + "library", + "throwable", + "whoops" + ], + "support": { + "issues": "https://github.com/filp/whoops/issues", + "source": "https://github.com/filp/whoops/tree/2.12.1" + }, + "funding": [ + { + "url": "https://github.com/denis-sokolov", + "type": "github" + } + ], + "time": "2021-04-25T12:00:00+00:00" + }, + { + "name": "getkirby/cms", + "version": "3.5.7.1", + "source": { + "type": "git", + "url": "https://github.com/getkirby/kirby.git", + "reference": "c77ccb82944b5fa0e3a453b4e203bd697e96330d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/getkirby/kirby/zipball/c77ccb82944b5fa0e3a453b4e203bd697e96330d", + "reference": "c77ccb82944b5fa0e3a453b4e203bd697e96330d", + "shasum": "" + }, + "require": { + "claviska/simpleimage": "3.6.3", + "ext-ctype": "*", + "ext-mbstring": "*", + "filp/whoops": "2.12.1", + "getkirby/composer-installer": "^1.2.0", + "laminas/laminas-escaper": "2.7.0", + "michelf/php-smartypants": "1.8.1", + "mustangostang/spyc": "0.6.3", + "php": ">=7.3.0 <8.1.0", + "phpmailer/phpmailer": "6.5.0", + "true/punycode": "2.1.1" + }, + "type": "kirby-cms", + "autoload": { + "psr-4": { + "Kirby\\": "src/" + }, + "classmap": [ + "dependencies/" + ], + "files": [ + "config/setup.php", + "config/helpers.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "proprietary" + ], + "authors": [ + { + "name": "Kirby Team", + "email": "support@getkirby.com", + "homepage": "https://getkirby.com" + } + ], + "description": "The Kirby 3 core", + "homepage": "https://getkirby.com", + "keywords": [ + "cms", + "core", + "kirby" + ], + "support": { + "email": "support@getkirby.com", + "forum": "https://forum.getkirby.com", + "issues": "https://github.com/getkirby/kirby/issues", + "source": "https://github.com/getkirby/kirby" + }, + "funding": [ + { + "url": "https://getkirby.com/buy", + "type": "custom" + } + ], + "time": "2021-07-07T09:21:03+00:00" + }, + { + "name": "getkirby/composer-installer", + "version": "1.2.1", + "source": { + "type": "git", + "url": "https://github.com/getkirby/composer-installer.git", + "reference": "c98ece30bfba45be7ce457e1102d1b169d922f3d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/getkirby/composer-installer/zipball/c98ece30bfba45be7ce457e1102d1b169d922f3d", + "reference": "c98ece30bfba45be7ce457e1102d1b169d922f3d", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.0 || ^2.0" + }, + "require-dev": { + "composer/composer": "^1.8 || ^2.0" + }, + "type": "composer-plugin", + "extra": { + "class": "Kirby\\ComposerInstaller\\Plugin" + }, + "autoload": { + "psr-4": { + "Kirby\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Kirby's custom Composer installer for the Kirby CMS and for Kirby plugins", + "homepage": "https://getkirby.com", + "support": { + "issues": "https://github.com/getkirby/composer-installer/issues", + "source": "https://github.com/getkirby/composer-installer/tree/1.2.1" + }, + "funding": [ + { + "url": "https://getkirby.com/buy", + "type": "custom" + } + ], + "time": "2020-12-28T12:54:39+00:00" + }, + { + "name": "laminas/laminas-escaper", + "version": "2.7.0", + "source": { + "type": "git", + "url": "https://github.com/laminas/laminas-escaper.git", + "reference": "5e04bc5ae5990b17159d79d331055e2c645e5cc5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laminas/laminas-escaper/zipball/5e04bc5ae5990b17159d79d331055e2c645e5cc5", + "reference": "5e04bc5ae5990b17159d79d331055e2c645e5cc5", + "shasum": "" + }, + "require": { + "laminas/laminas-zendframework-bridge": "^1.0", + "php": "^7.3 || ~8.0.0" + }, + "replace": { + "zendframework/zend-escaper": "^2.6.1" + }, + "require-dev": { + "laminas/laminas-coding-standard": "~1.0.0", + "phpunit/phpunit": "^9.3", + "psalm/plugin-phpunit": "^0.12.2", + "vimeo/psalm": "^3.16" + }, + "suggest": { + "ext-iconv": "*", + "ext-mbstring": "*" + }, + "type": "library", + "autoload": { + "psr-4": { + "Laminas\\Escaper\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "Securely and safely escape HTML, HTML attributes, JavaScript, CSS, and URLs", + "homepage": "https://laminas.dev", + "keywords": [ + "escaper", + "laminas" + ], + "support": { + "chat": "https://laminas.dev/chat", + "docs": "https://docs.laminas.dev/laminas-escaper/", + "forum": "https://discourse.laminas.dev", + "issues": "https://github.com/laminas/laminas-escaper/issues", + "rss": "https://github.com/laminas/laminas-escaper/releases.atom", + "source": "https://github.com/laminas/laminas-escaper" + }, + "funding": [ + { + "url": "https://funding.communitybridge.org/projects/laminas-project", + "type": "community_bridge" + } + ], + "time": "2020-11-17T21:26:43+00:00" + }, + { + "name": "laminas/laminas-zendframework-bridge", + "version": "1.4.0", + "source": { + "type": "git", + "url": "https://github.com/laminas/laminas-zendframework-bridge.git", + "reference": "bf180a382393e7db5c1e8d0f2ec0c4af9c724baf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laminas/laminas-zendframework-bridge/zipball/bf180a382393e7db5c1e8d0f2ec0c4af9c724baf", + "reference": "bf180a382393e7db5c1e8d0f2ec0c4af9c724baf", + "shasum": "" + }, + "require": { + "php": "^7.3 || ~8.0.0 || ~8.1.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.3", + "psalm/plugin-phpunit": "^0.15.1", + "squizlabs/php_codesniffer": "^3.5", + "vimeo/psalm": "^4.6" + }, + "type": "library", + "extra": { + "laminas": { + "module": "Laminas\\ZendFrameworkBridge" + } + }, + "autoload": { + "files": [ + "src/autoload.php" + ], + "psr-4": { + "Laminas\\ZendFrameworkBridge\\": "src//" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "Alias legacy ZF class names to Laminas Project equivalents.", + "keywords": [ + "ZendFramework", + "autoloading", + "laminas", + "zf" + ], + "support": { + "forum": "https://discourse.laminas.dev/", + "issues": "https://github.com/laminas/laminas-zendframework-bridge/issues", + "rss": "https://github.com/laminas/laminas-zendframework-bridge/releases.atom", + "source": "https://github.com/laminas/laminas-zendframework-bridge" + }, + "funding": [ + { + "url": "https://funding.communitybridge.org/projects/laminas-project", + "type": "community_bridge" + } + ], + "time": "2021-09-03T17:53:30+00:00" + }, + { + "name": "league/color-extractor", + "version": "0.3.2", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/color-extractor.git", + "reference": "837086ec60f50c84c611c613963e4ad2e2aec806" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/color-extractor/zipball/837086ec60f50c84c611c613963e4ad2e2aec806", + "reference": "837086ec60f50c84c611c613963e4ad2e2aec806", + "shasum": "" + }, + "require": { + "ext-gd": "*", + "php": ">=5.4.0" + }, + "replace": { + "matthecat/colorextractor": "*" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "~2", + "phpunit/phpunit": "~5" + }, + "type": "library", + "autoload": { + "psr-4": { + "": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mathieu Lechat", + "email": "math.lechat@gmail.com", + "homepage": "http://matthecat.com", + "role": "Developer" + } + ], + "description": "Extract colors from an image as a human would do.", + "homepage": "https://github.com/thephpleague/color-extractor", + "keywords": [ + "color", + "extract", + "human", + "image", + "palette" + ], + "support": { + "issues": "https://github.com/thephpleague/color-extractor/issues", + "source": "https://github.com/thephpleague/color-extractor/tree/master" + }, + "time": "2016-12-15T09:30:02+00:00" + }, + { + "name": "michelf/php-smartypants", + "version": "1.8.1", + "source": { + "type": "git", + "url": "https://github.com/michelf/php-smartypants.git", + "reference": "47d17c90a4dfd0ccf1f87e25c65e6c8012415aad" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/michelf/php-smartypants/zipball/47d17c90a4dfd0ccf1f87e25c65e6c8012415aad", + "reference": "47d17c90a4dfd0ccf1f87e25c65e6c8012415aad", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "autoload": { + "psr-0": { + "Michelf": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Michel Fortin", + "email": "michel.fortin@michelf.ca", + "homepage": "https://michelf.ca/", + "role": "Developer" + }, + { + "name": "John Gruber", + "homepage": "https://daringfireball.net/" + } + ], + "description": "PHP SmartyPants", + "homepage": "https://michelf.ca/projects/php-smartypants/", + "keywords": [ + "dashes", + "quotes", + "spaces", + "typographer", + "typography" + ], + "support": { + "issues": "https://github.com/michelf/php-smartypants/issues", + "source": "https://github.com/michelf/php-smartypants/tree/1.8.1" + }, + "time": "2016-12-13T01:01:17+00:00" + }, + { + "name": "mustangostang/spyc", + "version": "0.6.3", + "source": { + "type": "git", + "url": "git@github.com:mustangostang/spyc.git", + "reference": "4627c838b16550b666d15aeae1e5289dd5b77da0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/mustangostang/spyc/zipball/4627c838b16550b666d15aeae1e5289dd5b77da0", + "reference": "4627c838b16550b666d15aeae1e5289dd5b77da0", + "shasum": "" + }, + "require": { + "php": ">=5.3.1" + }, + "require-dev": { + "phpunit/phpunit": "4.3.*@dev" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "0.5.x-dev" + } + }, + "autoload": { + "files": [ + "Spyc.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "mustangostang", + "email": "vlad.andersen@gmail.com" + } + ], + "description": "A simple YAML loader/dumper class for PHP", + "homepage": "https://github.com/mustangostang/spyc/", + "keywords": [ + "spyc", + "yaml", + "yml" + ], + "time": "2019-09-10T13:16:29+00:00" + }, + { + "name": "phpmailer/phpmailer", + "version": "v6.5.0", + "source": { + "type": "git", + "url": "https://github.com/PHPMailer/PHPMailer.git", + "reference": "a5b5c43e50b7fba655f793ad27303cd74c57363c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHPMailer/PHPMailer/zipball/a5b5c43e50b7fba655f793ad27303cd74c57363c", + "reference": "a5b5c43e50b7fba655f793ad27303cd74c57363c", + "shasum": "" + }, + "require": { + "ext-ctype": "*", + "ext-filter": "*", + "ext-hash": "*", + "php": ">=5.5.0" + }, + "require-dev": { + "dealerdirect/phpcodesniffer-composer-installer": "^0.7.0", + "doctrine/annotations": "^1.2", + "phpcompatibility/php-compatibility": "^9.3.5", + "roave/security-advisories": "dev-latest", + "squizlabs/php_codesniffer": "^3.5.6", + "yoast/phpunit-polyfills": "^0.2.0" + }, + "suggest": { + "ext-mbstring": "Needed to send email in multibyte encoding charset or decode encoded addresses", + "hayageek/oauth2-yahoo": "Needed for Yahoo XOAUTH2 authentication", + "league/oauth2-google": "Needed for Google XOAUTH2 authentication", + "psr/log": "For optional PSR-3 debug logging", + "stevenmaguire/oauth2-microsoft": "Needed for Microsoft XOAUTH2 authentication", + "symfony/polyfill-mbstring": "To support UTF-8 if the Mbstring PHP extension is not enabled (^1.2)" + }, + "type": "library", + "autoload": { + "psr-4": { + "PHPMailer\\PHPMailer\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-2.1-only" + ], + "authors": [ + { + "name": "Marcus Bointon", + "email": "phpmailer@synchromedia.co.uk" + }, + { + "name": "Jim Jagielski", + "email": "jimjag@gmail.com" + }, + { + "name": "Andy Prevost", + "email": "codeworxtech@users.sourceforge.net" + }, + { + "name": "Brent R. Matzelle" + } + ], + "description": "PHPMailer is a full-featured email creation and transfer class for PHP", + "support": { + "issues": "https://github.com/PHPMailer/PHPMailer/issues", + "source": "https://github.com/PHPMailer/PHPMailer/tree/v6.5.0" + }, + "funding": [ + { + "url": "https://github.com/Synchro", + "type": "github" + } + ], + "time": "2021-06-16T14:33:43+00:00" + }, + { + "name": "psr/log", + "version": "1.1.4", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "d49695b909c3b7628b6289db5479a1c204601f11" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/d49695b909c3b7628b6289db5479a1c204601f11", + "reference": "d49695b909c3b7628b6289db5479a1c204601f11", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "Psr/Log/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "support": { + "source": "https://github.com/php-fig/log/tree/1.1.4" + }, + "time": "2021-05-03T11:20:27+00:00" + }, + { + "name": "symfony/polyfill-ctype", + "version": "v1.23.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "46cd95797e9df938fdd2b03693b5fca5e64b01ce" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/46cd95797e9df938fdd2b03693b5fca5e64b01ce", + "reference": "46cd95797e9df938fdd2b03693b5fca5e64b01ce", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "suggest": { + "ext-ctype": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.23-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Ctype\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Gert de Pagter", + "email": "BackEndTea@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for ctype functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "ctype", + "polyfill", + "portable" + ], + "support": { + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.23.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-02-19T12:13:01+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.23.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "9174a3d80210dca8daa7f31fec659150bbeabfc6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/9174a3d80210dca8daa7f31fec659150bbeabfc6", + "reference": "9174a3d80210dca8daa7f31fec659150bbeabfc6", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.23-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.23.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-05-27T12:26:48+00:00" + }, + { + "name": "true/punycode", + "version": "v2.1.1", + "source": { + "type": "git", + "url": "https://github.com/true/php-punycode.git", + "reference": "a4d0c11a36dd7f4e7cd7096076cab6d3378a071e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/true/php-punycode/zipball/a4d0c11a36dd7f4e7cd7096076cab6d3378a071e", + "reference": "a4d0c11a36dd7f4e7cd7096076cab6d3378a071e", + "shasum": "" + }, + "require": { + "php": ">=5.3.0", + "symfony/polyfill-mbstring": "^1.3" + }, + "require-dev": { + "phpunit/phpunit": "~4.7", + "squizlabs/php_codesniffer": "~2.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "TrueBV\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Renan Gonçalves", + "email": "renan.saddam@gmail.com" + } + ], + "description": "A Bootstring encoding of Unicode for Internationalized Domain Names in Applications (IDNA)", + "homepage": "https://github.com/true/php-punycode", + "keywords": [ + "idna", + "punycode" + ], + "support": { + "issues": "https://github.com/true/php-punycode/issues", + "source": "https://github.com/true/php-punycode/tree/master" + }, + "time": "2016-11-16T10:37:54+00:00" + }, + { + "name": "twig/twig", + "version": "v3.3.3", + "source": { + "type": "git", + "url": "https://github.com/twigphp/Twig.git", + "reference": "a27fa056df8a6384316288ca8b0fa3a35fdeb569" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/twigphp/Twig/zipball/a27fa056df8a6384316288ca8b0fa3a35fdeb569", + "reference": "a27fa056df8a6384316288ca8b0fa3a35fdeb569", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/polyfill-ctype": "^1.8", + "symfony/polyfill-mbstring": "^1.3" + }, + "require-dev": { + "psr/container": "^1.0", + "symfony/phpunit-bridge": "^4.4.9|^5.0.9|^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.3-dev" + } + }, + "autoload": { + "psr-4": { + "Twig\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com", + "homepage": "http://fabien.potencier.org", + "role": "Lead Developer" + }, + { + "name": "Twig Team", + "role": "Contributors" + }, + { + "name": "Armin Ronacher", + "email": "armin.ronacher@active-4.com", + "role": "Project Founder" + } + ], + "description": "Twig, the flexible, fast, and secure template language for PHP", + "homepage": "https://twig.symfony.com", + "keywords": [ + "templating" + ], + "support": { + "issues": "https://github.com/twigphp/Twig/issues", + "source": "https://github.com/twigphp/Twig/tree/v3.3.3" + }, + "funding": [ + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/twig/twig", + "type": "tidelift" + } + ], + "time": "2021-09-17T08:44:23+00:00" + } + ], + "packages-dev": [], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "php": ">=7.3.0 <8.1.0" + }, + "platform-dev": [], + "plugin-api-version": "2.1.0" +} diff --git a/content/error/error.txt b/content/error/error.txt new file mode 100644 index 0000000..b588b2a --- /dev/null +++ b/content/error/error.txt @@ -0,0 +1 @@ +Title: Error \ No newline at end of file diff --git a/content/home/home.txt b/content/home/home.txt new file mode 100644 index 0000000..02896ec --- /dev/null +++ b/content/home/home.txt @@ -0,0 +1 @@ +Title: Home \ No newline at end of file diff --git a/content/site.txt b/content/site.txt new file mode 100644 index 0000000..b1f98d7 --- /dev/null +++ b/content/site.txt @@ -0,0 +1 @@ +Title: Site Title \ No newline at end of file diff --git a/index.php b/index.php new file mode 100644 index 0000000..87ed01d --- /dev/null +++ b/index.php @@ -0,0 +1,5 @@ +render(); diff --git a/kirby/.editorconfig b/kirby/.editorconfig new file mode 100644 index 0000000..adbc151 --- /dev/null +++ b/kirby/.editorconfig @@ -0,0 +1,15 @@ +# This file is for unifying the coding style for different editors and IDEs +# editorconfig.org + +# PHP PSR-2 Coding Standards +# http://www.php-fig.org/psr/psr-2/ + +root = true + +[*.php] +charset = utf-8 +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true +indent_style = space +indent_size = 4 \ No newline at end of file diff --git a/kirby/SECURITY.md b/kirby/SECURITY.md new file mode 100644 index 0000000..ae42a38 --- /dev/null +++ b/kirby/SECURITY.md @@ -0,0 +1,3 @@ +# Security Policy + +Please see the [Security Policy on the Kirby website](https://getkirby.com/security) for a list of the currently supported Kirby versions and of past security incidents as well as for information on how to report security vulnerabilities in the Kirby core or in the Panel. diff --git a/kirby/assets/whoops.css b/kirby/assets/whoops.css new file mode 100644 index 0000000..bd35464 --- /dev/null +++ b/kirby/assets/whoops.css @@ -0,0 +1,83 @@ +body { + background: #efefef; + font: normal normal 400 12px/1.5 -apple-system, BlinkMacSystemFont, Segoe UI, + Roboto, Helvetica, Arial, sans-serif; +} + +.left-panel { + background: transparent; +} + +header { + background-color: #313740; +} + +.exc-title-primary { + color: hsl(0, 71%, 55%); +} + +.frame.active { + color: hsl(0, 71%, 55%); + box-shadow: inset -5px 0 0 0 #d16464; +} + +.frame:not(.active):hover { + background: rgba(203, 215, 229, 0.5); +} + +.rightButton { + color: #999; + box-shadow: inset 0 0 0 1px #777; + border-radius: 0; +} + +.rightButton:hover { + box-shadow: inset 0 0 0 1px #555; + color: #777; +} + +.details-heading { + color: #7e9abf; + font-weight: 500; +} + +.frame-code { + background: #000; +} + +pre.code-block, +code.code-block, +.frame-args.code-block, +.frame-args.code-block samp { + background: #16171a; +} + +.linenums li.current { + background: transparent; +} + +.linenums li.current.active { + background: rgba(209, 100, 100, 0.3); +} + +pre .atv, +code .atv, +pre .str, +code .str { + color: #a7bd68; +} + +pre .tag, +code .tag { + color: #d16464; +} + +pre .kwd, +code .kwd { + color: #8abeb7; +} + +pre .atn, +code .atn { + color: #de935f; +} diff --git a/kirby/bootstrap.php b/kirby/bootstrap.php new file mode 100644 index 0000000..16266e4 --- /dev/null +++ b/kirby/bootstrap.php @@ -0,0 +1,35 @@ +=') === false || + version_compare(PHP_VERSION, '8.1.0', '<') === false +) { + die(include __DIR__ . '/views/php.php'); +} + +if (is_file($autoloader = dirname(__DIR__) . '/vendor/autoload.php')) { + + /** + * Always prefer a site-wide Composer autoloader + * if it exists, it means that the user has probably + * installed additional packages + */ + include $autoloader; +} elseif (is_file($autoloader = __DIR__ . '/vendor/autoload.php')) { + + /** + * Fall back to the local autoloader if that exists + */ + include $autoloader; +} else { + + /** + * If neither one exists, don't bother searching; + * it's a custom directory setup and the users need to + * load the autoloader themselves + */ +} diff --git a/kirby/cacert.pem b/kirby/cacert.pem new file mode 100644 index 0000000..fdb454d --- /dev/null +++ b/kirby/cacert.pem @@ -0,0 +1,3174 @@ +## +## Bundle of CA Root Certificates +## +## Certificate data from Mozilla as of: Mon Jul 5 21:35:54 2021 GMT +## +## This is a bundle of X.509 certificates of public Certificate Authorities +## (CA). These were automatically extracted from Mozilla's root certificates +## file (certdata.txt). This file can be found in the mozilla source tree: +## https://hg.mozilla.org/releases/mozilla-release/raw-file/default/security/nss/lib/ckfw/builtins/certdata.txt +## +## It contains the certificates in PEM format and therefore +## can be directly used with curl / libcurl / php_curl, or with +## an Apache+mod_ssl webserver for SSL client authentication. +## Just configure this file as the SSLCACertificateFile. +## +## Conversion done with mk-ca-bundle.pl version 1.28. +## SHA256: c8f6733d1ff4e6a4769c182971a1234f95ae079247a9c439a13423fe8ba5c24f +## + + +GlobalSign Root CA +================== +-----BEGIN CERTIFICATE----- +MIIDdTCCAl2gAwIBAgILBAAAAAABFUtaw5QwDQYJKoZIhvcNAQEFBQAwVzELMAkGA1UEBhMCQkUx +GTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExEDAOBgNVBAsTB1Jvb3QgQ0ExGzAZBgNVBAMTEkds +b2JhbFNpZ24gUm9vdCBDQTAeFw05ODA5MDExMjAwMDBaFw0yODAxMjgxMjAwMDBaMFcxCzAJBgNV +BAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMRAwDgYDVQQLEwdSb290IENBMRswGQYD +VQQDExJHbG9iYWxTaWduIFJvb3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDa +DuaZjc6j40+Kfvvxi4Mla+pIH/EqsLmVEQS98GPR4mdmzxzdzxtIK+6NiY6arymAZavpxy0Sy6sc +THAHoT0KMM0VjU/43dSMUBUc71DuxC73/OlS8pF94G3VNTCOXkNz8kHp1Wrjsok6Vjk4bwY8iGlb +Kk3Fp1S4bInMm/k8yuX9ifUSPJJ4ltbcdG6TRGHRjcdGsnUOhugZitVtbNV4FpWi6cgKOOvyJBNP +c1STE4U6G7weNLWLBYy5d4ux2x8gkasJU26Qzns3dLlwR5EiUWMWea6xrkEmCMgZK9FGqkjWZCrX +gzT/LCrBbBlDSgeF59N89iFo7+ryUp9/k5DPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV +HRMBAf8EBTADAQH/MB0GA1UdDgQWBBRge2YaRQ2XyolQL30EzTSo//z9SzANBgkqhkiG9w0BAQUF +AAOCAQEA1nPnfE920I2/7LqivjTFKDK1fPxsnCwrvQmeU79rXqoRSLblCKOzyj1hTdNGCbM+w6Dj +Y1Ub8rrvrTnhQ7k4o+YviiY776BQVvnGCv04zcQLcFGUl5gE38NflNUVyRRBnMRddWQVDf9VMOyG +j/8N7yy5Y0b2qvzfvGn9LhJIZJrglfCm7ymPAbEVtQwdpf5pLGkkeB6zpxxxYu7KyJesF12KwvhH +hm4qxFYxldBniYUr+WymXUadDKqC5JlR3XC321Y9YeRq4VzW9v493kHMB65jUr9TU/Qr6cf9tveC +X4XSQRjbgbMEHMUfpIBvFSDJ3gyICh3WZlXi/EjJKSZp4A== +-----END CERTIFICATE----- + +GlobalSign Root CA - R2 +======================= +-----BEGIN CERTIFICATE----- +MIIDujCCAqKgAwIBAgILBAAAAAABD4Ym5g0wDQYJKoZIhvcNAQEFBQAwTDEgMB4GA1UECxMXR2xv +YmFsU2lnbiBSb290IENBIC0gUjIxEzARBgNVBAoTCkdsb2JhbFNpZ24xEzARBgNVBAMTCkdsb2Jh +bFNpZ24wHhcNMDYxMjE1MDgwMDAwWhcNMjExMjE1MDgwMDAwWjBMMSAwHgYDVQQLExdHbG9iYWxT +aWduIFJvb3QgQ0EgLSBSMjETMBEGA1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2ln +bjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKbPJA6+Lm8omUVCxKs+IVSbC9N/hHD6 +ErPLv4dfxn+G07IwXNb9rfF73OX4YJYJkhD10FPe+3t+c4isUoh7SqbKSaZeqKeMWhG8eoLrvozp +s6yWJQeXSpkqBy+0Hne/ig+1AnwblrjFuTosvNYSuetZfeLQBoZfXklqtTleiDTsvHgMCJiEbKjN +S7SgfQx5TfC4LcshytVsW33hoCmEofnTlEnLJGKRILzdC9XZzPnqJworc5HGnRusyMvo4KD0L5CL +TfuwNhv2GXqF4G3yYROIXJ/gkwpRl4pazq+r1feqCapgvdzZX99yqWATXgAByUr6P6TqBwMhAo6C +ygPCm48CAwEAAaOBnDCBmTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4E +FgQUm+IHV2ccHsBqBt5ZtJot39wZhi4wNgYDVR0fBC8wLTAroCmgJ4YlaHR0cDovL2NybC5nbG9i +YWxzaWduLm5ldC9yb290LXIyLmNybDAfBgNVHSMEGDAWgBSb4gdXZxwewGoG3lm0mi3f3BmGLjAN +BgkqhkiG9w0BAQUFAAOCAQEAmYFThxxol4aR7OBKuEQLq4GsJ0/WwbgcQ3izDJr86iw8bmEbTUsp +9Z8FHSbBuOmDAGJFtqkIk7mpM0sYmsL4h4hO291xNBrBVNpGP+DTKqttVCL1OmLNIG+6KYnX3ZHu +01yiPqFbQfXf5WRDLenVOavSot+3i9DAgBkcRcAtjOj4LaR0VknFBbVPFd5uRHg5h6h+u/N5GJG7 +9G+dwfCMNYxdAfvDbbnvRG15RjF+Cv6pgsH/76tuIMRQyV+dTZsXjAzlAcmgQWpzU/qlULRuJQ/7 +TBj0/VLZjmmx6BEP3ojY+x1J96relc8geMJgEtslQIxq/H5COEBkEveegeGTLg== +-----END CERTIFICATE----- + +Entrust.net Premium 2048 Secure Server CA +========================================= +-----BEGIN CERTIFICATE----- +MIIEKjCCAxKgAwIBAgIEOGPe+DANBgkqhkiG9w0BAQUFADCBtDEUMBIGA1UEChMLRW50cnVzdC5u +ZXQxQDA+BgNVBAsUN3d3dy5lbnRydXN0Lm5ldC9DUFNfMjA0OCBpbmNvcnAuIGJ5IHJlZi4gKGxp +bWl0cyBsaWFiLikxJTAjBgNVBAsTHChjKSAxOTk5IEVudHJ1c3QubmV0IExpbWl0ZWQxMzAxBgNV +BAMTKkVudHJ1c3QubmV0IENlcnRpZmljYXRpb24gQXV0aG9yaXR5ICgyMDQ4KTAeFw05OTEyMjQx +NzUwNTFaFw0yOTA3MjQxNDE1MTJaMIG0MRQwEgYDVQQKEwtFbnRydXN0Lm5ldDFAMD4GA1UECxQ3 +d3d3LmVudHJ1c3QubmV0L0NQU18yMDQ4IGluY29ycC4gYnkgcmVmLiAobGltaXRzIGxpYWIuKTEl +MCMGA1UECxMcKGMpIDE5OTkgRW50cnVzdC5uZXQgTGltaXRlZDEzMDEGA1UEAxMqRW50cnVzdC5u +ZXQgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgKDIwNDgpMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A +MIIBCgKCAQEArU1LqRKGsuqjIAcVFmQqK0vRvwtKTY7tgHalZ7d4QMBzQshowNtTK91euHaYNZOL +Gp18EzoOH1u3Hs/lJBQesYGpjX24zGtLA/ECDNyrpUAkAH90lKGdCCmziAv1h3edVc3kw37XamSr +hRSGlVuXMlBvPci6Zgzj/L24ScF2iUkZ/cCovYmjZy/Gn7xxGWC4LeksyZB2ZnuU4q941mVTXTzW +nLLPKQP5L6RQstRIzgUyVYr9smRMDuSYB3Xbf9+5CFVghTAp+XtIpGmG4zU/HoZdenoVve8AjhUi +VBcAkCaTvA5JaJG/+EfTnZVCwQ5N328mz8MYIWJmQ3DW1cAH4QIDAQABo0IwQDAOBgNVHQ8BAf8E +BAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUVeSB0RGAvtiJuQijMfmhJAkWuXAwDQYJ +KoZIhvcNAQEFBQADggEBADubj1abMOdTmXx6eadNl9cZlZD7Bh/KM3xGY4+WZiT6QBshJ8rmcnPy +T/4xmf3IDExoU8aAghOY+rat2l098c5u9hURlIIM7j+VrxGrD9cv3h8Dj1csHsm7mhpElesYT6Yf +zX1XEC+bBAlahLVu2B064dae0Wx5XnkcFMXj0EyTO2U87d89vqbllRrDtRnDvV5bu/8j72gZyxKT +J1wDLW8w0B62GqzeWvfRqqgnpv55gcR5mTNXuhKwqeBCbJPKVt7+bYQLCIt+jerXmCHG8+c8eS9e +nNFMFY3h7CI3zJpDC5fcgJCNs2ebb0gIFVbPv/ErfF6adulZkMV8gzURZVE= +-----END CERTIFICATE----- + +Baltimore CyberTrust Root +========================= +-----BEGIN CERTIFICATE----- +MIIDdzCCAl+gAwIBAgIEAgAAuTANBgkqhkiG9w0BAQUFADBaMQswCQYDVQQGEwJJRTESMBAGA1UE +ChMJQmFsdGltb3JlMRMwEQYDVQQLEwpDeWJlclRydXN0MSIwIAYDVQQDExlCYWx0aW1vcmUgQ3li +ZXJUcnVzdCBSb290MB4XDTAwMDUxMjE4NDYwMFoXDTI1MDUxMjIzNTkwMFowWjELMAkGA1UEBhMC +SUUxEjAQBgNVBAoTCUJhbHRpbW9yZTETMBEGA1UECxMKQ3liZXJUcnVzdDEiMCAGA1UEAxMZQmFs +dGltb3JlIEN5YmVyVHJ1c3QgUm9vdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKME +uyKrmD1X6CZymrV51Cni4eiVgLGw41uOKymaZN+hXe2wCQVt2yguzmKiYv60iNoS6zjrIZ3AQSsB +UnuId9Mcj8e6uYi1agnnc+gRQKfRzMpijS3ljwumUNKoUMMo6vWrJYeKmpYcqWe4PwzV9/lSEy/C +G9VwcPCPwBLKBsua4dnKM3p31vjsufFoREJIE9LAwqSuXmD+tqYF/LTdB1kC1FkYmGP1pWPgkAx9 +XbIGevOF6uvUA65ehD5f/xXtabz5OTZydc93Uk3zyZAsuT3lySNTPx8kmCFcB5kpvcY67Oduhjpr +l3RjM71oGDHweI12v/yejl0qhqdNkNwnGjkCAwEAAaNFMEMwHQYDVR0OBBYEFOWdWTCCR1jMrPoI +VDaGezq1BE3wMBIGA1UdEwEB/wQIMAYBAf8CAQMwDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3DQEB +BQUAA4IBAQCFDF2O5G9RaEIFoN27TyclhAO992T9Ldcw46QQF+vaKSm2eT929hkTI7gQCvlYpNRh +cL0EYWoSihfVCr3FvDB81ukMJY2GQE/szKN+OMY3EU/t3WgxjkzSswF07r51XgdIGn9w/xZchMB5 +hbgF/X++ZRGjD8ACtPhSNzkE1akxehi/oCr0Epn3o0WC4zxe9Z2etciefC7IpJ5OCBRLbf1wbWsa +Y71k5h+3zvDyny67G7fyUIhzksLi4xaNmjICq44Y3ekQEe5+NauQrz4wlHrQMz2nZQ/1/I6eYs9H +RCwBXbsdtTLSR9I4LtD+gdwyah617jzV/OeBHRnDJELqYzmp +-----END CERTIFICATE----- + +Entrust Root Certification Authority +==================================== +-----BEGIN CERTIFICATE----- +MIIEkTCCA3mgAwIBAgIERWtQVDANBgkqhkiG9w0BAQUFADCBsDELMAkGA1UEBhMCVVMxFjAUBgNV +BAoTDUVudHJ1c3QsIEluYy4xOTA3BgNVBAsTMHd3dy5lbnRydXN0Lm5ldC9DUFMgaXMgaW5jb3Jw +b3JhdGVkIGJ5IHJlZmVyZW5jZTEfMB0GA1UECxMWKGMpIDIwMDYgRW50cnVzdCwgSW5jLjEtMCsG +A1UEAxMkRW50cnVzdCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTA2MTEyNzIwMjM0 +MloXDTI2MTEyNzIwNTM0MlowgbAxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1FbnRydXN0LCBJbmMu +MTkwNwYDVQQLEzB3d3cuZW50cnVzdC5uZXQvQ1BTIGlzIGluY29ycG9yYXRlZCBieSByZWZlcmVu +Y2UxHzAdBgNVBAsTFihjKSAyMDA2IEVudHJ1c3QsIEluYy4xLTArBgNVBAMTJEVudHJ1c3QgUm9v +dCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB +ALaVtkNC+sZtKm9I35RMOVcF7sN5EUFoNu3s/poBj6E4KPz3EEZmLk0eGrEaTsbRwJWIsMn/MYsz +A9u3g3s+IIRe7bJWKKf44LlAcTfFy0cOlypowCKVYhXbR9n10Cv/gkvJrT7eTNuQgFA/CYqEAOww +Cj0Yzfv9KlmaI5UXLEWeH25DeW0MXJj+SKfFI0dcXv1u5x609mhF0YaDW6KKjbHjKYD+JXGIrb68 +j6xSlkuqUY3kEzEZ6E5Nn9uss2rVvDlUccp6en+Q3X0dgNmBu1kmwhH+5pPi94DkZfs0Nw4pgHBN +rziGLp5/V6+eF67rHMsoIV+2HNjnogQi+dPa2MsCAwEAAaOBsDCBrTAOBgNVHQ8BAf8EBAMCAQYw +DwYDVR0TAQH/BAUwAwEB/zArBgNVHRAEJDAigA8yMDA2MTEyNzIwMjM0MlqBDzIwMjYxMTI3MjA1 +MzQyWjAfBgNVHSMEGDAWgBRokORnpKZTgMeGZqTx90tD+4S9bTAdBgNVHQ4EFgQUaJDkZ6SmU4DH +hmak8fdLQ/uEvW0wHQYJKoZIhvZ9B0EABBAwDhsIVjcuMTo0LjADAgSQMA0GCSqGSIb3DQEBBQUA +A4IBAQCT1DCw1wMgKtD5Y+iRDAUgqV8ZyntyTtSx29CW+1RaGSwMCPeyvIWonX9tO1KzKtvn1ISM +Y/YPyyYBkVBs9F8U4pN0wBOeMDpQ47RgxRzwIkSNcUesyBrJ6ZuaAGAT/3B+XxFNSRuzFVJ7yVTa +v52Vr2ua2J7p8eRDjeIRRDq/r72DQnNSi6q7pynP9WQcCk3RvKqsnyrQ/39/2n3qse0wJcGE2jTS +W3iDVuycNsMm4hH2Z0kdkquM++v/eu6FSqdQgPCnXEqULl8FmTxSQeDNtGPPAUO6nIPcj2A781q0 +tHuu2guQOHXvgR1m0vdXcDazv/wor3ElhVsT/h5/WrQ8 +-----END CERTIFICATE----- + +Comodo AAA Services root +======================== +-----BEGIN CERTIFICATE----- +MIIEMjCCAxqgAwIBAgIBATANBgkqhkiG9w0BAQUFADB7MQswCQYDVQQGEwJHQjEbMBkGA1UECAwS +R3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHDAdTYWxmb3JkMRowGAYDVQQKDBFDb21vZG8gQ0Eg +TGltaXRlZDEhMB8GA1UEAwwYQUFBIENlcnRpZmljYXRlIFNlcnZpY2VzMB4XDTA0MDEwMTAwMDAw +MFoXDTI4MTIzMTIzNTk1OVowezELMAkGA1UEBhMCR0IxGzAZBgNVBAgMEkdyZWF0ZXIgTWFuY2hl +c3RlcjEQMA4GA1UEBwwHU2FsZm9yZDEaMBgGA1UECgwRQ29tb2RvIENBIExpbWl0ZWQxITAfBgNV +BAMMGEFBQSBDZXJ0aWZpY2F0ZSBTZXJ2aWNlczCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC +ggEBAL5AnfRu4ep2hxxNRUSOvkbIgwadwSr+GB+O5AL686tdUIoWMQuaBtDFcCLNSS1UY8y2bmhG +C1Pqy0wkwLxyTurxFa70VJoSCsN6sjNg4tqJVfMiWPPe3M/vg4aijJRPn2jymJBGhCfHdr/jzDUs +i14HZGWCwEiwqJH5YZ92IFCokcdmtet4YgNW8IoaE+oxox6gmf049vYnMlhvB/VruPsUK6+3qszW +Y19zjNoFmag4qMsXeDZRrOme9Hg6jc8P2ULimAyrL58OAd7vn5lJ8S3frHRNG5i1R8XlKdH5kBjH +Ypy+g8cmez6KJcfA3Z3mNWgQIJ2P2N7Sw4ScDV7oL8kCAwEAAaOBwDCBvTAdBgNVHQ4EFgQUoBEK +Iz6W8Qfs4q8p74Klf9AwpLQwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wewYDVR0f +BHQwcjA4oDagNIYyaHR0cDovL2NybC5jb21vZG9jYS5jb20vQUFBQ2VydGlmaWNhdGVTZXJ2aWNl +cy5jcmwwNqA0oDKGMGh0dHA6Ly9jcmwuY29tb2RvLm5ldC9BQUFDZXJ0aWZpY2F0ZVNlcnZpY2Vz +LmNybDANBgkqhkiG9w0BAQUFAAOCAQEACFb8AvCb6P+k+tZ7xkSAzk/ExfYAWMymtrwUSWgEdujm +7l3sAg9g1o1QGE8mTgHj5rCl7r+8dFRBv/38ErjHT1r0iWAFf2C3BUrz9vHCv8S5dIa2LX1rzNLz +Rt0vxuBqw8M0Ayx9lt1awg6nCpnBBYurDC/zXDrPbDdVCYfeU0BsWO/8tqtlbgT2G9w84FoVxp7Z +8VlIMCFlA2zs6SFz7JsDoeA3raAVGI/6ugLOpyypEBMs1OUIJqsil2D4kF501KKaU73yqWjgom7C +12yxow+ev+to51byrvLjKzg6CYG1a4XXvi3tPxq3smPi9WIsgtRqAEFQ8TmDn5XpNpaYbg== +-----END CERTIFICATE----- + +QuoVadis Root CA 2 +================== +-----BEGIN CERTIFICATE----- +MIIFtzCCA5+gAwIBAgICBQkwDQYJKoZIhvcNAQEFBQAwRTELMAkGA1UEBhMCQk0xGTAXBgNVBAoT +EFF1b1ZhZGlzIExpbWl0ZWQxGzAZBgNVBAMTElF1b1ZhZGlzIFJvb3QgQ0EgMjAeFw0wNjExMjQx +ODI3MDBaFw0zMTExMjQxODIzMzNaMEUxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBM +aW1pdGVkMRswGQYDVQQDExJRdW9WYWRpcyBSb290IENBIDIwggIiMA0GCSqGSIb3DQEBAQUAA4IC +DwAwggIKAoICAQCaGMpLlA0ALa8DKYrwD4HIrkwZhR0In6spRIXzL4GtMh6QRr+jhiYaHv5+HBg6 +XJxgFyo6dIMzMH1hVBHL7avg5tKifvVrbxi3Cgst/ek+7wrGsxDp3MJGF/hd/aTa/55JWpzmM+Yk +lvc/ulsrHHo1wtZn/qtmUIttKGAr79dgw8eTvI02kfN/+NsRE8Scd3bBrrcCaoF6qUWD4gXmuVbB +lDePSHFjIuwXZQeVikvfj8ZaCuWw419eaxGrDPmF60Tp+ARz8un+XJiM9XOva7R+zdRcAitMOeGy +lZUtQofX1bOQQ7dsE/He3fbE+Ik/0XX1ksOR1YqI0JDs3G3eicJlcZaLDQP9nL9bFqyS2+r+eXyt +66/3FsvbzSUr5R/7mp/iUcw6UwxI5g69ybR2BlLmEROFcmMDBOAENisgGQLodKcftslWZvB1Jdxn +wQ5hYIizPtGo/KPaHbDRsSNU30R2be1B2MGyIrZTHN81Hdyhdyox5C315eXbyOD/5YDXC2Og/zOh +D7osFRXql7PSorW+8oyWHhqPHWykYTe5hnMz15eWniN9gqRMgeKh0bpnX5UHoycR7hYQe7xFSkyy +BNKr79X9DFHOUGoIMfmR2gyPZFwDwzqLID9ujWc9Otb+fVuIyV77zGHcizN300QyNQliBJIWENie +J0f7OyHj+OsdWwIDAQABo4GwMIGtMA8GA1UdEwEB/wQFMAMBAf8wCwYDVR0PBAQDAgEGMB0GA1Ud +DgQWBBQahGK8SEwzJQTU7tD2A8QZRtGUazBuBgNVHSMEZzBlgBQahGK8SEwzJQTU7tD2A8QZRtGU +a6FJpEcwRTELMAkGA1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxGzAZBgNVBAMT +ElF1b1ZhZGlzIFJvb3QgQ0EgMoICBQkwDQYJKoZIhvcNAQEFBQADggIBAD4KFk2fBluornFdLwUv +Z+YTRYPENvbzwCYMDbVHZF34tHLJRqUDGCdViXh9duqWNIAXINzng/iN/Ae42l9NLmeyhP3ZRPx3 +UIHmfLTJDQtyU/h2BwdBR5YM++CCJpNVjP4iH2BlfF/nJrP3MpCYUNQ3cVX2kiF495V5+vgtJodm +VjB3pjd4M1IQWK4/YY7yarHvGH5KWWPKjaJW1acvvFYfzznB4vsKqBUsfU16Y8Zsl0Q80m/DShcK ++JDSV6IZUaUtl0HaB0+pUNqQjZRG4T7wlP0QADj1O+hA4bRuVhogzG9Yje0uRY/W6ZM/57Es3zrW +IozchLsib9D45MY56QSIPMO661V6bYCZJPVsAfv4l7CUW+v90m/xd2gNNWQjrLhVoQPRTUIZ3Ph1 +WVaj+ahJefivDrkRoHy3au000LYmYjgahwz46P0u05B/B5EqHdZ+XIWDmbA4CD/pXvk1B+TJYm5X +f6dQlfe6yJvmjqIBxdZmv3lh8zwc4bmCXF2gw+nYSL0ZohEUGW6yhhtoPkg3Goi3XZZenMfvJ2II +4pEZXNLxId26F0KCl3GBUzGpn/Z9Yr9y4aOTHcyKJloJONDO1w2AFrR4pTqHTI2KpdVGl/IsELm8 +VCLAAVBpQ570su9t+Oza8eOx79+Rj1QqCyXBJhnEUhAFZdWCEOrCMc0u +-----END CERTIFICATE----- + +QuoVadis Root CA 3 +================== +-----BEGIN CERTIFICATE----- +MIIGnTCCBIWgAwIBAgICBcYwDQYJKoZIhvcNAQEFBQAwRTELMAkGA1UEBhMCQk0xGTAXBgNVBAoT +EFF1b1ZhZGlzIExpbWl0ZWQxGzAZBgNVBAMTElF1b1ZhZGlzIFJvb3QgQ0EgMzAeFw0wNjExMjQx +OTExMjNaFw0zMTExMjQxOTA2NDRaMEUxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBM +aW1pdGVkMRswGQYDVQQDExJRdW9WYWRpcyBSb290IENBIDMwggIiMA0GCSqGSIb3DQEBAQUAA4IC +DwAwggIKAoICAQDMV0IWVJzmmNPTTe7+7cefQzlKZbPoFog02w1ZkXTPkrgEQK0CSzGrvI2RaNgg +DhoB4hp7Thdd4oq3P5kazethq8Jlph+3t723j/z9cI8LoGe+AaJZz3HmDyl2/7FWeUUrH556VOij +KTVopAFPD6QuN+8bv+OPEKhyq1hX51SGyMnzW9os2l2ObjyjPtr7guXd8lyyBTNvijbO0BNO/79K +DDRMpsMhvVAEVeuxu537RR5kFd5VAYwCdrXLoT9CabwvvWhDFlaJKjdhkf2mrk7AyxRllDdLkgbv +BNDInIjbC3uBr7E9KsRlOni27tyAsdLTmZw67mtaa7ONt9XOnMK+pUsvFrGeaDsGb659n/je7Mwp +p5ijJUMv7/FfJuGITfhebtfZFG4ZM2mnO4SJk8RTVROhUXhA+LjJou57ulJCg54U7QVSWllWp5f8 +nT8KKdjcT5EOE7zelaTfi5m+rJsziO+1ga8bxiJTyPbH7pcUsMV8eFLI8M5ud2CEpukqdiDtWAEX +MJPpGovgc2PZapKUSU60rUqFxKMiMPwJ7Wgic6aIDFUhWMXhOp8q3crhkODZc6tsgLjoC2SToJyM +Gf+z0gzskSaHirOi4XCPLArlzW1oUevaPwV/izLmE1xr/l9A4iLItLRkT9a6fUg+qGkM17uGcclz +uD87nSVL2v9A6wIDAQABo4IBlTCCAZEwDwYDVR0TAQH/BAUwAwEB/zCB4QYDVR0gBIHZMIHWMIHT +BgkrBgEEAb5YAAMwgcUwgZMGCCsGAQUFBwICMIGGGoGDQW55IHVzZSBvZiB0aGlzIENlcnRpZmlj +YXRlIGNvbnN0aXR1dGVzIGFjY2VwdGFuY2Ugb2YgdGhlIFF1b1ZhZGlzIFJvb3QgQ0EgMyBDZXJ0 +aWZpY2F0ZSBQb2xpY3kgLyBDZXJ0aWZpY2F0aW9uIFByYWN0aWNlIFN0YXRlbWVudC4wLQYIKwYB +BQUHAgEWIWh0dHA6Ly93d3cucXVvdmFkaXNnbG9iYWwuY29tL2NwczALBgNVHQ8EBAMCAQYwHQYD +VR0OBBYEFPLAE+CCQz777i9nMpY1XNu4ywLQMG4GA1UdIwRnMGWAFPLAE+CCQz777i9nMpY1XNu4 +ywLQoUmkRzBFMQswCQYDVQQGEwJCTTEZMBcGA1UEChMQUXVvVmFkaXMgTGltaXRlZDEbMBkGA1UE +AxMSUXVvVmFkaXMgUm9vdCBDQSAzggIFxjANBgkqhkiG9w0BAQUFAAOCAgEAT62gLEz6wPJv92ZV +qyM07ucp2sNbtrCD2dDQ4iH782CnO11gUyeim/YIIirnv6By5ZwkajGxkHon24QRiSemd1o417+s +hvzuXYO8BsbRd2sPbSQvS3pspweWyuOEn62Iix2rFo1bZhfZFvSLgNLd+LJ2w/w4E6oM3kJpK27z +POuAJ9v1pkQNn1pVWQvVDVJIxa6f8i+AxeoyUDUSly7B4f/xI4hROJ/yZlZ25w9Rl6VSDE1JUZU2 +Pb+iSwwQHYaZTKrzchGT5Or2m9qoXadNt54CrnMAyNojA+j56hl0YgCUyyIgvpSnWbWCar6ZeXqp +8kokUvd0/bpO5qgdAm6xDYBEwa7TIzdfu4V8K5Iu6H6li92Z4b8nby1dqnuH/grdS/yO9SbkbnBC +bjPsMZ57k8HkyWkaPcBrTiJt7qtYTcbQQcEr6k8Sh17rRdhs9ZgC06DYVYoGmRmioHfRMJ6szHXu +g/WwYjnPbFfiTNKRCw51KBuav/0aQ/HKd/s7j2G4aSgWQgRecCocIdiP4b0jWy10QJLZYxkNc91p +vGJHvOB0K7Lrfb5BG7XARsWhIstfTsEokt4YutUqKLsRixeTmJlglFwjz1onl14LBQaTNx47aTbr +qZ5hHY8y2o4M1nQ+ewkk2gF3R8Q7zTSMmfXK4SVhM7JZG+Ju1zdXtg2pEto= +-----END CERTIFICATE----- + +Security Communication Root CA +============================== +-----BEGIN CERTIFICATE----- +MIIDWjCCAkKgAwIBAgIBADANBgkqhkiG9w0BAQUFADBQMQswCQYDVQQGEwJKUDEYMBYGA1UEChMP +U0VDT00gVHJ1c3QubmV0MScwJQYDVQQLEx5TZWN1cml0eSBDb21tdW5pY2F0aW9uIFJvb3RDQTEw +HhcNMDMwOTMwMDQyMDQ5WhcNMjMwOTMwMDQyMDQ5WjBQMQswCQYDVQQGEwJKUDEYMBYGA1UEChMP +U0VDT00gVHJ1c3QubmV0MScwJQYDVQQLEx5TZWN1cml0eSBDb21tdW5pY2F0aW9uIFJvb3RDQTEw +ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCzs/5/022x7xZ8V6UMbXaKL0u/ZPtM7orw +8yl89f/uKuDp6bpbZCKamm8sOiZpUQWZJtzVHGpxxpp9Hp3dfGzGjGdnSj74cbAZJ6kJDKaVv0uM +DPpVmDvY6CKhS3E4eayXkmmziX7qIWgGmBSWh9JhNrxtJ1aeV+7AwFb9Ms+k2Y7CI9eNqPPYJayX +5HA49LY6tJ07lyZDo6G8SVlyTCMwhwFY9k6+HGhWZq/NQV3Is00qVUarH9oe4kA92819uZKAnDfd +DJZkndwi92SL32HeFZRSFaB9UslLqCHJxrHty8OVYNEP8Ktw+N/LTX7s1vqr2b1/VPKl6Xn62dZ2 +JChzAgMBAAGjPzA9MB0GA1UdDgQWBBSgc0mZaNyFW2XjmygvV5+9M7wHSDALBgNVHQ8EBAMCAQYw +DwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQUFAAOCAQEAaECpqLvkT115swW1F7NgE+vGkl3g +0dNq/vu+m22/xwVtWSDEHPC32oRYAmP6SBbvT6UL90qY8j+eG61Ha2POCEfrUj94nK9NrvjVT8+a +mCoQQTlSxN3Zmw7vkwGusi7KaEIkQmywszo+zenaSMQVy+n5Bw+SUEmK3TGXX8npN6o7WWWXlDLJ +s58+OmJYxUmtYg5xpTKqL8aJdkNAExNnPaJUJRDL8Try2frbSVa7pv6nQTXD4IhhyYjH3zYQIphZ +6rBK+1YWc26sTfcioU+tHXotRSflMMFe8toTyyVCUZVHA4xsIcx0Qu1T/zOLjw9XARYvz6buyXAi +FL39vmwLAw== +-----END CERTIFICATE----- + +XRamp Global CA Root +==================== +-----BEGIN CERTIFICATE----- +MIIEMDCCAxigAwIBAgIQUJRs7Bjq1ZxN1ZfvdY+grTANBgkqhkiG9w0BAQUFADCBgjELMAkGA1UE +BhMCVVMxHjAcBgNVBAsTFXd3dy54cmFtcHNlY3VyaXR5LmNvbTEkMCIGA1UEChMbWFJhbXAgU2Vj +dXJpdHkgU2VydmljZXMgSW5jMS0wKwYDVQQDEyRYUmFtcCBHbG9iYWwgQ2VydGlmaWNhdGlvbiBB +dXRob3JpdHkwHhcNMDQxMTAxMTcxNDA0WhcNMzUwMTAxMDUzNzE5WjCBgjELMAkGA1UEBhMCVVMx +HjAcBgNVBAsTFXd3dy54cmFtcHNlY3VyaXR5LmNvbTEkMCIGA1UEChMbWFJhbXAgU2VjdXJpdHkg +U2VydmljZXMgSW5jMS0wKwYDVQQDEyRYUmFtcCBHbG9iYWwgQ2VydGlmaWNhdGlvbiBBdXRob3Jp +dHkwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCYJB69FbS638eMpSe2OAtp87ZOqCwu +IR1cRN8hXX4jdP5efrRKt6atH67gBhbim1vZZ3RrXYCPKZ2GG9mcDZhtdhAoWORlsH9KmHmf4MMx +foArtYzAQDsRhtDLooY2YKTVMIJt2W7QDxIEM5dfT2Fa8OT5kavnHTu86M/0ay00fOJIYRyO82FE +zG+gSqmUsE3a56k0enI4qEHMPJQRfevIpoy3hsvKMzvZPTeL+3o+hiznc9cKV6xkmxnr9A8ECIqs +AxcZZPRaJSKNNCyy9mgdEm3Tih4U2sSPpuIjhdV6Db1q4Ons7Be7QhtnqiXtRYMh/MHJfNViPvry +xS3T/dRlAgMBAAGjgZ8wgZwwEwYJKwYBBAGCNxQCBAYeBABDAEEwCwYDVR0PBAQDAgGGMA8GA1Ud +EwEB/wQFMAMBAf8wHQYDVR0OBBYEFMZPoj0GY4QJnM5i5ASsjVy16bYbMDYGA1UdHwQvMC0wK6Ap +oCeGJWh0dHA6Ly9jcmwueHJhbXBzZWN1cml0eS5jb20vWEdDQS5jcmwwEAYJKwYBBAGCNxUBBAMC +AQEwDQYJKoZIhvcNAQEFBQADggEBAJEVOQMBG2f7Shz5CmBbodpNl2L5JFMn14JkTpAuw0kbK5rc +/Kh4ZzXxHfARvbdI4xD2Dd8/0sm2qlWkSLoC295ZLhVbO50WfUfXN+pfTXYSNrsf16GBBEYgoyxt +qZ4Bfj8pzgCT3/3JknOJiWSe5yvkHJEs0rnOfc5vMZnT5r7SHpDwCRR5XCOrTdLaIR9NmXmd4c8n +nxCbHIgNsIpkQTG4DmyQJKSbXHGPurt+HBvbaoAPIbzp26a3QPSyi6mx5O+aGtA9aZnuqCij4Tyz +8LIRnM98QObd50N9otg6tamN8jSZxNQQ4Qb9CYQQO+7ETPTsJ3xCwnR8gooJybQDJbw= +-----END CERTIFICATE----- + +Go Daddy Class 2 CA +=================== +-----BEGIN CERTIFICATE----- +MIIEADCCAuigAwIBAgIBADANBgkqhkiG9w0BAQUFADBjMQswCQYDVQQGEwJVUzEhMB8GA1UEChMY +VGhlIEdvIERhZGR5IEdyb3VwLCBJbmMuMTEwLwYDVQQLEyhHbyBEYWRkeSBDbGFzcyAyIENlcnRp +ZmljYXRpb24gQXV0aG9yaXR5MB4XDTA0MDYyOTE3MDYyMFoXDTM0MDYyOTE3MDYyMFowYzELMAkG +A1UEBhMCVVMxITAfBgNVBAoTGFRoZSBHbyBEYWRkeSBHcm91cCwgSW5jLjExMC8GA1UECxMoR28g +RGFkZHkgQ2xhc3MgMiBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTCCASAwDQYJKoZIhvcNAQEBBQAD +ggENADCCAQgCggEBAN6d1+pXGEmhW+vXX0iG6r7d/+TvZxz0ZWizV3GgXne77ZtJ6XCAPVYYYwhv +2vLM0D9/AlQiVBDYsoHUwHU9S3/Hd8M+eKsaA7Ugay9qK7HFiH7Eux6wwdhFJ2+qN1j3hybX2C32 +qRe3H3I2TqYXP2WYktsqbl2i/ojgC95/5Y0V4evLOtXiEqITLdiOr18SPaAIBQi2XKVlOARFmR6j +YGB0xUGlcmIbYsUfb18aQr4CUWWoriMYavx4A6lNf4DD+qta/KFApMoZFv6yyO9ecw3ud72a9nmY +vLEHZ6IVDd2gWMZEewo+YihfukEHU1jPEX44dMX4/7VpkI+EdOqXG68CAQOjgcAwgb0wHQYDVR0O +BBYEFNLEsNKR1EwRcbNhyz2h/t2oatTjMIGNBgNVHSMEgYUwgYKAFNLEsNKR1EwRcbNhyz2h/t2o +atTjoWekZTBjMQswCQYDVQQGEwJVUzEhMB8GA1UEChMYVGhlIEdvIERhZGR5IEdyb3VwLCBJbmMu +MTEwLwYDVQQLEyhHbyBEYWRkeSBDbGFzcyAyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5ggEAMAwG +A1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBADJL87LKPpH8EsahB4yOd6AzBhRckB4Y9wim +PQoZ+YeAEW5p5JYXMP80kWNyOO7MHAGjHZQopDH2esRU1/blMVgDoszOYtuURXO1v0XJJLXVggKt +I3lpjbi2Tc7PTMozI+gciKqdi0FuFskg5YmezTvacPd+mSYgFFQlq25zheabIZ0KbIIOqPjCDPoQ +HmyW74cNxA9hi63ugyuV+I6ShHI56yDqg+2DzZduCLzrTia2cyvk0/ZM/iZx4mERdEr/VxqHD3VI +Ls9RaRegAhJhldXRQLIQTO7ErBBDpqWeCtWVYpoNz4iCxTIM5CufReYNnyicsbkqWletNw+vHX/b +vZ8= +-----END CERTIFICATE----- + +Starfield Class 2 CA +==================== +-----BEGIN CERTIFICATE----- +MIIEDzCCAvegAwIBAgIBADANBgkqhkiG9w0BAQUFADBoMQswCQYDVQQGEwJVUzElMCMGA1UEChMc +U3RhcmZpZWxkIFRlY2hub2xvZ2llcywgSW5jLjEyMDAGA1UECxMpU3RhcmZpZWxkIENsYXNzIDIg +Q2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDQwNjI5MTczOTE2WhcNMzQwNjI5MTczOTE2WjBo +MQswCQYDVQQGEwJVUzElMCMGA1UEChMcU3RhcmZpZWxkIFRlY2hub2xvZ2llcywgSW5jLjEyMDAG +A1UECxMpU3RhcmZpZWxkIENsYXNzIDIgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwggEgMA0GCSqG +SIb3DQEBAQUAA4IBDQAwggEIAoIBAQC3Msj+6XGmBIWtDBFk385N78gDGIc/oav7PKaf8MOh2tTY +bitTkPskpD6E8J7oX+zlJ0T1KKY/e97gKvDIr1MvnsoFAZMej2YcOadN+lq2cwQlZut3f+dZxkqZ +JRRU6ybH838Z1TBwj6+wRir/resp7defqgSHo9T5iaU0X9tDkYI22WY8sbi5gv2cOj4QyDvvBmVm +epsZGD3/cVE8MC5fvj13c7JdBmzDI1aaK4UmkhynArPkPw2vCHmCuDY96pzTNbO8acr1zJ3o/WSN +F4Azbl5KXZnJHoe0nRrA1W4TNSNe35tfPe/W93bC6j67eA0cQmdrBNj41tpvi/JEoAGrAgEDo4HF +MIHCMB0GA1UdDgQWBBS/X7fRzt0fhvRbVazc1xDCDqmI5zCBkgYDVR0jBIGKMIGHgBS/X7fRzt0f +hvRbVazc1xDCDqmI56FspGowaDELMAkGA1UEBhMCVVMxJTAjBgNVBAoTHFN0YXJmaWVsZCBUZWNo +bm9sb2dpZXMsIEluYy4xMjAwBgNVBAsTKVN0YXJmaWVsZCBDbGFzcyAyIENlcnRpZmljYXRpb24g +QXV0aG9yaXR5ggEAMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAAWdP4id0ckaVaGs +afPzWdqbAYcaT1epoXkJKtv3L7IezMdeatiDh6GX70k1PncGQVhiv45YuApnP+yz3SFmH8lU+nLM +PUxA2IGvd56Deruix/U0F47ZEUD0/CwqTRV/p2JdLiXTAAsgGh1o+Re49L2L7ShZ3U0WixeDyLJl +xy16paq8U4Zt3VekyvggQQto8PT7dL5WXXp59fkdheMtlb71cZBDzI0fmgAKhynpVSJYACPq4xJD +KVtHCN2MQWplBqjlIapBtJUhlbl90TSrE9atvNziPTnNvT51cKEYWQPJIrSPnNVeKtelttQKbfi3 +QBFGmh95DmK/D5fs4C8fF5Q= +-----END CERTIFICATE----- + +DigiCert Assured ID Root CA +=========================== +-----BEGIN CERTIFICATE----- +MIIDtzCCAp+gAwIBAgIQDOfg5RfYRv6P5WD8G/AwOTANBgkqhkiG9w0BAQUFADBlMQswCQYDVQQG +EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSQw +IgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgQ0EwHhcNMDYxMTEwMDAwMDAwWhcNMzEx +MTEwMDAwMDAwWjBlMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQL +ExB3d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgQ0Ew +ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCtDhXO5EOAXLGH87dg+XESpa7cJpSIqvTO +9SA5KFhgDPiA2qkVlTJhPLWxKISKityfCgyDF3qPkKyK53lTXDGEKvYPmDI2dsze3Tyoou9q+yHy +UmHfnyDXH+Kx2f4YZNISW1/5WBg1vEfNoTb5a3/UsDg+wRvDjDPZ2C8Y/igPs6eD1sNuRMBhNZYW +/lmci3Zt1/GiSw0r/wty2p5g0I6QNcZ4VYcgoc/lbQrISXwxmDNsIumH0DJaoroTghHtORedmTpy +oeb6pNnVFzF1roV9Iq4/AUaG9ih5yLHa5FcXxH4cDrC0kqZWs72yl+2qp/C3xag/lRbQ/6GW6whf +GHdPAgMBAAGjYzBhMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRF +66Kv9JLLgjEtUYunpyGd823IDzAfBgNVHSMEGDAWgBRF66Kv9JLLgjEtUYunpyGd823IDzANBgkq +hkiG9w0BAQUFAAOCAQEAog683+Lt8ONyc3pklL/3cmbYMuRCdWKuh+vy1dneVrOfzM4UKLkNl2Bc +EkxY5NM9g0lFWJc1aRqoR+pWxnmrEthngYTffwk8lOa4JiwgvT2zKIn3X/8i4peEH+ll74fg38Fn +SbNd67IJKusm7Xi+fT8r87cmNW1fiQG2SVufAQWbqz0lwcy2f8Lxb4bG+mRo64EtlOtCt/qMHt1i +8b5QZ7dsvfPxH2sMNgcWfzd8qVttevESRmCD1ycEvkvOl77DZypoEd+A5wwzZr8TDRRu838fYxAe ++o0bJW1sj6W3YQGx0qMmoRBxna3iw/nDmVG3KwcIzi7mULKn+gpFL6Lw8g== +-----END CERTIFICATE----- + +DigiCert Global Root CA +======================= +-----BEGIN CERTIFICATE----- +MIIDrzCCApegAwIBAgIQCDvgVpBCRrGhdWrJWZHHSjANBgkqhkiG9w0BAQUFADBhMQswCQYDVQQG +EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSAw +HgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBDQTAeFw0wNjExMTAwMDAwMDBaFw0zMTExMTAw +MDAwMDBaMGExCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3 +dy5kaWdpY2VydC5jb20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IENBMIIBIjANBgkq +hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4jvhEXLeqKTTo1eqUKKPC3eQyaKl7hLOllsBCSDMAZOn +TjC3U/dDxGkAV53ijSLdhwZAAIEJzs4bg7/fzTtxRuLWZscFs3YnFo97nh6Vfe63SKMI2tavegw5 +BmV/Sl0fvBf4q77uKNd0f3p4mVmFaG5cIzJLv07A6Fpt43C/dxC//AH2hdmoRBBYMql1GNXRor5H +4idq9Joz+EkIYIvUX7Q6hL+hqkpMfT7PT19sdl6gSzeRntwi5m3OFBqOasv+zbMUZBfHWymeMr/y +7vrTC0LUq7dBMtoM1O/4gdW7jVg/tRvoSSiicNoxBN33shbyTApOB6jtSj1etX+jkMOvJwIDAQAB +o2MwYTAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUA95QNVbRTLtm +8KPiGxvDl7I90VUwHwYDVR0jBBgwFoAUA95QNVbRTLtm8KPiGxvDl7I90VUwDQYJKoZIhvcNAQEF +BQADggEBAMucN6pIExIK+t1EnE9SsPTfrgT1eXkIoyQY/EsrhMAtudXH/vTBH1jLuG2cenTnmCmr +EbXjcKChzUyImZOMkXDiqw8cvpOp/2PV5Adg06O/nVsJ8dWO41P0jmP6P6fbtGbfYmbW0W5BjfIt +tep3Sp+dWOIrWcBAI+0tKIJFPnlUkiaY4IBIqDfv8NZ5YBberOgOzW6sRBc4L0na4UU+Krk2U886 +UAb3LujEV0lsYSEY1QSteDwsOoBrp+uvFRTp2InBuThs4pFsiv9kuXclVzDAGySj4dzp30d8tbQk +CAUw7C29C79Fv1C5qfPrmAESrciIxpg0X40KPMbp1ZWVbd4= +-----END CERTIFICATE----- + +DigiCert High Assurance EV Root CA +================================== +-----BEGIN CERTIFICATE----- +MIIDxTCCAq2gAwIBAgIQAqxcJmoLQJuPC3nyrkYldzANBgkqhkiG9w0BAQUFADBsMQswCQYDVQQG +EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSsw +KQYDVQQDEyJEaWdpQ2VydCBIaWdoIEFzc3VyYW5jZSBFViBSb290IENBMB4XDTA2MTExMDAwMDAw +MFoXDTMxMTExMDAwMDAwMFowbDELMAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZ +MBcGA1UECxMQd3d3LmRpZ2ljZXJ0LmNvbTErMCkGA1UEAxMiRGlnaUNlcnQgSGlnaCBBc3N1cmFu +Y2UgRVYgUm9vdCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMbM5XPm+9S75S0t +Mqbf5YE/yc0lSbZxKsPVlDRnogocsF9ppkCxxLeyj9CYpKlBWTrT3JTWPNt0OKRKzE0lgvdKpVMS +OO7zSW1xkX5jtqumX8OkhPhPYlG++MXs2ziS4wblCJEMxChBVfvLWokVfnHoNb9Ncgk9vjo4UFt3 +MRuNs8ckRZqnrG0AFFoEt7oT61EKmEFBIk5lYYeBQVCmeVyJ3hlKV9Uu5l0cUyx+mM0aBhakaHPQ +NAQTXKFx01p8VdteZOE3hzBWBOURtCmAEvF5OYiiAhF8J2a3iLd48soKqDirCmTCv2ZdlYTBoSUe +h10aUAsgEsxBu24LUTi4S8sCAwEAAaNjMGEwDgYDVR0PAQH/BAQDAgGGMA8GA1UdEwEB/wQFMAMB +Af8wHQYDVR0OBBYEFLE+w2kD+L9HAdSYJhoIAu9jZCvDMB8GA1UdIwQYMBaAFLE+w2kD+L9HAdSY +JhoIAu9jZCvDMA0GCSqGSIb3DQEBBQUAA4IBAQAcGgaX3NecnzyIZgYIVyHbIUf4KmeqvxgydkAQ +V8GK83rZEWWONfqe/EW1ntlMMUu4kehDLI6zeM7b41N5cdblIZQB2lWHmiRk9opmzN6cN82oNLFp +myPInngiK3BD41VHMWEZ71jFhS9OMPagMRYjyOfiZRYzy78aG6A9+MpeizGLYAiJLQwGXFK3xPkK +mNEVX58Svnw2Yzi9RKR/5CYrCsSXaQ3pjOLAEFe4yHYSkVXySGnYvCoCWw9E1CAx2/S6cCZdkGCe +vEsXCS+0yx5DaMkHJ8HSXPfqIbloEpw8nL+e/IBcm2PN7EeqJSdnoDfzAIJ9VNep+OkuE6N36B9K +-----END CERTIFICATE----- + +DST Root CA X3 +============== +-----BEGIN CERTIFICATE----- +MIIDSjCCAjKgAwIBAgIQRK+wgNajJ7qJMDmGLvhAazANBgkqhkiG9w0BAQUFADA/MSQwIgYDVQQK +ExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMTDkRTVCBSb290IENBIFgzMB4X +DTAwMDkzMDIxMTIxOVoXDTIxMDkzMDE0MDExNVowPzEkMCIGA1UEChMbRGlnaXRhbCBTaWduYXR1 +cmUgVHJ1c3QgQ28uMRcwFQYDVQQDEw5EU1QgUm9vdCBDQSBYMzCCASIwDQYJKoZIhvcNAQEBBQAD +ggEPADCCAQoCggEBAN+v6ZdQCINXtMxiZfaQguzH0yxrMMpb7NnDfcdAwRgUi+DoM3ZJKuM/IUmT +rE4Orz5Iy2Xu/NMhD2XSKtkyj4zl93ewEnu1lcCJo6m67XMuegwGMoOifooUMM0RoOEqOLl5CjH9 +UL2AZd+3UWODyOKIYepLYYHsUmu5ouJLGiifSKOeDNoJjj4XLh7dIN9bxiqKqy69cK3FCxolkHRy +xXtqqzTWMIn/5WgTe1QLyNau7Fqckh49ZLOMxt+/yUFw7BZy1SbsOFU5Q9D8/RhcQPGX69Wam40d +utolucbY38EVAjqr2m7xPi71XAicPNaDaeQQmxkqtilX4+U9m5/wAl0CAwEAAaNCMEAwDwYDVR0T +AQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFMSnsaR7LHH62+FLkHX/xBVghYkQ +MA0GCSqGSIb3DQEBBQUAA4IBAQCjGiybFwBcqR7uKGY3Or+Dxz9LwwmglSBd49lZRNI+DT69ikug +dB/OEIKcdBodfpga3csTS7MgROSR6cz8faXbauX+5v3gTt23ADq1cEmv8uXrAvHRAosZy5Q6XkjE +GB5YGV8eAlrwDPGxrancWYaLbumR9YbK+rlmM6pZW87ipxZzR8srzJmwN0jP41ZL9c8PDHIyh8bw +RLtTcm1D9SZImlJnt1ir/md2cXjbDaJWFBM5JDGFoqgCWjBH4d1QB7wCCZAA62RjYJsWvIjJEubS +fZGL+T0yjWW06XyxV3bqxbYoOb8VZRzI9neWagqNdwvYkQsEjgfbKbYK7p2CNTUQ +-----END CERTIFICATE----- + +SwissSign Gold CA - G2 +====================== +-----BEGIN CERTIFICATE----- +MIIFujCCA6KgAwIBAgIJALtAHEP1Xk+wMA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNVBAYTAkNIMRUw +EwYDVQQKEwxTd2lzc1NpZ24gQUcxHzAdBgNVBAMTFlN3aXNzU2lnbiBHb2xkIENBIC0gRzIwHhcN +MDYxMDI1MDgzMDM1WhcNMzYxMDI1MDgzMDM1WjBFMQswCQYDVQQGEwJDSDEVMBMGA1UEChMMU3dp +c3NTaWduIEFHMR8wHQYDVQQDExZTd2lzc1NpZ24gR29sZCBDQSAtIEcyMIICIjANBgkqhkiG9w0B +AQEFAAOCAg8AMIICCgKCAgEAr+TufoskDhJuqVAtFkQ7kpJcyrhdhJJCEyq8ZVeCQD5XJM1QiyUq +t2/876LQwB8CJEoTlo8jE+YoWACjR8cGp4QjK7u9lit/VcyLwVcfDmJlD909Vopz2q5+bbqBHH5C +jCA12UNNhPqE21Is8w4ndwtrvxEvcnifLtg+5hg3Wipy+dpikJKVyh+c6bM8K8vzARO/Ws/BtQpg +vd21mWRTuKCWs2/iJneRjOBiEAKfNA+k1ZIzUd6+jbqEemA8atufK+ze3gE/bk3lUIbLtK/tREDF +ylqM2tIrfKjuvqblCqoOpd8FUrdVxyJdMmqXl2MT28nbeTZ7hTpKxVKJ+STnnXepgv9VHKVxaSvR +AiTysybUa9oEVeXBCsdtMDeQKuSeFDNeFhdVxVu1yzSJkvGdJo+hB9TGsnhQ2wwMC3wLjEHXuend +jIj3o02yMszYF9rNt85mndT9Xv+9lz4pded+p2JYryU0pUHHPbwNUMoDAw8IWh+Vc3hiv69yFGkO +peUDDniOJihC8AcLYiAQZzlG+qkDzAQ4embvIIO1jEpWjpEA/I5cgt6IoMPiaG59je883WX0XaxR +7ySArqpWl2/5rX3aYT+YdzylkbYcjCbaZaIJbcHiVOO5ykxMgI93e2CaHt+28kgeDrpOVG2Y4OGi +GqJ3UM/EY5LsRxmd6+ZrzsECAwEAAaOBrDCBqTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUw +AwEB/zAdBgNVHQ4EFgQUWyV7lqRlUX64OfPAeGZe6Drn8O4wHwYDVR0jBBgwFoAUWyV7lqRlUX64 +OfPAeGZe6Drn8O4wRgYDVR0gBD8wPTA7BglghXQBWQECAQEwLjAsBggrBgEFBQcCARYgaHR0cDov +L3JlcG9zaXRvcnkuc3dpc3NzaWduLmNvbS8wDQYJKoZIhvcNAQEFBQADggIBACe645R88a7A3hfm +5djV9VSwg/S7zV4Fe0+fdWavPOhWfvxyeDgD2StiGwC5+OlgzczOUYrHUDFu4Up+GC9pWbY9ZIEr +44OE5iKHjn3g7gKZYbge9LgriBIWhMIxkziWMaa5O1M/wySTVltpkuzFwbs4AOPsF6m43Md8AYOf +Mke6UiI0HTJ6CVanfCU2qT1L2sCCbwq7EsiHSycR+R4tx5M/nttfJmtS2S6K8RTGRI0Vqbe/vd6m +Gu6uLftIdxf+u+yvGPUqUfA5hJeVbG4bwyvEdGB5JbAKJ9/fXtI5z0V9QkvfsywexcZdylU6oJxp +mo/a77KwPJ+HbBIrZXAVUjEaJM9vMSNQH4xPjyPDdEFjHFWoFN0+4FFQz/EbMFYOkrCChdiDyyJk +vC24JdVUorgG6q2SpCSgwYa1ShNqR88uC1aVVMvOmttqtKay20EIhid392qgQmwLOM7XdVAyksLf +KzAiSNDVQTglXaTpXZ/GlHXQRf0wl0OPkKsKx4ZzYEppLd6leNcG2mqeSz53OiATIgHQv2ieY2Br +NU0LbbqhPcCT4H8js1WtciVORvnSFu+wZMEBnunKoGqYDs/YYPIvSbjkQuE4NRb0yG5P94FW6Lqj +viOvrv1vA+ACOzB2+httQc8Bsem4yWb02ybzOqR08kkkW8mw0FfB+j564ZfJ +-----END CERTIFICATE----- + +SwissSign Silver CA - G2 +======================== +-----BEGIN CERTIFICATE----- +MIIFvTCCA6WgAwIBAgIITxvUL1S7L0swDQYJKoZIhvcNAQEFBQAwRzELMAkGA1UEBhMCQ0gxFTAT +BgNVBAoTDFN3aXNzU2lnbiBBRzEhMB8GA1UEAxMYU3dpc3NTaWduIFNpbHZlciBDQSAtIEcyMB4X +DTA2MTAyNTA4MzI0NloXDTM2MTAyNTA4MzI0NlowRzELMAkGA1UEBhMCQ0gxFTATBgNVBAoTDFN3 +aXNzU2lnbiBBRzEhMB8GA1UEAxMYU3dpc3NTaWduIFNpbHZlciBDQSAtIEcyMIICIjANBgkqhkiG +9w0BAQEFAAOCAg8AMIICCgKCAgEAxPGHf9N4Mfc4yfjDmUO8x/e8N+dOcbpLj6VzHVxumK4DV644 +N0MvFz0fyM5oEMF4rhkDKxD6LHmD9ui5aLlV8gREpzn5/ASLHvGiTSf5YXu6t+WiE7brYT7QbNHm ++/pe7R20nqA1W6GSy/BJkv6FCgU+5tkL4k+73JU3/JHpMjUi0R86TieFnbAVlDLaYQ1HTWBCrpJH +6INaUFjpiou5XaHc3ZlKHzZnu0jkg7Y360g6rw9njxcH6ATK72oxh9TAtvmUcXtnZLi2kUpCe2Uu +MGoM9ZDulebyzYLs2aFK7PayS+VFheZteJMELpyCbTapxDFkH4aDCyr0NQp4yVXPQbBH6TCfmb5h +qAaEuSh6XzjZG6k4sIN/c8HDO0gqgg8hm7jMqDXDhBuDsz6+pJVpATqJAHgE2cn0mRmrVn5bi4Y5 +FZGkECwJMoBgs5PAKrYYC51+jUnyEEp/+dVGLxmSo5mnJqy7jDzmDrxHB9xzUfFwZC8I+bRHHTBs +ROopN4WSaGa8gzj+ezku01DwH/teYLappvonQfGbGHLy9YR0SslnxFSuSGTfjNFusB3hB48IHpmc +celM2KX3RxIfdNFRnobzwqIjQAtz20um53MGjMGg6cFZrEb65i/4z3GcRm25xBWNOHkDRUjvxF3X +CO6HOSKGsg0PWEP3calILv3q1h8CAwEAAaOBrDCBqTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/ +BAUwAwEB/zAdBgNVHQ4EFgQUF6DNweRBtjpbO8tFnb0cwpj6hlgwHwYDVR0jBBgwFoAUF6DNweRB +tjpbO8tFnb0cwpj6hlgwRgYDVR0gBD8wPTA7BglghXQBWQEDAQEwLjAsBggrBgEFBQcCARYgaHR0 +cDovL3JlcG9zaXRvcnkuc3dpc3NzaWduLmNvbS8wDQYJKoZIhvcNAQEFBQADggIBAHPGgeAn0i0P +4JUw4ppBf1AsX19iYamGamkYDHRJ1l2E6kFSGG9YrVBWIGrGvShpWJHckRE1qTodvBqlYJ7YH39F +kWnZfrt4csEGDyrOj4VwYaygzQu4OSlWhDJOhrs9xCrZ1x9y7v5RoSJBsXECYxqCsGKrXlcSH9/L +3XWgwF15kIwb4FDm3jH+mHtwX6WQ2K34ArZv02DdQEsixT2tOnqfGhpHkXkzuoLcMmkDlm4fS/Bx +/uNncqCxv1yL5PqZIseEuRuNI5c/7SXgz2W79WEE790eslpBIlqhn10s6FvJbakMDHiqYMZWjwFa +DGi8aRl5xB9+lwW/xekkUV7U1UtT7dkjWjYDZaPBA61BMPNGG4WQr2W11bHkFlt4dR2Xem1ZqSqP +e97Dh4kQmUlzeMg9vVE1dCrV8X5pGyq7O70luJpaPXJhkGaH7gzWTdQRdAtq/gsD/KNVV4n+Ssuu +WxcFyPKNIzFTONItaj+CuY0IavdeQXRuwxF+B6wpYJE/OMpXEA29MC/HpeZBoNquBYeaoKRlbEwJ +DIm6uNO5wJOKMPqN5ZprFQFOZ6raYlY+hAhm0sQ2fac+EPyI4NSA5QC9qvNOBqN6avlicuMJT+ub +DgEj8Z+7fNzcbBGXJbLytGMU0gYqZ4yD9c7qB9iaah7s5Aq7KkzrCWA5zspi2C5u +-----END CERTIFICATE----- + +SecureTrust CA +============== +-----BEGIN CERTIFICATE----- +MIIDuDCCAqCgAwIBAgIQDPCOXAgWpa1Cf/DrJxhZ0DANBgkqhkiG9w0BAQUFADBIMQswCQYDVQQG +EwJVUzEgMB4GA1UEChMXU2VjdXJlVHJ1c3QgQ29ycG9yYXRpb24xFzAVBgNVBAMTDlNlY3VyZVRy +dXN0IENBMB4XDTA2MTEwNzE5MzExOFoXDTI5MTIzMTE5NDA1NVowSDELMAkGA1UEBhMCVVMxIDAe +BgNVBAoTF1NlY3VyZVRydXN0IENvcnBvcmF0aW9uMRcwFQYDVQQDEw5TZWN1cmVUcnVzdCBDQTCC +ASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKukgeWVzfX2FI7CT8rU4niVWJxB4Q2ZQCQX +OZEzZum+4YOvYlyJ0fwkW2Gz4BERQRwdbvC4u/jep4G6pkjGnx29vo6pQT64lO0pGtSO0gMdA+9t +DWccV9cGrcrI9f4Or2YlSASWC12juhbDCE/RRvgUXPLIXgGZbf2IzIaowW8xQmxSPmjL8xk037uH +GFaAJsTQ3MBv396gwpEWoGQRS0S8Hvbn+mPeZqx2pHGj7DaUaHp3pLHnDi+BeuK1cobvomuL8A/b +01k/unK8RCSc43Oz969XL0Imnal0ugBS8kvNU3xHCzaFDmapCJcWNFfBZveA4+1wVMeT4C4oFVmH +ursCAwEAAaOBnTCBmjATBgkrBgEEAYI3FAIEBh4EAEMAQTALBgNVHQ8EBAMCAYYwDwYDVR0TAQH/ +BAUwAwEB/zAdBgNVHQ4EFgQUQjK2FvoE/f5dS3rD/fdMQB1aQ68wNAYDVR0fBC0wKzApoCegJYYj +aHR0cDovL2NybC5zZWN1cmV0cnVzdC5jb20vU1RDQS5jcmwwEAYJKwYBBAGCNxUBBAMCAQAwDQYJ +KoZIhvcNAQEFBQADggEBADDtT0rhWDpSclu1pqNlGKa7UTt36Z3q059c4EVlew3KW+JwULKUBRSu +SceNQQcSc5R+DCMh/bwQf2AQWnL1mA6s7Ll/3XpvXdMc9P+IBWlCqQVxyLesJugutIxq/3HcuLHf +mbx8IVQr5Fiiu1cprp6poxkmD5kuCLDv/WnPmRoJjeOnnyvJNjR7JLN4TJUXpAYmHrZkUjZfYGfZ +nMUFdAvnZyPSCPyI6a6Lf+Ew9Dd+/cYy2i2eRDAwbO4H3tI0/NL/QPZL9GZGBlSm8jIKYyYwa5vR +3ItHuuG51WLQoqD0ZwV4KWMabwTW+MZMo5qxN7SN5ShLHZ4swrhovO0C7jE= +-----END CERTIFICATE----- + +Secure Global CA +================ +-----BEGIN CERTIFICATE----- +MIIDvDCCAqSgAwIBAgIQB1YipOjUiolN9BPI8PjqpTANBgkqhkiG9w0BAQUFADBKMQswCQYDVQQG +EwJVUzEgMB4GA1UEChMXU2VjdXJlVHJ1c3QgQ29ycG9yYXRpb24xGTAXBgNVBAMTEFNlY3VyZSBH +bG9iYWwgQ0EwHhcNMDYxMTA3MTk0MjI4WhcNMjkxMjMxMTk1MjA2WjBKMQswCQYDVQQGEwJVUzEg +MB4GA1UEChMXU2VjdXJlVHJ1c3QgQ29ycG9yYXRpb24xGTAXBgNVBAMTEFNlY3VyZSBHbG9iYWwg +Q0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCvNS7YrGxVaQZx5RNoJLNP2MwhR/jx +YDiJiQPpvepeRlMJ3Fz1Wuj3RSoC6zFh1ykzTM7HfAo3fg+6MpjhHZevj8fcyTiW89sa/FHtaMbQ +bqR8JNGuQsiWUGMu4P51/pinX0kuleM5M2SOHqRfkNJnPLLZ/kG5VacJjnIFHovdRIWCQtBJwB1g +8NEXLJXr9qXBkqPFwqcIYA1gBBCWeZ4WNOaptvolRTnIHmX5k/Wq8VLcmZg9pYYaDDUz+kulBAYV +HDGA76oYa8J719rO+TMg1fW9ajMtgQT7sFzUnKPiXB3jqUJ1XnvUd+85VLrJChgbEplJL4hL/VBi +0XPnj3pDAgMBAAGjgZ0wgZowEwYJKwYBBAGCNxQCBAYeBABDAEEwCwYDVR0PBAQDAgGGMA8GA1Ud +EwEB/wQFMAMBAf8wHQYDVR0OBBYEFK9EBMJBfkiD2045AuzshHrmzsmkMDQGA1UdHwQtMCswKaAn +oCWGI2h0dHA6Ly9jcmwuc2VjdXJldHJ1c3QuY29tL1NHQ0EuY3JsMBAGCSsGAQQBgjcVAQQDAgEA +MA0GCSqGSIb3DQEBBQUAA4IBAQBjGghAfaReUw132HquHw0LURYD7xh8yOOvaliTFGCRsoTciE6+ +OYo68+aCiV0BN7OrJKQVDpI1WkpEXk5X+nXOH0jOZvQ8QCaSmGwb7iRGDBezUqXbpZGRzzfTb+cn +CDpOGR86p1hcF895P4vkp9MmI50mD1hp/Ed+stCNi5O/KU9DaXR2Z0vPB4zmAve14bRDtUstFJ/5 +3CYNv6ZHdAbYiNE6KTCEztI5gGIbqMdXSbxqVVFnFUq+NQfk1XWYN3kwFNspnWzFacxHVaIw98xc +f8LDmBxrThaA63p4ZUWiABqvDA1VZDRIuJK58bRQKfJPIx/abKwfROHdI3hRW8cW +-----END CERTIFICATE----- + +COMODO Certification Authority +============================== +-----BEGIN CERTIFICATE----- +MIIEHTCCAwWgAwIBAgIQToEtioJl4AsC7j41AkblPTANBgkqhkiG9w0BAQUFADCBgTELMAkGA1UE +BhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgG +A1UEChMRQ09NT0RPIENBIExpbWl0ZWQxJzAlBgNVBAMTHkNPTU9ETyBDZXJ0aWZpY2F0aW9uIEF1 +dGhvcml0eTAeFw0wNjEyMDEwMDAwMDBaFw0yOTEyMzEyMzU5NTlaMIGBMQswCQYDVQQGEwJHQjEb +MBkGA1UECBMSR3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHEwdTYWxmb3JkMRowGAYDVQQKExFD +T01PRE8gQ0EgTGltaXRlZDEnMCUGA1UEAxMeQ09NT0RPIENlcnRpZmljYXRpb24gQXV0aG9yaXR5 +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0ECLi3LjkRv3UcEbVASY06m/weaKXTuH ++7uIzg3jLz8GlvCiKVCZrts7oVewdFFxze1CkU1B/qnI2GqGd0S7WWaXUF601CxwRM/aN5VCaTww +xHGzUvAhTaHYujl8HJ6jJJ3ygxaYqhZ8Q5sVW7euNJH+1GImGEaaP+vB+fGQV+useg2L23IwambV +4EajcNxo2f8ESIl33rXp+2dtQem8Ob0y2WIC8bGoPW43nOIv4tOiJovGuFVDiOEjPqXSJDlqR6sA +1KGzqSX+DT+nHbrTUcELpNqsOO9VUCQFZUaTNE8tja3G1CEZ0o7KBWFxB3NH5YoZEr0ETc5OnKVI +rLsm9wIDAQABo4GOMIGLMB0GA1UdDgQWBBQLWOWLxkwVN6RAqTCpIb5HNlpW/zAOBgNVHQ8BAf8E +BAMCAQYwDwYDVR0TAQH/BAUwAwEB/zBJBgNVHR8EQjBAMD6gPKA6hjhodHRwOi8vY3JsLmNvbW9k +b2NhLmNvbS9DT01PRE9DZXJ0aWZpY2F0aW9uQXV0aG9yaXR5LmNybDANBgkqhkiG9w0BAQUFAAOC +AQEAPpiem/Yb6dc5t3iuHXIYSdOH5EOC6z/JqvWote9VfCFSZfnVDeFs9D6Mk3ORLgLETgdxb8CP +OGEIqB6BCsAvIC9Bi5HcSEW88cbeunZrM8gALTFGTO3nnc+IlP8zwFboJIYmuNg4ON8qa90SzMc/ +RxdMosIGlgnW2/4/PEZB31jiVg88O8EckzXZOFKs7sjsLjBOlDW0JB9LeGna8gI4zJVSk/BwJVmc +IGfE7vmLV2H0knZ9P4SNVbfo5azV8fUZVqZa+5Acr5Pr5RzUZ5ddBA6+C4OmF4O5MBKgxTMVBbkN ++8cFduPYSo38NBejxiEovjBFMR7HeL5YYTisO+IBZQ== +-----END CERTIFICATE----- + +Network Solutions Certificate Authority +======================================= +-----BEGIN CERTIFICATE----- +MIID5jCCAs6gAwIBAgIQV8szb8JcFuZHFhfjkDFo4DANBgkqhkiG9w0BAQUFADBiMQswCQYDVQQG +EwJVUzEhMB8GA1UEChMYTmV0d29yayBTb2x1dGlvbnMgTC5MLkMuMTAwLgYDVQQDEydOZXR3b3Jr +IFNvbHV0aW9ucyBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkwHhcNMDYxMjAxMDAwMDAwWhcNMjkxMjMx +MjM1OTU5WjBiMQswCQYDVQQGEwJVUzEhMB8GA1UEChMYTmV0d29yayBTb2x1dGlvbnMgTC5MLkMu +MTAwLgYDVQQDEydOZXR3b3JrIFNvbHV0aW9ucyBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkwggEiMA0G +CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDkvH6SMG3G2I4rC7xGzuAnlt7e+foS0zwzc7MEL7xx +jOWftiJgPl9dzgn/ggwbmlFQGiaJ3dVhXRncEg8tCqJDXRfQNJIg6nPPOCwGJgl6cvf6UDL4wpPT +aaIjzkGxzOTVHzbRijr4jGPiFFlp7Q3Tf2vouAPlT2rlmGNpSAW+Lv8ztumXWWn4Zxmuk2GWRBXT +crA/vGp97Eh/jcOrqnErU2lBUzS1sLnFBgrEsEX1QV1uiUV7PTsmjHTC5dLRfbIR1PtYMiKagMnc +/Qzpf14Dl847ABSHJ3A4qY5usyd2mFHgBeMhqxrVhSI8KbWaFsWAqPS7azCPL0YCorEMIuDTAgMB +AAGjgZcwgZQwHQYDVR0OBBYEFCEwyfsA106Y2oeqKtCnLrFAMadMMA4GA1UdDwEB/wQEAwIBBjAP +BgNVHRMBAf8EBTADAQH/MFIGA1UdHwRLMEkwR6BFoEOGQWh0dHA6Ly9jcmwubmV0c29sc3NsLmNv +bS9OZXR3b3JrU29sdXRpb25zQ2VydGlmaWNhdGVBdXRob3JpdHkuY3JsMA0GCSqGSIb3DQEBBQUA +A4IBAQC7rkvnt1frf6ott3NHhWrB5KUd5Oc86fRZZXe1eltajSU24HqXLjjAV2CDmAaDn7l2em5Q +4LqILPxFzBiwmZVRDuwduIj/h1AcgsLj4DKAv6ALR8jDMe+ZZzKATxcheQxpXN5eNK4CtSbqUN9/ +GGUsyfJj4akH/nxxH2szJGoeBfcFaMBqEssuXmHLrijTfsK0ZpEmXzwuJF/LWA/rKOyvEZbz3Htv +wKeI8lN3s2Berq4o2jUsbzRF0ybh3uxbTydrFny9RAQYgrOJeRcQcT16ohZO9QHNpGxlaKFJdlxD +ydi8NmdspZS11My5vWo1ViHe2MPr+8ukYEywVaCge1ey +-----END CERTIFICATE----- + +COMODO ECC Certification Authority +================================== +-----BEGIN CERTIFICATE----- +MIICiTCCAg+gAwIBAgIQH0evqmIAcFBUTAGem2OZKjAKBggqhkjOPQQDAzCBhTELMAkGA1UEBhMC +R0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgGA1UE +ChMRQ09NT0RPIENBIExpbWl0ZWQxKzApBgNVBAMTIkNPTU9ETyBFQ0MgQ2VydGlmaWNhdGlvbiBB +dXRob3JpdHkwHhcNMDgwMzA2MDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBhTELMAkGA1UEBhMCR0Ix +GzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgGA1UEChMR +Q09NT0RPIENBIExpbWl0ZWQxKzApBgNVBAMTIkNPTU9ETyBFQ0MgQ2VydGlmaWNhdGlvbiBBdXRo +b3JpdHkwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQDR3svdcmCFYX7deSRFtSrYpn1PlILBs5BAH+X +4QokPB0BBO490o0JlwzgdeT6+3eKKvUDYEs2ixYjFq0JcfRK9ChQtP6IHG4/bC8vCVlbpVsLM5ni +wz2J+Wos77LTBumjQjBAMB0GA1UdDgQWBBR1cacZSBm8nZ3qQUfflMRId5nTeTAOBgNVHQ8BAf8E +BAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAwNoADBlAjEA7wNbeqy3eApyt4jf/7VG +FAkK+qDmfQjGGoe9GKhzvSbKYAydzpmfz1wPMOG+FDHqAjAU9JM8SaczepBGR7NjfRObTrdvGDeA +U/7dIOA1mjbRxwG55tzd8/8dLDoWV9mSOdY= +-----END CERTIFICATE----- + +Certigna +======== +-----BEGIN CERTIFICATE----- +MIIDqDCCApCgAwIBAgIJAP7c4wEPyUj/MA0GCSqGSIb3DQEBBQUAMDQxCzAJBgNVBAYTAkZSMRIw +EAYDVQQKDAlEaGlteW90aXMxETAPBgNVBAMMCENlcnRpZ25hMB4XDTA3MDYyOTE1MTMwNVoXDTI3 +MDYyOTE1MTMwNVowNDELMAkGA1UEBhMCRlIxEjAQBgNVBAoMCURoaW15b3RpczERMA8GA1UEAwwI +Q2VydGlnbmEwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDIaPHJ1tazNHUmgh7stL7q +XOEm7RFHYeGifBZ4QCHkYJ5ayGPhxLGWkv8YbWkj4Sti993iNi+RB7lIzw7sebYs5zRLcAglozyH +GxnygQcPOJAZ0xH+hrTy0V4eHpbNgGzOOzGTtvKg0KmVEn2lmsxryIRWijOp5yIVUxbwzBfsV1/p +ogqYCd7jX5xv3EjjhQsVWqa6n6xI4wmy9/Qy3l40vhx4XUJbzg4ij02Q130yGLMLLGq/jj8UEYkg +DncUtT2UCIf3JR7VsmAA7G8qKCVuKj4YYxclPz5EIBb2JsglrgVKtOdjLPOMFlN+XPsRGgjBRmKf +Irjxwo1p3Po6WAbfAgMBAAGjgbwwgbkwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUGu3+QTmQ +tCRZvgHyUtVF9lo53BEwZAYDVR0jBF0wW4AUGu3+QTmQtCRZvgHyUtVF9lo53BGhOKQ2MDQxCzAJ +BgNVBAYTAkZSMRIwEAYDVQQKDAlEaGlteW90aXMxETAPBgNVBAMMCENlcnRpZ25hggkA/tzjAQ/J +SP8wDgYDVR0PAQH/BAQDAgEGMBEGCWCGSAGG+EIBAQQEAwIABzANBgkqhkiG9w0BAQUFAAOCAQEA +hQMeknH2Qq/ho2Ge6/PAD/Kl1NqV5ta+aDY9fm4fTIrv0Q8hbV6lUmPOEvjvKtpv6zf+EwLHyzs+ +ImvaYS5/1HI93TDhHkxAGYwP15zRgzB7mFncfca5DClMoTOi62c6ZYTTluLtdkVwj7Ur3vkj1klu +PBS1xp81HlDQwY9qcEQCYsuuHWhBp6pX6FOqB9IG9tUUBguRA3UsbHK1YZWaDYu5Def131TN3ubY +1gkIl2PlwS6wt0QmwCbAr1UwnjvVNioZBPRcHv/PLLf/0P2HQBHVESO7SMAhqaQoLf0V+LBOK/Qw +WyH8EZE0vkHve52Xdf+XlcCWWC/qu0bXu+TZLg== +-----END CERTIFICATE----- + +Cybertrust Global Root +====================== +-----BEGIN CERTIFICATE----- +MIIDoTCCAomgAwIBAgILBAAAAAABD4WqLUgwDQYJKoZIhvcNAQEFBQAwOzEYMBYGA1UEChMPQ3li +ZXJ0cnVzdCwgSW5jMR8wHQYDVQQDExZDeWJlcnRydXN0IEdsb2JhbCBSb290MB4XDTA2MTIxNTA4 +MDAwMFoXDTIxMTIxNTA4MDAwMFowOzEYMBYGA1UEChMPQ3liZXJ0cnVzdCwgSW5jMR8wHQYDVQQD +ExZDeWJlcnRydXN0IEdsb2JhbCBSb290MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA ++Mi8vRRQZhP/8NN57CPytxrHjoXxEnOmGaoQ25yiZXRadz5RfVb23CO21O1fWLE3TdVJDm71aofW +0ozSJ8bi/zafmGWgE07GKmSb1ZASzxQG9Dvj1Ci+6A74q05IlG2OlTEQXO2iLb3VOm2yHLtgwEZL +AfVJrn5GitB0jaEMAs7u/OePuGtm839EAL9mJRQr3RAwHQeWP032a7iPt3sMpTjr3kfb1V05/Iin +89cqdPHoWqI7n1C6poxFNcJQZZXcY4Lv3b93TZxiyWNzFtApD0mpSPCzqrdsxacwOUBdrsTiXSZT +8M4cIwhhqJQZugRiQOwfOHB3EgZxpzAYXSUnpQIDAQABo4GlMIGiMA4GA1UdDwEB/wQEAwIBBjAP +BgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBS2CHsNesysIEyGVjJez6tuhS1wVzA/BgNVHR8EODA2 +MDSgMqAwhi5odHRwOi8vd3d3Mi5wdWJsaWMtdHJ1c3QuY29tL2NybC9jdC9jdHJvb3QuY3JsMB8G +A1UdIwQYMBaAFLYIew16zKwgTIZWMl7Pq26FLXBXMA0GCSqGSIb3DQEBBQUAA4IBAQBW7wojoFRO +lZfJ+InaRcHUowAl9B8Tq7ejhVhpwjCt2BWKLePJzYFa+HMjWqd8BfP9IjsO0QbE2zZMcwSO5bAi +5MXzLqXZI+O4Tkogp24CJJ8iYGd7ix1yCcUxXOl5n4BHPa2hCwcUPUf/A2kaDAtE52Mlp3+yybh2 +hO0j9n0Hq0V+09+zv+mKts2oomcrUtW3ZfA5TGOgkXmTUg9U3YO7n9GPp1Nzw8v/MOx8BLjYRB+T +X3EJIrduPuocA06dGiBh+4E37F78CkWr1+cXVdCg6mCbpvbjjFspwgZgFJ0tl0ypkxWdYcQBX0jW +WL1WMRJOEcgh4LMRkWXbtKaIOM5V +-----END CERTIFICATE----- + +ePKI Root Certification Authority +================================= +-----BEGIN CERTIFICATE----- +MIIFsDCCA5igAwIBAgIQFci9ZUdcr7iXAF7kBtK8nTANBgkqhkiG9w0BAQUFADBeMQswCQYDVQQG +EwJUVzEjMCEGA1UECgwaQ2h1bmdod2EgVGVsZWNvbSBDby4sIEx0ZC4xKjAoBgNVBAsMIWVQS0kg +Um9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wNDEyMjAwMjMxMjdaFw0zNDEyMjAwMjMx +MjdaMF4xCzAJBgNVBAYTAlRXMSMwIQYDVQQKDBpDaHVuZ2h3YSBUZWxlY29tIENvLiwgTHRkLjEq +MCgGA1UECwwhZVBLSSBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIICIjANBgkqhkiG9w0B +AQEFAAOCAg8AMIICCgKCAgEA4SUP7o3biDN1Z82tH306Tm2d0y8U82N0ywEhajfqhFAHSyZbCUNs +IZ5qyNUD9WBpj8zwIuQf5/dqIjG3LBXy4P4AakP/h2XGtRrBp0xtInAhijHyl3SJCRImHJ7K2RKi +lTza6We/CKBk49ZCt0Xvl/T29de1ShUCWH2YWEtgvM3XDZoTM1PRYfl61dd4s5oz9wCGzh1NlDiv +qOx4UXCKXBCDUSH3ET00hl7lSM2XgYI1TBnsZfZrxQWh7kcT1rMhJ5QQCtkkO7q+RBNGMD+XPNjX +12ruOzjjK9SXDrkb5wdJfzcq+Xd4z1TtW0ado4AOkUPB1ltfFLqfpo0kR0BZv3I4sjZsN/+Z0V0O +WQqraffAsgRFelQArr5T9rXn4fg8ozHSqf4hUmTFpmfwdQcGlBSBVcYn5AGPF8Fqcde+S/uUWH1+ +ETOxQvdibBjWzwloPn9s9h6PYq2lY9sJpx8iQkEeb5mKPtf5P0B6ebClAZLSnT0IFaUQAS2zMnao +lQ2zepr7BxB4EW/hj8e6DyUadCrlHJhBmd8hh+iVBmoKs2pHdmX2Os+PYhcZewoozRrSgx4hxyy/ +vv9haLdnG7t4TY3OZ+XkwY63I2binZB1NJipNiuKmpS5nezMirH4JYlcWrYvjB9teSSnUmjDhDXi +Zo1jDiVN1Rmy5nk3pyKdVDECAwEAAaNqMGgwHQYDVR0OBBYEFB4M97Zn8uGSJglFwFU5Lnc/Qkqi +MAwGA1UdEwQFMAMBAf8wOQYEZyoHAAQxMC8wLQIBADAJBgUrDgMCGgUAMAcGBWcqAwAABBRFsMLH +ClZ87lt4DJX5GFPBphzYEDANBgkqhkiG9w0BAQUFAAOCAgEACbODU1kBPpVJufGBuvl2ICO1J2B0 +1GqZNF5sAFPZn/KmsSQHRGoqxqWOeBLoR9lYGxMqXnmbnwoqZ6YlPwZpVnPDimZI+ymBV3QGypzq +KOg4ZyYr8dW1P2WT+DZdjo2NQCCHGervJ8A9tDkPJXtoUHRVnAxZfVo9QZQlUgjgRywVMRnVvwdV +xrsStZf0X4OFunHB2WyBEXYKCrC/gpf36j36+uwtqSiUO1bd0lEursC9CBWMd1I0ltabrNMdjmEP +NXubrjlpC2JgQCA2j6/7Nu4tCEoduL+bXPjqpRugc6bY+G7gMwRfaKonh+3ZwZCc7b3jajWvY9+r +GNm65ulK6lCKD2GTHuItGeIwlDWSXQ62B68ZgI9HkFFLLk3dheLSClIKF5r8GrBQAuUBo2M3IUxE +xJtRmREOc5wGj1QupyheRDmHVi03vYVElOEMSyycw5KFNGHLD7ibSkNS/jQ6fbjpKdx2qcgw+BRx +gMYeNkh0IkFch4LoGHGLQYlE535YW6i4jRPpp2zDR+2zGp1iro2C6pSe3VkQw63d4k3jMdXH7Ojy +sP6SHhYKGvzZ8/gntsm+HbRsZJB/9OTEW9c3rkIO3aQab3yIVMUWbuF6aC74Or8NpDyJO3inTmOD +BCEIZ43ygknQW/2xzQ+DhNQ+IIX3Sj0rnP0qCglN6oH4EZw= +-----END CERTIFICATE----- + +certSIGN ROOT CA +================ +-----BEGIN CERTIFICATE----- +MIIDODCCAiCgAwIBAgIGIAYFFnACMA0GCSqGSIb3DQEBBQUAMDsxCzAJBgNVBAYTAlJPMREwDwYD +VQQKEwhjZXJ0U0lHTjEZMBcGA1UECxMQY2VydFNJR04gUk9PVCBDQTAeFw0wNjA3MDQxNzIwMDRa +Fw0zMTA3MDQxNzIwMDRaMDsxCzAJBgNVBAYTAlJPMREwDwYDVQQKEwhjZXJ0U0lHTjEZMBcGA1UE +CxMQY2VydFNJR04gUk9PVCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALczuX7I +JUqOtdu0KBuqV5Do0SLTZLrTk+jUrIZhQGpgV2hUhE28alQCBf/fm5oqrl0Hj0rDKH/v+yv6efHH +rfAQUySQi2bJqIirr1qjAOm+ukbuW3N7LBeCgV5iLKECZbO9xSsAfsT8AzNXDe3i+s5dRdY4zTW2 +ssHQnIFKquSyAVwdj1+ZxLGt24gh65AIgoDzMKND5pCCrlUoSe1b16kQOA7+j0xbm0bqQfWwCHTD +0IgztnzXdN/chNFDDnU5oSVAKOp4yw4sLjmdjItuFhwvJoIQ4uNllAoEwF73XVv4EOLQunpL+943 +AAAaWyjj0pxzPjKHmKHJUS/X3qwzs08CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8B +Af8EBAMCAcYwHQYDVR0OBBYEFOCMm9slSbPxfIbWskKHC9BroNnkMA0GCSqGSIb3DQEBBQUAA4IB +AQA+0hyJLjX8+HXd5n9liPRyTMks1zJO890ZeUe9jjtbkw9QSSQTaxQGcu8J06Gh40CEyecYMnQ8 +SG4Pn0vU9x7Tk4ZkVJdjclDVVc/6IJMCopvDI5NOFlV2oHB5bc0hH88vLbwZ44gx+FkagQnIl6Z0 +x2DEW8xXjrJ1/RsCCdtZb3KTafcxQdaIOL+Hsr0Wefmq5L6IJd1hJyMctTEHBDa0GpC9oHRxUIlt +vBTjD4au8as+x6AJzKNI0eDbZOeStc+vckNwi/nDhDwTqn6Sm1dTk/pwwpEOMfmbZ13pljheX7Nz +TogVZ96edhBiIL5VaZVDADlN9u6wWk5JRFRYX0KD +-----END CERTIFICATE----- + +NetLock Arany (Class Gold) Főtanúsítvány +======================================== +-----BEGIN CERTIFICATE----- +MIIEFTCCAv2gAwIBAgIGSUEs5AAQMA0GCSqGSIb3DQEBCwUAMIGnMQswCQYDVQQGEwJIVTERMA8G +A1UEBwwIQnVkYXBlc3QxFTATBgNVBAoMDE5ldExvY2sgS2Z0LjE3MDUGA1UECwwuVGFuw7pzw610 +dsOhbnlraWFkw7NrIChDZXJ0aWZpY2F0aW9uIFNlcnZpY2VzKTE1MDMGA1UEAwwsTmV0TG9jayBB +cmFueSAoQ2xhc3MgR29sZCkgRsWRdGFuw7pzw610dsOhbnkwHhcNMDgxMjExMTUwODIxWhcNMjgx +MjA2MTUwODIxWjCBpzELMAkGA1UEBhMCSFUxETAPBgNVBAcMCEJ1ZGFwZXN0MRUwEwYDVQQKDAxO +ZXRMb2NrIEtmdC4xNzA1BgNVBAsMLlRhbsO6c8OtdHbDoW55a2lhZMOzayAoQ2VydGlmaWNhdGlv +biBTZXJ2aWNlcykxNTAzBgNVBAMMLE5ldExvY2sgQXJhbnkgKENsYXNzIEdvbGQpIEbFkXRhbsO6 +c8OtdHbDoW55MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxCRec75LbRTDofTjl5Bu +0jBFHjzuZ9lk4BqKf8owyoPjIMHj9DrTlF8afFttvzBPhCf2nx9JvMaZCpDyD/V/Q4Q3Y1GLeqVw +/HpYzY6b7cNGbIRwXdrzAZAj/E4wqX7hJ2Pn7WQ8oLjJM2P+FpD/sLj916jAwJRDC7bVWaaeVtAk +H3B5r9s5VA1lddkVQZQBr17s9o3x/61k/iCa11zr/qYfCGSji3ZVrR47KGAuhyXoqq8fxmRGILdw +fzzeSNuWU7c5d+Qa4scWhHaXWy+7GRWF+GmF9ZmnqfI0p6m2pgP8b4Y9VHx2BJtr+UBdADTHLpl1 +neWIA6pN+APSQnbAGwIDAKiLo0UwQzASBgNVHRMBAf8ECDAGAQH/AgEEMA4GA1UdDwEB/wQEAwIB +BjAdBgNVHQ4EFgQUzPpnk/C2uNClwB7zU/2MU9+D15YwDQYJKoZIhvcNAQELBQADggEBAKt/7hwW +qZw8UQCgwBEIBaeZ5m8BiFRhbvG5GK1Krf6BQCOUL/t1fC8oS2IkgYIL9WHxHG64YTjrgfpioTta +YtOUZcTh5m2C+C8lcLIhJsFyUR+MLMOEkMNaj7rP9KdlpeuY0fsFskZ1FSNqb4VjMIDw1Z4fKRzC +bLBQWV2QWzuoDTDPv31/zvGdg73JRm4gpvlhUbohL3u+pRVjodSVh/GeufOJ8z2FuLjbvrW5Kfna +NwUASZQDhETnv0Mxz3WLJdH0pmT1kvarBes96aULNmLazAZfNou2XjG4Kvte9nHfRCaexOYNkbQu +dZWAUWpLMKawYqGT8ZvYzsRjdT9ZR7E= +-----END CERTIFICATE----- + +Hongkong Post Root CA 1 +======================= +-----BEGIN CERTIFICATE----- +MIIDMDCCAhigAwIBAgICA+gwDQYJKoZIhvcNAQEFBQAwRzELMAkGA1UEBhMCSEsxFjAUBgNVBAoT +DUhvbmdrb25nIFBvc3QxIDAeBgNVBAMTF0hvbmdrb25nIFBvc3QgUm9vdCBDQSAxMB4XDTAzMDUx +NTA1MTMxNFoXDTIzMDUxNTA0NTIyOVowRzELMAkGA1UEBhMCSEsxFjAUBgNVBAoTDUhvbmdrb25n +IFBvc3QxIDAeBgNVBAMTF0hvbmdrb25nIFBvc3QgUm9vdCBDQSAxMIIBIjANBgkqhkiG9w0BAQEF +AAOCAQ8AMIIBCgKCAQEArP84tulmAknjorThkPlAj3n54r15/gK97iSSHSL22oVyaf7XPwnU3ZG1 +ApzQjVrhVcNQhrkpJsLj2aDxaQMoIIBFIi1WpztUlVYiWR8o3x8gPW2iNr4joLFutbEnPzlTCeqr +auh0ssJlXI6/fMN4hM2eFvz1Lk8gKgifd/PFHsSaUmYeSF7jEAaPIpjhZY4bXSNmO7ilMlHIhqqh +qZ5/dpTCpmy3QfDVyAY45tQM4vM7TG1QjMSDJ8EThFk9nnV0ttgCXjqQesBCNnLsak3c78QA3xMY +V18meMjWCnl3v/evt3a5pQuEF10Q6m/hq5URX208o1xNg1vysxmKgIsLhwIDAQABoyYwJDASBgNV +HRMBAf8ECDAGAQH/AgEDMA4GA1UdDwEB/wQEAwIBxjANBgkqhkiG9w0BAQUFAAOCAQEADkbVPK7i +h9legYsCmEEIjEy82tvuJxuC52pF7BaLT4Wg87JwvVqWuspube5Gi27nKi6Wsxkz67SfqLI37pio +l7Yutmcn1KZJ/RyTZXaeQi/cImyaT/JaFTmxcdcrUehtHJjA2Sr0oYJ71clBoiMBdDhViw+5Lmei +IAQ32pwL0xch4I+XeTRvhEgCIDMb5jREn5Fw9IBehEPCKdJsEhTkYY2sEJCehFC78JZvRZ+K88ps +T/oROhUVRsPNH4NbLUES7VBnQRM9IauUiqpOfMGx+6fWtScvl6tu4B3i0RwsH0Ti/L6RoZz71ilT +c4afU9hDDl3WY4JxHYB0yvbiAmvZWg== +-----END CERTIFICATE----- + +SecureSign RootCA11 +=================== +-----BEGIN CERTIFICATE----- +MIIDbTCCAlWgAwIBAgIBATANBgkqhkiG9w0BAQUFADBYMQswCQYDVQQGEwJKUDErMCkGA1UEChMi +SmFwYW4gQ2VydGlmaWNhdGlvbiBTZXJ2aWNlcywgSW5jLjEcMBoGA1UEAxMTU2VjdXJlU2lnbiBS +b290Q0ExMTAeFw0wOTA0MDgwNDU2NDdaFw0yOTA0MDgwNDU2NDdaMFgxCzAJBgNVBAYTAkpQMSsw +KQYDVQQKEyJKYXBhbiBDZXJ0aWZpY2F0aW9uIFNlcnZpY2VzLCBJbmMuMRwwGgYDVQQDExNTZWN1 +cmVTaWduIFJvb3RDQTExMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA/XeqpRyQBTvL +TJszi1oURaTnkBbR31fSIRCkF/3frNYfp+TbfPfs37gD2pRY/V1yfIw/XwFndBWW4wI8h9uuywGO +wvNmxoVF9ALGOrVisq/6nL+k5tSAMJjzDbaTj6nU2DbysPyKyiyhFTOVMdrAG/LuYpmGYz+/3ZMq +g6h2uRMft85OQoWPIucuGvKVCbIFtUROd6EgvanyTgp9UK31BQ1FT0Zx/Sg+U/sE2C3XZR1KG/rP +O7AxmjVuyIsG0wCR8pQIZUyxNAYAeoni8McDWc/V1uinMrPmmECGxc0nEovMe863ETxiYAcjPitA +bpSACW22s293bzUIUPsCh8U+iQIDAQABo0IwQDAdBgNVHQ4EFgQUW/hNT7KlhtQ60vFjmqC+CfZX +t94wDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAKCh +OBZmLqdWHyGcBvod7bkixTgm2E5P7KN/ed5GIaGHd48HCJqypMWvDzKYC3xmKbabfSVSSUOrTC4r +bnpwrxYO4wJs+0LmGJ1F2FXI6Dvd5+H0LgscNFxsWEr7jIhQX5Ucv+2rIrVls4W6ng+4reV6G4pQ +Oh29Dbx7VFALuUKvVaAYga1lme++5Jy/xIWrQbJUb9wlze144o4MjQlJ3WN7WmmWAiGovVJZ6X01 +y8hSyn+B/tlr0/cR7SXf+Of5pPpyl4RTDaXQMhhRdlkUbA/r7F+AjHVDg8OFmP9Mni0N5HeDk061 +lgeLKBObjBmNQSdJQO7e5iNEOdyhIta6A/I= +-----END CERTIFICATE----- + +Microsec e-Szigno Root CA 2009 +============================== +-----BEGIN CERTIFICATE----- +MIIECjCCAvKgAwIBAgIJAMJ+QwRORz8ZMA0GCSqGSIb3DQEBCwUAMIGCMQswCQYDVQQGEwJIVTER +MA8GA1UEBwwIQnVkYXBlc3QxFjAUBgNVBAoMDU1pY3Jvc2VjIEx0ZC4xJzAlBgNVBAMMHk1pY3Jv +c2VjIGUtU3ppZ25vIFJvb3QgQ0EgMjAwOTEfMB0GCSqGSIb3DQEJARYQaW5mb0BlLXN6aWduby5o +dTAeFw0wOTA2MTYxMTMwMThaFw0yOTEyMzAxMTMwMThaMIGCMQswCQYDVQQGEwJIVTERMA8GA1UE +BwwIQnVkYXBlc3QxFjAUBgNVBAoMDU1pY3Jvc2VjIEx0ZC4xJzAlBgNVBAMMHk1pY3Jvc2VjIGUt +U3ppZ25vIFJvb3QgQ0EgMjAwOTEfMB0GCSqGSIb3DQEJARYQaW5mb0BlLXN6aWduby5odTCCASIw +DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAOn4j/NjrdqG2KfgQvvPkd6mJviZpWNwrZuuyjNA +fW2WbqEORO7hE52UQlKavXWFdCyoDh2Tthi3jCyoz/tccbna7P7ofo/kLx2yqHWH2Leh5TvPmUpG +0IMZfcChEhyVbUr02MelTTMuhTlAdX4UfIASmFDHQWe4oIBhVKZsTh/gnQ4H6cm6M+f+wFUoLAKA +pxn1ntxVUwOXewdI/5n7N4okxFnMUBBjjqqpGrCEGob5X7uxUG6k0QrM1XF+H6cbfPVTbiJfyyvm +1HxdrtbCxkzlBQHZ7Vf8wSN5/PrIJIOV87VqUQHQd9bpEqH5GoP7ghu5sJf0dgYzQ0mg/wu1+rUC +AwEAAaOBgDB+MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBTLD8bf +QkPMPcu1SCOhGnqmKrs0aDAfBgNVHSMEGDAWgBTLD8bfQkPMPcu1SCOhGnqmKrs0aDAbBgNVHREE +FDASgRBpbmZvQGUtc3ppZ25vLmh1MA0GCSqGSIb3DQEBCwUAA4IBAQDJ0Q5eLtXMs3w+y/w9/w0o +lZMEyL/azXm4Q5DwpL7v8u8hmLzU1F0G9u5C7DBsoKqpyvGvivo/C3NqPuouQH4frlRheesuCDfX +I/OMn74dseGkddug4lQUsbocKaQY9hK6ohQU4zE1yED/t+AFdlfBHFny+L/k7SViXITwfn4fs775 +tyERzAMBVnCnEJIeGzSBHq2cGsMEPO0CYdYeBvNfOofyK/FFh+U9rNHHV4S9a67c2Pm2G2JwCz02 +yULyMtd6YebS2z3PyKnJm9zbWETXbzivf3jTo60adbocwTZ8jx5tHMN1Rq41Bab2XD0h7lbwyYIi +LXpUq3DDfSJlgnCW +-----END CERTIFICATE----- + +GlobalSign Root CA - R3 +======================= +-----BEGIN CERTIFICATE----- +MIIDXzCCAkegAwIBAgILBAAAAAABIVhTCKIwDQYJKoZIhvcNAQELBQAwTDEgMB4GA1UECxMXR2xv +YmFsU2lnbiBSb290IENBIC0gUjMxEzARBgNVBAoTCkdsb2JhbFNpZ24xEzARBgNVBAMTCkdsb2Jh +bFNpZ24wHhcNMDkwMzE4MTAwMDAwWhcNMjkwMzE4MTAwMDAwWjBMMSAwHgYDVQQLExdHbG9iYWxT +aWduIFJvb3QgQ0EgLSBSMzETMBEGA1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2ln +bjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMwldpB5BngiFvXAg7aEyiie/QV2EcWt +iHL8RgJDx7KKnQRfJMsuS+FggkbhUqsMgUdwbN1k0ev1LKMPgj0MK66X17YUhhB5uzsTgHeMCOFJ +0mpiLx9e+pZo34knlTifBtc+ycsmWQ1z3rDI6SYOgxXG71uL0gRgykmmKPZpO/bLyCiR5Z2KYVc3 +rHQU3HTgOu5yLy6c+9C7v/U9AOEGM+iCK65TpjoWc4zdQQ4gOsC0p6Hpsk+QLjJg6VfLuQSSaGjl +OCZgdbKfd/+RFO+uIEn8rUAVSNECMWEZXriX7613t2Saer9fwRPvm2L7DWzgVGkWqQPabumDk3F2 +xmmFghcCAwEAAaNCMEAwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYE +FI/wS3+oLkUkrk1Q+mOai97i3Ru8MA0GCSqGSIb3DQEBCwUAA4IBAQBLQNvAUKr+yAzv95ZURUm7 +lgAJQayzE4aGKAczymvmdLm6AC2upArT9fHxD4q/c2dKg8dEe3jgr25sbwMpjjM5RcOO5LlXbKr8 +EpbsU8Yt5CRsuZRj+9xTaGdWPoO4zzUhw8lo/s7awlOqzJCK6fBdRoyV3XpYKBovHd7NADdBj+1E +bddTKJd+82cEHhXXipa0095MJ6RMG3NzdvQXmcIfeg7jLQitChws/zyrVQ4PkX4268NXSb7hLi18 +YIvDQVETI53O9zJrlAGomecsMx86OyXShkDOOyyGeMlhLxS67ttVb9+E7gUJTb0o2HLO02JQZR7r +kpeDMdmztcpHWD9f +-----END CERTIFICATE----- + +Autoridad de Certificacion Firmaprofesional CIF A62634068 +========================================================= +-----BEGIN CERTIFICATE----- +MIIGFDCCA/ygAwIBAgIIU+w77vuySF8wDQYJKoZIhvcNAQEFBQAwUTELMAkGA1UEBhMCRVMxQjBA +BgNVBAMMOUF1dG9yaWRhZCBkZSBDZXJ0aWZpY2FjaW9uIEZpcm1hcHJvZmVzaW9uYWwgQ0lGIEE2 +MjYzNDA2ODAeFw0wOTA1MjAwODM4MTVaFw0zMDEyMzEwODM4MTVaMFExCzAJBgNVBAYTAkVTMUIw +QAYDVQQDDDlBdXRvcmlkYWQgZGUgQ2VydGlmaWNhY2lvbiBGaXJtYXByb2Zlc2lvbmFsIENJRiBB +NjI2MzQwNjgwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDKlmuO6vj78aI14H9M2uDD +Utd9thDIAl6zQyrET2qyyhxdKJp4ERppWVevtSBC5IsP5t9bpgOSL/UR5GLXMnE42QQMcas9UX4P +B99jBVzpv5RvwSmCwLTaUbDBPLutN0pcyvFLNg4kq7/DhHf9qFD0sefGL9ItWY16Ck6WaVICqjaY +7Pz6FIMMNx/Jkjd/14Et5cS54D40/mf0PmbR0/RAz15iNA9wBj4gGFrO93IbJWyTdBSTo3OxDqqH +ECNZXyAFGUftaI6SEspd/NYrspI8IM/hX68gvqB2f3bl7BqGYTM+53u0P6APjqK5am+5hyZvQWyI +plD9amML9ZMWGxmPsu2bm8mQ9QEM3xk9Dz44I8kvjwzRAv4bVdZO0I08r0+k8/6vKtMFnXkIoctX +MbScyJCyZ/QYFpM6/EfY0XiWMR+6KwxfXZmtY4laJCB22N/9q06mIqqdXuYnin1oKaPnirjaEbsX +LZmdEyRG98Xi2J+Of8ePdG1asuhy9azuJBCtLxTa/y2aRnFHvkLfuwHb9H/TKI8xWVvTyQKmtFLK +bpf7Q8UIJm+K9Lv9nyiqDdVF8xM6HdjAeI9BZzwelGSuewvF6NkBiDkal4ZkQdU7hwxu+g/GvUgU +vzlN1J5Bto+WHWOWk9mVBngxaJ43BjuAiUVhOSPHG0SjFeUc+JIwuwIDAQABo4HvMIHsMBIGA1Ud +EwEB/wQIMAYBAf8CAQEwDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBRlzeurNR4APn7VdMActHNH +DhpkLzCBpgYDVR0gBIGeMIGbMIGYBgRVHSAAMIGPMC8GCCsGAQUFBwIBFiNodHRwOi8vd3d3LmZp +cm1hcHJvZmVzaW9uYWwuY29tL2NwczBcBggrBgEFBQcCAjBQHk4AUABhAHMAZQBvACAAZABlACAA +bABhACAAQgBvAG4AYQBuAG8AdgBhACAANAA3ACAAQgBhAHIAYwBlAGwAbwBuAGEAIAAwADgAMAAx +ADcwDQYJKoZIhvcNAQEFBQADggIBABd9oPm03cXF661LJLWhAqvdpYhKsg9VSytXjDvlMd3+xDLx +51tkljYyGOylMnfX40S2wBEqgLk9am58m9Ot/MPWo+ZkKXzR4Tgegiv/J2Wv+xYVxC5xhOW1//qk +R71kMrv2JYSiJ0L1ILDCExARzRAVukKQKtJE4ZYm6zFIEv0q2skGz3QeqUvVhyj5eTSSPi5E6PaP +T481PyWzOdxjKpBrIF/EUhJOlywqrJ2X3kjyo2bbwtKDlaZmp54lD+kLM5FlClrD2VQS3a/DTg4f +Jl4N3LON7NWBcN7STyQF82xO9UxJZo3R/9ILJUFI/lGExkKvgATP0H5kSeTy36LssUzAKh3ntLFl +osS88Zj0qnAHY7S42jtM+kAiMFsRpvAFDsYCA0irhpuF3dvd6qJ2gHN99ZwExEWN57kci57q13XR +crHedUTnQn3iV2t93Jm8PYMo6oCTjcVMZcFwgbg4/EMxsvYDNEeyrPsiBsse3RdHHF9mudMaotoR +saS8I8nkvof/uZS2+F0gStRf571oe2XyFR7SOqkt6dhrJKyXWERHrVkY8SFlcN7ONGCoQPHzPKTD +KCOM/iczQ0CgFzzr6juwcqajuUpLXhZI9LK8yIySxZ2frHI2vDSANGupi5LAuBft7HZT9SQBjLMi +6Et8Vcad+qMUu2WFbm5PEn4KPJ2V +-----END CERTIFICATE----- + +Izenpe.com +========== +-----BEGIN CERTIFICATE----- +MIIF8TCCA9mgAwIBAgIQALC3WhZIX7/hy/WL1xnmfTANBgkqhkiG9w0BAQsFADA4MQswCQYDVQQG +EwJFUzEUMBIGA1UECgwLSVpFTlBFIFMuQS4xEzARBgNVBAMMCkl6ZW5wZS5jb20wHhcNMDcxMjEz +MTMwODI4WhcNMzcxMjEzMDgyNzI1WjA4MQswCQYDVQQGEwJFUzEUMBIGA1UECgwLSVpFTlBFIFMu +QS4xEzARBgNVBAMMCkl6ZW5wZS5jb20wggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDJ +03rKDx6sp4boFmVqscIbRTJxldn+EFvMr+eleQGPicPK8lVx93e+d5TzcqQsRNiekpsUOqHnJJAK +ClaOxdgmlOHZSOEtPtoKct2jmRXagaKH9HtuJneJWK3W6wyyQXpzbm3benhB6QiIEn6HLmYRY2xU ++zydcsC8Lv/Ct90NduM61/e0aL6i9eOBbsFGb12N4E3GVFWJGjMxCrFXuaOKmMPsOzTFlUFpfnXC +PCDFYbpRR6AgkJOhkEvzTnyFRVSa0QUmQbC1TR0zvsQDyCV8wXDbO/QJLVQnSKwv4cSsPsjLkkxT +OTcj7NMB+eAJRE1NZMDhDVqHIrytG6P+JrUV86f8hBnp7KGItERphIPzidF0BqnMC9bC3ieFUCbK +F7jJeodWLBoBHmy+E60QrLUk9TiRodZL2vG70t5HtfG8gfZZa88ZU+mNFctKy6lvROUbQc/hhqfK +0GqfvEyNBjNaooXlkDWgYlwWTvDjovoDGrQscbNYLN57C9saD+veIR8GdwYDsMnvmfzAuU8Lhij+ +0rnq49qlw0dpEuDb8PYZi+17cNcC1u2HGCgsBCRMd+RIihrGO5rUD8r6ddIBQFqNeb+Lz0vPqhbB +leStTIo+F5HUsWLlguWABKQDfo2/2n+iD5dPDNMN+9fR5XJ+HMh3/1uaD7euBUbl8agW7EekFwID +AQABo4H2MIHzMIGwBgNVHREEgagwgaWBD2luZm9AaXplbnBlLmNvbaSBkTCBjjFHMEUGA1UECgw+ +SVpFTlBFIFMuQS4gLSBDSUYgQTAxMzM3MjYwLVJNZXJjLlZpdG9yaWEtR2FzdGVpeiBUMTA1NSBG +NjIgUzgxQzBBBgNVBAkMOkF2ZGEgZGVsIE1lZGl0ZXJyYW5lbyBFdG9yYmlkZWEgMTQgLSAwMTAx +MCBWaXRvcmlhLUdhc3RlaXowDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0O +BBYEFB0cZQ6o8iV7tJHP5LGx5r1VdGwFMA0GCSqGSIb3DQEBCwUAA4ICAQB4pgwWSp9MiDrAyw6l +Fn2fuUhfGI8NYjb2zRlrrKvV9pF9rnHzP7MOeIWblaQnIUdCSnxIOvVFfLMMjlF4rJUT3sb9fbga +kEyrkgPH7UIBzg/YsfqikuFgba56awmqxinuaElnMIAkejEWOVt+8Rwu3WwJrfIxwYJOubv5vr8q +hT/AQKM6WfxZSzwoJNu0FXWuDYi6LnPAvViH5ULy617uHjAimcs30cQhbIHsvm0m5hzkQiCeR7Cs +g1lwLDXWrzY0tM07+DKo7+N4ifuNRSzanLh+QBxh5z6ikixL8s36mLYp//Pye6kfLqCTVyvehQP5 +aTfLnnhqBbTFMXiJ7HqnheG5ezzevh55hM6fcA5ZwjUukCox2eRFekGkLhObNA5me0mrZJfQRsN5 +nXJQY6aYWwa9SG3YOYNw6DXwBdGqvOPbyALqfP2C2sJbUjWumDqtujWTI6cfSN01RpiyEGjkpTHC +ClguGYEQyVB1/OpaFs4R1+7vUIgtYf8/QnMFlEPVjjxOAToZpR9GTnfQXeWBIiGH/pR9hNiTrdZo +Q0iy2+tzJOeRf1SktoA+naM8THLCV8Sg1Mw4J87VBp6iSNnpn86CcDaTmjvfliHjWbcM2pE38P1Z +WrOZyGlsQyYBNWNgVYkDOnXYukrZVP/u3oDYLdE41V4tC5h9Pmzb/CaIxw== +-----END CERTIFICATE----- + +Go Daddy Root Certificate Authority - G2 +======================================== +-----BEGIN CERTIFICATE----- +MIIDxTCCAq2gAwIBAgIBADANBgkqhkiG9w0BAQsFADCBgzELMAkGA1UEBhMCVVMxEDAOBgNVBAgT +B0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxGjAYBgNVBAoTEUdvRGFkZHkuY29tLCBJbmMu +MTEwLwYDVQQDEyhHbyBEYWRkeSBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eSAtIEcyMB4XDTA5 +MDkwMTAwMDAwMFoXDTM3MTIzMTIzNTk1OVowgYMxCzAJBgNVBAYTAlVTMRAwDgYDVQQIEwdBcml6 +b25hMRMwEQYDVQQHEwpTY290dHNkYWxlMRowGAYDVQQKExFHb0RhZGR5LmNvbSwgSW5jLjExMC8G +A1UEAxMoR28gRGFkZHkgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgLSBHMjCCASIwDQYJKoZI +hvcNAQEBBQADggEPADCCAQoCggEBAL9xYgjx+lk09xvJGKP3gElY6SKDE6bFIEMBO4Tx5oVJnyfq +9oQbTqC023CYxzIBsQU+B07u9PpPL1kwIuerGVZr4oAH/PMWdYA5UXvl+TW2dE6pjYIT5LY/qQOD ++qK+ihVqf94Lw7YZFAXK6sOoBJQ7RnwyDfMAZiLIjWltNowRGLfTshxgtDj6AozO091GB94KPutd +fMh8+7ArU6SSYmlRJQVhGkSBjCypQ5Yj36w6gZoOKcUcqeldHraenjAKOc7xiID7S13MMuyFYkMl +NAJWJwGRtDtwKj9useiciAF9n9T521NtYJ2/LOdYq7hfRvzOxBsDPAnrSTFcaUaz4EcCAwEAAaNC +MEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFDqahQcQZyi27/a9 +BUFuIMGU2g/eMA0GCSqGSIb3DQEBCwUAA4IBAQCZ21151fmXWWcDYfF+OwYxdS2hII5PZYe096ac +vNjpL9DbWu7PdIxztDhC2gV7+AJ1uP2lsdeu9tfeE8tTEH6KRtGX+rcuKxGrkLAngPnon1rpN5+r +5N9ss4UXnT3ZJE95kTXWXwTrgIOrmgIttRD02JDHBHNA7XIloKmf7J6raBKZV8aPEjoJpL1E/QYV +N8Gb5DKj7Tjo2GTzLH4U/ALqn83/B2gX2yKQOC16jdFU8WnjXzPKej17CuPKf1855eJ1usV2GDPO +LPAvTK33sefOT6jEm0pUBsV/fdUID+Ic/n4XuKxe9tQWskMJDE32p2u0mYRlynqI4uJEvlz36hz1 +-----END CERTIFICATE----- + +Starfield Root Certificate Authority - G2 +========================================= +-----BEGIN CERTIFICATE----- +MIID3TCCAsWgAwIBAgIBADANBgkqhkiG9w0BAQsFADCBjzELMAkGA1UEBhMCVVMxEDAOBgNVBAgT +B0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxJTAjBgNVBAoTHFN0YXJmaWVsZCBUZWNobm9s +b2dpZXMsIEluYy4xMjAwBgNVBAMTKVN0YXJmaWVsZCBSb290IENlcnRpZmljYXRlIEF1dGhvcml0 +eSAtIEcyMB4XDTA5MDkwMTAwMDAwMFoXDTM3MTIzMTIzNTk1OVowgY8xCzAJBgNVBAYTAlVTMRAw +DgYDVQQIEwdBcml6b25hMRMwEQYDVQQHEwpTY290dHNkYWxlMSUwIwYDVQQKExxTdGFyZmllbGQg +VGVjaG5vbG9naWVzLCBJbmMuMTIwMAYDVQQDEylTdGFyZmllbGQgUm9vdCBDZXJ0aWZpY2F0ZSBB +dXRob3JpdHkgLSBHMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAL3twQP89o/8ArFv +W59I2Z154qK3A2FWGMNHttfKPTUuiUP3oWmb3ooa/RMgnLRJdzIpVv257IzdIvpy3Cdhl+72WoTs +bhm5iSzchFvVdPtrX8WJpRBSiUZV9Lh1HOZ/5FSuS/hVclcCGfgXcVnrHigHdMWdSL5stPSksPNk +N3mSwOxGXn/hbVNMYq/NHwtjuzqd+/x5AJhhdM8mgkBj87JyahkNmcrUDnXMN/uLicFZ8WJ/X7Nf +ZTD4p7dNdloedl40wOiWVpmKs/B/pM293DIxfJHP4F8R+GuqSVzRmZTRouNjWwl2tVZi4Ut0HZbU +JtQIBFnQmA4O5t78w+wfkPECAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC +AQYwHQYDVR0OBBYEFHwMMh+n2TB/xH1oo2Kooc6rB1snMA0GCSqGSIb3DQEBCwUAA4IBAQARWfol +TwNvlJk7mh+ChTnUdgWUXuEok21iXQnCoKjUsHU48TRqneSfioYmUeYs0cYtbpUgSpIB7LiKZ3sx +4mcujJUDJi5DnUox9g61DLu34jd/IroAow57UvtruzvE03lRTs2Q9GcHGcg8RnoNAX3FWOdt5oUw +F5okxBDgBPfg8n/Uqgr/Qh037ZTlZFkSIHc40zI+OIF1lnP6aI+xy84fxez6nH7PfrHxBy22/L/K +pL/QlwVKvOoYKAKQvVR4CSFx09F9HdkWsKlhPdAKACL8x3vLCWRFCztAgfd9fDL1mMpYjn0q7pBZ +c2T5NnReJaH1ZgUufzkVqSr7UIuOhWn0 +-----END CERTIFICATE----- + +Starfield Services Root Certificate Authority - G2 +================================================== +-----BEGIN CERTIFICATE----- +MIID7zCCAtegAwIBAgIBADANBgkqhkiG9w0BAQsFADCBmDELMAkGA1UEBhMCVVMxEDAOBgNVBAgT +B0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxJTAjBgNVBAoTHFN0YXJmaWVsZCBUZWNobm9s +b2dpZXMsIEluYy4xOzA5BgNVBAMTMlN0YXJmaWVsZCBTZXJ2aWNlcyBSb290IENlcnRpZmljYXRl +IEF1dGhvcml0eSAtIEcyMB4XDTA5MDkwMTAwMDAwMFoXDTM3MTIzMTIzNTk1OVowgZgxCzAJBgNV +BAYTAlVTMRAwDgYDVQQIEwdBcml6b25hMRMwEQYDVQQHEwpTY290dHNkYWxlMSUwIwYDVQQKExxT +dGFyZmllbGQgVGVjaG5vbG9naWVzLCBJbmMuMTswOQYDVQQDEzJTdGFyZmllbGQgU2VydmljZXMg +Um9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgLSBHMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC +AQoCggEBANUMOsQq+U7i9b4Zl1+OiFOxHz/Lz58gE20pOsgPfTz3a3Y4Y9k2YKibXlwAgLIvWX/2 +h/klQ4bnaRtSmpDhcePYLQ1Ob/bISdm28xpWriu2dBTrz/sm4xq6HZYuajtYlIlHVv8loJNwU4Pa +hHQUw2eeBGg6345AWh1KTs9DkTvnVtYAcMtS7nt9rjrnvDH5RfbCYM8TWQIrgMw0R9+53pBlbQLP +LJGmpufehRhJfGZOozptqbXuNC66DQO4M99H67FrjSXZm86B0UVGMpZwh94CDklDhbZsc7tk6mFB +rMnUVN+HL8cisibMn1lUaJ/8viovxFUcdUBgF4UCVTmLfwUCAwEAAaNCMEAwDwYDVR0TAQH/BAUw +AwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFJxfAN+qAdcwKziIorhtSpzyEZGDMA0GCSqG +SIb3DQEBCwUAA4IBAQBLNqaEd2ndOxmfZyMIbw5hyf2E3F/YNoHN2BtBLZ9g3ccaaNnRbobhiCPP +E95Dz+I0swSdHynVv/heyNXBve6SbzJ08pGCL72CQnqtKrcgfU28elUSwhXqvfdqlS5sdJ/PHLTy +xQGjhdByPq1zqwubdQxtRbeOlKyWN7Wg0I8VRw7j6IPdj/3vQQF3zCepYoUz8jcI73HPdwbeyBkd +iEDPfUYd/x7H4c7/I9vG+o1VTqkC50cRRj70/b17KSa7qWFiNyi2LSr2EIZkyXCn0q23KXB56jza +YyWf/Wi3MOxw+3WKt21gZ7IeyLnp2KhvAotnDU0mV3HaIPzBSlCNsSi6 +-----END CERTIFICATE----- + +AffirmTrust Commercial +====================== +-----BEGIN CERTIFICATE----- +MIIDTDCCAjSgAwIBAgIId3cGJyapsXwwDQYJKoZIhvcNAQELBQAwRDELMAkGA1UEBhMCVVMxFDAS +BgNVBAoMC0FmZmlybVRydXN0MR8wHQYDVQQDDBZBZmZpcm1UcnVzdCBDb21tZXJjaWFsMB4XDTEw +MDEyOTE0MDYwNloXDTMwMTIzMTE0MDYwNlowRDELMAkGA1UEBhMCVVMxFDASBgNVBAoMC0FmZmly +bVRydXN0MR8wHQYDVQQDDBZBZmZpcm1UcnVzdCBDb21tZXJjaWFsMIIBIjANBgkqhkiG9w0BAQEF +AAOCAQ8AMIIBCgKCAQEA9htPZwcroRX1BiLLHwGy43NFBkRJLLtJJRTWzsO3qyxPxkEylFf6Eqdb +DuKPHx6GGaeqtS25Xw2Kwq+FNXkyLbscYjfysVtKPcrNcV/pQr6U6Mje+SJIZMblq8Yrba0F8PrV +C8+a5fBQpIs7R6UjW3p6+DM/uO+Zl+MgwdYoic+U+7lF7eNAFxHUdPALMeIrJmqbTFeurCA+ukV6 +BfO9m2kVrn1OIGPENXY6BwLJN/3HR+7o8XYdcxXyl6S1yHp52UKqK39c/s4mT6NmgTWvRLpUHhww +MmWd5jyTXlBOeuM61G7MGvv50jeuJCqrVwMiKA1JdX+3KNp1v47j3A55MQIDAQABo0IwQDAdBgNV +HQ4EFgQUnZPGU4teyq8/nx4P5ZmVvCT2lI8wDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC +AQYwDQYJKoZIhvcNAQELBQADggEBAFis9AQOzcAN/wr91LoWXym9e2iZWEnStB03TX8nfUYGXUPG +hi4+c7ImfU+TqbbEKpqrIZcUsd6M06uJFdhrJNTxFq7YpFzUf1GO7RgBsZNjvbz4YYCanrHOQnDi +qX0GJX0nof5v7LMeJNrjS1UaADs1tDvZ110w/YETifLCBivtZ8SOyUOyXGsViQK8YvxO8rUzqrJv +0wqiUOP2O+guRMLbZjipM1ZI8W0bM40NjD9gN53Tym1+NH4Nn3J2ixufcv1SNUFFApYvHLKac0kh +sUlHRUe072o0EclNmsxZt9YCnlpOZbWUrhvfKbAW8b8Angc6F2S1BLUjIZkKlTuXfO8= +-----END CERTIFICATE----- + +AffirmTrust Networking +====================== +-----BEGIN CERTIFICATE----- +MIIDTDCCAjSgAwIBAgIIfE8EORzUmS0wDQYJKoZIhvcNAQEFBQAwRDELMAkGA1UEBhMCVVMxFDAS +BgNVBAoMC0FmZmlybVRydXN0MR8wHQYDVQQDDBZBZmZpcm1UcnVzdCBOZXR3b3JraW5nMB4XDTEw +MDEyOTE0MDgyNFoXDTMwMTIzMTE0MDgyNFowRDELMAkGA1UEBhMCVVMxFDASBgNVBAoMC0FmZmly +bVRydXN0MR8wHQYDVQQDDBZBZmZpcm1UcnVzdCBOZXR3b3JraW5nMIIBIjANBgkqhkiG9w0BAQEF +AAOCAQ8AMIIBCgKCAQEAtITMMxcua5Rsa2FSoOujz3mUTOWUgJnLVWREZY9nZOIG41w3SfYvm4SE +Hi3yYJ0wTsyEheIszx6e/jarM3c1RNg1lho9Nuh6DtjVR6FqaYvZ/Ls6rnla1fTWcbuakCNrmreI +dIcMHl+5ni36q1Mr3Lt2PpNMCAiMHqIjHNRqrSK6mQEubWXLviRmVSRLQESxG9fhwoXA3hA/Pe24 +/PHxI1Pcv2WXb9n5QHGNfb2V1M6+oF4nI979ptAmDgAp6zxG8D1gvz9Q0twmQVGeFDdCBKNwV6gb +h+0t+nvujArjqWaJGctB+d1ENmHP4ndGyH329JKBNv3bNPFyfvMMFr20FQIDAQABo0IwQDAdBgNV +HQ4EFgQUBx/S55zawm6iQLSwelAQUHTEyL0wDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC +AQYwDQYJKoZIhvcNAQEFBQADggEBAIlXshZ6qML91tmbmzTCnLQyFE2npN/svqe++EPbkTfOtDIu +UFUaNU52Q3Eg75N3ThVwLofDwR1t3Mu1J9QsVtFSUzpE0nPIxBsFZVpikpzuQY0x2+c06lkh1QF6 +12S4ZDnNye2v7UsDSKegmQGA3GWjNq5lWUhPgkvIZfFXHeVZLgo/bNjR9eUJtGxUAArgFU2HdW23 +WJZa3W3SAKD0m0i+wzekujbgfIeFlxoVot4uolu9rxj5kFDNcFn4J2dHy8egBzp90SxdbBk6ZrV9 +/ZFvgrG+CJPbFEfxojfHRZ48x3evZKiT3/Zpg4Jg8klCNO1aAFSFHBY2kgxc+qatv9s= +-----END CERTIFICATE----- + +AffirmTrust Premium +=================== +-----BEGIN CERTIFICATE----- +MIIFRjCCAy6gAwIBAgIIbYwURrGmCu4wDQYJKoZIhvcNAQEMBQAwQTELMAkGA1UEBhMCVVMxFDAS +BgNVBAoMC0FmZmlybVRydXN0MRwwGgYDVQQDDBNBZmZpcm1UcnVzdCBQcmVtaXVtMB4XDTEwMDEy +OTE0MTAzNloXDTQwMTIzMTE0MTAzNlowQTELMAkGA1UEBhMCVVMxFDASBgNVBAoMC0FmZmlybVRy +dXN0MRwwGgYDVQQDDBNBZmZpcm1UcnVzdCBQcmVtaXVtMIICIjANBgkqhkiG9w0BAQEFAAOCAg8A +MIICCgKCAgEAxBLfqV/+Qd3d9Z+K4/as4Tx4mrzY8H96oDMq3I0gW64tb+eT2TZwamjPjlGjhVtn +BKAQJG9dKILBl1fYSCkTtuG+kU3fhQxTGJoeJKJPj/CihQvL9Cl/0qRY7iZNyaqoe5rZ+jjeRFcV +5fiMyNlI4g0WJx0eyIOFJbe6qlVBzAMiSy2RjYvmia9mx+n/K+k8rNrSs8PhaJyJ+HoAVt70VZVs ++7pk3WKL3wt3MutizCaam7uqYoNMtAZ6MMgpv+0GTZe5HMQxK9VfvFMSF5yZVylmd2EhMQcuJUmd +GPLu8ytxjLW6OQdJd/zvLpKQBY0tL3d770O/Nbua2Plzpyzy0FfuKE4mX4+QaAkvuPjcBukumj5R +p9EixAqnOEhss/n/fauGV+O61oV4d7pD6kh/9ti+I20ev9E2bFhc8e6kGVQa9QPSdubhjL08s9NI +S+LI+H+SqHZGnEJlPqQewQcDWkYtuJfzt9WyVSHvutxMAJf7FJUnM7/oQ0dG0giZFmA7mn7S5u04 +6uwBHjxIVkkJx0w3AJ6IDsBz4W9m6XJHMD4Q5QsDyZpCAGzFlH5hxIrff4IaC1nEWTJ3s7xgaVY5 +/bQGeyzWZDbZvUjthB9+pSKPKrhC9IK31FOQeE4tGv2Bb0TXOwF0lkLgAOIua+rF7nKsu7/+6qqo ++Nz2snmKtmcCAwEAAaNCMEAwHQYDVR0OBBYEFJ3AZ6YMItkm9UWrpmVSESfYRaxjMA8GA1UdEwEB +/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3DQEBDAUAA4ICAQCzV00QYk465KzquByv +MiPIs0laUZx2KI15qldGF9X1Uva3ROgIRL8YhNILgM3FEv0AVQVhh0HctSSePMTYyPtwni94loMg +Nt58D2kTiKV1NpgIpsbfrM7jWNa3Pt668+s0QNiigfV4Py/VpfzZotReBA4Xrf5B8OWycvpEgjNC +6C1Y91aMYj+6QrCcDFx+LmUmXFNPALJ4fqENmS2NuB2OosSw/WDQMKSOyARiqcTtNd56l+0OOF6S +L5Nwpamcb6d9Ex1+xghIsV5n61EIJenmJWtSKZGc0jlzCFfemQa0W50QBuHCAKi4HEoCChTQwUHK ++4w1IX2COPKpVJEZNZOUbWo6xbLQu4mGk+ibyQ86p3q4ofB4Rvr8Ny/lioTz3/4E2aFooC8k4gmV +BtWVyuEklut89pMFu+1z6S3RdTnX5yTb2E5fQ4+e0BQ5v1VwSJlXMbSc7kqYA5YwH2AG7hsj/oFg +IxpHYoWlzBk0gG+zrBrjn/B7SK3VAdlntqlyk+otZrWyuOQ9PLLvTIzq6we/qzWaVYa8GKa1qF60 +g2xraUDTn9zxw2lrueFtCfTxqlB2Cnp9ehehVZZCmTEJ3WARjQUwfuaORtGdFNrHF+QFlozEJLUb +zxQHskD4o55BhrwE0GuWyCqANP2/7waj3VjFhT0+j/6eKeC2uAloGRwYQw== +-----END CERTIFICATE----- + +AffirmTrust Premium ECC +======================= +-----BEGIN CERTIFICATE----- +MIIB/jCCAYWgAwIBAgIIdJclisc/elQwCgYIKoZIzj0EAwMwRTELMAkGA1UEBhMCVVMxFDASBgNV +BAoMC0FmZmlybVRydXN0MSAwHgYDVQQDDBdBZmZpcm1UcnVzdCBQcmVtaXVtIEVDQzAeFw0xMDAx +MjkxNDIwMjRaFw00MDEyMzExNDIwMjRaMEUxCzAJBgNVBAYTAlVTMRQwEgYDVQQKDAtBZmZpcm1U +cnVzdDEgMB4GA1UEAwwXQWZmaXJtVHJ1c3QgUHJlbWl1bSBFQ0MwdjAQBgcqhkjOPQIBBgUrgQQA +IgNiAAQNMF4bFZ0D0KF5Nbc6PJJ6yhUczWLznCZcBz3lVPqj1swS6vQUX+iOGasvLkjmrBhDeKzQ +N8O9ss0s5kfiGuZjuD0uL3jET9v0D6RoTFVya5UdThhClXjMNzyR4ptlKymjQjBAMB0GA1UdDgQW +BBSaryl6wBE1NSZRMADDav5A1a7WPDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAK +BggqhkjOPQQDAwNnADBkAjAXCfOHiFBar8jAQr9HX/VsaobgxCd05DhT1wV/GzTjxi+zygk8N53X +57hG8f2h4nECMEJZh0PUUd+60wkyWs6Iflc9nF9Ca/UHLbXwgpP5WW+uZPpY5Yse42O+tYHNbwKM +eQ== +-----END CERTIFICATE----- + +Certum Trusted Network CA +========================= +-----BEGIN CERTIFICATE----- +MIIDuzCCAqOgAwIBAgIDBETAMA0GCSqGSIb3DQEBBQUAMH4xCzAJBgNVBAYTAlBMMSIwIAYDVQQK +ExlVbml6ZXRvIFRlY2hub2xvZ2llcyBTLkEuMScwJQYDVQQLEx5DZXJ0dW0gQ2VydGlmaWNhdGlv +biBBdXRob3JpdHkxIjAgBgNVBAMTGUNlcnR1bSBUcnVzdGVkIE5ldHdvcmsgQ0EwHhcNMDgxMDIy +MTIwNzM3WhcNMjkxMjMxMTIwNzM3WjB+MQswCQYDVQQGEwJQTDEiMCAGA1UEChMZVW5pemV0byBU +ZWNobm9sb2dpZXMgUy5BLjEnMCUGA1UECxMeQ2VydHVtIENlcnRpZmljYXRpb24gQXV0aG9yaXR5 +MSIwIAYDVQQDExlDZXJ0dW0gVHJ1c3RlZCBOZXR3b3JrIENBMIIBIjANBgkqhkiG9w0BAQEFAAOC +AQ8AMIIBCgKCAQEA4/t9o3K6wvDJFIf1awFO4W5AB7ptJ11/91sts1rHUV+rpDKmYYe2bg+G0jAC +l/jXaVehGDldamR5xgFZrDwxSjh80gTSSyjoIF87B6LMTXPb865Px1bVWqeWifrzq2jUI4ZZJ88J +J7ysbnKDHDBy3+Ci6dLhdHUZvSqeexVUBBvXQzmtVSjF4hq79MDkrjhJM8x2hZ85RdKknvISjFH4 +fOQtf/WsX+sWn7Et0brMkUJ3TCXJkDhv2/DM+44el1k+1WBO5gUo7Ul5E0u6SNsv+XLTOcr+H9g0 +cvW0QM8xAcPs3hEtF10fuFDRXhmnad4HMyjKUJX5p1TLVIZQRan5SQIDAQABo0IwQDAPBgNVHRMB +Af8EBTADAQH/MB0GA1UdDgQWBBQIds3LB/8k9sXN7buQvOKEN0Z19zAOBgNVHQ8BAf8EBAMCAQYw +DQYJKoZIhvcNAQEFBQADggEBAKaorSLOAT2mo/9i0Eidi15ysHhE49wcrwn9I0j6vSrEuVUEtRCj +jSfeC4Jj0O7eDDd5QVsisrCaQVymcODU0HfLI9MA4GxWL+FpDQ3Zqr8hgVDZBqWo/5U30Kr+4rP1 +mS1FhIrlQgnXdAIv94nYmem8J9RHjboNRhx3zxSkHLmkMcScKHQDNP8zGSal6Q10tz6XxnboJ5aj +Zt3hrvJBW8qYVoNzcOSGGtIxQbovvi0TWnZvTuhOgQ4/WwMioBK+ZlgRSssDxLQqKi2WF+A5VLxI +03YnnZotBqbJ7DnSq9ufmgsnAjUpsUCV5/nonFWIGUbWtzT1fs45mtk48VH3Tyw= +-----END CERTIFICATE----- + +TWCA Root Certification Authority +================================= +-----BEGIN CERTIFICATE----- +MIIDezCCAmOgAwIBAgIBATANBgkqhkiG9w0BAQUFADBfMQswCQYDVQQGEwJUVzESMBAGA1UECgwJ +VEFJV0FOLUNBMRAwDgYDVQQLDAdSb290IENBMSowKAYDVQQDDCFUV0NBIFJvb3QgQ2VydGlmaWNh +dGlvbiBBdXRob3JpdHkwHhcNMDgwODI4MDcyNDMzWhcNMzAxMjMxMTU1OTU5WjBfMQswCQYDVQQG +EwJUVzESMBAGA1UECgwJVEFJV0FOLUNBMRAwDgYDVQQLDAdSb290IENBMSowKAYDVQQDDCFUV0NB +IFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK +AoIBAQCwfnK4pAOU5qfeCTiRShFAh6d8WWQUe7UREN3+v9XAu1bihSX0NXIP+FPQQeFEAcK0HMMx +QhZHhTMidrIKbw/lJVBPhYa+v5guEGcevhEFhgWQxFnQfHgQsIBct+HHK3XLfJ+utdGdIzdjp9xC +oi2SBBtQwXu4PhvJVgSLL1KbralW6cH/ralYhzC2gfeXRfwZVzsrb+RH9JlF/h3x+JejiB03HFyP +4HYlmlD4oFT/RJB2I9IyxsOrBr/8+7/zrX2SYgJbKdM1o5OaQ2RgXbL6Mv87BK9NQGr5x+PvI/1r +y+UPizgN7gr8/g+YnzAx3WxSZfmLgb4i4RxYA7qRG4kHAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIB +BjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRqOFsmjd6LWvJPelSDGRjjCDWmujANBgkqhkiG +9w0BAQUFAAOCAQEAPNV3PdrfibqHDAhUaiBQkr6wQT25JmSDCi/oQMCXKCeCMErJk/9q56YAf4lC +mtYR5VPOL8zy2gXE/uJQxDqGfczafhAJO5I1KlOy/usrBdlsXebQ79NqZp4VKIV66IIArB6nCWlW +QtNoURi+VJq/REG6Sb4gumlc7rh3zc5sH62Dlhh9DrUUOYTxKOkto557HnpyWoOzeW/vtPzQCqVY +T0bf+215WfKEIlKuD8z7fDvnaspHYcN6+NOSBB+4IIThNlQWx0DeO4pz3N/GCUzf7Nr/1FNCocny +Yh0igzyXxfkZYiesZSLX0zzG5Y6yU8xJzrww/nsOM5D77dIUkR8Hrw== +-----END CERTIFICATE----- + +Security Communication RootCA2 +============================== +-----BEGIN CERTIFICATE----- +MIIDdzCCAl+gAwIBAgIBADANBgkqhkiG9w0BAQsFADBdMQswCQYDVQQGEwJKUDElMCMGA1UEChMc +U0VDT00gVHJ1c3QgU3lzdGVtcyBDTy4sTFRELjEnMCUGA1UECxMeU2VjdXJpdHkgQ29tbXVuaWNh +dGlvbiBSb290Q0EyMB4XDTA5MDUyOTA1MDAzOVoXDTI5MDUyOTA1MDAzOVowXTELMAkGA1UEBhMC +SlAxJTAjBgNVBAoTHFNFQ09NIFRydXN0IFN5c3RlbXMgQ08uLExURC4xJzAlBgNVBAsTHlNlY3Vy +aXR5IENvbW11bmljYXRpb24gUm9vdENBMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB +ANAVOVKxUrO6xVmCxF1SrjpDZYBLx/KWvNs2l9amZIyoXvDjChz335c9S672XewhtUGrzbl+dp++ ++T42NKA7wfYxEUV0kz1XgMX5iZnK5atq1LXaQZAQwdbWQonCv/Q4EpVMVAX3NuRFg3sUZdbcDE3R +3n4MqzvEFb46VqZab3ZpUql6ucjrappdUtAtCms1FgkQhNBqyjoGADdH5H5XTz+L62e4iKrFvlNV +spHEfbmwhRkGeC7bYRr6hfVKkaHnFtWOojnflLhwHyg/i/xAXmODPIMqGplrz95Zajv8bxbXH/1K +EOtOghY6rCcMU/Gt1SSwawNQwS08Ft1ENCcadfsCAwEAAaNCMEAwHQYDVR0OBBYEFAqFqXdlBZh8 +QIH4D5csOPEK7DzPMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEB +CwUAA4IBAQBMOqNErLlFsceTfsgLCkLfZOoc7llsCLqJX2rKSpWeeo8HxdpFcoJxDjrSzG+ntKEj +u/Ykn8sX/oymzsLS28yN/HH8AynBbF0zX2S2ZTuJbxh2ePXcokgfGT+Ok+vx+hfuzU7jBBJV1uXk +3fs+BXziHV7Gp7yXT2g69ekuCkO2r1dcYmh8t/2jioSgrGK+KwmHNPBqAbubKVY8/gA3zyNs8U6q +tnRGEmyR7jTV7JqR50S+kDFy1UkC9gLl9B/rfNmWVan/7Ir5mUf/NVoCqgTLiluHcSmRvaS0eg29 +mvVXIwAHIRc/SjnRBUkLp7Y3gaVdjKozXoEofKd9J+sAro03 +-----END CERTIFICATE----- + +EC-ACC +====== +-----BEGIN CERTIFICATE----- +MIIFVjCCBD6gAwIBAgIQ7is969Qh3hSoYqwE893EATANBgkqhkiG9w0BAQUFADCB8zELMAkGA1UE +BhMCRVMxOzA5BgNVBAoTMkFnZW5jaWEgQ2F0YWxhbmEgZGUgQ2VydGlmaWNhY2lvIChOSUYgUS0w +ODAxMTc2LUkpMSgwJgYDVQQLEx9TZXJ2ZWlzIFB1YmxpY3MgZGUgQ2VydGlmaWNhY2lvMTUwMwYD +VQQLEyxWZWdldSBodHRwczovL3d3dy5jYXRjZXJ0Lm5ldC92ZXJhcnJlbCAoYykwMzE1MDMGA1UE +CxMsSmVyYXJxdWlhIEVudGl0YXRzIGRlIENlcnRpZmljYWNpbyBDYXRhbGFuZXMxDzANBgNVBAMT +BkVDLUFDQzAeFw0wMzAxMDcyMzAwMDBaFw0zMTAxMDcyMjU5NTlaMIHzMQswCQYDVQQGEwJFUzE7 +MDkGA1UEChMyQWdlbmNpYSBDYXRhbGFuYSBkZSBDZXJ0aWZpY2FjaW8gKE5JRiBRLTA4MDExNzYt +SSkxKDAmBgNVBAsTH1NlcnZlaXMgUHVibGljcyBkZSBDZXJ0aWZpY2FjaW8xNTAzBgNVBAsTLFZl +Z2V1IGh0dHBzOi8vd3d3LmNhdGNlcnQubmV0L3ZlcmFycmVsIChjKTAzMTUwMwYDVQQLEyxKZXJh +cnF1aWEgRW50aXRhdHMgZGUgQ2VydGlmaWNhY2lvIENhdGFsYW5lczEPMA0GA1UEAxMGRUMtQUND +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsyLHT+KXQpWIR4NA9h0X84NzJB5R85iK +w5K4/0CQBXCHYMkAqbWUZRkiFRfCQ2xmRJoNBD45b6VLeqpjt4pEndljkYRm4CgPukLjbo73FCeT +ae6RDqNfDrHrZqJyTxIThmV6PttPB/SnCWDaOkKZx7J/sxaVHMf5NLWUhdWZXqBIoH7nF2W4onW4 +HvPlQn2v7fOKSGRdghST2MDk/7NQcvJ29rNdQlB50JQ+awwAvthrDk4q7D7SzIKiGGUzE3eeml0a +E9jD2z3Il3rucO2n5nzbcc8tlGLfbdb1OL4/pYUKGbio2Al1QnDE6u/LDsg0qBIimAy4E5S2S+zw +0JDnJwIDAQABo4HjMIHgMB0GA1UdEQQWMBSBEmVjX2FjY0BjYXRjZXJ0Lm5ldDAPBgNVHRMBAf8E +BTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUoMOLRKo3pUW/l4Ba0fF4opvpXY0wfwYD +VR0gBHgwdjB0BgsrBgEEAfV4AQMBCjBlMCwGCCsGAQUFBwIBFiBodHRwczovL3d3dy5jYXRjZXJ0 +Lm5ldC92ZXJhcnJlbDA1BggrBgEFBQcCAjApGidWZWdldSBodHRwczovL3d3dy5jYXRjZXJ0Lm5l +dC92ZXJhcnJlbCAwDQYJKoZIhvcNAQEFBQADggEBAKBIW4IB9k1IuDlVNZyAelOZ1Vr/sXE7zDkJ +lF7W2u++AVtd0x7Y/X1PzaBB4DSTv8vihpw3kpBWHNzrKQXlxJ7HNd+KDM3FIUPpqojlNcAZQmNa +Al6kSBg6hW/cnbw/nZzBh7h6YQjpdwt/cKt63dmXLGQehb+8dJahw3oS7AwaboMMPOhyRp/7SNVe +l+axofjk70YllJyJ22k4vuxcDlbHZVHlUIiIv0LVKz3l+bqeLrPK9HOSAgu+TGbrIP65y7WZf+a2 +E/rKS03Z7lNGBjvGTq2TWoF+bCpLagVFjPIhpDGQh2xlnJ2lYJU6Un/10asIbvPuW/mIPX64b24D +5EI= +-----END CERTIFICATE----- + +Hellenic Academic and Research Institutions RootCA 2011 +======================================================= +-----BEGIN CERTIFICATE----- +MIIEMTCCAxmgAwIBAgIBADANBgkqhkiG9w0BAQUFADCBlTELMAkGA1UEBhMCR1IxRDBCBgNVBAoT +O0hlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgQ2VydC4gQXV0aG9y +aXR5MUAwPgYDVQQDEzdIZWxsZW5pYyBBY2FkZW1pYyBhbmQgUmVzZWFyY2ggSW5zdGl0dXRpb25z +IFJvb3RDQSAyMDExMB4XDTExMTIwNjEzNDk1MloXDTMxMTIwMTEzNDk1MlowgZUxCzAJBgNVBAYT +AkdSMUQwQgYDVQQKEztIZWxsZW5pYyBBY2FkZW1pYyBhbmQgUmVzZWFyY2ggSW5zdGl0dXRpb25z +IENlcnQuIEF1dGhvcml0eTFAMD4GA1UEAxM3SGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJlc2VhcmNo +IEluc3RpdHV0aW9ucyBSb290Q0EgMjAxMTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB +AKlTAOMupvaO+mDYLZU++CwqVE7NuYRhlFhPjz2L5EPzdYmNUeTDN9KKiE15HrcS3UN4SoqS5tdI +1Q+kOilENbgH9mgdVc04UfCMJDGFr4PJfel3r+0ae50X+bOdOFAPplp5kYCvN66m0zH7tSYJnTxa +71HFK9+WXesyHgLacEnsbgzImjeN9/E2YEsmLIKe0HjzDQ9jpFEw4fkrJxIH2Oq9GGKYsFk3fb7u +8yBRQlqD75O6aRXxYp2fmTmCobd0LovUxQt7L/DICto9eQqakxylKHJzkUOap9FNhYS5qXSPFEDH +3N6sQWRstBmbAmNtJGSPRLIl6s5ddAxjMlyNh+UCAwEAAaOBiTCBhjAPBgNVHRMBAf8EBTADAQH/ +MAsGA1UdDwQEAwIBBjAdBgNVHQ4EFgQUppFC/RNhSiOeCKQp5dgTBCPuQSUwRwYDVR0eBEAwPqA8 +MAWCAy5ncjAFggMuZXUwBoIELmVkdTAGggQub3JnMAWBAy5ncjAFgQMuZXUwBoEELmVkdTAGgQQu +b3JnMA0GCSqGSIb3DQEBBQUAA4IBAQAf73lB4XtuP7KMhjdCSk4cNx6NZrokgclPEg8hwAOXhiVt +XdMiKahsog2p6z0GW5k6x8zDmjR/qw7IThzh+uTczQ2+vyT+bOdrwg3IBp5OjWEopmr95fZi6hg8 +TqBTnbI6nOulnJEWtk2C4AwFSKls9cz4y51JtPACpf1wA+2KIaWuE4ZJwzNzvoc7dIsXRSZMFpGD +/md9zU1jZ/rzAxKWeAaNsWftjj++n08C9bMJL/NMh98qy5V8AcysNnq/onN694/BtZqhFLKPM58N +7yLcZnuEvUUXBj08yrl3NI/K6s8/MT7jiOOASSXIl7WdmplNsDz4SgCbZN2fOUvRJ9e4 +-----END CERTIFICATE----- + +Actalis Authentication Root CA +============================== +-----BEGIN CERTIFICATE----- +MIIFuzCCA6OgAwIBAgIIVwoRl0LE48wwDQYJKoZIhvcNAQELBQAwazELMAkGA1UEBhMCSVQxDjAM +BgNVBAcMBU1pbGFuMSMwIQYDVQQKDBpBY3RhbGlzIFMucC5BLi8wMzM1ODUyMDk2NzEnMCUGA1UE +AwweQWN0YWxpcyBBdXRoZW50aWNhdGlvbiBSb290IENBMB4XDTExMDkyMjExMjIwMloXDTMwMDky +MjExMjIwMlowazELMAkGA1UEBhMCSVQxDjAMBgNVBAcMBU1pbGFuMSMwIQYDVQQKDBpBY3RhbGlz +IFMucC5BLi8wMzM1ODUyMDk2NzEnMCUGA1UEAwweQWN0YWxpcyBBdXRoZW50aWNhdGlvbiBSb290 +IENBMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAp8bEpSmkLO/lGMWwUKNvUTufClrJ +wkg4CsIcoBh/kbWHuUA/3R1oHwiD1S0eiKD4j1aPbZkCkpAW1V8IbInX4ay8IMKx4INRimlNAJZa +by/ARH6jDuSRzVju3PvHHkVH3Se5CAGfpiEd9UEtL0z9KK3giq0itFZljoZUj5NDKd45RnijMCO6 +zfB9E1fAXdKDa0hMxKufgFpbOr3JpyI/gCczWw63igxdBzcIy2zSekciRDXFzMwujt0q7bd9Zg1f +YVEiVRvjRuPjPdA1YprbrxTIW6HMiRvhMCb8oJsfgadHHwTrozmSBp+Z07/T6k9QnBn+locePGX2 +oxgkg4YQ51Q+qDp2JE+BIcXjDwL4k5RHILv+1A7TaLndxHqEguNTVHnd25zS8gebLra8Pu2Fbe8l +EfKXGkJh90qX6IuxEAf6ZYGyojnP9zz/GPvG8VqLWeICrHuS0E4UT1lF9gxeKF+w6D9Fz8+vm2/7 +hNN3WpVvrJSEnu68wEqPSpP4RCHiMUVhUE4Q2OM1fEwZtN4Fv6MGn8i1zeQf1xcGDXqVdFUNaBr8 +EBtiZJ1t4JWgw5QHVw0U5r0F+7if5t+L4sbnfpb2U8WANFAoWPASUHEXMLrmeGO89LKtmyuy/uE5 +jF66CyCU3nuDuP/jVo23Eek7jPKxwV2dpAtMK9myGPW1n0sCAwEAAaNjMGEwHQYDVR0OBBYEFFLY +iDrIn3hm7YnzezhwlMkCAjbQMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUUtiIOsifeGbt +ifN7OHCUyQICNtAwDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3DQEBCwUAA4ICAQALe3KHwGCmSUyI +WOYdiPcUZEim2FgKDk8TNd81HdTtBjHIgT5q1d07GjLukD0R0i70jsNjLiNmsGe+b7bAEzlgqqI0 +JZN1Ut6nna0Oh4lScWoWPBkdg/iaKWW+9D+a2fDzWochcYBNy+A4mz+7+uAwTc+G02UQGRjRlwKx +K3JCaKygvU5a2hi/a5iB0P2avl4VSM0RFbnAKVy06Ij3Pjaut2L9HmLecHgQHEhb2rykOLpn7VU+ +Xlff1ANATIGk0k9jpwlCCRT8AKnCgHNPLsBA2RF7SOp6AsDT6ygBJlh0wcBzIm2Tlf05fbsq4/aC +4yyXX04fkZT6/iyj2HYauE2yOE+b+h1IYHkm4vP9qdCa6HCPSXrW5b0KDtst842/6+OkfcvHlXHo +2qN8xcL4dJIEG4aspCJTQLas/kx2z/uUMsA1n3Y/buWQbqCmJqK4LL7RK4X9p2jIugErsWx0Hbhz +lefut8cl8ABMALJ+tguLHPPAUJ4lueAI3jZm/zel0btUZCzJJ7VLkn5l/9Mt4blOvH+kQSGQQXem +OR/qnuOf0GZvBeyqdn6/axag67XH/JJULysRJyU3eExRarDzzFhdFPFqSBX/wge2sY0PjlxQRrM9 +vwGYT7JZVEc+NHt4bVaTLnPqZih4zR0Uv6CPLy64Lo7yFIrM6bV8+2ydDKXhlg== +-----END CERTIFICATE----- + +Buypass Class 2 Root CA +======================= +-----BEGIN CERTIFICATE----- +MIIFWTCCA0GgAwIBAgIBAjANBgkqhkiG9w0BAQsFADBOMQswCQYDVQQGEwJOTzEdMBsGA1UECgwU +QnV5cGFzcyBBUy05ODMxNjMzMjcxIDAeBgNVBAMMF0J1eXBhc3MgQ2xhc3MgMiBSb290IENBMB4X +DTEwMTAyNjA4MzgwM1oXDTQwMTAyNjA4MzgwM1owTjELMAkGA1UEBhMCTk8xHTAbBgNVBAoMFEJ1 +eXBhc3MgQVMtOTgzMTYzMzI3MSAwHgYDVQQDDBdCdXlwYXNzIENsYXNzIDIgUm9vdCBDQTCCAiIw +DQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANfHXvfBB9R3+0Mh9PT1aeTuMgHbo4Yf5FkNuud1 +g1Lr6hxhFUi7HQfKjK6w3Jad6sNgkoaCKHOcVgb/S2TwDCo3SbXlzwx87vFKu3MwZfPVL4O2fuPn +9Z6rYPnT8Z2SdIrkHJasW4DptfQxh6NR/Md+oW+OU3fUl8FVM5I+GC911K2GScuVr1QGbNgGE41b +/+EmGVnAJLqBcXmQRFBoJJRfuLMR8SlBYaNByyM21cHxMlAQTn/0hpPshNOOvEu/XAFOBz3cFIqU +CqTqc/sLUegTBxj6DvEr0VQVfTzh97QZQmdiXnfgolXsttlpF9U6r0TtSsWe5HonfOV116rLJeff +awrbD02TTqigzXsu8lkBarcNuAeBfos4GzjmCleZPe4h6KP1DBbdi+w0jpwqHAAVF41og9JwnxgI +zRFo1clrUs3ERo/ctfPYV3Me6ZQ5BL/T3jjetFPsaRyifsSP5BtwrfKi+fv3FmRmaZ9JUaLiFRhn +Bkp/1Wy1TbMz4GHrXb7pmA8y1x1LPC5aAVKRCfLf6o3YBkBjqhHk/sM3nhRSP/TizPJhk9H9Z2vX +Uq6/aKtAQ6BXNVN48FP4YUIHZMbXb5tMOA1jrGKvNouicwoN9SG9dKpN6nIDSdvHXx1iY8f93ZHs +M+71bbRuMGjeyNYmsHVee7QHIJihdjK4TWxPAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYD +VR0OBBYEFMmAd+BikoL1RpzzuvdMw964o605MA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsF +AAOCAgEAU18h9bqwOlI5LJKwbADJ784g7wbylp7ppHR/ehb8t/W2+xUbP6umwHJdELFx7rxP462s +A20ucS6vxOOto70MEae0/0qyexAQH6dXQbLArvQsWdZHEIjzIVEpMMpghq9Gqx3tOluwlN5E40EI +osHsHdb9T7bWR9AUC8rmyrV7d35BH16Dx7aMOZawP5aBQW9gkOLo+fsicdl9sz1Gv7SEr5AcD48S +aq/v7h56rgJKihcrdv6sVIkkLE8/trKnToyokZf7KcZ7XC25y2a2t6hbElGFtQl+Ynhw/qlqYLYd +DnkM/crqJIByw5c/8nerQyIKx+u2DISCLIBrQYoIwOula9+ZEsuK1V6ADJHgJgg2SMX6OBE1/yWD +LfJ6v9r9jv6ly0UsH8SIU653DtmadsWOLB2jutXsMq7Aqqz30XpN69QH4kj3Io6wpJ9qzo6ysmD0 +oyLQI+uUWnpp3Q+/QFesa1lQ2aOZ4W7+jQF5JyMV3pKdewlNWudLSDBaGOYKbeaP4NK75t98biGC +wWg5TbSYWGZizEqQXsP6JwSxeRV0mcy+rSDeJmAc61ZRpqPq5KM/p/9h3PFaTWwyI0PurKju7koS +CTxdccK+efrCh2gdC/1cacwG0Jp9VJkqyTkaGa9LKkPzY11aWOIv4x3kqdbQCtCev9eBCfHJxyYN +rJgWVqA= +-----END CERTIFICATE----- + +Buypass Class 3 Root CA +======================= +-----BEGIN CERTIFICATE----- +MIIFWTCCA0GgAwIBAgIBAjANBgkqhkiG9w0BAQsFADBOMQswCQYDVQQGEwJOTzEdMBsGA1UECgwU +QnV5cGFzcyBBUy05ODMxNjMzMjcxIDAeBgNVBAMMF0J1eXBhc3MgQ2xhc3MgMyBSb290IENBMB4X +DTEwMTAyNjA4Mjg1OFoXDTQwMTAyNjA4Mjg1OFowTjELMAkGA1UEBhMCTk8xHTAbBgNVBAoMFEJ1 +eXBhc3MgQVMtOTgzMTYzMzI3MSAwHgYDVQQDDBdCdXlwYXNzIENsYXNzIDMgUm9vdCBDQTCCAiIw +DQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAKXaCpUWUOOV8l6ddjEGMnqb8RB2uACatVI2zSRH +sJ8YZLya9vrVediQYkwiL944PdbgqOkcLNt4EemOaFEVcsfzM4fkoF0LXOBXByow9c3EN3coTRiR +5r/VUv1xLXA+58bEiuPwKAv0dpihi4dVsjoT/Lc+JzeOIuOoTyrvYLs9tznDDgFHmV0ST9tD+leh +7fmdvhFHJlsTmKtdFoqwNxxXnUX/iJY2v7vKB3tvh2PX0DJq1l1sDPGzbjniazEuOQAnFN44wOwZ +ZoYS6J1yFhNkUsepNxz9gjDthBgd9K5c/3ATAOux9TN6S9ZV+AWNS2mw9bMoNlwUxFFzTWsL8TQH +2xc519woe2v1n/MuwU8XKhDzzMro6/1rqy6any2CbgTUUgGTLT2G/H783+9CHaZr77kgxve9oKeV +/afmiSTYzIw0bOIjL9kSGiG5VZFvC5F5GQytQIgLcOJ60g7YaEi7ghM5EFjp2CoHxhLbWNvSO1UQ +RwUVZ2J+GGOmRj8JDlQyXr8NYnon74Do29lLBlo3WiXQCBJ31G8JUJc9yB3D34xFMFbG02SrZvPA +Xpacw8Tvw3xrizp5f7NJzz3iiZ+gMEuFuZyUJHmPfWupRWgPK9Dx2hzLabjKSWJtyNBjYt1gD1iq +j6G8BaVmos8bdrKEZLFMOVLAMLrwjEsCsLa3AgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYD +VR0OBBYEFEe4zf/lb+74suwvTg75JbCOPGvDMA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsF +AAOCAgEAACAjQTUEkMJAYmDv4jVM1z+s4jSQuKFvdvoWFqRINyzpkMLyPPgKn9iB5btb2iUspKdV +cSQy9sgL8rxq+JOssgfCX5/bzMiKqr5qb+FJEMwx14C7u8jYog5kV+qi9cKpMRXSIGrs/CIBKM+G +uIAeqcwRpTzyFrNHnfzSgCHEy9BHcEGhyoMZCCxt8l13nIoUE9Q2HJLw5QY33KbmkJs4j1xrG0aG +Q0JfPgEHU1RdZX33inOhmlRaHylDFCfChQ+1iHsaO5S3HWCntZznKWlXWpuTekMwGwPXYshApqr8 +ZORK15FTAaggiG6cX0S5y2CBNOxv033aSF/rtJC8LakcC6wc1aJoIIAE1vyxjy+7SjENSoYc6+I2 +KSb12tjE8nVhz36udmNKekBlk4f4HoCMhuWG1o8O/FMsYOgWYRqiPkN7zTlgVGr18okmAWiDSKIz +6MkEkbIRNBE+6tBDGR8Dk5AM/1E9V/RBbuHLoL7ryWPNbczk+DaqaJ3tvV2XcEQNtg413OEMXbug +UZTLfhbrES+jkkXITHHZvMmZUldGL1DPvTVp9D0VzgalLA8+9oG6lLvDu79leNKGef9JOxqDDPDe +eOzI8k1MGt6CKfjBWtrt7uYnXuhF0J0cUahoq0Tj0Itq4/g7u9xN12TyUb7mqqta6THuBrxzvxNi +Cp/HuZc= +-----END CERTIFICATE----- + +T-TeleSec GlobalRoot Class 3 +============================ +-----BEGIN CERTIFICATE----- +MIIDwzCCAqugAwIBAgIBATANBgkqhkiG9w0BAQsFADCBgjELMAkGA1UEBhMCREUxKzApBgNVBAoM +IlQtU3lzdGVtcyBFbnRlcnByaXNlIFNlcnZpY2VzIEdtYkgxHzAdBgNVBAsMFlQtU3lzdGVtcyBU +cnVzdCBDZW50ZXIxJTAjBgNVBAMMHFQtVGVsZVNlYyBHbG9iYWxSb290IENsYXNzIDMwHhcNMDgx +MDAxMTAyOTU2WhcNMzMxMDAxMjM1OTU5WjCBgjELMAkGA1UEBhMCREUxKzApBgNVBAoMIlQtU3lz +dGVtcyBFbnRlcnByaXNlIFNlcnZpY2VzIEdtYkgxHzAdBgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBD +ZW50ZXIxJTAjBgNVBAMMHFQtVGVsZVNlYyBHbG9iYWxSb290IENsYXNzIDMwggEiMA0GCSqGSIb3 +DQEBAQUAA4IBDwAwggEKAoIBAQC9dZPwYiJvJK7genasfb3ZJNW4t/zN8ELg63iIVl6bmlQdTQyK +9tPPcPRStdiTBONGhnFBSivwKixVA9ZIw+A5OO3yXDw/RLyTPWGrTs0NvvAgJ1gORH8EGoel15YU +NpDQSXuhdfsaa3Ox+M6pCSzyU9XDFES4hqX2iys52qMzVNn6chr3IhUciJFrf2blw2qAsCTz34ZF +iP0Zf3WHHx+xGwpzJFu5ZeAsVMhg02YXP+HMVDNzkQI6pn97djmiH5a2OK61yJN0HZ65tOVgnS9W +0eDrXltMEnAMbEQgqxHY9Bn20pxSN+f6tsIxO0rUFJmtxxr1XV/6B7h8DR/Wgx6zAgMBAAGjQjBA +MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBS1A/d2O2GCahKqGFPr +AyGUv/7OyjANBgkqhkiG9w0BAQsFAAOCAQEAVj3vlNW92nOyWL6ukK2YJ5f+AbGwUgC4TeQbIXQb +fsDuXmkqJa9c1h3a0nnJ85cp4IaH3gRZD/FZ1GSFS5mvJQQeyUapl96Cshtwn5z2r3Ex3XsFpSzT +ucpH9sry9uetuUg/vBa3wW306gmv7PO15wWeph6KU1HWk4HMdJP2udqmJQV0eVp+QD6CSyYRMG7h +P0HHRwA11fXT91Q+gT3aSWqas+8QPebrb9HIIkfLzM8BMZLZGOMivgkeGj5asuRrDFR6fUNOuIml +e9eiPZaGzPImNC1qkp2aGtAw4l1OBLBfiyB+d8E9lYLRRpo7PHi4b6HQDWSieB4pTpPDpFQUWw== +-----END CERTIFICATE----- + +D-TRUST Root Class 3 CA 2 2009 +============================== +-----BEGIN CERTIFICATE----- +MIIEMzCCAxugAwIBAgIDCYPzMA0GCSqGSIb3DQEBCwUAME0xCzAJBgNVBAYTAkRFMRUwEwYDVQQK +DAxELVRydXN0IEdtYkgxJzAlBgNVBAMMHkQtVFJVU1QgUm9vdCBDbGFzcyAzIENBIDIgMjAwOTAe +Fw0wOTExMDUwODM1NThaFw0yOTExMDUwODM1NThaME0xCzAJBgNVBAYTAkRFMRUwEwYDVQQKDAxE +LVRydXN0IEdtYkgxJzAlBgNVBAMMHkQtVFJVU1QgUm9vdCBDbGFzcyAzIENBIDIgMjAwOTCCASIw +DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANOySs96R+91myP6Oi/WUEWJNTrGa9v+2wBoqOAD +ER03UAifTUpolDWzU9GUY6cgVq/eUXjsKj3zSEhQPgrfRlWLJ23DEE0NkVJD2IfgXU42tSHKXzlA +BF9bfsyjxiupQB7ZNoTWSPOSHjRGICTBpFGOShrvUD9pXRl/RcPHAY9RySPocq60vFYJfxLLHLGv +KZAKyVXMD9O0Gu1HNVpK7ZxzBCHQqr0ME7UAyiZsxGsMlFqVlNpQmvH/pStmMaTJOKDfHR+4CS7z +p+hnUquVH+BGPtikw8paxTGA6Eian5Rp/hnd2HN8gcqW3o7tszIFZYQ05ub9VxC1X3a/L7AQDcUC +AwEAAaOCARowggEWMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFP3aFMSfMN4hvR5COfyrYyNJ +4PGEMA4GA1UdDwEB/wQEAwIBBjCB0wYDVR0fBIHLMIHIMIGAoH6gfIZ6bGRhcDovL2RpcmVjdG9y +eS5kLXRydXN0Lm5ldC9DTj1ELVRSVVNUJTIwUm9vdCUyMENsYXNzJTIwMyUyMENBJTIwMiUyMDIw +MDksTz1ELVRydXN0JTIwR21iSCxDPURFP2NlcnRpZmljYXRlcmV2b2NhdGlvbmxpc3QwQ6BBoD+G +PWh0dHA6Ly93d3cuZC10cnVzdC5uZXQvY3JsL2QtdHJ1c3Rfcm9vdF9jbGFzc18zX2NhXzJfMjAw +OS5jcmwwDQYJKoZIhvcNAQELBQADggEBAH+X2zDI36ScfSF6gHDOFBJpiBSVYEQBrLLpME+bUMJm +2H6NMLVwMeniacfzcNsgFYbQDfC+rAF1hM5+n02/t2A7nPPKHeJeaNijnZflQGDSNiH+0LS4F9p0 +o3/U37CYAqxva2ssJSRyoWXuJVrl5jLn8t+rSfrzkGkj2wTZ51xY/GXUl77M/C4KzCUqNQT4YJEV +dT1B/yMfGchs64JTBKbkTCJNjYy6zltz7GRUUG3RnFX7acM2w4y8PIWmawomDeCTmGCufsYkl4ph +X5GOZpIJhzbNi5stPvZR1FDUWSi9g/LMKHtThm3YJohw1+qRzT65ysCQblrGXnRl11z+o+I= +-----END CERTIFICATE----- + +D-TRUST Root Class 3 CA 2 EV 2009 +================================= +-----BEGIN CERTIFICATE----- +MIIEQzCCAyugAwIBAgIDCYP0MA0GCSqGSIb3DQEBCwUAMFAxCzAJBgNVBAYTAkRFMRUwEwYDVQQK +DAxELVRydXN0IEdtYkgxKjAoBgNVBAMMIUQtVFJVU1QgUm9vdCBDbGFzcyAzIENBIDIgRVYgMjAw +OTAeFw0wOTExMDUwODUwNDZaFw0yOTExMDUwODUwNDZaMFAxCzAJBgNVBAYTAkRFMRUwEwYDVQQK +DAxELVRydXN0IEdtYkgxKjAoBgNVBAMMIUQtVFJVU1QgUm9vdCBDbGFzcyAzIENBIDIgRVYgMjAw +OTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJnxhDRwui+3MKCOvXwEz75ivJn9gpfS +egpnljgJ9hBOlSJzmY3aFS3nBfwZcyK3jpgAvDw9rKFs+9Z5JUut8Mxk2og+KbgPCdM03TP1YtHh +zRnp7hhPTFiu4h7WDFsVWtg6uMQYZB7jM7K1iXdODL/ZlGsTl28So/6ZqQTMFexgaDbtCHu39b+T +7WYxg4zGcTSHThfqr4uRjRxWQa4iN1438h3Z0S0NL2lRp75mpoo6Kr3HGrHhFPC+Oh25z1uxav60 +sUYgovseO3Dvk5h9jHOW8sXvhXCtKSb8HgQ+HKDYD8tSg2J87otTlZCpV6LqYQXY+U3EJ/pure35 +11H3a6UCAwEAAaOCASQwggEgMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFNOUikxiEyoZLsyv +cop9NteaHNxnMA4GA1UdDwEB/wQEAwIBBjCB3QYDVR0fBIHVMIHSMIGHoIGEoIGBhn9sZGFwOi8v +ZGlyZWN0b3J5LmQtdHJ1c3QubmV0L0NOPUQtVFJVU1QlMjBSb290JTIwQ2xhc3MlMjAzJTIwQ0El +MjAyJTIwRVYlMjAyMDA5LE89RC1UcnVzdCUyMEdtYkgsQz1ERT9jZXJ0aWZpY2F0ZXJldm9jYXRp +b25saXN0MEagRKBChkBodHRwOi8vd3d3LmQtdHJ1c3QubmV0L2NybC9kLXRydXN0X3Jvb3RfY2xh +c3NfM19jYV8yX2V2XzIwMDkuY3JsMA0GCSqGSIb3DQEBCwUAA4IBAQA07XtaPKSUiO8aEXUHL7P+ +PPoeUSbrh/Yp3uDx1MYkCenBz1UbtDDZzhr+BlGmFaQt77JLvyAoJUnRpjZ3NOhk31KxEcdzes05 +nsKtjHEh8lprr988TlWvsoRlFIm5d8sqMb7Po23Pb0iUMkZv53GMoKaEGTcH8gNFCSuGdXzfX2lX +ANtu2KZyIktQ1HWYVt+3GP9DQ1CuekR78HlR10M9p9OB0/DJT7naxpeG0ILD5EJt/rDiZE4OJudA +NCa1CInXCGNjOCd1HjPqbqjdn5lPdE2BiYBL3ZqXKVwvvoFBuYz/6n1gBp7N1z3TLqMVvKjmJuVv +w9y4AyHqnxbxLFS1 +-----END CERTIFICATE----- + +CA Disig Root R2 +================ +-----BEGIN CERTIFICATE----- +MIIFaTCCA1GgAwIBAgIJAJK4iNuwisFjMA0GCSqGSIb3DQEBCwUAMFIxCzAJBgNVBAYTAlNLMRMw +EQYDVQQHEwpCcmF0aXNsYXZhMRMwEQYDVQQKEwpEaXNpZyBhLnMuMRkwFwYDVQQDExBDQSBEaXNp +ZyBSb290IFIyMB4XDTEyMDcxOTA5MTUzMFoXDTQyMDcxOTA5MTUzMFowUjELMAkGA1UEBhMCU0sx +EzARBgNVBAcTCkJyYXRpc2xhdmExEzARBgNVBAoTCkRpc2lnIGEucy4xGTAXBgNVBAMTEENBIERp +c2lnIFJvb3QgUjIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCio8QACdaFXS1tFPbC +w3OeNcJxVX6B+6tGUODBfEl45qt5WDza/3wcn9iXAng+a0EE6UG9vgMsRfYvZNSrXaNHPWSb6Wia +xswbP7q+sos0Ai6YVRn8jG+qX9pMzk0DIaPY0jSTVpbLTAwAFjxfGs3Ix2ymrdMxp7zo5eFm1tL7 +A7RBZckQrg4FY8aAamkw/dLukO8NJ9+flXP04SXabBbeQTg06ov80egEFGEtQX6sx3dOy1FU+16S +GBsEWmjGycT6txOgmLcRK7fWV8x8nhfRyyX+hk4kLlYMeE2eARKmK6cBZW58Yh2EhN/qwGu1pSqV +g8NTEQxzHQuyRpDRQjrOQG6Vrf/GlK1ul4SOfW+eioANSW1z4nuSHsPzwfPrLgVv2RvPN3YEyLRa +5Beny912H9AZdugsBbPWnDTYltxhh5EF5EQIM8HauQhl1K6yNg3ruji6DOWbnuuNZt2Zz9aJQfYE +koopKW1rOhzndX0CcQ7zwOe9yxndnWCywmZgtrEE7snmhrmaZkCo5xHtgUUDi/ZnWejBBhG93c+A +Ak9lQHhcR1DIm+YfgXvkRKhbhZri3lrVx/k6RGZL5DJUfORsnLMOPReisjQS1n6yqEm70XooQL6i +Fh/f5DcfEXP7kAplQ6INfPgGAVUzfbANuPT1rqVCV3w2EYx7XsQDnYx5nQIDAQABo0IwQDAPBgNV +HRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUtZn4r7CU9eMg1gqtzk5WpC5u +Qu0wDQYJKoZIhvcNAQELBQADggIBACYGXnDnZTPIgm7ZnBc6G3pmsgH2eDtpXi/q/075KMOYKmFM +tCQSin1tERT3nLXK5ryeJ45MGcipvXrA1zYObYVybqjGom32+nNjf7xueQgcnYqfGopTpti72TVV +sRHFqQOzVju5hJMiXn7B9hJSi+osZ7z+Nkz1uM/Rs0mSO9MpDpkblvdhuDvEK7Z4bLQjb/D907Je +dR+Zlais9trhxTF7+9FGs9K8Z7RiVLoJ92Owk6Ka+elSLotgEqv89WBW7xBci8QaQtyDW2QOy7W8 +1k/BfDxujRNt+3vrMNDcTa/F1balTFtxyegxvug4BkihGuLq0t4SOVga/4AOgnXmt8kHbA7v/zjx +mHHEt38OFdAlab0inSvtBfZGR6ztwPDUO+Ls7pZbkBNOHlY667DvlruWIxG68kOGdGSVyCh13x01 +utI3gzhTODY7z2zp+WsO0PsE6E9312UBeIYMej4hYvF/Y3EMyZ9E26gnonW+boE+18DrG5gPcFw0 +sorMwIUY6256s/daoQe/qUKS82Ail+QUoQebTnbAjn39pCXHR+3/H3OszMOl6W8KjptlwlCFtaOg +UxLMVYdh84GuEEZhvUQhuMI9dM9+JDX6HAcOmz0iyu8xL4ysEr3vQCj8KWefshNPZiTEUxnpHikV +7+ZtsH8tZ/3zbBt1RqPlShfppNcL +-----END CERTIFICATE----- + +ACCVRAIZ1 +========= +-----BEGIN CERTIFICATE----- +MIIH0zCCBbugAwIBAgIIXsO3pkN/pOAwDQYJKoZIhvcNAQEFBQAwQjESMBAGA1UEAwwJQUNDVlJB +SVoxMRAwDgYDVQQLDAdQS0lBQ0NWMQ0wCwYDVQQKDARBQ0NWMQswCQYDVQQGEwJFUzAeFw0xMTA1 +MDUwOTM3MzdaFw0zMDEyMzEwOTM3MzdaMEIxEjAQBgNVBAMMCUFDQ1ZSQUlaMTEQMA4GA1UECwwH +UEtJQUNDVjENMAsGA1UECgwEQUNDVjELMAkGA1UEBhMCRVMwggIiMA0GCSqGSIb3DQEBAQUAA4IC +DwAwggIKAoICAQCbqau/YUqXry+XZpp0X9DZlv3P4uRm7x8fRzPCRKPfmt4ftVTdFXxpNRFvu8gM +jmoYHtiP2Ra8EEg2XPBjs5BaXCQ316PWywlxufEBcoSwfdtNgM3802/J+Nq2DoLSRYWoG2ioPej0 +RGy9ocLLA76MPhMAhN9KSMDjIgro6TenGEyxCQ0jVn8ETdkXhBilyNpAlHPrzg5XPAOBOp0KoVdD +aaxXbXmQeOW1tDvYvEyNKKGno6e6Ak4l0Squ7a4DIrhrIA8wKFSVf+DuzgpmndFALW4ir50awQUZ +0m/A8p/4e7MCQvtQqR0tkw8jq8bBD5L/0KIV9VMJcRz/RROE5iZe+OCIHAr8Fraocwa48GOEAqDG +WuzndN9wrqODJerWx5eHk6fGioozl2A3ED6XPm4pFdahD9GILBKfb6qkxkLrQaLjlUPTAYVtjrs7 +8yM2x/474KElB0iryYl0/wiPgL/AlmXz7uxLaL2diMMxs0Dx6M/2OLuc5NF/1OVYm3z61PMOm3WR +5LpSLhl+0fXNWhn8ugb2+1KoS5kE3fj5tItQo05iifCHJPqDQsGH+tUtKSpacXpkatcnYGMN285J +9Y0fkIkyF/hzQ7jSWpOGYdbhdQrqeWZ2iE9x6wQl1gpaepPluUsXQA+xtrn13k/c4LOsOxFwYIRK +Q26ZIMApcQrAZQIDAQABo4ICyzCCAscwfQYIKwYBBQUHAQEEcTBvMEwGCCsGAQUFBzAChkBodHRw +Oi8vd3d3LmFjY3YuZXMvZmlsZWFkbWluL0FyY2hpdm9zL2NlcnRpZmljYWRvcy9yYWl6YWNjdjEu +Y3J0MB8GCCsGAQUFBzABhhNodHRwOi8vb2NzcC5hY2N2LmVzMB0GA1UdDgQWBBTSh7Tj3zcnk1X2 +VuqB5TbMjB4/vTAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFNKHtOPfNyeTVfZW6oHlNsyM +Hj+9MIIBcwYDVR0gBIIBajCCAWYwggFiBgRVHSAAMIIBWDCCASIGCCsGAQUFBwICMIIBFB6CARAA +QQB1AHQAbwByAGkAZABhAGQAIABkAGUAIABDAGUAcgB0AGkAZgBpAGMAYQBjAGkA8wBuACAAUgBh +AO0AegAgAGQAZQAgAGwAYQAgAEEAQwBDAFYAIAAoAEEAZwBlAG4AYwBpAGEAIABkAGUAIABUAGUA +YwBuAG8AbABvAGcA7QBhACAAeQAgAEMAZQByAHQAaQBmAGkAYwBhAGMAaQDzAG4AIABFAGwAZQBj +AHQAcgDzAG4AaQBjAGEALAAgAEMASQBGACAAUQA0ADYAMAAxADEANQA2AEUAKQAuACAAQwBQAFMA +IABlAG4AIABoAHQAdABwADoALwAvAHcAdwB3AC4AYQBjAGMAdgAuAGUAczAwBggrBgEFBQcCARYk +aHR0cDovL3d3dy5hY2N2LmVzL2xlZ2lzbGFjaW9uX2MuaHRtMFUGA1UdHwROMEwwSqBIoEaGRGh0 +dHA6Ly93d3cuYWNjdi5lcy9maWxlYWRtaW4vQXJjaGl2b3MvY2VydGlmaWNhZG9zL3JhaXphY2N2 +MV9kZXIuY3JsMA4GA1UdDwEB/wQEAwIBBjAXBgNVHREEEDAOgQxhY2N2QGFjY3YuZXMwDQYJKoZI +hvcNAQEFBQADggIBAJcxAp/n/UNnSEQU5CmH7UwoZtCPNdpNYbdKl02125DgBS4OxnnQ8pdpD70E +R9m+27Up2pvZrqmZ1dM8MJP1jaGo/AaNRPTKFpV8M9xii6g3+CfYCS0b78gUJyCpZET/LtZ1qmxN +YEAZSUNUY9rizLpm5U9EelvZaoErQNV/+QEnWCzI7UiRfD+mAM/EKXMRNt6GGT6d7hmKG9Ww7Y49 +nCrADdg9ZuM8Db3VlFzi4qc1GwQA9j9ajepDvV+JHanBsMyZ4k0ACtrJJ1vnE5Bc5PUzolVt3OAJ +TS+xJlsndQAJxGJ3KQhfnlmstn6tn1QwIgPBHnFk/vk4CpYY3QIUrCPLBhwepH2NDd4nQeit2hW3 +sCPdK6jT2iWH7ehVRE2I9DZ+hJp4rPcOVkkO1jMl1oRQQmwgEh0q1b688nCBpHBgvgW1m54ERL5h +I6zppSSMEYCUWqKiuUnSwdzRp+0xESyeGabu4VXhwOrPDYTkF7eifKXeVSUG7szAh1xA2syVP1Xg +Nce4hL60Xc16gwFy7ofmXx2utYXGJt/mwZrpHgJHnyqobalbz+xFd3+YJ5oyXSrjhO7FmGYvliAd +3djDJ9ew+f7Zfc3Qn48LFFhRny+Lwzgt3uiP1o2HpPVWQxaZLPSkVrQ0uGE3ycJYgBugl6H8WY3p +EfbRD0tVNEYqi4Y7 +-----END CERTIFICATE----- + +TWCA Global Root CA +=================== +-----BEGIN CERTIFICATE----- +MIIFQTCCAymgAwIBAgICDL4wDQYJKoZIhvcNAQELBQAwUTELMAkGA1UEBhMCVFcxEjAQBgNVBAoT +CVRBSVdBTi1DQTEQMA4GA1UECxMHUm9vdCBDQTEcMBoGA1UEAxMTVFdDQSBHbG9iYWwgUm9vdCBD +QTAeFw0xMjA2MjcwNjI4MzNaFw0zMDEyMzExNTU5NTlaMFExCzAJBgNVBAYTAlRXMRIwEAYDVQQK +EwlUQUlXQU4tQ0ExEDAOBgNVBAsTB1Jvb3QgQ0ExHDAaBgNVBAMTE1RXQ0EgR2xvYmFsIFJvb3Qg +Q0EwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCwBdvI64zEbooh745NnHEKH1Jw7W2C +nJfF10xORUnLQEK1EjRsGcJ0pDFfhQKX7EMzClPSnIyOt7h52yvVavKOZsTuKwEHktSz0ALfUPZV +r2YOy+BHYC8rMjk1Ujoog/h7FsYYuGLWRyWRzvAZEk2tY/XTP3VfKfChMBwqoJimFb3u/Rk28OKR +Q4/6ytYQJ0lM793B8YVwm8rqqFpD/G2Gb3PpN0Wp8DbHzIh1HrtsBv+baz4X7GGqcXzGHaL3SekV +tTzWoWH1EfcFbx39Eb7QMAfCKbAJTibc46KokWofwpFFiFzlmLhxpRUZyXx1EcxwdE8tmx2RRP1W +KKD+u4ZqyPpcC1jcxkt2yKsi2XMPpfRaAok/T54igu6idFMqPVMnaR1sjjIsZAAmY2E2TqNGtz99 +sy2sbZCilaLOz9qC5wc0GZbpuCGqKX6mOL6OKUohZnkfs8O1CWfe1tQHRvMq2uYiN2DLgbYPoA/p +yJV/v1WRBXrPPRXAb94JlAGD1zQbzECl8LibZ9WYkTunhHiVJqRaCPgrdLQABDzfuBSO6N+pjWxn +kjMdwLfS7JLIvgm/LCkFbwJrnu+8vyq8W8BQj0FwcYeyTbcEqYSjMq+u7msXi7Kx/mzhkIyIqJdI +zshNy/MGz19qCkKxHh53L46g5pIOBvwFItIm4TFRfTLcDwIDAQABoyMwITAOBgNVHQ8BAf8EBAMC +AQYwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAgEAXzSBdu+WHdXltdkCY4QWwa6g +cFGn90xHNcgL1yg9iXHZqjNB6hQbbCEAwGxCGX6faVsgQt+i0trEfJdLjbDorMjupWkEmQqSpqsn +LhpNgb+E1HAerUf+/UqdM+DyucRFCCEK2mlpc3INvjT+lIutwx4116KD7+U4x6WFH6vPNOw/KP4M +8VeGTslV9xzU2KV9Bnpv1d8Q34FOIWWxtuEXeZVFBs5fzNxGiWNoRI2T9GRwoD2dKAXDOXC4Ynsg +/eTb6QihuJ49CcdP+yz4k3ZB3lLg4VfSnQO8d57+nile98FRYB/e2guyLXW3Q0iT5/Z5xoRdgFlg +lPx4mI88k1HtQJAH32RjJMtOcQWh15QaiDLxInQirqWm2BJpTGCjAu4r7NRjkgtevi92a6O2JryP +A9gK8kxkRr05YuWW6zRjESjMlfGt7+/cgFhI6Uu46mWs6fyAtbXIRfmswZ/ZuepiiI7E8UuDEq3m +i4TWnsLrgxifarsbJGAzcMzs9zLzXNl5fe+epP7JI8Mk7hWSsT2RTyaGvWZzJBPqpK5jwa19hAM8 +EHiGG3njxPPyBJUgriOCxLM6AGK/5jYk4Ve6xx6QddVfP5VhK8E7zeWzaGHQRiapIVJpLesux+t3 +zqY6tQMzT3bR51xUAV3LePTJDL/PEo4XLSNolOer/qmyKwbQBM0= +-----END CERTIFICATE----- + +TeliaSonera Root CA v1 +====================== +-----BEGIN CERTIFICATE----- +MIIFODCCAyCgAwIBAgIRAJW+FqD3LkbxezmCcvqLzZYwDQYJKoZIhvcNAQEFBQAwNzEUMBIGA1UE +CgwLVGVsaWFTb25lcmExHzAdBgNVBAMMFlRlbGlhU29uZXJhIFJvb3QgQ0EgdjEwHhcNMDcxMDE4 +MTIwMDUwWhcNMzIxMDE4MTIwMDUwWjA3MRQwEgYDVQQKDAtUZWxpYVNvbmVyYTEfMB0GA1UEAwwW +VGVsaWFTb25lcmEgUm9vdCBDQSB2MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMK+ +6yfwIaPzaSZVfp3FVRaRXP3vIb9TgHot0pGMYzHw7CTww6XScnwQbfQ3t+XmfHnqjLWCi65ItqwA +3GV17CpNX8GH9SBlK4GoRz6JI5UwFpB/6FcHSOcZrr9FZ7E3GwYq/t75rH2D+1665I+XZ75Ljo1k +B1c4VWk0Nj0TSO9P4tNmHqTPGrdeNjPUtAa9GAH9d4RQAEX1jF3oI7x+/jXh7VB7qTCNGdMJjmhn +Xb88lxhTuylixcpecsHHltTbLaC0H2kD7OriUPEMPPCs81Mt8Bz17Ww5OXOAFshSsCPN4D7c3TxH +oLs1iuKYaIu+5b9y7tL6pe0S7fyYGKkmdtwoSxAgHNN/Fnct7W+A90m7UwW7XWjH1Mh1Fj+JWov3 +F0fUTPHSiXk+TT2YqGHeOh7S+F4D4MHJHIzTjU3TlTazN19jY5szFPAtJmtTfImMMsJu7D0hADnJ +oWjiUIMusDor8zagrC/kb2HCUQk5PotTubtn2txTuXZZNp1D5SDgPTJghSJRt8czu90VL6R4pgd7 +gUY2BIbdeTXHlSw7sKMXNeVzH7RcWe/a6hBle3rQf5+ztCo3O3CLm1u5K7fsslESl1MpWtTwEhDc +TwK7EpIvYtQ/aUN8Ddb8WHUBiJ1YFkveupD/RwGJBmr2X7KQarMCpgKIv7NHfirZ1fpoeDVNAgMB +AAGjPzA9MA8GA1UdEwEB/wQFMAMBAf8wCwYDVR0PBAQDAgEGMB0GA1UdDgQWBBTwj1k4ALP1j5qW +DNXr+nuqF+gTEjANBgkqhkiG9w0BAQUFAAOCAgEAvuRcYk4k9AwI//DTDGjkk0kiP0Qnb7tt3oNm +zqjMDfz1mgbldxSR651Be5kqhOX//CHBXfDkH1e3damhXwIm/9fH907eT/j3HEbAek9ALCI18Bmx +0GtnLLCo4MBANzX2hFxc469CeP6nyQ1Q6g2EdvZR74NTxnr/DlZJLo961gzmJ1TjTQpgcmLNkQfW +pb/ImWvtxBnmq0wROMVvMeJuScg/doAmAyYp4Db29iBT4xdwNBedY2gea+zDTYa4EzAvXUYNR0PV +G6pZDrlcjQZIrXSHX8f8MVRBE+LHIQ6e4B4N4cB7Q4WQxYpYxmUKeFfyxiMPAdkgS94P+5KFdSpc +c41teyWRyu5FrgZLAMzTsVlQ2jqIOylDRl6XK1TOU2+NSueW+r9xDkKLfP0ooNBIytrEgUy7onOT +JsjrDNYmiLbAJM+7vVvrdX3pCI6GMyx5dwlppYn8s3CQh3aP0yK7Qs69cwsgJirQmz1wHiRszYd2 +qReWt88NkvuOGKmYSdGe/mBEciG5Ge3C9THxOUiIkCR1VBatzvT4aRRkOfujuLpwQMcnHL/EVlP6 +Y2XQ8xwOFvVrhlhNGNTkDY6lnVuR3HYkUD/GKvvZt5y11ubQ2egZixVxSK236thZiNSQvxaz2ems +WWFUyBy6ysHK4bkgTI86k4mloMy/0/Z1pHWWbVY= +-----END CERTIFICATE----- + +E-Tugra Certification Authority +=============================== +-----BEGIN CERTIFICATE----- +MIIGSzCCBDOgAwIBAgIIamg+nFGby1MwDQYJKoZIhvcNAQELBQAwgbIxCzAJBgNVBAYTAlRSMQ8w +DQYDVQQHDAZBbmthcmExQDA+BgNVBAoMN0UtVHXEn3JhIEVCRyBCaWxpxZ9pbSBUZWtub2xvamls +ZXJpIHZlIEhpem1ldGxlcmkgQS7Fni4xJjAkBgNVBAsMHUUtVHVncmEgU2VydGlmaWthc3lvbiBN +ZXJrZXppMSgwJgYDVQQDDB9FLVR1Z3JhIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTEzMDMw +NTEyMDk0OFoXDTIzMDMwMzEyMDk0OFowgbIxCzAJBgNVBAYTAlRSMQ8wDQYDVQQHDAZBbmthcmEx +QDA+BgNVBAoMN0UtVHXEn3JhIEVCRyBCaWxpxZ9pbSBUZWtub2xvamlsZXJpIHZlIEhpem1ldGxl +cmkgQS7Fni4xJjAkBgNVBAsMHUUtVHVncmEgU2VydGlmaWthc3lvbiBNZXJrZXppMSgwJgYDVQQD +DB9FLVR1Z3JhIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIICIjANBgkqhkiG9w0BAQEFAAOCAg8A +MIICCgKCAgEA4vU/kwVRHoViVF56C/UYB4Oufq9899SKa6VjQzm5S/fDxmSJPZQuVIBSOTkHS0vd +hQd2h8y/L5VMzH2nPbxHD5hw+IyFHnSOkm0bQNGZDbt1bsipa5rAhDGvykPL6ys06I+XawGb1Q5K +CKpbknSFQ9OArqGIW66z6l7LFpp3RMih9lRozt6Plyu6W0ACDGQXwLWTzeHxE2bODHnv0ZEoq1+g +ElIwcxmOj+GMB6LDu0rw6h8VqO4lzKRG+Bsi77MOQ7osJLjFLFzUHPhdZL3Dk14opz8n8Y4e0ypQ +BaNV2cvnOVPAmJ6MVGKLJrD3fY185MaeZkJVgkfnsliNZvcHfC425lAcP9tDJMW/hkd5s3kc91r0 +E+xs+D/iWR+V7kI+ua2oMoVJl0b+SzGPWsutdEcf6ZG33ygEIqDUD13ieU/qbIWGvaimzuT6w+Gz +rt48Ue7LE3wBf4QOXVGUnhMMti6lTPk5cDZvlsouDERVxcr6XQKj39ZkjFqzAQqptQpHF//vkUAq +jqFGOjGY5RH8zLtJVor8udBhmm9lbObDyz51Sf6Pp+KJxWfXnUYTTjF2OySznhFlhqt/7x3U+Lzn +rFpct1pHXFXOVbQicVtbC/DP3KBhZOqp12gKY6fgDT+gr9Oq0n7vUaDmUStVkhUXU8u3Zg5mTPj5 +dUyQ5xJwx0UCAwEAAaNjMGEwHQYDVR0OBBYEFC7j27JJ0JxUeVz6Jyr+zE7S6E5UMA8GA1UdEwEB +/wQFMAMBAf8wHwYDVR0jBBgwFoAULuPbsknQnFR5XPonKv7MTtLoTlQwDgYDVR0PAQH/BAQDAgEG +MA0GCSqGSIb3DQEBCwUAA4ICAQAFNzr0TbdF4kV1JI+2d1LoHNgQk2Xz8lkGpD4eKexd0dCrfOAK +kEh47U6YA5n+KGCRHTAduGN8qOY1tfrTYXbm1gdLymmasoR6d5NFFxWfJNCYExL/u6Au/U5Mh/jO +XKqYGwXgAEZKgoClM4so3O0409/lPun++1ndYYRP0lSWE2ETPo+Aab6TR7U1Q9Jauz1c77NCR807 +VRMGsAnb/WP2OogKmW9+4c4bU2pEZiNRCHu8W1Ki/QY3OEBhj0qWuJA3+GbHeJAAFS6LrVE1Uweo +a2iu+U48BybNCAVwzDk/dr2l02cmAYamU9JgO3xDf1WKvJUawSg5TB9D0pH0clmKuVb8P7Sd2nCc +dlqMQ1DujjByTd//SffGqWfZbawCEeI6FiWnWAjLb1NBnEg4R2gz0dfHj9R0IdTDBZB6/86WiLEV +KV0jq9BgoRJP3vQXzTLlyb/IQ639Lo7xr+L0mPoSHyDYwKcMhcWQ9DstliaxLL5Mq+ux0orJ23gT +Dx4JnW2PAJ8C2sH6H3p6CcRK5ogql5+Ji/03X186zjhZhkuvcQu02PJwT58yE+Owp1fl2tpDy4Q0 +8ijE6m30Ku/Ba3ba+367hTzSU8JNvnHhRdH9I2cNE3X7z2VnIp2usAnRCf8dNL/+I5c30jn6PQ0G +C7TbO6Orb1wdtn7os4I07QZcJA== +-----END CERTIFICATE----- + +T-TeleSec GlobalRoot Class 2 +============================ +-----BEGIN CERTIFICATE----- +MIIDwzCCAqugAwIBAgIBATANBgkqhkiG9w0BAQsFADCBgjELMAkGA1UEBhMCREUxKzApBgNVBAoM +IlQtU3lzdGVtcyBFbnRlcnByaXNlIFNlcnZpY2VzIEdtYkgxHzAdBgNVBAsMFlQtU3lzdGVtcyBU +cnVzdCBDZW50ZXIxJTAjBgNVBAMMHFQtVGVsZVNlYyBHbG9iYWxSb290IENsYXNzIDIwHhcNMDgx +MDAxMTA0MDE0WhcNMzMxMDAxMjM1OTU5WjCBgjELMAkGA1UEBhMCREUxKzApBgNVBAoMIlQtU3lz +dGVtcyBFbnRlcnByaXNlIFNlcnZpY2VzIEdtYkgxHzAdBgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBD +ZW50ZXIxJTAjBgNVBAMMHFQtVGVsZVNlYyBHbG9iYWxSb290IENsYXNzIDIwggEiMA0GCSqGSIb3 +DQEBAQUAA4IBDwAwggEKAoIBAQCqX9obX+hzkeXaXPSi5kfl82hVYAUdAqSzm1nzHoqvNK38DcLZ +SBnuaY/JIPwhqgcZ7bBcrGXHX+0CfHt8LRvWurmAwhiCFoT6ZrAIxlQjgeTNuUk/9k9uN0goOA/F +vudocP05l03Sx5iRUKrERLMjfTlH6VJi1hKTXrcxlkIF+3anHqP1wvzpesVsqXFP6st4vGCvx970 +2cu+fjOlbpSD8DT6IavqjnKgP6TeMFvvhk1qlVtDRKgQFRzlAVfFmPHmBiiRqiDFt1MmUUOyCxGV +WOHAD3bZwI18gfNycJ5v/hqO2V81xrJvNHy+SE/iWjnX2J14np+GPgNeGYtEotXHAgMBAAGjQjBA +MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBS/WSA2AHmgoCJrjNXy +YdK4LMuCSjANBgkqhkiG9w0BAQsFAAOCAQEAMQOiYQsfdOhyNsZt+U2e+iKo4YFWz827n+qrkRk4 +r6p8FU3ztqONpfSO9kSpp+ghla0+AGIWiPACuvxhI+YzmzB6azZie60EI4RYZeLbK4rnJVM3YlNf +vNoBYimipidx5joifsFvHZVwIEoHNN/q/xWA5brXethbdXwFeilHfkCoMRN3zUA7tFFHei4R40cR +3p1m0IvVVGb6g1XqfMIpiRvpb7PO4gWEyS8+eIVibslfwXhjdFjASBgMmTnrpMwatXlajRWc2BQN +9noHV8cigwUtPJslJj0Ys6lDfMjIq2SPDqO/nBudMNva0Bkuqjzx+zOAduTNrRlPBSeOE6Fuwg== +-----END CERTIFICATE----- + +Atos TrustedRoot 2011 +===================== +-----BEGIN CERTIFICATE----- +MIIDdzCCAl+gAwIBAgIIXDPLYixfszIwDQYJKoZIhvcNAQELBQAwPDEeMBwGA1UEAwwVQXRvcyBU +cnVzdGVkUm9vdCAyMDExMQ0wCwYDVQQKDARBdG9zMQswCQYDVQQGEwJERTAeFw0xMTA3MDcxNDU4 +MzBaFw0zMDEyMzEyMzU5NTlaMDwxHjAcBgNVBAMMFUF0b3MgVHJ1c3RlZFJvb3QgMjAxMTENMAsG +A1UECgwEQXRvczELMAkGA1UEBhMCREUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCV +hTuXbyo7LjvPpvMpNb7PGKw+qtn4TaA+Gke5vJrf8v7MPkfoepbCJI419KkM/IL9bcFyYie96mvr +54rMVD6QUM+A1JX76LWC1BTFtqlVJVfbsVD2sGBkWXppzwO3bw2+yj5vdHLqqjAqc2K+SZFhyBH+ +DgMq92og3AIVDV4VavzjgsG1xZ1kCWyjWZgHJ8cblithdHFsQ/H3NYkQ4J7sVaE3IqKHBAUsR320 +HLliKWYoyrfhk/WklAOZuXCFteZI6o1Q/NnezG8HDt0Lcp2AMBYHlT8oDv3FdU9T1nSatCQujgKR +z3bFmx5VdJx4IbHwLfELn8LVlhgf8FQieowHAgMBAAGjfTB7MB0GA1UdDgQWBBSnpQaxLKYJYO7R +l+lwrrw7GWzbITAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFKelBrEspglg7tGX6XCuvDsZ +bNshMBgGA1UdIAQRMA8wDQYLKwYBBAGwLQMEAQEwDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEB +CwUAA4IBAQAmdzTblEiGKkGdLD4GkGDEjKwLVLgfuXvTBznk+j57sj1O7Z8jvZfza1zv7v1Apt+h +k6EKhqzvINB5Ab149xnYJDE0BAGmuhWawyfc2E8PzBhj/5kPDpFrdRbhIfzYJsdHt6bPWHJxfrrh +TZVHO8mvbaG0weyJ9rQPOLXiZNwlz6bb65pcmaHFCN795trV1lpFDMS3wrUU77QR/w4VtfX128a9 +61qn8FYiqTxlVMYVqL2Gns2Dlmh6cYGJ4Qvh6hEbaAjMaZ7snkGeRDImeuKHCnE96+RapNLbxc3G +3mB/ufNPRJLvKrcYPqcZ2Qt9sTdBQrC6YB3y/gkRsPCHe6ed +-----END CERTIFICATE----- + +QuoVadis Root CA 1 G3 +===================== +-----BEGIN CERTIFICATE----- +MIIFYDCCA0igAwIBAgIUeFhfLq0sGUvjNwc1NBMotZbUZZMwDQYJKoZIhvcNAQELBQAwSDELMAkG +A1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxHjAcBgNVBAMTFVF1b1ZhZGlzIFJv +b3QgQ0EgMSBHMzAeFw0xMjAxMTIxNzI3NDRaFw00MjAxMTIxNzI3NDRaMEgxCzAJBgNVBAYTAkJN +MRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDEg +RzMwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCgvlAQjunybEC0BJyFuTHK3C3kEakE +PBtVwedYMB0ktMPvhd6MLOHBPd+C5k+tR4ds7FtJwUrVu4/sh6x/gpqG7D0DmVIB0jWerNrwU8lm +PNSsAgHaJNM7qAJGr6Qc4/hzWHa39g6QDbXwz8z6+cZM5cOGMAqNF34168Xfuw6cwI2H44g4hWf6 +Pser4BOcBRiYz5P1sZK0/CPTz9XEJ0ngnjybCKOLXSoh4Pw5qlPafX7PGglTvF0FBM+hSo+LdoIN +ofjSxxR3W5A2B4GbPgb6Ul5jxaYA/qXpUhtStZI5cgMJYr2wYBZupt0lwgNm3fME0UDiTouG9G/l +g6AnhF4EwfWQvTA9xO+oabw4m6SkltFi2mnAAZauy8RRNOoMqv8hjlmPSlzkYZqn0ukqeI1RPToV +7qJZjqlc3sX5kCLliEVx3ZGZbHqfPT2YfF72vhZooF6uCyP8Wg+qInYtyaEQHeTTRCOQiJ/GKubX +9ZqzWB4vMIkIG1SitZgj7Ah3HJVdYdHLiZxfokqRmu8hqkkWCKi9YSgxyXSthfbZxbGL0eUQMk1f +iyA6PEkfM4VZDdvLCXVDaXP7a3F98N/ETH3Goy7IlXnLc6KOTk0k+17kBL5yG6YnLUlamXrXXAkg +t3+UuU/xDRxeiEIbEbfnkduebPRq34wGmAOtzCjvpUfzUwIDAQABo0IwQDAPBgNVHRMBAf8EBTAD +AQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUo5fW816iEOGrRZ88F2Q87gFwnMwwDQYJKoZI +hvcNAQELBQADggIBABj6W3X8PnrHX3fHyt/PX8MSxEBd1DKquGrX1RUVRpgjpeaQWxiZTOOtQqOC +MTaIzen7xASWSIsBx40Bz1szBpZGZnQdT+3Btrm0DWHMY37XLneMlhwqI2hrhVd2cDMT/uFPpiN3 +GPoajOi9ZcnPP/TJF9zrx7zABC4tRi9pZsMbj/7sPtPKlL92CiUNqXsCHKnQO18LwIE6PWThv6ct +Tr1NxNgpxiIY0MWscgKCP6o6ojoilzHdCGPDdRS5YCgtW2jgFqlmgiNR9etT2DGbe+m3nUvriBbP ++V04ikkwj+3x6xn0dxoxGE1nVGwvb2X52z3sIexe9PSLymBlVNFxZPT5pqOBMzYzcfCkeF9OrYMh +3jRJjehZrJ3ydlo28hP0r+AJx2EqbPfgna67hkooby7utHnNkDPDs3b69fBsnQGQ+p6Q9pxyz0fa +wx/kNSBT8lTR32GDpgLiJTjehTItXnOQUl1CxM49S+H5GYQd1aJQzEH7QRTDvdbJWqNjZgKAvQU6 +O0ec7AAmTPWIUb+oI38YB7AL7YsmoWTTYUrrXJ/es69nA7Mf3W1daWhpq1467HxpvMc7hU6eFbm0 +FU/DlXpY18ls6Wy58yljXrQs8C097Vpl4KlbQMJImYFtnh8GKjwStIsPm6Ik8KaN1nrgS7ZklmOV +hMJKzRwuJIczYOXD +-----END CERTIFICATE----- + +QuoVadis Root CA 2 G3 +===================== +-----BEGIN CERTIFICATE----- +MIIFYDCCA0igAwIBAgIURFc0JFuBiZs18s64KztbpybwdSgwDQYJKoZIhvcNAQELBQAwSDELMAkG +A1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxHjAcBgNVBAMTFVF1b1ZhZGlzIFJv +b3QgQ0EgMiBHMzAeFw0xMjAxMTIxODU5MzJaFw00MjAxMTIxODU5MzJaMEgxCzAJBgNVBAYTAkJN +MRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDIg +RzMwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQChriWyARjcV4g/Ruv5r+LrI3HimtFh +ZiFfqq8nUeVuGxbULX1QsFN3vXg6YOJkApt8hpvWGo6t/x8Vf9WVHhLL5hSEBMHfNrMWn4rjyduY +NM7YMxcoRvynyfDStNVNCXJJ+fKH46nafaF9a7I6JaltUkSs+L5u+9ymc5GQYaYDFCDy54ejiK2t +oIz/pgslUiXnFgHVy7g1gQyjO/Dh4fxaXc6AcW34Sas+O7q414AB+6XrW7PFXmAqMaCvN+ggOp+o +MiwMzAkd056OXbxMmO7FGmh77FOm6RQ1o9/NgJ8MSPsc9PG/Srj61YxxSscfrf5BmrODXfKEVu+l +V0POKa2Mq1W/xPtbAd0jIaFYAI7D0GoT7RPjEiuA3GfmlbLNHiJuKvhB1PLKFAeNilUSxmn1uIZo +L1NesNKqIcGY5jDjZ1XHm26sGahVpkUG0CM62+tlXSoREfA7T8pt9DTEceT/AFr2XK4jYIVz8eQQ +sSWu1ZK7E8EM4DnatDlXtas1qnIhO4M15zHfeiFuuDIIfR0ykRVKYnLP43ehvNURG3YBZwjgQQvD +6xVu+KQZ2aKrr+InUlYrAoosFCT5v0ICvybIxo/gbjh9Uy3l7ZizlWNof/k19N+IxWA1ksB8aRxh +lRbQ694Lrz4EEEVlWFA4r0jyWbYW8jwNkALGcC4BrTwV1wIDAQABo0IwQDAPBgNVHRMBAf8EBTAD +AQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQU7edvdlq/YOxJW8ald7tyFnGbxD0wDQYJKoZI +hvcNAQELBQADggIBAJHfgD9DCX5xwvfrs4iP4VGyvD11+ShdyLyZm3tdquXK4Qr36LLTn91nMX66 +AarHakE7kNQIXLJgapDwyM4DYvmL7ftuKtwGTTwpD4kWilhMSA/ohGHqPHKmd+RCroijQ1h5fq7K +pVMNqT1wvSAZYaRsOPxDMuHBR//47PERIjKWnML2W2mWeyAMQ0GaW/ZZGYjeVYg3UQt4XAoeo0L9 +x52ID8DyeAIkVJOviYeIyUqAHerQbj5hLja7NQ4nlv1mNDthcnPxFlxHBlRJAHpYErAK74X9sbgz +dWqTHBLmYF5vHX/JHyPLhGGfHoJE+V+tYlUkmlKY7VHnoX6XOuYvHxHaU4AshZ6rNRDbIl9qxV6X +U/IyAgkwo1jwDQHVcsaxfGl7w/U2Rcxhbl5MlMVerugOXou/983g7aEOGzPuVBj+D77vfoRrQ+Nw +mNtddbINWQeFFSM51vHfqSYP1kjHs6Yi9TM3WpVHn3u6GBVv/9YUZINJ0gpnIdsPNWNgKCLjsZWD +zYWm3S8P52dSbrsvhXz1SnPnxT7AvSESBT/8twNJAlvIJebiVDj1eYeMHVOyToV7BjjHLPj4sHKN +JeV3UvQDHEimUF+IIDBu8oJDqz2XhOdT+yHBTw8imoa4WSr2Rz0ZiC3oheGe7IUIarFsNMkd7Egr +O3jtZsSOeWmD3n+M +-----END CERTIFICATE----- + +QuoVadis Root CA 3 G3 +===================== +-----BEGIN CERTIFICATE----- +MIIFYDCCA0igAwIBAgIULvWbAiin23r/1aOp7r0DoM8Sah0wDQYJKoZIhvcNAQELBQAwSDELMAkG +A1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxHjAcBgNVBAMTFVF1b1ZhZGlzIFJv +b3QgQ0EgMyBHMzAeFw0xMjAxMTIyMDI2MzJaFw00MjAxMTIyMDI2MzJaMEgxCzAJBgNVBAYTAkJN +MRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDMg +RzMwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCzyw4QZ47qFJenMioKVjZ/aEzHs286 +IxSR/xl/pcqs7rN2nXrpixurazHb+gtTTK/FpRp5PIpM/6zfJd5O2YIyC0TeytuMrKNuFoM7pmRL +Mon7FhY4futD4tN0SsJiCnMK3UmzV9KwCoWdcTzeo8vAMvMBOSBDGzXRU7Ox7sWTaYI+FrUoRqHe +6okJ7UO4BUaKhvVZR74bbwEhELn9qdIoyhA5CcoTNs+cra1AdHkrAj80//ogaX3T7mH1urPnMNA3 +I4ZyYUUpSFlob3emLoG+B01vr87ERRORFHAGjx+f+IdpsQ7vw4kZ6+ocYfx6bIrc1gMLnia6Et3U +VDmrJqMz6nWB2i3ND0/kA9HvFZcba5DFApCTZgIhsUfei5pKgLlVj7WiL8DWM2fafsSntARE60f7 +5li59wzweyuxwHApw0BiLTtIadwjPEjrewl5qW3aqDCYz4ByA4imW0aucnl8CAMhZa634RylsSqi +Md5mBPfAdOhx3v89WcyWJhKLhZVXGqtrdQtEPREoPHtht+KPZ0/l7DxMYIBpVzgeAVuNVejH38DM +dyM0SXV89pgR6y3e7UEuFAUCf+D+IOs15xGsIs5XPd7JMG0QA4XN8f+MFrXBsj6IbGB/kE+V9/Yt +rQE5BwT6dYB9v0lQ7e/JxHwc64B+27bQ3RP+ydOc17KXqQIDAQABo0IwQDAPBgNVHRMBAf8EBTAD +AQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUxhfQvKjqAkPyGwaZXSuQILnXnOQwDQYJKoZI +hvcNAQELBQADggIBADRh2Va1EodVTd2jNTFGu6QHcrxfYWLopfsLN7E8trP6KZ1/AvWkyaiTt3px +KGmPc+FSkNrVvjrlt3ZqVoAh313m6Tqe5T72omnHKgqwGEfcIHB9UqM+WXzBusnIFUBhynLWcKzS +t/Ac5IYp8M7vaGPQtSCKFWGafoaYtMnCdvvMujAWzKNhxnQT5WvvoxXqA/4Ti2Tk08HS6IT7SdEQ +TXlm66r99I0xHnAUrdzeZxNMgRVhvLfZkXdxGYFgu/BYpbWcC/ePIlUnwEsBbTuZDdQdm2NnL9Du +DcpmvJRPpq3t/O5jrFc/ZSXPsoaP0Aj/uHYUbt7lJ+yreLVTubY/6CD50qi+YUbKh4yE8/nxoGib +Ih6BJpsQBJFxwAYf3KDTuVan45gtf4Od34wrnDKOMpTwATwiKp9Dwi7DmDkHOHv8XgBCH/MyJnmD +hPbl8MFREsALHgQjDFSlTC9JxUrRtm5gDWv8a4uFJGS3iQ6rJUdbPM9+Sb3H6QrG2vd+DhcI00iX +0HGS8A85PjRqHH3Y8iKuu2n0M7SmSFXRDw4m6Oy2Cy2nhTXN/VnIn9HNPlopNLk9hM6xZdRZkZFW +dSHBd575euFgndOtBBj0fOtek49TSiIp+EgrPk2GrFt/ywaZWWDYWGWVjUTR939+J399roD1B0y2 +PpxxVJkES/1Y+Zj0 +-----END CERTIFICATE----- + +DigiCert Assured ID Root G2 +=========================== +-----BEGIN CERTIFICATE----- +MIIDljCCAn6gAwIBAgIQC5McOtY5Z+pnI7/Dr5r0SzANBgkqhkiG9w0BAQsFADBlMQswCQYDVQQG +EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSQw +IgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgRzIwHhcNMTMwODAxMTIwMDAwWhcNMzgw +MTE1MTIwMDAwWjBlMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQL +ExB3d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgRzIw +ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDZ5ygvUj82ckmIkzTz+GoeMVSAn61UQbVH +35ao1K+ALbkKz3X9iaV9JPrjIgwrvJUXCzO/GU1BBpAAvQxNEP4HteccbiJVMWWXvdMX0h5i89vq +bFCMP4QMls+3ywPgym2hFEwbid3tALBSfK+RbLE4E9HpEgjAALAcKxHad3A2m67OeYfcgnDmCXRw +VWmvo2ifv922ebPynXApVfSr/5Vh88lAbx3RvpO704gqu52/clpWcTs/1PPRCv4o76Pu2ZmvA9OP +YLfykqGxvYmJHzDNw6YuYjOuFgJ3RFrngQo8p0Quebg/BLxcoIfhG69Rjs3sLPr4/m3wOnyqi+Rn +lTGNAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQWBBTO +w0q5mVXyuNtgv6l+vVa1lzan1jANBgkqhkiG9w0BAQsFAAOCAQEAyqVVjOPIQW5pJ6d1Ee88hjZv +0p3GeDgdaZaikmkuOGybfQTUiaWxMTeKySHMq2zNixya1r9I0jJmwYrA8y8678Dj1JGG0VDjA9tz +d29KOVPt3ibHtX2vK0LRdWLjSisCx1BL4GnilmwORGYQRI+tBev4eaymG+g3NJ1TyWGqolKvSnAW +hsI6yLETcDbYz+70CjTVW0z9B5yiutkBclzzTcHdDrEcDcRjvq30FPuJ7KJBDkzMyFdA0G4Dqs0M +jomZmWzwPDCvON9vvKO+KSAnq3T/EyJ43pdSVR6DtVQgA+6uwE9W3jfMw3+qBCe703e4YtsXfJwo +IhNzbM8m9Yop5w== +-----END CERTIFICATE----- + +DigiCert Assured ID Root G3 +=========================== +-----BEGIN CERTIFICATE----- +MIICRjCCAc2gAwIBAgIQC6Fa+h3foLVJRK/NJKBs7DAKBggqhkjOPQQDAzBlMQswCQYDVQQGEwJV +UzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSQwIgYD +VQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgRzMwHhcNMTMwODAxMTIwMDAwWhcNMzgwMTE1 +MTIwMDAwWjBlMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 +d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgRzMwdjAQ +BgcqhkjOPQIBBgUrgQQAIgNiAAQZ57ysRGXtzbg/WPuNsVepRC0FFfLvC/8QdJ+1YlJfZn4f5dwb +RXkLzMZTCp2NXQLZqVneAlr2lSoOjThKiknGvMYDOAdfVdp+CW7if17QRSAPWXYQ1qAk8C3eNvJs +KTmjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQWBBTL0L2p4ZgF +UaFNN6KDec6NHSrkhDAKBggqhkjOPQQDAwNnADBkAjAlpIFFAmsSS3V0T8gj43DydXLefInwz5Fy +YZ5eEJJZVrmDxxDnOOlYJjZ91eQ0hjkCMHw2U/Aw5WJjOpnitqM7mzT6HtoQknFekROn3aRukswy +1vUhZscv6pZjamVFkpUBtA== +-----END CERTIFICATE----- + +DigiCert Global Root G2 +======================= +-----BEGIN CERTIFICATE----- +MIIDjjCCAnagAwIBAgIQAzrx5qcRqaC7KGSxHQn65TANBgkqhkiG9w0BAQsFADBhMQswCQYDVQQG +EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSAw +HgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBHMjAeFw0xMzA4MDExMjAwMDBaFw0zODAxMTUx +MjAwMDBaMGExCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3 +dy5kaWdpY2VydC5jb20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEcyMIIBIjANBgkq +hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuzfNNNx7a8myaJCtSnX/RrohCgiN9RlUyfuI2/Ou8jqJ +kTx65qsGGmvPrC3oXgkkRLpimn7Wo6h+4FR1IAWsULecYxpsMNzaHxmx1x7e/dfgy5SDN67sH0NO +3Xss0r0upS/kqbitOtSZpLYl6ZtrAGCSYP9PIUkY92eQq2EGnI/yuum06ZIya7XzV+hdG82MHauV +BJVJ8zUtluNJbd134/tJS7SsVQepj5WztCO7TG1F8PapspUwtP1MVYwnSlcUfIKdzXOS0xZKBgyM +UNGPHgm+F6HmIcr9g+UQvIOlCsRnKPZzFBQ9RnbDhxSJITRNrw9FDKZJobq7nMWxM4MphQIDAQAB +o0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjAdBgNVHQ4EFgQUTiJUIBiV5uNu +5g/6+rkS7QYXjzkwDQYJKoZIhvcNAQELBQADggEBAGBnKJRvDkhj6zHd6mcY1Yl9PMWLSn/pvtsr +F9+wX3N3KjITOYFnQoQj8kVnNeyIv/iPsGEMNKSuIEyExtv4NeF22d+mQrvHRAiGfzZ0JFrabA0U +WTW98kndth/Jsw1HKj2ZL7tcu7XUIOGZX1NGFdtom/DzMNU+MeKNhJ7jitralj41E6Vf8PlwUHBH +QRFXGU7Aj64GxJUTFy8bJZ918rGOmaFvE7FBcf6IKshPECBV1/MUReXgRPTqh5Uykw7+U0b6LJ3/ +iyK5S9kJRaTepLiaWN0bfVKfjllDiIGknibVb63dDcY3fe0Dkhvld1927jyNxF1WW6LZZm6zNTfl +MrY= +-----END CERTIFICATE----- + +DigiCert Global Root G3 +======================= +-----BEGIN CERTIFICATE----- +MIICPzCCAcWgAwIBAgIQBVVWvPJepDU1w6QP1atFcjAKBggqhkjOPQQDAzBhMQswCQYDVQQGEwJV +UzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSAwHgYD +VQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBHMzAeFw0xMzA4MDExMjAwMDBaFw0zODAxMTUxMjAw +MDBaMGExCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5k +aWdpY2VydC5jb20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEczMHYwEAYHKoZIzj0C +AQYFK4EEACIDYgAE3afZu4q4C/sLfyHS8L6+c/MzXRq8NOrexpu80JX28MzQC7phW1FGfp4tn+6O +YwwX7Adw9c+ELkCDnOg/QW07rdOkFFk2eJ0DQ+4QE2xy3q6Ip6FrtUPOZ9wj/wMco+I+o0IwQDAP +BgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjAdBgNVHQ4EFgQUs9tIpPmhxdiuNkHMEWNp +Yim8S8YwCgYIKoZIzj0EAwMDaAAwZQIxAK288mw/EkrRLTnDCgmXc/SINoyIJ7vmiI1Qhadj+Z4y +3maTD/HMsQmP3Wyr+mt/oAIwOWZbwmSNuJ5Q3KjVSaLtx9zRSX8XAbjIho9OjIgrqJqpisXRAL34 +VOKa5Vt8sycX +-----END CERTIFICATE----- + +DigiCert Trusted Root G4 +======================== +-----BEGIN CERTIFICATE----- +MIIFkDCCA3igAwIBAgIQBZsbV56OITLiOQe9p3d1XDANBgkqhkiG9w0BAQwFADBiMQswCQYDVQQG +EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSEw +HwYDVQQDExhEaWdpQ2VydCBUcnVzdGVkIFJvb3QgRzQwHhcNMTMwODAxMTIwMDAwWhcNMzgwMTE1 +MTIwMDAwWjBiMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 +d3cuZGlnaWNlcnQuY29tMSEwHwYDVQQDExhEaWdpQ2VydCBUcnVzdGVkIFJvb3QgRzQwggIiMA0G +CSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC/5pBzaN675F1KPDAiMGkz7MKnJS7JIT3yithZwuEp +pz1Yq3aaza57G4QNxDAf8xukOBbrVsaXbR2rsnnyyhHS5F/WBTxSD1Ifxp4VpX6+n6lXFllVcq9o +k3DCsrp1mWpzMpTREEQQLt+C8weE5nQ7bXHiLQwb7iDVySAdYyktzuxeTsiT+CFhmzTrBcZe7Fsa +vOvJz82sNEBfsXpm7nfISKhmV1efVFiODCu3T6cw2Vbuyntd463JT17lNecxy9qTXtyOj4DatpGY +QJB5w3jHtrHEtWoYOAMQjdjUN6QuBX2I9YI+EJFwq1WCQTLX2wRzKm6RAXwhTNS8rhsDdV14Ztk6 +MUSaM0C/CNdaSaTC5qmgZ92kJ7yhTzm1EVgX9yRcRo9k98FpiHaYdj1ZXUJ2h4mXaXpI8OCiEhtm +mnTK3kse5w5jrubU75KSOp493ADkRSWJtppEGSt+wJS00mFt6zPZxd9LBADMfRyVw4/3IbKyEbe7 +f/LVjHAsQWCqsWMYRJUadmJ+9oCw++hkpjPRiQfhvbfmQ6QYuKZ3AeEPlAwhHbJUKSWJbOUOUlFH +dL4mrLZBdd56rF+NP8m800ERElvlEFDrMcXKchYiCd98THU/Y+whX8QgUWtvsauGi0/C1kVfnSD8 +oR7FwI+isX4KJpn15GkvmB0t9dmpsh3lGwIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1Ud +DwEB/wQEAwIBhjAdBgNVHQ4EFgQU7NfjgtJxXWRM3y5nP+e6mK4cD08wDQYJKoZIhvcNAQEMBQAD +ggIBALth2X2pbL4XxJEbw6GiAI3jZGgPVs93rnD5/ZpKmbnJeFwMDF/k5hQpVgs2SV1EY+CtnJYY +ZhsjDT156W1r1lT40jzBQ0CuHVD1UvyQO7uYmWlrx8GnqGikJ9yd+SeuMIW59mdNOj6PWTkiU0Tr +yF0Dyu1Qen1iIQqAyHNm0aAFYF/opbSnr6j3bTWcfFqK1qI4mfN4i/RN0iAL3gTujJtHgXINwBQy +7zBZLq7gcfJW5GqXb5JQbZaNaHqasjYUegbyJLkJEVDXCLG4iXqEI2FCKeWjzaIgQdfRnGTZ6iah +ixTXTBmyUEFxPT9NcCOGDErcgdLMMpSEDQgJlxxPwO5rIHQw0uA5NBCFIRUBCOhVMt5xSdkoF1BN +5r5N0XWs0Mr7QbhDparTwwVETyw2m+L64kW4I1NsBm9nVX9GtUw/bihaeSbSpKhil9Ie4u1Ki7wb +/UdKDd9nZn6yW0HQO+T0O/QEY+nvwlQAUaCKKsnOeMzV6ocEGLPOr0mIr/OSmbaz5mEP0oUA51Aa +5BuVnRmhuZyxm7EAHu/QD09CbMkKvO5D+jpxpchNJqU1/YldvIViHTLSoCtU7ZpXwdv6EM8Zt4tK +G48BtieVU+i2iW1bvGjUI+iLUaJW+fCmgKDWHrO8Dw9TdSmq6hN35N6MgSGtBxBHEa2HPQfRdbzP +82Z+ +-----END CERTIFICATE----- + +COMODO RSA Certification Authority +================================== +-----BEGIN CERTIFICATE----- +MIIF2DCCA8CgAwIBAgIQTKr5yttjb+Af907YWwOGnTANBgkqhkiG9w0BAQwFADCBhTELMAkGA1UE +BhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgG +A1UEChMRQ09NT0RPIENBIExpbWl0ZWQxKzApBgNVBAMTIkNPTU9ETyBSU0EgQ2VydGlmaWNhdGlv +biBBdXRob3JpdHkwHhcNMTAwMTE5MDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBhTELMAkGA1UEBhMC +R0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgGA1UE +ChMRQ09NT0RPIENBIExpbWl0ZWQxKzApBgNVBAMTIkNPTU9ETyBSU0EgQ2VydGlmaWNhdGlvbiBB +dXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCR6FSS0gpWsawNJN3Fz0Rn +dJkrN6N9I3AAcbxT38T6KhKPS38QVr2fcHK3YX/JSw8Xpz3jsARh7v8Rl8f0hj4K+j5c+ZPmNHrZ +FGvnnLOFoIJ6dq9xkNfs/Q36nGz637CC9BR++b7Epi9Pf5l/tfxnQ3K9DADWietrLNPtj5gcFKt+ +5eNu/Nio5JIk2kNrYrhV/erBvGy2i/MOjZrkm2xpmfh4SDBF1a3hDTxFYPwyllEnvGfDyi62a+pG +x8cgoLEfZd5ICLqkTqnyg0Y3hOvozIFIQ2dOciqbXL1MGyiKXCJ7tKuY2e7gUYPDCUZObT6Z+pUX +2nwzV0E8jVHtC7ZcryxjGt9XyD+86V3Em69FmeKjWiS0uqlWPc9vqv9JWL7wqP/0uK3pN/u6uPQL +OvnoQ0IeidiEyxPx2bvhiWC4jChWrBQdnArncevPDt09qZahSL0896+1DSJMwBGB7FY79tOi4lu3 +sgQiUpWAk2nojkxl8ZEDLXB0AuqLZxUpaVICu9ffUGpVRr+goyhhf3DQw6KqLCGqR84onAZFdr+C +GCe01a60y1Dma/RMhnEw6abfFobg2P9A3fvQQoh/ozM6LlweQRGBY84YcWsr7KaKtzFcOmpH4MN5 +WdYgGq/yapiqcrxXStJLnbsQ/LBMQeXtHT1eKJ2czL+zUdqnR+WEUwIDAQABo0IwQDAdBgNVHQ4E +FgQUu69+Aj36pvE8hI6t7jiY7NkyMtQwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8w +DQYJKoZIhvcNAQEMBQADggIBAArx1UaEt65Ru2yyTUEUAJNMnMvlwFTPoCWOAvn9sKIN9SCYPBMt +rFaisNZ+EZLpLrqeLppysb0ZRGxhNaKatBYSaVqM4dc+pBroLwP0rmEdEBsqpIt6xf4FpuHA1sj+ +nq6PK7o9mfjYcwlYRm6mnPTXJ9OV2jeDchzTc+CiR5kDOF3VSXkAKRzH7JsgHAckaVd4sjn8OoSg +tZx8jb8uk2IntznaFxiuvTwJaP+EmzzV1gsD41eeFPfR60/IvYcjt7ZJQ3mFXLrrkguhxuhoqEwW +sRqZCuhTLJK7oQkYdQxlqHvLI7cawiiFwxv/0Cti76R7CZGYZ4wUAc1oBmpjIXUDgIiKboHGhfKp +pC3n9KUkEEeDys30jXlYsQab5xoq2Z0B15R97QNKyvDb6KkBPvVWmckejkk9u+UJueBPSZI9FoJA +zMxZxuY67RIuaTxslbH9qh17f4a+Hg4yRvv7E491f0yLS0Zj/gA0QHDBw7mh3aZw4gSzQbzpgJHq +ZJx64SIDqZxubw5lT2yHh17zbqD5daWbQOhTsiedSrnAdyGN/4fy3ryM7xfft0kL0fJuMAsaDk52 +7RH89elWsn2/x20Kk4yl0MC2Hb46TpSi125sC8KKfPog88Tk5c0NqMuRkrF8hey1FGlmDoLnzc7I +LaZRfyHBNVOFBkpdn627G190 +-----END CERTIFICATE----- + +USERTrust RSA Certification Authority +===================================== +-----BEGIN CERTIFICATE----- +MIIF3jCCA8agAwIBAgIQAf1tMPyjylGoG7xkDjUDLTANBgkqhkiG9w0BAQwFADCBiDELMAkGA1UE +BhMCVVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4wHAYDVQQK +ExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVzdCBSU0EgQ2VydGlmaWNh +dGlvbiBBdXRob3JpdHkwHhcNMTAwMjAxMDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBiDELMAkGA1UE +BhMCVVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4wHAYDVQQK +ExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVzdCBSU0EgQ2VydGlmaWNh +dGlvbiBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCAEmUXNg7D2wiz +0KxXDXbtzSfTTK1Qg2HiqiBNCS1kCdzOiZ/MPans9s/B3PHTsdZ7NygRK0faOca8Ohm0X6a9fZ2j +Y0K2dvKpOyuR+OJv0OwWIJAJPuLodMkYtJHUYmTbf6MG8YgYapAiPLz+E/CHFHv25B+O1ORRxhFn +RghRy4YUVD+8M/5+bJz/Fp0YvVGONaanZshyZ9shZrHUm3gDwFA66Mzw3LyeTP6vBZY1H1dat//O ++T23LLb2VN3I5xI6Ta5MirdcmrS3ID3KfyI0rn47aGYBROcBTkZTmzNg95S+UzeQc0PzMsNT79uq +/nROacdrjGCT3sTHDN/hMq7MkztReJVni+49Vv4M0GkPGw/zJSZrM233bkf6c0Plfg6lZrEpfDKE +Y1WJxA3Bk1QwGROs0303p+tdOmw1XNtB1xLaqUkL39iAigmTYo61Zs8liM2EuLE/pDkP2QKe6xJM +lXzzawWpXhaDzLhn4ugTncxbgtNMs+1b/97lc6wjOy0AvzVVdAlJ2ElYGn+SNuZRkg7zJn0cTRe8 +yexDJtC/QV9AqURE9JnnV4eeUB9XVKg+/XRjL7FQZQnmWEIuQxpMtPAlR1n6BB6T1CZGSlCBst6+ +eLf8ZxXhyVeEHg9j1uliutZfVS7qXMYoCAQlObgOK6nyTJccBz8NUvXt7y+CDwIDAQABo0IwQDAd +BgNVHQ4EFgQUU3m/WqorSs9UgOHYm8Cd8rIDZsswDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQF +MAMBAf8wDQYJKoZIhvcNAQEMBQADggIBAFzUfA3P9wF9QZllDHPFUp/L+M+ZBn8b2kMVn54CVVeW +FPFSPCeHlCjtHzoBN6J2/FNQwISbxmtOuowhT6KOVWKR82kV2LyI48SqC/3vqOlLVSoGIG1VeCkZ +7l8wXEskEVX/JJpuXior7gtNn3/3ATiUFJVDBwn7YKnuHKsSjKCaXqeYalltiz8I+8jRRa8YFWSQ +Eg9zKC7F4iRO/Fjs8PRF/iKz6y+O0tlFYQXBl2+odnKPi4w2r78NBc5xjeambx9spnFixdjQg3IM +8WcRiQycE0xyNN+81XHfqnHd4blsjDwSXWXavVcStkNr/+XeTWYRUc+ZruwXtuhxkYzeSf7dNXGi +FSeUHM9h4ya7b6NnJSFd5t0dCy5oGzuCr+yDZ4XUmFF0sbmZgIn/f3gZXHlKYC6SQK5MNyosycdi +yA5d9zZbyuAlJQG03RoHnHcAP9Dc1ew91Pq7P8yF1m9/qS3fuQL39ZeatTXaw2ewh0qpKJ4jjv9c +J2vhsE/zB+4ALtRZh8tSQZXq9EfX7mRBVXyNWQKV3WKdwrnuWih0hKWbt5DHDAff9Yk2dDLWKMGw +sAvgnEzDHNb842m1R0aBL6KCq9NjRHDEjf8tM7qtj3u1cIiuPhnPQCjY/MiQu12ZIvVS5ljFH4gx +Q+6IHdfGjjxDah2nGN59PRbxYvnKkKj9 +-----END CERTIFICATE----- + +USERTrust ECC Certification Authority +===================================== +-----BEGIN CERTIFICATE----- +MIICjzCCAhWgAwIBAgIQXIuZxVqUxdJxVt7NiYDMJjAKBggqhkjOPQQDAzCBiDELMAkGA1UEBhMC +VVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4wHAYDVQQKExVU +aGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVzdCBFQ0MgQ2VydGlmaWNhdGlv +biBBdXRob3JpdHkwHhcNMTAwMjAxMDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBiDELMAkGA1UEBhMC +VVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4wHAYDVQQKExVU +aGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVzdCBFQ0MgQ2VydGlmaWNhdGlv +biBBdXRob3JpdHkwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQarFRaqfloI+d61SRvU8Za2EurxtW2 +0eZzca7dnNYMYf3boIkDuAUU7FfO7l0/4iGzzvfUinngo4N+LZfQYcTxmdwlkWOrfzCjtHDix6Ez +nPO/LlxTsV+zfTJ/ijTjeXmjQjBAMB0GA1UdDgQWBBQ64QmG1M8ZwpZ2dEl23OA1xmNjmjAOBgNV +HQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAwNoADBlAjA2Z6EWCNzklwBB +HU6+4WMBzzuqQhFkoJ2UOQIReVx7Hfpkue4WQrO/isIJxOzksU0CMQDpKmFHjFJKS04YcPbWRNZu +9YO6bVi9JNlWSOrvxKJGgYhqOkbRqZtNyWHa0V1Xahg= +-----END CERTIFICATE----- + +GlobalSign ECC Root CA - R4 +=========================== +-----BEGIN CERTIFICATE----- +MIIB4TCCAYegAwIBAgIRKjikHJYKBN5CsiilC+g0mAIwCgYIKoZIzj0EAwIwUDEkMCIGA1UECxMb +R2xvYmFsU2lnbiBFQ0MgUm9vdCBDQSAtIFI0MRMwEQYDVQQKEwpHbG9iYWxTaWduMRMwEQYDVQQD +EwpHbG9iYWxTaWduMB4XDTEyMTExMzAwMDAwMFoXDTM4MDExOTAzMTQwN1owUDEkMCIGA1UECxMb +R2xvYmFsU2lnbiBFQ0MgUm9vdCBDQSAtIFI0MRMwEQYDVQQKEwpHbG9iYWxTaWduMRMwEQYDVQQD +EwpHbG9iYWxTaWduMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEuMZ5049sJQ6fLjkZHAOkrprl +OQcJFspjsbmG+IpXwVfOQvpzofdlQv8ewQCybnMO/8ch5RikqtlxP6jUuc6MHaNCMEAwDgYDVR0P +AQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFFSwe61FuOJAf/sKbvu+M8k8o4TV +MAoGCCqGSM49BAMCA0gAMEUCIQDckqGgE6bPA7DmxCGXkPoUVy0D7O48027KqGx2vKLeuwIgJ6iF +JzWbVsaj8kfSt24bAgAXqmemFZHe+pTsewv4n4Q= +-----END CERTIFICATE----- + +GlobalSign ECC Root CA - R5 +=========================== +-----BEGIN CERTIFICATE----- +MIICHjCCAaSgAwIBAgIRYFlJ4CYuu1X5CneKcflK2GwwCgYIKoZIzj0EAwMwUDEkMCIGA1UECxMb +R2xvYmFsU2lnbiBFQ0MgUm9vdCBDQSAtIFI1MRMwEQYDVQQKEwpHbG9iYWxTaWduMRMwEQYDVQQD +EwpHbG9iYWxTaWduMB4XDTEyMTExMzAwMDAwMFoXDTM4MDExOTAzMTQwN1owUDEkMCIGA1UECxMb +R2xvYmFsU2lnbiBFQ0MgUm9vdCBDQSAtIFI1MRMwEQYDVQQKEwpHbG9iYWxTaWduMRMwEQYDVQQD +EwpHbG9iYWxTaWduMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAER0UOlvt9Xb/pOdEh+J8LttV7HpI6 +SFkc8GIxLcB6KP4ap1yztsyX50XUWPrRd21DosCHZTQKH3rd6zwzocWdTaRvQZU4f8kehOvRnkmS +h5SHDDqFSmafnVmTTZdhBoZKo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAd +BgNVHQ4EFgQUPeYpSJvqB8ohREom3m7e0oPQn1kwCgYIKoZIzj0EAwMDaAAwZQIxAOVpEslu28Yx +uglB4Zf4+/2a4n0Sye18ZNPLBSWLVtmg515dTguDnFt2KaAJJiFqYgIwcdK1j1zqO+F4CYWodZI7 +yFz9SO8NdCKoCOJuxUnOxwy8p2Fp8fc74SrL+SvzZpA3 +-----END CERTIFICATE----- + +Staat der Nederlanden EV Root CA +================================ +-----BEGIN CERTIFICATE----- +MIIFcDCCA1igAwIBAgIEAJiWjTANBgkqhkiG9w0BAQsFADBYMQswCQYDVQQGEwJOTDEeMBwGA1UE +CgwVU3RhYXQgZGVyIE5lZGVybGFuZGVuMSkwJwYDVQQDDCBTdGFhdCBkZXIgTmVkZXJsYW5kZW4g +RVYgUm9vdCBDQTAeFw0xMDEyMDgxMTE5MjlaFw0yMjEyMDgxMTEwMjhaMFgxCzAJBgNVBAYTAk5M +MR4wHAYDVQQKDBVTdGFhdCBkZXIgTmVkZXJsYW5kZW4xKTAnBgNVBAMMIFN0YWF0IGRlciBOZWRl +cmxhbmRlbiBFViBSb290IENBMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA48d+ifkk +SzrSM4M1LGns3Amk41GoJSt5uAg94JG6hIXGhaTK5skuU6TJJB79VWZxXSzFYGgEt9nCUiY4iKTW +O0Cmws0/zZiTs1QUWJZV1VD+hq2kY39ch/aO5ieSZxeSAgMs3NZmdO3dZ//BYY1jTw+bbRcwJu+r +0h8QoPnFfxZpgQNH7R5ojXKhTbImxrpsX23Wr9GxE46prfNeaXUmGD5BKyF/7otdBwadQ8QpCiv8 +Kj6GyzyDOvnJDdrFmeK8eEEzduG/L13lpJhQDBXd4Pqcfzho0LKmeqfRMb1+ilgnQ7O6M5HTp5gV +XJrm0w912fxBmJc+qiXbj5IusHsMX/FjqTf5m3VpTCgmJdrV8hJwRVXj33NeN/UhbJCONVrJ0yPr +08C+eKxCKFhmpUZtcALXEPlLVPxdhkqHz3/KRawRWrUgUY0viEeXOcDPusBCAUCZSCELa6fS/ZbV +0b5GnUngC6agIk440ME8MLxwjyx1zNDFjFE7PZQIZCZhfbnDZY8UnCHQqv0XcgOPvZuM5l5Tnrmd +74K74bzickFbIZTTRTeU0d8JOV3nI6qaHcptqAqGhYqCvkIH1vI4gnPah1vlPNOePqc7nvQDs/nx +fRN0Av+7oeX6AHkcpmZBiFxgV6YuCcS6/ZrPpx9Aw7vMWgpVSzs4dlG4Y4uElBbmVvMCAwEAAaNC +MEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFP6rAJCYniT8qcwa +ivsnuL8wbqg7MA0GCSqGSIb3DQEBCwUAA4ICAQDPdyxuVr5Os7aEAJSrR8kN0nbHhp8dB9O2tLsI +eK9p0gtJ3jPFrK3CiAJ9Brc1AsFgyb/E6JTe1NOpEyVa/m6irn0F3H3zbPB+po3u2dfOWBfoqSmu +c0iH55vKbimhZF8ZE/euBhD/UcabTVUlT5OZEAFTdfETzsemQUHSv4ilf0X8rLiltTMMgsT7B/Zq +5SWEXwbKwYY5EdtYzXc7LMJMD16a4/CrPmEbUCTCwPTxGfARKbalGAKb12NMcIxHowNDXLldRqAN +b/9Zjr7dn3LDWyvfjFvO5QxGbJKyCqNMVEIYFRIYvdr8unRu/8G2oGTYqV9Vrp9canaW2HNnh/tN +f1zuacpzEPuKqf2evTY4SUmH9A4U8OmHuD+nT3pajnnUk+S7aFKErGzp85hwVXIy+TSrK0m1zSBi +5Dp6Z2Orltxtrpfs/J92VoguZs9btsmksNcFuuEnL5O7Jiqik7Ab846+HUCjuTaPPoIaGl6I6lD4 +WeKDRikL40Rc4ZW2aZCaFG+XroHPaO+Zmr615+F/+PoTRxZMzG0IQOeLeG9QgkRQP2YGiqtDhFZK +DyAthg710tvSeopLzaXoTvFeJiUBWSOgftL2fiFX1ye8FVdMpEbB4IMeDExNH08GGeL5qPQ6gqGy +eUN51q1veieQA6TqJIc/2b3Z6fJfUEkc7uzXLg== +-----END CERTIFICATE----- + +IdenTrust Commercial Root CA 1 +============================== +-----BEGIN CERTIFICATE----- +MIIFYDCCA0igAwIBAgIQCgFCgAAAAUUjyES1AAAAAjANBgkqhkiG9w0BAQsFADBKMQswCQYDVQQG +EwJVUzESMBAGA1UEChMJSWRlblRydXN0MScwJQYDVQQDEx5JZGVuVHJ1c3QgQ29tbWVyY2lhbCBS +b290IENBIDEwHhcNMTQwMTE2MTgxMjIzWhcNMzQwMTE2MTgxMjIzWjBKMQswCQYDVQQGEwJVUzES +MBAGA1UEChMJSWRlblRydXN0MScwJQYDVQQDEx5JZGVuVHJ1c3QgQ29tbWVyY2lhbCBSb290IENB +IDEwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCnUBneP5k91DNG8W9RYYKyqU+PZ4ld +hNlT3Qwo2dfw/66VQ3KZ+bVdfIrBQuExUHTRgQ18zZshq0PirK1ehm7zCYofWjK9ouuU+ehcCuz/ +mNKvcbO0U59Oh++SvL3sTzIwiEsXXlfEU8L2ApeN2WIrvyQfYo3fw7gpS0l4PJNgiCL8mdo2yMKi +1CxUAGc1bnO/AljwpN3lsKImesrgNqUZFvX9t++uP0D1bVoE/c40yiTcdCMbXTMTEl3EASX2MN0C +XZ/g1Ue9tOsbobtJSdifWwLziuQkkORiT0/Br4sOdBeo0XKIanoBScy0RnnGF7HamB4HWfp1IYVl +3ZBWzvurpWCdxJ35UrCLvYf5jysjCiN2O/cz4ckA82n5S6LgTrx+kzmEB/dEcH7+B1rlsazRGMzy +NeVJSQjKVsk9+w8YfYs7wRPCTY/JTw436R+hDmrfYi7LNQZReSzIJTj0+kuniVyc0uMNOYZKdHzV +WYfCP04MXFL0PfdSgvHqo6z9STQaKPNBiDoT7uje/5kdX7rL6B7yuVBgwDHTc+XvvqDtMwt0viAg +xGds8AgDelWAf0ZOlqf0Hj7h9tgJ4TNkK2PXMl6f+cB7D3hvl7yTmvmcEpB4eoCHFddydJxVdHix +uuFucAS6T6C6aMN7/zHwcz09lCqxC0EOoP5NiGVreTO01wIDAQABo0IwQDAOBgNVHQ8BAf8EBAMC +AQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU7UQZwNPwBovupHu+QucmVMiONnYwDQYJKoZI +hvcNAQELBQADggIBAA2ukDL2pkt8RHYZYR4nKM1eVO8lvOMIkPkp165oCOGUAFjvLi5+U1KMtlwH +6oi6mYtQlNeCgN9hCQCTrQ0U5s7B8jeUeLBfnLOic7iPBZM4zY0+sLj7wM+x8uwtLRvM7Kqas6pg +ghstO8OEPVeKlh6cdbjTMM1gCIOQ045U8U1mwF10A0Cj7oV+wh93nAbowacYXVKV7cndJZ5t+qnt +ozo00Fl72u1Q8zW/7esUTTHHYPTa8Yec4kjixsU3+wYQ+nVZZjFHKdp2mhzpgq7vmrlR94gjmmmV +YjzlVYA211QC//G5Xc7UI2/YRYRKW2XviQzdFKcgyxilJbQN+QHwotL0AMh0jqEqSI5l2xPE4iUX +feu+h1sXIFRRk0pTAwvsXcoz7WL9RccvW9xYoIA55vrX/hMUpu09lEpCdNTDd1lzzY9GvlU47/ro +kTLql1gEIt44w8y8bckzOmoKaT+gyOpyj4xjhiO9bTyWnpXgSUyqorkqG5w2gXjtw+hG4iZZRHUe +2XWJUc0QhJ1hYMtd+ZciTY6Y5uN/9lu7rs3KSoFrXgvzUeF0K+l+J6fZmUlO+KWA2yUPHGNiiskz +Z2s8EIPGrd6ozRaOjfAHN3Gf8qv8QfXBi+wAN10J5U6A7/qxXDgGpRtK4dw4LTzcqx+QGtVKnO7R +cGzM7vRX+Bi6hG6H +-----END CERTIFICATE----- + +IdenTrust Public Sector Root CA 1 +================================= +-----BEGIN CERTIFICATE----- +MIIFZjCCA06gAwIBAgIQCgFCgAAAAUUjz0Z8AAAAAjANBgkqhkiG9w0BAQsFADBNMQswCQYDVQQG +EwJVUzESMBAGA1UEChMJSWRlblRydXN0MSowKAYDVQQDEyFJZGVuVHJ1c3QgUHVibGljIFNlY3Rv +ciBSb290IENBIDEwHhcNMTQwMTE2MTc1MzMyWhcNMzQwMTE2MTc1MzMyWjBNMQswCQYDVQQGEwJV +UzESMBAGA1UEChMJSWRlblRydXN0MSowKAYDVQQDEyFJZGVuVHJ1c3QgUHVibGljIFNlY3RvciBS +b290IENBIDEwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC2IpT8pEiv6EdrCvsnduTy +P4o7ekosMSqMjbCpwzFrqHd2hCa2rIFCDQjrVVi7evi8ZX3yoG2LqEfpYnYeEe4IFNGyRBb06tD6 +Hi9e28tzQa68ALBKK0CyrOE7S8ItneShm+waOh7wCLPQ5CQ1B5+ctMlSbdsHyo+1W/CD80/HLaXI +rcuVIKQxKFdYWuSNG5qrng0M8gozOSI5Cpcu81N3uURF/YTLNiCBWS2ab21ISGHKTN9T0a9SvESf +qy9rg3LvdYDaBjMbXcjaY8ZNzaxmMc3R3j6HEDbhuaR672BQssvKplbgN6+rNBM5Jeg5ZuSYeqoS +mJxZZoY+rfGwyj4GD3vwEUs3oERte8uojHH01bWRNszwFcYr3lEXsZdMUD2xlVl8BX0tIdUAvwFn +ol57plzy9yLxkA2T26pEUWbMfXYD62qoKjgZl3YNa4ph+bz27nb9cCvdKTz4Ch5bQhyLVi9VGxyh +LrXHFub4qjySjmm2AcG1hp2JDws4lFTo6tyePSW8Uybt1as5qsVATFSrsrTZ2fjXctscvG29ZV/v +iDUqZi/u9rNl8DONfJhBaUYPQxxp+pu10GFqzcpL2UyQRqsVWaFHVCkugyhfHMKiq3IXAAaOReyL +4jM9f9oZRORicsPfIsbyVtTdX5Vy7W1f90gDW/3FKqD2cyOEEBsB5wIDAQABo0IwQDAOBgNVHQ8B +Af8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU43HgntinQtnbcZFrlJPrw6PRFKMw +DQYJKoZIhvcNAQELBQADggIBAEf63QqwEZE4rU1d9+UOl1QZgkiHVIyqZJnYWv6IAcVYpZmxI1Qj +t2odIFflAWJBF9MJ23XLblSQdf4an4EKwt3X9wnQW3IV5B4Jaj0z8yGa5hV+rVHVDRDtfULAj+7A +mgjVQdZcDiFpboBhDhXAuM/FSRJSzL46zNQuOAXeNf0fb7iAaJg9TaDKQGXSc3z1i9kKlT/YPyNt +GtEqJBnZhbMX73huqVjRI9PHE+1yJX9dsXNw0H8GlwmEKYBhHfpe/3OsoOOJuBxxFcbeMX8S3OFt +m6/n6J91eEyrRjuazr8FGF1NFTwWmhlQBJqymm9li1JfPFgEKCXAZmExfrngdbkaqIHWchezxQMx +NRF4eKLg6TCMf4DfWN88uieW4oA0beOY02QnrEh+KHdcxiVhJfiFDGX6xDIvpZgF5PgLZxYWxoK4 +Mhn5+bl53B/N66+rDt0b20XkeucC4pVd/GnwU2lhlXV5C15V5jgclKlZM57IcXR5f1GJtshquDDI +ajjDbp7hNxbqBWJMWxJH7ae0s1hWx0nzfxJoCTFx8G34Tkf71oXuxVhAGaQdp/lLQzfcaFpPz+vC +ZHTetBXZ9FRUGi8c15dxVJCO2SCdUyt/q4/i6jC8UDfv8Ue1fXwsBOxonbRJRBD0ckscZOf85muQ +3Wl9af0AVqW3rLatt8o+Ae+c +-----END CERTIFICATE----- + +Entrust Root Certification Authority - G2 +========================================= +-----BEGIN CERTIFICATE----- +MIIEPjCCAyagAwIBAgIESlOMKDANBgkqhkiG9w0BAQsFADCBvjELMAkGA1UEBhMCVVMxFjAUBgNV +BAoTDUVudHJ1c3QsIEluYy4xKDAmBgNVBAsTH1NlZSB3d3cuZW50cnVzdC5uZXQvbGVnYWwtdGVy +bXMxOTA3BgNVBAsTMChjKSAyMDA5IEVudHJ1c3QsIEluYy4gLSBmb3IgYXV0aG9yaXplZCB1c2Ug +b25seTEyMDAGA1UEAxMpRW50cnVzdCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IC0gRzIw +HhcNMDkwNzA3MTcyNTU0WhcNMzAxMjA3MTc1NTU0WjCBvjELMAkGA1UEBhMCVVMxFjAUBgNVBAoT +DUVudHJ1c3QsIEluYy4xKDAmBgNVBAsTH1NlZSB3d3cuZW50cnVzdC5uZXQvbGVnYWwtdGVybXMx +OTA3BgNVBAsTMChjKSAyMDA5IEVudHJ1c3QsIEluYy4gLSBmb3IgYXV0aG9yaXplZCB1c2Ugb25s +eTEyMDAGA1UEAxMpRW50cnVzdCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IC0gRzIwggEi +MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC6hLZy254Ma+KZ6TABp3bqMriVQRrJ2mFOWHLP +/vaCeb9zYQYKpSfYs1/TRU4cctZOMvJyig/3gxnQaoCAAEUesMfnmr8SVycco2gvCoe9amsOXmXz +HHfV1IWNcCG0szLni6LVhjkCsbjSR87kyUnEO6fe+1R9V77w6G7CebI6C1XiUJgWMhNcL3hWwcKU +s/Ja5CeanyTXxuzQmyWC48zCxEXFjJd6BmsqEZ+pCm5IO2/b1BEZQvePB7/1U1+cPvQXLOZprE4y +TGJ36rfo5bs0vBmLrpxR57d+tVOxMyLlbc9wPBr64ptntoP0jaWvYkxN4FisZDQSA/i2jZRjJKRx +AgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRqciZ6 +0B7vfec7aVHUbI2fkBJmqzANBgkqhkiG9w0BAQsFAAOCAQEAeZ8dlsa2eT8ijYfThwMEYGprmi5Z +iXMRrEPR9RP/jTkrwPK9T3CMqS/qF8QLVJ7UG5aYMzyorWKiAHarWWluBh1+xLlEjZivEtRh2woZ +Rkfz6/djwUAFQKXSt/S1mja/qYh2iARVBCuch38aNzx+LaUa2NSJXsq9rD1s2G2v1fN2D807iDgi +nWyTmsQ9v4IbZT+mD12q/OWyFcq1rca8PdCE6OoGcrBNOTJ4vz4RnAuknZoh8/CbCzB428Hch0P+ +vGOaysXCHMnHjf87ElgI5rY97HosTvuDls4MPGmHVHOkc8KT/1EQrBVUAdj8BbGJoX90g5pJ19xO +e4pIb4tF9g== +-----END CERTIFICATE----- + +Entrust Root Certification Authority - EC1 +========================================== +-----BEGIN CERTIFICATE----- +MIIC+TCCAoCgAwIBAgINAKaLeSkAAAAAUNCR+TAKBggqhkjOPQQDAzCBvzELMAkGA1UEBhMCVVMx +FjAUBgNVBAoTDUVudHJ1c3QsIEluYy4xKDAmBgNVBAsTH1NlZSB3d3cuZW50cnVzdC5uZXQvbGVn +YWwtdGVybXMxOTA3BgNVBAsTMChjKSAyMDEyIEVudHJ1c3QsIEluYy4gLSBmb3IgYXV0aG9yaXpl +ZCB1c2Ugb25seTEzMDEGA1UEAxMqRW50cnVzdCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5 +IC0gRUMxMB4XDTEyMTIxODE1MjUzNloXDTM3MTIxODE1NTUzNlowgb8xCzAJBgNVBAYTAlVTMRYw +FAYDVQQKEw1FbnRydXN0LCBJbmMuMSgwJgYDVQQLEx9TZWUgd3d3LmVudHJ1c3QubmV0L2xlZ2Fs +LXRlcm1zMTkwNwYDVQQLEzAoYykgMjAxMiBFbnRydXN0LCBJbmMuIC0gZm9yIGF1dGhvcml6ZWQg +dXNlIG9ubHkxMzAxBgNVBAMTKkVudHJ1c3QgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAt +IEVDMTB2MBAGByqGSM49AgEGBSuBBAAiA2IABIQTydC6bUF74mzQ61VfZgIaJPRbiWlH47jCffHy +AsWfoPZb1YsGGYZPUxBtByQnoaD41UcZYUx9ypMn6nQM72+WCf5j7HBdNq1nd67JnXxVRDqiY1Ef +9eNi1KlHBz7MIKNCMEAwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYE +FLdj5xrdjekIplWDpOBqUEFlEUJJMAoGCCqGSM49BAMDA2cAMGQCMGF52OVCR98crlOZF7ZvHH3h +vxGU0QOIdeSNiaSKd0bebWHvAvX7td/M/k7//qnmpwIwW5nXhTcGtXsI/esni0qU+eH6p44mCOh8 +kmhtc9hvJqwhAriZtyZBWyVgrtBIGu4G +-----END CERTIFICATE----- + +CFCA EV ROOT +============ +-----BEGIN CERTIFICATE----- +MIIFjTCCA3WgAwIBAgIEGErM1jANBgkqhkiG9w0BAQsFADBWMQswCQYDVQQGEwJDTjEwMC4GA1UE +CgwnQ2hpbmEgRmluYW5jaWFsIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MRUwEwYDVQQDDAxDRkNB +IEVWIFJPT1QwHhcNMTIwODA4MDMwNzAxWhcNMjkxMjMxMDMwNzAxWjBWMQswCQYDVQQGEwJDTjEw +MC4GA1UECgwnQ2hpbmEgRmluYW5jaWFsIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MRUwEwYDVQQD +DAxDRkNBIEVWIFJPT1QwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDXXWvNED8fBVnV +BU03sQ7smCuOFR36k0sXgiFxEFLXUWRwFsJVaU2OFW2fvwwbwuCjZ9YMrM8irq93VCpLTIpTUnrD +7i7es3ElweldPe6hL6P3KjzJIx1qqx2hp/Hz7KDVRM8Vz3IvHWOX6Jn5/ZOkVIBMUtRSqy5J35DN +uF++P96hyk0g1CXohClTt7GIH//62pCfCqktQT+x8Rgp7hZZLDRJGqgG16iI0gNyejLi6mhNbiyW +ZXvKWfry4t3uMCz7zEasxGPrb382KzRzEpR/38wmnvFyXVBlWY9ps4deMm/DGIq1lY+wejfeWkU7 +xzbh72fROdOXW3NiGUgthxwG+3SYIElz8AXSG7Ggo7cbcNOIabla1jj0Ytwli3i/+Oh+uFzJlU9f +py25IGvPa931DfSCt/SyZi4QKPaXWnuWFo8BGS1sbn85WAZkgwGDg8NNkt0yxoekN+kWzqotaK8K +gWU6cMGbrU1tVMoqLUuFG7OA5nBFDWteNfB/O7ic5ARwiRIlk9oKmSJgamNgTnYGmE69g60dWIol +hdLHZR4tjsbftsbhf4oEIRUpdPA+nJCdDC7xij5aqgwJHsfVPKPtl8MeNPo4+QgO48BdK4PRVmrJ +tqhUUy54Mmc9gn900PvhtgVguXDbjgv5E1hvcWAQUhC5wUEJ73IfZzF4/5YFjQIDAQABo2MwYTAf +BgNVHSMEGDAWgBTj/i39KNALtbq2osS/BqoFjJP7LzAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB +/wQEAwIBBjAdBgNVHQ4EFgQU4/4t/SjQC7W6tqLEvwaqBYyT+y8wDQYJKoZIhvcNAQELBQADggIB +ACXGumvrh8vegjmWPfBEp2uEcwPenStPuiB/vHiyz5ewG5zz13ku9Ui20vsXiObTej/tUxPQ4i9q +ecsAIyjmHjdXNYmEwnZPNDatZ8POQQaIxffu2Bq41gt/UP+TqhdLjOztUmCypAbqTuv0axn96/Ua +4CUqmtzHQTb3yHQFhDmVOdYLO6Qn+gjYXB74BGBSESgoA//vU2YApUo0FmZ8/Qmkrp5nGm9BC2sG +E5uPhnEFtC+NiWYzKXZUmhH4J/qyP5Hgzg0b8zAarb8iXRvTvyUFTeGSGn+ZnzxEk8rUQElsgIfX +BDrDMlI1Dlb4pd19xIsNER9Tyx6yF7Zod1rg1MvIB671Oi6ON7fQAUtDKXeMOZePglr4UeWJoBjn +aH9dCi77o0cOPaYjesYBx4/IXr9tgFa+iiS6M+qf4TIRnvHST4D2G0CvOJ4RUHlzEhLN5mydLIhy +PDCBBpEi6lmt2hkuIsKNuYyH4Ga8cyNfIWRjgEj1oDwYPZTISEEdQLpe/v5WOaHIz16eGWRGENoX +kbcFgKyLmZJ956LYBws2J+dIeWCKw9cTXPhyQN9Ky8+ZAAoACxGV2lZFA4gKn2fQ1XmxqI1AbQ3C +ekD6819kR5LLU7m7Wc5P/dAVUwHY3+vZ5nbv0CO7O6l5s9UCKc2Jo5YPSjXnTkLAdc0Hz+Ys63su +-----END CERTIFICATE----- + +OISTE WISeKey Global Root GB CA +=============================== +-----BEGIN CERTIFICATE----- +MIIDtTCCAp2gAwIBAgIQdrEgUnTwhYdGs/gjGvbCwDANBgkqhkiG9w0BAQsFADBtMQswCQYDVQQG +EwJDSDEQMA4GA1UEChMHV0lTZUtleTEiMCAGA1UECxMZT0lTVEUgRm91bmRhdGlvbiBFbmRvcnNl +ZDEoMCYGA1UEAxMfT0lTVEUgV0lTZUtleSBHbG9iYWwgUm9vdCBHQiBDQTAeFw0xNDEyMDExNTAw +MzJaFw0zOTEyMDExNTEwMzFaMG0xCzAJBgNVBAYTAkNIMRAwDgYDVQQKEwdXSVNlS2V5MSIwIAYD +VQQLExlPSVNURSBGb3VuZGF0aW9uIEVuZG9yc2VkMSgwJgYDVQQDEx9PSVNURSBXSVNlS2V5IEds +b2JhbCBSb290IEdCIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2Be3HEokKtaX +scriHvt9OO+Y9bI5mE4nuBFde9IllIiCFSZqGzG7qFshISvYD06fWvGxWuR51jIjK+FTzJlFXHtP +rby/h0oLS5daqPZI7H17Dc0hBt+eFf1Biki3IPShehtX1F1Q/7pn2COZH8g/497/b1t3sWtuuMlk +9+HKQUYOKXHQuSP8yYFfTvdv37+ErXNku7dCjmn21HYdfp2nuFeKUWdy19SouJVUQHMD9ur06/4o +Qnc/nSMbsrY9gBQHTC5P99UKFg29ZkM3fiNDecNAhvVMKdqOmq0NpQSHiB6F4+lT1ZvIiwNjeOvg +GUpuuy9rM2RYk61pv48b74JIxwIDAQABo1EwTzALBgNVHQ8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB +/zAdBgNVHQ4EFgQUNQ/INmNe4qPs+TtmFc5RUuORmj0wEAYJKwYBBAGCNxUBBAMCAQAwDQYJKoZI +hvcNAQELBQADggEBAEBM+4eymYGQfp3FsLAmzYh7KzKNbrghcViXfa43FK8+5/ea4n32cZiZBKpD +dHij40lhPnOMTZTg+XHEthYOU3gf1qKHLwI5gSk8rxWYITD+KJAAjNHhy/peyP34EEY7onhCkRd0 +VQreUGdNZtGn//3ZwLWoo4rOZvUPQ82nK1d7Y0Zqqi5S2PTt4W2tKZB4SLrhI6qjiey1q5bAtEui +HZeeevJuQHHfaPFlTc58Bd9TZaml8LGXBHAVRgOY1NK/VLSgWH1Sb9pWJmLU2NuJMW8c8CLC02Ic +Nc1MaRVUGpCY3useX8p3x8uOPUNpnJpY0CQ73xtAln41rYHHTnG6iBM= +-----END CERTIFICATE----- + +SZAFIR ROOT CA2 +=============== +-----BEGIN CERTIFICATE----- +MIIDcjCCAlqgAwIBAgIUPopdB+xV0jLVt+O2XwHrLdzk1uQwDQYJKoZIhvcNAQELBQAwUTELMAkG +A1UEBhMCUEwxKDAmBgNVBAoMH0tyYWpvd2EgSXpiYSBSb3psaWN6ZW5pb3dhIFMuQS4xGDAWBgNV +BAMMD1NaQUZJUiBST09UIENBMjAeFw0xNTEwMTkwNzQzMzBaFw0zNTEwMTkwNzQzMzBaMFExCzAJ +BgNVBAYTAlBMMSgwJgYDVQQKDB9LcmFqb3dhIEl6YmEgUm96bGljemVuaW93YSBTLkEuMRgwFgYD +VQQDDA9TWkFGSVIgUk9PVCBDQTIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC3vD5Q +qEvNQLXOYeeWyrSh2gwisPq1e3YAd4wLz32ohswmUeQgPYUM1ljj5/QqGJ3a0a4m7utT3PSQ1hNK +DJA8w/Ta0o4NkjrcsbH/ON7Dui1fgLkCvUqdGw+0w8LBZwPd3BucPbOw3gAeqDRHu5rr/gsUvTaE +2g0gv/pby6kWIK05YO4vdbbnl5z5Pv1+TW9NL++IDWr63fE9biCloBK0TXC5ztdyO4mTp4CEHCdJ +ckm1/zuVnsHMyAHs6A6KCpbns6aH5db5BSsNl0BwPLqsdVqc1U2dAgrSS5tmS0YHF2Wtn2yIANwi +ieDhZNRnvDF5YTy7ykHNXGoAyDw4jlivAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0P +AQH/BAQDAgEGMB0GA1UdDgQWBBQuFqlKGLXLzPVvUPMjX/hd56zwyDANBgkqhkiG9w0BAQsFAAOC +AQEAtXP4A9xZWx126aMqe5Aosk3AM0+qmrHUuOQn/6mWmc5G4G18TKI4pAZw8PRBEew/R40/cof5 +O/2kbytTAOD/OblqBw7rHRz2onKQy4I9EYKL0rufKq8h5mOGnXkZ7/e7DDWQw4rtTw/1zBLZpD67 +oPwglV9PJi8RI4NOdQcPv5vRtB3pEAT+ymCPoky4rc/hkA/NrgrHXXu3UNLUYfrVFdvXn4dRVOul +4+vJhaAlIDf7js4MNIThPIGyd05DpYhfhmehPea0XGG2Ptv+tyjFogeutcrKjSoS75ftwjCkySp6 ++/NNIxuZMzSgLvWpCz/UXeHPhJ/iGcJfitYgHuNztw== +-----END CERTIFICATE----- + +Certum Trusted Network CA 2 +=========================== +-----BEGIN CERTIFICATE----- +MIIF0jCCA7qgAwIBAgIQIdbQSk8lD8kyN/yqXhKN6TANBgkqhkiG9w0BAQ0FADCBgDELMAkGA1UE +BhMCUEwxIjAgBgNVBAoTGVVuaXpldG8gVGVjaG5vbG9naWVzIFMuQS4xJzAlBgNVBAsTHkNlcnR1 +bSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTEkMCIGA1UEAxMbQ2VydHVtIFRydXN0ZWQgTmV0d29y +ayBDQSAyMCIYDzIwMTExMDA2MDgzOTU2WhgPMjA0NjEwMDYwODM5NTZaMIGAMQswCQYDVQQGEwJQ +TDEiMCAGA1UEChMZVW5pemV0byBUZWNobm9sb2dpZXMgUy5BLjEnMCUGA1UECxMeQ2VydHVtIENl +cnRpZmljYXRpb24gQXV0aG9yaXR5MSQwIgYDVQQDExtDZXJ0dW0gVHJ1c3RlZCBOZXR3b3JrIENB +IDIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC9+Xj45tWADGSdhhuWZGc/IjoedQF9 +7/tcZ4zJzFxrqZHmuULlIEub2pt7uZld2ZuAS9eEQCsn0+i6MLs+CRqnSZXvK0AkwpfHp+6bJe+o +CgCXhVqqndwpyeI1B+twTUrWwbNWuKFBOJvR+zF/j+Bf4bE/D44WSWDXBo0Y+aomEKsq09DRZ40b +Rr5HMNUuctHFY9rnY3lEfktjJImGLjQ/KUxSiyqnwOKRKIm5wFv5HdnnJ63/mgKXwcZQkpsCLL2p +uTRZCr+ESv/f/rOf69me4Jgj7KZrdxYq28ytOxykh9xGc14ZYmhFV+SQgkK7QtbwYeDBoz1mo130 +GO6IyY0XRSmZMnUCMe4pJshrAua1YkV/NxVaI2iJ1D7eTiew8EAMvE0Xy02isx7QBlrd9pPPV3WZ +9fqGGmd4s7+W/jTcvedSVuWz5XV710GRBdxdaeOVDUO5/IOWOZV7bIBaTxNyxtd9KXpEulKkKtVB +Rgkg/iKgtlswjbyJDNXXcPiHUv3a76xRLgezTv7QCdpw75j6VuZt27VXS9zlLCUVyJ4ueE742pye +hizKV/Ma5ciSixqClnrDvFASadgOWkaLOusm+iPJtrCBvkIApPjW/jAux9JG9uWOdf3yzLnQh1vM +BhBgu4M1t15n3kfsmUjxpKEV/q2MYo45VU85FrmxY53/twIDAQABo0IwQDAPBgNVHRMBAf8EBTAD +AQH/MB0GA1UdDgQWBBS2oVQ5AsOgP46KvPrU+Bym0ToO/TAOBgNVHQ8BAf8EBAMCAQYwDQYJKoZI +hvcNAQENBQADggIBAHGlDs7k6b8/ONWJWsQCYftMxRQXLYtPU2sQF/xlhMcQSZDe28cmk4gmb3DW +Al45oPePq5a1pRNcgRRtDoGCERuKTsZPpd1iHkTfCVn0W3cLN+mLIMb4Ck4uWBzrM9DPhmDJ2vuA +L55MYIR4PSFk1vtBHxgP58l1cb29XN40hz5BsA72udY/CROWFC/emh1auVbONTqwX3BNXuMp8SMo +clm2q8KMZiYcdywmdjWLKKdpoPk79SPdhRB0yZADVpHnr7pH1BKXESLjokmUbOe3lEu6LaTaM4tM +pkT/WjzGHWTYtTHkpjx6qFcL2+1hGsvxznN3Y6SHb0xRONbkX8eftoEq5IVIeVheO/jbAoJnwTnb +w3RLPTYe+SmTiGhbqEQZIfCn6IENLOiTNrQ3ssqwGyZ6miUfmpqAnksqP/ujmv5zMnHCnsZy4Ypo +J/HkD7TETKVhk/iXEAcqMCWpuchxuO9ozC1+9eB+D4Kob7a6bINDd82Kkhehnlt4Fj1F4jNy3eFm +ypnTycUm/Q1oBEauttmbjL4ZvrHG8hnjXALKLNhvSgfZyTXaQHXyxKcZb55CEJh15pWLYLztxRLX +is7VmFxWlgPF7ncGNf/P5O4/E2Hu29othfDNrp2yGAlFw5Khchf8R7agCyzxxN5DaAhqXzvwdmP7 +zAYspsbiDrW5viSP +-----END CERTIFICATE----- + +Hellenic Academic and Research Institutions RootCA 2015 +======================================================= +-----BEGIN CERTIFICATE----- +MIIGCzCCA/OgAwIBAgIBADANBgkqhkiG9w0BAQsFADCBpjELMAkGA1UEBhMCR1IxDzANBgNVBAcT +BkF0aGVuczFEMEIGA1UEChM7SGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJlc2VhcmNoIEluc3RpdHV0 +aW9ucyBDZXJ0LiBBdXRob3JpdHkxQDA+BgNVBAMTN0hlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNl +YXJjaCBJbnN0aXR1dGlvbnMgUm9vdENBIDIwMTUwHhcNMTUwNzA3MTAxMTIxWhcNNDAwNjMwMTAx +MTIxWjCBpjELMAkGA1UEBhMCR1IxDzANBgNVBAcTBkF0aGVuczFEMEIGA1UEChM7SGVsbGVuaWMg +QWNhZGVtaWMgYW5kIFJlc2VhcmNoIEluc3RpdHV0aW9ucyBDZXJ0LiBBdXRob3JpdHkxQDA+BgNV +BAMTN0hlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgUm9vdENBIDIw +MTUwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDC+Kk/G4n8PDwEXT2QNrCROnk8Zlrv +bTkBSRq0t89/TSNTt5AA4xMqKKYx8ZEA4yjsriFBzh/a/X0SWwGDD7mwX5nh8hKDgE0GPt+sr+eh +iGsxr/CL0BgzuNtFajT0AoAkKAoCFZVedioNmToUW/bLy1O8E00BiDeUJRtCvCLYjqOWXjrZMts+ +6PAQZe104S+nfK8nNLspfZu2zwnI5dMK/IhlZXQK3HMcXM1AsRzUtoSMTFDPaI6oWa7CJ06CojXd +FPQf/7J31Ycvqm59JCfnxssm5uX+Zwdj2EUN3TpZZTlYepKZcj2chF6IIbjV9Cz82XBST3i4vTwr +i5WY9bPRaM8gFH5MXF/ni+X1NYEZN9cRCLdmvtNKzoNXADrDgfgXy5I2XdGj2HUb4Ysn6npIQf1F +GQatJ5lOwXBH3bWfgVMS5bGMSF0xQxfjjMZ6Y5ZLKTBOhE5iGV48zpeQpX8B653g+IuJ3SWYPZK2 +fu/Z8VFRfS0myGlZYeCsargqNhEEelC9MoS+L9xy1dcdFkfkR2YgP/SWxa+OAXqlD3pk9Q0Yh9mu +iNX6hME6wGkoLfINaFGq46V3xqSQDqE3izEjR8EJCOtu93ib14L8hCCZSRm2Ekax+0VVFqmjZayc +Bw/qa9wfLgZy7IaIEuQt218FL+TwA9MmM+eAws1CoRc0CwIDAQABo0IwQDAPBgNVHRMBAf8EBTAD +AQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUcRVnyMjJvXVdctA4GGqd83EkVAswDQYJKoZI +hvcNAQELBQADggIBAHW7bVRLqhBYRjTyYtcWNl0IXtVsyIe9tC5G8jH4fOpCtZMWVdyhDBKg2mF+ +D1hYc2Ryx+hFjtyp8iY/xnmMsVMIM4GwVhO+5lFc2JsKT0ucVlMC6U/2DWDqTUJV6HwbISHTGzrM +d/K4kPFox/la/vot9L/J9UUbzjgQKjeKeaO04wlshYaT/4mWJ3iBj2fjRnRUjtkNaeJK9E10A/+y +d+2VZ5fkscWrv2oj6NSU4kQoYsRL4vDY4ilrGnB+JGGTe08DMiUNRSQrlrRGar9KC/eaj8GsGsVn +82800vpzY4zvFrCopEYq+OsS7HK07/grfoxSwIuEVPkvPuNVqNxmsdnhX9izjFk0WaSrT2y7Hxjb +davYy5LNlDhhDgcGH0tGEPEVvo2FXDtKK4F5D7Rpn0lQl033DlZdwJVqwjbDG2jJ9SrcR5q+ss7F +Jej6A7na+RZukYT1HCjI/CbM1xyQVqdfbzoEvM14iQuODy+jqk+iGxI9FghAD/FGTNeqewjBCvVt +J94Cj8rDtSvK6evIIVM4pcw72Hc3MKJP2W/R8kCtQXoXxdZKNYm3QdV8hn9VTYNKpXMgwDqvkPGa +JI7ZjnHKe7iG2rKPmT4dEw0SEe7Uq/DpFXYC5ODfqiAeW2GFZECpkJcNrVPSWh2HagCXZWK0vm9q +p/UsQu0yrbYhnr68 +-----END CERTIFICATE----- + +Hellenic Academic and Research Institutions ECC RootCA 2015 +=========================================================== +-----BEGIN CERTIFICATE----- +MIICwzCCAkqgAwIBAgIBADAKBggqhkjOPQQDAjCBqjELMAkGA1UEBhMCR1IxDzANBgNVBAcTBkF0 +aGVuczFEMEIGA1UEChM7SGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJlc2VhcmNoIEluc3RpdHV0aW9u +cyBDZXJ0LiBBdXRob3JpdHkxRDBCBgNVBAMTO0hlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJj +aCBJbnN0aXR1dGlvbnMgRUNDIFJvb3RDQSAyMDE1MB4XDTE1MDcwNzEwMzcxMloXDTQwMDYzMDEw +MzcxMlowgaoxCzAJBgNVBAYTAkdSMQ8wDQYDVQQHEwZBdGhlbnMxRDBCBgNVBAoTO0hlbGxlbmlj +IEFjYWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgQ2VydC4gQXV0aG9yaXR5MUQwQgYD +VQQDEztIZWxsZW5pYyBBY2FkZW1pYyBhbmQgUmVzZWFyY2ggSW5zdGl0dXRpb25zIEVDQyBSb290 +Q0EgMjAxNTB2MBAGByqGSM49AgEGBSuBBAAiA2IABJKgQehLgoRc4vgxEZmGZE4JJS+dQS8KrjVP +dJWyUWRrjWvmP3CV8AVER6ZyOFB2lQJajq4onvktTpnvLEhvTCUp6NFxW98dwXU3tNf6e3pCnGoK +Vlp8aQuqgAkkbH7BRqNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0O +BBYEFLQiC4KZJAEOnLvkDv2/+5cgk5kqMAoGCCqGSM49BAMCA2cAMGQCMGfOFmI4oqxiRaeplSTA +GiecMjvAwNW6qef4BENThe5SId6d9SWDPp5YSy/XZxMOIQIwBeF1Ad5o7SofTUwJCA3sS61kFyjn +dc5FZXIhF8siQQ6ME5g4mlRtm8rifOoCWCKR +-----END CERTIFICATE----- + +ISRG Root X1 +============ +-----BEGIN CERTIFICATE----- +MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAwTzELMAkGA1UE +BhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2VhcmNoIEdyb3VwMRUwEwYDVQQD +EwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQG +EwJVUzEpMCcGA1UEChMgSW50ZXJuZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMT +DElTUkcgUm9vdCBYMTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54r +Vygch77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+0TM8ukj1 +3Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6UA5/TR5d8mUgjU+g4rk8K +b4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sWT8KOEUt+zwvo/7V3LvSye0rgTBIlDHCN +Aymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyHB5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ +4Q7e2RCOFvu396j3x+UCB5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf +1b0SHzUvKBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWnOlFu +hjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTnjh8BCNAw1FtxNrQH +usEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbwqHyGO0aoSCqI3Haadr8faqU9GY/r +OPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CIrU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4G +A1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY +9umbbjANBgkqhkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL +ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ3BebYhtF8GaV +0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KKNFtY2PwByVS5uCbMiogziUwt +hDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJw +TdwJx4nLCgdNbOhdjsnvzqvHu7UrTkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nx +e5AW0wdeRlN8NwdCjNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZA +JzVcoyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq4RgqsahD +YVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPAmRGunUHBcnWEvgJBQl9n +JEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57demyPxgcYxn/eR44/KJ4EBs+lVDR3veyJ +m+kXQ99b21/+jh5Xos1AnX5iItreGCc= +-----END CERTIFICATE----- + +AC RAIZ FNMT-RCM +================ +-----BEGIN CERTIFICATE----- +MIIFgzCCA2ugAwIBAgIPXZONMGc2yAYdGsdUhGkHMA0GCSqGSIb3DQEBCwUAMDsxCzAJBgNVBAYT +AkVTMREwDwYDVQQKDAhGTk1ULVJDTTEZMBcGA1UECwwQQUMgUkFJWiBGTk1ULVJDTTAeFw0wODEw +MjkxNTU5NTZaFw0zMDAxMDEwMDAwMDBaMDsxCzAJBgNVBAYTAkVTMREwDwYDVQQKDAhGTk1ULVJD +TTEZMBcGA1UECwwQQUMgUkFJWiBGTk1ULVJDTTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoC +ggIBALpxgHpMhm5/yBNtwMZ9HACXjywMI7sQmkCpGreHiPibVmr75nuOi5KOpyVdWRHbNi63URcf +qQgfBBckWKo3Shjf5TnUV/3XwSyRAZHiItQDwFj8d0fsjz50Q7qsNI1NOHZnjrDIbzAzWHFctPVr +btQBULgTfmxKo0nRIBnuvMApGGWn3v7v3QqQIecaZ5JCEJhfTzC8PhxFtBDXaEAUwED653cXeuYL +j2VbPNmaUtu1vZ5Gzz3rkQUCwJaydkxNEJY7kvqcfw+Z374jNUUeAlz+taibmSXaXvMiwzn15Cou +08YfxGyqxRxqAQVKL9LFwag0Jl1mpdICIfkYtwb1TplvqKtMUejPUBjFd8g5CSxJkjKZqLsXF3mw +WsXmo8RZZUc1g16p6DULmbvkzSDGm0oGObVo/CK67lWMK07q87Hj/LaZmtVC+nFNCM+HHmpxffnT +tOmlcYF7wk5HlqX2doWjKI/pgG6BU6VtX7hI+cL5NqYuSf+4lsKMB7ObiFj86xsc3i1w4peSMKGJ +47xVqCfWS+2QrYv6YyVZLag13cqXM7zlzced0ezvXg5KkAYmY6252TUtB7p2ZSysV4999AeU14EC +ll2jB0nVetBX+RvnU0Z1qrB5QstocQjpYL05ac70r8NWQMetUqIJ5G+GR4of6ygnXYMgrwTJbFaa +i0b1AgMBAAGjgYMwgYAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYE +FPd9xf3E6Jobd2Sn9R2gzL+HYJptMD4GA1UdIAQ3MDUwMwYEVR0gADArMCkGCCsGAQUFBwIBFh1o +dHRwOi8vd3d3LmNlcnQuZm5tdC5lcy9kcGNzLzANBgkqhkiG9w0BAQsFAAOCAgEAB5BK3/MjTvDD +nFFlm5wioooMhfNzKWtN/gHiqQxjAb8EZ6WdmF/9ARP67Jpi6Yb+tmLSbkyU+8B1RXxlDPiyN8+s +D8+Nb/kZ94/sHvJwnvDKuO+3/3Y3dlv2bojzr2IyIpMNOmqOFGYMLVN0V2Ue1bLdI4E7pWYjJ2cJ +j+F3qkPNZVEI7VFY/uY5+ctHhKQV8Xa7pO6kO8Rf77IzlhEYt8llvhjho6Tc+hj507wTmzl6NLrT +Qfv6MooqtyuGC2mDOL7Nii4LcK2NJpLuHvUBKwrZ1pebbuCoGRw6IYsMHkCtA+fdZn71uSANA+iW ++YJF1DngoABd15jmfZ5nc8OaKveri6E6FO80vFIOiZiaBECEHX5FaZNXzuvO+FB8TxxuBEOb+dY7 +Ixjp6o7RTUaN8Tvkasq6+yO3m/qZASlaWFot4/nUbQ4mrcFuNLwy+AwF+mWj2zs3gyLp1txyM/1d +8iC9djwj2ij3+RvrWWTV3F9yfiD8zYm1kGdNYno/Tq0dwzn+evQoFt9B9kiABdcPUXmsEKvU7ANm +5mqwujGSQkBqvjrTcuFqN1W8rB2Vt2lh8kORdOag0wokRqEIr9baRRmW1FMdW4R58MD3R++Lj8UG +rp1MYp3/RgT408m2ECVAdf4WqslKYIYvuu8wd+RU4riEmViAqhOLUTpPSPaLtrM= +-----END CERTIFICATE----- + +Amazon Root CA 1 +================ +-----BEGIN CERTIFICATE----- +MIIDQTCCAimgAwIBAgITBmyfz5m/jAo54vB4ikPmljZbyjANBgkqhkiG9w0BAQsFADA5MQswCQYD +VQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6b24gUm9vdCBDQSAxMB4XDTE1 +MDUyNjAwMDAwMFoXDTM4MDExNzAwMDAwMFowOTELMAkGA1UEBhMCVVMxDzANBgNVBAoTBkFtYXpv +bjEZMBcGA1UEAxMQQW1hem9uIFJvb3QgQ0EgMTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC +ggEBALJ4gHHKeNXjca9HgFB0fW7Y14h29Jlo91ghYPl0hAEvrAIthtOgQ3pOsqTQNroBvo3bSMgH +FzZM9O6II8c+6zf1tRn4SWiw3te5djgdYZ6k/oI2peVKVuRF4fn9tBb6dNqcmzU5L/qwIFAGbHrQ +gLKm+a/sRxmPUDgH3KKHOVj4utWp+UhnMJbulHheb4mjUcAwhmahRWa6VOujw5H5SNz/0egwLX0t +dHA114gk957EWW67c4cX8jJGKLhD+rcdqsq08p8kDi1L93FcXmn/6pUCyziKrlA4b9v7LWIbxcce +VOF34GfID5yHI9Y/QCB/IIDEgEw+OyQmjgSubJrIqg0CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB +/zAOBgNVHQ8BAf8EBAMCAYYwHQYDVR0OBBYEFIQYzIU07LwMlJQuCFmcx7IQTgoIMA0GCSqGSIb3 +DQEBCwUAA4IBAQCY8jdaQZChGsV2USggNiMOruYou6r4lK5IpDB/G/wkjUu0yKGX9rbxenDIU5PM +CCjjmCXPI6T53iHTfIUJrU6adTrCC2qJeHZERxhlbI1Bjjt/msv0tadQ1wUsN+gDS63pYaACbvXy +8MWy7Vu33PqUXHeeE6V/Uq2V8viTO96LXFvKWlJbYK8U90vvo/ufQJVtMVT8QtPHRh8jrdkPSHCa +2XV4cdFyQzR1bldZwgJcJmApzyMZFo6IQ6XU5MsI+yMRQ+hDKXJioaldXgjUkK642M4UwtBV8ob2 +xJNDd2ZhwLnoQdeXeGADbkpyrqXRfboQnoZsG4q5WTP468SQvvG5 +-----END CERTIFICATE----- + +Amazon Root CA 2 +================ +-----BEGIN CERTIFICATE----- +MIIFQTCCAymgAwIBAgITBmyf0pY1hp8KD+WGePhbJruKNzANBgkqhkiG9w0BAQwFADA5MQswCQYD +VQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6b24gUm9vdCBDQSAyMB4XDTE1 +MDUyNjAwMDAwMFoXDTQwMDUyNjAwMDAwMFowOTELMAkGA1UEBhMCVVMxDzANBgNVBAoTBkFtYXpv +bjEZMBcGA1UEAxMQQW1hem9uIFJvb3QgQ0EgMjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoC +ggIBAK2Wny2cSkxKgXlRmeyKy2tgURO8TW0G/LAIjd0ZEGrHJgw12MBvIITplLGbhQPDW9tK6Mj4 +kHbZW0/jTOgGNk3Mmqw9DJArktQGGWCsN0R5hYGCrVo34A3MnaZMUnbqQ523BNFQ9lXg1dKmSYXp +N+nKfq5clU1Imj+uIFptiJXZNLhSGkOQsL9sBbm2eLfq0OQ6PBJTYv9K8nu+NQWpEjTj82R0Yiw9 +AElaKP4yRLuH3WUnAnE72kr3H9rN9yFVkE8P7K6C4Z9r2UXTu/Bfh+08LDmG2j/e7HJV63mjrdvd +fLC6HM783k81ds8P+HgfajZRRidhW+mez/CiVX18JYpvL7TFz4QuK/0NURBs+18bvBt+xa47mAEx +kv8LV/SasrlX6avvDXbR8O70zoan4G7ptGmh32n2M8ZpLpcTnqWHsFcQgTfJU7O7f/aS0ZzQGPSS +btqDT6ZjmUyl+17vIWR6IF9sZIUVyzfpYgwLKhbcAS4y2j5L9Z469hdAlO+ekQiG+r5jqFoz7Mt0 +Q5X5bGlSNscpb/xVA1wf+5+9R+vnSUeVC06JIglJ4PVhHvG/LopyboBZ/1c6+XUyo05f7O0oYtlN +c/LMgRdg7c3r3NunysV+Ar3yVAhU/bQtCSwXVEqY0VThUWcI0u1ufm8/0i2BWSlmy5A5lREedCf+ +3euvAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQWBBSw +DPBMMPQFWAJI/TPlUq9LhONmUjANBgkqhkiG9w0BAQwFAAOCAgEAqqiAjw54o+Ci1M3m9Zh6O+oA +A7CXDpO8Wqj2LIxyh6mx/H9z/WNxeKWHWc8w4Q0QshNabYL1auaAn6AFC2jkR2vHat+2/XcycuUY ++gn0oJMsXdKMdYV2ZZAMA3m3MSNjrXiDCYZohMr/+c8mmpJ5581LxedhpxfL86kSk5Nrp+gvU5LE +YFiwzAJRGFuFjWJZY7attN6a+yb3ACfAXVU3dJnJUH/jWS5E4ywl7uxMMne0nxrpS10gxdr9HIcW +xkPo1LsmmkVwXqkLN1PiRnsn/eBG8om3zEK2yygmbtmlyTrIQRNg91CMFa6ybRoVGld45pIq2WWQ +gj9sAq+uEjonljYE1x2igGOpm/HlurR8FLBOybEfdF849lHqm/osohHUqS0nGkWxr7JOcQ3AWEbW +aQbLU8uz/mtBzUF+fUwPfHJ5elnNXkoOrJupmHN5fLT0zLm4BwyydFy4x2+IoZCn9Kr5v2c69BoV +Yh63n749sSmvZ6ES8lgQGVMDMBu4Gon2nL2XA46jCfMdiyHxtN/kHNGfZQIG6lzWE7OE76KlXIx3 +KadowGuuQNKotOrN8I1LOJwZmhsoVLiJkO/KdYE+HvJkJMcYr07/R54H9jVlpNMKVv/1F2Rs76gi +JUmTtt8AF9pYfl3uxRuw0dFfIRDH+fO6AgonB8Xx1sfT4PsJYGw= +-----END CERTIFICATE----- + +Amazon Root CA 3 +================ +-----BEGIN CERTIFICATE----- +MIIBtjCCAVugAwIBAgITBmyf1XSXNmY/Owua2eiedgPySjAKBggqhkjOPQQDAjA5MQswCQYDVQQG +EwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6b24gUm9vdCBDQSAzMB4XDTE1MDUy +NjAwMDAwMFoXDTQwMDUyNjAwMDAwMFowOTELMAkGA1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZ +MBcGA1UEAxMQQW1hem9uIFJvb3QgQ0EgMzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABCmXp8ZB +f8ANm+gBG1bG8lKlui2yEujSLtf6ycXYqm0fc4E7O5hrOXwzpcVOho6AF2hiRVd9RFgdszflZwjr +Zt6jQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQWBBSrttvXBp43 +rDCGB5Fwx5zEGbF4wDAKBggqhkjOPQQDAgNJADBGAiEA4IWSoxe3jfkrBqWTrBqYaGFy+uGh0Psc +eGCmQ5nFuMQCIQCcAu/xlJyzlvnrxir4tiz+OpAUFteMYyRIHN8wfdVoOw== +-----END CERTIFICATE----- + +Amazon Root CA 4 +================ +-----BEGIN CERTIFICATE----- +MIIB8jCCAXigAwIBAgITBmyf18G7EEwpQ+Vxe3ssyBrBDjAKBggqhkjOPQQDAzA5MQswCQYDVQQG +EwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6b24gUm9vdCBDQSA0MB4XDTE1MDUy +NjAwMDAwMFoXDTQwMDUyNjAwMDAwMFowOTELMAkGA1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZ +MBcGA1UEAxMQQW1hem9uIFJvb3QgQ0EgNDB2MBAGByqGSM49AgEGBSuBBAAiA2IABNKrijdPo1MN +/sGKe0uoe0ZLY7Bi9i0b2whxIdIA6GO9mif78DluXeo9pcmBqqNbIJhFXRbb/egQbeOc4OO9X4Ri +83BkM6DLJC9wuoihKqB1+IGuYgbEgds5bimwHvouXKNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNV +HQ8BAf8EBAMCAYYwHQYDVR0OBBYEFNPsxzplbszh2naaVvuc84ZtV+WBMAoGCCqGSM49BAMDA2gA +MGUCMDqLIfG9fhGt0O9Yli/W651+kI0rz2ZVwyzjKKlwCkcO8DdZEv8tmZQoTipPNU0zWgIxAOp1 +AE47xDqUEpHJWEadIRNyp4iciuRMStuW1KyLa2tJElMzrdfkviT8tQp21KW8EA== +-----END CERTIFICATE----- + +TUBITAK Kamu SM SSL Kok Sertifikasi - Surum 1 +============================================= +-----BEGIN CERTIFICATE----- +MIIEYzCCA0ugAwIBAgIBATANBgkqhkiG9w0BAQsFADCB0jELMAkGA1UEBhMCVFIxGDAWBgNVBAcT +D0dlYnplIC0gS29jYWVsaTFCMEAGA1UEChM5VHVya2l5ZSBCaWxpbXNlbCB2ZSBUZWtub2xvamlr +IEFyYXN0aXJtYSBLdXJ1bXUgLSBUVUJJVEFLMS0wKwYDVQQLEyRLYW11IFNlcnRpZmlrYXN5b24g +TWVya2V6aSAtIEthbXUgU00xNjA0BgNVBAMTLVRVQklUQUsgS2FtdSBTTSBTU0wgS29rIFNlcnRp +ZmlrYXNpIC0gU3VydW0gMTAeFw0xMzExMjUwODI1NTVaFw00MzEwMjUwODI1NTVaMIHSMQswCQYD +VQQGEwJUUjEYMBYGA1UEBxMPR2ViemUgLSBLb2NhZWxpMUIwQAYDVQQKEzlUdXJraXllIEJpbGlt +c2VsIHZlIFRla25vbG9qaWsgQXJhc3Rpcm1hIEt1cnVtdSAtIFRVQklUQUsxLTArBgNVBAsTJEth +bXUgU2VydGlmaWthc3lvbiBNZXJrZXppIC0gS2FtdSBTTTE2MDQGA1UEAxMtVFVCSVRBSyBLYW11 +IFNNIFNTTCBLb2sgU2VydGlmaWthc2kgLSBTdXJ1bSAxMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A +MIIBCgKCAQEAr3UwM6q7a9OZLBI3hNmNe5eA027n/5tQlT6QlVZC1xl8JoSNkvoBHToP4mQ4t4y8 +6Ij5iySrLqP1N+RAjhgleYN1Hzv/bKjFxlb4tO2KRKOrbEz8HdDc72i9z+SqzvBV96I01INrN3wc +wv61A+xXzry0tcXtAA9TNypN9E8Mg/uGz8v+jE69h/mniyFXnHrfA2eJLJ2XYacQuFWQfw4tJzh0 +3+f92k4S400VIgLI4OD8D62K18lUUMw7D8oWgITQUVbDjlZ/iSIzL+aFCr2lqBs23tPcLG07xxO9 +WSMs5uWk99gL7eqQQESolbuT1dCANLZGeA4fAJNG4e7p+exPFwIDAQABo0IwQDAdBgNVHQ4EFgQU +ZT/HiobGPN08VFw1+DrtUgxHV8gwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wDQYJ +KoZIhvcNAQELBQADggEBACo/4fEyjq7hmFxLXs9rHmoJ0iKpEsdeV31zVmSAhHqT5Am5EM2fKifh +AHe+SMg1qIGf5LgsyX8OsNJLN13qudULXjS99HMpw+0mFZx+CFOKWI3QSyjfwbPfIPP54+M638yc +lNhOT8NrF7f3cuitZjO1JVOr4PhMqZ398g26rrnZqsZr+ZO7rqu4lzwDGrpDxpa5RXI4s6ehlj2R +e37AIVNMh+3yC1SVUZPVIqUNivGTDj5UDrDYyU7c8jEyVupk+eq1nRZmQnLzf9OxMUP8pI4X8W0j +q5Rm+K37DwhuJi1/FwcJsoz7UMCflo3Ptv0AnVoUmr8CRPXBwp8iXqIPoeM= +-----END CERTIFICATE----- + +GDCA TrustAUTH R5 ROOT +====================== +-----BEGIN CERTIFICATE----- +MIIFiDCCA3CgAwIBAgIIfQmX/vBH6nowDQYJKoZIhvcNAQELBQAwYjELMAkGA1UEBhMCQ04xMjAw +BgNVBAoMKUdVQU5HIERPTkcgQ0VSVElGSUNBVEUgQVVUSE9SSVRZIENPLixMVEQuMR8wHQYDVQQD +DBZHRENBIFRydXN0QVVUSCBSNSBST09UMB4XDTE0MTEyNjA1MTMxNVoXDTQwMTIzMTE1NTk1OVow +YjELMAkGA1UEBhMCQ04xMjAwBgNVBAoMKUdVQU5HIERPTkcgQ0VSVElGSUNBVEUgQVVUSE9SSVRZ +IENPLixMVEQuMR8wHQYDVQQDDBZHRENBIFRydXN0QVVUSCBSNSBST09UMIICIjANBgkqhkiG9w0B +AQEFAAOCAg8AMIICCgKCAgEA2aMW8Mh0dHeb7zMNOwZ+Vfy1YI92hhJCfVZmPoiC7XJjDp6L3TQs +AlFRwxn9WVSEyfFrs0yw6ehGXTjGoqcuEVe6ghWinI9tsJlKCvLriXBjTnnEt1u9ol2x8kECK62p +OqPseQrsXzrj/e+APK00mxqriCZ7VqKChh/rNYmDf1+uKU49tm7srsHwJ5uu4/Ts765/94Y9cnrr +pftZTqfrlYwiOXnhLQiPzLyRuEH3FMEjqcOtmkVEs7LXLM3GKeJQEK5cy4KOFxg2fZfmiJqwTTQJ +9Cy5WmYqsBebnh52nUpmMUHfP/vFBu8btn4aRjb3ZGM74zkYI+dndRTVdVeSN72+ahsmUPI2JgaQ +xXABZG12ZuGR224HwGGALrIuL4xwp9E7PLOR5G62xDtw8mySlwnNR30YwPO7ng/Wi64HtloPzgsM +R6flPri9fcebNaBhlzpBdRfMK5Z3KpIhHtmVdiBnaM8Nvd/WHwlqmuLMc3GkL30SgLdTMEZeS1SZ +D2fJpcjyIMGC7J0R38IC+xo70e0gmu9lZJIQDSri3nDxGGeCjGHeuLzRL5z7D9Ar7Rt2ueQ5Vfj4 +oR24qoAATILnsn8JuLwwoC8N9VKejveSswoAHQBUlwbgsQfZxw9cZX08bVlX5O2ljelAU58VS6Bx +9hoh49pwBiFYFIeFd3mqgnkCAwEAAaNCMEAwHQYDVR0OBBYEFOLJQJ9NzuiaoXzPDj9lxSmIahlR +MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEBCwUAA4ICAQDRSVfg +p8xoWLoBDysZzY2wYUWsEe1jUGn4H3++Fo/9nesLqjJHdtJnJO29fDMylyrHBYZmDRd9FBUb1Ov9 +H5r2XpdptxolpAqzkT9fNqyL7FeoPueBihhXOYV0GkLH6VsTX4/5COmSdI31R9KrO9b7eGZONn35 +6ZLpBN79SWP8bfsUcZNnL0dKt7n/HipzcEYwv1ryL3ml4Y0M2fmyYzeMN2WFcGpcWwlyua1jPLHd ++PwyvzeG5LuOmCd+uh8W4XAR8gPfJWIyJyYYMoSf/wA6E7qaTfRPuBRwIrHKK5DOKcFw9C+df/KQ +HtZa37dG/OaG+svgIHZ6uqbL9XzeYqWxi+7egmaKTjowHz+Ay60nugxe19CxVsp3cbK1daFQqUBD +F8Io2c9Si1vIY9RCPqAzekYu9wogRlR+ak8x8YF+QnQ4ZXMn7sZ8uI7XpTrXmKGcjBBV09tL7ECQ +8s1uV9JiDnxXk7Gnbc2dg7sq5+W2O3FYrf3RRbxake5TFW/TRQl1brqQXR4EzzffHqhmsYzmIGrv +/EhOdJhCrylvLmrH+33RZjEizIYAfmaDDEL0vTSSwxrqT8p+ck0LcIymSLumoRT2+1hEmRSuqguT +aaApJUqlyyvdimYHFngVV3Eb7PVHhPOeMTd61X8kreS8/f3MboPoDKi3QWwH3b08hpcv0g== +-----END CERTIFICATE----- + +TrustCor RootCert CA-1 +====================== +-----BEGIN CERTIFICATE----- +MIIEMDCCAxigAwIBAgIJANqb7HHzA7AZMA0GCSqGSIb3DQEBCwUAMIGkMQswCQYDVQQGEwJQQTEP +MA0GA1UECAwGUGFuYW1hMRQwEgYDVQQHDAtQYW5hbWEgQ2l0eTEkMCIGA1UECgwbVHJ1c3RDb3Ig +U3lzdGVtcyBTLiBkZSBSLkwuMScwJQYDVQQLDB5UcnVzdENvciBDZXJ0aWZpY2F0ZSBBdXRob3Jp +dHkxHzAdBgNVBAMMFlRydXN0Q29yIFJvb3RDZXJ0IENBLTEwHhcNMTYwMjA0MTIzMjE2WhcNMjkx +MjMxMTcyMzE2WjCBpDELMAkGA1UEBhMCUEExDzANBgNVBAgMBlBhbmFtYTEUMBIGA1UEBwwLUGFu +YW1hIENpdHkxJDAiBgNVBAoMG1RydXN0Q29yIFN5c3RlbXMgUy4gZGUgUi5MLjEnMCUGA1UECwwe +VHJ1c3RDb3IgQ2VydGlmaWNhdGUgQXV0aG9yaXR5MR8wHQYDVQQDDBZUcnVzdENvciBSb290Q2Vy +dCBDQS0xMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAv463leLCJhJrMxnHQFgKq1mq +jQCj/IDHUHuO1CAmujIS2CNUSSUQIpidRtLByZ5OGy4sDjjzGiVoHKZaBeYei0i/mJZ0PmnK6bV4 +pQa81QBeCQryJ3pS/C3Vseq0iWEk8xoT26nPUu0MJLq5nux+AHT6k61sKZKuUbS701e/s/OojZz0 +JEsq1pme9J7+wH5COucLlVPat2gOkEz7cD+PSiyU8ybdY2mplNgQTsVHCJCZGxdNuWxu72CVEY4h +gLW9oHPY0LJ3xEXqWib7ZnZ2+AYfYW0PVcWDtxBWcgYHpfOxGgMFZA6dWorWhnAbJN7+KIor0Gqw +/Hqi3LJ5DotlDwIDAQABo2MwYTAdBgNVHQ4EFgQU7mtJPHo/DeOxCbeKyKsZn3MzUOcwHwYDVR0j +BBgwFoAU7mtJPHo/DeOxCbeKyKsZn3MzUOcwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC +AYYwDQYJKoZIhvcNAQELBQADggEBACUY1JGPE+6PHh0RU9otRCkZoB5rMZ5NDp6tPVxBb5UrJKF5 +mDo4Nvu7Zp5I/5CQ7z3UuJu0h3U/IJvOcs+hVcFNZKIZBqEHMwwLKeXx6quj7LUKdJDHfXLy11yf +ke+Ri7fc7Waiz45mO7yfOgLgJ90WmMCV1Aqk5IGadZQ1nJBfiDcGrVmVCrDRZ9MZyonnMlo2HD6C +qFqTvsbQZJG2z9m2GM/bftJlo6bEjhcxwft+dtvTheNYsnd6djtsL1Ac59v2Z3kf9YKVmgenFK+P +3CghZwnS1k1aHBkcjndcw5QkPTJrS37UeJSDvjdNzl/HHk484IkzlQsPpTLWPFp5LBk= +-----END CERTIFICATE----- + +TrustCor RootCert CA-2 +====================== +-----BEGIN CERTIFICATE----- +MIIGLzCCBBegAwIBAgIIJaHfyjPLWQIwDQYJKoZIhvcNAQELBQAwgaQxCzAJBgNVBAYTAlBBMQ8w +DQYDVQQIDAZQYW5hbWExFDASBgNVBAcMC1BhbmFtYSBDaXR5MSQwIgYDVQQKDBtUcnVzdENvciBT +eXN0ZW1zIFMuIGRlIFIuTC4xJzAlBgNVBAsMHlRydXN0Q29yIENlcnRpZmljYXRlIEF1dGhvcml0 +eTEfMB0GA1UEAwwWVHJ1c3RDb3IgUm9vdENlcnQgQ0EtMjAeFw0xNjAyMDQxMjMyMjNaFw0zNDEy +MzExNzI2MzlaMIGkMQswCQYDVQQGEwJQQTEPMA0GA1UECAwGUGFuYW1hMRQwEgYDVQQHDAtQYW5h +bWEgQ2l0eTEkMCIGA1UECgwbVHJ1c3RDb3IgU3lzdGVtcyBTLiBkZSBSLkwuMScwJQYDVQQLDB5U +cnVzdENvciBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkxHzAdBgNVBAMMFlRydXN0Q29yIFJvb3RDZXJ0 +IENBLTIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCnIG7CKqJiJJWQdsg4foDSq8Gb +ZQWU9MEKENUCrO2fk8eHyLAnK0IMPQo+QVqedd2NyuCb7GgypGmSaIwLgQ5WoD4a3SwlFIIvl9Nk +RvRUqdw6VC0xK5mC8tkq1+9xALgxpL56JAfDQiDyitSSBBtlVkxs1Pu2YVpHI7TYabS3OtB0PAx1 +oYxOdqHp2yqlO/rOsP9+aij9JxzIsekp8VduZLTQwRVtDr4uDkbIXvRR/u8OYzo7cbrPb1nKDOOb +XUm4TOJXsZiKQlecdu/vvdFoqNL0Cbt3Nb4lggjEFixEIFapRBF37120Hapeaz6LMvYHL1cEksr1 +/p3C6eizjkxLAjHZ5DxIgif3GIJ2SDpxsROhOdUuxTTCHWKF3wP+TfSvPd9cW436cOGlfifHhi5q +jxLGhF5DUVCcGZt45vz27Ud+ez1m7xMTiF88oWP7+ayHNZ/zgp6kPwqcMWmLmaSISo5uZk3vFsQP +eSghYA2FFn3XVDjxklb9tTNMg9zXEJ9L/cb4Qr26fHMC4P99zVvh1Kxhe1fVSntb1IVYJ12/+Ctg +rKAmrhQhJ8Z3mjOAPF5GP/fDsaOGM8boXg25NSyqRsGFAnWAoOsk+xWq5Gd/bnc/9ASKL3x74xdh +8N0JqSDIvgmk0H5Ew7IwSjiqqewYmgeCK9u4nBit2uBGF6zPXQIDAQABo2MwYTAdBgNVHQ4EFgQU +2f4hQG6UnrybPZx9mCAZ5YwwYrIwHwYDVR0jBBgwFoAU2f4hQG6UnrybPZx9mCAZ5YwwYrIwDwYD +VR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAYYwDQYJKoZIhvcNAQELBQADggIBAJ5Fngw7tu/h +Osh80QA9z+LqBrWyOrsGS2h60COXdKcs8AjYeVrXWoSK2BKaG9l9XE1wxaX5q+WjiYndAfrs3fnp +kpfbsEZC89NiqpX+MWcUaViQCqoL7jcjx1BRtPV+nuN79+TMQjItSQzL/0kMmx40/W5ulop5A7Zv +2wnL/V9lFDfhOPXzYRZY5LVtDQsEGz9QLX+zx3oaFoBg+Iof6Rsqxvm6ARppv9JYx1RXCI/hOWB3 +S6xZhBqI8d3LT3jX5+EzLfzuQfogsL7L9ziUwOHQhQ+77Sxzq+3+knYaZH9bDTMJBzN7Bj8RpFxw +PIXAz+OQqIN3+tvmxYxoZxBnpVIt8MSZj3+/0WvitUfW2dCFmU2Umw9Lje4AWkcdEQOsQRivh7dv +DDqPys/cA8GiCcjl/YBeyGBCARsaU1q7N6a3vLqE6R5sGtRk2tRD/pOLS/IseRYQ1JMLiI+h2IYU +RpFHmygk71dSTlxCnKr3Sewn6EAes6aJInKc9Q0ztFijMDvd1GpUk74aTfOTlPf8hAs/hCBcNANE +xdqtvArBAs8e5ZTZ845b2EzwnexhF7sUMlQMAimTHpKG9n/v55IFDlndmQguLvqcAFLTxWYp5KeX +RKQOKIETNcX2b2TmQcTVL8w0RSXPQQCWPUouwpaYT05KnJe32x+SMsj/D1Fu1uwJ +-----END CERTIFICATE----- + +TrustCor ECA-1 +============== +-----BEGIN CERTIFICATE----- +MIIEIDCCAwigAwIBAgIJAISCLF8cYtBAMA0GCSqGSIb3DQEBCwUAMIGcMQswCQYDVQQGEwJQQTEP +MA0GA1UECAwGUGFuYW1hMRQwEgYDVQQHDAtQYW5hbWEgQ2l0eTEkMCIGA1UECgwbVHJ1c3RDb3Ig +U3lzdGVtcyBTLiBkZSBSLkwuMScwJQYDVQQLDB5UcnVzdENvciBDZXJ0aWZpY2F0ZSBBdXRob3Jp +dHkxFzAVBgNVBAMMDlRydXN0Q29yIEVDQS0xMB4XDTE2MDIwNDEyMzIzM1oXDTI5MTIzMTE3Mjgw +N1owgZwxCzAJBgNVBAYTAlBBMQ8wDQYDVQQIDAZQYW5hbWExFDASBgNVBAcMC1BhbmFtYSBDaXR5 +MSQwIgYDVQQKDBtUcnVzdENvciBTeXN0ZW1zIFMuIGRlIFIuTC4xJzAlBgNVBAsMHlRydXN0Q29y +IENlcnRpZmljYXRlIEF1dGhvcml0eTEXMBUGA1UEAwwOVHJ1c3RDb3IgRUNBLTEwggEiMA0GCSqG +SIb3DQEBAQUAA4IBDwAwggEKAoIBAQDPj+ARtZ+odnbb3w9U73NjKYKtR8aja+3+XzP4Q1HpGjOR +MRegdMTUpwHmspI+ap3tDvl0mEDTPwOABoJA6LHip1GnHYMma6ve+heRK9jGrB6xnhkB1Zem6g23 +xFUfJ3zSCNV2HykVh0A53ThFEXXQmqc04L/NyFIduUd+Dbi7xgz2c1cWWn5DkR9VOsZtRASqnKmc +p0yJF4OuowReUoCLHhIlERnXDH19MURB6tuvsBzvgdAsxZohmz3tQjtQJvLsznFhBmIhVE5/wZ0+ +fyCMgMsq2JdiyIMzkX2woloPV+g7zPIlstR8L+xNxqE6FXrntl019fZISjZFZtS6mFjBAgMBAAGj +YzBhMB0GA1UdDgQWBBREnkj1zG1I1KBLf/5ZJC+Dl5mahjAfBgNVHSMEGDAWgBREnkj1zG1I1KBL +f/5ZJC+Dl5mahjAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQsF +AAOCAQEABT41XBVwm8nHc2FvcivUwo/yQ10CzsSUuZQRg2dd4mdsdXa/uwyqNsatR5Nj3B5+1t4u +/ukZMjgDfxT2AHMsWbEhBuH7rBiVDKP/mZb3Kyeb1STMHd3BOuCYRLDE5D53sXOpZCz2HAF8P11F +hcCF5yWPldwX8zyfGm6wyuMdKulMY/okYWLW2n62HGz1Ah3UKt1VkOsqEUc8Ll50soIipX1TH0Xs +J5F95yIW6MBoNtjG8U+ARDL54dHRHareqKucBK+tIA5kmE2la8BIWJZpTdwHjFGTot+fDz2LYLSC +jaoITmJF4PkL0uDgPFveXHEnJcLmA4GLEFPjx1WitJ/X5g== +-----END CERTIFICATE----- + +SSL.com Root Certification Authority RSA +======================================== +-----BEGIN CERTIFICATE----- +MIIF3TCCA8WgAwIBAgIIeyyb0xaAMpkwDQYJKoZIhvcNAQELBQAwfDELMAkGA1UEBhMCVVMxDjAM +BgNVBAgMBVRleGFzMRAwDgYDVQQHDAdIb3VzdG9uMRgwFgYDVQQKDA9TU0wgQ29ycG9yYXRpb24x +MTAvBgNVBAMMKFNTTC5jb20gUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSBSU0EwHhcNMTYw +MjEyMTczOTM5WhcNNDEwMjEyMTczOTM5WjB8MQswCQYDVQQGEwJVUzEOMAwGA1UECAwFVGV4YXMx +EDAOBgNVBAcMB0hvdXN0b24xGDAWBgNVBAoMD1NTTCBDb3Jwb3JhdGlvbjExMC8GA1UEAwwoU1NM +LmNvbSBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IFJTQTCCAiIwDQYJKoZIhvcNAQEBBQAD +ggIPADCCAgoCggIBAPkP3aMrfcvQKv7sZ4Wm5y4bunfh4/WvpOz6Sl2RxFdHaxh3a3by/ZPkPQ/C +Fp4LZsNWlJ4Xg4XOVu/yFv0AYvUiCVToZRdOQbngT0aXqhvIuG5iXmmxX9sqAn78bMrzQdjt0Oj8 +P2FI7bADFB0QDksZ4LtO7IZl/zbzXmcCC52GVWH9ejjt/uIZALdvoVBidXQ8oPrIJZK0bnoix/ge +oeOy3ZExqysdBP+lSgQ36YWkMyv94tZVNHwZpEpox7Ko07fKoZOI68GXvIz5HdkihCR0xwQ9aqkp +k8zruFvh/l8lqjRYyMEjVJ0bmBHDOJx+PYZspQ9AhnwC9FwCTyjLrnGfDzrIM/4RJTXq/LrFYD3Z +fBjVsqnTdXgDciLKOsMf7yzlLqn6niy2UUb9rwPW6mBo6oUWNmuF6R7As93EJNyAKoFBbZQ+yODJ +gUEAnl6/f8UImKIYLEJAs/lvOCdLToD0PYFH4Ih86hzOtXVcUS4cK38acijnALXRdMbX5J+tB5O2 +UzU1/Dfkw/ZdFr4hc96SCvigY2q8lpJqPvi8ZVWb3vUNiSYE/CUapiVpy8JtynziWV+XrOvvLsi8 +1xtZPCvM8hnIk2snYxnP/Okm+Mpxm3+T/jRnhE6Z6/yzeAkzcLpmpnbtG3PrGqUNxCITIJRWCk4s +bE6x/c+cCbqiM+2HAgMBAAGjYzBhMB0GA1UdDgQWBBTdBAkHovV6fVJTEpKV7jiAJQ2mWTAPBgNV +HRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFN0ECQei9Xp9UlMSkpXuOIAlDaZZMA4GA1UdDwEB/wQE +AwIBhjANBgkqhkiG9w0BAQsFAAOCAgEAIBgRlCn7Jp0cHh5wYfGVcpNxJK1ok1iOMq8bs3AD/CUr +dIWQPXhq9LmLpZc7tRiRux6n+UBbkflVma8eEdBcHadm47GUBwwyOabqG7B52B2ccETjit3E+ZUf +ijhDPwGFpUenPUayvOUiaPd7nNgsPgohyC0zrL/FgZkxdMF1ccW+sfAjRfSda/wZY52jvATGGAsl +u1OJD7OAUN5F7kR/q5R4ZJjT9ijdh9hwZXT7DrkT66cPYakylszeu+1jTBi7qUD3oFRuIIhxdRjq +erQ0cuAjJ3dctpDqhiVAq+8zD8ufgr6iIPv2tS0a5sKFsXQP+8hlAqRSAUfdSSLBv9jra6x+3uxj +MxW3IwiPxg+NQVrdjsW5j+VFP3jbutIbQLH+cU0/4IGiul607BXgk90IH37hVZkLId6Tngr75qNJ +vTYw/ud3sqB1l7UtgYgXZSD32pAAn8lSzDLKNXz1PQ/YK9f1JmzJBjSWFupwWRoyeXkLtoh/D1JI +Pb9s2KJELtFOt3JY04kTlf5Eq/jXixtunLwsoFvVagCvXzfh1foQC5ichucmj87w7G6KVwuA406y +wKBjYZC6VWg3dGq2ktufoYYitmUnDuy2n0Jg5GfCtdpBC8TTi2EbvPofkSvXRAdeuims2cXp71NI +WuuA8ShYIc2wBlX7Jz9TkHCpBB5XJ7k= +-----END CERTIFICATE----- + +SSL.com Root Certification Authority ECC +======================================== +-----BEGIN CERTIFICATE----- +MIICjTCCAhSgAwIBAgIIdebfy8FoW6gwCgYIKoZIzj0EAwIwfDELMAkGA1UEBhMCVVMxDjAMBgNV +BAgMBVRleGFzMRAwDgYDVQQHDAdIb3VzdG9uMRgwFgYDVQQKDA9TU0wgQ29ycG9yYXRpb24xMTAv +BgNVBAMMKFNTTC5jb20gUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSBFQ0MwHhcNMTYwMjEy +MTgxNDAzWhcNNDEwMjEyMTgxNDAzWjB8MQswCQYDVQQGEwJVUzEOMAwGA1UECAwFVGV4YXMxEDAO +BgNVBAcMB0hvdXN0b24xGDAWBgNVBAoMD1NTTCBDb3Jwb3JhdGlvbjExMC8GA1UEAwwoU1NMLmNv +bSBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IEVDQzB2MBAGByqGSM49AgEGBSuBBAAiA2IA +BEVuqVDEpiM2nl8ojRfLliJkP9x6jh3MCLOicSS6jkm5BBtHllirLZXI7Z4INcgn64mMU1jrYor+ +8FsPazFSY0E7ic3s7LaNGdM0B9y7xgZ/wkWV7Mt/qCPgCemB+vNH06NjMGEwHQYDVR0OBBYEFILR +hXMw5zUE044CkvvlpNHEIejNMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUgtGFczDnNQTT +jgKS++Wk0cQh6M0wDgYDVR0PAQH/BAQDAgGGMAoGCCqGSM49BAMCA2cAMGQCMG/n61kRpGDPYbCW +e+0F+S8Tkdzt5fxQaxFGRrMcIQBiu77D5+jNB5n5DQtdcj7EqgIwH7y6C+IwJPt8bYBVCpk+gA0z +5Wajs6O7pdWLjwkspl1+4vAHCGht0nxpbl/f5Wpl +-----END CERTIFICATE----- + +SSL.com EV Root Certification Authority RSA R2 +============================================== +-----BEGIN CERTIFICATE----- +MIIF6zCCA9OgAwIBAgIIVrYpzTS8ePYwDQYJKoZIhvcNAQELBQAwgYIxCzAJBgNVBAYTAlVTMQ4w +DAYDVQQIDAVUZXhhczEQMA4GA1UEBwwHSG91c3RvbjEYMBYGA1UECgwPU1NMIENvcnBvcmF0aW9u +MTcwNQYDVQQDDC5TU0wuY29tIEVWIFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgUlNBIFIy +MB4XDTE3MDUzMTE4MTQzN1oXDTQyMDUzMDE4MTQzN1owgYIxCzAJBgNVBAYTAlVTMQ4wDAYDVQQI +DAVUZXhhczEQMA4GA1UEBwwHSG91c3RvbjEYMBYGA1UECgwPU1NMIENvcnBvcmF0aW9uMTcwNQYD +VQQDDC5TU0wuY29tIEVWIFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgUlNBIFIyMIICIjAN +BgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAjzZlQOHWTcDXtOlG2mvqM0fNTPl9fb69LT3w23jh +hqXZuglXaO1XPqDQCEGD5yhBJB/jchXQARr7XnAjssufOePPxU7Gkm0mxnu7s9onnQqG6YE3Bf7w +cXHswxzpY6IXFJ3vG2fThVUCAtZJycxa4bH3bzKfydQ7iEGonL3Lq9ttewkfokxykNorCPzPPFTO +Zw+oz12WGQvE43LrrdF9HSfvkusQv1vrO6/PgN3B0pYEW3p+pKk8OHakYo6gOV7qd89dAFmPZiw+ +B6KjBSYRaZfqhbcPlgtLyEDhULouisv3D5oi53+aNxPN8k0TayHRwMwi8qFG9kRpnMphNQcAb9Zh +CBHqurj26bNg5U257J8UZslXWNvNh2n4ioYSA0e/ZhN2rHd9NCSFg83XqpyQGp8hLH94t2S42Oim +9HizVcuE0jLEeK6jj2HdzghTreyI/BXkmg3mnxp3zkyPuBQVPWKchjgGAGYS5Fl2WlPAApiiECto +RHuOec4zSnaqW4EWG7WK2NAAe15itAnWhmMOpgWVSbooi4iTsjQc2KRVbrcc0N6ZVTsj9CLg+Slm +JuwgUHfbSguPvuUCYHBBXtSuUDkiFCbLsjtzdFVHB3mBOagwE0TlBIqulhMlQg+5U8Sb/M3kHN48 ++qvWBkofZ6aYMBzdLNvcGJVXZsb/XItW9XcCAwEAAaNjMGEwDwYDVR0TAQH/BAUwAwEB/zAfBgNV +HSMEGDAWgBT5YLvU49U09rj1BoAlp3PbRmmonjAdBgNVHQ4EFgQU+WC71OPVNPa49QaAJadz20Zp +qJ4wDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEBCwUAA4ICAQBWs47LCp1Jjr+kxJG7ZhcFUZh1 +++VQLHqe8RT6q9OKPv+RKY9ji9i0qVQBDb6Thi/5Sm3HXvVX+cpVHBK+Rw82xd9qt9t1wkclf7nx +Y/hoLVUE0fKNsKTPvDxeH3jnpaAgcLAExbf3cqfeIg29MyVGjGSSJuM+LmOW2puMPfgYCdcDzH2G +guDKBAdRUNf/ktUM79qGn5nX67evaOI5JpS6aLe/g9Pqemc9YmeuJeVy6OLk7K4S9ksrPJ/psEDz +OFSz/bdoyNrGj1E8svuR3Bznm53htw1yj+KkxKl4+esUrMZDBcJlOSgYAsOCsp0FvmXtll9ldDz7 +CTUue5wT/RsPXcdtgTpWD8w74a8CLyKsRspGPKAcTNZEtF4uXBVmCeEmKf7GUmG6sXP/wwyc5Wxq +lD8UykAWlYTzWamsX0xhk23RO8yilQwipmdnRC652dKKQbNmC1r7fSOl8hqw/96bg5Qu0T/fkreR +rwU7ZcegbLHNYhLDkBvjJc40vG93drEQw/cFGsDWr3RiSBd3kmmQYRzelYB0VI8YHMPzA9C/pEN1 +hlMYegouCRw2n5H9gooiS9EOUCXdywMMF8mDAAhONU2Ki+3wApRmLER/y5UnlhetCTCstnEXbosX +9hwJ1C07mKVx01QT2WDz9UtmT/rx7iASjbSsV7FFY6GsdqnC+w== +-----END CERTIFICATE----- + +SSL.com EV Root Certification Authority ECC +=========================================== +-----BEGIN CERTIFICATE----- +MIIClDCCAhqgAwIBAgIILCmcWxbtBZUwCgYIKoZIzj0EAwIwfzELMAkGA1UEBhMCVVMxDjAMBgNV +BAgMBVRleGFzMRAwDgYDVQQHDAdIb3VzdG9uMRgwFgYDVQQKDA9TU0wgQ29ycG9yYXRpb24xNDAy +BgNVBAMMK1NTTC5jb20gRVYgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSBFQ0MwHhcNMTYw +MjEyMTgxNTIzWhcNNDEwMjEyMTgxNTIzWjB/MQswCQYDVQQGEwJVUzEOMAwGA1UECAwFVGV4YXMx +EDAOBgNVBAcMB0hvdXN0b24xGDAWBgNVBAoMD1NTTCBDb3Jwb3JhdGlvbjE0MDIGA1UEAwwrU1NM +LmNvbSBFViBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IEVDQzB2MBAGByqGSM49AgEGBSuB +BAAiA2IABKoSR5CYG/vvw0AHgyBO8TCCogbR8pKGYfL2IWjKAMTH6kMAVIbc/R/fALhBYlzccBYy +3h+Z1MzFB8gIH2EWB1E9fVwHU+M1OIzfzZ/ZLg1KthkuWnBaBu2+8KGwytAJKaNjMGEwHQYDVR0O +BBYEFFvKXuXe0oGqzagtZFG22XKbl+ZPMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUW8pe +5d7SgarNqC1kUbbZcpuX5k8wDgYDVR0PAQH/BAQDAgGGMAoGCCqGSM49BAMCA2gAMGUCMQCK5kCJ +N+vp1RPZytRrJPOwPYdGWBrssd9v+1a6cGvHOMzosYxPD/fxZ3YOg9AeUY8CMD32IygmTMZgh5Mm +m7I1HrrW9zzRHM76JTymGoEVW/MSD2zuZYrJh6j5B+BimoxcSg== +-----END CERTIFICATE----- + +GlobalSign Root CA - R6 +======================= +-----BEGIN CERTIFICATE----- +MIIFgzCCA2ugAwIBAgIORea7A4Mzw4VlSOb/RVEwDQYJKoZIhvcNAQEMBQAwTDEgMB4GA1UECxMX +R2xvYmFsU2lnbiBSb290IENBIC0gUjYxEzARBgNVBAoTCkdsb2JhbFNpZ24xEzARBgNVBAMTCkds +b2JhbFNpZ24wHhcNMTQxMjEwMDAwMDAwWhcNMzQxMjEwMDAwMDAwWjBMMSAwHgYDVQQLExdHbG9i +YWxTaWduIFJvb3QgQ0EgLSBSNjETMBEGA1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFs +U2lnbjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAJUH6HPKZvnsFMp7PPcNCPG0RQss +grRIxutbPK6DuEGSMxSkb3/pKszGsIhrxbaJ0cay/xTOURQh7ErdG1rG1ofuTToVBu1kZguSgMpE +3nOUTvOniX9PeGMIyBJQbUJmL025eShNUhqKGoC3GYEOfsSKvGRMIRxDaNc9PIrFsmbVkJq3MQbF +vuJtMgamHvm566qjuL++gmNQ0PAYid/kD3n16qIfKtJwLnvnvJO7bVPiSHyMEAc4/2ayd2F+4OqM +PKq0pPbzlUoSB239jLKJz9CgYXfIWHSw1CM69106yqLbnQneXUQtkPGBzVeS+n68UARjNN9rkxi+ +azayOeSsJDa38O+2HBNXk7besvjihbdzorg1qkXy4J02oW9UivFyVm4uiMVRQkQVlO6jxTiWm05O +WgtH8wY2SXcwvHE35absIQh1/OZhFj931dmRl4QKbNQCTXTAFO39OfuD8l4UoQSwC+n+7o/hbguy +CLNhZglqsQY6ZZZZwPA1/cnaKI0aEYdwgQqomnUdnjqGBQCe24DWJfncBZ4nWUx2OVvq+aWh2IMP +0f/fMBH5hc8zSPXKbWQULHpYT9NLCEnFlWQaYw55PfWzjMpYrZxCRXluDocZXFSxZba/jJvcE+kN +b7gu3GduyYsRtYQUigAZcIN5kZeR1BonvzceMgfYFGM8KEyvAgMBAAGjYzBhMA4GA1UdDwEB/wQE +AwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBSubAWjkxPioufi1xzWx/B/yGdToDAfBgNV +HSMEGDAWgBSubAWjkxPioufi1xzWx/B/yGdToDANBgkqhkiG9w0BAQwFAAOCAgEAgyXt6NH9lVLN +nsAEoJFp5lzQhN7craJP6Ed41mWYqVuoPId8AorRbrcWc+ZfwFSY1XS+wc3iEZGtIxg93eFyRJa0 +lV7Ae46ZeBZDE1ZXs6KzO7V33EByrKPrmzU+sQghoefEQzd5Mr6155wsTLxDKZmOMNOsIeDjHfrY +BzN2VAAiKrlNIC5waNrlU/yDXNOd8v9EDERm8tLjvUYAGm0CuiVdjaExUd1URhxN25mW7xocBFym +Fe944Hn+Xds+qkxV/ZoVqW/hpvvfcDDpw+5CRu3CkwWJ+n1jez/QcYF8AOiYrg54NMMl+68KnyBr +3TsTjxKM4kEaSHpzoHdpx7Zcf4LIHv5YGygrqGytXm3ABdJ7t+uA/iU3/gKbaKxCXcPu9czc8FB1 +0jZpnOZ7BN9uBmm23goJSFmH63sUYHpkqmlD75HHTOwY3WzvUy2MmeFe8nI+z1TIvWfspA9MRf/T +uTAjB0yPEL+GltmZWrSZVxykzLsViVO6LAUP5MSeGbEYNNVMnbrt9x+vJJUEeKgDu+6B5dpffItK +oZB0JaezPkvILFa9x8jvOOJckvB595yEunQtYQEgfn7R8k8HWV+LLUNS60YMlOH1Zkd5d9VUWx+t +JDfLRVpOoERIyNiwmcUVhAn21klJwGW45hpxbqCo8YLoRT5s1gLXCmeDBVrJpBA= +-----END CERTIFICATE----- + +OISTE WISeKey Global Root GC CA +=============================== +-----BEGIN CERTIFICATE----- +MIICaTCCAe+gAwIBAgIQISpWDK7aDKtARb8roi066jAKBggqhkjOPQQDAzBtMQswCQYDVQQGEwJD +SDEQMA4GA1UEChMHV0lTZUtleTEiMCAGA1UECxMZT0lTVEUgRm91bmRhdGlvbiBFbmRvcnNlZDEo +MCYGA1UEAxMfT0lTVEUgV0lTZUtleSBHbG9iYWwgUm9vdCBHQyBDQTAeFw0xNzA1MDkwOTQ4MzRa +Fw00MjA1MDkwOTU4MzNaMG0xCzAJBgNVBAYTAkNIMRAwDgYDVQQKEwdXSVNlS2V5MSIwIAYDVQQL +ExlPSVNURSBGb3VuZGF0aW9uIEVuZG9yc2VkMSgwJgYDVQQDEx9PSVNURSBXSVNlS2V5IEdsb2Jh +bCBSb290IEdDIENBMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAETOlQwMYPchi82PG6s4nieUqjFqdr +VCTbUf/q9Akkwwsin8tqJ4KBDdLArzHkdIJuyiXZjHWd8dvQmqJLIX4Wp2OQ0jnUsYd4XxiWD1Ab +NTcPasbc2RNNpI6QN+a9WzGRo1QwUjAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAd +BgNVHQ4EFgQUSIcUrOPDnpBgOtfKie7TrYy0UGYwEAYJKwYBBAGCNxUBBAMCAQAwCgYIKoZIzj0E +AwMDaAAwZQIwJsdpW9zV57LnyAyMjMPdeYwbY9XJUpROTYJKcx6ygISpJcBMWm1JKWB4E+J+SOtk +AjEA2zQgMgj/mkkCtojeFK9dbJlxjRo/i9fgojaGHAeCOnZT/cKi7e97sIBPWA9LUzm9 +-----END CERTIFICATE----- + +GTS Root R1 +=========== +-----BEGIN CERTIFICATE----- +MIIFWjCCA0KgAwIBAgIQbkepxUtHDA3sM9CJuRz04TANBgkqhkiG9w0BAQwFADBHMQswCQYDVQQG +EwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJv +b3QgUjEwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAwMDAwWjBHMQswCQYDVQQGEwJVUzEiMCAG +A1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjEwggIi +MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC2EQKLHuOhd5s73L+UPreVp0A8of2C+X0yBoJx +9vaMf/vo27xqLpeXo4xL+Sv2sfnOhB2x+cWX3u+58qPpvBKJXqeqUqv4IyfLpLGcY9vXmX7wCl7r +aKb0xlpHDU0QM+NOsROjyBhsS+z8CZDfnWQpJSMHobTSPS5g4M/SCYe7zUjwTcLCeoiKu7rPWRnW +r4+wB7CeMfGCwcDfLqZtbBkOtdh+JhpFAz2weaSUKK0PfyblqAj+lug8aJRT7oM6iCsVlgmy4HqM +LnXWnOunVmSPlk9orj2XwoSPwLxAwAtcvfaHszVsrBhQf4TgTM2S0yDpM7xSma8ytSmzJSq0SPly +4cpk9+aCEI3oncKKiPo4Zor8Y/kB+Xj9e1x3+naH+uzfsQ55lVe0vSbv1gHR6xYKu44LtcXFilWr +06zqkUspzBmkMiVOKvFlRNACzqrOSbTqn3yDsEB750Orp2yjj32JgfpMpf/VjsPOS+C12LOORc92 +wO1AK/1TD7Cn1TsNsYqiA94xrcx36m97PtbfkSIS5r762DL8EGMUUXLeXdYWk70paDPvOmbsB4om +3xPXV2V4J95eSRQAogB/mqghtqmxlbCluQ0WEdrHbEg8QOB+DVrNVjzRlwW5y0vtOUucxD/SVRNu +JLDWcfr0wbrM7Rv1/oFB2ACYPTrIrnqYNxgFlQIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYD +VR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU5K8rJnEaK0gnhS9SZizv8IkTcT4wDQYJKoZIhvcNAQEM +BQADggIBADiWCu49tJYeX++dnAsznyvgyv3SjgofQXSlfKqE1OXyHuY3UjKcC9FhHb8owbZEKTV1 +d5iyfNm9dKyKaOOpMQkpAWBz40d8U6iQSifvS9efk+eCNs6aaAyC58/UEBZvXw6ZXPYfcX3v73sv +fuo21pdwCxXu11xWajOl40k4DLh9+42FpLFZXvRq4d2h9mREruZRgyFmxhE+885H7pwoHyXa/6xm +ld01D1zvICxi/ZG6qcz8WpyTgYMpl0p8WnK0OdC3d8t5/Wk6kjftbjhlRn7pYL15iJdfOBL07q9b +gsiG1eGZbYwE8na6SfZu6W0eX6DvJ4J2QPim01hcDyxC2kLGe4g0x8HYRZvBPsVhHdljUEn2NIVq +4BjFbkerQUIpm/ZgDdIx02OYI5NaAIFItO/Nis3Jz5nu2Z6qNuFoS3FJFDYoOj0dzpqPJeaAcWEr +tXvM+SUWgeExX6GjfhaknBZqlxi9dnKlC54dNuYvoS++cJEPqOba+MSSQGwlfnuzCdyyF62ARPBo +pY+Udf90WuioAnwMCeKpSwughQtiue+hMZL77/ZRBIls6Kl0obsXs7X9SQ98POyDGCBDTtWTurQ0 +sR8WNh8M5mQ5Fkzc4P4dyKliPUDqysU0ArSuiYgzNdwsE3PYJ/HQcu51OyLemGhmW/HGY0dVHLql +CFF1pkgl +-----END CERTIFICATE----- + +GTS Root R2 +=========== +-----BEGIN CERTIFICATE----- +MIIFWjCCA0KgAwIBAgIQbkepxlqz5yDFMJo/aFLybzANBgkqhkiG9w0BAQwFADBHMQswCQYDVQQG +EwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJv +b3QgUjIwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAwMDAwWjBHMQswCQYDVQQGEwJVUzEiMCAG +A1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjIwggIi +MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDO3v2m++zsFDQ8BwZabFn3GTXd98GdVarTzTuk +k3LvCvptnfbwhYBboUhSnznFt+4orO/LdmgUud+tAWyZH8QiHZ/+cnfgLFuv5AS/T3KgGjSY6Dlo +7JUle3ah5mm5hRm9iYz+re026nO8/4Piy33B0s5Ks40FnotJk9/BW9BuXvAuMC6C/Pq8tBcKSOWI +m8Wba96wyrQD8Nr0kLhlZPdcTK3ofmZemde4wj7I0BOdre7kRXuJVfeKH2JShBKzwkCX44ofR5Gm +dFrS+LFjKBC4swm4VndAoiaYecb+3yXuPuWgf9RhD1FLPD+M2uFwdNjCaKH5wQzpoeJ/u1U8dgbu +ak7MkogwTZq9TwtImoS1mKPV+3PBV2HdKFZ1E66HjucMUQkQdYhMvI35ezzUIkgfKtzra7tEscsz +cTJGr61K8YzodDqs5xoic4DSMPclQsciOzsSrZYuxsN2B6ogtzVJV+mSSeh2FnIxZyuWfoqjx5RW +Ir9qS34BIbIjMt/kmkRtWVtd9QCgHJvGeJeNkP+byKq0rxFROV7Z+2et1VsRnTKaG73Vululycsl +aVNVJ1zgyjbLiGH7HrfQy+4W+9OmTN6SpdTi3/UGVN4unUu0kzCqgc7dGtxRcw1PcOnlthYhGXmy +5okLdWTK1au8CcEYof/UVKGFPP0UJAOyh9OktwIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYD +VR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUu//KjiOfT5nK2+JopqUVJxce2Q4wDQYJKoZIhvcNAQEM +BQADggIBALZp8KZ3/p7uC4Gt4cCpx/k1HUCCq+YEtN/L9x0Pg/B+E02NjO7jMyLDOfxA325BS0JT +vhaI8dI4XsRomRyYUpOM52jtG2pzegVATX9lO9ZY8c6DR2Dj/5epnGB3GFW1fgiTz9D2PGcDFWEJ ++YF59exTpJ/JjwGLc8R3dtyDovUMSRqodt6Sm2T4syzFJ9MHwAiApJiS4wGWAqoC7o87xdFtCjMw +c3i5T1QWvwsHoaRc5svJXISPD+AVdyx+Jn7axEvbpxZ3B7DNdehyQtaVhJ2Gg/LkkM0JR9SLA3Da +WsYDQvTtN6LwG1BUSw7YhN4ZKJmBR64JGz9I0cNv4rBgF/XuIwKl2gBbbZCr7qLpGzvpx0QnRY5r +n/WkhLx3+WuXrD5RRaIRpsyF7gpo8j5QOHokYh4XIDdtak23CZvJ/KRY9bb7nE4Yu5UC56Gtmwfu +Nmsk0jmGwZODUNKBRqhfYlcsu2xkiAhu7xNUX90txGdj08+JN7+dIPT7eoOboB6BAFDC5AwiWVIQ +7UNWhwD4FFKnHYuTjKJNRn8nxnGbJN7k2oaLDX5rIMHAnuFl2GqjpuiFizoHCBy69Y9Vmhh1fuXs +gWbRIXOhNUQLgD1bnF5vKheW0YMjiGZt5obicDIvUiLnyOd/xCxgXS/Dr55FBcOEArf9LAhST4Ld +o/DUhgkC +-----END CERTIFICATE----- + +GTS Root R3 +=========== +-----BEGIN CERTIFICATE----- +MIICDDCCAZGgAwIBAgIQbkepx2ypcyRAiQ8DVd2NHTAKBggqhkjOPQQDAzBHMQswCQYDVQQGEwJV +UzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3Qg +UjMwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAwMDAwWjBHMQswCQYDVQQGEwJVUzEiMCAGA1UE +ChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjMwdjAQBgcq +hkjOPQIBBgUrgQQAIgNiAAQfTzOHMymKoYTey8chWEGJ6ladK0uFxh1MJ7x/JlFyb+Kf1qPKzEUU +Rout736GjOyxfi//qXGdGIRFBEFVbivqJn+7kAHjSxm65FSWRQmx1WyRRK2EE46ajA2ADDL24Cej +QjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTB8Sa6oC2uhYHP +0/EqEr24Cmf9vDAKBggqhkjOPQQDAwNpADBmAjEAgFukfCPAlaUs3L6JbyO5o91lAFJekazInXJ0 +glMLfalAvWhgxeG4VDvBNhcl2MG9AjEAnjWSdIUlUfUk7GRSJFClH9voy8l27OyCbvWFGFPouOOa +KaqW04MjyaR7YbPMAuhd +-----END CERTIFICATE----- + +GTS Root R4 +=========== +-----BEGIN CERTIFICATE----- +MIICCjCCAZGgAwIBAgIQbkepyIuUtui7OyrYorLBmTAKBggqhkjOPQQDAzBHMQswCQYDVQQGEwJV +UzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3Qg +UjQwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAwMDAwWjBHMQswCQYDVQQGEwJVUzEiMCAGA1UE +ChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjQwdjAQBgcq +hkjOPQIBBgUrgQQAIgNiAATzdHOnaItgrkO4NcWBMHtLSZ37wWHO5t5GvWvVYRg1rkDdc/eJkTBa +6zzuhXyiQHY7qca4R9gq55KRanPpsXI5nymfopjTX15YhmUPoYRlBtHci8nHc8iMai/lxKvRHYqj +QjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBSATNbrdP9JNqPV +2Py1PsVq8JQdjDAKBggqhkjOPQQDAwNnADBkAjBqUFJ0CMRw3J5QdCHojXohw0+WbhXRIjVhLfoI +N+4Zba3bssx9BzT1YBkstTTZbyACMANxsbqjYAuG7ZoIapVon+Kz4ZNkfF6Tpt95LY2F45TPI11x +zPKwTdb+mciUqXWi4w== +-----END CERTIFICATE----- + +UCA Global G2 Root +================== +-----BEGIN CERTIFICATE----- +MIIFRjCCAy6gAwIBAgIQXd+x2lqj7V2+WmUgZQOQ7zANBgkqhkiG9w0BAQsFADA9MQswCQYDVQQG +EwJDTjERMA8GA1UECgwIVW5pVHJ1c3QxGzAZBgNVBAMMElVDQSBHbG9iYWwgRzIgUm9vdDAeFw0x +NjAzMTEwMDAwMDBaFw00MDEyMzEwMDAwMDBaMD0xCzAJBgNVBAYTAkNOMREwDwYDVQQKDAhVbmlU +cnVzdDEbMBkGA1UEAwwSVUNBIEdsb2JhbCBHMiBSb290MIICIjANBgkqhkiG9w0BAQEFAAOCAg8A +MIICCgKCAgEAxeYrb3zvJgUno4Ek2m/LAfmZmqkywiKHYUGRO8vDaBsGxUypK8FnFyIdK+35KYmT +oni9kmugow2ifsqTs6bRjDXVdfkX9s9FxeV67HeToI8jrg4aA3++1NDtLnurRiNb/yzmVHqUwCoV +8MmNsHo7JOHXaOIxPAYzRrZUEaalLyJUKlgNAQLx+hVRZ2zA+te2G3/RVogvGjqNO7uCEeBHANBS +h6v7hn4PJGtAnTRnvI3HLYZveT6OqTwXS3+wmeOwcWDcC/Vkw85DvG1xudLeJ1uK6NjGruFZfc8o +LTW4lVYa8bJYS7cSN8h8s+1LgOGN+jIjtm+3SJUIsUROhYw6AlQgL9+/V087OpAh18EmNVQg7Mc/ +R+zvWr9LesGtOxdQXGLYD0tK3Cv6brxzks3sx1DoQZbXqX5t2Okdj4q1uViSukqSKwxW/YDrCPBe +KW4bHAyvj5OJrdu9o54hyokZ7N+1wxrrFv54NkzWbtA+FxyQF2smuvt6L78RHBgOLXMDj6DlNaBa +4kx1HXHhOThTeEDMg5PXCp6dW4+K5OXgSORIskfNTip1KnvyIvbJvgmRlld6iIis7nCs+dwp4wwc +OxJORNanTrAmyPPZGpeRaOrvjUYG0lZFWJo8DA+DuAUlwznPO6Q0ibd5Ei9Hxeepl2n8pndntd97 +8XplFeRhVmUCAwEAAaNCMEAwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0O +BBYEFIHEjMz15DD/pQwIX4wVZyF0Ad/fMA0GCSqGSIb3DQEBCwUAA4ICAQATZSL1jiutROTL/7lo +5sOASD0Ee/ojL3rtNtqyzm325p7lX1iPyzcyochltq44PTUbPrw7tgTQvPlJ9Zv3hcU2tsu8+Mg5 +1eRfB70VVJd0ysrtT7q6ZHafgbiERUlMjW+i67HM0cOU2kTC5uLqGOiiHycFutfl1qnN3e92mI0A +Ds0b+gO3joBYDic/UvuUospeZcnWhNq5NXHzJsBPd+aBJ9J3O5oUb3n09tDh05S60FdRvScFDcH9 +yBIw7m+NESsIndTUv4BFFJqIRNow6rSn4+7vW4LVPtateJLbXDzz2K36uGt/xDYotgIVilQsnLAX +c47QN6MUPJiVAAwpBVueSUmxX8fjy88nZY41F7dXyDDZQVu5FLbowg+UMaeUmMxq67XhJ/UQqAHo +jhJi6IjMtX9Gl8CbEGY4GjZGXyJoPd/JxhMnq1MGrKI8hgZlb7F+sSlEmqO6SWkoaY/X5V+tBIZk +bxqgDMUIYs6Ao9Dz7GjevjPHF1t/gMRMTLGmhIrDO7gJzRSBuhjjVFc2/tsvfEehOjPI+Vg7RE+x +ygKJBJYoaMVLuCaJu9YzL1DV/pqJuhgyklTGW+Cd+V7lDSKb9triyCGyYiGqhkCyLmTTX8jjfhFn +RR8F/uOi77Oos/N9j/gMHyIfLXC0uAE0djAA5SN4p1bXUB+K+wb1whnw0A== +-----END CERTIFICATE----- + +UCA Extended Validation Root +============================ +-----BEGIN CERTIFICATE----- +MIIFWjCCA0KgAwIBAgIQT9Irj/VkyDOeTzRYZiNwYDANBgkqhkiG9w0BAQsFADBHMQswCQYDVQQG +EwJDTjERMA8GA1UECgwIVW5pVHJ1c3QxJTAjBgNVBAMMHFVDQSBFeHRlbmRlZCBWYWxpZGF0aW9u +IFJvb3QwHhcNMTUwMzEzMDAwMDAwWhcNMzgxMjMxMDAwMDAwWjBHMQswCQYDVQQGEwJDTjERMA8G +A1UECgwIVW5pVHJ1c3QxJTAjBgNVBAMMHFVDQSBFeHRlbmRlZCBWYWxpZGF0aW9uIFJvb3QwggIi +MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCpCQcoEwKwmeBkqh5DFnpzsZGgdT6o+uM4AHrs +iWogD4vFsJszA1qGxliG1cGFu0/GnEBNyr7uaZa4rYEwmnySBesFK5pI0Lh2PpbIILvSsPGP2KxF +Rv+qZ2C0d35qHzwaUnoEPQc8hQ2E0B92CvdqFN9y4zR8V05WAT558aopO2z6+I9tTcg1367r3CTu +eUWnhbYFiN6IXSV8l2RnCdm/WhUFhvMJHuxYMjMR83dksHYf5BA1FxvyDrFspCqjc/wJHx4yGVMR +59mzLC52LqGj3n5qiAno8geK+LLNEOfic0CTuwjRP+H8C5SzJe98ptfRr5//lpr1kXuYC3fUfugH +0mK1lTnj8/FtDw5lhIpjVMWAtuCeS31HJqcBCF3RiJ7XwzJE+oJKCmhUfzhTA8ykADNkUVkLo4KR +el7sFsLzKuZi2irbWWIQJUoqgQtHB0MGcIfS+pMRKXpITeuUx3BNr2fVUbGAIAEBtHoIppB/TuDv +B0GHr2qlXov7z1CymlSvw4m6WC31MJixNnI5fkkE/SmnTHnkBVfblLkWU41Gsx2VYVdWf6/wFlth +WG82UBEL2KwrlRYaDh8IzTY0ZRBiZtWAXxQgXy0MoHgKaNYs1+lvK9JKBZP8nm9rZ/+I8U6laUpS +NwXqxhaN0sSZ0YIrO7o1dfdRUVjzyAfd5LQDfwIDAQABo0IwQDAdBgNVHQ4EFgQU2XQ65DA9DfcS +3H5aBZ8eNJr34RQwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAYYwDQYJKoZIhvcNAQEL +BQADggIBADaNl8xCFWQpN5smLNb7rhVpLGsaGvdftvkHTFnq88nIua7Mui563MD1sC3AO6+fcAUR +ap8lTwEpcOPlDOHqWnzcSbvBHiqB9RZLcpHIojG5qtr8nR/zXUACE/xOHAbKsxSQVBcZEhrxH9cM +aVr2cXj0lH2RC47skFSOvG+hTKv8dGT9cZr4QQehzZHkPJrgmzI5c6sq1WnIeJEmMX3ixzDx/BR4 +dxIOE/TdFpS/S2d7cFOFyrC78zhNLJA5wA3CXWvp4uXViI3WLL+rG761KIcSF3Ru/H38j9CHJrAb ++7lsq+KePRXBOy5nAliRn+/4Qh8st2j1da3Ptfb/EX3C8CSlrdP6oDyp+l3cpaDvRKS+1ujl5BOW +F3sGPjLtx7dCvHaj2GU4Kzg1USEODm8uNBNA4StnDG1KQTAYI1oyVZnJF+A83vbsea0rWBmirSwi +GpWOvpaQXUJXxPkUAzUrHC1RVwinOt4/5Mi0A3PCwSaAuwtCH60NryZy2sy+s6ODWA2CxR9GUeOc +GMyNm43sSet1UNWMKFnKdDTajAshqx7qG+XH/RU+wBeq+yNuJkbL+vmxcmtpzyKEC2IPrNkZAJSi +djzULZrtBJ4tBmIQN1IchXIbJ+XMxjHsN+xjWZsLHXbMfjKaiJUINlK73nZfdklJrX+9ZSCyycEr +dhh2n1ax +-----END CERTIFICATE----- + +Certigna Root CA +================ +-----BEGIN CERTIFICATE----- +MIIGWzCCBEOgAwIBAgIRAMrpG4nxVQMNo+ZBbcTjpuEwDQYJKoZIhvcNAQELBQAwWjELMAkGA1UE +BhMCRlIxEjAQBgNVBAoMCURoaW15b3RpczEcMBoGA1UECwwTMDAwMiA0ODE0NjMwODEwMDAzNjEZ +MBcGA1UEAwwQQ2VydGlnbmEgUm9vdCBDQTAeFw0xMzEwMDEwODMyMjdaFw0zMzEwMDEwODMyMjda +MFoxCzAJBgNVBAYTAkZSMRIwEAYDVQQKDAlEaGlteW90aXMxHDAaBgNVBAsMEzAwMDIgNDgxNDYz +MDgxMDAwMzYxGTAXBgNVBAMMEENlcnRpZ25hIFJvb3QgQ0EwggIiMA0GCSqGSIb3DQEBAQUAA4IC +DwAwggIKAoICAQDNGDllGlmx6mQWDoyUJJV8g9PFOSbcDO8WV43X2KyjQn+Cyu3NW9sOty3tRQgX +stmzy9YXUnIo245Onoq2C/mehJpNdt4iKVzSs9IGPjA5qXSjklYcoW9MCiBtnyN6tMbaLOQdLNyz +KNAT8kxOAkmhVECe5uUFoC2EyP+YbNDrihqECB63aCPuI9Vwzm1RaRDuoXrC0SIxwoKF0vJVdlB8 +JXrJhFwLrN1CTivngqIkicuQstDuI7pmTLtipPlTWmR7fJj6o0ieD5Wupxj0auwuA0Wv8HT4Ks16 +XdG+RCYyKfHx9WzMfgIhC59vpD++nVPiz32pLHxYGpfhPTc3GGYo0kDFUYqMwy3OU4gkWGQwFsWq +4NYKpkDfePb1BHxpE4S80dGnBs8B92jAqFe7OmGtBIyT46388NtEbVncSVmurJqZNjBBe3YzIoej +wpKGbvlw7q6Hh5UbxHq9MfPU0uWZ/75I7HX1eBYdpnDBfzwboZL7z8g81sWTCo/1VTp2lc5ZmIoJ +lXcymoO6LAQ6l73UL77XbJuiyn1tJslV1c/DeVIICZkHJC1kJWumIWmbat10TWuXekG9qxf5kBdI +jzb5LdXF2+6qhUVB+s06RbFo5jZMm5BX7CO5hwjCxAnxl4YqKE3idMDaxIzb3+KhF1nOJFl0Mdp/ +/TBt2dzhauH8XwIDAQABo4IBGjCCARYwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYw +HQYDVR0OBBYEFBiHVuBud+4kNTxOc5of1uHieX4rMB8GA1UdIwQYMBaAFBiHVuBud+4kNTxOc5of +1uHieX4rMEQGA1UdIAQ9MDswOQYEVR0gADAxMC8GCCsGAQUFBwIBFiNodHRwczovL3d3d3cuY2Vy +dGlnbmEuZnIvYXV0b3JpdGVzLzBtBgNVHR8EZjBkMC+gLaArhilodHRwOi8vY3JsLmNlcnRpZ25h +LmZyL2NlcnRpZ25hcm9vdGNhLmNybDAxoC+gLYYraHR0cDovL2NybC5kaGlteW90aXMuY29tL2Nl +cnRpZ25hcm9vdGNhLmNybDANBgkqhkiG9w0BAQsFAAOCAgEAlLieT/DjlQgi581oQfccVdV8AOIt +OoldaDgvUSILSo3L6btdPrtcPbEo/uRTVRPPoZAbAh1fZkYJMyjhDSSXcNMQH+pkV5a7XdrnxIxP +TGRGHVyH41neQtGbqH6mid2PHMkwgu07nM3A6RngatgCdTer9zQoKJHyBApPNeNgJgH60BGM+RFq +7q89w1DTj18zeTyGqHNFkIwgtnJzFyO+B2XleJINugHA64wcZr+shncBlA2c5uk5jR+mUYyZDDl3 +4bSb+hxnV29qao6pK0xXeXpXIs/NX2NGjVxZOob4Mkdio2cNGJHc+6Zr9UhhcyNZjgKnvETq9Emd +8VRY+WCv2hikLyhF3HqgiIZd8zvn/yk1gPxkQ5Tm4xxvvq0OKmOZK8l+hfZx6AYDlf7ej0gcWtSS +6Cvu5zHbugRqh5jnxV/vfaci9wHYTfmJ0A6aBVmknpjZbyvKcL5kwlWj9Omvw5Ip3IgWJJk8jSaY +tlu3zM63Nwf9JtmYhST/WSMDmu2dnajkXjjO11INb9I/bbEFa0nOipFGc/T2L/Coc3cOZayhjWZS +aX5LaAzHHjcng6WMxwLkFM1JAbBzs/3GkDpv0mztO+7skb6iQ12LAEpmJURw3kAP+HwV96LOPNde +E4yBFxgX0b3xdxA61GU5wSesVywlVP+i2k+KYTlerj1KjL0= +-----END CERTIFICATE----- + +emSign Root CA - G1 +=================== +-----BEGIN CERTIFICATE----- +MIIDlDCCAnygAwIBAgIKMfXkYgxsWO3W2DANBgkqhkiG9w0BAQsFADBnMQswCQYDVQQGEwJJTjET +MBEGA1UECxMKZW1TaWduIFBLSTElMCMGA1UEChMcZU11ZGhyYSBUZWNobm9sb2dpZXMgTGltaXRl +ZDEcMBoGA1UEAxMTZW1TaWduIFJvb3QgQ0EgLSBHMTAeFw0xODAyMTgxODMwMDBaFw00MzAyMTgx +ODMwMDBaMGcxCzAJBgNVBAYTAklOMRMwEQYDVQQLEwplbVNpZ24gUEtJMSUwIwYDVQQKExxlTXVk +aHJhIFRlY2hub2xvZ2llcyBMaW1pdGVkMRwwGgYDVQQDExNlbVNpZ24gUm9vdCBDQSAtIEcxMIIB +IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAk0u76WaK7p1b1TST0Bsew+eeuGQzf2N4aLTN +LnF115sgxk0pvLZoYIr3IZpWNVrzdr3YzZr/k1ZLpVkGoZM0Kd0WNHVO8oG0x5ZOrRkVUkr+PHB1 +cM2vK6sVmjM8qrOLqs1D/fXqcP/tzxE7lM5OMhbTI0Aqd7OvPAEsbO2ZLIvZTmmYsvePQbAyeGHW +DV/D+qJAkh1cF+ZwPjXnorfCYuKrpDhMtTk1b+oDafo6VGiFbdbyL0NVHpENDtjVaqSW0RM8LHhQ +6DqS0hdW5TUaQBw+jSztOd9C4INBdN+jzcKGYEho42kLVACL5HZpIQ15TjQIXhTCzLG3rdd8cIrH +hQIDAQABo0IwQDAdBgNVHQ4EFgQU++8Nhp6w492pufEhF38+/PB3KxowDgYDVR0PAQH/BAQDAgEG +MA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAFn/8oz1h31xPaOfG1vR2vjTnGs2 +vZupYeveFix0PZ7mddrXuqe8QhfnPZHr5X3dPpzxz5KsbEjMwiI/aTvFthUvozXGaCocV685743Q +NcMYDHsAVhzNixl03r4PEuDQqqE/AjSxcM6dGNYIAwlG7mDgfrbESQRRfXBgvKqy/3lyeqYdPV8q ++Mri/Tm3R7nrft8EI6/6nAYH6ftjk4BAtcZsCjEozgyfz7MjNYBBjWzEN3uBL4ChQEKF6dk4jeih +U80Bv2noWgbyRQuQ+q7hv53yrlc8pa6yVvSLZUDp/TGBLPQ5Cdjua6e0ph0VpZj3AYHYhX3zUVxx +iN66zB+Afko= +-----END CERTIFICATE----- + +emSign ECC Root CA - G3 +======================= +-----BEGIN CERTIFICATE----- +MIICTjCCAdOgAwIBAgIKPPYHqWhwDtqLhDAKBggqhkjOPQQDAzBrMQswCQYDVQQGEwJJTjETMBEG +A1UECxMKZW1TaWduIFBLSTElMCMGA1UEChMcZU11ZGhyYSBUZWNobm9sb2dpZXMgTGltaXRlZDEg +MB4GA1UEAxMXZW1TaWduIEVDQyBSb290IENBIC0gRzMwHhcNMTgwMjE4MTgzMDAwWhcNNDMwMjE4 +MTgzMDAwWjBrMQswCQYDVQQGEwJJTjETMBEGA1UECxMKZW1TaWduIFBLSTElMCMGA1UEChMcZU11 +ZGhyYSBUZWNobm9sb2dpZXMgTGltaXRlZDEgMB4GA1UEAxMXZW1TaWduIEVDQyBSb290IENBIC0g +RzMwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQjpQy4LRL1KPOxst3iAhKAnjlfSU2fySU0WXTsuwYc +58Byr+iuL+FBVIcUqEqy6HyC5ltqtdyzdc6LBtCGI79G1Y4PPwT01xySfvalY8L1X44uT6EYGQIr +MgqCZH0Wk9GjQjBAMB0GA1UdDgQWBBR8XQKEE9TMipuBzhccLikenEhjQjAOBgNVHQ8BAf8EBAMC +AQYwDwYDVR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAwNpADBmAjEAvvNhzwIQHWSVB7gYboiFBS+D +CBeQyh+KTOgNG3qxrdWBCUfvO6wIBHxcmbHtRwfSAjEAnbpV/KlK6O3t5nYBQnvI+GDZjVGLVTv7 +jHvrZQnD+JbNR6iC8hZVdyR+EhCVBCyj +-----END CERTIFICATE----- + +emSign Root CA - C1 +=================== +-----BEGIN CERTIFICATE----- +MIIDczCCAlugAwIBAgILAK7PALrEzzL4Q7IwDQYJKoZIhvcNAQELBQAwVjELMAkGA1UEBhMCVVMx +EzARBgNVBAsTCmVtU2lnbiBQS0kxFDASBgNVBAoTC2VNdWRocmEgSW5jMRwwGgYDVQQDExNlbVNp +Z24gUm9vdCBDQSAtIEMxMB4XDTE4MDIxODE4MzAwMFoXDTQzMDIxODE4MzAwMFowVjELMAkGA1UE +BhMCVVMxEzARBgNVBAsTCmVtU2lnbiBQS0kxFDASBgNVBAoTC2VNdWRocmEgSW5jMRwwGgYDVQQD +ExNlbVNpZ24gUm9vdCBDQSAtIEMxMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAz+up +ufGZBczYKCFK83M0UYRWEPWgTywS4/oTmifQz/l5GnRfHXk5/Fv4cI7gklL35CX5VIPZHdPIWoU/ +Xse2B+4+wM6ar6xWQio5JXDWv7V7Nq2s9nPczdcdioOl+yuQFTdrHCZH3DspVpNqs8FqOp099cGX +OFgFixwR4+S0uF2FHYP+eF8LRWgYSKVGczQ7/g/IdrvHGPMF0Ybzhe3nudkyrVWIzqa2kbBPrH4V +I5b2P/AgNBbeCsbEBEV5f6f9vtKppa+cxSMq9zwhbL2vj07FOrLzNBL834AaSaTUqZX3noleooms +lMuoaJuvimUnzYnu3Yy1aylwQ6BpC+S5DwIDAQABo0IwQDAdBgNVHQ4EFgQU/qHgcB4qAzlSWkK+ +XJGFehiqTbUwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQAD +ggEBAMJKVvoVIXsoounlHfv4LcQ5lkFMOycsxGwYFYDGrK9HWS8mC+M2sO87/kOXSTKZEhVb3xEp +/6tT+LvBeA+snFOvV71ojD1pM/CjoCNjO2RnIkSt1XHLVip4kqNPEjE2NuLe/gDEo2APJ62gsIq1 +NnpSob0n9CAnYuhNlCQT5AoE6TyrLshDCUrGYQTlSTR+08TI9Q/Aqum6VF7zYytPT1DU/rl7mYw9 +wC68AivTxEDkigcxHpvOJpkT+xHqmiIMERnHXhuBUDDIlhJu58tBf5E7oke3VIAb3ADMmpDqw8NQ +BmIMMMAVSKeoWXzhriKi4gp6D/piq1JM4fHfyr6DDUI= +-----END CERTIFICATE----- + +emSign ECC Root CA - C3 +======================= +-----BEGIN CERTIFICATE----- +MIICKzCCAbGgAwIBAgIKe3G2gla4EnycqDAKBggqhkjOPQQDAzBaMQswCQYDVQQGEwJVUzETMBEG +A1UECxMKZW1TaWduIFBLSTEUMBIGA1UEChMLZU11ZGhyYSBJbmMxIDAeBgNVBAMTF2VtU2lnbiBF +Q0MgUm9vdCBDQSAtIEMzMB4XDTE4MDIxODE4MzAwMFoXDTQzMDIxODE4MzAwMFowWjELMAkGA1UE +BhMCVVMxEzARBgNVBAsTCmVtU2lnbiBQS0kxFDASBgNVBAoTC2VNdWRocmEgSW5jMSAwHgYDVQQD +ExdlbVNpZ24gRUNDIFJvb3QgQ0EgLSBDMzB2MBAGByqGSM49AgEGBSuBBAAiA2IABP2lYa57JhAd +6bciMK4G9IGzsUJxlTm801Ljr6/58pc1kjZGDoeVjbk5Wum739D+yAdBPLtVb4OjavtisIGJAnB9 +SMVK4+kiVCJNk7tCDK93nCOmfddhEc5lx/h//vXyqaNCMEAwHQYDVR0OBBYEFPtaSNCAIEDyqOkA +B2kZd6fmw/TPMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MAoGCCqGSM49BAMDA2gA +MGUCMQC02C8Cif22TGK6Q04ThHK1rt0c3ta13FaPWEBaLd4gTCKDypOofu4SQMfWh0/434UCMBwU +ZOR8loMRnLDRWmFLpg9J0wD8ofzkpf9/rdcw0Md3f76BB1UwUCAU9Vc4CqgxUQ== +-----END CERTIFICATE----- + +Hongkong Post Root CA 3 +======================= +-----BEGIN CERTIFICATE----- +MIIFzzCCA7egAwIBAgIUCBZfikyl7ADJk0DfxMauI7gcWqQwDQYJKoZIhvcNAQELBQAwbzELMAkG +A1UEBhMCSEsxEjAQBgNVBAgTCUhvbmcgS29uZzESMBAGA1UEBxMJSG9uZyBLb25nMRYwFAYDVQQK +Ew1Ib25na29uZyBQb3N0MSAwHgYDVQQDExdIb25na29uZyBQb3N0IFJvb3QgQ0EgMzAeFw0xNzA2 +MDMwMjI5NDZaFw00MjA2MDMwMjI5NDZaMG8xCzAJBgNVBAYTAkhLMRIwEAYDVQQIEwlIb25nIEtv +bmcxEjAQBgNVBAcTCUhvbmcgS29uZzEWMBQGA1UEChMNSG9uZ2tvbmcgUG9zdDEgMB4GA1UEAxMX +SG9uZ2tvbmcgUG9zdCBSb290IENBIDMwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCz +iNfqzg8gTr7m1gNt7ln8wlffKWihgw4+aMdoWJwcYEuJQwy51BWy7sFOdem1p+/l6TWZ5Mwc50tf +jTMwIDNT2aa71T4Tjukfh0mtUC1Qyhi+AViiE3CWu4mIVoBc+L0sPOFMV4i707mV78vH9toxdCim +5lSJ9UExyuUmGs2C4HDaOym71QP1mbpV9WTRYA6ziUm4ii8F0oRFKHyPaFASePwLtVPLwpgchKOe +sL4jpNrcyCse2m5FHomY2vkALgbpDDtw1VAliJnLzXNg99X/NWfFobxeq81KuEXryGgeDQ0URhLj +0mRiikKYvLTGCAj4/ahMZJx2Ab0vqWwzD9g/KLg8aQFChn5pwckGyuV6RmXpwtZQQS4/t+TtbNe/ +JgERohYpSms0BpDsE9K2+2p20jzt8NYt3eEV7KObLyzJPivkaTv/ciWxNoZbx39ri1UbSsUgYT2u +y1DhCDq+sI9jQVMwCFk8mB13umOResoQUGC/8Ne8lYePl8X+l2oBlKN8W4UdKjk60FSh0Tlxnf0h ++bV78OLgAo9uliQlLKAeLKjEiafv7ZkGL7YKTE/bosw3Gq9HhS2KX8Q0NEwA/RiTZxPRN+ZItIsG +xVd7GYYKecsAyVKvQv83j+GjHno9UKtjBucVtT+2RTeUN7F+8kjDf8V1/peNRY8apxpyKBpADwID +AQABo2MwYTAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAfBgNVHSMEGDAWgBQXnc0e +i9Y5K3DTXNSguB+wAPzFYTAdBgNVHQ4EFgQUF53NHovWOStw01zUoLgfsAD8xWEwDQYJKoZIhvcN +AQELBQADggIBAFbVe27mIgHSQpsY1Q7XZiNc4/6gx5LS6ZStS6LG7BJ8dNVI0lkUmcDrudHr9Egw +W62nV3OZqdPlt9EuWSRY3GguLmLYauRwCy0gUCCkMpXRAJi70/33MvJJrsZ64Ee+bs7Lo3I6LWld +y8joRTnU+kLBEUx3XZL7av9YROXrgZ6voJmtvqkBZss4HTzfQx/0TW60uhdG/H39h4F5ag0zD/ov ++BS5gLNdTaqX4fnkGMX41TiMJjz98iji7lpJiCzfeT2OnpA8vUFKOt1b9pq0zj8lMH8yfaIDlNDc +eqFS3m6TjRgm/VWsvY+b0s+v54Ysyx8Jb6NvqYTUc79NoXQbTiNg8swOqn+knEwlqLJmOzj/2ZQw +9nKEvmhVEA/GcywWaZMH/rFF7buiVWqw2rVKAiUnhde3t4ZEFolsgCs+l6mc1X5VTMbeRRAc6uk7 +nwNT7u56AQIWeNTowr5GdogTPyK7SBIdUgC0An4hGh6cJfTzPV4e0hz5sy229zdcxsshTrD3mUcY +hcErulWuBurQB7Lcq9CClnXO0lD+mefPL5/ndtFhKvshuzHQqp9HpLIiyhY6UFfEW0NnxWViA0kB +60PZ2Pierc+xYw5F9KBaLJstxabArahH9CdMOA0uG0k7UvToiIMrVCjU8jVStDKDYmlkDJGcn5fq +dBb9HxEGmpv0 +-----END CERTIFICATE----- + +Entrust Root Certification Authority - G4 +========================================= +-----BEGIN CERTIFICATE----- +MIIGSzCCBDOgAwIBAgIRANm1Q3+vqTkPAAAAAFVlrVgwDQYJKoZIhvcNAQELBQAwgb4xCzAJBgNV +BAYTAlVTMRYwFAYDVQQKEw1FbnRydXN0LCBJbmMuMSgwJgYDVQQLEx9TZWUgd3d3LmVudHJ1c3Qu +bmV0L2xlZ2FsLXRlcm1zMTkwNwYDVQQLEzAoYykgMjAxNSBFbnRydXN0LCBJbmMuIC0gZm9yIGF1 +dGhvcml6ZWQgdXNlIG9ubHkxMjAwBgNVBAMTKUVudHJ1c3QgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1 +dGhvcml0eSAtIEc0MB4XDTE1MDUyNzExMTExNloXDTM3MTIyNzExNDExNlowgb4xCzAJBgNVBAYT +AlVTMRYwFAYDVQQKEw1FbnRydXN0LCBJbmMuMSgwJgYDVQQLEx9TZWUgd3d3LmVudHJ1c3QubmV0 +L2xlZ2FsLXRlcm1zMTkwNwYDVQQLEzAoYykgMjAxNSBFbnRydXN0LCBJbmMuIC0gZm9yIGF1dGhv +cml6ZWQgdXNlIG9ubHkxMjAwBgNVBAMTKUVudHJ1c3QgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhv +cml0eSAtIEc0MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAsewsQu7i0TD/pZJH4i3D +umSXbcr3DbVZwbPLqGgZ2K+EbTBwXX7zLtJTmeH+H17ZSK9dE43b/2MzTdMAArzE+NEGCJR5WIoV +3imz/f3ET+iq4qA7ec2/a0My3dl0ELn39GjUu9CH1apLiipvKgS1sqbHoHrmSKvS0VnM1n4j5pds +8ELl3FFLFUHtSUrJ3hCX1nbB76W1NhSXNdh4IjVS70O92yfbYVaCNNzLiGAMC1rlLAHGVK/XqsEQ +e9IFWrhAnoanw5CGAlZSCXqc0ieCU0plUmr1POeo8pyvi73TDtTUXm6Hnmo9RR3RXRv06QqsYJn7 +ibT/mCzPfB3pAqoEmh643IhuJbNsZvc8kPNXwbMv9W3y+8qh+CmdRouzavbmZwe+LGcKKh9asj5X +xNMhIWNlUpEbsZmOeX7m640A2Vqq6nPopIICR5b+W45UYaPrL0swsIsjdXJ8ITzI9vF01Bx7owVV +7rtNOzK+mndmnqxpkCIHH2E6lr7lmk/MBTwoWdPBDFSoWWG9yHJM6Nyfh3+9nEg2XpWjDrk4JFX8 +dWbrAuMINClKxuMrLzOg2qOGpRKX/YAr2hRC45K9PvJdXmd0LhyIRyk0X+IyqJwlN4y6mACXi0mW +Hv0liqzc2thddG5msP9E36EYxr5ILzeUePiVSj9/E15dWf10hkNjc0kCAwEAAaNCMEAwDwYDVR0T +AQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFJ84xFYjwznooHFs6FRM5Og6sb9n +MA0GCSqGSIb3DQEBCwUAA4ICAQAS5UKme4sPDORGpbZgQIeMJX6tuGguW8ZAdjwD+MlZ9POrYs4Q +jbRaZIxowLByQzTSGwv2LFPSypBLhmb8qoMi9IsabyZIrHZ3CL/FmFz0Jomee8O5ZDIBf9PD3Vht +7LGrhFV0d4QEJ1JrhkzO3bll/9bGXp+aEJlLdWr+aumXIOTkdnrG0CSqkM0gkLpHZPt/B7NTeLUK +YvJzQ85BK4FqLoUWlFPUa19yIqtRLULVAJyZv967lDtX/Zr1hstWO1uIAeV8KEsD+UmDfLJ/fOPt +jqF/YFOOVZ1QNBIPt5d7bIdKROf1beyAN/BYGW5KaHbwH5Lk6rWS02FREAutp9lfx1/cH6NcjKF+ +m7ee01ZvZl4HliDtC3T7Zk6LERXpgUl+b7DUUH8i119lAg2m9IUe2K4GS0qn0jFmwvjO5QimpAKW +RGhXxNUzzxkvFMSUHHuk2fCfDrGA4tGeEWSpiBE6doLlYsKA2KSD7ZPvfC+QsDJMlhVoSFLUmQjA +JOgc47OlIQ6SwJAfzyBfyjs4x7dtOvPmRLgOMWuIjnDrnBdSqEGULoe256YSxXXfW8AKbnuk5F6G ++TaU33fD6Q3AOfF5u0aOq0NZJ7cguyPpVkAh7DE9ZapD8j3fcEThuk0mEDuYn/PIjhs4ViFqUZPT +kcpG2om3PVODLAgfi49T3f+sHw== +-----END CERTIFICATE----- + +Microsoft ECC Root Certificate Authority 2017 +============================================= +-----BEGIN CERTIFICATE----- +MIICWTCCAd+gAwIBAgIQZvI9r4fei7FK6gxXMQHC7DAKBggqhkjOPQQDAzBlMQswCQYDVQQGEwJV +UzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTYwNAYDVQQDEy1NaWNyb3NvZnQgRUND +IFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTcwHhcNMTkxMjE4MjMwNjQ1WhcNNDIwNzE4 +MjMxNjA0WjBlMQswCQYDVQQGEwJVUzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTYw +NAYDVQQDEy1NaWNyb3NvZnQgRUNDIFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTcwdjAQ +BgcqhkjOPQIBBgUrgQQAIgNiAATUvD0CQnVBEyPNgASGAlEvaqiBYgtlzPbKnR5vSmZRogPZnZH6 +thaxjG7efM3beaYvzrvOcS/lpaso7GMEZpn4+vKTEAXhgShC48Zo9OYbhGBKia/teQ87zvH2RPUB +eMCjVDBSMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTIy5lycFIM ++Oa+sgRXKSrPQhDtNTAQBgkrBgEEAYI3FQEEAwIBADAKBggqhkjOPQQDAwNoADBlAjBY8k3qDPlf +Xu5gKcs68tvWMoQZP3zVL8KxzJOuULsJMsbG7X7JNpQS5GiFBqIb0C8CMQCZ6Ra0DvpWSNSkMBaR +eNtUjGUBiudQZsIxtzm6uBoiB078a1QWIP8rtedMDE2mT3M= +-----END CERTIFICATE----- + +Microsoft RSA Root Certificate Authority 2017 +============================================= +-----BEGIN CERTIFICATE----- +MIIFqDCCA5CgAwIBAgIQHtOXCV/YtLNHcB6qvn9FszANBgkqhkiG9w0BAQwFADBlMQswCQYDVQQG +EwJVUzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTYwNAYDVQQDEy1NaWNyb3NvZnQg +UlNBIFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTcwHhcNMTkxMjE4MjI1MTIyWhcNNDIw +NzE4MjMwMDIzWjBlMQswCQYDVQQGEwJVUzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9u +MTYwNAYDVQQDEy1NaWNyb3NvZnQgUlNBIFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTcw +ggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDKW76UM4wplZEWCpW9R2LBifOZNt9GkMml +7Xhqb0eRaPgnZ1AzHaGm++DlQ6OEAlcBXZxIQIJTELy/xztokLaCLeX0ZdDMbRnMlfl7rEqUrQ7e +S0MdhweSE5CAg2Q1OQT85elss7YfUJQ4ZVBcF0a5toW1HLUX6NZFndiyJrDKxHBKrmCk3bPZ7Pw7 +1VdyvD/IybLeS2v4I2wDwAW9lcfNcztmgGTjGqwu+UcF8ga2m3P1eDNbx6H7JyqhtJqRjJHTOoI+ +dkC0zVJhUXAoP8XFWvLJjEm7FFtNyP9nTUwSlq31/niol4fX/V4ggNyhSyL71Imtus5Hl0dVe49F +yGcohJUcaDDv70ngNXtk55iwlNpNhTs+VcQor1fznhPbRiefHqJeRIOkpcrVE7NLP8TjwuaGYaRS +MLl6IE9vDzhTyzMMEyuP1pq9KsgtsRx9S1HKR9FIJ3Jdh+vVReZIZZ2vUpC6W6IYZVcSn2i51BVr +lMRpIpj0M+Dt+VGOQVDJNE92kKz8OMHY4Xu54+OU4UZpyw4KUGsTuqwPN1q3ErWQgR5WrlcihtnJ +0tHXUeOrO8ZV/R4O03QK0dqq6mm4lyiPSMQH+FJDOvTKVTUssKZqwJz58oHhEmrARdlns87/I6KJ +ClTUFLkqqNfs+avNJVgyeY+QW5g5xAgGwax/Dj0ApQIDAQABo1QwUjAOBgNVHQ8BAf8EBAMCAYYw +DwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUCctZf4aycI8awznjwNnpv7tNsiMwEAYJKwYBBAGC +NxUBBAMCAQAwDQYJKoZIhvcNAQEMBQADggIBAKyvPl3CEZaJjqPnktaXFbgToqZCLgLNFgVZJ8og +6Lq46BrsTaiXVq5lQ7GPAJtSzVXNUzltYkyLDVt8LkS/gxCP81OCgMNPOsduET/m4xaRhPtthH80 +dK2Jp86519efhGSSvpWhrQlTM93uCupKUY5vVau6tZRGrox/2KJQJWVggEbbMwSubLWYdFQl3JPk ++ONVFT24bcMKpBLBaYVu32TxU5nhSnUgnZUP5NbcA/FZGOhHibJXWpS2qdgXKxdJ5XbLwVaZOjex +/2kskZGT4d9Mozd2TaGf+G0eHdP67Pv0RR0Tbc/3WeUiJ3IrhvNXuzDtJE3cfVa7o7P4NHmJweDy +AmH3pvwPuxwXC65B2Xy9J6P9LjrRk5Sxcx0ki69bIImtt2dmefU6xqaWM/5TkshGsRGRxpl/j8nW +ZjEgQRCHLQzWwa80mMpkg/sTV9HB8Dx6jKXB/ZUhoHHBk2dxEuqPiAppGWSZI1b7rCoucL5mxAyE +7+WL85MB+GqQk2dLsmijtWKP6T+MejteD+eMuMZ87zf9dOLITzNy4ZQ5bb0Sr74MTnB8G2+NszKT +c0QWbej09+CVgI+WXTik9KveCjCHk9hNAHFiRSdLOkKEW39lt2c0Ui2cFmuqqNh7o0JMcccMyj6D +5KbvtwEwXlGjefVwaaZBRA+GsCyRxj3qrg+E +-----END CERTIFICATE----- + +e-Szigno Root CA 2017 +===================== +-----BEGIN CERTIFICATE----- +MIICQDCCAeWgAwIBAgIMAVRI7yH9l1kN9QQKMAoGCCqGSM49BAMCMHExCzAJBgNVBAYTAkhVMREw +DwYDVQQHDAhCdWRhcGVzdDEWMBQGA1UECgwNTWljcm9zZWMgTHRkLjEXMBUGA1UEYQwOVkFUSFUt +MjM1ODQ0OTcxHjAcBgNVBAMMFWUtU3ppZ25vIFJvb3QgQ0EgMjAxNzAeFw0xNzA4MjIxMjA3MDZa +Fw00MjA4MjIxMjA3MDZaMHExCzAJBgNVBAYTAkhVMREwDwYDVQQHDAhCdWRhcGVzdDEWMBQGA1UE +CgwNTWljcm9zZWMgTHRkLjEXMBUGA1UEYQwOVkFUSFUtMjM1ODQ0OTcxHjAcBgNVBAMMFWUtU3pp +Z25vIFJvb3QgQ0EgMjAxNzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABJbcPYrYsHtvxie+RJCx +s1YVe45DJH0ahFnuY2iyxl6H0BVIHqiQrb1TotreOpCmYF9oMrWGQd+HWyx7xf58etqjYzBhMA8G +A1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBSHERUI0arBeAyxr87GyZDv +vzAEwDAfBgNVHSMEGDAWgBSHERUI0arBeAyxr87GyZDvvzAEwDAKBggqhkjOPQQDAgNJADBGAiEA +tVfd14pVCzbhhkT61NlojbjcI4qKDdQvfepz7L9NbKgCIQDLpbQS+ue16M9+k/zzNY9vTlp8tLxO +svxyqltZ+efcMQ== +-----END CERTIFICATE----- + +certSIGN Root CA G2 +=================== +-----BEGIN CERTIFICATE----- +MIIFRzCCAy+gAwIBAgIJEQA0tk7GNi02MA0GCSqGSIb3DQEBCwUAMEExCzAJBgNVBAYTAlJPMRQw +EgYDVQQKEwtDRVJUU0lHTiBTQTEcMBoGA1UECxMTY2VydFNJR04gUk9PVCBDQSBHMjAeFw0xNzAy +MDYwOTI3MzVaFw00MjAyMDYwOTI3MzVaMEExCzAJBgNVBAYTAlJPMRQwEgYDVQQKEwtDRVJUU0lH +TiBTQTEcMBoGA1UECxMTY2VydFNJR04gUk9PVCBDQSBHMjCCAiIwDQYJKoZIhvcNAQEBBQADggIP +ADCCAgoCggIBAMDFdRmRfUR0dIf+DjuW3NgBFszuY5HnC2/OOwppGnzC46+CjobXXo9X69MhWf05 +N0IwvlDqtg+piNguLWkh59E3GE59kdUWX2tbAMI5Qw02hVK5U2UPHULlj88F0+7cDBrZuIt4Imfk +abBoxTzkbFpG583H+u/E7Eu9aqSs/cwoUe+StCmrqzWaTOTECMYmzPhpn+Sc8CnTXPnGFiWeI8Mg +wT0PPzhAsP6CRDiqWhqKa2NYOLQV07YRaXseVO6MGiKscpc/I1mbySKEwQdPzH/iV8oScLumZfNp +dWO9lfsbl83kqK/20U6o2YpxJM02PbyWxPFsqa7lzw1uKA2wDrXKUXt4FMMgL3/7FFXhEZn91Qqh +ngLjYl/rNUssuHLoPj1PrCy7Lobio3aP5ZMqz6WryFyNSwb/EkaseMsUBzXgqd+L6a8VTxaJW732 +jcZZroiFDsGJ6x9nxUWO/203Nit4ZoORUSs9/1F3dmKh7Gc+PoGD4FapUB8fepmrY7+EF3fxDTvf +95xhszWYijqy7DwaNz9+j5LP2RIUZNoQAhVB/0/E6xyjyfqZ90bp4RjZsbgyLcsUDFDYg2WD7rlc +z8sFWkz6GZdr1l0T08JcVLwyc6B49fFtHsufpaafItzRUZ6CeWRgKRM+o/1Pcmqr4tTluCRVLERL +iohEnMqE0yo7AgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1Ud +DgQWBBSCIS1mxteg4BXrzkwJd8RgnlRuAzANBgkqhkiG9w0BAQsFAAOCAgEAYN4auOfyYILVAzOB +ywaK8SJJ6ejqkX/GM15oGQOGO0MBzwdw5AgeZYWR5hEit/UCI46uuR59H35s5r0l1ZUa8gWmr4UC +b6741jH/JclKyMeKqdmfS0mbEVeZkkMR3rYzpMzXjWR91M08KCy0mpbqTfXERMQlqiCA2ClV9+BB +/AYm/7k29UMUA2Z44RGx2iBfRgB4ACGlHgAoYXhvqAEBj500mv/0OJD7uNGzcgbJceaBxXntC6Z5 +8hMLnPddDnskk7RI24Zf3lCGeOdA5jGokHZwYa+cNywRtYK3qq4kNFtyDGkNzVmf9nGvnAvRCjj5 +BiKDUyUM/FHE5r7iOZULJK2v0ZXkltd0ZGtxTgI8qoXzIKNDOXZbbFD+mpwUHmUUihW9o4JFWklW +atKcsWMy5WHgUyIOpwpJ6st+H6jiYoD2EEVSmAYY3qXNL3+q1Ok+CHLsIwMCPKaq2LxndD0UF/tU +Sxfj03k9bWtJySgOLnRQvwzZRjoQhsmnP+mg7H/rpXdYaXHmgwo38oZJar55CJD2AhZkPuXaTH4M +NMn5X7azKFGnpyuqSfqNZSlO42sTp5SjLVFteAxEy9/eCG/Oo2Sr05WE1LlSVHJ7liXMvGnjSG4N +0MedJ5qq+BOS3R7fY581qRY27Iy4g/Q9iY/NtBde17MXQRBdJ3NghVdJIgc= +-----END CERTIFICATE----- + +Trustwave Global Certification Authority +======================================== +-----BEGIN CERTIFICATE----- +MIIF2jCCA8KgAwIBAgIMBfcOhtpJ80Y1LrqyMA0GCSqGSIb3DQEBCwUAMIGIMQswCQYDVQQGEwJV +UzERMA8GA1UECAwISWxsaW5vaXMxEDAOBgNVBAcMB0NoaWNhZ28xITAfBgNVBAoMGFRydXN0d2F2 +ZSBIb2xkaW5ncywgSW5jLjExMC8GA1UEAwwoVHJ1c3R3YXZlIEdsb2JhbCBDZXJ0aWZpY2F0aW9u +IEF1dGhvcml0eTAeFw0xNzA4MjMxOTM0MTJaFw00MjA4MjMxOTM0MTJaMIGIMQswCQYDVQQGEwJV +UzERMA8GA1UECAwISWxsaW5vaXMxEDAOBgNVBAcMB0NoaWNhZ28xITAfBgNVBAoMGFRydXN0d2F2 +ZSBIb2xkaW5ncywgSW5jLjExMC8GA1UEAwwoVHJ1c3R3YXZlIEdsb2JhbCBDZXJ0aWZpY2F0aW9u +IEF1dGhvcml0eTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBALldUShLPDeS0YLOvR29 +zd24q88KPuFd5dyqCblXAj7mY2Hf8g+CY66j96xz0XznswuvCAAJWX/NKSqIk4cXGIDtiLK0thAf +LdZfVaITXdHG6wZWiYj+rDKd/VzDBcdu7oaJuogDnXIhhpCujwOl3J+IKMujkkkP7NAP4m1ET4Bq +stTnoApTAbqOl5F2brz81Ws25kCI1nsvXwXoLG0R8+eyvpJETNKXpP7ScoFDB5zpET71ixpZfR9o +WN0EACyW80OzfpgZdNmcc9kYvkHHNHnZ9GLCQ7mzJ7Aiy/k9UscwR7PJPrhq4ufogXBeQotPJqX+ +OsIgbrv4Fo7NDKm0G2x2EOFYeUY+VM6AqFcJNykbmROPDMjWLBz7BegIlT1lRtzuzWniTY+HKE40 +Cz7PFNm73bZQmq131BnW2hqIyE4bJ3XYsgjxroMwuREOzYfwhI0Vcnyh78zyiGG69Gm7DIwLdVcE +uE4qFC49DxweMqZiNu5m4iK4BUBjECLzMx10coos9TkpoNPnG4CELcU9402x/RpvumUHO1jsQkUm ++9jaJXLE9gCxInm943xZYkqcBW89zubWR2OZxiRvchLIrH+QtAuRcOi35hYQcRfO3gZPSEF9NUqj +ifLJS3tBEW1ntwiYTOURGa5CgNz7kAXU+FDKvuStx8KU1xad5hePrzb7AgMBAAGjQjBAMA8GA1Ud +EwEB/wQFMAMBAf8wHQYDVR0OBBYEFJngGWcNYtt2s9o9uFvo/ULSMQ6HMA4GA1UdDwEB/wQEAwIB +BjANBgkqhkiG9w0BAQsFAAOCAgEAmHNw4rDT7TnsTGDZqRKGFx6W0OhUKDtkLSGm+J1WE2pIPU/H +PinbbViDVD2HfSMF1OQc3Og4ZYbFdada2zUFvXfeuyk3QAUHw5RSn8pk3fEbK9xGChACMf1KaA0H +ZJDmHvUqoai7PF35owgLEQzxPy0QlG/+4jSHg9bP5Rs1bdID4bANqKCqRieCNqcVtgimQlRXtpla +4gt5kNdXElE1GYhBaCXUNxeEFfsBctyV3lImIJgm4nb1J2/6ADtKYdkNy1GTKv0WBpanI5ojSP5R +vbbEsLFUzt5sQa0WZ37b/TjNuThOssFgy50X31ieemKyJo90lZvkWx3SD92YHJtZuSPTMaCm/zjd +zyBP6VhWOmfD0faZmZ26NraAL4hHT4a/RDqA5Dccprrql5gR0IRiR2Qequ5AvzSxnI9O4fKSTx+O +856X3vOmeWqJcU9LJxdI/uz0UA9PSX3MReO9ekDFQdxhVicGaeVyQYHTtgGJoC86cnn+OjC/QezH +Yj6RS8fZMXZC+fc8Y+wmjHMMfRod6qh8h6jCJ3zhM0EPz8/8AKAigJ5Kp28AsEFFtyLKaEjFQqKu +3R3y4G5OBVixwJAWKqQ9EEC+j2Jjg6mcgn0tAumDMHzLJ8n9HmYAsC7TIS+OMxZsmO0QqAfWzJPP +29FpHOTKyeC2nOnOcXHebD8WpHk= +-----END CERTIFICATE----- + +Trustwave Global ECC P256 Certification Authority +================================================= +-----BEGIN CERTIFICATE----- +MIICYDCCAgegAwIBAgIMDWpfCD8oXD5Rld9dMAoGCCqGSM49BAMCMIGRMQswCQYDVQQGEwJVUzER +MA8GA1UECBMISWxsaW5vaXMxEDAOBgNVBAcTB0NoaWNhZ28xITAfBgNVBAoTGFRydXN0d2F2ZSBI +b2xkaW5ncywgSW5jLjE6MDgGA1UEAxMxVHJ1c3R3YXZlIEdsb2JhbCBFQ0MgUDI1NiBDZXJ0aWZp +Y2F0aW9uIEF1dGhvcml0eTAeFw0xNzA4MjMxOTM1MTBaFw00MjA4MjMxOTM1MTBaMIGRMQswCQYD +VQQGEwJVUzERMA8GA1UECBMISWxsaW5vaXMxEDAOBgNVBAcTB0NoaWNhZ28xITAfBgNVBAoTGFRy +dXN0d2F2ZSBIb2xkaW5ncywgSW5jLjE6MDgGA1UEAxMxVHJ1c3R3YXZlIEdsb2JhbCBFQ0MgUDI1 +NiBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABH77bOYj +43MyCMpg5lOcunSNGLB4kFKA3TjASh3RqMyTpJcGOMoNFWLGjgEqZZ2q3zSRLoHB5DOSMcT9CTqm +P62jQzBBMA8GA1UdEwEB/wQFMAMBAf8wDwYDVR0PAQH/BAUDAwcGADAdBgNVHQ4EFgQUo0EGrJBt +0UrrdaVKEJmzsaGLSvcwCgYIKoZIzj0EAwIDRwAwRAIgB+ZU2g6gWrKuEZ+Hxbb/ad4lvvigtwjz +RM4q3wghDDcCIC0mA6AFvWvR9lz4ZcyGbbOcNEhjhAnFjXca4syc4XR7 +-----END CERTIFICATE----- + +Trustwave Global ECC P384 Certification Authority +================================================= +-----BEGIN CERTIFICATE----- +MIICnTCCAiSgAwIBAgIMCL2Fl2yZJ6SAaEc7MAoGCCqGSM49BAMDMIGRMQswCQYDVQQGEwJVUzER +MA8GA1UECBMISWxsaW5vaXMxEDAOBgNVBAcTB0NoaWNhZ28xITAfBgNVBAoTGFRydXN0d2F2ZSBI +b2xkaW5ncywgSW5jLjE6MDgGA1UEAxMxVHJ1c3R3YXZlIEdsb2JhbCBFQ0MgUDM4NCBDZXJ0aWZp +Y2F0aW9uIEF1dGhvcml0eTAeFw0xNzA4MjMxOTM2NDNaFw00MjA4MjMxOTM2NDNaMIGRMQswCQYD +VQQGEwJVUzERMA8GA1UECBMISWxsaW5vaXMxEDAOBgNVBAcTB0NoaWNhZ28xITAfBgNVBAoTGFRy +dXN0d2F2ZSBIb2xkaW5ncywgSW5jLjE6MDgGA1UEAxMxVHJ1c3R3YXZlIEdsb2JhbCBFQ0MgUDM4 +NCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTB2MBAGByqGSM49AgEGBSuBBAAiA2IABGvaDXU1CDFH +Ba5FmVXxERMuSvgQMSOjfoPTfygIOiYaOs+Xgh+AtycJj9GOMMQKmw6sWASr9zZ9lCOkmwqKi6vr +/TklZvFe/oyujUF5nQlgziip04pt89ZF1PKYhDhloKNDMEEwDwYDVR0TAQH/BAUwAwEB/zAPBgNV +HQ8BAf8EBQMDBwYAMB0GA1UdDgQWBBRVqYSJ0sEyvRjLbKYHTsjnnb6CkDAKBggqhkjOPQQDAwNn +ADBkAjA3AZKXRRJ+oPM+rRk6ct30UJMDEr5E0k9BpIycnR+j9sKS50gU/k6bpZFXrsY3crsCMGcl +CrEMXu6pY5Jv5ZAL/mYiykf9ijH3g/56vxC+GCsej/YpHpRZ744hN8tRmKVuSw== +-----END CERTIFICATE----- + +NAVER Global Root Certification Authority +========================================= +-----BEGIN CERTIFICATE----- +MIIFojCCA4qgAwIBAgIUAZQwHqIL3fXFMyqxQ0Rx+NZQTQ0wDQYJKoZIhvcNAQEMBQAwaTELMAkG +A1UEBhMCS1IxJjAkBgNVBAoMHU5BVkVSIEJVU0lORVNTIFBMQVRGT1JNIENvcnAuMTIwMAYDVQQD +DClOQVZFUiBHbG9iYWwgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0xNzA4MTgwODU4 +NDJaFw0zNzA4MTgyMzU5NTlaMGkxCzAJBgNVBAYTAktSMSYwJAYDVQQKDB1OQVZFUiBCVVNJTkVT +UyBQTEFURk9STSBDb3JwLjEyMDAGA1UEAwwpTkFWRVIgR2xvYmFsIFJvb3QgQ2VydGlmaWNhdGlv +biBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC21PGTXLVAiQqrDZBb +UGOukJR0F0Vy1ntlWilLp1agS7gvQnXp2XskWjFlqxcX0TM62RHcQDaH38dq6SZeWYp34+hInDEW ++j6RscrJo+KfziFTowI2MMtSAuXaMl3Dxeb57hHHi8lEHoSTGEq0n+USZGnQJoViAbbJAh2+g1G7 +XNr4rRVqmfeSVPc0W+m/6imBEtRTkZazkVrd/pBzKPswRrXKCAfHcXLJZtM0l/aM9BhK4dA9WkW2 +aacp+yPOiNgSnABIqKYPszuSjXEOdMWLyEz59JuOuDxp7W87UC9Y7cSw0BwbagzivESq2M0UXZR4 +Yb8ObtoqvC8MC3GmsxY/nOb5zJ9TNeIDoKAYv7vxvvTWjIcNQvcGufFt7QSUqP620wbGQGHfnZ3z +VHbOUzoBppJB7ASjjw2i1QnK1sua8e9DXcCrpUHPXFNwcMmIpi3Ua2FzUCaGYQ5fG8Ir4ozVu53B +A0K6lNpfqbDKzE0K70dpAy8i+/Eozr9dUGWokG2zdLAIx6yo0es+nPxdGoMuK8u180SdOqcXYZai +cdNwlhVNt0xz7hlcxVs+Qf6sdWA7G2POAN3aCJBitOUt7kinaxeZVL6HSuOpXgRM6xBtVNbv8ejy +YhbLgGvtPe31HzClrkvJE+2KAQHJuFFYwGY6sWZLxNUxAmLpdIQM201GLQIDAQABo0IwQDAdBgNV +HQ4EFgQU0p+I36HNLL3s9TsBAZMzJ7LrYEswDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMB +Af8wDQYJKoZIhvcNAQEMBQADggIBADLKgLOdPVQG3dLSLvCkASELZ0jKbY7gyKoNqo0hV4/GPnrK +21HUUrPUloSlWGB/5QuOH/XcChWB5Tu2tyIvCZwTFrFsDDUIbatjcu3cvuzHV+YwIHHW1xDBE1UB +jCpD5EHxzzp6U5LOogMFDTjfArsQLtk70pt6wKGm+LUx5vR1yblTmXVHIloUFcd4G7ad6Qz4G3bx +hYTeodoS76TiEJd6eN4MUZeoIUCLhr0N8F5OSza7OyAfikJW4Qsav3vQIkMsRIz75Sq0bBwcupTg +E34h5prCy8VCZLQelHsIJchxzIdFV4XTnyliIoNRlwAYl3dqmJLJfGBs32x9SuRwTMKeuB330DTH +D8z7p/8Dvq1wkNoL3chtl1+afwkyQf3NosxabUzyqkn+Zvjp2DXrDige7kgvOtB5CTh8piKCk5XQ +A76+AqAF3SAi428diDRgxuYKuQl1C/AH6GmWNcf7I4GOODm4RStDeKLRLBT/DShycpWbXgnbiUSY +qqFJu3FS8r/2/yehNq+4tneI3TqkbZs0kNwUXTC/t+sX5Ie3cdCh13cV1ELX8vMxmV2b3RZtP+oG +I/hGoiLtk/bdmuYqh7GYVPEi92tF4+KOdh2ajcQGjTa3FPOdVGm3jjzVpG2Tgbet9r1ke8LJaDmg +kpzNNIaRkPpkUZ3+/uul9XXeifdy +-----END CERTIFICATE----- + +AC RAIZ FNMT-RCM SERVIDORES SEGUROS +=================================== +-----BEGIN CERTIFICATE----- +MIICbjCCAfOgAwIBAgIQYvYybOXE42hcG2LdnC6dlTAKBggqhkjOPQQDAzB4MQswCQYDVQQGEwJF +UzERMA8GA1UECgwIRk5NVC1SQ00xDjAMBgNVBAsMBUNlcmVzMRgwFgYDVQRhDA9WQVRFUy1RMjgy +NjAwNEoxLDAqBgNVBAMMI0FDIFJBSVogRk5NVC1SQ00gU0VSVklET1JFUyBTRUdVUk9TMB4XDTE4 +MTIyMDA5MzczM1oXDTQzMTIyMDA5MzczM1oweDELMAkGA1UEBhMCRVMxETAPBgNVBAoMCEZOTVQt +UkNNMQ4wDAYDVQQLDAVDZXJlczEYMBYGA1UEYQwPVkFURVMtUTI4MjYwMDRKMSwwKgYDVQQDDCNB +QyBSQUlaIEZOTVQtUkNNIFNFUlZJRE9SRVMgU0VHVVJPUzB2MBAGByqGSM49AgEGBSuBBAAiA2IA +BPa6V1PIyqvfNkpSIeSX0oNnnvBlUdBeh8dHsVnyV0ebAAKTRBdp20LHsbI6GA60XYyzZl2hNPk2 +LEnb80b8s0RpRBNm/dfF/a82Tc4DTQdxz69qBdKiQ1oKUm8BA06Oi6NCMEAwDwYDVR0TAQH/BAUw +AwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFAG5L++/EYZg8k/QQW6rcx/n0m5JMAoGCCqG +SM49BAMDA2kAMGYCMQCuSuMrQMN0EfKVrRYj3k4MGuZdpSRea0R7/DjiT8ucRRcRTBQnJlU5dUoD +zBOQn5ICMQD6SmxgiHPz7riYYqnOK8LZiqZwMR2vsJRM60/G49HzYqc8/5MuB1xJAWdpEgJyv+c= +-----END CERTIFICATE----- + +GlobalSign Root R46 +=================== +-----BEGIN CERTIFICATE----- +MIIFWjCCA0KgAwIBAgISEdK7udcjGJ5AXwqdLdDfJWfRMA0GCSqGSIb3DQEBDAUAMEYxCzAJBgNV +BAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMRwwGgYDVQQDExNHbG9iYWxTaWduIFJv +b3QgUjQ2MB4XDTE5MDMyMDAwMDAwMFoXDTQ2MDMyMDAwMDAwMFowRjELMAkGA1UEBhMCQkUxGTAX +BgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExHDAaBgNVBAMTE0dsb2JhbFNpZ24gUm9vdCBSNDYwggIi +MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCsrHQy6LNl5brtQyYdpokNRbopiLKkHWPd08Es +CVeJOaFV6Wc0dwxu5FUdUiXSE2te4R2pt32JMl8Nnp8semNgQB+msLZ4j5lUlghYruQGvGIFAha/ +r6gjA7aUD7xubMLL1aa7DOn2wQL7Id5m3RerdELv8HQvJfTqa1VbkNud316HCkD7rRlr+/fKYIje +2sGP1q7Vf9Q8g+7XFkyDRTNrJ9CG0Bwta/OrffGFqfUo0q3v84RLHIf8E6M6cqJaESvWJ3En7YEt +bWaBkoe0G1h6zD8K+kZPTXhc+CtI4wSEy132tGqzZfxCnlEmIyDLPRT5ge1lFgBPGmSXZgjPjHvj +K8Cd+RTyG/FWaha/LIWFzXg4mutCagI0GIMXTpRW+LaCtfOW3T3zvn8gdz57GSNrLNRyc0NXfeD4 +12lPFzYE+cCQYDdF3uYM2HSNrpyibXRdQr4G9dlkbgIQrImwTDsHTUB+JMWKmIJ5jqSngiCNI/on +ccnfxkF0oE32kRbcRoxfKWMxWXEM2G/CtjJ9++ZdU6Z+Ffy7dXxd7Pj2Fxzsx2sZy/N78CsHpdls +eVR2bJ0cpm4O6XkMqCNqo98bMDGfsVR7/mrLZqrcZdCinkqaByFrgY/bxFn63iLABJzjqls2k+g9 +vXqhnQt2sQvHnf3PmKgGwvgqo6GDoLclcqUC4wIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAYYwDwYD +VR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUA1yrc4GHqMywptWU4jaWSf8FmSwwDQYJKoZIhvcNAQEM +BQADggIBAHx47PYCLLtbfpIrXTncvtgdokIzTfnvpCo7RGkerNlFo048p9gkUbJUHJNOxO97k4Vg +JuoJSOD1u8fpaNK7ajFxzHmuEajwmf3lH7wvqMxX63bEIaZHU1VNaL8FpO7XJqti2kM3S+LGteWy +gxk6x9PbTZ4IevPuzz5i+6zoYMzRx6Fcg0XERczzF2sUyQQCPtIkpnnpHs6i58FZFZ8d4kuaPp92 +CC1r2LpXFNqD6v6MVenQTqnMdzGxRBF6XLE+0xRFFRhiJBPSy03OXIPBNvIQtQ6IbbjhVp+J3pZm +OUdkLG5NrmJ7v2B0GbhWrJKsFjLtrWhV/pi60zTe9Mlhww6G9kuEYO4Ne7UyWHmRVSyBQ7N0H3qq +JZ4d16GLuc1CLgSkZoNNiTW2bKg2SnkheCLQQrzRQDGQob4Ez8pn7fXwgNNgyYMqIgXQBztSvwye +qiv5u+YfjyW6hY0XHgL+XVAEV8/+LbzvXMAaq7afJMbfc2hIkCwU9D9SGuTSyxTDYWnP4vkYxboz +nxSjBF25cfe1lNj2M8FawTSLfJvdkzrnE6JwYZ+vj+vYxXX4M2bUdGc6N3ec592kD3ZDZopD8p/7 +DEJ4Y9HiD2971KE9dJeFt0g5QdYg/NA6s/rob8SKunE3vouXsXgxT7PntgMTzlSdriVZzH81Xwj3 +QEUxeCp6 +-----END CERTIFICATE----- + +GlobalSign Root E46 +=================== +-----BEGIN CERTIFICATE----- +MIICCzCCAZGgAwIBAgISEdK7ujNu1LzmJGjFDYQdmOhDMAoGCCqGSM49BAMDMEYxCzAJBgNVBAYT +AkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMRwwGgYDVQQDExNHbG9iYWxTaWduIFJvb3Qg +RTQ2MB4XDTE5MDMyMDAwMDAwMFoXDTQ2MDMyMDAwMDAwMFowRjELMAkGA1UEBhMCQkUxGTAXBgNV +BAoTEEdsb2JhbFNpZ24gbnYtc2ExHDAaBgNVBAMTE0dsb2JhbFNpZ24gUm9vdCBFNDYwdjAQBgcq +hkjOPQIBBgUrgQQAIgNiAAScDrHPt+ieUnd1NPqlRqetMhkytAepJ8qUuwzSChDH2omwlwxwEwkB +jtjqR+q+soArzfwoDdusvKSGN+1wCAB16pMLey5SnCNoIwZD7JIvU4Tb+0cUB+hflGddyXqBPCCj +QjBAMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBQxCpCPtsad0kRL +gLWi5h+xEk8blTAKBggqhkjOPQQDAwNoADBlAjEA31SQ7Zvvi5QCkxeCmb6zniz2C5GMn0oUsfZk +vLtoURMMA/cVi4RguYv/Uo7njLwcAjA8+RHUjE7AwWHCFUyqqx0LMV87HOIAl0Qx5v5zli/altP+ +CAezNIm8BZ/3Hobui3A= +-----END CERTIFICATE----- + +GLOBALTRUST 2020 +================ +-----BEGIN CERTIFICATE----- +MIIFgjCCA2qgAwIBAgILWku9WvtPilv6ZeUwDQYJKoZIhvcNAQELBQAwTTELMAkGA1UEBhMCQVQx +IzAhBgNVBAoTGmUtY29tbWVyY2UgbW9uaXRvcmluZyBHbWJIMRkwFwYDVQQDExBHTE9CQUxUUlVT +VCAyMDIwMB4XDTIwMDIxMDAwMDAwMFoXDTQwMDYxMDAwMDAwMFowTTELMAkGA1UEBhMCQVQxIzAh +BgNVBAoTGmUtY29tbWVyY2UgbW9uaXRvcmluZyBHbWJIMRkwFwYDVQQDExBHTE9CQUxUUlVTVCAy +MDIwMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAri5WrRsc7/aVj6B3GyvTY4+ETUWi +D59bRatZe1E0+eyLinjF3WuvvcTfk0Uev5E4C64OFudBc/jbu9G4UeDLgztzOG53ig9ZYybNpyrO +VPu44sB8R85gfD+yc/LAGbaKkoc1DZAoouQVBGM+uq/ufF7MpotQsjj3QWPKzv9pj2gOlTblzLmM +CcpL3TGQlsjMH/1WljTbjhzqLL6FLmPdqqmV0/0plRPwyJiT2S0WR5ARg6I6IqIoV6Lr/sCMKKCm +fecqQjuCgGOlYx8ZzHyyZqjC0203b+J+BlHZRYQfEs4kUmSFC0iAToexIiIwquuuvuAC4EDosEKA +A1GqtH6qRNdDYfOiaxaJSaSjpCuKAsR49GiKweR6NrFvG5Ybd0mN1MkGco/PU+PcF4UgStyYJ9OR +JitHHmkHr96i5OTUawuzXnzUJIBHKWk7buis/UDr2O1xcSvy6Fgd60GXIsUf1DnQJ4+H4xj04KlG +DfV0OoIu0G4skaMxXDtG6nsEEFZegB31pWXogvziB4xiRfUg3kZwhqG8k9MedKZssCz3AwyIDMvU +clOGvGBG85hqwvG/Q/lwIHfKN0F5VVJjjVsSn8VoxIidrPIwq7ejMZdnrY8XD2zHc+0klGvIg5rQ +mjdJBKuxFshsSUktq6HQjJLyQUp5ISXbY9e2nKd+Qmn7OmMCAwEAAaNjMGEwDwYDVR0TAQH/BAUw +AwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFNwuH9FhN3nkq9XVsxJxaD1qaJwiMB8GA1Ud +IwQYMBaAFNwuH9FhN3nkq9XVsxJxaD1qaJwiMA0GCSqGSIb3DQEBCwUAA4ICAQCR8EICaEDuw2jA +VC/f7GLDw56KoDEoqoOOpFaWEhCGVrqXctJUMHytGdUdaG/7FELYjQ7ztdGl4wJCXtzoRlgHNQIw +4Lx0SsFDKv/bGtCwr2zD/cuz9X9tAy5ZVp0tLTWMstZDFyySCstd6IwPS3BD0IL/qMy/pJTAvoe9 +iuOTe8aPmxadJ2W8esVCgmxcB9CpwYhgROmYhRZf+I/KARDOJcP5YBugxZfD0yyIMaK9MOzQ0MAS +8cE54+X1+NZK3TTN+2/BT+MAi1bikvcoskJ3ciNnxz8RFbLEAwW+uxF7Cr+obuf/WEPPm2eggAe2 +HcqtbepBEX4tdJP7wry+UUTF72glJ4DjyKDUEuzZpTcdN3y0kcra1LGWge9oXHYQSa9+pTeAsRxS +vTOBTI/53WXZFM2KJVj04sWDpQmQ1GwUY7VA3+vA/MRYfg0UFodUJ25W5HCEuGwyEn6CMUO+1918 +oa2u1qsgEu8KwxCMSZY13At1XrFP1U80DhEgB3VDRemjEdqso5nCtnkn4rnvyOL2NSl6dPrFf4IF +YqYK6miyeUcGbvJXqBUzxvd4Sj1Ce2t+/vdG6tHrju+IaFvowdlxfv1k7/9nR4hYJS8+hge9+6jl +gqispdNpQ80xiEmEU5LAsTkbOYMBMMTyqfrQA71yN2BWHzZ8vTmR9W0Nv3vXkg== +-----END CERTIFICATE----- + +ANF Secure Server Root CA +========================= +-----BEGIN CERTIFICATE----- +MIIF7zCCA9egAwIBAgIIDdPjvGz5a7EwDQYJKoZIhvcNAQELBQAwgYQxEjAQBgNVBAUTCUc2MzI4 +NzUxMDELMAkGA1UEBhMCRVMxJzAlBgNVBAoTHkFORiBBdXRvcmlkYWQgZGUgQ2VydGlmaWNhY2lv +bjEUMBIGA1UECxMLQU5GIENBIFJhaXoxIjAgBgNVBAMTGUFORiBTZWN1cmUgU2VydmVyIFJvb3Qg +Q0EwHhcNMTkwOTA0MTAwMDM4WhcNMzkwODMwMTAwMDM4WjCBhDESMBAGA1UEBRMJRzYzMjg3NTEw +MQswCQYDVQQGEwJFUzEnMCUGA1UEChMeQU5GIEF1dG9yaWRhZCBkZSBDZXJ0aWZpY2FjaW9uMRQw +EgYDVQQLEwtBTkYgQ0EgUmFpejEiMCAGA1UEAxMZQU5GIFNlY3VyZSBTZXJ2ZXIgUm9vdCBDQTCC +AiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANvrayvmZFSVgpCjcqQZAZ2cC4Ffc0m6p6zz +BE57lgvsEeBbphzOG9INgxwruJ4dfkUyYA8H6XdYfp9qyGFOtibBTI3/TO80sh9l2Ll49a2pcbnv +T1gdpd50IJeh7WhM3pIXS7yr/2WanvtH2Vdy8wmhrnZEE26cLUQ5vPnHO6RYPUG9tMJJo8gN0pcv +B2VSAKduyK9o7PQUlrZXH1bDOZ8rbeTzPvY1ZNoMHKGESy9LS+IsJJ1tk0DrtSOOMspvRdOoiXse +zx76W0OLzc2oD2rKDF65nkeP8Nm2CgtYZRczuSPkdxl9y0oukntPLxB3sY0vaJxizOBQ+OyRp1RM +VwnVdmPF6GUe7m1qzwmd+nxPrWAI/VaZDxUse6mAq4xhj0oHdkLePfTdsiQzW7i1o0TJrH93PB0j +7IKppuLIBkwC/qxcmZkLLxCKpvR/1Yd0DVlJRfbwcVw5Kda/SiOL9V8BY9KHcyi1Swr1+KuCLH5z +JTIdC2MKF4EA/7Z2Xue0sUDKIbvVgFHlSFJnLNJhiQcND85Cd8BEc5xEUKDbEAotlRyBr+Qc5RQe +8TZBAQIvfXOn3kLMTOmJDVb3n5HUA8ZsyY/b2BzgQJhdZpmYgG4t/wHFzstGH6wCxkPmrqKEPMVO +Hj1tyRRM4y5Bu8o5vzY8KhmqQYdOpc5LMnndkEl/AgMBAAGjYzBhMB8GA1UdIwQYMBaAFJxf0Gxj +o1+TypOYCK2Mh6UsXME3MB0GA1UdDgQWBBScX9BsY6Nfk8qTmAitjIelLFzBNzAOBgNVHQ8BAf8E +BAMCAYYwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAgEATh65isagmD9uw2nAalxJ +UqzLK114OMHVVISfk/CHGT0sZonrDUL8zPB1hT+L9IBdeeUXZ701guLyPI59WzbLWoAAKfLOKyzx +j6ptBZNscsdW699QIyjlRRA96Gejrw5VD5AJYu9LWaL2U/HANeQvwSS9eS9OICI7/RogsKQOLHDt +dD+4E5UGUcjohybKpFtqFiGS3XNgnhAY3jyB6ugYw3yJ8otQPr0R4hUDqDZ9MwFsSBXXiJCZBMXM +5gf0vPSQ7RPi6ovDj6MzD8EpTBNO2hVWcXNyglD2mjN8orGoGjR0ZVzO0eurU+AagNjqOknkJjCb +5RyKqKkVMoaZkgoQI1YS4PbOTOK7vtuNknMBZi9iPrJyJ0U27U1W45eZ/zo1PqVUSlJZS2Db7v54 +EX9K3BR5YLZrZAPbFYPhor72I5dQ8AkzNqdxliXzuUJ92zg/LFis6ELhDtjTO0wugumDLmsx2d1H +hk9tl5EuT+IocTUW0fJz/iUrB0ckYyfI+PbZa/wSMVYIwFNCr5zQM378BvAxRAMU8Vjq8moNqRGy +g77FGr8H6lnco4g175x2MjxNBiLOFeXdntiP2t7SxDnlF4HPOEfrf4htWRvfn0IUrn7PqLBmZdo3 +r5+qPeoott7VMVgWglvquxl1AnMaykgaIZOQCo6ThKd9OyMYkomgjaw= +-----END CERTIFICATE----- + +Certum EC-384 CA +================ +-----BEGIN CERTIFICATE----- +MIICZTCCAeugAwIBAgIQeI8nXIESUiClBNAt3bpz9DAKBggqhkjOPQQDAzB0MQswCQYDVQQGEwJQ +TDEhMB8GA1UEChMYQXNzZWNvIERhdGEgU3lzdGVtcyBTLkEuMScwJQYDVQQLEx5DZXJ0dW0gQ2Vy +dGlmaWNhdGlvbiBBdXRob3JpdHkxGTAXBgNVBAMTEENlcnR1bSBFQy0zODQgQ0EwHhcNMTgwMzI2 +MDcyNDU0WhcNNDMwMzI2MDcyNDU0WjB0MQswCQYDVQQGEwJQTDEhMB8GA1UEChMYQXNzZWNvIERh +dGEgU3lzdGVtcyBTLkEuMScwJQYDVQQLEx5DZXJ0dW0gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkx +GTAXBgNVBAMTEENlcnR1bSBFQy0zODQgQ0EwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAATEKI6rGFtq +vm5kN2PkzeyrOvfMobgOgknXhimfoZTy42B4mIF4Bk3y7JoOV2CDn7TmFy8as10CW4kjPMIRBSqn +iBMY81CE1700LCeJVf/OTOffph8oxPBUw7l8t1Ot68KjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYD +VR0OBBYEFI0GZnQkdjrzife81r1HfS+8EF9LMA4GA1UdDwEB/wQEAwIBBjAKBggqhkjOPQQDAwNo +ADBlAjADVS2m5hjEfO/JUG7BJw+ch69u1RsIGL2SKcHvlJF40jocVYli5RsJHrpka/F2tNQCMQC0 +QoSZ/6vnnvuRlydd3LBbMHHOXjgaatkl5+r3YZJW+OraNsKHZZYuciUvf9/DE8k= +-----END CERTIFICATE----- + +Certum Trusted Root CA +====================== +-----BEGIN CERTIFICATE----- +MIIFwDCCA6igAwIBAgIQHr9ZULjJgDdMBvfrVU+17TANBgkqhkiG9w0BAQ0FADB6MQswCQYDVQQG +EwJQTDEhMB8GA1UEChMYQXNzZWNvIERhdGEgU3lzdGVtcyBTLkEuMScwJQYDVQQLEx5DZXJ0dW0g +Q2VydGlmaWNhdGlvbiBBdXRob3JpdHkxHzAdBgNVBAMTFkNlcnR1bSBUcnVzdGVkIFJvb3QgQ0Ew +HhcNMTgwMzE2MTIxMDEzWhcNNDMwMzE2MTIxMDEzWjB6MQswCQYDVQQGEwJQTDEhMB8GA1UEChMY +QXNzZWNvIERhdGEgU3lzdGVtcyBTLkEuMScwJQYDVQQLEx5DZXJ0dW0gQ2VydGlmaWNhdGlvbiBB +dXRob3JpdHkxHzAdBgNVBAMTFkNlcnR1bSBUcnVzdGVkIFJvb3QgQ0EwggIiMA0GCSqGSIb3DQEB +AQUAA4ICDwAwggIKAoICAQDRLY67tzbqbTeRn06TpwXkKQMlzhyC93yZn0EGze2jusDbCSzBfN8p +fktlL5On1AFrAygYo9idBcEq2EXxkd7fO9CAAozPOA/qp1x4EaTByIVcJdPTsuclzxFUl6s1wB52 +HO8AU5853BSlLCIls3Jy/I2z5T4IHhQqNwuIPMqw9MjCoa68wb4pZ1Xi/K1ZXP69VyywkI3C7Te2 +fJmItdUDmj0VDT06qKhF8JVOJVkdzZhpu9PMMsmN74H+rX2Ju7pgE8pllWeg8xn2A1bUatMn4qGt +g/BKEiJ3HAVz4hlxQsDsdUaakFjgao4rpUYwBI4Zshfjvqm6f1bxJAPXsiEodg42MEx51UGamqi4 +NboMOvJEGyCI98Ul1z3G4z5D3Yf+xOr1Uz5MZf87Sst4WmsXXw3Hw09Omiqi7VdNIuJGmj8PkTQk +fVXjjJU30xrwCSss0smNtA0Aq2cpKNgB9RkEth2+dv5yXMSFytKAQd8FqKPVhJBPC/PgP5sZ0jeJ +P/J7UhyM9uH3PAeXjA6iWYEMspA90+NZRu0PqafegGtaqge2Gcu8V/OXIXoMsSt0Puvap2ctTMSY +njYJdmZm/Bo/6khUHL4wvYBQv3y1zgD2DGHZ5yQD4OMBgQ692IU0iL2yNqh7XAjlRICMb/gv1SHK +HRzQ+8S1h9E6Tsd2tTVItQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBSM+xx1 +vALTn04uSNn5YFSqxLNP+jAOBgNVHQ8BAf8EBAMCAQYwDQYJKoZIhvcNAQENBQADggIBAEii1QAL +LtA/vBzVtVRJHlpr9OTy4EA34MwUe7nJ+jW1dReTagVphZzNTxl4WxmB82M+w85bj/UvXgF2Ez8s +ALnNllI5SW0ETsXpD4YN4fqzX4IS8TrOZgYkNCvozMrnadyHncI013nR03e4qllY/p0m+jiGPp2K +h2RX5Rc64vmNueMzeMGQ2Ljdt4NR5MTMI9UGfOZR0800McD2RrsLrfw9EAUqO0qRJe6M1ISHgCq8 +CYyqOhNf6DR5UMEQGfnTKB7U0VEwKbOukGfWHwpjscWpxkIxYxeU72nLL/qMFH3EQxiJ2fAyQOaA +4kZf5ePBAFmo+eggvIksDkc0C+pXwlM2/KfUrzHN/gLldfq5Jwn58/U7yn2fqSLLiMmq0Uc9Nneo +WWRrJ8/vJ8HjJLWG965+Mk2weWjROeiQWMODvA8s1pfrzgzhIMfatz7DP78v3DSk+yshzWePS/Tj +6tQ/50+6uaWTRRxmHyH6ZF5v4HaUMst19W7l9o/HuKTMqJZ9ZPskWkoDbGs4xugDQ5r3V7mzKWmT +OPQD8rv7gmsHINFSH5pkAnuYZttcTVoP0ISVoDwUQwbKytu4QTbaakRnh6+v40URFWkIsr4WOZck +bxJF0WddCajJFdr60qZfE2Efv4WstK2tBZQIgx51F9NxO5NQI1mg7TyRVJ12AMXDuDjb +-----END CERTIFICATE----- diff --git a/kirby/composer.json b/kirby/composer.json new file mode 100644 index 0000000..fe84d66 --- /dev/null +++ b/kirby/composer.json @@ -0,0 +1,77 @@ +{ + "name": "getkirby/cms", + "type": "kirby-cms", + "description": "The Kirby 3 core", + "keywords": [ + "kirby", + "cms", + "core" + ], + "homepage": "https://getkirby.com", + "version": "3.5.7.1", + "license": "proprietary", + "authors": [ + { + "name": "Kirby Team", + "email": "support@getkirby.com", + "homepage": "https://getkirby.com" + } + ], + "require": { + "php": ">=7.3.0 <8.1.0", + "ext-ctype": "*", + "ext-mbstring": "*", + "claviska/simpleimage": "3.6.3", + "filp/whoops": "2.12.1", + "getkirby/composer-installer": "^1.2.0", + "laminas/laminas-escaper": "2.7.0", + "michelf/php-smartypants": "1.8.1", + "mustangostang/spyc": "0.6.3", + "phpmailer/phpmailer": "6.5.0", + "true/punycode": "2.1.1" + }, + "config": { + "optimize-autoloader": true, + "platform-check": false + }, + "autoload": { + "psr-4": { + "Kirby\\": "src/" + }, + "classmap": [ + "dependencies/" + ], + "files": [ + "config/setup.php", + "config/helpers.php" + ] + }, + "scripts": { + "post-update-cmd": "curl -o cacert.pem https://curl.se/ca/cacert.pem", + "analyze": [ + "@analyze:composer", + "@analyze:psalm", + "@analyze:phpcpd", + "@analyze:phpmd" + ], + "analyze:composer": "composer validate --strict --no-check-version --no-check-all", + "analyze:phpcpd": "phpcpd --fuzzy --exclude tests --exclude vendor .", + "analyze:phpmd": "phpmd . ansi phpmd.xml.dist --exclude 'dependencies/*,tests/*,vendor/*'", + "analyze:psalm": "psalm", + "build": "./scripts/build", + "ci": [ + "@fix", + "@analyze", + "@test" + ], + "fix": "php-cs-fixer fix", + "test": "phpunit --stderr --coverage-html=tests/coverage", + "zip": "composer archive --format=zip --file=dist" + }, + "support": { + "email": "support@getkirby.com", + "issues": "https://github.com/getkirby/kirby/issues", + "forum": "https://forum.getkirby.com", + "source": "https://github.com/getkirby/kirby" + } +} diff --git a/kirby/composer.lock b/kirby/composer.lock new file mode 100644 index 0000000..7e1c911 --- /dev/null +++ b/kirby/composer.lock @@ -0,0 +1,733 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "c087786719948c60e5d74492f70440a5", + "packages": [ + { + "name": "claviska/simpleimage", + "version": "3.6.3", + "source": { + "type": "git", + "url": "https://github.com/claviska/SimpleImage.git", + "reference": "21b6f4bf4ef1927158b3e29bd0c2d99c6681c750" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/claviska/SimpleImage/zipball/21b6f4bf4ef1927158b3e29bd0c2d99c6681c750", + "reference": "21b6f4bf4ef1927158b3e29bd0c2d99c6681c750", + "shasum": "" + }, + "require": { + "ext-gd": "*", + "league/color-extractor": "0.3.*", + "php": ">=5.6.0" + }, + "type": "library", + "autoload": { + "psr-0": { + "claviska": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Cory LaViska", + "homepage": "http://www.abeautifulsite.net/", + "role": "Developer" + } + ], + "description": "A PHP class that makes working with images as simple as possible.", + "support": { + "issues": "https://github.com/claviska/SimpleImage/issues", + "source": "https://github.com/claviska/SimpleImage/tree/3.6.3" + }, + "funding": [ + { + "url": "https://github.com/claviska", + "type": "github" + } + ], + "time": "2021-04-20T12:18:18+00:00" + }, + { + "name": "filp/whoops", + "version": "2.12.1", + "source": { + "type": "git", + "url": "https://github.com/filp/whoops.git", + "reference": "c13c0be93cff50f88bbd70827d993026821914dd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/filp/whoops/zipball/c13c0be93cff50f88bbd70827d993026821914dd", + "reference": "c13c0be93cff50f88bbd70827d993026821914dd", + "shasum": "" + }, + "require": { + "php": "^5.5.9 || ^7.0 || ^8.0", + "psr/log": "^1.0.1" + }, + "require-dev": { + "mockery/mockery": "^0.9 || ^1.0", + "phpunit/phpunit": "^4.8.36 || ^5.7.27 || ^6.5.14 || ^7.5.20 || ^8.5.8 || ^9.3.3", + "symfony/var-dumper": "^2.6 || ^3.0 || ^4.0 || ^5.0" + }, + "suggest": { + "symfony/var-dumper": "Pretty print complex values better with var-dumper available", + "whoops/soap": "Formats errors as SOAP responses" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.7-dev" + } + }, + "autoload": { + "psr-4": { + "Whoops\\": "src/Whoops/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Filipe Dobreira", + "homepage": "https://github.com/filp", + "role": "Developer" + } + ], + "description": "php error handling for cool kids", + "homepage": "https://filp.github.io/whoops/", + "keywords": [ + "error", + "exception", + "handling", + "library", + "throwable", + "whoops" + ], + "support": { + "issues": "https://github.com/filp/whoops/issues", + "source": "https://github.com/filp/whoops/tree/2.12.1" + }, + "funding": [ + { + "url": "https://github.com/denis-sokolov", + "type": "github" + } + ], + "time": "2021-04-25T12:00:00+00:00" + }, + { + "name": "getkirby/composer-installer", + "version": "1.2.1", + "source": { + "type": "git", + "url": "https://github.com/getkirby/composer-installer.git", + "reference": "c98ece30bfba45be7ce457e1102d1b169d922f3d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/getkirby/composer-installer/zipball/c98ece30bfba45be7ce457e1102d1b169d922f3d", + "reference": "c98ece30bfba45be7ce457e1102d1b169d922f3d", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.0 || ^2.0" + }, + "require-dev": { + "composer/composer": "^1.8 || ^2.0" + }, + "type": "composer-plugin", + "extra": { + "class": "Kirby\\ComposerInstaller\\Plugin" + }, + "autoload": { + "psr-4": { + "Kirby\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Kirby's custom Composer installer for the Kirby CMS and for Kirby plugins", + "homepage": "https://getkirby.com", + "support": { + "issues": "https://github.com/getkirby/composer-installer/issues", + "source": "https://github.com/getkirby/composer-installer/tree/1.2.1" + }, + "funding": [ + { + "url": "https://getkirby.com/buy", + "type": "custom" + } + ], + "time": "2020-12-28T12:54:39+00:00" + }, + { + "name": "laminas/laminas-escaper", + "version": "2.7.0", + "source": { + "type": "git", + "url": "https://github.com/laminas/laminas-escaper.git", + "reference": "5e04bc5ae5990b17159d79d331055e2c645e5cc5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laminas/laminas-escaper/zipball/5e04bc5ae5990b17159d79d331055e2c645e5cc5", + "reference": "5e04bc5ae5990b17159d79d331055e2c645e5cc5", + "shasum": "" + }, + "require": { + "laminas/laminas-zendframework-bridge": "^1.0", + "php": "^7.3 || ~8.0.0" + }, + "replace": { + "zendframework/zend-escaper": "^2.6.1" + }, + "require-dev": { + "laminas/laminas-coding-standard": "~1.0.0", + "phpunit/phpunit": "^9.3", + "psalm/plugin-phpunit": "^0.12.2", + "vimeo/psalm": "^3.16" + }, + "suggest": { + "ext-iconv": "*", + "ext-mbstring": "*" + }, + "type": "library", + "autoload": { + "psr-4": { + "Laminas\\Escaper\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "Securely and safely escape HTML, HTML attributes, JavaScript, CSS, and URLs", + "homepage": "https://laminas.dev", + "keywords": [ + "escaper", + "laminas" + ], + "support": { + "chat": "https://laminas.dev/chat", + "docs": "https://docs.laminas.dev/laminas-escaper/", + "forum": "https://discourse.laminas.dev", + "issues": "https://github.com/laminas/laminas-escaper/issues", + "rss": "https://github.com/laminas/laminas-escaper/releases.atom", + "source": "https://github.com/laminas/laminas-escaper" + }, + "funding": [ + { + "url": "https://funding.communitybridge.org/projects/laminas-project", + "type": "community_bridge" + } + ], + "time": "2020-11-17T21:26:43+00:00" + }, + { + "name": "laminas/laminas-zendframework-bridge", + "version": "1.3.0", + "source": { + "type": "git", + "url": "https://github.com/laminas/laminas-zendframework-bridge.git", + "reference": "13af2502d9bb6f7d33be2de4b51fb68c6cdb476e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laminas/laminas-zendframework-bridge/zipball/13af2502d9bb6f7d33be2de4b51fb68c6cdb476e", + "reference": "13af2502d9bb6f7d33be2de4b51fb68c6cdb476e", + "shasum": "" + }, + "require": { + "php": "^7.3 || ^8.0" + }, + "require-dev": { + "phpunit/phpunit": "^5.7 || ^6.5 || ^7.5 || ^8.1 || ^9.3", + "psalm/plugin-phpunit": "^0.15.1", + "squizlabs/php_codesniffer": "^3.5", + "vimeo/psalm": "^4.6" + }, + "type": "library", + "extra": { + "laminas": { + "module": "Laminas\\ZendFrameworkBridge" + } + }, + "autoload": { + "files": [ + "src/autoload.php" + ], + "psr-4": { + "Laminas\\ZendFrameworkBridge\\": "src//" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "Alias legacy ZF class names to Laminas Project equivalents.", + "keywords": [ + "ZendFramework", + "autoloading", + "laminas", + "zf" + ], + "support": { + "forum": "https://discourse.laminas.dev/", + "issues": "https://github.com/laminas/laminas-zendframework-bridge/issues", + "rss": "https://github.com/laminas/laminas-zendframework-bridge/releases.atom", + "source": "https://github.com/laminas/laminas-zendframework-bridge" + }, + "funding": [ + { + "url": "https://funding.communitybridge.org/projects/laminas-project", + "type": "community_bridge" + } + ], + "time": "2021-06-24T12:49:22+00:00" + }, + { + "name": "league/color-extractor", + "version": "0.3.2", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/color-extractor.git", + "reference": "837086ec60f50c84c611c613963e4ad2e2aec806" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/color-extractor/zipball/837086ec60f50c84c611c613963e4ad2e2aec806", + "reference": "837086ec60f50c84c611c613963e4ad2e2aec806", + "shasum": "" + }, + "require": { + "ext-gd": "*", + "php": ">=5.4.0" + }, + "replace": { + "matthecat/colorextractor": "*" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "~2", + "phpunit/phpunit": "~5" + }, + "type": "library", + "autoload": { + "psr-4": { + "": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mathieu Lechat", + "email": "math.lechat@gmail.com", + "homepage": "http://matthecat.com", + "role": "Developer" + } + ], + "description": "Extract colors from an image as a human would do.", + "homepage": "https://github.com/thephpleague/color-extractor", + "keywords": [ + "color", + "extract", + "human", + "image", + "palette" + ], + "support": { + "issues": "https://github.com/thephpleague/color-extractor/issues", + "source": "https://github.com/thephpleague/color-extractor/tree/master" + }, + "time": "2016-12-15T09:30:02+00:00" + }, + { + "name": "michelf/php-smartypants", + "version": "1.8.1", + "source": { + "type": "git", + "url": "https://github.com/michelf/php-smartypants.git", + "reference": "47d17c90a4dfd0ccf1f87e25c65e6c8012415aad" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/michelf/php-smartypants/zipball/47d17c90a4dfd0ccf1f87e25c65e6c8012415aad", + "reference": "47d17c90a4dfd0ccf1f87e25c65e6c8012415aad", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "autoload": { + "psr-0": { + "Michelf": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Michel Fortin", + "email": "michel.fortin@michelf.ca", + "homepage": "https://michelf.ca/", + "role": "Developer" + }, + { + "name": "John Gruber", + "homepage": "https://daringfireball.net/" + } + ], + "description": "PHP SmartyPants", + "homepage": "https://michelf.ca/projects/php-smartypants/", + "keywords": [ + "dashes", + "quotes", + "spaces", + "typographer", + "typography" + ], + "support": { + "issues": "https://github.com/michelf/php-smartypants/issues", + "source": "https://github.com/michelf/php-smartypants/tree/1.8.1" + }, + "time": "2016-12-13T01:01:17+00:00" + }, + { + "name": "mustangostang/spyc", + "version": "0.6.3", + "source": { + "type": "git", + "url": "git@github.com:mustangostang/spyc.git", + "reference": "4627c838b16550b666d15aeae1e5289dd5b77da0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/mustangostang/spyc/zipball/4627c838b16550b666d15aeae1e5289dd5b77da0", + "reference": "4627c838b16550b666d15aeae1e5289dd5b77da0", + "shasum": "" + }, + "require": { + "php": ">=5.3.1" + }, + "require-dev": { + "phpunit/phpunit": "4.3.*@dev" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "0.5.x-dev" + } + }, + "autoload": { + "files": [ + "Spyc.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "mustangostang", + "email": "vlad.andersen@gmail.com" + } + ], + "description": "A simple YAML loader/dumper class for PHP", + "homepage": "https://github.com/mustangostang/spyc/", + "keywords": [ + "spyc", + "yaml", + "yml" + ], + "time": "2019-09-10T13:16:29+00:00" + }, + { + "name": "phpmailer/phpmailer", + "version": "v6.5.0", + "source": { + "type": "git", + "url": "https://github.com/PHPMailer/PHPMailer.git", + "reference": "a5b5c43e50b7fba655f793ad27303cd74c57363c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHPMailer/PHPMailer/zipball/a5b5c43e50b7fba655f793ad27303cd74c57363c", + "reference": "a5b5c43e50b7fba655f793ad27303cd74c57363c", + "shasum": "" + }, + "require": { + "ext-ctype": "*", + "ext-filter": "*", + "ext-hash": "*", + "php": ">=5.5.0" + }, + "require-dev": { + "dealerdirect/phpcodesniffer-composer-installer": "^0.7.0", + "doctrine/annotations": "^1.2", + "phpcompatibility/php-compatibility": "^9.3.5", + "roave/security-advisories": "dev-latest", + "squizlabs/php_codesniffer": "^3.5.6", + "yoast/phpunit-polyfills": "^0.2.0" + }, + "suggest": { + "ext-mbstring": "Needed to send email in multibyte encoding charset or decode encoded addresses", + "hayageek/oauth2-yahoo": "Needed for Yahoo XOAUTH2 authentication", + "league/oauth2-google": "Needed for Google XOAUTH2 authentication", + "psr/log": "For optional PSR-3 debug logging", + "stevenmaguire/oauth2-microsoft": "Needed for Microsoft XOAUTH2 authentication", + "symfony/polyfill-mbstring": "To support UTF-8 if the Mbstring PHP extension is not enabled (^1.2)" + }, + "type": "library", + "autoload": { + "psr-4": { + "PHPMailer\\PHPMailer\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-2.1-only" + ], + "authors": [ + { + "name": "Marcus Bointon", + "email": "phpmailer@synchromedia.co.uk" + }, + { + "name": "Jim Jagielski", + "email": "jimjag@gmail.com" + }, + { + "name": "Andy Prevost", + "email": "codeworxtech@users.sourceforge.net" + }, + { + "name": "Brent R. Matzelle" + } + ], + "description": "PHPMailer is a full-featured email creation and transfer class for PHP", + "support": { + "issues": "https://github.com/PHPMailer/PHPMailer/issues", + "source": "https://github.com/PHPMailer/PHPMailer/tree/v6.5.0" + }, + "funding": [ + { + "url": "https://github.com/Synchro", + "type": "github" + } + ], + "time": "2021-06-16T14:33:43+00:00" + }, + { + "name": "psr/log", + "version": "1.1.4", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "d49695b909c3b7628b6289db5479a1c204601f11" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/d49695b909c3b7628b6289db5479a1c204601f11", + "reference": "d49695b909c3b7628b6289db5479a1c204601f11", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "Psr/Log/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "support": { + "source": "https://github.com/php-fig/log/tree/1.1.4" + }, + "time": "2021-05-03T11:20:27+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.23.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "2df51500adbaebdc4c38dea4c89a2e131c45c8a1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/2df51500adbaebdc4c38dea4c89a2e131c45c8a1", + "reference": "2df51500adbaebdc4c38dea4c89a2e131c45c8a1", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.23-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.23.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-05-27T09:27:20+00:00" + }, + { + "name": "true/punycode", + "version": "v2.1.1", + "source": { + "type": "git", + "url": "https://github.com/true/php-punycode.git", + "reference": "a4d0c11a36dd7f4e7cd7096076cab6d3378a071e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/true/php-punycode/zipball/a4d0c11a36dd7f4e7cd7096076cab6d3378a071e", + "reference": "a4d0c11a36dd7f4e7cd7096076cab6d3378a071e", + "shasum": "" + }, + "require": { + "php": ">=5.3.0", + "symfony/polyfill-mbstring": "^1.3" + }, + "require-dev": { + "phpunit/phpunit": "~4.7", + "squizlabs/php_codesniffer": "~2.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "TrueBV\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Renan Gonçalves", + "email": "renan.saddam@gmail.com" + } + ], + "description": "A Bootstring encoding of Unicode for Internationalized Domain Names in Applications (IDNA)", + "homepage": "https://github.com/true/php-punycode", + "keywords": [ + "idna", + "punycode" + ], + "support": { + "issues": "https://github.com/true/php-punycode/issues", + "source": "https://github.com/true/php-punycode/tree/master" + }, + "time": "2016-11-16T10:37:54+00:00" + } + ], + "packages-dev": [], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "php": ">=7.3.0 <8.1.0", + "ext-ctype": "*", + "ext-mbstring": "*" + }, + "platform-dev": [], + "plugin-api-version": "2.1.0" +} diff --git a/kirby/config/aliases.php b/kirby/config/aliases.php new file mode 100644 index 0000000..8c7016f --- /dev/null +++ b/kirby/config/aliases.php @@ -0,0 +1,62 @@ + 'Kirby\Cms\Asset', + 'collection' => 'Kirby\Cms\Collection', + 'dir' => 'Kirby\Cms\Dir', + 'field' => 'Kirby\Cms\Field', + 'file' => 'Kirby\Cms\File', + 'files' => 'Kirby\Cms\Files', + 'html' => 'Kirby\Cms\Html', + 'kirby' => 'Kirby\Cms\App', + 'page' => 'Kirby\Cms\Page', + 'pages' => 'Kirby\Cms\Pages', + 'pagination' => 'Kirby\Cms\Pagination', + 'r' => 'Kirby\Cms\R', + 'response' => 'Kirby\Cms\Response', + 's' => 'Kirby\Cms\S', + 'site' => 'Kirby\Cms\Site', + 'structure' => 'Kirby\Cms\Structure', + 'url' => 'Kirby\Cms\Url', + 'user' => 'Kirby\Cms\User', + 'users' => 'Kirby\Cms\Users', + 'visitor' => 'Kirby\Cms\Visitor', + + // data handler + 'data' => 'Kirby\Data\Data', + 'json' => 'Kirby\Data\Json', + 'yaml' => 'Kirby\Data\Yaml', + + // data classes + 'database' => 'Kirby\Database\Database', + 'db' => 'Kirby\Database\Db', + + // exceptions + 'errorpageexception' => 'Kirby\Exception\ErrorPageException', + + // http classes + 'cookie' => 'Kirby\Http\Cookie', + 'header' => 'Kirby\Http\Header', + 'remote' => 'Kirby\Http\Remote', + 'server' => 'Kirby\Http\Server', + + // image classes + 'dimensions' => 'Kirby\Image\Dimensions', + + // toolkit classes + 'a' => 'Kirby\Toolkit\A', + 'c' => 'Kirby\Toolkit\Config', + 'config' => 'Kirby\Toolkit\Config', + 'escape' => 'Kirby\Toolkit\Escape', + 'f' => 'Kirby\Toolkit\F', + 'i18n' => 'Kirby\Toolkit\I18n', + 'mime' => 'Kirby\Toolkit\Mime', + 'obj' => 'Kirby\Toolkit\Obj', + 'str' => 'Kirby\Toolkit\Str', + 'tpl' => 'Kirby\Toolkit\Tpl', + 'v' => 'Kirby\Toolkit\V', + 'xml' => 'Kirby\Toolkit\Xml' +]; +// @codeCoverageIgnoreEnd diff --git a/kirby/config/api/authentication.php b/kirby/config/api/authentication.php new file mode 100644 index 0000000..8dd5f33 --- /dev/null +++ b/kirby/config/api/authentication.php @@ -0,0 +1,24 @@ +kirby()->auth(); + $allowImpersonation = $this->kirby()->option('api.allowImpersonation') ?? false; + + // csrf token check + if ($auth->type($allowImpersonation) === 'session' && $auth->csrf() === false) { + throw new PermissionException('Unauthenticated'); + } + + // get user from session or basic auth + if ($user = $auth->user(null, $allowImpersonation)) { + if ($user->role()->permissions()->for('access', 'panel') === false) { + throw new PermissionException(['key' => 'access.panel']); + } + + return $user; + } + + throw new PermissionException('Unauthenticated'); +}; diff --git a/kirby/config/api/collections.php b/kirby/config/api/collections.php new file mode 100644 index 0000000..bd817e9 --- /dev/null +++ b/kirby/config/api/collections.php @@ -0,0 +1,72 @@ + [ + 'model' => 'page', + 'type' => 'Kirby\Cms\Pages', + 'view' => 'compact' + ], + + /** + * Files + */ + 'files' => [ + 'model' => 'file', + 'type' => 'Kirby\Cms\Files' + ], + + /** + * Languages + */ + 'languages' => [ + 'model' => 'language', + 'type' => 'Kirby\Cms\Languages' + ], + + /** + * Pages + */ + 'pages' => [ + 'model' => 'page', + 'type' => 'Kirby\Cms\Pages', + 'view' => 'compact' + ], + + /** + * Roles + */ + 'roles' => [ + 'model' => 'role', + 'type' => 'Kirby\Cms\Roles', + 'view' => 'compact' + ], + + /** + * Translations + */ + 'translations' => [ + 'model' => 'translation', + 'type' => 'Kirby\Cms\Translations', + 'view' => 'compact' + ], + + /** + * Users + */ + 'users' => [ + 'default' => function () { + return $this->users(); + }, + 'model' => 'user', + 'type' => 'Kirby\Cms\Users', + 'view' => 'compact' + ] + +]; diff --git a/kirby/config/api/models.php b/kirby/config/api/models.php new file mode 100644 index 0000000..51d19fb --- /dev/null +++ b/kirby/config/api/models.php @@ -0,0 +1,20 @@ + include __DIR__ . '/models/File.php', + 'FileBlueprint' => include __DIR__ . '/models/FileBlueprint.php', + 'FileVersion' => include __DIR__ . '/models/FileVersion.php', + 'Language' => include __DIR__ . '/models/Language.php', + 'Page' => include __DIR__ . '/models/Page.php', + 'PageBlueprint' => include __DIR__ . '/models/PageBlueprint.php', + 'Role' => include __DIR__ . '/models/Role.php', + 'Site' => include __DIR__ . '/models/Site.php', + 'SiteBlueprint' => include __DIR__ . '/models/SiteBlueprint.php', + 'System' => include __DIR__ . '/models/System.php', + 'Translation' => include __DIR__ . '/models/Translation.php', + 'User' => include __DIR__ . '/models/User.php', + 'UserBlueprint' => include __DIR__ . '/models/UserBlueprint.php', +]; diff --git a/kirby/config/api/models/File.php b/kirby/config/api/models/File.php new file mode 100644 index 0000000..7315170 --- /dev/null +++ b/kirby/config/api/models/File.php @@ -0,0 +1,166 @@ + [ + 'blueprint' => function (File $file) { + return $file->blueprint(); + }, + 'content' => function (File $file) { + return Form::for($file)->values(); + }, + 'dimensions' => function (File $file) { + return $file->dimensions()->toArray(); + }, + 'dragText' => function (File $file) { + return $file->dragText(); + }, + 'exists' => function (File $file) { + return $file->exists(); + }, + 'extension' => function (File $file) { + return $file->extension(); + }, + 'filename' => function (File $file) { + return $file->filename(); + }, + 'id' => function (File $file) { + return $file->id(); + }, + 'link' => function (File $file) { + return $file->panelUrl(true); + }, + 'mime' => function (File $file) { + return $file->mime(); + }, + 'modified' => function (File $file) { + return $file->modified('c'); + }, + 'name' => function (File $file) { + return $file->name(); + }, + 'next' => function (File $file) { + return $file->next(); + }, + 'nextWithTemplate' => function (File $file) { + $files = $file->templateSiblings()->sort('sort', 'asc', 'filename', 'asc'); + $index = $files->indexOf($file); + + return $files->nth($index + 1); + }, + 'niceSize' => function (File $file) { + return $file->niceSize(); + }, + 'options' => function (File $file) { + return $file->panelOptions(); + }, + 'panelIcon' => function (File $file) { + return $file->panelIcon(); + }, + 'panelImage' => function (File $file) { + return $file->panelImage(); + }, + 'panelUrl' => function (File $file) { + return $file->panelUrl(true); + }, + 'prev' => function (File $file) { + return $file->prev(); + }, + 'prevWithTemplate' => function (File $file) { + $files = $file->templateSiblings()->sort('sort', 'asc', 'filename', 'asc'); + $index = $files->indexOf($file); + + return $files->nth($index - 1); + }, + 'parent' => function (File $file) { + return $file->parent(); + }, + 'parents' => function (File $file) { + return $file->parents()->flip(); + }, + 'size' => function (File $file) { + return $file->size(); + }, + 'template' => function (File $file) { + return $file->template(); + }, + 'thumbs' => function ($file) { + if ($file->isResizable() === false) { + return null; + } + + return [ + 'tiny' => $file->resize(128)->url(), + 'small' => $file->resize(256)->url(), + 'medium' => $file->resize(512)->url(), + 'large' => $file->resize(768)->url(), + 'huge' => $file->resize(1024)->url(), + ]; + }, + 'type' => function (File $file) { + return $file->type(); + }, + 'url' => function (File $file) { + return $file->url(true); + }, + ], + 'type' => 'Kirby\Cms\File', + 'views' => [ + 'default' => [ + 'content', + 'dimensions', + 'exists', + 'extension', + 'filename', + 'id', + 'link', + 'mime', + 'modified', + 'name', + 'next' => 'compact', + 'niceSize', + 'parent' => 'compact', + 'options', + 'prev' => 'compact', + 'size', + 'template', + 'type', + 'url' + ], + 'compact' => [ + 'filename', + 'id', + 'link', + 'type', + 'url', + ], + 'panel' => [ + 'blueprint', + 'content', + 'dimensions', + 'extension', + 'filename', + 'id', + 'link', + 'mime', + 'modified', + 'name', + 'nextWithTemplate' => 'compact', + 'niceSize', + 'options', + 'panelIcon', + 'panelImage', + 'parent' => 'compact', + 'parents' => ['id', 'slug', 'title'], + 'prevWithTemplate' => 'compact', + 'template', + 'type', + 'url' + ] + ], +]; diff --git a/kirby/config/api/models/FileBlueprint.php b/kirby/config/api/models/FileBlueprint.php new file mode 100644 index 0000000..0ca7cc0 --- /dev/null +++ b/kirby/config/api/models/FileBlueprint.php @@ -0,0 +1,26 @@ + [ + 'name' => function (FileBlueprint $blueprint) { + return $blueprint->name(); + }, + 'options' => function (FileBlueprint $blueprint) { + return $blueprint->options(); + }, + 'tabs' => function (FileBlueprint $blueprint) { + return $blueprint->tabs(); + }, + 'title' => function (FileBlueprint $blueprint) { + return $blueprint->title(); + }, + ], + 'type' => 'Kirby\Cms\FileBlueprint', + 'views' => [ + ], +]; diff --git a/kirby/config/api/models/FileVersion.php b/kirby/config/api/models/FileVersion.php new file mode 100644 index 0000000..3d518b3 --- /dev/null +++ b/kirby/config/api/models/FileVersion.php @@ -0,0 +1,83 @@ + [ + 'dimensions' => function (FileVersion $file) { + return $file->dimensions()->toArray(); + }, + 'exists' => function (FileVersion $file) { + return $file->exists(); + }, + 'extension' => function (FileVersion $file) { + return $file->extension(); + }, + 'filename' => function (FileVersion $file) { + return $file->filename(); + }, + 'id' => function (FileVersion $file) { + return $file->id(); + }, + 'mime' => function (FileVersion $file) { + return $file->mime(); + }, + 'modified' => function (FileVersion $file) { + return $file->modified('c'); + }, + 'name' => function (FileVersion $file) { + return $file->name(); + }, + 'niceSize' => function (FileVersion $file) { + return $file->niceSize(); + }, + 'size' => function (FileVersion $file) { + return $file->size(); + }, + 'type' => function (FileVersion $file) { + return $file->type(); + }, + 'url' => function (FileVersion $file) { + return $file->url(true); + }, + ], + 'type' => 'Kirby\Cms\FileVersion', + 'views' => [ + 'default' => [ + 'dimensions', + 'exists', + 'extension', + 'filename', + 'id', + 'mime', + 'modified', + 'name', + 'niceSize', + 'size', + 'type', + 'url' + ], + 'compact' => [ + 'filename', + 'id', + 'type', + 'url', + ], + 'panel' => [ + 'dimensions', + 'extension', + 'filename', + 'id', + 'mime', + 'modified', + 'name', + 'niceSize', + 'template', + 'type', + 'url' + ] + ], +]; diff --git a/kirby/config/api/models/Language.php b/kirby/config/api/models/Language.php new file mode 100644 index 0000000..9350a89 --- /dev/null +++ b/kirby/config/api/models/Language.php @@ -0,0 +1,44 @@ + [ + 'code' => function (Language $language) { + return $language->code(); + }, + 'default' => function (Language $language) { + return $language->isDefault(); + }, + 'direction' => function (Language $language) { + return $language->direction(); + }, + 'locale' => function (Language $language) { + return $language->locale(); + }, + 'name' => function (Language $language) { + return $language->name(); + }, + 'rules' => function (Language $language) { + return $language->rules(); + }, + 'url' => function (Language $language) { + return $language->url(); + }, + ], + 'type' => 'Kirby\Cms\Language', + 'views' => [ + 'default' => [ + 'code', + 'default', + 'direction', + 'locale', + 'name', + 'rules', + 'url' + ] + ] +]; diff --git a/kirby/config/api/models/Page.php b/kirby/config/api/models/Page.php new file mode 100644 index 0000000..72084be --- /dev/null +++ b/kirby/config/api/models/Page.php @@ -0,0 +1,157 @@ + [ + 'blueprint' => function (Page $page) { + return $page->blueprint(); + }, + 'blueprints' => function (Page $page) { + return $page->blueprints(); + }, + 'children' => function (Page $page) { + return $page->children(); + }, + 'content' => function (Page $page) { + return Form::for($page)->values(); + }, + 'drafts' => function (Page $page) { + return $page->drafts(); + }, + 'errors' => function (Page $page) { + return $page->errors(); + }, + 'files' => function (Page $page) { + return $page->files()->sort('sort', 'asc', 'filename', 'asc'); + }, + 'hasChildren' => function (Page $page) { + return $page->hasChildren(); + }, + 'hasDrafts' => function (Page $page) { + return $page->hasDrafts(); + }, + 'hasFiles' => function (Page $page) { + return $page->hasFiles(); + }, + 'id' => function (Page $page) { + return $page->id(); + }, + 'isSortable' => function (Page $page) { + return $page->isSortable(); + }, + 'next' => function (Page $page) { + return $page + ->nextAll() + ->filter('intendedTemplate', $page->intendedTemplate()) + ->filter('status', $page->status()) + ->filter('isReadable', true) + ->first(); + }, + 'num' => function (Page $page) { + return $page->num(); + }, + 'options' => function (Page $page) { + return $page->panelOptions(['preview']); + }, + 'panelIcon' => function (Page $page) { + return $page->panelIcon(); + }, + 'panelImage' => function (Page $page) { + return $page->panelImage(); + }, + 'parent' => function (Page $page) { + return $page->parent(); + }, + 'parents' => function (Page $page) { + return $page->parents()->flip(); + }, + 'prev' => function (Page $page) { + return $page + ->prevAll() + ->filter('intendedTemplate', $page->intendedTemplate()) + ->filter('status', $page->status()) + ->filter('isReadable', true) + ->last(); + }, + 'previewUrl' => function (Page $page) { + return $page->previewUrl(); + }, + 'siblings' => function (Page $page) { + if ($page->isDraft() === true) { + return $page->parentModel()->children()->not($page); + } else { + return $page->siblings(); + } + }, + 'slug' => function (Page $page) { + return $page->slug(); + }, + 'status' => function (Page $page) { + return $page->status(); + }, + 'template' => function (Page $page) { + return $page->intendedTemplate()->name(); + }, + 'title' => function (Page $page) { + return $page->title()->value(); + }, + 'url' => function (Page $page) { + return $page->url(); + }, + ], + 'type' => 'Kirby\Cms\Page', + 'views' => [ + 'compact' => [ + 'id', + 'title', + 'url', + 'num' + ], + 'default' => [ + 'content', + 'id', + 'status', + 'num', + 'options', + 'parent' => 'compact', + 'slug', + 'template', + 'title', + 'url' + ], + 'panel' => [ + 'id', + 'blueprint', + 'content', + 'status', + 'options', + 'next' => ['id', 'slug', 'title'], + 'parents' => ['id', 'slug', 'title'], + 'prev' => ['id', 'slug', 'title'], + 'previewUrl', + 'slug', + 'title', + 'url' + ], + 'selector' => [ + 'id', + 'title', + 'parent' => [ + 'id', + 'title' + ], + 'children' => [ + 'hasChildren', + 'id', + 'panelIcon', + 'panelImage', + 'title', + ], + ] + ], +]; diff --git a/kirby/config/api/models/PageBlueprint.php b/kirby/config/api/models/PageBlueprint.php new file mode 100644 index 0000000..fd4cdef --- /dev/null +++ b/kirby/config/api/models/PageBlueprint.php @@ -0,0 +1,35 @@ + [ + 'name' => function (PageBlueprint $blueprint) { + return $blueprint->name(); + }, + 'num' => function (PageBlueprint $blueprint) { + return $blueprint->num(); + }, + 'options' => function (PageBlueprint $blueprint) { + return $blueprint->options(); + }, + 'preview' => function (PageBlueprint $blueprint) { + return $blueprint->preview(); + }, + 'status' => function (PageBlueprint $blueprint) { + return $blueprint->status(); + }, + 'tabs' => function (PageBlueprint $blueprint) { + return $blueprint->tabs(); + }, + 'title' => function (PageBlueprint $blueprint) { + return $blueprint->title(); + }, + ], + 'type' => 'Kirby\Cms\PageBlueprint', + 'views' => [ + ], +]; diff --git a/kirby/config/api/models/Role.php b/kirby/config/api/models/Role.php new file mode 100644 index 0000000..99aed16 --- /dev/null +++ b/kirby/config/api/models/Role.php @@ -0,0 +1,31 @@ + [ + 'description' => function (Role $role) { + return $role->description(); + }, + 'name' => function (Role $role) { + return $role->name(); + }, + 'permissions' => function (Role $role) { + return $role->permissions()->toArray(); + }, + 'title' => function (Role $role) { + return $role->title(); + }, + ], + 'type' => 'Kirby\Cms\Role', + 'views' => [ + 'compact' => [ + 'description', + 'name', + 'title' + ] + ] +]; diff --git a/kirby/config/api/models/Site.php b/kirby/config/api/models/Site.php new file mode 100644 index 0000000..ef31a07 --- /dev/null +++ b/kirby/config/api/models/Site.php @@ -0,0 +1,72 @@ + function () { + return $this->site(); + }, + 'fields' => [ + 'blueprint' => function (Site $site) { + return $site->blueprint(); + }, + 'children' => function (Site $site) { + return $site->children(); + }, + 'content' => function (Site $site) { + return Form::for($site)->values(); + }, + 'drafts' => function (Site $site) { + return $site->drafts(); + }, + 'files' => function (Site $site) { + return $site->files()->sort('sort', 'asc', 'filename', 'asc'); + }, + 'options' => function (Site $site) { + return $site->permissions()->toArray(); + }, + 'previewUrl' => function (Site $site) { + return $site->previewUrl(); + }, + 'title' => function (Site $site) { + return $site->title()->value(); + }, + 'url' => function (Site $site) { + return $site->url(); + }, + ], + 'type' => 'Kirby\Cms\Site', + 'views' => [ + 'compact' => [ + 'title', + 'url' + ], + 'default' => [ + 'content', + 'options', + 'title', + 'url' + ], + 'panel' => [ + 'title', + 'blueprint', + 'content', + 'options', + 'previewUrl', + 'url' + ], + 'selector' => [ + 'title', + 'children' => [ + 'id', + 'title', + 'panelIcon', + 'hasChildren' + ], + ] + ] +]; diff --git a/kirby/config/api/models/SiteBlueprint.php b/kirby/config/api/models/SiteBlueprint.php new file mode 100644 index 0000000..a3b0943 --- /dev/null +++ b/kirby/config/api/models/SiteBlueprint.php @@ -0,0 +1,26 @@ + [ + 'name' => function (SiteBlueprint $blueprint) { + return $blueprint->name(); + }, + 'options' => function (SiteBlueprint $blueprint) { + return $blueprint->options(); + }, + 'tabs' => function (SiteBlueprint $blueprint) { + return $blueprint->tabs(); + }, + 'title' => function (SiteBlueprint $blueprint) { + return $blueprint->title(); + }, + ], + 'type' => 'Kirby\Cms\SiteBlueprint', + 'views' => [ + ], +]; diff --git a/kirby/config/api/models/System.php b/kirby/config/api/models/System.php new file mode 100644 index 0000000..a162ce4 --- /dev/null +++ b/kirby/config/api/models/System.php @@ -0,0 +1,136 @@ + [ + 'ascii' => function () { + return Str::$ascii; + }, + 'authStatus' => function () { + return $this->kirby()->auth()->status()->toArray(); + }, + 'defaultLanguage' => function () { + return $this->kirby()->panelLanguage(); + }, + 'isOk' => function (System $system) { + return $system->isOk(); + }, + 'isInstallable' => function (System $system) { + return $system->isInstallable(); + }, + 'isInstalled' => function (System $system) { + return $system->isInstalled(); + }, + 'isLocal' => function (System $system) { + return $system->isLocal(); + }, + 'multilang' => function () { + return $this->kirby()->option('languages', false) !== false; + }, + 'languages' => function () { + return $this->kirby()->languages(); + }, + 'license' => function (System $system) { + return $system->license(); + }, + 'locales' => function () { + $locales = []; + $translations = $this->kirby()->translations(); + foreach ($translations as $translation) { + $locales[$translation->code()] = $translation->locale(); + } + return $locales; + }, + 'loginMethods' => function (System $system) { + return array_keys($system->loginMethods()); + }, + 'requirements' => function (System $system) { + return $system->toArray(); + }, + 'site' => function () { + try { + return $this->site()->blueprint()->title(); + } catch (Throwable $e) { + return $this->site()->title()->value(); + } + }, + 'slugs' => function () { + return Str::$language; + }, + 'title' => function () { + return $this->site()->title()->value(); + }, + 'translation' => function () { + if ($user = $this->user()) { + $translationCode = $user->language(); + } else { + $translationCode = $this->kirby()->panelLanguage(); + } + + if ($translation = $this->kirby()->translation($translationCode)) { + return $translation; + } else { + return $this->kirby()->translation('en'); + } + }, + 'kirbytext' => function () { + return $this->kirby()->option('panel.kirbytext') ?? true; + }, + 'user' => function () { + return $this->user(); + }, + 'version' => function () { + $user = $this->user(); + + if ($user && $user->role()->permissions()->for('access', 'settings') === true) { + return $this->kirby()->version(); + } else { + return null; + } + } + ], + 'type' => 'Kirby\Cms\System', + 'views' => [ + 'login' => [ + 'authStatus', + 'isOk', + 'isInstallable', + 'isInstalled', + 'loginMethods', + 'title', + 'translation' + ], + 'troubleshooting' => [ + 'isOk', + 'isInstallable', + 'isInstalled', + 'title', + 'translation', + 'requirements' + ], + 'panel' => [ + 'ascii', + 'defaultLanguage', + 'isOk', + 'isInstalled', + 'isLocal', + 'kirbytext', + 'languages', + 'license', + 'locales', + 'multilang', + 'requirements', + 'site', + 'slugs', + 'title', + 'translation', + 'user' => 'auth', + 'version' + ] + ], +]; diff --git a/kirby/config/api/models/Translation.php b/kirby/config/api/models/Translation.php new file mode 100644 index 0000000..13be9a0 --- /dev/null +++ b/kirby/config/api/models/Translation.php @@ -0,0 +1,34 @@ + [ + 'author' => function (Translation $translation) { + return $translation->author(); + }, + 'data' => function (Translation $translation) { + return $translation->dataWithFallback(); + }, + 'direction' => function (Translation $translation) { + return $translation->direction(); + }, + 'id' => function (Translation $translation) { + return $translation->id(); + }, + 'name' => function (Translation $translation) { + return $translation->name(); + }, + ], + 'type' => 'Kirby\Cms\Translation', + 'views' => [ + 'compact' => [ + 'direction', + 'id', + 'name' + ] + ] +]; diff --git a/kirby/config/api/models/User.php b/kirby/config/api/models/User.php new file mode 100644 index 0000000..f76ae53 --- /dev/null +++ b/kirby/config/api/models/User.php @@ -0,0 +1,108 @@ + function () { + return $this->user(); + }, + 'fields' => [ + 'avatar' => function (User $user) { + return $user->avatar() ? $user->avatar()->crop(512) : null; + }, + 'blueprint' => function (User $user) { + return $user->blueprint(); + }, + 'content' => function (User $user) { + return Form::for($user)->values(); + }, + 'email' => function (User $user) { + return $user->email(); + }, + 'files' => function (User $user) { + return $user->files()->sort('sort', 'asc', 'filename', 'asc'); + }, + 'id' => function (User $user) { + return $user->id(); + }, + 'language' => function (User $user) { + return $user->language(); + }, + 'name' => function (User $user) { + return $user->name()->value(); + }, + 'next' => function (User $user) { + return $user->next(); + }, + 'options' => function (User $user) { + return $user->panelOptions(); + }, + 'permissions' => function (User $user) { + return $user->role()->permissions()->toArray(); + }, + 'prev' => function (User $user) { + return $user->prev(); + }, + 'role' => function (User $user) { + return $user->role(); + }, + 'roles' => function (User $user) { + return $user->roles(); + }, + 'username' => function (User $user) { + return $user->username(); + } + ], + 'type' => 'Kirby\Cms\User', + 'views' => [ + 'default' => [ + 'avatar', + 'content', + 'email', + 'id', + 'language', + 'name', + 'next' => 'compact', + 'options', + 'prev' => 'compact', + 'role', + 'username' + ], + 'compact' => [ + 'avatar' => 'compact', + 'id', + 'email', + 'language', + 'name', + 'role' => 'compact', + 'username' + ], + 'auth' => [ + 'avatar' => 'compact', + 'permissions', + 'email', + 'id', + 'name', + 'role', + 'language' + ], + 'panel' => [ + 'avatar' => 'compact', + 'blueprint', + 'content', + 'email', + 'id', + 'language', + 'name', + 'next' => ['id', 'name'], + 'options', + 'prev' => ['id', 'name'], + 'role', + 'username', + ], + ] +]; diff --git a/kirby/config/api/models/UserBlueprint.php b/kirby/config/api/models/UserBlueprint.php new file mode 100644 index 0000000..fdf63eb --- /dev/null +++ b/kirby/config/api/models/UserBlueprint.php @@ -0,0 +1,26 @@ + [ + 'name' => function (UserBlueprint $blueprint) { + return $blueprint->name(); + }, + 'options' => function (UserBlueprint $blueprint) { + return $blueprint->options(); + }, + 'tabs' => function (UserBlueprint $blueprint) { + return $blueprint->tabs(); + }, + 'title' => function (UserBlueprint $blueprint) { + return $blueprint->title(); + }, + ], + 'type' => 'Kirby\Cms\UserBlueprint', + 'views' => [ + ], +]; diff --git a/kirby/config/api/routes.php b/kirby/config/api/routes.php new file mode 100644 index 0000000..fd3449d --- /dev/null +++ b/kirby/config/api/routes.php @@ -0,0 +1,26 @@ +option('languages', false) !== false) { + $routes = array_merge($routes, include __DIR__ . '/routes/languages.php'); + } + + return $routes; +}; diff --git a/kirby/config/api/routes/auth.php b/kirby/config/api/routes/auth.php new file mode 100644 index 0000000..19f45a5 --- /dev/null +++ b/kirby/config/api/routes/auth.php @@ -0,0 +1,108 @@ + 'auth', + 'method' => 'GET', + 'action' => function () { + if ($user = $this->kirby()->auth()->user()) { + return $this->resolve($user)->view('auth'); + } + + throw new NotFoundException('The user cannot be found'); + } + ], + [ + 'pattern' => 'auth/code', + 'method' => 'POST', + 'auth' => false, + 'action' => function () { + $auth = $this->kirby()->auth(); + + // csrf token check + if ($auth->type() === 'session' && $auth->csrf() === false) { + throw new InvalidArgumentException('Invalid CSRF token'); + } + + $user = $auth->verifyChallenge($this->requestBody('code')); + + return [ + 'code' => 200, + 'status' => 'ok', + 'user' => $this->resolve($user)->view('auth')->toArray() + ]; + } + ], + [ + 'pattern' => 'auth/login', + 'method' => 'POST', + 'auth' => false, + 'action' => function () { + $auth = $this->kirby()->auth(); + $methods = $this->kirby()->system()->loginMethods(); + + // csrf token check + if ($auth->type() === 'session' && $auth->csrf() === false) { + throw new InvalidArgumentException('Invalid CSRF token'); + } + + $email = $this->requestBody('email'); + $long = $this->requestBody('long'); + $password = $this->requestBody('password'); + + if ($password) { + if (isset($methods['password']) !== true) { + throw new InvalidArgumentException('Login with password is not enabled'); + } + + if ( + isset($methods['password']['2fa']) === true && + $methods['password']['2fa'] === true + ) { + $status = $auth->login2fa($email, $password, $long); + } else { + $user = $auth->login($email, $password, $long); + } + } else { + if (isset($methods['code']) === true) { + $mode = 'login'; + } elseif (isset($methods['password-reset']) === true) { + $mode = 'password-reset'; + } else { + throw new InvalidArgumentException('Login without password is not enabled'); + } + + $status = $auth->createChallenge($email, $long, $mode); + } + + if (isset($user)) { + return [ + 'code' => 200, + 'status' => 'ok', + 'user' => $this->resolve($user)->view('auth')->toArray() + ]; + } else { + return [ + 'code' => 200, + 'status' => 'ok', + 'challenge' => $status->challenge() + ]; + } + } + ], + [ + 'pattern' => 'auth/logout', + 'method' => 'POST', + 'auth' => false, + 'action' => function () { + $this->kirby()->auth()->logout(); + return true; + } + ], +]; diff --git a/kirby/config/api/routes/files.php b/kirby/config/api/routes/files.php new file mode 100644 index 0000000..e183716 --- /dev/null +++ b/kirby/config/api/routes/files.php @@ -0,0 +1,123 @@ + '(:all)/files/(:any)/sections/(:any)', + 'method' => 'GET', + 'action' => function (string $path, string $filename, string $sectionName) { + if ($section = $this->file($path, $filename)->blueprint()->section($sectionName)) { + return $section->toResponse(); + } + } + ], + [ + 'pattern' => '(:all)/files/(:any)/fields/(:any)/(:all?)', + 'method' => 'ALL', + 'action' => function (string $parent, string $filename, string $fieldName, string $path = null) { + if ($file = $this->file($parent, $filename)) { + return $this->fieldApi($file, $fieldName, $path); + } + } + ], + [ + 'pattern' => '(:all)/files', + 'method' => 'GET', + 'action' => function (string $path) { + return $this->parent($path)->files()->sort('sort', 'asc', 'filename', 'asc'); + } + ], + [ + 'pattern' => '(:all)/files', + 'method' => 'POST', + 'action' => function (string $path) { + return $this->upload(function ($source, $filename) use ($path) { + return $this->parent($path)->createFile([ + 'source' => $source, + 'template' => $this->requestBody('template'), + 'filename' => $filename + ]); + }); + } + ], + [ + 'pattern' => '(:all)/files/search', + 'method' => 'GET|POST', + 'action' => function (string $path) { + $files = $this->parent($path)->files(); + + if ($this->requestMethod() === 'GET') { + return $files->search($this->requestQuery('q')); + } else { + return $files->query($this->requestBody()); + } + } + ], + [ + 'pattern' => '(:all)/files/sort', + 'method' => 'PATCH', + 'action' => function (string $path) { + return $this->parent($path)->files()->changeSort( + $this->requestBody('files'), + $this->requestBody('index') + ); + } + ], + [ + 'pattern' => '(:all)/files/(:any)', + 'method' => 'GET', + 'action' => function (string $path, string $filename) { + return $this->file($path, $filename); + } + ], + [ + 'pattern' => '(:all)/files/(:any)', + 'method' => 'PATCH', + 'action' => function (string $path, string $filename) { + return $this->file($path, $filename)->update($this->requestBody(), $this->language(), true); + } + ], + [ + 'pattern' => '(:all)/files/(:any)', + 'method' => 'POST', + 'action' => function (string $path, string $filename) { + return $this->upload(function ($source) use ($path, $filename) { + return $this->file($path, $filename)->replace($source); + }); + } + ], + [ + 'pattern' => '(:all)/files/(:any)', + 'method' => 'DELETE', + 'action' => function (string $path, string $filename) { + return $this->file($path, $filename)->delete(); + } + ], + [ + 'pattern' => '(:all)/files/(:any)/name', + 'method' => 'PATCH', + 'action' => function (string $path, string $filename) { + return $this->file($path, $filename)->changeName($this->requestBody('name')); + } + ], + [ + 'pattern' => 'files/search', + 'method' => 'GET|POST', + 'action' => function () { + $files = $this + ->site() + ->index(true) + ->filter('isReadable', true) + ->files(); + + if ($this->requestMethod() === 'GET') { + return $files->search($this->requestQuery('q')); + } else { + return $files->query($this->requestBody()); + } + } + ], +]; diff --git a/kirby/config/api/routes/languages.php b/kirby/config/api/routes/languages.php new file mode 100644 index 0000000..8d8829b --- /dev/null +++ b/kirby/config/api/routes/languages.php @@ -0,0 +1,46 @@ + 'languages', + 'method' => 'GET', + 'action' => function () { + return $this->kirby()->languages(); + } + ], + [ + 'pattern' => 'languages', + 'method' => 'POST', + 'action' => function () { + return $this->kirby()->languages()->create($this->requestBody()); + } + ], + [ + 'pattern' => 'languages/(:any)', + 'method' => 'GET', + 'action' => function (string $code) { + return $this->kirby()->languages()->find($code); + } + ], + [ + 'pattern' => 'languages/(:any)', + 'method' => 'PATCH', + 'action' => function (string $code) { + if ($language = $this->kirby()->languages()->find($code)) { + return $language->update($this->requestBody()); + } + } + ], + [ + 'pattern' => 'languages/(:any)', + 'method' => 'DELETE', + 'action' => function (string $code) { + if ($language = $this->kirby()->languages()->find($code)) { + return $language->delete(); + } + } + ] +]; diff --git a/kirby/config/api/routes/lock.php b/kirby/config/api/routes/lock.php new file mode 100644 index 0000000..302a945 --- /dev/null +++ b/kirby/config/api/routes/lock.php @@ -0,0 +1,99 @@ + '(:all)/lock', + 'method' => 'GET', + 'action' => function (string $path) { + if ($lock = $this->parent($path)->lock()) { + return [ + 'supported' => true, + 'locked' => $lock->get() + ]; + } + + return [ + 'supported' => false, + 'locked' => null + ]; + } + ], + [ + 'pattern' => '(:all)/lock', + 'method' => 'PATCH', + 'action' => function (string $path) { + if ($lock = $this->parent($path)->lock()) { + return $lock->create(); + } + + throw new Exception([ + 'key' => 'lock.notImplemented', + 'httpCode' => 501 + ]); + } + ], + [ + 'pattern' => '(:all)/lock', + 'method' => 'DELETE', + 'action' => function (string $path) { + if ($lock = $this->parent($path)->lock()) { + return $lock->remove(); + } + + throw new Exception([ + 'key' => 'lock.notImplemented', + 'httpCode' => 501 + ]); + } + ], + [ + 'pattern' => '(:all)/unlock', + 'method' => 'GET', + 'action' => function (string $path) { + if ($lock = $this->parent($path)->lock()) { + return [ + 'supported' => true, + 'unlocked' => $lock->isUnlocked() + ]; + } + + return [ + 'supported' => false, + 'unlocked' => null + ]; + } + ], + [ + 'pattern' => '(:all)/unlock', + 'method' => 'PATCH', + 'action' => function (string $path) { + if ($lock = $this->parent($path)->lock()) { + return $lock->unlock(); + } + + throw new Exception([ + 'key' => 'lock.notImplemented', + 'httpCode' => 501 + ]); + } + ], + [ + 'pattern' => '(:all)/unlock', + 'method' => 'DELETE', + 'action' => function (string $path) { + if ($lock = $this->parent($path)->lock()) { + return $lock->resolve(); + } + + throw new Exception([ + 'key' => 'lock.notImplemented', + 'httpCode' => 501 + ]); + } + ], +]; diff --git a/kirby/config/api/routes/pages.php b/kirby/config/api/routes/pages.php new file mode 100644 index 0000000..04cc8a2 --- /dev/null +++ b/kirby/config/api/routes/pages.php @@ -0,0 +1,127 @@ + 'pages/(:any)', + 'method' => 'GET', + 'action' => function (string $id) { + return $this->page($id); + } + ], + [ + 'pattern' => 'pages/(:any)', + 'method' => 'PATCH', + 'action' => function (string $id) { + return $this->page($id)->update($this->requestBody(), $this->language(), true); + } + ], + [ + 'pattern' => 'pages/(:any)', + 'method' => 'DELETE', + 'action' => function (string $id) { + return $this->page($id)->delete($this->requestBody('force', false)); + } + ], + [ + 'pattern' => 'pages/(:any)/blueprint', + 'method' => 'GET', + 'action' => function (string $id) { + return $this->page($id)->blueprint(); + } + ], + [ + 'pattern' => [ + 'pages/(:any)/blueprints', + /** + * @deprecated + * @todo remove in 3.6.0 + */ + 'pages/(:any)/children/blueprints', + ], + 'method' => 'GET', + 'action' => function (string $id) { + return $this->page($id)->blueprints($this->requestQuery('section')); + } + ], + [ + 'pattern' => 'pages/(:any)/children', + 'method' => 'GET', + 'action' => function (string $id) { + return $this->pages($id, $this->requestQuery('status')); + } + ], + [ + 'pattern' => 'pages/(:any)/children', + 'method' => 'POST', + 'action' => function (string $id) { + return $this->page($id)->createChild($this->requestBody()); + } + ], + [ + 'pattern' => 'pages/(:any)/children/search', + 'method' => 'GET|POST', + 'action' => function (string $id) { + return $this->searchPages($id); + } + ], + [ + 'pattern' => 'pages/(:any)/duplicate', + 'method' => 'POST', + 'action' => function (string $id) { + return $this->page($id)->duplicate($this->requestBody('slug'), [ + 'children' => $this->requestBody('children'), + 'files' => $this->requestBody('files'), + ]); + } + ], + [ + 'pattern' => 'pages/(:any)/slug', + 'method' => 'PATCH', + 'action' => function (string $id) { + return $this->page($id)->changeSlug($this->requestBody('slug')); + } + ], + [ + 'pattern' => 'pages/(:any)/status', + 'method' => 'PATCH', + 'action' => function (string $id) { + return $this->page($id)->changeStatus($this->requestBody('status'), $this->requestBody('position')); + } + ], + [ + 'pattern' => 'pages/(:any)/template', + 'method' => 'PATCH', + 'action' => function (string $id) { + return $this->page($id)->changeTemplate($this->requestBody('template')); + } + ], + [ + 'pattern' => 'pages/(:any)/title', + 'method' => 'PATCH', + 'action' => function (string $id) { + return $this->page($id)->changeTitle($this->requestBody('title')); + } + ], + [ + 'pattern' => 'pages/(:any)/sections/(:any)', + 'method' => 'GET', + 'action' => function (string $id, string $sectionName) { + if ($section = $this->page($id)->blueprint()->section($sectionName)) { + return $section->toResponse(); + } + } + ], + [ + 'pattern' => 'pages/(:any)/fields/(:any)/(:all?)', + 'method' => 'ALL', + 'action' => function (string $id, string $fieldName, string $path = null) { + if ($page = $this->page($id)) { + return $this->fieldApi($page, $fieldName, $path); + } + } + ], +]; diff --git a/kirby/config/api/routes/roles.php b/kirby/config/api/routes/roles.php new file mode 100644 index 0000000..ab9505b --- /dev/null +++ b/kirby/config/api/routes/roles.php @@ -0,0 +1,28 @@ + 'roles', + 'method' => 'GET', + 'action' => function () { + switch (get('canBe')) { + case 'changed': + return $this->kirby()->roles()->canBeChanged(); + case 'created': + return $this->kirby()->roles()->canBeCreated(); + default: + return $this->kirby()->roles(); + } + } + ], + [ + 'pattern' => 'roles/(:any)', + 'method' => 'GET', + 'action' => function (string $name) { + return $this->kirby()->roles()->find($name); + } + ] +]; diff --git a/kirby/config/api/routes/site.php b/kirby/config/api/routes/site.php new file mode 100644 index 0000000..68c706f --- /dev/null +++ b/kirby/config/api/routes/site.php @@ -0,0 +1,110 @@ + 'site', + 'action' => function () { + return $this->site(); + } + ], + [ + 'pattern' => 'site', + 'method' => 'PATCH', + 'action' => function () { + return $this->site()->update($this->requestBody(), $this->language(), true); + } + ], + [ + 'pattern' => 'site/children', + 'method' => 'GET', + 'action' => function () { + return $this->pages(null, $this->requestQuery('status')); + } + ], + [ + 'pattern' => 'site/children', + 'method' => 'POST', + 'action' => function () { + return $this->site()->createChild($this->requestBody()); + } + ], + [ + 'pattern' => 'site/children/search', + 'method' => 'GET|POST', + 'action' => function () { + return $this->searchPages(); + } + ], + [ + 'pattern' => 'site/blueprint', + 'method' => 'GET', + 'action' => function () { + return $this->site()->blueprint(); + } + ], + [ + 'pattern' => [ + 'site/blueprints', + /** + * @deprecated + * @todo remove in 3.6.0 + */ + 'site/children/blueprints', + ], + 'method' => 'GET', + 'action' => function () { + return $this->site()->blueprints($this->requestQuery('section')); + } + ], + [ + 'pattern' => 'site/find', + 'method' => 'POST', + 'action' => function () { + return $this->site()->find(false, ...$this->requestBody()); + } + ], + [ + 'pattern' => 'site/title', + 'method' => 'PATCH', + 'action' => function () { + return $this->site()->changeTitle($this->requestBody('title')); + } + ], + [ + 'pattern' => 'site/search', + 'method' => 'GET|POST', + 'action' => function () { + $pages = $this + ->site() + ->index(true) + ->filter('isReadable', true); + + if ($this->requestMethod() === 'GET') { + return $pages->search($this->requestQuery('q')); + } else { + return $pages->query($this->requestBody()); + } + } + ], + [ + 'pattern' => 'site/sections/(:any)', + 'method' => 'GET', + 'action' => function (string $sectionName) { + if ($section = $this->site()->blueprint()->section($sectionName)) { + return $section->toResponse(); + } + } + ], + [ + 'pattern' => 'site/fields/(:any)/(:all?)', + 'method' => 'ALL', + 'action' => function (string $fieldName, string $path = null) { + return $this->fieldApi($this->site(), $fieldName, $path); + } + ] + +]; diff --git a/kirby/config/api/routes/system.php b/kirby/config/api/routes/system.php new file mode 100644 index 0000000..44c8807 --- /dev/null +++ b/kirby/config/api/routes/system.php @@ -0,0 +1,79 @@ + 'system', + 'method' => 'GET', + 'auth' => false, + 'action' => function () { + $system = $this->kirby()->system(); + + if ($this->kirby()->user()) { + return $system; + } else { + if ($system->isOk() === true) { + $info = $this->resolve($system)->view('login')->toArray(); + } else { + $info = $this->resolve($system)->view('troubleshooting')->toArray(); + } + + return [ + 'status' => 'ok', + 'data' => $info, + 'type' => 'model' + ]; + } + } + ], + [ + 'pattern' => 'system/register', + 'method' => 'POST', + 'action' => function () { + return $this->kirby()->system()->register($this->requestBody('license'), $this->requestBody('email')); + } + ], + [ + 'pattern' => 'system/install', + 'method' => 'POST', + 'auth' => false, + 'action' => function () { + $system = $this->kirby()->system(); + $auth = $this->kirby()->auth(); + + // csrf token check + if ($auth->type() === 'session' && $auth->csrf() === false) { + throw new InvalidArgumentException('Invalid CSRF token'); + } + + if ($system->isOk() === false) { + throw new Exception('The server is not setup correctly'); + } + + if ($system->isInstallable() === false) { + throw new Exception('The Panel cannot be installed'); + } + + if ($system->isInstalled() === true) { + throw new Exception('The Panel is already installed'); + } + + // create the first user + $user = $this->users()->create($this->requestBody()); + $token = $user->login($this->requestBody('password')); + + return [ + 'status' => 'ok', + 'token' => $token, + 'user' => $this->resolve($user)->view('auth')->toArray() + ]; + } + ] + +]; diff --git a/kirby/config/api/routes/translations.php b/kirby/config/api/routes/translations.php new file mode 100644 index 0000000..db7faca --- /dev/null +++ b/kirby/config/api/routes/translations.php @@ -0,0 +1,24 @@ + 'translations', + 'method' => 'GET', + 'auth' => false, + 'action' => function () { + return $this->kirby()->translations(); + } + ], + [ + 'pattern' => 'translations/(:any)', + 'method' => 'GET', + 'auth' => false, + 'action' => function (string $code) { + return $this->kirby()->translations()->find($code); + } + ] + +]; diff --git a/kirby/config/api/routes/users.php b/kirby/config/api/routes/users.php new file mode 100644 index 0000000..88068a4 --- /dev/null +++ b/kirby/config/api/routes/users.php @@ -0,0 +1,160 @@ + 'users', + 'method' => 'GET', + 'action' => function () { + return $this->users()->sort('username', 'asc', 'email', 'asc'); + } + ], + [ + 'pattern' => 'users', + 'method' => 'POST', + 'action' => function () { + return $this->users()->create($this->requestBody()); + } + ], + [ + 'pattern' => 'users/search', + 'method' => 'GET|POST', + 'action' => function () { + if ($this->requestMethod() === 'GET') { + return $this->users()->search($this->requestQuery('q')); + } else { + return $this->users()->query($this->requestBody()); + } + } + ], + [ + 'pattern' => 'users/(:any)', + 'method' => 'GET', + 'action' => function (string $id) { + return $this->user($id); + } + ], + [ + 'pattern' => 'users/(:any)', + 'method' => 'PATCH', + 'action' => function (string $id) { + return $this->user($id)->update($this->requestBody(), $this->language(), true); + } + ], + [ + 'pattern' => 'users/(:any)', + 'method' => 'DELETE', + 'action' => function (string $id) { + return $this->user($id)->delete(); + } + ], + [ + 'pattern' => 'users/(:any)/avatar', + 'method' => 'GET', + 'action' => function (string $id) { + return $this->user($id)->avatar(); + } + ], + [ + 'pattern' => 'users/(:any)/avatar', + 'method' => 'POST', + 'action' => function (string $id) { + if ($avatar = $this->user($id)->avatar()) { + $avatar->delete(); + } + + return $this->upload(function ($source, $filename) use ($id) { + return $this->user($id)->createFile([ + 'filename' => 'profile.' . F::extension($filename), + 'template' => 'avatar', + 'source' => $source + ]); + }, $single = true); + } + ], + [ + 'pattern' => 'users/(:any)/avatar', + 'method' => 'DELETE', + 'action' => function (string $id) { + return $this->user($id)->avatar()->delete(); + } + ], + [ + 'pattern' => 'users/(:any)/blueprint', + 'method' => 'GET', + 'action' => function (string $id) { + return $this->user($id)->blueprint(); + } + ], + [ + 'pattern' => 'users/(:any)/blueprints', + 'method' => 'GET', + 'action' => function (string $id) { + return $this->user($id)->blueprints($this->requestQuery('section')); + } + ], + [ + 'pattern' => 'users/(:any)/email', + 'method' => 'PATCH', + 'action' => function (string $id) { + return $this->user($id)->changeEmail($this->requestBody('email')); + } + ], + [ + 'pattern' => 'users/(:any)/fields/(:any)/(:all?)', + 'method' => 'ALL', + 'action' => function (string $id, string $fieldName, string $path = null) { + if ($user = $this->user($id)) { + return $this->fieldApi($user, $fieldName, $path); + } + } + ], + [ + 'pattern' => 'users/(:any)/language', + 'method' => 'PATCH', + 'action' => function (string $id) { + return $this->user($id)->changeLanguage($this->requestBody('language')); + } + ], + [ + 'pattern' => 'users/(:any)/name', + 'method' => 'PATCH', + 'action' => function (string $id) { + return $this->user($id)->changeName($this->requestBody('name')); + } + ], + [ + 'pattern' => 'users/(:any)/password', + 'method' => 'PATCH', + 'action' => function (string $id) { + return $this->user($id)->changePassword($this->requestBody('password')); + } + ], + [ + 'pattern' => 'users/(:any)/role', + 'method' => 'PATCH', + 'action' => function (string $id) { + return $this->user($id)->changeRole($this->requestBody('role')); + } + ], + [ + 'pattern' => 'users/(:any)/roles', + 'action' => function (string $id) { + return $this->user($id)->roles(); + } + ], + [ + 'pattern' => 'users/(:any)/sections/(:any)', + 'method' => 'GET', + 'action' => function (string $id, string $sectionName) { + if ($section = $this->user($id)->blueprint()->section($sectionName)) { + return $section->toResponse(); + } + } + ], +]; diff --git a/kirby/config/blocks/code/code.php b/kirby/config/blocks/code/code.php new file mode 100644 index 0000000..a7f88ff --- /dev/null +++ b/kirby/config/blocks/code/code.php @@ -0,0 +1,2 @@ + +
code()->html(false) ?>
diff --git a/kirby/config/blocks/code/code.yml b/kirby/config/blocks/code/code.yml new file mode 100644 index 0000000..b697784 --- /dev/null +++ b/kirby/config/blocks/code/code.yml @@ -0,0 +1,59 @@ +name: field.blocks.code.name +icon: code +wysiwyg: true +preview: code +fields: + code: + label: field.blocks.code.name + type: textarea + placeholder: field.blocks.code.placeholder + buttons: false + font: monospace + language: + label: field.blocks.code.language + type: select + default: text + options: + bash: Bash + basic: BASIC + c: C + clojure: Clojure + cpp: C++ + csharp: C# + css: CSS + diff: Diff + elixir: Elixir + elm: Elm + erlang: Erlang + go: Go + graphql: GraphQL + haskell: Haskell + html: HTML + java: Java + js: JavaScript + json: JSON + latext: LaTeX + less: Less + lisp: Lisp + lua: Lua + makefile: Makefile + markdown: Markdown + markup: Markup + objectivec: Objective-C + pascal: Pascal + perl: Perl + php: PHP + text: Plain Text + python: Python + r: R + ruby: Ruby + rust: Rust + sass: Sass + scss: SCSS + shell: Shell + sql: SQL + swift: Swift + typescript: TypeScript + vbnet: VB.net + xml: XML + yaml: YAML diff --git a/kirby/config/blocks/gallery/gallery.php b/kirby/config/blocks/gallery/gallery.php new file mode 100644 index 0000000..77ab465 --- /dev/null +++ b/kirby/config/blocks/gallery/gallery.php @@ -0,0 +1,10 @@ + +
+ +
diff --git a/kirby/config/blocks/gallery/gallery.yml b/kirby/config/blocks/gallery/gallery.yml new file mode 100644 index 0000000..a6844f2 --- /dev/null +++ b/kirby/config/blocks/gallery/gallery.yml @@ -0,0 +1,15 @@ +name: field.blocks.gallery.name +icon: dashboard +preview: gallery +fields: + images: + label: field.blocks.gallery.images.label + type: files + multiple: true + layout: cards + size: tiny + empty: field.blocks.gallery.images.empty + uploads: + template: blocks/image + image: + ratio: 1/1 diff --git a/kirby/config/blocks/heading/heading.php b/kirby/config/blocks/heading/heading.php new file mode 100644 index 0000000..e864bbf --- /dev/null +++ b/kirby/config/blocks/heading/heading.php @@ -0,0 +1,2 @@ + +<level()->or('h2') ?>>text() ?>> diff --git a/kirby/config/blocks/heading/heading.yml b/kirby/config/blocks/heading/heading.yml new file mode 100644 index 0000000..d7ee4f0 --- /dev/null +++ b/kirby/config/blocks/heading/heading.yml @@ -0,0 +1,24 @@ +name: field.blocks.heading.name +icon: title +wysiwyg: true +preview: heading +fields: + level: + label: field.blocks.heading.level + type: select + empty: false + default: "h2" + width: 1/6 + options: + - h1 + - h2 + - h3 + - h4 + - h5 + - h6 + text: + label: field.blocks.heading.text + type: writer + inline: true + width: 5/6 + placeholder: field.blocks.heading.placeholder diff --git a/kirby/config/blocks/image/image.php b/kirby/config/blocks/image/image.php new file mode 100644 index 0000000..9d250b0 --- /dev/null +++ b/kirby/config/blocks/image/image.php @@ -0,0 +1,35 @@ +alt(); +$caption = $block->caption(); +$crop = $block->crop()->isTrue(); +$link = $block->link(); +$ratio = $block->ratio()->or('auto'); +$src = null; + +if ($block->location() == 'web') { + $src = $block->src(); +} elseif ($image = $block->image()->toFile()) { + $alt = $alt ?? $image->alt(); + $src = $image->url(); +} + +?> + + $ratio, 'data-crop' => $crop], ' ') ?>> + isNotEmpty()): ?> + + <?= $alt ?> + + + <?= $alt ?> + + + isNotEmpty()): ?> +
+ +
+ + + diff --git a/kirby/config/blocks/image/image.yml b/kirby/config/blocks/image/image.yml new file mode 100644 index 0000000..feff6b0 --- /dev/null +++ b/kirby/config/blocks/image/image.yml @@ -0,0 +1,59 @@ +name: field.blocks.image.name +icon: image +preview: image +fields: + location: + label: field.blocks.image.location + type: radio + columns: 2 + default: "kirby" + options: + kirby: Kirby + web: Web + image: + label: field.blocks.image.name + type: files + multiple: false + image: + back: black + uploads: + template: blocks/image + when: + location: kirby + src: + label: field.blocks.image.url + type: url + when: + location: web + alt: + label: field.blocks.image.alt + type: text + icon: title + caption: + label: field.blocks.image.caption + type: writer + icon: text + inline: true + link: + label: field.blocks.image.link + type: text + icon: url + ratio: + label: field.blocks.image.ratio + type: select + placeholder: Auto + width: 1/2 + options: + 1/1: "1:1" + 16/9: "16:9" + 10/8: "10:8" + 21/9: "21:9" + 7/5: "7:5" + 4/3: "4:3" + 5/3: "5:3" + 3/2: "3:2" + 3/1: "3:1" + crop: + label: field.blocks.image.crop + type: toggle + width: 1/2 diff --git a/kirby/config/blocks/list/list.php b/kirby/config/blocks/list/list.php new file mode 100644 index 0000000..012a156 --- /dev/null +++ b/kirby/config/blocks/list/list.php @@ -0,0 +1,2 @@ + +text(); diff --git a/kirby/config/blocks/list/list.yml b/kirby/config/blocks/list/list.yml new file mode 100644 index 0000000..ded7519 --- /dev/null +++ b/kirby/config/blocks/list/list.yml @@ -0,0 +1,8 @@ +name: field.blocks.list.name +icon: list-bullet +wysiwyg: true +preview: list +fields: + text: + label: field.blocks.list.name + type: list diff --git a/kirby/config/blocks/markdown/markdown.php b/kirby/config/blocks/markdown/markdown.php new file mode 100644 index 0000000..7ab685c --- /dev/null +++ b/kirby/config/blocks/markdown/markdown.php @@ -0,0 +1,2 @@ + +text()->kt(); diff --git a/kirby/config/blocks/markdown/markdown.yml b/kirby/config/blocks/markdown/markdown.yml new file mode 100644 index 0000000..cecafe4 --- /dev/null +++ b/kirby/config/blocks/markdown/markdown.yml @@ -0,0 +1,11 @@ +name: field.blocks.markdown.name +icon: markdown +preview: markdown +wysiwyg: true +fields: + text: + label: field.blocks.markdown.label + placeholder: field.blocks.markdown.placeholder + type: textarea + buttons: false + font: monospace diff --git a/kirby/config/blocks/quote/quote.php b/kirby/config/blocks/quote/quote.php new file mode 100644 index 0000000..6ec1290 --- /dev/null +++ b/kirby/config/blocks/quote/quote.php @@ -0,0 +1,9 @@ + +
+ text() ?> + citation()->isNotEmpty()): ?> +
+ citation() ?> +
+ +
diff --git a/kirby/config/blocks/quote/quote.yml b/kirby/config/blocks/quote/quote.yml new file mode 100644 index 0000000..b14e126 --- /dev/null +++ b/kirby/config/blocks/quote/quote.yml @@ -0,0 +1,17 @@ +name: field.blocks.quote.name +icon: quote +wysiwyg: true +preview: quote +fields: + text: + label: field.blocks.quote.text.label + placeholder: field.blocks.quote.text.placeholder + type: writer + inline: true + icon: quote + citation: + label: field.blocks.quote.citation.label + placeholder: field.blocks.quote.citation.placeholder + type: writer + inline: true + icon: user diff --git a/kirby/config/blocks/table/table.yml b/kirby/config/blocks/table/table.yml new file mode 100644 index 0000000..8e0d0b2 --- /dev/null +++ b/kirby/config/blocks/table/table.yml @@ -0,0 +1,3 @@ +name: Table +icon: menu +preview: table diff --git a/kirby/config/blocks/text/text.php b/kirby/config/blocks/text/text.php new file mode 100644 index 0000000..012a156 --- /dev/null +++ b/kirby/config/blocks/text/text.php @@ -0,0 +1,2 @@ + +text(); diff --git a/kirby/config/blocks/text/text.yml b/kirby/config/blocks/text/text.yml new file mode 100644 index 0000000..90117a5 --- /dev/null +++ b/kirby/config/blocks/text/text.yml @@ -0,0 +1,9 @@ +name: field.blocks.text.name +icon: text +wysiwyg: true +preview: text +fields: + text: + type: writer + nodes: false + placeholder: field.blocks.text.placeholder diff --git a/kirby/config/blocks/video/video.php b/kirby/config/blocks/video/video.php new file mode 100644 index 0000000..f28438f --- /dev/null +++ b/kirby/config/blocks/video/video.php @@ -0,0 +1,9 @@ + +url()->isNotEmpty()): ?> +
+ url()) ?> + caption()->isNotEmpty()): ?> +
caption() ?>
+ +
+ diff --git a/kirby/config/blocks/video/video.yml b/kirby/config/blocks/video/video.yml new file mode 100644 index 0000000..6b5223a --- /dev/null +++ b/kirby/config/blocks/video/video.yml @@ -0,0 +1,12 @@ +name: field.blocks.video.name +icon: video +preview: video +fields: + url: + label: field.blocks.video.url.label + type: url + placeholder: field.blocks.video.url.placeholder + caption: + label: field.blocks.video.caption + type: writer + inline: true diff --git a/kirby/config/blueprints.php b/kirby/config/blueprints.php new file mode 100644 index 0000000..95ffade --- /dev/null +++ b/kirby/config/blueprints.php @@ -0,0 +1,26 @@ + $blocksRoot . '/code/code.yml', + 'blocks/gallery' => $blocksRoot . '/gallery/gallery.yml', + 'blocks/heading' => $blocksRoot . '/heading/heading.yml', + 'blocks/image' => $blocksRoot . '/image/image.yml', + 'blocks/list' => $blocksRoot . '/list/list.yml', + 'blocks/markdown' => $blocksRoot . '/markdown/markdown.yml', + 'blocks/quote' => $blocksRoot . '/quote/quote.yml', + 'blocks/table' => $blocksRoot . '/table/table.yml', + 'blocks/text' => $blocksRoot . '/text/text.yml', + 'blocks/video' => $blocksRoot . '/video/video.yml', + + // file blueprints + 'files/default' => __DIR__ . '/blueprints/files/default.yml', + + // page blueprints + 'pages/default' => __DIR__ . '/blueprints/pages/default.yml', + + // site blueprints + 'site' => __DIR__ . '/blueprints/site.yml' +]; diff --git a/kirby/config/blueprints/blocks/code.yml b/kirby/config/blueprints/blocks/code.yml new file mode 100644 index 0000000..a103422 --- /dev/null +++ b/kirby/config/blueprints/blocks/code.yml @@ -0,0 +1,56 @@ +name: Code +icon: code +fields: + code: + label: Code + type: textarea + buttons: false + font: monospace + language: + label: Language + type: select + default: text + options: + bash: Bash + basic: BASIC + c: C + clojure: Clojure + cpp: C++ + csharp: C# + css: CSS + diff: Diff + elixir: Elixir + elm: Elm + erlang: Erlang + go: Go + graphql: GraphQL + haskell: Haskell + html: HTML + java: Java + js: JavaScript + json: JSON + latext: LaTeX + less: Less + lisp: Lisp + lua: Lua + makefile: Makefile + markdown: Markdown + markup: Markup + objectivec: Objective-C + pascal: Pascal + perl: Perl + php: PHP + text: Plain Text + python: Python + r: R + ruby: Ruby + rust: Rust + sass: Sass + scss: SCSS + shell: Shell + sql: SQL + swift: Swift + typescript: TypeScript + vbnet: VB.net + xml: XML + yaml: YAML diff --git a/kirby/config/blueprints/blocks/heading.yml b/kirby/config/blueprints/blocks/heading.yml new file mode 100644 index 0000000..e381b5c --- /dev/null +++ b/kirby/config/blueprints/blocks/heading.yml @@ -0,0 +1,21 @@ +icon: title +fields: + text: + type: text + level: + type: select + width: 1/2 + default: 1 + empty: false + default: "2" + options: + - value: "1" + text: Heading 1 + - value: "2" + text: Heading 2 + - value: "3" + text: Heading 3 + id: + type: text + label: ID + width: 1/2 diff --git a/kirby/config/blueprints/blocks/image.yml b/kirby/config/blueprints/blocks/image.yml new file mode 100644 index 0000000..31b1ef1 --- /dev/null +++ b/kirby/config/blueprints/blocks/image.yml @@ -0,0 +1,16 @@ +name: Image +icon: image +fields: + image: + type: files + multiple: false + alt: + type: text + icon: title + caption: + type: writer + inline: true + icon: text + link: + type: text + icon: url diff --git a/kirby/config/blueprints/blocks/quote.yml b/kirby/config/blueprints/blocks/quote.yml new file mode 100644 index 0000000..78bf681 --- /dev/null +++ b/kirby/config/blueprints/blocks/quote.yml @@ -0,0 +1,12 @@ +name: Quote +icon: quote +fields: + text: + label: Quote Text + type: writer + inline: true + citation: + label: Citation + type: writer + inline: true + placeholder: by … diff --git a/kirby/config/blueprints/blocks/table.yml b/kirby/config/blueprints/blocks/table.yml new file mode 100644 index 0000000..a8513ba --- /dev/null +++ b/kirby/config/blueprints/blocks/table.yml @@ -0,0 +1,25 @@ +name: Table +icon: menu +fields: + rows: + label: Menu + type: structure + columns: + dish: true + description: true + price: + before: € + width: 1/4 + align: right + fields: + dish: + label: Dish + type: text + description: + label: Description + type: text + price: + label: Price + type: number + before: € + step: 0.01 diff --git a/kirby/config/blueprints/blocks/text.yml b/kirby/config/blueprints/blocks/text.yml new file mode 100644 index 0000000..c251959 --- /dev/null +++ b/kirby/config/blueprints/blocks/text.yml @@ -0,0 +1,5 @@ +name: Text +icon: text +fields: + text: + type: writer diff --git a/kirby/config/blueprints/blocks/video.yml b/kirby/config/blueprints/blocks/video.yml new file mode 100644 index 0000000..9131ace --- /dev/null +++ b/kirby/config/blueprints/blocks/video.yml @@ -0,0 +1,8 @@ +name: Video +icon: video +label: "{{ url }}" +fields: + url: + type: url + caption: + type: writer diff --git a/kirby/config/blueprints/files/default.yml b/kirby/config/blueprints/files/default.yml new file mode 100644 index 0000000..d5ef1df --- /dev/null +++ b/kirby/config/blueprints/files/default.yml @@ -0,0 +1,2 @@ +name: File +title: file diff --git a/kirby/config/blueprints/pages/default.yml b/kirby/config/blueprints/pages/default.yml new file mode 100644 index 0000000..ceb895a --- /dev/null +++ b/kirby/config/blueprints/pages/default.yml @@ -0,0 +1,3 @@ +name: Page +title: Page + diff --git a/kirby/config/blueprints/site.yml b/kirby/config/blueprints/site.yml new file mode 100644 index 0000000..04718f3 --- /dev/null +++ b/kirby/config/blueprints/site.yml @@ -0,0 +1,7 @@ +name: Site +title: Site +sections: + pages: + headline: Pages + type: pages + diff --git a/kirby/config/components.php b/kirby/config/components.php new file mode 100644 index 0000000..0dd34ef --- /dev/null +++ b/kirby/config/components.php @@ -0,0 +1,378 @@ + function (App $kirby, string $url, $options = null): string { + return $url; + }, + + + /** + * Object and variable dumper + * to help with debugging. + * + * @param \Kirby\Cms\App $kirby Kirby instance + * @param mixed $variable + * @param bool $echo + * @return string + */ + 'dump' => function (App $kirby, $variable, bool $echo = true) { + if (Server::cli() === true) { + $output = print_r($variable, true) . PHP_EOL; + } else { + $output = '
' . print_r($variable, true) . '
'; + } + + if ($echo === true) { + echo $output; + } + + return $output; + }, + + /** + * Modify URLs for file objects + * + * @param \Kirby\Cms\App $kirby Kirby instance + * @param \Kirby\Cms\File $file The original file object + * @return string + */ + 'file::url' => function (App $kirby, File $file): string { + return $file->mediaUrl(); + }, + + /** + * Adapt file characteristics + * + * @param \Kirby\Cms\App $kirby Kirby instance + * @param \Kirby\Cms\File|\Kirby\Cms\FileModifications $file The file object + * @param array $options All thumb options (width, height, crop, blur, grayscale) + * @return \Kirby\Cms\File|\Kirby\Cms\FileVersion + */ + 'file::version' => function (App $kirby, $file, array $options = []) { + if ($file->isResizable() === false) { + return $file; + } + + // create url and root + $mediaRoot = dirname($file->mediaRoot()); + $template = $mediaRoot . '/{{ name }}{{ attributes }}.{{ extension }}'; + $thumbRoot = (new Filename($file->root(), $template, $options))->toString(); + $thumbName = basename($thumbRoot); + $job = $mediaRoot . '/.jobs/' . $thumbName . '.json'; + + if (file_exists($thumbRoot) === false) { + try { + Data::write($job, array_merge($options, [ + 'filename' => $file->filename() + ])); + } catch (Throwable $e) { + return $file; + } + } + + return new FileVersion([ + 'modifications' => $options, + 'original' => $file, + 'root' => $thumbRoot, + 'url' => dirname($file->mediaUrl()) . '/' . $thumbName, + ]); + }, + + /** + * Used by the `js()` helper + * + * @param \Kirby\Cms\App $kirby Kirby instance + * @param string $url Relative or absolute URL + * @param string|array $options An array of attributes for the link tag or a media attribute string + */ + 'js' => function (App $kirby, string $url, $options = null): string { + return $url; + }, + + /** + * Add your own Markdown parser + * + * @param \Kirby\Cms\App $kirby Kirby instance + * @param string $text Text to parse + * @param array $options Markdown options + * @param bool $inline Whether to wrap the text in `

` tags + * @return string + */ + 'markdown' => function (App $kirby, string $text = null, array $options = [], bool $inline = false): string { + static $markdown; + static $config; + + // if the config options have changed or the component is called for the first time, + // (re-)initialize the parser object + if ($config !== $options) { + $markdown = new Markdown($options); + $config = $options; + } + + return $markdown->parse($text, $inline); + }, + + /** + * Add your own search engine + * + * @param \Kirby\Cms\App $kirby Kirby instance + * @param \Kirby\Cms\Collection $collection Collection of searchable models + * @param string $query + * @param mixed $params + * @return \Kirby\Cms\Collection|bool + */ + 'search' => function (App $kirby, Collection $collection, string $query = null, $params = []) { + if (empty(trim($query)) === true) { + return $collection->limit(0); + } + + if (is_string($params) === true) { + $params = ['fields' => Str::split($params, '|')]; + } + + $defaults = [ + 'fields' => [], + 'minlength' => 2, + 'score' => [], + 'words' => false, + ]; + + $options = array_merge($defaults, $params); + $collection = clone $collection; + $searchWords = preg_replace('/(\s)/u', ',', $query); + $searchWords = Str::split($searchWords, ',', $options['minlength']); + $lowerQuery = Str::lower($query); + $exactQuery = $options['words'] ? '(\b' . preg_quote($query) . '\b)' : preg_quote($query); + + if (empty($options['stopwords']) === false) { + $searchWords = array_diff($searchWords, $options['stopwords']); + } + + $searchWords = array_map(function ($value) use ($options) { + return $options['words'] ? '\b' . preg_quote($value) . '\b' : preg_quote($value); + }, $searchWords); + + $preg = '!(' . implode('|', $searchWords) . ')!i'; + $results = $collection->filter(function ($item) use ($query, $preg, $options, $lowerQuery, $exactQuery) { + $data = $item->content()->toArray(); + $keys = array_keys($data); + $keys[] = 'id'; + + if (is_a($item, 'Kirby\Cms\User') === true) { + $keys[] = 'name'; + $keys[] = 'email'; + $keys[] = 'role'; + } elseif (is_a($item, 'Kirby\Cms\Page') === true) { + // apply the default score for pages + $options['score'] = array_merge([ + 'id' => 64, + 'title' => 64, + ], $options['score']); + } + + if (empty($options['fields']) === false) { + $fields = array_map('strtolower', $options['fields']); + $keys = array_intersect($keys, $fields); + } + + $item->searchHits = 0; + $item->searchScore = 0; + + foreach ($keys as $key) { + $score = $options['score'][$key] ?? 1; + $value = $data[$key] ?? (string)$item->$key(); + + $lowerValue = Str::lower($value); + + // check for exact matches + if ($lowerQuery == $lowerValue) { + $item->searchScore += 16 * $score; + $item->searchHits += 1; + + // check for exact beginning matches + } elseif ($options['words'] === false && Str::startsWith($lowerValue, $lowerQuery) === true) { + $item->searchScore += 8 * $score; + $item->searchHits += 1; + + // check for exact query matches + } elseif ($matches = preg_match_all('!' . $exactQuery . '!i', $value, $r)) { + $item->searchScore += 2 * $score; + $item->searchHits += $matches; + } + + // check for any match + if ($matches = preg_match_all($preg, $value, $r)) { + $item->searchHits += $matches; + $item->searchScore += $matches * $score; + } + } + + return $item->searchHits > 0 ? true : false; + }); + + return $results->sort('searchScore', 'desc'); + }, + + /** + * Add your own SmartyPants parser + * + * @param \Kirby\Cms\App $kirby Kirby instance + * @param string $text Text to parse + * @param array $options SmartyPants options + * @return string + */ + 'smartypants' => function (App $kirby, string $text = null, array $options = []): string { + static $smartypants; + static $config; + + // if the config options have changed or the component is called for the first time, + // (re-)initialize the parser object + if ($config !== $options) { + $smartypants = new Smartypants($options); + $config = $options; + } + + return $smartypants->parse($text); + }, + + /** + * Add your own snippet loader + * + * @param \Kirby\Cms\App $kirby Kirby instance + * @param string|array $name Snippet name + * @param array $data Data array for the snippet + * @return string|null + */ + 'snippet' => function (App $kirby, $name, array $data = []): ?string { + $snippets = A::wrap($name); + + foreach ($snippets as $name) { + $name = (string)$name; + $file = $kirby->root('snippets') . '/' . $name . '.php'; + + if (file_exists($file) === false) { + $file = $kirby->extensions('snippets')[$name] ?? null; + } + + if ($file) { + break; + } + } + + return Snippet::load($file, $data); + }, + + /** + * Add your own template engine + * + * @param \Kirby\Cms\App $kirby Kirby instance + * @param string $name Template name + * @param string $type Extension type + * @param string $defaultType Default extension type + * @return \Kirby\Cms\Template + */ + 'template' => function (App $kirby, string $name, string $type = 'html', string $defaultType = 'html') { + return new Template($name, $type, $defaultType); + }, + + /** + * Add your own thumb generator + * + * @param \Kirby\Cms\App $kirby Kirby instance + * @param string $src The root of the original file + * @param string $template The template for the root to the desired destination + * @param array $options All thumb options that should be applied: `width`, `height`, `crop`, `blur`, `grayscale` + * @return string + */ + 'thumb' => function (App $kirby, string $src, string $template, array $options): string { + $darkroom = Darkroom::factory(option('thumbs.driver', 'gd'), option('thumbs', [])); + $options = $darkroom->preprocess($src, $options); + $root = (new Filename($src, $template, $options))->toString(); + + F::copy($src, $root, true); + $darkroom->process($root, $options); + + return $root; + }, + + /** + * Modify all URLs + * + * @param \Kirby\Cms\App $kirby Kirby instance + * @param string $path URL path + * @param array|string|null $options Array of options for the Uri class + * @param Closure $originalHandler Deprecated: Callback function to the original URL handler with `$path` and `$options` as parameters + * Use `$kirby->nativeComponent('url')` inside your URL component instead. + * @return string + * + * @todo Remove $originalHandler parameter in 3.6.0 + */ + 'url' => function (App $kirby, string $path = null, $options = null, Closure $originalHandler = null): string { + $language = null; + + // get language from simple string option + if (is_string($options) === true) { + $language = $options; + $options = null; + } + + // get language from array + if (is_array($options) === true && isset($options['language']) === true) { + $language = $options['language']; + unset($options['language']); + } + + // get a language url for the linked page, if the page can be found + if ($kirby->multilang() === true) { + $parts = Str::split($path, '#'); + + if ($page = page($parts[0] ?? null)) { + $path = $page->url($language); + + if (isset($parts[1]) === true) { + $path .= '#' . $parts[1]; + } + } + } + + // keep relative urls + if (substr($path, 0, 2) === './' || substr($path, 0, 3) === '../') { + return $path; + } + + $url = Url::makeAbsolute($path, $kirby->url()); + + if ($options === null) { + return $url; + } + + return (new Uri($url, $options))->toString(); + }, + +]; diff --git a/kirby/config/fields.php b/kirby/config/fields.php new file mode 100644 index 0000000..3c878b6 --- /dev/null +++ b/kirby/config/fields.php @@ -0,0 +1,32 @@ + 'Kirby\Form\Field\BlocksField', + 'checkboxes' => __DIR__ . '/fields/checkboxes.php', + 'date' => __DIR__ . '/fields/date.php', + 'email' => __DIR__ . '/fields/email.php', + 'files' => __DIR__ . '/fields/files.php', + 'gap' => __DIR__ . '/fields/gap.php', + 'headline' => __DIR__ . '/fields/headline.php', + 'hidden' => __DIR__ . '/fields/hidden.php', + 'info' => __DIR__ . '/fields/info.php', + 'layout' => 'Kirby\Form\Field\LayoutField', + 'line' => __DIR__ . '/fields/line.php', + 'list' => __DIR__ . '/fields/list.php', + 'multiselect' => __DIR__ . '/fields/multiselect.php', + 'number' => __DIR__ . '/fields/number.php', + 'pages' => __DIR__ . '/fields/pages.php', + 'radio' => __DIR__ . '/fields/radio.php', + 'range' => __DIR__ . '/fields/range.php', + 'select' => __DIR__ . '/fields/select.php', + 'structure' => __DIR__ . '/fields/structure.php', + 'tags' => __DIR__ . '/fields/tags.php', + 'tel' => __DIR__ . '/fields/tel.php', + 'text' => __DIR__ . '/fields/text.php', + 'textarea' => __DIR__ . '/fields/textarea.php', + 'time' => __DIR__ . '/fields/time.php', + 'toggle' => __DIR__ . '/fields/toggle.php', + 'url' => __DIR__ . '/fields/url.php', + 'users' => __DIR__ . '/fields/users.php', + 'writer' => __DIR__ . '/fields/writer.php' +]; diff --git a/kirby/config/fields/checkboxes.php b/kirby/config/fields/checkboxes.php new file mode 100644 index 0000000..6837b45 --- /dev/null +++ b/kirby/config/fields/checkboxes.php @@ -0,0 +1,61 @@ + ['min', 'options'], + 'props' => [ + /** + * Unset inherited props + */ + 'after' => null, + 'before' => null, + 'icon' => null, + 'placeholder' => null, + + /** + * Arranges the checkboxes in the given number of columns + */ + 'columns' => function (int $columns = 1) { + return $columns; + }, + /** + * Default value for the field, which will be used when a page/file/user is created + */ + 'default' => function ($default = null) { + return Str::split($default, ','); + }, + /** + * Maximum number of checked boxes + */ + 'max' => function (int $max = null) { + return $max; + }, + /** + * Minimum number of checked boxes + */ + 'min' => function (int $min = null) { + return $min; + }, + 'value' => function ($value = null) { + return Str::split($value, ','); + }, + ], + 'computed' => [ + 'default' => function () { + return $this->sanitizeOptions($this->default); + }, + 'value' => function () { + return $this->sanitizeOptions($this->value); + }, + ], + 'save' => function ($value): string { + return A::join($value, ', '); + }, + 'validations' => [ + 'options', + 'max', + 'min' + ] +]; diff --git a/kirby/config/fields/date.php b/kirby/config/fields/date.php new file mode 100644 index 0000000..48bc2b4 --- /dev/null +++ b/kirby/config/fields/date.php @@ -0,0 +1,179 @@ + ['datetime'], + 'props' => [ + /** + * Unset inherited props + */ + 'placeholder' => null, + + /** + * Activate/deactivate the dropdown calendar + */ + 'calendar' => function (bool $calendar = true) { + return $calendar; + }, + + + /** + * Default date when a new page/file/user gets created + */ + 'default' => function (string $default = null) { + return $default; + }, + + /** + * Custom format (dayjs tokens: `DD`, `MM`, `YYYY`) that is + * used to display the field in the Panel + */ + 'display' => function ($display = 'YYYY-MM-DD') { + return I18n::translate($display, $display); + }, + + /** + * Changes the calendar icon to something custom + */ + 'icon' => function (string $icon = 'calendar') { + return $icon; + }, + + /** + * Latest date, which can be selected/saved (Y-m-d) + */ + 'max' => function (string $max = null) { + return $this->toDatetime($max); + }, + /** + * Earliest date, which can be selected/saved (Y-m-d) + */ + 'min' => function (string $min = null) { + return $this->toDatetime($min); + }, + + /** + * Round to the nearest: sub-options for `unit` (day) and `size` (1) + */ + 'step' => function ($step = null) { + $default = [ + 'size' => 1, + 'unit' => 'day' + ]; + + if ($step === null) { + return $default; + } + + if (is_array($step) === true) { + $step = array_merge($default, $step); + $step['unit'] = strtolower($step['unit']); + return $step; + } + + if (is_int($step) === true) { + return array_merge($default, ['size' => $step]); + } + + if (is_string($step) === true) { + return array_merge($default, ['unit' => strtolower($step)]); + } + }, + + /** + * Pass `true` or an array of time field options to show the time selector. + */ + 'time' => function ($time = false) { + return $time; + }, + /** + * Must be a parseable date string + */ + 'value' => function ($value = null) { + return $value; + }, + ], + 'computed' => [ + 'default' => function () { + return $this->toDatetime($this->default); + }, + 'display' => function () { + if ($this->display) { + return Str::upper($this->display); + } + }, + 'format' => function () { + return $this->props['format'] ?? ($this->time === false ? 'Y-m-d' : 'Y-m-d H:i:s'); + }, + 'time' => function () { + if ($this->time === false) { + return false; + } + + $props = is_array($this->time) ? $this->time : []; + $props['model'] = $this->model(); + $field = new Field('time', $props); + return $field->toArray(); + }, + 'step' => function () { + if ($this->time === false) { + return $this->step; + } + + return $this->time['step']; + }, + 'value' => function () { + return $this->toDatetime($this->value); + }, + ], + 'validations' => [ + 'date', + 'minMax' => function ($value) { + $min = $this->min ? strtotime($this->min) : null; + $max = $this->max ? strtotime($this->max) : null; + $value = strtotime($this->value()); + $format = $this->time === false ? 'd.m.Y' : 'd.m.Y H:i'; + $errors = []; + + if ($value && $min && $value < $min) { + $errors['min'] = $min; + } + + if ($value && $max && $value > $max) { + $errors['max'] = $max; + } + + if (empty($errors) === false) { + if ($min && $max) { + throw new Exception([ + 'key' => 'validation.date.between', + 'data' => [ + 'min' => date($format, $min), + 'max' => date($format, $max) + ] + ]); + } elseif ($min) { + throw new Exception([ + 'key' => 'validation.date.after', + 'data' => [ + 'date' => date($format, $min), + ] + ]); + } else { + throw new Exception([ + 'key' => 'validation.date.before', + 'data' => [ + 'date' => date($format, $max), + ] + ]); + } + } + + return true; + }, + ] +]; diff --git a/kirby/config/fields/email.php b/kirby/config/fields/email.php new file mode 100644 index 0000000..e7892b8 --- /dev/null +++ b/kirby/config/fields/email.php @@ -0,0 +1,40 @@ + 'text', + 'props' => [ + /** + * Unset inherited props + */ + 'converter' => null, + 'counter' => null, + + /** + * Sets the HTML5 autocomplete mode for the input + */ + 'autocomplete' => function (string $autocomplete = 'email') { + return $autocomplete; + }, + + /** + * Changes the email icon to something custom + */ + 'icon' => function (string $icon = 'email') { + return $icon; + }, + + /** + * Custom placeholder text, when the field is empty. + */ + 'placeholder' => function ($value = null) { + return I18n::translate($value, $value) ?? I18n::translate('email.placeholder'); + } + ], + 'validations' => [ + 'minlength', + 'maxlength', + 'email' + ] +]; diff --git a/kirby/config/fields/files.php b/kirby/config/fields/files.php new file mode 100644 index 0000000..e4cb1f4 --- /dev/null +++ b/kirby/config/fields/files.php @@ -0,0 +1,138 @@ + [ + 'picker', + 'filepicker', + 'min', + 'upload' + ], + 'props' => [ + /** + * Unset inherited props + */ + 'after' => null, + 'before' => null, + 'autofocus' => null, + 'icon' => null, + 'placeholder' => null, + + /** + * Sets the file(s), which are selected by default when a new page is created + */ + 'default' => function ($default = null) { + return $default; + }, + + /** + * Changes the layout of the selected files. Available layouts: `list`, `cards` + */ + 'layout' => function (string $layout = 'list') { + return $layout; + }, + + /** + * Layout size for cards: `tiny`, `small`, `medium`, `large` or `huge` + */ + 'size' => function (string $size = 'auto') { + return $size; + }, + + 'value' => function ($value = null) { + return $value; + } + ], + 'computed' => [ + 'parentModel' => function () { + if (is_string($this->parent) === true && $model = $this->model()->query($this->parent, 'Kirby\Cms\Model')) { + return $model; + } + + return $this->model(); + }, + 'parent' => function () { + return $this->parentModel->apiUrl(true); + }, + 'query' => function () { + return $this->query ?? $this->parentModel::CLASS_ALIAS . '.files'; + }, + 'default' => function () { + return $this->toFiles($this->default); + }, + 'value' => function () { + return $this->toFiles($this->value); + }, + ], + 'methods' => [ + 'fileResponse' => function ($file) { + return $file->panelPickerData([ + 'image' => $this->image, + 'info' => $this->info ?? false, + 'model' => $this->model(), + 'text' => $this->text, + ]); + }, + 'toFiles' => function ($value = null) { + $files = []; + + foreach (Data::decode($value, 'yaml') as $id) { + if (is_array($id) === true) { + $id = $id['id'] ?? null; + } + + if ($id !== null && ($file = $this->kirby()->file($id, $this->model()))) { + $files[] = $this->fileResponse($file); + } + } + + return $files; + } + ], + 'api' => function () { + return [ + [ + 'pattern' => '/', + 'action' => function () { + $field = $this->field(); + + return $field->filepicker([ + 'image' => $field->image(), + 'info' => $field->info(), + 'limit' => $field->limit(), + 'page' => $this->requestQuery('page'), + 'query' => $field->query(), + 'search' => $this->requestQuery('search'), + 'text' => $field->text() + ]); + } + ], + [ + 'pattern' => 'upload', + 'method' => 'POST', + 'action' => function () { + $field = $this->field(); + $uploads = $field->uploads(); + + return $field->upload($this, $uploads, function ($file, $parent) use ($field) { + return $file->panelPickerData([ + 'image' => $field->image(), + 'info' => $field->info(), + 'model' => $field->model(), + 'text' => $field->text(), + ]); + }); + } + ] + ]; + }, + 'save' => function ($value = null) { + return A::pluck($value, 'uuid'); + }, + 'validations' => [ + 'max', + 'min' + ] +]; diff --git a/kirby/config/fields/gap.php b/kirby/config/fields/gap.php new file mode 100644 index 0000000..6844d6c --- /dev/null +++ b/kirby/config/fields/gap.php @@ -0,0 +1,5 @@ + false +]; diff --git a/kirby/config/fields/headline.php b/kirby/config/fields/headline.php new file mode 100644 index 0000000..c87dd53 --- /dev/null +++ b/kirby/config/fields/headline.php @@ -0,0 +1,26 @@ + false, + 'props' => [ + /** + * Unset inherited props + */ + 'after' => null, + 'autofocus' => null, + 'before' => null, + 'default' => null, + 'disabled' => null, + 'icon' => null, + 'placeholder' => null, + 'required' => null, + 'translate' => null, + + /** + * If `false`, the prepended number will be hidden + */ + 'numbered' => function (bool $numbered = true) { + return $numbered; + } + ] +]; diff --git a/kirby/config/fields/hidden.php b/kirby/config/fields/hidden.php new file mode 100644 index 0000000..0b67a5f --- /dev/null +++ b/kirby/config/fields/hidden.php @@ -0,0 +1,3 @@ + [ + /** + * Unset inherited props + */ + 'after' => null, + 'autofocus' => null, + 'before' => null, + 'default' => null, + 'disabled' => null, + 'icon' => null, + 'placeholder' => null, + 'required' => null, + 'translate' => null, + + /** + * Text to be displayed + */ + 'text' => function ($value = null) { + return I18n::translate($value, $value); + }, + + /** + * Change the design of the info box + */ + 'theme' => function (string $theme = null) { + return $theme; + } + ], + 'computed' => [ + 'text' => function () { + if ($text = $this->text) { + $text = $this->model()->toString($text); + $text = $this->kirby()->kirbytext($text); + return $text; + } + } + ], + 'save' => false, +]; diff --git a/kirby/config/fields/line.php b/kirby/config/fields/line.php new file mode 100644 index 0000000..6844d6c --- /dev/null +++ b/kirby/config/fields/line.php @@ -0,0 +1,5 @@ + false +]; diff --git a/kirby/config/fields/list.php b/kirby/config/fields/list.php new file mode 100644 index 0000000..b4b1ba7 --- /dev/null +++ b/kirby/config/fields/list.php @@ -0,0 +1,17 @@ + [ + /** + * Sets the allowed HTML formats. Available formats: `bold`, `italic`, `underline`, `strike`, `code`, `link`. Activate them all by passing `true`. Deactivate them all by passing `false` + */ + 'marks' => function ($marks = true) { + return $marks; + } + ], + 'computed' => [ + 'value' => function () { + return trim($this->value); + } + ] +]; diff --git a/kirby/config/fields/mixins/datetime.php b/kirby/config/fields/mixins/datetime.php new file mode 100644 index 0000000..4498e3f --- /dev/null +++ b/kirby/config/fields/mixins/datetime.php @@ -0,0 +1,28 @@ + [ + /** + * Defines a custom format that is used when the field is saved + */ + 'format' => function (string $format = null) { + return $format; + } + ], + 'methods' => [ + 'toDatetime' => function ($value, string $format = 'Y-m-d H:i:s') { + if ($timestamp = timestamp($value, $this->step)) { + return date($format, $timestamp); + } + + return null; + } + ], + 'save' => function ($value) { + if ($value !== null && $timestamp = strtotime($value)) { + return date($this->format, $timestamp); + } + + return ''; + }, +]; diff --git a/kirby/config/fields/mixins/filepicker.php b/kirby/config/fields/mixins/filepicker.php new file mode 100644 index 0000000..ba81230 --- /dev/null +++ b/kirby/config/fields/mixins/filepicker.php @@ -0,0 +1,14 @@ + [ + 'filepicker' => function (array $params = []) { + // fetch the parent model + $params['model'] = $this->model(); + + return (new FilePicker($params))->toArray(); + } + ] +]; diff --git a/kirby/config/fields/mixins/min.php b/kirby/config/fields/mixins/min.php new file mode 100644 index 0000000..33e24d4 --- /dev/null +++ b/kirby/config/fields/mixins/min.php @@ -0,0 +1,22 @@ + [ + 'min' => function () { + // set min to at least 1, if required + if ($this->required === true) { + return $this->min ?? 1; + } + + return $this->min; + }, + 'required' => function () { + // set required to true if min is set + if ($this->min) { + return true; + } + + return $this->required; + } + ] +]; diff --git a/kirby/config/fields/mixins/options.php b/kirby/config/fields/mixins/options.php new file mode 100644 index 0000000..170761a --- /dev/null +++ b/kirby/config/fields/mixins/options.php @@ -0,0 +1,48 @@ + [ + /** + * API settings for options requests. This will only take affect when `options` is set to `api`. + */ + 'api' => function ($api = null) { + return $api; + }, + /** + * An array with options + */ + 'options' => function ($options = []) { + return $options; + }, + /** + * Query settings for options queries. This will only take affect when `options` is set to `query`. + */ + 'query' => function ($query = null) { + return $query; + }, + ], + 'computed' => [ + 'options' => function (): array { + return $this->getOptions(); + } + ], + 'methods' => [ + 'getOptions' => function () { + return Options::factory( + $this->options(), + $this->props, + $this->model() + ); + }, + 'sanitizeOption' => function ($option) { + $allowed = array_column($this->options(), 'value'); + return in_array($option, $allowed, true) === true ? $option : null; + }, + 'sanitizeOptions' => function ($options) { + $allowed = array_column($this->options(), 'value'); + return array_intersect($options, $allowed); + }, + ] +]; diff --git a/kirby/config/fields/mixins/pagepicker.php b/kirby/config/fields/mixins/pagepicker.php new file mode 100644 index 0000000..bbdc86e --- /dev/null +++ b/kirby/config/fields/mixins/pagepicker.php @@ -0,0 +1,14 @@ + [ + 'pagepicker' => function (array $params = []) { + // inject the current model + $params['model'] = $this->model(); + + return (new PagePicker($params))->toArray(); + } + ] +]; diff --git a/kirby/config/fields/mixins/picker.php b/kirby/config/fields/mixins/picker.php new file mode 100644 index 0000000..c2660ad --- /dev/null +++ b/kirby/config/fields/mixins/picker.php @@ -0,0 +1,78 @@ + [ + /** + * The placeholder text if none have been selected yet + */ + 'empty' => function ($empty = null) { + return I18n::translate($empty, $empty); + }, + + /** + * Image settings for each item + */ + 'image' => function ($image = null) { + return $image; + }, + + /** + * Info text for each item + */ + 'info' => function (string $info = null) { + return $info; + }, + + /** + * Whether each item should be clickable + */ + 'link' => function (bool $link = true) { + return $link; + }, + + /** + * The minimum number of required selected + */ + 'min' => function (int $min = null) { + return $min; + }, + + /** + * The maximum number of allowed selected + */ + 'max' => function (int $max = null) { + return $max; + }, + + /** + * If `false`, only a single one can be selected + */ + 'multiple' => function (bool $multiple = true) { + return $multiple; + }, + + /** + * Query for the items to be included in the picker + */ + 'query' => function (string $query = null) { + return $query; + }, + + /** + * Enable/disable the search field in the picker + */ + 'search' => function (bool $search = true) { + return $search; + }, + + /** + * Main text for each item + */ + 'text' => function (string $text = null) { + return $text; + }, + + ], +]; diff --git a/kirby/config/fields/mixins/upload.php b/kirby/config/fields/mixins/upload.php new file mode 100644 index 0000000..dd6422d --- /dev/null +++ b/kirby/config/fields/mixins/upload.php @@ -0,0 +1,72 @@ + [ + /** + * Sets the upload options for linked files (since 3.2.0) + */ + 'uploads' => function ($uploads = []) { + if ($uploads === false) { + return false; + } + + if (is_string($uploads) === true) { + $uploads = ['template' => $uploads]; + } + + if (is_array($uploads) === false) { + $uploads = []; + } + + $template = $uploads['template'] ?? null; + + if ($template) { + $file = new File([ + 'filename' => 'tmp', + 'template' => $template + ]); + + $uploads['accept'] = $file->blueprint()->acceptMime(); + } else { + $uploads['accept'] = '*'; + } + + return $uploads; + }, + ], + 'methods' => [ + 'upload' => function (Api $api, $params, Closure $map) { + if ($params === false) { + throw new Exception('Uploads are disabled for this field'); + } + + if ($parentQuery = ($params['parent'] ?? null)) { + $parent = $this->model()->query($parentQuery); + } else { + $parent = $this->model(); + } + + if (is_a($parent, 'Kirby\Cms\File') === true) { + $parent = $parent->parent(); + } + + return $api->upload(function ($source, $filename) use ($parent, $params, $map) { + $file = $parent->createFile([ + 'source' => $source, + 'template' => $params['template'] ?? null, + 'filename' => $filename, + ]); + + if (is_a($file, 'Kirby\Cms\File') === false) { + throw new Exception('The file could not be uploaded'); + } + + return $map($file, $parent); + }); + } + ] +]; diff --git a/kirby/config/fields/mixins/userpicker.php b/kirby/config/fields/mixins/userpicker.php new file mode 100644 index 0000000..41c2b62 --- /dev/null +++ b/kirby/config/fields/mixins/userpicker.php @@ -0,0 +1,13 @@ + [ + 'userpicker' => function (array $params = []) { + $params['model'] = $this->model(); + + return (new UserPicker($params))->toArray(); + } + ] +]; diff --git a/kirby/config/fields/multiselect.php b/kirby/config/fields/multiselect.php new file mode 100644 index 0000000..4ed2422 --- /dev/null +++ b/kirby/config/fields/multiselect.php @@ -0,0 +1,32 @@ + 'tags', + 'props' => [ + /** + * Unset inherited props + */ + 'accept' => null, + /** + * Custom icon to replace the arrow down. + */ + 'icon' => function (string $icon = null) { + return $icon; + }, + /** + * Enable/disable the search in the dropdown + * Also limit displayed items (display: 20) + * and set minimum number of characters to search (min: 3) + */ + 'search' => function ($search = true) { + return $search; + }, + /** + * If `true`, selected entries will be sorted + * according to their position in the dropdown + */ + 'sort' => function (bool $sort = false) { + return $sort; + }, + ] +]; diff --git a/kirby/config/fields/number.php b/kirby/config/fields/number.php new file mode 100644 index 0000000..92a23ee --- /dev/null +++ b/kirby/config/fields/number.php @@ -0,0 +1,48 @@ + [ + /** + * Default number that will be saved when a new page/user/file is created + */ + 'default' => function ($default = null) { + return $this->toNumber($default); + }, + /** + * The lowest allowed number + */ + 'min' => function (float $min = null) { + return $min; + }, + /** + * The highest allowed number + */ + 'max' => function (float $max = null) { + return $max; + }, + /** + * Allowed incremental steps between numbers (i.e `0.5`) + */ + 'step' => function ($step = null) { + return $this->toNumber($step); + }, + 'value' => function ($value = null) { + return $this->toNumber($value); + } + ], + 'methods' => [ + 'toNumber' => function ($value) { + if ($this->isEmpty($value) === true) { + return null; + } + + return is_float($value) === true ? $value : (float)Str::float($value); + } + ], + 'validations' => [ + 'min', + 'max' + ] +]; diff --git a/kirby/config/fields/pages.php b/kirby/config/fields/pages.php new file mode 100644 index 0000000..471f8b1 --- /dev/null +++ b/kirby/config/fields/pages.php @@ -0,0 +1,117 @@ + ['min', 'pagepicker', 'picker'], + 'props' => [ + /** + * Unset inherited props + */ + 'after' => null, + 'autofocus' => null, + 'before' => null, + 'icon' => null, + 'placeholder' => null, + + /** + * Default selected page(s) when a new page/file/user is created + */ + 'default' => function ($default = null) { + return $this->toPages($default); + }, + + /** + * Changes the layout of the selected files. Available layouts: `list`, `cards` + */ + 'layout' => function (string $layout = 'list') { + return $layout; + }, + + /** + * Optional query to select a specific set of pages + */ + 'query' => function (string $query = null) { + return $query; + }, + + /** + * Layout size for cards: `tiny`, `small`, `medium`, `large` or `huge` + */ + 'size' => function (string $size = 'auto') { + return $size; + }, + + /** + * Optionally include subpages of pages + */ + 'subpages' => function (bool $subpages = true) { + return $subpages; + }, + + 'value' => function ($value = null) { + return $this->toPages($value); + }, + ], + 'computed' => [ + /** + * Unset inherited computed + */ + 'default' => null + ], + 'methods' => [ + 'pageResponse' => function ($page) { + return $page->panelPickerData([ + 'image' => $this->image, + 'info' => $this->info, + 'text' => $this->text, + ]); + }, + 'toPages' => function ($value = null) { + $pages = []; + $kirby = kirby(); + + foreach (Data::decode($value, 'yaml') as $id) { + if (is_array($id) === true) { + $id = $id['id'] ?? null; + } + + if ($id !== null && ($page = $kirby->page($id))) { + $pages[] = $this->pageResponse($page); + } + } + + return $pages; + } + ], + 'api' => function () { + return [ + [ + 'pattern' => '/', + 'action' => function () { + $field = $this->field(); + + return $field->pagepicker([ + 'image' => $field->image(), + 'info' => $field->info(), + 'limit' => $field->limit(), + 'page' => $this->requestQuery('page'), + 'parent' => $this->requestQuery('parent'), + 'query' => $field->query(), + 'search' => $this->requestQuery('search'), + 'subpages' => $field->subpages(), + 'text' => $field->text() + ]); + } + ] + ]; + }, + 'save' => function ($value = null) { + return A::pluck($value, 'id'); + }, + 'validations' => [ + 'max', + 'min' + ] +]; diff --git a/kirby/config/fields/radio.php b/kirby/config/fields/radio.php new file mode 100644 index 0000000..dd9ffc3 --- /dev/null +++ b/kirby/config/fields/radio.php @@ -0,0 +1,29 @@ + ['options'], + 'props' => [ + /** + * Unset inherited props + */ + 'after' => null, + 'before' => null, + 'icon' => null, + 'placeholder' => null, + + /** + * Arranges the radio buttons in the given number of columns + */ + 'columns' => function (int $columns = 1) { + return $columns; + }, + ], + 'computed' => [ + 'default' => function () { + return $this->sanitizeOption($this->default); + }, + 'value' => function () { + return $this->sanitizeOption($this->value) ?? ''; + } + ] +]; diff --git a/kirby/config/fields/range.php b/kirby/config/fields/range.php new file mode 100644 index 0000000..5f14388 --- /dev/null +++ b/kirby/config/fields/range.php @@ -0,0 +1,24 @@ + 'number', + 'props' => [ + /** + * Unset inherited props + */ + 'placeholder' => null, + + /** + * The maximum value on the slider + */ + 'max' => function (float $max = 100) { + return $max; + }, + /** + * Enables/disables the tooltip and set the before and after values + */ + 'tooltip' => function ($tooltip = true) { + return $tooltip; + }, + ] +]; diff --git a/kirby/config/fields/select.php b/kirby/config/fields/select.php new file mode 100644 index 0000000..24b14b6 --- /dev/null +++ b/kirby/config/fields/select.php @@ -0,0 +1,24 @@ + 'radio', + 'props' => [ + /** + * Unset inherited props + */ + 'columns' => null, + + /** + * Custom icon to replace the arrow down. + */ + 'icon' => function (string $icon = null) { + return $icon; + }, + /** + * Custom placeholder string for empty option. + */ + 'placeholder' => function (string $placeholder = '—') { + return $placeholder; + }, + ] +]; diff --git a/kirby/config/fields/structure.php b/kirby/config/fields/structure.php new file mode 100644 index 0000000..b834b2c --- /dev/null +++ b/kirby/config/fields/structure.php @@ -0,0 +1,193 @@ + ['min'], + 'props' => [ + /** + * Unset inherited props + */ + 'after' => null, + 'before' => null, + 'autofocus' => null, + 'icon' => null, + 'placeholder' => null, + + /** + * Optional columns definition to only show selected fields in the structure table. + */ + 'columns' => function (array $columns = []) { + // lower case all keys, because field names will + // be lowercase as well. + return array_change_key_case($columns); + }, + + /** + * Toggles duplicating rows for the structure + */ + 'duplicate' => function (bool $duplicate = true) { + return $duplicate; + }, + + /** + * The placeholder text if no items have been added yet + */ + 'empty' => function ($empty = null) { + return I18n::translate($empty, $empty); + }, + + /** + * Set the default rows for the structure + */ + 'default' => function (array $default = null) { + return $default; + }, + + /** + * Fields setup for the structure form. Works just like fields in regular forms. + */ + 'fields' => function (array $fields) { + return $fields; + }, + /** + * The number of entries that will be displayed on a single page. Afterwards pagination kicks in. + */ + 'limit' => function (int $limit = null) { + return $limit; + }, + /** + * Maximum allowed entries in the structure. Afterwards the "Add" button will be switched off. + */ + 'max' => function (int $max = null) { + return $max; + }, + /** + * Minimum required entries in the structure + */ + 'min' => function (int $min = null) { + return $min; + }, + /** + * Toggles adding to the top or bottom of the list + */ + 'prepend' => function (bool $prepend = null) { + return $prepend; + }, + /** + * Toggles drag & drop sorting + */ + 'sortable' => function (bool $sortable = null) { + return $sortable; + }, + /** + * Sorts the entries by the given field and order (i.e. `title desc`) + * Drag & drop is disabled in this case + */ + 'sortBy' => function (string $sort = null) { + return $sort; + } + ], + 'computed' => [ + 'default' => function () { + return $this->rows($this->default); + }, + 'value' => function () { + return $this->rows($this->value); + }, + 'fields' => function () { + if (empty($this->fields) === true) { + throw new Exception('Please provide some fields for the structure'); + } + + return $this->form()->fields()->toArray(); + }, + 'columns' => function () { + $columns = []; + + if (empty($this->columns)) { + foreach ($this->fields as $field) { + + // Skip hidden and unsaveable fields + // They should never be included as column + if ($field['type'] === 'hidden' || $field['saveable'] === false) { + continue; + } + + $columns[$field['name']] = [ + 'type' => $field['type'], + 'label' => $field['label'] ?? $field['name'] + ]; + } + } else { + foreach ($this->columns as $columnName => $columnProps) { + if (is_array($columnProps) === false) { + $columnProps = []; + } + + $field = $this->fields[$columnName] ?? null; + + if (empty($field) === true || $field['saveable'] === false) { + continue; + } + + $columns[$columnName] = array_merge($columnProps, [ + 'type' => $field['type'], + 'label' => $field['label'] ?? $field['name'] + ]); + } + } + + return $columns; + } + ], + 'methods' => [ + 'rows' => function ($value) { + $rows = Data::decode($value, 'yaml'); + $value = []; + + foreach ($rows as $index => $row) { + if (is_array($row) === false) { + continue; + } + + $value[] = $this->form($row)->values(); + } + + return $value; + }, + 'form' => function (array $values = []) { + return new Form([ + 'fields' => $this->attrs['fields'], + 'values' => $values, + 'model' => $this->model + ]); + }, + ], + 'api' => function () { + return [ + [ + 'pattern' => 'validate', + 'method' => 'ALL', + 'action' => function () { + return array_values($this->field()->form($this->requestBody())->errors()); + } + ] + ]; + }, + 'save' => function ($value) { + $data = []; + + foreach ($value as $row) { + $data[] = $this->form($row)->content(); + } + + return $data; + }, + 'validations' => [ + 'min', + 'max' + ] +]; diff --git a/kirby/config/fields/tags.php b/kirby/config/fields/tags.php new file mode 100644 index 0000000..5cfd4f4 --- /dev/null +++ b/kirby/config/fields/tags.php @@ -0,0 +1,103 @@ + ['min', 'options'], + 'props' => [ + + /** + * Unset inherited props + */ + 'after' => null, + 'before' => null, + 'placeholder' => null, + + /** + * If set to `all`, any type of input is accepted. If set to `options` only the predefined options are accepted as input. + */ + 'accept' => function ($value = 'all') { + return V::in($value, ['all', 'options']) ? $value : 'all'; + }, + /** + * Changes the tag icon + */ + 'icon' => function ($icon = 'tag') { + return $icon; + }, + /** + * Set to `list` to display each tag with 100% width, + * otherwise the tags are displayed inline + */ + 'layout' => function (?string $layout = null) { + return $layout; + }, + /** + * Minimum number of required entries/tags + */ + 'min' => function (int $min = null) { + return $min; + }, + /** + * Maximum number of allowed entries/tags + */ + 'max' => function (int $max = null) { + return $max; + }, + /** + * Custom tags separator, which will be used to store tags in the content file + */ + 'separator' => function (string $separator = ',') { + return $separator; + }, + ], + 'computed' => [ + 'default' => function (): array { + return $this->toTags($this->default); + }, + 'value' => function (): array { + return $this->toTags($this->value); + } + ], + 'methods' => [ + 'toTags' => function ($value) { + if (is_null($value) === true) { + return []; + } + + $options = $this->options(); + + // transform into value-text objects + return array_map(function ($option) use ($options) { + + // already a valid object + if (is_array($option) === true && isset($option['value'], $option['text']) === true) { + return $option; + } + + $index = array_search($option, array_column($options, 'value')); + + if ($index !== false) { + return $options[$index]; + } + + return [ + 'value' => $option, + 'text' => $option, + ]; + }, Str::split($value, $this->separator())); + } + ], + 'save' => function (array $value = null): string { + return A::join( + A::pluck($value, 'value'), + $this->separator() . ' ' + ); + }, + 'validations' => [ + 'min', + 'max' + ] +]; diff --git a/kirby/config/fields/tel.php b/kirby/config/fields/tel.php new file mode 100644 index 0000000..3d73430 --- /dev/null +++ b/kirby/config/fields/tel.php @@ -0,0 +1,27 @@ + 'text', + 'props' => [ + /** + * Unset inherited props + */ + 'converter' => null, + 'counter' => null, + 'spellcheck' => null, + + /** + * Sets the HTML5 autocomplete attribute + */ + 'autocomplete' => function (string $autocomplete = 'tel') { + return $autocomplete; + }, + + /** + * Changes the phone icon + */ + 'icon' => function (string $icon = 'phone') { + return $icon; + } + ] +]; diff --git a/kirby/config/fields/text.php b/kirby/config/fields/text.php new file mode 100644 index 0000000..c32a037 --- /dev/null +++ b/kirby/config/fields/text.php @@ -0,0 +1,103 @@ + [ + + /** + * The field value will be converted with the selected converter before the value gets saved. Available converters: `lower`, `upper`, `ucfirst`, `slug` + */ + 'converter' => function ($value = null) { + if ($value !== null && in_array($value, array_keys($this->converters())) === false) { + throw new InvalidArgumentException([ + 'key' => 'field.converter.invalid', + 'data' => ['converter' => $value] + ]); + } + + return $value; + }, + + /** + * Shows or hides the character counter in the top right corner + */ + 'counter' => function (bool $counter = true) { + return $counter; + }, + + /** + * Maximum number of allowed characters + */ + 'maxlength' => function (int $maxlength = null) { + return $maxlength; + }, + + /** + * Minimum number of required characters + */ + 'minlength' => function (int $minlength = null) { + return $minlength; + }, + + /** + * A regular expression, which will be used to validate the input + */ + 'pattern' => function (string $pattern = null) { + return $pattern; + }, + + /** + * If `false`, spellcheck will be switched off + */ + 'spellcheck' => function (bool $spellcheck = false) { + return $spellcheck; + }, + ], + 'computed' => [ + 'default' => function () { + return $this->convert($this->default); + }, + 'value' => function () { + return (string)$this->convert($this->value); + } + ], + 'methods' => [ + 'convert' => function ($value) { + if ($this->converter() === null) { + return $value; + } + + $value = trim($value); + $converter = $this->converters()[$this->converter()]; + + if (is_array($value) === true) { + return array_map($converter, $value); + } + + return call_user_func($converter, $value); + }, + 'converters' => function (): array { + return [ + 'lower' => function ($value) { + return Str::lower($value); + }, + 'slug' => function ($value) { + return Str::slug($value); + }, + 'ucfirst' => function ($value) { + return Str::ucfirst($value); + }, + 'upper' => function ($value) { + return Str::upper($value); + }, + ]; + }, + ], + 'validations' => [ + 'minlength', + 'maxlength', + 'pattern' + ] +]; diff --git a/kirby/config/fields/textarea.php b/kirby/config/fields/textarea.php new file mode 100644 index 0000000..8c8976a --- /dev/null +++ b/kirby/config/fields/textarea.php @@ -0,0 +1,123 @@ + ['filepicker', 'upload'], + 'props' => [ + /** + * Unset inherited props + */ + 'after' => null, + 'before' => null, + + /** + * Enables/disables the format buttons. Can either be `true`/`false` or a list of allowed buttons. Available buttons: `headlines`, `italic`, `bold`, `link`, `email`, `file`, `code`, `ul`, `ol` (as well as `|` for a divider) + */ + 'buttons' => function ($buttons = true) { + return $buttons; + }, + + /** + * Enables/disables the character counter in the top right corner + */ + 'counter' => function (bool $counter = true) { + return $counter; + }, + + /** + * Sets the default text when a new page/file/user is created + */ + 'default' => function (string $default = null) { + return trim($default); + }, + + /** + * Sets the options for the files picker + */ + 'files' => function ($files = []) { + if (is_string($files) === true) { + return ['query' => $files]; + } + + if (is_array($files) === false) { + $files = []; + } + + return $files; + }, + + /** + * Sets the font family (sans or monospace) + */ + 'font' => function (string $font = null) { + return $font === 'monospace' ? 'monospace' : 'sans-serif'; + }, + + /** + * Maximum number of allowed characters + */ + 'maxlength' => function (int $maxlength = null) { + return $maxlength; + }, + + /** + * Minimum number of required characters + */ + 'minlength' => function (int $minlength = null) { + return $minlength; + }, + + /** + * Changes the size of the textarea. Available sizes: `small`, `medium`, `large`, `huge` + */ + 'size' => function (string $size = null) { + return $size; + }, + + /** + * If `false`, spellcheck will be switched off + */ + 'spellcheck' => function (bool $spellcheck = true) { + return $spellcheck; + }, + + 'value' => function (string $value = null) { + return trim($value); + } + ], + 'api' => function () { + return [ + [ + 'pattern' => 'files', + 'action' => function () { + $params = array_merge($this->field()->files(), [ + 'page' => $this->requestQuery('page'), + 'search' => $this->requestQuery('search') + ]); + + return $this->field()->filepicker($params); + } + ], + [ + 'pattern' => 'upload', + 'method' => 'POST', + 'action' => function () { + $field = $this->field(); + $uploads = $field->uploads(); + + return $this->field()->upload($this, $uploads, function ($file, $parent) use ($field) { + $absolute = $field->model()->is($parent) === false; + + return [ + 'filename' => $file->filename(), + 'dragText' => $file->dragText('auto', $absolute), + ]; + }); + } + ] + ]; + }, + 'validations' => [ + 'minlength', + 'maxlength' + ] +]; diff --git a/kirby/config/fields/time.php b/kirby/config/fields/time.php new file mode 100644 index 0000000..7f18e69 --- /dev/null +++ b/kirby/config/fields/time.php @@ -0,0 +1,151 @@ + ['datetime'], + 'props' => [ + /** + * Unset inherited props + */ + 'placeholder' => null, + + + /** + * Sets the default time when a new page/file/user is created + */ + 'default' => function ($default = null) { + return $default; + }, + + /** + * Custom format (dayjs tokens: `HH`, `hh`, `mm`, `ss`, `a`) that is + * used to display the field in the Panel + */ + 'display' => function ($display = null) { + return I18n::translate($display, $display); + }, + + /** + * Changes the clock icon + */ + 'icon' => function (string $icon = 'clock') { + return $icon; + }, + /** + * Latest time, which can be selected/saved (H:i or H:i:s) + */ + 'max' => function (string $max = null) { + return $max ? $this->toDatetime(date('Y-m-d ') . $max) : null; + }, + /** + * Earliest time, which can be selected/saved (H:i or H:i:s) + */ + 'min' => function (string $min = null) { + return $min ? $this->toDatetime(date('Y-m-d ') . $min) : null; + }, + + /** + * `12` or `24` hour notation. If `12`, an AM/PM selector will be shown. + * If `display` is defined, that option will take priority. + */ + 'notation' => function (int $value = 24) { + return $value === 24 ? 24 : 12; + }, + /** + * Round to the nearest: sub-options for `unit` (minute) and `size` (5) + */ + 'step' => function ($step = null) { + $default = [ + 'size' => 5, + 'unit' => 'minute' + ]; + + if ($step === null) { + return $default; + } + + if (is_array($step) === true) { + $step = array_merge($default, $step); + $step['unit'] = strtolower($step['unit']); + return $step; + } + + if (is_int($step) === true) { + return array_merge($default, ['size' => $step]); + } + + if (is_string($step) === true) { + return array_merge($default, ['unit' => strtolower($step)]); + } + }, + 'value' => function ($value = null) { + return $value; + } + ], + 'computed' => [ + 'default' => function () { + return $this->toDatetime($this->default, 'H:i:s'); + }, + 'display' => function () { + if ($this->display) { + return $this->display; + } + + return $this->notation === 24 ? 'HH:mm' : 'h:mm a'; + }, + 'format' => function () { + return $this->props['format'] ?? 'H:i:s'; + }, + 'value' => function () { + return $this->toDatetime($this->value, 'H:i:s'); + } + ], + 'validations' => [ + 'time', + 'minMax' => function ($value) { + $min = $this->min ? strtotime($this->min) : null; + $max = $this->max ? strtotime($this->max) : null; + $value = strtotime($this->value()); + $format = 'H:i:s'; + $errors = []; + + if ($value && $min && $value < $min) { + $errors['min'] = $min; + } + + if ($value && $max && $value > $max) { + $errors['max'] = $max; + } + + if (empty($errors) === false) { + if ($min && $max) { + throw new Exception([ + 'key' => 'validation.time.between', + 'data' => [ + 'min' => date($format, $min), + 'max' => date($format, $max) + ] + ]); + } elseif ($min) { + throw new Exception([ + 'key' => 'validation.time.after', + 'data' => [ + 'time' => date($format, $min), + ] + ]); + } else { + throw new Exception([ + 'key' => 'validation.time.before', + 'data' => [ + 'time' => date($format, $max), + ] + ]); + } + } + + return true; + }, + ] +]; diff --git a/kirby/config/fields/toggle.php b/kirby/config/fields/toggle.php new file mode 100644 index 0000000..b8e9fef --- /dev/null +++ b/kirby/config/fields/toggle.php @@ -0,0 +1,66 @@ + [ + /** + * Unset inherited props + */ + 'placeholder' => null, + + /** + * Default value which will be saved when a new page/user/file is created + */ + 'default' => function ($default = null) { + return $this->default = $default; + }, + /** + * Sets the text next to the toggle. The text can be a string or an array of two options. The first one is the negative text and the second one the positive. The text will automatically switch when the toggle is triggered. + */ + 'text' => function ($value = null) { + if (is_array($value) === true) { + if (A::isAssociative($value) === true) { + return I18n::translate($value, $value); + } + + foreach ($value as $key => $val) { + $value[$key] = I18n::translate($val, $val); + } + + return $value; + } + + return I18n::translate($value, $value); + }, + ], + 'computed' => [ + 'default' => function () { + return $this->toBool($this->default); + }, + 'value' => function () { + if ($this->props['value'] === null) { + return $this->default(); + } else { + return $this->toBool($this->props['value']); + } + } + ], + 'methods' => [ + 'toBool' => function ($value) { + return in_array($value, [true, 'true', 1, '1', 'on'], true) === true; + } + ], + 'save' => function (): string { + return $this->value() === true ? 'true' : 'false'; + }, + 'validations' => [ + 'boolean', + 'required' => function ($value) { + if ($this->isRequired() && ($value === false || $this->isEmpty($value))) { + throw new InvalidArgumentException(I18n::translate('field.required')); + } + }, + ] +]; diff --git a/kirby/config/fields/url.php b/kirby/config/fields/url.php new file mode 100644 index 0000000..f92dd2c --- /dev/null +++ b/kirby/config/fields/url.php @@ -0,0 +1,41 @@ + 'text', + 'props' => [ + /** + * Unset inherited props + */ + 'converter' => null, + 'counter' => null, + 'spellcheck' => null, + + /** + * Sets the HTML5 autocomplete attribute + */ + 'autocomplete' => function (string $autocomplete = 'url') { + return $autocomplete; + }, + + /** + * Changes the link icon + */ + 'icon' => function (string $icon = 'url') { + return $icon; + }, + + /** + * Sets custom placeholder text, when the field is empty + */ + 'placeholder' => function ($value = null) { + return I18n::translate($value, $value) ?? 'https://example.com'; + } + ], + 'validations' => [ + 'minlength', + 'maxlength', + 'url' + ], +]; diff --git a/kirby/config/fields/users.php b/kirby/config/fields/users.php new file mode 100644 index 0000000..6da224a --- /dev/null +++ b/kirby/config/fields/users.php @@ -0,0 +1,97 @@ + ['min', 'picker', 'userpicker'], + 'props' => [ + /** + * Unset inherited props + */ + 'after' => null, + 'autofocus' => null, + 'before' => null, + 'icon' => null, + 'placeholder' => null, + + /** + * Default selected user(s) when a new page/file/user is created + */ + 'default' => function ($default = null) { + if ($default === false) { + return []; + } + + if ($default === null && $user = $this->kirby()->user()) { + return [ + $this->userResponse($user) + ]; + } + + return $this->toUsers($default); + }, + + 'value' => function ($value = null) { + return $this->toUsers($value); + }, + ], + 'computed' => [ + /** + * Unset inherited computed + */ + 'default' => null + ], + 'methods' => [ + 'userResponse' => function ($user) { + return $user->panelPickerData([ + 'info' => $this->info, + 'image' => $this->image, + 'text' => $this->text, + ]); + }, + 'toUsers' => function ($value = null) { + $users = []; + $kirby = kirby(); + + foreach (Data::decode($value, 'yaml') as $email) { + if (is_array($email) === true) { + $email = $email['email'] ?? null; + } + + if ($email !== null && ($user = $kirby->user($email))) { + $users[] = $this->userResponse($user); + } + } + + return $users; + } + ], + 'api' => function () { + return [ + [ + 'pattern' => '/', + 'action' => function () { + $field = $this->field(); + + return $field->userpicker([ + 'image' => $field->image(), + 'info' => $field->info(), + 'limit' => $field->limit(), + 'page' => $this->requestQuery('page'), + 'query' => $field->query(), + 'search' => $this->requestQuery('search'), + 'text' => $field->text() + ]); + } + ] + ]; + }, + 'save' => function ($value = null) { + return A::pluck($value, 'id'); + }, + 'validations' => [ + 'max', + 'min' + ] +]; diff --git a/kirby/config/fields/writer.php b/kirby/config/fields/writer.php new file mode 100644 index 0000000..67ca348 --- /dev/null +++ b/kirby/config/fields/writer.php @@ -0,0 +1,33 @@ + [ + /** + * Enables inline mode, which will not wrap new lines in paragraphs and creates hard breaks instead. + * + * @param bool $inline + */ + 'inline' => function (bool $inline = false) { + return $inline; + }, + /** + * Sets the allowed HTML formats. Available formats: `bold`, `italic`, `underline`, `strike`, `code`, `link`. Activate them all by passing `true`. Deactivate them all by passing `false` + * @param array|bool $marks + */ + 'marks' => function ($marks = true) { + return $marks; + }, + /** + * Sets the allowed nodes. Available nodes: `bulletList`, `orderedList`, `heading`, `horizontalRule`, `listItem`. Activate/deactivate them all by passing `true`/`false`. Default nodes are `heading`, `bulletList`, `orderedList`. + * @param array|bool|null $nodes + */ + 'nodes' => function ($nodes = null) { + return $nodes; + } + ], + 'computed' => [ + 'value' => function () { + return trim($this->value); + } + ], +]; diff --git a/kirby/config/helpers.php b/kirby/config/helpers.php new file mode 100644 index 0000000..800538e --- /dev/null +++ b/kirby/config/helpers.php @@ -0,0 +1,953 @@ +collection($name); +} + +/** + * Checks / returns a CSRF token + * + * @param string $check Pass a token here to compare it to the one in the session + * @return string|bool Either the token or a boolean check result + */ +function csrf(string $check = null) +{ + $session = App::instance()->session(); + + // check explicitly if there have been no arguments at all; + // checking for null introduces a security issue because null could come + // from user input or bugs in the calling code! + if (func_num_args() === 0) { + // no arguments, generate/return a token + + $token = $session->get('kirby.csrf'); + if (is_string($token) !== true) { + $token = bin2hex(random_bytes(32)); + $session->set('kirby.csrf', $token); + } + + return $token; + } elseif (is_string($check) === true && is_string($session->get('kirby.csrf')) === true) { + // argument has been passed, check the token + return hash_equals($session->get('kirby.csrf'), $check) === true; + } + + return false; +} + +/** + * Creates one or multiple CSS link tags + * + * @param string|array $url Relative or absolute URLs, an array of URLs or `@auto` for automatic template css loading + * @param string|array $options Pass an array of attributes for the link tag or a media attribute string + * @return string|null + */ +function css($url, $options = null): ?string +{ + if (is_array($url) === true) { + $links = array_map(function ($url) use ($options) { + return css($url, $options); + }, $url); + + return implode(PHP_EOL, $links); + } + + if (is_string($options) === true) { + $options = ['media' => $options]; + } + + $kirby = App::instance(); + + if ($url === '@auto') { + if (!$url = Url::toTemplateAsset('css/templates', 'css')) { + return null; + } + } + + $url = ($kirby->component('css'))($kirby, $url, $options); + $url = Url::to($url); + $attr = array_merge((array)$options, [ + 'href' => $url, + 'rel' => 'stylesheet' + ]); + + return ''; +} + +/** + * Triggers a deprecation warning if debug mode is active + * @since 3.3.0 + * + * @param string $message + * @return bool Whether the warning was triggered + */ +function deprecated(string $message): bool +{ + if (App::instance()->option('debug') === true) { + return trigger_error($message, E_USER_DEPRECATED) === true; + } + + return false; +} + +if (function_exists('dump') === false) { + /** + * Simple object and variable dumper + * to help with debugging. + * + * @param mixed $variable + * @param bool $echo + * @return string + */ + function dump($variable, bool $echo = true): string + { + $kirby = App::instance(); + return ($kirby->component('dump'))($kirby, $variable, $echo); + } +} + +if (function_exists('e') === false) { + /** + * Smart version of echo with an if condition as first argument + * + * @param mixed $condition + * @param mixed $value The string to be echoed if the condition is true + * @param mixed $alternative An alternative string which should be echoed when the condition is false + */ + function e($condition, $value, $alternative = null) + { + echo r($condition, $value, $alternative); + } +} + +/** + * Escape context specific output + * + * @param string $string Untrusted data + * @param string $context Location of output (`html`, `attr`, `js`, `css`, `url` or `xml`) + * @return string Escaped data + */ +function esc($string, $context = 'html') +{ + if (method_exists('Kirby\Toolkit\Escape', $context) === true) { + return Escape::$context($string); + } + + return $string; +} + + +/** + * Shortcut for $kirby->request()->get() + * + * @param mixed $key The key to look for. Pass false or null to return the entire request array. + * @param mixed $default Optional default value, which should be returned if no element has been found + * @return mixed + */ +function get($key = null, $default = null) +{ + return App::instance()->request()->get($key, $default); +} + +/** + * Embeds a Github Gist + * + * @param string $url + * @param string $file + * @return string + */ +function gist(string $url, string $file = null): string +{ + return kirbytag([ + 'gist' => $url, + 'file' => $file, + ]); +} + +/** + * Redirects to the given Urls + * Urls can be relative or absolute. + * + * @param string $url + * @param int $code + * @return void + */ +function go(string $url = '/', int $code = 302) +{ + die(Response::redirect($url, $code)); +} + +/** + * Shortcut for html() + * + * @param string|null $string unencoded text + * @param bool $keepTags + * @return string + */ +function h(?string $string, bool $keepTags = false) +{ + return Html::encode($string, $keepTags); +} + +/** + * Creates safe html by encoding special characters + * + * @param string|null $string unencoded text + * @param bool $keepTags + * @return string + */ +function html(?string $string, bool $keepTags = false) +{ + return Html::encode($string, $keepTags); +} + +/** + * Return an image from any page + * specified by the path + * + * Example: + * + * + * @param string $path + * @return \Kirby\Cms\File|null + */ +function image(string $path = null) +{ + if ($path === null) { + return page()->image(); + } + + $uri = dirname($path); + $filename = basename($path); + + if ($uri === '.') { + $uri = null; + } + + switch ($uri) { + case '/': + $parent = site(); + break; + case null: + $parent = page(); + break; + default: + $parent = page($uri); + break; + } + + if ($parent) { + return $parent->image($filename); + } else { + return null; + } +} + +/** + * Runs a number of validators on a set of data and checks if the data is invalid + * + * @param array $data + * @param array $rules + * @param array $messages + * @return false|array + */ +function invalid(array $data = [], array $rules = [], array $messages = []) +{ + $errors = []; + + foreach ($rules as $field => $validations) { + $validationIndex = -1; + + // See: http://php.net/manual/en/types.comparisons.php + // only false for: null, undefined variable, '', [] + $value = $data[$field] ?? null; + $filled = $value !== null && $value !== '' && $value !== []; + $message = $messages[$field] ?? $field; + + // True if there is an error message for each validation method. + $messageArray = is_array($message); + + foreach ($validations as $method => $options) { + // If the index is numeric, there is no option + // and `$value` is sent directly as a `$options` parameter + if (is_numeric($method) === true) { + $method = $options; + $options = [$value]; + } else { + if (is_array($options) === false) { + $options = [$options]; + } + + array_unshift($options, $value); + } + + $validationIndex++; + + if ($method === 'required') { + if ($filled) { + // Field is required and filled. + continue; + } + } elseif ($filled) { + if (V::$method(...$options) === true) { + // Field is filled and passes validation method. + continue; + } + } else { + // If a field is not required and not filled, no validation should be done. + continue; + } + + // If no continue was called we have a failed validation. + if ($messageArray) { + $errors[$field][] = $message[$validationIndex] ?? $field; + } else { + $errors[$field] = $message; + } + } + } + + return $errors; +} + +/** + * Creates a script tag to load a javascript file + * + * @param string|array $url + * @param string|array $options + * @return string|null + */ +function js($url, $options = null): ?string +{ + if (is_array($url) === true) { + $scripts = array_map(function ($url) use ($options) { + return js($url, $options); + }, $url); + + return implode(PHP_EOL, $scripts); + } + + if (is_bool($options) === true) { + $options = ['async' => $options]; + } + + $kirby = App::instance(); + + if ($url === '@auto') { + if (!$url = Url::toTemplateAsset('js/templates', 'js')) { + return null; + } + } + + $url = ($kirby->component('js'))($kirby, $url, $options); + $url = Url::to($url); + $attr = array_merge((array)$options, ['src' => $url]); + + return ''; +} + +/** + * Returns the Kirby object in any situation + * + * @return \Kirby\Cms\App + */ +function kirby() +{ + return App::instance(); +} + +/** + * Makes it possible to use any defined Kirbytag as standalone function + * + * @param string|array $type + * @param string $value + * @param array $attr + * @param array $data + * @return string + */ +function kirbytag($type, string $value = null, array $attr = [], array $data = []): string +{ + if (is_array($type) === true) { + $kirbytag = $type; + $type = key($kirbytag); + $value = current($kirbytag); + $attr = $kirbytag; + + // check data attribute and separate from attr data if exists + if (isset($attr['data']) === true) { + $data = $attr['data']; + unset($attr['data']); + } + } + + return App::instance()->kirbytag($type, $value, $attr, $data); +} + +/** + * Parses KirbyTags in the given string. Shortcut + * for `$kirby->kirbytags($text, $data)` + * + * @param string $text + * @param array $data + * @return string + */ +function kirbytags(string $text = null, array $data = []): string +{ + return App::instance()->kirbytags($text, $data); +} + +/** + * Parses KirbyTags and Markdown in the + * given string. Shortcut for `$kirby->kirbytext()` + * + * @param string $text + * @param array $data + * @return string + */ +function kirbytext(string $text = null, array $data = []): string +{ + return App::instance()->kirbytext($text, $data); +} + +/** + * Parses KirbyTags and inline Markdown in the + * given string. + * @since 3.1.0 + * + * @param string $text + * @param array $data + * @return string + */ +function kirbytextinline(string $text = null, array $data = []): string +{ + return App::instance()->kirbytext($text, $data, true); +} + +/** + * Shortcut for `kirbytext()` helper + * + * @param string $text + * @param array $data + * @return string + */ +function kt(string $text = null, array $data = []): string +{ + return kirbytext($text, $data); +} + +/** + * Shortcut for `kirbytextinline()` helper + * @since 3.1.0 + * + * @param string $text + * @param array $data + * @return string + */ +function kti(string $text = null, array $data = []): string +{ + return kirbytextinline($text, $data); +} + +/** + * A super simple class autoloader + * + * @param array $classmap + * @param string $base + * @return void + */ +function load(array $classmap, string $base = null) +{ + // convert all classnames to lowercase + $classmap = array_change_key_case($classmap); + + spl_autoload_register(function ($class) use ($classmap, $base) { + $class = strtolower($class); + + if (!isset($classmap[$class])) { + return false; + } + + if ($base) { + include $base . '/' . $classmap[$class]; + } else { + include $classmap[$class]; + } + }); +} + +/** + * Parses markdown in the given string. Shortcut for + * `$kirby->markdown($text)` + * + * @param string $text + * @return string + */ +function markdown(string $text = null): string +{ + return App::instance()->markdown($text); +} + +/** + * Shortcut for `$kirby->option($key, $default)` + * + * @param string $key + * @param mixed $default + * @return mixed + */ +function option(string $key, $default = null) +{ + return App::instance()->option($key, $default); +} + +/** + * Fetches a single page or multiple pages by + * id or the current page when no id is specified + * + * @param string|array ...$id + * @return \Kirby\Cms\Page|null + */ +function page(...$id) +{ + if (empty($id) === true) { + return App::instance()->site()->page(); + } + + return App::instance()->site()->find(...$id); +} + +/** + * Helper to build page collections + * + * @param string|array ...$id + * @return \Kirby\Cms\Pages + */ +function pages(...$id) +{ + return App::instance()->site()->find(...$id); +} + +/** + * Returns a single param from the URL + * + * @param string $key + * @param string $fallback + * @return string|null + */ +function param(string $key, string $fallback = null): ?string +{ + return App::instance()->request()->url()->params()->$key ?? $fallback; +} + +/** + * Returns all params from the current Url + * + * @return array + */ +function params(): array +{ + return App::instance()->request()->url()->params()->toArray(); +} + +/** + * Smart version of return with an if condition as first argument + * + * @param mixed $condition + * @param mixed $value The string to be returned if the condition is true + * @param mixed $alternative An alternative string which should be returned when the condition is false + * @return mixed + */ +function r($condition, $value, $alternative = null) +{ + return $condition ? $value : $alternative; +} + +/** + * Returns the current site object + * + * @return \Kirby\Cms\Site + */ +function site() +{ + return App::instance()->site(); +} + +/** + * Determines the size/length of numbers, strings, arrays and countable objects + * + * @param mixed $value + * @return int + */ +function size($value): int +{ + if (is_numeric($value)) { + return (int)$value; + } + + if (is_string($value)) { + return Str::length(trim($value)); + } + + if (is_array($value)) { + return count($value); + } + + if (is_object($value)) { + if (is_a($value, 'Countable') === true) { + return count($value); + } + + if (is_a($value, 'Kirby\Toolkit\Collection') === true) { + return $value->count(); + } + } + + throw new InvalidArgumentException('Could not determine the size of the given value'); +} + +/** + * Enhances the given string with + * smartypants. Shortcut for `$kirby->smartypants($text)` + * + * @param string $text + * @return string + */ +function smartypants(string $text = null): string +{ + return App::instance()->smartypants($text); +} + +/** + * Embeds a snippet from the snippet folder + * + * @param string|array $name + * @param array|object $data + * @param bool $return + * @return string + */ +function snippet($name, $data = [], bool $return = false) +{ + if (is_object($data) === true) { + $data = ['item' => $data]; + } + + $snippet = App::instance()->snippet($name, $data); + + if ($return === true) { + return $snippet; + } + + echo $snippet; +} + +/** + * Includes an SVG file by absolute or + * relative file path. + * + * @param string|\Kirby\Cms\File $file + * @return string|false + */ +function svg($file) +{ + // support for Kirby's file objects + if (is_a($file, 'Kirby\Cms\File') === true && $file->extension() === 'svg') { + return $file->read(); + } + + if (is_string($file) === false) { + return false; + } + + $extension = F::extension($file); + + // check for valid svg files + if ($extension !== 'svg') { + return false; + } + + // try to convert relative paths to absolute + if (file_exists($file) === false) { + $root = App::instance()->root(); + $file = realpath($root . '/' . $file); + } + + return F::read($file); +} + +/** + * Returns translate string for key from translation file + * + * @param string|array $key + * @param string|null $fallback + * @return mixed + */ +function t($key, string $fallback = null) +{ + return I18n::translate($key, $fallback); +} + +/** + * Translates a count + * + * @param string|array $key + * @param int $count + * @return mixed + */ +function tc($key, int $count) +{ + return I18n::translateCount($key, $count); +} + +/** + * Rounds the minutes of the given date + * by the defined step + * + * @param string $date + * @param int $step array of `unit` and `size` to round to nearest + * @return int|null + */ +function timestamp(string $date = null, $step = null): ?int +{ + if (V::date($date) === false) { + return null; + } + + $date = strtotime($date); + + if ($step === null) { + return $date; + } + + // fallback for pre-3.5.0 usage + if (is_int($step) === true) { + $step = [ + 'unit' => 'minute', + 'size' => $step + ]; + } + + if (is_array($step) === false) { + return $date; + } + + $parts = [ + 'second' => date('s', $date), + 'minute' => date('i', $date), + 'hour' => date('H', $date), + 'day' => date('d', $date), + 'month' => date('m', $date), + 'year' => date('Y', $date), + ]; + + $current = $parts[$step['unit']]; + $nearest = round($current / $step['size']) * $step['size']; + $parts[$step['unit']] = $nearest; + + foreach ($parts as $part => $value) { + if ($part === $step['unit']) { + break; + } + + $parts[$part] = 0; + } + + $timestamp = strtotime( + $parts['year'] . '-' . + str_pad($parts['month'], 2, 0, STR_PAD_LEFT) . '-' . + str_pad($parts['day'], 2, 0, STR_PAD_LEFT) . ' ' . + str_pad($parts['hour'], 2, 0, STR_PAD_LEFT) . ':' . + str_pad($parts['minute'], 2, 0, STR_PAD_LEFT) . ':' . + str_pad($parts['second'], 2, 0, STR_PAD_LEFT) + ); + + // on error, convert `false` into `null` + return $timestamp ? $timestamp : null; +} + +/** + * Translate by key and then replace + * placeholders in the text + * + * @param string $key + * @param string $fallback + * @param array $replace + * @param string $locale + * @return string + */ +function tt(string $key, $fallback = null, array $replace = null, string $locale = null) +{ + return I18n::template($key, $fallback, $replace, $locale); +} + +/** + * Builds a Twitter link + * + * @param string $username + * @param string $text + * @param string $title + * @param string $class + * @return string + */ +function twitter(string $username, string $text = null, string $title = null, string $class = null): string +{ + return kirbytag([ + 'twitter' => $username, + 'text' => $text, + 'title' => $title, + 'class' => $class + ]); +} + +/** + * Shortcut for url() + * + * @param string $path + * @param array|string|null $options + * @return string + */ +function u(string $path = null, $options = null): string +{ + return Url::to($path, $options); +} + +/** + * Builds an absolute URL for a given path + * + * @param string $path + * @param array|string|null $options + * @return string + */ +function url(string $path = null, $options = null): string +{ + return Url::to($path, $options); +} + +/** + * Creates a compliant v4 UUID + * Taken from: https://github.com/symfony/polyfill + * + * @return string + */ +function uuid(): string +{ + $uuid = bin2hex(random_bytes(16)); + + return sprintf( + '%08s-%04s-4%03s-%04x-%012s', + // 32 bits for "time_low" + substr($uuid, 0, 8), + // 16 bits for "time_mid" + substr($uuid, 8, 4), + // 16 bits for "time_hi_and_version", + // four most significant bits holds version number 4 + substr($uuid, 13, 3), + // 16 bits: + // * 8 bits for "clk_seq_hi_res", + // * 8 bits for "clk_seq_low", + // two most significant bits holds zero and one for variant DCE1.1 + hexdec(substr($uuid, 16, 4)) & 0x3fff | 0x8000, + // 48 bits for "node" + substr($uuid, 20, 12) + ); +} + + +/** + * Creates a video embed via iframe for Youtube or Vimeo + * videos. The embed Urls are automatically detected from + * the given Url. + * + * @param string $url + * @param array $options + * @param array $attr + * @return string + */ +function video(string $url, array $options = [], array $attr = []): string +{ + return Html::video($url, $options, $attr); +} + +/** + * Embeds a Vimeo video by URL in an iframe + * + * @param string $url + * @param array $options + * @param array $attr + * @return string + */ +function vimeo(string $url, array $options = [], array $attr = []): string +{ + return Html::vimeo($url, $options, $attr); +} + +/** + * The widont function makes sure that there are no + * typographical widows at the end of a paragraph – + * that's a single word in the last line + * + * @param string|null $string + * @return string + */ +function widont(string $string = null): string +{ + return Str::widont($string); +} + +/** + * Embeds a Youtube video by URL in an iframe + * + * @param string $url + * @param array $options + * @param array $attr + * @return string + */ +function youtube(string $url, array $options = [], array $attr = []): string +{ + return Html::youtube($url, $options, $attr); +} diff --git a/kirby/config/methods.php b/kirby/config/methods.php new file mode 100644 index 0000000..04491a3 --- /dev/null +++ b/kirby/config/methods.php @@ -0,0 +1,606 @@ + function (Field $field): bool { + return $field->toBool() === false; + }, + + /** + * Converts the field value into a proper boolean + * + * @param \Kirby\Cms\Field $field + * @return bool + */ + 'isTrue' => function (Field $field): bool { + return $field->toBool() === true; + }, + + /** + * Validates the field content with the given validator and parameters + * + * @param string $validator + * @param mixed ...$arguments A list of optional validator arguments + * @return bool + */ + 'isValid' => function (Field $field, string $validator, ...$arguments): bool { + return V::$validator($field->value, ...$arguments); + }, + + // converters + /** + * Converts a yaml or json field to a Blocks object + * + * @param \Kirby\Cms\Field $field + * @return \Kirby\Cms\Blocks + */ + 'toBlocks' => function (Field $field) { + try { + $blocks = Blocks::factory(Blocks::parse($field->value()), [ + 'parent' => $field->parent(), + ]); + + return $blocks->filter('isHidden', false); + } catch (Throwable $e) { + if ($field->parent() === null) { + $message = 'Invalid blocks data for "' . $field->key() . '" field'; + } else { + $message = 'Invalid blocks data for "' . $field->key() . '" field on parent "' . $field->parent()->title() . '"'; + } + + throw new InvalidArgumentException($message); + } + }, + + /** + * Converts the field value into a proper boolean + * + * @param \Kirby\Cms\Field $field + * @param bool $default Default value if the field is empty + * @return bool + */ + 'toBool' => function (Field $field, $default = false): bool { + $value = $field->isEmpty() ? $default : $field->value; + return filter_var($value, FILTER_VALIDATE_BOOLEAN); + }, + + /** + * Parses the field value with the given method + * + * @param \Kirby\Cms\Field $field + * @param string $method [',', 'yaml', 'json'] + * @return array + */ + 'toData' => function (Field $field, string $method = ',') { + switch ($method) { + case 'yaml': + case 'json': + return Data::decode($field->value, $method); + default: + return $field->split($method); + } + }, + + /** + * Converts the field value to a timestamp or a formatted date + * + * @param \Kirby\Cms\Field $field + * @param string|null $format PHP date formatting string + * @param string|null $fallback Fallback string for `strtotime` (since 3.2) + * @return string|int + */ + 'toDate' => function (Field $field, string $format = null, string $fallback = null) use ($app) { + if (empty($field->value) === true && $fallback === null) { + return null; + } + + $time = empty($field->value) === true ? strtotime($fallback) : $field->toTimestamp(); + + if ($format === null) { + return $time; + } + + return ($app->option('date.handler', 'date'))($format, $time); + }, + + /** + * Returns a file object from a filename in the field + * + * @param \Kirby\Cms\Field $field + * @return \Kirby\Cms\File|null + */ + 'toFile' => function (Field $field) { + return $field->toFiles()->first(); + }, + + /** + * Returns a file collection from a yaml list of filenames in the field + * + * @param \Kirby\Cms\Field $field + * @param string $separator + * @return \Kirby\Cms\Files + */ + 'toFiles' => function (Field $field, string $separator = 'yaml') { + $parent = $field->parent(); + $files = new Files([]); + + foreach ($field->toData($separator) as $id) { + if ($file = $parent->kirby()->file($id, $parent)) { + $files->add($file); + } + } + + return $files; + }, + + /** + * Converts the field value into a proper float + * + * @param \Kirby\Cms\Field $field + * @param float $default Default value if the field is empty + * @return float + */ + 'toFloat' => function (Field $field, float $default = 0) { + $value = $field->isEmpty() ? $default : $field->value; + return (float)$value; + }, + + /** + * Converts the field value into a proper integer + * + * @param \Kirby\Cms\Field $field + * @param int $default Default value if the field is empty + * @return int + */ + 'toInt' => function (Field $field, int $default = 0) { + $value = $field->isEmpty() ? $default : $field->value; + return (int)$value; + }, + + /** + * Parse layouts and turn them into + * Layout objects + * + * @param \Kirby\Cms\Field $field + * @return \Kirby\Cms\Layouts + */ + 'toLayouts' => function (Field $field) { + return Layouts::factory(Layouts::parse($field->value()), [ + 'parent' => $field->parent() + ]); + }, + + /** + * Wraps a link tag around the field value. The field value is used as the link text + * + * @param \Kirby\Cms\Field $field + * @param mixed $attr1 Can be an optional Url. If no Url is set, the Url of the Page, File or Site will be used. Can also be an array of link attributes + * @param mixed $attr2 If `$attr1` is used to set the Url, you can use `$attr2` to pass an array of additional attributes. + * @return string + */ + 'toLink' => function (Field $field, $attr1 = null, $attr2 = null) { + if (is_string($attr1) === true) { + $href = $attr1; + $attr = $attr2; + } else { + $href = $field->parent()->url(); + $attr = $attr1; + } + + if ($field->parent()->isActive()) { + $attr['aria-current'] = 'page'; + } + + return Html::a($href, $field->value, $attr ?? []); + }, + + /** + * Returns a page object from a page id in the field + * + * @param \Kirby\Cms\Field $field + * @return \Kirby\Cms\Page|null + */ + 'toPage' => function (Field $field) { + return $field->toPages()->first(); + }, + + /** + * Returns a pages collection from a yaml list of page ids in the field + * + * @param \Kirby\Cms\Field $field + * @param string $separator Can be any other separator to split the field value by + * @return \Kirby\Cms\Pages + */ + 'toPages' => function (Field $field, string $separator = 'yaml') use ($app) { + return $app->site()->find(false, false, ...$field->toData($separator)); + }, + + /** + * Converts a yaml field to a Structure object + * + * @param \Kirby\Cms\Field $field + * @return \Kirby\Cms\Structure + */ + 'toStructure' => function (Field $field) { + try { + return new Structure(Data::decode($field->value, 'yaml'), $field->parent()); + } catch (Exception $e) { + if ($field->parent() === null) { + $message = 'Invalid structure data for "' . $field->key() . '" field'; + } else { + $message = 'Invalid structure data for "' . $field->key() . '" field on parent "' . $field->parent()->title() . '"'; + } + + throw new InvalidArgumentException($message); + } + }, + + /** + * Converts the field value to a Unix timestamp + * + * @param \Kirby\Cms\Field $field + * @return int + */ + 'toTimestamp' => function (Field $field): int { + return strtotime($field->value); + }, + + /** + * Turns the field value into an absolute Url + * + * @param \Kirby\Cms\Field $field + * @return string + */ + 'toUrl' => function (Field $field): string { + return Url::to($field->value); + }, + + /** + * Converts a user email address to a user object + * + * @param \Kirby\Cms\Field $field + * @return \Kirby\Cms\User|null + */ + 'toUser' => function (Field $field) { + return $field->toUsers()->first(); + }, + + /** + * Returns a users collection from a yaml list of user email addresses in the field + * + * @param \Kirby\Cms\Field $field + * @param string $separator + * @return \Kirby\Cms\Users + */ + 'toUsers' => function (Field $field, string $separator = 'yaml') use ($app) { + return $app->users()->find(false, false, ...$field->toData($separator)); + }, + + // inspectors + + /** + * Returns the length of the field content + */ + 'length' => function (Field $field) { + return Str::length($field->value); + }, + + /** + * Returns the number of words in the text + */ + 'words' => function (Field $field) { + return str_word_count(strip_tags($field->value)); + }, + + // manipulators + + /** + * Applies the callback function to the field + * @since 3.4.0 + * + * @param \Kirby\Cms\Field $field + * @param Closure $callback + */ + 'callback' => function (Field $field, Closure $callback) { + return $callback($field); + }, + + /** + * Escapes the field value to be safely used in HTML + * templates without the risk of XSS attacks + * + * @param \Kirby\Cms\Field $field + * @param string $context Location of output (`html`, `attr`, `js`, `css`, `url` or `xml`) + */ + 'escape' => function (Field $field, string $context = 'html') { + $field->value = esc($field->value, $context); + return $field; + }, + + /** + * Creates an excerpt of the field value without html + * or any other formatting. + * + * @param \Kirby\Cms\Field $field + * @param int $cahrs + * @param bool $strip + * @param string $rep + * @return \Kirby\Cms\Field + */ + 'excerpt' => function (Field $field, int $chars = 0, bool $strip = true, string $rep = ' …') { + $field->value = Str::excerpt($field->kirbytext()->value(), $chars, $strip, $rep); + return $field; + }, + + /** + * Converts the field content to valid HTML + * + * @param \Kirby\Cms\Field $field + * @return \Kirby\Cms\Field + */ + 'html' => function (Field $field) { + $field->value = htmlentities($field->value, ENT_COMPAT, 'utf-8'); + return $field; + }, + + /** + * Strips all block-level HTML elements from the field value, + * it can be safely placed inside of other inline elements + * without the risk of breaking the HTML structure. + * @since 3.3.0 + * + * @param \Kirby\Cms\Field $field + * @return \Kirby\Cms\Field + */ + 'inline' => function (Field $field) { + // List of valid inline elements taken from: https://developer.mozilla.org/de/docs/Web/HTML/Inline_elemente + // Obsolete elements, script tags, image maps and form elements have + // been excluded for safety reasons and as they are most likely not + // needed in most cases. + $field->value = strip_tags($field->value, '
'); + return $field; + }, + + /** + * Converts the field content from Markdown/Kirbytext to valid HTML + * + * @param \Kirby\Cms\Field $field + * @return \Kirby\Cms\Field + */ + 'kirbytext' => function (Field $field) use ($app) { + $field->value = $app->kirbytext($field->value, [ + 'parent' => $field->parent(), + 'field' => $field + ]); + + return $field; + }, + + /** + * Converts the field content from inline Markdown/Kirbytext + * to valid HTML + * @since 3.1.0 + * + * @param \Kirby\Cms\Field $field + * @return \Kirby\Cms\Field + */ + 'kirbytextinline' => function (Field $field) use ($app) { + $field->value = $app->kirbytext($field->value, [ + 'parent' => $field->parent(), + 'field' => $field + ], true); + + return $field; + }, + + /** + * Parses all KirbyTags without also parsing Markdown + * + * @param \Kirby\Cms\Field $field + * @return \Kirby\Cms\Field + */ + 'kirbytags' => function (Field $field) use ($app) { + $field->value = $app->kirbytags($field->value, [ + 'parent' => $field->parent(), + 'field' => $field + ]); + + return $field; + }, + + /** + * Converts the field content to lowercase + * + * @param \Kirby\Cms\Field $field + * @return \Kirby\Cms\Field + */ + 'lower' => function (Field $field) { + $field->value = Str::lower($field->value); + return $field; + }, + + /** + * Converts markdown to valid HTML + * + * @param \Kirby\Cms\Field $field + * @return \Kirby\Cms\Field + */ + 'markdown' => function (Field $field) use ($app) { + $field->value = $app->markdown($field->value); + return $field; + }, + + /** + * Converts all line breaks in the field content to `
` tags. + * @since 3.3.0 + * + * @param \Kirby\Cms\Field $field + * @return \Kirby\Cms\Field + */ + 'nl2br' => function (Field $field) { + $field->value = nl2br($field->value, false); + return $field; + }, + + /** + * Uses the field value as Kirby query + * + * @param \Kirby\Cms\Field $field + * @param string|null $expect + * @return mixed + */ + 'query' => function (Field $field, string $expect = null) use ($app) { + if ($parent = $field->parent()) { + return $parent->query($field->value, $expect); + } + + return Str::query($field->value, [ + 'kirby' => $app, + 'site' => $app->site(), + 'page' => $app->page() + ]); + }, + + /** + * It parses any queries found in the field value. + * + * @param \Kirby\Cms\Field $field + * @param array $data + * @param string $fallback Fallback for tokens in the template that cannot be replaced + * @return \Kirby\Cms\Field + */ + 'replace' => function (Field $field, array $data = [], string $fallback = '') use ($app) { + if ($parent = $field->parent()) { + $field->value = $field->parent()->toString($field->value, $data, $fallback); + } else { + $field->value = Str::template($field->value, array_replace([ + 'kirby' => $app, + 'site' => $app->site(), + 'page' => $app->page() + ], $data), $fallback); + } + + return $field; + }, + + /** + * Cuts the string after the given length and + * adds "…" if it is longer + * + * @param \Kirby\Cms\Field $field + * @param int $length The number of characters in the string + * @param string $appendix An optional replacement for the missing rest + * @return \Kirby\Cms\Field + */ + 'short' => function (Field $field, int $length, string $appendix = '…') { + $field->value = Str::short($field->value, $length, $appendix); + return $field; + }, + + /** + * Converts the field content to a slug + * + * @param \Kirby\Cms\Field $field + * @return \Kirby\Cms\Field + */ + 'slug' => function (Field $field) { + $field->value = Str::slug($field->value); + return $field; + }, + + /** + * Applies SmartyPants to the field + * + * @param \Kirby\Cms\Field $field + * @return \Kirby\Cms\Field + */ + 'smartypants' => function (Field $field) use ($app) { + $field->value = $app->smartypants($field->value); + return $field; + }, + + /** + * Splits the field content into an array + * + * @param \Kirby\Cms\Field $field + * @return array + */ + 'split' => function (Field $field, $separator = ',') { + return Str::split((string)$field->value, $separator); + }, + + /** + * Converts the field content to uppercase + * + * @param \Kirby\Cms\Field $field + * @return \Kirby\Cms\Field + */ + 'upper' => function (Field $field) { + $field->value = Str::upper($field->value); + return $field; + }, + + /** + * Avoids typographical widows in strings by replacing + * the last space with ` ` + * + * @param \Kirby\Cms\Field $field + * @return \Kirby\Cms\Field + */ + 'widont' => function (Field $field) { + $field->value = Str::widont($field->value); + return $field; + }, + + /** + * Converts the field content to valid XML + * + * @param \Kirby\Cms\Field $field + * @return \Kirby\Cms\Field + */ + 'xml' => function (Field $field) { + $field->value = Xml::encode($field->value); + return $field; + }, + + // aliases + + /** + * Parses yaml in the field content and returns an array + * + * @param \Kirby\Cms\Field $field + * @return array + */ + 'yaml' => function (Field $field): array { + return $field->toData('yaml'); + }, + + ]; +}; diff --git a/kirby/config/presets/files.php b/kirby/config/presets/files.php new file mode 100644 index 0000000..fe0a7e5 --- /dev/null +++ b/kirby/config/presets/files.php @@ -0,0 +1,24 @@ + [ + 'headline' => $props['headline'] ?? t('files'), + 'type' => 'files', + 'layout' => $props['layout'] ?? 'cards', + 'template' => $props['template'] ?? null, + 'image' => $props['image'] ?? null, + 'info' => '{{ file.dimensions }}' + ] + ]; + + // remove global options + unset( + $props['headline'], + $props['layout'], + $props['template'], + $props['image'] + ); + + return $props; +}; diff --git a/kirby/config/presets/page.php b/kirby/config/presets/page.php new file mode 100644 index 0000000..62df5de --- /dev/null +++ b/kirby/config/presets/page.php @@ -0,0 +1,72 @@ + $props + ]; + } + + return array_replace_recursive($defaults, $props); + }; + + if (empty($props['sidebar']) === false) { + $sidebar = $props['sidebar']; + } else { + $sidebar = []; + + $pages = $props['pages'] ?? []; + $files = $props['files'] ?? []; + + if ($pages !== false) { + $sidebar['pages'] = $section([ + 'headline' => t('pages'), + 'type' => 'pages', + 'status' => 'all', + 'layout' => 'list', + ], $pages); + } + + if ($files !== false) { + $sidebar['files'] = $section([ + 'headline' => t('files'), + 'type' => 'files', + 'layout' => 'list' + ], $files); + } + } + + if (empty($sidebar) === true) { + $props['fields'] = $props['fields'] ?? []; + + unset( + $props['files'], + $props['pages'] + ); + } else { + $props['columns'] = [ + [ + 'width' => '2/3', + 'fields' => $props['fields'] ?? [] + ], + [ + 'width' => '1/3', + 'sections' => $sidebar + ], + ]; + + unset( + $props['fields'], + $props['files'], + $props['pages'], + $props['sidebar'] + ); + } + + return $props; +}; diff --git a/kirby/config/presets/pages.php b/kirby/config/presets/pages.php new file mode 100644 index 0000000..5bba76b --- /dev/null +++ b/kirby/config/presets/pages.php @@ -0,0 +1,57 @@ + $headline, + 'type' => 'pages', + 'layout' => 'list', + 'status' => $status + ]; + + if ($props === true) { + $props = []; + } + + if (is_string($props) === true) { + $props = [ + 'headline' => $props + ]; + } + + // inject the global templates definition + if (empty($templates) === false) { + $props['templates'] = $props['templates'] ?? $templates; + } + + return array_replace_recursive($defaults, $props); + }; + + $sections = []; + + $drafts = $props['drafts'] ?? []; + $unlisted = $props['unlisted'] ?? false; + $listed = $props['listed'] ?? []; + + + if ($drafts !== false) { + $sections['drafts'] = $section(t('pages.status.draft'), 'drafts', $drafts); + } + + if ($unlisted !== false) { + $sections['unlisted'] = $section(t('pages.status.unlisted'), 'unlisted', $unlisted); + } + + if ($listed !== false) { + $sections['listed'] = $section(t('pages.status.listed'), 'listed', $listed); + } + + // cleaning up + unset($props['drafts'], $props['unlisted'], $props['listed'], $props['templates']); + + return array_merge($props, ['sections' => $sections]); +}; diff --git a/kirby/config/roots.php b/kirby/config/roots.php new file mode 100644 index 0000000..d4f5b20 --- /dev/null +++ b/kirby/config/roots.php @@ -0,0 +1,93 @@ + function (array $roots) { + return realpath(__DIR__ . '/../'); + }, + + // i18n + 'i18n' => function (array $roots) { + return $roots['kirby'] . '/i18n'; + }, + 'i18n:translations' => function (array $roots) { + return $roots['i18n'] . '/translations'; + }, + 'i18n:rules' => function (array $roots) { + return $roots['i18n'] . '/rules'; + }, + + // index + 'index' => function (array $roots) { + return realpath(__DIR__ . '/../../'); + }, + + // assets + 'assets' => function (array $roots) { + return $roots['index'] . '/assets'; + }, + + // content + 'content' => function (array $roots) { + return $roots['index'] . '/content'; + }, + + // media + 'media' => function (array $roots) { + return $roots['index'] . '/media'; + }, + + // panel + 'panel' => function (array $roots) { + return $roots['kirby'] . '/panel'; + }, + + // site + 'site' => function (array $roots) { + return $roots['index'] . '/site'; + }, + 'accounts' => function (array $roots) { + return $roots['site'] . '/accounts'; + }, + 'blueprints' => function (array $roots) { + return $roots['site'] . '/blueprints'; + }, + 'cache' => function (array $roots) { + return $roots['site'] . '/cache'; + }, + 'collections' => function (array $roots) { + return $roots['site'] . '/collections'; + }, + 'config' => function (array $roots) { + return $roots['site'] . '/config'; + }, + 'controllers' => function (array $roots) { + return $roots['site'] . '/controllers'; + }, + 'languages' => function (array $roots) { + return $roots['site'] . '/languages'; + }, + 'logs' => function (array $roots) { + return $roots['site'] . '/logs'; + }, + 'models' => function (array $roots) { + return $roots['site'] . '/models'; + }, + 'plugins' => function (array $roots) { + return $roots['site'] . '/plugins'; + }, + 'sessions' => function (array $roots) { + return $roots['site'] . '/sessions'; + }, + 'snippets' => function (array $roots) { + return $roots['site'] . '/snippets'; + }, + 'templates' => function (array $roots) { + return $roots['site'] . '/templates'; + }, + + // blueprints + 'roles' => function (array $roots) { + return $roots['blueprints'] . '/users'; + }, +]; diff --git a/kirby/config/routes.php b/kirby/config/routes.php new file mode 100644 index 0000000..6e1d418 --- /dev/null +++ b/kirby/config/routes.php @@ -0,0 +1,151 @@ +option('api.slug', 'api'); + $panel = $kirby->option('panel.slug', 'panel'); + $index = $kirby->url('index'); + $media = $kirby->url('media'); + + if (Str::startsWith($media, $index) === true) { + $media = Str::after($media, $index); + } else { + // media URL is outside of the site, we can't make routing work; + // fall back to the standard media route + $media = 'media'; + } + + /** + * Before routes are running before the + * plugin routes and cannot be overwritten by + * plugins. + */ + $before = [ + [ + 'pattern' => $api . '/(:all)', + 'method' => 'ALL', + 'env' => 'api', + 'action' => function ($path = null) use ($kirby) { + if ($kirby->option('api') === false) { + return null; + } + + $request = $kirby->request(); + + return $kirby->api()->render($path, $this->method(), [ + 'body' => $request->body()->toArray(), + 'files' => $request->files()->toArray(), + 'headers' => $request->headers(), + 'query' => $request->query()->toArray(), + ]); + } + ], + [ + 'pattern' => $media . '/plugins/index.(css|js)', + 'env' => 'media', + 'action' => function (string $type) use ($kirby) { + $plugins = new PanelPlugins(); + + return $kirby + ->response() + ->type($type) + ->body($plugins->read($type)); + } + ], + [ + 'pattern' => $media . '/plugins/(:any)/(:any)/(:all).(css|map|gif|js|mjs|jpg|png|svg|webp|avif|woff2|woff|json)', + 'env' => 'media', + 'action' => function (string $provider, string $pluginName, string $filename, string $extension) { + return PluginAssets::resolve($provider . '/' . $pluginName, $filename . '.' . $extension); + } + ], + [ + 'pattern' => $panel . '/(:all?)', + 'env' => 'panel', + 'action' => function () use ($kirby) { + if ($kirby->option('panel') === false) { + return null; + } + + return Panel::render($kirby); + } + ], + [ + 'pattern' => $media . '/pages/(:all)/(:any)/(:any)', + 'env' => 'media', + 'action' => function ($path, $hash, $filename) use ($kirby) { + return Media::link($kirby->page($path), $hash, $filename); + } + ], + [ + 'pattern' => $media . '/site/(:any)/(:any)', + 'env' => 'media', + 'action' => function ($hash, $filename) use ($kirby) { + return Media::link($kirby->site(), $hash, $filename); + } + ], + [ + 'pattern' => $media . '/users/(:any)/(:any)/(:any)', + 'env' => 'media', + 'action' => function ($id, $hash, $filename) use ($kirby) { + return Media::link($kirby->user($id), $hash, $filename); + } + ], + [ + 'pattern' => $media . '/assets/(:all)/(:any)/(:any)', + 'env' => 'media', + 'action' => function ($path, $hash, $filename) { + return Media::thumb($path, $hash, $filename); + } + ] + ]; + + // Multi-language setup + if ($kirby->multilang() === true) { + $after = LanguageRoutes::create($kirby); + } else { + + // Single-language home + $after[] = [ + 'pattern' => '', + 'method' => 'ALL', + 'env' => 'site', + 'action' => function () use ($kirby) { + return $kirby->resolve(); + } + ]; + + // redirect the home page folder to the real homepage + $after[] = [ + 'pattern' => $kirby->option('home', 'home'), + 'method' => 'ALL', + 'env' => 'site', + 'action' => function () use ($kirby) { + return $kirby + ->response() + ->redirect($kirby->site()->url()); + } + ]; + + // Single-language subpages + $after[] = [ + 'pattern' => '(:all)', + 'method' => 'ALL', + 'env' => 'site', + 'action' => function (string $path) use ($kirby) { + return $kirby->resolve($path); + } + ]; + } + + return [ + 'before' => $before, + 'after' => $after + ]; +}; diff --git a/kirby/config/sections/fields.php b/kirby/config/sections/fields.php new file mode 100644 index 0000000..c9a2661 --- /dev/null +++ b/kirby/config/sections/fields.php @@ -0,0 +1,56 @@ + [ + 'fields' => function (array $fields = []) { + return $fields; + } + ], + 'computed' => [ + 'form' => function () { + $fields = $this->fields; + $disabled = $this->model->permissions()->update() === false; + $content = $this->model->content()->toArray(); + + if ($disabled === true) { + foreach ($fields as $key => $props) { + $fields[$key]['disabled'] = true; + } + } + + return new Form([ + 'fields' => $fields, + 'values' => $content, + 'model' => $this->model, + 'strict' => true + ]); + }, + 'fields' => function () { + $fields = $this->form->fields()->toArray(); + + if (is_a($this->model, 'Kirby\Cms\Page') === true || is_a($this->model, 'Kirby\Cms\Site') === true) { + // the title should never be updated directly via + // fields section to avoid conflicts with the rename dialog + unset($fields['title']); + } + + foreach ($fields as $index => $props) { + unset($fields[$index]['value']); + } + + return $fields; + } + ], + 'methods' => [ + 'errors' => function () { + return $this->form->errors(); + } + ], + 'toArray' => function () { + return [ + 'fields' => $this->fields, + ]; + } +]; diff --git a/kirby/config/sections/files.php b/kirby/config/sections/files.php new file mode 100644 index 0000000..4dd1387 --- /dev/null +++ b/kirby/config/sections/files.php @@ -0,0 +1,251 @@ + [ + 'empty', + 'headline', + 'help', + 'layout', + 'min', + 'max', + 'pagination', + 'parent', + ], + 'props' => [ + /** + * Enables/disables reverse sorting + */ + 'flip' => function (bool $flip = false) { + return $flip; + }, + /** + * Image options to control the source and look of file previews + */ + 'image' => function ($image = null) { + return $image ?? []; + }, + /** + * Optional info text setup. Info text is shown on the right (lists) or below (cards) the filename. + */ + 'info' => function ($info = null) { + return I18n::translate($info, $info); + }, + /** + * The size option controls the size of cards. By default cards are auto-sized and the cards grid will always fill the full width. With a size you can disable auto-sizing. Available sizes: `tiny`, `small`, `medium`, `large`, `huge` + */ + 'size' => function (string $size = 'auto') { + return $size; + }, + /** + * Enables/disables manual sorting + */ + 'sortable' => function (bool $sortable = true) { + return $sortable; + }, + /** + * Overwrites manual sorting and sorts by the given field and sorting direction (i.e. `filename desc`) + */ + 'sortBy' => function (string $sortBy = null) { + return $sortBy; + }, + /** + * Filters all files by template and also sets the template, which will be used for all uploads + */ + 'template' => function (string $template = null) { + return $template; + }, + /** + * Setup for the main text in the list or cards. By default this will display the filename. + */ + 'text' => function ($text = '{{ file.filename }}') { + return I18n::translate($text, $text); + } + ], + 'computed' => [ + 'accept' => function () { + if ($this->template) { + $file = new File([ + 'filename' => 'tmp', + 'template' => $this->template + ]); + + return $file->blueprint()->acceptMime(); + } + + return null; + }, + 'parent' => function () { + return $this->parentModel(); + }, + 'files' => function () { + $files = $this->parent->files()->template($this->template); + + // filter out all protected files + $files = $files->filter('isReadable', true); + + if ($this->sortBy) { + $files = $files->sort(...$files::sortArgs($this->sortBy)); + } else { + $files = $files->sort('sort', 'asc', 'filename', 'asc'); + } + + // flip + if ($this->flip === true) { + $files = $files->flip(); + } + + // apply the default pagination + $files = $files->paginate([ + 'page' => $this->page, + 'limit' => $this->limit, + 'method' => 'none' // the page is manually provided + ]); + + return $files; + }, + 'data' => function () { + $data = []; + + // the drag text needs to be absolute when the files come from + // a different parent model + $dragTextAbsolute = $this->model->is($this->parent) === false; + + foreach ($this->files as $file) { + $image = $file->panelImage($this->image); + + // escape the default text + // TODO: no longer needed in 3.6 + $text = $file->toString($this->text); + if ($this->text === '{{ file.filename }}') { + $text = Escape::html($text); + } + + $data[] = [ + 'dragText' => $file->dragText('auto', $dragTextAbsolute), + 'extension' => $file->extension(), + 'filename' => $file->filename(), + 'id' => $file->id(), + 'icon' => $file->panelIcon($image), + 'image' => $image, + 'info' => $file->toString($this->info ?? false), + 'link' => $file->panelUrl(true), + 'mime' => $file->mime(), + 'parent' => $file->parent()->panelPath(), + 'text' => $text, + 'url' => $file->url(), + ]; + } + + return $data; + }, + 'total' => function () { + return $this->files->pagination()->total(); + }, + 'errors' => function () { + $errors = []; + + if ($this->validateMax() === false) { + $errors['max'] = I18n::template('error.section.files.max.' . I18n::form($this->max), [ + 'max' => $this->max, + 'section' => $this->headline + ]); + } + + if ($this->validateMin() === false) { + $errors['min'] = I18n::template('error.section.files.min.' . I18n::form($this->min), [ + 'min' => $this->min, + 'section' => $this->headline + ]); + } + + if (empty($errors) === true) { + return []; + } + + return [ + $this->name => [ + 'label' => $this->headline, + 'message' => $errors, + ] + ]; + }, + 'link' => function () { + $modelLink = $this->model->panelUrl(true); + $parentLink = $this->parent->panelUrl(true); + + if ($modelLink !== $parentLink) { + return $parentLink; + } + }, + 'pagination' => function () { + return $this->pagination(); + }, + 'sortable' => function () { + if ($this->sortable === false) { + return false; + } + + if ($this->sortBy !== null) { + return false; + } + + if ($this->flip === true) { + return false; + } + + return true; + }, + 'upload' => function () { + if ($this->isFull() === true) { + return false; + } + + // count all uploaded files + $total = count($this->data); + $max = $this->max ? $this->max - $total : null; + + if ($this->max && $total === $this->max - 1) { + $multiple = false; + } else { + $multiple = true; + } + + $template = $this->template === 'default' ? null : $this->template; + + return [ + 'accept' => $this->accept, + 'multiple' => $multiple, + 'max' => $max, + 'api' => $this->parent->apiUrl(true) . '/files', + 'attributes' => array_filter([ + 'template' => $template + ]) + ]; + } + ], + 'toArray' => function () { + return [ + 'data' => $this->data, + 'errors' => $this->errors, + 'options' => [ + 'accept' => $this->accept, + 'apiUrl' => $this->parent->apiUrl(true), + 'empty' => $this->empty, + 'headline' => $this->headline, + 'help' => $this->help, + 'layout' => $this->layout, + 'link' => $this->link, + 'max' => $this->max, + 'min' => $this->min, + 'size' => $this->size, + 'sortable' => $this->sortable, + 'upload' => $this->upload + ], + 'pagination' => $this->pagination + ]; + } +]; diff --git a/kirby/config/sections/info.php b/kirby/config/sections/info.php new file mode 100644 index 0000000..254a76b --- /dev/null +++ b/kirby/config/sections/info.php @@ -0,0 +1,35 @@ + [ + 'headline', + ], + 'props' => [ + 'text' => function ($text = null) { + return I18n::translate($text, $text); + }, + 'theme' => function (string $theme = null) { + return $theme; + } + ], + 'computed' => [ + 'text' => function () { + if ($this->text) { + $text = $this->model()->toString($this->text); + $text = $this->kirby()->kirbytext($text); + return $text; + } + }, + ], + 'toArray' => function () { + return [ + 'options' => [ + 'headline' => $this->headline, + 'text' => $this->text, + 'theme' => $this->theme + ] + ]; + } +]; diff --git a/kirby/config/sections/mixins/empty.php b/kirby/config/sections/mixins/empty.php new file mode 100644 index 0000000..1c58194 --- /dev/null +++ b/kirby/config/sections/mixins/empty.php @@ -0,0 +1,21 @@ + [ + /** + * Sets the text for the empty state box + */ + 'empty' => function ($empty = null) { + return I18n::translate($empty, $empty); + } + ], + 'computed' => [ + 'empty' => function () { + if ($this->empty) { + return $this->model()->toString($this->empty); + } + } + ] +]; diff --git a/kirby/config/sections/mixins/headline.php b/kirby/config/sections/mixins/headline.php new file mode 100644 index 0000000..f4bb7e1 --- /dev/null +++ b/kirby/config/sections/mixins/headline.php @@ -0,0 +1,23 @@ + [ + /** + * The headline for the section. This can be a simple string or a template with additional info from the parent page. + */ + 'headline' => function ($headline = null) { + return I18n::translate($headline, $headline); + } + ], + 'computed' => [ + 'headline' => function () { + if ($this->headline) { + return $this->model()->toString($this->headline); + } + + return ucfirst($this->name); + } + ] +]; diff --git a/kirby/config/sections/mixins/help.php b/kirby/config/sections/mixins/help.php new file mode 100644 index 0000000..80f42ee --- /dev/null +++ b/kirby/config/sections/mixins/help.php @@ -0,0 +1,23 @@ + [ + /** + * Sets the help text + */ + 'help' => function ($help = null) { + return I18n::translate($help, $help); + } + ], + 'computed' => [ + 'help' => function () { + if ($this->help) { + $help = $this->model()->toString($this->help); + $help = $this->kirby()->kirbytext($help); + return $help; + } + } + ] +]; diff --git a/kirby/config/sections/mixins/layout.php b/kirby/config/sections/mixins/layout.php new file mode 100644 index 0000000..4a7a621 --- /dev/null +++ b/kirby/config/sections/mixins/layout.php @@ -0,0 +1,12 @@ + [ + /** + * Section layout. Available layout methods: `list`, `cards`. + */ + 'layout' => function (string $layout = 'list') { + return $layout === 'cards' ? 'cards' : 'list'; + } + ] +]; diff --git a/kirby/config/sections/mixins/max.php b/kirby/config/sections/mixins/max.php new file mode 100644 index 0000000..5ce303c --- /dev/null +++ b/kirby/config/sections/mixins/max.php @@ -0,0 +1,28 @@ + [ + /** + * Sets the maximum number of allowed entries in the section + */ + 'max' => function (int $max = null) { + return $max; + } + ], + 'methods' => [ + 'isFull' => function () { + if ($this->max) { + return $this->total >= $this->max; + } + + return false; + }, + 'validateMax' => function () { + if ($this->max && $this->total > $this->max) { + return false; + } + + return true; + } + ] +]; diff --git a/kirby/config/sections/mixins/min.php b/kirby/config/sections/mixins/min.php new file mode 100644 index 0000000..bfc495d --- /dev/null +++ b/kirby/config/sections/mixins/min.php @@ -0,0 +1,21 @@ + [ + /** + * Sets the minimum number of required entries in the section + */ + 'min' => function (int $min = null) { + return $min; + } + ], + 'methods' => [ + 'validateMin' => function () { + if ($this->min && $this->min > $this->total) { + return false; + } + + return true; + } + ] +]; diff --git a/kirby/config/sections/mixins/pagination.php b/kirby/config/sections/mixins/pagination.php new file mode 100644 index 0000000..8bf3dee --- /dev/null +++ b/kirby/config/sections/mixins/pagination.php @@ -0,0 +1,36 @@ + [ + /** + * Sets the number of items per page. If there are more items the pagination navigation will be shown at the bottom of the section. + */ + 'limit' => function (int $limit = 20) { + return $limit; + }, + /** + * Sets the default page for the pagination. This will overwrite default pagination. + */ + 'page' => function (int $page = null) { + return get('page', $page); + }, + ], + 'methods' => [ + 'pagination' => function () { + $pagination = new Pagination([ + 'limit' => $this->limit, + 'page' => $this->page, + 'total' => $this->total + ]); + + return [ + 'limit' => $pagination->limit(), + 'offset' => $pagination->offset(), + 'page' => $pagination->page(), + 'total' => $pagination->total(), + ]; + }, + ] +]; diff --git a/kirby/config/sections/mixins/parent.php b/kirby/config/sections/mixins/parent.php new file mode 100644 index 0000000..3534acf --- /dev/null +++ b/kirby/config/sections/mixins/parent.php @@ -0,0 +1,43 @@ + [ + /** + * Sets the query to a parent to find items for the list + */ + 'parent' => function (string $parent = null) { + return $parent; + } + ], + 'methods' => [ + 'parentModel' => function () { + $parent = $this->parent; + + if (is_string($parent) === true) { + $query = $parent; + $parent = $this->model->query($query); + + if (!$parent) { + throw new Exception('The parent for the query "' . $query . '" cannot be found in the section "' . $this->name() . '"'); + } + + if ( + is_a($parent, 'Kirby\Cms\Page') === false && + is_a($parent, 'Kirby\Cms\Site') === false && + is_a($parent, 'Kirby\Cms\File') === false && + is_a($parent, 'Kirby\Cms\User') === false + ) { + throw new Exception('The parent for the section "' . $this->name() . '" has to be a page, site or user object'); + } + } + + if ($parent === null) { + return $this->model; + } + + return $parent; + } + ] +]; diff --git a/kirby/config/sections/pages.php b/kirby/config/sections/pages.php new file mode 100644 index 0000000..b8b8814 --- /dev/null +++ b/kirby/config/sections/pages.php @@ -0,0 +1,308 @@ + [ + 'empty', + 'headline', + 'help', + 'layout', + 'min', + 'max', + 'pagination', + 'parent' + ], + 'props' => [ + /** + * Optional array of templates that should only be allowed to add + * or `false` to completely disable page creation + */ + 'create' => function ($create = null) { + return $create; + }, + /** + * Enables/disables reverse sorting + */ + 'flip' => function (bool $flip = false) { + return $flip; + }, + /** + * Image options to control the source and look of page previews + */ + 'image' => function ($image = null) { + return $image ?? []; + }, + /** + * Optional info text setup. Info text is shown on the right (lists) or below (cards) the page title. + */ + 'info' => function ($info = null) { + return I18n::translate($info, $info); + }, + /** + * The size option controls the size of cards. By default cards are auto-sized and the cards grid will always fill the full width. With a size you can disable auto-sizing. Available sizes: `tiny`, `small`, `medium`, `large`, `huge` + */ + 'size' => function (string $size = 'auto') { + return $size; + }, + /** + * Enables/disables manual sorting + */ + 'sortable' => function (bool $sortable = true) { + return $sortable; + }, + /** + * Overwrites manual sorting and sorts by the given field and sorting direction (i.e. `date desc`) + */ + 'sortBy' => function (string $sortBy = null) { + return $sortBy; + }, + /** + * Filters pages by their status. Available status settings: `draft`, `unlisted`, `listed`, `published`, `all`. + */ + 'status' => function (string $status = '') { + if ($status === 'drafts') { + $status = 'draft'; + } + + if (in_array($status, ['all', 'draft', 'published', 'listed', 'unlisted']) === false) { + $status = 'all'; + } + + return $status; + }, + /** + * Filters the list by templates and sets template options when adding new pages to the section. + */ + 'templates' => function ($templates = null) { + return A::wrap($templates ?? $this->template); + }, + /** + * Setup for the main text in the list or cards. By default this will display the page title. + */ + 'text' => function ($text = '{{ page.title }}') { + return I18n::translate($text, $text); + } + ], + 'computed' => [ + 'parent' => function () { + return $this->parentModel(); + }, + 'pages' => function () { + switch ($this->status) { + case 'draft': + $pages = $this->parent->drafts(); + break; + case 'listed': + $pages = $this->parent->children()->listed(); + break; + case 'published': + $pages = $this->parent->children(); + break; + case 'unlisted': + $pages = $this->parent->children()->unlisted(); + break; + default: + $pages = $this->parent->childrenAndDrafts(); + } + + // loop for the best performance + foreach ($pages->data as $id => $page) { + + // remove all protected pages + if ($page->isReadable() === false) { + unset($pages->data[$id]); + continue; + } + + // filter by all set templates + if ($this->templates && in_array($page->intendedTemplate()->name(), $this->templates) === false) { + unset($pages->data[$id]); + continue; + } + } + + // sort + if ($this->sortBy) { + $pages = $pages->sort(...$pages::sortArgs($this->sortBy)); + } + + // flip + if ($this->flip === true) { + $pages = $pages->flip(); + } + + // pagination + $pages = $pages->paginate([ + 'page' => $this->page, + 'limit' => $this->limit, + 'method' => 'none' // the page is manually provided + ]); + + return $pages; + }, + 'total' => function () { + return $this->pages->pagination()->total(); + }, + 'data' => function () { + $data = []; + + foreach ($this->pages as $item) { + $permissions = $item->permissions(); + $image = $item->panelImage($this->image); + + // escape the default text + // TODO: no longer needed in 3.6 + $text = $item->toString($this->text); + if ($this->text === '{{ page.title }}') { + $text = Escape::html($text); + } + + $data[] = [ + 'id' => $item->id(), + 'dragText' => $item->dragText(), + 'text' => $text, + 'info' => $item->toString($this->info ?? false), + 'parent' => $item->parentId(), + 'icon' => $item->panelIcon($image), + 'image' => $image, + 'link' => $item->panelUrl(true), + 'status' => $item->status(), + 'permissions' => [ + 'sort' => $permissions->can('sort'), + 'changeSlug' => $permissions->can('changeSlug'), + 'changeStatus' => $permissions->can('changeStatus'), + 'changeTitle' => $permissions->can('changeTitle') + ] + ]; + } + + return $data; + }, + 'errors' => function () { + $errors = []; + + if ($this->validateMax() === false) { + $errors['max'] = I18n::template('error.section.pages.max.' . I18n::form($this->max), [ + 'max' => $this->max, + 'section' => $this->headline + ]); + } + + if ($this->validateMin() === false) { + $errors['min'] = I18n::template('error.section.pages.min.' . I18n::form($this->min), [ + 'min' => $this->min, + 'section' => $this->headline + ]); + } + + if (empty($errors) === true) { + return []; + } + + return [ + $this->name => [ + 'label' => $this->headline, + 'message' => $errors, + ] + ]; + }, + 'add' => function () { + if ($this->create === false) { + return false; + } + + if (in_array($this->status, ['draft', 'all']) === false) { + return false; + } + + if ($this->isFull() === true) { + return false; + } + + return true; + }, + 'link' => function () { + $modelLink = $this->model->panelUrl(true); + $parentLink = $this->parent->panelUrl(true); + + if ($modelLink !== $parentLink) { + return $parentLink; + } + }, + 'pagination' => function () { + return $this->pagination(); + }, + 'sortable' => function () { + if (in_array($this->status, ['listed', 'published', 'all']) === false) { + return false; + } + + if ($this->sortable === false) { + return false; + } + + if ($this->sortBy !== null) { + return false; + } + + if ($this->flip === true) { + return false; + } + + return true; + } + ], + 'methods' => [ + 'blueprints' => function () { + $blueprints = []; + $templates = empty($this->create) === false ? A::wrap($this->create) : $this->templates; + + if (empty($templates) === true) { + $templates = $this->kirby()->blueprints(); + } + + // convert every template to a usable option array + // for the template select box + foreach ($templates as $template) { + try { + $props = Blueprint::load('pages/' . $template); + + $blueprints[] = [ + 'name' => basename($props['name']), + 'title' => $props['title'], + ]; + } catch (Throwable $e) { + $blueprints[] = [ + 'name' => basename($template), + 'title' => ucfirst($template), + ]; + } + } + + return $blueprints; + } + ], + 'toArray' => function () { + return [ + 'data' => $this->data, + 'errors' => $this->errors, + 'options' => [ + 'add' => $this->add, + 'empty' => $this->empty, + 'headline' => $this->headline, + 'help' => $this->help, + 'layout' => $this->layout, + 'link' => $this->link, + 'max' => $this->max, + 'min' => $this->min, + 'size' => $this->size, + 'sortable' => $this->sortable + ], + 'pagination' => $this->pagination, + ]; + } +]; diff --git a/kirby/config/setup.php b/kirby/config/setup.php new file mode 100644 index 0000000..eadb131 --- /dev/null +++ b/kirby/config/setup.php @@ -0,0 +1,36 @@ + $blocksRoot . '/code/code.php', + 'blocks/gallery' => $blocksRoot . '/gallery/gallery.php', + 'blocks/heading' => $blocksRoot . '/heading/heading.php', + 'blocks/image' => $blocksRoot . '/image/image.php', + 'blocks/list' => $blocksRoot . '/list/list.php', + 'blocks/markdown' => $blocksRoot . '/markdown/markdown.php', + 'blocks/quote' => $blocksRoot . '/quote/quote.php', + 'blocks/table' => $blocksRoot . '/table/table.php', + 'blocks/text' => $blocksRoot . '/text/text.php', + 'blocks/video' => $blocksRoot . '/video/video.php', +]; diff --git a/kirby/config/tags.php b/kirby/config/tags.php new file mode 100644 index 0000000..00168f7 --- /dev/null +++ b/kirby/config/tags.php @@ -0,0 +1,353 @@ + [ + 'attr' => [], + 'html' => function ($tag) { + return strtolower($tag->date) === 'year' ? date('Y') : date($tag->date); + } + ], + + /** + * Email + */ + 'email' => [ + 'attr' => [ + 'class', + 'rel', + 'target', + 'text', + 'title' + ], + 'html' => function ($tag) { + return Html::email($tag->value, $tag->text, [ + 'class' => $tag->class, + 'rel' => $tag->rel, + 'target' => $tag->target, + 'title' => $tag->title, + ]); + } + ], + + /** + * File + */ + 'file' => [ + 'attr' => [ + 'class', + 'download', + 'rel', + 'target', + 'text', + 'title' + ], + 'html' => function ($tag) { + if (!$file = $tag->file($tag->value)) { + return $tag->text; + } + + // use filename if the text is empty and make sure to + // ignore markdown italic underscores in filenames + if (empty($tag->text) === true) { + $tag->text = str_replace('_', '\_', $file->filename()); + } + + return Html::a($file->url(), $tag->text, [ + 'class' => $tag->class, + 'download' => $tag->download !== 'false', + 'rel' => $tag->rel, + 'target' => $tag->target, + 'title' => $tag->title, + ]); + } + ], + + /** + * Gist + */ + 'gist' => [ + 'attr' => [ + 'file' + ], + 'html' => function ($tag) { + return Html::gist($tag->value, $tag->file); + } + ], + + /** + * Image + */ + 'image' => [ + 'attr' => [ + 'alt', + 'caption', + 'class', + 'height', + 'imgclass', + 'link', + 'linkclass', + 'rel', + 'target', + 'title', + 'width' + ], + 'html' => function ($tag) { + if ($tag->file = $tag->file($tag->value)) { + $tag->src = $tag->file->url(); + $tag->alt = $tag->alt ?? $tag->file->alt()->or(' ')->value(); + $tag->title = $tag->title ?? $tag->file->title()->value(); + $tag->caption = $tag->caption ?? $tag->file->caption()->value(); + } else { + $tag->src = Url::to($tag->value); + } + + $link = function ($img) use ($tag) { + if (empty($tag->link) === true) { + return $img; + } + + if ($link = $tag->file($tag->link)) { + $link = $link->url(); + } else { + $link = $tag->link === 'self' ? $tag->src : $tag->link; + } + + return Html::a($link, [$img], [ + 'rel' => $tag->rel, + 'class' => $tag->linkclass, + 'target' => $tag->target + ]); + }; + + $image = Html::img($tag->src, [ + 'width' => $tag->width, + 'height' => $tag->height, + 'class' => $tag->imgclass, + 'title' => $tag->title, + 'alt' => $tag->alt ?? ' ' + ]); + + if ($tag->kirby()->option('kirbytext.image.figure', true) === false) { + return $link($image); + } + + // render KirbyText in caption + if ($tag->caption) { + $tag->caption = [$tag->kirby()->kirbytext($tag->caption, [], true)]; + } + + return Html::figure([ $link($image) ], $tag->caption, [ + 'class' => $tag->class + ]); + } + ], + + /** + * Link + */ + 'link' => [ + 'attr' => [ + 'class', + 'lang', + 'rel', + 'role', + 'target', + 'title', + 'text', + ], + 'html' => function ($tag) { + if (empty($tag->lang) === false) { + $tag->value = Url::to($tag->value, $tag->lang); + } + + return Html::a($tag->value, $tag->text, [ + 'rel' => $tag->rel, + 'class' => $tag->class, + 'role' => $tag->role, + 'title' => $tag->title, + 'target' => $tag->target, + ]); + } + ], + + /** + * Tel + */ + 'tel' => [ + 'attr' => [ + 'class', + 'rel', + 'text', + 'title' + ], + 'html' => function ($tag) { + return Html::tel($tag->value, $tag->text, [ + 'class' => $tag->class, + 'rel' => $tag->rel, + 'title' => $tag->title + ]); + } + ], + + /** + * Twitter + */ + 'twitter' => [ + 'attr' => [ + 'class', + 'rel', + 'target', + 'text', + 'title' + ], + 'html' => function ($tag) { + + // get and sanitize the username + $username = str_replace('@', '', $tag->value); + + // build the profile url + $url = 'https://twitter.com/' . $username; + + // sanitize the link text + $text = $tag->text ?? '@' . $username; + + // build the final link + return Html::a($url, $text, [ + 'class' => $tag->class, + 'rel' => $tag->rel, + 'target' => $tag->target, + 'title' => $tag->title, + ]); + } + ], + + /** + * Video + */ + 'video' => [ + 'attr' => [ + 'autoplay', + 'caption', + 'controls', + 'class', + 'height', + 'loop', + 'muted', + 'poster', + 'preload', + 'style', + 'width', + ], + 'html' => function ($tag) { + // all available video tag attributes + $availableAttrs = KirbyTag::$types[$tag->type]['attr']; + + // global video tag options + $attrs = $tag->kirby()->option('kirbytext.video', []); + $options = $attrs['options'] ?? []; + + // removes options from attributes + if (isset($attrs['options']) === true) { + unset($attrs['options']); + } + + // injects default values from global options + // applies only defined attributes to safely update tag props + foreach ($attrs as $key => $value) { + if ( + in_array($key, $availableAttrs) === true && + (isset($tag->{$key}) === false || $tag->{$key} === null) + ) { + $tag->{$key} = $value; + } + } + + // checks and gets if poster is local file + if ( + empty($tag->poster) === false && + Str::startsWith($tag->poster, 'http://') !== true && + Str::startsWith($tag->poster, 'https://') !== true + ) { + if ($poster = $tag->file($tag->poster)) { + $tag->poster = $poster->url(); + } + } + + // converts tag attributes to supported formats (listed below) to output correct html + // booleans: autoplay, controls, loop, muted + // strings : height, poster, preload, width + // for ex : `autoplay` will not work if `false` is a `string` instead of a `boolean` + $attrs = [ + 'autoplay' => $autoplay = Str::toType($tag->autoplay, 'bool'), + 'controls' => Str::toType($tag->controls ?? true, 'bool'), + 'height' => $tag->height, + 'loop' => Str::toType($tag->loop, 'bool'), + 'muted' => Str::toType($tag->muted ?? $autoplay, 'bool'), + 'poster' => $tag->poster, + 'preload' => $tag->preload, + 'width' => $tag->width + ]; + + // handles local and remote video file + if ( + Str::startsWith($tag->value, 'http://') !== true && + Str::startsWith($tag->value, 'https://') !== true + ) { + // handles local video file + if ($tag->file = $tag->file($tag->value)) { + $source = Html::tag('source', null, [ + 'src' => $tag->file->url(), + 'type' => $tag->file->mime() + ]); + $video = Html::tag('video', [$source], $attrs); + } + } else { + // firstly handles supported video providers as youtube, vimeo, etc + try { + $video = Html::video( + $tag->value, + $options, + // providers only support width and height attributes + [ + 'height' => $tag->height, + 'width' => $tag->width + ] + ); + } catch (Exception $e) { + // if not one of the supported video providers + // it checks if there is a valid remote video file + $extension = F::extension($tag->value); + $type = F::extensionToType($extension); + $mime = F::extensionToMime($extension); + + if ($type === 'video') { + $source = Html::tag('source', null, [ + 'src' => $tag->value, + 'type' => $mime + ]); + $video = Html::tag('video', [$source], $attrs); + } + } + } + + return Html::figure([$video ?? ''], $tag->caption, [ + 'class' => $tag->class ?? 'video', + 'style' => $tag->style + ]); + } + ], + +]; diff --git a/kirby/config/templates.php b/kirby/config/templates.php new file mode 100644 index 0000000..b699394 --- /dev/null +++ b/kirby/config/templates.php @@ -0,0 +1,8 @@ + __DIR__ . '/templates/emails/auth/login.php', + 'emails/auth/password-reset' => __DIR__ . '/templates/emails/auth/password-reset.php' +]; +// @codeCoverageIgnoreEnd diff --git a/kirby/config/templates/emails/auth/login.php b/kirby/config/templates/emails/auth/login.php new file mode 100644 index 0000000..55d9dce --- /dev/null +++ b/kirby/config/templates/emails/auth/login.php @@ -0,0 +1,8 @@ +language()); diff --git a/kirby/config/templates/emails/auth/password-reset.php b/kirby/config/templates/emails/auth/password-reset.php new file mode 100644 index 0000000..ff1e09c --- /dev/null +++ b/kirby/config/templates/emails/auth/password-reset.php @@ -0,0 +1,8 @@ +language()); diff --git a/kirby/config/urls.php b/kirby/config/urls.php new file mode 100644 index 0000000..1bfe0fd --- /dev/null +++ b/kirby/config/urls.php @@ -0,0 +1,33 @@ + function () { + return Url::index(); + }, + 'base' => function (array $urls) { + return rtrim($urls['index'], '/'); + }, + 'current' => function (array $urls) { + $path = trim($this->path(), '/'); + + if (empty($path) === true) { + return $urls['index']; + } else { + return $urls['base'] . '/' . $path; + } + }, + 'assets' => function (array $urls) { + return $urls['base'] . '/assets'; + }, + 'api' => function (array $urls) { + return $urls['base'] . '/' . ($this->options['api']['slug'] ?? 'api'); + }, + 'media' => function (array $urls) { + return $urls['base'] . '/media'; + }, + 'panel' => function (array $urls) { + return $urls['base'] . '/' . ($this->options['panel']['slug'] ?? 'panel'); + } +]; diff --git a/kirby/dependencies/parsedown-extra/ParsedownExtra.php b/kirby/dependencies/parsedown-extra/ParsedownExtra.php new file mode 100644 index 0000000..c3db03a --- /dev/null +++ b/kirby/dependencies/parsedown-extra/ParsedownExtra.php @@ -0,0 +1,627 @@ +BlockTypes[':'] []= 'DefinitionList'; + $this->BlockTypes['*'] []= 'Abbreviation'; + + # identify footnote definitions before reference definitions + array_unshift($this->BlockTypes['['], 'Footnote'); + + # identify footnote markers before before links + array_unshift($this->InlineTypes['['], 'FootnoteMarker'); + } + + # + # ~ + + public function text($text) + { + $Elements = $this->textElements($text); + + # convert to markup + $markup = $this->elements($Elements); + + # trim line breaks + $markup = trim($markup, "\n"); + + # merge consecutive dl elements + + $markup = preg_replace('/<\/dl>\s+

\s+/', '', $markup); + + # add footnotes + + if (isset($this->DefinitionData['Footnote'])) { + $Element = $this->buildFootnoteElement(); + + $markup .= "\n" . $this->element($Element); + } + + return $markup; + } + + # + # Blocks + # + + # + # Abbreviation + + protected function blockAbbreviation($Line) + { + if (preg_match('/^\*\[(.+?)\]:[ ]*(.+?)[ ]*$/', $Line['text'], $matches)) { + $this->DefinitionData['Abbreviation'][$matches[1]] = $matches[2]; + + $Block = array( + 'hidden' => true, + ); + + return $Block; + } + } + + # + # Footnote + + protected function blockFootnote($Line) + { + if (preg_match('/^\[\^(.+?)\]:[ ]?(.*)$/', $Line['text'], $matches)) { + $Block = array( + 'label' => $matches[1], + 'text' => $matches[2], + 'hidden' => true, + ); + + return $Block; + } + } + + protected function blockFootnoteContinue($Line, $Block) + { + if ($Line['text'][0] === '[' and preg_match('/^\[\^(.+?)\]:/', $Line['text'])) { + return; + } + + if (isset($Block['interrupted'])) { + if ($Line['indent'] >= 4) { + $Block['text'] .= "\n\n" . $Line['text']; + + return $Block; + } + } else { + $Block['text'] .= "\n" . $Line['text']; + + return $Block; + } + } + + protected function blockFootnoteComplete($Block) + { + $this->DefinitionData['Footnote'][$Block['label']] = array( + 'text' => $Block['text'], + 'count' => null, + 'number' => null, + ); + + return $Block; + } + + # + # Definition List + + protected function blockDefinitionList($Line, $Block) + { + if (! isset($Block) or $Block['type'] !== 'Paragraph') { + return; + } + + $Element = array( + 'name' => 'dl', + 'elements' => array(), + ); + + $terms = explode("\n", $Block['element']['handler']['argument']); + + foreach ($terms as $term) { + $Element['elements'] []= array( + 'name' => 'dt', + 'handler' => array( + 'function' => 'lineElements', + 'argument' => $term, + 'destination' => 'elements' + ), + ); + } + + $Block['element'] = $Element; + + $Block = $this->addDdElement($Line, $Block); + + return $Block; + } + + protected function blockDefinitionListContinue($Line, array $Block) + { + if ($Line['text'][0] === ':') { + $Block = $this->addDdElement($Line, $Block); + + return $Block; + } else { + if (isset($Block['interrupted']) and $Line['indent'] === 0) { + return; + } + + if (isset($Block['interrupted'])) { + $Block['dd']['handler']['function'] = 'textElements'; + $Block['dd']['handler']['argument'] .= "\n\n"; + + $Block['dd']['handler']['destination'] = 'elements'; + + unset($Block['interrupted']); + } + + $text = substr($Line['body'], min($Line['indent'], 4)); + + $Block['dd']['handler']['argument'] .= "\n" . $text; + + return $Block; + } + } + + # + # Header + + protected function blockHeader($Line) + { + $Block = parent::blockHeader($Line); + + if (preg_match('/[ #]*{('.$this->regexAttribute.'+)}[ ]*$/', $Block['element']['handler']['argument'], $matches, PREG_OFFSET_CAPTURE)) { + $attributeString = $matches[1][0]; + + $Block['element']['attributes'] = $this->parseAttributeData($attributeString); + + $Block['element']['handler']['argument'] = substr($Block['element']['handler']['argument'], 0, $matches[0][1]); + } + + return $Block; + } + + # + # Markup + + protected function blockMarkup($Line) + { + if ($this->markupEscaped or $this->safeMode) { + return; + } + + if (preg_match('/^<(\w[\w-]*)(?:[ ]*'.$this->regexHtmlAttribute.')*[ ]*(\/)?>/', $Line['text'], $matches)) { + $element = strtolower($matches[1]); + + if (in_array($element, $this->textLevelElements)) { + return; + } + + $Block = array( + 'name' => $matches[1], + 'depth' => 0, + 'element' => array( + 'rawHtml' => $Line['text'], + 'autobreak' => true, + ), + ); + + $length = strlen($matches[0]); + $remainder = substr($Line['text'], $length); + + if (trim($remainder) === '') { + if (isset($matches[2]) or in_array($matches[1], $this->voidElements)) { + $Block['closed'] = true; + $Block['void'] = true; + } + } else { + if (isset($matches[2]) or in_array($matches[1], $this->voidElements)) { + return; + } + if (preg_match('/<\/'.$matches[1].'>[ ]*$/i', $remainder)) { + $Block['closed'] = true; + } + } + + return $Block; + } + } + + protected function blockMarkupContinue($Line, array $Block) + { + if (isset($Block['closed'])) { + return; + } + + if (preg_match('/^<'.$Block['name'].'(?:[ ]*'.$this->regexHtmlAttribute.')*[ ]*>/i', $Line['text'])) { # open + $Block['depth'] ++; + } + + if (preg_match('/(.*?)<\/'.$Block['name'].'>[ ]*$/i', $Line['text'], $matches)) { # close + if ($Block['depth'] > 0) { + $Block['depth'] --; + } else { + $Block['closed'] = true; + } + } + + if (isset($Block['interrupted'])) { + $Block['element']['rawHtml'] .= "\n"; + unset($Block['interrupted']); + } + + $Block['element']['rawHtml'] .= "\n".$Line['body']; + + return $Block; + } + + protected function blockMarkupComplete($Block) + { + if (! isset($Block['void'])) { + $Block['element']['rawHtml'] = $this->processTag($Block['element']['rawHtml']); + } + + return $Block; + } + + # + # Setext + + protected function blockSetextHeader($Line, array $Block = null) + { + $Block = parent::blockSetextHeader($Line, $Block); + + if (preg_match('/[ ]*{('.$this->regexAttribute.'+)}[ ]*$/', $Block['element']['handler']['argument'], $matches, PREG_OFFSET_CAPTURE)) { + $attributeString = $matches[1][0]; + + $Block['element']['attributes'] = $this->parseAttributeData($attributeString); + + $Block['element']['handler']['argument'] = substr($Block['element']['handler']['argument'], 0, $matches[0][1]); + } + + return $Block; + } + + # + # Inline Elements + # + + # + # Footnote Marker + + protected function inlineFootnoteMarker($Excerpt) + { + if (preg_match('/^\[\^(.+?)\]/', $Excerpt['text'], $matches)) { + $name = $matches[1]; + + if (! isset($this->DefinitionData['Footnote'][$name])) { + return; + } + + $this->DefinitionData['Footnote'][$name]['count'] ++; + + if (! isset($this->DefinitionData['Footnote'][$name]['number'])) { + $this->DefinitionData['Footnote'][$name]['number'] = ++ $this->footnoteCount; # » & + } + + $Element = array( + 'name' => 'sup', + 'attributes' => array('id' => 'fnref'.$this->DefinitionData['Footnote'][$name]['count'].':'.$name), + 'element' => array( + 'name' => 'a', + 'attributes' => array('href' => '#fn:'.$name, 'class' => 'footnote-ref'), + 'text' => $this->DefinitionData['Footnote'][$name]['number'], + ), + ); + + return array( + 'extent' => strlen($matches[0]), + 'element' => $Element, + ); + } + } + + private $footnoteCount = 0; + + # + # Link + + protected function inlineLink($Excerpt) + { + $Link = parent::inlineLink($Excerpt); + + $remainder = substr($Excerpt['text'], $Link['extent']); + + if (preg_match('/^[ ]*{('.$this->regexAttribute.'+)}/', $remainder, $matches)) { + $Link['element']['attributes'] += $this->parseAttributeData($matches[1]); + + $Link['extent'] += strlen($matches[0]); + } + + return $Link; + } + + # + # ~ + # + + private $currentAbreviation; + private $currentMeaning; + + protected function insertAbreviation(array $Element) + { + if (isset($Element['text'])) { + $Element['elements'] = self::pregReplaceElements( + '/\b'.preg_quote($this->currentAbreviation, '/').'\b/', + array( + array( + 'name' => 'abbr', + 'attributes' => array( + 'title' => $this->currentMeaning, + ), + 'text' => $this->currentAbreviation, + ) + ), + $Element['text'] + ); + + unset($Element['text']); + } + + return $Element; + } + + protected function inlineText($text) + { + $Inline = parent::inlineText($text); + + if (isset($this->DefinitionData['Abbreviation'])) { + foreach ($this->DefinitionData['Abbreviation'] as $abbreviation => $meaning) { + $this->currentAbreviation = $abbreviation; + $this->currentMeaning = $meaning; + + $Inline['element'] = $this->elementApplyRecursiveDepthFirst( + array($this, 'insertAbreviation'), + $Inline['element'] + ); + } + } + + return $Inline; + } + + # + # Util Methods + # + + protected function addDdElement(array $Line, array $Block) + { + $text = substr($Line['text'], 1); + $text = trim($text); + + unset($Block['dd']); + + $Block['dd'] = array( + 'name' => 'dd', + 'handler' => array( + 'function' => 'lineElements', + 'argument' => $text, + 'destination' => 'elements' + ), + ); + + if (isset($Block['interrupted'])) { + $Block['dd']['handler']['function'] = 'textElements'; + + unset($Block['interrupted']); + } + + $Block['element']['elements'] []= & $Block['dd']; + + return $Block; + } + + protected function buildFootnoteElement() + { + $Element = array( + 'name' => 'div', + 'attributes' => array('class' => 'footnotes'), + 'elements' => array( + array('name' => 'hr'), + array( + 'name' => 'ol', + 'elements' => array(), + ), + ), + ); + + uasort($this->DefinitionData['Footnote'], 'self::sortFootnotes'); + + foreach ($this->DefinitionData['Footnote'] as $definitionId => $DefinitionData) { + if (! isset($DefinitionData['number'])) { + continue; + } + + $text = $DefinitionData['text']; + + $textElements = parent::textElements($text); + + $numbers = range(1, $DefinitionData['count']); + + $backLinkElements = array(); + + foreach ($numbers as $number) { + $backLinkElements[] = array('text' => ' '); + $backLinkElements[] = array( + 'name' => 'a', + 'attributes' => array( + 'href' => "#fnref$number:$definitionId", + 'rev' => 'footnote', + 'class' => 'footnote-backref', + ), + 'rawHtml' => '↩', + 'allowRawHtmlInSafeMode' => true, + 'autobreak' => false, + ); + } + + unset($backLinkElements[0]); + + $n = count($textElements) -1; + + if ($textElements[$n]['name'] === 'p') { + $backLinkElements = array_merge( + array( + array( + 'rawHtml' => ' ', + 'allowRawHtmlInSafeMode' => true, + ), + ), + $backLinkElements + ); + + unset($textElements[$n]['name']); + + $textElements[$n] = array( + 'name' => 'p', + 'elements' => array_merge( + array($textElements[$n]), + $backLinkElements + ), + ); + } else { + $textElements[] = array( + 'name' => 'p', + 'elements' => $backLinkElements + ); + } + + $Element['elements'][1]['elements'] []= array( + 'name' => 'li', + 'attributes' => array('id' => 'fn:'.$definitionId), + 'elements' => array_merge( + $textElements + ), + ); + } + + return $Element; + } + + # ~ + + protected function parseAttributeData($attributeString) + { + $Data = array(); + + $attributes = preg_split('/[ ]+/', $attributeString, - 1, PREG_SPLIT_NO_EMPTY); + + foreach ($attributes as $attribute) { + if ($attribute[0] === '#') { + $Data['id'] = substr($attribute, 1); + } else { # "." + $classes []= substr($attribute, 1); + } + } + + if (isset($classes)) { + $Data['class'] = implode(' ', $classes); + } + + return $Data; + } + + # ~ + + protected function processTag($elementMarkup) # recursive + { + # http://stackoverflow.com/q/1148928/200145 + libxml_use_internal_errors(true); + + $DOMDocument = new DOMDocument; + + # http://stackoverflow.com/q/11309194/200145 + $elementMarkup = mb_convert_encoding($elementMarkup, 'HTML-ENTITIES', 'UTF-8'); + + # Ensure that saveHTML() is not remove new line characters. New lines will be split by this character. + $DOMDocument->formatOutput = true; + + # http://stackoverflow.com/q/4879946/200145 + $DOMDocument->loadHTML($elementMarkup); + $DOMDocument->removeChild($DOMDocument->doctype); + $DOMDocument->replaceChild($DOMDocument->firstChild->firstChild->firstChild, $DOMDocument->firstChild); + + $elementText = ''; + + if ($DOMDocument->documentElement->getAttribute('markdown') === '1') { + foreach ($DOMDocument->documentElement->childNodes as $Node) { + $elementText .= $DOMDocument->saveHTML($Node); + } + + $DOMDocument->documentElement->removeAttribute('markdown'); + + $elementText = "\n".$this->text($elementText)."\n"; + } else { + foreach ($DOMDocument->documentElement->childNodes as $Node) { + $nodeMarkup = $DOMDocument->saveHTML($Node); + + if ($Node instanceof DOMElement and ! in_array($Node->nodeName, $this->textLevelElements)) { + $elementText .= $this->processTag($nodeMarkup); + } else { + $elementText .= $nodeMarkup; + } + } + } + + # because we don't want for markup to get encoded + $DOMDocument->documentElement->nodeValue = 'placeholder\x1A'; + + $markup = $DOMDocument->saveHTML($DOMDocument->documentElement); + $markup = str_replace('placeholder\x1A', $elementText, $markup); + + return $markup; + } + + # ~ + + protected function sortFootnotes($A, $B) # callback + { + return $A['number'] - $B['number']; + } + + # + # Fields + # + + protected $regexAttribute = '(?:[#.][-\w]+[ ]*)'; +} diff --git a/kirby/dependencies/parsedown/Parsedown.php b/kirby/dependencies/parsedown/Parsedown.php new file mode 100644 index 0000000..6552c0c --- /dev/null +++ b/kirby/dependencies/parsedown/Parsedown.php @@ -0,0 +1,1822 @@ +textElements($text); + + # convert to markup + $markup = $this->elements($Elements); + + # trim line breaks + $markup = trim($markup, "\n"); + + return $markup; + } + + protected function textElements($text) + { + # make sure no definitions are set + $this->DefinitionData = array(); + + # standardize line breaks + $text = str_replace(array("\r\n", "\r"), "\n", $text); + + # remove surrounding line breaks + $text = trim($text, "\n"); + + # split text into lines + $lines = explode("\n", $text); + + # iterate through lines to identify blocks + return $this->linesElements($lines); + } + + # + # Setters + # + + public function setBreaksEnabled($breaksEnabled) + { + $this->breaksEnabled = $breaksEnabled; + + return $this; + } + + protected $breaksEnabled; + + public function setMarkupEscaped($markupEscaped) + { + $this->markupEscaped = $markupEscaped; + + return $this; + } + + protected $markupEscaped; + + public function setUrlsLinked($urlsLinked) + { + $this->urlsLinked = $urlsLinked; + + return $this; + } + + protected $urlsLinked = true; + + public function setSafeMode($safeMode) + { + $this->safeMode = (bool) $safeMode; + + return $this; + } + + protected $safeMode; + + public function setStrictMode($strictMode) + { + $this->strictMode = (bool) $strictMode; + + return $this; + } + + protected $strictMode; + + protected $safeLinksWhitelist = array( + 'http://', + 'https://', + 'ftp://', + 'ftps://', + 'mailto:', + 'data:image/png;base64,', + 'data:image/gif;base64,', + 'data:image/jpeg;base64,', + 'irc:', + 'ircs:', + 'git:', + 'ssh:', + 'news:', + 'steam:', + ); + + # + # Lines + # + + protected $BlockTypes = array( + '#' => array('Header'), + '*' => array('Rule', 'List'), + '+' => array('List'), + '-' => array('SetextHeader', 'Table', 'Rule', 'List'), + '0' => array('List'), + '1' => array('List'), + '2' => array('List'), + '3' => array('List'), + '4' => array('List'), + '5' => array('List'), + '6' => array('List'), + '7' => array('List'), + '8' => array('List'), + '9' => array('List'), + ':' => array('Table'), + '<' => array('Comment', 'Markup'), + '=' => array('SetextHeader'), + '>' => array('Quote'), + '[' => array('Reference'), + '_' => array('Rule'), + '`' => array('FencedCode'), + '|' => array('Table'), + '~' => array('FencedCode'), + ); + + # ~ + + protected $unmarkedBlockTypes = array( + 'Code', + ); + + # + # Blocks + # + + protected function lines(array $lines) + { + return $this->elements($this->linesElements($lines)); + } + + protected function linesElements(array $lines) + { + $Elements = array(); + $CurrentBlock = null; + + foreach ($lines as $line) { + if (chop($line) === '') { + if (isset($CurrentBlock)) { + $CurrentBlock['interrupted'] = ( + isset($CurrentBlock['interrupted']) + ? $CurrentBlock['interrupted'] + 1 : 1 + ); + } + + continue; + } + + while (($beforeTab = strstr($line, "\t", true)) !== false) { + $shortage = 4 - mb_strlen($beforeTab, 'utf-8') % 4; + + $line = $beforeTab + . str_repeat(' ', $shortage) + . substr($line, strlen($beforeTab) + 1) + ; + } + + $indent = strspn($line, ' '); + + $text = $indent > 0 ? substr($line, $indent) : $line; + + # ~ + + $Line = array('body' => $line, 'indent' => $indent, 'text' => $text); + + # ~ + + if (isset($CurrentBlock['continuable'])) { + $methodName = 'block' . $CurrentBlock['type'] . 'Continue'; + $Block = $this->$methodName($Line, $CurrentBlock); + + if (isset($Block)) { + $CurrentBlock = $Block; + + continue; + } else { + if ($this->isBlockCompletable($CurrentBlock['type'])) { + $methodName = 'block' . $CurrentBlock['type'] . 'Complete'; + $CurrentBlock = $this->$methodName($CurrentBlock); + } + } + } + + # ~ + + $marker = $text[0]; + + # ~ + + $blockTypes = $this->unmarkedBlockTypes; + + if (isset($this->BlockTypes[$marker])) { + foreach ($this->BlockTypes[$marker] as $blockType) { + $blockTypes []= $blockType; + } + } + + # + # ~ + + foreach ($blockTypes as $blockType) { + $Block = $this->{"block$blockType"}($Line, $CurrentBlock); + + if (isset($Block)) { + $Block['type'] = $blockType; + + if (! isset($Block['identified'])) { + if (isset($CurrentBlock)) { + $Elements[] = $this->extractElement($CurrentBlock); + } + + $Block['identified'] = true; + } + + if ($this->isBlockContinuable($blockType)) { + $Block['continuable'] = true; + } + + $CurrentBlock = $Block; + + continue 2; + } + } + + # ~ + + if (isset($CurrentBlock) and $CurrentBlock['type'] === 'Paragraph') { + $Block = $this->paragraphContinue($Line, $CurrentBlock); + } + + if (isset($Block)) { + $CurrentBlock = $Block; + } else { + if (isset($CurrentBlock)) { + $Elements[] = $this->extractElement($CurrentBlock); + } + + $CurrentBlock = $this->paragraph($Line); + + $CurrentBlock['identified'] = true; + } + } + + # ~ + + if (isset($CurrentBlock['continuable']) and $this->isBlockCompletable($CurrentBlock['type'])) { + $methodName = 'block' . $CurrentBlock['type'] . 'Complete'; + $CurrentBlock = $this->$methodName($CurrentBlock); + } + + # ~ + + if (isset($CurrentBlock)) { + $Elements[] = $this->extractElement($CurrentBlock); + } + + # ~ + + return $Elements; + } + + protected function extractElement(array $Component) + { + if (! isset($Component['element'])) { + if (isset($Component['markup'])) { + $Component['element'] = array('rawHtml' => $Component['markup']); + } elseif (isset($Component['hidden'])) { + $Component['element'] = array(); + } + } + + return $Component['element']; + } + + protected function isBlockContinuable($Type) + { + return method_exists($this, 'block' . $Type . 'Continue'); + } + + protected function isBlockCompletable($Type) + { + return method_exists($this, 'block' . $Type . 'Complete'); + } + + # + # Code + + protected function blockCode($Line, $Block = null) + { + if (isset($Block) and $Block['type'] === 'Paragraph' and ! isset($Block['interrupted'])) { + return; + } + + if ($Line['indent'] >= 4) { + $text = substr($Line['body'], 4); + + $Block = array( + 'element' => array( + 'name' => 'pre', + 'element' => array( + 'name' => 'code', + 'text' => $text, + ), + ), + ); + + return $Block; + } + } + + protected function blockCodeContinue($Line, $Block) + { + if ($Line['indent'] >= 4) { + if (isset($Block['interrupted'])) { + $Block['element']['element']['text'] .= str_repeat("\n", $Block['interrupted']); + + unset($Block['interrupted']); + } + + $Block['element']['element']['text'] .= "\n"; + + $text = substr($Line['body'], 4); + + $Block['element']['element']['text'] .= $text; + + return $Block; + } + } + + protected function blockCodeComplete($Block) + { + return $Block; + } + + # + # Comment + + protected function blockComment($Line) + { + if ($this->markupEscaped or $this->safeMode) { + return; + } + + if (strpos($Line['text'], '') !== false) { + $Block['closed'] = true; + } + + return $Block; + } + } + + protected function blockCommentContinue($Line, array $Block) + { + if (isset($Block['closed'])) { + return; + } + + $Block['element']['rawHtml'] .= "\n" . $Line['body']; + + if (strpos($Line['text'], '-->') !== false) { + $Block['closed'] = true; + } + + return $Block; + } + + # + # Fenced Code + + protected function blockFencedCode($Line) + { + $marker = $Line['text'][0]; + + $openerLength = strspn($Line['text'], $marker); + + if ($openerLength < 3) { + return; + } + + $infostring = trim(substr($Line['text'], $openerLength), "\t "); + + if (strpos($infostring, '`') !== false) { + return; + } + + $Element = array( + 'name' => 'code', + 'text' => '', + ); + + if ($infostring !== '') { + /** + * https://www.w3.org/TR/2011/WD-html5-20110525/elements.html#classes + * Every HTML element may have a class attribute specified. + * The attribute, if specified, must have a value that is a set + * of space-separated tokens representing the various classes + * that the element belongs to. + * [...] + * The space characters, for the purposes of this specification, + * are U+0020 SPACE, U+0009 CHARACTER TABULATION (tab), + * U+000A LINE FEED (LF), U+000C FORM FEED (FF), and + * U+000D CARRIAGE RETURN (CR). + */ + $language = substr($infostring, 0, strcspn($infostring, " \t\n\f\r")); + + $Element['attributes'] = array('class' => "language-$language"); + } + + $Block = array( + 'char' => $marker, + 'openerLength' => $openerLength, + 'element' => array( + 'name' => 'pre', + 'element' => $Element, + ), + ); + + return $Block; + } + + protected function blockFencedCodeContinue($Line, $Block) + { + if (isset($Block['complete'])) { + return; + } + + if (isset($Block['interrupted'])) { + $Block['element']['element']['text'] .= str_repeat("\n", $Block['interrupted']); + + unset($Block['interrupted']); + } + + if (($len = strspn($Line['text'], $Block['char'])) >= $Block['openerLength'] + and chop(substr($Line['text'], $len), ' ') === '' + ) { + $Block['element']['element']['text'] = substr($Block['element']['element']['text'], 1); + + $Block['complete'] = true; + + return $Block; + } + + $Block['element']['element']['text'] .= "\n" . $Line['body']; + + return $Block; + } + + protected function blockFencedCodeComplete($Block) + { + return $Block; + } + + # + # Header + + protected function blockHeader($Line) + { + $level = strspn($Line['text'], '#'); + + if ($level > 6) { + return; + } + + $text = trim($Line['text'], '#'); + + if ($this->strictMode and isset($text[0]) and $text[0] !== ' ') { + return; + } + + $text = trim($text, ' '); + + $Block = array( + 'element' => array( + 'name' => 'h' . min(6, $level), + 'handler' => array( + 'function' => 'lineElements', + 'argument' => $text, + 'destination' => 'elements', + ) + ), + ); + + return $Block; + } + + # + # List + + protected function blockList($Line, array $CurrentBlock = null) + { + list($name, $pattern) = $Line['text'][0] <= '-' ? array('ul', '[*+-]') : array('ol', '[0-9]{1,9}+[.\)]'); + + if (preg_match('/^('.$pattern.'([ ]++|$))(.*+)/', $Line['text'], $matches)) { + $contentIndent = strlen($matches[2]); + + if ($contentIndent >= 5) { + $contentIndent -= 1; + $matches[1] = substr($matches[1], 0, -$contentIndent); + $matches[3] = str_repeat(' ', $contentIndent) . $matches[3]; + } elseif ($contentIndent === 0) { + $matches[1] .= ' '; + } + + $markerWithoutWhitespace = strstr($matches[1], ' ', true); + + $Block = array( + 'indent' => $Line['indent'], + 'pattern' => $pattern, + 'data' => array( + 'type' => $name, + 'marker' => $matches[1], + 'markerType' => ($name === 'ul' ? $markerWithoutWhitespace : substr($markerWithoutWhitespace, -1)), + ), + 'element' => array( + 'name' => $name, + 'elements' => array(), + ), + ); + $Block['data']['markerTypeRegex'] = preg_quote($Block['data']['markerType'], '/'); + + if ($name === 'ol') { + $listStart = ltrim(strstr($matches[1], $Block['data']['markerType'], true), '0') ?: '0'; + + if ($listStart !== '1') { + if ( + isset($CurrentBlock) + and $CurrentBlock['type'] === 'Paragraph' + and ! isset($CurrentBlock['interrupted']) + ) { + return; + } + + $Block['element']['attributes'] = array('start' => $listStart); + } + } + + $Block['li'] = array( + 'name' => 'li', + 'handler' => array( + 'function' => 'li', + 'argument' => !empty($matches[3]) ? array($matches[3]) : array(), + 'destination' => 'elements' + ) + ); + + $Block['element']['elements'] []= & $Block['li']; + + return $Block; + } + } + + protected function blockListContinue($Line, array $Block) + { + if (isset($Block['interrupted']) and empty($Block['li']['handler']['argument'])) { + return null; + } + + $requiredIndent = ($Block['indent'] + strlen($Block['data']['marker'])); + + if ($Line['indent'] < $requiredIndent + and ( + ( + $Block['data']['type'] === 'ol' + and preg_match('/^[0-9]++'.$Block['data']['markerTypeRegex'].'(?:[ ]++(.*)|$)/', $Line['text'], $matches) + ) or ( + $Block['data']['type'] === 'ul' + and preg_match('/^'.$Block['data']['markerTypeRegex'].'(?:[ ]++(.*)|$)/', $Line['text'], $matches) + ) + ) + ) { + if (isset($Block['interrupted'])) { + $Block['li']['handler']['argument'] []= ''; + + $Block['loose'] = true; + + unset($Block['interrupted']); + } + + unset($Block['li']); + + $text = isset($matches[1]) ? $matches[1] : ''; + + $Block['indent'] = $Line['indent']; + + $Block['li'] = array( + 'name' => 'li', + 'handler' => array( + 'function' => 'li', + 'argument' => array($text), + 'destination' => 'elements' + ) + ); + + $Block['element']['elements'] []= & $Block['li']; + + return $Block; + } elseif ($Line['indent'] < $requiredIndent and $this->blockList($Line)) { + return null; + } + + if ($Line['text'][0] === '[' and $this->blockReference($Line)) { + return $Block; + } + + if ($Line['indent'] >= $requiredIndent) { + if (isset($Block['interrupted'])) { + $Block['li']['handler']['argument'] []= ''; + + $Block['loose'] = true; + + unset($Block['interrupted']); + } + + $text = substr($Line['body'], $requiredIndent); + + $Block['li']['handler']['argument'] []= $text; + + return $Block; + } + + if (! isset($Block['interrupted'])) { + $text = preg_replace('/^[ ]{0,'.$requiredIndent.'}+/', '', $Line['body']); + + $Block['li']['handler']['argument'] []= $text; + + return $Block; + } + } + + protected function blockListComplete(array $Block) + { + if (isset($Block['loose'])) { + foreach ($Block['element']['elements'] as &$li) { + if (end($li['handler']['argument']) !== '') { + $li['handler']['argument'] []= ''; + } + } + } + + return $Block; + } + + # + # Quote + + protected function blockQuote($Line) + { + if (preg_match('/^>[ ]?+(.*+)/', $Line['text'], $matches)) { + $Block = array( + 'element' => array( + 'name' => 'blockquote', + 'handler' => array( + 'function' => 'linesElements', + 'argument' => (array) $matches[1], + 'destination' => 'elements', + ) + ), + ); + + return $Block; + } + } + + protected function blockQuoteContinue($Line, array $Block) + { + if (isset($Block['interrupted'])) { + return; + } + + if ($Line['text'][0] === '>' and preg_match('/^>[ ]?+(.*+)/', $Line['text'], $matches)) { + $Block['element']['handler']['argument'] []= $matches[1]; + + return $Block; + } + + if (! isset($Block['interrupted'])) { + $Block['element']['handler']['argument'] []= $Line['text']; + + return $Block; + } + } + + # + # Rule + + protected function blockRule($Line) + { + $marker = $Line['text'][0]; + + if (substr_count($Line['text'], $marker) >= 3 and chop($Line['text'], " $marker") === '') { + $Block = array( + 'element' => array( + 'name' => 'hr', + ), + ); + + return $Block; + } + } + + # + # Setext + + protected function blockSetextHeader($Line, array $Block = null) + { + if (! isset($Block) or $Block['type'] !== 'Paragraph' or isset($Block['interrupted'])) { + return; + } + + if ($Line['indent'] < 4 and chop(chop($Line['text'], ' '), $Line['text'][0]) === '') { + $Block['element']['name'] = $Line['text'][0] === '=' ? 'h1' : 'h2'; + + return $Block; + } + } + + # + # Markup + + protected function blockMarkup($Line) + { + if ($this->markupEscaped or $this->safeMode) { + return; + } + + if (preg_match('/^<[\/]?+(\w*)(?:[ ]*+'.$this->regexHtmlAttribute.')*+[ ]*+(\/)?>/', $Line['text'], $matches)) { + $element = strtolower($matches[1]); + + if (in_array($element, $this->textLevelElements)) { + return; + } + + $Block = array( + 'name' => $matches[1], + 'element' => array( + 'rawHtml' => $Line['text'], + 'autobreak' => true, + ), + ); + + return $Block; + } + } + + protected function blockMarkupContinue($Line, array $Block) + { + if (isset($Block['closed']) or isset($Block['interrupted'])) { + return; + } + + $Block['element']['rawHtml'] .= "\n" . $Line['body']; + + return $Block; + } + + # + # Reference + + protected function blockReference($Line) + { + if (strpos($Line['text'], ']') !== false + and preg_match('/^\[(.+?)\]:[ ]*+?(?:[ ]+["\'(](.+)["\')])?[ ]*+$/', $Line['text'], $matches) + ) { + $id = strtolower($matches[1]); + + $Data = array( + 'url' => $matches[2], + 'title' => isset($matches[3]) ? $matches[3] : null, + ); + + $this->DefinitionData['Reference'][$id] = $Data; + + $Block = array( + 'element' => array(), + ); + + return $Block; + } + } + + # + # Table + + protected function blockTable($Line, array $Block = null) + { + if (! isset($Block) or $Block['type'] !== 'Paragraph' or isset($Block['interrupted'])) { + return; + } + + if ( + strpos($Block['element']['handler']['argument'], '|') === false + and strpos($Line['text'], '|') === false + and strpos($Line['text'], ':') === false + or strpos($Block['element']['handler']['argument'], "\n") !== false + ) { + return; + } + + if (chop($Line['text'], ' -:|') !== '') { + return; + } + + $alignments = array(); + + $divider = $Line['text']; + + $divider = trim($divider); + $divider = trim($divider, '|'); + + $dividerCells = explode('|', $divider); + + foreach ($dividerCells as $dividerCell) { + $dividerCell = trim($dividerCell); + + if ($dividerCell === '') { + return; + } + + $alignment = null; + + if ($dividerCell[0] === ':') { + $alignment = 'left'; + } + + if (substr($dividerCell, - 1) === ':') { + $alignment = $alignment === 'left' ? 'center' : 'right'; + } + + $alignments []= $alignment; + } + + # ~ + + $HeaderElements = array(); + + $header = $Block['element']['handler']['argument']; + + $header = trim($header); + $header = trim($header, '|'); + + $headerCells = explode('|', $header); + + if (count($headerCells) !== count($alignments)) { + return; + } + + foreach ($headerCells as $index => $headerCell) { + $headerCell = trim($headerCell); + + $HeaderElement = array( + 'name' => 'th', + 'handler' => array( + 'function' => 'lineElements', + 'argument' => $headerCell, + 'destination' => 'elements', + ) + ); + + if (isset($alignments[$index])) { + $alignment = $alignments[$index]; + + $HeaderElement['attributes'] = array( + 'style' => "text-align: $alignment;", + ); + } + + $HeaderElements []= $HeaderElement; + } + + # ~ + + $Block = array( + 'alignments' => $alignments, + 'identified' => true, + 'element' => array( + 'name' => 'table', + 'elements' => array(), + ), + ); + + $Block['element']['elements'] []= array( + 'name' => 'thead', + ); + + $Block['element']['elements'] []= array( + 'name' => 'tbody', + 'elements' => array(), + ); + + $Block['element']['elements'][0]['elements'] []= array( + 'name' => 'tr', + 'elements' => $HeaderElements, + ); + + return $Block; + } + + protected function blockTableContinue($Line, array $Block) + { + if (isset($Block['interrupted'])) { + return; + } + + if (count($Block['alignments']) === 1 or $Line['text'][0] === '|' or strpos($Line['text'], '|')) { + $Elements = array(); + + $row = $Line['text']; + + $row = trim($row); + $row = trim($row, '|'); + + preg_match_all('/(?:(\\\\[|])|[^|`]|`[^`]++`|`)++/', $row, $matches); + + $cells = array_slice($matches[0], 0, count($Block['alignments'])); + + foreach ($cells as $index => $cell) { + $cell = trim($cell); + + $Element = array( + 'name' => 'td', + 'handler' => array( + 'function' => 'lineElements', + 'argument' => $cell, + 'destination' => 'elements', + ) + ); + + if (isset($Block['alignments'][$index])) { + $Element['attributes'] = array( + 'style' => 'text-align: ' . $Block['alignments'][$index] . ';', + ); + } + + $Elements []= $Element; + } + + $Element = array( + 'name' => 'tr', + 'elements' => $Elements, + ); + + $Block['element']['elements'][1]['elements'] []= $Element; + + return $Block; + } + } + + # + # ~ + # + + protected function paragraph($Line) + { + return array( + 'type' => 'Paragraph', + 'element' => array( + 'name' => 'p', + 'handler' => array( + 'function' => 'lineElements', + 'argument' => $Line['text'], + 'destination' => 'elements', + ), + ), + ); + } + + protected function paragraphContinue($Line, array $Block) + { + if (isset($Block['interrupted'])) { + return; + } + + $Block['element']['handler']['argument'] .= "\n".$Line['text']; + + return $Block; + } + + # + # Inline Elements + # + + protected $InlineTypes = array( + '!' => array('Image'), + '&' => array('SpecialCharacter'), + '*' => array('Emphasis'), + ':' => array('Url'), + '<' => array('UrlTag', 'EmailTag', 'Markup'), + '[' => array('Link'), + '_' => array('Emphasis'), + '`' => array('Code'), + '~' => array('Strikethrough'), + '\\' => array('EscapeSequence'), + ); + + # ~ + + protected $inlineMarkerList = '!*_&[:<`~\\'; + + # + # ~ + # + + public function line($text, $nonNestables = array()) + { + return $this->elements($this->lineElements($text, $nonNestables)); + } + + protected function lineElements($text, $nonNestables = array()) + { + $Elements = array(); + + $nonNestables = ( + empty($nonNestables) + ? array() + : array_combine($nonNestables, $nonNestables) + ); + + # $excerpt is based on the first occurrence of a marker + + while ($excerpt = strpbrk($text, $this->inlineMarkerList)) { + $marker = $excerpt[0]; + + $markerPosition = strlen($text) - strlen($excerpt); + + $Excerpt = array('text' => $excerpt, 'context' => $text); + + foreach ($this->InlineTypes[$marker] as $inlineType) { + # check to see if the current inline type is nestable in the current context + + if (isset($nonNestables[$inlineType])) { + continue; + } + + $Inline = $this->{"inline$inlineType"}($Excerpt); + + if (! isset($Inline)) { + continue; + } + + # makes sure that the inline belongs to "our" marker + + if (isset($Inline['position']) and $Inline['position'] > $markerPosition) { + continue; + } + + # sets a default inline position + + if (! isset($Inline['position'])) { + $Inline['position'] = $markerPosition; + } + + # cause the new element to 'inherit' our non nestables + + + $Inline['element']['nonNestables'] = isset($Inline['element']['nonNestables']) + ? array_merge($Inline['element']['nonNestables'], $nonNestables) + : $nonNestables + ; + + # the text that comes before the inline + $unmarkedText = substr($text, 0, $Inline['position']); + + # compile the unmarked text + $InlineText = $this->inlineText($unmarkedText); + $Elements[] = $InlineText['element']; + + # compile the inline + $Elements[] = $this->extractElement($Inline); + + # remove the examined text + $text = substr($text, $Inline['position'] + $Inline['extent']); + + continue 2; + } + + # the marker does not belong to an inline + + $unmarkedText = substr($text, 0, $markerPosition + 1); + + $InlineText = $this->inlineText($unmarkedText); + $Elements[] = $InlineText['element']; + + $text = substr($text, $markerPosition + 1); + } + + $InlineText = $this->inlineText($text); + $Elements[] = $InlineText['element']; + + foreach ($Elements as &$Element) { + if (! isset($Element['autobreak'])) { + $Element['autobreak'] = false; + } + } + + return $Elements; + } + + # + # ~ + # + + protected function inlineText($text) + { + $Inline = array( + 'extent' => strlen($text), + 'element' => array(), + ); + + $Inline['element']['elements'] = self::pregReplaceElements( + $this->breaksEnabled ? '/[ ]*+\n/' : '/(?:[ ]*+\\\\|[ ]{2,}+)\n/', + array( + array('name' => 'br'), + array('text' => "\n"), + ), + $text + ); + + return $Inline; + } + + protected function inlineCode($Excerpt) + { + $marker = $Excerpt['text'][0]; + + if (preg_match('/^(['.$marker.']++)[ ]*+(.+?)[ ]*+(? strlen($matches[0]), + 'element' => array( + 'name' => 'code', + 'text' => $text, + ), + ); + } + } + + protected function inlineEmailTag($Excerpt) + { + $hostnameLabel = '[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?'; + + $commonMarkEmail = '[a-zA-Z0-9.!#$%&\'*+\/=?^_`{|}~-]++@' + . $hostnameLabel . '(?:\.' . $hostnameLabel . ')*'; + + if (strpos($Excerpt['text'], '>') !== false + and preg_match("/^<((mailto:)?$commonMarkEmail)>/i", $Excerpt['text'], $matches) + ) { + $url = $matches[1]; + + if (! isset($matches[2])) { + $url = "mailto:$url"; + } + + return array( + 'extent' => strlen($matches[0]), + 'element' => array( + 'name' => 'a', + 'text' => $matches[1], + 'attributes' => array( + 'href' => $url, + ), + ), + ); + } + } + + protected function inlineEmphasis($Excerpt) + { + if (! isset($Excerpt['text'][1])) { + return; + } + + $marker = $Excerpt['text'][0]; + + if ($Excerpt['text'][1] === $marker and preg_match($this->StrongRegex[$marker], $Excerpt['text'], $matches)) { + $emphasis = 'strong'; + } elseif (preg_match($this->EmRegex[$marker], $Excerpt['text'], $matches)) { + $emphasis = 'em'; + } else { + return; + } + + return array( + 'extent' => strlen($matches[0]), + 'element' => array( + 'name' => $emphasis, + 'handler' => array( + 'function' => 'lineElements', + 'argument' => $matches[1], + 'destination' => 'elements', + ) + ), + ); + } + + protected function inlineEscapeSequence($Excerpt) + { + if (isset($Excerpt['text'][1]) and in_array($Excerpt['text'][1], $this->specialCharacters)) { + return array( + 'element' => array('rawHtml' => $Excerpt['text'][1]), + 'extent' => 2, + ); + } + } + + protected function inlineImage($Excerpt) + { + if (! isset($Excerpt['text'][1]) or $Excerpt['text'][1] !== '[') { + return; + } + + $Excerpt['text']= substr($Excerpt['text'], 1); + + $Link = $this->inlineLink($Excerpt); + + if ($Link === null) { + return; + } + + $Inline = array( + 'extent' => $Link['extent'] + 1, + 'element' => array( + 'name' => 'img', + 'attributes' => array( + 'src' => $Link['element']['attributes']['href'], + 'alt' => $Link['element']['handler']['argument'], + ), + 'autobreak' => true, + ), + ); + + $Inline['element']['attributes'] += $Link['element']['attributes']; + + unset($Inline['element']['attributes']['href']); + + return $Inline; + } + + protected function inlineLink($Excerpt) + { + $Element = array( + 'name' => 'a', + 'handler' => array( + 'function' => 'lineElements', + 'argument' => null, + 'destination' => 'elements', + ), + 'nonNestables' => array('Url', 'Link'), + 'attributes' => array( + 'href' => null, + 'title' => null, + ), + ); + + $extent = 0; + + $remainder = $Excerpt['text']; + + if (preg_match('/\[((?:[^][]++|(?R))*+)\]/', $remainder, $matches)) { + $Element['handler']['argument'] = $matches[1]; + + $extent += strlen($matches[0]); + + $remainder = substr($remainder, $extent); + } else { + return; + } + + if (preg_match('/^[(]\s*+((?:[^ ()]++|[(][^ )]+[)])++)(?:[ ]+("[^"]*+"|\'[^\']*+\'))?\s*+[)]/', $remainder, $matches)) { + $Element['attributes']['href'] = $matches[1]; + + if (isset($matches[2])) { + $Element['attributes']['title'] = substr($matches[2], 1, - 1); + } + + $extent += strlen($matches[0]); + } else { + if (preg_match('/^\s*\[(.*?)\]/', $remainder, $matches)) { + $definition = strlen($matches[1]) ? $matches[1] : $Element['handler']['argument']; + $definition = strtolower($definition); + + $extent += strlen($matches[0]); + } else { + $definition = strtolower($Element['handler']['argument']); + } + + if (! isset($this->DefinitionData['Reference'][$definition])) { + return; + } + + $Definition = $this->DefinitionData['Reference'][$definition]; + + $Element['attributes']['href'] = $Definition['url']; + $Element['attributes']['title'] = $Definition['title']; + } + + return array( + 'extent' => $extent, + 'element' => $Element, + ); + } + + protected function inlineMarkup($Excerpt) + { + if ($this->markupEscaped or $this->safeMode or strpos($Excerpt['text'], '>') === false) { + return; + } + + if ($Excerpt['text'][1] === '/' and preg_match('/^<\/\w[\w-]*+[ ]*+>/s', $Excerpt['text'], $matches)) { + return array( + 'element' => array('rawHtml' => $matches[0]), + 'extent' => strlen($matches[0]), + ); + } + + if ($Excerpt['text'][1] === '!' and preg_match('/^/s', $Excerpt['text'], $matches)) { + return array( + 'element' => array('rawHtml' => $matches[0]), + 'extent' => strlen($matches[0]), + ); + } + + if ($Excerpt['text'][1] !== ' ' and preg_match('/^<\w[\w-]*+(?:[ ]*+'.$this->regexHtmlAttribute.')*+[ ]*+\/?>/s', $Excerpt['text'], $matches)) { + return array( + 'element' => array('rawHtml' => $matches[0]), + 'extent' => strlen($matches[0]), + ); + } + } + + protected function inlineSpecialCharacter($Excerpt) + { + if ($Excerpt['text'][1] !== ' ' and strpos($Excerpt['text'], ';') !== false + and preg_match('/^&(#?+[0-9a-zA-Z]++);/', $Excerpt['text'], $matches) + ) { + return array( + 'element' => array('rawHtml' => '&' . $matches[1] . ';'), + 'extent' => strlen($matches[0]), + ); + } + + return; + } + + protected function inlineStrikethrough($Excerpt) + { + if (! isset($Excerpt['text'][1])) { + return; + } + + if ($Excerpt['text'][1] === '~' and preg_match('/^~~(?=\S)(.+?)(?<=\S)~~/', $Excerpt['text'], $matches)) { + return array( + 'extent' => strlen($matches[0]), + 'element' => array( + 'name' => 'del', + 'handler' => array( + 'function' => 'lineElements', + 'argument' => $matches[1], + 'destination' => 'elements', + ) + ), + ); + } + } + + protected function inlineUrl($Excerpt) + { + if ($this->urlsLinked !== true or ! isset($Excerpt['text'][2]) or $Excerpt['text'][2] !== '/') { + return; + } + + if (strpos($Excerpt['context'], 'http') !== false + and preg_match('/\bhttps?+:[\/]{2}[^\s<]+\b\/*+/ui', $Excerpt['context'], $matches, PREG_OFFSET_CAPTURE) + ) { + $url = $matches[0][0]; + + $Inline = array( + 'extent' => strlen($matches[0][0]), + 'position' => $matches[0][1], + 'element' => array( + 'name' => 'a', + 'text' => $url, + 'attributes' => array( + 'href' => $url, + ), + ), + ); + + return $Inline; + } + } + + protected function inlineUrlTag($Excerpt) + { + if (strpos($Excerpt['text'], '>') !== false and preg_match('/^<(\w++:\/{2}[^ >]++)>/i', $Excerpt['text'], $matches)) { + $url = $matches[1]; + + return array( + 'extent' => strlen($matches[0]), + 'element' => array( + 'name' => 'a', + 'text' => $url, + 'attributes' => array( + 'href' => $url, + ), + ), + ); + } + } + + # ~ + + protected function unmarkedText($text) + { + $Inline = $this->inlineText($text); + return $this->element($Inline['element']); + } + + # + # Handlers + # + + protected function handle(array $Element) + { + if (isset($Element['handler'])) { + if (!isset($Element['nonNestables'])) { + $Element['nonNestables'] = array(); + } + + if (is_string($Element['handler'])) { + $function = $Element['handler']; + $argument = $Element['text']; + unset($Element['text']); + $destination = 'rawHtml'; + } else { + $function = $Element['handler']['function']; + $argument = $Element['handler']['argument']; + $destination = $Element['handler']['destination']; + } + + $Element[$destination] = $this->{$function}($argument, $Element['nonNestables']); + + if ($destination === 'handler') { + $Element = $this->handle($Element); + } + + unset($Element['handler']); + } + + return $Element; + } + + protected function handleElementRecursive(array $Element) + { + return $this->elementApplyRecursive(array($this, 'handle'), $Element); + } + + protected function handleElementsRecursive(array $Elements) + { + return $this->elementsApplyRecursive(array($this, 'handle'), $Elements); + } + + protected function elementApplyRecursive($closure, array $Element) + { + $Element = call_user_func($closure, $Element); + + if (isset($Element['elements'])) { + $Element['elements'] = $this->elementsApplyRecursive($closure, $Element['elements']); + } elseif (isset($Element['element'])) { + $Element['element'] = $this->elementApplyRecursive($closure, $Element['element']); + } + + return $Element; + } + + protected function elementApplyRecursiveDepthFirst($closure, array $Element) + { + if (isset($Element['elements'])) { + $Element['elements'] = $this->elementsApplyRecursiveDepthFirst($closure, $Element['elements']); + } elseif (isset($Element['element'])) { + $Element['element'] = $this->elementsApplyRecursiveDepthFirst($closure, $Element['element']); + } + + $Element = call_user_func($closure, $Element); + + return $Element; + } + + protected function elementsApplyRecursive($closure, array $Elements) + { + foreach ($Elements as &$Element) { + $Element = $this->elementApplyRecursive($closure, $Element); + } + + return $Elements; + } + + protected function elementsApplyRecursiveDepthFirst($closure, array $Elements) + { + foreach ($Elements as &$Element) { + $Element = $this->elementApplyRecursiveDepthFirst($closure, $Element); + } + + return $Elements; + } + + protected function element(array $Element) + { + if ($this->safeMode) { + $Element = $this->sanitiseElement($Element); + } + + # identity map if element has no handler + $Element = $this->handle($Element); + + $hasName = isset($Element['name']); + + $markup = ''; + + if ($hasName) { + $markup .= '<' . $Element['name']; + + if (isset($Element['attributes'])) { + foreach ($Element['attributes'] as $name => $value) { + if ($value === null) { + continue; + } + + $markup .= " $name=\"".self::escape($value).'"'; + } + } + } + + $permitRawHtml = false; + + if (isset($Element['text'])) { + $text = $Element['text']; + } + // very strongly consider an alternative if you're writing an + // extension + elseif (isset($Element['rawHtml'])) { + $text = $Element['rawHtml']; + + $allowRawHtmlInSafeMode = isset($Element['allowRawHtmlInSafeMode']) && $Element['allowRawHtmlInSafeMode']; + $permitRawHtml = !$this->safeMode || $allowRawHtmlInSafeMode; + } + + $hasContent = isset($text) || isset($Element['element']) || isset($Element['elements']); + + if ($hasContent) { + $markup .= $hasName ? '>' : ''; + + if (isset($Element['elements'])) { + $markup .= $this->elements($Element['elements']); + } elseif (isset($Element['element'])) { + $markup .= $this->element($Element['element']); + } else { + if (!$permitRawHtml) { + $markup .= self::escape($text, true); + } else { + $markup .= $text; + } + } + + $markup .= $hasName ? '' : ''; + } elseif ($hasName) { + $markup .= ' />'; + } + + return $markup; + } + + protected function elements(array $Elements) + { + $markup = ''; + + $autoBreak = true; + + foreach ($Elements as $Element) { + if (empty($Element)) { + continue; + } + + $autoBreakNext = ( + isset($Element['autobreak']) + ? $Element['autobreak'] : isset($Element['name']) + ); + // (autobreak === false) covers both sides of an element + $autoBreak = !$autoBreak ? $autoBreak : $autoBreakNext; + + $markup .= ($autoBreak ? "\n" : '') . $this->element($Element); + $autoBreak = $autoBreakNext; + } + + $markup .= $autoBreak ? "\n" : ''; + + return $markup; + } + + # ~ + + protected function li($lines) + { + $Elements = $this->linesElements($lines); + + if (! in_array('', $lines) + and isset($Elements[0]) and isset($Elements[0]['name']) + and $Elements[0]['name'] === 'p' + ) { + unset($Elements[0]['name']); + } + + return $Elements; + } + + # + # AST Convenience + # + + /** + * Replace occurrences $regexp with $Elements in $text. Return an array of + * elements representing the replacement. + */ + protected static function pregReplaceElements($regexp, $Elements, $text) + { + $newElements = array(); + + while (preg_match($regexp, $text, $matches, PREG_OFFSET_CAPTURE)) { + $offset = $matches[0][1]; + $before = substr($text, 0, $offset); + $after = substr($text, $offset + strlen($matches[0][0])); + + $newElements[] = array('text' => $before); + + foreach ($Elements as $Element) { + $newElements[] = $Element; + } + + $text = $after; + } + + $newElements[] = array('text' => $text); + + return $newElements; + } + + # + # Deprecated Methods + # + + public function parse($text) + { + $markup = $this->text($text); + + return $markup; + } + + protected function sanitiseElement(array $Element) + { + static $goodAttribute = '/^[a-zA-Z0-9][a-zA-Z0-9-_]*+$/'; + static $safeUrlNameToAtt = array( + 'a' => 'href', + 'img' => 'src', + ); + + if (! isset($Element['name'])) { + unset($Element['attributes']); + return $Element; + } + + if (isset($safeUrlNameToAtt[$Element['name']])) { + $Element = $this->filterUnsafeUrlInAttribute($Element, $safeUrlNameToAtt[$Element['name']]); + } + + if (! empty($Element['attributes'])) { + foreach ($Element['attributes'] as $att => $val) { + # filter out badly parsed attribute + if (! preg_match($goodAttribute, $att)) { + unset($Element['attributes'][$att]); + } + # dump onevent attribute + elseif (self::striAtStart($att, 'on')) { + unset($Element['attributes'][$att]); + } + } + } + + return $Element; + } + + protected function filterUnsafeUrlInAttribute(array $Element, $attribute) + { + foreach ($this->safeLinksWhitelist as $scheme) { + if (self::striAtStart($Element['attributes'][$attribute], $scheme)) { + return $Element; + } + } + + $Element['attributes'][$attribute] = str_replace(':', '%3A', $Element['attributes'][$attribute]); + + return $Element; + } + + # + # Static Methods + # + + protected static function escape($text, $allowQuotes = false) + { + return htmlspecialchars($text, $allowQuotes ? ENT_NOQUOTES : ENT_QUOTES, 'UTF-8'); + } + + protected static function striAtStart($string, $needle) + { + $len = strlen($needle); + + if ($len > strlen($string)) { + return false; + } else { + return strtolower(substr($string, 0, $len)) === strtolower($needle); + } + } + + public static function instance($name = 'default') + { + if (isset(self::$instances[$name])) { + return self::$instances[$name]; + } + + $instance = new static(); + + self::$instances[$name] = $instance; + + return $instance; + } + + private static $instances = array(); + + # + # Fields + # + + protected $DefinitionData; + + # + # Read-Only + + protected $specialCharacters = array( + '\\', '`', '*', '_', '{', '}', '[', ']', '(', ')', '>', '#', '+', '-', '.', '!', '|', '~' + ); + + protected $StrongRegex = array( + '*' => '/^[*]{2}((?:\\\\\*|[^*]|[*][^*]*+[*])+?)[*]{2}(?![*])/s', + '_' => '/^__((?:\\\\_|[^_]|_[^_]*+_)+?)__(?!_)/us', + ); + + protected $EmRegex = array( + '*' => '/^[*]((?:\\\\\*|[^*]|[*][*][^*]+?[*][*])+?)[*](?![*])/s', + '_' => '/^_((?:\\\\_|[^_]|__[^_]*__)+?)_(?!_)\b/us', + ); + + protected $regexHtmlAttribute = '[a-zA-Z_:][\w:.-]*+(?:\s*+=\s*+(?:[^"\'=<>`\s]+|"[^"]*+"|\'[^\']*+\'))?+'; + + protected $voidElements = array( + 'area', 'base', 'br', 'col', 'command', 'embed', 'hr', 'img', 'input', 'link', 'meta', 'param', 'source', + ); + + protected $textLevelElements = array( + 'a', 'br', 'bdo', 'abbr', 'blink', 'nextid', 'acronym', 'basefont', + 'b', 'em', 'big', 'cite', 'small', 'spacer', 'listing', + 'i', 'rp', 'del', 'code', 'strike', 'marquee', + 'q', 'rt', 'ins', 'font', 'strong', + 's', 'tt', 'kbd', 'mark', + 'u', 'xm', 'sub', 'nobr', + 'sup', 'ruby', + 'var', 'span', + 'wbr', 'time', + ); +} diff --git a/kirby/i18n/rules/LICENSE b/kirby/i18n/rules/LICENSE new file mode 100644 index 0000000..36c3036 --- /dev/null +++ b/kirby/i18n/rules/LICENSE @@ -0,0 +1,9 @@ +The MIT License (MIT) + +Copyright (c) 2012-217 Florian Eckerstorfer + +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/kirby/i18n/rules/ar.json b/kirby/i18n/rules/ar.json new file mode 100644 index 0000000..e46915f --- /dev/null +++ b/kirby/i18n/rules/ar.json @@ -0,0 +1,30 @@ +{ + "أ" : "a", + "ب" : "b", + "ت" : "t", + "ث" : "th", + "ج" : "g", + "ح" : "h", + "خ" : "kh", + "د" : "d", + "ذ" : "th", + "ر" : "r", + "ز" : "z", + "س" : "s", + "ش" : "sh", + "ص" : "s", + "ض" : "d", + "ط" : "t", + "ظ" : "th", + "ع" : "aa", + "غ" : "gh", + "ف" : "f", + "ق" : "k", + "ك" : "k", + "ل" : "l", + "م" : "m", + "ن" : "n", + "ه" : "h", + "و" : "o", + "ي" : "y" +} diff --git a/kirby/i18n/rules/az.json b/kirby/i18n/rules/az.json new file mode 100644 index 0000000..ad6e2a9 --- /dev/null +++ b/kirby/i18n/rules/az.json @@ -0,0 +1,16 @@ +{ + "Ə": "E", + "Ç": "C", + "Ğ": "G", + "İ": "I", + "Ş": "S", + "Ö": "O", + "Ü": "U", + "ə": "e", + "ç": "c", + "ğ": "g", + "ı": "i", + "ş": "s", + "ö": "o", + "ü": "u" +} diff --git a/kirby/i18n/rules/bg.json b/kirby/i18n/rules/bg.json new file mode 100644 index 0000000..4c45ca1 --- /dev/null +++ b/kirby/i18n/rules/bg.json @@ -0,0 +1,65 @@ +{ + "А": "A", + "Б": "B", + "В": "V", + "Г": "G", + "Д": "D", + "Е": "E", + "Ж": "J", + "З": "Z", + "И": "I", + "Й": "Y", + "К": "K", + "Л": "L", + "М": "M", + "Н": "N", + "О": "O", + "П": "P", + "Р": "R", + "С": "S", + "Т": "T", + "У": "U", + "Ф": "F", + "Х": "H", + "Ц": "Ts", + "Ч": "Ch", + "Ш": "Sh", + "Щ": "Sht", + "Ъ": "A", + "Ь": "I", + "Ю": "Iu", + "Я": "Ia", + "а": "a", + "б": "b", + "в": "v", + "г": "g", + "д": "d", + "е": "e", + "ж": "j", + "з": "z", + "и": "i", + "й": "y", + "к": "k", + "л": "l", + "м": "m", + "н": "n", + "о": "o", + "п": "p", + "р": "r", + "с": "s", + "т": "t", + "у": "u", + "ф": "f", + "х": "h", + "ц": "ts", + "ч": "ch", + "ш": "sh", + "щ": "sht", + "ъ": "a", + "ь": "i", + "ю": "iu", + "я": "ia", + "ия": "ia", + "йо": "iо", + "ьо": "io" +} diff --git a/kirby/i18n/rules/cs.json b/kirby/i18n/rules/cs.json new file mode 100644 index 0000000..549f805 --- /dev/null +++ b/kirby/i18n/rules/cs.json @@ -0,0 +1,20 @@ +{ + "Č": "C", + "Ď": "D", + "Ě": "E", + "Ň": "N", + "Ř": "R", + "Š": "S", + "Ť": "T", + "Ů": "U", + "Ž": "Z", + "č": "c", + "ď": "d", + "ě": "e", + "ň": "n", + "ř": "r", + "š": "s", + "ť": "t", + "ů": "u", + "ž": "z" +} diff --git a/kirby/i18n/rules/da.json b/kirby/i18n/rules/da.json new file mode 100644 index 0000000..b88c17c --- /dev/null +++ b/kirby/i18n/rules/da.json @@ -0,0 +1,10 @@ +{ + "Æ": "Ae", + "æ": "ae", + "Ø": "Oe", + "ø": "oe", + "Å": "Aa", + "å": "aa", + "É": "E", + "é": "e" +} diff --git a/kirby/i18n/rules/de.json b/kirby/i18n/rules/de.json new file mode 100644 index 0000000..881b68c --- /dev/null +++ b/kirby/i18n/rules/de.json @@ -0,0 +1,9 @@ +{ + "Ä": "AE", + "Ö": "OE", + "Ü": "UE", + "ß": "ss", + "ä": "ae", + "ö": "oe", + "ü": "ue" +} diff --git a/kirby/i18n/rules/el.json b/kirby/i18n/rules/el.json new file mode 100644 index 0000000..767a223 --- /dev/null +++ b/kirby/i18n/rules/el.json @@ -0,0 +1,111 @@ +{ + "ΑΥ": "AU", + "Αυ": "Au", + "ΟΥ": "OU", + "Ου": "Ou", + "ΕΥ": "EU", + "Ευ": "Eu", + "ΕΙ": "I", + "Ει": "I", + "ΟΙ": "I", + "Οι": "I", + "ΥΙ": "I", + "Υι": "I", + "ΑΎ": "AU", + "Αύ": "Au", + "ΟΎ": "OU", + "Ού": "Ou", + "ΕΎ": "EU", + "Εύ": "Eu", + "ΕΊ": "I", + "Εί": "I", + "ΟΊ": "I", + "Οί": "I", + "ΎΙ": "I", + "Ύι": "I", + "ΥΊ": "I", + "Υί": "I", + "αυ": "au", + "ου": "ou", + "ευ": "eu", + "ει": "i", + "οι": "i", + "υι": "i", + "αύ": "au", + "ού": "ou", + "εύ": "eu", + "εί": "i", + "οί": "i", + "ύι": "i", + "υί": "i", + "Α": "A", + "Β": "V", + "Γ": "G", + "Δ": "D", + "Ε": "E", + "Ζ": "Z", + "Η": "I", + "Θ": "Th", + "Ι": "I", + "Κ": "K", + "Λ": "L", + "Μ": "M", + "Ν": "N", + "Ξ": "X", + "Ο": "O", + "Π": "P", + "Ρ": "R", + "Σ": "S", + "Τ": "T", + "Υ": "I", + "Φ": "F", + "Χ": "Ch", + "Ψ": "Ps", + "Ω": "O", + "Ά": "A", + "Έ": "E", + "Ή": "I", + "Ί": "I", + "Ό": "O", + "Ύ": "I", + "Ϊ": "I", + "Ϋ": "I", + "ϒ": "I", + "α": "a", + "β": "v", + "γ": "g", + "δ": "d", + "ε": "e", + "ζ": "z", + "η": "i", + "θ": "th", + "ι": "i", + "κ": "k", + "λ": "l", + "μ": "m", + "ν": "n", + "ξ": "x", + "ο": "o", + "π": "p", + "ρ": "r", + "ς": "s", + "σ": "s", + "τ": "t", + "υ": "i", + "φ": "f", + "χ": "ch", + "ψ": "ps", + "ω": "o", + "ά": "a", + "έ": "e", + "ή": "i", + "ί": "i", + "ό": "o", + "ύ": "i", + "ϊ": "i", + "ϋ": "i", + "ΰ": "i", + "ώ": "o", + "ϐ": "v", + "ϑ": "th" +} diff --git a/kirby/i18n/rules/eo.json b/kirby/i18n/rules/eo.json new file mode 100644 index 0000000..9a4e658 --- /dev/null +++ b/kirby/i18n/rules/eo.json @@ -0,0 +1,14 @@ +{ + "ĉ": "cx", + "ĝ": "gx", + "ĥ": "hx", + "ĵ": "jx", + "ŝ": "sx", + "ŭ": "ux", + "Ĉ": "CX", + "Ĝ": "GX", + "Ĥ": "HX", + "Ĵ": "JX", + "Ŝ": "SX", + "Ŭ": "UX" +} diff --git a/kirby/i18n/rules/et.json b/kirby/i18n/rules/et.json new file mode 100644 index 0000000..fcea469 --- /dev/null +++ b/kirby/i18n/rules/et.json @@ -0,0 +1,14 @@ +{ + "Š": "S", + "Ž": "Z", + "Õ": "O", + "Ä": "A", + "Ö": "O", + "Ü": "U", + "š": "s", + "ž": "z", + "õ": "o", + "ä": "a", + "ö": "o", + "ü": "u" +} \ No newline at end of file diff --git a/kirby/i18n/rules/fa.json b/kirby/i18n/rules/fa.json new file mode 100644 index 0000000..0448016 --- /dev/null +++ b/kirby/i18n/rules/fa.json @@ -0,0 +1,36 @@ +{ + "آ" : "A", + "ا" : "a", + "ب" : "b", + "پ" : "p", + "ت" : "t", + "ث" : "th", + "ج" : "j", + "چ" : "ch", + "ح" : "h", + "خ" : "kh", + "د" : "d", + "ذ" : "th", + "ر" : "r", + "ز" : "z", + "ژ" : "zh", + "س" : "s", + "ش" : "sh", + "ص" : "s", + "ض" : "z", + "ط" : "t", + "ظ" : "z", + "ع" : "a", + "غ" : "gh", + "ف" : "f", + "ق" : "g", + "ك" : "k", + "ک" : "k", + "گ" : "g", + "ل" : "l", + "م" : "m", + "ن" : "n", + "و" : "o", + "ه" : "h", + "ی" : "y" +} diff --git a/kirby/i18n/rules/fi.json b/kirby/i18n/rules/fi.json new file mode 100644 index 0000000..fd35423 --- /dev/null +++ b/kirby/i18n/rules/fi.json @@ -0,0 +1,6 @@ +{ + "Ä": "A", + "Ö": "O", + "ä": "a", + "ö": "o" +} diff --git a/kirby/i18n/rules/fr.json b/kirby/i18n/rules/fr.json new file mode 100644 index 0000000..29c94b9 --- /dev/null +++ b/kirby/i18n/rules/fr.json @@ -0,0 +1,34 @@ +{ + "À": "A", + "Â": "A", + "Æ": "AE", + "Ç": "C", + "É": "E", + "È": "E", + "Ê": "E", + "Ë": "E", + "Ï": "I", + "Î": "I", + "Ô": "O", + "Œ": "OE", + "Ù": "U", + "Û": "U", + "Ü": "U", + "à": "a", + "â": "a", + "æ": "ae", + "ç": "c", + "é": "e", + "è": "e", + "ê": "e", + "ë": "e", + "ï": "i", + "î": "i", + "ô": "o", + "œ": "oe", + "ù": "u", + "û": "u", + "ü": "u", + "ÿ": "y", + "Ÿ": "Y" +} diff --git a/kirby/i18n/rules/hi.json b/kirby/i18n/rules/hi.json new file mode 100644 index 0000000..f653f15 --- /dev/null +++ b/kirby/i18n/rules/hi.json @@ -0,0 +1,66 @@ +{ + "अ": "a", + "आ": "aa", + "ए": "e", + "ई": "ii", + "ऍ": "ei", + "ऎ": "ae", + "ऐ": "ai", + "इ": "i", + "ओ": "o", + "ऑ": "oi", + "ऒ": "oii", + "ऊ": "uu", + "औ": "ou", + "उ": "u", + "ब": "B", + "भ": "Bha", + "च": "Ca", + "छ": "Chha", + "ड": "Da", + "ढ": "Dha", + "फ": "Fa", + "फ़": "Fi", + "ग": "Ga", + "घ": "Gha", + "ग़": "Ghi", + "ह": "Ha", + "ज": "Ja", + "झ": "Jha", + "क": "Ka", + "ख": "Kha", + "ख़": "Khi", + "ल": "L", + "ळ": "Li", + "ऌ": "Li", + "ऴ": "Lii", + "ॡ": "Lii", + "म": "Ma", + "न": "Na", + "ङ": "Na", + "ञ": "Nia", + "ण": "Nae", + "ऩ": "Ni", + "ॐ": "oms", + "प": "Pa", + "क़": "Qi", + "र": "Ra", + "ऋ": "Ri", + "ॠ": "Ri", + "ऱ": "Ri", + "स": "Sa", + "श": "Sha", + "ष": "Shha", + "ट": "Ta", + "त": "Ta", + "ठ": "Tha", + "द": "Tha", + "थ": "Tha", + "ध": "Thha", + "ड़": "ugDha", + "ढ़": "ugDhha", + "व": "Va", + "य": "Ya", + "य़": "Yi", + "ज़": "Za" +} diff --git a/kirby/i18n/rules/hr.json b/kirby/i18n/rules/hr.json new file mode 100644 index 0000000..bf2b10d --- /dev/null +++ b/kirby/i18n/rules/hr.json @@ -0,0 +1,12 @@ +{ + "Č": "C", + "Ć": "C", + "Ž": "Z", + "Š": "S", + "Đ": "Dj", + "č": "c", + "ć": "c", + "ž": "z", + "š": "s", + "đ": "dj" +} \ No newline at end of file diff --git a/kirby/i18n/rules/hu.json b/kirby/i18n/rules/hu.json new file mode 100644 index 0000000..2bb2f3a --- /dev/null +++ b/kirby/i18n/rules/hu.json @@ -0,0 +1,20 @@ +{ + "Á": "a", + "É": "e", + "Í": "i", + "Ó": "o", + "Ö": "o", + "Ő": "o", + "Ú": "u", + "Ü": "u", + "Ű": "u", + "á": "a", + "é": "e", + "í": "i", + "ó": "o", + "ö": "o", + "ő": "o", + "ú": "u", + "ü": "u", + "ű": "u" +} diff --git a/kirby/i18n/rules/hy.json b/kirby/i18n/rules/hy.json new file mode 100644 index 0000000..08188e6 --- /dev/null +++ b/kirby/i18n/rules/hy.json @@ -0,0 +1,79 @@ +{ + "Ա": "A", + "Բ": "B", + "Գ": "G", + "Դ": "D", + "Ե": "E", + "Զ": "Z", + "Է": "E", + "Ը": "Y", + "Թ": "Th", + "Ժ": "Zh", + "Ի": "I", + "Լ": "L", + "Խ": "Kh", + "Ծ": "Ts", + "Կ": "K", + "Հ": "H", + "Ձ": "Dz", + "Ղ": "Gh", + "Ճ": "Tch", + "Մ": "M", + "Յ": "Y", + "Ն": "N", + "Շ": "Sh", + "Ո": "Vo", + "Չ": "Ch", + "Պ": "P", + "Ջ": "J", + "Ռ": "R", + "Ս": "S", + "Վ": "V", + "Տ": "T", + "Ր": "R", + "Ց": "C", + "Ւ": "u", + "Փ": "Ph", + "Ք": "Q", + "և": "ev", + "Օ": "O", + "Ֆ": "F", + "ա": "a", + "բ": "b", + "գ": "g", + "դ": "d", + "ե": "e", + "զ": "z", + "է": "e", + "ը": "y", + "թ": "th", + "ժ": "zh", + "ի": "i", + "լ": "l", + "խ": "kh", + "ծ": "ts", + "կ": "k", + "հ": "h", + "ձ": "dz", + "ղ": "gh", + "ճ": "tch", + "մ": "m", + "յ": "y", + "ն": "n", + "շ": "sh", + "ո": "vo", + "չ": "ch", + "պ": "p", + "ջ": "j", + "ռ": "r", + "ս": "s", + "վ": "v", + "տ": "t", + "ր": "r", + "ց": "c", + "ւ": "u", + "փ": "ph", + "ք": "q", + "օ": "o", + "ֆ": "f" +} diff --git a/kirby/i18n/rules/it.json b/kirby/i18n/rules/it.json new file mode 100644 index 0000000..647c2cf --- /dev/null +++ b/kirby/i18n/rules/it.json @@ -0,0 +1,13 @@ +{ + "À": "a", + "È": "e", + "Ì": "i", + "Ò": "o", + "Ù": "u", + "à": "a", + "é": "e", + "è": "e", + "ì": "i", + "ò": "o", + "ù": "u" +} diff --git a/kirby/i18n/rules/ja.json b/kirby/i18n/rules/ja.json new file mode 100644 index 0000000..12f842d --- /dev/null +++ b/kirby/i18n/rules/ja.json @@ -0,0 +1,182 @@ +{ + "きゃ": "kya", + "しゃ": "sha", + "ちゃ": "cha", + "にゃ": "nya", + "ひゃ": "hya", + "みゃ": "mya", + "りゃ": "rya", + "ぎゃ": "gya", + "じゃ": "ja", + "ぢゃ": "ja", + "びゃ": "bya", + "ぴゃ": "pya", + + "きゅ": "kyu", + "しゅ": "shu", + "ちゅ": "chu", + "にゅ": "nyu", + "ひゅ": "hyu", + "みゅ": "myu", + "りゅ": "ryu", + "ぎゅ": "gyu", + "じゅ": "ju", + "ぢゅ": "ju", + "びゅ": "byu", + "ぴゅ": "pyu", + + "きょ": "kyo", + "しょ": "sho", + "ちょ": "cho", + "にょ": "nyo", + "ひょ": "hyo", + "みょ": "myo", + "りょ": "ryo", + "ぎょ": "gyo", + "じょ": "jo", + "ぢょ": "jo", + "びょ": "byo", + "ぴょ": "pyo", + + "あ": "a", + "ア": "a", + "か": "ka", + "カ": "ka", + "さ": "sa", + "サ": "sa", + "た": "ta", + "タ": "ta", + "な": "na", + "ナ": "na", + "は": "ha", + "ハ": "ha", + "ま": "ma", + "マ": "ma", + "や": "ya", + "ヤ": "ya", + "ら": "ra", + "ラ": "ra", + "わ": "wa", + "ワ": "wa", + "が": "ga", + "ざ": "za", + "ザ": "za", + "だ": "da", + "ば": "ba", + "ぱ": "pa", + "中": "naka", + "場": "ba", + "版": "han", + + "い": "i", + "イ": "i", + "き": "ki", + "キ": "ki", + "し": "shi", + "シ": "shi", + "ち": "chi", + "チ": "chi", + "に": "ni", + "ニ": "ni", + "ひ": "hi", + "ヒ": "hi", + "み": "mi", + "ミ": "mi", + "り": "ri", + "リ": "ri", + "ゐ": "wi", + "ヰ": "wi", + "ぎ": "gi", + "じ": "dji", + "ぢ": "ji", + "び": "bi", + "ぴ": "pi", + "仮": "kari", + "国": "kuni", + "鳥": "tori", + "劇": "geki", + + "う": "u", + "ウ": "u", + "く": "ku", + "ク": "ku", + "す": "su", + "ス": "su", + "つ": "tsu", + "ツ": "tsu", + "ぬ": "nu", + "ヌ": "nu", + "ふ": "fu", + "フ": "fu", + "む": "mu", + "ム": "mu", + "ゆ": "yu", + "ユ": "yu", + "る": "ru", + "ル": "ru", + "ぐ": "gu", + "ず": "zu", + "づ": "dzu", + "ぶ": "bu", + "ぷ": "pu", + "プ": "pu", + "ズ": "zu", + "グ": "gu", + + "え": "e", + "エ": "e", + "け": "ke", + "ケ": "ke", + "せ": "se", + "セ": "se", + "て": "te", + "テ": "te", + "ね": "ne", + "ネ": "ne", + "へ": "he", + "ヘ": "he", + "め": "me", + "メ": "me", + "れ": "re", + "レ": "re", + "ゑ": "we", + "ヱ": "we", + "げ": "ge", + "ぜ": "ze", + "で": "de", + "べ": "be", + "ぺ": "pe", + "面": "men", + + "お": "o", + "オ": "o", + "こ": "ko", + "コ": "ko", + "そ": "so", + "ソ": "so", + "と": "to", + "ト": "to", + "の": "no", + "ノ": "no", + "ほ": "ho", + "ホ": "ho", + "も": "mo", + "モ": "mo", + "よ": "yo", + "ヨ": "yo", + "ろ": "ro", + "ロ": "ro", + "を": "wo", + "ヲ": "wo", + "ん": "n", + "ン": "n", + "ご": "go", + "ぞ": "zo", + "ど": "do", + "ド": "do", + "ぼ": "bo", + "ポ": "po", + "ぽ": "po", + "男": "otoko", + "人": "hito" +} diff --git a/kirby/i18n/rules/ka.json b/kirby/i18n/rules/ka.json new file mode 100644 index 0000000..2c63573 --- /dev/null +++ b/kirby/i18n/rules/ka.json @@ -0,0 +1,35 @@ +{ + "ა": "a", + "ბ": "b", + "გ": "g", + "დ": "d", + "ე": "e", + "ვ": "v", + "ზ": "z", + "თ": "t", + "ი": "i", + "კ": "k", + "ლ": "l", + "მ": "m", + "ნ": "n", + "ო": "o", + "პ": "p", + "ჟ": "zh", + "რ": "r", + "ს": "s", + "ტ": "t", + "უ": "u", + "ფ": "f", + "ქ": "k", + "ღ": "gh", + "ყ": "q", + "შ": "sh", + "ჩ": "ch", + "ც": "ts", + "ძ": "dz", + "წ": "ts", + "ჭ": "ch", + "ხ": "kh", + "ჯ": "j", + "ჰ": "h" +} diff --git a/kirby/i18n/rules/ko.json b/kirby/i18n/rules/ko.json new file mode 100644 index 0000000..8dad2c0 --- /dev/null +++ b/kirby/i18n/rules/ko.json @@ -0,0 +1,11174 @@ +{ + "가": "ga", + "각": "gak", + "갂": "gakk", + "갃": "gak", + "간": "gan", + "갅": "gan", + "갆": "gan", + "갇": "gat", + "갈": "gal", + "갉": "gak", + "갊": "gam", + "갋": "gap", + "갌": "gat", + "갍": "gat", + "갎": "gap", + "갏": "gal", + "감": "gam", + "갑": "gap", + "값": "gap", + "갓": "gat", + "갔": "gat", + "강": "gang", + "갖": "gat", + "갗": "gat", + "갘": "gak", + "같": "gat", + "갚": "gap", + "갛": "gat", + "개": "gae", + "객": "gaek", + "갞": "gaekk", + "갟": "gaek", + "갠": "gaen", + "갡": "gaen", + "갢": "gaen", + "갣": "gaet", + "갤": "gael", + "갥": "gaek", + "갦": "gaem", + "갧": "gaep", + "갨": "gaet", + "갩": "gaet", + "갪": "gaep", + "갫": "gael", + "갬": "gaem", + "갭": "gaep", + "갮": "gaep", + "갯": "gaet", + "갰": "gaet", + "갱": "gaeng", + "갲": "gaet", + "갳": "gaet", + "갴": "gaek", + "갵": "gaet", + "갶": "gaep", + "갷": "gaet", + "갸": "gya", + "갹": "gyak", + "갺": "gyakk", + "갻": "gyak", + "갼": "gyan", + "갽": "gyan", + "갾": "gyan", + "갿": "gyat", + "걀": "gyal", + "걁": "gyak", + "걂": "gyam", + "걃": "gyap", + "걄": "gyat", + "걅": "gyat", + "걆": "gyap", + "걇": "gyal", + "걈": "gyam", + "걉": "gyap", + "걊": "gyap", + "걋": "gyat", + "걌": "gyat", + "걍": "gyang", + "걎": "gyat", + "걏": "gyat", + "걐": "gyak", + "걑": "gyat", + "걒": "gyap", + "걓": "gyat", + "걔": "gyae", + "걕": "gyaek", + "걖": "gyaekk", + "걗": "gyaek", + "걘": "gyaen", + "걙": "gyaen", + "걚": "gyaen", + "걛": "gyaet", + "걜": "gyael", + "걝": "gyaek", + "걞": "gyaem", + "걟": "gyaep", + "걠": "gyaet", + "걡": "gyaet", + "걢": "gyaep", + "걣": "gyael", + "걤": "gyaem", + "걥": "gyaep", + "걦": "gyaep", + "걧": "gyaet", + "걨": "gyaet", + "걩": "gyaeng", + "걪": "gyaet", + "걫": "gyaet", + "걬": "gyaek", + "걭": "gyaet", + "걮": "gyaep", + "걯": "gyaet", + "거": "geo", + "걱": "geok", + "걲": "geokk", + "걳": "geok", + "건": "geon", + "걵": "geon", + "걶": "geon", + "걷": "geot", + "걸": "geol", + "걹": "geok", + "걺": "geom", + "걻": "geop", + "걼": "geot", + "걽": "geot", + "걾": "geop", + "걿": "geol", + "검": "geom", + "겁": "geop", + "겂": "geop", + "것": "geot", + "겄": "geot", + "겅": "geong", + "겆": "geot", + "겇": "geot", + "겈": "geok", + "겉": "geot", + "겊": "geop", + "겋": "geot", + "게": "ge", + "겍": "gek", + "겎": "gekk", + "겏": "gek", + "겐": "gen", + "겑": "gen", + "겒": "gen", + "겓": "get", + "겔": "gel", + "겕": "gek", + "겖": "gem", + "겗": "gep", + "겘": "get", + "겙": "get", + "겚": "gep", + "겛": "gel", + "겜": "gem", + "겝": "gep", + "겞": "gep", + "겟": "get", + "겠": "get", + "겡": "geng", + "겢": "get", + "겣": "get", + "겤": "gek", + "겥": "get", + "겦": "gep", + "겧": "get", + "겨": "gyeo", + "격": "gyeok", + "겪": "gyeokk", + "겫": "gyeok", + "견": "gyeon", + "겭": "gyeon", + "겮": "gyeon", + "겯": "gyeot", + "결": "gyeol", + "겱": "gyeok", + "겲": "gyeom", + "겳": "gyeop", + "겴": "gyeot", + "겵": "gyeot", + "겶": "gyeop", + "겷": "gyeol", + "겸": "gyeom", + "겹": "gyeop", + "겺": "gyeop", + "겻": "gyeot", + "겼": "gyeot", + "경": "gyeong", + "겾": "gyeot", + "겿": "gyeot", + "곀": "gyeok", + "곁": "gyeot", + "곂": "gyeop", + "곃": "gyeot", + "계": "gye", + "곅": "gyek", + "곆": "gyekk", + "곇": "gyek", + "곈": "gyen", + "곉": "gyen", + "곊": "gyen", + "곋": "gyet", + "곌": "gyel", + "곍": "gyek", + "곎": "gyem", + "곏": "gyep", + "곐": "gyet", + "곑": "gyet", + "곒": "gyep", + "곓": "gyel", + "곔": "gyem", + "곕": "gyep", + "곖": "gyep", + "곗": "gyet", + "곘": "gyet", + "곙": "gyeng", + "곚": "gyet", + "곛": "gyet", + "곜": "gyek", + "곝": "gyet", + "곞": "gyep", + "곟": "gyet", + "고": "go", + "곡": "gok", + "곢": "gokk", + "곣": "gok", + "곤": "gon", + "곥": "gon", + "곦": "gon", + "곧": "got", + "골": "gol", + "곩": "gok", + "곪": "gom", + "곫": "gop", + "곬": "got", + "곭": "got", + "곮": "gop", + "곯": "gol", + "곰": "gom", + "곱": "gop", + "곲": "gop", + "곳": "got", + "곴": "got", + "공": "gong", + "곶": "got", + "곷": "got", + "곸": "gok", + "곹": "got", + "곺": "gop", + "곻": "got", + "과": "gwa", + "곽": "gwak", + "곾": "gwakk", + "곿": "gwak", + "관": "gwan", + "괁": "gwan", + "괂": "gwan", + "괃": "gwat", + "괄": "gwal", + "괅": "gwak", + "괆": "gwam", + "괇": "gwap", + "괈": "gwat", + "괉": "gwat", + "괊": "gwap", + "괋": "gwal", + "괌": "gwam", + "괍": "gwap", + "괎": "gwap", + "괏": "gwat", + "괐": "gwat", + "광": "gwang", + "괒": "gwat", + "괓": "gwat", + "괔": "gwak", + "괕": "gwat", + "괖": "gwap", + "괗": "gwat", + "괘": "gwae", + "괙": "gwaek", + "괚": "gwaekk", + "괛": "gwaek", + "괜": "gwaen", + "괝": "gwaen", + "괞": "gwaen", + "괟": "gwaet", + "괠": "gwael", + "괡": "gwaek", + "괢": "gwaem", + "괣": "gwaep", + "괤": "gwaet", + "괥": "gwaet", + "괦": "gwaep", + "괧": "gwael", + "괨": "gwaem", + "괩": "gwaep", + "괪": "gwaep", + "괫": "gwaet", + "괬": "gwaet", + "괭": "gwaeng", + "괮": "gwaet", + "괯": "gwaet", + "괰": "gwaek", + "괱": "gwaet", + "괲": "gwaep", + "괳": "gwaet", + "괴": "goe", + "괵": "goek", + "괶": "goekk", + "괷": "goek", + "괸": "goen", + "괹": "goen", + "괺": "goen", + "괻": "goet", + "괼": "goel", + "괽": "goek", + "괾": "goem", + "괿": "goep", + "굀": "goet", + "굁": "goet", + "굂": "goep", + "굃": "goel", + "굄": "goem", + "굅": "goep", + "굆": "goep", + "굇": "goet", + "굈": "goet", + "굉": "goeng", + "굊": "goet", + "굋": "goet", + "굌": "goek", + "굍": "goet", + "굎": "goep", + "굏": "goet", + "교": "gyo", + "굑": "gyok", + "굒": "gyokk", + "굓": "gyok", + "굔": "gyon", + "굕": "gyon", + "굖": "gyon", + "굗": "gyot", + "굘": "gyol", + "굙": "gyok", + "굚": "gyom", + "굛": "gyop", + "굜": "gyot", + "굝": "gyot", + "굞": "gyop", + "굟": "gyol", + "굠": "gyom", + "굡": "gyop", + "굢": "gyop", + "굣": "gyot", + "굤": "gyot", + "굥": "gyong", + "굦": "gyot", + "굧": "gyot", + "굨": "gyok", + "굩": "gyot", + "굪": "gyop", + "굫": "gyot", + "구": "gu", + "국": "guk", + "굮": "gukk", + "굯": "guk", + "군": "gun", + "굱": "gun", + "굲": "gun", + "굳": "gut", + "굴": "gul", + "굵": "guk", + "굶": "gum", + "굷": "gup", + "굸": "gut", + "굹": "gut", + "굺": "gup", + "굻": "gul", + "굼": "gum", + "굽": "gup", + "굾": "gup", + "굿": "gut", + "궀": "gut", + "궁": "gung", + "궂": "gut", + "궃": "gut", + "궄": "guk", + "궅": "gut", + "궆": "gup", + "궇": "gut", + "궈": "gwo", + "궉": "gwok", + "궊": "gwokk", + "궋": "gwok", + "권": "gwon", + "궍": "gwon", + "궎": "gwon", + "궏": "gwot", + "궐": "gwol", + "궑": "gwok", + "궒": "gwom", + "궓": "gwop", + "궔": "gwot", + "궕": "gwot", + "궖": "gwop", + "궗": "gwol", + "궘": "gwom", + "궙": "gwop", + "궚": "gwop", + "궛": "gwot", + "궜": "gwot", + "궝": "gwong", + "궞": "gwot", + "궟": "gwot", + "궠": "gwok", + "궡": "gwot", + "궢": "gwop", + "궣": "gwot", + "궤": "gwe", + "궥": "gwek", + "궦": "gwekk", + "궧": "gwek", + "궨": "gwen", + "궩": "gwen", + "궪": "gwen", + "궫": "gwet", + "궬": "gwel", + "궭": "gwek", + "궮": "gwem", + "궯": "gwep", + "궰": "gwet", + "궱": "gwet", + "궲": "gwep", + "궳": "gwel", + "궴": "gwem", + "궵": "gwep", + "궶": "gwep", + "궷": "gwet", + "궸": "gwet", + "궹": "gweng", + "궺": "gwet", + "궻": "gwet", + "궼": "gwek", + "궽": "gwet", + "궾": "gwep", + "궿": "gwet", + "귀": "gwi", + "귁": "gwik", + "귂": "gwikk", + "귃": "gwik", + "귄": "gwin", + "귅": "gwin", + "귆": "gwin", + "귇": "gwit", + "귈": "gwil", + "귉": "gwik", + "귊": "gwim", + "귋": "gwip", + "귌": "gwit", + "귍": "gwit", + "귎": "gwip", + "귏": "gwil", + "귐": "gwim", + "귑": "gwip", + "귒": "gwip", + "귓": "gwit", + "귔": "gwit", + "귕": "gwing", + "귖": "gwit", + "귗": "gwit", + "귘": "gwik", + "귙": "gwit", + "귚": "gwip", + "귛": "gwit", + "규": "gyu", + "귝": "gyuk", + "귞": "gyukk", + "귟": "gyuk", + "균": "gyun", + "귡": "gyun", + "귢": "gyun", + "귣": "gyut", + "귤": "gyul", + "귥": "gyuk", + "귦": "gyum", + "귧": "gyup", + "귨": "gyut", + "귩": "gyut", + "귪": "gyup", + "귫": "gyul", + "귬": "gyum", + "귭": "gyup", + "귮": "gyup", + "귯": "gyut", + "귰": "gyut", + "귱": "gyung", + "귲": "gyut", + "귳": "gyut", + "귴": "gyuk", + "귵": "gyut", + "귶": "gyup", + "귷": "gyut", + "그": "geu", + "극": "geuk", + "귺": "geukk", + "귻": "geuk", + "근": "geun", + "귽": "geun", + "귾": "geun", + "귿": "geut", + "글": "geul", + "긁": "geuk", + "긂": "geum", + "긃": "geup", + "긄": "geut", + "긅": "geut", + "긆": "geup", + "긇": "geul", + "금": "geum", + "급": "geup", + "긊": "geup", + "긋": "geut", + "긌": "geut", + "긍": "geung", + "긎": "geut", + "긏": "geut", + "긐": "geuk", + "긑": "geut", + "긒": "geup", + "긓": "geut", + "긔": "geui", + "긕": "geuik", + "긖": "geuikk", + "긗": "geuik", + "긘": "geuin", + "긙": "geuin", + "긚": "geuin", + "긛": "geuit", + "긜": "geuil", + "긝": "geuik", + "긞": "geuim", + "긟": "geuip", + "긠": "geuit", + "긡": "geuit", + "긢": "geuip", + "긣": "geuil", + "긤": "geuim", + "긥": "geuip", + "긦": "geuip", + "긧": "geuit", + "긨": "geuit", + "긩": "geuing", + "긪": "geuit", + "긫": "geuit", + "긬": "geuik", + "긭": "geuit", + "긮": "geuip", + "긯": "geuit", + "기": "gi", + "긱": "gik", + "긲": "gikk", + "긳": "gik", + "긴": "gin", + "긵": "gin", + "긶": "gin", + "긷": "git", + "길": "gil", + "긹": "gik", + "긺": "gim", + "긻": "gip", + "긼": "git", + "긽": "git", + "긾": "gip", + "긿": "gil", + "김": "gim", + "깁": "gip", + "깂": "gip", + "깃": "git", + "깄": "git", + "깅": "ging", + "깆": "git", + "깇": "git", + "깈": "gik", + "깉": "git", + "깊": "gip", + "깋": "git", + "까": "kka", + "깍": "kkak", + "깎": "kkakk", + "깏": "kkak", + "깐": "kkan", + "깑": "kkan", + "깒": "kkan", + "깓": "kkat", + "깔": "kkal", + "깕": "kkak", + "깖": "kkam", + "깗": "kkap", + "깘": "kkat", + "깙": "kkat", + "깚": "kkap", + "깛": "kkal", + "깜": "kkam", + "깝": "kkap", + "깞": "kkap", + "깟": "kkat", + "깠": "kkat", + "깡": "kkang", + "깢": "kkat", + "깣": "kkat", + "깤": "kkak", + "깥": "kkat", + "깦": "kkap", + "깧": "kkat", + "깨": "kkae", + "깩": "kkaek", + "깪": "kkaekk", + "깫": "kkaek", + "깬": "kkaen", + "깭": "kkaen", + "깮": "kkaen", + "깯": "kkaet", + "깰": "kkael", + "깱": "kkaek", + "깲": "kkaem", + "깳": "kkaep", + "깴": "kkaet", + "깵": "kkaet", + "깶": "kkaep", + "깷": "kkael", + "깸": "kkaem", + "깹": "kkaep", + "깺": "kkaep", + "깻": "kkaet", + "깼": "kkaet", + "깽": "kkaeng", + "깾": "kkaet", + "깿": "kkaet", + "꺀": "kkaek", + "꺁": "kkaet", + "꺂": "kkaep", + "꺃": "kkaet", + "꺄": "kkya", + "꺅": "kkyak", + "꺆": "kkyakk", + "꺇": "kkyak", + "꺈": "kkyan", + "꺉": "kkyan", + "꺊": "kkyan", + "꺋": "kkyat", + "꺌": "kkyal", + "꺍": "kkyak", + "꺎": "kkyam", + "꺏": "kkyap", + "꺐": "kkyat", + "꺑": "kkyat", + "꺒": "kkyap", + "꺓": "kkyal", + "꺔": "kkyam", + "꺕": "kkyap", + "꺖": "kkyap", + "꺗": "kkyat", + "꺘": "kkyat", + "꺙": "kkyang", + "꺚": "kkyat", + "꺛": "kkyat", + "꺜": "kkyak", + "꺝": "kkyat", + "꺞": "kkyap", + "꺟": "kkyat", + "꺠": "kkyae", + "꺡": "kkyaek", + "꺢": "kkyaekk", + "꺣": "kkyaek", + "꺤": "kkyaen", + "꺥": "kkyaen", + "꺦": "kkyaen", + "꺧": "kkyaet", + "꺨": "kkyael", + "꺩": "kkyaek", + "꺪": "kkyaem", + "꺫": "kkyaep", + "꺬": "kkyaet", + "꺭": "kkyaet", + "꺮": "kkyaep", + "꺯": "kkyael", + "꺰": "kkyaem", + "꺱": "kkyaep", + "꺲": "kkyaep", + "꺳": "kkyaet", + "꺴": "kkyaet", + "꺵": "kkyaeng", + "꺶": "kkyaet", + "꺷": "kkyaet", + "꺸": "kkyaek", + "꺹": "kkyaet", + "꺺": "kkyaep", + "꺻": "kkyaet", + "꺼": "kkeo", + "꺽": "kkeok", + "꺾": "kkeokk", + "꺿": "kkeok", + "껀": "kkeon", + "껁": "kkeon", + "껂": "kkeon", + "껃": "kkeot", + "껄": "kkeol", + "껅": "kkeok", + "껆": "kkeom", + "껇": "kkeop", + "껈": "kkeot", + "껉": "kkeot", + "껊": "kkeop", + "껋": "kkeol", + "껌": "kkeom", + "껍": "kkeop", + "껎": "kkeop", + "껏": "kkeot", + "껐": "kkeot", + "껑": "kkeong", + "껒": "kkeot", + "껓": "kkeot", + "껔": "kkeok", + "껕": "kkeot", + "껖": "kkeop", + "껗": "kkeot", + "께": "kke", + "껙": "kkek", + "껚": "kkekk", + "껛": "kkek", + "껜": "kken", + "껝": "kken", + "껞": "kken", + "껟": "kket", + "껠": "kkel", + "껡": "kkek", + "껢": "kkem", + "껣": "kkep", + "껤": "kket", + "껥": "kket", + "껦": "kkep", + "껧": "kkel", + "껨": "kkem", + "껩": "kkep", + "껪": "kkep", + "껫": "kket", + "껬": "kket", + "껭": "kkeng", + "껮": "kket", + "껯": "kket", + "껰": "kkek", + "껱": "kket", + "껲": "kkep", + "껳": "kket", + "껴": "kkyeo", + "껵": "kkyeok", + "껶": "kkyeokk", + "껷": "kkyeok", + "껸": "kkyeon", + "껹": "kkyeon", + "껺": "kkyeon", + "껻": "kkyeot", + "껼": "kkyeol", + "껽": "kkyeok", + "껾": "kkyeom", + "껿": "kkyeop", + "꼀": "kkyeot", + "꼁": "kkyeot", + "꼂": "kkyeop", + "꼃": "kkyeol", + "꼄": "kkyeom", + "꼅": "kkyeop", + "꼆": "kkyeop", + "꼇": "kkyeot", + "꼈": "kkyeot", + "꼉": "kkyeong", + "꼊": "kkyeot", + "꼋": "kkyeot", + "꼌": "kkyeok", + "꼍": "kkyeot", + "꼎": "kkyeop", + "꼏": "kkyeot", + "꼐": "kkye", + "꼑": "kkyek", + "꼒": "kkyekk", + "꼓": "kkyek", + "꼔": "kkyen", + "꼕": "kkyen", + "꼖": "kkyen", + "꼗": "kkyet", + "꼘": "kkyel", + "꼙": "kkyek", + "꼚": "kkyem", + "꼛": "kkyep", + "꼜": "kkyet", + "꼝": "kkyet", + "꼞": "kkyep", + "꼟": "kkyel", + "꼠": "kkyem", + "꼡": "kkyep", + "꼢": "kkyep", + "꼣": "kkyet", + "꼤": "kkyet", + "꼥": "kkyeng", + "꼦": "kkyet", + "꼧": "kkyet", + "꼨": "kkyek", + "꼩": "kkyet", + "꼪": "kkyep", + "꼫": "kkyet", + "꼬": "kko", + "꼭": "kkok", + "꼮": "kkokk", + "꼯": "kkok", + "꼰": "kkon", + "꼱": "kkon", + "꼲": "kkon", + "꼳": "kkot", + "꼴": "kkol", + "꼵": "kkok", + "꼶": "kkom", + "꼷": "kkop", + "꼸": "kkot", + "꼹": "kkot", + "꼺": "kkop", + "꼻": "kkol", + "꼼": "kkom", + "꼽": "kkop", + "꼾": "kkop", + "꼿": "kkot", + "꽀": "kkot", + "꽁": "kkong", + "꽂": "kkot", + "꽃": "kkot", + "꽄": "kkok", + "꽅": "kkot", + "꽆": "kkop", + "꽇": "kkot", + "꽈": "kkwa", + "꽉": "kkwak", + "꽊": "kkwakk", + "꽋": "kkwak", + "꽌": "kkwan", + "꽍": "kkwan", + "꽎": "kkwan", + "꽏": "kkwat", + "꽐": "kkwal", + "꽑": "kkwak", + "꽒": "kkwam", + "꽓": "kkwap", + "꽔": "kkwat", + "꽕": "kkwat", + "꽖": "kkwap", + "꽗": "kkwal", + "꽘": "kkwam", + "꽙": "kkwap", + "꽚": "kkwap", + "꽛": "kkwat", + "꽜": "kkwat", + "꽝": "kkwang", + "꽞": "kkwat", + "꽟": "kkwat", + "꽠": "kkwak", + "꽡": "kkwat", + "꽢": "kkwap", + "꽣": "kkwat", + "꽤": "kkwae", + "꽥": "kkwaek", + "꽦": "kkwaekk", + "꽧": "kkwaek", + "꽨": "kkwaen", + "꽩": "kkwaen", + "꽪": "kkwaen", + "꽫": "kkwaet", + "꽬": "kkwael", + "꽭": "kkwaek", + "꽮": "kkwaem", + "꽯": "kkwaep", + "꽰": "kkwaet", + "꽱": "kkwaet", + "꽲": "kkwaep", + "꽳": "kkwael", + "꽴": "kkwaem", + "꽵": "kkwaep", + "꽶": "kkwaep", + "꽷": "kkwaet", + "꽸": "kkwaet", + "꽹": "kkwaeng", + "꽺": "kkwaet", + "꽻": "kkwaet", + "꽼": "kkwaek", + "꽽": "kkwaet", + "꽾": "kkwaep", + "꽿": "kkwaet", + "꾀": "kkoe", + "꾁": "kkoek", + "꾂": "kkoekk", + "꾃": "kkoek", + "꾄": "kkoen", + "꾅": "kkoen", + "꾆": "kkoen", + "꾇": "kkoet", + "꾈": "kkoel", + "꾉": "kkoek", + "꾊": "kkoem", + "꾋": "kkoep", + "꾌": "kkoet", + "꾍": "kkoet", + "꾎": "kkoep", + "꾏": "kkoel", + "꾐": "kkoem", + "꾑": "kkoep", + "꾒": "kkoep", + "꾓": "kkoet", + "꾔": "kkoet", + "꾕": "kkoeng", + "꾖": "kkoet", + "꾗": "kkoet", + "꾘": "kkoek", + "꾙": "kkoet", + "꾚": "kkoep", + "꾛": "kkoet", + "꾜": "kkyo", + "꾝": "kkyok", + "꾞": "kkyokk", + "꾟": "kkyok", + "꾠": "kkyon", + "꾡": "kkyon", + "꾢": "kkyon", + "꾣": "kkyot", + "꾤": "kkyol", + "꾥": "kkyok", + "꾦": "kkyom", + "꾧": "kkyop", + "꾨": "kkyot", + "꾩": "kkyot", + "꾪": "kkyop", + "꾫": "kkyol", + "꾬": "kkyom", + "꾭": "kkyop", + "꾮": "kkyop", + "꾯": "kkyot", + "꾰": "kkyot", + "꾱": "kkyong", + "꾲": "kkyot", + "꾳": "kkyot", + "꾴": "kkyok", + "꾵": "kkyot", + "꾶": "kkyop", + "꾷": "kkyot", + "꾸": "kku", + "꾹": "kkuk", + "꾺": "kkukk", + "꾻": "kkuk", + "꾼": "kkun", + "꾽": "kkun", + "꾾": "kkun", + "꾿": "kkut", + "꿀": "kkul", + "꿁": "kkuk", + "꿂": "kkum", + "꿃": "kkup", + "꿄": "kkut", + "꿅": "kkut", + "꿆": "kkup", + "꿇": "kkul", + "꿈": "kkum", + "꿉": "kkup", + "꿊": "kkup", + "꿋": "kkut", + "꿌": "kkut", + "꿍": "kkung", + "꿎": "kkut", + "꿏": "kkut", + "꿐": "kkuk", + "꿑": "kkut", + "꿒": "kkup", + "꿓": "kkut", + "꿔": "kkwo", + "꿕": "kkwok", + "꿖": "kkwokk", + "꿗": "kkwok", + "꿘": "kkwon", + "꿙": "kkwon", + "꿚": "kkwon", + "꿛": "kkwot", + "꿜": "kkwol", + "꿝": "kkwok", + "꿞": "kkwom", + "꿟": "kkwop", + "꿠": "kkwot", + "꿡": "kkwot", + "꿢": "kkwop", + "꿣": "kkwol", + "꿤": "kkwom", + "꿥": "kkwop", + "꿦": "kkwop", + "꿧": "kkwot", + "꿨": "kkwot", + "꿩": "kkwong", + "꿪": "kkwot", + "꿫": "kkwot", + "꿬": "kkwok", + "꿭": "kkwot", + "꿮": "kkwop", + "꿯": "kkwot", + "꿰": "kkwe", + "꿱": "kkwek", + "꿲": "kkwekk", + "꿳": "kkwek", + "꿴": "kkwen", + "꿵": "kkwen", + "꿶": "kkwen", + "꿷": "kkwet", + "꿸": "kkwel", + "꿹": "kkwek", + "꿺": "kkwem", + "꿻": "kkwep", + "꿼": "kkwet", + "꿽": "kkwet", + "꿾": "kkwep", + "꿿": "kkwel", + "뀀": "kkwem", + "뀁": "kkwep", + "뀂": "kkwep", + "뀃": "kkwet", + "뀄": "kkwet", + "뀅": "kkweng", + "뀆": "kkwet", + "뀇": "kkwet", + "뀈": "kkwek", + "뀉": "kkwet", + "뀊": "kkwep", + "뀋": "kkwet", + "뀌": "kkwi", + "뀍": "kkwik", + "뀎": "kkwikk", + "뀏": "kkwik", + "뀐": "kkwin", + "뀑": "kkwin", + "뀒": "kkwin", + "뀓": "kkwit", + "뀔": "kkwil", + "뀕": "kkwik", + "뀖": "kkwim", + "뀗": "kkwip", + "뀘": "kkwit", + "뀙": "kkwit", + "뀚": "kkwip", + "뀛": "kkwil", + "뀜": "kkwim", + "뀝": "kkwip", + "뀞": "kkwip", + "뀟": "kkwit", + "뀠": "kkwit", + "뀡": "kkwing", + "뀢": "kkwit", + "뀣": "kkwit", + "뀤": "kkwik", + "뀥": "kkwit", + "뀦": "kkwip", + "뀧": "kkwit", + "뀨": "kkyu", + "뀩": "kkyuk", + "뀪": "kkyukk", + "뀫": "kkyuk", + "뀬": "kkyun", + "뀭": "kkyun", + "뀮": "kkyun", + "뀯": "kkyut", + "뀰": "kkyul", + "뀱": "kkyuk", + "뀲": "kkyum", + "뀳": "kkyup", + "뀴": "kkyut", + "뀵": "kkyut", + "뀶": "kkyup", + "뀷": "kkyul", + "뀸": "kkyum", + "뀹": "kkyup", + "뀺": "kkyup", + "뀻": "kkyut", + "뀼": "kkyut", + "뀽": "kkyung", + "뀾": "kkyut", + "뀿": "kkyut", + "끀": "kkyuk", + "끁": "kkyut", + "끂": "kkyup", + "끃": "kkyut", + "끄": "kkeu", + "끅": "kkeuk", + "끆": "kkeukk", + "끇": "kkeuk", + "끈": "kkeun", + "끉": "kkeun", + "끊": "kkeun", + "끋": "kkeut", + "끌": "kkeul", + "끍": "kkeuk", + "끎": "kkeum", + "끏": "kkeup", + "끐": "kkeut", + "끑": "kkeut", + "끒": "kkeup", + "끓": "kkeul", + "끔": "kkeum", + "끕": "kkeup", + "끖": "kkeup", + "끗": "kkeut", + "끘": "kkeut", + "끙": "kkeung", + "끚": "kkeut", + "끛": "kkeut", + "끜": "kkeuk", + "끝": "kkeut", + "끞": "kkeup", + "끟": "kkeut", + "끠": "kkeui", + "끡": "kkeuik", + "끢": "kkeuikk", + "끣": "kkeuik", + "끤": "kkeuin", + "끥": "kkeuin", + "끦": "kkeuin", + "끧": "kkeuit", + "끨": "kkeuil", + "끩": "kkeuik", + "끪": "kkeuim", + "끫": "kkeuip", + "끬": "kkeuit", + "끭": "kkeuit", + "끮": "kkeuip", + "끯": "kkeuil", + "끰": "kkeuim", + "끱": "kkeuip", + "끲": "kkeuip", + "끳": "kkeuit", + "끴": "kkeuit", + "끵": "kkeuing", + "끶": "kkeuit", + "끷": "kkeuit", + "끸": "kkeuik", + "끹": "kkeuit", + "끺": "kkeuip", + "끻": "kkeuit", + "끼": "kki", + "끽": "kkik", + "끾": "kkikk", + "끿": "kkik", + "낀": "kkin", + "낁": "kkin", + "낂": "kkin", + "낃": "kkit", + "낄": "kkil", + "낅": "kkik", + "낆": "kkim", + "낇": "kkip", + "낈": "kkit", + "낉": "kkit", + "낊": "kkip", + "낋": "kkil", + "낌": "kkim", + "낍": "kkip", + "낎": "kkip", + "낏": "kkit", + "낐": "kkit", + "낑": "kking", + "낒": "kkit", + "낓": "kkit", + "낔": "kkik", + "낕": "kkit", + "낖": "kkip", + "낗": "kkit", + "나": "na", + "낙": "nak", + "낚": "nakk", + "낛": "nak", + "난": "nan", + "낝": "nan", + "낞": "nan", + "낟": "nat", + "날": "nal", + "낡": "nak", + "낢": "nam", + "낣": "nap", + "낤": "nat", + "낥": "nat", + "낦": "nap", + "낧": "nal", + "남": "nam", + "납": "nap", + "낪": "nap", + "낫": "nat", + "났": "nat", + "낭": "nang", + "낮": "nat", + "낯": "nat", + "낰": "nak", + "낱": "nat", + "낲": "nap", + "낳": "nat", + "내": "nae", + "낵": "naek", + "낶": "naekk", + "낷": "naek", + "낸": "naen", + "낹": "naen", + "낺": "naen", + "낻": "naet", + "낼": "nael", + "낽": "naek", + "낾": "naem", + "낿": "naep", + "냀": "naet", + "냁": "naet", + "냂": "naep", + "냃": "nael", + "냄": "naem", + "냅": "naep", + "냆": "naep", + "냇": "naet", + "냈": "naet", + "냉": "naeng", + "냊": "naet", + "냋": "naet", + "냌": "naek", + "냍": "naet", + "냎": "naep", + "냏": "naet", + "냐": "nya", + "냑": "nyak", + "냒": "nyakk", + "냓": "nyak", + "냔": "nyan", + "냕": "nyan", + "냖": "nyan", + "냗": "nyat", + "냘": "nyal", + "냙": "nyak", + "냚": "nyam", + "냛": "nyap", + "냜": "nyat", + "냝": "nyat", + "냞": "nyap", + "냟": "nyal", + "냠": "nyam", + "냡": "nyap", + "냢": "nyap", + "냣": "nyat", + "냤": "nyat", + "냥": "nyang", + "냦": "nyat", + "냧": "nyat", + "냨": "nyak", + "냩": "nyat", + "냪": "nyap", + "냫": "nyat", + "냬": "nyae", + "냭": "nyaek", + "냮": "nyaekk", + "냯": "nyaek", + "냰": "nyaen", + "냱": "nyaen", + "냲": "nyaen", + "냳": "nyaet", + "냴": "nyael", + "냵": "nyaek", + "냶": "nyaem", + "냷": "nyaep", + "냸": "nyaet", + "냹": "nyaet", + "냺": "nyaep", + "냻": "nyael", + "냼": "nyaem", + "냽": "nyaep", + "냾": "nyaep", + "냿": "nyaet", + "넀": "nyaet", + "넁": "nyaeng", + "넂": "nyaet", + "넃": "nyaet", + "넄": "nyaek", + "넅": "nyaet", + "넆": "nyaep", + "넇": "nyaet", + "너": "neo", + "넉": "neok", + "넊": "neokk", + "넋": "neok", + "넌": "neon", + "넍": "neon", + "넎": "neon", + "넏": "neot", + "널": "neol", + "넑": "neok", + "넒": "neom", + "넓": "neop", + "넔": "neot", + "넕": "neot", + "넖": "neop", + "넗": "neol", + "넘": "neom", + "넙": "neop", + "넚": "neop", + "넛": "neot", + "넜": "neot", + "넝": "neong", + "넞": "neot", + "넟": "neot", + "넠": "neok", + "넡": "neot", + "넢": "neop", + "넣": "neot", + "네": "ne", + "넥": "nek", + "넦": "nekk", + "넧": "nek", + "넨": "nen", + "넩": "nen", + "넪": "nen", + "넫": "net", + "넬": "nel", + "넭": "nek", + "넮": "nem", + "넯": "nep", + "넰": "net", + "넱": "net", + "넲": "nep", + "넳": "nel", + "넴": "nem", + "넵": "nep", + "넶": "nep", + "넷": "net", + "넸": "net", + "넹": "neng", + "넺": "net", + "넻": "net", + "넼": "nek", + "넽": "net", + "넾": "nep", + "넿": "net", + "녀": "nyeo", + "녁": "nyeok", + "녂": "nyeokk", + "녃": "nyeok", + "년": "nyeon", + "녅": "nyeon", + "녆": "nyeon", + "녇": "nyeot", + "녈": "nyeol", + "녉": "nyeok", + "녊": "nyeom", + "녋": "nyeop", + "녌": "nyeot", + "녍": "nyeot", + "녎": "nyeop", + "녏": "nyeol", + "념": "nyeom", + "녑": "nyeop", + "녒": "nyeop", + "녓": "nyeot", + "녔": "nyeot", + "녕": "nyeong", + "녖": "nyeot", + "녗": "nyeot", + "녘": "nyeok", + "녙": "nyeot", + "녚": "nyeop", + "녛": "nyeot", + "녜": "nye", + "녝": "nyek", + "녞": "nyekk", + "녟": "nyek", + "녠": "nyen", + "녡": "nyen", + "녢": "nyen", + "녣": "nyet", + "녤": "nyel", + "녥": "nyek", + "녦": "nyem", + "녧": "nyep", + "녨": "nyet", + "녩": "nyet", + "녪": "nyep", + "녫": "nyel", + "녬": "nyem", + "녭": "nyep", + "녮": "nyep", + "녯": "nyet", + "녰": "nyet", + "녱": "nyeng", + "녲": "nyet", + "녳": "nyet", + "녴": "nyek", + "녵": "nyet", + "녶": "nyep", + "녷": "nyet", + "노": "no", + "녹": "nok", + "녺": "nokk", + "녻": "nok", + "논": "non", + "녽": "non", + "녾": "non", + "녿": "not", + "놀": "nol", + "놁": "nok", + "놂": "nom", + "놃": "nop", + "놄": "not", + "놅": "not", + "놆": "nop", + "놇": "nol", + "놈": "nom", + "놉": "nop", + "놊": "nop", + "놋": "not", + "놌": "not", + "농": "nong", + "놎": "not", + "놏": "not", + "놐": "nok", + "놑": "not", + "높": "nop", + "놓": "not", + "놔": "nwa", + "놕": "nwak", + "놖": "nwakk", + "놗": "nwak", + "놘": "nwan", + "놙": "nwan", + "놚": "nwan", + "놛": "nwat", + "놜": "nwal", + "놝": "nwak", + "놞": "nwam", + "놟": "nwap", + "놠": "nwat", + "놡": "nwat", + "놢": "nwap", + "놣": "nwal", + "놤": "nwam", + "놥": "nwap", + "놦": "nwap", + "놧": "nwat", + "놨": "nwat", + "놩": "nwang", + "놪": "nwat", + "놫": "nwat", + "놬": "nwak", + "놭": "nwat", + "놮": "nwap", + "놯": "nwat", + "놰": "nwae", + "놱": "nwaek", + "놲": "nwaekk", + "놳": "nwaek", + "놴": "nwaen", + "놵": "nwaen", + "놶": "nwaen", + "놷": "nwaet", + "놸": "nwael", + "놹": "nwaek", + "놺": "nwaem", + "놻": "nwaep", + "놼": "nwaet", + "놽": "nwaet", + "놾": "nwaep", + "놿": "nwael", + "뇀": "nwaem", + "뇁": "nwaep", + "뇂": "nwaep", + "뇃": "nwaet", + "뇄": "nwaet", + "뇅": "nwaeng", + "뇆": "nwaet", + "뇇": "nwaet", + "뇈": "nwaek", + "뇉": "nwaet", + "뇊": "nwaep", + "뇋": "nwaet", + "뇌": "noe", + "뇍": "noek", + "뇎": "noekk", + "뇏": "noek", + "뇐": "noen", + "뇑": "noen", + "뇒": "noen", + "뇓": "noet", + "뇔": "noel", + "뇕": "noek", + "뇖": "noem", + "뇗": "noep", + "뇘": "noet", + "뇙": "noet", + "뇚": "noep", + "뇛": "noel", + "뇜": "noem", + "뇝": "noep", + "뇞": "noep", + "뇟": "noet", + "뇠": "noet", + "뇡": "noeng", + "뇢": "noet", + "뇣": "noet", + "뇤": "noek", + "뇥": "noet", + "뇦": "noep", + "뇧": "noet", + "뇨": "nyo", + "뇩": "nyok", + "뇪": "nyokk", + "뇫": "nyok", + "뇬": "nyon", + "뇭": "nyon", + "뇮": "nyon", + "뇯": "nyot", + "뇰": "nyol", + "뇱": "nyok", + "뇲": "nyom", + "뇳": "nyop", + "뇴": "nyot", + "뇵": "nyot", + "뇶": "nyop", + "뇷": "nyol", + "뇸": "nyom", + "뇹": "nyop", + "뇺": "nyop", + "뇻": "nyot", + "뇼": "nyot", + "뇽": "nyong", + "뇾": "nyot", + "뇿": "nyot", + "눀": "nyok", + "눁": "nyot", + "눂": "nyop", + "눃": "nyot", + "누": "nu", + "눅": "nuk", + "눆": "nukk", + "눇": "nuk", + "눈": "nun", + "눉": "nun", + "눊": "nun", + "눋": "nut", + "눌": "nul", + "눍": "nuk", + "눎": "num", + "눏": "nup", + "눐": "nut", + "눑": "nut", + "눒": "nup", + "눓": "nul", + "눔": "num", + "눕": "nup", + "눖": "nup", + "눗": "nut", + "눘": "nut", + "눙": "nung", + "눚": "nut", + "눛": "nut", + "눜": "nuk", + "눝": "nut", + "눞": "nup", + "눟": "nut", + "눠": "nwo", + "눡": "nwok", + "눢": "nwokk", + "눣": "nwok", + "눤": "nwon", + "눥": "nwon", + "눦": "nwon", + "눧": "nwot", + "눨": "nwol", + "눩": "nwok", + "눪": "nwom", + "눫": "nwop", + "눬": "nwot", + "눭": "nwot", + "눮": "nwop", + "눯": "nwol", + "눰": "nwom", + "눱": "nwop", + "눲": "nwop", + "눳": "nwot", + "눴": "nwot", + "눵": "nwong", + "눶": "nwot", + "눷": "nwot", + "눸": "nwok", + "눹": "nwot", + "눺": "nwop", + "눻": "nwot", + "눼": "nwe", + "눽": "nwek", + "눾": "nwekk", + "눿": "nwek", + "뉀": "nwen", + "뉁": "nwen", + "뉂": "nwen", + "뉃": "nwet", + "뉄": "nwel", + "뉅": "nwek", + "뉆": "nwem", + "뉇": "nwep", + "뉈": "nwet", + "뉉": "nwet", + "뉊": "nwep", + "뉋": "nwel", + "뉌": "nwem", + "뉍": "nwep", + "뉎": "nwep", + "뉏": "nwet", + "뉐": "nwet", + "뉑": "nweng", + "뉒": "nwet", + "뉓": "nwet", + "뉔": "nwek", + "뉕": "nwet", + "뉖": "nwep", + "뉗": "nwet", + "뉘": "nwi", + "뉙": "nwik", + "뉚": "nwikk", + "뉛": "nwik", + "뉜": "nwin", + "뉝": "nwin", + "뉞": "nwin", + "뉟": "nwit", + "뉠": "nwil", + "뉡": "nwik", + "뉢": "nwim", + "뉣": "nwip", + "뉤": "nwit", + "뉥": "nwit", + "뉦": "nwip", + "뉧": "nwil", + "뉨": "nwim", + "뉩": "nwip", + "뉪": "nwip", + "뉫": "nwit", + "뉬": "nwit", + "뉭": "nwing", + "뉮": "nwit", + "뉯": "nwit", + "뉰": "nwik", + "뉱": "nwit", + "뉲": "nwip", + "뉳": "nwit", + "뉴": "nyu", + "뉵": "nyuk", + "뉶": "nyukk", + "뉷": "nyuk", + "뉸": "nyun", + "뉹": "nyun", + "뉺": "nyun", + "뉻": "nyut", + "뉼": "nyul", + "뉽": "nyuk", + "뉾": "nyum", + "뉿": "nyup", + "늀": "nyut", + "늁": "nyut", + "늂": "nyup", + "늃": "nyul", + "늄": "nyum", + "늅": "nyup", + "늆": "nyup", + "늇": "nyut", + "늈": "nyut", + "늉": "nyung", + "늊": "nyut", + "늋": "nyut", + "늌": "nyuk", + "늍": "nyut", + "늎": "nyup", + "늏": "nyut", + "느": "neu", + "늑": "neuk", + "늒": "neukk", + "늓": "neuk", + "는": "neun", + "늕": "neun", + "늖": "neun", + "늗": "neut", + "늘": "neul", + "늙": "neuk", + "늚": "neum", + "늛": "neup", + "늜": "neut", + "늝": "neut", + "늞": "neup", + "늟": "neul", + "늠": "neum", + "늡": "neup", + "늢": "neup", + "늣": "neut", + "늤": "neut", + "능": "neung", + "늦": "neut", + "늧": "neut", + "늨": "neuk", + "늩": "neut", + "늪": "neup", + "늫": "neut", + "늬": "neui", + "늭": "neuik", + "늮": "neuikk", + "늯": "neuik", + "늰": "neuin", + "늱": "neuin", + "늲": "neuin", + "늳": "neuit", + "늴": "neuil", + "늵": "neuik", + "늶": "neuim", + "늷": "neuip", + "늸": "neuit", + "늹": "neuit", + "늺": "neuip", + "늻": "neuil", + "늼": "neuim", + "늽": "neuip", + "늾": "neuip", + "늿": "neuit", + "닀": "neuit", + "닁": "neuing", + "닂": "neuit", + "닃": "neuit", + "닄": "neuik", + "닅": "neuit", + "닆": "neuip", + "닇": "neuit", + "니": "ni", + "닉": "nik", + "닊": "nikk", + "닋": "nik", + "닌": "nin", + "닍": "nin", + "닎": "nin", + "닏": "nit", + "닐": "nil", + "닑": "nik", + "닒": "nim", + "닓": "nip", + "닔": "nit", + "닕": "nit", + "닖": "nip", + "닗": "nil", + "님": "nim", + "닙": "nip", + "닚": "nip", + "닛": "nit", + "닜": "nit", + "닝": "ning", + "닞": "nit", + "닟": "nit", + "닠": "nik", + "닡": "nit", + "닢": "nip", + "닣": "nit", + "다": "da", + "닥": "dak", + "닦": "dakk", + "닧": "dak", + "단": "dan", + "닩": "dan", + "닪": "dan", + "닫": "dat", + "달": "dal", + "닭": "dak", + "닮": "dam", + "닯": "dap", + "닰": "dat", + "닱": "dat", + "닲": "dap", + "닳": "dal", + "담": "dam", + "답": "dap", + "닶": "dap", + "닷": "dat", + "닸": "dat", + "당": "dang", + "닺": "dat", + "닻": "dat", + "닼": "dak", + "닽": "dat", + "닾": "dap", + "닿": "dat", + "대": "dae", + "댁": "daek", + "댂": "daekk", + "댃": "daek", + "댄": "daen", + "댅": "daen", + "댆": "daen", + "댇": "daet", + "댈": "dael", + "댉": "daek", + "댊": "daem", + "댋": "daep", + "댌": "daet", + "댍": "daet", + "댎": "daep", + "댏": "dael", + "댐": "daem", + "댑": "daep", + "댒": "daep", + "댓": "daet", + "댔": "daet", + "댕": "daeng", + "댖": "daet", + "댗": "daet", + "댘": "daek", + "댙": "daet", + "댚": "daep", + "댛": "daet", + "댜": "dya", + "댝": "dyak", + "댞": "dyakk", + "댟": "dyak", + "댠": "dyan", + "댡": "dyan", + "댢": "dyan", + "댣": "dyat", + "댤": "dyal", + "댥": "dyak", + "댦": "dyam", + "댧": "dyap", + "댨": "dyat", + "댩": "dyat", + "댪": "dyap", + "댫": "dyal", + "댬": "dyam", + "댭": "dyap", + "댮": "dyap", + "댯": "dyat", + "댰": "dyat", + "댱": "dyang", + "댲": "dyat", + "댳": "dyat", + "댴": "dyak", + "댵": "dyat", + "댶": "dyap", + "댷": "dyat", + "댸": "dyae", + "댹": "dyaek", + "댺": "dyaekk", + "댻": "dyaek", + "댼": "dyaen", + "댽": "dyaen", + "댾": "dyaen", + "댿": "dyaet", + "덀": "dyael", + "덁": "dyaek", + "덂": "dyaem", + "덃": "dyaep", + "덄": "dyaet", + "덅": "dyaet", + "덆": "dyaep", + "덇": "dyael", + "덈": "dyaem", + "덉": "dyaep", + "덊": "dyaep", + "덋": "dyaet", + "덌": "dyaet", + "덍": "dyaeng", + "덎": "dyaet", + "덏": "dyaet", + "덐": "dyaek", + "덑": "dyaet", + "덒": "dyaep", + "덓": "dyaet", + "더": "deo", + "덕": "deok", + "덖": "deokk", + "덗": "deok", + "던": "deon", + "덙": "deon", + "덚": "deon", + "덛": "deot", + "덜": "deol", + "덝": "deok", + "덞": "deom", + "덟": "deop", + "덠": "deot", + "덡": "deot", + "덢": "deop", + "덣": "deol", + "덤": "deom", + "덥": "deop", + "덦": "deop", + "덧": "deot", + "덨": "deot", + "덩": "deong", + "덪": "deot", + "덫": "deot", + "덬": "deok", + "덭": "deot", + "덮": "deop", + "덯": "deot", + "데": "de", + "덱": "dek", + "덲": "dekk", + "덳": "dek", + "덴": "den", + "덵": "den", + "덶": "den", + "덷": "det", + "델": "del", + "덹": "dek", + "덺": "dem", + "덻": "dep", + "덼": "det", + "덽": "det", + "덾": "dep", + "덿": "del", + "뎀": "dem", + "뎁": "dep", + "뎂": "dep", + "뎃": "det", + "뎄": "det", + "뎅": "deng", + "뎆": "det", + "뎇": "det", + "뎈": "dek", + "뎉": "det", + "뎊": "dep", + "뎋": "det", + "뎌": "dyeo", + "뎍": "dyeok", + "뎎": "dyeokk", + "뎏": "dyeok", + "뎐": "dyeon", + "뎑": "dyeon", + "뎒": "dyeon", + "뎓": "dyeot", + "뎔": "dyeol", + "뎕": "dyeok", + "뎖": "dyeom", + "뎗": "dyeop", + "뎘": "dyeot", + "뎙": "dyeot", + "뎚": "dyeop", + "뎛": "dyeol", + "뎜": "dyeom", + "뎝": "dyeop", + "뎞": "dyeop", + "뎟": "dyeot", + "뎠": "dyeot", + "뎡": "dyeong", + "뎢": "dyeot", + "뎣": "dyeot", + "뎤": "dyeok", + "뎥": "dyeot", + "뎦": "dyeop", + "뎧": "dyeot", + "뎨": "dye", + "뎩": "dyek", + "뎪": "dyekk", + "뎫": "dyek", + "뎬": "dyen", + "뎭": "dyen", + "뎮": "dyen", + "뎯": "dyet", + "뎰": "dyel", + "뎱": "dyek", + "뎲": "dyem", + "뎳": "dyep", + "뎴": "dyet", + "뎵": "dyet", + "뎶": "dyep", + "뎷": "dyel", + "뎸": "dyem", + "뎹": "dyep", + "뎺": "dyep", + "뎻": "dyet", + "뎼": "dyet", + "뎽": "dyeng", + "뎾": "dyet", + "뎿": "dyet", + "돀": "dyek", + "돁": "dyet", + "돂": "dyep", + "돃": "dyet", + "도": "do", + "독": "dok", + "돆": "dokk", + "돇": "dok", + "돈": "don", + "돉": "don", + "돊": "don", + "돋": "dot", + "돌": "dol", + "돍": "dok", + "돎": "dom", + "돏": "dop", + "돐": "dot", + "돑": "dot", + "돒": "dop", + "돓": "dol", + "돔": "dom", + "돕": "dop", + "돖": "dop", + "돗": "dot", + "돘": "dot", + "동": "dong", + "돚": "dot", + "돛": "dot", + "돜": "dok", + "돝": "dot", + "돞": "dop", + "돟": "dot", + "돠": "dwa", + "돡": "dwak", + "돢": "dwakk", + "돣": "dwak", + "돤": "dwan", + "돥": "dwan", + "돦": "dwan", + "돧": "dwat", + "돨": "dwal", + "돩": "dwak", + "돪": "dwam", + "돫": "dwap", + "돬": "dwat", + "돭": "dwat", + "돮": "dwap", + "돯": "dwal", + "돰": "dwam", + "돱": "dwap", + "돲": "dwap", + "돳": "dwat", + "돴": "dwat", + "돵": "dwang", + "돶": "dwat", + "돷": "dwat", + "돸": "dwak", + "돹": "dwat", + "돺": "dwap", + "돻": "dwat", + "돼": "dwae", + "돽": "dwaek", + "돾": "dwaekk", + "돿": "dwaek", + "됀": "dwaen", + "됁": "dwaen", + "됂": "dwaen", + "됃": "dwaet", + "됄": "dwael", + "됅": "dwaek", + "됆": "dwaem", + "됇": "dwaep", + "됈": "dwaet", + "됉": "dwaet", + "됊": "dwaep", + "됋": "dwael", + "됌": "dwaem", + "됍": "dwaep", + "됎": "dwaep", + "됏": "dwaet", + "됐": "dwaet", + "됑": "dwaeng", + "됒": "dwaet", + "됓": "dwaet", + "됔": "dwaek", + "됕": "dwaet", + "됖": "dwaep", + "됗": "dwaet", + "되": "doe", + "됙": "doek", + "됚": "doekk", + "됛": "doek", + "된": "doen", + "됝": "doen", + "됞": "doen", + "됟": "doet", + "될": "doel", + "됡": "doek", + "됢": "doem", + "됣": "doep", + "됤": "doet", + "됥": "doet", + "됦": "doep", + "됧": "doel", + "됨": "doem", + "됩": "doep", + "됪": "doep", + "됫": "doet", + "됬": "doet", + "됭": "doeng", + "됮": "doet", + "됯": "doet", + "됰": "doek", + "됱": "doet", + "됲": "doep", + "됳": "doet", + "됴": "dyo", + "됵": "dyok", + "됶": "dyokk", + "됷": "dyok", + "됸": "dyon", + "됹": "dyon", + "됺": "dyon", + "됻": "dyot", + "됼": "dyol", + "됽": "dyok", + "됾": "dyom", + "됿": "dyop", + "둀": "dyot", + "둁": "dyot", + "둂": "dyop", + "둃": "dyol", + "둄": "dyom", + "둅": "dyop", + "둆": "dyop", + "둇": "dyot", + "둈": "dyot", + "둉": "dyong", + "둊": "dyot", + "둋": "dyot", + "둌": "dyok", + "둍": "dyot", + "둎": "dyop", + "둏": "dyot", + "두": "du", + "둑": "duk", + "둒": "dukk", + "둓": "duk", + "둔": "dun", + "둕": "dun", + "둖": "dun", + "둗": "dut", + "둘": "dul", + "둙": "duk", + "둚": "dum", + "둛": "dup", + "둜": "dut", + "둝": "dut", + "둞": "dup", + "둟": "dul", + "둠": "dum", + "둡": "dup", + "둢": "dup", + "둣": "dut", + "둤": "dut", + "둥": "dung", + "둦": "dut", + "둧": "dut", + "둨": "duk", + "둩": "dut", + "둪": "dup", + "둫": "dut", + "둬": "dwo", + "둭": "dwok", + "둮": "dwokk", + "둯": "dwok", + "둰": "dwon", + "둱": "dwon", + "둲": "dwon", + "둳": "dwot", + "둴": "dwol", + "둵": "dwok", + "둶": "dwom", + "둷": "dwop", + "둸": "dwot", + "둹": "dwot", + "둺": "dwop", + "둻": "dwol", + "둼": "dwom", + "둽": "dwop", + "둾": "dwop", + "둿": "dwot", + "뒀": "dwot", + "뒁": "dwong", + "뒂": "dwot", + "뒃": "dwot", + "뒄": "dwok", + "뒅": "dwot", + "뒆": "dwop", + "뒇": "dwot", + "뒈": "dwe", + "뒉": "dwek", + "뒊": "dwekk", + "뒋": "dwek", + "뒌": "dwen", + "뒍": "dwen", + "뒎": "dwen", + "뒏": "dwet", + "뒐": "dwel", + "뒑": "dwek", + "뒒": "dwem", + "뒓": "dwep", + "뒔": "dwet", + "뒕": "dwet", + "뒖": "dwep", + "뒗": "dwel", + "뒘": "dwem", + "뒙": "dwep", + "뒚": "dwep", + "뒛": "dwet", + "뒜": "dwet", + "뒝": "dweng", + "뒞": "dwet", + "뒟": "dwet", + "뒠": "dwek", + "뒡": "dwet", + "뒢": "dwep", + "뒣": "dwet", + "뒤": "dwi", + "뒥": "dwik", + "뒦": "dwikk", + "뒧": "dwik", + "뒨": "dwin", + "뒩": "dwin", + "뒪": "dwin", + "뒫": "dwit", + "뒬": "dwil", + "뒭": "dwik", + "뒮": "dwim", + "뒯": "dwip", + "뒰": "dwit", + "뒱": "dwit", + "뒲": "dwip", + "뒳": "dwil", + "뒴": "dwim", + "뒵": "dwip", + "뒶": "dwip", + "뒷": "dwit", + "뒸": "dwit", + "뒹": "dwing", + "뒺": "dwit", + "뒻": "dwit", + "뒼": "dwik", + "뒽": "dwit", + "뒾": "dwip", + "뒿": "dwit", + "듀": "dyu", + "듁": "dyuk", + "듂": "dyukk", + "듃": "dyuk", + "듄": "dyun", + "듅": "dyun", + "듆": "dyun", + "듇": "dyut", + "듈": "dyul", + "듉": "dyuk", + "듊": "dyum", + "듋": "dyup", + "듌": "dyut", + "듍": "dyut", + "듎": "dyup", + "듏": "dyul", + "듐": "dyum", + "듑": "dyup", + "듒": "dyup", + "듓": "dyut", + "듔": "dyut", + "듕": "dyung", + "듖": "dyut", + "듗": "dyut", + "듘": "dyuk", + "듙": "dyut", + "듚": "dyup", + "듛": "dyut", + "드": "deu", + "득": "deuk", + "듞": "deukk", + "듟": "deuk", + "든": "deun", + "듡": "deun", + "듢": "deun", + "듣": "deut", + "들": "deul", + "듥": "deuk", + "듦": "deum", + "듧": "deup", + "듨": "deut", + "듩": "deut", + "듪": "deup", + "듫": "deul", + "듬": "deum", + "듭": "deup", + "듮": "deup", + "듯": "deut", + "듰": "deut", + "등": "deung", + "듲": "deut", + "듳": "deut", + "듴": "deuk", + "듵": "deut", + "듶": "deup", + "듷": "deut", + "듸": "deui", + "듹": "deuik", + "듺": "deuikk", + "듻": "deuik", + "듼": "deuin", + "듽": "deuin", + "듾": "deuin", + "듿": "deuit", + "딀": "deuil", + "딁": "deuik", + "딂": "deuim", + "딃": "deuip", + "딄": "deuit", + "딅": "deuit", + "딆": "deuip", + "딇": "deuil", + "딈": "deuim", + "딉": "deuip", + "딊": "deuip", + "딋": "deuit", + "딌": "deuit", + "딍": "deuing", + "딎": "deuit", + "딏": "deuit", + "딐": "deuik", + "딑": "deuit", + "딒": "deuip", + "딓": "deuit", + "디": "di", + "딕": "dik", + "딖": "dikk", + "딗": "dik", + "딘": "din", + "딙": "din", + "딚": "din", + "딛": "dit", + "딜": "dil", + "딝": "dik", + "딞": "dim", + "딟": "dip", + "딠": "dit", + "딡": "dit", + "딢": "dip", + "딣": "dil", + "딤": "dim", + "딥": "dip", + "딦": "dip", + "딧": "dit", + "딨": "dit", + "딩": "ding", + "딪": "dit", + "딫": "dit", + "딬": "dik", + "딭": "dit", + "딮": "dip", + "딯": "dit", + "따": "tta", + "딱": "ttak", + "딲": "ttakk", + "딳": "ttak", + "딴": "ttan", + "딵": "ttan", + "딶": "ttan", + "딷": "ttat", + "딸": "ttal", + "딹": "ttak", + "딺": "ttam", + "딻": "ttap", + "딼": "ttat", + "딽": "ttat", + "딾": "ttap", + "딿": "ttal", + "땀": "ttam", + "땁": "ttap", + "땂": "ttap", + "땃": "ttat", + "땄": "ttat", + "땅": "ttang", + "땆": "ttat", + "땇": "ttat", + "땈": "ttak", + "땉": "ttat", + "땊": "ttap", + "땋": "ttat", + "때": "ttae", + "땍": "ttaek", + "땎": "ttaekk", + "땏": "ttaek", + "땐": "ttaen", + "땑": "ttaen", + "땒": "ttaen", + "땓": "ttaet", + "땔": "ttael", + "땕": "ttaek", + "땖": "ttaem", + "땗": "ttaep", + "땘": "ttaet", + "땙": "ttaet", + "땚": "ttaep", + "땛": "ttael", + "땜": "ttaem", + "땝": "ttaep", + "땞": "ttaep", + "땟": "ttaet", + "땠": "ttaet", + "땡": "ttaeng", + "땢": "ttaet", + "땣": "ttaet", + "땤": "ttaek", + "땥": "ttaet", + "땦": "ttaep", + "땧": "ttaet", + "땨": "ttya", + "땩": "ttyak", + "땪": "ttyakk", + "땫": "ttyak", + "땬": "ttyan", + "땭": "ttyan", + "땮": "ttyan", + "땯": "ttyat", + "땰": "ttyal", + "땱": "ttyak", + "땲": "ttyam", + "땳": "ttyap", + "땴": "ttyat", + "땵": "ttyat", + "땶": "ttyap", + "땷": "ttyal", + "땸": "ttyam", + "땹": "ttyap", + "땺": "ttyap", + "땻": "ttyat", + "땼": "ttyat", + "땽": "ttyang", + "땾": "ttyat", + "땿": "ttyat", + "떀": "ttyak", + "떁": "ttyat", + "떂": "ttyap", + "떃": "ttyat", + "떄": "ttyae", + "떅": "ttyaek", + "떆": "ttyaekk", + "떇": "ttyaek", + "떈": "ttyaen", + "떉": "ttyaen", + "떊": "ttyaen", + "떋": "ttyaet", + "떌": "ttyael", + "떍": "ttyaek", + "떎": "ttyaem", + "떏": "ttyaep", + "떐": "ttyaet", + "떑": "ttyaet", + "떒": "ttyaep", + "떓": "ttyael", + "떔": "ttyaem", + "떕": "ttyaep", + "떖": "ttyaep", + "떗": "ttyaet", + "떘": "ttyaet", + "떙": "ttyaeng", + "떚": "ttyaet", + "떛": "ttyaet", + "떜": "ttyaek", + "떝": "ttyaet", + "떞": "ttyaep", + "떟": "ttyaet", + "떠": "tteo", + "떡": "tteok", + "떢": "tteokk", + "떣": "tteok", + "떤": "tteon", + "떥": "tteon", + "떦": "tteon", + "떧": "tteot", + "떨": "tteol", + "떩": "tteok", + "떪": "tteom", + "떫": "tteop", + "떬": "tteot", + "떭": "tteot", + "떮": "tteop", + "떯": "tteol", + "떰": "tteom", + "떱": "tteop", + "떲": "tteop", + "떳": "tteot", + "떴": "tteot", + "떵": "tteong", + "떶": "tteot", + "떷": "tteot", + "떸": "tteok", + "떹": "tteot", + "떺": "tteop", + "떻": "tteot", + "떼": "tte", + "떽": "ttek", + "떾": "ttekk", + "떿": "ttek", + "뗀": "tten", + "뗁": "tten", + "뗂": "tten", + "뗃": "ttet", + "뗄": "ttel", + "뗅": "ttek", + "뗆": "ttem", + "뗇": "ttep", + "뗈": "ttet", + "뗉": "ttet", + "뗊": "ttep", + "뗋": "ttel", + "뗌": "ttem", + "뗍": "ttep", + "뗎": "ttep", + "뗏": "ttet", + "뗐": "ttet", + "뗑": "tteng", + "뗒": "ttet", + "뗓": "ttet", + "뗔": "ttek", + "뗕": "ttet", + "뗖": "ttep", + "뗗": "ttet", + "뗘": "ttyeo", + "뗙": "ttyeok", + "뗚": "ttyeokk", + "뗛": "ttyeok", + "뗜": "ttyeon", + "뗝": "ttyeon", + "뗞": "ttyeon", + "뗟": "ttyeot", + "뗠": "ttyeol", + "뗡": "ttyeok", + "뗢": "ttyeom", + "뗣": "ttyeop", + "뗤": "ttyeot", + "뗥": "ttyeot", + "뗦": "ttyeop", + "뗧": "ttyeol", + "뗨": "ttyeom", + "뗩": "ttyeop", + "뗪": "ttyeop", + "뗫": "ttyeot", + "뗬": "ttyeot", + "뗭": "ttyeong", + "뗮": "ttyeot", + "뗯": "ttyeot", + "뗰": "ttyeok", + "뗱": "ttyeot", + "뗲": "ttyeop", + "뗳": "ttyeot", + "뗴": "ttye", + "뗵": "ttyek", + "뗶": "ttyekk", + "뗷": "ttyek", + "뗸": "ttyen", + "뗹": "ttyen", + "뗺": "ttyen", + "뗻": "ttyet", + "뗼": "ttyel", + "뗽": "ttyek", + "뗾": "ttyem", + "뗿": "ttyep", + "똀": "ttyet", + "똁": "ttyet", + "똂": "ttyep", + "똃": "ttyel", + "똄": "ttyem", + "똅": "ttyep", + "똆": "ttyep", + "똇": "ttyet", + "똈": "ttyet", + "똉": "ttyeng", + "똊": "ttyet", + "똋": "ttyet", + "똌": "ttyek", + "똍": "ttyet", + "똎": "ttyep", + "똏": "ttyet", + "또": "tto", + "똑": "ttok", + "똒": "ttokk", + "똓": "ttok", + "똔": "tton", + "똕": "tton", + "똖": "tton", + "똗": "ttot", + "똘": "ttol", + "똙": "ttok", + "똚": "ttom", + "똛": "ttop", + "똜": "ttot", + "똝": "ttot", + "똞": "ttop", + "똟": "ttol", + "똠": "ttom", + "똡": "ttop", + "똢": "ttop", + "똣": "ttot", + "똤": "ttot", + "똥": "ttong", + "똦": "ttot", + "똧": "ttot", + "똨": "ttok", + "똩": "ttot", + "똪": "ttop", + "똫": "ttot", + "똬": "ttwa", + "똭": "ttwak", + "똮": "ttwakk", + "똯": "ttwak", + "똰": "ttwan", + "똱": "ttwan", + "똲": "ttwan", + "똳": "ttwat", + "똴": "ttwal", + "똵": "ttwak", + "똶": "ttwam", + "똷": "ttwap", + "똸": "ttwat", + "똹": "ttwat", + "똺": "ttwap", + "똻": "ttwal", + "똼": "ttwam", + "똽": "ttwap", + "똾": "ttwap", + "똿": "ttwat", + "뙀": "ttwat", + "뙁": "ttwang", + "뙂": "ttwat", + "뙃": "ttwat", + "뙄": "ttwak", + "뙅": "ttwat", + "뙆": "ttwap", + "뙇": "ttwat", + "뙈": "ttwae", + "뙉": "ttwaek", + "뙊": "ttwaekk", + "뙋": "ttwaek", + "뙌": "ttwaen", + "뙍": "ttwaen", + "뙎": "ttwaen", + "뙏": "ttwaet", + "뙐": "ttwael", + "뙑": "ttwaek", + "뙒": "ttwaem", + "뙓": "ttwaep", + "뙔": "ttwaet", + "뙕": "ttwaet", + "뙖": "ttwaep", + "뙗": "ttwael", + "뙘": "ttwaem", + "뙙": "ttwaep", + "뙚": "ttwaep", + "뙛": "ttwaet", + "뙜": "ttwaet", + "뙝": "ttwaeng", + "뙞": "ttwaet", + "뙟": "ttwaet", + "뙠": "ttwaek", + "뙡": "ttwaet", + "뙢": "ttwaep", + "뙣": "ttwaet", + "뙤": "ttoe", + "뙥": "ttoek", + "뙦": "ttoekk", + "뙧": "ttoek", + "뙨": "ttoen", + "뙩": "ttoen", + "뙪": "ttoen", + "뙫": "ttoet", + "뙬": "ttoel", + "뙭": "ttoek", + "뙮": "ttoem", + "뙯": "ttoep", + "뙰": "ttoet", + "뙱": "ttoet", + "뙲": "ttoep", + "뙳": "ttoel", + "뙴": "ttoem", + "뙵": "ttoep", + "뙶": "ttoep", + "뙷": "ttoet", + "뙸": "ttoet", + "뙹": "ttoeng", + "뙺": "ttoet", + "뙻": "ttoet", + "뙼": "ttoek", + "뙽": "ttoet", + "뙾": "ttoep", + "뙿": "ttoet", + "뚀": "ttyo", + "뚁": "ttyok", + "뚂": "ttyokk", + "뚃": "ttyok", + "뚄": "ttyon", + "뚅": "ttyon", + "뚆": "ttyon", + "뚇": "ttyot", + "뚈": "ttyol", + "뚉": "ttyok", + "뚊": "ttyom", + "뚋": "ttyop", + "뚌": "ttyot", + "뚍": "ttyot", + "뚎": "ttyop", + "뚏": "ttyol", + "뚐": "ttyom", + "뚑": "ttyop", + "뚒": "ttyop", + "뚓": "ttyot", + "뚔": "ttyot", + "뚕": "ttyong", + "뚖": "ttyot", + "뚗": "ttyot", + "뚘": "ttyok", + "뚙": "ttyot", + "뚚": "ttyop", + "뚛": "ttyot", + "뚜": "ttu", + "뚝": "ttuk", + "뚞": "ttukk", + "뚟": "ttuk", + "뚠": "ttun", + "뚡": "ttun", + "뚢": "ttun", + "뚣": "ttut", + "뚤": "ttul", + "뚥": "ttuk", + "뚦": "ttum", + "뚧": "ttup", + "뚨": "ttut", + "뚩": "ttut", + "뚪": "ttup", + "뚫": "ttul", + "뚬": "ttum", + "뚭": "ttup", + "뚮": "ttup", + "뚯": "ttut", + "뚰": "ttut", + "뚱": "ttung", + "뚲": "ttut", + "뚳": "ttut", + "뚴": "ttuk", + "뚵": "ttut", + "뚶": "ttup", + "뚷": "ttut", + "뚸": "ttwo", + "뚹": "ttwok", + "뚺": "ttwokk", + "뚻": "ttwok", + "뚼": "ttwon", + "뚽": "ttwon", + "뚾": "ttwon", + "뚿": "ttwot", + "뛀": "ttwol", + "뛁": "ttwok", + "뛂": "ttwom", + "뛃": "ttwop", + "뛄": "ttwot", + "뛅": "ttwot", + "뛆": "ttwop", + "뛇": "ttwol", + "뛈": "ttwom", + "뛉": "ttwop", + "뛊": "ttwop", + "뛋": "ttwot", + "뛌": "ttwot", + "뛍": "ttwong", + "뛎": "ttwot", + "뛏": "ttwot", + "뛐": "ttwok", + "뛑": "ttwot", + "뛒": "ttwop", + "뛓": "ttwot", + "뛔": "ttwe", + "뛕": "ttwek", + "뛖": "ttwekk", + "뛗": "ttwek", + "뛘": "ttwen", + "뛙": "ttwen", + "뛚": "ttwen", + "뛛": "ttwet", + "뛜": "ttwel", + "뛝": "ttwek", + "뛞": "ttwem", + "뛟": "ttwep", + "뛠": "ttwet", + "뛡": "ttwet", + "뛢": "ttwep", + "뛣": "ttwel", + "뛤": "ttwem", + "뛥": "ttwep", + "뛦": "ttwep", + "뛧": "ttwet", + "뛨": "ttwet", + "뛩": "ttweng", + "뛪": "ttwet", + "뛫": "ttwet", + "뛬": "ttwek", + "뛭": "ttwet", + "뛮": "ttwep", + "뛯": "ttwet", + "뛰": "ttwi", + "뛱": "ttwik", + "뛲": "ttwikk", + "뛳": "ttwik", + "뛴": "ttwin", + "뛵": "ttwin", + "뛶": "ttwin", + "뛷": "ttwit", + "뛸": "ttwil", + "뛹": "ttwik", + "뛺": "ttwim", + "뛻": "ttwip", + "뛼": "ttwit", + "뛽": "ttwit", + "뛾": "ttwip", + "뛿": "ttwil", + "뜀": "ttwim", + "뜁": "ttwip", + "뜂": "ttwip", + "뜃": "ttwit", + "뜄": "ttwit", + "뜅": "ttwing", + "뜆": "ttwit", + "뜇": "ttwit", + "뜈": "ttwik", + "뜉": "ttwit", + "뜊": "ttwip", + "뜋": "ttwit", + "뜌": "ttyu", + "뜍": "ttyuk", + "뜎": "ttyukk", + "뜏": "ttyuk", + "뜐": "ttyun", + "뜑": "ttyun", + "뜒": "ttyun", + "뜓": "ttyut", + "뜔": "ttyul", + "뜕": "ttyuk", + "뜖": "ttyum", + "뜗": "ttyup", + "뜘": "ttyut", + "뜙": "ttyut", + "뜚": "ttyup", + "뜛": "ttyul", + "뜜": "ttyum", + "뜝": "ttyup", + "뜞": "ttyup", + "뜟": "ttyut", + "뜠": "ttyut", + "뜡": "ttyung", + "뜢": "ttyut", + "뜣": "ttyut", + "뜤": "ttyuk", + "뜥": "ttyut", + "뜦": "ttyup", + "뜧": "ttyut", + "뜨": "tteu", + "뜩": "tteuk", + "뜪": "tteukk", + "뜫": "tteuk", + "뜬": "tteun", + "뜭": "tteun", + "뜮": "tteun", + "뜯": "tteut", + "뜰": "tteul", + "뜱": "tteuk", + "뜲": "tteum", + "뜳": "tteup", + "뜴": "tteut", + "뜵": "tteut", + "뜶": "tteup", + "뜷": "tteul", + "뜸": "tteum", + "뜹": "tteup", + "뜺": "tteup", + "뜻": "tteut", + "뜼": "tteut", + "뜽": "tteung", + "뜾": "tteut", + "뜿": "tteut", + "띀": "tteuk", + "띁": "tteut", + "띂": "tteup", + "띃": "tteut", + "띄": "tteui", + "띅": "tteuik", + "띆": "tteuikk", + "띇": "tteuik", + "띈": "tteuin", + "띉": "tteuin", + "띊": "tteuin", + "띋": "tteuit", + "띌": "tteuil", + "띍": "tteuik", + "띎": "tteuim", + "띏": "tteuip", + "띐": "tteuit", + "띑": "tteuit", + "띒": "tteuip", + "띓": "tteuil", + "띔": "tteuim", + "띕": "tteuip", + "띖": "tteuip", + "띗": "tteuit", + "띘": "tteuit", + "띙": "tteuing", + "띚": "tteuit", + "띛": "tteuit", + "띜": "tteuik", + "띝": "tteuit", + "띞": "tteuip", + "띟": "tteuit", + "띠": "tti", + "띡": "ttik", + "띢": "ttikk", + "띣": "ttik", + "띤": "ttin", + "띥": "ttin", + "띦": "ttin", + "띧": "ttit", + "띨": "ttil", + "띩": "ttik", + "띪": "ttim", + "띫": "ttip", + "띬": "ttit", + "띭": "ttit", + "띮": "ttip", + "띯": "ttil", + "띰": "ttim", + "띱": "ttip", + "띲": "ttip", + "띳": "ttit", + "띴": "ttit", + "띵": "tting", + "띶": "ttit", + "띷": "ttit", + "띸": "ttik", + "띹": "ttit", + "띺": "ttip", + "띻": "ttit", + "라": "ra", + "락": "rak", + "띾": "rakk", + "띿": "rak", + "란": "ran", + "랁": "ran", + "랂": "ran", + "랃": "rat", + "랄": "ral", + "랅": "rak", + "랆": "ram", + "랇": "rap", + "랈": "rat", + "랉": "rat", + "랊": "rap", + "랋": "ral", + "람": "ram", + "랍": "rap", + "랎": "rap", + "랏": "rat", + "랐": "rat", + "랑": "rang", + "랒": "rat", + "랓": "rat", + "랔": "rak", + "랕": "rat", + "랖": "rap", + "랗": "rat", + "래": "rae", + "랙": "raek", + "랚": "raekk", + "랛": "raek", + "랜": "raen", + "랝": "raen", + "랞": "raen", + "랟": "raet", + "랠": "rael", + "랡": "raek", + "랢": "raem", + "랣": "raep", + "랤": "raet", + "랥": "raet", + "랦": "raep", + "랧": "rael", + "램": "raem", + "랩": "raep", + "랪": "raep", + "랫": "raet", + "랬": "raet", + "랭": "raeng", + "랮": "raet", + "랯": "raet", + "랰": "raek", + "랱": "raet", + "랲": "raep", + "랳": "raet", + "랴": "rya", + "략": "ryak", + "랶": "ryakk", + "랷": "ryak", + "랸": "ryan", + "랹": "ryan", + "랺": "ryan", + "랻": "ryat", + "랼": "ryal", + "랽": "ryak", + "랾": "ryam", + "랿": "ryap", + "럀": "ryat", + "럁": "ryat", + "럂": "ryap", + "럃": "ryal", + "럄": "ryam", + "럅": "ryap", + "럆": "ryap", + "럇": "ryat", + "럈": "ryat", + "량": "ryang", + "럊": "ryat", + "럋": "ryat", + "럌": "ryak", + "럍": "ryat", + "럎": "ryap", + "럏": "ryat", + "럐": "ryae", + "럑": "ryaek", + "럒": "ryaekk", + "럓": "ryaek", + "럔": "ryaen", + "럕": "ryaen", + "럖": "ryaen", + "럗": "ryaet", + "럘": "ryael", + "럙": "ryaek", + "럚": "ryaem", + "럛": "ryaep", + "럜": "ryaet", + "럝": "ryaet", + "럞": "ryaep", + "럟": "ryael", + "럠": "ryaem", + "럡": "ryaep", + "럢": "ryaep", + "럣": "ryaet", + "럤": "ryaet", + "럥": "ryaeng", + "럦": "ryaet", + "럧": "ryaet", + "럨": "ryaek", + "럩": "ryaet", + "럪": "ryaep", + "럫": "ryaet", + "러": "reo", + "럭": "reok", + "럮": "reokk", + "럯": "reok", + "런": "reon", + "럱": "reon", + "럲": "reon", + "럳": "reot", + "럴": "reol", + "럵": "reok", + "럶": "reom", + "럷": "reop", + "럸": "reot", + "럹": "reot", + "럺": "reop", + "럻": "reol", + "럼": "reom", + "럽": "reop", + "럾": "reop", + "럿": "reot", + "렀": "reot", + "렁": "reong", + "렂": "reot", + "렃": "reot", + "렄": "reok", + "렅": "reot", + "렆": "reop", + "렇": "reot", + "레": "re", + "렉": "rek", + "렊": "rekk", + "렋": "rek", + "렌": "ren", + "렍": "ren", + "렎": "ren", + "렏": "ret", + "렐": "rel", + "렑": "rek", + "렒": "rem", + "렓": "rep", + "렔": "ret", + "렕": "ret", + "렖": "rep", + "렗": "rel", + "렘": "rem", + "렙": "rep", + "렚": "rep", + "렛": "ret", + "렜": "ret", + "렝": "reng", + "렞": "ret", + "렟": "ret", + "렠": "rek", + "렡": "ret", + "렢": "rep", + "렣": "ret", + "려": "ryeo", + "력": "ryeok", + "렦": "ryeokk", + "렧": "ryeok", + "련": "ryeon", + "렩": "ryeon", + "렪": "ryeon", + "렫": "ryeot", + "렬": "ryeol", + "렭": "ryeok", + "렮": "ryeom", + "렯": "ryeop", + "렰": "ryeot", + "렱": "ryeot", + "렲": "ryeop", + "렳": "ryeol", + "렴": "ryeom", + "렵": "ryeop", + "렶": "ryeop", + "렷": "ryeot", + "렸": "ryeot", + "령": "ryeong", + "렺": "ryeot", + "렻": "ryeot", + "렼": "ryeok", + "렽": "ryeot", + "렾": "ryeop", + "렿": "ryeot", + "례": "rye", + "롁": "ryek", + "롂": "ryekk", + "롃": "ryek", + "롄": "ryen", + "롅": "ryen", + "롆": "ryen", + "롇": "ryet", + "롈": "ryel", + "롉": "ryek", + "롊": "ryem", + "롋": "ryep", + "롌": "ryet", + "롍": "ryet", + "롎": "ryep", + "롏": "ryel", + "롐": "ryem", + "롑": "ryep", + "롒": "ryep", + "롓": "ryet", + "롔": "ryet", + "롕": "ryeng", + "롖": "ryet", + "롗": "ryet", + "롘": "ryek", + "롙": "ryet", + "롚": "ryep", + "롛": "ryet", + "로": "ro", + "록": "rok", + "롞": "rokk", + "롟": "rok", + "론": "ron", + "롡": "ron", + "롢": "ron", + "롣": "rot", + "롤": "rol", + "롥": "rok", + "롦": "rom", + "롧": "rop", + "롨": "rot", + "롩": "rot", + "롪": "rop", + "롫": "rol", + "롬": "rom", + "롭": "rop", + "롮": "rop", + "롯": "rot", + "롰": "rot", + "롱": "rong", + "롲": "rot", + "롳": "rot", + "롴": "rok", + "롵": "rot", + "롶": "rop", + "롷": "rot", + "롸": "rwa", + "롹": "rwak", + "롺": "rwakk", + "롻": "rwak", + "롼": "rwan", + "롽": "rwan", + "롾": "rwan", + "롿": "rwat", + "뢀": "rwal", + "뢁": "rwak", + "뢂": "rwam", + "뢃": "rwap", + "뢄": "rwat", + "뢅": "rwat", + "뢆": "rwap", + "뢇": "rwal", + "뢈": "rwam", + "뢉": "rwap", + "뢊": "rwap", + "뢋": "rwat", + "뢌": "rwat", + "뢍": "rwang", + "뢎": "rwat", + "뢏": "rwat", + "뢐": "rwak", + "뢑": "rwat", + "뢒": "rwap", + "뢓": "rwat", + "뢔": "rwae", + "뢕": "rwaek", + "뢖": "rwaekk", + "뢗": "rwaek", + "뢘": "rwaen", + "뢙": "rwaen", + "뢚": "rwaen", + "뢛": "rwaet", + "뢜": "rwael", + "뢝": "rwaek", + "뢞": "rwaem", + "뢟": "rwaep", + "뢠": "rwaet", + "뢡": "rwaet", + "뢢": "rwaep", + "뢣": "rwael", + "뢤": "rwaem", + "뢥": "rwaep", + "뢦": "rwaep", + "뢧": "rwaet", + "뢨": "rwaet", + "뢩": "rwaeng", + "뢪": "rwaet", + "뢫": "rwaet", + "뢬": "rwaek", + "뢭": "rwaet", + "뢮": "rwaep", + "뢯": "rwaet", + "뢰": "roe", + "뢱": "roek", + "뢲": "roekk", + "뢳": "roek", + "뢴": "roen", + "뢵": "roen", + "뢶": "roen", + "뢷": "roet", + "뢸": "roel", + "뢹": "roek", + "뢺": "roem", + "뢻": "roep", + "뢼": "roet", + "뢽": "roet", + "뢾": "roep", + "뢿": "roel", + "룀": "roem", + "룁": "roep", + "룂": "roep", + "룃": "roet", + "룄": "roet", + "룅": "roeng", + "룆": "roet", + "룇": "roet", + "룈": "roek", + "룉": "roet", + "룊": "roep", + "룋": "roet", + "료": "ryo", + "룍": "ryok", + "룎": "ryokk", + "룏": "ryok", + "룐": "ryon", + "룑": "ryon", + "룒": "ryon", + "룓": "ryot", + "룔": "ryol", + "룕": "ryok", + "룖": "ryom", + "룗": "ryop", + "룘": "ryot", + "룙": "ryot", + "룚": "ryop", + "룛": "ryol", + "룜": "ryom", + "룝": "ryop", + "룞": "ryop", + "룟": "ryot", + "룠": "ryot", + "룡": "ryong", + "룢": "ryot", + "룣": "ryot", + "룤": "ryok", + "룥": "ryot", + "룦": "ryop", + "룧": "ryot", + "루": "ru", + "룩": "ruk", + "룪": "rukk", + "룫": "ruk", + "룬": "run", + "룭": "run", + "룮": "run", + "룯": "rut", + "룰": "rul", + "룱": "ruk", + "룲": "rum", + "룳": "rup", + "룴": "rut", + "룵": "rut", + "룶": "rup", + "룷": "rul", + "룸": "rum", + "룹": "rup", + "룺": "rup", + "룻": "rut", + "룼": "rut", + "룽": "rung", + "룾": "rut", + "룿": "rut", + "뤀": "ruk", + "뤁": "rut", + "뤂": "rup", + "뤃": "rut", + "뤄": "rwo", + "뤅": "rwok", + "뤆": "rwokk", + "뤇": "rwok", + "뤈": "rwon", + "뤉": "rwon", + "뤊": "rwon", + "뤋": "rwot", + "뤌": "rwol", + "뤍": "rwok", + "뤎": "rwom", + "뤏": "rwop", + "뤐": "rwot", + "뤑": "rwot", + "뤒": "rwop", + "뤓": "rwol", + "뤔": "rwom", + "뤕": "rwop", + "뤖": "rwop", + "뤗": "rwot", + "뤘": "rwot", + "뤙": "rwong", + "뤚": "rwot", + "뤛": "rwot", + "뤜": "rwok", + "뤝": "rwot", + "뤞": "rwop", + "뤟": "rwot", + "뤠": "rwe", + "뤡": "rwek", + "뤢": "rwekk", + "뤣": "rwek", + "뤤": "rwen", + "뤥": "rwen", + "뤦": "rwen", + "뤧": "rwet", + "뤨": "rwel", + "뤩": "rwek", + "뤪": "rwem", + "뤫": "rwep", + "뤬": "rwet", + "뤭": "rwet", + "뤮": "rwep", + "뤯": "rwel", + "뤰": "rwem", + "뤱": "rwep", + "뤲": "rwep", + "뤳": "rwet", + "뤴": "rwet", + "뤵": "rweng", + "뤶": "rwet", + "뤷": "rwet", + "뤸": "rwek", + "뤹": "rwet", + "뤺": "rwep", + "뤻": "rwet", + "뤼": "rwi", + "뤽": "rwik", + "뤾": "rwikk", + "뤿": "rwik", + "륀": "rwin", + "륁": "rwin", + "륂": "rwin", + "륃": "rwit", + "륄": "rwil", + "륅": "rwik", + "륆": "rwim", + "륇": "rwip", + "륈": "rwit", + "륉": "rwit", + "륊": "rwip", + "륋": "rwil", + "륌": "rwim", + "륍": "rwip", + "륎": "rwip", + "륏": "rwit", + "륐": "rwit", + "륑": "rwing", + "륒": "rwit", + "륓": "rwit", + "륔": "rwik", + "륕": "rwit", + "륖": "rwip", + "륗": "rwit", + "류": "ryu", + "륙": "ryuk", + "륚": "ryukk", + "륛": "ryuk", + "륜": "ryun", + "륝": "ryun", + "륞": "ryun", + "륟": "ryut", + "률": "ryul", + "륡": "ryuk", + "륢": "ryum", + "륣": "ryup", + "륤": "ryut", + "륥": "ryut", + "륦": "ryup", + "륧": "ryul", + "륨": "ryum", + "륩": "ryup", + "륪": "ryup", + "륫": "ryut", + "륬": "ryut", + "륭": "ryung", + "륮": "ryut", + "륯": "ryut", + "륰": "ryuk", + "륱": "ryut", + "륲": "ryup", + "륳": "ryut", + "르": "reu", + "륵": "reuk", + "륶": "reukk", + "륷": "reuk", + "른": "reun", + "륹": "reun", + "륺": "reun", + "륻": "reut", + "를": "reul", + "륽": "reuk", + "륾": "reum", + "륿": "reup", + "릀": "reut", + "릁": "reut", + "릂": "reup", + "릃": "reul", + "름": "reum", + "릅": "reup", + "릆": "reup", + "릇": "reut", + "릈": "reut", + "릉": "reung", + "릊": "reut", + "릋": "reut", + "릌": "reuk", + "릍": "reut", + "릎": "reup", + "릏": "reut", + "릐": "reui", + "릑": "reuik", + "릒": "reuikk", + "릓": "reuik", + "릔": "reuin", + "릕": "reuin", + "릖": "reuin", + "릗": "reuit", + "릘": "reuil", + "릙": "reuik", + "릚": "reuim", + "릛": "reuip", + "릜": "reuit", + "릝": "reuit", + "릞": "reuip", + "릟": "reuil", + "릠": "reuim", + "릡": "reuip", + "릢": "reuip", + "릣": "reuit", + "릤": "reuit", + "릥": "reuing", + "릦": "reuit", + "릧": "reuit", + "릨": "reuik", + "릩": "reuit", + "릪": "reuip", + "릫": "reuit", + "리": "ri", + "릭": "rik", + "릮": "rikk", + "릯": "rik", + "린": "rin", + "릱": "rin", + "릲": "rin", + "릳": "rit", + "릴": "ril", + "릵": "rik", + "릶": "rim", + "릷": "rip", + "릸": "rit", + "릹": "rit", + "릺": "rip", + "릻": "ril", + "림": "rim", + "립": "rip", + "릾": "rip", + "릿": "rit", + "맀": "rit", + "링": "ring", + "맂": "rit", + "맃": "rit", + "맄": "rik", + "맅": "rit", + "맆": "rip", + "맇": "rit", + "마": "ma", + "막": "mak", + "맊": "makk", + "맋": "mak", + "만": "man", + "맍": "man", + "많": "man", + "맏": "mat", + "말": "mal", + "맑": "mak", + "맒": "mam", + "맓": "map", + "맔": "mat", + "맕": "mat", + "맖": "map", + "맗": "mal", + "맘": "mam", + "맙": "map", + "맚": "map", + "맛": "mat", + "맜": "mat", + "망": "mang", + "맞": "mat", + "맟": "mat", + "맠": "mak", + "맡": "mat", + "맢": "map", + "맣": "mat", + "매": "mae", + "맥": "maek", + "맦": "maekk", + "맧": "maek", + "맨": "maen", + "맩": "maen", + "맪": "maen", + "맫": "maet", + "맬": "mael", + "맭": "maek", + "맮": "maem", + "맯": "maep", + "맰": "maet", + "맱": "maet", + "맲": "maep", + "맳": "mael", + "맴": "maem", + "맵": "maep", + "맶": "maep", + "맷": "maet", + "맸": "maet", + "맹": "maeng", + "맺": "maet", + "맻": "maet", + "맼": "maek", + "맽": "maet", + "맾": "maep", + "맿": "maet", + "먀": "mya", + "먁": "myak", + "먂": "myakk", + "먃": "myak", + "먄": "myan", + "먅": "myan", + "먆": "myan", + "먇": "myat", + "먈": "myal", + "먉": "myak", + "먊": "myam", + "먋": "myap", + "먌": "myat", + "먍": "myat", + "먎": "myap", + "먏": "myal", + "먐": "myam", + "먑": "myap", + "먒": "myap", + "먓": "myat", + "먔": "myat", + "먕": "myang", + "먖": "myat", + "먗": "myat", + "먘": "myak", + "먙": "myat", + "먚": "myap", + "먛": "myat", + "먜": "myae", + "먝": "myaek", + "먞": "myaekk", + "먟": "myaek", + "먠": "myaen", + "먡": "myaen", + "먢": "myaen", + "먣": "myaet", + "먤": "myael", + "먥": "myaek", + "먦": "myaem", + "먧": "myaep", + "먨": "myaet", + "먩": "myaet", + "먪": "myaep", + "먫": "myael", + "먬": "myaem", + "먭": "myaep", + "먮": "myaep", + "먯": "myaet", + "먰": "myaet", + "먱": "myaeng", + "먲": "myaet", + "먳": "myaet", + "먴": "myaek", + "먵": "myaet", + "먶": "myaep", + "먷": "myaet", + "머": "meo", + "먹": "meok", + "먺": "meokk", + "먻": "meok", + "먼": "meon", + "먽": "meon", + "먾": "meon", + "먿": "meot", + "멀": "meol", + "멁": "meok", + "멂": "meom", + "멃": "meop", + "멄": "meot", + "멅": "meot", + "멆": "meop", + "멇": "meol", + "멈": "meom", + "멉": "meop", + "멊": "meop", + "멋": "meot", + "멌": "meot", + "멍": "meong", + "멎": "meot", + "멏": "meot", + "멐": "meok", + "멑": "meot", + "멒": "meop", + "멓": "meot", + "메": "me", + "멕": "mek", + "멖": "mekk", + "멗": "mek", + "멘": "men", + "멙": "men", + "멚": "men", + "멛": "met", + "멜": "mel", + "멝": "mek", + "멞": "mem", + "멟": "mep", + "멠": "met", + "멡": "met", + "멢": "mep", + "멣": "mel", + "멤": "mem", + "멥": "mep", + "멦": "mep", + "멧": "met", + "멨": "met", + "멩": "meng", + "멪": "met", + "멫": "met", + "멬": "mek", + "멭": "met", + "멮": "mep", + "멯": "met", + "며": "myeo", + "멱": "myeok", + "멲": "myeokk", + "멳": "myeok", + "면": "myeon", + "멵": "myeon", + "멶": "myeon", + "멷": "myeot", + "멸": "myeol", + "멹": "myeok", + "멺": "myeom", + "멻": "myeop", + "멼": "myeot", + "멽": "myeot", + "멾": "myeop", + "멿": "myeol", + "몀": "myeom", + "몁": "myeop", + "몂": "myeop", + "몃": "myeot", + "몄": "myeot", + "명": "myeong", + "몆": "myeot", + "몇": "myeot", + "몈": "myeok", + "몉": "myeot", + "몊": "myeop", + "몋": "myeot", + "몌": "mye", + "몍": "myek", + "몎": "myekk", + "몏": "myek", + "몐": "myen", + "몑": "myen", + "몒": "myen", + "몓": "myet", + "몔": "myel", + "몕": "myek", + "몖": "myem", + "몗": "myep", + "몘": "myet", + "몙": "myet", + "몚": "myep", + "몛": "myel", + "몜": "myem", + "몝": "myep", + "몞": "myep", + "몟": "myet", + "몠": "myet", + "몡": "myeng", + "몢": "myet", + "몣": "myet", + "몤": "myek", + "몥": "myet", + "몦": "myep", + "몧": "myet", + "모": "mo", + "목": "mok", + "몪": "mokk", + "몫": "mok", + "몬": "mon", + "몭": "mon", + "몮": "mon", + "몯": "mot", + "몰": "mol", + "몱": "mok", + "몲": "mom", + "몳": "mop", + "몴": "mot", + "몵": "mot", + "몶": "mop", + "몷": "mol", + "몸": "mom", + "몹": "mop", + "몺": "mop", + "못": "mot", + "몼": "mot", + "몽": "mong", + "몾": "mot", + "몿": "mot", + "뫀": "mok", + "뫁": "mot", + "뫂": "mop", + "뫃": "mot", + "뫄": "mwa", + "뫅": "mwak", + "뫆": "mwakk", + "뫇": "mwak", + "뫈": "mwan", + "뫉": "mwan", + "뫊": "mwan", + "뫋": "mwat", + "뫌": "mwal", + "뫍": "mwak", + "뫎": "mwam", + "뫏": "mwap", + "뫐": "mwat", + "뫑": "mwat", + "뫒": "mwap", + "뫓": "mwal", + "뫔": "mwam", + "뫕": "mwap", + "뫖": "mwap", + "뫗": "mwat", + "뫘": "mwat", + "뫙": "mwang", + "뫚": "mwat", + "뫛": "mwat", + "뫜": "mwak", + "뫝": "mwat", + "뫞": "mwap", + "뫟": "mwat", + "뫠": "mwae", + "뫡": "mwaek", + "뫢": "mwaekk", + "뫣": "mwaek", + "뫤": "mwaen", + "뫥": "mwaen", + "뫦": "mwaen", + "뫧": "mwaet", + "뫨": "mwael", + "뫩": "mwaek", + "뫪": "mwaem", + "뫫": "mwaep", + "뫬": "mwaet", + "뫭": "mwaet", + "뫮": "mwaep", + "뫯": "mwael", + "뫰": "mwaem", + "뫱": "mwaep", + "뫲": "mwaep", + "뫳": "mwaet", + "뫴": "mwaet", + "뫵": "mwaeng", + "뫶": "mwaet", + "뫷": "mwaet", + "뫸": "mwaek", + "뫹": "mwaet", + "뫺": "mwaep", + "뫻": "mwaet", + "뫼": "moe", + "뫽": "moek", + "뫾": "moekk", + "뫿": "moek", + "묀": "moen", + "묁": "moen", + "묂": "moen", + "묃": "moet", + "묄": "moel", + "묅": "moek", + "묆": "moem", + "묇": "moep", + "묈": "moet", + "묉": "moet", + "묊": "moep", + "묋": "moel", + "묌": "moem", + "묍": "moep", + "묎": "moep", + "묏": "moet", + "묐": "moet", + "묑": "moeng", + "묒": "moet", + "묓": "moet", + "묔": "moek", + "묕": "moet", + "묖": "moep", + "묗": "moet", + "묘": "myo", + "묙": "myok", + "묚": "myokk", + "묛": "myok", + "묜": "myon", + "묝": "myon", + "묞": "myon", + "묟": "myot", + "묠": "myol", + "묡": "myok", + "묢": "myom", + "묣": "myop", + "묤": "myot", + "묥": "myot", + "묦": "myop", + "묧": "myol", + "묨": "myom", + "묩": "myop", + "묪": "myop", + "묫": "myot", + "묬": "myot", + "묭": "myong", + "묮": "myot", + "묯": "myot", + "묰": "myok", + "묱": "myot", + "묲": "myop", + "묳": "myot", + "무": "mu", + "묵": "muk", + "묶": "mukk", + "묷": "muk", + "문": "mun", + "묹": "mun", + "묺": "mun", + "묻": "mut", + "물": "mul", + "묽": "muk", + "묾": "mum", + "묿": "mup", + "뭀": "mut", + "뭁": "mut", + "뭂": "mup", + "뭃": "mul", + "뭄": "mum", + "뭅": "mup", + "뭆": "mup", + "뭇": "mut", + "뭈": "mut", + "뭉": "mung", + "뭊": "mut", + "뭋": "mut", + "뭌": "muk", + "뭍": "mut", + "뭎": "mup", + "뭏": "mut", + "뭐": "mwo", + "뭑": "mwok", + "뭒": "mwokk", + "뭓": "mwok", + "뭔": "mwon", + "뭕": "mwon", + "뭖": "mwon", + "뭗": "mwot", + "뭘": "mwol", + "뭙": "mwok", + "뭚": "mwom", + "뭛": "mwop", + "뭜": "mwot", + "뭝": "mwot", + "뭞": "mwop", + "뭟": "mwol", + "뭠": "mwom", + "뭡": "mwop", + "뭢": "mwop", + "뭣": "mwot", + "뭤": "mwot", + "뭥": "mwong", + "뭦": "mwot", + "뭧": "mwot", + "뭨": "mwok", + "뭩": "mwot", + "뭪": "mwop", + "뭫": "mwot", + "뭬": "mwe", + "뭭": "mwek", + "뭮": "mwekk", + "뭯": "mwek", + "뭰": "mwen", + "뭱": "mwen", + "뭲": "mwen", + "뭳": "mwet", + "뭴": "mwel", + "뭵": "mwek", + "뭶": "mwem", + "뭷": "mwep", + "뭸": "mwet", + "뭹": "mwet", + "뭺": "mwep", + "뭻": "mwel", + "뭼": "mwem", + "뭽": "mwep", + "뭾": "mwep", + "뭿": "mwet", + "뮀": "mwet", + "뮁": "mweng", + "뮂": "mwet", + "뮃": "mwet", + "뮄": "mwek", + "뮅": "mwet", + "뮆": "mwep", + "뮇": "mwet", + "뮈": "mwi", + "뮉": "mwik", + "뮊": "mwikk", + "뮋": "mwik", + "뮌": "mwin", + "뮍": "mwin", + "뮎": "mwin", + "뮏": "mwit", + "뮐": "mwil", + "뮑": "mwik", + "뮒": "mwim", + "뮓": "mwip", + "뮔": "mwit", + "뮕": "mwit", + "뮖": "mwip", + "뮗": "mwil", + "뮘": "mwim", + "뮙": "mwip", + "뮚": "mwip", + "뮛": "mwit", + "뮜": "mwit", + "뮝": "mwing", + "뮞": "mwit", + "뮟": "mwit", + "뮠": "mwik", + "뮡": "mwit", + "뮢": "mwip", + "뮣": "mwit", + "뮤": "myu", + "뮥": "myuk", + "뮦": "myukk", + "뮧": "myuk", + "뮨": "myun", + "뮩": "myun", + "뮪": "myun", + "뮫": "myut", + "뮬": "myul", + "뮭": "myuk", + "뮮": "myum", + "뮯": "myup", + "뮰": "myut", + "뮱": "myut", + "뮲": "myup", + "뮳": "myul", + "뮴": "myum", + "뮵": "myup", + "뮶": "myup", + "뮷": "myut", + "뮸": "myut", + "뮹": "myung", + "뮺": "myut", + "뮻": "myut", + "뮼": "myuk", + "뮽": "myut", + "뮾": "myup", + "뮿": "myut", + "므": "meu", + "믁": "meuk", + "믂": "meukk", + "믃": "meuk", + "믄": "meun", + "믅": "meun", + "믆": "meun", + "믇": "meut", + "믈": "meul", + "믉": "meuk", + "믊": "meum", + "믋": "meup", + "믌": "meut", + "믍": "meut", + "믎": "meup", + "믏": "meul", + "믐": "meum", + "믑": "meup", + "믒": "meup", + "믓": "meut", + "믔": "meut", + "믕": "meung", + "믖": "meut", + "믗": "meut", + "믘": "meuk", + "믙": "meut", + "믚": "meup", + "믛": "meut", + "믜": "meui", + "믝": "meuik", + "믞": "meuikk", + "믟": "meuik", + "믠": "meuin", + "믡": "meuin", + "믢": "meuin", + "믣": "meuit", + "믤": "meuil", + "믥": "meuik", + "믦": "meuim", + "믧": "meuip", + "믨": "meuit", + "믩": "meuit", + "믪": "meuip", + "믫": "meuil", + "믬": "meuim", + "믭": "meuip", + "믮": "meuip", + "믯": "meuit", + "믰": "meuit", + "믱": "meuing", + "믲": "meuit", + "믳": "meuit", + "믴": "meuik", + "믵": "meuit", + "믶": "meuip", + "믷": "meuit", + "미": "mi", + "믹": "mik", + "믺": "mikk", + "믻": "mik", + "민": "min", + "믽": "min", + "믾": "min", + "믿": "mit", + "밀": "mil", + "밁": "mik", + "밂": "mim", + "밃": "mip", + "밄": "mit", + "밅": "mit", + "밆": "mip", + "밇": "mil", + "밈": "mim", + "밉": "mip", + "밊": "mip", + "밋": "mit", + "밌": "mit", + "밍": "ming", + "밎": "mit", + "및": "mit", + "밐": "mik", + "밑": "mit", + "밒": "mip", + "밓": "mit", + "바": "ba", + "박": "bak", + "밖": "bakk", + "밗": "bak", + "반": "ban", + "밙": "ban", + "밚": "ban", + "받": "bat", + "발": "bal", + "밝": "bak", + "밞": "bam", + "밟": "bap", + "밠": "bat", + "밡": "bat", + "밢": "bap", + "밣": "bal", + "밤": "bam", + "밥": "bap", + "밦": "bap", + "밧": "bat", + "밨": "bat", + "방": "bang", + "밪": "bat", + "밫": "bat", + "밬": "bak", + "밭": "bat", + "밮": "bap", + "밯": "bat", + "배": "bae", + "백": "baek", + "밲": "baekk", + "밳": "baek", + "밴": "baen", + "밵": "baen", + "밶": "baen", + "밷": "baet", + "밸": "bael", + "밹": "baek", + "밺": "baem", + "밻": "baep", + "밼": "baet", + "밽": "baet", + "밾": "baep", + "밿": "bael", + "뱀": "baem", + "뱁": "baep", + "뱂": "baep", + "뱃": "baet", + "뱄": "baet", + "뱅": "baeng", + "뱆": "baet", + "뱇": "baet", + "뱈": "baek", + "뱉": "baet", + "뱊": "baep", + "뱋": "baet", + "뱌": "bya", + "뱍": "byak", + "뱎": "byakk", + "뱏": "byak", + "뱐": "byan", + "뱑": "byan", + "뱒": "byan", + "뱓": "byat", + "뱔": "byal", + "뱕": "byak", + "뱖": "byam", + "뱗": "byap", + "뱘": "byat", + "뱙": "byat", + "뱚": "byap", + "뱛": "byal", + "뱜": "byam", + "뱝": "byap", + "뱞": "byap", + "뱟": "byat", + "뱠": "byat", + "뱡": "byang", + "뱢": "byat", + "뱣": "byat", + "뱤": "byak", + "뱥": "byat", + "뱦": "byap", + "뱧": "byat", + "뱨": "byae", + "뱩": "byaek", + "뱪": "byaekk", + "뱫": "byaek", + "뱬": "byaen", + "뱭": "byaen", + "뱮": "byaen", + "뱯": "byaet", + "뱰": "byael", + "뱱": "byaek", + "뱲": "byaem", + "뱳": "byaep", + "뱴": "byaet", + "뱵": "byaet", + "뱶": "byaep", + "뱷": "byael", + "뱸": "byaem", + "뱹": "byaep", + "뱺": "byaep", + "뱻": "byaet", + "뱼": "byaet", + "뱽": "byaeng", + "뱾": "byaet", + "뱿": "byaet", + "벀": "byaek", + "벁": "byaet", + "벂": "byaep", + "벃": "byaet", + "버": "beo", + "벅": "beok", + "벆": "beokk", + "벇": "beok", + "번": "beon", + "벉": "beon", + "벊": "beon", + "벋": "beot", + "벌": "beol", + "벍": "beok", + "벎": "beom", + "벏": "beop", + "벐": "beot", + "벑": "beot", + "벒": "beop", + "벓": "beol", + "범": "beom", + "법": "beop", + "벖": "beop", + "벗": "beot", + "벘": "beot", + "벙": "beong", + "벚": "beot", + "벛": "beot", + "벜": "beok", + "벝": "beot", + "벞": "beop", + "벟": "beot", + "베": "be", + "벡": "bek", + "벢": "bekk", + "벣": "bek", + "벤": "ben", + "벥": "ben", + "벦": "ben", + "벧": "bet", + "벨": "bel", + "벩": "bek", + "벪": "bem", + "벫": "bep", + "벬": "bet", + "벭": "bet", + "벮": "bep", + "벯": "bel", + "벰": "bem", + "벱": "bep", + "벲": "bep", + "벳": "bet", + "벴": "bet", + "벵": "beng", + "벶": "bet", + "벷": "bet", + "벸": "bek", + "벹": "bet", + "벺": "bep", + "벻": "bet", + "벼": "byeo", + "벽": "byeok", + "벾": "byeokk", + "벿": "byeok", + "변": "byeon", + "볁": "byeon", + "볂": "byeon", + "볃": "byeot", + "별": "byeol", + "볅": "byeok", + "볆": "byeom", + "볇": "byeop", + "볈": "byeot", + "볉": "byeot", + "볊": "byeop", + "볋": "byeol", + "볌": "byeom", + "볍": "byeop", + "볎": "byeop", + "볏": "byeot", + "볐": "byeot", + "병": "byeong", + "볒": "byeot", + "볓": "byeot", + "볔": "byeok", + "볕": "byeot", + "볖": "byeop", + "볗": "byeot", + "볘": "bye", + "볙": "byek", + "볚": "byekk", + "볛": "byek", + "볜": "byen", + "볝": "byen", + "볞": "byen", + "볟": "byet", + "볠": "byel", + "볡": "byek", + "볢": "byem", + "볣": "byep", + "볤": "byet", + "볥": "byet", + "볦": "byep", + "볧": "byel", + "볨": "byem", + "볩": "byep", + "볪": "byep", + "볫": "byet", + "볬": "byet", + "볭": "byeng", + "볮": "byet", + "볯": "byet", + "볰": "byek", + "볱": "byet", + "볲": "byep", + "볳": "byet", + "보": "bo", + "복": "bok", + "볶": "bokk", + "볷": "bok", + "본": "bon", + "볹": "bon", + "볺": "bon", + "볻": "bot", + "볼": "bol", + "볽": "bok", + "볾": "bom", + "볿": "bop", + "봀": "bot", + "봁": "bot", + "봂": "bop", + "봃": "bol", + "봄": "bom", + "봅": "bop", + "봆": "bop", + "봇": "bot", + "봈": "bot", + "봉": "bong", + "봊": "bot", + "봋": "bot", + "봌": "bok", + "봍": "bot", + "봎": "bop", + "봏": "bot", + "봐": "bwa", + "봑": "bwak", + "봒": "bwakk", + "봓": "bwak", + "봔": "bwan", + "봕": "bwan", + "봖": "bwan", + "봗": "bwat", + "봘": "bwal", + "봙": "bwak", + "봚": "bwam", + "봛": "bwap", + "봜": "bwat", + "봝": "bwat", + "봞": "bwap", + "봟": "bwal", + "봠": "bwam", + "봡": "bwap", + "봢": "bwap", + "봣": "bwat", + "봤": "bwat", + "봥": "bwang", + "봦": "bwat", + "봧": "bwat", + "봨": "bwak", + "봩": "bwat", + "봪": "bwap", + "봫": "bwat", + "봬": "bwae", + "봭": "bwaek", + "봮": "bwaekk", + "봯": "bwaek", + "봰": "bwaen", + "봱": "bwaen", + "봲": "bwaen", + "봳": "bwaet", + "봴": "bwael", + "봵": "bwaek", + "봶": "bwaem", + "봷": "bwaep", + "봸": "bwaet", + "봹": "bwaet", + "봺": "bwaep", + "봻": "bwael", + "봼": "bwaem", + "봽": "bwaep", + "봾": "bwaep", + "봿": "bwaet", + "뵀": "bwaet", + "뵁": "bwaeng", + "뵂": "bwaet", + "뵃": "bwaet", + "뵄": "bwaek", + "뵅": "bwaet", + "뵆": "bwaep", + "뵇": "bwaet", + "뵈": "boe", + "뵉": "boek", + "뵊": "boekk", + "뵋": "boek", + "뵌": "boen", + "뵍": "boen", + "뵎": "boen", + "뵏": "boet", + "뵐": "boel", + "뵑": "boek", + "뵒": "boem", + "뵓": "boep", + "뵔": "boet", + "뵕": "boet", + "뵖": "boep", + "뵗": "boel", + "뵘": "boem", + "뵙": "boep", + "뵚": "boep", + "뵛": "boet", + "뵜": "boet", + "뵝": "boeng", + "뵞": "boet", + "뵟": "boet", + "뵠": "boek", + "뵡": "boet", + "뵢": "boep", + "뵣": "boet", + "뵤": "byo", + "뵥": "byok", + "뵦": "byokk", + "뵧": "byok", + "뵨": "byon", + "뵩": "byon", + "뵪": "byon", + "뵫": "byot", + "뵬": "byol", + "뵭": "byok", + "뵮": "byom", + "뵯": "byop", + "뵰": "byot", + "뵱": "byot", + "뵲": "byop", + "뵳": "byol", + "뵴": "byom", + "뵵": "byop", + "뵶": "byop", + "뵷": "byot", + "뵸": "byot", + "뵹": "byong", + "뵺": "byot", + "뵻": "byot", + "뵼": "byok", + "뵽": "byot", + "뵾": "byop", + "뵿": "byot", + "부": "bu", + "북": "buk", + "붂": "bukk", + "붃": "buk", + "분": "bun", + "붅": "bun", + "붆": "bun", + "붇": "but", + "불": "bul", + "붉": "buk", + "붊": "bum", + "붋": "bup", + "붌": "but", + "붍": "but", + "붎": "bup", + "붏": "bul", + "붐": "bum", + "붑": "bup", + "붒": "bup", + "붓": "but", + "붔": "but", + "붕": "bung", + "붖": "but", + "붗": "but", + "붘": "buk", + "붙": "but", + "붚": "bup", + "붛": "but", + "붜": "bwo", + "붝": "bwok", + "붞": "bwokk", + "붟": "bwok", + "붠": "bwon", + "붡": "bwon", + "붢": "bwon", + "붣": "bwot", + "붤": "bwol", + "붥": "bwok", + "붦": "bwom", + "붧": "bwop", + "붨": "bwot", + "붩": "bwot", + "붪": "bwop", + "붫": "bwol", + "붬": "bwom", + "붭": "bwop", + "붮": "bwop", + "붯": "bwot", + "붰": "bwot", + "붱": "bwong", + "붲": "bwot", + "붳": "bwot", + "붴": "bwok", + "붵": "bwot", + "붶": "bwop", + "붷": "bwot", + "붸": "bwe", + "붹": "bwek", + "붺": "bwekk", + "붻": "bwek", + "붼": "bwen", + "붽": "bwen", + "붾": "bwen", + "붿": "bwet", + "뷀": "bwel", + "뷁": "bwek", + "뷂": "bwem", + "뷃": "bwep", + "뷄": "bwet", + "뷅": "bwet", + "뷆": "bwep", + "뷇": "bwel", + "뷈": "bwem", + "뷉": "bwep", + "뷊": "bwep", + "뷋": "bwet", + "뷌": "bwet", + "뷍": "bweng", + "뷎": "bwet", + "뷏": "bwet", + "뷐": "bwek", + "뷑": "bwet", + "뷒": "bwep", + "뷓": "bwet", + "뷔": "bwi", + "뷕": "bwik", + "뷖": "bwikk", + "뷗": "bwik", + "뷘": "bwin", + "뷙": "bwin", + "뷚": "bwin", + "뷛": "bwit", + "뷜": "bwil", + "뷝": "bwik", + "뷞": "bwim", + "뷟": "bwip", + "뷠": "bwit", + "뷡": "bwit", + "뷢": "bwip", + "뷣": "bwil", + "뷤": "bwim", + "뷥": "bwip", + "뷦": "bwip", + "뷧": "bwit", + "뷨": "bwit", + "뷩": "bwing", + "뷪": "bwit", + "뷫": "bwit", + "뷬": "bwik", + "뷭": "bwit", + "뷮": "bwip", + "뷯": "bwit", + "뷰": "byu", + "뷱": "byuk", + "뷲": "byukk", + "뷳": "byuk", + "뷴": "byun", + "뷵": "byun", + "뷶": "byun", + "뷷": "byut", + "뷸": "byul", + "뷹": "byuk", + "뷺": "byum", + "뷻": "byup", + "뷼": "byut", + "뷽": "byut", + "뷾": "byup", + "뷿": "byul", + "븀": "byum", + "븁": "byup", + "븂": "byup", + "븃": "byut", + "븄": "byut", + "븅": "byung", + "븆": "byut", + "븇": "byut", + "븈": "byuk", + "븉": "byut", + "븊": "byup", + "븋": "byut", + "브": "beu", + "븍": "beuk", + "븎": "beukk", + "븏": "beuk", + "븐": "beun", + "븑": "beun", + "븒": "beun", + "븓": "beut", + "블": "beul", + "븕": "beuk", + "븖": "beum", + "븗": "beup", + "븘": "beut", + "븙": "beut", + "븚": "beup", + "븛": "beul", + "븜": "beum", + "븝": "beup", + "븞": "beup", + "븟": "beut", + "븠": "beut", + "븡": "beung", + "븢": "beut", + "븣": "beut", + "븤": "beuk", + "븥": "beut", + "븦": "beup", + "븧": "beut", + "븨": "beui", + "븩": "beuik", + "븪": "beuikk", + "븫": "beuik", + "븬": "beuin", + "븭": "beuin", + "븮": "beuin", + "븯": "beuit", + "븰": "beuil", + "븱": "beuik", + "븲": "beuim", + "븳": "beuip", + "븴": "beuit", + "븵": "beuit", + "븶": "beuip", + "븷": "beuil", + "븸": "beuim", + "븹": "beuip", + "븺": "beuip", + "븻": "beuit", + "븼": "beuit", + "븽": "beuing", + "븾": "beuit", + "븿": "beuit", + "빀": "beuik", + "빁": "beuit", + "빂": "beuip", + "빃": "beuit", + "비": "bi", + "빅": "bik", + "빆": "bikk", + "빇": "bik", + "빈": "bin", + "빉": "bin", + "빊": "bin", + "빋": "bit", + "빌": "bil", + "빍": "bik", + "빎": "bim", + "빏": "bip", + "빐": "bit", + "빑": "bit", + "빒": "bip", + "빓": "bil", + "빔": "bim", + "빕": "bip", + "빖": "bip", + "빗": "bit", + "빘": "bit", + "빙": "bing", + "빚": "bit", + "빛": "bit", + "빜": "bik", + "빝": "bit", + "빞": "bip", + "빟": "bit", + "빠": "ppa", + "빡": "ppak", + "빢": "ppakk", + "빣": "ppak", + "빤": "ppan", + "빥": "ppan", + "빦": "ppan", + "빧": "ppat", + "빨": "ppal", + "빩": "ppak", + "빪": "ppam", + "빫": "ppap", + "빬": "ppat", + "빭": "ppat", + "빮": "ppap", + "빯": "ppal", + "빰": "ppam", + "빱": "ppap", + "빲": "ppap", + "빳": "ppat", + "빴": "ppat", + "빵": "ppang", + "빶": "ppat", + "빷": "ppat", + "빸": "ppak", + "빹": "ppat", + "빺": "ppap", + "빻": "ppat", + "빼": "ppae", + "빽": "ppaek", + "빾": "ppaekk", + "빿": "ppaek", + "뺀": "ppaen", + "뺁": "ppaen", + "뺂": "ppaen", + "뺃": "ppaet", + "뺄": "ppael", + "뺅": "ppaek", + "뺆": "ppaem", + "뺇": "ppaep", + "뺈": "ppaet", + "뺉": "ppaet", + "뺊": "ppaep", + "뺋": "ppael", + "뺌": "ppaem", + "뺍": "ppaep", + "뺎": "ppaep", + "뺏": "ppaet", + "뺐": "ppaet", + "뺑": "ppaeng", + "뺒": "ppaet", + "뺓": "ppaet", + "뺔": "ppaek", + "뺕": "ppaet", + "뺖": "ppaep", + "뺗": "ppaet", + "뺘": "ppya", + "뺙": "ppyak", + "뺚": "ppyakk", + "뺛": "ppyak", + "뺜": "ppyan", + "뺝": "ppyan", + "뺞": "ppyan", + "뺟": "ppyat", + "뺠": "ppyal", + "뺡": "ppyak", + "뺢": "ppyam", + "뺣": "ppyap", + "뺤": "ppyat", + "뺥": "ppyat", + "뺦": "ppyap", + "뺧": "ppyal", + "뺨": "ppyam", + "뺩": "ppyap", + "뺪": "ppyap", + "뺫": "ppyat", + "뺬": "ppyat", + "뺭": "ppyang", + "뺮": "ppyat", + "뺯": "ppyat", + "뺰": "ppyak", + "뺱": "ppyat", + "뺲": "ppyap", + "뺳": "ppyat", + "뺴": "ppyae", + "뺵": "ppyaek", + "뺶": "ppyaekk", + "뺷": "ppyaek", + "뺸": "ppyaen", + "뺹": "ppyaen", + "뺺": "ppyaen", + "뺻": "ppyaet", + "뺼": "ppyael", + "뺽": "ppyaek", + "뺾": "ppyaem", + "뺿": "ppyaep", + "뻀": "ppyaet", + "뻁": "ppyaet", + "뻂": "ppyaep", + "뻃": "ppyael", + "뻄": "ppyaem", + "뻅": "ppyaep", + "뻆": "ppyaep", + "뻇": "ppyaet", + "뻈": "ppyaet", + "뻉": "ppyaeng", + "뻊": "ppyaet", + "뻋": "ppyaet", + "뻌": "ppyaek", + "뻍": "ppyaet", + "뻎": "ppyaep", + "뻏": "ppyaet", + "뻐": "ppeo", + "뻑": "ppeok", + "뻒": "ppeokk", + "뻓": "ppeok", + "뻔": "ppeon", + "뻕": "ppeon", + "뻖": "ppeon", + "뻗": "ppeot", + "뻘": "ppeol", + "뻙": "ppeok", + "뻚": "ppeom", + "뻛": "ppeop", + "뻜": "ppeot", + "뻝": "ppeot", + "뻞": "ppeop", + "뻟": "ppeol", + "뻠": "ppeom", + "뻡": "ppeop", + "뻢": "ppeop", + "뻣": "ppeot", + "뻤": "ppeot", + "뻥": "ppeong", + "뻦": "ppeot", + "뻧": "ppeot", + "뻨": "ppeok", + "뻩": "ppeot", + "뻪": "ppeop", + "뻫": "ppeot", + "뻬": "ppe", + "뻭": "ppek", + "뻮": "ppekk", + "뻯": "ppek", + "뻰": "ppen", + "뻱": "ppen", + "뻲": "ppen", + "뻳": "ppet", + "뻴": "ppel", + "뻵": "ppek", + "뻶": "ppem", + "뻷": "ppep", + "뻸": "ppet", + "뻹": "ppet", + "뻺": "ppep", + "뻻": "ppel", + "뻼": "ppem", + "뻽": "ppep", + "뻾": "ppep", + "뻿": "ppet", + "뼀": "ppet", + "뼁": "ppeng", + "뼂": "ppet", + "뼃": "ppet", + "뼄": "ppek", + "뼅": "ppet", + "뼆": "ppep", + "뼇": "ppet", + "뼈": "ppyeo", + "뼉": "ppyeok", + "뼊": "ppyeokk", + "뼋": "ppyeok", + "뼌": "ppyeon", + "뼍": "ppyeon", + "뼎": "ppyeon", + "뼏": "ppyeot", + "뼐": "ppyeol", + "뼑": "ppyeok", + "뼒": "ppyeom", + "뼓": "ppyeop", + "뼔": "ppyeot", + "뼕": "ppyeot", + "뼖": "ppyeop", + "뼗": "ppyeol", + "뼘": "ppyeom", + "뼙": "ppyeop", + "뼚": "ppyeop", + "뼛": "ppyeot", + "뼜": "ppyeot", + "뼝": "ppyeong", + "뼞": "ppyeot", + "뼟": "ppyeot", + "뼠": "ppyeok", + "뼡": "ppyeot", + "뼢": "ppyeop", + "뼣": "ppyeot", + "뼤": "ppye", + "뼥": "ppyek", + "뼦": "ppyekk", + "뼧": "ppyek", + "뼨": "ppyen", + "뼩": "ppyen", + "뼪": "ppyen", + "뼫": "ppyet", + "뼬": "ppyel", + "뼭": "ppyek", + "뼮": "ppyem", + "뼯": "ppyep", + "뼰": "ppyet", + "뼱": "ppyet", + "뼲": "ppyep", + "뼳": "ppyel", + "뼴": "ppyem", + "뼵": "ppyep", + "뼶": "ppyep", + "뼷": "ppyet", + "뼸": "ppyet", + "뼹": "ppyeng", + "뼺": "ppyet", + "뼻": "ppyet", + "뼼": "ppyek", + "뼽": "ppyet", + "뼾": "ppyep", + "뼿": "ppyet", + "뽀": "ppo", + "뽁": "ppok", + "뽂": "ppokk", + "뽃": "ppok", + "뽄": "ppon", + "뽅": "ppon", + "뽆": "ppon", + "뽇": "ppot", + "뽈": "ppol", + "뽉": "ppok", + "뽊": "ppom", + "뽋": "ppop", + "뽌": "ppot", + "뽍": "ppot", + "뽎": "ppop", + "뽏": "ppol", + "뽐": "ppom", + "뽑": "ppop", + "뽒": "ppop", + "뽓": "ppot", + "뽔": "ppot", + "뽕": "ppong", + "뽖": "ppot", + "뽗": "ppot", + "뽘": "ppok", + "뽙": "ppot", + "뽚": "ppop", + "뽛": "ppot", + "뽜": "ppwa", + "뽝": "ppwak", + "뽞": "ppwakk", + "뽟": "ppwak", + "뽠": "ppwan", + "뽡": "ppwan", + "뽢": "ppwan", + "뽣": "ppwat", + "뽤": "ppwal", + "뽥": "ppwak", + "뽦": "ppwam", + "뽧": "ppwap", + "뽨": "ppwat", + "뽩": "ppwat", + "뽪": "ppwap", + "뽫": "ppwal", + "뽬": "ppwam", + "뽭": "ppwap", + "뽮": "ppwap", + "뽯": "ppwat", + "뽰": "ppwat", + "뽱": "ppwang", + "뽲": "ppwat", + "뽳": "ppwat", + "뽴": "ppwak", + "뽵": "ppwat", + "뽶": "ppwap", + "뽷": "ppwat", + "뽸": "ppwae", + "뽹": "ppwaek", + "뽺": "ppwaekk", + "뽻": "ppwaek", + "뽼": "ppwaen", + "뽽": "ppwaen", + "뽾": "ppwaen", + "뽿": "ppwaet", + "뾀": "ppwael", + "뾁": "ppwaek", + "뾂": "ppwaem", + "뾃": "ppwaep", + "뾄": "ppwaet", + "뾅": "ppwaet", + "뾆": "ppwaep", + "뾇": "ppwael", + "뾈": "ppwaem", + "뾉": "ppwaep", + "뾊": "ppwaep", + "뾋": "ppwaet", + "뾌": "ppwaet", + "뾍": "ppwaeng", + "뾎": "ppwaet", + "뾏": "ppwaet", + "뾐": "ppwaek", + "뾑": "ppwaet", + "뾒": "ppwaep", + "뾓": "ppwaet", + "뾔": "ppoe", + "뾕": "ppoek", + "뾖": "ppoekk", + "뾗": "ppoek", + "뾘": "ppoen", + "뾙": "ppoen", + "뾚": "ppoen", + "뾛": "ppoet", + "뾜": "ppoel", + "뾝": "ppoek", + "뾞": "ppoem", + "뾟": "ppoep", + "뾠": "ppoet", + "뾡": "ppoet", + "뾢": "ppoep", + "뾣": "ppoel", + "뾤": "ppoem", + "뾥": "ppoep", + "뾦": "ppoep", + "뾧": "ppoet", + "뾨": "ppoet", + "뾩": "ppoeng", + "뾪": "ppoet", + "뾫": "ppoet", + "뾬": "ppoek", + "뾭": "ppoet", + "뾮": "ppoep", + "뾯": "ppoet", + "뾰": "ppyo", + "뾱": "ppyok", + "뾲": "ppyokk", + "뾳": "ppyok", + "뾴": "ppyon", + "뾵": "ppyon", + "뾶": "ppyon", + "뾷": "ppyot", + "뾸": "ppyol", + "뾹": "ppyok", + "뾺": "ppyom", + "뾻": "ppyop", + "뾼": "ppyot", + "뾽": "ppyot", + "뾾": "ppyop", + "뾿": "ppyol", + "뿀": "ppyom", + "뿁": "ppyop", + "뿂": "ppyop", + "뿃": "ppyot", + "뿄": "ppyot", + "뿅": "ppyong", + "뿆": "ppyot", + "뿇": "ppyot", + "뿈": "ppyok", + "뿉": "ppyot", + "뿊": "ppyop", + "뿋": "ppyot", + "뿌": "ppu", + "뿍": "ppuk", + "뿎": "ppukk", + "뿏": "ppuk", + "뿐": "ppun", + "뿑": "ppun", + "뿒": "ppun", + "뿓": "pput", + "뿔": "ppul", + "뿕": "ppuk", + "뿖": "ppum", + "뿗": "ppup", + "뿘": "pput", + "뿙": "pput", + "뿚": "ppup", + "뿛": "ppul", + "뿜": "ppum", + "뿝": "ppup", + "뿞": "ppup", + "뿟": "pput", + "뿠": "pput", + "뿡": "ppung", + "뿢": "pput", + "뿣": "pput", + "뿤": "ppuk", + "뿥": "pput", + "뿦": "ppup", + "뿧": "pput", + "뿨": "ppwo", + "뿩": "ppwok", + "뿪": "ppwokk", + "뿫": "ppwok", + "뿬": "ppwon", + "뿭": "ppwon", + "뿮": "ppwon", + "뿯": "ppwot", + "뿰": "ppwol", + "뿱": "ppwok", + "뿲": "ppwom", + "뿳": "ppwop", + "뿴": "ppwot", + "뿵": "ppwot", + "뿶": "ppwop", + "뿷": "ppwol", + "뿸": "ppwom", + "뿹": "ppwop", + "뿺": "ppwop", + "뿻": "ppwot", + "뿼": "ppwot", + "뿽": "ppwong", + "뿾": "ppwot", + "뿿": "ppwot", + "쀀": "ppwok", + "쀁": "ppwot", + "쀂": "ppwop", + "쀃": "ppwot", + "쀄": "ppwe", + "쀅": "ppwek", + "쀆": "ppwekk", + "쀇": "ppwek", + "쀈": "ppwen", + "쀉": "ppwen", + "쀊": "ppwen", + "쀋": "ppwet", + "쀌": "ppwel", + "쀍": "ppwek", + "쀎": "ppwem", + "쀏": "ppwep", + "쀐": "ppwet", + "쀑": "ppwet", + "쀒": "ppwep", + "쀓": "ppwel", + "쀔": "ppwem", + "쀕": "ppwep", + "쀖": "ppwep", + "쀗": "ppwet", + "쀘": "ppwet", + "쀙": "ppweng", + "쀚": "ppwet", + "쀛": "ppwet", + "쀜": "ppwek", + "쀝": "ppwet", + "쀞": "ppwep", + "쀟": "ppwet", + "쀠": "ppwi", + "쀡": "ppwik", + "쀢": "ppwikk", + "쀣": "ppwik", + "쀤": "ppwin", + "쀥": "ppwin", + "쀦": "ppwin", + "쀧": "ppwit", + "쀨": "ppwil", + "쀩": "ppwik", + "쀪": "ppwim", + "쀫": "ppwip", + "쀬": "ppwit", + "쀭": "ppwit", + "쀮": "ppwip", + "쀯": "ppwil", + "쀰": "ppwim", + "쀱": "ppwip", + "쀲": "ppwip", + "쀳": "ppwit", + "쀴": "ppwit", + "쀵": "ppwing", + "쀶": "ppwit", + "쀷": "ppwit", + "쀸": "ppwik", + "쀹": "ppwit", + "쀺": "ppwip", + "쀻": "ppwit", + "쀼": "ppyu", + "쀽": "ppyuk", + "쀾": "ppyukk", + "쀿": "ppyuk", + "쁀": "ppyun", + "쁁": "ppyun", + "쁂": "ppyun", + "쁃": "ppyut", + "쁄": "ppyul", + "쁅": "ppyuk", + "쁆": "ppyum", + "쁇": "ppyup", + "쁈": "ppyut", + "쁉": "ppyut", + "쁊": "ppyup", + "쁋": "ppyul", + "쁌": "ppyum", + "쁍": "ppyup", + "쁎": "ppyup", + "쁏": "ppyut", + "쁐": "ppyut", + "쁑": "ppyung", + "쁒": "ppyut", + "쁓": "ppyut", + "쁔": "ppyuk", + "쁕": "ppyut", + "쁖": "ppyup", + "쁗": "ppyut", + "쁘": "ppeu", + "쁙": "ppeuk", + "쁚": "ppeukk", + "쁛": "ppeuk", + "쁜": "ppeun", + "쁝": "ppeun", + "쁞": "ppeun", + "쁟": "ppeut", + "쁠": "ppeul", + "쁡": "ppeuk", + "쁢": "ppeum", + "쁣": "ppeup", + "쁤": "ppeut", + "쁥": "ppeut", + "쁦": "ppeup", + "쁧": "ppeul", + "쁨": "ppeum", + "쁩": "ppeup", + "쁪": "ppeup", + "쁫": "ppeut", + "쁬": "ppeut", + "쁭": "ppeung", + "쁮": "ppeut", + "쁯": "ppeut", + "쁰": "ppeuk", + "쁱": "ppeut", + "쁲": "ppeup", + "쁳": "ppeut", + "쁴": "ppeui", + "쁵": "ppeuik", + "쁶": "ppeuikk", + "쁷": "ppeuik", + "쁸": "ppeuin", + "쁹": "ppeuin", + "쁺": "ppeuin", + "쁻": "ppeuit", + "쁼": "ppeuil", + "쁽": "ppeuik", + "쁾": "ppeuim", + "쁿": "ppeuip", + "삀": "ppeuit", + "삁": "ppeuit", + "삂": "ppeuip", + "삃": "ppeuil", + "삄": "ppeuim", + "삅": "ppeuip", + "삆": "ppeuip", + "삇": "ppeuit", + "삈": "ppeuit", + "삉": "ppeuing", + "삊": "ppeuit", + "삋": "ppeuit", + "삌": "ppeuik", + "삍": "ppeuit", + "삎": "ppeuip", + "삏": "ppeuit", + "삐": "ppi", + "삑": "ppik", + "삒": "ppikk", + "삓": "ppik", + "삔": "ppin", + "삕": "ppin", + "삖": "ppin", + "삗": "ppit", + "삘": "ppil", + "삙": "ppik", + "삚": "ppim", + "삛": "ppip", + "삜": "ppit", + "삝": "ppit", + "삞": "ppip", + "삟": "ppil", + "삠": "ppim", + "삡": "ppip", + "삢": "ppip", + "삣": "ppit", + "삤": "ppit", + "삥": "pping", + "삦": "ppit", + "삧": "ppit", + "삨": "ppik", + "삩": "ppit", + "삪": "ppip", + "삫": "ppit", + "사": "sa", + "삭": "sak", + "삮": "sakk", + "삯": "sak", + "산": "san", + "삱": "san", + "삲": "san", + "삳": "sat", + "살": "sal", + "삵": "sak", + "삶": "sam", + "삷": "sap", + "삸": "sat", + "삹": "sat", + "삺": "sap", + "삻": "sal", + "삼": "sam", + "삽": "sap", + "삾": "sap", + "삿": "sat", + "샀": "sat", + "상": "sang", + "샂": "sat", + "샃": "sat", + "샄": "sak", + "샅": "sat", + "샆": "sap", + "샇": "sat", + "새": "sae", + "색": "saek", + "샊": "saekk", + "샋": "saek", + "샌": "saen", + "샍": "saen", + "샎": "saen", + "샏": "saet", + "샐": "sael", + "샑": "saek", + "샒": "saem", + "샓": "saep", + "샔": "saet", + "샕": "saet", + "샖": "saep", + "샗": "sael", + "샘": "saem", + "샙": "saep", + "샚": "saep", + "샛": "saet", + "샜": "saet", + "생": "saeng", + "샞": "saet", + "샟": "saet", + "샠": "saek", + "샡": "saet", + "샢": "saep", + "샣": "saet", + "샤": "sya", + "샥": "syak", + "샦": "syakk", + "샧": "syak", + "샨": "syan", + "샩": "syan", + "샪": "syan", + "샫": "syat", + "샬": "syal", + "샭": "syak", + "샮": "syam", + "샯": "syap", + "샰": "syat", + "샱": "syat", + "샲": "syap", + "샳": "syal", + "샴": "syam", + "샵": "syap", + "샶": "syap", + "샷": "syat", + "샸": "syat", + "샹": "syang", + "샺": "syat", + "샻": "syat", + "샼": "syak", + "샽": "syat", + "샾": "syap", + "샿": "syat", + "섀": "syae", + "섁": "syaek", + "섂": "syaekk", + "섃": "syaek", + "섄": "syaen", + "섅": "syaen", + "섆": "syaen", + "섇": "syaet", + "섈": "syael", + "섉": "syaek", + "섊": "syaem", + "섋": "syaep", + "섌": "syaet", + "섍": "syaet", + "섎": "syaep", + "섏": "syael", + "섐": "syaem", + "섑": "syaep", + "섒": "syaep", + "섓": "syaet", + "섔": "syaet", + "섕": "syaeng", + "섖": "syaet", + "섗": "syaet", + "섘": "syaek", + "섙": "syaet", + "섚": "syaep", + "섛": "syaet", + "서": "seo", + "석": "seok", + "섞": "seokk", + "섟": "seok", + "선": "seon", + "섡": "seon", + "섢": "seon", + "섣": "seot", + "설": "seol", + "섥": "seok", + "섦": "seom", + "섧": "seop", + "섨": "seot", + "섩": "seot", + "섪": "seop", + "섫": "seol", + "섬": "seom", + "섭": "seop", + "섮": "seop", + "섯": "seot", + "섰": "seot", + "성": "seong", + "섲": "seot", + "섳": "seot", + "섴": "seok", + "섵": "seot", + "섶": "seop", + "섷": "seot", + "세": "se", + "섹": "sek", + "섺": "sekk", + "섻": "sek", + "센": "sen", + "섽": "sen", + "섾": "sen", + "섿": "set", + "셀": "sel", + "셁": "sek", + "셂": "sem", + "셃": "sep", + "셄": "set", + "셅": "set", + "셆": "sep", + "셇": "sel", + "셈": "sem", + "셉": "sep", + "셊": "sep", + "셋": "set", + "셌": "set", + "셍": "seng", + "셎": "set", + "셏": "set", + "셐": "sek", + "셑": "set", + "셒": "sep", + "셓": "set", + "셔": "syeo", + "셕": "syeok", + "셖": "syeokk", + "셗": "syeok", + "션": "syeon", + "셙": "syeon", + "셚": "syeon", + "셛": "syeot", + "셜": "syeol", + "셝": "syeok", + "셞": "syeom", + "셟": "syeop", + "셠": "syeot", + "셡": "syeot", + "셢": "syeop", + "셣": "syeol", + "셤": "syeom", + "셥": "syeop", + "셦": "syeop", + "셧": "syeot", + "셨": "syeot", + "셩": "syeong", + "셪": "syeot", + "셫": "syeot", + "셬": "syeok", + "셭": "syeot", + "셮": "syeop", + "셯": "syeot", + "셰": "sye", + "셱": "syek", + "셲": "syekk", + "셳": "syek", + "셴": "syen", + "셵": "syen", + "셶": "syen", + "셷": "syet", + "셸": "syel", + "셹": "syek", + "셺": "syem", + "셻": "syep", + "셼": "syet", + "셽": "syet", + "셾": "syep", + "셿": "syel", + "솀": "syem", + "솁": "syep", + "솂": "syep", + "솃": "syet", + "솄": "syet", + "솅": "syeng", + "솆": "syet", + "솇": "syet", + "솈": "syek", + "솉": "syet", + "솊": "syep", + "솋": "syet", + "소": "so", + "속": "sok", + "솎": "sokk", + "솏": "sok", + "손": "son", + "솑": "son", + "솒": "son", + "솓": "sot", + "솔": "sol", + "솕": "sok", + "솖": "som", + "솗": "sop", + "솘": "sot", + "솙": "sot", + "솚": "sop", + "솛": "sol", + "솜": "som", + "솝": "sop", + "솞": "sop", + "솟": "sot", + "솠": "sot", + "송": "song", + "솢": "sot", + "솣": "sot", + "솤": "sok", + "솥": "sot", + "솦": "sop", + "솧": "sot", + "솨": "swa", + "솩": "swak", + "솪": "swakk", + "솫": "swak", + "솬": "swan", + "솭": "swan", + "솮": "swan", + "솯": "swat", + "솰": "swal", + "솱": "swak", + "솲": "swam", + "솳": "swap", + "솴": "swat", + "솵": "swat", + "솶": "swap", + "솷": "swal", + "솸": "swam", + "솹": "swap", + "솺": "swap", + "솻": "swat", + "솼": "swat", + "솽": "swang", + "솾": "swat", + "솿": "swat", + "쇀": "swak", + "쇁": "swat", + "쇂": "swap", + "쇃": "swat", + "쇄": "swae", + "쇅": "swaek", + "쇆": "swaekk", + "쇇": "swaek", + "쇈": "swaen", + "쇉": "swaen", + "쇊": "swaen", + "쇋": "swaet", + "쇌": "swael", + "쇍": "swaek", + "쇎": "swaem", + "쇏": "swaep", + "쇐": "swaet", + "쇑": "swaet", + "쇒": "swaep", + "쇓": "swael", + "쇔": "swaem", + "쇕": "swaep", + "쇖": "swaep", + "쇗": "swaet", + "쇘": "swaet", + "쇙": "swaeng", + "쇚": "swaet", + "쇛": "swaet", + "쇜": "swaek", + "쇝": "swaet", + "쇞": "swaep", + "쇟": "swaet", + "쇠": "soe", + "쇡": "soek", + "쇢": "soekk", + "쇣": "soek", + "쇤": "soen", + "쇥": "soen", + "쇦": "soen", + "쇧": "soet", + "쇨": "soel", + "쇩": "soek", + "쇪": "soem", + "쇫": "soep", + "쇬": "soet", + "쇭": "soet", + "쇮": "soep", + "쇯": "soel", + "쇰": "soem", + "쇱": "soep", + "쇲": "soep", + "쇳": "soet", + "쇴": "soet", + "쇵": "soeng", + "쇶": "soet", + "쇷": "soet", + "쇸": "soek", + "쇹": "soet", + "쇺": "soep", + "쇻": "soet", + "쇼": "syo", + "쇽": "syok", + "쇾": "syokk", + "쇿": "syok", + "숀": "syon", + "숁": "syon", + "숂": "syon", + "숃": "syot", + "숄": "syol", + "숅": "syok", + "숆": "syom", + "숇": "syop", + "숈": "syot", + "숉": "syot", + "숊": "syop", + "숋": "syol", + "숌": "syom", + "숍": "syop", + "숎": "syop", + "숏": "syot", + "숐": "syot", + "숑": "syong", + "숒": "syot", + "숓": "syot", + "숔": "syok", + "숕": "syot", + "숖": "syop", + "숗": "syot", + "수": "su", + "숙": "suk", + "숚": "sukk", + "숛": "suk", + "순": "sun", + "숝": "sun", + "숞": "sun", + "숟": "sut", + "술": "sul", + "숡": "suk", + "숢": "sum", + "숣": "sup", + "숤": "sut", + "숥": "sut", + "숦": "sup", + "숧": "sul", + "숨": "sum", + "숩": "sup", + "숪": "sup", + "숫": "sut", + "숬": "sut", + "숭": "sung", + "숮": "sut", + "숯": "sut", + "숰": "suk", + "숱": "sut", + "숲": "sup", + "숳": "sut", + "숴": "swo", + "숵": "swok", + "숶": "swokk", + "숷": "swok", + "숸": "swon", + "숹": "swon", + "숺": "swon", + "숻": "swot", + "숼": "swol", + "숽": "swok", + "숾": "swom", + "숿": "swop", + "쉀": "swot", + "쉁": "swot", + "쉂": "swop", + "쉃": "swol", + "쉄": "swom", + "쉅": "swop", + "쉆": "swop", + "쉇": "swot", + "쉈": "swot", + "쉉": "swong", + "쉊": "swot", + "쉋": "swot", + "쉌": "swok", + "쉍": "swot", + "쉎": "swop", + "쉏": "swot", + "쉐": "swe", + "쉑": "swek", + "쉒": "swekk", + "쉓": "swek", + "쉔": "swen", + "쉕": "swen", + "쉖": "swen", + "쉗": "swet", + "쉘": "swel", + "쉙": "swek", + "쉚": "swem", + "쉛": "swep", + "쉜": "swet", + "쉝": "swet", + "쉞": "swep", + "쉟": "swel", + "쉠": "swem", + "쉡": "swep", + "쉢": "swep", + "쉣": "swet", + "쉤": "swet", + "쉥": "sweng", + "쉦": "swet", + "쉧": "swet", + "쉨": "swek", + "쉩": "swet", + "쉪": "swep", + "쉫": "swet", + "쉬": "swi", + "쉭": "swik", + "쉮": "swikk", + "쉯": "swik", + "쉰": "swin", + "쉱": "swin", + "쉲": "swin", + "쉳": "swit", + "쉴": "swil", + "쉵": "swik", + "쉶": "swim", + "쉷": "swip", + "쉸": "swit", + "쉹": "swit", + "쉺": "swip", + "쉻": "swil", + "쉼": "swim", + "쉽": "swip", + "쉾": "swip", + "쉿": "swit", + "슀": "swit", + "슁": "swing", + "슂": "swit", + "슃": "swit", + "슄": "swik", + "슅": "swit", + "슆": "swip", + "슇": "swit", + "슈": "syu", + "슉": "syuk", + "슊": "syukk", + "슋": "syuk", + "슌": "syun", + "슍": "syun", + "슎": "syun", + "슏": "syut", + "슐": "syul", + "슑": "syuk", + "슒": "syum", + "슓": "syup", + "슔": "syut", + "슕": "syut", + "슖": "syup", + "슗": "syul", + "슘": "syum", + "슙": "syup", + "슚": "syup", + "슛": "syut", + "슜": "syut", + "슝": "syung", + "슞": "syut", + "슟": "syut", + "슠": "syuk", + "슡": "syut", + "슢": "syup", + "슣": "syut", + "스": "seu", + "슥": "seuk", + "슦": "seukk", + "슧": "seuk", + "슨": "seun", + "슩": "seun", + "슪": "seun", + "슫": "seut", + "슬": "seul", + "슭": "seuk", + "슮": "seum", + "슯": "seup", + "슰": "seut", + "슱": "seut", + "슲": "seup", + "슳": "seul", + "슴": "seum", + "습": "seup", + "슶": "seup", + "슷": "seut", + "슸": "seut", + "승": "seung", + "슺": "seut", + "슻": "seut", + "슼": "seuk", + "슽": "seut", + "슾": "seup", + "슿": "seut", + "싀": "seui", + "싁": "seuik", + "싂": "seuikk", + "싃": "seuik", + "싄": "seuin", + "싅": "seuin", + "싆": "seuin", + "싇": "seuit", + "싈": "seuil", + "싉": "seuik", + "싊": "seuim", + "싋": "seuip", + "싌": "seuit", + "싍": "seuit", + "싎": "seuip", + "싏": "seuil", + "싐": "seuim", + "싑": "seuip", + "싒": "seuip", + "싓": "seuit", + "싔": "seuit", + "싕": "seuing", + "싖": "seuit", + "싗": "seuit", + "싘": "seuik", + "싙": "seuit", + "싚": "seuip", + "싛": "seuit", + "시": "si", + "식": "sik", + "싞": "sikk", + "싟": "sik", + "신": "sin", + "싡": "sin", + "싢": "sin", + "싣": "sit", + "실": "sil", + "싥": "sik", + "싦": "sim", + "싧": "sip", + "싨": "sit", + "싩": "sit", + "싪": "sip", + "싫": "sil", + "심": "sim", + "십": "sip", + "싮": "sip", + "싯": "sit", + "싰": "sit", + "싱": "sing", + "싲": "sit", + "싳": "sit", + "싴": "sik", + "싵": "sit", + "싶": "sip", + "싷": "sit", + "싸": "ssa", + "싹": "ssak", + "싺": "ssakk", + "싻": "ssak", + "싼": "ssan", + "싽": "ssan", + "싾": "ssan", + "싿": "ssat", + "쌀": "ssal", + "쌁": "ssak", + "쌂": "ssam", + "쌃": "ssap", + "쌄": "ssat", + "쌅": "ssat", + "쌆": "ssap", + "쌇": "ssal", + "쌈": "ssam", + "쌉": "ssap", + "쌊": "ssap", + "쌋": "ssat", + "쌌": "ssat", + "쌍": "ssang", + "쌎": "ssat", + "쌏": "ssat", + "쌐": "ssak", + "쌑": "ssat", + "쌒": "ssap", + "쌓": "ssat", + "쌔": "ssae", + "쌕": "ssaek", + "쌖": "ssaekk", + "쌗": "ssaek", + "쌘": "ssaen", + "쌙": "ssaen", + "쌚": "ssaen", + "쌛": "ssaet", + "쌜": "ssael", + "쌝": "ssaek", + "쌞": "ssaem", + "쌟": "ssaep", + "쌠": "ssaet", + "쌡": "ssaet", + "쌢": "ssaep", + "쌣": "ssael", + "쌤": "ssaem", + "쌥": "ssaep", + "쌦": "ssaep", + "쌧": "ssaet", + "쌨": "ssaet", + "쌩": "ssaeng", + "쌪": "ssaet", + "쌫": "ssaet", + "쌬": "ssaek", + "쌭": "ssaet", + "쌮": "ssaep", + "쌯": "ssaet", + "쌰": "ssya", + "쌱": "ssyak", + "쌲": "ssyakk", + "쌳": "ssyak", + "쌴": "ssyan", + "쌵": "ssyan", + "쌶": "ssyan", + "쌷": "ssyat", + "쌸": "ssyal", + "쌹": "ssyak", + "쌺": "ssyam", + "쌻": "ssyap", + "쌼": "ssyat", + "쌽": "ssyat", + "쌾": "ssyap", + "쌿": "ssyal", + "썀": "ssyam", + "썁": "ssyap", + "썂": "ssyap", + "썃": "ssyat", + "썄": "ssyat", + "썅": "ssyang", + "썆": "ssyat", + "썇": "ssyat", + "썈": "ssyak", + "썉": "ssyat", + "썊": "ssyap", + "썋": "ssyat", + "썌": "ssyae", + "썍": "ssyaek", + "썎": "ssyaekk", + "썏": "ssyaek", + "썐": "ssyaen", + "썑": "ssyaen", + "썒": "ssyaen", + "썓": "ssyaet", + "썔": "ssyael", + "썕": "ssyaek", + "썖": "ssyaem", + "썗": "ssyaep", + "썘": "ssyaet", + "썙": "ssyaet", + "썚": "ssyaep", + "썛": "ssyael", + "썜": "ssyaem", + "썝": "ssyaep", + "썞": "ssyaep", + "썟": "ssyaet", + "썠": "ssyaet", + "썡": "ssyaeng", + "썢": "ssyaet", + "썣": "ssyaet", + "썤": "ssyaek", + "썥": "ssyaet", + "썦": "ssyaep", + "썧": "ssyaet", + "써": "sseo", + "썩": "sseok", + "썪": "sseokk", + "썫": "sseok", + "썬": "sseon", + "썭": "sseon", + "썮": "sseon", + "썯": "sseot", + "썰": "sseol", + "썱": "sseok", + "썲": "sseom", + "썳": "sseop", + "썴": "sseot", + "썵": "sseot", + "썶": "sseop", + "썷": "sseol", + "썸": "sseom", + "썹": "sseop", + "썺": "sseop", + "썻": "sseot", + "썼": "sseot", + "썽": "sseong", + "썾": "sseot", + "썿": "sseot", + "쎀": "sseok", + "쎁": "sseot", + "쎂": "sseop", + "쎃": "sseot", + "쎄": "sse", + "쎅": "ssek", + "쎆": "ssekk", + "쎇": "ssek", + "쎈": "ssen", + "쎉": "ssen", + "쎊": "ssen", + "쎋": "sset", + "쎌": "ssel", + "쎍": "ssek", + "쎎": "ssem", + "쎏": "ssep", + "쎐": "sset", + "쎑": "sset", + "쎒": "ssep", + "쎓": "ssel", + "쎔": "ssem", + "쎕": "ssep", + "쎖": "ssep", + "쎗": "sset", + "쎘": "sset", + "쎙": "sseng", + "쎚": "sset", + "쎛": "sset", + "쎜": "ssek", + "쎝": "sset", + "쎞": "ssep", + "쎟": "sset", + "쎠": "ssyeo", + "쎡": "ssyeok", + "쎢": "ssyeokk", + "쎣": "ssyeok", + "쎤": "ssyeon", + "쎥": "ssyeon", + "쎦": "ssyeon", + "쎧": "ssyeot", + "쎨": "ssyeol", + "쎩": "ssyeok", + "쎪": "ssyeom", + "쎫": "ssyeop", + "쎬": "ssyeot", + "쎭": "ssyeot", + "쎮": "ssyeop", + "쎯": "ssyeol", + "쎰": "ssyeom", + "쎱": "ssyeop", + "쎲": "ssyeop", + "쎳": "ssyeot", + "쎴": "ssyeot", + "쎵": "ssyeong", + "쎶": "ssyeot", + "쎷": "ssyeot", + "쎸": "ssyeok", + "쎹": "ssyeot", + "쎺": "ssyeop", + "쎻": "ssyeot", + "쎼": "ssye", + "쎽": "ssyek", + "쎾": "ssyekk", + "쎿": "ssyek", + "쏀": "ssyen", + "쏁": "ssyen", + "쏂": "ssyen", + "쏃": "ssyet", + "쏄": "ssyel", + "쏅": "ssyek", + "쏆": "ssyem", + "쏇": "ssyep", + "쏈": "ssyet", + "쏉": "ssyet", + "쏊": "ssyep", + "쏋": "ssyel", + "쏌": "ssyem", + "쏍": "ssyep", + "쏎": "ssyep", + "쏏": "ssyet", + "쏐": "ssyet", + "쏑": "ssyeng", + "쏒": "ssyet", + "쏓": "ssyet", + "쏔": "ssyek", + "쏕": "ssyet", + "쏖": "ssyep", + "쏗": "ssyet", + "쏘": "sso", + "쏙": "ssok", + "쏚": "ssokk", + "쏛": "ssok", + "쏜": "sson", + "쏝": "sson", + "쏞": "sson", + "쏟": "ssot", + "쏠": "ssol", + "쏡": "ssok", + "쏢": "ssom", + "쏣": "ssop", + "쏤": "ssot", + "쏥": "ssot", + "쏦": "ssop", + "쏧": "ssol", + "쏨": "ssom", + "쏩": "ssop", + "쏪": "ssop", + "쏫": "ssot", + "쏬": "ssot", + "쏭": "ssong", + "쏮": "ssot", + "쏯": "ssot", + "쏰": "ssok", + "쏱": "ssot", + "쏲": "ssop", + "쏳": "ssot", + "쏴": "sswa", + "쏵": "sswak", + "쏶": "sswakk", + "쏷": "sswak", + "쏸": "sswan", + "쏹": "sswan", + "쏺": "sswan", + "쏻": "sswat", + "쏼": "sswal", + "쏽": "sswak", + "쏾": "sswam", + "쏿": "sswap", + "쐀": "sswat", + "쐁": "sswat", + "쐂": "sswap", + "쐃": "sswal", + "쐄": "sswam", + "쐅": "sswap", + "쐆": "sswap", + "쐇": "sswat", + "쐈": "sswat", + "쐉": "sswang", + "쐊": "sswat", + "쐋": "sswat", + "쐌": "sswak", + "쐍": "sswat", + "쐎": "sswap", + "쐏": "sswat", + "쐐": "sswae", + "쐑": "sswaek", + "쐒": "sswaekk", + "쐓": "sswaek", + "쐔": "sswaen", + "쐕": "sswaen", + "쐖": "sswaen", + "쐗": "sswaet", + "쐘": "sswael", + "쐙": "sswaek", + "쐚": "sswaem", + "쐛": "sswaep", + "쐜": "sswaet", + "쐝": "sswaet", + "쐞": "sswaep", + "쐟": "sswael", + "쐠": "sswaem", + "쐡": "sswaep", + "쐢": "sswaep", + "쐣": "sswaet", + "쐤": "sswaet", + "쐥": "sswaeng", + "쐦": "sswaet", + "쐧": "sswaet", + "쐨": "sswaek", + "쐩": "sswaet", + "쐪": "sswaep", + "쐫": "sswaet", + "쐬": "ssoe", + "쐭": "ssoek", + "쐮": "ssoekk", + "쐯": "ssoek", + "쐰": "ssoen", + "쐱": "ssoen", + "쐲": "ssoen", + "쐳": "ssoet", + "쐴": "ssoel", + "쐵": "ssoek", + "쐶": "ssoem", + "쐷": "ssoep", + "쐸": "ssoet", + "쐹": "ssoet", + "쐺": "ssoep", + "쐻": "ssoel", + "쐼": "ssoem", + "쐽": "ssoep", + "쐾": "ssoep", + "쐿": "ssoet", + "쑀": "ssoet", + "쑁": "ssoeng", + "쑂": "ssoet", + "쑃": "ssoet", + "쑄": "ssoek", + "쑅": "ssoet", + "쑆": "ssoep", + "쑇": "ssoet", + "쑈": "ssyo", + "쑉": "ssyok", + "쑊": "ssyokk", + "쑋": "ssyok", + "쑌": "ssyon", + "쑍": "ssyon", + "쑎": "ssyon", + "쑏": "ssyot", + "쑐": "ssyol", + "쑑": "ssyok", + "쑒": "ssyom", + "쑓": "ssyop", + "쑔": "ssyot", + "쑕": "ssyot", + "쑖": "ssyop", + "쑗": "ssyol", + "쑘": "ssyom", + "쑙": "ssyop", + "쑚": "ssyop", + "쑛": "ssyot", + "쑜": "ssyot", + "쑝": "ssyong", + "쑞": "ssyot", + "쑟": "ssyot", + "쑠": "ssyok", + "쑡": "ssyot", + "쑢": "ssyop", + "쑣": "ssyot", + "쑤": "ssu", + "쑥": "ssuk", + "쑦": "ssukk", + "쑧": "ssuk", + "쑨": "ssun", + "쑩": "ssun", + "쑪": "ssun", + "쑫": "ssut", + "쑬": "ssul", + "쑭": "ssuk", + "쑮": "ssum", + "쑯": "ssup", + "쑰": "ssut", + "쑱": "ssut", + "쑲": "ssup", + "쑳": "ssul", + "쑴": "ssum", + "쑵": "ssup", + "쑶": "ssup", + "쑷": "ssut", + "쑸": "ssut", + "쑹": "ssung", + "쑺": "ssut", + "쑻": "ssut", + "쑼": "ssuk", + "쑽": "ssut", + "쑾": "ssup", + "쑿": "ssut", + "쒀": "sswo", + "쒁": "sswok", + "쒂": "sswokk", + "쒃": "sswok", + "쒄": "sswon", + "쒅": "sswon", + "쒆": "sswon", + "쒇": "sswot", + "쒈": "sswol", + "쒉": "sswok", + "쒊": "sswom", + "쒋": "sswop", + "쒌": "sswot", + "쒍": "sswot", + "쒎": "sswop", + "쒏": "sswol", + "쒐": "sswom", + "쒑": "sswop", + "쒒": "sswop", + "쒓": "sswot", + "쒔": "sswot", + "쒕": "sswong", + "쒖": "sswot", + "쒗": "sswot", + "쒘": "sswok", + "쒙": "sswot", + "쒚": "sswop", + "쒛": "sswot", + "쒜": "sswe", + "쒝": "sswek", + "쒞": "sswekk", + "쒟": "sswek", + "쒠": "sswen", + "쒡": "sswen", + "쒢": "sswen", + "쒣": "sswet", + "쒤": "sswel", + "쒥": "sswek", + "쒦": "sswem", + "쒧": "sswep", + "쒨": "sswet", + "쒩": "sswet", + "쒪": "sswep", + "쒫": "sswel", + "쒬": "sswem", + "쒭": "sswep", + "쒮": "sswep", + "쒯": "sswet", + "쒰": "sswet", + "쒱": "ssweng", + "쒲": "sswet", + "쒳": "sswet", + "쒴": "sswek", + "쒵": "sswet", + "쒶": "sswep", + "쒷": "sswet", + "쒸": "sswi", + "쒹": "sswik", + "쒺": "sswikk", + "쒻": "sswik", + "쒼": "sswin", + "쒽": "sswin", + "쒾": "sswin", + "쒿": "sswit", + "쓀": "sswil", + "쓁": "sswik", + "쓂": "sswim", + "쓃": "sswip", + "쓄": "sswit", + "쓅": "sswit", + "쓆": "sswip", + "쓇": "sswil", + "쓈": "sswim", + "쓉": "sswip", + "쓊": "sswip", + "쓋": "sswit", + "쓌": "sswit", + "쓍": "sswing", + "쓎": "sswit", + "쓏": "sswit", + "쓐": "sswik", + "쓑": "sswit", + "쓒": "sswip", + "쓓": "sswit", + "쓔": "ssyu", + "쓕": "ssyuk", + "쓖": "ssyukk", + "쓗": "ssyuk", + "쓘": "ssyun", + "쓙": "ssyun", + "쓚": "ssyun", + "쓛": "ssyut", + "쓜": "ssyul", + "쓝": "ssyuk", + "쓞": "ssyum", + "쓟": "ssyup", + "쓠": "ssyut", + "쓡": "ssyut", + "쓢": "ssyup", + "쓣": "ssyul", + "쓤": "ssyum", + "쓥": "ssyup", + "쓦": "ssyup", + "쓧": "ssyut", + "쓨": "ssyut", + "쓩": "ssyung", + "쓪": "ssyut", + "쓫": "ssyut", + "쓬": "ssyuk", + "쓭": "ssyut", + "쓮": "ssyup", + "쓯": "ssyut", + "쓰": "sseu", + "쓱": "sseuk", + "쓲": "sseukk", + "쓳": "sseuk", + "쓴": "sseun", + "쓵": "sseun", + "쓶": "sseun", + "쓷": "sseut", + "쓸": "sseul", + "쓹": "sseuk", + "쓺": "sseum", + "쓻": "sseup", + "쓼": "sseut", + "쓽": "sseut", + "쓾": "sseup", + "쓿": "sseul", + "씀": "sseum", + "씁": "sseup", + "씂": "sseup", + "씃": "sseut", + "씄": "sseut", + "씅": "sseung", + "씆": "sseut", + "씇": "sseut", + "씈": "sseuk", + "씉": "sseut", + "씊": "sseup", + "씋": "sseut", + "씌": "sseui", + "씍": "sseuik", + "씎": "sseuikk", + "씏": "sseuik", + "씐": "sseuin", + "씑": "sseuin", + "씒": "sseuin", + "씓": "sseuit", + "씔": "sseuil", + "씕": "sseuik", + "씖": "sseuim", + "씗": "sseuip", + "씘": "sseuit", + "씙": "sseuit", + "씚": "sseuip", + "씛": "sseuil", + "씜": "sseuim", + "씝": "sseuip", + "씞": "sseuip", + "씟": "sseuit", + "씠": "sseuit", + "씡": "sseuing", + "씢": "sseuit", + "씣": "sseuit", + "씤": "sseuik", + "씥": "sseuit", + "씦": "sseuip", + "씧": "sseuit", + "씨": "ssi", + "씩": "ssik", + "씪": "ssikk", + "씫": "ssik", + "씬": "ssin", + "씭": "ssin", + "씮": "ssin", + "씯": "ssit", + "씰": "ssil", + "씱": "ssik", + "씲": "ssim", + "씳": "ssip", + "씴": "ssit", + "씵": "ssit", + "씶": "ssip", + "씷": "ssil", + "씸": "ssim", + "씹": "ssip", + "씺": "ssip", + "씻": "ssit", + "씼": "ssit", + "씽": "ssing", + "씾": "ssit", + "씿": "ssit", + "앀": "ssik", + "앁": "ssit", + "앂": "ssip", + "앃": "ssit", + "아": "a", + "악": "ak", + "앆": "akk", + "앇": "ak", + "안": "an", + "앉": "an", + "않": "an", + "앋": "at", + "알": "al", + "앍": "ak", + "앎": "am", + "앏": "ap", + "앐": "at", + "앑": "at", + "앒": "ap", + "앓": "al", + "암": "am", + "압": "ap", + "앖": "ap", + "앗": "at", + "았": "at", + "앙": "ang", + "앚": "at", + "앛": "at", + "앜": "ak", + "앝": "at", + "앞": "ap", + "앟": "at", + "애": "ae", + "액": "aek", + "앢": "aekk", + "앣": "aek", + "앤": "aen", + "앥": "aen", + "앦": "aen", + "앧": "aet", + "앨": "ael", + "앩": "aek", + "앪": "aem", + "앫": "aep", + "앬": "aet", + "앭": "aet", + "앮": "aep", + "앯": "ael", + "앰": "aem", + "앱": "aep", + "앲": "aep", + "앳": "aet", + "앴": "aet", + "앵": "aeng", + "앶": "aet", + "앷": "aet", + "앸": "aek", + "앹": "aet", + "앺": "aep", + "앻": "aet", + "야": "ya", + "약": "yak", + "앾": "yakk", + "앿": "yak", + "얀": "yan", + "얁": "yan", + "얂": "yan", + "얃": "yat", + "얄": "yal", + "얅": "yak", + "얆": "yam", + "얇": "yap", + "얈": "yat", + "얉": "yat", + "얊": "yap", + "얋": "yal", + "얌": "yam", + "얍": "yap", + "얎": "yap", + "얏": "yat", + "얐": "yat", + "양": "yang", + "얒": "yat", + "얓": "yat", + "얔": "yak", + "얕": "yat", + "얖": "yap", + "얗": "yat", + "얘": "yae", + "얙": "yaek", + "얚": "yaekk", + "얛": "yaek", + "얜": "yaen", + "얝": "yaen", + "얞": "yaen", + "얟": "yaet", + "얠": "yael", + "얡": "yaek", + "얢": "yaem", + "얣": "yaep", + "얤": "yaet", + "얥": "yaet", + "얦": "yaep", + "얧": "yael", + "얨": "yaem", + "얩": "yaep", + "얪": "yaep", + "얫": "yaet", + "얬": "yaet", + "얭": "yaeng", + "얮": "yaet", + "얯": "yaet", + "얰": "yaek", + "얱": "yaet", + "얲": "yaep", + "얳": "yaet", + "어": "eo", + "억": "eok", + "얶": "eokk", + "얷": "eok", + "언": "eon", + "얹": "eon", + "얺": "eon", + "얻": "eot", + "얼": "eol", + "얽": "eok", + "얾": "eom", + "얿": "eop", + "엀": "eot", + "엁": "eot", + "엂": "eop", + "엃": "eol", + "엄": "eom", + "업": "eop", + "없": "eop", + "엇": "eot", + "었": "eot", + "엉": "eong", + "엊": "eot", + "엋": "eot", + "엌": "eok", + "엍": "eot", + "엎": "eop", + "엏": "eot", + "에": "e", + "엑": "ek", + "엒": "ekk", + "엓": "ek", + "엔": "en", + "엕": "en", + "엖": "en", + "엗": "et", + "엘": "el", + "엙": "ek", + "엚": "em", + "엛": "ep", + "엜": "et", + "엝": "et", + "엞": "ep", + "엟": "el", + "엠": "em", + "엡": "ep", + "엢": "ep", + "엣": "et", + "엤": "et", + "엥": "eng", + "엦": "et", + "엧": "et", + "엨": "ek", + "엩": "et", + "엪": "ep", + "엫": "et", + "여": "yeo", + "역": "yeok", + "엮": "yeokk", + "엯": "yeok", + "연": "yeon", + "엱": "yeon", + "엲": "yeon", + "엳": "yeot", + "열": "yeol", + "엵": "yeok", + "엶": "yeom", + "엷": "yeop", + "엸": "yeot", + "엹": "yeot", + "엺": "yeop", + "엻": "yeol", + "염": "yeom", + "엽": "yeop", + "엾": "yeop", + "엿": "yeot", + "였": "yeot", + "영": "yeong", + "옂": "yeot", + "옃": "yeot", + "옄": "yeok", + "옅": "yeot", + "옆": "yeop", + "옇": "yeot", + "예": "ye", + "옉": "yek", + "옊": "yekk", + "옋": "yek", + "옌": "yen", + "옍": "yen", + "옎": "yen", + "옏": "yet", + "옐": "yel", + "옑": "yek", + "옒": "yem", + "옓": "yep", + "옔": "yet", + "옕": "yet", + "옖": "yep", + "옗": "yel", + "옘": "yem", + "옙": "yep", + "옚": "yep", + "옛": "yet", + "옜": "yet", + "옝": "yeng", + "옞": "yet", + "옟": "yet", + "옠": "yek", + "옡": "yet", + "옢": "yep", + "옣": "yet", + "오": "o", + "옥": "ok", + "옦": "okk", + "옧": "ok", + "온": "on", + "옩": "on", + "옪": "on", + "옫": "ot", + "올": "ol", + "옭": "ok", + "옮": "om", + "옯": "op", + "옰": "ot", + "옱": "ot", + "옲": "op", + "옳": "ol", + "옴": "om", + "옵": "op", + "옶": "op", + "옷": "ot", + "옸": "ot", + "옹": "ong", + "옺": "ot", + "옻": "ot", + "옼": "ok", + "옽": "ot", + "옾": "op", + "옿": "ot", + "와": "wa", + "왁": "wak", + "왂": "wakk", + "왃": "wak", + "완": "wan", + "왅": "wan", + "왆": "wan", + "왇": "wat", + "왈": "wal", + "왉": "wak", + "왊": "wam", + "왋": "wap", + "왌": "wat", + "왍": "wat", + "왎": "wap", + "왏": "wal", + "왐": "wam", + "왑": "wap", + "왒": "wap", + "왓": "wat", + "왔": "wat", + "왕": "wang", + "왖": "wat", + "왗": "wat", + "왘": "wak", + "왙": "wat", + "왚": "wap", + "왛": "wat", + "왜": "wae", + "왝": "waek", + "왞": "waekk", + "왟": "waek", + "왠": "waen", + "왡": "waen", + "왢": "waen", + "왣": "waet", + "왤": "wael", + "왥": "waek", + "왦": "waem", + "왧": "waep", + "왨": "waet", + "왩": "waet", + "왪": "waep", + "왫": "wael", + "왬": "waem", + "왭": "waep", + "왮": "waep", + "왯": "waet", + "왰": "waet", + "왱": "waeng", + "왲": "waet", + "왳": "waet", + "왴": "waek", + "왵": "waet", + "왶": "waep", + "왷": "waet", + "외": "oe", + "왹": "oek", + "왺": "oekk", + "왻": "oek", + "왼": "oen", + "왽": "oen", + "왾": "oen", + "왿": "oet", + "욀": "oel", + "욁": "oek", + "욂": "oem", + "욃": "oep", + "욄": "oet", + "욅": "oet", + "욆": "oep", + "욇": "oel", + "욈": "oem", + "욉": "oep", + "욊": "oep", + "욋": "oet", + "욌": "oet", + "욍": "oeng", + "욎": "oet", + "욏": "oet", + "욐": "oek", + "욑": "oet", + "욒": "oep", + "욓": "oet", + "요": "yo", + "욕": "yok", + "욖": "yokk", + "욗": "yok", + "욘": "yon", + "욙": "yon", + "욚": "yon", + "욛": "yot", + "욜": "yol", + "욝": "yok", + "욞": "yom", + "욟": "yop", + "욠": "yot", + "욡": "yot", + "욢": "yop", + "욣": "yol", + "욤": "yom", + "욥": "yop", + "욦": "yop", + "욧": "yot", + "욨": "yot", + "용": "yong", + "욪": "yot", + "욫": "yot", + "욬": "yok", + "욭": "yot", + "욮": "yop", + "욯": "yot", + "우": "u", + "욱": "uk", + "욲": "ukk", + "욳": "uk", + "운": "un", + "욵": "un", + "욶": "un", + "욷": "ut", + "울": "ul", + "욹": "uk", + "욺": "um", + "욻": "up", + "욼": "ut", + "욽": "ut", + "욾": "up", + "욿": "ul", + "움": "um", + "웁": "up", + "웂": "up", + "웃": "ut", + "웄": "ut", + "웅": "ung", + "웆": "ut", + "웇": "ut", + "웈": "uk", + "웉": "ut", + "웊": "up", + "웋": "ut", + "워": "wo", + "웍": "wok", + "웎": "wokk", + "웏": "wok", + "원": "won", + "웑": "won", + "웒": "won", + "웓": "wot", + "월": "wol", + "웕": "wok", + "웖": "wom", + "웗": "wop", + "웘": "wot", + "웙": "wot", + "웚": "wop", + "웛": "wol", + "웜": "wom", + "웝": "wop", + "웞": "wop", + "웟": "wot", + "웠": "wot", + "웡": "wong", + "웢": "wot", + "웣": "wot", + "웤": "wok", + "웥": "wot", + "웦": "wop", + "웧": "wot", + "웨": "we", + "웩": "wek", + "웪": "wekk", + "웫": "wek", + "웬": "wen", + "웭": "wen", + "웮": "wen", + "웯": "wet", + "웰": "wel", + "웱": "wek", + "웲": "wem", + "웳": "wep", + "웴": "wet", + "웵": "wet", + "웶": "wep", + "웷": "wel", + "웸": "wem", + "웹": "wep", + "웺": "wep", + "웻": "wet", + "웼": "wet", + "웽": "weng", + "웾": "wet", + "웿": "wet", + "윀": "wek", + "윁": "wet", + "윂": "wep", + "윃": "wet", + "위": "wi", + "윅": "wik", + "윆": "wikk", + "윇": "wik", + "윈": "win", + "윉": "win", + "윊": "win", + "윋": "wit", + "윌": "wil", + "윍": "wik", + "윎": "wim", + "윏": "wip", + "윐": "wit", + "윑": "wit", + "윒": "wip", + "윓": "wil", + "윔": "wim", + "윕": "wip", + "윖": "wip", + "윗": "wit", + "윘": "wit", + "윙": "wing", + "윚": "wit", + "윛": "wit", + "윜": "wik", + "윝": "wit", + "윞": "wip", + "윟": "wit", + "유": "yu", + "육": "yuk", + "윢": "yukk", + "윣": "yuk", + "윤": "yun", + "윥": "yun", + "윦": "yun", + "윧": "yut", + "율": "yul", + "윩": "yuk", + "윪": "yum", + "윫": "yup", + "윬": "yut", + "윭": "yut", + "윮": "yup", + "윯": "yul", + "윰": "yum", + "윱": "yup", + "윲": "yup", + "윳": "yut", + "윴": "yut", + "융": "yung", + "윶": "yut", + "윷": "yut", + "윸": "yuk", + "윹": "yut", + "윺": "yup", + "윻": "yut", + "으": "eu", + "윽": "euk", + "윾": "eukk", + "윿": "euk", + "은": "eun", + "읁": "eun", + "읂": "eun", + "읃": "eut", + "을": "eul", + "읅": "euk", + "읆": "eum", + "읇": "eup", + "읈": "eut", + "읉": "eut", + "읊": "eup", + "읋": "eul", + "음": "eum", + "읍": "eup", + "읎": "eup", + "읏": "eut", + "읐": "eut", + "응": "eung", + "읒": "eut", + "읓": "eut", + "읔": "euk", + "읕": "eut", + "읖": "eup", + "읗": "eut", + "의": "eui", + "읙": "euik", + "읚": "euikk", + "읛": "euik", + "읜": "euin", + "읝": "euin", + "읞": "euin", + "읟": "euit", + "읠": "euil", + "읡": "euik", + "읢": "euim", + "읣": "euip", + "읤": "euit", + "읥": "euit", + "읦": "euip", + "읧": "euil", + "읨": "euim", + "읩": "euip", + "읪": "euip", + "읫": "euit", + "읬": "euit", + "읭": "euing", + "읮": "euit", + "읯": "euit", + "읰": "euik", + "읱": "euit", + "읲": "euip", + "읳": "euit", + "이": "i", + "익": "ik", + "읶": "ikk", + "읷": "ik", + "인": "in", + "읹": "in", + "읺": "in", + "읻": "it", + "일": "il", + "읽": "ik", + "읾": "im", + "읿": "ip", + "잀": "it", + "잁": "it", + "잂": "ip", + "잃": "il", + "임": "im", + "입": "ip", + "잆": "ip", + "잇": "it", + "있": "it", + "잉": "ing", + "잊": "it", + "잋": "it", + "잌": "ik", + "잍": "it", + "잎": "ip", + "잏": "it", + "자": "ja", + "작": "jak", + "잒": "jakk", + "잓": "jak", + "잔": "jan", + "잕": "jan", + "잖": "jan", + "잗": "jat", + "잘": "jal", + "잙": "jak", + "잚": "jam", + "잛": "jap", + "잜": "jat", + "잝": "jat", + "잞": "jap", + "잟": "jal", + "잠": "jam", + "잡": "jap", + "잢": "jap", + "잣": "jat", + "잤": "jat", + "장": "jang", + "잦": "jat", + "잧": "jat", + "잨": "jak", + "잩": "jat", + "잪": "jap", + "잫": "jat", + "재": "jae", + "잭": "jaek", + "잮": "jaekk", + "잯": "jaek", + "잰": "jaen", + "잱": "jaen", + "잲": "jaen", + "잳": "jaet", + "잴": "jael", + "잵": "jaek", + "잶": "jaem", + "잷": "jaep", + "잸": "jaet", + "잹": "jaet", + "잺": "jaep", + "잻": "jael", + "잼": "jaem", + "잽": "jaep", + "잾": "jaep", + "잿": "jaet", + "쟀": "jaet", + "쟁": "jaeng", + "쟂": "jaet", + "쟃": "jaet", + "쟄": "jaek", + "쟅": "jaet", + "쟆": "jaep", + "쟇": "jaet", + "쟈": "jya", + "쟉": "jyak", + "쟊": "jyakk", + "쟋": "jyak", + "쟌": "jyan", + "쟍": "jyan", + "쟎": "jyan", + "쟏": "jyat", + "쟐": "jyal", + "쟑": "jyak", + "쟒": "jyam", + "쟓": "jyap", + "쟔": "jyat", + "쟕": "jyat", + "쟖": "jyap", + "쟗": "jyal", + "쟘": "jyam", + "쟙": "jyap", + "쟚": "jyap", + "쟛": "jyat", + "쟜": "jyat", + "쟝": "jyang", + "쟞": "jyat", + "쟟": "jyat", + "쟠": "jyak", + "쟡": "jyat", + "쟢": "jyap", + "쟣": "jyat", + "쟤": "jyae", + "쟥": "jyaek", + "쟦": "jyaekk", + "쟧": "jyaek", + "쟨": "jyaen", + "쟩": "jyaen", + "쟪": "jyaen", + "쟫": "jyaet", + "쟬": "jyael", + "쟭": "jyaek", + "쟮": "jyaem", + "쟯": "jyaep", + "쟰": "jyaet", + "쟱": "jyaet", + "쟲": "jyaep", + "쟳": "jyael", + "쟴": "jyaem", + "쟵": "jyaep", + "쟶": "jyaep", + "쟷": "jyaet", + "쟸": "jyaet", + "쟹": "jyaeng", + "쟺": "jyaet", + "쟻": "jyaet", + "쟼": "jyaek", + "쟽": "jyaet", + "쟾": "jyaep", + "쟿": "jyaet", + "저": "jeo", + "적": "jeok", + "젂": "jeokk", + "젃": "jeok", + "전": "jeon", + "젅": "jeon", + "젆": "jeon", + "젇": "jeot", + "절": "jeol", + "젉": "jeok", + "젊": "jeom", + "젋": "jeop", + "젌": "jeot", + "젍": "jeot", + "젎": "jeop", + "젏": "jeol", + "점": "jeom", + "접": "jeop", + "젒": "jeop", + "젓": "jeot", + "젔": "jeot", + "정": "jeong", + "젖": "jeot", + "젗": "jeot", + "젘": "jeok", + "젙": "jeot", + "젚": "jeop", + "젛": "jeot", + "제": "je", + "젝": "jek", + "젞": "jekk", + "젟": "jek", + "젠": "jen", + "젡": "jen", + "젢": "jen", + "젣": "jet", + "젤": "jel", + "젥": "jek", + "젦": "jem", + "젧": "jep", + "젨": "jet", + "젩": "jet", + "젪": "jep", + "젫": "jel", + "젬": "jem", + "젭": "jep", + "젮": "jep", + "젯": "jet", + "젰": "jet", + "젱": "jeng", + "젲": "jet", + "젳": "jet", + "젴": "jek", + "젵": "jet", + "젶": "jep", + "젷": "jet", + "져": "jyeo", + "젹": "jyeok", + "젺": "jyeokk", + "젻": "jyeok", + "젼": "jyeon", + "젽": "jyeon", + "젾": "jyeon", + "젿": "jyeot", + "졀": "jyeol", + "졁": "jyeok", + "졂": "jyeom", + "졃": "jyeop", + "졄": "jyeot", + "졅": "jyeot", + "졆": "jyeop", + "졇": "jyeol", + "졈": "jyeom", + "졉": "jyeop", + "졊": "jyeop", + "졋": "jyeot", + "졌": "jyeot", + "졍": "jyeong", + "졎": "jyeot", + "졏": "jyeot", + "졐": "jyeok", + "졑": "jyeot", + "졒": "jyeop", + "졓": "jyeot", + "졔": "jye", + "졕": "jyek", + "졖": "jyekk", + "졗": "jyek", + "졘": "jyen", + "졙": "jyen", + "졚": "jyen", + "졛": "jyet", + "졜": "jyel", + "졝": "jyek", + "졞": "jyem", + "졟": "jyep", + "졠": "jyet", + "졡": "jyet", + "졢": "jyep", + "졣": "jyel", + "졤": "jyem", + "졥": "jyep", + "졦": "jyep", + "졧": "jyet", + "졨": "jyet", + "졩": "jyeng", + "졪": "jyet", + "졫": "jyet", + "졬": "jyek", + "졭": "jyet", + "졮": "jyep", + "졯": "jyet", + "조": "jo", + "족": "jok", + "졲": "jokk", + "졳": "jok", + "존": "jon", + "졵": "jon", + "졶": "jon", + "졷": "jot", + "졸": "jol", + "졹": "jok", + "졺": "jom", + "졻": "jop", + "졼": "jot", + "졽": "jot", + "졾": "jop", + "졿": "jol", + "좀": "jom", + "좁": "jop", + "좂": "jop", + "좃": "jot", + "좄": "jot", + "종": "jong", + "좆": "jot", + "좇": "jot", + "좈": "jok", + "좉": "jot", + "좊": "jop", + "좋": "jot", + "좌": "jwa", + "좍": "jwak", + "좎": "jwakk", + "좏": "jwak", + "좐": "jwan", + "좑": "jwan", + "좒": "jwan", + "좓": "jwat", + "좔": "jwal", + "좕": "jwak", + "좖": "jwam", + "좗": "jwap", + "좘": "jwat", + "좙": "jwat", + "좚": "jwap", + "좛": "jwal", + "좜": "jwam", + "좝": "jwap", + "좞": "jwap", + "좟": "jwat", + "좠": "jwat", + "좡": "jwang", + "좢": "jwat", + "좣": "jwat", + "좤": "jwak", + "좥": "jwat", + "좦": "jwap", + "좧": "jwat", + "좨": "jwae", + "좩": "jwaek", + "좪": "jwaekk", + "좫": "jwaek", + "좬": "jwaen", + "좭": "jwaen", + "좮": "jwaen", + "좯": "jwaet", + "좰": "jwael", + "좱": "jwaek", + "좲": "jwaem", + "좳": "jwaep", + "좴": "jwaet", + "좵": "jwaet", + "좶": "jwaep", + "좷": "jwael", + "좸": "jwaem", + "좹": "jwaep", + "좺": "jwaep", + "좻": "jwaet", + "좼": "jwaet", + "좽": "jwaeng", + "좾": "jwaet", + "좿": "jwaet", + "죀": "jwaek", + "죁": "jwaet", + "죂": "jwaep", + "죃": "jwaet", + "죄": "joe", + "죅": "joek", + "죆": "joekk", + "죇": "joek", + "죈": "joen", + "죉": "joen", + "죊": "joen", + "죋": "joet", + "죌": "joel", + "죍": "joek", + "죎": "joem", + "죏": "joep", + "죐": "joet", + "죑": "joet", + "죒": "joep", + "죓": "joel", + "죔": "joem", + "죕": "joep", + "죖": "joep", + "죗": "joet", + "죘": "joet", + "죙": "joeng", + "죚": "joet", + "죛": "joet", + "죜": "joek", + "죝": "joet", + "죞": "joep", + "죟": "joet", + "죠": "jyo", + "죡": "jyok", + "죢": "jyokk", + "죣": "jyok", + "죤": "jyon", + "죥": "jyon", + "죦": "jyon", + "죧": "jyot", + "죨": "jyol", + "죩": "jyok", + "죪": "jyom", + "죫": "jyop", + "죬": "jyot", + "죭": "jyot", + "죮": "jyop", + "죯": "jyol", + "죰": "jyom", + "죱": "jyop", + "죲": "jyop", + "죳": "jyot", + "죴": "jyot", + "죵": "jyong", + "죶": "jyot", + "죷": "jyot", + "죸": "jyok", + "죹": "jyot", + "죺": "jyop", + "죻": "jyot", + "주": "ju", + "죽": "juk", + "죾": "jukk", + "죿": "juk", + "준": "jun", + "줁": "jun", + "줂": "jun", + "줃": "jut", + "줄": "jul", + "줅": "juk", + "줆": "jum", + "줇": "jup", + "줈": "jut", + "줉": "jut", + "줊": "jup", + "줋": "jul", + "줌": "jum", + "줍": "jup", + "줎": "jup", + "줏": "jut", + "줐": "jut", + "중": "jung", + "줒": "jut", + "줓": "jut", + "줔": "juk", + "줕": "jut", + "줖": "jup", + "줗": "jut", + "줘": "jwo", + "줙": "jwok", + "줚": "jwokk", + "줛": "jwok", + "줜": "jwon", + "줝": "jwon", + "줞": "jwon", + "줟": "jwot", + "줠": "jwol", + "줡": "jwok", + "줢": "jwom", + "줣": "jwop", + "줤": "jwot", + "줥": "jwot", + "줦": "jwop", + "줧": "jwol", + "줨": "jwom", + "줩": "jwop", + "줪": "jwop", + "줫": "jwot", + "줬": "jwot", + "줭": "jwong", + "줮": "jwot", + "줯": "jwot", + "줰": "jwok", + "줱": "jwot", + "줲": "jwop", + "줳": "jwot", + "줴": "jwe", + "줵": "jwek", + "줶": "jwekk", + "줷": "jwek", + "줸": "jwen", + "줹": "jwen", + "줺": "jwen", + "줻": "jwet", + "줼": "jwel", + "줽": "jwek", + "줾": "jwem", + "줿": "jwep", + "쥀": "jwet", + "쥁": "jwet", + "쥂": "jwep", + "쥃": "jwel", + "쥄": "jwem", + "쥅": "jwep", + "쥆": "jwep", + "쥇": "jwet", + "쥈": "jwet", + "쥉": "jweng", + "쥊": "jwet", + "쥋": "jwet", + "쥌": "jwek", + "쥍": "jwet", + "쥎": "jwep", + "쥏": "jwet", + "쥐": "jwi", + "쥑": "jwik", + "쥒": "jwikk", + "쥓": "jwik", + "쥔": "jwin", + "쥕": "jwin", + "쥖": "jwin", + "쥗": "jwit", + "쥘": "jwil", + "쥙": "jwik", + "쥚": "jwim", + "쥛": "jwip", + "쥜": "jwit", + "쥝": "jwit", + "쥞": "jwip", + "쥟": "jwil", + "쥠": "jwim", + "쥡": "jwip", + "쥢": "jwip", + "쥣": "jwit", + "쥤": "jwit", + "쥥": "jwing", + "쥦": "jwit", + "쥧": "jwit", + "쥨": "jwik", + "쥩": "jwit", + "쥪": "jwip", + "쥫": "jwit", + "쥬": "jyu", + "쥭": "jyuk", + "쥮": "jyukk", + "쥯": "jyuk", + "쥰": "jyun", + "쥱": "jyun", + "쥲": "jyun", + "쥳": "jyut", + "쥴": "jyul", + "쥵": "jyuk", + "쥶": "jyum", + "쥷": "jyup", + "쥸": "jyut", + "쥹": "jyut", + "쥺": "jyup", + "쥻": "jyul", + "쥼": "jyum", + "쥽": "jyup", + "쥾": "jyup", + "쥿": "jyut", + "즀": "jyut", + "즁": "jyung", + "즂": "jyut", + "즃": "jyut", + "즄": "jyuk", + "즅": "jyut", + "즆": "jyup", + "즇": "jyut", + "즈": "jeu", + "즉": "jeuk", + "즊": "jeukk", + "즋": "jeuk", + "즌": "jeun", + "즍": "jeun", + "즎": "jeun", + "즏": "jeut", + "즐": "jeul", + "즑": "jeuk", + "즒": "jeum", + "즓": "jeup", + "즔": "jeut", + "즕": "jeut", + "즖": "jeup", + "즗": "jeul", + "즘": "jeum", + "즙": "jeup", + "즚": "jeup", + "즛": "jeut", + "즜": "jeut", + "증": "jeung", + "즞": "jeut", + "즟": "jeut", + "즠": "jeuk", + "즡": "jeut", + "즢": "jeup", + "즣": "jeut", + "즤": "jeui", + "즥": "jeuik", + "즦": "jeuikk", + "즧": "jeuik", + "즨": "jeuin", + "즩": "jeuin", + "즪": "jeuin", + "즫": "jeuit", + "즬": "jeuil", + "즭": "jeuik", + "즮": "jeuim", + "즯": "jeuip", + "즰": "jeuit", + "즱": "jeuit", + "즲": "jeuip", + "즳": "jeuil", + "즴": "jeuim", + "즵": "jeuip", + "즶": "jeuip", + "즷": "jeuit", + "즸": "jeuit", + "즹": "jeuing", + "즺": "jeuit", + "즻": "jeuit", + "즼": "jeuik", + "즽": "jeuit", + "즾": "jeuip", + "즿": "jeuit", + "지": "ji", + "직": "jik", + "짂": "jikk", + "짃": "jik", + "진": "jin", + "짅": "jin", + "짆": "jin", + "짇": "jit", + "질": "jil", + "짉": "jik", + "짊": "jim", + "짋": "jip", + "짌": "jit", + "짍": "jit", + "짎": "jip", + "짏": "jil", + "짐": "jim", + "집": "jip", + "짒": "jip", + "짓": "jit", + "짔": "jit", + "징": "jing", + "짖": "jit", + "짗": "jit", + "짘": "jik", + "짙": "jit", + "짚": "jip", + "짛": "jit", + "짜": "jja", + "짝": "jjak", + "짞": "jjakk", + "짟": "jjak", + "짠": "jjan", + "짡": "jjan", + "짢": "jjan", + "짣": "jjat", + "짤": "jjal", + "짥": "jjak", + "짦": "jjam", + "짧": "jjap", + "짨": "jjat", + "짩": "jjat", + "짪": "jjap", + "짫": "jjal", + "짬": "jjam", + "짭": "jjap", + "짮": "jjap", + "짯": "jjat", + "짰": "jjat", + "짱": "jjang", + "짲": "jjat", + "짳": "jjat", + "짴": "jjak", + "짵": "jjat", + "짶": "jjap", + "짷": "jjat", + "째": "jjae", + "짹": "jjaek", + "짺": "jjaekk", + "짻": "jjaek", + "짼": "jjaen", + "짽": "jjaen", + "짾": "jjaen", + "짿": "jjaet", + "쨀": "jjael", + "쨁": "jjaek", + "쨂": "jjaem", + "쨃": "jjaep", + "쨄": "jjaet", + "쨅": "jjaet", + "쨆": "jjaep", + "쨇": "jjael", + "쨈": "jjaem", + "쨉": "jjaep", + "쨊": "jjaep", + "쨋": "jjaet", + "쨌": "jjaet", + "쨍": "jjaeng", + "쨎": "jjaet", + "쨏": "jjaet", + "쨐": "jjaek", + "쨑": "jjaet", + "쨒": "jjaep", + "쨓": "jjaet", + "쨔": "jjya", + "쨕": "jjyak", + "쨖": "jjyakk", + "쨗": "jjyak", + "쨘": "jjyan", + "쨙": "jjyan", + "쨚": "jjyan", + "쨛": "jjyat", + "쨜": "jjyal", + "쨝": "jjyak", + "쨞": "jjyam", + "쨟": "jjyap", + "쨠": "jjyat", + "쨡": "jjyat", + "쨢": "jjyap", + "쨣": "jjyal", + "쨤": "jjyam", + "쨥": "jjyap", + "쨦": "jjyap", + "쨧": "jjyat", + "쨨": "jjyat", + "쨩": "jjyang", + "쨪": "jjyat", + "쨫": "jjyat", + "쨬": "jjyak", + "쨭": "jjyat", + "쨮": "jjyap", + "쨯": "jjyat", + "쨰": "jjyae", + "쨱": "jjyaek", + "쨲": "jjyaekk", + "쨳": "jjyaek", + "쨴": "jjyaen", + "쨵": "jjyaen", + "쨶": "jjyaen", + "쨷": "jjyaet", + "쨸": "jjyael", + "쨹": "jjyaek", + "쨺": "jjyaem", + "쨻": "jjyaep", + "쨼": "jjyaet", + "쨽": "jjyaet", + "쨾": "jjyaep", + "쨿": "jjyael", + "쩀": "jjyaem", + "쩁": "jjyaep", + "쩂": "jjyaep", + "쩃": "jjyaet", + "쩄": "jjyaet", + "쩅": "jjyaeng", + "쩆": "jjyaet", + "쩇": "jjyaet", + "쩈": "jjyaek", + "쩉": "jjyaet", + "쩊": "jjyaep", + "쩋": "jjyaet", + "쩌": "jjeo", + "쩍": "jjeok", + "쩎": "jjeokk", + "쩏": "jjeok", + "쩐": "jjeon", + "쩑": "jjeon", + "쩒": "jjeon", + "쩓": "jjeot", + "쩔": "jjeol", + "쩕": "jjeok", + "쩖": "jjeom", + "쩗": "jjeop", + "쩘": "jjeot", + "쩙": "jjeot", + "쩚": "jjeop", + "쩛": "jjeol", + "쩜": "jjeom", + "쩝": "jjeop", + "쩞": "jjeop", + "쩟": "jjeot", + "쩠": "jjeot", + "쩡": "jjeong", + "쩢": "jjeot", + "쩣": "jjeot", + "쩤": "jjeok", + "쩥": "jjeot", + "쩦": "jjeop", + "쩧": "jjeot", + "쩨": "jje", + "쩩": "jjek", + "쩪": "jjekk", + "쩫": "jjek", + "쩬": "jjen", + "쩭": "jjen", + "쩮": "jjen", + "쩯": "jjet", + "쩰": "jjel", + "쩱": "jjek", + "쩲": "jjem", + "쩳": "jjep", + "쩴": "jjet", + "쩵": "jjet", + "쩶": "jjep", + "쩷": "jjel", + "쩸": "jjem", + "쩹": "jjep", + "쩺": "jjep", + "쩻": "jjet", + "쩼": "jjet", + "쩽": "jjeng", + "쩾": "jjet", + "쩿": "jjet", + "쪀": "jjek", + "쪁": "jjet", + "쪂": "jjep", + "쪃": "jjet", + "쪄": "jjyeo", + "쪅": "jjyeok", + "쪆": "jjyeokk", + "쪇": "jjyeok", + "쪈": "jjyeon", + "쪉": "jjyeon", + "쪊": "jjyeon", + "쪋": "jjyeot", + "쪌": "jjyeol", + "쪍": "jjyeok", + "쪎": "jjyeom", + "쪏": "jjyeop", + "쪐": "jjyeot", + "쪑": "jjyeot", + "쪒": "jjyeop", + "쪓": "jjyeol", + "쪔": "jjyeom", + "쪕": "jjyeop", + "쪖": "jjyeop", + "쪗": "jjyeot", + "쪘": "jjyeot", + "쪙": "jjyeong", + "쪚": "jjyeot", + "쪛": "jjyeot", + "쪜": "jjyeok", + "쪝": "jjyeot", + "쪞": "jjyeop", + "쪟": "jjyeot", + "쪠": "jjye", + "쪡": "jjyek", + "쪢": "jjyekk", + "쪣": "jjyek", + "쪤": "jjyen", + "쪥": "jjyen", + "쪦": "jjyen", + "쪧": "jjyet", + "쪨": "jjyel", + "쪩": "jjyek", + "쪪": "jjyem", + "쪫": "jjyep", + "쪬": "jjyet", + "쪭": "jjyet", + "쪮": "jjyep", + "쪯": "jjyel", + "쪰": "jjyem", + "쪱": "jjyep", + "쪲": "jjyep", + "쪳": "jjyet", + "쪴": "jjyet", + "쪵": "jjyeng", + "쪶": "jjyet", + "쪷": "jjyet", + "쪸": "jjyek", + "쪹": "jjyet", + "쪺": "jjyep", + "쪻": "jjyet", + "쪼": "jjo", + "쪽": "jjok", + "쪾": "jjokk", + "쪿": "jjok", + "쫀": "jjon", + "쫁": "jjon", + "쫂": "jjon", + "쫃": "jjot", + "쫄": "jjol", + "쫅": "jjok", + "쫆": "jjom", + "쫇": "jjop", + "쫈": "jjot", + "쫉": "jjot", + "쫊": "jjop", + "쫋": "jjol", + "쫌": "jjom", + "쫍": "jjop", + "쫎": "jjop", + "쫏": "jjot", + "쫐": "jjot", + "쫑": "jjong", + "쫒": "jjot", + "쫓": "jjot", + "쫔": "jjok", + "쫕": "jjot", + "쫖": "jjop", + "쫗": "jjot", + "쫘": "jjwa", + "쫙": "jjwak", + "쫚": "jjwakk", + "쫛": "jjwak", + "쫜": "jjwan", + "쫝": "jjwan", + "쫞": "jjwan", + "쫟": "jjwat", + "쫠": "jjwal", + "쫡": "jjwak", + "쫢": "jjwam", + "쫣": "jjwap", + "쫤": "jjwat", + "쫥": "jjwat", + "쫦": "jjwap", + "쫧": "jjwal", + "쫨": "jjwam", + "쫩": "jjwap", + "쫪": "jjwap", + "쫫": "jjwat", + "쫬": "jjwat", + "쫭": "jjwang", + "쫮": "jjwat", + "쫯": "jjwat", + "쫰": "jjwak", + "쫱": "jjwat", + "쫲": "jjwap", + "쫳": "jjwat", + "쫴": "jjwae", + "쫵": "jjwaek", + "쫶": "jjwaekk", + "쫷": "jjwaek", + "쫸": "jjwaen", + "쫹": "jjwaen", + "쫺": "jjwaen", + "쫻": "jjwaet", + "쫼": "jjwael", + "쫽": "jjwaek", + "쫾": "jjwaem", + "쫿": "jjwaep", + "쬀": "jjwaet", + "쬁": "jjwaet", + "쬂": "jjwaep", + "쬃": "jjwael", + "쬄": "jjwaem", + "쬅": "jjwaep", + "쬆": "jjwaep", + "쬇": "jjwaet", + "쬈": "jjwaet", + "쬉": "jjwaeng", + "쬊": "jjwaet", + "쬋": "jjwaet", + "쬌": "jjwaek", + "쬍": "jjwaet", + "쬎": "jjwaep", + "쬏": "jjwaet", + "쬐": "jjoe", + "쬑": "jjoek", + "쬒": "jjoekk", + "쬓": "jjoek", + "쬔": "jjoen", + "쬕": "jjoen", + "쬖": "jjoen", + "쬗": "jjoet", + "쬘": "jjoel", + "쬙": "jjoek", + "쬚": "jjoem", + "쬛": "jjoep", + "쬜": "jjoet", + "쬝": "jjoet", + "쬞": "jjoep", + "쬟": "jjoel", + "쬠": "jjoem", + "쬡": "jjoep", + "쬢": "jjoep", + "쬣": "jjoet", + "쬤": "jjoet", + "쬥": "jjoeng", + "쬦": "jjoet", + "쬧": "jjoet", + "쬨": "jjoek", + "쬩": "jjoet", + "쬪": "jjoep", + "쬫": "jjoet", + "쬬": "jjyo", + "쬭": "jjyok", + "쬮": "jjyokk", + "쬯": "jjyok", + "쬰": "jjyon", + "쬱": "jjyon", + "쬲": "jjyon", + "쬳": "jjyot", + "쬴": "jjyol", + "쬵": "jjyok", + "쬶": "jjyom", + "쬷": "jjyop", + "쬸": "jjyot", + "쬹": "jjyot", + "쬺": "jjyop", + "쬻": "jjyol", + "쬼": "jjyom", + "쬽": "jjyop", + "쬾": "jjyop", + "쬿": "jjyot", + "쭀": "jjyot", + "쭁": "jjyong", + "쭂": "jjyot", + "쭃": "jjyot", + "쭄": "jjyok", + "쭅": "jjyot", + "쭆": "jjyop", + "쭇": "jjyot", + "쭈": "jju", + "쭉": "jjuk", + "쭊": "jjukk", + "쭋": "jjuk", + "쭌": "jjun", + "쭍": "jjun", + "쭎": "jjun", + "쭏": "jjut", + "쭐": "jjul", + "쭑": "jjuk", + "쭒": "jjum", + "쭓": "jjup", + "쭔": "jjut", + "쭕": "jjut", + "쭖": "jjup", + "쭗": "jjul", + "쭘": "jjum", + "쭙": "jjup", + "쭚": "jjup", + "쭛": "jjut", + "쭜": "jjut", + "쭝": "jjung", + "쭞": "jjut", + "쭟": "jjut", + "쭠": "jjuk", + "쭡": "jjut", + "쭢": "jjup", + "쭣": "jjut", + "쭤": "jjwo", + "쭥": "jjwok", + "쭦": "jjwokk", + "쭧": "jjwok", + "쭨": "jjwon", + "쭩": "jjwon", + "쭪": "jjwon", + "쭫": "jjwot", + "쭬": "jjwol", + "쭭": "jjwok", + "쭮": "jjwom", + "쭯": "jjwop", + "쭰": "jjwot", + "쭱": "jjwot", + "쭲": "jjwop", + "쭳": "jjwol", + "쭴": "jjwom", + "쭵": "jjwop", + "쭶": "jjwop", + "쭷": "jjwot", + "쭸": "jjwot", + "쭹": "jjwong", + "쭺": "jjwot", + "쭻": "jjwot", + "쭼": "jjwok", + "쭽": "jjwot", + "쭾": "jjwop", + "쭿": "jjwot", + "쮀": "jjwe", + "쮁": "jjwek", + "쮂": "jjwekk", + "쮃": "jjwek", + "쮄": "jjwen", + "쮅": "jjwen", + "쮆": "jjwen", + "쮇": "jjwet", + "쮈": "jjwel", + "쮉": "jjwek", + "쮊": "jjwem", + "쮋": "jjwep", + "쮌": "jjwet", + "쮍": "jjwet", + "쮎": "jjwep", + "쮏": "jjwel", + "쮐": "jjwem", + "쮑": "jjwep", + "쮒": "jjwep", + "쮓": "jjwet", + "쮔": "jjwet", + "쮕": "jjweng", + "쮖": "jjwet", + "쮗": "jjwet", + "쮘": "jjwek", + "쮙": "jjwet", + "쮚": "jjwep", + "쮛": "jjwet", + "쮜": "jjwi", + "쮝": "jjwik", + "쮞": "jjwikk", + "쮟": "jjwik", + "쮠": "jjwin", + "쮡": "jjwin", + "쮢": "jjwin", + "쮣": "jjwit", + "쮤": "jjwil", + "쮥": "jjwik", + "쮦": "jjwim", + "쮧": "jjwip", + "쮨": "jjwit", + "쮩": "jjwit", + "쮪": "jjwip", + "쮫": "jjwil", + "쮬": "jjwim", + "쮭": "jjwip", + "쮮": "jjwip", + "쮯": "jjwit", + "쮰": "jjwit", + "쮱": "jjwing", + "쮲": "jjwit", + "쮳": "jjwit", + "쮴": "jjwik", + "쮵": "jjwit", + "쮶": "jjwip", + "쮷": "jjwit", + "쮸": "jjyu", + "쮹": "jjyuk", + "쮺": "jjyukk", + "쮻": "jjyuk", + "쮼": "jjyun", + "쮽": "jjyun", + "쮾": "jjyun", + "쮿": "jjyut", + "쯀": "jjyul", + "쯁": "jjyuk", + "쯂": "jjyum", + "쯃": "jjyup", + "쯄": "jjyut", + "쯅": "jjyut", + "쯆": "jjyup", + "쯇": "jjyul", + "쯈": "jjyum", + "쯉": "jjyup", + "쯊": "jjyup", + "쯋": "jjyut", + "쯌": "jjyut", + "쯍": "jjyung", + "쯎": "jjyut", + "쯏": "jjyut", + "쯐": "jjyuk", + "쯑": "jjyut", + "쯒": "jjyup", + "쯓": "jjyut", + "쯔": "jjeu", + "쯕": "jjeuk", + "쯖": "jjeukk", + "쯗": "jjeuk", + "쯘": "jjeun", + "쯙": "jjeun", + "쯚": "jjeun", + "쯛": "jjeut", + "쯜": "jjeul", + "쯝": "jjeuk", + "쯞": "jjeum", + "쯟": "jjeup", + "쯠": "jjeut", + "쯡": "jjeut", + "쯢": "jjeup", + "쯣": "jjeul", + "쯤": "jjeum", + "쯥": "jjeup", + "쯦": "jjeup", + "쯧": "jjeut", + "쯨": "jjeut", + "쯩": "jjeung", + "쯪": "jjeut", + "쯫": "jjeut", + "쯬": "jjeuk", + "쯭": "jjeut", + "쯮": "jjeup", + "쯯": "jjeut", + "쯰": "jjeui", + "쯱": "jjeuik", + "쯲": "jjeuikk", + "쯳": "jjeuik", + "쯴": "jjeuin", + "쯵": "jjeuin", + "쯶": "jjeuin", + "쯷": "jjeuit", + "쯸": "jjeuil", + "쯹": "jjeuik", + "쯺": "jjeuim", + "쯻": "jjeuip", + "쯼": "jjeuit", + "쯽": "jjeuit", + "쯾": "jjeuip", + "쯿": "jjeuil", + "찀": "jjeuim", + "찁": "jjeuip", + "찂": "jjeuip", + "찃": "jjeuit", + "찄": "jjeuit", + "찅": "jjeuing", + "찆": "jjeuit", + "찇": "jjeuit", + "찈": "jjeuik", + "찉": "jjeuit", + "찊": "jjeuip", + "찋": "jjeuit", + "찌": "jji", + "찍": "jjik", + "찎": "jjikk", + "찏": "jjik", + "찐": "jjin", + "찑": "jjin", + "찒": "jjin", + "찓": "jjit", + "찔": "jjil", + "찕": "jjik", + "찖": "jjim", + "찗": "jjip", + "찘": "jjit", + "찙": "jjit", + "찚": "jjip", + "찛": "jjil", + "찜": "jjim", + "찝": "jjip", + "찞": "jjip", + "찟": "jjit", + "찠": "jjit", + "찡": "jjing", + "찢": "jjit", + "찣": "jjit", + "찤": "jjik", + "찥": "jjit", + "찦": "jjip", + "찧": "jjit", + "차": "cha", + "착": "chak", + "찪": "chakk", + "찫": "chak", + "찬": "chan", + "찭": "chan", + "찮": "chan", + "찯": "chat", + "찰": "chal", + "찱": "chak", + "찲": "cham", + "찳": "chap", + "찴": "chat", + "찵": "chat", + "찶": "chap", + "찷": "chal", + "참": "cham", + "찹": "chap", + "찺": "chap", + "찻": "chat", + "찼": "chat", + "창": "chang", + "찾": "chat", + "찿": "chat", + "챀": "chak", + "챁": "chat", + "챂": "chap", + "챃": "chat", + "채": "chae", + "책": "chaek", + "챆": "chaekk", + "챇": "chaek", + "챈": "chaen", + "챉": "chaen", + "챊": "chaen", + "챋": "chaet", + "챌": "chael", + "챍": "chaek", + "챎": "chaem", + "챏": "chaep", + "챐": "chaet", + "챑": "chaet", + "챒": "chaep", + "챓": "chael", + "챔": "chaem", + "챕": "chaep", + "챖": "chaep", + "챗": "chaet", + "챘": "chaet", + "챙": "chaeng", + "챚": "chaet", + "챛": "chaet", + "챜": "chaek", + "챝": "chaet", + "챞": "chaep", + "챟": "chaet", + "챠": "chya", + "챡": "chyak", + "챢": "chyakk", + "챣": "chyak", + "챤": "chyan", + "챥": "chyan", + "챦": "chyan", + "챧": "chyat", + "챨": "chyal", + "챩": "chyak", + "챪": "chyam", + "챫": "chyap", + "챬": "chyat", + "챭": "chyat", + "챮": "chyap", + "챯": "chyal", + "챰": "chyam", + "챱": "chyap", + "챲": "chyap", + "챳": "chyat", + "챴": "chyat", + "챵": "chyang", + "챶": "chyat", + "챷": "chyat", + "챸": "chyak", + "챹": "chyat", + "챺": "chyap", + "챻": "chyat", + "챼": "chyae", + "챽": "chyaek", + "챾": "chyaekk", + "챿": "chyaek", + "첀": "chyaen", + "첁": "chyaen", + "첂": "chyaen", + "첃": "chyaet", + "첄": "chyael", + "첅": "chyaek", + "첆": "chyaem", + "첇": "chyaep", + "첈": "chyaet", + "첉": "chyaet", + "첊": "chyaep", + "첋": "chyael", + "첌": "chyaem", + "첍": "chyaep", + "첎": "chyaep", + "첏": "chyaet", + "첐": "chyaet", + "첑": "chyaeng", + "첒": "chyaet", + "첓": "chyaet", + "첔": "chyaek", + "첕": "chyaet", + "첖": "chyaep", + "첗": "chyaet", + "처": "cheo", + "척": "cheok", + "첚": "cheokk", + "첛": "cheok", + "천": "cheon", + "첝": "cheon", + "첞": "cheon", + "첟": "cheot", + "철": "cheol", + "첡": "cheok", + "첢": "cheom", + "첣": "cheop", + "첤": "cheot", + "첥": "cheot", + "첦": "cheop", + "첧": "cheol", + "첨": "cheom", + "첩": "cheop", + "첪": "cheop", + "첫": "cheot", + "첬": "cheot", + "청": "cheong", + "첮": "cheot", + "첯": "cheot", + "첰": "cheok", + "첱": "cheot", + "첲": "cheop", + "첳": "cheot", + "체": "che", + "첵": "chek", + "첶": "chekk", + "첷": "chek", + "첸": "chen", + "첹": "chen", + "첺": "chen", + "첻": "chet", + "첼": "chel", + "첽": "chek", + "첾": "chem", + "첿": "chep", + "쳀": "chet", + "쳁": "chet", + "쳂": "chep", + "쳃": "chel", + "쳄": "chem", + "쳅": "chep", + "쳆": "chep", + "쳇": "chet", + "쳈": "chet", + "쳉": "cheng", + "쳊": "chet", + "쳋": "chet", + "쳌": "chek", + "쳍": "chet", + "쳎": "chep", + "쳏": "chet", + "쳐": "chyeo", + "쳑": "chyeok", + "쳒": "chyeokk", + "쳓": "chyeok", + "쳔": "chyeon", + "쳕": "chyeon", + "쳖": "chyeon", + "쳗": "chyeot", + "쳘": "chyeol", + "쳙": "chyeok", + "쳚": "chyeom", + "쳛": "chyeop", + "쳜": "chyeot", + "쳝": "chyeot", + "쳞": "chyeop", + "쳟": "chyeol", + "쳠": "chyeom", + "쳡": "chyeop", + "쳢": "chyeop", + "쳣": "chyeot", + "쳤": "chyeot", + "쳥": "chyeong", + "쳦": "chyeot", + "쳧": "chyeot", + "쳨": "chyeok", + "쳩": "chyeot", + "쳪": "chyeop", + "쳫": "chyeot", + "쳬": "chye", + "쳭": "chyek", + "쳮": "chyekk", + "쳯": "chyek", + "쳰": "chyen", + "쳱": "chyen", + "쳲": "chyen", + "쳳": "chyet", + "쳴": "chyel", + "쳵": "chyek", + "쳶": "chyem", + "쳷": "chyep", + "쳸": "chyet", + "쳹": "chyet", + "쳺": "chyep", + "쳻": "chyel", + "쳼": "chyem", + "쳽": "chyep", + "쳾": "chyep", + "쳿": "chyet", + "촀": "chyet", + "촁": "chyeng", + "촂": "chyet", + "촃": "chyet", + "촄": "chyek", + "촅": "chyet", + "촆": "chyep", + "촇": "chyet", + "초": "cho", + "촉": "chok", + "촊": "chokk", + "촋": "chok", + "촌": "chon", + "촍": "chon", + "촎": "chon", + "촏": "chot", + "촐": "chol", + "촑": "chok", + "촒": "chom", + "촓": "chop", + "촔": "chot", + "촕": "chot", + "촖": "chop", + "촗": "chol", + "촘": "chom", + "촙": "chop", + "촚": "chop", + "촛": "chot", + "촜": "chot", + "총": "chong", + "촞": "chot", + "촟": "chot", + "촠": "chok", + "촡": "chot", + "촢": "chop", + "촣": "chot", + "촤": "chwa", + "촥": "chwak", + "촦": "chwakk", + "촧": "chwak", + "촨": "chwan", + "촩": "chwan", + "촪": "chwan", + "촫": "chwat", + "촬": "chwal", + "촭": "chwak", + "촮": "chwam", + "촯": "chwap", + "촰": "chwat", + "촱": "chwat", + "촲": "chwap", + "촳": "chwal", + "촴": "chwam", + "촵": "chwap", + "촶": "chwap", + "촷": "chwat", + "촸": "chwat", + "촹": "chwang", + "촺": "chwat", + "촻": "chwat", + "촼": "chwak", + "촽": "chwat", + "촾": "chwap", + "촿": "chwat", + "쵀": "chwae", + "쵁": "chwaek", + "쵂": "chwaekk", + "쵃": "chwaek", + "쵄": "chwaen", + "쵅": "chwaen", + "쵆": "chwaen", + "쵇": "chwaet", + "쵈": "chwael", + "쵉": "chwaek", + "쵊": "chwaem", + "쵋": "chwaep", + "쵌": "chwaet", + "쵍": "chwaet", + "쵎": "chwaep", + "쵏": "chwael", + "쵐": "chwaem", + "쵑": "chwaep", + "쵒": "chwaep", + "쵓": "chwaet", + "쵔": "chwaet", + "쵕": "chwaeng", + "쵖": "chwaet", + "쵗": "chwaet", + "쵘": "chwaek", + "쵙": "chwaet", + "쵚": "chwaep", + "쵛": "chwaet", + "최": "choe", + "쵝": "choek", + "쵞": "choekk", + "쵟": "choek", + "쵠": "choen", + "쵡": "choen", + "쵢": "choen", + "쵣": "choet", + "쵤": "choel", + "쵥": "choek", + "쵦": "choem", + "쵧": "choep", + "쵨": "choet", + "쵩": "choet", + "쵪": "choep", + "쵫": "choel", + "쵬": "choem", + "쵭": "choep", + "쵮": "choep", + "쵯": "choet", + "쵰": "choet", + "쵱": "choeng", + "쵲": "choet", + "쵳": "choet", + "쵴": "choek", + "쵵": "choet", + "쵶": "choep", + "쵷": "choet", + "쵸": "chyo", + "쵹": "chyok", + "쵺": "chyokk", + "쵻": "chyok", + "쵼": "chyon", + "쵽": "chyon", + "쵾": "chyon", + "쵿": "chyot", + "춀": "chyol", + "춁": "chyok", + "춂": "chyom", + "춃": "chyop", + "춄": "chyot", + "춅": "chyot", + "춆": "chyop", + "춇": "chyol", + "춈": "chyom", + "춉": "chyop", + "춊": "chyop", + "춋": "chyot", + "춌": "chyot", + "춍": "chyong", + "춎": "chyot", + "춏": "chyot", + "춐": "chyok", + "춑": "chyot", + "춒": "chyop", + "춓": "chyot", + "추": "chu", + "축": "chuk", + "춖": "chukk", + "춗": "chuk", + "춘": "chun", + "춙": "chun", + "춚": "chun", + "춛": "chut", + "출": "chul", + "춝": "chuk", + "춞": "chum", + "춟": "chup", + "춠": "chut", + "춡": "chut", + "춢": "chup", + "춣": "chul", + "춤": "chum", + "춥": "chup", + "춦": "chup", + "춧": "chut", + "춨": "chut", + "충": "chung", + "춪": "chut", + "춫": "chut", + "춬": "chuk", + "춭": "chut", + "춮": "chup", + "춯": "chut", + "춰": "chwo", + "춱": "chwok", + "춲": "chwokk", + "춳": "chwok", + "춴": "chwon", + "춵": "chwon", + "춶": "chwon", + "춷": "chwot", + "춸": "chwol", + "춹": "chwok", + "춺": "chwom", + "춻": "chwop", + "춼": "chwot", + "춽": "chwot", + "춾": "chwop", + "춿": "chwol", + "췀": "chwom", + "췁": "chwop", + "췂": "chwop", + "췃": "chwot", + "췄": "chwot", + "췅": "chwong", + "췆": "chwot", + "췇": "chwot", + "췈": "chwok", + "췉": "chwot", + "췊": "chwop", + "췋": "chwot", + "췌": "chwe", + "췍": "chwek", + "췎": "chwekk", + "췏": "chwek", + "췐": "chwen", + "췑": "chwen", + "췒": "chwen", + "췓": "chwet", + "췔": "chwel", + "췕": "chwek", + "췖": "chwem", + "췗": "chwep", + "췘": "chwet", + "췙": "chwet", + "췚": "chwep", + "췛": "chwel", + "췜": "chwem", + "췝": "chwep", + "췞": "chwep", + "췟": "chwet", + "췠": "chwet", + "췡": "chweng", + "췢": "chwet", + "췣": "chwet", + "췤": "chwek", + "췥": "chwet", + "췦": "chwep", + "췧": "chwet", + "취": "chwi", + "췩": "chwik", + "췪": "chwikk", + "췫": "chwik", + "췬": "chwin", + "췭": "chwin", + "췮": "chwin", + "췯": "chwit", + "췰": "chwil", + "췱": "chwik", + "췲": "chwim", + "췳": "chwip", + "췴": "chwit", + "췵": "chwit", + "췶": "chwip", + "췷": "chwil", + "췸": "chwim", + "췹": "chwip", + "췺": "chwip", + "췻": "chwit", + "췼": "chwit", + "췽": "chwing", + "췾": "chwit", + "췿": "chwit", + "츀": "chwik", + "츁": "chwit", + "츂": "chwip", + "츃": "chwit", + "츄": "chyu", + "츅": "chyuk", + "츆": "chyukk", + "츇": "chyuk", + "츈": "chyun", + "츉": "chyun", + "츊": "chyun", + "츋": "chyut", + "츌": "chyul", + "츍": "chyuk", + "츎": "chyum", + "츏": "chyup", + "츐": "chyut", + "츑": "chyut", + "츒": "chyup", + "츓": "chyul", + "츔": "chyum", + "츕": "chyup", + "츖": "chyup", + "츗": "chyut", + "츘": "chyut", + "츙": "chyung", + "츚": "chyut", + "츛": "chyut", + "츜": "chyuk", + "츝": "chyut", + "츞": "chyup", + "츟": "chyut", + "츠": "cheu", + "측": "cheuk", + "츢": "cheukk", + "츣": "cheuk", + "츤": "cheun", + "츥": "cheun", + "츦": "cheun", + "츧": "cheut", + "츨": "cheul", + "츩": "cheuk", + "츪": "cheum", + "츫": "cheup", + "츬": "cheut", + "츭": "cheut", + "츮": "cheup", + "츯": "cheul", + "츰": "cheum", + "츱": "cheup", + "츲": "cheup", + "츳": "cheut", + "츴": "cheut", + "층": "cheung", + "츶": "cheut", + "츷": "cheut", + "츸": "cheuk", + "츹": "cheut", + "츺": "cheup", + "츻": "cheut", + "츼": "cheui", + "츽": "cheuik", + "츾": "cheuikk", + "츿": "cheuik", + "칀": "cheuin", + "칁": "cheuin", + "칂": "cheuin", + "칃": "cheuit", + "칄": "cheuil", + "칅": "cheuik", + "칆": "cheuim", + "칇": "cheuip", + "칈": "cheuit", + "칉": "cheuit", + "칊": "cheuip", + "칋": "cheuil", + "칌": "cheuim", + "칍": "cheuip", + "칎": "cheuip", + "칏": "cheuit", + "칐": "cheuit", + "칑": "cheuing", + "칒": "cheuit", + "칓": "cheuit", + "칔": "cheuik", + "칕": "cheuit", + "칖": "cheuip", + "칗": "cheuit", + "치": "chi", + "칙": "chik", + "칚": "chikk", + "칛": "chik", + "친": "chin", + "칝": "chin", + "칞": "chin", + "칟": "chit", + "칠": "chil", + "칡": "chik", + "칢": "chim", + "칣": "chip", + "칤": "chit", + "칥": "chit", + "칦": "chip", + "칧": "chil", + "침": "chim", + "칩": "chip", + "칪": "chip", + "칫": "chit", + "칬": "chit", + "칭": "ching", + "칮": "chit", + "칯": "chit", + "칰": "chik", + "칱": "chit", + "칲": "chip", + "칳": "chit", + "카": "ka", + "칵": "kak", + "칶": "kakk", + "칷": "kak", + "칸": "kan", + "칹": "kan", + "칺": "kan", + "칻": "kat", + "칼": "kal", + "칽": "kak", + "칾": "kam", + "칿": "kap", + "캀": "kat", + "캁": "kat", + "캂": "kap", + "캃": "kal", + "캄": "kam", + "캅": "kap", + "캆": "kap", + "캇": "kat", + "캈": "kat", + "캉": "kang", + "캊": "kat", + "캋": "kat", + "캌": "kak", + "캍": "kat", + "캎": "kap", + "캏": "kat", + "캐": "kae", + "캑": "kaek", + "캒": "kaekk", + "캓": "kaek", + "캔": "kaen", + "캕": "kaen", + "캖": "kaen", + "캗": "kaet", + "캘": "kael", + "캙": "kaek", + "캚": "kaem", + "캛": "kaep", + "캜": "kaet", + "캝": "kaet", + "캞": "kaep", + "캟": "kael", + "캠": "kaem", + "캡": "kaep", + "캢": "kaep", + "캣": "kaet", + "캤": "kaet", + "캥": "kaeng", + "캦": "kaet", + "캧": "kaet", + "캨": "kaek", + "캩": "kaet", + "캪": "kaep", + "캫": "kaet", + "캬": "kya", + "캭": "kyak", + "캮": "kyakk", + "캯": "kyak", + "캰": "kyan", + "캱": "kyan", + "캲": "kyan", + "캳": "kyat", + "캴": "kyal", + "캵": "kyak", + "캶": "kyam", + "캷": "kyap", + "캸": "kyat", + "캹": "kyat", + "캺": "kyap", + "캻": "kyal", + "캼": "kyam", + "캽": "kyap", + "캾": "kyap", + "캿": "kyat", + "컀": "kyat", + "컁": "kyang", + "컂": "kyat", + "컃": "kyat", + "컄": "kyak", + "컅": "kyat", + "컆": "kyap", + "컇": "kyat", + "컈": "kyae", + "컉": "kyaek", + "컊": "kyaekk", + "컋": "kyaek", + "컌": "kyaen", + "컍": "kyaen", + "컎": "kyaen", + "컏": "kyaet", + "컐": "kyael", + "컑": "kyaek", + "컒": "kyaem", + "컓": "kyaep", + "컔": "kyaet", + "컕": "kyaet", + "컖": "kyaep", + "컗": "kyael", + "컘": "kyaem", + "컙": "kyaep", + "컚": "kyaep", + "컛": "kyaet", + "컜": "kyaet", + "컝": "kyaeng", + "컞": "kyaet", + "컟": "kyaet", + "컠": "kyaek", + "컡": "kyaet", + "컢": "kyaep", + "컣": "kyaet", + "커": "keo", + "컥": "keok", + "컦": "keokk", + "컧": "keok", + "컨": "keon", + "컩": "keon", + "컪": "keon", + "컫": "keot", + "컬": "keol", + "컭": "keok", + "컮": "keom", + "컯": "keop", + "컰": "keot", + "컱": "keot", + "컲": "keop", + "컳": "keol", + "컴": "keom", + "컵": "keop", + "컶": "keop", + "컷": "keot", + "컸": "keot", + "컹": "keong", + "컺": "keot", + "컻": "keot", + "컼": "keok", + "컽": "keot", + "컾": "keop", + "컿": "keot", + "케": "ke", + "켁": "kek", + "켂": "kekk", + "켃": "kek", + "켄": "ken", + "켅": "ken", + "켆": "ken", + "켇": "ket", + "켈": "kel", + "켉": "kek", + "켊": "kem", + "켋": "kep", + "켌": "ket", + "켍": "ket", + "켎": "kep", + "켏": "kel", + "켐": "kem", + "켑": "kep", + "켒": "kep", + "켓": "ket", + "켔": "ket", + "켕": "keng", + "켖": "ket", + "켗": "ket", + "켘": "kek", + "켙": "ket", + "켚": "kep", + "켛": "ket", + "켜": "kyeo", + "켝": "kyeok", + "켞": "kyeokk", + "켟": "kyeok", + "켠": "kyeon", + "켡": "kyeon", + "켢": "kyeon", + "켣": "kyeot", + "켤": "kyeol", + "켥": "kyeok", + "켦": "kyeom", + "켧": "kyeop", + "켨": "kyeot", + "켩": "kyeot", + "켪": "kyeop", + "켫": "kyeol", + "켬": "kyeom", + "켭": "kyeop", + "켮": "kyeop", + "켯": "kyeot", + "켰": "kyeot", + "켱": "kyeong", + "켲": "kyeot", + "켳": "kyeot", + "켴": "kyeok", + "켵": "kyeot", + "켶": "kyeop", + "켷": "kyeot", + "켸": "kye", + "켹": "kyek", + "켺": "kyekk", + "켻": "kyek", + "켼": "kyen", + "켽": "kyen", + "켾": "kyen", + "켿": "kyet", + "콀": "kyel", + "콁": "kyek", + "콂": "kyem", + "콃": "kyep", + "콄": "kyet", + "콅": "kyet", + "콆": "kyep", + "콇": "kyel", + "콈": "kyem", + "콉": "kyep", + "콊": "kyep", + "콋": "kyet", + "콌": "kyet", + "콍": "kyeng", + "콎": "kyet", + "콏": "kyet", + "콐": "kyek", + "콑": "kyet", + "콒": "kyep", + "콓": "kyet", + "코": "ko", + "콕": "kok", + "콖": "kokk", + "콗": "kok", + "콘": "kon", + "콙": "kon", + "콚": "kon", + "콛": "kot", + "콜": "kol", + "콝": "kok", + "콞": "kom", + "콟": "kop", + "콠": "kot", + "콡": "kot", + "콢": "kop", + "콣": "kol", + "콤": "kom", + "콥": "kop", + "콦": "kop", + "콧": "kot", + "콨": "kot", + "콩": "kong", + "콪": "kot", + "콫": "kot", + "콬": "kok", + "콭": "kot", + "콮": "kop", + "콯": "kot", + "콰": "kwa", + "콱": "kwak", + "콲": "kwakk", + "콳": "kwak", + "콴": "kwan", + "콵": "kwan", + "콶": "kwan", + "콷": "kwat", + "콸": "kwal", + "콹": "kwak", + "콺": "kwam", + "콻": "kwap", + "콼": "kwat", + "콽": "kwat", + "콾": "kwap", + "콿": "kwal", + "쾀": "kwam", + "쾁": "kwap", + "쾂": "kwap", + "쾃": "kwat", + "쾄": "kwat", + "쾅": "kwang", + "쾆": "kwat", + "쾇": "kwat", + "쾈": "kwak", + "쾉": "kwat", + "쾊": "kwap", + "쾋": "kwat", + "쾌": "kwae", + "쾍": "kwaek", + "쾎": "kwaekk", + "쾏": "kwaek", + "쾐": "kwaen", + "쾑": "kwaen", + "쾒": "kwaen", + "쾓": "kwaet", + "쾔": "kwael", + "쾕": "kwaek", + "쾖": "kwaem", + "쾗": "kwaep", + "쾘": "kwaet", + "쾙": "kwaet", + "쾚": "kwaep", + "쾛": "kwael", + "쾜": "kwaem", + "쾝": "kwaep", + "쾞": "kwaep", + "쾟": "kwaet", + "쾠": "kwaet", + "쾡": "kwaeng", + "쾢": "kwaet", + "쾣": "kwaet", + "쾤": "kwaek", + "쾥": "kwaet", + "쾦": "kwaep", + "쾧": "kwaet", + "쾨": "koe", + "쾩": "koek", + "쾪": "koekk", + "쾫": "koek", + "쾬": "koen", + "쾭": "koen", + "쾮": "koen", + "쾯": "koet", + "쾰": "koel", + "쾱": "koek", + "쾲": "koem", + "쾳": "koep", + "쾴": "koet", + "쾵": "koet", + "쾶": "koep", + "쾷": "koel", + "쾸": "koem", + "쾹": "koep", + "쾺": "koep", + "쾻": "koet", + "쾼": "koet", + "쾽": "koeng", + "쾾": "koet", + "쾿": "koet", + "쿀": "koek", + "쿁": "koet", + "쿂": "koep", + "쿃": "koet", + "쿄": "kyo", + "쿅": "kyok", + "쿆": "kyokk", + "쿇": "kyok", + "쿈": "kyon", + "쿉": "kyon", + "쿊": "kyon", + "쿋": "kyot", + "쿌": "kyol", + "쿍": "kyok", + "쿎": "kyom", + "쿏": "kyop", + "쿐": "kyot", + "쿑": "kyot", + "쿒": "kyop", + "쿓": "kyol", + "쿔": "kyom", + "쿕": "kyop", + "쿖": "kyop", + "쿗": "kyot", + "쿘": "kyot", + "쿙": "kyong", + "쿚": "kyot", + "쿛": "kyot", + "쿜": "kyok", + "쿝": "kyot", + "쿞": "kyop", + "쿟": "kyot", + "쿠": "ku", + "쿡": "kuk", + "쿢": "kukk", + "쿣": "kuk", + "쿤": "kun", + "쿥": "kun", + "쿦": "kun", + "쿧": "kut", + "쿨": "kul", + "쿩": "kuk", + "쿪": "kum", + "쿫": "kup", + "쿬": "kut", + "쿭": "kut", + "쿮": "kup", + "쿯": "kul", + "쿰": "kum", + "쿱": "kup", + "쿲": "kup", + "쿳": "kut", + "쿴": "kut", + "쿵": "kung", + "쿶": "kut", + "쿷": "kut", + "쿸": "kuk", + "쿹": "kut", + "쿺": "kup", + "쿻": "kut", + "쿼": "kwo", + "쿽": "kwok", + "쿾": "kwokk", + "쿿": "kwok", + "퀀": "kwon", + "퀁": "kwon", + "퀂": "kwon", + "퀃": "kwot", + "퀄": "kwol", + "퀅": "kwok", + "퀆": "kwom", + "퀇": "kwop", + "퀈": "kwot", + "퀉": "kwot", + "퀊": "kwop", + "퀋": "kwol", + "퀌": "kwom", + "퀍": "kwop", + "퀎": "kwop", + "퀏": "kwot", + "퀐": "kwot", + "퀑": "kwong", + "퀒": "kwot", + "퀓": "kwot", + "퀔": "kwok", + "퀕": "kwot", + "퀖": "kwop", + "퀗": "kwot", + "퀘": "kwe", + "퀙": "kwek", + "퀚": "kwekk", + "퀛": "kwek", + "퀜": "kwen", + "퀝": "kwen", + "퀞": "kwen", + "퀟": "kwet", + "퀠": "kwel", + "퀡": "kwek", + "퀢": "kwem", + "퀣": "kwep", + "퀤": "kwet", + "퀥": "kwet", + "퀦": "kwep", + "퀧": "kwel", + "퀨": "kwem", + "퀩": "kwep", + "퀪": "kwep", + "퀫": "kwet", + "퀬": "kwet", + "퀭": "kweng", + "퀮": "kwet", + "퀯": "kwet", + "퀰": "kwek", + "퀱": "kwet", + "퀲": "kwep", + "퀳": "kwet", + "퀴": "kwi", + "퀵": "kwik", + "퀶": "kwikk", + "퀷": "kwik", + "퀸": "kwin", + "퀹": "kwin", + "퀺": "kwin", + "퀻": "kwit", + "퀼": "kwil", + "퀽": "kwik", + "퀾": "kwim", + "퀿": "kwip", + "큀": "kwit", + "큁": "kwit", + "큂": "kwip", + "큃": "kwil", + "큄": "kwim", + "큅": "kwip", + "큆": "kwip", + "큇": "kwit", + "큈": "kwit", + "큉": "kwing", + "큊": "kwit", + "큋": "kwit", + "큌": "kwik", + "큍": "kwit", + "큎": "kwip", + "큏": "kwit", + "큐": "kyu", + "큑": "kyuk", + "큒": "kyukk", + "큓": "kyuk", + "큔": "kyun", + "큕": "kyun", + "큖": "kyun", + "큗": "kyut", + "큘": "kyul", + "큙": "kyuk", + "큚": "kyum", + "큛": "kyup", + "큜": "kyut", + "큝": "kyut", + "큞": "kyup", + "큟": "kyul", + "큠": "kyum", + "큡": "kyup", + "큢": "kyup", + "큣": "kyut", + "큤": "kyut", + "큥": "kyung", + "큦": "kyut", + "큧": "kyut", + "큨": "kyuk", + "큩": "kyut", + "큪": "kyup", + "큫": "kyut", + "크": "keu", + "큭": "keuk", + "큮": "keukk", + "큯": "keuk", + "큰": "keun", + "큱": "keun", + "큲": "keun", + "큳": "keut", + "클": "keul", + "큵": "keuk", + "큶": "keum", + "큷": "keup", + "큸": "keut", + "큹": "keut", + "큺": "keup", + "큻": "keul", + "큼": "keum", + "큽": "keup", + "큾": "keup", + "큿": "keut", + "킀": "keut", + "킁": "keung", + "킂": "keut", + "킃": "keut", + "킄": "keuk", + "킅": "keut", + "킆": "keup", + "킇": "keut", + "킈": "keui", + "킉": "keuik", + "킊": "keuikk", + "킋": "keuik", + "킌": "keuin", + "킍": "keuin", + "킎": "keuin", + "킏": "keuit", + "킐": "keuil", + "킑": "keuik", + "킒": "keuim", + "킓": "keuip", + "킔": "keuit", + "킕": "keuit", + "킖": "keuip", + "킗": "keuil", + "킘": "keuim", + "킙": "keuip", + "킚": "keuip", + "킛": "keuit", + "킜": "keuit", + "킝": "keuing", + "킞": "keuit", + "킟": "keuit", + "킠": "keuik", + "킡": "keuit", + "킢": "keuip", + "킣": "keuit", + "키": "ki", + "킥": "kik", + "킦": "kikk", + "킧": "kik", + "킨": "kin", + "킩": "kin", + "킪": "kin", + "킫": "kit", + "킬": "kil", + "킭": "kik", + "킮": "kim", + "킯": "kip", + "킰": "kit", + "킱": "kit", + "킲": "kip", + "킳": "kil", + "킴": "kim", + "킵": "kip", + "킶": "kip", + "킷": "kit", + "킸": "kit", + "킹": "king", + "킺": "kit", + "킻": "kit", + "킼": "kik", + "킽": "kit", + "킾": "kip", + "킿": "kit", + "타": "ta", + "탁": "tak", + "탂": "takk", + "탃": "tak", + "탄": "tan", + "탅": "tan", + "탆": "tan", + "탇": "tat", + "탈": "tal", + "탉": "tak", + "탊": "tam", + "탋": "tap", + "탌": "tat", + "탍": "tat", + "탎": "tap", + "탏": "tal", + "탐": "tam", + "탑": "tap", + "탒": "tap", + "탓": "tat", + "탔": "tat", + "탕": "tang", + "탖": "tat", + "탗": "tat", + "탘": "tak", + "탙": "tat", + "탚": "tap", + "탛": "tat", + "태": "tae", + "택": "taek", + "탞": "taekk", + "탟": "taek", + "탠": "taen", + "탡": "taen", + "탢": "taen", + "탣": "taet", + "탤": "tael", + "탥": "taek", + "탦": "taem", + "탧": "taep", + "탨": "taet", + "탩": "taet", + "탪": "taep", + "탫": "tael", + "탬": "taem", + "탭": "taep", + "탮": "taep", + "탯": "taet", + "탰": "taet", + "탱": "taeng", + "탲": "taet", + "탳": "taet", + "탴": "taek", + "탵": "taet", + "탶": "taep", + "탷": "taet", + "탸": "tya", + "탹": "tyak", + "탺": "tyakk", + "탻": "tyak", + "탼": "tyan", + "탽": "tyan", + "탾": "tyan", + "탿": "tyat", + "턀": "tyal", + "턁": "tyak", + "턂": "tyam", + "턃": "tyap", + "턄": "tyat", + "턅": "tyat", + "턆": "tyap", + "턇": "tyal", + "턈": "tyam", + "턉": "tyap", + "턊": "tyap", + "턋": "tyat", + "턌": "tyat", + "턍": "tyang", + "턎": "tyat", + "턏": "tyat", + "턐": "tyak", + "턑": "tyat", + "턒": "tyap", + "턓": "tyat", + "턔": "tyae", + "턕": "tyaek", + "턖": "tyaekk", + "턗": "tyaek", + "턘": "tyaen", + "턙": "tyaen", + "턚": "tyaen", + "턛": "tyaet", + "턜": "tyael", + "턝": "tyaek", + "턞": "tyaem", + "턟": "tyaep", + "턠": "tyaet", + "턡": "tyaet", + "턢": "tyaep", + "턣": "tyael", + "턤": "tyaem", + "턥": "tyaep", + "턦": "tyaep", + "턧": "tyaet", + "턨": "tyaet", + "턩": "tyaeng", + "턪": "tyaet", + "턫": "tyaet", + "턬": "tyaek", + "턭": "tyaet", + "턮": "tyaep", + "턯": "tyaet", + "터": "teo", + "턱": "teok", + "턲": "teokk", + "턳": "teok", + "턴": "teon", + "턵": "teon", + "턶": "teon", + "턷": "teot", + "털": "teol", + "턹": "teok", + "턺": "teom", + "턻": "teop", + "턼": "teot", + "턽": "teot", + "턾": "teop", + "턿": "teol", + "텀": "teom", + "텁": "teop", + "텂": "teop", + "텃": "teot", + "텄": "teot", + "텅": "teong", + "텆": "teot", + "텇": "teot", + "텈": "teok", + "텉": "teot", + "텊": "teop", + "텋": "teot", + "테": "te", + "텍": "tek", + "텎": "tekk", + "텏": "tek", + "텐": "ten", + "텑": "ten", + "텒": "ten", + "텓": "tet", + "텔": "tel", + "텕": "tek", + "텖": "tem", + "텗": "tep", + "텘": "tet", + "텙": "tet", + "텚": "tep", + "텛": "tel", + "템": "tem", + "텝": "tep", + "텞": "tep", + "텟": "tet", + "텠": "tet", + "텡": "teng", + "텢": "tet", + "텣": "tet", + "텤": "tek", + "텥": "tet", + "텦": "tep", + "텧": "tet", + "텨": "tyeo", + "텩": "tyeok", + "텪": "tyeokk", + "텫": "tyeok", + "텬": "tyeon", + "텭": "tyeon", + "텮": "tyeon", + "텯": "tyeot", + "텰": "tyeol", + "텱": "tyeok", + "텲": "tyeom", + "텳": "tyeop", + "텴": "tyeot", + "텵": "tyeot", + "텶": "tyeop", + "텷": "tyeol", + "텸": "tyeom", + "텹": "tyeop", + "텺": "tyeop", + "텻": "tyeot", + "텼": "tyeot", + "텽": "tyeong", + "텾": "tyeot", + "텿": "tyeot", + "톀": "tyeok", + "톁": "tyeot", + "톂": "tyeop", + "톃": "tyeot", + "톄": "tye", + "톅": "tyek", + "톆": "tyekk", + "톇": "tyek", + "톈": "tyen", + "톉": "tyen", + "톊": "tyen", + "톋": "tyet", + "톌": "tyel", + "톍": "tyek", + "톎": "tyem", + "톏": "tyep", + "톐": "tyet", + "톑": "tyet", + "톒": "tyep", + "톓": "tyel", + "톔": "tyem", + "톕": "tyep", + "톖": "tyep", + "톗": "tyet", + "톘": "tyet", + "톙": "tyeng", + "톚": "tyet", + "톛": "tyet", + "톜": "tyek", + "톝": "tyet", + "톞": "tyep", + "톟": "tyet", + "토": "to", + "톡": "tok", + "톢": "tokk", + "톣": "tok", + "톤": "ton", + "톥": "ton", + "톦": "ton", + "톧": "tot", + "톨": "tol", + "톩": "tok", + "톪": "tom", + "톫": "top", + "톬": "tot", + "톭": "tot", + "톮": "top", + "톯": "tol", + "톰": "tom", + "톱": "top", + "톲": "top", + "톳": "tot", + "톴": "tot", + "통": "tong", + "톶": "tot", + "톷": "tot", + "톸": "tok", + "톹": "tot", + "톺": "top", + "톻": "tot", + "톼": "twa", + "톽": "twak", + "톾": "twakk", + "톿": "twak", + "퇀": "twan", + "퇁": "twan", + "퇂": "twan", + "퇃": "twat", + "퇄": "twal", + "퇅": "twak", + "퇆": "twam", + "퇇": "twap", + "퇈": "twat", + "퇉": "twat", + "퇊": "twap", + "퇋": "twal", + "퇌": "twam", + "퇍": "twap", + "퇎": "twap", + "퇏": "twat", + "퇐": "twat", + "퇑": "twang", + "퇒": "twat", + "퇓": "twat", + "퇔": "twak", + "퇕": "twat", + "퇖": "twap", + "퇗": "twat", + "퇘": "twae", + "퇙": "twaek", + "퇚": "twaekk", + "퇛": "twaek", + "퇜": "twaen", + "퇝": "twaen", + "퇞": "twaen", + "퇟": "twaet", + "퇠": "twael", + "퇡": "twaek", + "퇢": "twaem", + "퇣": "twaep", + "퇤": "twaet", + "퇥": "twaet", + "퇦": "twaep", + "퇧": "twael", + "퇨": "twaem", + "퇩": "twaep", + "퇪": "twaep", + "퇫": "twaet", + "퇬": "twaet", + "퇭": "twaeng", + "퇮": "twaet", + "퇯": "twaet", + "퇰": "twaek", + "퇱": "twaet", + "퇲": "twaep", + "퇳": "twaet", + "퇴": "toe", + "퇵": "toek", + "퇶": "toekk", + "퇷": "toek", + "퇸": "toen", + "퇹": "toen", + "퇺": "toen", + "퇻": "toet", + "퇼": "toel", + "퇽": "toek", + "퇾": "toem", + "퇿": "toep", + "툀": "toet", + "툁": "toet", + "툂": "toep", + "툃": "toel", + "툄": "toem", + "툅": "toep", + "툆": "toep", + "툇": "toet", + "툈": "toet", + "툉": "toeng", + "툊": "toet", + "툋": "toet", + "툌": "toek", + "툍": "toet", + "툎": "toep", + "툏": "toet", + "툐": "tyo", + "툑": "tyok", + "툒": "tyokk", + "툓": "tyok", + "툔": "tyon", + "툕": "tyon", + "툖": "tyon", + "툗": "tyot", + "툘": "tyol", + "툙": "tyok", + "툚": "tyom", + "툛": "tyop", + "툜": "tyot", + "툝": "tyot", + "툞": "tyop", + "툟": "tyol", + "툠": "tyom", + "툡": "tyop", + "툢": "tyop", + "툣": "tyot", + "툤": "tyot", + "툥": "tyong", + "툦": "tyot", + "툧": "tyot", + "툨": "tyok", + "툩": "tyot", + "툪": "tyop", + "툫": "tyot", + "투": "tu", + "툭": "tuk", + "툮": "tukk", + "툯": "tuk", + "툰": "tun", + "툱": "tun", + "툲": "tun", + "툳": "tut", + "툴": "tul", + "툵": "tuk", + "툶": "tum", + "툷": "tup", + "툸": "tut", + "툹": "tut", + "툺": "tup", + "툻": "tul", + "툼": "tum", + "툽": "tup", + "툾": "tup", + "툿": "tut", + "퉀": "tut", + "퉁": "tung", + "퉂": "tut", + "퉃": "tut", + "퉄": "tuk", + "퉅": "tut", + "퉆": "tup", + "퉇": "tut", + "퉈": "two", + "퉉": "twok", + "퉊": "twokk", + "퉋": "twok", + "퉌": "twon", + "퉍": "twon", + "퉎": "twon", + "퉏": "twot", + "퉐": "twol", + "퉑": "twok", + "퉒": "twom", + "퉓": "twop", + "퉔": "twot", + "퉕": "twot", + "퉖": "twop", + "퉗": "twol", + "퉘": "twom", + "퉙": "twop", + "퉚": "twop", + "퉛": "twot", + "퉜": "twot", + "퉝": "twong", + "퉞": "twot", + "퉟": "twot", + "퉠": "twok", + "퉡": "twot", + "퉢": "twop", + "퉣": "twot", + "퉤": "twe", + "퉥": "twek", + "퉦": "twekk", + "퉧": "twek", + "퉨": "twen", + "퉩": "twen", + "퉪": "twen", + "퉫": "twet", + "퉬": "twel", + "퉭": "twek", + "퉮": "twem", + "퉯": "twep", + "퉰": "twet", + "퉱": "twet", + "퉲": "twep", + "퉳": "twel", + "퉴": "twem", + "퉵": "twep", + "퉶": "twep", + "퉷": "twet", + "퉸": "twet", + "퉹": "tweng", + "퉺": "twet", + "퉻": "twet", + "퉼": "twek", + "퉽": "twet", + "퉾": "twep", + "퉿": "twet", + "튀": "twi", + "튁": "twik", + "튂": "twikk", + "튃": "twik", + "튄": "twin", + "튅": "twin", + "튆": "twin", + "튇": "twit", + "튈": "twil", + "튉": "twik", + "튊": "twim", + "튋": "twip", + "튌": "twit", + "튍": "twit", + "튎": "twip", + "튏": "twil", + "튐": "twim", + "튑": "twip", + "튒": "twip", + "튓": "twit", + "튔": "twit", + "튕": "twing", + "튖": "twit", + "튗": "twit", + "튘": "twik", + "튙": "twit", + "튚": "twip", + "튛": "twit", + "튜": "tyu", + "튝": "tyuk", + "튞": "tyukk", + "튟": "tyuk", + "튠": "tyun", + "튡": "tyun", + "튢": "tyun", + "튣": "tyut", + "튤": "tyul", + "튥": "tyuk", + "튦": "tyum", + "튧": "tyup", + "튨": "tyut", + "튩": "tyut", + "튪": "tyup", + "튫": "tyul", + "튬": "tyum", + "튭": "tyup", + "튮": "tyup", + "튯": "tyut", + "튰": "tyut", + "튱": "tyung", + "튲": "tyut", + "튳": "tyut", + "튴": "tyuk", + "튵": "tyut", + "튶": "tyup", + "튷": "tyut", + "트": "teu", + "특": "teuk", + "튺": "teukk", + "튻": "teuk", + "튼": "teun", + "튽": "teun", + "튾": "teun", + "튿": "teut", + "틀": "teul", + "틁": "teuk", + "틂": "teum", + "틃": "teup", + "틄": "teut", + "틅": "teut", + "틆": "teup", + "틇": "teul", + "틈": "teum", + "틉": "teup", + "틊": "teup", + "틋": "teut", + "틌": "teut", + "틍": "teung", + "틎": "teut", + "틏": "teut", + "틐": "teuk", + "틑": "teut", + "틒": "teup", + "틓": "teut", + "틔": "teui", + "틕": "teuik", + "틖": "teuikk", + "틗": "teuik", + "틘": "teuin", + "틙": "teuin", + "틚": "teuin", + "틛": "teuit", + "틜": "teuil", + "틝": "teuik", + "틞": "teuim", + "틟": "teuip", + "틠": "teuit", + "틡": "teuit", + "틢": "teuip", + "틣": "teuil", + "틤": "teuim", + "틥": "teuip", + "틦": "teuip", + "틧": "teuit", + "틨": "teuit", + "틩": "teuing", + "틪": "teuit", + "틫": "teuit", + "틬": "teuik", + "틭": "teuit", + "틮": "teuip", + "틯": "teuit", + "티": "ti", + "틱": "tik", + "틲": "tikk", + "틳": "tik", + "틴": "tin", + "틵": "tin", + "틶": "tin", + "틷": "tit", + "틸": "til", + "틹": "tik", + "틺": "tim", + "틻": "tip", + "틼": "tit", + "틽": "tit", + "틾": "tip", + "틿": "til", + "팀": "tim", + "팁": "tip", + "팂": "tip", + "팃": "tit", + "팄": "tit", + "팅": "ting", + "팆": "tit", + "팇": "tit", + "팈": "tik", + "팉": "tit", + "팊": "tip", + "팋": "tit", + "파": "pa", + "팍": "pak", + "팎": "pakk", + "팏": "pak", + "판": "pan", + "팑": "pan", + "팒": "pan", + "팓": "pat", + "팔": "pal", + "팕": "pak", + "팖": "pam", + "팗": "pap", + "팘": "pat", + "팙": "pat", + "팚": "pap", + "팛": "pal", + "팜": "pam", + "팝": "pap", + "팞": "pap", + "팟": "pat", + "팠": "pat", + "팡": "pang", + "팢": "pat", + "팣": "pat", + "팤": "pak", + "팥": "pat", + "팦": "pap", + "팧": "pat", + "패": "pae", + "팩": "paek", + "팪": "paekk", + "팫": "paek", + "팬": "paen", + "팭": "paen", + "팮": "paen", + "팯": "paet", + "팰": "pael", + "팱": "paek", + "팲": "paem", + "팳": "paep", + "팴": "paet", + "팵": "paet", + "팶": "paep", + "팷": "pael", + "팸": "paem", + "팹": "paep", + "팺": "paep", + "팻": "paet", + "팼": "paet", + "팽": "paeng", + "팾": "paet", + "팿": "paet", + "퍀": "paek", + "퍁": "paet", + "퍂": "paep", + "퍃": "paet", + "퍄": "pya", + "퍅": "pyak", + "퍆": "pyakk", + "퍇": "pyak", + "퍈": "pyan", + "퍉": "pyan", + "퍊": "pyan", + "퍋": "pyat", + "퍌": "pyal", + "퍍": "pyak", + "퍎": "pyam", + "퍏": "pyap", + "퍐": "pyat", + "퍑": "pyat", + "퍒": "pyap", + "퍓": "pyal", + "퍔": "pyam", + "퍕": "pyap", + "퍖": "pyap", + "퍗": "pyat", + "퍘": "pyat", + "퍙": "pyang", + "퍚": "pyat", + "퍛": "pyat", + "퍜": "pyak", + "퍝": "pyat", + "퍞": "pyap", + "퍟": "pyat", + "퍠": "pyae", + "퍡": "pyaek", + "퍢": "pyaekk", + "퍣": "pyaek", + "퍤": "pyaen", + "퍥": "pyaen", + "퍦": "pyaen", + "퍧": "pyaet", + "퍨": "pyael", + "퍩": "pyaek", + "퍪": "pyaem", + "퍫": "pyaep", + "퍬": "pyaet", + "퍭": "pyaet", + "퍮": "pyaep", + "퍯": "pyael", + "퍰": "pyaem", + "퍱": "pyaep", + "퍲": "pyaep", + "퍳": "pyaet", + "퍴": "pyaet", + "퍵": "pyaeng", + "퍶": "pyaet", + "퍷": "pyaet", + "퍸": "pyaek", + "퍹": "pyaet", + "퍺": "pyaep", + "퍻": "pyaet", + "퍼": "peo", + "퍽": "peok", + "퍾": "peokk", + "퍿": "peok", + "펀": "peon", + "펁": "peon", + "펂": "peon", + "펃": "peot", + "펄": "peol", + "펅": "peok", + "펆": "peom", + "펇": "peop", + "펈": "peot", + "펉": "peot", + "펊": "peop", + "펋": "peol", + "펌": "peom", + "펍": "peop", + "펎": "peop", + "펏": "peot", + "펐": "peot", + "펑": "peong", + "펒": "peot", + "펓": "peot", + "펔": "peok", + "펕": "peot", + "펖": "peop", + "펗": "peot", + "페": "pe", + "펙": "pek", + "펚": "pekk", + "펛": "pek", + "펜": "pen", + "펝": "pen", + "펞": "pen", + "펟": "pet", + "펠": "pel", + "펡": "pek", + "펢": "pem", + "펣": "pep", + "펤": "pet", + "펥": "pet", + "펦": "pep", + "펧": "pel", + "펨": "pem", + "펩": "pep", + "펪": "pep", + "펫": "pet", + "펬": "pet", + "펭": "peng", + "펮": "pet", + "펯": "pet", + "펰": "pek", + "펱": "pet", + "펲": "pep", + "펳": "pet", + "펴": "pyeo", + "펵": "pyeok", + "펶": "pyeokk", + "펷": "pyeok", + "편": "pyeon", + "펹": "pyeon", + "펺": "pyeon", + "펻": "pyeot", + "펼": "pyeol", + "펽": "pyeok", + "펾": "pyeom", + "펿": "pyeop", + "폀": "pyeot", + "폁": "pyeot", + "폂": "pyeop", + "폃": "pyeol", + "폄": "pyeom", + "폅": "pyeop", + "폆": "pyeop", + "폇": "pyeot", + "폈": "pyeot", + "평": "pyeong", + "폊": "pyeot", + "폋": "pyeot", + "폌": "pyeok", + "폍": "pyeot", + "폎": "pyeop", + "폏": "pyeot", + "폐": "pye", + "폑": "pyek", + "폒": "pyekk", + "폓": "pyek", + "폔": "pyen", + "폕": "pyen", + "폖": "pyen", + "폗": "pyet", + "폘": "pyel", + "폙": "pyek", + "폚": "pyem", + "폛": "pyep", + "폜": "pyet", + "폝": "pyet", + "폞": "pyep", + "폟": "pyel", + "폠": "pyem", + "폡": "pyep", + "폢": "pyep", + "폣": "pyet", + "폤": "pyet", + "폥": "pyeng", + "폦": "pyet", + "폧": "pyet", + "폨": "pyek", + "폩": "pyet", + "폪": "pyep", + "폫": "pyet", + "포": "po", + "폭": "pok", + "폮": "pokk", + "폯": "pok", + "폰": "pon", + "폱": "pon", + "폲": "pon", + "폳": "pot", + "폴": "pol", + "폵": "pok", + "폶": "pom", + "폷": "pop", + "폸": "pot", + "폹": "pot", + "폺": "pop", + "폻": "pol", + "폼": "pom", + "폽": "pop", + "폾": "pop", + "폿": "pot", + "퐀": "pot", + "퐁": "pong", + "퐂": "pot", + "퐃": "pot", + "퐄": "pok", + "퐅": "pot", + "퐆": "pop", + "퐇": "pot", + "퐈": "pwa", + "퐉": "pwak", + "퐊": "pwakk", + "퐋": "pwak", + "퐌": "pwan", + "퐍": "pwan", + "퐎": "pwan", + "퐏": "pwat", + "퐐": "pwal", + "퐑": "pwak", + "퐒": "pwam", + "퐓": "pwap", + "퐔": "pwat", + "퐕": "pwat", + "퐖": "pwap", + "퐗": "pwal", + "퐘": "pwam", + "퐙": "pwap", + "퐚": "pwap", + "퐛": "pwat", + "퐜": "pwat", + "퐝": "pwang", + "퐞": "pwat", + "퐟": "pwat", + "퐠": "pwak", + "퐡": "pwat", + "퐢": "pwap", + "퐣": "pwat", + "퐤": "pwae", + "퐥": "pwaek", + "퐦": "pwaekk", + "퐧": "pwaek", + "퐨": "pwaen", + "퐩": "pwaen", + "퐪": "pwaen", + "퐫": "pwaet", + "퐬": "pwael", + "퐭": "pwaek", + "퐮": "pwaem", + "퐯": "pwaep", + "퐰": "pwaet", + "퐱": "pwaet", + "퐲": "pwaep", + "퐳": "pwael", + "퐴": "pwaem", + "퐵": "pwaep", + "퐶": "pwaep", + "퐷": "pwaet", + "퐸": "pwaet", + "퐹": "pwaeng", + "퐺": "pwaet", + "퐻": "pwaet", + "퐼": "pwaek", + "퐽": "pwaet", + "퐾": "pwaep", + "퐿": "pwaet", + "푀": "poe", + "푁": "poek", + "푂": "poekk", + "푃": "poek", + "푄": "poen", + "푅": "poen", + "푆": "poen", + "푇": "poet", + "푈": "poel", + "푉": "poek", + "푊": "poem", + "푋": "poep", + "푌": "poet", + "푍": "poet", + "푎": "poep", + "푏": "poel", + "푐": "poem", + "푑": "poep", + "푒": "poep", + "푓": "poet", + "푔": "poet", + "푕": "poeng", + "푖": "poet", + "푗": "poet", + "푘": "poek", + "푙": "poet", + "푚": "poep", + "푛": "poet", + "표": "pyo", + "푝": "pyok", + "푞": "pyokk", + "푟": "pyok", + "푠": "pyon", + "푡": "pyon", + "푢": "pyon", + "푣": "pyot", + "푤": "pyol", + "푥": "pyok", + "푦": "pyom", + "푧": "pyop", + "푨": "pyot", + "푩": "pyot", + "푪": "pyop", + "푫": "pyol", + "푬": "pyom", + "푭": "pyop", + "푮": "pyop", + "푯": "pyot", + "푰": "pyot", + "푱": "pyong", + "푲": "pyot", + "푳": "pyot", + "푴": "pyok", + "푵": "pyot", + "푶": "pyop", + "푷": "pyot", + "푸": "pu", + "푹": "puk", + "푺": "pukk", + "푻": "puk", + "푼": "pun", + "푽": "pun", + "푾": "pun", + "푿": "put", + "풀": "pul", + "풁": "puk", + "풂": "pum", + "풃": "pup", + "풄": "put", + "풅": "put", + "풆": "pup", + "풇": "pul", + "품": "pum", + "풉": "pup", + "풊": "pup", + "풋": "put", + "풌": "put", + "풍": "pung", + "풎": "put", + "풏": "put", + "풐": "puk", + "풑": "put", + "풒": "pup", + "풓": "put", + "풔": "pwo", + "풕": "pwok", + "풖": "pwokk", + "풗": "pwok", + "풘": "pwon", + "풙": "pwon", + "풚": "pwon", + "풛": "pwot", + "풜": "pwol", + "풝": "pwok", + "풞": "pwom", + "풟": "pwop", + "풠": "pwot", + "풡": "pwot", + "풢": "pwop", + "풣": "pwol", + "풤": "pwom", + "풥": "pwop", + "풦": "pwop", + "풧": "pwot", + "풨": "pwot", + "풩": "pwong", + "풪": "pwot", + "풫": "pwot", + "풬": "pwok", + "풭": "pwot", + "풮": "pwop", + "풯": "pwot", + "풰": "pwe", + "풱": "pwek", + "풲": "pwekk", + "풳": "pwek", + "풴": "pwen", + "풵": "pwen", + "풶": "pwen", + "풷": "pwet", + "풸": "pwel", + "풹": "pwek", + "풺": "pwem", + "풻": "pwep", + "풼": "pwet", + "풽": "pwet", + "풾": "pwep", + "풿": "pwel", + "퓀": "pwem", + "퓁": "pwep", + "퓂": "pwep", + "퓃": "pwet", + "퓄": "pwet", + "퓅": "pweng", + "퓆": "pwet", + "퓇": "pwet", + "퓈": "pwek", + "퓉": "pwet", + "퓊": "pwep", + "퓋": "pwet", + "퓌": "pwi", + "퓍": "pwik", + "퓎": "pwikk", + "퓏": "pwik", + "퓐": "pwin", + "퓑": "pwin", + "퓒": "pwin", + "퓓": "pwit", + "퓔": "pwil", + "퓕": "pwik", + "퓖": "pwim", + "퓗": "pwip", + "퓘": "pwit", + "퓙": "pwit", + "퓚": "pwip", + "퓛": "pwil", + "퓜": "pwim", + "퓝": "pwip", + "퓞": "pwip", + "퓟": "pwit", + "퓠": "pwit", + "퓡": "pwing", + "퓢": "pwit", + "퓣": "pwit", + "퓤": "pwik", + "퓥": "pwit", + "퓦": "pwip", + "퓧": "pwit", + "퓨": "pyu", + "퓩": "pyuk", + "퓪": "pyukk", + "퓫": "pyuk", + "퓬": "pyun", + "퓭": "pyun", + "퓮": "pyun", + "퓯": "pyut", + "퓰": "pyul", + "퓱": "pyuk", + "퓲": "pyum", + "퓳": "pyup", + "퓴": "pyut", + "퓵": "pyut", + "퓶": "pyup", + "퓷": "pyul", + "퓸": "pyum", + "퓹": "pyup", + "퓺": "pyup", + "퓻": "pyut", + "퓼": "pyut", + "퓽": "pyung", + "퓾": "pyut", + "퓿": "pyut", + "픀": "pyuk", + "픁": "pyut", + "픂": "pyup", + "픃": "pyut", + "프": "peu", + "픅": "peuk", + "픆": "peukk", + "픇": "peuk", + "픈": "peun", + "픉": "peun", + "픊": "peun", + "픋": "peut", + "플": "peul", + "픍": "peuk", + "픎": "peum", + "픏": "peup", + "픐": "peut", + "픑": "peut", + "픒": "peup", + "픓": "peul", + "픔": "peum", + "픕": "peup", + "픖": "peup", + "픗": "peut", + "픘": "peut", + "픙": "peung", + "픚": "peut", + "픛": "peut", + "픜": "peuk", + "픝": "peut", + "픞": "peup", + "픟": "peut", + "픠": "peui", + "픡": "peuik", + "픢": "peuikk", + "픣": "peuik", + "픤": "peuin", + "픥": "peuin", + "픦": "peuin", + "픧": "peuit", + "픨": "peuil", + "픩": "peuik", + "픪": "peuim", + "픫": "peuip", + "픬": "peuit", + "픭": "peuit", + "픮": "peuip", + "픯": "peuil", + "픰": "peuim", + "픱": "peuip", + "픲": "peuip", + "픳": "peuit", + "픴": "peuit", + "픵": "peuing", + "픶": "peuit", + "픷": "peuit", + "픸": "peuik", + "픹": "peuit", + "픺": "peuip", + "픻": "peuit", + "피": "pi", + "픽": "pik", + "픾": "pikk", + "픿": "pik", + "핀": "pin", + "핁": "pin", + "핂": "pin", + "핃": "pit", + "필": "pil", + "핅": "pik", + "핆": "pim", + "핇": "pip", + "핈": "pit", + "핉": "pit", + "핊": "pip", + "핋": "pil", + "핌": "pim", + "핍": "pip", + "핎": "pip", + "핏": "pit", + "핐": "pit", + "핑": "ping", + "핒": "pit", + "핓": "pit", + "핔": "pik", + "핕": "pit", + "핖": "pip", + "핗": "pit", + "하": "ha", + "학": "hak", + "핚": "hakk", + "핛": "hak", + "한": "han", + "핝": "han", + "핞": "han", + "핟": "hat", + "할": "hal", + "핡": "hak", + "핢": "ham", + "핣": "hap", + "핤": "hat", + "핥": "hat", + "핦": "hap", + "핧": "hal", + "함": "ham", + "합": "hap", + "핪": "hap", + "핫": "hat", + "핬": "hat", + "항": "hang", + "핮": "hat", + "핯": "hat", + "핰": "hak", + "핱": "hat", + "핲": "hap", + "핳": "hat", + "해": "hae", + "핵": "haek", + "핶": "haekk", + "핷": "haek", + "핸": "haen", + "핹": "haen", + "핺": "haen", + "핻": "haet", + "핼": "hael", + "핽": "haek", + "핾": "haem", + "핿": "haep", + "햀": "haet", + "햁": "haet", + "햂": "haep", + "햃": "hael", + "햄": "haem", + "햅": "haep", + "햆": "haep", + "햇": "haet", + "했": "haet", + "행": "haeng", + "햊": "haet", + "햋": "haet", + "햌": "haek", + "햍": "haet", + "햎": "haep", + "햏": "haet", + "햐": "hya", + "햑": "hyak", + "햒": "hyakk", + "햓": "hyak", + "햔": "hyan", + "햕": "hyan", + "햖": "hyan", + "햗": "hyat", + "햘": "hyal", + "햙": "hyak", + "햚": "hyam", + "햛": "hyap", + "햜": "hyat", + "햝": "hyat", + "햞": "hyap", + "햟": "hyal", + "햠": "hyam", + "햡": "hyap", + "햢": "hyap", + "햣": "hyat", + "햤": "hyat", + "향": "hyang", + "햦": "hyat", + "햧": "hyat", + "햨": "hyak", + "햩": "hyat", + "햪": "hyap", + "햫": "hyat", + "햬": "hyae", + "햭": "hyaek", + "햮": "hyaekk", + "햯": "hyaek", + "햰": "hyaen", + "햱": "hyaen", + "햲": "hyaen", + "햳": "hyaet", + "햴": "hyael", + "햵": "hyaek", + "햶": "hyaem", + "햷": "hyaep", + "햸": "hyaet", + "햹": "hyaet", + "햺": "hyaep", + "햻": "hyael", + "햼": "hyaem", + "햽": "hyaep", + "햾": "hyaep", + "햿": "hyaet", + "헀": "hyaet", + "헁": "hyaeng", + "헂": "hyaet", + "헃": "hyaet", + "헄": "hyaek", + "헅": "hyaet", + "헆": "hyaep", + "헇": "hyaet", + "허": "heo", + "헉": "heok", + "헊": "heokk", + "헋": "heok", + "헌": "heon", + "헍": "heon", + "헎": "heon", + "헏": "heot", + "헐": "heol", + "헑": "heok", + "헒": "heom", + "헓": "heop", + "헔": "heot", + "헕": "heot", + "헖": "heop", + "헗": "heol", + "험": "heom", + "헙": "heop", + "헚": "heop", + "헛": "heot", + "헜": "heot", + "헝": "heong", + "헞": "heot", + "헟": "heot", + "헠": "heok", + "헡": "heot", + "헢": "heop", + "헣": "heot", + "헤": "he", + "헥": "hek", + "헦": "hekk", + "헧": "hek", + "헨": "hen", + "헩": "hen", + "헪": "hen", + "헫": "het", + "헬": "hel", + "헭": "hek", + "헮": "hem", + "헯": "hep", + "헰": "het", + "헱": "het", + "헲": "hep", + "헳": "hel", + "헴": "hem", + "헵": "hep", + "헶": "hep", + "헷": "het", + "헸": "het", + "헹": "heng", + "헺": "het", + "헻": "het", + "헼": "hek", + "헽": "het", + "헾": "hep", + "헿": "het", + "혀": "hyeo", + "혁": "hyeok", + "혂": "hyeokk", + "혃": "hyeok", + "현": "hyeon", + "혅": "hyeon", + "혆": "hyeon", + "혇": "hyeot", + "혈": "hyeol", + "혉": "hyeok", + "혊": "hyeom", + "혋": "hyeop", + "혌": "hyeot", + "혍": "hyeot", + "혎": "hyeop", + "혏": "hyeol", + "혐": "hyeom", + "협": "hyeop", + "혒": "hyeop", + "혓": "hyeot", + "혔": "hyeot", + "형": "hyeong", + "혖": "hyeot", + "혗": "hyeot", + "혘": "hyeok", + "혙": "hyeot", + "혚": "hyeop", + "혛": "hyeot", + "혜": "hye", + "혝": "hyek", + "혞": "hyekk", + "혟": "hyek", + "혠": "hyen", + "혡": "hyen", + "혢": "hyen", + "혣": "hyet", + "혤": "hyel", + "혥": "hyek", + "혦": "hyem", + "혧": "hyep", + "혨": "hyet", + "혩": "hyet", + "혪": "hyep", + "혫": "hyel", + "혬": "hyem", + "혭": "hyep", + "혮": "hyep", + "혯": "hyet", + "혰": "hyet", + "혱": "hyeng", + "혲": "hyet", + "혳": "hyet", + "혴": "hyek", + "혵": "hyet", + "혶": "hyep", + "혷": "hyet", + "호": "ho", + "혹": "hok", + "혺": "hokk", + "혻": "hok", + "혼": "hon", + "혽": "hon", + "혾": "hon", + "혿": "hot", + "홀": "hol", + "홁": "hok", + "홂": "hom", + "홃": "hop", + "홄": "hot", + "홅": "hot", + "홆": "hop", + "홇": "hol", + "홈": "hom", + "홉": "hop", + "홊": "hop", + "홋": "hot", + "홌": "hot", + "홍": "hong", + "홎": "hot", + "홏": "hot", + "홐": "hok", + "홑": "hot", + "홒": "hop", + "홓": "hot", + "화": "hwa", + "확": "hwak", + "홖": "hwakk", + "홗": "hwak", + "환": "hwan", + "홙": "hwan", + "홚": "hwan", + "홛": "hwat", + "활": "hwal", + "홝": "hwak", + "홞": "hwam", + "홟": "hwap", + "홠": "hwat", + "홡": "hwat", + "홢": "hwap", + "홣": "hwal", + "홤": "hwam", + "홥": "hwap", + "홦": "hwap", + "홧": "hwat", + "홨": "hwat", + "황": "hwang", + "홪": "hwat", + "홫": "hwat", + "홬": "hwak", + "홭": "hwat", + "홮": "hwap", + "홯": "hwat", + "홰": "hwae", + "홱": "hwaek", + "홲": "hwaekk", + "홳": "hwaek", + "홴": "hwaen", + "홵": "hwaen", + "홶": "hwaen", + "홷": "hwaet", + "홸": "hwael", + "홹": "hwaek", + "홺": "hwaem", + "홻": "hwaep", + "홼": "hwaet", + "홽": "hwaet", + "홾": "hwaep", + "홿": "hwael", + "횀": "hwaem", + "횁": "hwaep", + "횂": "hwaep", + "횃": "hwaet", + "횄": "hwaet", + "횅": "hwaeng", + "횆": "hwaet", + "횇": "hwaet", + "횈": "hwaek", + "횉": "hwaet", + "횊": "hwaep", + "횋": "hwaet", + "회": "hoe", + "획": "hoek", + "횎": "hoekk", + "횏": "hoek", + "횐": "hoen", + "횑": "hoen", + "횒": "hoen", + "횓": "hoet", + "횔": "hoel", + "횕": "hoek", + "횖": "hoem", + "횗": "hoep", + "횘": "hoet", + "횙": "hoet", + "횚": "hoep", + "횛": "hoel", + "횜": "hoem", + "횝": "hoep", + "횞": "hoep", + "횟": "hoet", + "횠": "hoet", + "횡": "hoeng", + "횢": "hoet", + "횣": "hoet", + "횤": "hoek", + "횥": "hoet", + "횦": "hoep", + "횧": "hoet", + "효": "hyo", + "횩": "hyok", + "횪": "hyokk", + "횫": "hyok", + "횬": "hyon", + "횭": "hyon", + "횮": "hyon", + "횯": "hyot", + "횰": "hyol", + "횱": "hyok", + "횲": "hyom", + "횳": "hyop", + "횴": "hyot", + "횵": "hyot", + "횶": "hyop", + "횷": "hyol", + "횸": "hyom", + "횹": "hyop", + "횺": "hyop", + "횻": "hyot", + "횼": "hyot", + "횽": "hyong", + "횾": "hyot", + "횿": "hyot", + "훀": "hyok", + "훁": "hyot", + "훂": "hyop", + "훃": "hyot", + "후": "hu", + "훅": "huk", + "훆": "hukk", + "훇": "huk", + "훈": "hun", + "훉": "hun", + "훊": "hun", + "훋": "hut", + "훌": "hul", + "훍": "huk", + "훎": "hum", + "훏": "hup", + "훐": "hut", + "훑": "hut", + "훒": "hup", + "훓": "hul", + "훔": "hum", + "훕": "hup", + "훖": "hup", + "훗": "hut", + "훘": "hut", + "훙": "hung", + "훚": "hut", + "훛": "hut", + "훜": "huk", + "훝": "hut", + "훞": "hup", + "훟": "hut", + "훠": "hwo", + "훡": "hwok", + "훢": "hwokk", + "훣": "hwok", + "훤": "hwon", + "훥": "hwon", + "훦": "hwon", + "훧": "hwot", + "훨": "hwol", + "훩": "hwok", + "훪": "hwom", + "훫": "hwop", + "훬": "hwot", + "훭": "hwot", + "훮": "hwop", + "훯": "hwol", + "훰": "hwom", + "훱": "hwop", + "훲": "hwop", + "훳": "hwot", + "훴": "hwot", + "훵": "hwong", + "훶": "hwot", + "훷": "hwot", + "훸": "hwok", + "훹": "hwot", + "훺": "hwop", + "훻": "hwot", + "훼": "hwe", + "훽": "hwek", + "훾": "hwekk", + "훿": "hwek", + "휀": "hwen", + "휁": "hwen", + "휂": "hwen", + "휃": "hwet", + "휄": "hwel", + "휅": "hwek", + "휆": "hwem", + "휇": "hwep", + "휈": "hwet", + "휉": "hwet", + "휊": "hwep", + "휋": "hwel", + "휌": "hwem", + "휍": "hwep", + "휎": "hwep", + "휏": "hwet", + "휐": "hwet", + "휑": "hweng", + "휒": "hwet", + "휓": "hwet", + "휔": "hwek", + "휕": "hwet", + "휖": "hwep", + "휗": "hwet", + "휘": "hwi", + "휙": "hwik", + "휚": "hwikk", + "휛": "hwik", + "휜": "hwin", + "휝": "hwin", + "휞": "hwin", + "휟": "hwit", + "휠": "hwil", + "휡": "hwik", + "휢": "hwim", + "휣": "hwip", + "휤": "hwit", + "휥": "hwit", + "휦": "hwip", + "휧": "hwil", + "휨": "hwim", + "휩": "hwip", + "휪": "hwip", + "휫": "hwit", + "휬": "hwit", + "휭": "hwing", + "휮": "hwit", + "휯": "hwit", + "휰": "hwik", + "휱": "hwit", + "휲": "hwip", + "휳": "hwit", + "휴": "hyu", + "휵": "hyuk", + "휶": "hyukk", + "휷": "hyuk", + "휸": "hyun", + "휹": "hyun", + "휺": "hyun", + "휻": "hyut", + "휼": "hyul", + "휽": "hyuk", + "휾": "hyum", + "휿": "hyup", + "흀": "hyut", + "흁": "hyut", + "흂": "hyup", + "흃": "hyul", + "흄": "hyum", + "흅": "hyup", + "흆": "hyup", + "흇": "hyut", + "흈": "hyut", + "흉": "hyung", + "흊": "hyut", + "흋": "hyut", + "흌": "hyuk", + "흍": "hyut", + "흎": "hyup", + "흏": "hyut", + "흐": "heu", + "흑": "heuk", + "흒": "heukk", + "흓": "heuk", + "흔": "heun", + "흕": "heun", + "흖": "heun", + "흗": "heut", + "흘": "heul", + "흙": "heuk", + "흚": "heum", + "흛": "heup", + "흜": "heut", + "흝": "heut", + "흞": "heup", + "흟": "heul", + "흠": "heum", + "흡": "heup", + "흢": "heup", + "흣": "heut", + "흤": "heut", + "흥": "heung", + "흦": "heut", + "흧": "heut", + "흨": "heuk", + "흩": "heut", + "흪": "heup", + "흫": "heut", + "희": "heui", + "흭": "heuik", + "흮": "heuikk", + "흯": "heuik", + "흰": "heuin", + "흱": "heuin", + "흲": "heuin", + "흳": "heuit", + "흴": "heuil", + "흵": "heuik", + "흶": "heuim", + "흷": "heuip", + "흸": "heuit", + "흹": "heuit", + "흺": "heuip", + "흻": "heuil", + "흼": "heuim", + "흽": "heuip", + "흾": "heuip", + "흿": "heuit", + "힀": "heuit", + "힁": "heuing", + "힂": "heuit", + "힃": "heuit", + "힄": "heuik", + "힅": "heuit", + "힆": "heuip", + "힇": "heuit", + "히": "hi", + "힉": "hik", + "힊": "hikk", + "힋": "hik", + "힌": "hin", + "힍": "hin", + "힎": "hin", + "힏": "hit", + "힐": "hil", + "힑": "hik", + "힒": "him", + "힓": "hip", + "힔": "hit", + "힕": "hit", + "힖": "hip", + "힗": "hil", + "힘": "him", + "힙": "hip", + "힚": "hip", + "힛": "hit", + "힜": "hit", + "힝": "hing", + "힞": "hit", + "힟": "hit", + "힠": "hik", + "힡": "hit", + "힢": "hip", + "힣": "hit" +} \ No newline at end of file diff --git a/kirby/i18n/rules/lt.json b/kirby/i18n/rules/lt.json new file mode 100644 index 0000000..23e0d70 --- /dev/null +++ b/kirby/i18n/rules/lt.json @@ -0,0 +1,20 @@ +{ + "Ą": "A", + "Č": "C", + "Ę": "E", + "Ė": "E", + "Į": "I", + "Š": "S", + "Ų": "U", + "Ū": "U", + "Ž": "Z", + "ą": "a", + "č": "c", + "ę": "e", + "ė": "e", + "į": "i", + "š": "s", + "ų": "u", + "ū": "u", + "ž": "z" +} diff --git a/kirby/i18n/rules/lv.json b/kirby/i18n/rules/lv.json new file mode 100644 index 0000000..d5b0010 --- /dev/null +++ b/kirby/i18n/rules/lv.json @@ -0,0 +1,18 @@ +{ + "Ā": "A", + "Ē": "E", + "Ģ": "G", + "Ī": "I", + "Ķ": "K", + "Ļ": "L", + "Ņ": "N", + "Ū": "U", + "ā": "a", + "ē": "e", + "ģ": "g", + "ī": "i", + "ķ": "k", + "ļ": "l", + "ņ": "n", + "ū": "u" +} diff --git a/kirby/i18n/rules/mk.json b/kirby/i18n/rules/mk.json new file mode 100644 index 0000000..7a87f46 --- /dev/null +++ b/kirby/i18n/rules/mk.json @@ -0,0 +1,64 @@ +{ + "А": "A", + "Б": "B", + "В": "V", + "Г": "G", + "Д": "D", + "Ѓ": "Gj", + "Е": "E", + "Ж": "Zh", + "З": "Z", + "Ѕ": "Dz", + "И": "I", + "Ј": "J", + "К": "K", + "Л": "L", + "Љ": "Lj", + "М": "M", + "Н": "N", + "Њ": "Nj", + "О": "O", + "П": "P", + "Р": "R", + "С": "S", + "Т": "T", + "Ќ": "Kj", + "У": "U", + "Ф": "F", + "Х": "H", + "Ц": "C", + "Ч": "Ch", + "Џ": "Dj", + "Ш": "Sh", + "а": "a", + "б": "b", + "в": "v", + "г": "g", + "д": "d", + "ѓ": "gj", + "е": "e", + "ж": "zh", + "з": "z", + "ѕ": "dz", + "и": "i", + "ј": "j", + "к": "k", + "л": "l", + "љ": "lj", + "м": "m", + "н": "n", + "њ": "nj", + "о": "o", + "п": "p", + "р": "r", + "с": "s", + "т": "t", + "ќ": "kj", + "у": "u", + "ф": "f", + "х": "h", + "ц": "c", + "ч": "ch", + "џ": "dj", + "ш": "sh" +} diff --git a/kirby/i18n/rules/my.json b/kirby/i18n/rules/my.json new file mode 100644 index 0000000..08f5a0a --- /dev/null +++ b/kirby/i18n/rules/my.json @@ -0,0 +1,121 @@ +{ + "က": "k", + "ခ": "kh", + "ဂ": "g", + "ဃ": "ga", + "င": "ng", + "စ": "s", + "ဆ": "sa", + "ဇ": "z", + "စျ" : "za", + "ည": "ny", + "ဋ": "t", + "ဌ": "ta", + "ဍ": "d", + "ဎ": "da", + "ဏ": "na", + "တ": "t", + "ထ": "ta", + "ဒ": "d", + "ဓ": "da", + "န": "n", + "ပ": "p", + "ဖ": "pa", + "ဗ": "b", + "ဘ": "ba", + "မ": "m", + "ယ": "y", + "ရ": "ya", + "လ": "l", + "ဝ": "w", + "သ": "th", + "ဟ": "h", + "ဠ": "la", + "အ": "a", + + "ြ": "y", + "ျ": "ya", + "ွ": "w", + "ြွ": "yw", + "ျွ": "ywa", + "ှ": "h", + + "ဧ": "e", + "၏": "-e", + "ဣ": "i", + "ဤ": "-i", + "ဉ": "u", + "ဦ": "-u", + "ဩ": "aw", + "သြော" : "aw", + "ဪ": "aw", + "၍": "ywae", + "၌": "hnaik", + + "၀": "0", + "၁": "1", + "၂": "2", + "၃": "3", + "၄": "4", + "၅": "5", + "၆": "6", + "၇": "7", + "၈": "8", + "၉": "9", + + "္": "", + "့": "", + "း": "", + + "ာ": "a", + "ါ": "a", + "ေ": "e", + "ဲ": "e", + "ိ": "i", + "ီ": "i", + "ို": "o", + "ု": "u", + "ူ": "u", + "ေါင်": "aung", + "ော": "aw", + "ော်": "aw", + "ေါ": "aw", + "ေါ်": "aw", + "်": "at", + "က်": "et", + "ိုက်" : "aik", + "ောက်" : "auk", + "င်" : "in", + "ိုင်" : "aing", + "ောင်" : "aung", + "စ်" : "it", + "ည်" : "i", + "တ်" : "at", + "ိတ်" : "eik", + "ုတ်" : "ok", + "ွတ်" : "ut", + "ေတ်" : "it", + "ဒ်" : "d", + "ိုဒ်" : "ok", + "ုဒ်" : "ait", + "န်" : "an", + "ာန်" : "an", + "ိန်" : "ein", + "ုန်" : "on", + "ွန်" : "un", + "ပ်" : "at", + "ိပ်" : "eik", + "ုပ်" : "ok", + "ွပ်" : "ut", + "န်ုပ်" : "nub", + "မ်" : "an", + "ိမ်" : "ein", + "ုမ်" : "on", + "ွမ်" : "un", + "ယ်" : "e", + "ိုလ်" : "ol", + "ဉ်" : "in", + "ံ": "an", + "ိံ" : "ein", + "ုံ" : "on" +} diff --git a/kirby/i18n/rules/nb.json b/kirby/i18n/rules/nb.json new file mode 100644 index 0000000..66000ba --- /dev/null +++ b/kirby/i18n/rules/nb.json @@ -0,0 +1,8 @@ +{ + "Æ": "AE", + "Ø": "OE", + "Å": "AA", + "æ": "ae", + "ø": "oe", + "å": "aa" +} diff --git a/kirby/i18n/rules/pl.json b/kirby/i18n/rules/pl.json new file mode 100644 index 0000000..5d0c123 --- /dev/null +++ b/kirby/i18n/rules/pl.json @@ -0,0 +1,20 @@ +{ + "Ą": "A", + "Ć": "C", + "Ę": "E", + "Ł": "L", + "Ń": "N", + "Ó": "O", + "Ś": "S", + "Ź": "Z", + "Ż": "Z", + "ą": "a", + "ć": "c", + "ę": "e", + "ł": "l", + "ń": "n", + "ó": "o", + "ś": "s", + "ź": "z", + "ż": "z" +} diff --git a/kirby/i18n/rules/pt_BR.json b/kirby/i18n/rules/pt_BR.json new file mode 100644 index 0000000..39bca6c --- /dev/null +++ b/kirby/i18n/rules/pt_BR.json @@ -0,0 +1,187 @@ + +{ + "°": "0", + "¹": "1", + "²": "2", + "³": "3", + "⁴": "4", + "⁵": "5", + "⁶": "6", + "⁷": "7", + "⁸": "8", + "⁹": "9", + + "₀": "0", + "₁": "1", + "₂": "2", + "₃": "3", + "₄": "4", + "₅": "5", + "₆": "6", + "₇": "7", + "₈": "8", + "₉": "9", + + + "æ": "ae", + "ǽ": "ae", + "À": "A", + "Á": "A", + "Â": "A", + "Ã": "A", + "Å": "AA", + "Ǻ": "A", + "Ă": "A", + "Ǎ": "A", + "Æ": "AE", + "Ǽ": "AE", + "à": "a", + "á": "a", + "â": "a", + "ã": "a", + "å": "aa", + "ǻ": "a", + "ă": "a", + "ǎ": "a", + "ª": "a", + "@": "at", + "Ĉ": "C", + "Ċ": "C", + "Ç": "Ç", + "ç": "ç", + "ĉ": "c", + "ċ": "c", + "©": "c", + "Ð": "Dj", + "Đ": "D", + "ð": "dj", + "đ": "d", + "È": "E", + "É": "E", + "Ê": "E", + "Ë": "E", + "Ĕ": "E", + "Ė": "E", + "è": "e", + "é": "é", + "ê": "e", + "ë": "e", + "ĕ": "e", + "ė": "e", + "ƒ": "f", + "Ĝ": "G", + "Ġ": "G", + "ĝ": "g", + "ġ": "g", + "Ĥ": "H", + "Ħ": "H", + "ĥ": "h", + "ħ": "h", + "Ì": "I", + "Í": "I", + "Î": "I", + "Ï": "I", + "Ĩ": "I", + "Ĭ": "I", + "Ǐ": "I", + "Į": "I", + "IJ": "IJ", + "ì": "i", + "í": "i", + "î": "i", + "ï": "i", + "ĩ": "i", + "ĭ": "i", + "ǐ": "i", + "į": "i", + "ij": "ij", + "Ĵ": "J", + "ĵ": "j", + "Ĺ": "L", + "Ľ": "L", + "Ŀ": "L", + "ĺ": "l", + "ľ": "l", + "ŀ": "l", + "Ñ": "N", + "ñ": "n", + "ʼn": "n", + "Ò": "O", + "Ó": "O", + "Ô": "O", + "Õ": "O", + "Ō": "O", + "Ŏ": "O", + "Ǒ": "O", + "Ő": "O", + "Ơ": "O", + "Ø": "OE", + "Ǿ": "O", + "Œ": "OE", + "ò": "o", + "ó": "o", + "ô": "o", + "õ": "o", + "ō": "o", + "ŏ": "o", + "ǒ": "o", + "ő": "o", + "ơ": "o", + "ø": "oe", + "ǿ": "o", + "º": "o", + "œ": "oe", + "Ŕ": "R", + "Ŗ": "R", + "ŕ": "r", + "ŗ": "r", + "Ŝ": "S", + "Ș": "S", + "ŝ": "s", + "ș": "s", + "ſ": "s", + "Ţ": "T", + "Ț": "T", + "Ŧ": "T", + "Þ": "TH", + "ţ": "t", + "ț": "t", + "ŧ": "t", + "þ": "th", + "Ù": "U", + "Ú": "U", + "Û": "U", + "Ü": "U", + "Ũ": "U", + "Ŭ": "U", + "Ű": "U", + "Ų": "U", + "Ư": "U", + "Ǔ": "U", + "Ǖ": "U", + "Ǘ": "U", + "Ǚ": "U", + "Ǜ": "U", + "ù": "u", + "ú": "u", + "û": "u", + "ü": "u", + "ũ": "u", + "ŭ": "u", + "ű": "u", + "ų": "u", + "ư": "u", + "ǔ": "u", + "ǖ": "u", + "ǘ": "u", + "ǚ": "u", + "ǜ": "u", + "Ŵ": "W", + "ŵ": "w", + "Ý": "Y", + "Ÿ": "Y", + "Ŷ": "Y", + "ý": "y", + "ÿ": "y", + "ŷ": "y" +} diff --git a/kirby/i18n/rules/rm.json b/kirby/i18n/rules/rm.json new file mode 100644 index 0000000..47b9d9b --- /dev/null +++ b/kirby/i18n/rules/rm.json @@ -0,0 +1,16 @@ +{ + "ă": "a", + "î": "i", + "â": "a", + "ş": "s", + "ș": "s", + "ţ": "t", + "ț": "t", + "Ă": "A", + "Î": "I", + "Â": "A", + "Ş": "S", + "Ș": "S", + "Ţ": "T", + "Ț": "T" +} diff --git a/kirby/i18n/rules/ru.json b/kirby/i18n/rules/ru.json new file mode 100644 index 0000000..b8b354c --- /dev/null +++ b/kirby/i18n/rules/ru.json @@ -0,0 +1,68 @@ +{ + "Ъ": "", + "Ь": "", + "А": "A", + "Б": "B", + "Ц": "C", + "Ч": "Ch", + "Д": "D", + "Е": "E", + "Ё": "E", + "Э": "E", + "Ф": "F", + "Г": "G", + "Х": "H", + "И": "I", + "Й": "Y", + "Я": "Ya", + "Ю": "Yu", + "К": "K", + "Л": "L", + "М": "M", + "Н": "N", + "О": "O", + "П": "P", + "Р": "R", + "С": "S", + "Ш": "Sh", + "Щ": "Shch", + "Т": "T", + "У": "U", + "В": "V", + "Ы": "Y", + "З": "Z", + "Ж": "Zh", + "ъ": "", + "ь": "", + "а": "a", + "б": "b", + "ц": "c", + "ч": "ch", + "д": "d", + "е": "e", + "ё": "e", + "э": "e", + "ф": "f", + "г": "g", + "х": "h", + "и": "i", + "й": "y", + "я": "ya", + "ю": "yu", + "к": "k", + "л": "l", + "м": "m", + "н": "n", + "о": "o", + "п": "p", + "р": "r", + "с": "s", + "ш": "sh", + "щ": "shch", + "т": "t", + "у": "u", + "в": "v", + "ы": "y", + "з": "z", + "ж": "zh" +} diff --git a/kirby/i18n/rules/sr.json b/kirby/i18n/rules/sr.json new file mode 100644 index 0000000..f4c11db --- /dev/null +++ b/kirby/i18n/rules/sr.json @@ -0,0 +1,72 @@ +{ + "а": "a", + "б": "b", + "в": "v", + "г": "g", + "д": "d", + "ђ": "dj", + "е": "e", + "ж": "z", + "з": "z", + "и": "i", + "ј": "j", + "к": "k", + "л": "l", + "љ": "lj", + "м": "m", + "н": "n", + "њ": "nj", + "о": "o", + "п": "p", + "р": "r", + "с": "s", + "т": "t", + "ћ": "c", + "у": "u", + "ф": "f", + "х": "h", + "ц": "c", + "ч": "c", + "џ": "dz", + "ш": "s", + "А": "A", + "Б": "B", + "В": "V", + "Г": "G", + "Д": "D", + "Ђ": "Dj", + "Е": "E", + "Ж": "Z", + "З": "Z", + "И": "I", + "Ј": "J", + "К": "K", + "Л": "L", + "Љ": "Lj", + "М": "M", + "Н": "N", + "Њ": "Nj", + "О": "O", + "П": "P", + "Р": "R", + "С": "S", + "Т": "T", + "Ћ": "C", + "У": "U", + "Ф": "F", + "Х": "H", + "Ц": "C", + "Ч": "C", + "Џ": "Dz", + "Ш": "S", + "š": "s", + "đ": "dj", + "ž": "z", + "ć": "c", + "č": "c", + "Š": "S", + "Đ": "DJ", + "Ž": "Z", + "Ć": "C", + "Č": "C" +} \ No newline at end of file diff --git a/kirby/i18n/rules/sv_SE.json b/kirby/i18n/rules/sv_SE.json new file mode 100644 index 0000000..a22f3eb --- /dev/null +++ b/kirby/i18n/rules/sv_SE.json @@ -0,0 +1,8 @@ +{ + "Ä": "A", + "Å": "a", + "Ö": "O", + "ä": "a", + "å": "a", + "ö": "o" +} diff --git a/kirby/i18n/rules/tr.json b/kirby/i18n/rules/tr.json new file mode 100644 index 0000000..07fbae5 --- /dev/null +++ b/kirby/i18n/rules/tr.json @@ -0,0 +1,14 @@ +{ + "Ç": "C", + "Ğ": "G", + "İ": "I", + "Ş": "S", + "Ö": "O", + "Ü": "U", + "ç": "c", + "ğ": "g", + "ı": "i", + "ş": "s", + "ö": "o", + "ü": "u" +} diff --git a/kirby/i18n/rules/uk.json b/kirby/i18n/rules/uk.json new file mode 100644 index 0000000..673b7ed --- /dev/null +++ b/kirby/i18n/rules/uk.json @@ -0,0 +1,10 @@ +{ + "Ґ": "G", + "І": "I", + "Ї": "Ji", + "Є": "Ye", + "ґ": "g", + "і": "i", + "ї": "ji", + "є": "ye" +} diff --git a/kirby/i18n/rules/vi.json b/kirby/i18n/rules/vi.json new file mode 100644 index 0000000..fdeff69 --- /dev/null +++ b/kirby/i18n/rules/vi.json @@ -0,0 +1,135 @@ +{ + "à": "a", + "ạ": "a", + "á": "a", + "ả": "a", + "ã": "a", + "â": "a", + "ầ": "a", + "ấ": "a", + "ậ": "a", + "ẩ": "a", + "ẫ": "a", + "ă": "a", + "ằ": "a", + "ắ": "a", + "ặ": "a", + "ẳ": "a", + "ẵ": "a", + "è": "e", + "é": "e", + "ẹ": "e", + "ẻ": "e", + "ẽ": "e", + "ê": "e", + "ề": "e", + "ế": "e", + "ệ": "e", + "ể": "e", + "ễ": "e", + "ì": "i", + "í": "i", + "ị": "i", + "ỉ": "i", + "ĩ": "i", + "ò": "o", + "ó": "o", + "ọ": "o", + "ỏ": "o", + "õ": "o", + "ô": "o", + "ồ": "o", + "ố": "o", + "ộ": "o", + "ổ": "o", + "ỗ": "o", + "ơ": "o", + "ờ": "o", + "ớ": "o", + "ợ": "o", + "ở": "o", + "ỡ": "o", + "ù": "u", + "ú": "u", + "ụ": "u", + "ủ": "u", + "ũ": "u", + "ư": "u", + "ừ": "u", + "ứ": "u", + "ự": "u", + "ử": "u", + "ữ": "u", + "y": "y", + "ỳ": "y", + "ý": "y", + "ỵ": "y", + "ỷ": "y", + "ỹ": "y", + "À": "A", + "Á": "A", + "Ạ": "A", + "Ả": "A", + "Ã": "A", + "Â": "A", + "Ầ": "A", + "Ấ": "A", + "Ậ": "A", + "Ẩ": "A", + "Ẫ": "A", + "Ă": "A", + "Ằ": "A", + "Ắ": "A", + "Ặ": "A", + "Ẳ": "A", + "Ẵ": "A", + "È": "E", + "É": "E", + "Ẹ": "E", + "Ẻ": "E", + "Ẽ": "E", + "Ê": "E", + "Ề": "E", + "Ế": "E", + "Ệ": "E", + "Ể": "E", + "Ễ": "E", + "Ì": "I", + "Í": "I", + "Ị": "I", + "Ỉ": "I", + "Ĩ": "I", + "Ò": "O", + "Ó": "O", + "Ọ": "O", + "Ỏ": "O", + "Õ": "O", + "Ô": "O", + "Ồ": "O", + "Ố": "O", + "Ộ": "O", + "Ổ": "O", + "Ỗ": "O", + "Ơ": "O", + "Ờ": "O", + "Ớ": "O", + "Ợ": "O", + "Ở": "O", + "Ỡ": "O", + "Ù": "U", + "Ụ": "U", + "Ủ": "U", + "Ũ": "U", + "Ư": "U", + "Ừ": "U", + "Ứ": "U", + "Ự": "U", + "Ử": "U", + "Ữ": "U", + "Y": "Y", + "Ỳ": "Y", + "Ý": "Y", + "Ỵ": "Y", + "Ỷ": "Y", + "Ỹ": "Y" +} diff --git a/kirby/i18n/rules/zh.json b/kirby/i18n/rules/zh.json new file mode 100644 index 0000000..21ec594 --- /dev/null +++ b/kirby/i18n/rules/zh.json @@ -0,0 +1,6937 @@ +{ + "腌" : "yan", + "嗄" : "a", + "迫" : "po", + "捱" : "ai", + "艾" : "ai", + "瑷" : "ai", + "嗌" : "ai", + "犴" : "an", + "鳌" : "ao", + "廒" : "ao", + "拗" : "niu", + "岙" : "ao", + "鏊" : "ao", + "扒" : "ba", + "岜" : "ba", + "耙" : "pa", + "鲅" : "ba", + "癍" : "ban", + "膀" : "pang", + "磅" : "bang", + "炮" : "pao", + "曝" : "pu", + "刨" : "pao", + "瀑" : "pu", + "陂" : "bei", + "埤" : "pi", + "鹎" : "bei", + "邶" : "bei", + "孛" : "bei", + "鐾" : "bei", + "鞴" : "bei", + "畚" : "ben", + "甏" : "beng", + "舭" : "bi", + "秘" : "mi", + "辟" : "pi", + "泌" : "mi", + "裨" : "bi", + "濞" : "bi", + "庳" : "bi", + "嬖" : "bi", + "畀" : "bi", + "筚" : "bi", + "箅" : "bi", + "襞" : "bi", + "跸" : "bi", + "笾" : "bian", + "扁" : "bian", + "碥" : "bian", + "窆" : "bian", + "便" : "bian", + "弁" : "bian", + "缏" : "bian", + "骠" : "biao", + "杓" : "shao", + "飚" : "biao", + "飑" : "biao", + "瘭" : "biao", + "髟" : "biao", + "玢" : "bin", + "豳" : "bin", + "镔" : "bin", + "膑" : "bin", + "屏" : "ping", + "泊" : "bo", + "逋" : "bu", + "晡" : "bu", + "钸" : "bu", + "醭" : "bu", + "埔" : "pu", + "瓿" : "bu", + "礤" : "ca", + "骖" : "can", + "藏" : "cang", + "艚" : "cao", + "侧" : "ce", + "喳" : "zha", + "刹" : "sha", + "瘥" : "chai", + "禅" : "chan", + "廛" : "chan", + "镡" : "tan", + "澶" : "chan", + "躔" : "chan", + "阊" : "chang", + "鲳" : "chang", + "长" : "chang", + "苌" : "chang", + "氅" : "chang", + "鬯" : "chang", + "焯" : "chao", + "朝" : "chao", + "车" : "che", + "琛" : "chen", + "谶" : "chen", + "榇" : "chen", + "蛏" : "cheng", + "埕" : "cheng", + "枨" : "cheng", + "塍" : "cheng", + "裎" : "cheng", + "螭" : "chi", + "眵" : "chi", + "墀" : "chi", + "篪" : "chi", + "坻" : "di", + "瘛" : "chi", + "种" : "zhong", + "重" : "zhong", + "仇" : "chou", + "帱" : "chou", + "俦" : "chou", + "雠" : "chou", + "臭" : "chou", + "楮" : "chu", + "畜" : "chu", + "嘬" : "zuo", + "膪" : "chuai", + "巛" : "chuan", + "椎" : "zhui", + "呲" : "ci", + "兹" : "zi", + "伺" : "si", + "璁" : "cong", + "楱" : "cou", + "攒" : "zan", + "爨" : "cuan", + "隹" : "zhui", + "榱" : "cui", + "撮" : "cuo", + "鹾" : "cuo", + "嗒" : "da", + "哒" : "da", + "沓" : "ta", + "骀" : "tai", + "绐" : "dai", + "埭" : "dai", + "甙" : "dai", + "弹" : "dan", + "澹" : "dan", + "叨" : "dao", + "纛" : "dao", + "簦" : "deng", + "提" : "ti", + "翟" : "zhai", + "绨" : "ti", + "丶" : "dian", + "佃" : "dian", + "簟" : "dian", + "癜" : "dian", + "调" : "tiao", + "铞" : "diao", + "佚" : "yi", + "堞" : "die", + "瓞" : "die", + "揲" : "die", + "垤" : "die", + "疔" : "ding", + "岽" : "dong", + "硐" : "dong", + "恫" : "dong", + "垌" : "dong", + "峒" : "dong", + "芏" : "du", + "煅" : "duan", + "碓" : "dui", + "镦" : "dui", + "囤" : "tun", + "铎" : "duo", + "缍" : "duo", + "驮" : "tuo", + "沲" : "tuo", + "柁" : "tuo", + "哦" : "o", + "恶" : "e", + "轭" : "e", + "锷" : "e", + "鹗" : "e", + "阏" : "e", + "诶" : "ea", + "鲕" : "er", + "珥" : "er", + "佴" : "er", + "番" : "fan", + "彷" : "pang", + "霏" : "fei", + "蜚" : "fei", + "鲱" : "fei", + "芾" : "fei", + "瀵" : "fen", + "鲼" : "fen", + "否" : "fou", + "趺" : "fu", + "桴" : "fu", + "莩" : "fu", + "菔" : "fu", + "幞" : "fu", + "郛" : "fu", + "绂" : "fu", + "绋" : "fu", + "祓" : "fu", + "砩" : "fu", + "黻" : "fu", + "罘" : "fu", + "蚨" : "fu", + "脯" : "pu", + "滏" : "fu", + "黼" : "fu", + "鲋" : "fu", + "鳆" : "fu", + "咖" : "ka", + "噶" : "ga", + "轧" : "zha", + "陔" : "gai", + "戤" : "gai", + "扛" : "kang", + "戆" : "gang", + "筻" : "gang", + "槔" : "gao", + "藁" : "gao", + "缟" : "gao", + "咯" : "ge", + "仡" : "yi", + "搿" : "ge", + "塥" : "ge", + "鬲" : "ge", + "哿" : "ge", + "句" : "ju", + "缑" : "gou", + "鞲" : "gou", + "笱" : "gou", + "遘" : "gou", + "瞽" : "gu", + "罟" : "gu", + "嘏" : "gu", + "牿" : "gu", + "鲴" : "gu", + "栝" : "kuo", + "莞" : "guan", + "纶" : "lun", + "涫" : "guan", + "涡" : "wo", + "呙" : "guo", + "馘" : "guo", + "猓" : "guo", + "咳" : "ke", + "氦" : "hai", + "颔" : "han", + "吭" : "keng", + "颃" : "hang", + "巷" : "xiang", + "蚵" : "ke", + "翮" : "he", + "吓" : "xia", + "桁" : "heng", + "泓" : "hong", + "蕻" : "hong", + "黉" : "hong", + "後" : "hou", + "唿" : "hu", + "煳" : "hu", + "浒" : "hu", + "祜" : "hu", + "岵" : "hu", + "鬟" : "huan", + "圜" : "huan", + "郇" : "xun", + "锾" : "huan", + "逭" : "huan", + "咴" : "hui", + "虺" : "hui", + "会" : "hui", + "溃" : "kui", + "哕" : "hui", + "缋" : "hui", + "锪" : "huo", + "蠖" : "huo", + "缉" : "ji", + "稽" : "ji", + "赍" : "ji", + "丌" : "ji", + "咭" : "ji", + "亟" : "ji", + "殛" : "ji", + "戢" : "ji", + "嵴" : "ji", + "蕺" : "ji", + "系" : "xi", + "蓟" : "ji", + "霁" : "ji", + "荠" : "qi", + "跽" : "ji", + "哜" : "ji", + "鲚" : "ji", + "洎" : "ji", + "芰" : "ji", + "茄" : "qie", + "珈" : "jia", + "迦" : "jia", + "笳" : "jia", + "葭" : "jia", + "跏" : "jia", + "郏" : "jia", + "恝" : "jia", + "铗" : "jia", + "袷" : "qia", + "蛱" : "jia", + "角" : "jiao", + "挢" : "jiao", + "岬" : "jia", + "徼" : "jiao", + "湫" : "qiu", + "敫" : "jiao", + "瘕" : "jia", + "浅" : "qian", + "蒹" : "jian", + "搛" : "jian", + "湔" : "jian", + "缣" : "jian", + "犍" : "jian", + "鹣" : "jian", + "鲣" : "jian", + "鞯" : "jian", + "蹇" : "jian", + "謇" : "jian", + "硷" : "jian", + "枧" : "jian", + "戬" : "jian", + "谫" : "jian", + "囝" : "jian", + "裥" : "jian", + "笕" : "jian", + "翦" : "jian", + "趼" : "jian", + "楗" : "jian", + "牮" : "jian", + "踺" : "jian", + "茳" : "jiang", + "礓" : "jiang", + "耩" : "jiang", + "降" : "jiang", + "绛" : "jiang", + "洚" : "jiang", + "鲛" : "jiao", + "僬" : "jiao", + "鹪" : "jiao", + "艽" : "jiao", + "茭" : "jiao", + "嚼" : "jiao", + "峤" : "qiao", + "觉" : "jiao", + "校" : "xiao", + "噍" : "jiao", + "醮" : "jiao", + "疖" : "jie", + "喈" : "jie", + "桔" : "ju", + "拮" : "jie", + "桀" : "jie", + "颉" : "jie", + "婕" : "jie", + "羯" : "jie", + "鲒" : "jie", + "蚧" : "jie", + "骱" : "jie", + "衿" : "jin", + "馑" : "jin", + "卺" : "jin", + "廑" : "jin", + "堇" : "jin", + "槿" : "jin", + "靳" : "jin", + "缙" : "jin", + "荩" : "jin", + "赆" : "jin", + "妗" : "jin", + "旌" : "jing", + "腈" : "jing", + "憬" : "jing", + "肼" : "jing", + "迳" : "jing", + "胫" : "jing", + "弪" : "jing", + "獍" : "jing", + "扃" : "jiong", + "鬏" : "jiu", + "疚" : "jiu", + "僦" : "jiu", + "桕" : "jiu", + "疽" : "ju", + "裾" : "ju", + "苴" : "ju", + "椐" : "ju", + "锔" : "ju", + "琚" : "ju", + "鞫" : "ju", + "踽" : "ju", + "榉" : "ju", + "莒" : "ju", + "遽" : "ju", + "倨" : "ju", + "钜" : "ju", + "犋" : "ju", + "屦" : "ju", + "榘" : "ju", + "窭" : "ju", + "讵" : "ju", + "醵" : "ju", + "苣" : "ju", + "圈" : "quan", + "镌" : "juan", + "蠲" : "juan", + "锩" : "juan", + "狷" : "juan", + "桊" : "juan", + "鄄" : "juan", + "獗" : "jue", + "攫" : "jue", + "孓" : "jue", + "橛" : "jue", + "珏" : "jue", + "桷" : "jue", + "劂" : "jue", + "爝" : "jue", + "镢" : "jue", + "觖" : "jue", + "筠" : "jun", + "麇" : "jun", + "捃" : "jun", + "浚" : "jun", + "喀" : "ka", + "卡" : "ka", + "佧" : "ka", + "胩" : "ka", + "锎" : "kai", + "蒈" : "kai", + "剀" : "kai", + "垲" : "kai", + "锴" : "kai", + "戡" : "kan", + "莰" : "kan", + "闶" : "kang", + "钪" : "kang", + "尻" : "kao", + "栲" : "kao", + "柯" : "ke", + "疴" : "ke", + "钶" : "ke", + "颏" : "ke", + "珂" : "ke", + "髁" : "ke", + "壳" : "ke", + "岢" : "ke", + "溘" : "ke", + "骒" : "ke", + "缂" : "ke", + "氪" : "ke", + "锞" : "ke", + "裉" : "ken", + "倥" : "kong", + "崆" : "kong", + "箜" : "kong", + "芤" : "kou", + "眍" : "kou", + "筘" : "kou", + "刳" : "ku", + "堀" : "ku", + "喾" : "ku", + "侉" : "kua", + "蒯" : "kuai", + "哙" : "kuai", + "狯" : "kuai", + "郐" : "kuai", + "匡" : "kuang", + "夼" : "kuang", + "邝" : "kuang", + "圹" : "kuang", + "纩" : "kuang", + "贶" : "kuang", + "岿" : "kui", + "悝" : "kui", + "睽" : "kui", + "逵" : "kui", + "馗" : "kui", + "夔" : "kui", + "喹" : "kui", + "隗" : "wei", + "暌" : "kui", + "揆" : "kui", + "蝰" : "kui", + "跬" : "kui", + "喟" : "kui", + "聩" : "kui", + "篑" : "kui", + "蒉" : "kui", + "愦" : "kui", + "锟" : "kun", + "醌" : "kun", + "琨" : "kun", + "髡" : "kun", + "悃" : "kun", + "阃" : "kun", + "蛞" : "kuo", + "砬" : "la", + "落" : "luo", + "剌" : "la", + "瘌" : "la", + "涞" : "lai", + "崃" : "lai", + "铼" : "lai", + "赉" : "lai", + "濑" : "lai", + "斓" : "lan", + "镧" : "lan", + "谰" : "lan", + "漤" : "lan", + "罱" : "lan", + "稂" : "lang", + "阆" : "lang", + "莨" : "liang", + "蒗" : "lang", + "铹" : "lao", + "痨" : "lao", + "醪" : "lao", + "栳" : "lao", + "铑" : "lao", + "耢" : "lao", + "勒" : "le", + "仂" : "le", + "叻" : "le", + "泐" : "le", + "鳓" : "le", + "了" : "le", + "镭" : "lei", + "嫘" : "lei", + "缧" : "lei", + "檑" : "lei", + "诔" : "lei", + "耒" : "lei", + "酹" : "lei", + "塄" : "leng", + "愣" : "leng", + "藜" : "li", + "骊" : "li", + "黧" : "li", + "缡" : "li", + "嫠" : "li", + "鲡" : "li", + "蓠" : "li", + "澧" : "li", + "锂" : "li", + "醴" : "li", + "鳢" : "li", + "俪" : "li", + "砺" : "li", + "郦" : "li", + "詈" : "li", + "猁" : "li", + "溧" : "li", + "栎" : "li", + "轹" : "li", + "傈" : "li", + "坜" : "li", + "苈" : "li", + "疠" : "li", + "疬" : "li", + "篥" : "li", + "粝" : "li", + "跞" : "li", + "俩" : "liang", + "裢" : "lian", + "濂" : "lian", + "臁" : "lian", + "奁" : "lian", + "蠊" : "lian", + "琏" : "lian", + "蔹" : "lian", + "裣" : "lian", + "楝" : "lian", + "潋" : "lian", + "椋" : "liang", + "墚" : "liang", + "寮" : "liao", + "鹩" : "liao", + "蓼" : "liao", + "钌" : "liao", + "廖" : "liao", + "尥" : "liao", + "洌" : "lie", + "捩" : "lie", + "埒" : "lie", + "躐" : "lie", + "鬣" : "lie", + "辚" : "lin", + "遴" : "lin", + "啉" : "lin", + "瞵" : "lin", + "懔" : "lin", + "廪" : "lin", + "蔺" : "lin", + "膦" : "lin", + "酃" : "ling", + "柃" : "ling", + "鲮" : "ling", + "呤" : "ling", + "镏" : "liu", + "旒" : "liu", + "骝" : "liu", + "鎏" : "liu", + "锍" : "liu", + "碌" : "lu", + "鹨" : "liu", + "茏" : "long", + "栊" : "long", + "泷" : "long", + "砻" : "long", + "癃" : "long", + "垅" : "long", + "偻" : "lou", + "蝼" : "lou", + "蒌" : "lou", + "耧" : "lou", + "嵝" : "lou", + "露" : "lu", + "瘘" : "lou", + "噜" : "lu", + "轳" : "lu", + "垆" : "lu", + "胪" : "lu", + "舻" : "lu", + "栌" : "lu", + "镥" : "lu", + "绿" : "lv", + "辘" : "lu", + "簏" : "lu", + "潞" : "lu", + "辂" : "lu", + "渌" : "lu", + "氇" : "lu", + "捋" : "lv", + "稆" : "lv", + "率" : "lv", + "闾" : "lv", + "栾" : "luan", + "銮" : "luan", + "滦" : "luan", + "娈" : "luan", + "脔" : "luan", + "锊" : "lve", + "猡" : "luo", + "椤" : "luo", + "脶" : "luo", + "镙" : "luo", + "倮" : "luo", + "蠃" : "luo", + "瘰" : "luo", + "珞" : "luo", + "泺" : "luo", + "荦" : "luo", + "雒" : "luo", + "呒" : "mu", + "抹" : "mo", + "唛" : "mai", + "杩" : "ma", + "么" : "me", + "埋" : "mai", + "荬" : "mai", + "脉" : "mai", + "劢" : "mai", + "颟" : "man", + "蔓" : "man", + "鳗" : "man", + "鞔" : "man", + "螨" : "man", + "墁" : "man", + "缦" : "man", + "熳" : "man", + "镘" : "man", + "邙" : "mang", + "硭" : "mang", + "旄" : "mao", + "茆" : "mao", + "峁" : "mao", + "泖" : "mao", + "昴" : "mao", + "耄" : "mao", + "瑁" : "mao", + "懋" : "mao", + "瞀" : "mao", + "麽" : "me", + "没" : "mei", + "嵋" : "mei", + "湄" : "mei", + "猸" : "mei", + "镅" : "mei", + "鹛" : "mei", + "浼" : "mei", + "钔" : "men", + "瞢" : "meng", + "甍" : "meng", + "礞" : "meng", + "艨" : "meng", + "黾" : "mian", + "鳘" : "min", + "溟" : "ming", + "暝" : "ming", + "模" : "mo", + "谟" : "mo", + "嫫" : "mo", + "镆" : "mo", + "瘼" : "mo", + "耱" : "mo", + "貊" : "mo", + "貘" : "mo", + "牟" : "mou", + "鍪" : "mou", + "蛑" : "mou", + "侔" : "mou", + "毪" : "mu", + "坶" : "mu", + "仫" : "mu", + "唔" : "wu", + "那" : "na", + "镎" : "na", + "哪" : "na", + "呢" : "ne", + "肭" : "na", + "艿" : "nai", + "鼐" : "nai", + "萘" : "nai", + "柰" : "nai", + "蝻" : "nan", + "馕" : "nang", + "攮" : "nang", + "曩" : "nang", + "猱" : "nao", + "铙" : "nao", + "硇" : "nao", + "蛲" : "nao", + "垴" : "nao", + "坭" : "ni", + "猊" : "ni", + "铌" : "ni", + "鲵" : "ni", + "祢" : "mi", + "睨" : "ni", + "慝" : "te", + "伲" : "ni", + "鲇" : "nian", + "鲶" : "nian", + "埝" : "nian", + "嬲" : "niao", + "茑" : "niao", + "脲" : "niao", + "啮" : "nie", + "陧" : "nie", + "颞" : "nie", + "臬" : "nie", + "蘖" : "nie", + "甯" : "ning", + "聍" : "ning", + "狃" : "niu", + "侬" : "nong", + "耨" : "nou", + "孥" : "nu", + "胬" : "nu", + "钕" : "nv", + "恧" : "nv", + "褰" : "qian", + "掮" : "qian", + "荨" : "xun", + "钤" : "qian", + "箝" : "qian", + "鬈" : "quan", + "缱" : "qian", + "肷" : "qian", + "纤" : "xian", + "茜" : "qian", + "慊" : "qian", + "椠" : "qian", + "戗" : "qiang", + "镪" : "qiang", + "锖" : "qiang", + "樯" : "qiang", + "嫱" : "qiang", + "雀" : "que", + "缲" : "qiao", + "硗" : "qiao", + "劁" : "qiao", + "樵" : "qiao", + "谯" : "qiao", + "鞒" : "qiao", + "愀" : "qiao", + "鞘" : "qiao", + "郄" : "xi", + "箧" : "qie", + "亲" : "qin", + "覃" : "tan", + "溱" : "qin", + "檎" : "qin", + "锓" : "qin", + "嗪" : "qin", + "螓" : "qin", + "揿" : "qin", + "吣" : "qin", + "圊" : "qing", + "鲭" : "qing", + "檠" : "qing", + "黥" : "qing", + "謦" : "qing", + "苘" : "qing", + "磬" : "qing", + "箐" : "qing", + "綮" : "qi", + "茕" : "qiong", + "邛" : "dao", + "蛩" : "tun", + "筇" : "qiong", + "跫" : "qiong", + "銎" : "qiong", + "楸" : "qiu", + "俅" : "qiu", + "赇" : "qiu", + "逑" : "qiu", + "犰" : "qiu", + "蝤" : "qiu", + "巯" : "qiu", + "鼽" : "qiu", + "糗" : "qiu", + "区" : "qu", + "祛" : "qu", + "麴" : "qu", + "诎" : "qu", + "衢" : "qu", + "癯" : "qu", + "劬" : "qu", + "璩" : "qu", + "氍" : "qu", + "朐" : "qu", + "磲" : "qu", + "鸲" : "qu", + "蕖" : "qu", + "蠼" : "qu", + "蘧" : "qu", + "阒" : "qu", + "颧" : "quan", + "荃" : "quan", + "铨" : "quan", + "辁" : "quan", + "筌" : "quan", + "绻" : "quan", + "畎" : "quan", + "阕" : "que", + "悫" : "que", + "髯" : "ran", + "禳" : "rang", + "穰" : "rang", + "仞" : "ren", + "妊" : "ren", + "轫" : "ren", + "衽" : "ren", + "狨" : "rong", + "肜" : "rong", + "蝾" : "rong", + "嚅" : "ru", + "濡" : "ru", + "薷" : "ru", + "襦" : "ru", + "颥" : "ru", + "洳" : "ru", + "溽" : "ru", + "蓐" : "ru", + "朊" : "ruan", + "蕤" : "rui", + "枘" : "rui", + "箬" : "ruo", + "挲" : "suo", + "脎" : "sa", + "塞" : "sai", + "鳃" : "sai", + "噻" : "sai", + "毵" : "san", + "馓" : "san", + "糁" : "san", + "霰" : "xian", + "磉" : "sang", + "颡" : "sang", + "缫" : "sao", + "鳋" : "sao", + "埽" : "sao", + "瘙" : "sao", + "色" : "se", + "杉" : "shan", + "鲨" : "sha", + "痧" : "sha", + "裟" : "sha", + "铩" : "sha", + "唼" : "sha", + "酾" : "shai", + "栅" : "zha", + "跚" : "shan", + "芟" : "shan", + "埏" : "shan", + "钐" : "shan", + "舢" : "shan", + "剡" : "yan", + "鄯" : "shan", + "疝" : "shan", + "蟮" : "shan", + "墒" : "shang", + "垧" : "shang", + "绱" : "shang", + "蛸" : "shao", + "筲" : "shao", + "苕" : "tiao", + "召" : "zhao", + "劭" : "shao", + "猞" : "she", + "畲" : "she", + "折" : "zhe", + "滠" : "she", + "歙" : "xi", + "厍" : "she", + "莘" : "shen", + "娠" : "shen", + "诜" : "shen", + "什" : "shen", + "谂" : "shen", + "渖" : "shen", + "矧" : "shen", + "胂" : "shen", + "椹" : "shen", + "省" : "sheng", + "眚" : "sheng", + "嵊" : "sheng", + "嘘" : "xu", + "蓍" : "shi", + "鲺" : "shi", + "识" : "shi", + "拾" : "shi", + "埘" : "shi", + "莳" : "shi", + "炻" : "shi", + "鲥" : "shi", + "豕" : "shi", + "似" : "si", + "噬" : "shi", + "贳" : "shi", + "铈" : "shi", + "螫" : "shi", + "筮" : "shi", + "殖" : "zhi", + "熟" : "shu", + "艏" : "shou", + "菽" : "shu", + "摅" : "shu", + "纾" : "shu", + "毹" : "shu", + "疋" : "shu", + "数" : "shu", + "属" : "shu", + "术" : "shu", + "澍" : "shu", + "沭" : "shu", + "丨" : "shu", + "腧" : "shu", + "说" : "shuo", + "妁" : "shuo", + "蒴" : "shuo", + "槊" : "shuo", + "搠" : "shuo", + "鸶" : "si", + "澌" : "si", + "缌" : "si", + "锶" : "si", + "厶" : "si", + "蛳" : "si", + "驷" : "si", + "泗" : "si", + "汜" : "si", + "兕" : "si", + "姒" : "si", + "耜" : "si", + "笥" : "si", + "忪" : "song", + "淞" : "song", + "崧" : "song", + "凇" : "song", + "菘" : "song", + "竦" : "song", + "溲" : "sou", + "飕" : "sou", + "蜩" : "tiao", + "萜" : "tie", + "汀" : "ting", + "葶" : "ting", + "莛" : "ting", + "梃" : "ting", + "佟" : "tong", + "酮" : "tong", + "仝" : "tong", + "茼" : "tong", + "砼" : "tong", + "钭" : "dou", + "酴" : "tu", + "钍" : "tu", + "堍" : "tu", + "抟" : "tuan", + "忒" : "te", + "煺" : "tui", + "暾" : "tun", + "氽" : "tun", + "乇" : "tuo", + "砣" : "tuo", + "沱" : "tuo", + "跎" : "tuo", + "坨" : "tuo", + "橐" : "tuo", + "酡" : "tuo", + "鼍" : "tuo", + "庹" : "tuo", + "拓" : "tuo", + "柝" : "tuo", + "箨" : "tuo", + "腽" : "wa", + "崴" : "wai", + "芄" : "wan", + "畹" : "wan", + "琬" : "wan", + "脘" : "wan", + "菀" : "wan", + "尢" : "you", + "辋" : "wang", + "魍" : "wang", + "逶" : "wei", + "葳" : "wei", + "隈" : "wei", + "惟" : "wei", + "帏" : "wei", + "圩" : "wei", + "囗" : "wei", + "潍" : "wei", + "嵬" : "wei", + "沩" : "wei", + "涠" : "wei", + "尾" : "wei", + "玮" : "wei", + "炜" : "wei", + "韪" : "wei", + "洧" : "wei", + "艉" : "wei", + "鲔" : "wei", + "遗" : "yi", + "尉" : "wei", + "軎" : "wei", + "璺" : "wen", + "阌" : "wen", + "蓊" : "weng", + "蕹" : "weng", + "渥" : "wo", + "硪" : "wo", + "龌" : "wo", + "圬" : "wu", + "吾" : "wu", + "浯" : "wu", + "鼯" : "wu", + "牾" : "wu", + "迕" : "wu", + "庑" : "wu", + "痦" : "wu", + "芴" : "wu", + "杌" : "wu", + "焐" : "wu", + "阢" : "wu", + "婺" : "wu", + "鋈" : "wu", + "樨" : "xi", + "栖" : "qi", + "郗" : "xi", + "蹊" : "qi", + "淅" : "xi", + "熹" : "xi", + "浠" : "xi", + "僖" : "xi", + "穸" : "xi", + "螅" : "xi", + "菥" : "xi", + "舾" : "xi", + "矽" : "xi", + "粞" : "xi", + "硒" : "xi", + "醯" : "xi", + "欷" : "xi", + "鼷" : "xi", + "檄" : "xi", + "隰" : "xi", + "觋" : "xi", + "屣" : "xi", + "葸" : "xi", + "蓰" : "xi", + "铣" : "xi", + "饩" : "xi", + "阋" : "xi", + "禊" : "xi", + "舄" : "xi", + "狎" : "xia", + "硖" : "xia", + "柙" : "xia", + "暹" : "xian", + "莶" : "xian", + "祆" : "xian", + "籼" : "xian", + "跹" : "xian", + "鹇" : "xian", + "痫" : "xian", + "猃" : "xian", + "燹" : "xian", + "蚬" : "xian", + "筅" : "xian", + "冼" : "xian", + "岘" : "xian", + "骧" : "xiang", + "葙" : "xiang", + "芗" : "xiang", + "缃" : "xiang", + "庠" : "xiang", + "鲞" : "xiang", + "蟓" : "xiang", + "削" : "xue", + "枵" : "xiao", + "绡" : "xiao", + "筱" : "xiao", + "邪" : "xie", + "勰" : "xie", + "缬" : "xie", + "血" : "xue", + "榭" : "xie", + "瀣" : "xie", + "薤" : "xie", + "燮" : "xie", + "躞" : "xie", + "廨" : "xie", + "绁" : "xie", + "渫" : "xie", + "榍" : "xie", + "獬" : "xie", + "昕" : "xin", + "忻" : "xin", + "囟" : "xin", + "陉" : "jing", + "荥" : "ying", + "饧" : "tang", + "硎" : "xing", + "荇" : "xing", + "芎" : "xiong", + "馐" : "xiu", + "庥" : "xiu", + "鸺" : "xiu", + "貅" : "xiu", + "髹" : "xiu", + "宿" : "xiu", + "岫" : "xiu", + "溴" : "xiu", + "吁" : "xu", + "盱" : "xu", + "顼" : "xu", + "糈" : "xu", + "醑" : "xu", + "洫" : "xu", + "溆" : "xu", + "蓿" : "xu", + "萱" : "xuan", + "谖" : "xuan", + "儇" : "xuan", + "煊" : "xuan", + "痃" : "xuan", + "铉" : "xuan", + "泫" : "xuan", + "碹" : "xuan", + "楦" : "xuan", + "镟" : "xuan", + "踅" : "xue", + "泶" : "xue", + "鳕" : "xue", + "埙" : "xun", + "曛" : "xun", + "窨" : "xun", + "獯" : "xun", + "峋" : "xun", + "洵" : "xun", + "恂" : "xun", + "浔" : "xun", + "鲟" : "xun", + "蕈" : "xun", + "垭" : "ya", + "岈" : "ya", + "琊" : "ya", + "痖" : "ya", + "迓" : "ya", + "砑" : "ya", + "咽" : "yan", + "鄢" : "yan", + "菸" : "yan", + "崦" : "yan", + "铅" : "qian", + "芫" : "yuan", + "兖" : "yan", + "琰" : "yan", + "罨" : "yan", + "厣" : "yan", + "焱" : "yan", + "酽" : "yan", + "谳" : "yan", + "鞅" : "yang", + "炀" : "yang", + "蛘" : "yang", + "约" : "yue", + "珧" : "yao", + "轺" : "yao", + "繇" : "yao", + "鳐" : "yao", + "崾" : "yao", + "钥" : "yao", + "曜" : "yao", + "铘" : "ye", + "烨" : "ye", + "邺" : "ye", + "靥" : "ye", + "晔" : "ye", + "猗" : "yi", + "铱" : "yi", + "欹" : "qi", + "黟" : "yi", + "怡" : "yi", + "沂" : "yi", + "圯" : "yi", + "荑" : "yi", + "诒" : "yi", + "眙" : "yi", + "嶷" : "yi", + "钇" : "yi", + "舣" : "yi", + "酏" : "yi", + "熠" : "yi", + "弋" : "yi", + "懿" : "yi", + "镒" : "yi", + "峄" : "yi", + "怿" : "yi", + "悒" : "yi", + "佾" : "yi", + "殪" : "yi", + "挹" : "yi", + "埸" : "yi", + "劓" : "yi", + "镱" : "yi", + "瘗" : "yi", + "癔" : "yi", + "翊" : "yi", + "蜴" : "yi", + "氤" : "yin", + "堙" : "yin", + "洇" : "yin", + "鄞" : "yin", + "狺" : "yin", + "夤" : "yin", + "圻" : "qi", + "饮" : "yin", + "吲" : "yin", + "胤" : "yin", + "茚" : "yin", + "璎" : "ying", + "撄" : "ying", + "嬴" : "ying", + "滢" : "ying", + "潆" : "ying", + "蓥" : "ying", + "瘿" : "ying", + "郢" : "ying", + "媵" : "ying", + "邕" : "yong", + "镛" : "yong", + "墉" : "yong", + "慵" : "yong", + "痈" : "yong", + "鳙" : "yong", + "饔" : "yong", + "喁" : "yong", + "俑" : "yong", + "莸" : "you", + "猷" : "you", + "疣" : "you", + "蚰" : "you", + "蝣" : "you", + "莜" : "you", + "牖" : "you", + "铕" : "you", + "卣" : "you", + "宥" : "you", + "侑" : "you", + "蚴" : "you", + "釉" : "you", + "馀" : "yu", + "萸" : "yu", + "禺" : "yu", + "妤" : "yu", + "欤" : "yu", + "觎" : "yu", + "窬" : "yu", + "蝓" : "yu", + "嵛" : "yu", + "舁" : "yu", + "雩" : "yu", + "龉" : "yu", + "伛" : "yu", + "圉" : "yu", + "庾" : "yu", + "瘐" : "yu", + "窳" : "yu", + "俣" : "yu", + "毓" : "yu", + "峪" : "yu", + "煜" : "yu", + "燠" : "yu", + "蓣" : "yu", + "饫" : "yu", + "阈" : "yu", + "鬻" : "yu", + "聿" : "yu", + "钰" : "yu", + "鹆" : "yu", + "蜮" : "yu", + "眢" : "yuan", + "箢" : "yuan", + "员" : "yuan", + "沅" : "yuan", + "橼" : "yuan", + "塬" : "yuan", + "爰" : "yuan", + "螈" : "yuan", + "鼋" : "yuan", + "掾" : "yuan", + "垸" : "yuan", + "瑗" : "yuan", + "刖" : "yue", + "瀹" : "yue", + "樾" : "yue", + "龠" : "yue", + "氲" : "yun", + "昀" : "yun", + "郧" : "yun", + "狁" : "yun", + "郓" : "yun", + "韫" : "yun", + "恽" : "yun", + "扎" : "zha", + "拶" : "za", + "咋" : "za", + "仔" : "zai", + "昝" : "zan", + "瓒" : "zan", + "藏" : "zang", + "奘" : "zang", + "唣" : "zao", + "择" : "ze", + "迮" : "ze", + "赜" : "ze", + "笮" : "ze", + "箦" : "ze", + "舴" : "ze", + "昃" : "ze", + "缯" : "zeng", + "罾" : "zeng", + "齄" : "zha", + "柞" : "zha", + "痄" : "zha", + "瘵" : "zhai", + "旃" : "zhan", + "璋" : "zhang", + "漳" : "zhang", + "嫜" : "zhang", + "鄣" : "zhang", + "仉" : "zhang", + "幛" : "zhang", + "着" : "zhe", + "啁" : "zhou", + "爪" : "zhao", + "棹" : "zhao", + "笊" : "zhao", + "摺" : "zhe", + "磔" : "zhe", + "这" : "zhe", + "柘" : "zhe", + "桢" : "zhen", + "蓁" : "zhen", + "祯" : "zhen", + "浈" : "zhen", + "畛" : "zhen", + "轸" : "zhen", + "稹" : "zhen", + "圳" : "zhen", + "徵" : "zhi", + "钲" : "zheng", + "卮" : "zhi", + "胝" : "zhi", + "祗" : "zhi", + "摭" : "zhi", + "絷" : "zhi", + "埴" : "zhi", + "轵" : "zhi", + "黹" : "zhi", + "帙" : "zhi", + "轾" : "zhi", + "贽" : "zhi", + "陟" : "zhi", + "忮" : "zhi", + "彘" : "zhi", + "膣" : "zhi", + "鸷" : "zhi", + "骘" : "zhi", + "踬" : "zhi", + "郅" : "zhi", + "觯" : "zhi", + "锺" : "zhong", + "螽" : "zhong", + "舯" : "zhong", + "碡" : "zhou", + "绉" : "zhou", + "荮" : "zhou", + "籀" : "zhou", + "酎" : "zhou", + "洙" : "zhu", + "邾" : "zhu", + "潴" : "zhu", + "槠" : "zhu", + "橥" : "zhu", + "舳" : "zhu", + "瘃" : "zhu", + "渚" : "zhu", + "麈" : "zhu", + "箸" : "zhu", + "炷" : "zhu", + "杼" : "zhu", + "翥" : "zhu", + "疰" : "zhu", + "颛" : "zhuan", + "赚" : "zhuan", + "馔" : "zhuan", + "僮" : "tong", + "缒" : "zhui", + "肫" : "zhun", + "窀" : "zhun", + "涿" : "zhuo", + "倬" : "zhuo", + "濯" : "zhuo", + "诼" : "zhuo", + "禚" : "zhuo", + "浞" : "zhuo", + "谘" : "zi", + "淄" : "zi", + "髭" : "zi", + "孳" : "zi", + "粢" : "zi", + "趑" : "zi", + "觜" : "zui", + "缁" : "zi", + "鲻" : "zi", + "嵫" : "zi", + "笫" : "zi", + "耔" : "zi", + "腙" : "zong", + "偬" : "zong", + "诹" : "zou", + "陬" : "zou", + "鄹" : "zou", + "驺" : "zou", + "鲰" : "zou", + "菹" : "ju", + "镞" : "zu", + "躜" : "zuan", + "缵" : "zuan", + "蕞" : "zui", + "撙" : "zun", + "胙" : "zuo", + "阿" : "a", + "阿" : "e", + "柏" : "bai", + "蚌" : "beng", + "薄" : "bo", + "堡" : "bao", + "呗" : "bei", + "贲" : "ben", + "臂" : "bi", + "瘪" : "bie", + "槟" : "bin", + "剥" : "bo", + "伯" : "bo", + "卜" : "bu", + "参" : "can", + "嚓" : "ca", + "差" : "cha", + "孱" : "chan", + "绰" : "chuo", + "称" : "cheng", + "澄" : "cheng", + "大" : "da", + "单" : "dan", + "得" : "de", + "的" : "de", + "地" : "di", + "都" : "dou", + "读" : "du", + "度" : "du", + "蹲" : "dun", + "佛" : "fo", + "伽" : "jia", + "盖" : "gai", + "镐" : "hao", + "给" : "gei", + "呱" : "gua", + "氿" : "jiu", + "桧" : "hui", + "掴" : "guo", + "蛤" : "ha", + "还" : "hai", + "和" : "he", + "核" : "he", + "哼" : "heng", + "鹄" : "hu", + "划" : "hua", + "夹" : "jia", + "贾" : "jia", + "芥" : "jie", + "劲" : "jin", + "荆" : "jing", + "颈" : "jing", + "貉" : "he", + "吖" : "a", + "啊" : "a", + "锕" : "a", + "哎" : "ai", + "哀" : "ai", + "埃" : "ai", + "唉" : "ai", + "欸" : "ai", + "锿" : "ai", + "挨" : "ai", + "皑" : "ai", + "癌" : "ai", + "毐" : "ai", + "矮" : "ai", + "蔼" : "ai", + "霭" : "ai", + "砹" : "ai", + "爱" : "ai", + "隘" : "ai", + "碍" : "ai", + "嗳" : "ai", + "嫒" : "ai", + "叆" : "ai", + "暧" : "ai", + "安" : "an", + "桉" : "an", + "氨" : "an", + "庵" : "an", + "谙" : "an", + "鹌" : "an", + "鞍" : "an", + "俺" : "an", + "埯" : "an", + "唵" : "an", + "铵" : "an", + "揞" : "an", + "岸" : "an", + "按" : "an", + "胺" : "an", + "案" : "an", + "暗" : "an", + "黯" : "an", + "玵" : "an", + "肮" : "ang", + "昂" : "ang", + "盎" : "ang", + "凹" : "ao", + "敖" : "ao", + "遨" : "ao", + "嗷" : "ao", + "獒" : "ao", + "熬" : "ao", + "聱" : "ao", + "螯" : "ao", + "翱" : "ao", + "謷" : "ao", + "鏖" : "ao", + "袄" : "ao", + "媪" : "ao", + "坳" : "ao", + "傲" : "ao", + "奥" : "ao", + "骜" : "ao", + "澳" : "ao", + "懊" : "ao", + "八" : "ba", + "巴" : "ba", + "叭" : "ba", + "芭" : "ba", + "疤" : "ba", + "捌" : "ba", + "笆" : "ba", + "粑" : "ba", + "拔" : "ba", + "茇" : "ba", + "妭" : "ba", + "菝" : "ba", + "跋" : "ba", + "魃" : "ba", + "把" : "ba", + "靶" : "ba", + "坝" : "ba", + "爸" : "ba", + "罢" : "ba", + "霸" : "ba", + "灞" : "ba", + "吧" : "ba", + "钯" : "ba", + "掰" : "bai", + "白" : "bai", + "百" : "bai", + "佰" : "bai", + "捭" : "bai", + "摆" : "bai", + "败" : "bai", + "拜" : "bai", + "稗" : "bai", + "扳" : "ban", + "攽" : "ban", + "班" : "ban", + "般" : "ban", + "颁" : "ban", + "斑" : "ban", + "搬" : "ban", + "瘢" : "ban", + "阪" : "ban", + "坂" : "ban", + "板" : "ban", + "版" : "ban", + "钣" : "ban", + "舨" : "ban", + "办" : "ban", + "半" : "ban", + "伴" : "ban", + "拌" : "ban", + "绊" : "ban", + "瓣" : "ban", + "扮" : "ban", + "邦" : "bang", + "帮" : "bang", + "梆" : "bang", + "浜" : "bang", + "绑" : "bang", + "榜" : "bang", + "棒" : "bang", + "傍" : "bang", + "谤" : "bang", + "蒡" : "bang", + "镑" : "bang", + "包" : "bao", + "苞" : "bao", + "孢" : "bao", + "胞" : "bao", + "龅" : "bao", + "煲" : "bao", + "褒" : "bao", + "雹" : "bao", + "饱" : "bao", + "宝" : "bao", + "保" : "bao", + "鸨" : "bao", + "葆" : "bao", + "褓" : "bao", + "报" : "bao", + "抱" : "bao", + "趵" : "bao", + "豹" : "bao", + "鲍" : "bao", + "暴" : "bao", + "爆" : "bao", + "枹" : "bao", + "杯" : "bei", + "卑" : "bei", + "悲" : "bei", + "碑" : "bei", + "北" : "bei", + "贝" : "bei", + "狈" : "bei", + "备" : "bei", + "背" : "bei", + "钡" : "bei", + "倍" : "bei", + "悖" : "bei", + "被" : "bei", + "辈" : "bei", + "惫" : "bei", + "焙" : "bei", + "蓓" : "bei", + "碚" : "bei", + "褙" : "bei", + "别" : "bei", + "蹩" : "bei", + "椑" : "bei", + "奔" : "ben", + "倴" : "ben", + "犇" : "ben", + "锛" : "ben", + "本" : "ben", + "苯" : "ben", + "坌" : "ben", + "笨" : "ben", + "崩" : "beng", + "绷" : "beng", + "嘣" : "beng", + "甭" : "beng", + "泵" : "beng", + "迸" : "beng", + "镚" : "beng", + "蹦" : "beng", + "屄" : "bi", + "逼" : "bi", + "荸" : "bi", + "鼻" : "bi", + "匕" : "bi", + "比" : "bi", + "吡" : "bi", + "沘" : "bi", + "妣" : "bi", + "彼" : "bi", + "秕" : "bi", + "笔" : "bi", + "俾" : "bi", + "鄙" : "bi", + "币" : "bi", + "必" : "bi", + "毕" : "bi", + "闭" : "bi", + "庇" : "bi", + "诐" : "bi", + "苾" : "bi", + "荜" : "bi", + "毖" : "bi", + "哔" : "bi", + "陛" : "bi", + "毙" : "bi", + "铋" : "bi", + "狴" : "bi", + "萆" : "bi", + "梐" : "bi", + "敝" : "bi", + "婢" : "bi", + "赑" : "bi", + "愎" : "bi", + "弼" : "bi", + "蓖" : "bi", + "痹" : "bi", + "滗" : "bi", + "碧" : "bi", + "蔽" : "bi", + "馝" : "bi", + "弊" : "bi", + "薜" : "bi", + "篦" : "bi", + "壁" : "bi", + "避" : "bi", + "髀" : "bi", + "璧" : "bi", + "芘" : "bi", + "边" : "bian", + "砭" : "bian", + "萹" : "bian", + "编" : "bian", + "煸" : "bian", + "蝙" : "bian", + "鳊" : "bian", + "鞭" : "bian", + "贬" : "bian", + "匾" : "bian", + "褊" : "bian", + "藊" : "bian", + "卞" : "bian", + "抃" : "bian", + "苄" : "bian", + "汴" : "bian", + "忭" : "bian", + "变" : "bian", + "遍" : "bian", + "辨" : "bian", + "辩" : "bian", + "辫" : "bian", + "标" : "biao", + "骉" : "biao", + "彪" : "biao", + "摽" : "biao", + "膘" : "biao", + "飙" : "biao", + "镖" : "biao", + "瀌" : "biao", + "镳" : "biao", + "表" : "biao", + "婊" : "biao", + "裱" : "biao", + "鳔" : "biao", + "憋" : "bie", + "鳖" : "bie", + "宾" : "bin", + "彬" : "bin", + "傧" : "bin", + "滨" : "bin", + "缤" : "bin", + "濒" : "bin", + "摈" : "bin", + "殡" : "bin", + "髌" : "bin", + "鬓" : "bin", + "冰" : "bing", + "兵" : "bing", + "丙" : "bing", + "邴" : "bing", + "秉" : "bing", + "柄" : "bing", + "饼" : "bing", + "炳" : "bing", + "禀" : "bing", + "并" : "bing", + "病" : "bing", + "摒" : "bing", + "拨" : "bo", + "波" : "bo", + "玻" : "bo", + "钵" : "bo", + "饽" : "bo", + "袯" : "bo", + "菠" : "bo", + "播" : "bo", + "驳" : "bo", + "帛" : "bo", + "勃" : "bo", + "钹" : "bo", + "铂" : "bo", + "亳" : "bo", + "舶" : "bo", + "脖" : "bo", + "博" : "bo", + "鹁" : "bo", + "渤" : "bo", + "搏" : "bo", + "馎" : "bo", + "箔" : "bo", + "膊" : "bo", + "踣" : "bo", + "馞" : "bo", + "礴" : "bo", + "跛" : "bo", + "檗" : "bo", + "擘" : "bo", + "簸" : "bo", + "啵" : "bo", + "蕃" : "bo", + "哱" : "bo", + "卟" : "bu", + "补" : "bu", + "捕" : "bu", + "哺" : "bu", + "不" : "bu", + "布" : "bu", + "步" : "bu", + "怖" : "bu", + "钚" : "bu", + "部" : "bu", + "埠" : "bu", + "簿" : "bu", + "擦" : "ca", + "猜" : "cai", + "才" : "cai", + "材" : "cai", + "财" : "cai", + "裁" : "cai", + "采" : "cai", + "彩" : "cai", + "睬" : "cai", + "踩" : "cai", + "菜" : "cai", + "蔡" : "cai", + "餐" : "can", + "残" : "can", + "蚕" : "can", + "惭" : "can", + "惨" : "can", + "黪" : "can", + "灿" : "can", + "粲" : "can", + "璨" : "can", + "穇" : "can", + "仓" : "cang", + "伧" : "cang", + "苍" : "cang", + "沧" : "cang", + "舱" : "cang", + "操" : "cao", + "糙" : "cao", + "曹" : "cao", + "嘈" : "cao", + "漕" : "cao", + "槽" : "cao", + "螬" : "cao", + "草" : "cao", + "册" : "ce", + "厕" : "ce", + "测" : "ce", + "恻" : "ce", + "策" : "ce", + "岑" : "cen", + "涔" : "cen", + "噌" : "ceng", + "层" : "ceng", + "嶒" : "ceng", + "蹭" : "ceng", + "叉" : "cha", + "杈" : "cha", + "插" : "cha", + "馇" : "cha", + "锸" : "cha", + "茬" : "cha", + "茶" : "cha", + "搽" : "cha", + "嵖" : "cha", + "猹" : "cha", + "槎" : "cha", + "碴" : "cha", + "察" : "cha", + "檫" : "cha", + "衩" : "cha", + "镲" : "cha", + "汊" : "cha", + "岔" : "cha", + "侘" : "cha", + "诧" : "cha", + "姹" : "cha", + "蹅" : "cha", + "拆" : "chai", + "钗" : "chai", + "侪" : "chai", + "柴" : "chai", + "豺" : "chai", + "虿" : "chai", + "茝" : "chai", + "觇" : "chan", + "掺" : "chan", + "搀" : "chan", + "襜" : "chan", + "谗" : "chan", + "婵" : "chan", + "馋" : "chan", + "缠" : "chan", + "蝉" : "chan", + "潺" : "chan", + "蟾" : "chan", + "巉" : "chan", + "产" : "chan", + "浐" : "chan", + "谄" : "chan", + "铲" : "chan", + "阐" : "chan", + "蒇" : "chan", + "骣" : "chan", + "冁" : "chan", + "忏" : "chan", + "颤" : "chan", + "羼" : "chan", + "韂" : "chan", + "伥" : "chang", + "昌" : "chang", + "菖" : "chang", + "猖" : "chang", + "娼" : "chang", + "肠" : "chang", + "尝" : "chang", + "常" : "chang", + "偿" : "chang", + "徜" : "chang", + "嫦" : "chang", + "厂" : "chang", + "场" : "chang", + "昶" : "chang", + "惝" : "chang", + "敞" : "chang", + "怅" : "chang", + "畅" : "chang", + "倡" : "chang", + "唱" : "chang", + "裳" : "chang", + "抄" : "chao", + "怊" : "chao", + "钞" : "chao", + "超" : "chao", + "晁" : "chao", + "巢" : "chao", + "嘲" : "chao", + "潮" : "chao", + "吵" : "chao", + "炒" : "chao", + "耖" : "chao", + "砗" : "che", + "扯" : "che", + "彻" : "che", + "坼" : "che", + "掣" : "che", + "撤" : "che", + "澈" : "che", + "瞮" : "che", + "抻" : "chen", + "郴" : "chen", + "嗔" : "chen", + "瞋" : "chen", + "臣" : "chen", + "尘" : "chen", + "辰" : "chen", + "沉" : "chen", + "忱" : "chen", + "陈" : "chen", + "宸" : "chen", + "晨" : "chen", + "谌" : "chen", + "碜" : "chen", + "衬" : "chen", + "龀" : "chen", + "趁" : "chen", + "柽" : "cheng", + "琤" : "cheng", + "撑" : "cheng", + "瞠" : "cheng", + "成" : "cheng", + "丞" : "cheng", + "呈" : "cheng", + "诚" : "cheng", + "承" : "cheng", + "城" : "cheng", + "铖" : "cheng", + "程" : "cheng", + "惩" : "cheng", + "酲" : "cheng", + "橙" : "cheng", + "逞" : "cheng", + "骋" : "cheng", + "秤" : "cheng", + "铛" : "cheng", + "樘" : "cheng", + "吃" : "chi", + "哧" : "chi", + "鸱" : "chi", + "蚩" : "chi", + "笞" : "chi", + "嗤" : "chi", + "痴" : "chi", + "媸" : "chi", + "魑" : "chi", + "池" : "chi", + "弛" : "chi", + "驰" : "chi", + "迟" : "chi", + "茌" : "chi", + "持" : "chi", + "踟" : "chi", + "尺" : "chi", + "齿" : "chi", + "侈" : "chi", + "耻" : "chi", + "豉" : "chi", + "褫" : "chi", + "彳" : "chi", + "叱" : "chi", + "斥" : "chi", + "赤" : "chi", + "饬" : "chi", + "炽" : "chi", + "翅" : "chi", + "敕" : "chi", + "啻" : "chi", + "傺" : "chi", + "匙" : "chi", + "冲" : "chong", + "充" : "chong", + "忡" : "chong", + "茺" : "chong", + "舂" : "chong", + "憧" : "chong", + "艟" : "chong", + "虫" : "chong", + "崇" : "chong", + "宠" : "chong", + "铳" : "chong", + "抽" : "chou", + "瘳" : "chou", + "惆" : "chou", + "绸" : "chou", + "畴" : "chou", + "酬" : "chou", + "稠" : "chou", + "愁" : "chou", + "筹" : "chou", + "踌" : "chou", + "丑" : "chou", + "瞅" : "chou", + "出" : "chu", + "初" : "chu", + "樗" : "chu", + "刍" : "chu", + "除" : "chu", + "厨" : "chu", + "锄" : "chu", + "滁" : "chu", + "蜍" : "chu", + "雏" : "chu", + "橱" : "chu", + "躇" : "chu", + "蹰" : "chu", + "杵" : "chu", + "础" : "chu", + "储" : "chu", + "楚" : "chu", + "褚" : "chu", + "亍" : "chu", + "处" : "chu", + "怵" : "chu", + "绌" : "chu", + "搐" : "chu", + "触" : "chu", + "憷" : "chu", + "黜" : "chu", + "矗" : "chu", + "揣" : "chuai", + "搋" : "chuai", + "膗" : "chuai", + "踹" : "chuai", + "川" : "chuan", + "氚" : "chuan", + "穿" : "chuan", + "舡" : "chuan", + "船" : "chuan", + "遄" : "chuan", + "椽" : "chuan", + "舛" : "chuan", + "喘" : "chuan", + "串" : "chuan", + "钏" : "chuan", + "疮" : "chuang", + "窗" : "chuang", + "床" : "chuang", + "闯" : "chuang", + "创" : "chuang", + "怆" : "chuang", + "吹" : "chui", + "炊" : "chui", + "垂" : "chui", + "陲" : "chui", + "捶" : "chui", + "棰" : "chui", + "槌" : "chui", + "锤" : "chui", + "春" : "chun", + "瑃" : "chun", + "椿" : "chun", + "蝽" : "chun", + "纯" : "chun", + "莼" : "chun", + "唇" : "chun", + "淳" : "chun", + "鹑" : "chun", + "醇" : "chun", + "蠢" : "chun", + "踔" : "chuo", + "戳" : "chuo", + "啜" : "chuo", + "惙" : "chuo", + "辍" : "chuo", + "龊" : "chuo", + "歠" : "chuo", + "疵" : "ci", + "词" : "ci", + "茈" : "ci", + "茨" : "ci", + "祠" : "ci", + "瓷" : "ci", + "辞" : "ci", + "慈" : "ci", + "磁" : "ci", + "雌" : "ci", + "鹚" : "ci", + "糍" : "ci", + "此" : "ci", + "泚" : "ci", + "跐" : "ci", + "次" : "ci", + "刺" : "ci", + "佽" : "ci", + "赐" : "ci", + "匆" : "cong", + "苁" : "cong", + "囱" : "cong", + "枞" : "cong", + "葱" : "cong", + "骢" : "cong", + "聪" : "cong", + "从" : "cong", + "丛" : "cong", + "淙" : "cong", + "悰" : "cong", + "琮" : "cong", + "凑" : "cou", + "辏" : "cou", + "腠" : "cou", + "粗" : "cu", + "徂" : "cu", + "殂" : "cu", + "促" : "cu", + "猝" : "cu", + "蔟" : "cu", + "醋" : "cu", + "踧" : "cu", + "簇" : "cu", + "蹙" : "cu", + "蹴" : "cu", + "汆" : "cuan", + "撺" : "cuan", + "镩" : "cuan", + "蹿" : "cuan", + "窜" : "cuan", + "篡" : "cuan", + "崔" : "cui", + "催" : "cui", + "摧" : "cui", + "璀" : "cui", + "脆" : "cui", + "萃" : "cui", + "啐" : "cui", + "淬" : "cui", + "悴" : "cui", + "毳" : "cui", + "瘁" : "cui", + "粹" : "cui", + "翠" : "cui", + "村" : "cun", + "皴" : "cun", + "存" : "cun", + "忖" : "cun", + "寸" : "cun", + "吋" : "cun", + "搓" : "cuo", + "磋" : "cuo", + "蹉" : "cuo", + "嵯" : "cuo", + "矬" : "cuo", + "痤" : "cuo", + "脞" : "cuo", + "挫" : "cuo", + "莝" : "cuo", + "厝" : "cuo", + "措" : "cuo", + "锉" : "cuo", + "错" : "cuo", + "酇" : "cuo", + "咑" : "da", + "垯" : "da", + "耷" : "da", + "搭" : "da", + "褡" : "da", + "达" : "da", + "怛" : "da", + "妲" : "da", + "荙" : "da", + "笪" : "da", + "答" : "da", + "跶" : "da", + "靼" : "da", + "瘩" : "da", + "鞑" : "da", + "打" : "da", + "呆" : "dai", + "歹" : "dai", + "逮" : "dai", + "傣" : "dai", + "代" : "dai", + "岱" : "dai", + "迨" : "dai", + "玳" : "dai", + "带" : "dai", + "殆" : "dai", + "贷" : "dai", + "待" : "dai", + "怠" : "dai", + "袋" : "dai", + "叇" : "dai", + "戴" : "dai", + "黛" : "dai", + "襶" : "dai", + "呔" : "dai", + "丹" : "dan", + "担" : "dan", + "眈" : "dan", + "耽" : "dan", + "郸" : "dan", + "聃" : "dan", + "殚" : "dan", + "瘅" : "dan", + "箪" : "dan", + "儋" : "dan", + "胆" : "dan", + "疸" : "dan", + "掸" : "dan", + "亶" : "dan", + "旦" : "dan", + "但" : "dan", + "诞" : "dan", + "萏" : "dan", + "啖" : "dan", + "淡" : "dan", + "惮" : "dan", + "蛋" : "dan", + "氮" : "dan", + "赕" : "dan", + "当" : "dang", + "裆" : "dang", + "挡" : "dang", + "档" : "dang", + "党" : "dang", + "谠" : "dang", + "凼" : "dang", + "砀" : "dang", + "宕" : "dang", + "荡" : "dang", + "菪" : "dang", + "刀" : "dao", + "忉" : "dao", + "氘" : "dao", + "舠" : "dao", + "导" : "dao", + "岛" : "dao", + "捣" : "dao", + "倒" : "dao", + "捯" : "dao", + "祷" : "dao", + "蹈" : "dao", + "到" : "dao", + "盗" : "dao", + "悼" : "dao", + "道" : "dao", + "稻" : "dao", + "焘" : "dao", + "锝" : "de", + "嘚" : "de", + "德" : "de", + "扽" : "den", + "灯" : "deng", + "登" : "deng", + "噔" : "deng", + "蹬" : "deng", + "等" : "deng", + "戥" : "deng", + "邓" : "deng", + "僜" : "deng", + "凳" : "deng", + "嶝" : "deng", + "磴" : "deng", + "瞪" : "deng", + "镫" : "deng", + "低" : "di", + "羝" : "di", + "堤" : "di", + "嘀" : "di", + "滴" : "di", + "狄" : "di", + "迪" : "di", + "籴" : "di", + "荻" : "di", + "敌" : "di", + "涤" : "di", + "笛" : "di", + "觌" : "di", + "嫡" : "di", + "镝" : "di", + "氐" : "di", + "邸" : "di", + "诋" : "di", + "抵" : "di", + "底" : "di", + "柢" : "di", + "砥" : "di", + "骶" : "di", + "玓" : "di", + "弟" : "di", + "帝" : "di", + "递" : "di", + "娣" : "di", + "第" : "di", + "谛" : "di", + "蒂" : "di", + "棣" : "di", + "睇" : "di", + "缔" : "di", + "碲" : "di", + "嗲" : "dia", + "掂" : "dian", + "滇" : "dian", + "颠" : "dian", + "巅" : "dian", + "癫" : "dian", + "典" : "dian", + "点" : "dian", + "碘" : "dian", + "踮" : "dian", + "电" : "dian", + "甸" : "dian", + "阽" : "dian", + "坫" : "dian", + "店" : "dian", + "玷" : "dian", + "垫" : "dian", + "钿" : "dian", + "淀" : "dian", + "惦" : "dian", + "奠" : "dian", + "殿" : "dian", + "靛" : "dian", + "刁" : "diao", + "叼" : "diao", + "汈" : "diao", + "凋" : "diao", + "貂" : "diao", + "碉" : "diao", + "雕" : "diao", + "鲷" : "diao", + "屌" : "diao", + "吊" : "diao", + "钓" : "diao", + "窎" : "diao", + "掉" : "diao", + "铫" : "diao", + "爹" : "die", + "跌" : "die", + "迭" : "die", + "谍" : "die", + "耋" : "die", + "喋" : "die", + "牒" : "die", + "叠" : "die", + "碟" : "die", + "嵽" : "die", + "蝶" : "die", + "蹀" : "die", + "鲽" : "die", + "仃" : "ding", + "叮" : "ding", + "玎" : "ding", + "盯" : "ding", + "町" : "ding", + "耵" : "ding", + "顶" : "ding", + "酊" : "ding", + "鼎" : "ding", + "订" : "ding", + "钉" : "ding", + "定" : "ding", + "啶" : "ding", + "腚" : "ding", + "碇" : "ding", + "锭" : "ding", + "丢" : "diu", + "铥" : "diu", + "东" : "dong", + "冬" : "dong", + "咚" : "dong", + "氡" : "dong", + "鸫" : "dong", + "董" : "dong", + "懂" : "dong", + "动" : "dong", + "冻" : "dong", + "侗" : "dong", + "栋" : "dong", + "胨" : "dong", + "洞" : "dong", + "胴" : "dong", + "兜" : "dou", + "蔸" : "dou", + "篼" : "dou", + "抖" : "dou", + "陡" : "dou", + "蚪" : "dou", + "斗" : "dou", + "豆" : "dou", + "逗" : "dou", + "痘" : "dou", + "窦" : "dou", + "督" : "du", + "嘟" : "du", + "毒" : "du", + "独" : "du", + "渎" : "du", + "椟" : "du", + "犊" : "du", + "牍" : "du", + "黩" : "du", + "髑" : "du", + "厾" : "du", + "笃" : "du", + "堵" : "du", + "赌" : "du", + "睹" : "du", + "杜" : "du", + "肚" : "du", + "妒" : "du", + "渡" : "du", + "镀" : "du", + "蠹" : "du", + "端" : "duan", + "短" : "duan", + "段" : "duan", + "断" : "duan", + "缎" : "duan", + "椴" : "duan", + "锻" : "duan", + "簖" : "duan", + "堆" : "dui", + "队" : "dui", + "对" : "dui", + "兑" : "dui", + "怼" : "dui", + "憝" : "dui", + "吨" : "dun", + "惇" : "dun", + "敦" : "dun", + "墩" : "dun", + "礅" : "dun", + "盹" : "dun", + "趸" : "dun", + "沌" : "dun", + "炖" : "dun", + "砘" : "dun", + "钝" : "dun", + "盾" : "dun", + "顿" : "dun", + "遁" : "dun", + "多" : "duo", + "咄" : "duo", + "哆" : "duo", + "掇" : "duo", + "裰" : "duo", + "夺" : "duo", + "踱" : "duo", + "朵" : "duo", + "垛" : "duo", + "哚" : "duo", + "躲" : "duo", + "亸" : "duo", + "剁" : "duo", + "舵" : "duo", + "堕" : "duo", + "惰" : "duo", + "跺" : "duo", + "屙" : "e", + "婀" : "e", + "讹" : "e", + "囮" : "e", + "俄" : "e", + "莪" : "e", + "峨" : "e", + "娥" : "e", + "锇" : "e", + "鹅" : "e", + "蛾" : "e", + "额" : "e", + "厄" : "e", + "扼" : "e", + "苊" : "e", + "呃" : "e", + "垩" : "e", + "饿" : "e", + "鄂" : "e", + "谔" : "e", + "萼" : "e", + "遏" : "e", + "愕" : "e", + "腭" : "e", + "颚" : "e", + "噩" : "e", + "鳄" : "e", + "恩" : "en", + "蒽" : "en", + "摁" : "en", + "鞥" : "eng", + "儿" : "er", + "而" : "er", + "鸸" : "er", + "尔" : "er", + "耳" : "er", + "迩" : "er", + "饵" : "er", + "洱" : "er", + "铒" : "er", + "二" : "er", + "贰" : "er", + "发" : "fa", + "乏" : "fa", + "伐" : "fa", + "罚" : "fa", + "垡" : "fa", + "阀" : "fa", + "筏" : "fa", + "法" : "fa", + "砝" : "fa", + "珐" : "fa", + "帆" : "fan", + "幡" : "fan", + "藩" : "fan", + "翻" : "fan", + "凡" : "fan", + "矾" : "fan", + "钒" : "fan", + "烦" : "fan", + "樊" : "fan", + "燔" : "fan", + "繁" : "fan", + "蹯" : "fan", + "蘩" : "fan", + "反" : "fan", + "返" : "fan", + "犯" : "fan", + "饭" : "fan", + "泛" : "fan", + "范" : "fan", + "贩" : "fan", + "畈" : "fan", + "梵" : "fan", + "方" : "fang", + "邡" : "fang", + "坊" : "fang", + "芳" : "fang", + "枋" : "fang", + "钫" : "fang", + "防" : "fang", + "妨" : "fang", + "肪" : "fang", + "房" : "fang", + "鲂" : "fang", + "仿" : "fang", + "访" : "fang", + "纺" : "fang", + "舫" : "fang", + "放" : "fang", + "飞" : "fei", + "妃" : "fei", + "非" : "fei", + "菲" : "fei", + "啡" : "fei", + "绯" : "fei", + "扉" : "fei", + "肥" : "fei", + "淝" : "fei", + "腓" : "fei", + "匪" : "fei", + "诽" : "fei", + "悱" : "fei", + "棐" : "fei", + "斐" : "fei", + "榧" : "fei", + "翡" : "fei", + "篚" : "fei", + "吠" : "fei", + "肺" : "fei", + "狒" : "fei", + "废" : "fei", + "沸" : "fei", + "费" : "fei", + "痱" : "fei", + "镄" : "fei", + "分" : "fen", + "芬" : "fen", + "吩" : "fen", + "纷" : "fen", + "氛" : "fen", + "酚" : "fen", + "坟" : "fen", + "汾" : "fen", + "棼" : "fen", + "焚" : "fen", + "鼢" : "fen", + "粉" : "fen", + "份" : "fen", + "奋" : "fen", + "忿" : "fen", + "偾" : "fen", + "粪" : "fen", + "愤" : "fen", + "丰" : "feng", + "风" : "feng", + "沣" : "feng", + "枫" : "feng", + "封" : "feng", + "砜" : "feng", + "疯" : "feng", + "峰" : "feng", + "烽" : "feng", + "葑" : "feng", + "锋" : "feng", + "蜂" : "feng", + "酆" : "feng", + "冯" : "feng", + "逢" : "feng", + "缝" : "feng", + "讽" : "feng", + "唪" : "feng", + "凤" : "feng", + "奉" : "feng", + "俸" : "feng", + "缶" : "fou", + "夫" : "fu", + "呋" : "fu", + "肤" : "fu", + "麸" : "fu", + "跗" : "fu", + "稃" : "fu", + "孵" : "fu", + "敷" : "fu", + "弗" : "fu", + "伏" : "fu", + "凫" : "fu", + "扶" : "fu", + "芙" : "fu", + "孚" : "fu", + "拂" : "fu", + "苻" : "fu", + "服" : "fu", + "怫" : "fu", + "茯" : "fu", + "氟" : "fu", + "俘" : "fu", + "浮" : "fu", + "符" : "fu", + "匐" : "fu", + "涪" : "fu", + "艴" : "fu", + "幅" : "fu", + "辐" : "fu", + "蜉" : "fu", + "福" : "fu", + "蝠" : "fu", + "抚" : "fu", + "甫" : "fu", + "拊" : "fu", + "斧" : "fu", + "府" : "fu", + "俯" : "fu", + "釜" : "fu", + "辅" : "fu", + "腑" : "fu", + "腐" : "fu", + "父" : "fu", + "讣" : "fu", + "付" : "fu", + "负" : "fu", + "妇" : "fu", + "附" : "fu", + "咐" : "fu", + "阜" : "fu", + "驸" : "fu", + "赴" : "fu", + "复" : "fu", + "副" : "fu", + "赋" : "fu", + "傅" : "fu", + "富" : "fu", + "腹" : "fu", + "缚" : "fu", + "赙" : "fu", + "蝮" : "fu", + "覆" : "fu", + "馥" : "fu", + "袱" : "fu", + "旮" : "ga", + "嘎" : "ga", + "钆" : "ga", + "尜" : "ga", + "尕" : "ga", + "尬" : "ga", + "该" : "gai", + "垓" : "gai", + "荄" : "gai", + "赅" : "gai", + "改" : "gai", + "丐" : "gai", + "钙" : "gai", + "溉" : "gai", + "概" : "gai", + "甘" : "gan", + "玕" : "gan", + "肝" : "gan", + "坩" : "gan", + "苷" : "gan", + "矸" : "gan", + "泔" : "gan", + "柑" : "gan", + "竿" : "gan", + "酐" : "gan", + "疳" : "gan", + "尴" : "gan", + "杆" : "gan", + "秆" : "gan", + "赶" : "gan", + "敢" : "gan", + "感" : "gan", + "澉" : "gan", + "橄" : "gan", + "擀" : "gan", + "干" : "gan", + "旰" : "gan", + "绀" : "gan", + "淦" : "gan", + "骭" : "gan", + "赣" : "gan", + "冈" : "gang", + "冮" : "gang", + "刚" : "gang", + "肛" : "gang", + "纲" : "gang", + "钢" : "gang", + "缸" : "gang", + "罡" : "gang", + "岗" : "gang", + "港" : "gang", + "杠" : "gang", + "皋" : "gao", + "高" : "gao", + "羔" : "gao", + "睾" : "gao", + "膏" : "gao", + "篙" : "gao", + "糕" : "gao", + "杲" : "gao", + "搞" : "gao", + "槁" : "gao", + "稿" : "gao", + "告" : "gao", + "郜" : "gao", + "诰" : "gao", + "锆" : "gao", + "戈" : "ge", + "圪" : "ge", + "纥" : "ge", + "疙" : "ge", + "哥" : "ge", + "胳" : "ge", + "鸽" : "ge", + "袼" : "ge", + "搁" : "ge", + "割" : "ge", + "歌" : "ge", + "革" : "ge", + "阁" : "ge", + "格" : "ge", + "隔" : "ge", + "嗝" : "ge", + "膈" : "ge", + "骼" : "ge", + "镉" : "ge", + "舸" : "ge", + "葛" : "ge", + "个" : "ge", + "各" : "ge", + "虼" : "ge", + "硌" : "ge", + "铬" : "ge", + "根" : "gen", + "跟" : "gen", + "哏" : "gen", + "亘" : "gen", + "艮" : "gen", + "茛" : "gen", + "庚" : "geng", + "耕" : "geng", + "浭" : "geng", + "赓" : "geng", + "羹" : "geng", + "埂" : "geng", + "耿" : "geng", + "哽" : "geng", + "绠" : "geng", + "梗" : "geng", + "鲠" : "geng", + "更" : "geng", + "工" : "gong", + "弓" : "gong", + "公" : "gong", + "功" : "gong", + "攻" : "gong", + "肱" : "gong", + "宫" : "gong", + "恭" : "gong", + "蚣" : "gong", + "躬" : "gong", + "龚" : "gong", + "塨" : "gong", + "觥" : "gong", + "巩" : "gong", + "汞" : "gong", + "拱" : "gong", + "珙" : "gong", + "共" : "gong", + "贡" : "gong", + "供" : "gong", + "勾" : "gou", + "佝" : "gou", + "沟" : "gou", + "钩" : "gou", + "篝" : "gou", + "苟" : "gou", + "岣" : "gou", + "狗" : "gou", + "枸" : "gou", + "构" : "gou", + "购" : "gou", + "诟" : "gou", + "垢" : "gou", + "够" : "gou", + "彀" : "gou", + "媾" : "gou", + "觏" : "gou", + "估" : "gu", + "咕" : "gu", + "沽" : "gu", + "孤" : "gu", + "姑" : "gu", + "轱" : "gu", + "鸪" : "gu", + "菰" : "gu", + "菇" : "gu", + "蛄" : "gu", + "蓇" : "gu", + "辜" : "gu", + "酤" : "gu", + "觚" : "gu", + "毂" : "gu", + "箍" : "gu", + "古" : "gu", + "谷" : "gu", + "汩" : "gu", + "诂" : "gu", + "股" : "gu", + "骨" : "gu", + "牯" : "gu", + "钴" : "gu", + "羖" : "gu", + "蛊" : "gu", + "鼓" : "gu", + "榾" : "gu", + "鹘" : "gu", + "臌" : "gu", + "瀔" : "gu", + "固" : "gu", + "故" : "gu", + "顾" : "gu", + "梏" : "gu", + "崮" : "gu", + "雇" : "gu", + "锢" : "gu", + "痼" : "gu", + "瓜" : "gua", + "刮" : "gua", + "胍" : "gua", + "鸹" : "gua", + "剐" : "gua", + "寡" : "gua", + "卦" : "gua", + "诖" : "gua", + "挂" : "gua", + "褂" : "gua", + "乖" : "guai", + "拐" : "guai", + "怪" : "guai", + "关" : "guan", + "观" : "guan", + "官" : "guan", + "倌" : "guan", + "蒄" : "guan", + "棺" : "guan", + "瘝" : "guan", + "鳏" : "guan", + "馆" : "guan", + "管" : "guan", + "贯" : "guan", + "冠" : "guan", + "掼" : "guan", + "惯" : "guan", + "祼" : "guan", + "盥" : "guan", + "灌" : "guan", + "瓘" : "guan", + "鹳" : "guan", + "罐" : "guan", + "琯" : "guan", + "光" : "guang", + "咣" : "guang", + "胱" : "guang", + "广" : "guang", + "犷" : "guang", + "桄" : "guang", + "逛" : "guang", + "归" : "gui", + "圭" : "gui", + "龟" : "gui", + "妫" : "gui", + "规" : "gui", + "皈" : "gui", + "闺" : "gui", + "硅" : "gui", + "瑰" : "gui", + "鲑" : "gui", + "宄" : "gui", + "轨" : "gui", + "庋" : "gui", + "匦" : "gui", + "诡" : "gui", + "鬼" : "gui", + "姽" : "gui", + "癸" : "gui", + "晷" : "gui", + "簋" : "gui", + "柜" : "gui", + "炅" : "gui", + "刿" : "gui", + "刽" : "gui", + "贵" : "gui", + "桂" : "gui", + "跪" : "gui", + "鳜" : "gui", + "衮" : "gun", + "绲" : "gun", + "辊" : "gun", + "滚" : "gun", + "磙" : "gun", + "鲧" : "gun", + "棍" : "gun", + "埚" : "guo", + "郭" : "guo", + "啯" : "guo", + "崞" : "guo", + "聒" : "guo", + "锅" : "guo", + "蝈" : "guo", + "国" : "guo", + "帼" : "guo", + "虢" : "guo", + "果" : "guo", + "椁" : "guo", + "蜾" : "guo", + "裹" : "guo", + "过" : "guo", + "哈" : "ha", + "铪" : "ha", + "孩" : "hai", + "骸" : "hai", + "胲" : "hai", + "海" : "hai", + "醢" : "hai", + "亥" : "hai", + "骇" : "hai", + "害" : "hai", + "嗐" : "hai", + "嗨" : "hai", + "顸" : "han", + "蚶" : "han", + "酣" : "han", + "憨" : "han", + "鼾" : "han", + "邗" : "han", + "邯" : "han", + "含" : "han", + "函" : "han", + "晗" : "han", + "焓" : "han", + "涵" : "han", + "韩" : "han", + "寒" : "han", + "罕" : "han", + "喊" : "han", + "蔊" : "han", + "汉" : "han", + "汗" : "han", + "旱" : "han", + "捍" : "han", + "悍" : "han", + "菡" : "han", + "焊" : "han", + "撖" : "han", + "撼" : "han", + "翰" : "han", + "憾" : "han", + "瀚" : "han", + "夯" : "hang", + "杭" : "hang", + "绗" : "hang", + "航" : "hang", + "沆" : "hang", + "蒿" : "hao", + "薅" : "hao", + "嚆" : "hao", + "蚝" : "hao", + "毫" : "hao", + "嗥" : "hao", + "豪" : "hao", + "壕" : "hao", + "嚎" : "hao", + "濠" : "hao", + "好" : "hao", + "郝" : "hao", + "号" : "hao", + "昊" : "hao", + "耗" : "hao", + "浩" : "hao", + "皓" : "hao", + "滈" : "hao", + "颢" : "hao", + "灏" : "hao", + "诃" : "he", + "呵" : "he", + "喝" : "he", + "嗬" : "he", + "禾" : "he", + "合" : "he", + "何" : "he", + "劾" : "he", + "河" : "he", + "曷" : "he", + "阂" : "he", + "盍" : "he", + "荷" : "he", + "菏" : "he", + "盒" : "he", + "涸" : "he", + "颌" : "he", + "阖" : "he", + "贺" : "he", + "赫" : "he", + "褐" : "he", + "鹤" : "he", + "壑" : "he", + "黑" : "hei", + "嘿" : "hei", + "痕" : "hen", + "很" : "hen", + "狠" : "hen", + "恨" : "hen", + "亨" : "heng", + "恒" : "heng", + "珩" : "heng", + "横" : "heng", + "衡" : "heng", + "蘅" : "heng", + "啈" : "heng", + "轰" : "hong", + "訇" : "hong", + "烘" : "hong", + "薨" : "hong", + "弘" : "hong", + "红" : "hong", + "闳" : "hong", + "宏" : "hong", + "荭" : "hong", + "虹" : "hong", + "竑" : "hong", + "洪" : "hong", + "鸿" : "hong", + "哄" : "hong", + "讧" : "hong", + "吽" : "hong", + "齁" : "hou", + "侯" : "hou", + "喉" : "hou", + "猴" : "hou", + "瘊" : "hou", + "骺" : "hou", + "篌" : "hou", + "糇" : "hou", + "吼" : "hou", + "后" : "hou", + "郈" : "hou", + "厚" : "hou", + "垕" : "hou", + "逅" : "hou", + "候" : "hou", + "堠" : "hou", + "鲎" : "hou", + "乎" : "hu", + "呼" : "hu", + "忽" : "hu", + "轷" : "hu", + "烀" : "hu", + "惚" : "hu", + "滹" : "hu", + "囫" : "hu", + "狐" : "hu", + "弧" : "hu", + "胡" : "hu", + "壶" : "hu", + "斛" : "hu", + "葫" : "hu", + "猢" : "hu", + "湖" : "hu", + "瑚" : "hu", + "鹕" : "hu", + "槲" : "hu", + "蝴" : "hu", + "糊" : "hu", + "醐" : "hu", + "觳" : "hu", + "虎" : "hu", + "唬" : "hu", + "琥" : "hu", + "互" : "hu", + "户" : "hu", + "冱" : "hu", + "护" : "hu", + "沪" : "hu", + "枑" : "hu", + "怙" : "hu", + "戽" : "hu", + "笏" : "hu", + "瓠" : "hu", + "扈" : "hu", + "鹱" : "hu", + "花" : "hua", + "砉" : "hua", + "华" : "hua", + "哗" : "hua", + "骅" : "hua", + "铧" : "hua", + "猾" : "hua", + "滑" : "hua", + "化" : "hua", + "画" : "hua", + "话" : "hua", + "桦" : "hua", + "婳" : "hua", + "觟" : "hua", + "怀" : "huai", + "徊" : "huai", + "淮" : "huai", + "槐" : "huai", + "踝" : "huai", + "耲" : "huai", + "坏" : "huai", + "欢" : "huan", + "獾" : "huan", + "环" : "huan", + "洹" : "huan", + "桓" : "huan", + "萑" : "huan", + "寰" : "huan", + "缳" : "huan", + "缓" : "huan", + "幻" : "huan", + "奂" : "huan", + "宦" : "huan", + "换" : "huan", + "唤" : "huan", + "涣" : "huan", + "浣" : "huan", + "患" : "huan", + "焕" : "huan", + "痪" : "huan", + "豢" : "huan", + "漶" : "huan", + "鲩" : "huan", + "擐" : "huan", + "肓" : "huang", + "荒" : "huang", + "塃" : "huang", + "慌" : "huang", + "皇" : "huang", + "黄" : "huang", + "凰" : "huang", + "隍" : "huang", + "喤" : "huang", + "遑" : "huang", + "徨" : "huang", + "湟" : "huang", + "惶" : "huang", + "媓" : "huang", + "煌" : "huang", + "锽" : "huang", + "潢" : "huang", + "璜" : "huang", + "蝗" : "huang", + "篁" : "huang", + "艎" : "huang", + "磺" : "huang", + "癀" : "huang", + "蟥" : "huang", + "簧" : "huang", + "鳇" : "huang", + "恍" : "huang", + "晃" : "huang", + "谎" : "huang", + "幌" : "huang", + "滉" : "huang", + "皝" : "huang", + "灰" : "hui", + "诙" : "hui", + "挥" : "hui", + "恢" : "hui", + "晖" : "hui", + "辉" : "hui", + "麾" : "hui", + "徽" : "hui", + "隳" : "hui", + "回" : "hui", + "茴" : "hui", + "洄" : "hui", + "蛔" : "hui", + "悔" : "hui", + "毁" : "hui", + "卉" : "hui", + "汇" : "hui", + "讳" : "hui", + "荟" : "hui", + "浍" : "hui", + "诲" : "hui", + "绘" : "hui", + "恚" : "hui", + "贿" : "hui", + "烩" : "hui", + "彗" : "hui", + "晦" : "hui", + "秽" : "hui", + "惠" : "hui", + "喙" : "hui", + "慧" : "hui", + "蕙" : "hui", + "蟪" : "hui", + "珲" : "hun", + "昏" : "hun", + "荤" : "hun", + "阍" : "hun", + "惛" : "hun", + "婚" : "hun", + "浑" : "hun", + "馄" : "hun", + "混" : "hun", + "魂" : "hun", + "诨" : "hun", + "溷" : "hun", + "耠" : "huo", + "劐" : "huo", + "豁" : "huo", + "活" : "huo", + "火" : "huo", + "伙" : "huo", + "钬" : "huo", + "夥" : "huo", + "或" : "huo", + "货" : "huo", + "获" : "huo", + "祸" : "huo", + "惑" : "huo", + "霍" : "huo", + "镬" : "huo", + "攉" : "huo", + "藿" : "huo", + "嚯" : "huo", + "讥" : "ji", + "击" : "ji", + "叽" : "ji", + "饥" : "ji", + "玑" : "ji", + "圾" : "ji", + "芨" : "ji", + "机" : "ji", + "乩" : "ji", + "肌" : "ji", + "矶" : "ji", + "鸡" : "ji", + "剞" : "ji", + "唧" : "ji", + "积" : "ji", + "笄" : "ji", + "屐" : "ji", + "姬" : "ji", + "基" : "ji", + "犄" : "ji", + "嵇" : "ji", + "畸" : "ji", + "跻" : "ji", + "箕" : "ji", + "齑" : "ji", + "畿" : "ji", + "墼" : "ji", + "激" : "ji", + "羁" : "ji", + "及" : "ji", + "吉" : "ji", + "岌" : "ji", + "汲" : "ji", + "级" : "ji", + "极" : "ji", + "即" : "ji", + "佶" : "ji", + "笈" : "ji", + "急" : "ji", + "疾" : "ji", + "棘" : "ji", + "集" : "ji", + "蒺" : "ji", + "楫" : "ji", + "辑" : "ji", + "嫉" : "ji", + "瘠" : "ji", + "藉" : "ji", + "籍" : "ji", + "几" : "ji", + "己" : "ji", + "虮" : "ji", + "挤" : "ji", + "脊" : "ji", + "掎" : "ji", + "戟" : "ji", + "麂" : "ji", + "计" : "ji", + "记" : "ji", + "伎" : "ji", + "纪" : "ji", + "技" : "ji", + "忌" : "ji", + "际" : "ji", + "妓" : "ji", + "季" : "ji", + "剂" : "ji", + "迹" : "ji", + "济" : "ji", + "既" : "ji", + "觊" : "ji", + "继" : "ji", + "偈" : "ji", + "祭" : "ji", + "悸" : "ji", + "寄" : "ji", + "寂" : "ji", + "绩" : "ji", + "暨" : "ji", + "稷" : "ji", + "鲫" : "ji", + "髻" : "ji", + "冀" : "ji", + "骥" : "ji", + "加" : "jia", + "佳" : "jia", + "枷" : "jia", + "浃" : "jia", + "痂" : "jia", + "家" : "jia", + "袈" : "jia", + "嘉" : "jia", + "镓" : "jia", + "荚" : "jia", + "戛" : "jia", + "颊" : "jia", + "甲" : "jia", + "胛" : "jia", + "钾" : "jia", + "假" : "jia", + "价" : "jia", + "驾" : "jia", + "架" : "jia", + "嫁" : "jia", + "稼" : "jia", + "戋" : "jian", + "尖" : "jian", + "奸" : "jian", + "歼" : "jian", + "坚" : "jian", + "间" : "jian", + "肩" : "jian", + "艰" : "jian", + "监" : "jian", + "兼" : "jian", + "菅" : "jian", + "笺" : "jian", + "缄" : "jian", + "煎" : "jian", + "拣" : "jian", + "茧" : "jian", + "柬" : "jian", + "俭" : "jian", + "捡" : "jian", + "检" : "jian", + "减" : "jian", + "剪" : "jian", + "睑" : "jian", + "简" : "jian", + "碱" : "jian", + "见" : "jian", + "件" : "jian", + "饯" : "jian", + "建" : "jian", + "荐" : "jian", + "贱" : "jian", + "剑" : "jian", + "健" : "jian", + "舰" : "jian", + "涧" : "jian", + "渐" : "jian", + "谏" : "jian", + "践" : "jian", + "锏" : "jian", + "毽" : "jian", + "腱" : "jian", + "溅" : "jian", + "鉴" : "jian", + "键" : "jian", + "僭" : "jian", + "箭" : "jian", + "江" : "jiang", + "将" : "jiang", + "姜" : "jiang", + "豇" : "jiang", + "浆" : "jiang", + "僵" : "jiang", + "缰" : "jiang", + "疆" : "jiang", + "讲" : "jiang", + "奖" : "jiang", + "桨" : "jiang", + "蒋" : "jiang", + "匠" : "jiang", + "酱" : "jiang", + "犟" : "jiang", + "糨" : "jiang", + "交" : "jiao", + "郊" : "jiao", + "浇" : "jiao", + "娇" : "jiao", + "姣" : "jiao", + "骄" : "jiao", + "胶" : "jiao", + "椒" : "jiao", + "蛟" : "jiao", + "焦" : "jiao", + "跤" : "jiao", + "蕉" : "jiao", + "礁" : "jiao", + "佼" : "jiao", + "狡" : "jiao", + "饺" : "jiao", + "绞" : "jiao", + "铰" : "jiao", + "矫" : "jiao", + "皎" : "jiao", + "脚" : "jiao", + "搅" : "jiao", + "剿" : "jiao", + "缴" : "jiao", + "叫" : "jiao", + "轿" : "jiao", + "较" : "jiao", + "教" : "jiao", + "窖" : "jiao", + "酵" : "jiao", + "侥" : "jiao", + "阶" : "jie", + "皆" : "jie", + "接" : "jie", + "秸" : "jie", + "揭" : "jie", + "嗟" : "jie", + "街" : "jie", + "孑" : "jie", + "节" : "jie", + "讦" : "jie", + "劫" : "jie", + "杰" : "jie", + "诘" : "jie", + "洁" : "jie", + "结" : "jie", + "捷" : "jie", + "睫" : "jie", + "截" : "jie", + "碣" : "jie", + "竭" : "jie", + "姐" : "jie", + "解" : "jie", + "介" : "jie", + "戒" : "jie", + "届" : "jie", + "界" : "jie", + "疥" : "jie", + "诫" : "jie", + "借" : "jie", + "巾" : "jin", + "斤" : "jin", + "今" : "jin", + "金" : "jin", + "津" : "jin", + "矜" : "jin", + "筋" : "jin", + "襟" : "jin", + "仅" : "jin", + "紧" : "jin", + "锦" : "jin", + "谨" : "jin", + "尽" : "jin", + "进" : "jin", + "近" : "jin", + "晋" : "jin", + "烬" : "jin", + "浸" : "jin", + "禁" : "jin", + "觐" : "jin", + "噤" : "jin", + "茎" : "jing", + "京" : "jing", + "泾" : "jing", + "经" : "jing", + "菁" : "jing", + "惊" : "jing", + "晶" : "jing", + "睛" : "jing", + "粳" : "jing", + "兢" : "jing", + "精" : "jing", + "鲸" : "jing", + "井" : "jing", + "阱" : "jing", + "刭" : "jing", + "景" : "jing", + "儆" : "jing", + "警" : "jing", + "径" : "jing", + "净" : "jing", + "痉" : "jing", + "竞" : "jing", + "竟" : "jing", + "敬" : "jing", + "靖" : "jing", + "静" : "jing", + "境" : "jing", + "镜" : "jing", + "迥" : "jiong", + "炯" : "jiong", + "窘" : "jiong", + "纠" : "jiu", + "鸠" : "jiu", + "究" : "jiu", + "赳" : "jiu", + "阄" : "jiu", + "揪" : "jiu", + "啾" : "jiu", + "九" : "jiu", + "久" : "jiu", + "玖" : "jiu", + "灸" : "jiu", + "韭" : "jiu", + "酒" : "jiu", + "旧" : "jiu", + "臼" : "jiu", + "咎" : "jiu", + "柩" : "jiu", + "救" : "jiu", + "厩" : "jiu", + "就" : "jiu", + "舅" : "jiu", + "鹫" : "jiu", + "军" : "jun", + "均" : "jun", + "君" : "jun", + "钧" : "jun", + "菌" : "jun", + "皲" : "jun", + "俊" : "jun", + "郡" : "jun", + "峻" : "jun", + "骏" : "jun", + "竣" : "jun", + "拘" : "ju", + "狙" : "ju", + "居" : "ju", + "驹" : "ju", + "掬" : "ju", + "雎" : "ju", + "鞠" : "ju", + "局" : "ju", + "菊" : "ju", + "焗" : "ju", + "橘" : "ju", + "咀" : "ju", + "沮" : "ju", + "矩" : "ju", + "举" : "ju", + "龃" : "ju", + "巨" : "ju", + "拒" : "ju", + "具" : "ju", + "炬" : "ju", + "俱" : "ju", + "剧" : "ju", + "据" : "ju", + "距" : "ju", + "惧" : "ju", + "飓" : "ju", + "锯" : "ju", + "聚" : "ju", + "踞" : "ju", + "捐" : "juan", + "涓" : "juan", + "娟" : "juan", + "鹃" : "juan", + "卷" : "juan", + "倦" : "juan", + "绢" : "juan", + "眷" : "juan", + "隽" : "juan", + "撅" : "jue", + "噘" : "jue", + "决" : "jue", + "诀" : "jue", + "抉" : "jue", + "绝" : "jue", + "掘" : "jue", + "崛" : "jue", + "厥" : "jue", + "谲" : "jue", + "蕨" : "jue", + "爵" : "jue", + "蹶" : "jue", + "矍" : "jue", + "倔" : "jue", + "咔" : "ka", + "开" : "kai", + "揩" : "kai", + "凯" : "kai", + "铠" : "kai", + "慨" : "kai", + "楷" : "kai", + "忾" : "kai", + "刊" : "kan", + "勘" : "kan", + "龛" : "kan", + "堪" : "kan", + "坎" : "kan", + "侃" : "kan", + "砍" : "kan", + "槛" : "kan", + "看" : "kan", + "瞰" : "kan", + "康" : "kang", + "慷" : "kang", + "糠" : "kang", + "亢" : "kang", + "伉" : "kang", + "抗" : "kang", + "炕" : "kang", + "考" : "kao", + "拷" : "kao", + "烤" : "kao", + "铐" : "kao", + "犒" : "kao", + "靠" : "kao", + "苛" : "ke", + "轲" : "ke", + "科" : "ke", + "棵" : "ke", + "搕" : "ke", + "嗑" : "ke", + "稞" : "ke", + "窠" : "ke", + "颗" : "ke", + "磕" : "ke", + "瞌" : "ke", + "蝌" : "ke", + "可" : "ke", + "坷" : "ke", + "渴" : "ke", + "克" : "ke", + "刻" : "ke", + "恪" : "ke", + "客" : "ke", + "课" : "ke", + "肯" : "ken", + "垦" : "ken", + "恳" : "ken", + "啃" : "ken", + "坑" : "keng", + "铿" : "keng", + "空" : "kong", + "孔" : "kong", + "恐" : "kong", + "控" : "kong", + "抠" : "kou", + "口" : "kou", + "叩" : "kou", + "扣" : "kou", + "寇" : "kou", + "蔻" : "kou", + "枯" : "ku", + "哭" : "ku", + "窟" : "ku", + "骷" : "ku", + "苦" : "ku", + "库" : "ku", + "绔" : "ku", + "裤" : "ku", + "酷" : "ku", + "夸" : "kua", + "垮" : "kua", + "挎" : "kua", + "胯" : "kua", + "跨" : "kua", + "块" : "kuai", + "快" : "kuai", + "侩" : "kuai", + "脍" : "kuai", + "筷" : "kuai", + "宽" : "kuan", + "髋" : "kuan", + "款" : "kuan", + "诓" : "kuang", + "哐" : "kuang", + "筐" : "kuang", + "狂" : "kuang", + "诳" : "kuang", + "旷" : "kuang", + "况" : "kuang", + "矿" : "kuang", + "框" : "kuang", + "眶" : "kuang", + "亏" : "kui", + "盔" : "kui", + "窥" : "kui", + "葵" : "kui", + "魁" : "kui", + "傀" : "kui", + "匮" : "kui", + "馈" : "kui", + "愧" : "kui", + "坤" : "kun", + "昆" : "kun", + "鲲" : "kun", + "捆" : "kun", + "困" : "kun", + "扩" : "kuo", + "括" : "kuo", + "阔" : "kuo", + "廓" : "kuo", + "垃" : "la", + "拉" : "la", + "啦" : "la", + "邋" : "la", + "旯" : "la", + "喇" : "la", + "腊" : "la", + "蜡" : "la", + "辣" : "la", + "来" : "lai", + "莱" : "lai", + "徕" : "lai", + "睐" : "lai", + "赖" : "lai", + "癞" : "lai", + "籁" : "lai", + "兰" : "lan", + "岚" : "lan", + "拦" : "lan", + "栏" : "lan", + "婪" : "lan", + "阑" : "lan", + "蓝" : "lan", + "澜" : "lan", + "褴" : "lan", + "篮" : "lan", + "览" : "lan", + "揽" : "lan", + "缆" : "lan", + "榄" : "lan", + "懒" : "lan", + "烂" : "lan", + "滥" : "lan", + "啷" : "lang", + "郎" : "lang", + "狼" : "lang", + "琅" : "lang", + "廊" : "lang", + "榔" : "lang", + "锒" : "lang", + "螂" : "lang", + "朗" : "lang", + "浪" : "lang", + "捞" : "lao", + "劳" : "lao", + "牢" : "lao", + "崂" : "lao", + "老" : "lao", + "佬" : "lao", + "姥" : "lao", + "唠" : "lao", + "烙" : "lao", + "涝" : "lao", + "酪" : "lao", + "雷" : "lei", + "羸" : "lei", + "垒" : "lei", + "磊" : "lei", + "蕾" : "lei", + "儡" : "lei", + "肋" : "lei", + "泪" : "lei", + "类" : "lei", + "累" : "lei", + "擂" : "lei", + "嘞" : "lei", + "棱" : "leng", + "楞" : "leng", + "冷" : "leng", + "睖" : "leng", + "厘" : "li", + "狸" : "li", + "离" : "li", + "梨" : "li", + "犁" : "li", + "鹂" : "li", + "喱" : "li", + "蜊" : "li", + "漓" : "li", + "璃" : "li", + "黎" : "li", + "罹" : "li", + "篱" : "li", + "蠡" : "li", + "礼" : "li", + "李" : "li", + "里" : "li", + "俚" : "li", + "逦" : "li", + "哩" : "li", + "娌" : "li", + "理" : "li", + "鲤" : "li", + "力" : "li", + "历" : "li", + "厉" : "li", + "立" : "li", + "吏" : "li", + "丽" : "li", + "励" : "li", + "呖" : "li", + "利" : "li", + "沥" : "li", + "枥" : "li", + "例" : "li", + "戾" : "li", + "隶" : "li", + "荔" : "li", + "俐" : "li", + "莉" : "li", + "莅" : "li", + "栗" : "li", + "砾" : "li", + "蛎" : "li", + "唳" : "li", + "笠" : "li", + "粒" : "li", + "雳" : "li", + "痢" : "li", + "连" : "lian", + "怜" : "lian", + "帘" : "lian", + "莲" : "lian", + "涟" : "lian", + "联" : "lian", + "廉" : "lian", + "鲢" : "lian", + "镰" : "lian", + "敛" : "lian", + "脸" : "lian", + "练" : "lian", + "炼" : "lian", + "恋" : "lian", + "殓" : "lian", + "链" : "lian", + "良" : "liang", + "凉" : "liang", + "梁" : "liang", + "粮" : "liang", + "粱" : "liang", + "两" : "liang", + "魉" : "liang", + "亮" : "liang", + "谅" : "liang", + "辆" : "liang", + "靓" : "liang", + "量" : "liang", + "晾" : "liang", + "踉" : "liang", + "辽" : "liao", + "疗" : "liao", + "聊" : "liao", + "僚" : "liao", + "寥" : "liao", + "撩" : "liao", + "嘹" : "liao", + "獠" : "liao", + "潦" : "liao", + "缭" : "liao", + "燎" : "liao", + "料" : "liao", + "撂" : "liao", + "瞭" : "liao", + "镣" : "liao", + "咧" : "lie", + "列" : "lie", + "劣" : "lie", + "冽" : "lie", + "烈" : "lie", + "猎" : "lie", + "裂" : "lie", + "趔" : "lie", + "拎" : "lin", + "邻" : "lin", + "林" : "lin", + "临" : "lin", + "淋" : "lin", + "琳" : "lin", + "粼" : "lin", + "嶙" : "lin", + "潾" : "lin", + "霖" : "lin", + "磷" : "lin", + "鳞" : "lin", + "麟" : "lin", + "凛" : "lin", + "檩" : "lin", + "吝" : "lin", + "赁" : "lin", + "躏" : "lin", + "伶" : "ling", + "灵" : "ling", + "苓" : "ling", + "囹" : "ling", + "泠" : "ling", + "玲" : "ling", + "瓴" : "ling", + "铃" : "ling", + "凌" : "ling", + "陵" : "ling", + "聆" : "ling", + "菱" : "ling", + "棂" : "ling", + "蛉" : "ling", + "翎" : "ling", + "羚" : "ling", + "绫" : "ling", + "零" : "ling", + "龄" : "ling", + "岭" : "ling", + "领" : "ling", + "另" : "ling", + "令" : "ling", + "溜" : "liu", + "熘" : "liu", + "刘" : "liu", + "浏" : "liu", + "留" : "liu", + "流" : "liu", + "琉" : "liu", + "硫" : "liu", + "馏" : "liu", + "榴" : "liu", + "瘤" : "liu", + "柳" : "liu", + "绺" : "liu", + "六" : "liu", + "遛" : "liu", + "龙" : "long", + "咙" : "long", + "珑" : "long", + "胧" : "long", + "聋" : "long", + "笼" : "long", + "隆" : "long", + "窿" : "long", + "陇" : "long", + "拢" : "long", + "垄" : "long", + "娄" : "lou", + "楼" : "lou", + "髅" : "lou", + "搂" : "lou", + "篓" : "lou", + "陋" : "lou", + "镂" : "lou", + "漏" : "lou", + "喽" : "lou", + "撸" : "lu", + "卢" : "lu", + "芦" : "lu", + "庐" : "lu", + "炉" : "lu", + "泸" : "lu", + "鸬" : "lu", + "颅" : "lu", + "鲈" : "lu", + "卤" : "lu", + "虏" : "lu", + "掳" : "lu", + "鲁" : "lu", + "橹" : "lu", + "录" : "lu", + "赂" : "lu", + "鹿" : "lu", + "禄" : "lu", + "路" : "lu", + "箓" : "lu", + "漉" : "lu", + "戮" : "lu", + "鹭" : "lu", + "麓" : "lu", + "峦" : "luan", + "孪" : "luan", + "挛" : "luan", + "鸾" : "luan", + "卵" : "luan", + "乱" : "luan", + "抡" : "lun", + "仑" : "lun", + "伦" : "lun", + "囵" : "lun", + "沦" : "lun", + "轮" : "lun", + "论" : "lun", + "啰" : "luo", + "罗" : "luo", + "萝" : "luo", + "逻" : "luo", + "锣" : "luo", + "箩" : "luo", + "骡" : "luo", + "螺" : "luo", + "裸" : "luo", + "洛" : "luo", + "络" : "luo", + "骆" : "luo", + "摞" : "luo", + "漯" : "luo", + "驴" : "lv", + "榈" : "lv", + "吕" : "lv", + "侣" : "lv", + "旅" : "lv", + "铝" : "lv", + "屡" : "lv", + "缕" : "lv", + "膂" : "lv", + "褛" : "lv", + "履" : "lv", + "律" : "lv", + "虑" : "lv", + "氯" : "lv", + "滤" : "lv", + "掠" : "lve", + "略" : "lve", + "妈" : "ma", + "麻" : "ma", + "蟆" : "ma", + "马" : "ma", + "犸" : "ma", + "玛" : "ma", + "码" : "ma", + "蚂" : "ma", + "骂" : "ma", + "吗" : "ma", + "嘛" : "ma", + "霾" : "mai", + "买" : "mai", + "迈" : "mai", + "麦" : "mai", + "卖" : "mai", + "霡" : "mai", + "蛮" : "man", + "馒" : "man", + "瞒" : "man", + "满" : "man", + "曼" : "man", + "谩" : "man", + "幔" : "man", + "漫" : "man", + "慢" : "man", + "牤" : "mang", + "芒" : "mang", + "忙" : "mang", + "盲" : "mang", + "氓" : "mang", + "茫" : "mang", + "莽" : "mang", + "漭" : "mang", + "蟒" : "mang", + "猫" : "mao", + "毛" : "mao", + "矛" : "mao", + "茅" : "mao", + "牦" : "mao", + "锚" : "mao", + "髦" : "mao", + "蝥" : "mao", + "蟊" : "mao", + "冇" : "mao", + "卯" : "mao", + "铆" : "mao", + "茂" : "mao", + "冒" : "mao", + "贸" : "mao", + "袤" : "mao", + "帽" : "mao", + "貌" : "mao", + "玫" : "mei", + "枚" : "mei", + "眉" : "mei", + "莓" : "mei", + "梅" : "mei", + "媒" : "mei", + "楣" : "mei", + "煤" : "mei", + "酶" : "mei", + "霉" : "mei", + "每" : "mei", + "美" : "mei", + "镁" : "mei", + "妹" : "mei", + "昧" : "mei", + "袂" : "mei", + "寐" : "mei", + "媚" : "mei", + "魅" : "mei", + "门" : "men", + "扪" : "men", + "闷" : "men", + "焖" : "men", + "懑" : "men", + "们" : "men", + "虻" : "meng", + "萌" : "meng", + "蒙" : "meng", + "盟" : "meng", + "檬" : "meng", + "曚" : "meng", + "朦" : "meng", + "猛" : "meng", + "锰" : "meng", + "蜢" : "meng", + "懵" : "meng", + "孟" : "meng", + "梦" : "meng", + "咪" : "mi", + "眯" : "mi", + "弥" : "mi", + "迷" : "mi", + "猕" : "mi", + "谜" : "mi", + "醚" : "mi", + "糜" : "mi", + "麋" : "mi", + "靡" : "mi", + "米" : "mi", + "弭" : "mi", + "觅" : "mi", + "密" : "mi", + "幂" : "mi", + "谧" : "mi", + "蜜" : "mi", + "眠" : "mian", + "绵" : "mian", + "棉" : "mian", + "免" : "mian", + "勉" : "mian", + "娩" : "mian", + "冕" : "mian", + "渑" : "mian", + "湎" : "mian", + "缅" : "mian", + "腼" : "mian", + "面" : "mian", + "喵" : "miao", + "苗" : "miao", + "描" : "miao", + "瞄" : "miao", + "秒" : "miao", + "渺" : "miao", + "藐" : "miao", + "妙" : "miao", + "庙" : "miao", + "缥" : "miao", + "咩" : "mie", + "灭" : "mie", + "蔑" : "mie", + "篾" : "mie", + "乜" : "mie", + "民" : "min", + "皿" : "min", + "抿" : "min", + "泯" : "min", + "闽" : "min", + "悯" : "min", + "敏" : "min", + "名" : "ming", + "明" : "ming", + "鸣" : "ming", + "茗" : "ming", + "冥" : "ming", + "铭" : "ming", + "瞑" : "ming", + "螟" : "ming", + "酩" : "ming", + "命" : "ming", + "谬" : "miu", + "摸" : "mo", + "馍" : "mo", + "摹" : "mo", + "膜" : "mo", + "摩" : "mo", + "磨" : "mo", + "蘑" : "mo", + "魔" : "mo", + "末" : "mo", + "茉" : "mo", + "殁" : "mo", + "沫" : "mo", + "陌" : "mo", + "莫" : "mo", + "秣" : "mo", + "蓦" : "mo", + "漠" : "mo", + "寞" : "mo", + "墨" : "mo", + "默" : "mo", + "嬷" : "mo", + "缪" : "mou", + "哞" : "mou", + "眸" : "mou", + "谋" : "mou", + "某" : "mou", + "母" : "mu", + "牡" : "mu", + "亩" : "mu", + "拇" : "mu", + "姆" : "mu", + "木" : "mu", + "目" : "mu", + "沐" : "mu", + "苜" : "mu", + "牧" : "mu", + "钼" : "mu", + "募" : "mu", + "墓" : "mu", + "幕" : "mu", + "睦" : "mu", + "慕" : "mu", + "暮" : "mu", + "穆" : "mu", + "拿" : "na", + "呐" : "na", + "纳" : "na", + "钠" : "na", + "衲" : "na", + "捺" : "na", + "乃" : "nai", + "奶" : "nai", + "氖" : "nai", + "奈" : "nai", + "耐" : "nai", + "囡" : "nan", + "男" : "nan", + "南" : "nan", + "难" : "nan", + "喃" : "nan", + "楠" : "nan", + "赧" : "nan", + "腩" : "nan", + "囔" : "nang", + "囊" : "nang", + "孬" : "nao", + "呶" : "nao", + "挠" : "nao", + "恼" : "nao", + "脑" : "nao", + "瑙" : "nao", + "闹" : "nao", + "淖" : "nao", + "讷" : "ne", + "馁" : "nei", + "内" : "nei", + "嫩" : "nen", + "恁" : "nen", + "能" : "neng", + "嗯" : "ng", + "妮" : "ni", + "尼" : "ni", + "泥" : "ni", + "怩" : "ni", + "倪" : "ni", + "霓" : "ni", + "拟" : "ni", + "你" : "ni", + "旎" : "ni", + "昵" : "ni", + "逆" : "ni", + "匿" : "ni", + "腻" : "ni", + "溺" : "ni", + "拈" : "nian", + "蔫" : "nian", + "年" : "nian", + "黏" : "nian", + "捻" : "nian", + "辇" : "nian", + "撵" : "nian", + "碾" : "nian", + "廿" : "nian", + "念" : "nian", + "娘" : "niang", + "酿" : "niang", + "鸟" : "niao", + "袅" : "niao", + "尿" : "niao", + "捏" : "nie", + "聂" : "nie", + "涅" : "nie", + "嗫" : "nie", + "镊" : "nie", + "镍" : "nie", + "蹑" : "nie", + "孽" : "nie", + "您" : "nin", + "宁" : "ning", + "咛" : "ning", + "狞" : "ning", + "柠" : "ning", + "凝" : "ning", + "拧" : "ning", + "佞" : "ning", + "泞" : "ning", + "妞" : "niu", + "牛" : "niu", + "扭" : "niu", + "忸" : "niu", + "纽" : "niu", + "钮" : "niu", + "农" : "nong", + "哝" : "nong", + "浓" : "nong", + "脓" : "nong", + "弄" : "nong", + "奴" : "nu", + "驽" : "nu", + "努" : "nu", + "弩" : "nu", + "怒" : "nu", + "暖" : "nuan", + "疟" : "nue", + "虐" : "nue", + "挪" : "nuo", + "诺" : "nuo", + "喏" : "nuo", + "懦" : "nuo", + "糯" : "nuo", + "女" : "nv", + "噢" : "o", + "讴" : "ou", + "瓯" : "ou", + "欧" : "ou", + "殴" : "ou", + "鸥" : "ou", + "呕" : "ou", + "偶" : "ou", + "藕" : "ou", + "怄" : "ou", + "趴" : "pa", + "啪" : "pa", + "葩" : "pa", + "杷" : "pa", + "爬" : "pa", + "琶" : "pa", + "帕" : "pa", + "怕" : "pa", + "拍" : "pai", + "排" : "pai", + "徘" : "pai", + "牌" : "pai", + "哌" : "pai", + "派" : "pai", + "湃" : "pai", + "潘" : "pan", + "攀" : "pan", + "爿" : "pan", + "盘" : "pan", + "磐" : "pan", + "蹒" : "pan", + "蟠" : "pan", + "判" : "pan", + "盼" : "pan", + "叛" : "pan", + "畔" : "pan", + "乓" : "pang", + "滂" : "pang", + "庞" : "pang", + "旁" : "pang", + "螃" : "pang", + "耪" : "pang", + "抛" : "pao", + "咆" : "pao", + "庖" : "pao", + "袍" : "pao", + "跑" : "pao", + "泡" : "pao", + "呸" : "pei", + "胚" : "pei", + "陪" : "pei", + "培" : "pei", + "赔" : "pei", + "裴" : "pei", + "沛" : "pei", + "佩" : "pei", + "配" : "pei", + "喷" : "pen", + "盆" : "pen", + "抨" : "peng", + "怦" : "peng", + "砰" : "peng", + "烹" : "peng", + "嘭" : "peng", + "朋" : "peng", + "彭" : "peng", + "棚" : "peng", + "蓬" : "peng", + "硼" : "peng", + "鹏" : "peng", + "澎" : "peng", + "篷" : "peng", + "膨" : "peng", + "捧" : "peng", + "碰" : "peng", + "丕" : "pi", + "批" : "pi", + "纰" : "pi", + "坯" : "pi", + "披" : "pi", + "砒" : "pi", + "劈" : "pi", + "噼" : "pi", + "霹" : "pi", + "皮" : "pi", + "枇" : "pi", + "毗" : "pi", + "蚍" : "pi", + "疲" : "pi", + "啤" : "pi", + "琵" : "pi", + "脾" : "pi", + "貔" : "pi", + "匹" : "pi", + "痞" : "pi", + "癖" : "pi", + "屁" : "pi", + "睥" : "pi", + "媲" : "pi", + "僻" : "pi", + "譬" : "pi", + "偏" : "pian", + "篇" : "pian", + "翩" : "pian", + "骈" : "pian", + "蹁" : "pian", + "片" : "pian", + "骗" : "pian", + "剽" : "piao", + "漂" : "piao", + "飘" : "piao", + "瓢" : "piao", + "殍" : "piao", + "瞟" : "piao", + "票" : "piao", + "氕" : "pie", + "瞥" : "pie", + "撇" : "pie", + "拼" : "pin", + "姘" : "pin", + "贫" : "pin", + "频" : "pin", + "嫔" : "pin", + "颦" : "pin", + "品" : "pin", + "聘" : "pin", + "乒" : "ping", + "娉" : "ping", + "平" : "ping", + "评" : "ping", + "坪" : "ping", + "苹" : "ping", + "凭" : "ping", + "瓶" : "ping", + "萍" : "ping", + "钋" : "po", + "坡" : "po", + "泼" : "po", + "颇" : "po", + "婆" : "po", + "鄱" : "po", + "叵" : "po", + "珀" : "po", + "破" : "po", + "粕" : "po", + "魄" : "po", + "剖" : "pou", + "抔" : "pou", + "扑" : "pu", + "铺" : "pu", + "噗" : "pu", + "仆" : "pu", + "匍" : "pu", + "菩" : "pu", + "葡" : "pu", + "蒲" : "pu", + "璞" : "pu", + "圃" : "pu", + "浦" : "pu", + "普" : "pu", + "谱" : "pu", + "蹼" : "pu", + "七" : "qi", + "沏" : "qi", + "妻" : "qi", + "柒" : "qi", + "凄" : "qi", + "萋" : "qi", + "戚" : "qi", + "期" : "qi", + "欺" : "qi", + "嘁" : "qi", + "漆" : "qi", + "齐" : "qi", + "芪" : "qi", + "其" : "qi", + "歧" : "qi", + "祈" : "qi", + "祇" : "qi", + "脐" : "qi", + "畦" : "qi", + "跂" : "qi", + "崎" : "qi", + "骑" : "qi", + "琪" : "qi", + "棋" : "qi", + "旗" : "qi", + "鳍" : "qi", + "麒" : "qi", + "乞" : "qi", + "岂" : "qi", + "企" : "qi", + "杞" : "qi", + "启" : "qi", + "起" : "qi", + "绮" : "qi", + "气" : "qi", + "讫" : "qi", + "迄" : "qi", + "弃" : "qi", + "汽" : "qi", + "泣" : "qi", + "契" : "qi", + "砌" : "qi", + "葺" : "qi", + "器" : "qi", + "憩" : "qi", + "俟" : "qi", + "掐" : "qia", + "洽" : "qia", + "恰" : "qia", + "千" : "qian", + "仟" : "qian", + "阡" : "qian", + "芊" : "qian", + "迁" : "qian", + "钎" : "qian", + "牵" : "qian", + "悭" : "qian", + "谦" : "qian", + "签" : "qian", + "愆" : "qian", + "前" : "qian", + "虔" : "qian", + "钱" : "qian", + "钳" : "qian", + "乾" : "qian", + "潜" : "qian", + "黔" : "qian", + "遣" : "qian", + "谴" : "qian", + "欠" : "qian", + "芡" : "qian", + "倩" : "qian", + "堑" : "qian", + "嵌" : "qian", + "歉" : "qian", + "羌" : "qiang", + "枪" : "qiang", + "戕" : "qiang", + "腔" : "qiang", + "蜣" : "qiang", + "锵" : "qiang", + "墙" : "qiang", + "蔷" : "qiang", + "抢" : "qiang", + "羟" : "qiang", + "襁" : "qiang", + "呛" : "qiang", + "炝" : "qiang", + "跄" : "qiang", + "悄" : "qiao", + "跷" : "qiao", + "锹" : "qiao", + "敲" : "qiao", + "橇" : "qiao", + "乔" : "qiao", + "侨" : "qiao", + "荞" : "qiao", + "桥" : "qiao", + "憔" : "qiao", + "瞧" : "qiao", + "巧" : "qiao", + "俏" : "qiao", + "诮" : "qiao", + "峭" : "qiao", + "窍" : "qiao", + "翘" : "qiao", + "撬" : "qiao", + "切" : "qie", + "且" : "qie", + "妾" : "qie", + "怯" : "qie", + "窃" : "qie", + "挈" : "qie", + "惬" : "qie", + "趄" : "qie", + "锲" : "qie", + "钦" : "qin", + "侵" : "qin", + "衾" : "qin", + "芹" : "qin", + "芩" : "qin", + "秦" : "qin", + "琴" : "qin", + "禽" : "qin", + "勤" : "qin", + "擒" : "qin", + "噙" : "qin", + "寝" : "qin", + "沁" : "qin", + "青" : "qing", + "轻" : "qing", + "氢" : "qing", + "倾" : "qing", + "卿" : "qing", + "清" : "qing", + "蜻" : "qing", + "情" : "qing", + "晴" : "qing", + "氰" : "qing", + "擎" : "qing", + "顷" : "qing", + "请" : "qing", + "庆" : "qing", + "罄" : "qing", + "穷" : "qiong", + "穹" : "qiong", + "琼" : "qiong", + "丘" : "qiu", + "秋" : "qiu", + "蚯" : "qiu", + "鳅" : "qiu", + "囚" : "qiu", + "求" : "qiu", + "虬" : "qiu", + "泅" : "qiu", + "酋" : "qiu", + "球" : "qiu", + "遒" : "qiu", + "裘" : "qiu", + "岖" : "qu", + "驱" : "qu", + "屈" : "qu", + "蛆" : "qu", + "躯" : "qu", + "趋" : "qu", + "蛐" : "qu", + "黢" : "qu", + "渠" : "qu", + "瞿" : "qu", + "曲" : "qu", + "取" : "qu", + "娶" : "qu", + "龋" : "qu", + "去" : "qu", + "趣" : "qu", + "觑" : "qu", + "悛" : "quan", + "权" : "quan", + "全" : "quan", + "诠" : "quan", + "泉" : "quan", + "拳" : "quan", + "痊" : "quan", + "蜷" : "quan", + "醛" : "quan", + "犬" : "quan", + "劝" : "quan", + "券" : "quan", + "炔" : "que", + "缺" : "que", + "瘸" : "que", + "却" : "que", + "确" : "que", + "鹊" : "que", + "阙" : "que", + "榷" : "que", + "逡" : "qun", + "裙" : "qun", + "群" : "qun", + "蚺" : "ran", + "然" : "ran", + "燃" : "ran", + "冉" : "ran", + "苒" : "ran", + "染" : "ran", + "瓤" : "rang", + "壤" : "rang", + "攘" : "rang", + "嚷" : "rang", + "让" : "rang", + "荛" : "rao", + "饶" : "rao", + "娆" : "rao", + "桡" : "rao", + "扰" : "rao", + "绕" : "rao", + "惹" : "re", + "热" : "re", + "人" : "ren", + "壬" : "ren", + "仁" : "ren", + "忍" : "ren", + "荏" : "ren", + "稔" : "ren", + "刃" : "ren", + "认" : "ren", + "任" : "ren", + "纫" : "ren", + "韧" : "ren", + "饪" : "ren", + "扔" : "reng", + "仍" : "reng", + "日" : "ri", + "戎" : "rong", + "茸" : "rong", + "荣" : "rong", + "绒" : "rong", + "容" : "rong", + "嵘" : "rong", + "蓉" : "rong", + "溶" : "rong", + "榕" : "rong", + "熔" : "rong", + "融" : "rong", + "冗" : "rong", + "氄" : "rong", + "柔" : "rou", + "揉" : "rou", + "糅" : "rou", + "蹂" : "rou", + "鞣" : "rou", + "肉" : "rou", + "如" : "ru", + "茹" : "ru", + "铷" : "ru", + "儒" : "ru", + "孺" : "ru", + "蠕" : "ru", + "汝" : "ru", + "乳" : "ru", + "辱" : "ru", + "入" : "ru", + "缛" : "ru", + "褥" : "ru", + "阮" : "ruan", + "软" : "ruan", + "蕊" : "rui", + "蚋" : "rui", + "锐" : "rui", + "瑞" : "rui", + "睿" : "rui", + "闰" : "run", + "润" : "run", + "若" : "ruo", + "偌" : "ruo", + "弱" : "ruo", + "仨" : "sa", + "洒" : "sa", + "撒" : "sa", + "卅" : "sa", + "飒" : "sa", + "萨" : "sa", + "腮" : "sai", + "赛" : "sai", + "三" : "san", + "叁" : "san", + "伞" : "san", + "散" : "san", + "桑" : "sang", + "搡" : "sang", + "嗓" : "sang", + "丧" : "sang", + "搔" : "sao", + "骚" : "sao", + "扫" : "sao", + "嫂" : "sao", + "臊" : "sao", + "涩" : "se", + "啬" : "se", + "铯" : "se", + "瑟" : "se", + "穑" : "se", + "森" : "sen", + "僧" : "seng", + "杀" : "sha", + "沙" : "sha", + "纱" : "sha", + "砂" : "sha", + "啥" : "sha", + "傻" : "sha", + "厦" : "sha", + "歃" : "sha", + "煞" : "sha", + "霎" : "sha", + "筛" : "shai", + "晒" : "shai", + "山" : "shan", + "删" : "shan", + "苫" : "shan", + "衫" : "shan", + "姗" : "shan", + "珊" : "shan", + "煽" : "shan", + "潸" : "shan", + "膻" : "shan", + "闪" : "shan", + "陕" : "shan", + "讪" : "shan", + "汕" : "shan", + "扇" : "shan", + "善" : "shan", + "骟" : "shan", + "缮" : "shan", + "擅" : "shan", + "膳" : "shan", + "嬗" : "shan", + "赡" : "shan", + "鳝" : "shan", + "伤" : "shang", + "殇" : "shang", + "商" : "shang", + "觞" : "shang", + "熵" : "shang", + "晌" : "shang", + "赏" : "shang", + "上" : "shang", + "尚" : "shang", + "捎" : "shao", + "烧" : "shao", + "梢" : "shao", + "稍" : "shao", + "艄" : "shao", + "勺" : "shao", + "芍" : "shao", + "韶" : "shao", + "少" : "shao", + "邵" : "shao", + "绍" : "shao", + "哨" : "shao", + "潲" : "shao", + "奢" : "she", + "赊" : "she", + "舌" : "she", + "佘" : "she", + "蛇" : "she", + "舍" : "she", + "设" : "she", + "社" : "she", + "射" : "she", + "涉" : "she", + "赦" : "she", + "摄" : "she", + "慑" : "she", + "麝" : "she", + "申" : "shen", + "伸" : "shen", + "身" : "shen", + "呻" : "shen", + "绅" : "shen", + "砷" : "shen", + "深" : "shen", + "神" : "shen", + "沈" : "shen", + "审" : "shen", + "哂" : "shen", + "婶" : "shen", + "肾" : "shen", + "甚" : "shen", + "渗" : "shen", + "葚" : "shen", + "蜃" : "shen", + "慎" : "shen", + "升" : "sheng", + "生" : "sheng", + "声" : "sheng", + "昇" : "sheng", + "牲" : "sheng", + "笙" : "sheng", + "甥" : "sheng", + "绳" : "sheng", + "圣" : "sheng", + "胜" : "sheng", + "晟" : "sheng", + "剩" : "sheng", + "尸" : "shi", + "失" : "shi", + "师" : "shi", + "诗" : "shi", + "虱" : "shi", + "狮" : "shi", + "施" : "shi", + "湿" : "shi", + "十" : "shi", + "时" : "shi", + "实" : "shi", + "食" : "shi", + "蚀" : "shi", + "史" : "shi", + "矢" : "shi", + "使" : "shi", + "始" : "shi", + "驶" : "shi", + "屎" : "shi", + "士" : "shi", + "氏" : "shi", + "示" : "shi", + "世" : "shi", + "仕" : "shi", + "市" : "shi", + "式" : "shi", + "势" : "shi", + "事" : "shi", + "侍" : "shi", + "饰" : "shi", + "试" : "shi", + "视" : "shi", + "拭" : "shi", + "柿" : "shi", + "是" : "shi", + "适" : "shi", + "恃" : "shi", + "室" : "shi", + "逝" : "shi", + "轼" : "shi", + "舐" : "shi", + "弑" : "shi", + "释" : "shi", + "谥" : "shi", + "嗜" : "shi", + "誓" : "shi", + "收" : "shou", + "手" : "shou", + "守" : "shou", + "首" : "shou", + "寿" : "shou", + "受" : "shou", + "狩" : "shou", + "授" : "shou", + "售" : "shou", + "兽" : "shou", + "绶" : "shou", + "瘦" : "shou", + "殳" : "shu", + "书" : "shu", + "抒" : "shu", + "枢" : "shu", + "叔" : "shu", + "姝" : "shu", + "殊" : "shu", + "倏" : "shu", + "梳" : "shu", + "淑" : "shu", + "舒" : "shu", + "疏" : "shu", + "输" : "shu", + "蔬" : "shu", + "秫" : "shu", + "孰" : "shu", + "赎" : "shu", + "塾" : "shu", + "暑" : "shu", + "黍" : "shu", + "署" : "shu", + "蜀" : "shu", + "鼠" : "shu", + "薯" : "shu", + "曙" : "shu", + "戍" : "shu", + "束" : "shu", + "述" : "shu", + "树" : "shu", + "竖" : "shu", + "恕" : "shu", + "庶" : "shu", + "墅" : "shu", + "漱" : "shu", + "刷" : "shua", + "唰" : "shua", + "耍" : "shua", + "衰" : "shuai", + "摔" : "shuai", + "甩" : "shuai", + "帅" : "shuai", + "蟀" : "shuai", + "闩" : "shuan", + "拴" : "shuan", + "栓" : "shuan", + "涮" : "shuan", + "双" : "shuang", + "霜" : "shuang", + "孀" : "shuang", + "爽" : "shuang", + "谁" : "shui", + "水" : "shui", + "税" : "shui", + "睡" : "shui", + "吮" : "shun", + "顺" : "shun", + "舜" : "shun", + "瞬" : "shun", + "烁" : "shuo", + "铄" : "shuo", + "朔" : "shuo", + "硕" : "shuo", + "司" : "si", + "丝" : "si", + "私" : "si", + "咝" : "si", + "思" : "si", + "斯" : "si", + "厮" : "si", + "撕" : "si", + "嘶" : "si", + "死" : "si", + "巳" : "si", + "四" : "si", + "寺" : "si", + "祀" : "si", + "饲" : "si", + "肆" : "si", + "嗣" : "si", + "松" : "song", + "嵩" : "song", + "怂" : "song", + "耸" : "song", + "悚" : "song", + "讼" : "song", + "宋" : "song", + "送" : "song", + "诵" : "song", + "颂" : "song", + "搜" : "sou", + "嗖" : "sou", + "馊" : "sou", + "艘" : "sou", + "叟" : "sou", + "擞" : "sou", + "嗽" : "sou", + "苏" : "su", + "酥" : "su", + "俗" : "su", + "夙" : "su", + "诉" : "su", + "肃" : "su", + "素" : "su", + "速" : "su", + "粟" : "su", + "嗉" : "su", + "塑" : "su", + "溯" : "su", + "簌" : "su", + "酸" : "suan", + "蒜" : "suan", + "算" : "suan", + "虽" : "sui", + "睢" : "sui", + "绥" : "sui", + "隋" : "sui", + "随" : "sui", + "髓" : "sui", + "岁" : "sui", + "祟" : "sui", + "遂" : "sui", + "碎" : "sui", + "隧" : "sui", + "穗" : "sui", + "孙" : "sun", + "损" : "sun", + "笋" : "sun", + "隼" : "sun", + "唆" : "suo", + "梭" : "suo", + "蓑" : "suo", + "羧" : "suo", + "缩" : "suo", + "所" : "suo", + "索" : "suo", + "唢" : "suo", + "琐" : "suo", + "锁" : "suo", + "他" : "ta", + "它" : "ta", + "她" : "ta", + "铊" : "ta", + "塌" : "ta", + "塔" : "ta", + "獭" : "ta", + "挞" : "ta", + "榻" : "ta", + "踏" : "ta", + "蹋" : "ta", + "胎" : "tai", + "台" : "tai", + "邰" : "tai", + "抬" : "tai", + "苔" : "tai", + "跆" : "tai", + "太" : "tai", + "汰" : "tai", + "态" : "tai", + "钛" : "tai", + "泰" : "tai", + "酞" : "tai", + "贪" : "tan", + "摊" : "tan", + "滩" : "tan", + "瘫" : "tan", + "坛" : "tan", + "昙" : "tan", + "谈" : "tan", + "痰" : "tan", + "谭" : "tan", + "潭" : "tan", + "檀" : "tan", + "坦" : "tan", + "袒" : "tan", + "毯" : "tan", + "叹" : "tan", + "炭" : "tan", + "探" : "tan", + "碳" : "tan", + "汤" : "tang", + "嘡" : "tang", + "羰" : "tang", + "唐" : "tang", + "堂" : "tang", + "棠" : "tang", + "塘" : "tang", + "搪" : "tang", + "膛" : "tang", + "镗" : "tang", + "糖" : "tang", + "螳" : "tang", + "倘" : "tang", + "淌" : "tang", + "躺" : "tang", + "烫" : "tang", + "趟" : "tang", + "涛" : "tao", + "绦" : "tao", + "掏" : "tao", + "滔" : "tao", + "韬" : "tao", + "饕" : "tao", + "逃" : "tao", + "桃" : "tao", + "陶" : "tao", + "萄" : "tao", + "淘" : "tao", + "讨" : "tao", + "套" : "tao", + "特" : "te", + "疼" : "teng", + "腾" : "teng", + "誊" : "teng", + "滕" : "teng", + "藤" : "teng", + "剔" : "ti", + "梯" : "ti", + "踢" : "ti", + "啼" : "ti", + "题" : "ti", + "醍" : "ti", + "蹄" : "ti", + "体" : "ti", + "屉" : "ti", + "剃" : "ti", + "涕" : "ti", + "悌" : "ti", + "惕" : "ti", + "替" : "ti", + "天" : "tian", + "添" : "tian", + "田" : "tian", + "恬" : "tian", + "甜" : "tian", + "填" : "tian", + "忝" : "tian", + "殄" : "tian", + "舔" : "tian", + "掭" : "tian", + "佻" : "tiao", + "挑" : "tiao", + "条" : "tiao", + "迢" : "tiao", + "笤" : "tiao", + "髫" : "tiao", + "窕" : "tiao", + "眺" : "tiao", + "粜" : "tiao", + "跳" : "tiao", + "帖" : "tie", + "贴" : "tie", + "铁" : "tie", + "餮" : "tie", + "铤" : "ting", + "厅" : "ting", + "听" : "ting", + "烃" : "ting", + "廷" : "ting", + "亭" : "ting", + "庭" : "ting", + "停" : "ting", + "蜓" : "ting", + "婷" : "ting", + "霆" : "ting", + "挺" : "ting", + "艇" : "ting", + "通" : "tong", + "嗵" : "tong", + "同" : "tong", + "彤" : "tong", + "桐" : "tong", + "铜" : "tong", + "童" : "tong", + "潼" : "tong", + "瞳" : "tong", + "统" : "tong", + "捅" : "tong", + "桶" : "tong", + "筒" : "tong", + "恸" : "tong", + "痛" : "tong", + "偷" : "tou", + "头" : "tou", + "投" : "tou", + "骰" : "tou", + "透" : "tou", + "凸" : "tu", + "秃" : "tu", + "突" : "tu", + "图" : "tu", + "荼" : "tu", + "徒" : "tu", + "途" : "tu", + "涂" : "tu", + "屠" : "tu", + "土" : "tu", + "吐" : "tu", + "兔" : "tu", + "菟" : "tu", + "湍" : "tuan", + "团" : "tuan", + "疃" : "tuan", + "彖" : "tuan", + "推" : "tui", + "颓" : "tui", + "腿" : "tui", + "退" : "tui", + "蜕" : "tui", + "褪" : "tui", + "吞" : "tun", + "屯" : "tun", + "饨" : "tun", + "豚" : "tun", + "臀" : "tun", + "托" : "tuo", + "拖" : "tuo", + "脱" : "tuo", + "佗" : "tuo", + "陀" : "tuo", + "驼" : "tuo", + "鸵" : "tuo", + "妥" : "tuo", + "椭" : "tuo", + "唾" : "tuo", + "挖" : "wa", + "哇" : "wa", + "洼" : "wa", + "娲" : "wa", + "蛙" : "wa", + "娃" : "wa", + "瓦" : "wa", + "佤" : "wa", + "袜" : "wa", + "歪" : "wai", + "外" : "wai", + "弯" : "wan", + "剜" : "wan", + "湾" : "wan", + "蜿" : "wan", + "豌" : "wan", + "丸" : "wan", + "纨" : "wan", + "完" : "wan", + "玩" : "wan", + "顽" : "wan", + "烷" : "wan", + "宛" : "wan", + "挽" : "wan", + "晚" : "wan", + "惋" : "wan", + "婉" : "wan", + "绾" : "wan", + "皖" : "wan", + "碗" : "wan", + "万" : "wan", + "腕" : "wan", + "汪" : "wang", + "亡" : "wang", + "王" : "wang", + "网" : "wang", + "枉" : "wang", + "罔" : "wang", + "往" : "wang", + "惘" : "wang", + "妄" : "wang", + "忘" : "wang", + "旺" : "wang", + "望" : "wang", + "危" : "wei", + "威" : "wei", + "偎" : "wei", + "微" : "wei", + "煨" : "wei", + "薇" : "wei", + "巍" : "wei", + "韦" : "wei", + "为" : "wei", + "违" : "wei", + "围" : "wei", + "闱" : "wei", + "桅" : "wei", + "唯" : "wei", + "帷" : "wei", + "维" : "wei", + "伟" : "wei", + "伪" : "wei", + "苇" : "wei", + "纬" : "wei", + "委" : "wei", + "诿" : "wei", + "娓" : "wei", + "萎" : "wei", + "猥" : "wei", + "痿" : "wei", + "卫" : "wei", + "未" : "wei", + "位" : "wei", + "味" : "wei", + "畏" : "wei", + "胃" : "wei", + "谓" : "wei", + "喂" : "wei", + "猬" : "wei", + "渭" : "wei", + "蔚" : "wei", + "慰" : "wei", + "魏" : "wei", + "温" : "wen", + "瘟" : "wen", + "文" : "wen", + "纹" : "wen", + "闻" : "wen", + "蚊" : "wen", + "雯" : "wen", + "刎" : "wen", + "吻" : "wen", + "紊" : "wen", + "稳" : "wen", + "问" : "wen", + "汶" : "wen", + "翁" : "weng", + "嗡" : "weng", + "瓮" : "weng", + "挝" : "wo", + "莴" : "wo", + "倭" : "wo", + "喔" : "wo", + "窝" : "wo", + "蜗" : "wo", + "我" : "wo", + "肟" : "wo", + "沃" : "wo", + "卧" : "wo", + "握" : "wo", + "幄" : "wo", + "斡" : "wo", + "乌" : "wu", + "邬" : "wu", + "污" : "wu", + "巫" : "wu", + "呜" : "wu", + "钨" : "wu", + "诬" : "wu", + "屋" : "wu", + "无" : "wu", + "毋" : "wu", + "芜" : "wu", + "吴" : "wu", + "梧" : "wu", + "蜈" : "wu", + "五" : "wu", + "午" : "wu", + "伍" : "wu", + "仵" : "wu", + "怃" : "wu", + "忤" : "wu", + "妩" : "wu", + "武" : "wu", + "侮" : "wu", + "捂" : "wu", + "鹉" : "wu", + "舞" : "wu", + "兀" : "wu", + "勿" : "wu", + "戊" : "wu", + "务" : "wu", + "坞" : "wu", + "物" : "wu", + "误" : "wu", + "悟" : "wu", + "晤" : "wu", + "骛" : "wu", + "雾" : "wu", + "寤" : "wu", + "鹜" : "wu", + "夕" : "xi", + "兮" : "xi", + "西" : "xi", + "吸" : "xi", + "汐" : "xi", + "希" : "xi", + "昔" : "xi", + "析" : "xi", + "唏" : "xi", + "牺" : "xi", + "息" : "xi", + "奚" : "xi", + "悉" : "xi", + "烯" : "xi", + "惜" : "xi", + "晰" : "xi", + "稀" : "xi", + "翕" : "xi", + "犀" : "xi", + "皙" : "xi", + "锡" : "xi", + "溪" : "xi", + "熙" : "xi", + "蜥" : "xi", + "熄" : "xi", + "嘻" : "xi", + "膝" : "xi", + "嬉" : "xi", + "羲" : "xi", + "蟋" : "xi", + "曦" : "xi", + "习" : "xi", + "席" : "xi", + "袭" : "xi", + "媳" : "xi", + "洗" : "xi", + "玺" : "xi", + "徙" : "xi", + "喜" : "xi", + "禧" : "xi", + "戏" : "xi", + "细" : "xi", + "隙" : "xi", + "呷" : "xia", + "虾" : "xia", + "瞎" : "xia", + "匣" : "xia", + "侠" : "xia", + "峡" : "xia", + "狭" : "xia", + "遐" : "xia", + "瑕" : "xia", + "暇" : "xia", + "辖" : "xia", + "霞" : "xia", + "黠" : "xia", + "下" : "xia", + "夏" : "xia", + "罅" : "xia", + "仙" : "xian", + "先" : "xian", + "氙" : "xian", + "掀" : "xian", + "酰" : "xian", + "锨" : "xian", + "鲜" : "xian", + "闲" : "xian", + "贤" : "xian", + "弦" : "xian", + "咸" : "xian", + "涎" : "xian", + "娴" : "xian", + "衔" : "xian", + "舷" : "xian", + "嫌" : "xian", + "显" : "xian", + "险" : "xian", + "跣" : "xian", + "藓" : "xian", + "苋" : "xian", + "县" : "xian", + "现" : "xian", + "限" : "xian", + "线" : "xian", + "宪" : "xian", + "陷" : "xian", + "馅" : "xian", + "羡" : "xian", + "献" : "xian", + "腺" : "xian", + "乡" : "xiang", + "相" : "xiang", + "香" : "xiang", + "厢" : "xiang", + "湘" : "xiang", + "箱" : "xiang", + "襄" : "xiang", + "镶" : "xiang", + "详" : "xiang", + "祥" : "xiang", + "翔" : "xiang", + "享" : "xiang", + "响" : "xiang", + "饷" : "xiang", + "飨" : "xiang", + "想" : "xiang", + "向" : "xiang", + "项" : "xiang", + "象" : "xiang", + "像" : "xiang", + "橡" : "xiang", + "肖" : "xiao", + "枭" : "xiao", + "哓" : "xiao", + "骁" : "xiao", + "逍" : "xiao", + "消" : "xiao", + "宵" : "xiao", + "萧" : "xiao", + "硝" : "xiao", + "销" : "xiao", + "箫" : "xiao", + "潇" : "xiao", + "霄" : "xiao", + "魈" : "xiao", + "嚣" : "xiao", + "崤" : "xiao", + "淆" : "xiao", + "小" : "xiao", + "晓" : "xiao", + "孝" : "xiao", + "哮" : "xiao", + "笑" : "xiao", + "效" : "xiao", + "啸" : "xiao", + "挟" : "xie", + "些" : "xie", + "楔" : "xie", + "歇" : "xie", + "蝎" : "xie", + "协" : "xie", + "胁" : "xie", + "偕" : "xie", + "斜" : "xie", + "谐" : "xie", + "揳" : "xie", + "携" : "xie", + "撷" : "xie", + "鞋" : "xie", + "写" : "xie", + "泄" : "xie", + "泻" : "xie", + "卸" : "xie", + "屑" : "xie", + "械" : "xie", + "亵" : "xie", + "谢" : "xie", + "邂" : "xie", + "懈" : "xie", + "蟹" : "xie", + "心" : "xin", + "芯" : "xin", + "辛" : "xin", + "欣" : "xin", + "锌" : "xin", + "新" : "xin", + "歆" : "xin", + "薪" : "xin", + "馨" : "xin", + "鑫" : "xin", + "信" : "xin", + "衅" : "xin", + "星" : "xing", + "猩" : "xing", + "惺" : "xing", + "腥" : "xing", + "刑" : "xing", + "邢" : "xing", + "形" : "xing", + "型" : "xing", + "醒" : "xing", + "擤" : "xing", + "兴" : "xing", + "杏" : "xing", + "幸" : "xing", + "性" : "xing", + "姓" : "xing", + "悻" : "xing", + "凶" : "xiong", + "兄" : "xiong", + "匈" : "xiong", + "讻" : "xiong", + "汹" : "xiong", + "胸" : "xiong", + "雄" : "xiong", + "熊" : "xiong", + "休" : "xiu", + "咻" : "xiu", + "修" : "xiu", + "羞" : "xiu", + "朽" : "xiu", + "秀" : "xiu", + "袖" : "xiu", + "绣" : "xiu", + "锈" : "xiu", + "嗅" : "xiu", + "欻" : "xu", + "戌" : "xu", + "须" : "xu", + "胥" : "xu", + "虚" : "xu", + "墟" : "xu", + "需" : "xu", + "魆" : "xu", + "徐" : "xu", + "许" : "xu", + "诩" : "xu", + "栩" : "xu", + "旭" : "xu", + "序" : "xu", + "叙" : "xu", + "恤" : "xu", + "酗" : "xu", + "勖" : "xu", + "绪" : "xu", + "续" : "xu", + "絮" : "xu", + "婿" : "xu", + "蓄" : "xu", + "煦" : "xu", + "轩" : "xuan", + "宣" : "xuan", + "揎" : "xuan", + "喧" : "xuan", + "暄" : "xuan", + "玄" : "xuan", + "悬" : "xuan", + "旋" : "xuan", + "漩" : "xuan", + "璇" : "xuan", + "选" : "xuan", + "癣" : "xuan", + "炫" : "xuan", + "绚" : "xuan", + "眩" : "xuan", + "渲" : "xuan", + "靴" : "xue", + "薛" : "xue", + "穴" : "xue", + "学" : "xue", + "噱" : "xue", + "雪" : "xue", + "谑" : "xue", + "勋" : "xun", + "熏" : "xun", + "薰" : "xun", + "醺" : "xun", + "旬" : "xun", + "寻" : "xun", + "巡" : "xun", + "询" : "xun", + "荀" : "xun", + "循" : "xun", + "训" : "xun", + "讯" : "xun", + "汛" : "xun", + "迅" : "xun", + "驯" : "xun", + "徇" : "xun", + "逊" : "xun", + "殉" : "xun", + "巽" : "xun", + "丫" : "ya", + "压" : "ya", + "押" : "ya", + "鸦" : "ya", + "桠" : "ya", + "鸭" : "ya", + "牙" : "ya", + "伢" : "ya", + "芽" : "ya", + "蚜" : "ya", + "崖" : "ya", + "涯" : "ya", + "睚" : "ya", + "衙" : "ya", + "哑" : "ya", + "雅" : "ya", + "亚" : "ya", + "讶" : "ya", + "娅" : "ya", + "氩" : "ya", + "揠" : "ya", + "呀" : "ya", + "恹" : "yan", + "胭" : "yan", + "烟" : "yan", + "焉" : "yan", + "阉" : "yan", + "淹" : "yan", + "湮" : "yan", + "嫣" : "yan", + "延" : "yan", + "闫" : "yan", + "严" : "yan", + "言" : "yan", + "妍" : "yan", + "岩" : "yan", + "炎" : "yan", + "沿" : "yan", + "研" : "yan", + "盐" : "yan", + "阎" : "yan", + "蜒" : "yan", + "筵" : "yan", + "颜" : "yan", + "檐" : "yan", + "奄" : "yan", + "俨" : "yan", + "衍" : "yan", + "掩" : "yan", + "郾" : "yan", + "眼" : "yan", + "偃" : "yan", + "演" : "yan", + "魇" : "yan", + "鼹" : "yan", + "厌" : "yan", + "砚" : "yan", + "彦" : "yan", + "艳" : "yan", + "晏" : "yan", + "唁" : "yan", + "宴" : "yan", + "验" : "yan", + "谚" : "yan", + "堰" : "yan", + "雁" : "yan", + "焰" : "yan", + "滟" : "yan", + "餍" : "yan", + "燕" : "yan", + "赝" : "yan", + "央" : "yang", + "泱" : "yang", + "殃" : "yang", + "鸯" : "yang", + "秧" : "yang", + "扬" : "yang", + "羊" : "yang", + "阳" : "yang", + "杨" : "yang", + "佯" : "yang", + "疡" : "yang", + "徉" : "yang", + "洋" : "yang", + "仰" : "yang", + "养" : "yang", + "氧" : "yang", + "痒" : "yang", + "怏" : "yang", + "样" : "yang", + "恙" : "yang", + "烊" : "yang", + "漾" : "yang", + "幺" : "yao", + "夭" : "yao", + "吆" : "yao", + "妖" : "yao", + "腰" : "yao", + "邀" : "yao", + "爻" : "yao", + "尧" : "yao", + "肴" : "yao", + "姚" : "yao", + "窑" : "yao", + "谣" : "yao", + "摇" : "yao", + "徭" : "yao", + "遥" : "yao", + "瑶" : "yao", + "杳" : "yao", + "咬" : "yao", + "舀" : "yao", + "窈" : "yao", + "药" : "yao", + "要" : "yao", + "鹞" : "yao", + "耀" : "yao", + "耶" : "ye", + "掖" : "ye", + "椰" : "ye", + "噎" : "ye", + "爷" : "ye", + "揶" : "ye", + "也" : "ye", + "冶" : "ye", + "野" : "ye", + "业" : "ye", + "叶" : "ye", + "页" : "ye", + "曳" : "ye", + "夜" : "ye", + "液" : "ye", + "谒" : "ye", + "腋" : "ye", + "一" : "yi", + "伊" : "yi", + "衣" : "yi", + "医" : "yi", + "依" : "yi", + "咿" : "yi", + "揖" : "yi", + "壹" : "yi", + "漪" : "yi", + "噫" : "yi", + "仪" : "yi", + "夷" : "yi", + "饴" : "yi", + "宜" : "yi", + "咦" : "yi", + "贻" : "yi", + "姨" : "yi", + "胰" : "yi", + "移" : "yi", + "痍" : "yi", + "颐" : "yi", + "疑" : "yi", + "彝" : "yi", + "乙" : "yi", + "已" : "yi", + "以" : "yi", + "苡" : "yi", + "矣" : "yi", + "迤" : "yi", + "蚁" : "yi", + "倚" : "yi", + "椅" : "yi", + "旖" : "yi", + "乂" : "yi", + "亿" : "yi", + "义" : "yi", + "艺" : "yi", + "刈" : "yi", + "忆" : "yi", + "议" : "yi", + "屹" : "yi", + "亦" : "yi", + "异" : "yi", + "抑" : "yi", + "呓" : "yi", + "邑" : "yi", + "役" : "yi", + "译" : "yi", + "易" : "yi", + "诣" : "yi", + "绎" : "yi", + "驿" : "yi", + "轶" : "yi", + "弈" : "yi", + "奕" : "yi", + "疫" : "yi", + "羿" : "yi", + "益" : "yi", + "谊" : "yi", + "逸" : "yi", + "翌" : "yi", + "肄" : "yi", + "裔" : "yi", + "意" : "yi", + "溢" : "yi", + "缢" : "yi", + "毅" : "yi", + "薏" : "yi", + "翳" : "yi", + "臆" : "yi", + "翼" : "yi", + "因" : "yin", + "阴" : "yin", + "茵" : "yin", + "荫" : "yin", + "音" : "yin", + "姻" : "yin", + "铟" : "yin", + "喑" : "yin", + "愔" : "yin", + "吟" : "yin", + "垠" : "yin", + "银" : "yin", + "淫" : "yin", + "寅" : "yin", + "龈" : "yin", + "霪" : "yin", + "尹" : "yin", + "引" : "yin", + "蚓" : "yin", + "隐" : "yin", + "瘾" : "yin", + "印" : "yin", + "英" : "ying", + "莺" : "ying", + "婴" : "ying", + "嘤" : "ying", + "罂" : "ying", + "缨" : "ying", + "樱" : "ying", + "鹦" : "ying", + "膺" : "ying", + "鹰" : "ying", + "迎" : "ying", + "茔" : "ying", + "荧" : "ying", + "盈" : "ying", + "莹" : "ying", + "萤" : "ying", + "营" : "ying", + "萦" : "ying", + "楹" : "ying", + "蝇" : "ying", + "赢" : "ying", + "瀛" : "ying", + "颍" : "ying", + "颖" : "ying", + "影" : "ying", + "应" : "ying", + "映" : "ying", + "硬" : "ying", + "哟" : "yo", + "唷" : "yo", + "佣" : "yong", + "拥" : "yong", + "庸" : "yong", + "雍" : "yong", + "壅" : "yong", + "臃" : "yong", + "永" : "yong", + "甬" : "yong", + "咏" : "yong", + "泳" : "yong", + "勇" : "yong", + "涌" : "yong", + "恿" : "yong", + "蛹" : "yong", + "踊" : "yong", + "用" : "yong", + "优" : "you", + "攸" : "you", + "忧" : "you", + "呦" : "you", + "幽" : "you", + "悠" : "you", + "尤" : "you", + "由" : "you", + "邮" : "you", + "犹" : "you", + "油" : "you", + "铀" : "you", + "鱿" : "you", + "游" : "you", + "友" : "you", + "有" : "you", + "酉" : "you", + "莠" : "you", + "黝" : "you", + "又" : "you", + "右" : "you", + "幼" : "you", + "佑" : "you", + "柚" : "you", + "囿" : "you", + "诱" : "you", + "鼬" : "you", + "迂" : "yu", + "纡" : "yu", + "於" : "yu", + "淤" : "yu", + "瘀" : "yu", + "于" : "yu", + "余" : "yu", + "盂" : "yu", + "臾" : "yu", + "鱼" : "yu", + "竽" : "yu", + "俞" : "yu", + "狳" : "yu", + "谀" : "yu", + "娱" : "yu", + "渔" : "yu", + "隅" : "yu", + "揄" : "yu", + "逾" : "yu", + "腴" : "yu", + "渝" : "yu", + "愉" : "yu", + "瑜" : "yu", + "榆" : "yu", + "虞" : "yu", + "愚" : "yu", + "舆" : "yu", + "与" : "yu", + "予" : "yu", + "屿" : "yu", + "宇" : "yu", + "羽" : "yu", + "雨" : "yu", + "禹" : "yu", + "语" : "yu", + "圄" : "yu", + "玉" : "yu", + "驭" : "yu", + "芋" : "yu", + "妪" : "yu", + "郁" : "yu", + "育" : "yu", + "狱" : "yu", + "浴" : "yu", + "预" : "yu", + "域" : "yu", + "欲" : "yu", + "谕" : "yu", + "遇" : "yu", + "喻" : "yu", + "御" : "yu", + "寓" : "yu", + "裕" : "yu", + "愈" : "yu", + "誉" : "yu", + "豫" : "yu", + "鹬" : "yu", + "鸢" : "yuan", + "鸳" : "yuan", + "冤" : "yuan", + "渊" : "yuan", + "元" : "yuan", + "园" : "yuan", + "垣" : "yuan", + "袁" : "yuan", + "原" : "yuan", + "圆" : "yuan", + "援" : "yuan", + "媛" : "yuan", + "缘" : "yuan", + "猿" : "yuan", + "源" : "yuan", + "辕" : "yuan", + "远" : "yuan", + "苑" : "yuan", + "怨" : "yuan", + "院" : "yuan", + "愿" : "yuan", + "曰" : "yue", + "月" : "yue", + "岳" : "yue", + "钺" : "yue", + "阅" : "yue", + "悦" : "yue", + "跃" : "yue", + "越" : "yue", + "粤" : "yue", + "晕" : "yun", + "云" : "yun", + "匀" : "yun", + "芸" : "yun", + "纭" : "yun", + "耘" : "yun", + "允" : "yun", + "陨" : "yun", + "殒" : "yun", + "孕" : "yun", + "运" : "yun", + "酝" : "yun", + "愠" : "yun", + "韵" : "yun", + "蕴" : "yun", + "熨" : "yun", + "匝" : "za", + "咂" : "za", + "杂" : "za", + "砸" : "za", + "灾" : "zai", + "甾" : "zai", + "哉" : "zai", + "栽" : "zai", + "载" : "zai", + "宰" : "zai", + "崽" : "zai", + "再" : "zai", + "在" : "zai", + "糌" : "zan", + "簪" : "zan", + "咱" : "zan", + "趱" : "zan", + "暂" : "zan", + "錾" : "zan", + "赞" : "zan", + "赃" : "zang", + "脏" : "zang", + "臧" : "zang", + "驵" : "zang", + "葬" : "zang", + "遭" : "zao", + "糟" : "zao", + "凿" : "zao", + "早" : "zao", + "枣" : "zao", + "蚤" : "zao", + "澡" : "zao", + "藻" : "zao", + "皂" : "zao", + "灶" : "zao", + "造" : "zao", + "噪" : "zao", + "燥" : "zao", + "躁" : "zao", + "则" : "ze", + "责" : "ze", + "泽" : "ze", + "啧" : "ze", + "帻" : "ze", + "仄" : "ze", + "贼" : "zei", + "怎" : "zen", + "谮" : "zen", + "增" : "zeng", + "憎" : "zeng", + "锃" : "zeng", + "赠" : "zeng", + "甑" : "zeng", + "吒" : "zha", + "挓" : "zha", + "哳" : "zha", + "揸" : "zha", + "渣" : "zha", + "楂" : "zha", + "札" : "zha", + "闸" : "zha", + "铡" : "zha", + "眨" : "zha", + "砟" : "zha", + "乍" : "zha", + "诈" : "zha", + "咤" : "zha", + "炸" : "zha", + "蚱" : "zha", + "榨" : "zha", + "拃" : "zha", + "斋" : "zhai", + "摘" : "zhai", + "宅" : "zhai", + "窄" : "zhai", + "债" : "zhai", + "砦" : "zhai", + "寨" : "zhai", + "沾" : "zhan", + "毡" : "zhan", + "粘" : "zhan", + "詹" : "zhan", + "谵" : "zhan", + "瞻" : "zhan", + "斩" : "zhan", + "盏" : "zhan", + "展" : "zhan", + "崭" : "zhan", + "搌" : "zhan", + "辗" : "zhan", + "占" : "zhan", + "栈" : "zhan", + "战" : "zhan", + "站" : "zhan", + "绽" : "zhan", + "湛" : "zhan", + "蘸" : "zhan", + "张" : "zhang", + "章" : "zhang", + "獐" : "zhang", + "彰" : "zhang", + "樟" : "zhang", + "蟑" : "zhang", + "涨" : "zhang", + "掌" : "zhang", + "丈" : "zhang", + "仗" : "zhang", + "杖" : "zhang", + "帐" : "zhang", + "账" : "zhang", + "胀" : "zhang", + "障" : "zhang", + "嶂" : "zhang", + "瘴" : "zhang", + "钊" : "zhao", + "招" : "zhao", + "昭" : "zhao", + "找" : "zhao", + "沼" : "zhao", + "兆" : "zhao", + "诏" : "zhao", + "赵" : "zhao", + "照" : "zhao", + "罩" : "zhao", + "肇" : "zhao", + "蜇" : "zhe", + "遮" : "zhe", + "哲" : "zhe", + "辄" : "zhe", + "蛰" : "zhe", + "谪" : "zhe", + "辙" : "zhe", + "者" : "zhe", + "锗" : "zhe", + "赭" : "zhe", + "褶" : "zhe", + "浙" : "zhe", + "蔗" : "zhe", + "鹧" : "zhe", + "贞" : "zhen", + "针" : "zhen", + "侦" : "zhen", + "珍" : "zhen", + "帧" : "zhen", + "胗" : "zhen", + "真" : "zhen", + "砧" : "zhen", + "斟" : "zhen", + "甄" : "zhen", + "榛" : "zhen", + "箴" : "zhen", + "臻" : "zhen", + "诊" : "zhen", + "枕" : "zhen", + "疹" : "zhen", + "缜" : "zhen", + "阵" : "zhen", + "鸩" : "zhen", + "振" : "zhen", + "朕" : "zhen", + "赈" : "zhen", + "震" : "zhen", + "镇" : "zhen", + "争" : "zheng", + "征" : "zheng", + "怔" : "zheng", + "峥" : "zheng", + "狰" : "zheng", + "睁" : "zheng", + "铮" : "zheng", + "筝" : "zheng", + "蒸" : "zheng", + "拯" : "zheng", + "整" : "zheng", + "正" : "zheng", + "证" : "zheng", + "郑" : "zheng", + "诤" : "zheng", + "政" : "zheng", + "挣" : "zheng", + "症" : "zheng", + "之" : "zhi", + "支" : "zhi", + "只" : "zhi", + "汁" : "zhi", + "芝" : "zhi", + "吱" : "zhi", + "枝" : "zhi", + "知" : "zhi", + "肢" : "zhi", + "织" : "zhi", + "栀" : "zhi", + "脂" : "zhi", + "蜘" : "zhi", + "执" : "zhi", + "直" : "zhi", + "侄" : "zhi", + "值" : "zhi", + "职" : "zhi", + "植" : "zhi", + "跖" : "zhi", + "踯" : "zhi", + "止" : "zhi", + "旨" : "zhi", + "址" : "zhi", + "芷" : "zhi", + "纸" : "zhi", + "祉" : "zhi", + "指" : "zhi", + "枳" : "zhi", + "咫" : "zhi", + "趾" : "zhi", + "酯" : "zhi", + "至" : "zhi", + "志" : "zhi", + "豸" : "zhi", + "帜" : "zhi", + "制" : "zhi", + "质" : "zhi", + "炙" : "zhi", + "治" : "zhi", + "栉" : "zhi", + "峙" : "zhi", + "挚" : "zhi", + "桎" : "zhi", + "致" : "zhi", + "秩" : "zhi", + "掷" : "zhi", + "痔" : "zhi", + "窒" : "zhi", + "蛭" : "zhi", + "智" : "zhi", + "痣" : "zhi", + "滞" : "zhi", + "置" : "zhi", + "雉" : "zhi", + "稚" : "zhi", + "中" : "zhong", + "忠" : "zhong", + "终" : "zhong", + "盅" : "zhong", + "钟" : "zhong", + "衷" : "zhong", + "肿" : "zhong", + "冢" : "zhong", + "踵" : "zhong", + "仲" : "zhong", + "众" : "zhong", + "舟" : "zhou", + "州" : "zhou", + "诌" : "zhou", + "周" : "zhou", + "洲" : "zhou", + "粥" : "zhou", + "妯" : "zhou", + "轴" : "zhou", + "肘" : "zhou", + "纣" : "zhou", + "咒" : "zhou", + "宙" : "zhou", + "胄" : "zhou", + "昼" : "zhou", + "皱" : "zhou", + "骤" : "zhou", + "帚" : "zhou", + "朱" : "zhu", + "侏" : "zhu", + "诛" : "zhu", + "茱" : "zhu", + "珠" : "zhu", + "株" : "zhu", + "诸" : "zhu", + "铢" : "zhu", + "猪" : "zhu", + "蛛" : "zhu", + "竹" : "zhu", + "竺" : "zhu", + "逐" : "zhu", + "烛" : "zhu", + "躅" : "zhu", + "主" : "zhu", + "拄" : "zhu", + "煮" : "zhu", + "嘱" : "zhu", + "瞩" : "zhu", + "伫" : "zhu", + "苎" : "zhu", + "助" : "zhu", + "住" : "zhu", + "贮" : "zhu", + "注" : "zhu", + "驻" : "zhu", + "柱" : "zhu", + "祝" : "zhu", + "著" : "zhu", + "蛀" : "zhu", + "铸" : "zhu", + "筑" : "zhu", + "抓" : "zhua", + "跩" : "zhuai", + "拽" : "zhuai", + "专" : "zhuan", + "砖" : "zhuan", + "转" : "zhuan", + "啭" : "zhuan", + "撰" : "zhuan", + "篆" : "zhuan", + "妆" : "zhuang", + "庄" : "zhuang", + "桩" : "zhuang", + "装" : "zhuang", + "壮" : "zhuang", + "状" : "zhuang", + "撞" : "zhuang", + "幢" : "zhuang", + "追" : "zhui", + "骓" : "zhui", + "锥" : "zhui", + "坠" : "zhui", + "缀" : "zhui", + "惴" : "zhui", + "赘" : "zhui", + "谆" : "zhun", + "准" : "zhun", + "拙" : "zhuo", + "捉" : "zhuo", + "桌" : "zhuo", + "灼" : "zhuo", + "茁" : "zhuo", + "卓" : "zhuo", + "斫" : "zhuo", + "浊" : "zhuo", + "酌" : "zhuo", + "啄" : "zhuo", + "擢" : "zhuo", + "镯" : "zhuo", + "孜" : "zi", + "咨" : "zi", + "姿" : "zi", + "赀" : "zi", + "资" : "zi", + "辎" : "zi", + "嗞" : "zi", + "滋" : "zi", + "锱" : "zi", + "龇" : "zi", + "子" : "zi", + "姊" : "zi", + "秭" : "zi", + "籽" : "zi", + "梓" : "zi", + "紫" : "zi", + "訾" : "zi", + "滓" : "zi", + "自" : "zi", + "字" : "zi", + "恣" : "zi", + "眦" : "zi", + "渍" : "zi", + "宗" : "zong", + "综" : "zong", + "棕" : "zong", + "踪" : "zong", + "鬃" : "zong", + "总" : "zong", + "纵" : "zong", + "粽" : "zong", + "邹" : "zou", + "走" : "zou", + "奏" : "zou", + "揍" : "zou", + "租" : "zu", + "足" : "zu", + "卒" : "zu", + "族" : "zu", + "诅" : "zu", + "阻" : "zu", + "组" : "zu", + "俎" : "zu", + "祖" : "zu", + "纂" : "zuan", + "钻" : "zuan", + "攥" : "zuan", + "嘴" : "zui", + "最" : "zui", + "罪" : "zui", + "醉" : "zui", + "尊" : "zun", + "遵" : "zun", + "樽" : "zun", + "鳟" : "zun", + "昨" : "zuo", + "左" : "zuo", + "佐" : "zuo", + "作" : "zuo", + "坐" : "zuo", + "阼" : "zuo", + "怍" : "zuo", + "祚" : "zuo", + "唑" : "zuo", + "座" : "zuo", + "做" : "zuo", + "酢" : "zuo", + "斌" : "bin", + "曾" : "zeng", + "查" : "zha", + "査" : "zha", + "乘" : "cheng", + "传" : "chuan", + "丁" : "ding", + "行" : "xing", + "瑾" : "jin", + "婧" : "jing", + "恺" : "kai", + "阚" : "kan", + "奎" : "kui", + "乐" : "le", + "陆" : "lu", + "逯" : "lv", + "璐" : "lu", + "淼" : "miao", + "闵" : "min", + "娜" : "na", + "奇" : "qi", + "琦" : "qi", + "强" : "qiang", + "邱" : "qiu", + "芮" : "rui", + "莎" : "sha", + "盛" : "sheng", + "石" : "shi", + "祎" : "yi", + "殷" : "yin", + "瑛" : "ying", + "昱" : "yu", + "眃" : "yun", + "琢" : "zhuo", + "枰" : "ping", + "玟" : "min", + "珉" : "min", + "珣" : "xun", + "淇" : "qi", + "缈" : "miao", + "彧" : "yu", + "祺" : "qi", + "骞" : "qian", + "垚" : "yao", + "妸" : "e", + "烜" : "hui", + "祁" : "qi", + "傢" : "jia", + "珮" : "pei", + "濮" : "pu", + "屺" : "qi", + "珅" : "shen", + "缇" : "ti", + "霈" : "pei", + "晞" : "xi", + "璠" : "fan", + "骐" : "qi", + "姞" : "ji", + "偲" : "cai", + "齼" : "chu", + "宓" : "mi", + "朴" : "pu", + "萁" : "qi", + "颀" : "qi", + "阗" : "tian", + "湉" : "tian", + "翀" : "chong", + "岷" : "min", + "桤" : "qi", + "囯" : "guo", + "浛" : "han", + "勐" : "meng", + "苠" : "min", + "岍" : "qian", + "皞" : "hao", + "岐" : "qi", + "溥" : "pu", + "锘" : "muo", + "渼" : "mei", + "燊" : "shen", + "玚" : "chang", + "亓" : "qi", + "湋" : "wei", + "涴" : "wan", + "沤" : "ou", + "胖" : "pang", + "莆" : "pu", + "扦" : "qian", + "僳" : "su", + "坍" : "tan", + "锑" : "ti", + "嚏" : "ti", + "腆" : "tian", + "丿" : "pie", + "鼗" : "tao", + "芈" : "mi", + "匚" : "fang", + "刂" : "li", + "冂" : "tong", + "亻" : "dan", + "仳" : "pi", + "俜" : "ping", + "俳" : "pai", + "倜" : "ti", + "傥" : "tang", + "傩" : "nuo", + "佥" : "qian", + "勹" : "bao", + "亠" : "tou", + "廾" : "gong", + "匏" : "pao", + "扌" : "ti", + "拚" : "pin", + "掊" : "pou", + "搦" : "nuo", + "擗" : "pi", + "啕" : "tao", + "嗦" : "suo", + "嗍" : "suo", + "辔" : "pei", + "嘌" : "piao", + "嗾" : "sou", + "嘧" : "mi", + "帔" : "pei", + "帑" : "tang", + "彡" : "san", + "犭" : "fan", + "狍" : "pao", + "狲" : "sun", + "狻" : "jun", + "飧" : "sun", + "夂" : "zhi", + "饣" : "shi", + "庀" : "pi", + "忄" : "shu", + "愫" : "su", + "闼" : "ta", + "丬" : "jiang", + "氵" : "san", + "汔" : "qi", + "沔" : "mian", + "汨" : "mi", + "泮" : "pan", + "洮" : "tao", + "涑" : "su", + "淠" : "pi", + "湓" : "pen", + "溻" : "ta", + "溏" : "tang", + "濉" : "sui", + "宀" : "bao", + "搴" : "qian", + "辶" : "zou", + "逄" : "pang", + "逖" : "ti", + "遢" : "ta", + "邈" : "miao", + "邃" : "sui", + "彐" : "ji", + "屮" : "cao", + "娑" : "suo", + "嫖" : "piao", + "纟" : "jiao", + "缗" : "min", + "瑭" : "tang", + "杪" : "miao", + "桫" : "suo", + "榀" : "pin", + "榫" : "sun", + "槭" : "qi", + "甓" : "pi", + "攴" : "po", + "耆" : "qi", + "牝" : "pin", + "犏" : "pian", + "氆" : "pu", + "攵" : "fan", + "肽" : "tai", + "胼" : "pian", + "脒" : "mi", + "脬" : "pao", + "旆" : "pei", + "炱" : "tai", + "燧" : "sui", + "灬" : "biao", + "礻" : "shi", + "祧" : "tiao", + "忑" : "te", + "忐" : "tan", + "愍" : "min", + "肀" : "yu", + "碛" : "qi", + "眄" : "mian", + "眇" : "miao", + "眭" : "sui", + "睃" : "suo", + "瞍" : "sou", + "畋" : "tian", + "罴" : "pi", + "蠓" : "meng", + "蠛" : "mie", + "笸" : "po", + "筢" : "pa", + "衄" : "nv", + "艋" : "meng", + "敉" : "mi", + "糸" : "mi", + "綦" : "qi", + "醅" : "pei", + "醣" : "tang", + "趿" : "ta", + "觫" : "su", + "龆" : "tiao", + "鲆" : "ping", + "稣" : "su", + "鲐" : "tai", + "鲦" : "tiao", + "鳎" : "ta", + "髂" : "qia", + "縻" : "mi", + "裒" : "pou", + "冫" : "liang", + "冖" : "tu", + "讠" : "yan", + "谇" : "sui", + "谝" : "pian", + "谡" : "su", + "卩" : "dan", + "阝" : "zuo", + "陴" : "pi", + "邳" : "pi", + "郫" : "pi", + "郯" : "tan", + "廴" : "yin", + "凵" : "qian", + "圮" : "pi", + "堋" : "peng", + "鼙" : "pi", + "艹" : "cao", + "芑" : "qi", + "苤" : "pie", + "荪" : "sun", + "荽" : "sui", + "葜" : "qia", + "蒎" : "pai", + "蔌" : "su", + "蕲" : "qi", + "薮" : "sou", + "薹" : "tai", + "蘼" : "mi", + "钅" : "jin", + "钷" : "po", + "钽" : "tan", + "铍" : "pi", + "铴" : "tang", + "铽" : "te", + "锫" : "pei", + "锬" : "tan", + "锼" : "sou", + "镤" : "pu", + "镨" : "pu", + "皤" : "po", + "鹈" : "ti", + "鹋" : "miao", + "疒" : "bing", + "疱" : "pao", + "衤" : "yi", + "袢" : "pan", + "裼" : "ti", + "襻" : "pan", + "耥" : "tang", + "耦" : "ou", + "虍" : "hu", + "蛴" : "qi", + "蜞" : "qi", + "蜱" : "pi", + "螋" : "sou", + "螗" : "tang", + "螵" : "piao", + "蟛" : "peng" +} diff --git a/kirby/i18n/translations/bg.json b/kirby/i18n/translations/bg.json new file mode 100644 index 0000000..24d79d3 --- /dev/null +++ b/kirby/i18n/translations/bg.json @@ -0,0 +1,527 @@ +{ + "add": "\u0414\u043e\u0431\u0430\u0432\u0438", + "avatar": "Профилна снимка", + "back": "Назад", + "cancel": "\u041e\u0442\u043a\u0430\u0436\u0438", + "change": "\u041f\u0440\u043e\u043c\u0435\u043d\u0438", + "close": "\u0417\u0430\u0442\u0432\u043e\u0440\u0438", + "confirm": "Ок", + "collapse": "Collapse", + "collapse.all": "Collapse All", + "copy": "Копирай", + "create": "Създай", + + "date": "Дата", + "date.select": "Select a date", + + "day": "Day", + "days.fri": "\u041f\u0442", + "days.mon": "\u041f\u043d", + "days.sat": "\u0421\u0431", + "days.sun": "\u041d\u0434", + "days.thu": "\u0427\u0442", + "days.tue": "\u0412\u0442", + "days.wed": "\u0421\u0440", + + "delete": "\u0418\u0437\u0442\u0440\u0438\u0439", + "delete.all": "Delete all", + "dimensions": "Размери", + "disabled": "Disabled", + "discard": "\u041e\u0442\u043c\u0435\u043d\u0438", + "download": "Download", + "duplicate": "Duplicate", + "edit": "\u0420\u0435\u0434\u0430\u043a\u0442\u0438\u0440\u0430\u0439", + "expand": "Expand", + "expand.all": "Expand All", + + "dialog.files.empty": "No files to select", + "dialog.pages.empty": "No pages to select", + "dialog.users.empty": "No users to select", + + "email": "Email", + "email.placeholder": "mail@example.com", + + "error.access.code": "Invalid code", + "error.access.login": "Invalid login", + "error.access.panel": "Нямате права за достъп до панела", + "error.access.view": "You are not allowed to access this part of the panel", + + "error.avatar.create.fail": "Профилната снимка не може да се качи", + "error.avatar.delete.fail": "Профилната снимка не може да бъде изтрита", + "error.avatar.dimensions.invalid": "Моля запазете ширината и височината на профилната снимка под 3000 пиксела", + "error.avatar.mime.forbidden": "Профилната снимка трябва да бъде в JPEG или PNG формат", + + "error.blueprint.notFound": "Образецът \"{name}\" не може да бъде зареден", + + "error.blocks.max.plural": "You must not add more than {max} blocks", + "error.blocks.max.singular": "You must not add more than one block", + "error.blocks.min.plural": "You must add at least {min} blocks", + "error.blocks.min.singular": "You must add at least one block", + "error.blocks.validation": "There's an error in block {index}", + + "error.email.preset.notFound": "Email шаблонът \"{name}\" не може да бъде открит", + + "error.field.converter.invalid": "Невалиден конвертор \"{converter}\"", + + "error.file.changeName.empty": "The name must not be empty", + "error.file.changeName.permission": "Не можете да смените името на \"{filename}\"", + "error.file.duplicate": "Файл с име \"{filename}\" вече съществува", + "error.file.extension.forbidden": "Файловото разширение \"{extension}\" не е позволено", + "error.file.extension.invalid": "Invalid extension: {extension}", + "error.file.extension.missing": "Липсва файлово разширение за файла \"{filename}\"", + "error.file.maxheight": "The height of the image must not exceed {height} pixels", + "error.file.maxsize": "The file is too large", + "error.file.maxwidth": "The width of the image must not exceed {width} pixels", + "error.file.mime.differs": "Каченият файл трябва да бъде от същия mime тип \"{mime}\"", + "error.file.mime.forbidden": "The media type \"{mime}\" is not allowed", + "error.file.mime.invalid": "Invalid mime type: {mime}", + "error.file.mime.missing": "The media type for \"{filename}\" cannot be detected", + "error.file.minheight": "The height of the image must be at least {height} pixels", + "error.file.minsize": "The file is too small", + "error.file.minwidth": "The width of the image must be at least {width} pixels", + "error.file.name.missing": "Името на файла е задължително", + "error.file.notFound": "Файлът \"{filename}\" не може да бъде намерен", + "error.file.orientation": "The orientation of the image must be \"{orientation}\"", + "error.file.type.forbidden": "Не е позволен ъплоуда на файлове от тип {type}", + "error.file.type.invalid": "Invalid file type: {type}", + "error.file.undefined": "\u0424\u0430\u0439\u043b\u044a\u0442 \u043d\u0435 \u043c\u043e\u0436\u0435 \u0434\u0430 \u0431\u044a\u0434\u0435 \u043d\u0430\u043c\u0435\u0440\u0435\u043d", + + "error.form.incomplete": "Моля коригирайте всички грешки във формата...", + "error.form.notSaved": "Формата не може да бъде запазена", + + "error.language.code": "Please enter a valid code for the language", + "error.language.duplicate": "The language already exists", + "error.language.name": "Please enter a valid name for the language", + + "error.layout.validation.block": "There's an error in block {blockIndex} in layout {layoutIndex}", + "error.layout.validation.settings": "There's an error in layout {index} settings", + + "error.license.format": "Please enter a valid license key", + "error.license.email": "Моля въведете валиден email адрес", + "error.license.verification": "The license could not be verified", + + "error.page.changeSlug.permission": "Не можете да смените URL на \"{slug}\"", + "error.page.changeStatus.incomplete": "Страницата съдържа грешки и не може да бъде публикувана", + "error.page.changeStatus.permission": "Статусът на страницата не може да бъде променен", + "error.page.changeStatus.toDraft.invalid": "Страницата \"{slug}\" не може да бъде променена в чернова", + "error.page.changeTemplate.invalid": "Темплейтът за страница \"{slug}\" не може да бъде променен", + "error.page.changeTemplate.permission": "Нямате права за да промените шаблона за \"{slug}\"", + "error.page.changeTitle.empty": "Заглавието е задължително", + "error.page.changeTitle.permission": "Не можете да промените заглавието на \"{slug}\"", + "error.page.create.permission": "Не можете да създадете \"{slug}\"", + "error.page.delete": "Страницата \"{slug}\" не може да бъде изтрита", + "error.page.delete.confirm": "Моля въведете името на страницата, за да потвърдите", + "error.page.delete.hasChildren": "Страницата има подстраници и не може да бъде изтрита", + "error.page.delete.permission": "Не можете да изтриете \"{slug}\"", + "error.page.draft.duplicate": "Вече съществува чернова с URL-добавка \"{slug}\"", + "error.page.duplicate": "Страница с URL-добавка \"{slug}\" вече съществува", + "error.page.duplicate.permission": "You are not allowed to duplicate \"{slug}\"", + "error.page.notFound": "Страницата \"{slug}\" не може да бъде намерена", + "error.page.num.invalid": "Моля въведете валидно число за сортиране. Числата не трябва да са негативни.", + "error.page.slug.invalid": "Please enter a valid URL appendix", + "error.page.slug.maxlength": "Slug length must be less than \"{length}\" characters", + "error.page.sort.permission": "Страницата \"{slug}\" не може да бъде сортирана", + "error.page.status.invalid": "Моля изберете валиден статус на страницата", + "error.page.undefined": "\u0421\u0442\u0440\u0430\u043d\u0438\u0446\u0430\u0442\u0430 \u043d\u0435 \u043c\u043e\u0436\u0435 \u0434\u0430 \u0431\u044a\u0434\u0435 \u043d\u0430\u043c\u0435\u0440\u0435\u043d\u0430", + "error.page.update.permission": "Не можете да обновите \"{slug}\"", + + "error.section.files.max.plural": "Не можете да добавяте повече от {max} файлa в секция \"{section}\"", + "error.section.files.max.singular": "Не можете да добавяте повече от един файл в секция \"{section}\"", + "error.section.files.min.plural": "The \"{section}\" section requires at least {min} files", + "error.section.files.min.singular": "The \"{section}\" section requires at least one file", + + "error.section.pages.max.plural": "Не можете да добавяте повече от {max} страници в секция \"{section}\"", + "error.section.pages.max.singular": "Не можете да добавяте повече от една страница в секция \"{section}\"", + "error.section.pages.min.plural": "The \"{section}\" section requires at least {min} pages", + "error.section.pages.min.singular": "The \"{section}\" section requires at least one page", + + "error.section.notLoaded": "Секция \"{name}\" не може да бъде заредена", + "error.section.type.invalid": "Типът \"{type}\" на секция не е валиден", + + "error.site.changeTitle.empty": "Заглавието е задължително", + "error.site.changeTitle.permission": "Не може да променяте заглавието на сайта", + "error.site.update.permission": "Нямате права за да обновите сайта", + + "error.template.default.notFound": "Стандартният шаблон не съществува", + + "error.user.changeEmail.permission": "Нямате права да промените имейла на този потребител \"{name}\"", + "error.user.changeLanguage.permission": "Нямате права да промените езика за този потребител \"{name}\"", + "error.user.changeName.permission": "Нямате права да промените името на този потребител \"{name}\"", + "error.user.changePassword.permission": "Нямате права да промените паролата за този потребител \"{name}\"", + "error.user.changeRole.lastAdmin": "Ролята на последния администратор не може да бъде променена", + "error.user.changeRole.permission": "Нямате права да промените ролята на този потребител \"{name}\"", + "error.user.changeRole.toAdmin": "You are not allowed to promote someone to the admin role", + "error.user.create.permission": "Нямате права да създадете този потребител", + "error.user.delete": "\u041f\u043e\u0442\u0440\u0435\u0431\u0438\u0442\u0435\u043b\u044f\u0442 \u043d\u0435 \u043c\u043e\u0436\u0435 \u0434\u0430 \u0431\u044a\u0434\u0435 \u0438\u0437\u0442\u0440\u0438\u0442", + "error.user.delete.lastAdmin": "\u041d\u0435 \u043c\u043e\u0436\u0435\u0442\u0435 \u0434\u0430 \u0438\u0437\u0442\u0440\u0438\u0435\u0442\u0435 \u043f\u043e\u0441\u043b\u0435\u0434\u043d\u0438\u044f \u0430\u0434\u043c\u0438\u043d\u0438\u0441\u0442\u0440\u0430\u0442\u043e\u0440", + "error.user.delete.lastUser": "Последният потребител не може да бъде изтрит", + "error.user.delete.permission": "\u041d\u0435 \u0435 \u043f\u043e\u0437\u0432\u043e\u043b\u0435\u043d\u043e \u0434\u0430 \u0438\u0437\u0442\u0440\u0438\u0432\u0430\u0442\u0435 \u0442\u043e\u0437\u0438 \u043f\u043e\u0442\u0440\u0435\u0431\u0438\u0442\u0435\u043b", + "error.user.duplicate": "Потребител с имейл \"{email}\" вече съществува", + "error.user.email.invalid": "Моля въведете валиден email адрес", + "error.user.language.invalid": "Моля въведете валиден език", + "error.user.notFound": "\u041f\u043e\u0442\u0440\u0435\u0431\u0438\u0442\u0435\u043b\u044f\u0442 \u043d\u0435 \u043c\u043e\u0436\u0435 \u0434\u0430 \u0431\u044a\u0434\u0435 \u043d\u0430\u043c\u0435\u0440\u0435\u043d.", + "error.user.password.invalid": "Моля въведете валидна парола. Тя трабва да съдържа поне 8 символа.", + "error.user.password.notSame": "\u041c\u043e\u043b\u044f, \u043f\u043e\u0442\u0432\u044a\u0440\u0434\u0435\u0442\u0435 \u043f\u0430\u0440\u043e\u043b\u0430\u0442\u0430", + "error.user.password.undefined": "Потребителят няма парола", + "error.user.password.wrong": "Wrong password", + "error.user.role.invalid": "Моля въведете валидна роля", + "error.user.update.permission": "Нямате права да обновите този потребител \"{name}\"", + + "error.validation.accepted": "Моля потвърдете", + "error.validation.alpha": "Моля въвдете символи измежду a-z", + "error.validation.alphanum": "Моля въвдете символи измежду a-z или цифри 0-9", + "error.validation.between": "Моля въведете стойност между \"{min}\" и \"{max}\"", + "error.validation.boolean": "Моля потвърдете или откажете", + "error.validation.contains": "Моля въведете стойност, която съдържа \"{needle}\"", + "error.validation.date": "Моля въведете валидна дата", + "error.validation.date.after": "Please enter a date after {date}", + "error.validation.date.before": "Please enter a date before {date}", + "error.validation.date.between": "Please enter a date between {min} and {max}", + "error.validation.denied": "Моля откажете", + "error.validation.different": "Стойността не трябва да е \"{other}\"", + "error.validation.email": "Моля въведете валиден email адрес", + "error.validation.endswith": "Стойността трябва да завършва с \"{end\"}", + "error.validation.filename": "Моля въведете валидно име на файла", + "error.validation.in": "Моля въведете едно от следните: ({in})", + "error.validation.integer": "Моля въведете валидно цяло число", + "error.validation.ip": "Моля въведете валиден IP адрес", + "error.validation.less": "Моля въведете стойност по-ниска от {max}", + "error.validation.match": "Стойността не съвпада с очаквания модел", + "error.validation.max": "Please enter a value equal to or lower than {max}", + "error.validation.maxlength": "Моля въведете по-къса стойност. (макс. {max} символа)", + "error.validation.maxwords": "Моля въведете не повече от {max} дума(и)", + "error.validation.min": "Please enter a value equal to or greater than {min}", + "error.validation.minlength": "Моля въведете по-дълга стойност. (мин. {min} символа)", + "error.validation.minwords": "Моля въведете поне {min} дума(и).", + "error.validation.more": "Моля въведете стойност по-висока от {min}", + "error.validation.notcontains": "Моля въведете стойност, която не съдържа \"{needle}\"", + "error.validation.notin": "Моля не въвеждайте нито едно от следните: ({notIn})", + "error.validation.option": "Моля изберете валидна опция", + "error.validation.num": "Моля въведете валидно число", + "error.validation.required": "Моля въведете нещо", + "error.validation.same": "Моля въведете \"{other}\"", + "error.validation.size": "Размерът на стойността трябва да бъде \"{size}\"", + "error.validation.startswith": "Стойността трябва да започва с \"{start}\"", + "error.validation.time": "Моля въведете валидно време", + "error.validation.time.after": "Please enter a time after {time}", + "error.validation.time.before": "Please enter a time before {time}", + "error.validation.time.between": "Please enter a time between {min} and {max}", + "error.validation.url": "Моля въведете валиден URL", + + "field.required": "The field is required", + "field.blocks.changeType": "Change type", + "field.blocks.code.name": "Код", + "field.blocks.code.language": "Език", + "field.blocks.code.placeholder": "Your code …", + "field.blocks.delete.confirm": "Do you really want to delete this block?", + "field.blocks.delete.confirm.all": "Do you really want to delete all blocks?", + "field.blocks.delete.confirm.selected": "Do you really want to delete the selected blocks?", + "field.blocks.empty": "No blocks yet", + "field.blocks.fieldsets.label": "Please select a block type …", + "field.blocks.gallery.name": "Gallery", + "field.blocks.gallery.images.empty": "No images yet", + "field.blocks.gallery.images.label": "Images", + "field.blocks.heading.level": "Level", + "field.blocks.heading.name": "Heading", + "field.blocks.heading.text": "Text", + "field.blocks.heading.placeholder": "Heading …", + "field.blocks.image.alt": "Alternative text", + "field.blocks.image.caption": "Caption", + "field.blocks.image.crop": "Crop", + "field.blocks.image.link": "Връзка", + "field.blocks.image.location": "Location", + "field.blocks.image.name": "Изображение", + "field.blocks.image.placeholder": "Select an image", + "field.blocks.image.ratio": "Ratio", + "field.blocks.image.url": "Image URL", + "field.blocks.list.name": "List", + "field.blocks.markdown.name": "Markdown", + "field.blocks.markdown.label": "Text", + "field.blocks.markdown.placeholder": "Markdown …", + "field.blocks.quote.name": "Quote", + "field.blocks.quote.text.label": "Text", + "field.blocks.quote.text.placeholder": "Quote …", + "field.blocks.quote.citation.label": "Citation", + "field.blocks.quote.citation.placeholder": "by …", + "field.blocks.text.name": "Text", + "field.blocks.text.placeholder": "Text …", + "field.blocks.video.caption": "Caption", + "field.blocks.video.name": "Video", + "field.blocks.video.placeholder": "Enter a video URL", + "field.blocks.video.url.label": "Video-URL", + "field.blocks.video.url.placeholder": "https://youtube.com/?v=", + + "field.files.empty": "Все още не са избрани файлове", + + "field.layout.delete": "Delete layout", + "field.layout.delete.confirm": "Do you really want to delete this layout?", + "field.layout.empty": "No rows yet", + "field.layout.select": "Select a layout", + + "field.pages.empty": "Все още не са избрани страници", + "field.structure.delete.confirm": "Сигурни ли сте, че искате да изтриете това вписване?", + "field.structure.empty": "Все още няма статии", + "field.users.empty": "Все още не са избрани потребители", + + "file.blueprint": "This file has no blueprint yet. You can define the setup in /site/blueprints/{template}.yml", + "file.delete.confirm": "Сигурни ли сте, че искате да изтриете
{filename}?", + "file.sort": "Change position", + + "files": "Файлове", + "files.empty": "Няма файлове", + + "hide": "Hide", + "hour": "Hour", + "insert": "\u0412\u043c\u044a\u043a\u043d\u0438", + "insert.after": "Insert after", + "insert.before": "Insert before", + "install": "Инсталирай", + + "installation": "Инсталация", + "installation.completed": "The panel has been installed", + "installation.disabled": "The panel installer is disabled on public servers by default. Please run the installer on a local machine or enable it with the panel.install option.", + "installation.issues.accounts": "Папката /site/accounts не съществува или не позволява запис", + "installation.issues.content": "Папката /content и всички файлове в нея трябва да позволяват запис", + "installation.issues.curl": "Изисква се CURL разширението", + "installation.issues.headline": "Панелът не може да бъде инсталиран", + "installation.issues.mbstring": "Изисква се разширението MB String", + "installation.issues.media": "Папката /media не съществува или няма права за запис", + "installation.issues.php": "Бъдете сигурни, че използвате PHP 7+", + "installation.issues.server": "Kirby изисква Apache, Nginx или Caddy", + "installation.issues.sessions": "The /site/sessions folder does not exist or is not writable", + + "language": "\u0415\u0437\u0438\u043a", + "language.code": "Код", + "language.convert": "Направи по подразбиране", + "language.convert.confirm": "

Сигурни ли сте, че искате да зададете {name} за език по подразбиране? Действието не може да бъде отменено.

В случай, че в {name} има непреведено съдържание, то части от сайта ви могат да останат празни.

", + "language.create": "Добавете нов език", + "language.delete.confirm": "Сигурни ли сте, че искате да изтриете език {name}, включително всички негови преводи? Действието не може да бъде отменено!", + "language.deleted": "Езикът беше изтрит", + "language.direction": "Посока на четене", + "language.direction.ltr": "Отляво надясно", + "language.direction.rtl": "Отдясно наляво", + "language.locale": "PHP locale string", + "language.locale.warning": "You are using a custom locale set up. Please modify it in the language file in /site/languages", + "language.name": "Име", + "language.updated": "Езикът беше обновен", + + "languages": "Езици", + "languages.default": "Език по подразбиране", + "languages.empty": "Все още няма добавени езици", + "languages.secondary": "Второстепенни езици", + "languages.secondary.empty": "Все още няма второстепенни езици", + + "license": "\u041b\u0438\u0446\u0435\u043d\u0437 \u0437\u0430 Kirby", + "license.buy": "Купи лиценз", + "license.register": "Регистрирай", + "license.register.help": "You received your license code after the purchase via email. Please copy and paste it to register.", + "license.register.label": "Please enter your license code", + "license.register.success": "Thank you for supporting Kirby", + "license.unregistered": "Това е нерегистрирана демо версия на Kirby", + + "link": "\u0412\u0440\u044a\u0437\u043a\u0430", + "link.text": "Текстова връзка", + + "loading": "Зареждане", + + "lock.unsaved": "Unsaved changes", + "lock.unsaved.empty": "There are no more unsaved changes", + "lock.isLocked": "Unsaved changes by {email}", + "lock.file.isLocked": "The file is currently being edited by {email} and cannot be changed.", + "lock.page.isLocked": "The page is currently being edited by {email} and cannot be changed.", + "lock.unlock": "Unlock", + "lock.isUnlocked": "Your unsaved changes have been overwritten by another user. You can download your changes to merge them manually.", + + "login": "Вход", + "login.code.label.login": "Login code", + "login.code.label.password-reset": "Password reset code", + "login.code.placeholder.email": "000 000", + "login.code.text.email": "If your email address is registered, the requested code was sent via email.", + "login.email.login.body": "Hi {user.nameOrEmail},\n\nYou recently requested a login code for the Kirby Panel.\nThe following login code will be valid for {timeout} minutes:\n\n{code}\n\nIf you did not request a login code, please ignore this email or contact your administrator if you have questions.\nFor security, please DO NOT forward this email.", + "login.email.login.subject": "Your login code", + "login.email.password-reset.body": "Hi {user.nameOrEmail},\n\nYou recently requested a password reset code for the Kirby Panel.\nThe following password reset code will be valid for {timeout} minutes:\n\n{code}\n\nIf you did not request a password reset code, please ignore this email or contact your administrator if you have questions.\nFor security, please DO NOT forward this email.", + "login.email.password-reset.subject": "Your password reset code", + "login.remember": "Keep me logged in", + "login.reset": "Reset password", + "login.toggleText.code.email": "Login via email", + "login.toggleText.code.email-password": "Login with password", + "login.toggleText.password-reset.email": "Forgot your password?", + "login.toggleText.password-reset.email-password": "← Back to login", + + "logout": "Изход", + + "menu": "Меню", + "meridiem": "AM/PM", + "mime": "Media Type", + "minutes": "Minutes", + + "month": "Month", + "months.april": "\u0410\u043f\u0440\u0438\u043b", + "months.august": "\u0410\u0432\u0433\u0443\u0441\u0442", + "months.december": "\u0414\u0435\u043a\u0435\u043c\u0432\u0440\u0438", + "months.february": "Февруари", + "months.january": "\u042f\u043d\u0443\u0430\u0440\u0438", + "months.july": "\u042e\u043b\u0438", + "months.june": "\u042e\u043d\u0438", + "months.march": "\u041c\u0430\u0440\u0442", + "months.may": "\u041c\u0430\u0439", + "months.november": "\u041d\u043e\u0435\u043c\u0432\u0440\u0438", + "months.october": "\u041e\u043a\u0442\u043e\u043c\u0432\u0440\u0438", + "months.september": "\u0421\u0435\u043f\u0442\u0435\u043c\u0432\u0440\u0438", + + "more": "Още", + "name": "Име", + "next": "Next", + "no": "no", + "off": "off", + "on": "on", + "open": "Отвори", + "open.newWindow": "Open in new window", + "options": "Options", + "options.none": "No options", + + "orientation": "Ориентация", + "orientation.landscape": "Пейзаж", + "orientation.portrait": "Портрет", + "orientation.square": "Квадрат", + + "page.blueprint": "This page has no blueprint yet. You can define the setup in /site/blueprints/{template}.yml", + "page.changeSlug": "\u041f\u0440\u043e\u043c\u0435\u043d\u0438 URL", + "page.changeSlug.fromTitle": "\u0421\u044a\u0437\u0434\u0430\u0439\u0442\u0435 \u043e\u0442 \u0437\u0430\u0433\u043b\u0430\u0432\u0438\u0435\u0442\u043e", + "page.changeStatus": "Промени статус", + "page.changeStatus.position": "Моля изберете позиция", + "page.changeStatus.select": "Изберете нов статус", + "page.changeTemplate": "Промени шаблон", + "page.delete.confirm": "Сигурни ли сте, че искате да изтриете {title}?", + "page.delete.confirm.subpages": "Тази страница има подстраници.
Всички подстраници също ще бъдат изтрити.", + "page.delete.confirm.title": "Въведи заглавие на страница за да потвърдиш", + "page.draft.create": "Създай чернова", + "page.duplicate.appendix": "Копирай", + "page.duplicate.files": "Copy files", + "page.duplicate.pages": "Copy pages", + "page.sort": "Change position", + "page.status": "Status", + "page.status.draft": "Чернова", + "page.status.draft.description": "The page is in draft mode and only visible for logged in editors or via secret link", + "page.status.listed": "Публично", + "page.status.listed.description": "Страницата е публична за всички", + "page.status.unlisted": "Скрит", + "page.status.unlisted.description": "Страницата е достъпна само чрез URL", + + "pages": "Страници", + "pages.empty": "Все още няма страници", + "pages.status.draft": "Drafts", + "pages.status.listed": "Published", + "pages.status.unlisted": "Скрит", + + "pagination.page": "Страница", + + "password": "\u041f\u0430\u0440\u043e\u043b\u0430", + "pixel": "Пиксел", + "prev": "Previous", + "preview": "Preview", + "remove": "Премахни", + "rename": "Преименувай", + "replace": "\u0417\u0430\u043c\u0435\u0441\u0442\u0438", + "retry": "\u041e\u043f\u0438\u0442\u0430\u0439 \u043f\u0430\u043a", + "revert": "\u041e\u0442\u043c\u0435\u043d\u0438", + "revert.confirm": "Do you really want to delete all unsaved changes?", + + "role": "\u0420\u043e\u043b\u044f", + "role.admin.description": "The admin has all rights", + "role.admin.title": "Admin", + "role.all": "Всички", + "role.empty": "Не съществуват потребители с тази роля", + "role.description.placeholder": "Липсва описание", + "role.nobody.description": "This is a fallback role without any permissions", + "role.nobody.title": "Nobody", + + "save": "\u0417\u0430\u043f\u0438\u0448\u0438", + "search": "Търси", + "search.min": "Enter {min} characters to search", + "search.all": "Show all", + "search.results.none": "No results", + + "section.required": "The section is required", + + "select": "Избери", + "settings": "Настройки", + "show": "Show", + "size": "Размер", + "slug": "URL-\u0434\u043e\u0431\u0430\u0432\u043a\u0430", + "sort": "Сортирай", + "title": "Заглавие", + "template": "Образец", + "today": "Днес", + + "site.blueprint": "The site has no blueprint yet. You can define the setup in /site/blueprints/site.yml", + + "toolbar.button.code": "Код", + "toolbar.button.bold": "\u041f\u043e\u043b\u0443\u0447\u0435\u0440 \u0448\u0440\u0438\u0444\u0442", + "toolbar.button.email": "Email", + "toolbar.button.headings": "Заглавия", + "toolbar.button.heading.1": "Заглавие 1", + "toolbar.button.heading.2": "Заглавие 2", + "toolbar.button.heading.3": "Заглавие 3", + "toolbar.button.italic": "\u041d\u0430\u043a\u043b\u043e\u043d\u0435\u043d \u0448\u0440\u0438\u0444\u0442", + "toolbar.button.file": "Файл", + "toolbar.button.file.select": "Select a file", + "toolbar.button.file.upload": "Upload a file", + "toolbar.button.link": "\u0412\u0440\u044a\u0437\u043a\u0430", + "toolbar.button.strike": "Strike-through", + "toolbar.button.ol": "Подреден списък", + "toolbar.button.underline": "Underline", + "toolbar.button.ul": "Списък", + + "translation.author": "Kirby екип", + "translation.direction": "ltr", + "translation.name": "Български", + "translation.locale": "bg_BG", + + "upload": "Прикачи", + "upload.error.cantMove": "The uploaded file could not be moved", + "upload.error.cantWrite": "Failed to write file to disk", + "upload.error.default": "The file could not be uploaded", + "upload.error.extension": "File upload stopped by extension", + "upload.error.formSize": "The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the form", + "upload.error.iniPostSize": "The uploaded file exceeds the post_max_size directive in php.ini", + "upload.error.iniSize": "The uploaded file exceeds the upload_max_filesize directive in php.ini", + "upload.error.noFile": "No file was uploaded", + "upload.error.noFiles": "No files were uploaded", + "upload.error.partial": "The uploaded file was only partially uploaded", + "upload.error.tmpDir": "Missing a temporary folder", + "upload.errors": "Грешка", + "upload.progress": "Uploading…", + + "url": "Url", + "url.placeholder": "https://example.com", + + "user": "Потребител", + "user.blueprint": "Можете да дефинирате допълнителни секции и полета на форми за тази потребителска роля в /site/blueprints/users/{role}.yml", + "user.changeEmail": "Промени email", + "user.changeLanguage": "Промени език", + "user.changeName": "Преименувай този потребител", + "user.changePassword": "Промени парола", + "user.changePassword.new": "Нова парола", + "user.changePassword.new.confirm": "Потвърдете новата парола...", + "user.changeRole": "Променете роля", + "user.changeRole.select": "Изберете нова роля", + "user.create": "Добавете нов потребител", + "user.delete": "Изтрийте потребителя", + "user.delete.confirm": "Сигурни ли сте, че искате да изтриете
{email}?", + + "users": "Потребители", + + "version": "\u0412\u0435\u0440\u0441\u0438\u044f \u043d\u0430 Kirby", + + "view.account": "\u0412\u0430\u0448\u0438\u044f \u0430\u043a\u0430\u0443\u043d\u0442", + "view.installation": "\u0418\u043d\u0441\u0442\u0430\u043b\u0430\u0446\u0438\u044f", + "view.resetPassword": "Reset password", + "view.settings": "Настройки", + "view.site": "Сайт", + "view.users": "\u041f\u043e\u0442\u0440\u0435\u0431\u0438\u0442\u0435\u043b\u0438", + + "welcome": "Добре дошли", + "year": "Year", + "yes": "yes" +} diff --git a/kirby/i18n/translations/ca.json b/kirby/i18n/translations/ca.json new file mode 100644 index 0000000..87911e0 --- /dev/null +++ b/kirby/i18n/translations/ca.json @@ -0,0 +1,527 @@ +{ + "add": "Afegir", + "avatar": "Imatge del perfil", + "back": "Tornar", + "cancel": "Cancel\u00b7lar", + "change": "Canviar", + "close": "Tancar", + "confirm": "Ok", + "collapse": "Col·lapsar", + "collapse.all": "Col·lapsar tot", + "copy": "Copiar", + "create": "Crear", + + "date": "Data", + "date.select": "Selecciona una data", + + "day": "Dia", + "days.fri": "dv.", + "days.mon": "dl.", + "days.sat": "ds.", + "days.sun": "dg.", + "days.thu": "dj.", + "days.tue": "dt.", + "days.wed": "dc.", + + "delete": "Eliminar", + "delete.all": "Eliminar tot", + "dimensions": "Dimensions", + "disabled": "Desactivat", + "discard": "Descartar", + "download": "Descarregar", + "duplicate": "Duplicar", + "edit": "Editar", + "expand": "Expandir", + "expand.all": "Expandir tot", + + "dialog.files.empty": "No hi ha cap fitxer per seleccionar", + "dialog.pages.empty": "No hi ha cap pàgina per seleccionar", + "dialog.users.empty": "No hi ha cap usuari per seleccionar", + + "email": "Email", + "email.placeholder": "mail@exemple.com", + + "error.access.code": "Codi invàlid", + "error.access.login": "Inici de sessió no vàlid", + "error.access.panel": "No tens permís per accedir al panell", + "error.access.view": "No tens accés a aquesta part del tauler", + + "error.avatar.create.fail": "No s'ha pogut carregar la imatge del perfil", + "error.avatar.delete.fail": "La imatge del perfil no s'ha pogut eliminar", + "error.avatar.dimensions.invalid": "Mantingueu l'amplada i l'alçada de la imatge de perfil de menys de 3000 píxels", + "error.avatar.mime.forbidden": "La imatge del perfil ha de ser fitxers JPEG o PNG", + + "error.blueprint.notFound": "No s'ha potgut carregar el blueprint \"{name}\"", + + "error.blocks.max.plural": "You must not add more than {max} blocks", + "error.blocks.max.singular": "You must not add more than one block", + "error.blocks.min.plural": "You must add at least {min} blocks", + "error.blocks.min.singular": "You must add at least one block", + "error.blocks.validation": "There's an error in block {index}", + + "error.email.preset.notFound": "No es pot trobar la configuració de correu electrònic \"{name}\"", + + "error.field.converter.invalid": "Convertidor no vàlid \"{converter}\"", + + "error.file.changeName.empty": "El nom no pot estar buit", + "error.file.changeName.permission": "No tens permís per canviar el nom de \"{filename}\"", + "error.file.duplicate": "Ja existeix un fitxer amb el nom \"{filename}\"", + "error.file.extension.forbidden": "L'extensió de l'arxiu \"{extension}\" no està permesa", + "error.file.extension.invalid": "Invalid extension: {extension}", + "error.file.extension.missing": "Falta l'extensió de l'arxiu \"{filename}\"", + "error.file.maxheight": "L'alçada de la imatge no ha de ser superior a {height} píxels", + "error.file.maxsize": "El fitxer és massa gran", + "error.file.maxwidth": "L'amplada de la imatge no ha de ser superior a {width} píxels", + "error.file.mime.differs": "L'arxiu carregat ha ha de ser del mateix tipus de mime \"{mime}\"", + "error.file.mime.forbidden": "El tipus de mitjà \"{mime}\" no està permès", + "error.file.mime.invalid": "Mime type no vàlid: {mime}", + "error.file.mime.missing": "El tipus de suport per a \"{filename}\" no es pot detectar", + "error.file.minheight": "L'alçada de la imatge ha de ser com a mínim de {height} píxels", + "error.file.minsize": "El fitxer és massa petit", + "error.file.minwidth": "L'amplada de la imatge ha de ser com a mínim de {width} píxels", + "error.file.name.missing": "El nom del fitxer no pot estar buit", + "error.file.notFound": "L'arxiu \"{filename}\" no s'ha trobat", + "error.file.orientation": "L’orientació de la imatge ha de ser \"{orientation}\"", + "error.file.type.forbidden": "No tens permís per penjar fitxers {type}", + "error.file.type.invalid": "Invalid file type: {type}", + "error.file.undefined": "L'arxiu no s'ha trobat", + + "error.form.incomplete": "Si us plau, corregeix els errors del formulari ...", + "error.form.notSaved": "No s'ha pogut desar el formulari", + + "error.language.code": "Introdueix un codi vàlid per a l’idioma", + "error.language.duplicate": "L'idioma ja existeix", + "error.language.name": "Introdueix un nom vàlid per a l'idioma", + + "error.layout.validation.block": "There's an error in block {blockIndex} in layout {layoutIndex}", + "error.layout.validation.settings": "There's an error in layout {index} settings", + + "error.license.format": "Introduïu una clau de llicència vàlida", + "error.license.email": "Si us plau, introdueix una adreça de correu electrònic vàlida", + "error.license.verification": "No s’ha pogut verificar la llicència", + + "error.page.changeSlug.permission": "No teniu permís per canviar l'apèndix d'URL per a \"{slug}\"", + "error.page.changeStatus.incomplete": "La pàgina té errors i no es pot publicar", + "error.page.changeStatus.permission": "No es pot canviar l'estat d'aquesta pàgina", + "error.page.changeStatus.toDraft.invalid": "La pàgina \"{slug}\" no es pot convertir en un esborrany", + "error.page.changeTemplate.invalid": "La plantilla per a la pàgina \"{slug}\" no es pot canviar", + "error.page.changeTemplate.permission": "No tens permís per canviar la plantilla per \"{slug}\"", + "error.page.changeTitle.empty": "El títol no pot estar buit", + "error.page.changeTitle.permission": "No tens permís per canviar el títol de \"{slug}\"", + "error.page.create.permission": "No tens permís per crear \"{slug}\"", + "error.page.delete": "La pàgina \"{slug}\" no es pot esborrar", + "error.page.delete.confirm": "Si us plau, introdueix el títol de la pàgina per confirmar", + "error.page.delete.hasChildren": "La pàgina té subpàgines i no es pot esborrar", + "error.page.delete.permission": "No tens permís per esborrar \"{slug}\"", + "error.page.draft.duplicate": "Ja existeix un esborrany de pàgina amb l'apèndix d'URL \"{slug}\"", + "error.page.duplicate": "Ja existeix una pàgina amb l'apèndix d'URL \"{slug}\"", + "error.page.duplicate.permission": "No tens permís per duplicar \"{slug}\"", + "error.page.notFound": "La pàgina \"{slug}\" no s'ha trobat", + "error.page.num.invalid": "Si us plau, introdueix un número d 'ordenació vàlid. Els números no poden ser negatius.", + "error.page.slug.invalid": "Please enter a valid URL appendix", + "error.page.slug.maxlength": "La longitud del nom ha de tenir menys de caràcters \"{length}\"", + "error.page.sort.permission": "La pàgina \"{slug}\" no es pot ordenar", + "error.page.status.invalid": "Si us plau, estableix un estat de pàgina vàlid", + "error.page.undefined": "La p\u00e0gina no s'ha trobat", + "error.page.update.permission": "No tens permís per actualitzar \"{slug}\"", + + "error.section.files.max.plural": "No has d'afegir més de {max} fitxers a la secció \"{section}\"", + "error.section.files.max.singular": "No podeu afegir més d'un fitxer a la secció \"{section}\"", + "error.section.files.min.plural": "La secció \"{section}\" requereix almenys {min} fitxer", + "error.section.files.min.singular": "La secció \"{section}\" requereix almenys un fitxer", + + "error.section.pages.max.plural": "No heu d'afegir més de {max} pàgines a la secció \"{section}\"", + "error.section.pages.max.singular": "No podeu afegir més d'una pàgina a la secció \"{section}\"", + "error.section.pages.min.plural": "La secció \"{section}\" requereix almenys {min} pàgines", + "error.section.pages.min.singular": "La secció \"{section}\" requereix almenys una pàgina", + + "error.section.notLoaded": "No s'ha pogut carregar la secció \"{name}\"", + "error.section.type.invalid": "La secció tipus \"{type}\" no és vàlida", + + "error.site.changeTitle.empty": "El títol no pot estar buit", + "error.site.changeTitle.permission": "No tens permís per canviar el títol del lloc web", + "error.site.update.permission": "No tens permís per actualitzar el lloc web", + + "error.template.default.notFound": "La plantilla predeterminada no existeix", + + "error.user.changeEmail.permission": "No tens permís per canviar el correu electrònic per a l'usuari \"{name}\"", + "error.user.changeLanguage.permission": "No tens permís per canviar l'idioma de l'usuari \"{name}\"", + "error.user.changeName.permission": "No tens permís per canviar el nom de l'usuari \"{name}\"", + "error.user.changePassword.permission": "No tens permís per canviar la contrasenya de l'usuari \"{name}\"", + "error.user.changeRole.lastAdmin": "El rol del darrer administrador no es pot canviar", + "error.user.changeRole.permission": "No tens permís per canviar el rol de l'usuari \"{name}\"", + "error.user.changeRole.toAdmin": "No tens permís per promocionar algú al rol d’administrador", + "error.user.create.permission": "No tens permís per crear aquest usuari", + "error.user.delete": "L'usuari \"{name}\" no es pot eliminar", + "error.user.delete.lastAdmin": "No es pot eliminar l'\u00faltim administrador", + "error.user.delete.lastUser": "El darrer usuari no es pot eliminar", + "error.user.delete.permission": "No pots eliminar l'usuari \"{name}\"", + "error.user.duplicate": "Ja existeix un usuari amb l'adreça electrònica \"{email}\"", + "error.user.email.invalid": "Si us plau, introdueix una adreça de correu electrònic vàlida", + "error.user.language.invalid": "Introduïu un idioma vàlid", + "error.user.notFound": "L'usuari \"{name}\" no s'ha trobat", + "error.user.password.invalid": "Introduïu una contrasenya vàlida. Les contrasenyes han de tenir com a mínim 8 caràcters.", + "error.user.password.notSame": "Les contrasenyes no coincideixen", + "error.user.password.undefined": "L'usuari no té una contrasenya", + "error.user.password.wrong": "Wrong password", + "error.user.role.invalid": "Si us plau, introdueix un rol vàlid", + "error.user.update.permission": "No tens permís per actualitzar l'usuari \"{name}\"", + + "error.validation.accepted": "Si us plau confirma", + "error.validation.alpha": "Si us plau, introdueix únicament caràcters entre a-z", + "error.validation.alphanum": "Si us plau, introdueix únicament caràcters entre a-z o números de 0-9", + "error.validation.between": "Introdueix un valor entre \"{min}\" i \"{max}\"", + "error.validation.boolean": "Si us plau confirma o denega", + "error.validation.contains": "Si us plau, introduïu un valor que contingui \"{needle}\"", + "error.validation.date": "Si us plau, introdueix una data vàlida", + "error.validation.date.after": "Introdueix una data posterior {date}", + "error.validation.date.before": "Introdueix una data anterior {date}", + "error.validation.date.between": "Introdueix una data entre {min} i {max}", + "error.validation.denied": "Si us plau, denegui", + "error.validation.different": "El valor no ha de ser \"{other}\"", + "error.validation.email": "Si us plau, introdueix una adreça de correu electrònic vàlida", + "error.validation.endswith": "El valor ha de finalitzar amb \"{end}\"", + "error.validation.filename": "Si us plau, introdueix un nom de fitxer vàlid", + "error.validation.in": "Si us plau, introduïu una de les opcions següents: ({in})", + "error.validation.integer": "Si us plau, introduïu un nombre enter vàlid", + "error.validation.ip": "Si us plau, introduïu una adreça IP vàlida", + "error.validation.less": "Si us plau, introduïu un valor inferior a {max}", + "error.validation.match": "El valor no coincideix amb el patró esperat", + "error.validation.max": "Si us plau, introduïu un valor igual o inferior a {max}", + "error.validation.maxlength": "Si us plau, introduïu un valor més curt. (màxim {max} caràcters)", + "error.validation.maxwords": "Si us plau, introduïu no més de {max} paraula(es)", + "error.validation.min": "Si us plau, introduïu un valor igual o superior a {min}", + "error.validation.minlength": "Si us plau, introduïu un valor més llarg. (min. {min} caràcters)", + "error.validation.minwords": "Si us plau, introduïu almenys {min} paraula(es)", + "error.validation.more": "Si us plau, introduïu un valor més gran que {min}", + "error.validation.notcontains": "Introduïu un valor que no contingui \"{needle}\"", + "error.validation.notin": "Si us plau, no introduïu cap d'aquests elements: ({notIn})", + "error.validation.option": "Si us plau, seleccioneu una opció vàlida", + "error.validation.num": "Si us plau, introduïu un número vàlid", + "error.validation.required": "Si us plau, introduïu alguna cosa", + "error.validation.same": "Si us plau, introduïu \"{other}\"", + "error.validation.size": "La mida del valor ha de ser \"{size}\"", + "error.validation.startswith": "El valor ha de començar amb \"{start}\"", + "error.validation.time": "Si us plau, introduïu una hora vàlida", + "error.validation.time.after": "Please enter a time after {time}", + "error.validation.time.before": "Please enter a time before {time}", + "error.validation.time.between": "Please enter a time between {min} and {max}", + "error.validation.url": "Si us plau, introduïu una URL vàlida", + + "field.required": "El camp és obligatori", + "field.blocks.changeType": "Change type", + "field.blocks.code.name": "Codi", + "field.blocks.code.language": "Idioma", + "field.blocks.code.placeholder": "Your code …", + "field.blocks.delete.confirm": "Do you really want to delete this block?", + "field.blocks.delete.confirm.all": "Do you really want to delete all blocks?", + "field.blocks.delete.confirm.selected": "Do you really want to delete the selected blocks?", + "field.blocks.empty": "No blocks yet", + "field.blocks.fieldsets.label": "Please select a block type …", + "field.blocks.gallery.name": "Gallery", + "field.blocks.gallery.images.empty": "No images yet", + "field.blocks.gallery.images.label": "Images", + "field.blocks.heading.level": "Level", + "field.blocks.heading.name": "Heading", + "field.blocks.heading.text": "Text", + "field.blocks.heading.placeholder": "Heading …", + "field.blocks.image.alt": "Alternative text", + "field.blocks.image.caption": "Caption", + "field.blocks.image.crop": "Crop", + "field.blocks.image.link": "Enllaç", + "field.blocks.image.location": "Location", + "field.blocks.image.name": "Imatge", + "field.blocks.image.placeholder": "Select an image", + "field.blocks.image.ratio": "Ratio", + "field.blocks.image.url": "Image URL", + "field.blocks.list.name": "List", + "field.blocks.markdown.name": "Markdown", + "field.blocks.markdown.label": "Text", + "field.blocks.markdown.placeholder": "Markdown …", + "field.blocks.quote.name": "Quote", + "field.blocks.quote.text.label": "Text", + "field.blocks.quote.text.placeholder": "Quote …", + "field.blocks.quote.citation.label": "Citation", + "field.blocks.quote.citation.placeholder": "by …", + "field.blocks.text.name": "Text", + "field.blocks.text.placeholder": "Text …", + "field.blocks.video.caption": "Caption", + "field.blocks.video.name": "Video", + "field.blocks.video.placeholder": "Enter a video URL", + "field.blocks.video.url.label": "Video-URL", + "field.blocks.video.url.placeholder": "https://youtube.com/?v=", + + "field.files.empty": "Encara no hi ha cap fitxer seleccionat", + + "field.layout.delete": "Delete layout", + "field.layout.delete.confirm": "Do you really want to delete this layout?", + "field.layout.empty": "No rows yet", + "field.layout.select": "Select a layout", + + "field.pages.empty": "Encara no s'ha seleccionat cap pàgina", + "field.structure.delete.confirm": "Segur que voleu eliminar aquesta fila?", + "field.structure.empty": "Encara no hi ha entrades.", + "field.users.empty": "Encara no s'ha seleccionat cap usuari", + + "file.blueprint": "This file has no blueprint yet. You can define the setup in /site/blueprints/{template}.yml", + "file.delete.confirm": "Esteu segurs d'eliminar
{filename}?", + "file.sort": "Change position", + + "files": "Arxius", + "files.empty": "Encara no hi ha fitxers", + + "hide": "Hide", + "hour": "Hora", + "insert": "Insertar", + "insert.after": "Insert after", + "insert.before": "Insert before", + "install": "Instal·lar", + + "installation": "Instal·lació", + "installation.completed": "S'ha instal·lat el panell", + "installation.disabled": "L'instal·lador del panell està desactivat per defecte als servidors públics. Si us plau, executeu l'instal·lador en una màquina local o habiliteu-lo amb l'opció panel.install", + "installation.issues.accounts": "La carpeta /site/accounts no existeix o no es pot escriure", + "installation.issues.content": "La carpeta /content no existeix o no es pot escriure", + "installation.issues.curl": "Es requereix l'extensió CURL", + "installation.issues.headline": "El panell no es pot instal·lar", + "installation.issues.mbstring": "Es requereix l'extensió de MB String", + "installation.issues.media": "La carpeta /media no existeix o no es pot escriure", + "installation.issues.php": "Assegureu-vos d'utilitzar PHP 7+", + "installation.issues.server": "Kirby requereix Apache, Nginx o Caddy", + "installation.issues.sessions": "La carpeta /site/sessions no existeix o no es pot escriure", + + "language": "Idioma", + "language.code": "Codi", + "language.convert": "Fer per defecte", + "language.convert.confirm": "

Segur que voleu convertir {name} a l'idioma predeterminat? Això no es pot desfer.

Si {name} té contingut no traduït, ja no podreu tornar enrere i algunes parts del vostre lloc poden quedar buides.

", + "language.create": "Afegir un nou idioma", + "language.delete.confirm": "Segur que voleu eliminar l'idioma {name} incloent totes les traduccions? Això no es pot desfer!", + "language.deleted": "S'ha suprimit l'idioma", + "language.direction": "Direcció de lectura", + "language.direction.ltr": "Esquerra a dreta", + "language.direction.rtl": "De dreta a esquerra", + "language.locale": "Cadena local de PHP", + "language.locale.warning": "S'està fent servir una configuració regional personalitzada. Modifica el fitxer d'idioma a /site/languages", + "language.name": "Nom", + "language.updated": "S'ha actualitzat l'idioma", + + "languages": "Idiomes", + "languages.default": "Idioma per defecte", + "languages.empty": "Encara no hi ha cap idioma", + "languages.secondary": "Idiomes secundaris", + "languages.secondary.empty": "Encara no hi ha idiomes secundaris", + + "license": "Llic\u00e8ncia Kirby", + "license.buy": "Comprar una llicència", + "license.register": "Registrar", + "license.register.help": "Heu rebut el codi de la vostra llicència després de la compra, per correu electrònic. Copieu-lo i enganxeu-lo per registrar-vos.", + "license.register.label": "Si us plau, introdueixi el seu codi de llicència", + "license.register.success": "Gràcies per donar suport a Kirby", + "license.unregistered": "Aquesta és una demo no registrada de Kirby", + + "link": "Enlla\u00e7", + "link.text": "Enllaç de text", + + "loading": "Carregant", + + "lock.unsaved": "Canvis no guardats", + "lock.unsaved.empty": "Ja no hi ha canvis no guardats", + "lock.isLocked": "Canvis no guardats per {email}", + "lock.file.isLocked": "El fitxer està sent editat actualment per {email} i no pot ser modificat.", + "lock.page.isLocked": "La pàgina està sent editat actualment per {email} i no pot ser modificat.", + "lock.unlock": "Desbloquejar", + "lock.isUnlocked": "Els teus canvis sense guardar han estat sobreescrits per a un altra usuario. Pots descarregar els teus canvis per combinar-los manualment.", + + "login": "Entrar", + "login.code.label.login": "Login code", + "login.code.label.password-reset": "Password reset code", + "login.code.placeholder.email": "000 000", + "login.code.text.email": "If your email address is registered, the requested code was sent via email.", + "login.email.login.body": "Hi {user.nameOrEmail},\n\nYou recently requested a login code for the Kirby Panel.\nThe following login code will be valid for {timeout} minutes:\n\n{code}\n\nIf you did not request a login code, please ignore this email or contact your administrator if you have questions.\nFor security, please DO NOT forward this email.", + "login.email.login.subject": "Your login code", + "login.email.password-reset.body": "Hi {user.nameOrEmail},\n\nYou recently requested a password reset code for the Kirby Panel.\nThe following password reset code will be valid for {timeout} minutes:\n\n{code}\n\nIf you did not request a password reset code, please ignore this email or contact your administrator if you have questions.\nFor security, please DO NOT forward this email.", + "login.email.password-reset.subject": "Your password reset code", + "login.remember": "Manten-me connectat", + "login.reset": "Reset password", + "login.toggleText.code.email": "Login via email", + "login.toggleText.code.email-password": "Login with password", + "login.toggleText.password-reset.email": "Forgot your password?", + "login.toggleText.password-reset.email-password": "← Back to login", + + "logout": "Tancar sessi\u00f3", + + "menu": "Menú", + "meridiem": "AM/PM", + "mime": "Tipus de mitjà", + "minutes": "Minuts", + + "month": "Mes", + "months.april": "Abril", + "months.august": "Agost", + "months.december": "Desembre", + "months.february": "Febrer", + "months.january": "Gener", + "months.july": "Juliol", + "months.june": "Juny", + "months.march": "Mar\u00e7", + "months.may": "Maig", + "months.november": "Novembre", + "months.october": "Octubre", + "months.september": "Setembre", + + "more": "Més", + "name": "Nom", + "next": "Següent", + "no": "no", + "off": "apagat", + "on": "encès", + "open": "Obrir", + "open.newWindow": "Open in new window", + "options": "Opcions", + "options.none": "Sense opcions", + + "orientation": "Orientació", + "orientation.landscape": "Horitzontal", + "orientation.portrait": "Vertical", + "orientation.square": "Quadrat", + + "page.blueprint": "This page has no blueprint yet. You can define the setup in /site/blueprints/{template}.yml", + "page.changeSlug": "Canviar URL", + "page.changeSlug.fromTitle": "Crear a partir del t\u00edtol", + "page.changeStatus": "Canviar l'estat", + "page.changeStatus.position": "Si us plau, seleccioneu una posició", + "page.changeStatus.select": "Seleccioneu un nou estat", + "page.changeTemplate": "Canviar la plantilla", + "page.delete.confirm": "Segur que voleu eliminar {title}?", + "page.delete.confirm.subpages": "Aquesta pàgina té subpàgines.
Totes les subpàgines també s'eliminaran.", + "page.delete.confirm.title": "Introduïu el títol de la pàgina per confirmar", + "page.draft.create": "Crear un esborrany", + "page.duplicate.appendix": "Copiar", + "page.duplicate.files": "Copiar fitxers", + "page.duplicate.pages": "Copiar pàgines", + "page.sort": "Change position", + "page.status": "Estat", + "page.status.draft": "Esborrany", + "page.status.draft.description": "La pàgina està en mode d'esborrany i només és visible per als editors registrats o a través d'un enllaç secret", + "page.status.listed": "Públic", + "page.status.listed.description": "La pàgina és pública per a tothom", + "page.status.unlisted": "Sense classificar", + "page.status.unlisted.description": "La pàgina només es pot accedir a través de l'URL", + + "pages": "Pàgines", + "pages.empty": "Encara no hi ha pàgines", + "pages.status.draft": "Esborranys", + "pages.status.listed": "Publicat", + "pages.status.unlisted": "Sense classificar", + + "pagination.page": "Pàgina", + + "password": "Contrasenya", + "pixel": "Pixel", + "prev": "Anterior", + "preview": "Preview", + "remove": "Eliminar", + "rename": "Canviar el nom", + "replace": "Reempla\u00e7ar", + "retry": "Reintentar", + "revert": "Revertir", + "revert.confirm": "Segur que voleu eliminar tots els canvis pendents desar?", + + "role": "Rol", + "role.admin.description": "L’administrador té tots els permisos", + "role.admin.title": "Administrador", + "role.all": "Tots", + "role.empty": "No hi ha usuaris amb aquest rol", + "role.description.placeholder": "Sense descripció", + "role.nobody.description": "Aquest és un rol per defecte sense permisos", + "role.nobody.title": "Ningú", + + "save": "Desar", + "search": "Cercar", + "search.min": "Introduïu {min} caràcters per cercar", + "search.all": "Mostrar tots", + "search.results.none": "Sense resultats", + + "section.required": "La secció és obligatòria", + + "select": "Seleccionar", + "settings": "Configuració", + "show": "Show", + "size": "Tamany", + "slug": "URL-ap\u00e8ndix", + "sort": "Ordenar", + "title": "Títol", + "template": "Plantilla", + "today": "Avui", + + "site.blueprint": "The site has no blueprint yet. You can define the setup in /site/blueprints/site.yml", + + "toolbar.button.code": "Codi", + "toolbar.button.bold": "Negreta", + "toolbar.button.email": "Email", + "toolbar.button.headings": "Encapçalaments", + "toolbar.button.heading.1": "Encapçalament 1", + "toolbar.button.heading.2": "Encapçalament 2", + "toolbar.button.heading.3": "Encapçalament 3", + "toolbar.button.italic": "Cursiva", + "toolbar.button.file": "Arxiu", + "toolbar.button.file.select": "Selecciona un fitxer", + "toolbar.button.file.upload": "Carrega un fitxer", + "toolbar.button.link": "Enlla\u00e7", + "toolbar.button.strike": "Strike-through", + "toolbar.button.ol": "Llista ordenada", + "toolbar.button.underline": "Underline", + "toolbar.button.ul": "Llista de vinyetes", + + "translation.author": "Equip Kirby", + "translation.direction": "ltr", + "translation.name": "Catalan", + "translation.locale": "ca_ES", + + "upload": "Carregar", + "upload.error.cantMove": "El fitxer carregat no s'ha pogut moure", + "upload.error.cantWrite": "No s'ha pogut escriure el fitxer al disc", + "upload.error.default": "No s'ha pogut carregar el fitxer", + "upload.error.extension": "La càrrega del fitxer s'ha aturat per l'extensió", + "upload.error.formSize": "El fitxer carregat supera la directiva MAX_FILE_SIZE especificada en el formulari", + "upload.error.iniPostSize": "El fitxer carregat supera la directiva post_max_size especifiada al php.ini", + "upload.error.iniSize": "El fitxer carregat supera la directiva upload_max_filesize especifiada al php.ini", + "upload.error.noFile": "No s'ha carregat cap fitxer", + "upload.error.noFiles": "No s'ha penjat cap fitxer", + "upload.error.partial": "El fitxer carregat només s'ha carregat parcialment", + "upload.error.tmpDir": "Falta una carpeta temporal", + "upload.errors": "Error", + "upload.progress": "Carregant...", + + "url": "Url", + "url.placeholder": "https://example.com", + + "user": "Usuari", + "user.blueprint": "Podeu definir seccions addicionals i camps de formulari per a aquest rol d'usuari a /site/blueprints/users/{role}.yml", + "user.changeEmail": "Canviar e-mail", + "user.changeLanguage": "Canviar idioma", + "user.changeName": "Canviar el nom d'aquest usuari", + "user.changePassword": "Canviar contrasenya", + "user.changePassword.new": "Nova contrasenya", + "user.changePassword.new.confirm": "Confirma la nova contrasenya ...", + "user.changeRole": "Canviar el rol", + "user.changeRole.select": "Seleccionar un nou rol", + "user.create": "Afegir un nou usuari", + "user.delete": "Eliminar aquest usuari", + "user.delete.confirm": "Segur que voleu eliminar
{email}?", + + "users": "Usuaris", + + "version": "Versi\u00f3 de Kirby", + + "view.account": "La teva compta", + "view.installation": "Instal·lació", + "view.resetPassword": "Reset password", + "view.settings": "Configuració", + "view.site": "Lloc web", + "view.users": "Usuaris", + + "welcome": "Benvinguda", + "year": "Any", + "yes": "yes" +} diff --git a/kirby/i18n/translations/cs.json b/kirby/i18n/translations/cs.json new file mode 100644 index 0000000..aecb896 --- /dev/null +++ b/kirby/i18n/translations/cs.json @@ -0,0 +1,527 @@ +{ + "add": "P\u0159idat", + "avatar": "Profilov\u00fd obr\u00e1zek", + "back": "Zpět", + "cancel": "Zru\u0161it", + "change": "Zm\u011bnit", + "close": "Zav\u0159it", + "confirm": "Ok", + "collapse": "Sbalit", + "collapse.all": "Sbalit vše", + "copy": "Kopírovat", + "create": "Vytvořit", + + "date": "Datum", + "date.select": "Vyberte datum", + + "day": "Den", + "days.fri": "p\u00e1", + "days.mon": "po", + "days.sat": "so", + "days.sun": "ne", + "days.thu": "\u010dt", + "days.tue": "\u00fat", + "days.wed": "st", + + "delete": "Smazat", + "delete.all": "Smazat vše", + "dimensions": "Rozměry", + "disabled": "Zakázáno", + "discard": "Zahodit", + "download": "Stáhnout", + "duplicate": "Duplikovat", + "edit": "Upravit", + "expand": "Rozbalit", + "expand.all": "Rozbalit vše", + + "dialog.files.empty": "Žádné soubory k výběru", + "dialog.pages.empty": "Žádné stránky k výběru", + "dialog.users.empty": "Žádní uživatelé k výběru", + + "email": "Email", + "email.placeholder": "mail@example.com", + + "error.access.code": "Neplatný kód", + "error.access.login": "Neplatné přihlášení", + "error.access.panel": "Nemáte oprávnění k přihlášení do panelu", + "error.access.view": "Nemáte oprávnění ke vstupu do této části panelu.", + + "error.avatar.create.fail": "Nebylo možné nahrát profilový obrázek", + "error.avatar.delete.fail": "Nebylo mo\u017en\u00e9 smazat profilov\u00fd obr\u00e1zek", + "error.avatar.dimensions.invalid": "Šířka a výška obrázku musí být pod 3000 pixelů", + "error.avatar.mime.forbidden": "Profilový obrázek musí být ve formátu JPEG nebo PNG", + + "error.blueprint.notFound": "Nelze načíst blueprint \"{name}\" ", + + "error.blocks.max.plural": "Nelze přidat více něž {max} bloků", + "error.blocks.max.singular": "Nelze přidat více než jeden blok", + "error.blocks.min.plural": "Musíte přidat alespoň {min} bloků", + "error.blocks.min.singular": "Musíte přidat alespoň jeden blok", + "error.blocks.validation": "Chyba v bloku {index}", + + "error.email.preset.notFound": "Nelze nalézt emailové přednastavení \"{name}\"", + + "error.field.converter.invalid": "Neplatný konvertor \"{converter}\"", + + "error.file.changeName.empty": "Toto jméno nesmí být prázdné", + "error.file.changeName.permission": "Nemáte povoleno změnit jméno souboru \"{filename}\"", + "error.file.duplicate": "Soubor s názvem \"{filename}\" již existuje", + "error.file.extension.forbidden": "Přípona souboru \"{extension}\" není povolena", + "error.file.extension.invalid": "Neplatná přípona souboru: {extension}", + "error.file.extension.missing": "Nem\u016f\u017eete nahr\u00e1t soubor bez p\u0159\u00edpony", + "error.file.maxheight": "Výška obrázku nesmí přesáhnout {height} pixelů", + "error.file.maxsize": "Soubor je příliš velký", + "error.file.maxwidth": "Šířka obrázku nesmí přesáhnout {width} pixelů", + "error.file.mime.differs": "Nahraný soubor musí být stejného typu \"{mime}\"", + "error.file.mime.forbidden": "Soubor typu \"{mime}\" není povolený", + "error.file.mime.invalid": "Neplatný MIME typ: {mime}", + "error.file.mime.missing": "Nelze rozeznat mime typ souboru \"{filename}\"", + "error.file.minheight": "Výška obrázku musí být alespoň {height} pixelů", + "error.file.minsize": "Soubor je příliš malý", + "error.file.minwidth": "Šířka obrázku musí být alespoň {width} pixelů", + "error.file.name.missing": "Název souboru nesmí být prázdný", + "error.file.notFound": "Soubor se nepoda\u0159ilo nal\u00e9zt", + "error.file.orientation": "Orientace obrázku másí být \"{orientation}\"", + "error.file.type.forbidden": "Nemáte povoleno nahrávat soubory typu {type} ", + "error.file.type.invalid": "Neplatný typ souboru: {type}", + "error.file.undefined": "Soubor se nepoda\u0159ilo nal\u00e9zt", + + "error.form.incomplete": "Prosím opravte všechny chyby ve formuláři", + "error.form.notSaved": "Formulář nemohl být uložen", + + "error.language.code": "Zadejte prosím platný kód jazyka", + "error.language.duplicate": "Jazyk již existuje", + "error.language.name": "Zadejte prosím platné jméno jazyka", + + "error.layout.validation.block": "Chyba v bloku {blockIndex} v rozvržení {layoutIndex}", + "error.layout.validation.settings": "Chyba v nastavení rozvržení {index}", + + "error.license.format": "Zadejte prosím platné licenční číslo", + "error.license.email": "Zadejte prosím platnou emailovou adresu", + "error.license.verification": "Licenci nelze ověřit", + + "error.page.changeSlug.permission": "Nem\u016f\u017eete zm\u011bnit URL t\u00e9to str\u00e1nky", + "error.page.changeStatus.incomplete": "Stránka obsahuje chyby a nemohla být zveřejněna", + "error.page.changeStatus.permission": "Status této stránky nelze změnit", + "error.page.changeStatus.toDraft.invalid": "Stránka \"{slug}\" nemůže být převedena na koncept", + "error.page.changeTemplate.invalid": "Šablonu stránky \"{slug}\" nelze změnit", + "error.page.changeTemplate.permission": "Nemáte dovoleno změnit šablonu stránky \"{slug}\"", + "error.page.changeTitle.empty": "Titulek nesmí být prázdný", + "error.page.changeTitle.permission": "Nemáte dovoleno změnit titulek stránky \"{slug}\"", + "error.page.create.permission": "Nemáte dovoleno vytvořit \"{slug}\"", + "error.page.delete": "Stránku \"{slug}\" nelze vymazat", + "error.page.delete.confirm": "Pro potvrzení prosím zadejte titulek stránky", + "error.page.delete.hasChildren": "Stránka má podstránky, nemůže být vymazána", + "error.page.delete.permission": "Nemáte dovoleno odstranit \"{slug}\"", + "error.page.draft.duplicate": "Koncept stránky, který obsahuje v adrese URL \"{slug}\" již existuje ", + "error.page.duplicate": "Stránka, která v adrese URL obsahuje \"{slug}\" již existuje", + "error.page.duplicate.permission": "Nemáte dovoleno duplikovat \"{slug}\"", + "error.page.notFound": "Str\u00e1nku se nepoda\u0159ilo nal\u00e9zt.", + "error.page.num.invalid": "Zadejte prosím platné pořadové číslo. Čísla nesmí být záporná.", + "error.page.slug.invalid": "Podtržení", + "error.page.slug.maxlength": "URL musí mít méně než \"{length}\" znaků", + "error.page.sort.permission": "Stránce \"{slug}\" nelze změnit pořadí", + "error.page.status.invalid": "Nastavte prosím platný status stránky", + "error.page.undefined": "Str\u00e1nku se nepoda\u0159ilo nal\u00e9zt.", + "error.page.update.permission": "Nemáte dovoleno upravit \"{slug}\"", + + "error.section.files.max.plural": "Sekce \"{section}\" nesmí obsahovat více jak {max} souborů", + "error.section.files.max.singular": "Sekce \"{section}\" může obsahovat nejvýše jeden soubor", + "error.section.files.min.plural": "Sekce \"{section}\" vyžaduje nejméně {min} souborů", + "error.section.files.min.singular": "Sekce \"{section}\" vyžaduje alespoň jeden soubor", + + "error.section.pages.max.plural": "Sekce \"{section}\" nesmí obsahovat více jak {max} stránek", + "error.section.pages.max.singular": "Sekce \"{section}\" může obsahovat nejvýše jednu stránku", + "error.section.pages.min.plural": "Sekce \"{section}\" vyžaduje alespoň {min} stránek", + "error.section.pages.min.singular": "Sekce \"{section}\" vyžaduje alespoň jednu stránku", + + "error.section.notLoaded": "Nelze načíst sekci \"{name}\"", + "error.section.type.invalid": "Typ sekce \"{type}\" není platný", + + "error.site.changeTitle.empty": "Titulek nesmí být prázdný", + "error.site.changeTitle.permission": "Nemáte dovoleno změnit titulek stránky", + "error.site.update.permission": "Nemáte dovoleno upravit stránku", + + "error.template.default.notFound": "Výchozí šablona neexistuje", + + "error.user.changeEmail.permission": "Nemáte dovoleno měnit email uživatele \"{name}\"", + "error.user.changeLanguage.permission": "Nemáte dovoleno změnit jazyk uživatele \"{name}\"", + "error.user.changeName.permission": "Nemáte dovoleno změnit jméno uživatele \"{name}\"", + "error.user.changePassword.permission": "Nemáte dovoleno změnit heslo uživatele \"{name}\"", + "error.user.changeRole.lastAdmin": "Role posledního administrátora nemůže být změněna", + "error.user.changeRole.permission": "Nemáte dovoleno změnit roli uživatele \"{name}\"", + "error.user.changeRole.toAdmin": "Nemáte dovoleno povýšit uživatele do role administrátora.", + "error.user.create.permission": "Nemáte dovoleno vytvořit tohoto uživatele", + "error.user.delete": "U\u017eivatel nemohl b\u00fdt smaz\u00e1n", + "error.user.delete.lastAdmin": "Nem\u016f\u017eete smazat posledn\u00edho administr\u00e1tora", + "error.user.delete.lastUser": "Poslední uživatel nemůže být smazán", + "error.user.delete.permission": "Nem\u00e1te dovoleno smazat tohoto u\u017eivatele", + "error.user.duplicate": "Uživatel s emailovou adresou \"{email}\" již existuje", + "error.user.email.invalid": "Zadejte prosím platnou emailovou adresu", + "error.user.language.invalid": "Zadejte prosím platný jazyk", + "error.user.notFound": "U\u017eivatele se nepoda\u0159ilo nal\u00e9zt", + "error.user.password.invalid": "Zadejte prosím platné heslo. Heslo musí být dlouhé alespoň 8 znaků.", + "error.user.password.notSame": "Pros\u00edm potvr\u010fte heslo", + "error.user.password.undefined": "Uživatel nemá nastavené heslo.", + "error.user.password.wrong": "Špatné heslo", + "error.user.role.invalid": "Zadejte prosím platnou roli", + "error.user.update.permission": "Nemáte dovoleno upravit uživatele \"{name}\"", + + "error.validation.accepted": "Potvrďte prosím", + "error.validation.alpha": "Zadávejte prosím pouze znaky v rozmezí a-z", + "error.validation.alphanum": "Zadávejte prosím pouze znaky v rozmezí a-z nebo čísla v rozmezí 0-9", + "error.validation.between": "Zadejte prosím hodnotu mez \"{min}\" a \"{max}\"", + "error.validation.boolean": "Potvrďte prosím, nebo odmítněte", + "error.validation.contains": "Zadejte prosím hodnotu, která obsahuje \"{needle}\"", + "error.validation.date": "Zadejte prosím platné datum", + "error.validation.date.after": "Zadejte prosím datum po {date}", + "error.validation.date.before": "Zadejte prosím datum před {date}", + "error.validation.date.between": "Zadejte prosím datum mezi {min} a {max}", + "error.validation.denied": "Prosím, odmítněte", + "error.validation.different": "Hodnota nesmí být \"{other}\"", + "error.validation.email": "Zadejte prosím platnou emailovou adresu", + "error.validation.endswith": "Hodnota nesmí končit \"{end}\"", + "error.validation.filename": "Zadejte prosím platný název souboru", + "error.validation.in": "Zadejte prosím některou z následujíích hodnot: ({in})", + "error.validation.integer": "Zadejte prosím platné celé číslo", + "error.validation.ip": "Zadejte prosím platnou IP adresu", + "error.validation.less": "Zadejte prosím hodnotu menší než {max}", + "error.validation.match": "Hodnota neodpovídá očekávanému vzoru", + "error.validation.max": "Zadejte prosím hodnotu rovnou, nebo menší než {max}", + "error.validation.maxlength": "Zadaná hodnota je příliš dlouhá. (Povoleno nejvýše {max} znaků)", + "error.validation.maxwords": "Nezadávejte prosím více jak {max} slov", + "error.validation.min": "Zadejte prosím hodnotu rovnou, nebo větší než {min}", + "error.validation.minlength": "Zadaná hodnota je příliš krátká. (Požadováno nejméně {min} znaků)", + "error.validation.minwords": "Zadejte prosím alespoň {min} slov", + "error.validation.more": "Zadejte prosím hodnotu větší než {min}", + "error.validation.notcontains": "Zadejte prosím hodnotu, která neobsahuje \"{needle}\"", + "error.validation.notin": "Nezadávejte prosím žádnou z následujíích hodnot: ({notIn})", + "error.validation.option": "Vyberte prosím platnou možnost", + "error.validation.num": "Zadejte prosím platné číslo", + "error.validation.required": "Zadejte prosím jakoukoli hodnotu", + "error.validation.same": "Zadejte prosím \"{other}\"", + "error.validation.size": "Velikost hodnoty musí být \"{size}\"", + "error.validation.startswith": "Hodnota musí začínat \"{start}\"", + "error.validation.time": "Zadejte prosím platný čas", + "error.validation.time.after": "Zadejte prosím čas po {time}", + "error.validation.time.before": "Zadejte prosím čas před {time}", + "error.validation.time.between": "Zadejte prosím čas v rozmezí od {min} do {max}", + "error.validation.url": "Zadejte prosím platnou adresu URL", + + "field.required": "Pole musí být vyplněno.", + "field.blocks.changeType": "Změnit typ", + "field.blocks.code.name": "Kód", + "field.blocks.code.language": "Jazyk", + "field.blocks.code.placeholder": "Váš kód …", + "field.blocks.delete.confirm": "Opravdu chcete smazat tento blok?", + "field.blocks.delete.confirm.all": "Opravdu chcete smazat všechny bloky?", + "field.blocks.delete.confirm.selected": "Opravdu chcete smazat vybrané bloky?", + "field.blocks.empty": "Zatím žádné bloky", + "field.blocks.fieldsets.label": "Vyberte prosím typ bloku …", + "field.blocks.gallery.name": "Galerie", + "field.blocks.gallery.images.empty": "Zatím žádné obrázky", + "field.blocks.gallery.images.label": "Obrázky", + "field.blocks.heading.level": "Úroveň", + "field.blocks.heading.name": "Nadpis", + "field.blocks.heading.text": "Text", + "field.blocks.heading.placeholder": "Nadpis …", + "field.blocks.image.alt": "Alternativní text", + "field.blocks.image.caption": "Titulek", + "field.blocks.image.crop": "Oříznout", + "field.blocks.image.link": "Odkaz", + "field.blocks.image.location": "Pozice", + "field.blocks.image.name": "Obrázek", + "field.blocks.image.placeholder": "Vyberte obrázek", + "field.blocks.image.ratio": "Poměr stran", + "field.blocks.image.url": "URL obrázku", + "field.blocks.list.name": "Seznam", + "field.blocks.markdown.name": "Markdown", + "field.blocks.markdown.label": "Text", + "field.blocks.markdown.placeholder": "Markdown …", + "field.blocks.quote.name": "Citát", + "field.blocks.quote.text.label": "Text", + "field.blocks.quote.text.placeholder": "Citát …", + "field.blocks.quote.citation.label": "Citace", + "field.blocks.quote.citation.placeholder": "od …", + "field.blocks.text.name": "Text", + "field.blocks.text.placeholder": "Text …", + "field.blocks.video.caption": "Titulek", + "field.blocks.video.name": "Video", + "field.blocks.video.placeholder": "Zadejte URL adresu videa", + "field.blocks.video.url.label": "URL adresa videa", + "field.blocks.video.url.placeholder": "https://youtube.com/?v=", + + "field.files.empty": "Nebyly zatím vybrány žádné soubory", + + "field.layout.delete": "Smazat rozložení", + "field.layout.delete.confirm": "Opravdu chcete smazat toto rozložení?", + "field.layout.empty": "Zatím žádné řádky", + "field.layout.select": "Vyberte rozložení", + + "field.pages.empty": "Nebyly zatím vybrány žádné stránky", + "field.structure.delete.confirm": "Opravdu chcete smazat tento z\u00e1znam?", + "field.structure.empty": "Zat\u00edm nejsou \u017e\u00e1dn\u00e9 z\u00e1znamy.", + "field.users.empty": "Nebyli zatím vybráni žádní uživatelé", + + "file.blueprint": "Tento typ souboru nemá blueprint. Blueprint můžete definovat v /site/blueprints/{template}.yml", + "file.delete.confirm": "Opravdu chcete smazat tento soubor?", + "file.sort": "Změnit pozici", + + "files": "Soubory", + "files.empty": "Zatím žádné soubory", + + "hide": "Skrýt", + "hour": "Hodina", + "insert": "Vlo\u017eit", + "insert.after": "Vložit za", + "insert.before": "Vložit před", + "install": "Instalovat", + + "installation": "Instalace", + "installation.completed": "Panel byl nainstalován", + "installation.disabled": "Instalátor panelu je ve výchozím nastavení na veřejných serverech zakázán. Spusťte prosím instalátor na lokálním počítači nebo jej povolte prostřednictvím panel.install.", + "installation.issues.accounts": "\/site\/accounts nen\u00ed zapisovateln\u00e9", + "installation.issues.content": "Slo\u017eka content a v\u0161echny soubory a slo\u017eky v n\u00ed mus\u00ed b\u00fdt zapisovateln\u00e9.", + "installation.issues.curl": "Je vyžadováno rozšířeníCURL", + "installation.issues.headline": "Panel nelze nainstalovat", + "installation.issues.mbstring": "Je vyžadováno rozšířeníMB String", + "installation.issues.media": "Složka/media neexistuje, nebo nemá povolený zápis", + "installation.issues.php": "Ujistěte se, že používátePHP 7+", + "installation.issues.server": "Kirby vyžadujeApache, Nginx neboCaddy", + "installation.issues.sessions": "Složka/site/sessions neexistuje, nebo nemá povolený zápis", + + "language": "Jazyk", + "language.code": "Kód", + "language.convert": "Nastavte výchozí možnost", + "language.convert.confirm": "

Opravdu chcete převést{name} na výchozí jazyk? Tuto volbu nelze vzít zpátky.

Pokud {name} obsahuje nepřeložený text, nebude již k dispozici záložní varianta a části stránky mohou zůstat prázdné.

", + "language.create": "Přidat nový jazyk", + "language.delete.confirm": "Opravdu chcete smazat jazyk {name} včetně všech překladů? Tuto volbu nelze vzít zpátky!", + "language.deleted": "Jazyk byl smazán", + "language.direction": "Směr čtení", + "language.direction.ltr": "Zleva doprava", + "language.direction.rtl": "Zprava doleva", + "language.locale": "Řetězec lokalizace PHP", + "language.locale.warning": "Používáte vlastní jazykové nastavení. Upravte prosím soubor s nastavením v /site/languages", + "language.name": "Jméno", + "language.updated": "Jazyk byl aktualizován", + + "languages": "Jazyky", + "languages.default": "Výchozí jazyk", + "languages.empty": "Zatím neexistují žádné jazyky", + "languages.secondary": "Další jazyky", + "languages.secondary.empty": "Neexistují zatím žádné další jazyky", + + "license": "Kirby licence", + "license.buy": "Zakoupit licenci", + "license.register": "Registrovat", + "license.register.help": "Licenční kód jste po zakoupení obdrželi na email. Vložte prosím kód a zaregistrujte Vaší kopii.", + "license.register.label": "Zadejte prosím licenční kód", + "license.register.success": "Děkujeme Vám za podporu Kirby", + "license.unregistered": "Toto je neregistrovaná kopie Kirby", + + "link": "Odkaz", + "link.text": "Text odkazu", + + "loading": "Načítám", + + "lock.unsaved": "Neuložené změny", + "lock.unsaved.empty": "Nezbývají již žádné neuložené změny.", + "lock.isLocked": "Neuložené změny provedené {email}", + "lock.file.isLocked": "Soubor nelze změnit, právě jej upravuje {email}.", + "lock.page.isLocked": "Stránku nelze změnit, právě jí upravuje {email} .", + "lock.unlock": "Odemknout", + "lock.isUnlocked": "Vaše neuložené změny byly přepsány jiným uživatelem. Můžeze si své úpravy stáhnout a zapracovat je ručně.", + + "login": "P\u0159ihl\u00e1sit se", + "login.code.label.login": "Kód pro přihlášení", + "login.code.label.password-reset": "Kód pro resetování hesla", + "login.code.placeholder.email": "000 000", + "login.code.text.email": "Vaše e-mailová adresa byla zaregistrována, kód byl odeslán do Vaší e-mailové schránky.", + "login.email.login.body": "Ahoj {user.nameOrEmail},\n\nV nedávné době jsi zažádal(a) o kód pro přihlášení do Kirby Panelu.\nNásledující kód pro přihlášení je platný {timeout} minut:\n\n{code}\n\nPokud jsi o kód pro přihlášení nežádal(a), tuto zprávu prosím ignoruj a v případě dotazů prosím kontaktuj svého administrátora.\nZ bezpečnostních důvodů prosím tuto zprávu nepřeposílej.", + "login.email.login.subject": "Váš kód pro přihlášení", + "login.email.password-reset.body": "Ahoj {user.nameOrEmail},\n\nV nedávné době jsi zažádal(a) o kód pro přihlášení do Kirby Panelu.\nNásledující kód pro přihlášení je platný {timeout} minut:\n\n{code}\n\nPokud jsi o kód pro přihlášení nežádal(a), tuto zprávu prosím ignoruj a v případě dotazů prosím kontaktuj svého administrátora.\nZ bezpečnostních důvodů prosím tuto zprávu nepřeposílej.", + "login.email.password-reset.subject": "Váš kód pro resetování hesla", + "login.remember": "Zůstat přihlášen", + "login.reset": "Resetovat heslo", + "login.toggleText.code.email": "Přihlásit se pomocí e-mailu", + "login.toggleText.code.email-password": "Přihlásit se pomocí hesla", + "login.toggleText.password-reset.email": "Zapomenuté heslo?", + "login.toggleText.password-reset.email-password": "← Zpět na přihlášení", + + "logout": "Odhl\u00e1sit se", + + "menu": "Menu", + "meridiem": "AM/PM", + "mime": "Typ média", + "minutes": "Minuty", + + "month": "Měsíc", + "months.april": "Duben", + "months.august": "Srpen", + "months.december": "Prosinec", + "months.february": "Únor", + "months.january": "Leden", + "months.july": "\u010cervenec", + "months.june": "\u010cerven", + "months.march": "B\u0159ezen", + "months.may": "Kv\u011bten", + "months.november": "Listopad", + "months.october": "\u0158\u00edjen", + "months.september": "Z\u00e1\u0159\u00ed", + + "more": "Více", + "name": "Jméno", + "next": "Další", + "no": "ne", + "off": "vypnuto", + "on": "zapnuto", + "open": "Otevřít", + "open.newWindow": "Otevřít v novém okně", + "options": "Možnosti", + "options.none": "Žádné možnosti", + + "orientation": "Orientace", + "orientation.landscape": "Na šířku", + "orientation.portrait": "Na výšku", + "orientation.square": "Čtverec", + + "page.blueprint": "Tento typ stránky nemá blueprint. Blueprint můžete definovat v /site/blueprints/{template}.yml", + "page.changeSlug": "Zm\u011bnit URL", + "page.changeSlug.fromTitle": "Vytvo\u0159it z n\u00e1zvu", + "page.changeStatus": "Změnit status", + "page.changeStatus.position": "Vyberte prosím pozici", + "page.changeStatus.select": "Vybrat nový status", + "page.changeTemplate": "Změnit šablonu", + "page.delete.confirm": "Opravdu chcete smazat tuto str\u00e1nku?", + "page.delete.confirm.subpages": "Tato stránka má podstránky.
Všechny podstránky budou vymazány.", + "page.delete.confirm.title": "Pro potvrzení zadejte titulek stránky", + "page.draft.create": "Vytvořit koncept", + "page.duplicate.appendix": "Kopírovat", + "page.duplicate.files": "Kopírovat soubory", + "page.duplicate.pages": "Kopírovat stránky", + "page.sort": "Změnit pozici", + "page.status": "Stav", + "page.status.draft": "Koncept", + "page.status.draft.description": "Stránka je ve stavu konceptu a je viditelná pouze pro přihlášené editory, nebo přes tajný odkaz", + "page.status.listed": "Veřejná", + "page.status.listed.description": "Stránka je zveřejněná pro všechny", + "page.status.unlisted": "Neveřejná", + "page.status.unlisted.description": "Tato stránka je dostupná pouze přes URL.", + + "pages": "Stránky", + "pages.empty": "Zatím žádné stránky", + "pages.status.draft": "Koncepty", + "pages.status.listed": "Zveřejněno", + "pages.status.unlisted": "Neveřejná", + + "pagination.page": "Stránka", + + "password": "Heslo", + "pixel": "Pixel", + "prev": "Předchozí", + "preview": "Náhled", + "remove": "Odstranit", + "rename": "Přejmenovat", + "replace": "Nahradit", + "retry": "Zkusit znovu", + "revert": "Zahodit", + "revert.confirm": "Opravdu chcete smazat všechny provedené změny?", + + "role": "Role", + "role.admin.description": "Administrátor má všechna práva", + "role.admin.title": "Administrátor", + "role.all": "Vše", + "role.empty": "Neexistují uživatelé s touto rolí", + "role.description.placeholder": "Žádný popis", + "role.nobody.description": "Toto je výchozí role bez jakýchkoli oprávnění", + "role.nobody.title": "Nikdo", + + "save": "Ulo\u017eit", + "search": "Hledat", + "search.min": "Pro vyhledání zadejte alespoň {min} znaky", + "search.all": "Zobrazit vše", + "search.results.none": "Žádné výsledky", + + "section.required": "Sekce musí být vyplněna", + + "select": "Vybrat", + "settings": "Nastavení", + "show": "Zobrazit", + "size": "Velikost", + "slug": "P\u0159\u00edpona URL", + "sort": "Řadit", + "title": "Název", + "template": "\u0160ablona", + "today": "Dnes", + + "site.blueprint": "Hlavní panel nemá blueprint. Blueprint můžete definovat v /site/blueprints/site.yml", + + "toolbar.button.code": "Kód", + "toolbar.button.bold": "Tu\u010dn\u00fd text", + "toolbar.button.email": "Email", + "toolbar.button.headings": "Nadpisy", + "toolbar.button.heading.1": "Nadpis 1", + "toolbar.button.heading.2": "Nadpis 2", + "toolbar.button.heading.3": "Nadpis 3", + "toolbar.button.italic": "Kurz\u00edva", + "toolbar.button.file": "Soubor", + "toolbar.button.file.select": "Vyberte soubor", + "toolbar.button.file.upload": "Nahrajte soubor", + "toolbar.button.link": "Odkaz", + "toolbar.button.strike": "Přeškrtnutí", + "toolbar.button.ol": "Číslovaný seznam", + "toolbar.button.underline": "Podtržení", + "toolbar.button.ul": "Odrážkový seznam", + + "translation.author": "Kirby tým", + "translation.direction": "ltr", + "translation.name": "\u010cesky", + "translation.locale": "cs_CZ", + + "upload": "Nahrát", + "upload.error.cantMove": "Nahraný soubor nemohl být přesunut", + "upload.error.cantWrite": "Zápis souboru na disk se nezdařil", + "upload.error.default": "Soubor se nepodařilo nahrát", + "upload.error.extension": "Nahrávání souboru přerušeno rozšířením.", + "upload.error.formSize": "Velikost nahrávaného souboru převyšuje omezení stanovené direktivou MAX_FILE_SIZE", + "upload.error.iniPostSize": "Velikost nahrávaného souboru převyšuje omezení stanovené direktivou post_max_size, která je nastavena v php.ini", + "upload.error.iniSize": "Velikost nahrávaného souboru převyšuje omezení stanovené direktivou upload_max_filesize, která je nastavena v php.ini ", + "upload.error.noFile": "Nebyl nahrán žádný soubor", + "upload.error.noFiles": "Nebyly nahrány žádné soubory", + "upload.error.partial": "Soubor byl nahrán pouze z části", + "upload.error.tmpDir": "Chybí dočasná složka", + "upload.errors": "Chyba", + "upload.progress": "Nahrávání...", + + "url": "Url", + "url.placeholder": "https://example.com", + + "user": "Uživatel", + "user.blueprint": "Pro tuto uživatelskou roli můžete definovat další sekce a pole v /site/blueprints/users/{role}.yml", + "user.changeEmail": "Změnit email", + "user.changeLanguage": "Změnit jazyk", + "user.changeName": "Přejmenovat tohoto uživatele", + "user.changePassword": "Změnit heslo", + "user.changePassword.new": "Nové heslo", + "user.changePassword.new.confirm": "Potvrdit nové heslo...", + "user.changeRole": "Změnit roli", + "user.changeRole.select": "Vybrat novou roli", + "user.create": "Přidat nového uživatele", + "user.delete": "Smazat tohoto uživatele", + "user.delete.confirm": "Opravdu chcete smazat tohoto u\u017eivatele?", + + "users": "Uživatelé", + + "version": "Verze Kirby", + + "view.account": "V\u00e1\u0161 \u00fa\u010det", + "view.installation": "Instalace", + "view.resetPassword": "Resetovat heslo", + "view.settings": "Nastavení", + "view.site": "Stránka", + "view.users": "U\u017eivatel\u00e9", + + "welcome": "Vítejte", + "year": "Rok", + "yes": "ano" +} diff --git a/kirby/i18n/translations/da.json b/kirby/i18n/translations/da.json new file mode 100644 index 0000000..b40d2e1 --- /dev/null +++ b/kirby/i18n/translations/da.json @@ -0,0 +1,527 @@ +{ + "add": "Ny", + "avatar": "Profilbillede", + "back": "Tilbage", + "cancel": "Annuller", + "change": "\u00c6ndre", + "close": "Luk", + "confirm": "Gem", + "collapse": "Fold sammen", + "collapse.all": "Fold alle sammen", + "copy": "Kopier", + "create": "Opret", + + "date": "Dato", + "date.select": "Vælg en dato", + + "day": "Dag", + "days.fri": "Fre", + "days.mon": "Man", + "days.sat": "L\u00f8r", + "days.sun": "S\u00f8n", + "days.thu": "Tor", + "days.tue": "Tir", + "days.wed": "Ons", + + "delete": "Slet", + "delete.all": "Slet alle", + "dimensions": "Dimensioner", + "disabled": "Deaktiveret", + "discard": "Kass\u00e9r", + "download": "Download", + "duplicate": "Dupliker", + "edit": "Rediger", + "expand": "Fold ud", + "expand.all": "Fold alle ud", + + "dialog.files.empty": "Ingen filer kan vælges", + "dialog.pages.empty": "Ingen sider kan vælges", + "dialog.users.empty": "Ingen brugere kan vælges", + + "email": "Email", + "email.placeholder": "mail@eksempel.dk", + + "error.access.code": "Ugyldig kode", + "error.access.login": "Ugyldigt log ind", + "error.access.panel": "Du har ikke adgang til panelet", + "error.access.view": "Du har ikke adgang til denne del af panelet", + + "error.avatar.create.fail": "Profilbilledet kunne blev ikke uploadet ", + "error.avatar.delete.fail": "Profilbilledet kunne ikke slettes", + "error.avatar.dimensions.invalid": "Hold venligst bredte og højde på billedet under 3000 pixels", + "error.avatar.mime.forbidden": "Uacceptabel fil-type", + + "error.blueprint.notFound": "Blueprint \"{name}\" kunne ikke indlæses", + + "error.blocks.max.plural": "Du må ikke tilføje flere end {max} blokke", + "error.blocks.max.singular": "Du må ikke tilføje mere end een blok", + "error.blocks.min.plural": "Du skal tilføje minimum {min} blokke", + "error.blocks.min.singular": "Du skal tilføje minimum een blok", + "error.blocks.validation": "Der er fejl i blok {index}", + + "error.email.preset.notFound": "Email preset \"{name}\" findes ikke", + + "error.field.converter.invalid": "Ugyldig converter \"{converter}\"", + + "error.file.changeName.empty": "Navn kan ikke efterlades tomt", + "error.file.changeName.permission": "Du har ikke tilladelse til at ændre navnet på filen \"{filename}\"", + "error.file.duplicate": "En fil med navnet \"{filename}\" eksisterer allerede", + "error.file.extension.forbidden": "Uacceptabel fil-endelse", + "error.file.extension.invalid": "Ugyldig endelse: {extension}", + "error.file.extension.missing": "Du kan ikke uploade filer uden fil-endelse", + "error.file.maxheight": "Højden på billedet af billedet må ikke være større end {height} pixels", + "error.file.maxsize": "Filen er for stor", + "error.file.maxwidth": "Bredden af billedet må ikke være større end {width} pixels", + "error.file.mime.differs": "Den uploadede fil skal være af samme mime type \"{mime}\"", + "error.file.mime.forbidden": "Media typen \"{mime}\" er ikke tilladt", + "error.file.mime.invalid": "Ugyldig mime type: {mime}", + "error.file.mime.missing": "Media typen for \"{filename}\" kan ikke bestemmes", + "error.file.minheight": "Højden af billedet skal mindst være {height} pixels", + "error.file.minsize": "Filen er for lille", + "error.file.minwidth": "Bredden af billedet skal mindst være {width} pixels", + "error.file.name.missing": "Filnavn må ikke være tomt", + "error.file.notFound": "Filen kunne ikke findes", + "error.file.orientation": "Formatet på billedet skal være \"{orientation}\"", + "error.file.type.forbidden": "Du har ikke tilladelse til at uploade {type} filer", + "error.file.type.invalid": "Ugyldig filtype: {type}", + "error.file.undefined": "Filen kunne ikke findes", + + "error.form.incomplete": "Ret venligst alle fejl i formularen...", + "error.form.notSaved": "Formularen kunne ikke gemmes", + + "error.language.code": "Indtast venligst en gyldig kode for sproget", + "error.language.duplicate": "Sproget eksisterer allerede", + "error.language.name": "Indtast venligst et gyldigt navn for sproget", + + "error.layout.validation.block": "Der er fejl i blok {blockIndex} i layout {layoutIndex}", + "error.layout.validation.settings": "Der er fejl i layout {index} indstillinger", + + "error.license.format": "Indtast venligst en gyldig licensnøgle", + "error.license.email": "Indtast venligst en gyldig email adresse", + "error.license.verification": "Licensen kunne ikke verificeres", + + "error.page.changeSlug.permission": "Du kan ikke ændre URL-endelse for \"{slug}\"", + "error.page.changeStatus.incomplete": "Siden indeholder fejl og kan derfor ikke udgives", + "error.page.changeStatus.permission": "Status for denne side kan ikke ændres", + "error.page.changeStatus.toDraft.invalid": "Siden \"{slug}\" kan ikke konverteres om til en kladde", + "error.page.changeTemplate.invalid": "Skabelonen for siden \"{slug}\" kan ikke ændres", + "error.page.changeTemplate.permission": "Du har ikke tilladelse til at ændre skabelonen for \"{slug}\"", + "error.page.changeTitle.empty": "Titlen kan ikke være tom", + "error.page.changeTitle.permission": "Du har ikke tilladelse til at ændre titlen for \"{slug}\"", + "error.page.create.permission": "Du har ikke tilladelse til at oprette \"{slug}\"", + "error.page.delete": "Siden \"{slug}\" kan ikke slettes", + "error.page.delete.confirm": "Indtast venligst sidens titel for at bekræfte", + "error.page.delete.hasChildren": "Siden har unsersider og kan derfor ikke slettes", + "error.page.delete.permission": "Du har ikke tilladelse til at slette \"{slug}\"", + "error.page.draft.duplicate": "En sidekladde med URL-endelsen \"{slug}\" eksisterer allerede", + "error.page.duplicate": "En side med URL-endelsen \"{slug}\" eksisterer allerede", + "error.page.duplicate.permission": "Du har ikke mulighed for at duplikere \"{slug}\"", + "error.page.notFound": "Siden kunne ikke findes", + "error.page.num.invalid": "Indtast venligst et gyldigt sorteringsnummer. Nummeret kan ikke være negativt.", + "error.page.slug.invalid": "Please enter a valid URL appendix", + "error.page.slug.maxlength": "Navnet skal være kortere end \"{length}\" tegn", + "error.page.sort.permission": "Siden \"{slug}\" kan ikke sorteres", + "error.page.status.invalid": "Sæt venligst en gyldig status for siden", + "error.page.undefined": "Siden kunne ikke findes", + "error.page.update.permission": "Du har ikke tilladelse til at opdatere \"{slug}\"", + + "error.section.files.max.plural": "Du kan ikk tilføje mere end {max} filer til \"{section}\" sektionen", + "error.section.files.max.singular": "Du kan ikke tilføje mere end een fil til \"{section}\" sektionen", + "error.section.files.min.plural": "Sektionen \"{section}\" kræver mindst {min} filer", + "error.section.files.min.singular": "Sektionen \"{section}\" kræver mindst een fil", + + "error.section.pages.max.plural": "Du kan ikke tilføje flere end {max} sider til \"{section}\" sektionen", + "error.section.pages.max.singular": "Du kan ikke tilføje mere end een side til \"{section}\" sektionen", + "error.section.pages.min.plural": "Sektionen \"{section}\" kræver mindst {min} sider", + "error.section.pages.min.singular": "Sektionen \"{section}\" kræver mindst een side", + + "error.section.notLoaded": "Sektionen \"{section}\" kunne ikke indlæses", + "error.section.type.invalid": "Sektionstypen \"{type}\" er ikke gyldig", + + "error.site.changeTitle.empty": "Titlen kan ikke være tom", + "error.site.changeTitle.permission": "Du har ikke tilladelse til at ændre titlen på sitet", + "error.site.update.permission": "Du har ikke tilladelse til at opdatere sitet", + + "error.template.default.notFound": "Standardskabelonen eksisterer ikke", + + "error.user.changeEmail.permission": "Du har ikke tilladelse til at ændre emailen for brugeren \"{name}\"", + "error.user.changeLanguage.permission": "Du har ikke tilladelse til at ændre sproget for brugeren \"{name}\"", + "error.user.changeName.permission": "Du har ikke tilladelse til at ændre navn på brugeren \"{name}\"", + "error.user.changePassword.permission": "Du har ikke tilladelse til at ændre adgangskoden for brugeren \"{name}\"", + "error.user.changeRole.lastAdmin": "Rollen for den sidste admin kan ikke ændres", + "error.user.changeRole.permission": "Du har ikke tilladelse til at ændre rollen for brugeren \"{name}\"", + "error.user.changeRole.toAdmin": "Du har ikke tilladelse til at tildele nogen admin rollen", + "error.user.create.permission": "Du har ikke tilladelse til at oprette denne bruger", + "error.user.delete": "Brugeren kunne ikke slettes", + "error.user.delete.lastAdmin": "Du kan ikke slette den sidste admin", + "error.user.delete.lastUser": "Den sidste bruger kan ikke slettes", + "error.user.delete.permission": "Du har ikke tilladelse til at slette denne bruger", + "error.user.duplicate": "En bruger med email adresse \"{email}\" eksisterer allerede", + "error.user.email.invalid": "Indtast venligst en gyldig email adresse", + "error.user.language.invalid": "Indtast venligst et gyldigt sprog", + "error.user.notFound": "Brugeren kunne ikke findes", + "error.user.password.invalid": "Indtast venligst en gyldig adgangskode. Adgangskoder skal minimum være 8 tegn lange.", + "error.user.password.notSame": "Bekr\u00e6ft venligst adgangskoden", + "error.user.password.undefined": "Brugeren har ikke en adgangskode", + "error.user.password.wrong": "Wrong password", + "error.user.role.invalid": "Indtast venligst en gyldig rolle", + "error.user.update.permission": "Du har ikke tilladelse til at opdatere brugeren \"{name}\"", + + "error.validation.accepted": "Bekræft venligst", + "error.validation.alpha": "Indtast venligst kun bogstaver imellem a-z", + "error.validation.alphanum": "Indtast venligst kun bogstaver og tal imellem a-z eller 0-9", + "error.validation.between": "Indtast venligst en værdi imellem \"{min}\" og \"{max}\"", + "error.validation.boolean": "Venligst bekræft eller afvis", + "error.validation.contains": "Indtast venligst en værdi der indeholder \"{needle}\"", + "error.validation.date": "Indtast venligst en gyldig dato", + "error.validation.date.after": "Indtast venligst en dato efter {date}", + "error.validation.date.before": "Indtast venligst en dato før {date}", + "error.validation.date.between": "Indtast venligst en dato imellem {min} og {max}", + "error.validation.denied": "Venligst afvis", + "error.validation.different": "Værdien må ikke være \"{other}\"", + "error.validation.email": "Indtast venligst en gyldig email adresse", + "error.validation.endswith": "Værdi skal ende med \"{end}\"", + "error.validation.filename": "Indtast venligst et gyldigt filnavn", + "error.validation.in": "Indtast venligst en af følgende: ({in})", + "error.validation.integer": "Indtast et gyldigt tal", + "error.validation.ip": "Indtast en gyldig IP adresse", + "error.validation.less": "Indtast venligst en værdi mindre end {max}", + "error.validation.match": "Værdien matcher ikke det forventede mønster", + "error.validation.max": "Indtast venligst en værdi lig med eller lavere end {max}", + "error.validation.maxlength": "Indtast venligst en kortere værdi. (maks. {max} karakterer)", + "error.validation.maxwords": "Indtast ikke flere end {max} ord", + "error.validation.min": "Indtast en værdi lig med eller højere end {min}", + "error.validation.minlength": "Indtast venligst en længere værdi. (min. {min} karakterer)", + "error.validation.minwords": "Indtast venligst mindst {min} ord", + "error.validation.more": "Indtast venligst en værdi større end {min}", + "error.validation.notcontains": "Indtast venligst en værdi der ikke indeholder \"{needle}\"", + "error.validation.notin": "Indtast venligst ikke nogen af følgende: ({notIn})", + "error.validation.option": "Vælg venligst en gyldig mulighed", + "error.validation.num": "Indtast venligst et gyldigt nummer", + "error.validation.required": "Indtast venligst noget", + "error.validation.same": "Indtast venligst \"{other}\"", + "error.validation.size": "Størrelsen på værdien skal være \"{size}\"", + "error.validation.startswith": "Værdien skal starte med \"{start}\"", + "error.validation.time": "Indtast venligst et gyldigt tidspunkt", + "error.validation.time.after": "Indtast venligst et tidspunkt efter {time}", + "error.validation.time.before": "Indtast venligst et tidspunkt inden {time}", + "error.validation.time.between": "Indtast venligst et tidspunkt imellem {min} og {max}", + "error.validation.url": "Indtast venligst en gyldig URL", + + "field.required": "Feltet er påkrævet", + "field.blocks.changeType": "Skift type", + "field.blocks.code.name": "Kode", + "field.blocks.code.language": "Sprog", + "field.blocks.code.placeholder": "Din kode …", + "field.blocks.delete.confirm": "Ønsker du virkelig at slette denne blok?", + "field.blocks.delete.confirm.all": "Ønsker du virkelig at slette alle blokke?", + "field.blocks.delete.confirm.selected": "Ønsker du virkelig at slette de valgte blokke?", + "field.blocks.empty": "Ingen blokke endnu", + "field.blocks.fieldsets.label": "Vælg venligst en blok type", + "field.blocks.gallery.name": "Galleri", + "field.blocks.gallery.images.empty": "Ingen billeder endnu", + "field.blocks.gallery.images.label": "Billeder", + "field.blocks.heading.level": "Niveau", + "field.blocks.heading.name": "Overskrift", + "field.blocks.heading.text": "Tekst", + "field.blocks.heading.placeholder": "Overskrift …", + "field.blocks.image.alt": "Alternativ tekst", + "field.blocks.image.caption": "Billedtekst", + "field.blocks.image.crop": "Beskær", + "field.blocks.image.link": "Link", + "field.blocks.image.location": "Placering", + "field.blocks.image.name": "Billede", + "field.blocks.image.placeholder": "Vælg et billede", + "field.blocks.image.ratio": "Størrelsesforhold", + "field.blocks.image.url": "Billede URL", + "field.blocks.list.name": "Liste", + "field.blocks.markdown.name": "Markdown", + "field.blocks.markdown.label": "Tekst", + "field.blocks.markdown.placeholder": "Markdown …", + "field.blocks.quote.name": "Citat", + "field.blocks.quote.text.label": "Tekst", + "field.blocks.quote.text.placeholder": "Citat …", + "field.blocks.quote.citation.label": "Citeret af", + "field.blocks.quote.citation.placeholder": "af …", + "field.blocks.text.name": "Tekst", + "field.blocks.text.placeholder": "Tekst …", + "field.blocks.video.caption": "Billedtekst", + "field.blocks.video.name": "Video", + "field.blocks.video.placeholder": "Indtast URL til en video", + "field.blocks.video.url.label": "Video-URL", + "field.blocks.video.url.placeholder": "https://youtube.com/?v=", + + "field.files.empty": "Ingen filer valgt endnu", + + "field.layout.delete": "Slet layout", + "field.layout.delete.confirm": "Ønsker du virkelig at slette dette layout", + "field.layout.empty": "Ingen rækker endnu", + "field.layout.select": "Vælg et layout", + + "field.pages.empty": "Ingen sider valgt endnu", + "field.structure.delete.confirm": "\u00d8nsker du virkelig at slette denne indtastning?", + "field.structure.empty": "Ingen indtastninger endnu.", + "field.users.empty": "Ingen brugere er valgt", + + "file.blueprint": "Denne fil har intet blueprint endnu. Du kan definere opsætningen i /site/blueprints/{template}.yml", + "file.delete.confirm": "\u00d8nsker du virkelig at slette denne fil?", + "file.sort": "Skift position", + + "files": "Filer", + "files.empty": "Ingen filer endnu", + + "hide": "Skjul", + "hour": "Time", + "insert": "Inds\u00e6t", + "insert.after": "Indsæt efter", + "insert.before": "Indsæt før", + "install": "Installer", + + "installation": "Installation", + "installation.completed": "Panelet er blevet installeret", + "installation.disabled": "Panel installationen er deaktiveret på offentlige servere som standard. Kør venligst installationen på en lokal maskine eller aktiver det med panel.install panel.install muligheden.", + "installation.issues.accounts": "\/site\/accounts er ikke skrivbar", + "installation.issues.content": "Content mappen samt alle underliggende filer og mapper skal v\u00e6re skrivbare.", + "installation.issues.curl": "CURL extension er påkrævet", + "installation.issues.headline": "Panelet kan ikke installeres", + "installation.issues.mbstring": "MB String extension er påkrævet", + "installation.issues.media": "/media mappen eksisterer ikke eller er ikke skrivbar", + "installation.issues.php": "Sikre dig at der benyttes PHP 7+", + "installation.issues.server": "Kirby kræver Apache, Nginx eller Caddy", + "installation.issues.sessions": "/site/sessions mappen eksisterer ikke eller er ikke skrivbar", + + "language": "Sprog", + "language.code": "Kode", + "language.convert": "Gør standard", + "language.convert.confirm": "

Ønsker du virkelig at konvertere {name} til standardsproget? Dette kan ikke fortrydes.

Hvis {name} har uoversat indhold, vil der ikke længere være et gyldigt tilbagefald og dele af dit website vil måske fremstå tomt.

", + "language.create": "Tilføj nyt sprog", + "language.delete.confirm": "Ønsker du virkelig at slette sproget {name} inklusiv alle oversættelser? Kan ikke fortrydes!", + "language.deleted": "Sproget er blevet slettet", + "language.direction": "Læseretning", + "language.direction.ltr": "Venstre mod højre", + "language.direction.rtl": "Højre mod venstre", + "language.locale": "PHP locale string", + "language.locale.warning": "Du benytter en brugerdefineret sprogopsætning. Rediger venligst dette i sprogfilen i /site/languages", + "language.name": "Navn", + "language.updated": "Sproget er blevet opdateret", + + "languages": "Sprog", + "languages.default": "Standardsprog", + "languages.empty": "Der er ingen sprog endnu", + "languages.secondary": "Sekundære sprog", + "languages.secondary.empty": "Der er ingen sekundære sprog endnu", + + "license": "Kirby licens", + "license.buy": "Køb en licens", + "license.register": "Registrer", + "license.register.help": "Du modtog din licenskode efter købet via email. Venligst kopier og indsæt den for at registrere.", + "license.register.label": "Indtast venligst din licenskode", + "license.register.success": "Tak for din støtte af Kirby", + "license.unregistered": "Dette er en uregistreret demo af Kirby", + + "link": "Link", + "link.text": "Link tekst", + + "loading": "Indlæser", + + "lock.unsaved": "Ugemte ændringer", + "lock.unsaved.empty": "Der er ikke flere ændringer der ikke er gamt", + "lock.isLocked": "Ugemte ændringer af {email}", + "lock.file.isLocked": "Filen redigeres på nuværende af {email} og kan derfor ikke ændres.", + "lock.page.isLocked": "Siden redigeres på nuværende af {email} og kan derfor ikke ændres.", + "lock.unlock": "Lås op", + "lock.isUnlocked": "Dine ugemte ændringer er blevet overskrevet af en anden bruger. Du kan downloade dine ændringer for at flette dem ind manuelt.", + + "login": "Log ind", + "login.code.label.login": "Log ind kode", + "login.code.label.password-reset": "Sikkerhedskode", + "login.code.placeholder.email": "000 000", + "login.code.text.email": "Hvis din email adresse er registreret er en sikkerhedskode blevet sendt via email.", + "login.email.login.body": "Hej {user.nameOrEmail},\n\nDu har for nyligt anmodet om en log ind kode til Kirby Panel.\nFølgende log ind kode vil være gyldig i {timeout} minutter:\n\n{code}\n\nHvis du ikke har anmodet om en log ind kode, kan du blot ignorere denne email eller kontakte din administrator hvis du har spørgsmål.\nAf sikkerhedsmæssige årsager, bør du IKKE videresende denne email.", + "login.email.login.subject": "Din log ind kode", + "login.email.password-reset.body": "Hej {user.nameOrEmail},\n\nDu har for nyligt anmodet om kode til nulstilling af adgangskode til Kirby Panel.\nFølgende kode til nulstilling af adgangskode vil være gyldig i {timeout} minutter:\n\n{code}\n\nHvis du ikke har anmodet om kode til nulstilling af adgangskode, kan du blot ignorere denne email eller kontakte din administrator hvis du har spørgsmål.\nAf sikkerhedsmæssige årsager, bør du IKKE videresende denne email.", + "login.email.password-reset.subject": "Din kode til nulstilling af adgangskode", + "login.remember": "Forbliv logget ind", + "login.reset": "Nulstil adgangskode", + "login.toggleText.code.email": "Log ind via email", + "login.toggleText.code.email-password": "Log ind med adgangskode", + "login.toggleText.password-reset.email": "Glemt din adgangskode?", + "login.toggleText.password-reset.email-password": "← Tilbage til log ind", + + "logout": "Log ud", + + "menu": "Menu", + "meridiem": "AM/PM", + "mime": "Medie Type", + "minutes": "Minutter", + + "month": "Måned", + "months.april": "April", + "months.august": "August", + "months.december": "December", + "months.february": "Februar", + "months.january": "Januar", + "months.july": "Juli", + "months.june": "Juni", + "months.march": "Marts", + "months.may": "Maj", + "months.november": "November", + "months.october": "Oktober", + "months.september": "September", + + "more": "Mere", + "name": "Navn", + "next": "Næste", + "no": "no", + "off": "Sluk", + "on": "Tænd", + "open": "Åben", + "open.newWindow": "Open in new window", + "options": "Indstillinger", + "options.none": "Ingen muligheder", + + "orientation": "Orientering", + "orientation.landscape": "Landskab", + "orientation.portrait": "Portræt", + "orientation.square": "Kvadrat", + + "page.blueprint": "Denne side har intet blueprint endnu. Du kan definere opsætningen i /site/blueprints/{template}.yml", + "page.changeSlug": "\u00c6ndre URL", + "page.changeSlug.fromTitle": "Generer udfra titel", + "page.changeStatus": "Skift status", + "page.changeStatus.position": "Vælg venligst position", + "page.changeStatus.select": "Vælg en ny status", + "page.changeTemplate": "Skift skabelon", + "page.delete.confirm": "\u00d8nsker du virkelig at slette denne side?", + "page.delete.confirm.subpages": "Denne side har undersider.
Alle undersider vil også blive slettet.", + "page.delete.confirm.title": "Indtast sidens titel for at bekræfte", + "page.draft.create": "Opret kladde", + "page.duplicate.appendix": "Kopier", + "page.duplicate.files": "Kopier filer", + "page.duplicate.pages": "Kopier sider", + "page.sort": "Skift position", + "page.status": "Status", + "page.status.draft": "Kladde", + "page.status.draft.description": "Siden er i kladde udgave og er kun synlig for redaktører der er logget ind eller via hemmeligt link", + "page.status.listed": "Offentlig", + "page.status.listed.description": "Siden er offentlig for enhver", + "page.status.unlisted": "Ulistede", + "page.status.unlisted.description": "Siden er kun tilgængelig via URL", + + "pages": "Sider", + "pages.empty": "Ingen sider endnu", + "pages.status.draft": "Kladder", + "pages.status.listed": "Udgivede", + "pages.status.unlisted": "Ulistede", + + "pagination.page": "Side", + + "password": "Adgangskode", + "pixel": "Pixel", + "prev": "Forrige", + "preview": "Forhåndsvisning", + "remove": "Fjern", + "rename": "Omdøb", + "replace": "Erstat", + "retry": "Pr\u00f8v igen", + "revert": "Kass\u00e9r", + "revert.confirm": "Ønsker du virkelig at slette all ændringer der ikke er gemt?", + + "role": "Rolle", + "role.admin.description": "Admin har alle rettigheder", + "role.admin.title": "Admin", + "role.all": "All", + "role.empty": "Der er ingen bruger med denne rolle", + "role.description.placeholder": "Ingen beskrivelse", + "role.nobody.description": "Dette er en tilbagefaldsrolle uden rettigheder", + "role.nobody.title": "Ingen", + + "save": "Gem", + "search": "Søg", + "search.min": "Indtast {min} tegn for at søge", + "search.all": "Vis alle", + "search.results.none": "Ingen resultater", + + "section.required": "Sektionen er påkrævet", + + "select": "Vælg", + "settings": "Indstillinger", + "show": "Vis", + "size": "Størrelse", + "slug": "URL-appendiks", + "sort": "Sorter", + "title": "Titel", + "template": "Skabelon", + "today": "Idag", + + "site.blueprint": "Sitet har intet blueprint endnu. Du kan definere opsætningen i /site/blueprints/site.yml", + + "toolbar.button.code": "Kode", + "toolbar.button.bold": "Fed tekst", + "toolbar.button.email": "Email", + "toolbar.button.headings": "Overskrifter", + "toolbar.button.heading.1": "Overskrift 1", + "toolbar.button.heading.2": "Overskrift 2", + "toolbar.button.heading.3": "Overskrift 3", + "toolbar.button.italic": "Kursiv tekst", + "toolbar.button.file": "Fil", + "toolbar.button.file.select": "Vælg en fil", + "toolbar.button.file.upload": "Upload en fil", + "toolbar.button.link": "Link", + "toolbar.button.strike": "Strike-through", + "toolbar.button.ol": "Ordnet liste", + "toolbar.button.underline": "Underline", + "toolbar.button.ul": "Punktliste", + + "translation.author": "Kirby Team", + "translation.direction": "ltr", + "translation.name": "Dansk", + "translation.locale": "da_DK", + + "upload": "Upload", + "upload.error.cantMove": "Den uploadede fil kunne ikke flyttes", + "upload.error.cantWrite": "Kunne ikke skrive fil til disk", + "upload.error.default": "Filen kunne ikke uploades", + "upload.error.extension": "Upload af filen blev stoppet af dens type", + "upload.error.formSize": "Filen overskrider MAX_FILE_SIZE direktivet der er specificeret for formularen", + "upload.error.iniPostSize": "FIlen overskrider post_max_size direktivet i php.ini", + "upload.error.iniSize": "FIlen overskrider upload_max_filesize direktivet i php.ini", + "upload.error.noFile": "Ingen fil blev uploadet", + "upload.error.noFiles": "Ingen filer blev uploadet", + "upload.error.partial": "Den uploadede fil blev kun delvist uploadet", + "upload.error.tmpDir": "Der mangler en midlertidig mappe", + "upload.errors": "Fejl", + "upload.progress": "Uploader...", + + "url": "Url", + "url.placeholder": "https://example.com", + + "user": "Bruger", + "user.blueprint": "Du kan definere yderligere sektioner og formular felter for denne brugerrolle i /site/blueprints/users/{role}.yml", + "user.changeEmail": "Skift email", + "user.changeLanguage": "Skift sprog", + "user.changeName": "Omdøb denne bruger", + "user.changePassword": "Skift adgangskode", + "user.changePassword.new": "Ny adgangskode", + "user.changePassword.new.confirm": "Bekræft den nye adgangskode...", + "user.changeRole": "Skift rolle", + "user.changeRole.select": "Vælg en ny rolle", + "user.create": "Tilføj en ny bruger", + "user.delete": "Slet denne bruger", + "user.delete.confirm": "\u00d8nsker du virkelig at slette denne bruger?", + + "users": "Brugere", + + "version": "Kirby version", + + "view.account": "Din konto", + "view.installation": "Installation", + "view.resetPassword": "Nulstil adgangskode", + "view.settings": "Indstillinger", + "view.site": "Website", + "view.users": "Brugere", + + "welcome": "Velkommen", + "year": "År", + "yes": "yes" +} diff --git a/kirby/i18n/translations/de.json b/kirby/i18n/translations/de.json new file mode 100644 index 0000000..389db26 --- /dev/null +++ b/kirby/i18n/translations/de.json @@ -0,0 +1,527 @@ +{ + "add": "Hinzuf\u00fcgen", + "avatar": "Profilbild", + "back": "Zurück", + "cancel": "Abbrechen", + "change": "\u00c4ndern", + "close": "Schlie\u00dfen", + "confirm": "OK", + "collapse": "Zusammenklappen", + "collapse.all": "Alle zusammenklappen", + "copy": "Kopieren", + "create": "Erstellen", + + "date": "Datum", + "date.select": "Datum auswählen", + + "day": "Tag", + "days.fri": "Fr", + "days.mon": "Mo", + "days.sat": "Sa", + "days.sun": "So", + "days.thu": "Do", + "days.tue": "Di", + "days.wed": "Mi", + + "delete": "L\u00f6schen", + "delete.all": "Alle löschen", + "dimensions": "Maße", + "disabled": "Gesperrt", + "discard": "Verwerfen", + "download": "Download", + "duplicate": "Duplizieren", + "edit": "Bearbeiten", + "expand": "Aufklappen", + "expand.all": "Alle aufklappen", + + "dialog.files.empty": "Keine verfügbaren Dateien", + "dialog.pages.empty": "Keine verfügbaren Seiten", + "dialog.users.empty": "Keine verfügbaren Accounts", + + "email": "E-Mail", + "email.placeholder": "mail@beispiel.de", + + "error.access.code": "Ungültiger Code", + "error.access.login": "Ungültige Zugangsdaten", + "error.access.panel": "Du hast keinen Zugang zum Panel", + "error.access.view": "Du hast keinen Zugriff auf diesen Teil des Panels", + + "error.avatar.create.fail": "Das Profilbild konnte nicht hochgeladen werden", + "error.avatar.delete.fail": "Das Profilbild konnte nicht gel\u00f6scht werden", + "error.avatar.dimensions.invalid": "Bitte lade ein Profilbild hoch, das nicht breiter oder höher als 3000 Pixel ist.", + "error.avatar.mime.forbidden": "Das Profilbild muss vom Format JPEG oder PNG sein", + + "error.blueprint.notFound": "Das Blueprint \"{name}\" konnte nicht geladen werden.", + + "error.blocks.max.plural": "Bitte füge nicht mehr als {max} Blöcke hinzu", + "error.blocks.max.singular": "Bitte füge nicht mehr als einen Block hinzu", + "error.blocks.min.plural": "Bitte füge mindestens {min} Blöcke hinzu", + "error.blocks.min.singular": "Bitte füge mindestens einen Block hinzu", + "error.blocks.validation": "Fehler in Block {index}", + + "error.email.preset.notFound": "Die E-Mailvorlage \"{name}\" wurde nicht gefunden", + + "error.field.converter.invalid": "Ungültiger Konverter: \"{converter}\"", + + "error.file.changeName.empty": "Bitte gib einen Namen an", + "error.file.changeName.permission": "Du darfst den Dateinamen von \"{filename}\" nicht ändern", + "error.file.duplicate": "Eine Datei mit dem Dateinamen \"{filename}\" besteht bereits", + "error.file.extension.forbidden": "Verbotene Dateiendung \"{extension}\"", + "error.file.extension.invalid": "Verbotene Dateiendung \"{extension}\"", + "error.file.extension.missing": "Du kannst keine Dateien ohne Dateiendung hochladen", + "error.file.maxheight": "Die Bildhöhe darf {height} Pixel nicht überschreiten", + "error.file.maxsize": "Die Datei ist zu groß", + "error.file.maxwidth": "Die Bildbreite darf {width} Pixel nicht überschreiten", + "error.file.mime.differs": "Die Datei muss den Medientyp \"{mime}\" haben.", + "error.file.mime.forbidden": "Der Medientyp \"{mime}\" ist nicht erlaubt", + "error.file.mime.invalid": "Ungültiger Dateityp: {mime}", + "error.file.mime.missing": "Der Medientyp für \"{filename}\" konnte nicht erkannt werden", + "error.file.minheight": "Die Bildhöhe muss mindestens {height} Pixel betragen", + "error.file.minsize": "Die Datei ist zu klein", + "error.file.minwidth": "Die Bildbreite muss mindestens {height} Pixel betragen", + "error.file.name.missing": "Bitte gib einen Dateinamen an", + "error.file.notFound": "Die Datei \"{filename}\" konnte nicht gefunden werden", + "error.file.orientation": "Das Bildformat ist ungültig. Erwartetes Format: \"{orientation}\"", + "error.file.type.forbidden": "Du kannst keinen {type}-Dateien hochladen", + "error.file.type.invalid": "Ungültiger Dateityp: {mime}", + "error.file.undefined": "Die Datei konnte nicht gefunden werden", + + "error.form.incomplete": "Bitte behebe alle Fehler …", + "error.form.notSaved": "Das Formular konnte nicht gespeichert werden", + + "error.language.code": "Bitte gib einen gültigen Code für die Sprache an", + "error.language.duplicate": "Die Sprache besteht bereits", + "error.language.name": "Bitte gib einen gültigen Namen für die Sprache an", + + "error.layout.validation.block": "Fehler in Block {blockIndex} in Layout {layoutIndex}", + "error.layout.validation.settings": "Fehler in den Einstellungen von Layout {index}", + + "error.license.format": "Bitte gib einen gültigen Lizenzschlüssel ein", + "error.license.email": "Bitte gib eine gültige E-Mailadresse an", + "error.license.verification": "Die Lizenz konnte nicht verifiziert werden", + + "error.page.changeSlug.permission": "Du darfst die URL der Seite \"{slug}\" nicht ändern", + "error.page.changeStatus.incomplete": "Die Seite ist nicht vollständig und kann daher nicht veröffentlicht werden", + "error.page.changeStatus.permission": "Der Status der Seite kann nicht geändert werden", + "error.page.changeStatus.toDraft.invalid": "Die Seite \"{slug}\" kann nicht in einen Entwurf umgewandelt werden", + "error.page.changeTemplate.invalid": "Die Vorlage für die Seite \"{slug}\" kann nicht geändert werden", + "error.page.changeTemplate.permission": "Du kannst die Vorlage für die Seite \"{slug}\" nicht ändern", + "error.page.changeTitle.empty": "Bitte gib einen Titel an", + "error.page.changeTitle.permission": "Du kannst den Titel für die Seite \"{slug}\" nicht ändern", + "error.page.create.permission": "Du kannst die Seite \"{slug}\" nicht anlegen", + "error.page.delete": "Die Seite \"{slug}\" kann nicht gelöscht werden", + "error.page.delete.confirm": "Bitte gib zur Bestätigung den Seitentitel ein", + "error.page.delete.hasChildren": "Die Seite hat Unterseiten und kann nicht gelöscht werden", + "error.page.delete.permission": "Du kannst die Seite \"{slug}\" nicht löschen", + "error.page.draft.duplicate": "Ein Entwurf mit dem URL-Kürzel \"{slug}\" besteht bereits", + "error.page.duplicate": "Eine Seite mit dem URL-Kürzel \"{slug}\" besteht bereits", + "error.page.duplicate.permission": "Du kannst die Seite \"{slug}\" nicht duplizieren", + "error.page.notFound": "Die Seite \"{slug}\" konnte nicht gefunden werden", + "error.page.num.invalid": "Bitte gib eine gültige Sortierungszahl an. Negative Zahlen sind nicht erlaubt.", + "error.page.slug.invalid": "Bitte gib ein gültiges URL-Kürzel an", + "error.page.slug.maxlength": "Die Pfadlänge darf {length} Zeichen nicht überschreiten", + "error.page.sort.permission": "Die Seite \"{slug}\" kann nicht umsortiert werden", + "error.page.status.invalid": "Bitte gib einen gültigen Seitenstatus an", + "error.page.undefined": "Die Seite konnte nicht gefunden werden", + "error.page.update.permission": "Du kannst die Seite \"{slug}\" nicht editieren", + + "error.section.files.max.plural": "Bitte füge nicht mehr als {max} Dateien zum Bereich \"{section}\" hinzu", + "error.section.files.max.singular": "Bitte füge nicht mehr als eine Datei zum Bereich \"{section}\" hinzu", + "error.section.files.min.plural": "Der Bereich \"{section}\" benötigt mindestens {min} Dateien", + "error.section.files.min.singular": "Der Bereich \"{section}\" benötigt mindestens eine Datei", + + "error.section.pages.max.plural": "Bitte füge nicht mehr als {max} Seiten zum Bereich \"{section}\" hinzu", + "error.section.pages.max.singular": "Bitte füge nicht mehr als eine Seite zum Bereich \"{section}\" hinzu", + "error.section.pages.min.plural": "Der Bereich \"{section}\" benötigt mindestens {min} Seiten", + "error.section.pages.min.singular": "Der Bereich \"{section}\" benötigt mindestens eine Seite", + + "error.section.notLoaded": "Der Bereich \"{name}\" konnte nicht geladen werden", + "error.section.type.invalid": "Der Bereichstyp \"{type}\" ist nicht gültig", + + "error.site.changeTitle.empty": "Bitte gib einen Titel an", + "error.site.changeTitle.permission": "Du kannst den Titel der Seite nicht ändern", + "error.site.update.permission": "Du darfst die Seite nicht bearbeiten", + + "error.template.default.notFound": "Die \"Default\"-Vorlage existiert nicht", + + "error.user.changeEmail.permission": "Du kannst die E-Mailadresse für den Account \"{name}\" nicht ändern", + "error.user.changeLanguage.permission": "Du kannst die Sprache für den Account \"{name}\" nicht ändern", + "error.user.changeName.permission": "Du kannst den Namen für den Account \"{name}\" nicht ändern", + "error.user.changePassword.permission": "Du kannst das Passwort für den Account \"{name}\" nicht ändern", + "error.user.changeRole.lastAdmin": "Die Rolle des letzten Accounts mit Administrationsrechten kann nicht geändert werden", + "error.user.changeRole.permission": "Du kannst die Rolle für den Benutzer \"{name}\" nicht ändern", + "error.user.changeRole.toAdmin": "Du darfst die Admin-Rolle nicht an andere Accounts vergeben", + "error.user.create.permission": "Du darfst diesen Account nicht anlegen", + "error.user.delete": "Der Account \"{name}\" konnte nicht gelöscht werden", + "error.user.delete.lastAdmin": "Du kannst den letzten Account mit Administrationsrechten nicht löschen", + "error.user.delete.lastUser": "Der letzte Account kann nicht gelöscht werden", + "error.user.delete.permission": "Du darfst den Account \"{name}\" nicht löschen", + "error.user.duplicate": "Ein Account mit der E-Mailadresse \"{email}\" besteht bereits", + "error.user.email.invalid": "Bitte gib eine gültige E-Mailadresse an", + "error.user.language.invalid": "Bitte gib eine gültige Sprache an", + "error.user.notFound": "Der Account \"{name}\" wurde nicht gefunden", + "error.user.password.invalid": "Bitte gib ein gültiges Passwort ein. Passwörter müssen mindestens 8 Zeichen lang sein.", + "error.user.password.notSame": "Die Passwörter stimmen nicht überein", + "error.user.password.undefined": "Der Account hat kein Passwort", + "error.user.password.wrong": "Falsches Passwort", + "error.user.role.invalid": "Bitte gib eine gültige Rolle an", + "error.user.update.permission": "Du darfst den den Account \"{name}\" nicht bearbeiten", + + "error.validation.accepted": "Bitte bestätige", + "error.validation.alpha": "Bitte gib nur Zeichen zwischen A und Z ein", + "error.validation.alphanum": "Bitte gib nur Zeichen zwischen A und Z und Zahlen zwischen 0 und 9 ein", + "error.validation.between": "Bitte gib einen Wert zwischen \"{min}\" und \"{max}\" ein", + "error.validation.boolean": "Bitte bestätige oder lehne ab", + "error.validation.contains": "Bitte gib einen Wert ein, der \"{needle}\" enthält", + "error.validation.date": "Bitte gib ein gültiges Datum ein", + "error.validation.date.after": "Bitte gib ein Datum nach dem {date} ein", + "error.validation.date.before": "Bitte gib ein Datum vor dem {date} ein", + "error.validation.date.between": "Bitte gib ein Datum zwischen dem {min} und dem {max} ein", + "error.validation.denied": "Bitte lehne die Eingabe ab", + "error.validation.different": "Der Wert darf nicht \"{other}\" sein", + "error.validation.email": "Bitte gib eine gültige E-Mailadresse an", + "error.validation.endswith": "Der Wert muss auf \"{end}\" enden", + "error.validation.filename": "Bitte gib einen gültigen Dateinamen ein", + "error.validation.in": "Bitte gib einen der folgenden Werte ein: ({in})", + "error.validation.integer": "Bitte gib eine ganze Zahl ein", + "error.validation.ip": "Bitte gib eine gültige IP Adresse ein", + "error.validation.less": "Bitte gib einen Wert kleiner als {max} ein", + "error.validation.match": "Der Wert entspricht nicht dem erwarteten Muster", + "error.validation.max": "Bitte gib einen Wert ein, der nicht größer als {max} ist", + "error.validation.maxlength": "Bitte gib einen kürzeren Text ein (max. {max} Zeichen)", + "error.validation.maxwords": "Bitte nutze nicht mehr als {max} Wort(e)", + "error.validation.min": "Bitte gib einen Wert ein, der nicht kleiner als {min} ist", + "error.validation.minlength": "Bitte gib einen längeren Text ein. (min. {min} Zeichen)", + "error.validation.minwords": "Bitte nutze mindestens {min} Wort(e)", + "error.validation.more": "Bitte gib einen größeren Wert als {min} ein", + "error.validation.notcontains": "Bitte gib einen Wert ein, der nicht \"{needle}\" enthält", + "error.validation.notin": "Bitte gib keinen der folgenden Werte ein: ({notIn})", + "error.validation.option": "Bitte wähle eine gültige Option aus", + "error.validation.num": "Bitte gib eine gültige Zahl an", + "error.validation.required": "Bitte gib etwas ein", + "error.validation.same": "Bitte gib \"{other}\" ein", + "error.validation.size": "Die Größe des Wertes muss \"{size}\" sein", + "error.validation.startswith": "Der Wert muss mit \"{start}\" beginnen", + "error.validation.time": "Bitte gib eine gültige Uhrzeit ein", + "error.validation.time.after": "Bitte gib eine Zeit nach {time} ein", + "error.validation.time.before": "Bitte gib eine Zeit vor {time} ein", + "error.validation.time.between": "Bitte gib eine Zeit zwischen {min} und {max} ein", + "error.validation.url": "Bitte gib eine gültige URL ein", + + "field.required": "Das Feld ist Pflicht", + "field.blocks.changeType": "Blocktyp ändern", + "field.blocks.code.name": "Code", + "field.blocks.code.language": "Sprache", + "field.blocks.code.placeholder": "Code …", + "field.blocks.delete.confirm": "Willst du diesen Block wirklich löschen?", + "field.blocks.delete.confirm.all": "Willst du wirklich alle Blöcke löschen?", + "field.blocks.delete.confirm.selected": "Willst du wirklich die ausgewählten Blöcke löschen?", + "field.blocks.empty": "Keine Blöcke", + "field.blocks.fieldsets.label": "Bitte wähle einen Blocktyp aus …", + "field.blocks.gallery.name": "Galerie", + "field.blocks.gallery.images.empty": "Keine Bilder", + "field.blocks.gallery.images.label": "Bilder", + "field.blocks.heading.level": "Ebene", + "field.blocks.heading.name": "Überschrift", + "field.blocks.heading.text": "Text", + "field.blocks.heading.placeholder": "Überschrift …", + "field.blocks.image.alt": "Alternativer Text", + "field.blocks.image.caption": "Bildunterschrift", + "field.blocks.image.crop": "Beschneiden", + "field.blocks.image.link": "Link", + "field.blocks.image.location": "Ort", + "field.blocks.image.name": "Bild", + "field.blocks.image.placeholder": "Bild auswählen", + "field.blocks.image.ratio": "Seitenverhältnis", + "field.blocks.image.url": "Bild URL", + "field.blocks.list.name": "Liste", + "field.blocks.markdown.name": "Markdown", + "field.blocks.markdown.label": "Text", + "field.blocks.markdown.placeholder": "Markdown …", + "field.blocks.quote.name": "Zitat", + "field.blocks.quote.text.label": "Text", + "field.blocks.quote.text.placeholder": "Zitat …", + "field.blocks.quote.citation.label": "Quelle", + "field.blocks.quote.citation.placeholder": "Quelle …", + "field.blocks.text.name": "Text", + "field.blocks.text.placeholder": "Text …", + "field.blocks.video.caption": "Bildunterschrift", + "field.blocks.video.name": "Video", + "field.blocks.video.placeholder": "Video-URL eingeben", + "field.blocks.video.url.label": "Video-URL", + "field.blocks.video.url.placeholder": "https://youtube.com/?v=", + + "field.files.empty": "Keine Dateien ausgewählt", + + "field.layout.delete": "Layout löschen", + "field.layout.delete.confirm": "Willst du dieses Layout wirklich löschen?", + "field.layout.empty": "Keine Layouts", + "field.layout.select": "Layout auswählen", + + "field.pages.empty": "Keine Seiten ausgewählt", + "field.structure.delete.confirm": "Willst du diesen Eintrag wirklich l\u00f6schen?", + "field.structure.empty": "Es bestehen keine Eintr\u00e4ge.", + "field.users.empty": "Keine Accounts ausgewählt", + + "file.blueprint": "Du kannst zusätzliche Felder und Bereiche für diese Datei in /site/blueprints/{template}.yml anlegen", + "file.delete.confirm": "Willst du die Datei {filename}
wirklich löschen?", + "file.sort": "Position ändern", + + "files": "Dateien", + "files.empty": "Keine Dateien", + + "hide": "Verbergen", + "hour": "Stunde", + "insert": "Einf\u00fcgen", + "insert.after": "Danach einfügen", + "insert.before": "Davor einfügen", + "install": "Installieren", + + "installation": "Installation", + "installation.completed": "Das Panel wurde installiert", + "installation.disabled": "Die Panel-Installation ist auf öffentlichen Servern automatisch deaktiviert. Bitte installiere das Panel auf einem lokalen Server oder aktiviere die Installation gezielt mit der panel.install Option. ", + "installation.issues.accounts": "/site/accounts ist nicht beschreibbar", + "installation.issues.content": "/content existiert nicht oder ist nicht beschreibbar", + "installation.issues.curl": "Die CURL Erweiterung wird benötigt", + "installation.issues.headline": "Das Panel kann nicht installiert werden", + "installation.issues.mbstring": "Die MB String Erweiterung wird benötigt", + "installation.issues.media": "Der /media Ordner ist nicht beschreibbar", + "installation.issues.php": "Bitte verwende PHP 7+", + "installation.issues.server": "Kirby benötigt Apache, Nginx or Caddy", + "installation.issues.sessions": "/site/sessions ist nicht beschreibbar", + + "language": "Sprache", + "language.code": "Code", + "language.convert": "Als Standard auswählen", + "language.convert.confirm": "

Willst du {name} wirklich in die Standardsprache umwandeln? Dieser Schritt kann nicht rückgängig gemacht werden.

Wenn {name} unübersetzte Felder hat, gibt es keine gültigen Standardwerte für diese Felder und Inhalte könnten verloren gehen.

", + "language.create": "Neue Sprache anlegen", + "language.delete.confirm": "Willst du {name} inklusive aller Übersetzungen wirklich löschen? Dieser Schritt kann nicht rückgängig gemacht werden!", + "language.deleted": "Die Sprache wurde gelöscht", + "language.direction": "Leserichtung", + "language.direction.ltr": "Von links nach rechts", + "language.direction.rtl": "Von rechts nach links", + "language.locale": "PHP locale string", + "language.locale.warning": "Du nutzt ein angepasstes Setup for PHP Locales. Bitte bearbeite dieses direkt in der entsprechenden Sprachdatei in /site/languages", + "language.name": "Name", + "language.updated": "Die Sprache wurde gespeichert", + + "languages": "Sprachen", + "languages.default": "Standardsprache", + "languages.empty": "Noch keine Sprachen", + "languages.secondary": "Sekundäre Sprachen", + "languages.secondary.empty": "Noch keine sekundären Sprachen", + + "license": "Lizenz", + "license.buy": "Kaufe eine Lizenz", + "license.register": "Registrieren", + "license.register.help": "Den Lizenzcode findest du in der Bestätigungsmail zu deinem Kauf. Bitte kopiere und füge ihn ein, um Kirby zu registrieren.", + "license.register.label": "Bitte gib deinen Lizenzcode ein", + "license.register.success": "Vielen Dank für deine Unterstützung", + "license.unregistered": "Dies ist eine unregistrierte Kirby-Demo", + + "link": "Link", + "link.text": "Linktext", + + "loading": "Laden", + + "lock.unsaved": "Ungespeicherte Änderungen", + "lock.unsaved.empty": "Keine ungespeicherten Änderungen", + "lock.isLocked": "Ungespeicherte Änderungen von {email}", + "lock.file.isLocked": "Die Datei wird von {email} bearbeitet und kann nicht geändert werden.", + "lock.page.isLocked": "Die Seite wird von {email} bearbeitet und kann nicht geändert werden.", + "lock.unlock": "Entsperren", + "lock.isUnlocked": "Deine ungespeicherten Änderungen wurden von einem anderen Account überschrieben. Du kannst sie herunterladen, um sie manuell einzufügen. ", + + "login": "Anmelden", + "login.code.label.login": "Anmeldecode", + "login.code.label.password-reset": "Anmeldecode", + "login.code.placeholder.email": "000 000", + "login.code.text.email": "Wenn deine E-Mail-Adresse registriert ist, wurde der angeforderte Code per E-Mail versendet.", + "login.email.login.body": "Hallo {user.nameOrEmail},\n\ndu hast gerade einen Anmeldecode für das Kirby Panel angefordert.\nDer folgende Anmeldecode ist für die nächsten {timeout} Minuten gültig:\n\n{code}\n\nWenn du keinen Anmeldecode angefordert hast, ignoriere bitte diese E-Mail oder kontaktiere bei Fragen deinen Administrator.\nBitte leite diese E-Mail aus Sicherheitsgründen NICHT weiter.", + "login.email.login.subject": "Dein Anmeldecode", + "login.email.password-reset.body": "Hallo {user.nameOrEmail},\n\ndu hast gerade einen Anmeldecode für das Kirby Panel angefordert.\nDer folgende Anmeldecode ist für die nächsten {timeout} Minuten gültig:\n\n{code}\n\nWenn du keinen Anmeldecode angefordert hast, ignoriere bitte diese E-Mail oder kontaktiere bei Fragen deinen Administrator.\nBitte leite diese E-Mail aus Sicherheitsgründen NICHT weiter.", + "login.email.password-reset.subject": "Dein Anmeldecode", + "login.remember": "Angemeldet bleiben", + "login.reset": "Passwort zurücksetzen", + "login.toggleText.code.email": "Anmelden über E-Mail", + "login.toggleText.code.email-password": "Anmelden mit Passwort", + "login.toggleText.password-reset.email": "Passwort vergessen?", + "login.toggleText.password-reset.email-password": "← Zurück zur Anmeldung", + + "logout": "Abmelden", + + "menu": "Menü", + "meridiem": "AM/PM", + "mime": "Medientyp", + "minutes": "Minuten", + + "month": "Monat", + "months.april": "April", + "months.august": "August", + "months.december": "Dezember", + "months.february": "Februar", + "months.january": "Januar", + "months.july": "Juli", + "months.june": "Juni", + "months.march": "M\u00e4rz", + "months.may": "Mai", + "months.november": "November", + "months.october": "Oktober", + "months.september": "September", + + "more": "Mehr", + "name": "Name", + "next": "Nächster Eintrag", + "no": "nein", + "off": "aus", + "on": "an", + "open": "Öffnen", + "open.newWindow": "In neuem Fenster öffnen", + "options": "Optionen", + "options.none": "Keine Optionen", + + "orientation": "Ausrichtung", + "orientation.landscape": "Querformat", + "orientation.portrait": "Hochformat", + "orientation.square": "Quadratisch", + + "page.blueprint": "Du kannst zusätzliche Felder und Bereiche für diese Seite in /site/blueprints/{template}.yml anlegen", + "page.changeSlug": "URL \u00e4ndern", + "page.changeSlug.fromTitle": "Aus Titel erzeugen", + "page.changeStatus": "Status ändern", + "page.changeStatus.position": "Bitte wähle eine Position aus", + "page.changeStatus.select": "Wähle einen neuen Status aus", + "page.changeTemplate": "Vorlage ändern", + "page.delete.confirm": "Willst du die Seite {title} wirklich löschen?", + "page.delete.confirm.subpages": "Diese Seite hat Unterseiten.
Alle Unterseiten werden ebenfalls gelöscht.", + "page.delete.confirm.title": "Gib zur Bestätigung den Seitentitel ein", + "page.draft.create": "Entwurf anlegen", + "page.duplicate.appendix": "Kopie", + "page.duplicate.files": "Dateien kopieren", + "page.duplicate.pages": "Seiten kopieren", + "page.sort": "Position ändern", + "page.status": "Status", + "page.status.draft": "Entwurf", + "page.status.draft.description": "Die Seite ist im Entwurfsmodus und ist nur nach Anmeldung oder über den geheimen Link sichtbar", + "page.status.listed": "Öffentlich", + "page.status.listed.description": "Die Seite ist öffentlich für alle", + "page.status.unlisted": "Ungelistet", + "page.status.unlisted.description": "Die Seite kann nur über die URL aufgerufen werden", + + "pages": "Seiten", + "pages.empty": "Keine Seiten", + "pages.status.draft": "Entwürfe", + "pages.status.listed": "Veröffentlicht", + "pages.status.unlisted": "Ungelistet", + + "pagination.page": "Seite", + + "password": "Passwort", + "pixel": "Pixel", + "prev": "Vorheriger Eintrag", + "preview": "Vorschau", + "remove": "Entfernen", + "rename": "Umbenennen", + "replace": "Ersetzen", + "retry": "Wiederholen", + "revert": "Verwerfen", + "revert.confirm": "Willst du wirklich alle ungespeicherten Änderungen verwerfen? ", + + "role": "Rolle", + "role.admin.description": "Admins haben alle Rechte", + "role.admin.title": "Admin", + "role.all": "Alle", + "role.empty": "Keine Accounts mit dieser Rolle", + "role.description.placeholder": "Keine Beschreibung", + "role.nobody.description": "Dies ist die Platzhalterrolle ohne Rechte", + "role.nobody.title": "Niemand", + + "save": "Speichern", + "search": "Suchen", + "search.min": "Gib mindestens {min}  Zeichen ein, um zu suchen", + "search.all": "Alles zeigen", + "search.results.none": "Keine Ergebnisse", + + "section.required": "Der Bereich ist Pflicht", + + "select": "Auswählen", + "settings": "Einstellungen", + "show": "Anzeigen", + "size": "Größe", + "slug": "URL-Anhang", + "sort": "Sortieren", + "title": "Titel", + "template": "Vorlage", + "today": "Heute", + + "site.blueprint": "Du kannst zusätzliche Felder und Bereiche für die Seite in /site/blueprints/site.yml anlegen", + + "toolbar.button.code": "Code", + "toolbar.button.bold": "Fetter Text", + "toolbar.button.email": "E-Mail", + "toolbar.button.headings": "Überschriften", + "toolbar.button.heading.1": "Überschrift 1", + "toolbar.button.heading.2": "Überschrift 2", + "toolbar.button.heading.3": "Überschrift 3", + "toolbar.button.italic": "Kursiver Text", + "toolbar.button.file": "Datei", + "toolbar.button.file.select": "Datei auswählen", + "toolbar.button.file.upload": "Datei hochladen", + "toolbar.button.link": "Link", + "toolbar.button.strike": "Durchgestrichen", + "toolbar.button.ol": "Geordnete Liste", + "toolbar.button.underline": "Unterstrichen", + "toolbar.button.ul": "Ungeordnete Liste", + + "translation.author": "Kirby Team", + "translation.direction": "ltr", + "translation.name": "Deutsch", + "translation.locale": "de_DE", + + "upload": "Hochladen", + "upload.error.cantMove": "Die Datei konnte nicht an ihren Zielort bewegt werden", + "upload.error.cantWrite": "Die Datei konnte nicht auf der Festplatte gespeichert werden", + "upload.error.default": "Die Datei konnte nicht hochgeladen werden", + "upload.error.extension": "Der Dateiupload wurde durch eine Erweiterung verhindert", + "upload.error.formSize": "Die Datei ist größer als die MAX_FILE_SIZE Einstellung im Formular", + "upload.error.iniPostSize": "Die Datei ist größer als die post_max_size Einstellung in der php.ini", + "upload.error.iniSize": "Die Datei ist größer als die upload_max_filesize Einstellung in der php.ini", + "upload.error.noFile": "Es wurde keine Datei hochgeladen", + "upload.error.noFiles": "Es wurden keine Dateien hochgeladen", + "upload.error.partial": "Die Datei wurde nur teilweise hochgeladen", + "upload.error.tmpDir": "Der temporäre Ordner für den Dateiupload existiert leider nicht", + "upload.errors": "Fehler", + "upload.progress": "Hochladen …", + + "url": "Url", + "url.placeholder": "https://beispiel.de", + + "user": "Account", + "user.blueprint": "Du kannst zusätzliche Felder und Bereiche für diese Rolle in /site/blueprints/users/{role}.yml anlegen", + "user.changeEmail": "E-Mail ändern", + "user.changeLanguage": "Sprache ändern", + "user.changeName": "Account umbenennen", + "user.changePassword": "Passwort ändern", + "user.changePassword.new": "Neues Passwort", + "user.changePassword.new.confirm": "Wiederhole das Passwort …", + "user.changeRole": "Rolle ändern", + "user.changeRole.select": "Neue Rolle auswählen", + "user.create": "Neuen Account anlegen", + "user.delete": "Account löschen", + "user.delete.confirm": "Willst du den Account
{email} wirklich löschen?", + + "users": "Accounts", + + "version": "Version", + + "view.account": "Dein Account", + "view.installation": "Installation", + "view.resetPassword": "Passwort zurücksetzen", + "view.settings": "Einstellungen", + "view.site": "Seite", + "view.users": "Accounts", + + "welcome": "Willkommen", + "year": "Jahr", + "yes": "ja" +} diff --git a/kirby/i18n/translations/el.json b/kirby/i18n/translations/el.json new file mode 100644 index 0000000..5d1cb4f --- /dev/null +++ b/kirby/i18n/translations/el.json @@ -0,0 +1,527 @@ +{ + "add": "\u03a0\u03c1\u03bf\u03c3\u03b8\u03ae\u03ba\u03b7", + "avatar": "\u0395\u03b9\u03ba\u03cc\u03bd\u03b1 \u03c0\u03c1\u03bf\u03c6\u03af\u03bb", + "back": "Πίσω", + "cancel": "\u0391\u03ba\u03cd\u03c1\u03c9\u03c3\u03b7", + "change": "\u0391\u03bb\u03bb\u03b1\u03b3\u03ae", + "close": "\u039a\u03bb\u03b5\u03af\u03c3\u03b9\u03bc\u03bf", + "confirm": "Εντάξει", + "collapse": "Collapse", + "collapse.all": "Collapse All", + "copy": "Αντιγραφή", + "create": "Δημιουργία", + + "date": "Ημερομηνία", + "date.select": "Επιλογή ημερομηνίας", + + "day": "Ημέρα", + "days.fri": "\u03a0\u03b1\u03c1", + "days.mon": "\u0394\u03b5\u03c5", + "days.sat": "\u03a3\u03ac\u03b2", + "days.sun": "\u039a\u03c5\u03c1", + "days.thu": "\u03a0\u03ad\u03bc", + "days.tue": "\u03a4\u03c1\u03af", + "days.wed": "\u03a4\u03b5\u03c4", + + "delete": "\u0394\u03b9\u03b1\u03b3\u03c1\u03b1\u03c6\u03ae", + "delete.all": "Delete all", + "dimensions": "Διαστάσεις", + "disabled": "Disabled", + "discard": "Απόρριψη", + "download": "Λήψη", + "duplicate": "Αντίγραφο", + "edit": "\u0395\u03c0\u03b5\u03be\u03b5\u03c1\u03b3\u03b1\u03c3\u03af\u03b1", + "expand": "Expand", + "expand.all": "Expand All", + + "dialog.files.empty": "No files to select", + "dialog.pages.empty": "No pages to select", + "dialog.users.empty": "No users to select", + + "email": "Διεύθυνση ηλεκτρονικού ταχυδρομείου", + "email.placeholder": "mail@example.com", + + "error.access.code": "Mη έγκυρος κωδικός", + "error.access.login": "Mη έγκυρη σύνδεση", + "error.access.panel": "Δεν επιτρέπεται η πρόσβαση στον πίνακα ελέγχου", + "error.access.view": "Δεν επιτρέπεται η πρόσβαση σε αυτό το τμήμα του πίνακα ελέγχου", + + "error.avatar.create.fail": "Δεν ήταν δυνατή η μεταφόρτωση της εικόνας προφίλ", + "error.avatar.delete.fail": "Δεν ήταν δυνατή η διαγραφή της εικόνας προφίλ", + "error.avatar.dimensions.invalid": "Διατηρήστε το πλάτος και το ύψος της εικόνας προφίλ κάτω από 3000 εικονοστοιχεία", + "error.avatar.mime.forbidden": "\u039c\u03b7 \u03b1\u03c0\u03bf\u03b4\u03b5\u03ba\u03c4\u03cc\u03c2 \u03c4\u03cd\u03c0\u03bf\u03c2 \u03b1\u03c1\u03c7\u03b5\u03af\u03bf\u03c5", + + "error.blueprint.notFound": "Δεν ήταν δυνατή η φόρτωση του προσχεδίου \"{name}\"", + + "error.blocks.max.plural": "You must not add more than {max} blocks", + "error.blocks.max.singular": "You must not add more than one block", + "error.blocks.min.plural": "You must add at least {min} blocks", + "error.blocks.min.singular": "You must add at least one block", + "error.blocks.validation": "There's an error in block {index}", + + "error.email.preset.notFound": "Δεν είναι δυνατή η εύρεση της προεπιλογής διεύθινσης ηλεκτρονικού ταχυδρομείου \"{name}\"", + + "error.field.converter.invalid": "Μη έγκυρος μετατροπέας \"{converter}\"", + + "error.file.changeName.empty": "The name must not be empty", + "error.file.changeName.permission": "Δεν επιτρέπεται να αλλάξετε το όνομα του \"{filename}\"", + "error.file.duplicate": "Ένα αρχείο με το όνομα \"{filename}\" υπάρχει ήδη", + "error.file.extension.forbidden": "\u039c\u03b7 \u03b1\u03c0\u03bf\u03b4\u03b5\u03ba\u03c4\u03ae \u03b5\u03c0\u03ad\u03ba\u03c4\u03b1\u03c3\u03b7 \u03b1\u03c1\u03c7\u03b5\u03af\u03bf\u03c5", + "error.file.extension.invalid": "Invalid extension: {extension}", + "error.file.extension.missing": "Λείπει η επέκταση για το \"{filename}\"", + "error.file.maxheight": "The height of the image must not exceed {height} pixels", + "error.file.maxsize": "The file is too large", + "error.file.maxwidth": "The width of the image must not exceed {width} pixels", + "error.file.mime.differs": "Το αρχείο πρέπει να είναι του ίδιου τύπου mime \"{mime}\"", + "error.file.mime.forbidden": "Ο τύπος μέσου \"{mime}\" δεν επιτρέπεται", + "error.file.mime.invalid": "Invalid mime type: {mime}", + "error.file.mime.missing": "Δεν είναι δυνατό να εντοπιστεί ο τύπος μέσου για το \"{filename}\"", + "error.file.minheight": "The height of the image must be at least {height} pixels", + "error.file.minsize": "The file is too small", + "error.file.minwidth": "The width of the image must be at least {width} pixels", + "error.file.name.missing": "Το όνομα αρχείου δεν μπορεί να είναι άδειο", + "error.file.notFound": "Δεν είναι δυνατό να βρεθεί το αρχείο \"{filename}\"", + "error.file.orientation": "The orientation of the image must be \"{orientation}\"", + "error.file.type.forbidden": "Δεν επιτρέπεται η μεταφόρτωση αρχείων {type}", + "error.file.type.invalid": "Invalid file type: {type}", + "error.file.undefined": "Δεν ήταν δυνατή η εύρεση του αρχείου", + + "error.form.incomplete": "Παρακαλώ διορθώστε τα σφάλματα στη φόρμα...", + "error.form.notSaved": "Δεν ήταν δυνατή η αποθήκευση της φόρμας", + + "error.language.code": "Please enter a valid code for the language", + "error.language.duplicate": "The language already exists", + "error.language.name": "Please enter a valid name for the language", + + "error.layout.validation.block": "There's an error in block {blockIndex} in layout {layoutIndex}", + "error.layout.validation.settings": "There's an error in layout {index} settings", + + "error.license.format": "Please enter a valid license key", + "error.license.email": "Παρακαλώ εισάγετε μια έγκυρη διεύθυνση ηλεκτρονικού ταχυδρομείου", + "error.license.verification": "The license could not be verified", + + "error.page.changeSlug.permission": "Δεν επιτρέπεται να αλλάξετε το URL της σελίδας \"{slug}\"", + "error.page.changeStatus.incomplete": "Δεν ήταν δυνατή η δημοσίευση της σελίδας καθώς περιέχει σφάλματα", + "error.page.changeStatus.permission": "Δεν είναι δυνατή η αλλαγή κατάστασης για αυτή τη σελίδα", + "error.page.changeStatus.toDraft.invalid": "Δεν είναι δυνατή η μετατροπή της σελίδας \"{slug}\" σε προσχέδιο", + "error.page.changeTemplate.invalid": "Δεν είναι δυνατή η αλλαγή προτύπου για τη σελίδα \"{slug}\"", + "error.page.changeTemplate.permission": "Δεν επιτρέπεται να αλλάξετε το πρότυπο για τη σελίδα \"{slug}\"", + "error.page.changeTitle.empty": "Ο τίτλος δεν μπορεί να είναι κενός", + "error.page.changeTitle.permission": "Δεν επιτρέπεται να αλλάξετε τον τίτλο για τη σελίδα \"{slug}\"", + "error.page.create.permission": "Δεν επιτρέπεται να δημιουργήσετε τη σελίδα \"{slug}\"", + "error.page.delete": "Δεν είναι δυνατή η διαγραφή της σελίδας \"{slug}\"", + "error.page.delete.confirm": "Παρακαλώ εισάγετε τον τίτλο της σελίδας για επιβεβαίωση", + "error.page.delete.hasChildren": "Δεν είναι δυνατή η διαγραφή της σελίδας καθώς περιέχει υποσελίδες", + "error.page.delete.permission": "Δεν επιτρέπεται η διαγραφή της σελίδας \"{slug}\"", + "error.page.draft.duplicate": "Υπάρχει ήδη ένα προσχέδιο σελίδας με την διεύθυνση URL \"{slug}\"", + "error.page.duplicate": "Υπάρχει ήδη μια σελίδα με την διεύθυνση URL \"{slug}\"", + "error.page.duplicate.permission": "You are not allowed to duplicate \"{slug}\"", + "error.page.notFound": "Δεν ήταν δυνατή η εύρεση της σελίδας \"{slug}\"", + "error.page.num.invalid": "Παρακαλώ εισάγετε έναν έγκυρο αριθμό ταξινόμησης. Οι αριθμοί δεν μπορεί να είναι αρνητικοί.", + "error.page.slug.invalid": "Please enter a valid URL appendix", + "error.page.slug.maxlength": "Slug length must be less than \"{length}\" characters", + "error.page.sort.permission": "Δεν είναι δυνατή η ταξινόμηση της σελίδας \"{slug}\"", + "error.page.status.invalid": "Ορίστε μια έγκυρη κατάσταση σελίδας", + "error.page.undefined": "Δεν ήταν δυνατή η εύρεση της σελίδας", + "error.page.update.permission": "Δεν επιτρέπεται η ενημέρωση της σελίδας \"{slug}\"", + + "error.section.files.max.plural": "Δεν πρέπει να προσθέσετε περισσότερα από {max} αρχεία στην ενότητα \"{section}\"", + "error.section.files.max.singular": "Δεν πρέπει να προσθέσετε περισσότερα από ένα αρχεία στην ενότητα \"{section}\"", + "error.section.files.min.plural": "The \"{section}\" section requires at least {min} files", + "error.section.files.min.singular": "The \"{section}\" section requires at least one file", + + "error.section.pages.max.plural": "Δεν μπορείτε να προσθέσετε περισσότερες από {max} σελίδες στην ενότητα \"{section}\"", + "error.section.pages.max.singular": "Δεν μπορείτε να προσθέσετε περισσότερες από μία σελίδες στην ενότητα \"{section}\"", + "error.section.pages.min.plural": "The \"{section}\" section requires at least {min} pages", + "error.section.pages.min.singular": "The \"{section}\" section requires at least one page", + + "error.section.notLoaded": "Δεν ήταν δυνατή η φόρτωση της ενότητας \"{name}\"", + "error.section.type.invalid": "Ο τύπος ενότητας \"{type}\" δεν είναι έγκυρος", + + "error.site.changeTitle.empty": "Ο τίτλος δεν μπορεί να είναι κενός", + "error.site.changeTitle.permission": "Δεν επιτρέπεται να αλλάξετε τον τίτλο του ιστότοπου", + "error.site.update.permission": "Δεν επιτρέπεται η ενημέρωση του ιστότοπου", + + "error.template.default.notFound": "Το προεπιλεγμένο πρότυπο δεν υπάρχει", + + "error.user.changeEmail.permission": "Δεν επιτρέπεται να αλλάξετε τη διεύθινση ηλεκτρονικού ταχυδρομείου για τον χρήστη \"{name}\"", + "error.user.changeLanguage.permission": "Δεν επιτρέπεται να αλλάξετε τη γλώσσα για τον χρήστη \"{name}\"", + "error.user.changeName.permission": "Δεν επιτρέπεται να αλλάξετε το όνομα του χρήστη \"{name}", + "error.user.changePassword.permission": "Δεν επιτρέπεται να αλλάξετε τον κωδικό πρόσβασης για τον χρήστη \"{name}\"", + "error.user.changeRole.lastAdmin": "Ο ρόλος του τελευταίου διαχειριστή δεν μπορεί να αλλάξει", + "error.user.changeRole.permission": "Δεν επιτρέπεται να αλλάξετε το ρόλο του χρήστη \"{name}\"", + "error.user.changeRole.toAdmin": "You are not allowed to promote someone to the admin role", + "error.user.create.permission": "Δεν επιτρέπεται η δημιουργία αυτού του χρήστη", + "error.user.delete": "\u039f \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7\u03c2 \u03b4\u03b5\u03bd \u03bc\u03c0\u03bf\u03c1\u03bf\u03cd\u03c3\u03b5 \u03bd\u03b1 \u03b4\u03b9\u03b1\u03b3\u03c1\u03b1\u03c6\u03b5\u03af", + "error.user.delete.lastAdmin": "Δεν είναι δυνατή η διαγραφή του τελευταίου διαχειριστή", + "error.user.delete.lastUser": "Δεν είναι δυνατή η διαγραφή του τελευταίου χρήστη", + "error.user.delete.permission": "Δεν επιτρέπεται να διαγράψετ τον χρήστη \"{name}\"", + "error.user.duplicate": "Ένας χρήστης με τη διεύθυνση ηλεκτρονικού ταχυδρομείου \"{email}\" υπάρχει ήδη", + "error.user.email.invalid": "Παρακαλώ εισάγετε μια έγκυρη διεύθυνση ηλεκτρονικού ταχυδρομείου", + "error.user.language.invalid": "Παρακαλώ εισαγάγετε μια έγκυρη γλώσσα", + "error.user.notFound": "Δεν είναι δυνατή η εύρεση του χρήστη \"{name}\"", + "error.user.password.invalid": "Παρακαλώ εισάγετε έναν έγκυρο κωδικό πρόσβασης. Οι κωδικοί πρόσβασης πρέπει να έχουν μήκος τουλάχιστον 8 χαρακτήρων.", + "error.user.password.notSame": "\u03a0\u03b1\u03c1\u03b1\u03ba\u03b1\u03bb\u03bf\u03cd\u03bc\u03b5 \u03b5\u03c0\u03b9\u03b2\u03b5\u03b2\u03b1\u03b9\u03ce\u03c3\u03c4\u03b5 \u03c4\u03bf\u03bd \u039a\u03c9\u03b4\u03b9\u03ba\u03cc \u03a0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", + "error.user.password.undefined": "Ο χρήστης δεν έχει κωδικό πρόσβασης", + "error.user.password.wrong": "Wrong password", + "error.user.role.invalid": "Παρακαλώ εισαγάγετε έναν έγκυρο ρόλο", + "error.user.update.permission": "Δεν επιτρέπεται η ενημέρωση του χρήστη \"{name}\"", + + "error.validation.accepted": "Παρακαλώ επιβεβαιώστε", + "error.validation.alpha": "Παρακαλώ εισάγετε μόνο χαρακτήρες μεταξύ των a-z", + "error.validation.alphanum": "Παρακαλώ εισάγετε μόνο χαρακτήρες μεταξύ των a-z ή αριθμούς απο το 0 έως το 9", + "error.validation.between": "Παρακαλώ εισάγετε μια τιμή μεταξύ \"{min}\" και \"{max}\"", + "error.validation.boolean": "Παρακαλώ επιβεβαιώστε ή αρνηθείτε", + "error.validation.contains": "Παρακαλώ καταχωρίστε μια τιμή που περιέχει \"{needle}\"", + "error.validation.date": "Παρακαλώ εισάγετε μία έγκυρη ημερομηνία", + "error.validation.date.after": "Please enter a date after {date}", + "error.validation.date.before": "Please enter a date before {date}", + "error.validation.date.between": "Please enter a date between {min} and {max}", + "error.validation.denied": "Παρακαλώ αρνηθείτε", + "error.validation.different": "Η τιμή δεν μπορεί να είναι \"{other}\"", + "error.validation.email": "Παρακαλώ εισάγετε μια έγκυρη διεύθυνση ηλεκτρονικού ταχυδρομείου", + "error.validation.endswith": "Η τιμή πρέπει να τελειώνει με \"{end}\"", + "error.validation.filename": "Παρακαλώ εισάγετε ένα έγκυρο όνομα αρχείου", + "error.validation.in": "Παρακαλώ εισάγετε ένα από τα παρακάτω: ({in})", + "error.validation.integer": "Παρακαλώ εισάγετε έναν έγκυρο ακέραιο αριθμό", + "error.validation.ip": "Παρακαλώ εισάγετε μια έγκυρη διεύθυνση IP", + "error.validation.less": "Παρακαλώ εισάγετε μια τιμή μικρότερη από {max}", + "error.validation.match": "Η τιμή δεν ταιριάζει με το αναμενόμενο πρότυπο", + "error.validation.max": "Παρακαλώ εισάγετε μια τιμή ίση ή μικρότερη από {max}", + "error.validation.maxlength": "Παρακαλώ εισάγετε μια μικρότερη τιμή. (max. {max} χαρακτήρες)", + "error.validation.maxwords": "Παρακαλώ εισάγετε το πολύ {max} λέξεις", + "error.validation.min": "Παρακαλώ εισάγετε μια τιμή ίση ή μεγαλύτερη από {min}", + "error.validation.minlength": "Παρακαλώ εισάγετε μεγαλύτερη τιμή. (τουλάχιστον {min} χαρακτήρες)", + "error.validation.minwords": "Παρακαλώ εισάγετε τουλάχιστον {min} λέξεις", + "error.validation.more": "Παρακαλώ εισάγετε τουλάχιστον {min} λέξεις", + "error.validation.notcontains": "Παρακαλώ εισάγετε μια τιμή που δεν περιέχει \"{needle}\"", + "error.validation.notin": "Παρακαλώ μην εισάγετε κανένα από τα παρακάτω: ({notIn})", + "error.validation.option": "Παρακαλώ κάντε μια έγκυρη επιλογή", + "error.validation.num": "Παρακαλώ εισάγετε έναν έγκυρο αριθμό", + "error.validation.required": "Παρακαλώ εισάγετε κάτι", + "error.validation.same": "Παρακαλώ εισάγετε \"{other}\"", + "error.validation.size": "Το μέγεθος της τιμής πρέπει να είναι \"{size}\"", + "error.validation.startswith": "Η τιμή πρέπει να αρχίζει με \"{start}\"", + "error.validation.time": "Παρακαλώ εισάγετε μια έγκυρη ώρα", + "error.validation.time.after": "Please enter a time after {time}", + "error.validation.time.before": "Please enter a time before {time}", + "error.validation.time.between": "Please enter a time between {min} and {max}", + "error.validation.url": "Παρακαλώ εισάγετε μια έγκυρη διεύθυνση URL", + + "field.required": "The field is required", + "field.blocks.changeType": "Change type", + "field.blocks.code.name": "Κώδικας", + "field.blocks.code.language": "Γλώσσα", + "field.blocks.code.placeholder": "Your code …", + "field.blocks.delete.confirm": "Do you really want to delete this block?", + "field.blocks.delete.confirm.all": "Do you really want to delete all blocks?", + "field.blocks.delete.confirm.selected": "Do you really want to delete the selected blocks?", + "field.blocks.empty": "No blocks yet", + "field.blocks.fieldsets.label": "Please select a block type …", + "field.blocks.gallery.name": "Gallery", + "field.blocks.gallery.images.empty": "No images yet", + "field.blocks.gallery.images.label": "Images", + "field.blocks.heading.level": "Level", + "field.blocks.heading.name": "Heading", + "field.blocks.heading.text": "Text", + "field.blocks.heading.placeholder": "Heading …", + "field.blocks.image.alt": "Alternative text", + "field.blocks.image.caption": "Caption", + "field.blocks.image.crop": "Crop", + "field.blocks.image.link": "Σύνδεσμος", + "field.blocks.image.location": "Location", + "field.blocks.image.name": "Εικόνα", + "field.blocks.image.placeholder": "Select an image", + "field.blocks.image.ratio": "Ratio", + "field.blocks.image.url": "Image URL", + "field.blocks.list.name": "List", + "field.blocks.markdown.name": "Markdown", + "field.blocks.markdown.label": "Text", + "field.blocks.markdown.placeholder": "Markdown …", + "field.blocks.quote.name": "Quote", + "field.blocks.quote.text.label": "Text", + "field.blocks.quote.text.placeholder": "Quote …", + "field.blocks.quote.citation.label": "Citation", + "field.blocks.quote.citation.placeholder": "by …", + "field.blocks.text.name": "Text", + "field.blocks.text.placeholder": "Text …", + "field.blocks.video.caption": "Caption", + "field.blocks.video.name": "Video", + "field.blocks.video.placeholder": "Enter a video URL", + "field.blocks.video.url.label": "Video-URL", + "field.blocks.video.url.placeholder": "https://youtube.com/?v=", + + "field.files.empty": "Δεν έχουν επιλεγεί αρχεία ακόμα", + + "field.layout.delete": "Delete layout", + "field.layout.delete.confirm": "Do you really want to delete this layout?", + "field.layout.empty": "No rows yet", + "field.layout.select": "Select a layout", + + "field.pages.empty": "Δεν έχουν επιλεγεί ακόμη σελίδες", + "field.structure.delete.confirm": "\u0395\u03af\u03c3\u03c4\u03b5 \u03c3\u03af\u03b3\u03bf\u03c5\u03c1\u03bf\u03c2 \u03cc\u03c4\u03b9 \u03b8\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03b4\u03b9\u03b1\u03b3\u03c1\u03ac\u03c8\u03b5\u03c4\u03b5 \u03b1\u03c5\u03c4\u03ae\u03bd \u03c4\u03b7\u03bd \u03ba\u03b1\u03c4\u03b1\u03c7\u03ce\u03c1\u03b9\u03c3\u03b7;", + "field.structure.empty": "\u0394\u03b5\u03bd \u03c5\u03c0\u03ac\u03c1\u03c7\u03bf\u03c5\u03bd \u03b1\u03ba\u03cc\u03bc\u03b7 \u03ba\u03b1\u03c4\u03b1\u03c7\u03c9\u03c1\u03af\u03c3\u03b5\u03b9\u03c2.", + "field.users.empty": "Δεν έχουν επιλεγεί ακόμη χρήστες", + + "file.blueprint": "This file has no blueprint yet. You can define the setup in /site/blueprints/{template}.yml", + "file.delete.confirm": "\u0398\u03ad\u03bb\u03b5\u03c4\u03b5 \u03c3\u03af\u03b3\u03bf\u03c5\u03c1\u03b1 \u03bd\u03b1 \u03b4\u03b9\u03b1\u03b3\u03c1\u03ac\u03c8\u03b5\u03c4\u03b5 \u03b1\u03c5\u03c4\u03cc \u03c4\u03bf \u03b1\u03c1\u03c7\u03b5\u03af\u03bf;", + "file.sort": "Change position", + + "files": "Αρχεία", + "files.empty": "Δεν υπάρχουν ακόμα αρχεία", + + "hide": "Hide", + "hour": "Ώρα", + "insert": "\u0395\u03b9\u03c3\u03b1\u03b3\u03c9\u03b3\u03ae", + "insert.after": "Insert after", + "insert.before": "Insert before", + "install": "Εγκατάσταση", + + "installation": "Εγκατάσταση", + "installation.completed": "Ο πίνακας ελέγχου έχει εγκατασταθεί", + "installation.disabled": "Η εγκατάσταση του πίνακα ελέγχου είναι απενεργοποιημένη για δημόσιους διακομιστές από προεπιλογή. Εκτελέστε την εγκατάσταση σε ένα τοπικό μηχάνημα ή ενεργοποιήστε την με την επιλογή panel.install.", + "installation.issues.accounts": "\u039f \u03c6\u03ac\u03ba\u03b5\u03bb\u03bf\u03c2 \/site\/accounts \u03b4\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03b5\u03b3\u03b3\u03c1\u03ac\u03c8\u03b9\u03bc\u03bf\u03c2", + "installation.issues.content": "\u039f \u03c6\u03ac\u03ba\u03b5\u03bb\u03bf\u03c2 content \u03ba\u03b1\u03b9 \u03cc\u03bb\u03bf\u03b9 \u03bf\u03b9 \u03c5\u03c0\u03bf\u03c6\u03ac\u03ba\u03b5\u03bb\u03bf\u03b9 \u03c0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03b5\u03af\u03bd\u03b1\u03b9 \u03b5\u03b3\u03b3\u03c1\u03ac\u03c8\u03b9\u03bc\u03bf\u03b9.", + "installation.issues.curl": "Απαιτείται η επέκταση CURL", + "installation.issues.headline": "Ο πίνακας ελέγχου δεν μπορεί να εγκατασταθεί", + "installation.issues.mbstring": "Απαιτείται η επέκταση MB String ", + "installation.issues.media": "Ο φάκελος /media δεν υπάρχει ή δεν είναι εγγράψιμος", + "installation.issues.php": "Βεβαιωθείτε ότι χρησιμοποιήτε PHP 7+", + "installation.issues.server": "To Kirby απαιτεί Apache, Nginx ή Caddy", + "installation.issues.sessions": "Ο φάκελος /site/sessions δεν υπάρχει ή δεν είναι εγγράψιμος", + + "language": "\u0393\u03bb\u03ce\u03c3\u03c3\u03b1", + "language.code": "Κώδικας", + "language.convert": "Χρήση ως προεπιλογή", + "language.convert.confirm": "

Θέλετε πραγματικά να μετατρέψετε τη {name} στην προεπιλεγμένη γλώσσα; Αυτό δεν μπορεί να ανακληθεί.

Αν το {name} χει μη μεταφρασμένο περιεχόμενο, δεν θα υπάρχει πλέον έγκυρη εναλλακτική λύση και τμήματα του ιστότοπού σας ενδέχεται να είναι κενά.

", + "language.create": "Προσθέστε μια νέα γλώσσα", + "language.delete.confirm": "Θέλετε πραγματικά να διαγράψετε τη γλώσσα {name} συμπεριλαμβανομένων όλων των μεταφράσεων; Αυτό δεν μπορεί να αναιρεθεί!", + "language.deleted": "Η γλώσσα έχει διαγραφεί", + "language.direction": "Κατεύθυνση ανάγνωσης", + "language.direction.ltr": "Αριστερά προς τα δεξιά", + "language.direction.rtl": "Δεξιά προς τα αριστερά", + "language.locale": "Συμβολοσειρά τοπικής γλώσσας PHP", + "language.locale.warning": "You are using a custom locale set up. Please modify it in the language file in /site/languages", + "language.name": "Ονομασία", + "language.updated": "Η γλώσσα έχει ενημερωθεί", + + "languages": "Γλώσσες", + "languages.default": "Προεπιλεγμένη γλώσσα", + "languages.empty": "Δεν υπάρχουν ακόμη γλώσσες", + "languages.secondary": "Δευτερεύουσες γλώσσες", + "languages.secondary.empty": "Δεν υπάρχουν ακόμα δευτερεύουσες γλώσσες", + + "license": "\u0386\u03b4\u03b5\u03b9\u03b1 \u03a7\u03c1\u03ae\u03c3\u03b7\u03c2 \u03c4\u03bf\u03c5 Kirby", + "license.buy": "Αγοράστε μια άδεια", + "license.register": "Εγγραφή", + "license.register.help": "Έχετε λάβει τον κωδικό άδειας χρήσης μετά την αγορά μέσω ηλεκτρονικού ταχυδρομείου. Παρακαλώ αντιγράψτε και επικολλήστε τον για να εγγραφείτε.", + "license.register.label": "Παρακαλώ εισαγάγετε τον κωδικό άδειας χρήσης", + "license.register.success": "Σας ευχαριστούμε για την υποστήριξη του Kirby", + "license.unregistered": "Αυτό είναι ένα μη καταχωρημένο demo του Kirby", + + "link": "\u03a3\u03cd\u03bd\u03b4\u03b5\u03c3\u03bc\u03bf\u03c2", + "link.text": "\u039a\u03b5\u03af\u03bc\u03b5\u03bd\u03bf \u03c3\u03c5\u03bd\u03b4\u03ad\u03c3\u03bc\u03bf\u03c5", + + "loading": "Φόρτωση", + + "lock.unsaved": "Unsaved changes", + "lock.unsaved.empty": "There are no more unsaved changes", + "lock.isLocked": "Unsaved changes by {email}", + "lock.file.isLocked": "The file is currently being edited by {email} and cannot be changed.", + "lock.page.isLocked": "The page is currently being edited by {email} and cannot be changed.", + "lock.unlock": "Unlock", + "lock.isUnlocked": "Your unsaved changes have been overwritten by another user. You can download your changes to merge them manually.", + + "login": "\u03a3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7", + "login.code.label.login": "Login code", + "login.code.label.password-reset": "Password reset code", + "login.code.placeholder.email": "000 000", + "login.code.text.email": "If your email address is registered, the requested code was sent via email.", + "login.email.login.body": "Hi {user.nameOrEmail},\n\nYou recently requested a login code for the Kirby Panel.\nThe following login code will be valid for {timeout} minutes:\n\n{code}\n\nIf you did not request a login code, please ignore this email or contact your administrator if you have questions.\nFor security, please DO NOT forward this email.", + "login.email.login.subject": "Your login code", + "login.email.password-reset.body": "Hi {user.nameOrEmail},\n\nYou recently requested a password reset code for the Kirby Panel.\nThe following password reset code will be valid for {timeout} minutes:\n\n{code}\n\nIf you did not request a password reset code, please ignore this email or contact your administrator if you have questions.\nFor security, please DO NOT forward this email.", + "login.email.password-reset.subject": "Your password reset code", + "login.remember": "Κρατήστε με συνδεδεμένο", + "login.reset": "Reset password", + "login.toggleText.code.email": "Login via email", + "login.toggleText.code.email-password": "Login with password", + "login.toggleText.password-reset.email": "Forgot your password?", + "login.toggleText.password-reset.email-password": "← Back to login", + + "logout": "\u0391\u03c0\u03bf\u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7", + + "menu": "Μενού", + "meridiem": "Π.Μ./Μ.Μ", + "mime": "Τύπος πολυμέσων", + "minutes": "Λεπτά", + + "month": "Μήνας", + "months.april": "\u0391\u03c0\u03c1\u03af\u03bb\u03b9\u03bf\u03c2", + "months.august": "\u0391\u03cd\u03b3\u03bf\u03c5\u03c3\u03c4\u03bf\u03c2", + "months.december": "\u0394\u03b5\u03ba\u03ad\u03bc\u03b2\u03c1\u03b9\u03bf\u03c2", + "months.february": "Φεβρουάριος", + "months.january": "\u0399\u03b1\u03bd\u03bf\u03c5\u03ac\u03c1\u03b9\u03bf\u03c2", + "months.july": "\u0399\u03bf\u03cd\u03bb\u03b9\u03bf\u03c2", + "months.june": "\u0399\u03bf\u03cd\u03bd\u03b9\u03bf\u03c2", + "months.march": "\u039c\u03ac\u03c1\u03c4\u03b9\u03bf\u03c2", + "months.may": "\u039c\u03ac\u03b9\u03bf\u03c2", + "months.november": "\u039d\u03bf\u03ad\u03bc\u03b2\u03c1\u03b9\u03bf\u03c2", + "months.october": "\u039f\u03ba\u03c4\u03ce\u03b2\u03c1\u03b9\u03bf\u03c2", + "months.september": "\u03a3\u03b5\u03c0\u03c4\u03ad\u03bc\u03b2\u03c1\u03b9\u03bf\u03c2", + + "more": "Περισσότερα", + "name": "Ονομασία", + "next": "Επόμενο", + "no": "no", + "off": "off", + "on": "on", + "open": "Άνοιγμα", + "open.newWindow": "Open in new window", + "options": "Eπιλογές", + "options.none": "No options", + + "orientation": "Προσανατολισμός", + "orientation.landscape": "Οριζόντιος", + "orientation.portrait": "Κάθετος", + "orientation.square": "Τετράγωνος", + + "page.blueprint": "This page has no blueprint yet. You can define the setup in /site/blueprints/{template}.yml", + "page.changeSlug": "\u0391\u03bb\u03bb\u03b1\u03b3\u03ae URL", + "page.changeSlug.fromTitle": "\u0394\u03b7\u03bc\u03b9\u03bf\u03c5\u03c1\u03b3\u03af\u03b1 \u03b1\u03c0\u03cc \u03c4\u03bf\u03bd \u03c4\u03af\u03c4\u03bb\u03bf", + "page.changeStatus": "Αλλαγή κατάστασης", + "page.changeStatus.position": "Επιλέξτε μια θέση", + "page.changeStatus.select": "Επιλέξτε μια νέα κατάσταση", + "page.changeTemplate": "Αλλαγή προτύπου", + "page.delete.confirm": "\u0398\u03ad\u03bb\u03b5\u03c4\u03b5 \u03c3\u03af\u03b3\u03bf\u03c5\u03c1\u03b1 \u03bd\u03b1 \u03b4\u03b9\u03b1\u03b3\u03c1\u03ac\u03c8\u03b5\u03c4\u03b5 \u03b1\u03c5\u03c4\u03ae\u03bd \u03c4\u03b7 \u03c3\u03b5\u03bb\u03af\u03b4\u03b1;", + "page.delete.confirm.subpages": "Αυτή η σελίδα έχει υποσελίδες.
Όλες οι υποσελίδες θα διαγραφούν επίσης.", + "page.delete.confirm.title": "Εισάγετε τον τίτλο της σελίδας για επιβεβαίωση", + "page.draft.create": "Δημιουργία προσχεδίου", + "page.duplicate.appendix": "Αντιγραφή", + "page.duplicate.files": "Copy files", + "page.duplicate.pages": "Copy pages", + "page.sort": "Change position", + "page.status": "Kατάσταση", + "page.status.draft": "Προσχέδιο", + "page.status.draft.description": "The page is in draft mode and only visible for logged in editors or via secret link", + "page.status.listed": "Δημοσιευμένο", + "page.status.listed.description": "Αυτή η σελίδα είναι δημοσιευμένη για οποιονδήποτε", + "page.status.unlisted": "Μη καταχωρημένο", + "page.status.unlisted.description": "Η σελίδα είναι προσβάσιμη μόνο μέσω της διεύθυνσης URL", + + "pages": "Σελίδες", + "pages.empty": "Δεν υπάρχουν ακόμα σελίδες", + "pages.status.draft": "Προσχέδια", + "pages.status.listed": "Δημοσιευμένο", + "pages.status.unlisted": "Μη καταχωρημένο", + + "pagination.page": "Σελίδα", + + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03a0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", + "pixel": "Εικονοστοιχέιο", + "prev": "Προηγούμενο", + "preview": "Preview", + "remove": "Αφαίρεση", + "rename": "Μετονομασία", + "replace": "\u0391\u03bd\u03c4\u03b9\u03ba\u03b1\u03c4\u03ac\u03c3\u03c4\u03b1\u03c3\u03b7", + "retry": "\u0395\u03c0\u03b1\u03bd\u03ac\u03bb\u03b7\u03c8\u03b7", + "revert": "\u0391\u03b3\u03bd\u03cc\u03b7\u03c3\u03b7", + "revert.confirm": "Do you really want to delete all unsaved changes?", + + "role": "\u03a1\u03cc\u03bb\u03bf\u03c2", + "role.admin.description": "The admin has all rights", + "role.admin.title": "Admin", + "role.all": "Όλα", + "role.empty": "Δεν υπάρχουν χρήστες με αυτόν τον ρόλο", + "role.description.placeholder": "Χωρίς περιγραφή", + "role.nobody.description": "This is a fallback role without any permissions", + "role.nobody.title": "Nobody", + + "save": "\u0391\u03c0\u03bf\u03b8\u03ae\u03ba\u03b5\u03c5\u03c3\u03b7", + "search": "Αναζήτηση", + "search.min": "Enter {min} characters to search", + "search.all": "Show all", + "search.results.none": "No results", + + "section.required": "The section is required", + + "select": "Επιλογή", + "settings": "Ρυθμίσεις", + "show": "Show", + "size": "Μέγεθος", + "slug": "\u0395\u03c0\u03af\u03b8\u03b5\u03bc\u03b1 URL", + "sort": "Ταξινόμηση", + "title": "Τίτλος", + "template": "\u03a0\u03c1\u03cc\u03c4\u03c5\u03c0\u03bf", + "today": "Σήμερα", + + "site.blueprint": "The site has no blueprint yet. You can define the setup in /site/blueprints/site.yml", + + "toolbar.button.code": "Κώδικας", + "toolbar.button.bold": "\u0388\u03bd\u03c4\u03bf\u03bd\u03b7 \u03b3\u03c1\u03b1\u03c6\u03ae", + "toolbar.button.email": "Email", + "toolbar.button.headings": "Επικεφαλίδες", + "toolbar.button.heading.1": "Επικεφαλίδα 1", + "toolbar.button.heading.2": "Επικεφαλίδα 2", + "toolbar.button.heading.3": "Επικεφαλίδα 3", + "toolbar.button.italic": "\u03a0\u03bb\u03ac\u03b3\u03b9\u03b1 \u03b3\u03c1\u03b1\u03c6\u03ae", + "toolbar.button.file": "Αρχείο", + "toolbar.button.file.select": "Select a file", + "toolbar.button.file.upload": "Upload a file", + "toolbar.button.link": "\u03a3\u03cd\u03bd\u03b4\u03b5\u03c3\u03bc\u03bf\u03c2", + "toolbar.button.strike": "Strike-through", + "toolbar.button.ol": "Ταξινομημένη λίστα", + "toolbar.button.underline": "Underline", + "toolbar.button.ul": "Λίστα κουκκίδων", + + "translation.author": "Ομάδα Kirby", + "translation.direction": "ltr", + "translation.name": "\u0395\u03bb\u03bb\u03b7\u03bd\u03b9\u03ba\u03ac", + "translation.locale": "el_GR", + + "upload": "Μεταφόρτωση", + "upload.error.cantMove": "The uploaded file could not be moved", + "upload.error.cantWrite": "Failed to write file to disk", + "upload.error.default": "The file could not be uploaded", + "upload.error.extension": "File upload stopped by extension", + "upload.error.formSize": "The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the form", + "upload.error.iniPostSize": "The uploaded file exceeds the post_max_size directive in php.ini", + "upload.error.iniSize": "The uploaded file exceeds the upload_max_filesize directive in php.ini", + "upload.error.noFile": "No file was uploaded", + "upload.error.noFiles": "No files were uploaded", + "upload.error.partial": "The uploaded file was only partially uploaded", + "upload.error.tmpDir": "Missing a temporary folder", + "upload.errors": "Σφάλμα", + "upload.progress": "Μεταφόρτωση...", + + "url": "Διεύθινση url", + "url.placeholder": "https://example.com", + + "user": "Χρήστης", + "user.blueprint": "Μπορείτε να ορίσετε επιπλέον τμήματα και πεδία φόρμας για αυτόν τον ρόλο χρήστη στο /site/blueprints/users/{role}.yml", + "user.changeEmail": "Αλλαγή διεύθινσης ηλεκτρονικού ταχυδρομείου", + "user.changeLanguage": "Αλλαγή γλώσσας", + "user.changeName": "Μετονομασία χρήστη", + "user.changePassword": "Αλλαγή κωδικού πρόσβασης", + "user.changePassword.new": "Νέος Κωδικός Πρόσβασης", + "user.changePassword.new.confirm": "Επαληθεύση κωδικού πρόσβασης", + "user.changeRole": "Αλλαγή ρόλου", + "user.changeRole.select": "Επιλογή νέου ρόλου", + "user.create": "Προσθήκη νέου χρήστη", + "user.delete": "Διαγραφή χρήστη", + "user.delete.confirm": "\u0398\u03ad\u03bb\u03b5\u03c4\u03b5 \u03c3\u03af\u03b3\u03bf\u03c5\u03c1\u03b1 \u03bd\u03b1 \u03b4\u03b9\u03b1\u03b3\u03c1\u03ac\u03c8\u03b5\u03c4\u03b5 \u03b1\u03c5\u03c4\u03cc\u03bd \u03c4\u03bf\u03bd \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7;", + + "users": "Χρήστες", + + "version": "\u0388\u03ba\u03b4\u03bf\u03c3\u03b7 Kirby", + + "view.account": "\u039f \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc\u03c2 \u03c3\u03b1\u03c2", + "view.installation": "\u0395\u03b3\u03ba\u03b1\u03c4\u03ac\u03c3\u03c4\u03b1\u03c3\u03b7", + "view.resetPassword": "Reset password", + "view.settings": "Ρυθμίσεις", + "view.site": "Iστοσελίδα", + "view.users": "\u03a7\u03c1\u03ae\u03c3\u03c4\u03b5\u03c2", + + "welcome": "Καλώς ήρθατε", + "year": "Έτος", + "yes": "yes" +} diff --git a/kirby/i18n/translations/en.json b/kirby/i18n/translations/en.json new file mode 100644 index 0000000..d484572 --- /dev/null +++ b/kirby/i18n/translations/en.json @@ -0,0 +1,527 @@ +{ + "add": "Add", + "avatar": "Profile picture", + "back": "Back", + "cancel": "Cancel", + "change": "Change", + "close": "Close", + "confirm": "Ok", + "collapse": "Collapse", + "collapse.all": "Collapse All", + "copy": "Copy", + "create": "Create", + + "date": "Date", + "date.select": "Select a date", + + "day": "Day", + "days.fri": "Fri", + "days.mon": "Mon", + "days.sat": "Sat", + "days.sun": "Sun", + "days.thu": "Thu", + "days.tue": "Tue", + "days.wed": "Wed", + + "delete": "Delete", + "delete.all": "Delete all", + "dimensions": "Dimensions", + "disabled": "Disabled", + "discard": "Discard", + "download": "Download", + "duplicate": "Duplicate", + "edit": "Edit", + "expand": "Expand", + "expand.all": "Expand All", + + "dialog.files.empty": "No files to select", + "dialog.pages.empty": "No pages to select", + "dialog.users.empty": "No users to select", + + "email": "Email", + "email.placeholder": "mail@example.com", + + "error.access.code": "Invalid code", + "error.access.login": "Invalid login", + "error.access.panel": "You are not allowed to access the panel", + "error.access.view": "You are not allowed to access this part of the panel", + + "error.avatar.create.fail": "The profile picture could not be uploaded", + "error.avatar.delete.fail": "The profile picture could not be deleted", + "error.avatar.dimensions.invalid": "Please keep the width and height of the profile picture under 3000 pixels", + "error.avatar.mime.forbidden": "The profile picture must be JPEG or PNG files", + + "error.blueprint.notFound": "The blueprint \"{name}\" could not be loaded", + + "error.blocks.max.plural": "You must not add more than {max} blocks", + "error.blocks.max.singular": "You must not add more than one block", + "error.blocks.min.plural": "You must add at least {min} blocks", + "error.blocks.min.singular": "You must add at least one block", + "error.blocks.validation": "There's an error in block {index}", + + "error.email.preset.notFound": "The email preset \"{name}\" cannot be found", + + "error.field.converter.invalid": "Invalid converter \"{converter}\"", + + "error.file.changeName.empty": "The name must not be empty", + "error.file.changeName.permission": "You are not allowed to change the name of \"{filename}\"", + "error.file.duplicate": "A file with the name \"{filename}\" already exists", + "error.file.extension.forbidden": "The extension \"{extension}\" is not allowed", + "error.file.extension.invalid": "Invalid extension: {extension}", + "error.file.extension.missing": "The extensions for \"{filename}\" is missing", + "error.file.maxheight": "The height of the image must not exceed {height} pixels", + "error.file.maxsize": "The file is too large", + "error.file.maxwidth": "The width of the image must not exceed {width} pixels", + "error.file.mime.differs": "The uploaded file must be of the same mime type \"{mime}\"", + "error.file.mime.forbidden": "The media type \"{mime}\" is not allowed", + "error.file.mime.invalid": "Invalid mime type: {mime}", + "error.file.mime.missing": "The media type for \"{filename}\" cannot be detected", + "error.file.minheight": "The height of the image must be at least {height} pixels", + "error.file.minsize": "The file is too small", + "error.file.minwidth": "The width of the image must be at least {width} pixels", + "error.file.name.missing": "The filename must not be empty", + "error.file.notFound": "The file \"{filename}\" cannot be found", + "error.file.orientation": "The orientation of the image must be \"{orientation}\"", + "error.file.type.forbidden": "You are not allowed to upload {type} files", + "error.file.type.invalid": "Invalid file type: {type}", + "error.file.undefined": "The file cannot be found", + + "error.form.incomplete": "Please fix all form errors…", + "error.form.notSaved": "The form could not be saved", + + "error.language.code": "Please enter a valid code for the language", + "error.language.duplicate": "The language already exists", + "error.language.name": "Please enter a valid name for the language", + + "error.layout.validation.block": "There's an error in block {blockIndex} in layout {layoutIndex}", + "error.layout.validation.settings": "There's an error in layout {index} settings", + + "error.license.format": "Please enter a valid license key", + "error.license.email": "Please enter a valid email address", + "error.license.verification": "The license could not be verified", + + "error.page.changeSlug.permission": "You are not allowed to change the URL appendix for \"{slug}\"", + "error.page.changeStatus.incomplete": "The page has errors and cannot be published", + "error.page.changeStatus.permission": "The status for this page cannot be changed", + "error.page.changeStatus.toDraft.invalid": "The page \"{slug}\" cannot be converted to a draft", + "error.page.changeTemplate.invalid": "The template for the page \"{slug}\" cannot be changed", + "error.page.changeTemplate.permission": "You are not allowed to change the template for \"{slug}\"", + "error.page.changeTitle.empty": "The title must not be empty", + "error.page.changeTitle.permission": "You are not allowed to change the title for \"{slug}\"", + "error.page.create.permission": "You are not allowed to create \"{slug}\"", + "error.page.delete": "The page \"{slug}\" cannot be deleted", + "error.page.delete.confirm": "Please enter the page title to confirm", + "error.page.delete.hasChildren": "The page has subpages and cannot be deleted", + "error.page.delete.permission": "You are not allowed to delete \"{slug}\"", + "error.page.draft.duplicate": "A page draft with the URL appendix \"{slug}\" already exists", + "error.page.duplicate": "A page with the URL appendix \"{slug}\" already exists", + "error.page.duplicate.permission": "You are not allowed to duplicate \"{slug}\"", + "error.page.notFound": "The page \"{slug}\" cannot be found", + "error.page.num.invalid": "Please enter a valid sorting number. Numbers must not be negative.", + "error.page.slug.invalid": "Please enter a valid URL appendix", + "error.page.slug.maxlength": "Slug length must be less than \"{length}\" characters", + "error.page.sort.permission": "The page \"{slug}\" cannot be sorted", + "error.page.status.invalid": "Please set a valid page status", + "error.page.undefined": "The page cannot be found", + "error.page.update.permission": "You are not allowed to update \"{slug}\"", + + "error.section.files.max.plural": "You must not add more than {max} files to the \"{section}\" section", + "error.section.files.max.singular": "You must not add more than one file to the \"{section}\" section", + "error.section.files.min.plural": "The \"{section}\" section requires at least {min} files", + "error.section.files.min.singular": "The \"{section}\" section requires at least one file", + + "error.section.pages.max.plural": "You must not add more than {max} pages to the \"{section}\" section", + "error.section.pages.max.singular": "You must not add more than one page to the \"{section}\" section", + "error.section.pages.min.plural": "The \"{section}\" section requires at least {min} pages", + "error.section.pages.min.singular": "The \"{section}\" section requires at least one page", + + "error.section.notLoaded": "The section \"{name}\" could not be loaded", + "error.section.type.invalid": "The section type \"{type}\" is not valid", + + "error.site.changeTitle.empty": "The title must not be empty", + "error.site.changeTitle.permission": "You are not allowed to change the title of the site", + "error.site.update.permission": "You are not allowed to update the site", + + "error.template.default.notFound": "The default template does not exist", + + "error.user.changeEmail.permission": "You are not allowed to change the email for the user \"{name}\"", + "error.user.changeLanguage.permission": "You are not allowed to change the language for the user \"{name}\"", + "error.user.changeName.permission": "You are not allowed to change the name for the user \"{name}\"", + "error.user.changePassword.permission": "You are not allowed to change the password for the user \"{name}\"", + "error.user.changeRole.lastAdmin": "The role for the last admin cannot be changed", + "error.user.changeRole.permission": "You are not allowed to change the role for the user \"{name}\"", + "error.user.changeRole.toAdmin": "You are not allowed to promote someone to the admin role", + "error.user.create.permission": "You are not allowed to create this user", + "error.user.delete": "The user \"{name}\" cannot be deleted", + "error.user.delete.lastAdmin": "The last admin cannot be deleted", + "error.user.delete.lastUser": "The last user cannot be deleted", + "error.user.delete.permission": "You are not allowed to delete the user \"{name}\"", + "error.user.duplicate": "A user with the email address \"{email}\" already exists", + "error.user.email.invalid": "Please enter a valid email address", + "error.user.language.invalid": "Please enter a valid language", + "error.user.notFound": "The user \"{name}\" cannot be found", + "error.user.password.invalid": "Please enter a valid password. Passwords must be at least 8 characters long.", + "error.user.password.notSame": "The passwords do not match", + "error.user.password.undefined": "The user does not have a password", + "error.user.password.wrong": "Wrong password", + "error.user.role.invalid": "Please enter a valid role", + "error.user.update.permission": "You are not allowed to update the user \"{name}\"", + + "error.validation.accepted": "Please confirm", + "error.validation.alpha": "Please only enter characters between a-z", + "error.validation.alphanum": "Please only enter characters between a-z or numerals 0-9", + "error.validation.between": "Please enter a value between \"{min}\" and \"{max}\"", + "error.validation.boolean": "Please confirm or deny", + "error.validation.contains": "Please enter a value that contains \"{needle}\"", + "error.validation.date": "Please enter a valid date", + "error.validation.date.after": "Please enter a date after {date}", + "error.validation.date.before": "Please enter a date before {date}", + "error.validation.date.between": "Please enter a date between {min} and {max}", + "error.validation.denied": "Please deny", + "error.validation.different": "The value must not be \"{other}\"", + "error.validation.email": "Please enter a valid email address", + "error.validation.endswith": "The value must end with \"{end}\"", + "error.validation.filename": "Please enter a valid filename", + "error.validation.in": "Please enter one of the following: ({in})", + "error.validation.integer": "Please enter a valid integer", + "error.validation.ip": "Please enter a valid IP address", + "error.validation.less": "Please enter a value lower than {max}", + "error.validation.match": "The value does not match the expected pattern", + "error.validation.max": "Please enter a value equal to or lower than {max}", + "error.validation.maxlength": "Please enter a shorter value. (max. {max} characters)", + "error.validation.maxwords": "Please enter no more than {max} word(s)", + "error.validation.min": "Please enter a value equal to or greater than {min}", + "error.validation.minlength": "Please enter a longer value. (min. {min} characters)", + "error.validation.minwords": "Please enter at least {min} word(s)", + "error.validation.more": "Please enter a greater value than {min}", + "error.validation.notcontains": "Please enter a value that does not contain \"{needle}\"", + "error.validation.notin": "Please don't enter any of the following: ({notIn})", + "error.validation.option": "Please select a valid option", + "error.validation.num": "Please enter a valid number", + "error.validation.required": "Please enter something", + "error.validation.same": "Please enter \"{other}\"", + "error.validation.size": "The size of the value must be \"{size}\"", + "error.validation.startswith": "The value must start with \"{start}\"", + "error.validation.time": "Please enter a valid time", + "error.validation.time.after": "Please enter a time after {time}", + "error.validation.time.before": "Please enter a time before {time}", + "error.validation.time.between": "Please enter a time between {min} and {max}", + "error.validation.url": "Please enter a valid URL", + + "field.required": "The field is required", + "field.blocks.changeType": "Change type", + "field.blocks.code.name": "Code", + "field.blocks.code.language": "Language", + "field.blocks.code.placeholder": "Your code …", + "field.blocks.delete.confirm": "Do you really want to delete this block?", + "field.blocks.delete.confirm.all": "Do you really want to delete all blocks?", + "field.blocks.delete.confirm.selected": "Do you really want to delete the selected blocks?", + "field.blocks.empty": "No blocks yet", + "field.blocks.fieldsets.label": "Please select a block type …", + "field.blocks.gallery.name": "Gallery", + "field.blocks.gallery.images.empty": "No images yet", + "field.blocks.gallery.images.label": "Images", + "field.blocks.heading.level": "Level", + "field.blocks.heading.name": "Heading", + "field.blocks.heading.text": "Text", + "field.blocks.heading.placeholder": "Heading …", + "field.blocks.image.alt": "Alternative text", + "field.blocks.image.caption": "Caption", + "field.blocks.image.crop": "Crop", + "field.blocks.image.link": "Link", + "field.blocks.image.location": "Location", + "field.blocks.image.name": "Image", + "field.blocks.image.placeholder": "Select an image", + "field.blocks.image.ratio": "Ratio", + "field.blocks.image.url": "Image URL", + "field.blocks.list.name": "List", + "field.blocks.markdown.name": "Markdown", + "field.blocks.markdown.label": "Text", + "field.blocks.markdown.placeholder": "Markdown …", + "field.blocks.quote.name": "Quote", + "field.blocks.quote.text.label": "Text", + "field.blocks.quote.text.placeholder": "Quote …", + "field.blocks.quote.citation.label": "Citation", + "field.blocks.quote.citation.placeholder": "by …", + "field.blocks.text.name": "Text", + "field.blocks.text.placeholder": "Text …", + "field.blocks.video.caption": "Caption", + "field.blocks.video.name": "Video", + "field.blocks.video.placeholder": "Enter a video URL", + "field.blocks.video.url.label": "Video-URL", + "field.blocks.video.url.placeholder": "https://youtube.com/?v=", + + "field.files.empty": "No files selected yet", + + "field.layout.delete": "Delete layout", + "field.layout.delete.confirm": "Do you really want to delete this layout?", + "field.layout.empty": "No rows yet", + "field.layout.select": "Select a layout", + + "field.pages.empty": "No pages selected yet", + "field.structure.delete.confirm": "Do you really want to delete this row?", + "field.structure.empty": "No entries yet", + "field.users.empty": "No users selected yet", + + "file.blueprint": "This file has no blueprint yet. You can define the setup in /site/blueprints/{template}.yml", + "file.delete.confirm": "Do you really want to delete
{filename}?", + "file.sort": "Change position", + + "files": "Files", + "files.empty": "No files yet", + + "hide": "Hide", + "hour": "Hour", + "insert": "Insert", + "insert.after": "Insert after", + "insert.before": "Insert before", + "install": "Install", + + "installation": "Installation", + "installation.completed": "The panel has been installed", + "installation.disabled": "The panel installer is disabled on public servers by default. Please run the installer on a local machine or enable it with the panel.install option.", + "installation.issues.accounts": "The /site/accounts folder does not exist or is not writable", + "installation.issues.content": "The /content folder does not exist or is not writable", + "installation.issues.curl": "The CURL extension is required", + "installation.issues.headline": "The panel cannot be installed", + "installation.issues.mbstring": "The MB String extension is required", + "installation.issues.media": "The /media folder does not exist or is not writable", + "installation.issues.php": "Make sure to use PHP 7+", + "installation.issues.server": "Kirby requires Apache, Nginx or Caddy", + "installation.issues.sessions": "The /site/sessions folder does not exist or is not writable", + + "language": "Language", + "language.code": "Code", + "language.convert": "Make default", + "language.convert.confirm": "

Do you really want to convert {name} to the default language? This cannot be undone.

If {name} has untranslated content, there will no longer be a valid fallback and parts of your site might be empty.

", + "language.create": "Add a new language", + "language.delete.confirm": "Do you really want to delete the language {name} including all translations? This cannot be undone!", + "language.deleted": "The language has been deleted", + "language.direction": "Reading direction", + "language.direction.ltr": "Left to right", + "language.direction.rtl": "Right to left", + "language.locale": "PHP locale string", + "language.locale.warning": "You are using a custom locale set up. Please modify it in the language file in /site/languages", + "language.name": "Name", + "language.updated": "The language has been updated", + + "languages": "Languages", + "languages.default": "Default language", + "languages.empty": "There are no languages yet", + "languages.secondary": "Secondary languages", + "languages.secondary.empty": "There are no secondary languages yet", + + "license": "License", + "license.buy": "Buy a license", + "license.register": "Register", + "license.register.help": "You received your license code after the purchase via email. Please copy and paste it to register.", + "license.register.label": "Please enter your license code", + "license.register.success": "Thank you for supporting Kirby", + "license.unregistered": "This is an unregistered demo of Kirby", + + "link": "Link", + "link.text": "Link text", + + "loading": "Loading", + + "lock.unsaved": "Unsaved changes", + "lock.unsaved.empty": "There are no more unsaved changes", + "lock.isLocked": "Unsaved changes by {email}", + "lock.file.isLocked": "The file is currently being edited by {email} and cannot be changed.", + "lock.page.isLocked": "The page is currently being edited by {email} and cannot be changed.", + "lock.unlock": "Unlock", + "lock.isUnlocked": "Your unsaved changes have been overwritten by another user. You can download your changes to merge them manually.", + + "login": "Login", + "login.code.label.login": "Login code", + "login.code.label.password-reset": "Password reset code", + "login.code.placeholder.email": "000 000", + "login.code.text.email": "If your email address is registered, the requested code was sent via email.", + "login.email.login.body": "Hi {user.nameOrEmail},\n\nYou recently requested a login code for the Kirby Panel.\nThe following login code will be valid for {timeout} minutes:\n\n{code}\n\nIf you did not request a login code, please ignore this email or contact your administrator if you have questions.\nFor security, please DO NOT forward this email.", + "login.email.login.subject": "Your login code", + "login.email.password-reset.body": "Hi {user.nameOrEmail},\n\nYou recently requested a password reset code for the Kirby Panel.\nThe following password reset code will be valid for {timeout} minutes:\n\n{code}\n\nIf you did not request a password reset code, please ignore this email or contact your administrator if you have questions.\nFor security, please DO NOT forward this email.", + "login.email.password-reset.subject": "Your password reset code", + "login.remember": "Keep me logged in", + "login.reset": "Reset password", + "login.toggleText.code.email": "Login via email", + "login.toggleText.code.email-password": "Login with password", + "login.toggleText.password-reset.email": "Forgot your password?", + "login.toggleText.password-reset.email-password": "← Back to login", + + "logout": "Logout", + + "menu": "Menu", + "meridiem": "AM/PM", + "mime": "Media Type", + "minutes": "Minutes", + + "month": "Month", + "months.april": "April", + "months.august": "August", + "months.december": "December", + "months.february": "Feburary", + "months.january": "January", + "months.july": "July", + "months.june": "June", + "months.march": "March", + "months.may": "May", + "months.november": "November", + "months.october": "October", + "months.september": "September", + + "more": "More", + "name": "Name", + "next": "Next", + "no": "no", + "off": "off", + "on": "on", + "open": "Open", + "open.newWindow": "Open in new window", + "options": "Options", + "options.none": "No options", + + "orientation": "Orientation", + "orientation.landscape": "Landscape", + "orientation.portrait": "Portrait", + "orientation.square": "Square", + + "page.blueprint": "This page has no blueprint yet. You can define the setup in /site/blueprints/{template}.yml", + "page.changeSlug": "Change URL", + "page.changeSlug.fromTitle": "Create from title", + "page.changeStatus": "Change status", + "page.changeStatus.position": "Please select a position", + "page.changeStatus.select": "Select a new status", + "page.changeTemplate": "Change template", + "page.delete.confirm": "Do you really want to delete {title}?", + "page.delete.confirm.subpages": "This page has subpages.
All subpages will be deleted as well.", + "page.delete.confirm.title": "Enter the page title to confirm", + "page.draft.create": "Create draft", + "page.duplicate.appendix": "Copy", + "page.duplicate.files": "Copy files", + "page.duplicate.pages": "Copy pages", + "page.sort": "Change position", + "page.status": "Status", + "page.status.draft": "Draft", + "page.status.draft.description": "The page is in draft mode and only visible for logged in editors or via secret link", + "page.status.listed": "Public", + "page.status.listed.description": "The page is public for anyone", + "page.status.unlisted": "Unlisted", + "page.status.unlisted.description": "The page is only accessible via URL", + + "pages": "Pages", + "pages.empty": "No pages yet", + "pages.status.draft": "Drafts", + "pages.status.listed": "Published", + "pages.status.unlisted": "Unlisted", + + "pagination.page": "Page", + + "password": "Password", + "pixel": "Pixel", + "prev": "Previous", + "preview": "Preview", + "remove": "Remove", + "rename": "Rename", + "replace": "Replace", + "retry": "Try again", + "revert": "Revert", + "revert.confirm": "Do you really want to delete all unsaved changes?", + + "role": "Role", + "role.admin.description": "The admin has all rights", + "role.admin.title": "Admin", + "role.all": "All", + "role.empty": "There are no users with this role", + "role.description.placeholder": "No description", + "role.nobody.description": "This is a fallback role without any permissions", + "role.nobody.title": "Nobody", + + "save": "Save", + "search": "Search", + "search.min": "Enter {min} characters to search", + "search.all": "Show all", + "search.results.none": "No results", + + "section.required": "The section is required", + + "select": "Select", + "settings": "Settings", + "show": "Show", + "size": "Size", + "slug": "URL appendix", + "sort": "Sort", + "title": "Title", + "template": "Template", + "today": "Today", + + "site.blueprint": "The site has no blueprint yet. You can define the setup in /site/blueprints/site.yml", + + "toolbar.button.code": "Code", + "toolbar.button.bold": "Bold", + "toolbar.button.email": "Email", + "toolbar.button.headings": "Headings", + "toolbar.button.heading.1": "Heading 1", + "toolbar.button.heading.2": "Heading 2", + "toolbar.button.heading.3": "Heading 3", + "toolbar.button.italic": "Italic", + "toolbar.button.file": "File", + "toolbar.button.file.select": "Select a file", + "toolbar.button.file.upload": "Upload a file", + "toolbar.button.link": "Link", + "toolbar.button.strike": "Strike-through", + "toolbar.button.ol": "Ordered list", + "toolbar.button.underline": "Underline", + "toolbar.button.ul": "Bullet list", + + "translation.author": "Kirby Team", + "translation.direction": "ltr", + "translation.name": "English", + "translation.locale": "en_US", + + "upload": "Upload", + "upload.error.cantMove": "The uploaded file could not be moved", + "upload.error.cantWrite": "Failed to write file to disk", + "upload.error.default": "The file could not be uploaded", + "upload.error.extension": "File upload stopped by extension", + "upload.error.formSize": "The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the form", + "upload.error.iniPostSize": "The uploaded file exceeds the post_max_size directive in php.ini", + "upload.error.iniSize": "The uploaded file exceeds the upload_max_filesize directive in php.ini", + "upload.error.noFile": "No file was uploaded", + "upload.error.noFiles": "No files were uploaded", + "upload.error.partial": "The uploaded file was only partially uploaded", + "upload.error.tmpDir": "Missing a temporary folder", + "upload.errors": "Error", + "upload.progress": "Uploading…", + + "url": "Url", + "url.placeholder": "https://example.com", + + "user": "User", + "user.blueprint": "You can define additional sections and form fields for this user role in /site/blueprints/users/{role}.yml", + "user.changeEmail": "Change email", + "user.changeLanguage": "Change language", + "user.changeName": "Rename this user", + "user.changePassword": "Change password", + "user.changePassword.new": "New password", + "user.changePassword.new.confirm": "Confirm the new password…", + "user.changeRole": "Change role", + "user.changeRole.select": "Select a new role", + "user.create": "Add a new user", + "user.delete": "Delete this user", + "user.delete.confirm": "Do you really want to delete
{email}?", + + "users": "Users", + + "version": "Version", + + "view.account": "Your account", + "view.installation": "Installation", + "view.resetPassword": "Reset password", + "view.settings": "Settings", + "view.site": "Site", + "view.users": "Users", + + "welcome": "Welcome", + "year": "Year", + "yes": "yes" +} diff --git a/kirby/i18n/translations/es_419.json b/kirby/i18n/translations/es_419.json new file mode 100644 index 0000000..1da05f3 --- /dev/null +++ b/kirby/i18n/translations/es_419.json @@ -0,0 +1,527 @@ +{ + "add": "Agregar", + "avatar": "Foto de perfil", + "back": "Regresar", + "cancel": "Cancelar", + "change": "Cambiar", + "close": "Cerrar", + "confirm": "De acuerdo", + "collapse": "Collapse", + "collapse.all": "Collapse All", + "copy": "Copiar", + "create": "Crear", + + "date": "Fecha", + "date.select": "Selecciona una fecha", + + "day": "Día", + "days.fri": "Vie", + "days.mon": "Lun", + "days.sat": "S\u00e1b", + "days.sun": "Dom", + "days.thu": "Jue", + "days.tue": "Mar", + "days.wed": "Mi\u00e9", + + "delete": "Eliminar", + "delete.all": "Eliminar todos", + "dimensions": "Dimensiones", + "disabled": "Desabilitado", + "discard": "Descartar", + "download": "Descargar", + "duplicate": "Duplicar", + "edit": "Editar", + "expand": "Expandir", + "expand.all": "Expandir todo", + + "dialog.files.empty": "No has seleccionado ningún archivo", + "dialog.pages.empty": "No has seleccionado ninguna página", + "dialog.users.empty": "No has seleccionado ningún usuario", + + "email": "Correo Electrónico", + "email.placeholder": "correo@ejemplo.com", + + "error.access.code": "Código inválido", + "error.access.login": "Ingreso inválido", + "error.access.panel": "No tienes permitido acceder al panel", + "error.access.view": "No tienes permiso para acceder a esta parte del panel", + + "error.avatar.create.fail": "No se pudo subir la foto de perfil", + "error.avatar.delete.fail": "No se pudo eliminar la foto de perfil", + "error.avatar.dimensions.invalid": "Por favor, mantén el ancho y la altura de la imagen de perfil por debajo de 3000 pixeles", + "error.avatar.mime.forbidden": "La foto de perfil debe de ser un archivo JPG o PNG", + + "error.blueprint.notFound": "El blueprint \"{name}\" no se pudo cargar.", + + "error.blocks.max.plural": "You must not add more than {max} blocks", + "error.blocks.max.singular": "You must not add more than one block", + "error.blocks.min.plural": "You must add at least {min} blocks", + "error.blocks.min.singular": "You must add at least one block", + "error.blocks.validation": "There's an error in block {index}", + + "error.email.preset.notFound": "El preajuste de email \"{name}\" no se pudo encontrar.", + + "error.field.converter.invalid": "Convertidor inválido \"{converter}\"", + + "error.file.changeName.empty": "El nombre no debe estar vacío", + "error.file.changeName.permission": "No tienes permitido cambiar el nombre de \"{filename}\"", + "error.file.duplicate": "Ya existe un archivo con el nombre \"{filename}\".", + "error.file.extension.forbidden": "La extensión \"{extension}\" no está permitida.", + "error.file.extension.invalid": "Extensión inválida: {extension}", + "error.file.extension.missing": "Falta la extensión para \"{filename}\".", + "error.file.maxheight": "La altura de la imagen no debe exceder {height} pixeles", + "error.file.maxsize": "El archivo es muy grande", + "error.file.maxwidth": "El ancho de la imagen no debe exceder {width} pixeles", + "error.file.mime.differs": "El archivo cargado debe ser del mismo tipo mime \"{mime}\".", + "error.file.mime.forbidden": "El tipo de medios \"{mime}\" no está permitido.", + "error.file.mime.invalid": "Tipo invalido de mime: {mime}", + "error.file.mime.missing": "No se puede detectar el tipo de medio para \"{filename}\".", + "error.file.minheight": "La altura de la imagen debe ser de al menos {height} pixeles", + "error.file.minsize": "El archivo es muy pequeño", + "error.file.minwidth": "El ancho de la imagen debe ser de al menos {width} pixeles", + "error.file.name.missing": "El nombre del archivo no debe estar vacío.", + "error.file.notFound": "El archivo \"{filename}\" no pudo ser encontrado.", + "error.file.orientation": "La orientación de la imagen debe ser \"{orientation}\"", + "error.file.type.forbidden": "No está permitido subir archivos {type}.", + "error.file.type.invalid": "Tipo de archivo inválido: {type}", + "error.file.undefined": "El archivo no se puede encontrar", + + "error.form.incomplete": "Por favor, corrige todos los errores del formulario...", + "error.form.notSaved": "No se pudo guardar el formulario", + + "error.language.code": "Por favor introduce un código válido para el idioma", + "error.language.duplicate": "El idioma ya existe", + "error.language.name": "Por favor introduce un nombre válido para el idioma", + + "error.layout.validation.block": "There's an error in block {blockIndex} in layout {layoutIndex}", + "error.layout.validation.settings": "There's an error in layout {index} settings", + + "error.license.format": "Por favor introduce una llave de licencia válida", + "error.license.email": "Por favor ingresa un correo electrónico valido", + "error.license.verification": "La licencia no pude ser verificada", + + "error.page.changeSlug.permission": "No está permitido cambiar el apéndice de URL para \"{slug}\".", + "error.page.changeStatus.incomplete": "La página tiene errores y no puede ser publicada.", + "error.page.changeStatus.permission": "El estado de esta página no se puede cambiar.", + "error.page.changeStatus.toDraft.invalid": "La página \"{slug}\" no se puede convertir en un borrador", + "error.page.changeTemplate.invalid": "La plantilla para la página \"{slug}\" no se puede cambiar", + "error.page.changeTemplate.permission": "No está permitido cambiar la plantilla para \"{slug}\"", + "error.page.changeTitle.empty": "El título no debe estar vacío.", + "error.page.changeTitle.permission": "No tienes permiso para cambiar el título de \"{slug}\"", + "error.page.create.permission": "No tienes permiso para crear \"{slug}\"", + "error.page.delete": "La página \"{slug}\" no se puede eliminar", + "error.page.delete.confirm": "Por favor, introduce el título de la página para confirmar", + "error.page.delete.hasChildren": "La página tiene subpáginas y no se puede eliminar", + "error.page.delete.permission": "No tienes permiso para borrar \"{slug}\"", + "error.page.draft.duplicate": "Ya existe un borrador de página con el apéndice de URL \"{slug}\"", + "error.page.duplicate": "Ya existe una página con el apéndice de URL \"{slug}\"", + "error.page.duplicate.permission": "No tienes permitido duplicar \"{slug}\"", + "error.page.notFound": "La página \"{slug}\" no se encuentra", + "error.page.num.invalid": "Por favor, introduce un número de posición válido. Los números no deben ser negativos.", + "error.page.slug.invalid": "Please enter a valid URL appendix", + "error.page.slug.maxlength": "Slug length must be less than \"{length}\" characters", + "error.page.sort.permission": "La página \"{slug}\" no se puede ordenar", + "error.page.status.invalid": "Por favor, establece una estado de página válido", + "error.page.undefined": "La p\u00e1gina no fue encontrada", + "error.page.update.permission": "No tienes permiso para actualizar \"{slug}\"", + + "error.section.files.max.plural": "No debes agregar más de {max} archivos a la sección \"{section}\"", + "error.section.files.max.singular": "No debes agregar más de un archivo a la sección \"{section}\"", + "error.section.files.min.plural": "La sección \"{section}\" requiere al menos {min} archivos", + "error.section.files.min.singular": "La sección \"{section}\" requiere al menos un archivo", + + "error.section.pages.max.plural": "No debes agregar más de {max} páginas a la sección \"{section}\"", + "error.section.pages.max.singular": "No debes agregar más de una página a la sección \"{section}\"", + "error.section.pages.min.plural": "La sección \"{section}\" requiere al menos {min} páginas", + "error.section.pages.min.singular": "La sección \"{section}\" requiere al menos una página", + + "error.section.notLoaded": "La sección \"{name}\" no se pudo cargar", + "error.section.type.invalid": "La sección \"{type}\" no es valida", + + "error.site.changeTitle.empty": "El título no debe estar vacío.", + "error.site.changeTitle.permission": "No tienes permiso para cambiar el título del sitio", + "error.site.update.permission": "No tienes permiso de actualizar el sitio", + + "error.template.default.notFound": "La plantilla predeterminada no existe", + + "error.user.changeEmail.permission": "No tienes permiso para cambiar el email del usuario \"{name}\"", + "error.user.changeLanguage.permission": "No tienes permiso para cambiar el idioma del usuario \"{name}\"", + "error.user.changeName.permission": "No tienes permiso para cambiar el nombre del usuario \"{name}\"", + "error.user.changePassword.permission": "No tienes permiso para cambiar la contraseña del usuario \"{name}\"", + "error.user.changeRole.lastAdmin": "El rol del último administrador no puede ser cambiado", + "error.user.changeRole.permission": "No tienes permiso para cambiar el rol del usuario \"{name}\"", + "error.user.changeRole.toAdmin": "No tienes permitido promover a alguien al rol de admin", + "error.user.create.permission": "No tienes permiso de crear este usuario", + "error.user.delete": "El ususario no pudo ser eliminado", + "error.user.delete.lastAdmin": "Usted no puede borrar el \u00faltimo administrador", + "error.user.delete.lastUser": "El último usuario no puede ser borrado", + "error.user.delete.permission": "Usted no tiene permitido borrar este usuario", + "error.user.duplicate": "Ya existe un usuario con el email \"{email}\"", + "error.user.email.invalid": "Por favor ingresa un correo electrónico valido", + "error.user.language.invalid": "Por favor ingresa un idioma valido", + "error.user.notFound": "El usuario no pudo ser encontrado", + "error.user.password.invalid": "Por favor ingresa una contraseña valida. Las contraseñas deben tener al menos 8 caracteres de largo.", + "error.user.password.notSame": "Por favor confirma la contrase\u00f1a", + "error.user.password.undefined": "El usuario no tiene contraseña", + "error.user.password.wrong": "Wrong password", + "error.user.role.invalid": "Por favor ingresa un rol valido", + "error.user.update.permission": "No tienes permiso para actualizar al usuario \"{name}\"", + + "error.validation.accepted": "Por favor, confirma", + "error.validation.alpha": "Por favor ingrese solo caracteres entre a-z", + "error.validation.alphanum": "Por favor ingrese solo caracteres entre a-z o números entre 0-9", + "error.validation.between": "Por favor ingrese valores entre \"{min}\" y \"{max}\"", + "error.validation.boolean": "Por favor confirme o niegue", + "error.validation.contains": "Por favor ingrese valores que contengan \"{needle}\"", + "error.validation.date": "Por favor ingresa una fecha válida", + "error.validation.date.after": "Por favor introduce una fecha posterior a {date}", + "error.validation.date.before": "Por favor introduce una fecha anterior a {date}", + "error.validation.date.between": "Por favor introduce un número entre {min} y {max}", + "error.validation.denied": "Por favor niegue", + "error.validation.different": "EL valor no debe ser \"{other}\"", + "error.validation.email": "Por favor ingresa un correo electrónico valido", + "error.validation.endswith": "El valor no debe terminar con \"{end}\"", + "error.validation.filename": "Por favor ingresa un nombre de archivo válido", + "error.validation.in": "Por favor ingresa uno de los siguientes: ({in})", + "error.validation.integer": "Por favor ingresa un entero válido", + "error.validation.ip": "Por favor ingresa una dirección IP válida", + "error.validation.less": "Por favor ingresa un valor menor a {max}", + "error.validation.match": "El valor no coincide con el patrón esperado", + "error.validation.max": "Por favor ingresa un valor menor o igual a {max}", + "error.validation.maxlength": "Por favor ingresa un valor mas corto. (max. {max} caracteres)", + "error.validation.maxwords": "Por favor ingresa no mas de {max} palabra(s)", + "error.validation.min": "Por favor ingresa un valor mayor o igual a {min}", + "error.validation.minlength": "Por favor ingresa un valor mas largo. (min. {min} caracteres)", + "error.validation.minwords": "Por favor ingresa al menos {min} palabra(s)", + "error.validation.more": "Por favor ingresa un valor mayor a {min}", + "error.validation.notcontains": "Por favor ingresa un valor que no contenga \"{needle}\"", + "error.validation.notin": "Por favor no ingreses ninguno de las siguientes: ({notIn})", + "error.validation.option": "Por favor selecciona una de las opciones válidas", + "error.validation.num": "Por favor ingresa un numero válido", + "error.validation.required": "Por favor ingresa algo", + "error.validation.same": "Por favor ingresa \"{other}\"", + "error.validation.size": "El tamaño del valor debe ser \"{size}\"", + "error.validation.startswith": "El valor debe comenzar con \"{start}\"", + "error.validation.time": "Por favor ingresa una hora válida", + "error.validation.time.after": "Please enter a time after {time}", + "error.validation.time.before": "Please enter a time before {time}", + "error.validation.time.between": "Please enter a time between {min} and {max}", + "error.validation.url": "Por favor ingresa un URL válido", + + "field.required": "Este campo es requerido", + "field.blocks.changeType": "Change type", + "field.blocks.code.name": "Código", + "field.blocks.code.language": "Idioma", + "field.blocks.code.placeholder": "Your code …", + "field.blocks.delete.confirm": "Do you really want to delete this block?", + "field.blocks.delete.confirm.all": "Do you really want to delete all blocks?", + "field.blocks.delete.confirm.selected": "Do you really want to delete the selected blocks?", + "field.blocks.empty": "No blocks yet", + "field.blocks.fieldsets.label": "Please select a block type …", + "field.blocks.gallery.name": "Gallery", + "field.blocks.gallery.images.empty": "No images yet", + "field.blocks.gallery.images.label": "Images", + "field.blocks.heading.level": "Level", + "field.blocks.heading.name": "Heading", + "field.blocks.heading.text": "Text", + "field.blocks.heading.placeholder": "Heading …", + "field.blocks.image.alt": "Alternative text", + "field.blocks.image.caption": "Caption", + "field.blocks.image.crop": "Crop", + "field.blocks.image.link": "Enlace", + "field.blocks.image.location": "Location", + "field.blocks.image.name": "Imágen", + "field.blocks.image.placeholder": "Select an image", + "field.blocks.image.ratio": "Ratio", + "field.blocks.image.url": "Image URL", + "field.blocks.list.name": "List", + "field.blocks.markdown.name": "Markdown", + "field.blocks.markdown.label": "Text", + "field.blocks.markdown.placeholder": "Markdown …", + "field.blocks.quote.name": "Quote", + "field.blocks.quote.text.label": "Text", + "field.blocks.quote.text.placeholder": "Quote …", + "field.blocks.quote.citation.label": "Citation", + "field.blocks.quote.citation.placeholder": "by …", + "field.blocks.text.name": "Text", + "field.blocks.text.placeholder": "Text …", + "field.blocks.video.caption": "Caption", + "field.blocks.video.name": "Video", + "field.blocks.video.placeholder": "Enter a video URL", + "field.blocks.video.url.label": "Video-URL", + "field.blocks.video.url.placeholder": "https://youtube.com/?v=", + + "field.files.empty": "Aún no ha seleccionado ningún archivo", + + "field.layout.delete": "Delete layout", + "field.layout.delete.confirm": "Do you really want to delete this layout?", + "field.layout.empty": "No rows yet", + "field.layout.select": "Select a layout", + + "field.pages.empty": "Aún no ha seleccionado ningúna pagina", + "field.structure.delete.confirm": "\u00bfEn realidad desea borrar esta entrada?", + "field.structure.empty": "A\u00fan no existen entradas.", + "field.users.empty": "Aún no ha seleccionado ningún usuario", + + "file.blueprint": "This file has no blueprint yet. You can define the setup in /site/blueprints/{template}.yml", + "file.delete.confirm": "\u00bfEst\u00e1s seguro que deseas eliminar este archivo?", + "file.sort": "Change position", + + "files": "Archivos", + "files.empty": "Aún no existen archivos", + + "hide": "Hide", + "hour": "Hora", + "insert": "Insertar", + "insert.after": "Insert after", + "insert.before": "Insert before", + "install": "Instalar", + + "installation": "Instalación", + "installation.completed": "El panel ha sido instalado.", + "installation.disabled": "El instalador del panel está deshabilitado en servidores públicos por defecto. Ejecute el instalador en una máquina local o habilítelo con la opción panel.install.", + "installation.issues.accounts": "La carpeta /site/accounts no existe o no posee permisos de escritura.", + "installation.issues.content": "La carpeta /content no existe o no posee permisos de escritura.", + "installation.issues.curl": "Se requiere la extensión CURL.", + "installation.issues.headline": "El panel no puede ser instalado.", + "installation.issues.mbstring": "Se requiere la extensión MB String.", + "installation.issues.media": "La carpeta /media no existe o no posee permisos de escritura.", + "installation.issues.php": "Asegurese de estar usando PHP 7+", + "installation.issues.server": "Kirby requiere Apache, Nginx, Caddy", + "installation.issues.sessions": "La carpeta /site/sessions no existe o no posee permisos de escritura.", + + "language": "Idioma", + "language.code": "Código", + "language.convert": "Hacer por defecto", + "language.convert.confirm": "

Realmente deseas convertir {name} al idioma por defecto? Esta acción no se puede deshacer.

Si {name} tiene contenido sin traducir, no habrá vuelta atras y tu sitio puede quedar con partes sin contenido.

", + "language.create": "Añadir nuevo idioma", + "language.delete.confirm": "

", + "language.deleted": "El idioma ha sido borrado", + "language.direction": "Dirección de lectura", + "language.direction.ltr": "De Izquierda a derecha", + "language.direction.rtl": "De derecha a izquierda", + "language.locale": "Cadena de localización PHP", + "language.locale.warning": "Estas utilizando un configuración local. Por favor modifícalo en el archivo del lenguaje en /site/languages", + "language.name": "Nombre", + "language.updated": "El idioma a sido actualizado", + + "languages": "Idiomas", + "languages.default": "Idioma por defecto", + "languages.empty": "Todavía no hay idiomas", + "languages.secondary": "Idiomas secundarios", + "languages.secondary.empty": "Todavía no hay idiomas secundarios", + + "license": "Licencia", + "license.buy": "Comprar una licencia", + "license.register": "Registrar", + "license.register.help": "Recibió su código de licencia después de la compra por correo electrónico. Por favor copie y pegue para registrarse.", + "license.register.label": "Por favor, ingresa tu código de licencia", + "license.register.success": "Gracias por apoyar a Kirby", + "license.unregistered": "Este es un demo no registrado de Kirby", + + "link": "Enlace", + "link.text": "Texto de Enlace", + + "loading": "Cargando", + + "lock.unsaved": "Cambios sin guardar", + "lock.unsaved.empty": "No hay más cambios sin guardar", + "lock.isLocked": "Cambios sin guardar por {email}", + "lock.file.isLocked": "El archivo está siendo actualmente editado por {email} y no puede ser cambiado.", + "lock.page.isLocked": "La página está siendo actualmente editada por {email} y no puede ser cambiada.", + "lock.unlock": "Desbloquear", + "lock.isUnlocked": "Tus cambios sin guardar han sido sobrescritos por otro usuario. Puedes descargar los cambios y fusionarlos manualmente.", + + "login": "Iniciar sesi\u00f3n", + "login.code.label.login": "Login code", + "login.code.label.password-reset": "Password reset code", + "login.code.placeholder.email": "000 000", + "login.code.text.email": "If your email address is registered, the requested code was sent via email.", + "login.email.login.body": "Hi {user.nameOrEmail},\n\nYou recently requested a login code for the Kirby Panel.\nThe following login code will be valid for {timeout} minutes:\n\n{code}\n\nIf you did not request a login code, please ignore this email or contact your administrator if you have questions.\nFor security, please DO NOT forward this email.", + "login.email.login.subject": "Your login code", + "login.email.password-reset.body": "Hi {user.nameOrEmail},\n\nYou recently requested a password reset code for the Kirby Panel.\nThe following password reset code will be valid for {timeout} minutes:\n\n{code}\n\nIf you did not request a password reset code, please ignore this email or contact your administrator if you have questions.\nFor security, please DO NOT forward this email.", + "login.email.password-reset.subject": "Your password reset code", + "login.remember": "Mantener mi sesión iniciada", + "login.reset": "Reset password", + "login.toggleText.code.email": "Login via email", + "login.toggleText.code.email-password": "Login with password", + "login.toggleText.password-reset.email": "Forgot your password?", + "login.toggleText.password-reset.email-password": "← Back to login", + + "logout": "Cerrar sesi\u00f3n", + + "menu": "Menù", + "meridiem": "AM/PM", + "mime": "Tipos de medios", + "minutes": "Minutos", + + "month": "Mes", + "months.april": "Abril", + "months.august": "Agosto", + "months.december": "Diciembre", + "months.february": "Febrero", + "months.january": "Enero", + "months.july": "Julio", + "months.june": "Junio", + "months.march": "Marzo", + "months.may": "Mayo", + "months.november": "Noviembre", + "months.october": "Octubre", + "months.september": "Septiembre", + + "more": "Màs", + "name": "Nombre", + "next": "Siguiente", + "no": "no", + "off": "Apagado", + "on": "Encendido", + "open": "Abrir", + "open.newWindow": "Open in new window", + "options": "Opciones", + "options.none": "No options", + + "orientation": "Orientación", + "orientation.landscape": "Paisaje", + "orientation.portrait": "Retrato", + "orientation.square": "Diapositiva", + + "page.blueprint": "This page has no blueprint yet. You can define the setup in /site/blueprints/{template}.yml", + "page.changeSlug": "Cambiar URL", + "page.changeSlug.fromTitle": "Crear a partir del t\u00edtulo", + "page.changeStatus": "Cambiar estado", + "page.changeStatus.position": "Por favor selecciona una posición", + "page.changeStatus.select": "Selecciona un nuevo estado", + "page.changeTemplate": "Cambiar plantilla", + "page.delete.confirm": "¿Estás seguro que deseas eliminar {title}?", + "page.delete.confirm.subpages": "Esta página tiene subpáginas.
Todas las súbpaginas serán eliminadas también.", + "page.delete.confirm.title": "Introduce el título de la página para confirmar", + "page.draft.create": "Crear borrador", + "page.duplicate.appendix": "Copiar", + "page.duplicate.files": "Copiar archivos", + "page.duplicate.pages": "Copiar páginas", + "page.sort": "Change position", + "page.status": "Estado", + "page.status.draft": "Borrador", + "page.status.draft.description": "The page is in draft mode and only visible for logged in editors or via secret link", + "page.status.listed": "Pública", + "page.status.listed.description": "La página es pública para cualquiera", + "page.status.unlisted": "No publicada", + "page.status.unlisted.description": "La página sólo es accesible vía URL", + + "pages": "Páginas", + "pages.empty": "No hay páginas aún", + "pages.status.draft": "Borradores", + "pages.status.listed": "Publicado", + "pages.status.unlisted": "No publicado", + + "pagination.page": "Página", + + "password": "Contrase\u00f1a", + "pixel": "Pixel", + "prev": "Anterior", + "preview": "Preview", + "remove": "Eliminar", + "rename": "Renombrar", + "replace": "Reemplazar", + "retry": "Reintentar", + "revert": "Revertir", + "revert.confirm": "Do you really want to delete all unsaved changes?", + + "role": "Rol", + "role.admin.description": "El administrador tiene todos los derechos", + "role.admin.title": "Administrador", + "role.all": "Todos", + "role.empty": "No hay usuarios con este rol", + "role.description.placeholder": "Sin descripción", + "role.nobody.description": "Este es un rol alternativo sin permisos", + "role.nobody.title": "Nadie", + + "save": "Guardar", + "search": "Buscar", + "search.min": "Enter {min} characters to search", + "search.all": "Show all", + "search.results.none": "No results", + + "section.required": "Esta sección es requerida", + + "select": "Seleccionar", + "settings": "Ajustes", + "show": "Show", + "size": "Tamaño", + "slug": "Apéndice URL", + "sort": "Ordenar", + "title": "Título", + "template": "Plantilla", + "today": "Hoy", + + "site.blueprint": "The site has no blueprint yet. You can define the setup in /site/blueprints/site.yml", + + "toolbar.button.code": "Código", + "toolbar.button.bold": "Negrita", + "toolbar.button.email": "Email", + "toolbar.button.headings": "Encabezados", + "toolbar.button.heading.1": "Encabezado 1", + "toolbar.button.heading.2": "Encabezado 2", + "toolbar.button.heading.3": "Encabezado 3", + "toolbar.button.italic": "Texto en It\u00e1licas", + "toolbar.button.file": "Archivo", + "toolbar.button.file.select": "Selecciona un archivo", + "toolbar.button.file.upload": "Sube un archivo", + "toolbar.button.link": "Enlace", + "toolbar.button.strike": "Strike-through", + "toolbar.button.ol": "Lista en orden", + "toolbar.button.underline": "Underline", + "toolbar.button.ul": "Lista de viñetas", + + "translation.author": "Equipo Kirby", + "translation.direction": "ltr", + "translation.name": "Español (América Latina)", + "translation.locale": "es_419", + + "upload": "Subir", + "upload.error.cantMove": "El archivo subido no puede ser movido", + "upload.error.cantWrite": "Error al escribir el archivo en el disco", + "upload.error.default": "El archivo no pudo ser subido", + "upload.error.extension": "Subida de archivo detenida por la extensión", + "upload.error.formSize": "El archivo subido excede la directiva MAX_FILE_SIZE que fue especificada en el formulario", + "upload.error.iniPostSize": "El archivo subido excede la directiva post_max_size directive en php.ini", + "upload.error.iniSize": "El archivo subido excede la directiva upload_max_filesize en php.ini", + "upload.error.noFile": "Ningún archivo ha sido subido", + "upload.error.noFiles": "Ningún archivo ha sido subido", + "upload.error.partial": "El archivo ha sido subido solo parcialmente", + "upload.error.tmpDir": "No se encuentra la carpeta temporal", + "upload.errors": "Error", + "upload.progress": "Subiendo...", + + "url": "Url", + "url.placeholder": "https://ejemplo.com", + + "user": "Usuario", + "user.blueprint": "Puedes definir secciones adicionales y campos de formulario para este rol de usuario en /site/blueprints/users/{role}.yml", + "user.changeEmail": "Cambiar correo electrónico", + "user.changeLanguage": "Cambiar idioma", + "user.changeName": "Renombrar este usuario", + "user.changePassword": "Cambiar la contraseña", + "user.changePassword.new": "Nueva contraseña", + "user.changePassword.new.confirm": "Confirma la nueva contraseña...", + "user.changeRole": "Cambiar rol", + "user.changeRole.select": "Selecciona un nuevo rol", + "user.create": "Agregar un nuevo usuario", + "user.delete": "Eliminar este usuario", + "user.delete.confirm": "¿Estás seguro que deseas eliminar
{email}?", + + "users": "Usuarios", + + "version": "Versión", + + "view.account": "Tu cuenta", + "view.installation": "Instalaci\u00f3n", + "view.resetPassword": "Reset password", + "view.settings": "Ajustes", + "view.site": "Sitio", + "view.users": "Usuarios", + + "welcome": "Bienvenido", + "year": "Año", + "yes": "yes" +} diff --git a/kirby/i18n/translations/es_ES.json b/kirby/i18n/translations/es_ES.json new file mode 100644 index 0000000..35664f8 --- /dev/null +++ b/kirby/i18n/translations/es_ES.json @@ -0,0 +1,527 @@ +{ + "add": "Añadir", + "avatar": "Foto de perfil", + "back": "Atrás", + "cancel": "Cancelar", + "change": "Cambiar", + "close": "Cerrar", + "confirm": "Confirmar", + "collapse": "Collapse", + "collapse.all": "Collapse All", + "copy": "Copiar", + "create": "Crear", + + "date": "Fecha", + "date.select": "Selecciona una fecha", + + "day": "Día", + "days.fri": "Vi", + "days.mon": "Lu", + "days.sat": "Sá", + "days.sun": "Do", + "days.thu": "Ju", + "days.tue": "Ma", + "days.wed": "Mi", + + "delete": "Borrar", + "delete.all": "Delete all", + "dimensions": "Dimensiones", + "disabled": "Desabilitado", + "discard": "Descartar", + "download": "Descargar", + "duplicate": "Duplicar", + "edit": "Editar", + "expand": "Expand", + "expand.all": "Expand All", + + "dialog.files.empty": "No se ha seleccionado ningún archivo", + "dialog.pages.empty": "No se ha seleccionado ninguna página", + "dialog.users.empty": "No se ha seleccionado ningún usuario", + + "email": "Correo electrónico", + "email.placeholder": "correo@ejemplo.com", + + "error.access.code": "Invalid code", + "error.access.login": "Ingreso inválido", + "error.access.panel": "No estás autorizado para acceder al panel", + "error.access.view": "You are not allowed to access this part of the panel", + + "error.avatar.create.fail": "No se pudo subir la foto de perfil.", + "error.avatar.delete.fail": "No se pudo borrar la foto de perfil", + "error.avatar.dimensions.invalid": "Por favor, mantenga el ancho y la altura de la imagen de perfil debajo de 3000 píxeles", + "error.avatar.mime.forbidden": "La imagen del perfil debe ser JPEG o PNG.", + + "error.blueprint.notFound": "El blueprint \"{name}\" no pudo ser cargado", + + "error.blocks.max.plural": "You must not add more than {max} blocks", + "error.blocks.max.singular": "You must not add more than one block", + "error.blocks.min.plural": "You must add at least {min} blocks", + "error.blocks.min.singular": "You must add at least one block", + "error.blocks.validation": "There's an error in block {index}", + + "error.email.preset.notFound": "El preset del correo \"{name}\" no pudo ser encontrado", + + "error.field.converter.invalid": "Convertidor \"{converter}\" inválido", + + "error.file.changeName.empty": "The name must not be empty", + "error.file.changeName.permission": "No tienes permitido cambiar el nombre de \"{filename}\"", + "error.file.duplicate": "Ya existe un archivo con el nombre \"{filename}\"", + "error.file.extension.forbidden": "La extensión \"{extension}\" no está permitida", + "error.file.extension.invalid": "Invalid extension: {extension}", + "error.file.extension.missing": "Falta la extensión para \"{filename}\"", + "error.file.maxheight": "The height of the image must not exceed {height} pixels", + "error.file.maxsize": "The file is too large", + "error.file.maxwidth": "The width of the image must not exceed {width} pixels", + "error.file.mime.differs": "El archivo cargado debe ser del mismo tipo mime \"{mime}\"", + "error.file.mime.forbidden": "Los medios tipo \"{mime}\" no están permitidos", + "error.file.mime.invalid": "Invalid mime type: {mime}", + "error.file.mime.missing": "El tipo de medio para \"{filename}\" no pudo ser detectado", + "error.file.minheight": "The height of the image must be at least {height} pixels", + "error.file.minsize": "The file is too small", + "error.file.minwidth": "The width of the image must be at least {width} pixels", + "error.file.name.missing": "El nombre de archivo no debe estar vacío", + "error.file.notFound": "El archivo \"{filename}\" no pudo ser encontrado", + "error.file.orientation": "The orientation of the image must be \"{orientation}\"", + "error.file.type.forbidden": "No está permitido subir archivos {type}", + "error.file.type.invalid": "Invalid file type: {type}", + "error.file.undefined": "El archivo no pudo ser encontrado", + + "error.form.incomplete": "Por favor, corrija todos los errores del formulario…", + "error.form.notSaved": "El formulario no pudo ser guardado", + + "error.language.code": "Please enter a valid code for the language", + "error.language.duplicate": "The language already exists", + "error.language.name": "Please enter a valid name for the language", + + "error.layout.validation.block": "There's an error in block {blockIndex} in layout {layoutIndex}", + "error.layout.validation.settings": "There's an error in layout {index} settings", + + "error.license.format": "Please enter a valid license key", + "error.license.email": "Por favor, introduce un correo electrónico válido", + "error.license.verification": "The license could not be verified", + + "error.page.changeSlug.permission": "No está permitido cambiar el apéndice de URL para \"{slug}\"", + "error.page.changeStatus.incomplete": "La página tiene errores y no puede ser publicada.", + "error.page.changeStatus.permission": "El estado de esta página no se puede cambiar", + "error.page.changeStatus.toDraft.invalid": "La página \"{slug}\" no se puede convertir a borrador", + "error.page.changeTemplate.invalid": "La plantilla para la página \"{slug}\" no se puede cambiar", + "error.page.changeTemplate.permission": "No tienes permitido cambiar la plantilla para \"{slug}\"", + "error.page.changeTitle.empty": "El título no debe estar vacío.", + "error.page.changeTitle.permission": "No tienes permitido cambiar el título por \"{slug}\"", + "error.page.create.permission": "No tienes permitido crear \"{slug}\"", + "error.page.delete": "La página \"{slug}\" no puede ser eliminada", + "error.page.delete.confirm": "Por favor, introduzca el título de la página para confirmar", + "error.page.delete.hasChildren": "La página tiene subpáginas y no se puede eliminar", + "error.page.delete.permission": "No tienes permiso de eliminar \"{slug}\"", + "error.page.draft.duplicate": "Un borrador de página con el apéndice de URL \"{slug}\" ya existe", + "error.page.duplicate": "Una página con el apéndice de URL. \"{slug}\" ya existe", + "error.page.duplicate.permission": "You are not allowed to duplicate \"{slug}\"", + "error.page.notFound": "La página \"{slug}\" no puede ser encontrada", + "error.page.num.invalid": "Por favor, introduzca un número válido. Estos no deben ser negativos.", + "error.page.slug.invalid": "Please enter a valid URL appendix", + "error.page.slug.maxlength": "Slug length must be less than \"{length}\" characters", + "error.page.sort.permission": "La página \"{slug}\" no se puede ordenar", + "error.page.status.invalid": "Por favor, establezca un estado de página válido", + "error.page.undefined": "La página no se puede encontrar", + "error.page.update.permission": "No tienes permitido actualizar \"{slug}\"", + + "error.section.files.max.plural": "No debes agregar más de {max} archivos a la sección \"{section}\"", + "error.section.files.max.singular": "No debes agregar más de 1 archivo a la sección \"{section}\"", + "error.section.files.min.plural": "La sección \"{section}\" requiere al menos {min} archivos", + "error.section.files.min.singular": "La sección \"{section}\" requiere al menos un archivo", + + "error.section.pages.max.plural": "No debe agregar más de {max} páginas a la sección \"{section}\"", + "error.section.pages.max.singular": "No debe agregar más de una página a la sección \"{section}\"", + "error.section.pages.min.plural": "La sección \"{section}\" requiere al menos {min} páginas", + "error.section.pages.min.singular": "La sección \"{section}\" requiere al menos una página", + + "error.section.notLoaded": "La sección \"{name}\" no pudo ser cargada", + "error.section.type.invalid": "El sección tipo \"{tipo}\" no es válido", + + "error.site.changeTitle.empty": "El título no debe estar vacío.", + "error.site.changeTitle.permission": "No está permitido cambiar el título del sitio", + "error.site.update.permission": "No tienes permitido actualizar el sitio", + + "error.template.default.notFound": "La plantilla por defecto no existe", + + "error.user.changeEmail.permission": "No tienes permitido cambiar el correo electrónico para el usuario \"{name}\"", + "error.user.changeLanguage.permission": "No tienes permitido cambiar el idioma para el usuario \"{name}\"", + "error.user.changeName.permission": "No tienes permitido cambiar el nombre del usuario \"{name}\"", + "error.user.changePassword.permission": "No tienes permitido cambiar la contraseña del usuario \"{name}\"", + "error.user.changeRole.lastAdmin": "El rol para el último administrador no puede ser cambiado", + "error.user.changeRole.permission": "No tienes permitido cambiar el rol del usuario \"{name}\"", + "error.user.changeRole.toAdmin": "You are not allowed to promote someone to the admin role", + "error.user.create.permission": "No tienes permiso para crear este usuario", + "error.user.delete": "El usuario \"{name}\" no puede ser eliminado", + "error.user.delete.lastAdmin": "El último administrador no puede ser eliminado", + "error.user.delete.lastUser": "El último usuario no puede ser eliminado", + "error.user.delete.permission": "No tienes permitido eliminar el usuario \"{name}\"", + "error.user.duplicate": "Un usuario con la dirección de correo electrónico \"{email}\" ya existe", + "error.user.email.invalid": "Por favor, introduce una dirección de correo electrónico válida", + "error.user.language.invalid": "Por favor ingrese un idioma válido", + "error.user.notFound": "El usuario \"{name}\" no pudo ser encontrado", + "error.user.password.invalid": "Por favor introduce una contraseña válida. Las contraseñas deben tener al menos 8 caracteres de largo.", + "error.user.password.notSame": "Las contraseñas no coinciden", + "error.user.password.undefined": "El usuario no tiene contraseña", + "error.user.password.wrong": "Wrong password", + "error.user.role.invalid": "Por favor ingrese un rol válido", + "error.user.update.permission": "No tienes permitido actualizar al usuario \"{name}\"", + + "error.validation.accepted": "Por favor, confirma", + "error.validation.alpha": "Por favor solo ingresa caracteres entre a-z", + "error.validation.alphanum": "Por favor solo ingrese caracteres entre a-z o numerales 0-9", + "error.validation.between": "Por favor, introduzca un valor entre \"{min}\" y \"{max}\"", + "error.validation.boolean": "Por favor confirme o rechace", + "error.validation.contains": "Por favor ingrese un valor que contenga \"{needle}\"", + "error.validation.date": "Por favor introduzca una fecha valida", + "error.validation.date.after": "Please enter a date after {date}", + "error.validation.date.before": "Please enter a date before {date}", + "error.validation.date.between": "Please enter a date between {min} and {max}", + "error.validation.denied": "Por favor, rechace", + "error.validation.different": "El valor no debe ser \"{other}\"", + "error.validation.email": "Por favor, introduce un correo electrónico válido", + "error.validation.endswith": "El valor debe terminar con \"{end}\"", + "error.validation.filename": "Por favor ingrese un nombre de archivo válido", + "error.validation.in": "Por favor ingrese uno de los siguientes: ({in})", + "error.validation.integer": "Por favor, introduce un numero integro válido", + "error.validation.ip": "Por favor ingrese una dirección IP válida", + "error.validation.less": "Por favor, introduzca un valor inferior a {max}", + "error.validation.match": "El valor no coincide con el patrón esperado", + "error.validation.max": "Por favor, introduzca un valor igual o inferior a {max}", + "error.validation.maxlength": "Por favor, introduzca un valor más corto. (max. {max} caracteres)", + "error.validation.maxwords": "Por favor ingrese no más de {max} palabra(s)", + "error.validation.min": "Por favor, introduzca un valor igual o mayor a {min}", + "error.validation.minlength": "Por favor, introduzca un valor más largo. (min. {min} caracteres)", + "error.validation.minwords": "Por favor ingrese al menos {min} palabra(s)", + "error.validation.more": "Por favor, introduzca un valor mayor a {min}", + "error.validation.notcontains": "Por favor ingrese un valor que no contenga \"{needle}\"", + "error.validation.notin": "Por favor, no ingrese ninguno de los siguientes: ({notIn})", + "error.validation.option": "Por favor seleccione una opción válida", + "error.validation.num": "Por favor ingrese un número valido", + "error.validation.required": "Por favor ingrese algo", + "error.validation.same": "Por favor escribe \"{other}\"", + "error.validation.size": "El tamaño del valor debe ser \"{size}\"", + "error.validation.startswith": "El valor debe comenzar con \"{start}\"", + "error.validation.time": "Por favor ingrese una hora válida", + "error.validation.time.after": "Please enter a time after {time}", + "error.validation.time.before": "Please enter a time before {time}", + "error.validation.time.between": "Please enter a time between {min} and {max}", + "error.validation.url": "Por favor introduzca un URL válido", + + "field.required": "The field is required", + "field.blocks.changeType": "Change type", + "field.blocks.code.name": "Código", + "field.blocks.code.language": "Idioma", + "field.blocks.code.placeholder": "Your code …", + "field.blocks.delete.confirm": "Do you really want to delete this block?", + "field.blocks.delete.confirm.all": "Do you really want to delete all blocks?", + "field.blocks.delete.confirm.selected": "Do you really want to delete the selected blocks?", + "field.blocks.empty": "No blocks yet", + "field.blocks.fieldsets.label": "Please select a block type …", + "field.blocks.gallery.name": "Gallery", + "field.blocks.gallery.images.empty": "No images yet", + "field.blocks.gallery.images.label": "Images", + "field.blocks.heading.level": "Level", + "field.blocks.heading.name": "Heading", + "field.blocks.heading.text": "Text", + "field.blocks.heading.placeholder": "Heading …", + "field.blocks.image.alt": "Alternative text", + "field.blocks.image.caption": "Caption", + "field.blocks.image.crop": "Crop", + "field.blocks.image.link": "Enlace", + "field.blocks.image.location": "Location", + "field.blocks.image.name": "Imágen", + "field.blocks.image.placeholder": "Select an image", + "field.blocks.image.ratio": "Ratio", + "field.blocks.image.url": "Image URL", + "field.blocks.list.name": "List", + "field.blocks.markdown.name": "Markdown", + "field.blocks.markdown.label": "Text", + "field.blocks.markdown.placeholder": "Markdown …", + "field.blocks.quote.name": "Quote", + "field.blocks.quote.text.label": "Text", + "field.blocks.quote.text.placeholder": "Quote …", + "field.blocks.quote.citation.label": "Citation", + "field.blocks.quote.citation.placeholder": "by …", + "field.blocks.text.name": "Text", + "field.blocks.text.placeholder": "Text …", + "field.blocks.video.caption": "Caption", + "field.blocks.video.name": "Video", + "field.blocks.video.placeholder": "Enter a video URL", + "field.blocks.video.url.label": "Video-URL", + "field.blocks.video.url.placeholder": "https://youtube.com/?v=", + + "field.files.empty": "Aún no hay archivos seleccionados", + + "field.layout.delete": "Delete layout", + "field.layout.delete.confirm": "Do you really want to delete this layout?", + "field.layout.empty": "No rows yet", + "field.layout.select": "Select a layout", + + "field.pages.empty": "Aún no hay páginas seleccionadas", + "field.structure.delete.confirm": "¿Realmente quieres eliminar esta fila?", + "field.structure.empty": "Aún no hay entradas", + "field.users.empty": "Aún no hay usuarios seleccionados", + + "file.blueprint": "This file has no blueprint yet. You can define the setup in /site/blueprints/{template}.yml", + "file.delete.confirm": "¿Realmente quieres eliminar
{filename}?", + "file.sort": "Change position", + + "files": "Archivos", + "files.empty": "Aún no hay archivos", + + "hide": "Hide", + "hour": "Hora", + "insert": "Insertar", + "insert.after": "Insert after", + "insert.before": "Insert before", + "install": "Instalar", + + "installation": "Instalación", + "installation.completed": "El panel ha sido instalado", + "installation.disabled": "El instalador del panel está deshabilitado en servidores públicos por defecto. Ejecute el instalador en una máquina local o habilítelo con la opción panel.install.", + "installation.issues.accounts": "La carpeta /site/accounts no existe o no se puede escribir", + "installation.issues.content": "La carpeta /content no existe o no se puede escribir", + "installation.issues.curl": "La extensión CURL es requerida", + "installation.issues.headline": "No se pudo instalar el panel", + "installation.issues.mbstring": "La extension MB String es requerida", + "installation.issues.media": "La carpeta /media no existe o no se puede escribir", + "installation.issues.php": "Asegúrate de usar PHP 7+", + "installation.issues.server": "Kirby requiere Apache, Nginx o Caddy", + "installation.issues.sessions": "La carpeta /site/sessions no existe o no se puede escribir", + + "language": "Idioma", + "language.code": "Código", + "language.convert": "Hacer por defecto", + "language.convert.confirm": "{name}
al idioma por defecto? Esto no se puede deshacer.

Si {name} tiene contenido sin traducir, ya no habrá un respaldo válido y algunas partes de su sitio podrían estar vacías.

", + "language.create": "Añadir un nuevo idioma", + "language.delete.confirm": "¿De verdad quieres eliminar el idioma {name} incluyendo todas las traducciones? ¡Esto no se puede deshacer!", + "language.deleted": "El idioma ha sido eliminado", + "language.direction": "Leyendo dirección", + "language.direction.ltr": "De izquierda a derecha", + "language.direction.rtl": "De derecha a izquierda", + "language.locale": "PHP locale string", + "language.locale.warning": "You are using a custom locale set up. Please modify it in the language file in /site/languages", + "language.name": "Nombre", + "language.updated": "El idioma ha sido actualizado", + + "languages": "Idiomas", + "languages.default": "Idioma predeterminado", + "languages.empty": "Todavía no hay idiomas", + "languages.secondary": "Idiomas secundarios", + "languages.secondary.empty": "Todavía no hay idiomas secundarios", + + "license": "Licencia", + "license.buy": "Comprar una licencia", + "license.register": "Registro", + "license.register.help": "Recibió su código de licencia después de la compra por correo electrónico. Por favor copie y pegue para registrarse.", + "license.register.label": "Por favor ingrese su código de licencia", + "license.register.success": "Gracias por apoyar a Kirby", + "license.unregistered": "Esta es una demo no registrada de Kirby", + + "link": "Enlace", + "link.text": "Texto del enlace", + + "loading": "Cargando", + + "lock.unsaved": "Unsaved changes", + "lock.unsaved.empty": "There are no more unsaved changes", + "lock.isLocked": "Unsaved changes by {email}", + "lock.file.isLocked": "The file is currently being edited by {email} and cannot be changed.", + "lock.page.isLocked": "The page is currently being edited by {email} and cannot be changed.", + "lock.unlock": "Unlock", + "lock.isUnlocked": "Your unsaved changes have been overwritten by another user. You can download your changes to merge them manually.", + + "login": "Iniciar sesión", + "login.code.label.login": "Login code", + "login.code.label.password-reset": "Password reset code", + "login.code.placeholder.email": "000 000", + "login.code.text.email": "If your email address is registered, the requested code was sent via email.", + "login.email.login.body": "Hi {user.nameOrEmail},\n\nYou recently requested a login code for the Kirby Panel.\nThe following login code will be valid for {timeout} minutes:\n\n{code}\n\nIf you did not request a login code, please ignore this email or contact your administrator if you have questions.\nFor security, please DO NOT forward this email.", + "login.email.login.subject": "Your login code", + "login.email.password-reset.body": "Hi {user.nameOrEmail},\n\nYou recently requested a password reset code for the Kirby Panel.\nThe following password reset code will be valid for {timeout} minutes:\n\n{code}\n\nIf you did not request a password reset code, please ignore this email or contact your administrator if you have questions.\nFor security, please DO NOT forward this email.", + "login.email.password-reset.subject": "Your password reset code", + "login.remember": "Mantener sesión iniciada", + "login.reset": "Reset password", + "login.toggleText.code.email": "Login via email", + "login.toggleText.code.email-password": "Login with password", + "login.toggleText.password-reset.email": "Forgot your password?", + "login.toggleText.password-reset.email-password": "← Back to login", + + "logout": "Cerrar sesión", + + "menu": "Menu", + "meridiem": "AM/PM", + "mime": "Tipos de medios", + "minutes": "Minutos", + + "month": "Mes", + "months.april": "Abril", + "months.august": "Agosto", + "months.december": "Diciembre", + "months.february": "Febrero", + "months.january": "Enero", + "months.july": "Julio", + "months.june": "Junio", + "months.march": "Marzo", + "months.may": "Mayo", + "months.november": "Noviembre", + "months.october": "Octubre", + "months.september": "Septiembre", + + "more": "Más", + "name": "Nombre", + "next": "Siguiente", + "no": "no", + "off": "off", + "on": "on", + "open": "Abrir", + "open.newWindow": "Open in new window", + "options": "Opciones", + "options.none": "No options", + + "orientation": "Orientación", + "orientation.landscape": "Paisaje", + "orientation.portrait": "Retrato", + "orientation.square": "Cuadrado", + + "page.blueprint": "This page has no blueprint yet. You can define the setup in /site/blueprints/{template}.yml", + "page.changeSlug": "Cambiar URL", + "page.changeSlug.fromTitle": "Crear en base al título", + "page.changeStatus": "Cambiar estado", + "page.changeStatus.position": "Por favor seleccione una posición", + "page.changeStatus.select": "Seleccione un nuevo estado", + "page.changeTemplate": "Cambiar plantilla", + "page.delete.confirm": "¿Realmente quieres eliminar {title}?", + "page.delete.confirm.subpages": "Esta página tiene subpáginas.
Todas las subpáginas también serán eliminadas.", + "page.delete.confirm.title": "Introduzca el título de la página para confirmar", + "page.draft.create": "Crear borrador", + "page.duplicate.appendix": "Copiar", + "page.duplicate.files": "Copy files", + "page.duplicate.pages": "Copy pages", + "page.sort": "Change position", + "page.status": "Estado", + "page.status.draft": "Borrador", + "page.status.draft.description": "The page is in draft mode and only visible for logged in editors or via secret link", + "page.status.listed": "Publica", + "page.status.listed.description": "La página es pública para cualquiera", + "page.status.unlisted": "Sin publicar", + "page.status.unlisted.description": "La página solo es accesible vía URL", + + "pages": "Paginas", + "pages.empty": "Aún no hay páginas", + "pages.status.draft": "Borradores", + "pages.status.listed": "Publicadas", + "pages.status.unlisted": "Sin publicar", + + "pagination.page": "Página", + + "password": "Contraseña", + "pixel": "Pixel", + "prev": "Anterior", + "preview": "Preview", + "remove": "Eliminar", + "rename": "Renombrar", + "replace": "Remplazar", + "retry": "Inténtalo de nuevo", + "revert": "Revertir", + "revert.confirm": "Do you really want to delete all unsaved changes?", + + "role": "Rol", + "role.admin.description": "The admin has all rights", + "role.admin.title": "Admin", + "role.all": "Todos", + "role.empty": "No hay usuarios con este rol", + "role.description.placeholder": "Sin descripción", + "role.nobody.description": "This is a fallback role without any permissions", + "role.nobody.title": "Nobody", + + "save": "Guardar", + "search": "Buscar", + "search.min": "Enter {min} characters to search", + "search.all": "Show all", + "search.results.none": "No results", + + "section.required": "The section is required", + + "select": "Seleccionar", + "settings": "Ajustes", + "show": "Show", + "size": "Tamaño", + "slug": "Apéndice URL", + "sort": "Ordenar", + "title": "Titulo", + "template": "Plantilla", + "today": "Hoy", + + "site.blueprint": "The site has no blueprint yet. You can define the setup in /site/blueprints/site.yml", + + "toolbar.button.code": "Código", + "toolbar.button.bold": "Negritas", + "toolbar.button.email": "Correo electrónico", + "toolbar.button.headings": "Encabezados", + "toolbar.button.heading.1": "Encabezado 1", + "toolbar.button.heading.2": "Encabezado 2", + "toolbar.button.heading.3": "Encabezado 3", + "toolbar.button.italic": "Italica", + "toolbar.button.file": "Archivo", + "toolbar.button.file.select": "Seleccione un archivo", + "toolbar.button.file.upload": "Sube un archivo", + "toolbar.button.link": "Enlace", + "toolbar.button.strike": "Strike-through", + "toolbar.button.ol": "Lista ordenada", + "toolbar.button.underline": "Underline", + "toolbar.button.ul": "Lista de viñetas", + + "translation.author": "Turqueso", + "translation.direction": "ltr", + "translation.name": "Español", + "translation.locale": "es_ES", + + "upload": "Subir", + "upload.error.cantMove": "The uploaded file could not be moved", + "upload.error.cantWrite": "Failed to write file to disk", + "upload.error.default": "The file could not be uploaded", + "upload.error.extension": "File upload stopped by extension", + "upload.error.formSize": "The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the form", + "upload.error.iniPostSize": "The uploaded file exceeds the post_max_size directive in php.ini", + "upload.error.iniSize": "The uploaded file exceeds the upload_max_filesize directive in php.ini", + "upload.error.noFile": "No file was uploaded", + "upload.error.noFiles": "No files were uploaded", + "upload.error.partial": "The uploaded file was only partially uploaded", + "upload.error.tmpDir": "Missing a temporary folder", + "upload.errors": "Error", + "upload.progress": "Cargando…", + + "url": "Url", + "url.placeholder": "https://ejemplo.com", + + "user": "Usuario", + "user.blueprint": "Puede definir secciones adicionales y campos de formulario para este rol de usuario en /site/blueprints/users/{role}.yml", + "user.changeEmail": "Cambiar correo electrónico", + "user.changeLanguage": "Cambiar idioma", + "user.changeName": "Renombrar a este usuario", + "user.changePassword": "Cambia contraseña", + "user.changePassword.new": "Nueva contraseña", + "user.changePassword.new.confirm": "Confirmar nueva contraseña…", + "user.changeRole": "Cambiar rol", + "user.changeRole.select": "Seleccione un nuevo rol", + "user.create": "Añadir un nuevo usuario", + "user.delete": "Eliminar este usuario", + "user.delete.confirm": "¿Realmente quieres eliminar
{email}?", + + "users": "Usuarios", + + "version": "Versión", + + "view.account": "Su cuenta", + "view.installation": "Instalación", + "view.resetPassword": "Reset password", + "view.settings": "Ajustes", + "view.site": "Sitio", + "view.users": "Usuarios", + + "welcome": "Bienvenido(a)", + "year": "Año", + "yes": "yes" +} diff --git a/kirby/i18n/translations/fa.json b/kirby/i18n/translations/fa.json new file mode 100644 index 0000000..3b3283a --- /dev/null +++ b/kirby/i18n/translations/fa.json @@ -0,0 +1,527 @@ +{ + "add": "\u0627\u0641\u0632\u0648\u062f\u0646", + "avatar": "\u062a\u0635\u0648\u06cc\u0631 \u067e\u0631\u0648\u0641\u0627\u06cc\u0644", + "back": "بازگشت", + "cancel": "\u0627\u0646\u0635\u0631\u0627\u0641", + "change": "\u0627\u0635\u0644\u0627\u062d", + "close": "\u0628\u0633\u062a\u0646", + "confirm": "تایید", + "collapse": "Collapse", + "collapse.all": "Collapse All", + "copy": "کپی", + "create": "ایجاد", + + "date": "تاریخ", + "date.select": "یک تاریخ را انتخاب کنید", + + "day": "روز", + "days.fri": "\u062c\u0645\u0639\u0647", + "days.mon": "\u062f\u0648\u0634\u0646\u0628\u0647", + "days.sat": "\u0634\u0646\u0628\u0647", + "days.sun": "\u06cc\u06a9\u0634\u0646\u0628\u0647", + "days.thu": "\u067e\u0646\u062c\u0634\u0646\u0628\u0647", + "days.tue": "\u0633\u0647 \u0634\u0646\u0628\u0647", + "days.wed": "\u0686\u0647\u0627\u0631\u0634\u0646\u0628\u0647", + + "delete": "\u062d\u0630\u0641", + "delete.all": "Delete all", + "dimensions": "ابعاد", + "disabled": "Disabled", + "discard": "\u0627\u0646\u0635\u0631\u0627\u0641", + "download": "Download", + "duplicate": "Duplicate", + "edit": "\u0648\u06cc\u0631\u0627\u06cc\u0634", + "expand": "Expand", + "expand.all": "Expand All", + + "dialog.files.empty": "No files to select", + "dialog.pages.empty": "No pages to select", + "dialog.users.empty": "No users to select", + + "email": "\u067e\u0633\u062a \u0627\u0644\u06a9\u062a\u0631\u0648\u0646\u06cc\u06a9", + "email.placeholder": "mail@example.com", + + "error.access.code": "Invalid code", + "error.access.login": "اطلاعات ورودی نامعتبر است", + "error.access.panel": "شما اجازه دسترسی به پانل را ندارید", + "error.access.view": "You are not allowed to access this part of the panel", + + "error.avatar.create.fail": "بارگزاری تصویر پروفایل موفق نبود", + "error.avatar.delete.fail": "\u062a\u0635\u0648\u06cc\u0631 \u067e\u0631\u0648\u0641\u0627\u06cc\u0644 \u0631\u0627 \u0646\u0645\u06cc\u062a\u0648\u0627\u0646 \u062d\u0630\u0641 \u06a9\u0631\u062f", + "error.avatar.dimensions.invalid": "لطفا طول و عرض تصویر پروفایل را زیر 3000 پیکسل انتخاب کنید", + "error.avatar.mime.forbidden": "تصویر پروفایل باید از نوع JPEG یا PNG باشد", + + "error.blueprint.notFound": "بلوپرینت با نام «{name}» قابل بارگذاری نیست", + + "error.blocks.max.plural": "You must not add more than {max} blocks", + "error.blocks.max.singular": "You must not add more than one block", + "error.blocks.min.plural": "You must add at least {min} blocks", + "error.blocks.min.singular": "You must add at least one block", + "error.blocks.validation": "There's an error in block {index}", + + "error.email.preset.notFound": "قالب ایمیل «{name}» پیدا نشد", + + "error.field.converter.invalid": "مبدل «{converter}» نامعتبر است", + + "error.file.changeName.empty": "The name must not be empty", + "error.file.changeName.permission": "شما اجازه تنغییر نام فایل «{filename}» را ندارید", + "error.file.duplicate": "فایلی هم نام با «{filename}» هم اکنون موجود است", + "error.file.extension.forbidden": "پسوند فایل «{extension}» غیرمجاز است", + "error.file.extension.invalid": "Invalid extension: {extension}", + "error.file.extension.missing": "\u0634\u0645\u0627 \u0646\u0645\u06cc\u200c\u062a\u0648\u0627\u0646\u06cc\u062f \u0641\u0627\u06cc\u0644\u200c\u0647\u0627\u06cc \u0628\u062f\u0648\u0646 \u067e\u0633\u0648\u0646\u062f \u0631\u0627 \u0622\u067e\u0644\u0648\u062f \u06a9\u0646\u06cc\u062f", + "error.file.maxheight": "The height of the image must not exceed {height} pixels", + "error.file.maxsize": "The file is too large", + "error.file.maxwidth": "The width of the image must not exceed {width} pixels", + "error.file.mime.differs": "فایل آپلود شده باید از همان نوع باشد «{mime}»", + "error.file.mime.forbidden": "فرمت فایل «{mime}» غیرمجاز است", + "error.file.mime.invalid": "Invalid mime type: {mime}", + "error.file.mime.missing": "فرمت فایل «{filename}» قابل شناسایی نیست", + "error.file.minheight": "The height of the image must be at least {height} pixels", + "error.file.minsize": "The file is too small", + "error.file.minwidth": "The width of the image must be at least {width} pixels", + "error.file.name.missing": "نام فایل اجباری است", + "error.file.notFound": "فایل «{filename}» پیدا نشد.", + "error.file.orientation": "The orientation of the image must be \"{orientation}\"", + "error.file.type.forbidden": "شما اجازه بارگذاری فایلهای «{type}» را ندارید", + "error.file.type.invalid": "Invalid file type: {type}", + "error.file.undefined": "\u0641\u0627\u06cc\u0644 \u0645\u0648\u0631\u062f \u0646\u0638\u0631 \u067e\u06cc\u062f\u0627 \u0646\u0634\u062f.", + + "error.form.incomplete": "لطفا کلیه خطاهای فرم را برطرف کنید", + "error.form.notSaved": "امکان دخیره فرم وجود ندارد", + + "error.language.code": "Please enter a valid code for the language", + "error.language.duplicate": "The language already exists", + "error.language.name": "Please enter a valid name for the language", + + "error.layout.validation.block": "There's an error in block {blockIndex} in layout {layoutIndex}", + "error.layout.validation.settings": "There's an error in layout {index} settings", + + "error.license.format": "Please enter a valid license key", + "error.license.email": "لطفا ایمیل صحیحی وارد کنید", + "error.license.verification": "The license could not be verified", + + "error.page.changeSlug.permission": "شما امکان تغییر پسوند Url صفحه «{slug}» را ندارید", + "error.page.changeStatus.incomplete": "صفحه حاوی خطا است و قابل انتشار نیست", + "error.page.changeStatus.permission": "وضعیت صفحه جاری قابل تغییر نیست", + "error.page.changeStatus.toDraft.invalid": "صفحه «{slug}» قابل تبدیل به پیش نویس نیست", + "error.page.changeTemplate.invalid": "قالب صفحه «{slug}» قابل تغییر نیست", + "error.page.changeTemplate.permission": "شما اجازه تغییر قالب «{slug}» را ندارید", + "error.page.changeTitle.empty": "عنوان اجباری است", + "error.page.changeTitle.permission": "شما اجازه تغییر عنوان «{slug}» را ندارید", + "error.page.create.permission": "شما اجازه ایجاد «{slug}» را ندارید", + "error.page.delete": "حذف صفحه «{slug}» ممکن نیست", + "error.page.delete.confirm": "جهت ادامه عنوان صفحه را وارد کنید", + "error.page.delete.hasChildren": "این صفحه جاوی زیرصفحه است و نمی تواند حذف شود", + "error.page.delete.permission": "شما اجازه حذف «{slug}» را ندارید", + "error.page.draft.duplicate": "صفحه پیش‌نویسی با پسوند Url مشابه «{slug}» هم اکنون موجود است", + "error.page.duplicate": "صفحه‌ای با آدرس Url مشابه «{slug}» هم اکنون موجود است", + "error.page.duplicate.permission": "You are not allowed to duplicate \"{slug}\"", + "error.page.notFound": "صفحه مورد نظر با آدرس «{slug}» پیدا نشد.", + "error.page.num.invalid": "لطفا شماره ترتیب را بدرستی وارد نمایید. اعداد نباید منفی باشند.", + "error.page.slug.invalid": "Please enter a valid URL appendix", + "error.page.slug.maxlength": "Slug length must be less than \"{length}\" characters", + "error.page.sort.permission": "امکان مرتب‌سازی «{slug}» نیست", + "error.page.status.invalid": "لطفا وضعیت صحیحی برای صفحه انتخاب کنید", + "error.page.undefined": "صفحه مورد نظر پیدا نشد", + "error.page.update.permission": "شما اجازه بروزرسانی «{slug}» را ندارید", + + "error.section.files.max.plural": "نباید بیش از {max} فایل به بخش «{section}» اضافه کنید", + "error.section.files.max.singular": "نباید بیش از یک فایل به بخش «{section}» اضافه کنید", + "error.section.files.min.plural": "The \"{section}\" section requires at least {min} files", + "error.section.files.min.singular": "The \"{section}\" section requires at least one file", + + "error.section.pages.max.plural": "نباید بیش از {max} صفحه به بخش «{section}» اضافه کنید", + "error.section.pages.max.singular": "نباید بیش از یک صفحه به بخش «{section}» اضافه کنید", + "error.section.pages.min.plural": "The \"{section}\" section requires at least {min} pages", + "error.section.pages.min.singular": "The \"{section}\" section requires at least one page", + + "error.section.notLoaded": "بخش «{name}» قابل بارکذاری نیست", + "error.section.type.invalid": "نوع بخش «{type}» غیرمجاز است", + + "error.site.changeTitle.empty": "عنوان اجباری است", + "error.site.changeTitle.permission": "شما اجازه تغییر عنوان سایت را ندارید", + "error.site.update.permission": "شما اجازه بروزرسانی سایت را ندارید", + + "error.template.default.notFound": "قالب پیش فرض موجود نیست", + + "error.user.changeEmail.permission": "شما اجازه تغییر ایمیل کاربر «{name}» را ندارید", + "error.user.changeLanguage.permission": "شما اجازه تغییر زبان برای کاربر «{name}» را ندارید", + "error.user.changeName.permission": "شما اجازه تنغییر نام کاربر «{name}» را ندارید", + "error.user.changePassword.permission": "شما اجازه تغییر رمز عبور کاربر «{name}» را ندارید", + "error.user.changeRole.lastAdmin": "نقش آخرین مدیر سیستم قابل تغییر نیست", + "error.user.changeRole.permission": "شما اجازه تغییر نقش کاربر «{name}» را ندارید", + "error.user.changeRole.toAdmin": "You are not allowed to promote someone to the admin role", + "error.user.create.permission": "شما اجازه ایجاد این کاربر را ندارید", + "error.user.delete": "کاربر «{name}» نمی تواند حذف شود", + "error.user.delete.lastAdmin": "\u062d\u0630\u0641 \u0622\u062e\u0631\u06cc\u0646 \u0645\u062f\u06cc\u0631 \u0633\u06cc\u0633\u062a\u0645 \u0645\u0645\u06a9\u0646 \u0646\u06cc\u0633\u062a", + "error.user.delete.lastUser": "حذف آخرین کاربر ممکن نیست", + "error.user.delete.permission": "شما اجازه حذف کاربر «{name}» را ندارید", + "error.user.duplicate": "کاربری با ایمیل «{email}» هم اکنون موجود است", + "error.user.email.invalid": "لطفا یک ایمیل معتبر وارد کنید", + "error.user.language.invalid": "لطفا زبان معتبری انتخاب کنید", + "error.user.notFound": "کاربر «{name}» پیدا نشد", + "error.user.password.invalid": "لطفا گذرواژه صحیحی با حداقل طول 8 حرف وارد کنید. ", + "error.user.password.notSame": "\u0644\u0637\u0641\u0627 \u062a\u06a9\u0631\u0627\u0631 \u06af\u0630\u0631\u0648\u0627\u0698\u0647 \u0631\u0627 \u0648\u0627\u0631\u062f \u0646\u0645\u0627\u06cc\u06cc\u062f", + "error.user.password.undefined": "کاربر فاقد گذرواژه است", + "error.user.password.wrong": "Wrong password", + "error.user.role.invalid": "لطفا نقش صحیحی وارد نمایید", + "error.user.update.permission": "شما اجازه بروزرسانی کاربر «{name}» را ندارید", + + "error.validation.accepted": "لطفا تایید کنید", + "error.validation.alpha": "لطفا تنها از بین حروف a-z انتخاب کنید", + "error.validation.alphanum": "لطفا تنها از بین حروف a-z و اعداد 0-9 انتخاب کنید", + "error.validation.between": "لطفا مقداری مابین «{min}» و «{max}» وارد کنید", + "error.validation.boolean": "لطفا تایید یا رد کنید", + "error.validation.contains": "لطفا مقداری شامل «{needle}» وارد کنید", + "error.validation.date": "لطفا تاریخ معتبری وارد کنید", + "error.validation.date.after": "Please enter a date after {date}", + "error.validation.date.before": "Please enter a date before {date}", + "error.validation.date.between": "Please enter a date between {min} and {max}", + "error.validation.denied": "لطفا رد کنید", + "error.validation.different": "مقدار نباید مساوی «{other}» باشد", + "error.validation.email": "لطفا ایمیل صحیحی وارد کنید", + "error.validation.endswith": "مقدار باید با «{end}» ختم شود", + "error.validation.filename": "لطفا نام فایل صحیحی وارد کنید", + "error.validation.in": "لطفا یکی از مقادیر روبرو را وارد کنید: ({in})", + "error.validation.integer": "لطفا عدد صحیحی وارد کنید", + "error.validation.ip": "لطفا IP آدرس صحیحی وارد کنید", + "error.validation.less": "لطفا مقداری کمتر از {max} وارد کنید", + "error.validation.match": "مقدار وارد شده با الگوی مورد نظر همخوانی ندارد", + "error.validation.max": "لطفا مقداری کوچکتر یا مساوی {min} وارد کنید", + "error.validation.maxlength": "لطفا عبارت کوتاه‌تری وارد کنید. (حداکثر {max} حرف)", + "error.validation.maxwords": "لطفا بیش از {max} کلمه وارد نکنید", + "error.validation.min": "لطفا مقداری بزرگتر یا مساوی با {min} وارد کنید", + "error.validation.minlength": "لطفا عبارتی طولانی‌تری وارد کنید. (حداقل {min} حرف)", + "error.validation.minwords": "لطفا حداقل {min} کلمه وارد کنید", + "error.validation.more": "لطفا مقداری بیش از {min} وارد کنید", + "error.validation.notcontains": "لطفا مقداری فاقد «{needle}» وارد کنید", + "error.validation.notin": "لطفا از مقادیر روبرو استفاده نکنید: ({notin})", + "error.validation.option": "لطفا گزینه معتبری انتخاب کنید", + "error.validation.num": "لطفا عدد صحیحی وارد کنید", + "error.validation.required": "لطفا مقداری وارد کنید", + "error.validation.same": "لطفا مقدار «{other}» را وارد کنید", + "error.validation.size": "اندازه ورودی باید معادل «{size}» باشد", + "error.validation.startswith": "مقدار باید با «{start}» شروع شود", + "error.validation.time": "لطفا زمان معتبری وارد کنید", + "error.validation.time.after": "Please enter a time after {time}", + "error.validation.time.before": "Please enter a time before {time}", + "error.validation.time.between": "Please enter a time between {min} and {max}", + "error.validation.url": "لطفا آدرس URL صحیح وارد کنید", + + "field.required": "The field is required", + "field.blocks.changeType": "Change type", + "field.blocks.code.name": "کد", + "field.blocks.code.language": "زبان", + "field.blocks.code.placeholder": "Your code …", + "field.blocks.delete.confirm": "Do you really want to delete this block?", + "field.blocks.delete.confirm.all": "Do you really want to delete all blocks?", + "field.blocks.delete.confirm.selected": "Do you really want to delete the selected blocks?", + "field.blocks.empty": "No blocks yet", + "field.blocks.fieldsets.label": "Please select a block type …", + "field.blocks.gallery.name": "Gallery", + "field.blocks.gallery.images.empty": "No images yet", + "field.blocks.gallery.images.label": "Images", + "field.blocks.heading.level": "Level", + "field.blocks.heading.name": "Heading", + "field.blocks.heading.text": "Text", + "field.blocks.heading.placeholder": "Heading …", + "field.blocks.image.alt": "Alternative text", + "field.blocks.image.caption": "Caption", + "field.blocks.image.crop": "Crop", + "field.blocks.image.link": "پیوند", + "field.blocks.image.location": "Location", + "field.blocks.image.name": "تصویر", + "field.blocks.image.placeholder": "Select an image", + "field.blocks.image.ratio": "Ratio", + "field.blocks.image.url": "Image URL", + "field.blocks.list.name": "List", + "field.blocks.markdown.name": "Markdown", + "field.blocks.markdown.label": "Text", + "field.blocks.markdown.placeholder": "Markdown …", + "field.blocks.quote.name": "Quote", + "field.blocks.quote.text.label": "Text", + "field.blocks.quote.text.placeholder": "Quote …", + "field.blocks.quote.citation.label": "Citation", + "field.blocks.quote.citation.placeholder": "by …", + "field.blocks.text.name": "Text", + "field.blocks.text.placeholder": "Text …", + "field.blocks.video.caption": "Caption", + "field.blocks.video.name": "Video", + "field.blocks.video.placeholder": "Enter a video URL", + "field.blocks.video.url.label": "Video-URL", + "field.blocks.video.url.placeholder": "https://youtube.com/?v=", + + "field.files.empty": "فایلی انتخاب نشده است", + + "field.layout.delete": "Delete layout", + "field.layout.delete.confirm": "Do you really want to delete this layout?", + "field.layout.empty": "No rows yet", + "field.layout.select": "Select a layout", + + "field.pages.empty": "صفحه‌ای انتخاب نشده است", + "field.structure.delete.confirm": "\u0645\u062f\u062e\u0644 \u062c\u0627\u0631\u06cc \u062d\u0630\u0641 \u0634\u0648\u062f\u061f", + "field.structure.empty": "\u0645\u0648\u0631\u062f\u06cc \u0648\u062c\u0648\u062f \u0646\u062f\u0627\u0631\u062f.", + "field.users.empty": "کاربری انتخاب نشده است", + + "file.blueprint": "This file has no blueprint yet. You can define the setup in /site/blueprints/{template}.yml", + "file.delete.confirm": "آیا واقعا می خواهید این فایل را حذف کنید؟
{filename}", + "file.sort": "Change position", + + "files": "فایل‌ها", + "files.empty": "فایلی موجود نیست", + + "hide": "Hide", + "hour": "ساعت", + "insert": "\u062f\u0631\u062c", + "insert.after": "Insert after", + "insert.before": "Insert before", + "install": "نصب", + + "installation": "نصب و راه اندازی", + "installation.completed": "پنل کاربری نصب شد", + "installation.disabled": "نصب کننده پانل کاربری بصورت پیش‌فرض در سرورهای عمومی غیرفعال است. لطفا نصب را روی یک ماشین محلی اجرا کنید یا آن را با استفاده از panel.install فعال کنید.", + "installation.issues.accounts": "پوشه /site/accounts موجود نیست یا قابل نوشتن نیست.", + "installation.issues.content": "پوشه /content موجود نیست یا قابل نوشتن نیست", + "installation.issues.curl": "افزونه CURL مورد نیاز است", + "installation.issues.headline": "نصب پانل کاربری ممکن نیست", + "installation.issues.mbstring": "افزونه MB String مورد نیاز است", + "installation.issues.media": "پوشه /media موجود نیست یا قابل نوشتن نیست", + "installation.issues.php": "لطفا از پی‌اچ‌پی 7 یا بالاتر استفاده کنید", + "installation.issues.server": "کربی نیاز به Apache، Nginx یا Caddy دارد", + "installation.issues.sessions": "پوشه /site/sessions وجود ندارد یا قابل نوشتن نیست", + + "language": "\u0632\u0628\u0627\u0646", + "language.code": "کد", + "language.convert": "پیش‌فرض شود", + "language.convert.confirm": "

آیا واقعا میخواهید {name} را به زبان پیشفرض تبدیل کنید؟ این عمل برگشت ناپذیر است.

اگر {name} دارای محتوای غیر ترجمه شده باشد، جایگزین معتبر دیگری نخواهد بود و ممکن است بخش‌هایی از سایت شما خالی باشد.

", + "language.create": "افزودن زبان جدید", + "language.delete.confirm": "آیا واقعا میخواهید زبان {name} را به همراه تمام ترجمه‌ها حذف کنید؟ این عمل قابل بازگشت نخواهد بود!", + "language.deleted": "زبان مورد نظر حذف شد", + "language.direction": "rtl", + "language.direction.ltr": "چپ به راست", + "language.direction.rtl": "راست به چپ", + "language.locale": "PHP locale string", + "language.locale.warning": "You are using a custom locale set up. Please modify it in the language file in /site/languages", + "language.name": "پارسی", + "language.updated": "زبان به روز شد", + + "languages": "زبان‌ها", + "languages.default": "زبان پیش‌فرض", + "languages.empty": "هنوز هیچ زبانی موجود نیست", + "languages.secondary": "زبان‌های ثانویه", + "languages.secondary.empty": "هنوز هیچ زبان ثانویه‌ای موجود نیست", + + "license": "\u0645\u062c\u0648\u0632", + "license.buy": "خرید مجوز", + "license.register": "ثبت", + "license.register.help": "پس از خرید از طریق ایمیل، کد مجوز خود را دریافت کردید. لطفا برای ثبت‌نام آن را کپی و اینجا پیست کنید.", + "license.register.label": "لطفا کد مجوز خود را وارد کنید", + "license.register.success": "با تشکر از شما برای حمایت از کربی", + "license.unregistered": "این یک نسخه آزمایشی ثبت نشده از کربی است", + + "link": "\u067e\u06cc\u0648\u0646\u062f", + "link.text": "\u0645\u062a\u0646 \u067e\u06cc\u0648\u0646\u062f", + + "loading": "بارگزاری", + + "lock.unsaved": "Unsaved changes", + "lock.unsaved.empty": "There are no more unsaved changes", + "lock.isLocked": "Unsaved changes by {email}", + "lock.file.isLocked": "The file is currently being edited by {email} and cannot be changed.", + "lock.page.isLocked": "The page is currently being edited by {email} and cannot be changed.", + "lock.unlock": "Unlock", + "lock.isUnlocked": "Your unsaved changes have been overwritten by another user. You can download your changes to merge them manually.", + + "login": "ورود", + "login.code.label.login": "Login code", + "login.code.label.password-reset": "Password reset code", + "login.code.placeholder.email": "000 000", + "login.code.text.email": "If your email address is registered, the requested code was sent via email.", + "login.email.login.body": "Hi {user.nameOrEmail},\n\nYou recently requested a login code for the Kirby Panel.\nThe following login code will be valid for {timeout} minutes:\n\n{code}\n\nIf you did not request a login code, please ignore this email or contact your administrator if you have questions.\nFor security, please DO NOT forward this email.", + "login.email.login.subject": "Your login code", + "login.email.password-reset.body": "Hi {user.nameOrEmail},\n\nYou recently requested a password reset code for the Kirby Panel.\nThe following password reset code will be valid for {timeout} minutes:\n\n{code}\n\nIf you did not request a password reset code, please ignore this email or contact your administrator if you have questions.\nFor security, please DO NOT forward this email.", + "login.email.password-reset.subject": "Your password reset code", + "login.remember": "مرا به خاطر بسپار", + "login.reset": "Reset password", + "login.toggleText.code.email": "Login via email", + "login.toggleText.code.email-password": "Login with password", + "login.toggleText.password-reset.email": "Forgot your password?", + "login.toggleText.password-reset.email-password": "← Back to login", + + "logout": "خروج", + + "menu": "منو", + "meridiem": "ق.ظ/ب.ظ", + "mime": "نوع رسانه", + "minutes": "دقیقه", + + "month": "ماه", + "months.april": "\u0622\u0648\u0631\u06cc\u0644", + "months.august": "\u0627\u0648\u062a", + "months.december": "\u062f\u0633\u0627\u0645\u0628\u0631", + "months.february": "فوریه", + "months.january": "\u0698\u0627\u0646\u0648\u06cc\u0647", + "months.july": "\u0698\u0648\u0626\u06cc\u0647", + "months.june": "\u0698\u0648\u0626\u0646", + "months.march": "\u0645\u0627\u0631\u0633", + "months.may": "\u0645\u06cc", + "months.november": "\u0646\u0648\u0627\u0645\u0628\u0631", + "months.october": "\u0627\u06a9\u062a\u0628\u0631", + "months.september": "\u0633\u067e\u062a\u0627\u0645\u0628\u0631", + + "more": "بیشتر", + "name": "نام", + "next": "بعدی", + "no": "no", + "off": "off", + "on": "on", + "open": "بازکردن", + "open.newWindow": "Open in new window", + "options": "گزینه‌ها", + "options.none": "No options", + + "orientation": "جهت", + "orientation.landscape": "افقی", + "orientation.portrait": "عمودی", + "orientation.square": "مربع", + + "page.blueprint": "This page has no blueprint yet. You can define the setup in /site/blueprints/{template}.yml", + "page.changeSlug": "تغییر Url صفحه", + "page.changeSlug.fromTitle": "\u0627\u06cc\u062c\u0627\u062f \u0627\u0632 \u0631\u0648\u06cc \u0639\u0646\u0648\u0627\u0646", + "page.changeStatus": "تغییر وضعیت", + "page.changeStatus.position": "لطفا یک موقعیت را انتخاب کنید", + "page.changeStatus.select": "یک وضعیت جدید را انتخاب کنید", + "page.changeTemplate": "تغییر قالب", + "page.delete.confirm": "صفحه {title} حذف شود؟", + "page.delete.confirm.subpages": "این صفحه دارای زیرصفحه است.
تمام زیر صفحات نیز حذف خواهد شد.", + "page.delete.confirm.title": "جهت ادامه عنوان صفحه را وارد کنید", + "page.draft.create": "ایجاد پیش‌نویس", + "page.duplicate.appendix": "کپی", + "page.duplicate.files": "Copy files", + "page.duplicate.pages": "Copy pages", + "page.sort": "Change position", + "page.status": "وضعیت", + "page.status.draft": "پیش‌نویس", + "page.status.draft.description": "The page is in draft mode and only visible for logged in editors or via secret link", + "page.status.listed": "عمومی", + "page.status.listed.description": "این صفحه برای عموم قابل مشاهده است", + "page.status.unlisted": "فهرست نشده", + "page.status.unlisted.description": "این صفحه فقط از طریق URL قابل دسترسی است", + + "pages": "صفحات", + "pages.empty": "هنوز هیچ صفحه‌ای موجود نیست", + "pages.status.draft": "پیش‌نویس‌ها", + "pages.status.listed": "منتشر شده", + "pages.status.unlisted": "فهرست نشده", + + "pagination.page": "صفحه", + + "password": "\u06af\u0630\u0631\u0648\u0627\u0698\u0647", + "pixel": "پیکسل", + "prev": "قبلی", + "preview": "Preview", + "remove": "حذف", + "rename": "تغییر نام", + "replace": "\u062c\u0627\u06cc\u06af\u0632\u06cc\u0646\u06cc", + "retry": "\u062a\u0644\u0627\u0634 \u0645\u062c\u062f\u062f", + "revert": "بازگرداندن تغییرات", + "revert.confirm": "Do you really want to delete all unsaved changes?", + + "role": "\u0646\u0642\u0634", + "role.admin.description": "The admin has all rights", + "role.admin.title": "Admin", + "role.all": "همه", + "role.empty": "هیچ کاربری با این نقش وجود ندارد", + "role.description.placeholder": "فاقد شرح", + "role.nobody.description": "This is a fallback role without any permissions", + "role.nobody.title": "Nobody", + + "save": "\u0630\u062e\u06cc\u0631\u0647", + "search": "جستجو", + "search.min": "Enter {min} characters to search", + "search.all": "Show all", + "search.results.none": "No results", + + "section.required": "The section is required", + + "select": "انتخاب", + "settings": "تنظیمات", + "show": "Show", + "size": "اندازه", + "slug": "پسوند Url", + "sort": "ترتیب", + "title": "عنوان", + "template": "\u0642\u0627\u0644\u0628 \u0635\u0641\u062d\u0647", + "today": "امروز", + + "site.blueprint": "The site has no blueprint yet. You can define the setup in /site/blueprints/site.yml", + + "toolbar.button.code": "کد", + "toolbar.button.bold": "\u0645\u062a\u0646 \u0628\u0627 \u062d\u0631\u0648\u0641 \u062f\u0631\u0634\u062a", + "toolbar.button.email": "\u067e\u0633\u062a \u0627\u0644\u06a9\u062a\u0631\u0648\u0646\u06cc\u06a9", + "toolbar.button.headings": "عنوان‌ها", + "toolbar.button.heading.1": "عنوان 1", + "toolbar.button.heading.2": "عنوان 2", + "toolbar.button.heading.3": "عنوان 3", + "toolbar.button.italic": "\u0645\u062a\u0646 \u0627\u0631\u06cc\u0628", + "toolbar.button.file": "فایل", + "toolbar.button.file.select": "Select a file", + "toolbar.button.file.upload": "Upload a file", + "toolbar.button.link": "\u067e\u06cc\u0648\u0646\u062f", + "toolbar.button.strike": "Strike-through", + "toolbar.button.ol": "لیست مرتب", + "toolbar.button.underline": "Underline", + "toolbar.button.ul": "لیست معمولی", + + "translation.author": "تیم کربی", + "translation.direction": "rtl", + "translation.name": "انگلیسی", + "translation.locale": "fa_IR", + + "upload": "بارگذاری", + "upload.error.cantMove": "The uploaded file could not be moved", + "upload.error.cantWrite": "Failed to write file to disk", + "upload.error.default": "The file could not be uploaded", + "upload.error.extension": "File upload stopped by extension", + "upload.error.formSize": "The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the form", + "upload.error.iniPostSize": "The uploaded file exceeds the post_max_size directive in php.ini", + "upload.error.iniSize": "The uploaded file exceeds the upload_max_filesize directive in php.ini", + "upload.error.noFile": "No file was uploaded", + "upload.error.noFiles": "No files were uploaded", + "upload.error.partial": "The uploaded file was only partially uploaded", + "upload.error.tmpDir": "Missing a temporary folder", + "upload.errors": "خطا", + "upload.progress": "در حال بارگذاری...", + + "url": "Url", + "url.placeholder": "https://example.com", + + "user": "کاربر", + "user.blueprint": "شما می توانید قسمت‌های اضافی و فیلدهای فرم را برای این نقش کاربر در /site/blueprints/users/{role}.yml تعریف کنید", + "user.changeEmail": "تغییر ایمیل", + "user.changeLanguage": "تغییر زبان", + "user.changeName": "تغییر نام این کاربر", + "user.changePassword": "تغییر گذرواژه", + "user.changePassword.new": "گذرواژه جدید", + "user.changePassword.new.confirm": "تایید گذرواژه جدید...", + "user.changeRole": "تغییر نقش", + "user.changeRole.select": "یک نقش جدید را انتخاب کنید", + "user.create": "افزودن کاربر جدید", + "user.delete": "حذف کاربر جاری", + "user.delete.confirm": "آیا واقعا میخواهید {email} را حذف کنید؟", + + "users": "کاربران", + + "version": "\u0646\u0633\u062e\u0647 \u0646\u0631\u0645 \u0627\u0641\u0632\u0627\u0631", + + "view.account": "حساب کاربری شما", + "view.installation": "\u0646\u0635\u0628 \u0648 \u0631\u0627\u0647 \u0627\u0646\u062f\u0627\u0632\u06cc", + "view.resetPassword": "Reset password", + "view.settings": "تنظیمات", + "view.site": "سایت", + "view.users": "\u06a9\u0627\u0631\u0628\u0631\u0627\u0646", + + "welcome": "خوش آمدید", + "year": "سال", + "yes": "yes" +} diff --git a/kirby/i18n/translations/fi.json b/kirby/i18n/translations/fi.json new file mode 100644 index 0000000..f1c5410 --- /dev/null +++ b/kirby/i18n/translations/fi.json @@ -0,0 +1,527 @@ +{ + "add": "Lis\u00e4\u00e4", + "avatar": "Profiilikuva", + "back": "Takaisin", + "cancel": "Peruuta", + "change": "Muuta", + "close": "Sulje", + "confirm": "Ok", + "collapse": "Collapse", + "collapse.all": "Collapse All", + "copy": "Kopioi", + "create": "Luo", + + "date": "Päivämäärä", + "date.select": "Valitse päivämäärä", + + "day": "Päivä", + "days.fri": "Pe", + "days.mon": "Ma", + "days.sat": "La", + "days.sun": "Su", + "days.thu": "To", + "days.tue": "Ti", + "days.wed": "Ke", + + "delete": "Poista", + "delete.all": "Delete all", + "dimensions": "Mitat", + "disabled": "Disabled", + "discard": "Hylkää", + "download": "Lataa", + "duplicate": "Kahdenna", + "edit": "Muokkaa", + "expand": "Expand", + "expand.all": "Expand All", + + "dialog.files.empty": "Ei valittavissa olevia tiedostoja", + "dialog.pages.empty": "Ei valittavissa olevia sivuja", + "dialog.users.empty": "Ei valittavissa olevia käyttäjiä", + + "email": "S\u00e4hk\u00f6posti", + "email.placeholder": "nimi@osoite.fi", + + "error.access.code": "Invalid code", + "error.access.login": "Kirjautumistiedot eivät kelpaa", + "error.access.panel": "Sinulla ei ole oikeutta käyttää paneelia", + "error.access.view": "Sinulla ei ole oikeutta käyttää tätä osaa paneelista", + + "error.avatar.create.fail": "Profiilikuvaa ei voitu lähettää", + "error.avatar.delete.fail": "Profiilikuvaa ei voitu poistaa", + "error.avatar.dimensions.invalid": "Profiilikuvan leveys ja korkeus voivat olla enintään 3000 pikseliä", + "error.avatar.mime.forbidden": "Profiilikuvan täytyy olla joko JPEG- tai PNG-formaatissa", + + "error.blueprint.notFound": "Kaavaa \"{name}\" ei voitu ladata", + + "error.blocks.max.plural": "You must not add more than {max} blocks", + "error.blocks.max.singular": "You must not add more than one block", + "error.blocks.min.plural": "You must add at least {min} blocks", + "error.blocks.min.singular": "You must add at least one block", + "error.blocks.validation": "There's an error in block {index}", + + "error.email.preset.notFound": "Nimellä \"{name}\" ja kyseisellä verkkotunnuksella ei löydy sähköpostiosoitetta", + + "error.field.converter.invalid": "Muunnin \"{converter}\" ei kelpaa", + + "error.file.changeName.empty": "Nimi ei voi olla tyhjä", + "error.file.changeName.permission": "Sinulla ei ole oikeutta muuttaa tiedoston \"{filename}\" nimeä", + "error.file.duplicate": "Tiedosto nimellä \"{filename}\" on jo olemassa", + "error.file.extension.forbidden": "Tiedostopääte \"{extension}\" ei ole sallittu", + "error.file.extension.invalid": "Invalid extension: {extension}", + "error.file.extension.missing": "Tiedoston \"{filename}\" tiedostopääte puuttuu", + "error.file.maxheight": "The height of the image must not exceed {height} pixels", + "error.file.maxsize": "The file is too large", + "error.file.maxwidth": "The width of the image must not exceed {width} pixels", + "error.file.mime.differs": "Lähetetyllä tiedostolla täytyy olla sama mime-tyyppi \"{mime}\"", + "error.file.mime.forbidden": "Median tyyppi \"{mime}\" ei ole sallittu", + "error.file.mime.invalid": "Invalid mime type: {mime}", + "error.file.mime.missing": "Tiedoston \"{filename}\" mediatyyppiä ei voida tunnistaa", + "error.file.minheight": "The height of the image must be at least {height} pixels", + "error.file.minsize": "The file is too small", + "error.file.minwidth": "The width of the image must be at least {width} pixels", + "error.file.name.missing": "Tiedostonimi ei voi olla tyhjä", + "error.file.notFound": "Tiedostoa \"{filename}\" ei löytynyt", + "error.file.orientation": "The orientation of the image must be \"{orientation}\"", + "error.file.type.forbidden": "Sinulla ei ole oikeutta lähettää tiedostoja joiden tyyppi on {type}", + "error.file.type.invalid": "Invalid file type: {type}", + "error.file.undefined": "Tiedostoa ei l\u00f6ytynyt", + + "error.form.incomplete": "Korjaa kaikki lomakkeen virheet...", + "error.form.notSaved": "Lomaketta ei voitu tallentaa", + + "error.language.code": "Anna kielen lyhenne", + "error.language.duplicate": "Kieli on jo olemassa", + "error.language.name": "Anna kielen nimi", + + "error.layout.validation.block": "There's an error in block {blockIndex} in layout {layoutIndex}", + "error.layout.validation.settings": "There's an error in layout {index} settings", + + "error.license.format": "Anna lisenssiavain", + "error.license.email": "Anna kelpaava sähköpostiosoite", + "error.license.verification": "Lisenssiä ei voitu vahvistaa", + + "error.page.changeSlug.permission": "Sinulla ei ole oikeutta muuttaa URL-liitettä sivulle \"{slug}\"", + "error.page.changeStatus.incomplete": "Sivulla on virheitä eikä sitä voitu julkaista", + "error.page.changeStatus.permission": "Tämän sivun tilaa ei voi muuttaa", + "error.page.changeStatus.toDraft.invalid": "Sivua \"{slug}\" ei voi muuttaa luonnokseksi", + "error.page.changeTemplate.invalid": "Sivun \"{slug}\" pohjaa ei voi muuttaa", + "error.page.changeTemplate.permission": "Sinulla ei ole oikeutta muuttaa sivun \"{slug}\" sivupohjaa", + "error.page.changeTitle.empty": "Nimi ei voi olla tyhjä", + "error.page.changeTitle.permission": "Sinulla ei ole oikeutta muuttaa sivun \"{slug}\" nimeä", + "error.page.create.permission": "Sinulla ei ole oikeutta luoda sivua \"{slug}\"", + "error.page.delete": "Sivua \"{slug}\" ei voi poistaa", + "error.page.delete.confirm": "Anna vahvistuksena sivun nimi", + "error.page.delete.hasChildren": "Sivu sisältää alasivuja eikä sitä voida poistaa", + "error.page.delete.permission": "Sinulla ei ole oikeutta poistaa sivua \"{slug}\"", + "error.page.draft.duplicate": "Sivuluonnos URL-liitteellä \"{slug}\" on jo olemassa", + "error.page.duplicate": "Sivu URL-liitteellä \"{slug}\" on jo olemassa", + "error.page.duplicate.permission": "Sinulla ei ole oikeutta kahdentaa sivua \"{slug}\"", + "error.page.notFound": "Sivua \"{slug}\" ei löytynyt", + "error.page.num.invalid": "Anna kelpaava järjestysnumero. Numero ei voi olla negatiivinen.", + "error.page.slug.invalid": "Please enter a valid URL appendix", + "error.page.slug.maxlength": "Slug length must be less than \"{length}\" characters", + "error.page.sort.permission": "Sivua \"{slug}\" ei voi järjestellä", + "error.page.status.invalid": "Aseta kelvollinen sivun tila", + "error.page.undefined": "Sivua ei l\u00f6ytynyt", + "error.page.update.permission": "Sinulla ei ole oikeutta päivittää sivua \"{slug}\"", + + "error.section.files.max.plural": "Et voi lisätä enemmän kuin {max} tiedostoa osioon \"{section}\"", + "error.section.files.max.singular": "Et voi lisätä enempää kuin yhden tiedoston osioon \"{section}\"", + "error.section.files.min.plural": "Osio \"{section}\" vaatii ainakin {min} tiedostoa", + "error.section.files.min.singular": "Osio \"{section}\" vaatii ainakin yhden sivun", + + "error.section.pages.max.plural": "Et voi lisätä enemmän kuin {max} sivua osioon \"{section}\"", + "error.section.pages.max.singular": "Et voi lisätä enempää kuin yhden sivun osioon \"{section}\"", + "error.section.pages.min.plural": "Osio \"{section}\" vaatii ainakin {min} sivua", + "error.section.pages.min.singular": "Osio \"{section}\" vaatii ainakin yhden sivun", + + "error.section.notLoaded": "Osiota \"{name}\" ei voitu ladata", + "error.section.type.invalid": "Osion tyyppi \"{type}\" ei ole kelvollinen", + + "error.site.changeTitle.empty": "Nimi ei voi olla tyhjä", + "error.site.changeTitle.permission": "Sinulla ei ole oikeutta päivittää sivuston nimeä", + "error.site.update.permission": "Sinulla ei ole oikeutta päivittää sivuston tietoja", + + "error.template.default.notFound": "Oletussivupohjaa ei ole määritetty", + + "error.user.changeEmail.permission": "Sinulla ei ole oikeutta vaihtaa käyttäjän \"{name}\" sähköpostiosoitetta", + "error.user.changeLanguage.permission": "Sinulla ei ole oikeutta vaihtaa käyttäjän \"{name}\" kieltä", + "error.user.changeName.permission": "Sinulla ei ole oikeutta vaihtaa käyttäjän \"{name}\" nimeä", + "error.user.changePassword.permission": "Sinulla ei ole oikeutta vaihtaa käyttäjän \"{name}\" salasanaa", + "error.user.changeRole.lastAdmin": "Ainoan pääkäyttäjän roolia ei voi muuttaa", + "error.user.changeRole.permission": "Sinulla ei ole oikeutta vaihtaa käyttäjän \"{name}\" käyttäjätasoa", + "error.user.changeRole.toAdmin": "Sinulla ei ole oikeutta vaihtaa käyttäjätasoa pääkäyttäjäksi", + "error.user.create.permission": "Sinulla ei ole oikeutta luoda tätä käyttäjää", + "error.user.delete": "Käyttäjää \"{name}\" ei voi poistaa", + "error.user.delete.lastAdmin": "Ainoaa pääkäyttäjää ei voi poistaa", + "error.user.delete.lastUser": "Ainoaa käyttäjää ei voi poistaa", + "error.user.delete.permission": "Sinulla ei ole oikeutta poistaa käyttäjää \"{name}\"", + "error.user.duplicate": "Käyttäjä, jonka sähköpostiosoite on \"{name}\", on jo olemassa", + "error.user.email.invalid": "Anna kelpaava sähköpostiosoite", + "error.user.language.invalid": "Anna kelpaava kieli", + "error.user.notFound": "K\u00e4ytt\u00e4j\u00e4\u00e4 ei l\u00f6ytynyt", + "error.user.password.invalid": "Anna kelpaava salasana. Salasanan täytyy olla ainakin 8 merkkiä pitkä.", + "error.user.password.notSame": "Salasanat eivät täsmää", + "error.user.password.undefined": "Käyttäjällä ei ole salasanaa", + "error.user.password.wrong": "Wrong password", + "error.user.role.invalid": "Anna kelpaava käyttäjätaso", + "error.user.update.permission": "Sinulla ei ole oikeutta päivittää käyttäjää \"{name}\"", + + "error.validation.accepted": "Ole hyvä ja vahvista", + "error.validation.alpha": "Anna vain merkkejä väliltä a-z", + "error.validation.alphanum": "Anna vain merkkejä väliltä a-z tai/ja numeroita väliltä 0-9", + "error.validation.between": "Anna arvo väliltä \"{min}\" ja \"{max}\"", + "error.validation.boolean": "Vahvista tai peruuta", + "error.validation.contains": "Anna arvo joka sisältää \"{needle}\"", + "error.validation.date": "Anna kelpaava päivämäärä", + "error.validation.date.after": "Anna päivämäärä {date} jälkeen", + "error.validation.date.before": "Anna päivämäärä ennen {date}", + "error.validation.date.between": "Anna päivämäärä väliltä {min} ja {max}", + "error.validation.denied": "Ole hyvä ja peruuta", + "error.validation.different": "Arvo ei voi olla \"{other}\"", + "error.validation.email": "Anna kelpaava sähköpostiosoite", + "error.validation.endswith": "Arvon loppuosa täytyy olla \"{end}\"", + "error.validation.filename": "Anna kelpaava tiedostonimi", + "error.validation.in": "Anna joku seuraavista: ({in})", + "error.validation.integer": "Anna kelpaava kokonaisluku", + "error.validation.ip": "Anna kelpaava IP-osoite", + "error.validation.less": "Anna arvo joka on pienempi kuin {max}", + "error.validation.match": "Arvo ei vastaa vaadittua kaavaa", + "error.validation.max": "Anna arvo joka on enintään {max}", + "error.validation.maxlength": "Anna lyhyempi arvo. (enintään {max} merkkiä)", + "error.validation.maxwords": "Anna korkeintaan {max} sana(a)", + "error.validation.min": "Anna arvo joka on vähintään {min}", + "error.validation.minlength": "Anna pidempi arvo. (vähintään {min} merkkiä)", + "error.validation.minwords": "Anna vähintään {min} sana(a)", + "error.validation.more": "Anna suurempi arvo kuin {min}", + "error.validation.notcontains": "Anna arvo joka ei sisällä \"{needle}\"", + "error.validation.notin": "Arvo ei voi sisältää mitään seuraavista: ({notIn})", + "error.validation.option": "Valitse kelpaava vaihtoehto", + "error.validation.num": "Anna kelpaava numero", + "error.validation.required": "Arvo ei voi olla tyhjä", + "error.validation.same": "Anna \"{other}\"", + "error.validation.size": "Arvon koko täytyy olla \"{size}\"", + "error.validation.startswith": "Arvon alkuosa täytyy olla \"{start}\"", + "error.validation.time": "Anna kelpaava aika", + "error.validation.time.after": "Please enter a time after {time}", + "error.validation.time.before": "Please enter a time before {time}", + "error.validation.time.between": "Please enter a time between {min} and {max}", + "error.validation.url": "Anna kelpaava URL", + + "field.required": "Kenttä on pakollinen", + "field.blocks.changeType": "Change type", + "field.blocks.code.name": "Koodi", + "field.blocks.code.language": "Kieli", + "field.blocks.code.placeholder": "Your code …", + "field.blocks.delete.confirm": "Do you really want to delete this block?", + "field.blocks.delete.confirm.all": "Do you really want to delete all blocks?", + "field.blocks.delete.confirm.selected": "Do you really want to delete the selected blocks?", + "field.blocks.empty": "No blocks yet", + "field.blocks.fieldsets.label": "Please select a block type …", + "field.blocks.gallery.name": "Gallery", + "field.blocks.gallery.images.empty": "No images yet", + "field.blocks.gallery.images.label": "Images", + "field.blocks.heading.level": "Level", + "field.blocks.heading.name": "Heading", + "field.blocks.heading.text": "Text", + "field.blocks.heading.placeholder": "Heading …", + "field.blocks.image.alt": "Alternative text", + "field.blocks.image.caption": "Caption", + "field.blocks.image.crop": "Crop", + "field.blocks.image.link": "Linkki", + "field.blocks.image.location": "Location", + "field.blocks.image.name": "Kuva", + "field.blocks.image.placeholder": "Select an image", + "field.blocks.image.ratio": "Ratio", + "field.blocks.image.url": "Image URL", + "field.blocks.list.name": "List", + "field.blocks.markdown.name": "Markdown", + "field.blocks.markdown.label": "Text", + "field.blocks.markdown.placeholder": "Markdown …", + "field.blocks.quote.name": "Quote", + "field.blocks.quote.text.label": "Text", + "field.blocks.quote.text.placeholder": "Quote …", + "field.blocks.quote.citation.label": "Citation", + "field.blocks.quote.citation.placeholder": "by …", + "field.blocks.text.name": "Text", + "field.blocks.text.placeholder": "Text …", + "field.blocks.video.caption": "Caption", + "field.blocks.video.name": "Video", + "field.blocks.video.placeholder": "Enter a video URL", + "field.blocks.video.url.label": "Video-URL", + "field.blocks.video.url.placeholder": "https://youtube.com/?v=", + + "field.files.empty": "Tiedostoja ei ole vielä valittu", + + "field.layout.delete": "Delete layout", + "field.layout.delete.confirm": "Do you really want to delete this layout?", + "field.layout.empty": "No rows yet", + "field.layout.select": "Select a layout", + + "field.pages.empty": " Sivuja ei ole vielä valittu", + "field.structure.delete.confirm": "Haluatko varmasti poistaa tämän rivin?", + "field.structure.empty": "Rivejä ei ole vielä lisätty", + "field.users.empty": "Käyttäjiä ei ole vielä valittu", + + "file.blueprint": "This file has no blueprint yet. You can define the setup in /site/blueprints/{template}.yml", + "file.delete.confirm": "Haluatko varmasti poistaa tiedoston
{filename}?", + "file.sort": "Change position", + + "files": "Tiedostot", + "files.empty": "Tiedostoja ei ole vielä lisätty", + + "hide": "Hide", + "hour": "Tunti", + "insert": "Lis\u00e4\u00e4", + "insert.after": "Insert after", + "insert.before": "Insert before", + "install": "Asenna", + + "installation": "Asennus", + "installation.completed": "Paneeli on asennettu", + "installation.disabled": "Paneelin asennus on oletuksena poissa käytöstä julkisilla palvelimilla. Aja asennus paikallisella koneella, tai ota paneeli käyttöön panel.install-optiolla.", + "installation.issues.accounts": "/site/accounts -kansio ei ole olemassa tai siihen ei voi kirjoittaa", + "installation.issues.content": "/content -kansio ei ole olemassa tai siihen ei voi kirjoittaa", + "installation.issues.curl": "CURL-laajennos on pakollinen", + "installation.issues.headline": "Paneelia ei voida asentaa", + "installation.issues.mbstring": "MB String-laajennos on pakollinen", + "installation.issues.media": "/media -kansio ei ole olemassa tai siihen ei voi kirjoittaa", + "installation.issues.php": "Varmista että PHP 7+ on käytössä", + "installation.issues.server": "Kirby tarvitsee jonkun seuraavista: Apache, Nginx tai Caddy", + "installation.issues.sessions": "/site/sessions -kansio ei ole olemassa tai siihen ei voi kirjoittaa", + + "language": "Kieli", + "language.code": "Tunniste", + "language.convert": "Muuta oletukseksi", + "language.convert.confirm": "

Haluatko varmasti muuttaa kielen {name} oletuskieleksi? Tätä muutosta ei voi peruuttaa.

Jos{name} sisältää kääntämättömiä kohtia, varakäännöstä ei enää ole näille kohdille ja sivustosi saattaa olla osittain tyhjä.

", + "language.create": "Lisää uusi kieli", + "language.delete.confirm": "Haluatko varmasti poistaa kielen {name}, mukaanlukien kaikki käännökset? Tätä toimintoa ei voi peruuttaa!", + "language.deleted": "Kieli on poistettu", + "language.direction": "Lukusuunta", + "language.direction.ltr": "Vasemmalta oikealle", + "language.direction.rtl": "Oikealta vasemmalle", + "language.locale": "PHP-lokaalin tunniste", + "language.locale.warning": "You are using a custom locale set up. Please modify it in the language file in /site/languages", + "language.name": "Nimi", + "language.updated": "Kieli on päivitetty", + + "languages": "Kielet", + "languages.default": "Oletuskieli", + "languages.empty": "Kieliä ei ole vielä määritetty", + "languages.secondary": "Toissijaiset kielet", + "languages.secondary.empty": "Toissijaisia kieliä ei ole vielä määritetty", + + "license": "Lisenssi", + "license.buy": "Osta lisenssi", + "license.register": "Rekisteröi", + "license.register.help": "Lisenssiavain on lähetetty oston jälkeen sähköpostiisi. Kopioi ja liitä avain tähän.", + "license.register.label": "Anna lisenssiavain", + "license.register.success": "Kiitos kun tuet Kirbyä", + "license.unregistered": "Tämä on rekisteröimätön demo Kirbystä", + + "link": "Linkki", + "link.text": "Linkin teksti", + + "loading": "Ladataan", + + "lock.unsaved": "Tallentamattomia muutoksia", + "lock.unsaved.empty": "There are no more unsaved changes", + "lock.isLocked": "Käyttäjällä {email} on tallentamattomia muutoksia", + "lock.file.isLocked": "The file is currently being edited by {email} and cannot be changed.", + "lock.page.isLocked": "The page is currently being edited by {email} and cannot be changed.", + "lock.unlock": "Vapauta", + "lock.isUnlocked": "Your unsaved changes have been overwritten by another user. You can download your changes to merge them manually.", + + "login": "Kirjaudu", + "login.code.label.login": "Login code", + "login.code.label.password-reset": "Password reset code", + "login.code.placeholder.email": "000 000", + "login.code.text.email": "If your email address is registered, the requested code was sent via email.", + "login.email.login.body": "Hi {user.nameOrEmail},\n\nYou recently requested a login code for the Kirby Panel.\nThe following login code will be valid for {timeout} minutes:\n\n{code}\n\nIf you did not request a login code, please ignore this email or contact your administrator if you have questions.\nFor security, please DO NOT forward this email.", + "login.email.login.subject": "Your login code", + "login.email.password-reset.body": "Hi {user.nameOrEmail},\n\nYou recently requested a password reset code for the Kirby Panel.\nThe following password reset code will be valid for {timeout} minutes:\n\n{code}\n\nIf you did not request a password reset code, please ignore this email or contact your administrator if you have questions.\nFor security, please DO NOT forward this email.", + "login.email.password-reset.subject": "Your password reset code", + "login.remember": "Pidä minut kirjautuneena", + "login.reset": "Reset password", + "login.toggleText.code.email": "Login via email", + "login.toggleText.code.email-password": "Login with password", + "login.toggleText.password-reset.email": "Forgot your password?", + "login.toggleText.password-reset.email-password": "← Back to login", + + "logout": "Kirjaudu ulos", + + "menu": "Valikko", + "meridiem": "am/pm", + "mime": "Median tyyppi", + "minutes": "Minuutit", + + "month": "Kuukausi", + "months.april": "Huhtikuu", + "months.august": "Elokuu", + "months.december": "Joulukuu", + "months.february": "Helmikuu", + "months.january": "Tammikuu", + "months.july": "Hein\u00e4kuu", + "months.june": "Kes\u00e4kuu", + "months.march": "Maaliskuu", + "months.may": "Toukokuu", + "months.november": "Marraskuu", + "months.october": "Lokakuu", + "months.september": "Syyskuu", + + "more": "Lisää", + "name": "Nimi", + "next": "Seuraava", + "no": "no", + "off": "off", + "on": "on", + "open": "Avaa", + "open.newWindow": "Open in new window", + "options": "Asetukset", + "options.none": "No options", + + "orientation": "Suunta", + "orientation.landscape": "Vaakasuuntainen", + "orientation.portrait": "Pystysuuntainen", + "orientation.square": "Neliskulmainen", + + "page.blueprint": "This page has no blueprint yet. You can define the setup in /site/blueprints/{template}.yml", + "page.changeSlug": "Vaihda URL-osoite", + "page.changeSlug.fromTitle": "Luo nimen perusteella", + "page.changeStatus": "Muuta tilaa", + "page.changeStatus.position": "Valitse järjestyspaikka", + "page.changeStatus.select": "Valitse uusi tila", + "page.changeTemplate": "Vaihda sivupohja", + "page.delete.confirm": "Haluatko varmasti poistaa sivun {title}?", + "page.delete.confirm.subpages": "Tällä sivulla on alasivuja.
Myös kaikki alasivut poistetaan.", + "page.delete.confirm.title": "Anna vahvistuksena sivun nimi", + "page.draft.create": "Uusi luonnos", + "page.duplicate.appendix": "Kopioi", + "page.duplicate.files": "Copy files", + "page.duplicate.pages": "Copy pages", + "page.sort": "Change position", + "page.status": "Tila", + "page.status.draft": "Luonnos", + "page.status.draft.description": "The page is in draft mode and only visible for logged in editors or via secret link", + "page.status.listed": "Julkinen", + "page.status.listed.description": "Sivu on julkinen kaikille", + "page.status.unlisted": "Listaamaton", + "page.status.unlisted.description": "Sivulle pääsee vain URL:n kautta", + + "pages": "Sivut", + "pages.empty": "Sivuja ei ole vielä lisätty", + "pages.status.draft": "Luonnokset", + "pages.status.listed": "Julkaistut", + "pages.status.unlisted": "Listaamaton", + + "pagination.page": "Sivu", + + "password": "Salasana", + "pixel": "Pikseli", + "prev": "Edellinen", + "preview": "Preview", + "remove": "Poista", + "rename": "Nimeä uudelleen", + "replace": "Korvaa", + "retry": "Yrit\u00e4 uudelleen", + "revert": "Palauta", + "revert.confirm": "Do you really want to delete all unsaved changes?", + + "role": "K\u00e4ytt\u00e4j\u00e4taso", + "role.admin.description": "Pääkäyttäjällä on kaikki oikeudet", + "role.admin.title": "Pääkäyttäjä", + "role.all": "Kaikki", + "role.empty": "Tällä käyttäjätasolla ei ole yhtään käyttäjää", + "role.description.placeholder": "Ei kuvausta", + "role.nobody.description": "This is a fallback role without any permissions", + "role.nobody.title": "Nobody", + + "save": "Tallenna", + "search": "Haku", + "search.min": "Enter {min} characters to search", + "search.all": "Show all", + "search.results.none": "No results", + + "section.required": "Osio on pakollinen", + + "select": "Valitse", + "settings": "Asetukset", + "show": "Show", + "size": "Koko", + "slug": "URL-tunniste", + "sort": "Järjestele", + "title": "Nimi", + "template": "Sivupohja", + "today": "Tänään", + + "site.blueprint": "The site has no blueprint yet. You can define the setup in /site/blueprints/site.yml", + + "toolbar.button.code": "Koodi", + "toolbar.button.bold": "Lihavointi", + "toolbar.button.email": "S\u00e4hk\u00f6posti", + "toolbar.button.headings": "Otsikot", + "toolbar.button.heading.1": "Otsikko 1", + "toolbar.button.heading.2": "Otsikko 2", + "toolbar.button.heading.3": "Otsikko 3", + "toolbar.button.italic": "Kursivointi", + "toolbar.button.file": "Tiedosto", + "toolbar.button.file.select": "Valitse tiedosto", + "toolbar.button.file.upload": "Lähetä tiedosto", + "toolbar.button.link": "Linkki", + "toolbar.button.strike": "Strike-through", + "toolbar.button.ol": "Järjestetty lista", + "toolbar.button.underline": "Underline", + "toolbar.button.ul": "Järjestämätön lista", + + "translation.author": "Kirby-tiimi", + "translation.direction": "ltr", + "translation.name": "Suomi", + "translation.locale": "fi_FI", + + "upload": "Lähetä", + "upload.error.cantMove": "Lähetettyä tiedostoa ei voitu siirtää", + "upload.error.cantWrite": "Tiedoston kirjoitus levylle epäonnistui", + "upload.error.default": "Tiedostoa ei voitu lähettää", + "upload.error.extension": "File upload stopped by extension", + "upload.error.formSize": "Lähetetyn tiedoston koko ylittää lomakkeen sallitun ylärajan MAX_FILE_SIZE", + "upload.error.iniPostSize": "Lähetetyn tiedoston koko ylittää sallitun ylärajan post_max_size asetustiedostossa php.ini", + "upload.error.iniSize": "Lähetetyn tiedoston koko ylittää sallitun ylärajan upload_max_filesize asetustiedostossa php.ini", + "upload.error.noFile": "Tiedostoa ei lähetetty", + "upload.error.noFiles": "Tiedostoja ei lähetetty", + "upload.error.partial": "The uploaded file was only partially uploaded", + "upload.error.tmpDir": "Missing a temporary folder", + "upload.errors": "Virhe", + "upload.progress": "Lähetetään...", + + "url": "Url", + "url.placeholder": "https://esimerkki.fi", + + "user": "Käyttäjä", + "user.blueprint": "Voit määrittää lisää osioita ja lomakekenttiä tälle käyttäjälle kaavassa /site/blueprints/users/{role}.yml", + "user.changeEmail": "Muuta sähköpostiosoite", + "user.changeLanguage": "Vaihda kieli", + "user.changeName": "Nimeä uudelleen", + "user.changePassword": "Vaihda salasana", + "user.changePassword.new": "Uusi salasana", + "user.changePassword.new.confirm": "Vahvista uusi salasana...", + "user.changeRole": "Muuta käyttäjätasoa", + "user.changeRole.select": "Valitse uusi käyttäjätaso", + "user.create": "Lisää uusi käyttäjä", + "user.delete": "Poista tämä käyttäjä", + "user.delete.confirm": "Haluatko varmsti poistaa käyttäjän
{email}?", + + "users": "Käyttäjät", + + "version": "Versio", + + "view.account": "Oma käyttäjätili", + "view.installation": "Asennus", + "view.resetPassword": "Reset password", + "view.settings": "Asetukset", + "view.site": "Sivusto", + "view.users": "K\u00e4ytt\u00e4j\u00e4t", + + "welcome": "Tervetuloa", + "year": "Vuosi", + "yes": "yes" +} diff --git a/kirby/i18n/translations/fr.json b/kirby/i18n/translations/fr.json new file mode 100644 index 0000000..3297e81 --- /dev/null +++ b/kirby/i18n/translations/fr.json @@ -0,0 +1,527 @@ +{ + "add": "Ajouter", + "avatar": "Image du profil", + "back": "Retour", + "cancel": "Annuler", + "change": "Changer", + "close": "Fermer", + "confirm": "Ok", + "collapse": "Replier", + "collapse.all": "Tout replier", + "copy": "Copier", + "create": "Créer", + + "date": "Date", + "date.select": "Choisissez une date", + + "day": "Jour", + "days.fri": "Ven", + "days.mon": "Lun", + "days.sat": "Sam", + "days.sun": "Dim", + "days.thu": "Jeu", + "days.tue": "Mar", + "days.wed": "Mer", + + "delete": "Supprimer", + "delete.all": "Tout supprimer", + "dimensions": "Dimensions", + "disabled": "Désactivé", + "discard": "Supprimer", + "download": "Télécharger", + "duplicate": "Dupliquer", + "edit": "Éditer", + "expand": "Déplier", + "expand.all": "Tout déplier", + + "dialog.files.empty": "Aucun fichier à sélectionner", + "dialog.pages.empty": "Aucune page à sélectionner", + "dialog.users.empty": "Aucun utilisateur à sélectionner", + + "email": "Courriel", + "email.placeholder": "mail@example.com", + + "error.access.code": "Code incorrect", + "error.access.login": "Identifiant incorrect", + "error.access.panel": "Vous n’êtes pas autorisé à accéder au Panel", + "error.access.view": "Vous n’êtes pas autorisé à accéder à cette section du Panel", + + "error.avatar.create.fail": "L’image du profil n’a pu être transférée", + "error.avatar.delete.fail": "L’image du profil n’a pu être supprimée", + "error.avatar.dimensions.invalid": "Veuillez choisir une image de profil de largeur et hauteur inférieures à 3000 pixels", + "error.avatar.mime.forbidden": "L'image du profil utilisateur doit être un fichier JPEG ou PNG", + + "error.blueprint.notFound": "Le blueprint « {name} » n’a pu être chargé", + + "error.blocks.max.plural": "Vous ne devez pas ajouter plus de {max} blocs", + "error.blocks.max.singular": "Vous ne devez pas ajouter plus d'un bloc", + "error.blocks.min.plural": "Vous devez ajouter au moins {min} blocs", + "error.blocks.min.singular": "Vous devez ajouter au moins un bloc", + "error.blocks.validation": "Il y a une erreur dans le bloc {index}", + + "error.email.preset.notFound": "La configuration de courriel « {name} » n’a pu être trouvé", + + "error.field.converter.invalid": "Convertisseur « {converter} » incorrect", + + "error.file.changeName.empty": "Le nom ne peut être vide", + "error.file.changeName.permission": "Vous n’êtes pas autorisé à modifier le nom de « {filename} »", + "error.file.duplicate": "Un fichier nommé « {filename} » existe déjà", + "error.file.extension.forbidden": "L’extension « {extension} » n’est pas autorisée", + "error.file.extension.invalid": "Extension non valide : {extension}", + "error.file.extension.missing": "L’extension pour « {filename} » est manquante", + "error.file.maxheight": "La hauteur de l'image ne doit pas excéder {height} pixels", + "error.file.maxsize": "Le fichier est trop volumineux", + "error.file.maxwidth": "La largeur de l'image ne doit pas excéder {width} pixels", + "error.file.mime.differs": "Le fichier transféré doit être du même type de média « {mime} »", + "error.file.mime.forbidden": "Le type de média « {mime} » n’est pas autorisé", + "error.file.mime.invalid": "Type de média non valide : {mime}", + "error.file.mime.missing": "Le type de média de « {filename} » n’a pu être détecté", + "error.file.minheight": "La hauteur de l'image doit être au moins {height} pixels", + "error.file.minsize": "Le fichier n'est pas assez volumineux", + "error.file.minwidth": "La largeur de l'image doit être au moins {width} pixels", + "error.file.name.missing": "Veuillez entrer un titre", + "error.file.notFound": "Le fichier « {filename} » n’a pu être trouvé", + "error.file.orientation": "L'orientation de l'image doit être \"{orientation}\"", + "error.file.type.forbidden": "Vous n’êtes pas autorisé à transférer des fichiers {type}", + "error.file.type.invalid": "Type de fichier non valide : {type}", + "error.file.undefined": "Le fichier n’a pu être trouvé", + + "error.form.incomplete": "Veuillez corriger toutes les erreurs du formulaire…", + "error.form.notSaved": "Le formulaire n’a pu être enregistré", + + "error.language.code": "Veuillez saisir un code valide pour cette langue", + "error.language.duplicate": "Cette langue existe déjà", + "error.language.name": "Veuillez saisir un nom valide pour cette langue", + + "error.layout.validation.block": "Il y a une erreur dans le block {blockIndex} de la disposition {layoutIndex}", + "error.layout.validation.settings": "Il y a une erreur dans les paramètres de la disposition {index}", + + "error.license.format": "Veuillez saisir un numéro de licence valide", + "error.license.email": "Veuillez saisir un courriel valide", + "error.license.verification": "La licence n’a pu être vérifiée", + + "error.page.changeSlug.permission": "Vous n’êtes pas autorisé à modifier l’identifiant d’URL pour « {slug} »", + "error.page.changeStatus.incomplete": "La page comporte des erreurs et ne peut pas être publiée", + "error.page.changeStatus.permission": "Le statut de cette page ne peut être modifié", + "error.page.changeStatus.toDraft.invalid": "La page « {slug} » ne peut être convertie en brouillon", + "error.page.changeTemplate.invalid": "Le modèle de la page « {slug} » ne peut être changé", + "error.page.changeTemplate.permission": "Vous n’êtes pas autorisé à changer le modèle de « {slug} »", + "error.page.changeTitle.empty": "Le titre ne peut être vide", + "error.page.changeTitle.permission": "Vous n’êtes pas autorisé à modifier le titre de « {slug} »", + "error.page.create.permission": "Vous n’êtes pas autorisé à créer « {slug} »", + "error.page.delete": "La page « {slug} » ne peut être supprimée", + "error.page.delete.confirm": "Veuillez saisir le titre de la page pour confirmer", + "error.page.delete.hasChildren": "La page comporte des sous-pages et ne peut pas être supprimée", + "error.page.delete.permission": "Vous n’êtes pas autorisé à supprimer « {slug} »", + "error.page.draft.duplicate": "Un brouillon avec l’identifiant d’URL « {slug} » existe déjà", + "error.page.duplicate": "Une page avec l’identifiant d’URL « {slug} » existe déjà", + "error.page.duplicate.permission": "Vous n'êtes pas autorisé à dupliquer « {slug} »", + "error.page.notFound": "La page « {slug} » n’a pu être trouvée", + "error.page.num.invalid": "Veuillez saisir un numéro de position valide. Les numéros ne doivent pas être négatifs.", + "error.page.slug.invalid": "Veuillez entrer un identifiant d’URL valide", + "error.page.slug.maxlength": "L’identifiant d’URL doit faire moins de \"{length}\" caractères", + "error.page.sort.permission": "La page « {slug} » ne peut être réordonnée", + "error.page.status.invalid": "Veuillez choisir un statut de page valide", + "error.page.undefined": "La page n’a pu être trouvée", + "error.page.update.permission": "Vous n’êtes pas autorisé à modifier « {slug} »", + + "error.section.files.max.plural": "Vous ne pouvez ajouter plus de {max} fichier(s) à la section « {section} »", + "error.section.files.max.singular": "Vous ne pouvez ajouter plus d’un fichier à la section « {section} »", + "error.section.files.min.plural": "La section « {section}\" » requiert au moins {min} fichiers", + "error.section.files.min.singular": "La section « {section}\" » requiert au moins un fichier", + + "error.section.pages.max.plural": "Vous ne pouvez ajouter plus de {max} pages à la section « {section} »", + "error.section.pages.max.singular": "Vous ne pouvez ajouter plus d’une page à la section « {section} »", + "error.section.pages.min.plural": "La section « {section}\" » requiert au moins {min} pages", + "error.section.pages.min.singular": "La section « {section}\" » requiert au moins une page", + + "error.section.notLoaded": "La section « {name} » n’a pu être chargée", + "error.section.type.invalid": "Le type de section « {type} » est incorrect", + + "error.site.changeTitle.empty": "Le titre ne peut être vide", + "error.site.changeTitle.permission": "Vous n’êtes pas autorisé à modifier le titre du site", + "error.site.update.permission": "Vous n’êtes pas autorisé à modifier le contenu global du site", + + "error.template.default.notFound": "Le modèle par défaut n’existe pas", + + "error.user.changeEmail.permission": "Vous n’êtes pas autorisé à modifier le courriel de l’utilisateur « {name} »", + "error.user.changeLanguage.permission": "Vous n’êtes pas autorisé à changer la langue de l’utilisateur « {name} »", + "error.user.changeName.permission": "Vous n’êtes pas autorisé à modifier le nom de l’utilisateur « {name} »", + "error.user.changePassword.permission": "Vous n’êtes pas autorisé à changer le mot de passe de l’utilisateur « {name} »", + "error.user.changeRole.lastAdmin": "Le rôle du dernier administrateur ne peut être modifié", + "error.user.changeRole.permission": "Vous n’êtes pas autorisé à changer le rôle de l’utilisateur « {name} »", + "error.user.changeRole.toAdmin": "Vous n’êtes pas autorisé à attribuer le rôle d’administrateur aux utilisateurs", + "error.user.create.permission": "Vous n’êtes pas autorisé à créer cet utilisateur", + "error.user.delete": "L’utilisateur « {name} » ne peut être supprimé", + "error.user.delete.lastAdmin": "Le dernier administrateur ne peut être supprimé", + "error.user.delete.lastUser": "Le dernier utilisateur ne peut être supprimé", + "error.user.delete.permission": "Vous n’êtes pas autorisé à supprimer l’utilisateur « {name} »", + "error.user.duplicate": "Un utilisateur avec le courriel « {email} » existe déjà", + "error.user.email.invalid": "Veuillez saisir un courriel valide", + "error.user.language.invalid": "Veuillez saisir une langue valide", + "error.user.notFound": "L’utilisateur « {name} » n’a pu être trouvé", + "error.user.password.invalid": "Veuillez saisir un mot de passe valide. Les mots de passe doivent comporter au moins 8 caractères.", + "error.user.password.notSame": "Les mots de passe ne sont pas identiques", + "error.user.password.undefined": "Cet utilisateur n’a pas de mot de passe", + "error.user.password.wrong": "Mot de passe incorrect", + "error.user.role.invalid": "Veuillez saisir un rôle valide", + "error.user.update.permission": "Vous n’êtes pas autorisé à modifier l’utilisateur « {name} »", + + "error.validation.accepted": "Veuillez confirmer", + "error.validation.alpha": "Veuillez saisir uniquement des caractères alphabétiques minuscules", + "error.validation.alphanum": "Veuillez ne saisir que des minuscules de a à z et des chiffres de 0 à 9", + "error.validation.between": "Veuillez saisir une valeur entre « {min} » et « {max} »", + "error.validation.boolean": "Veuillez confirmer ou refuser", + "error.validation.contains": "Veuillez saisir une valeur contenant « {needle} »", + "error.validation.date": "Veuillez saisir une date valide", + "error.validation.date.after": "Veuillez saisir une date après {date}", + "error.validation.date.before": "Veuillez saisir une date avant {date}", + "error.validation.date.between": "Veuillez saisir une date entre {min} et {max}", + "error.validation.denied": "Veuillez refuser", + "error.validation.different": "La valeur ne doit pas être « {other} »", + "error.validation.email": "Veuillez saisir un courriel valide", + "error.validation.endswith": "La valeur doit se terminer par « {end} »", + "error.validation.filename": "Veuillez saisir un nom de fichier valide", + "error.validation.in": "Veuillez saisir l’un des éléments suivants: ({in})", + "error.validation.integer": "Veuillez saisir un entier valide", + "error.validation.ip": "Veuillez saisir une adresse IP valide", + "error.validation.less": "Veuillez saisir une valeur inférieure à {max}", + "error.validation.match": "La valeur ne correspond pas au modèle attendu", + "error.validation.max": "Veuillez saisir une valeur inférieure ou égale à {max}", + "error.validation.maxlength": "Veuillez saisir une valeur plus courte (max. {max} caractères)", + "error.validation.maxwords": "Veuillez ne pas saisir plus de {max} mot(s)", + "error.validation.min": "Veuillez saisir une valeur supérieure ou égale à {min}", + "error.validation.minlength": "Veuillez saisir une valeur plus longue (min. {min} caractères)", + "error.validation.minwords": "Veuillez saisir au moins {min} mot(s)", + "error.validation.more": "Veuillez saisir une valeur supérieure à {min}", + "error.validation.notcontains": "Veuillez saisir une valeur ne contenant pas « {needle} »", + "error.validation.notin": "Veuillez ne saisir aucun des éléments suivants: ({notIn})", + "error.validation.option": "Veuillez sélectionner une option valide", + "error.validation.num": "Veuillez saisir un nombre valide", + "error.validation.required": "Veuillez saisir quelque chose", + "error.validation.same": "Veuillez saisir « {other} »", + "error.validation.size": "La grandeur de la valeur doit être « {size} »", + "error.validation.startswith": "La valeur doit commencer par « {start} »", + "error.validation.time": "Veuillez saisir une heure valide", + "error.validation.time.after": "Veuillez entrer une heure après {time}", + "error.validation.time.before": "Veuillez entrer une heure avant {time}", + "error.validation.time.between": "Veuillez entrer une heure entre {min} et {max}", + "error.validation.url": "Veuillez saisir une URL valide", + + "field.required": "Le champ est obligatoire", + "field.blocks.changeType": "Changer le type", + "field.blocks.code.name": "Code", + "field.blocks.code.language": "Langue", + "field.blocks.code.placeholder": "Votre code…", + "field.blocks.delete.confirm": "Voulez-vous vraiment supprimer ce bloc ?", + "field.blocks.delete.confirm.all": "Voulez-vous vraiment supprimer tous les blocs ?", + "field.blocks.delete.confirm.selected": "Voulez-vous vraiment supprimer les blocs sélectionnés ?", + "field.blocks.empty": "Pas encore de blocs", + "field.blocks.fieldsets.label": "Veuillez sélectionner un type de bloc…", + "field.blocks.gallery.name": "Galerie", + "field.blocks.gallery.images.empty": "Pas encore d’images", + "field.blocks.gallery.images.label": "Images", + "field.blocks.heading.level": "Niveau", + "field.blocks.heading.name": "Titre", + "field.blocks.heading.text": "Texte", + "field.blocks.heading.placeholder": "Titre…", + "field.blocks.image.alt": "Texte alternatif", + "field.blocks.image.caption": "Légende", + "field.blocks.image.crop": "Recadrer", + "field.blocks.image.link": "Lien", + "field.blocks.image.location": "Emplacement", + "field.blocks.image.name": "Image", + "field.blocks.image.placeholder": "Sélectionnez une image", + "field.blocks.image.ratio": "Proportions", + "field.blocks.image.url": "URL de l'image", + "field.blocks.list.name": "Liste", + "field.blocks.markdown.name": "Markdown", + "field.blocks.markdown.label": "Texte", + "field.blocks.markdown.placeholder": "Markdown…", + "field.blocks.quote.name": "Citation", + "field.blocks.quote.text.label": "Texte", + "field.blocks.quote.text.placeholder": "Citation…", + "field.blocks.quote.citation.label": "Citation", + "field.blocks.quote.citation.placeholder": "par…", + "field.blocks.text.name": "Texte", + "field.blocks.text.placeholder": "Texte…", + "field.blocks.video.caption": "Légende", + "field.blocks.video.name": "Vidéo", + "field.blocks.video.placeholder": "Entrez l’URL d’une vidéo", + "field.blocks.video.url.label": "URL de la vidéo", + "field.blocks.video.url.placeholder": "https://youtube.com/?v=", + + "field.files.empty": "Pas encore de fichier sélectionné", + + "field.layout.delete": "Supprimer cette disposition", + "field.layout.delete.confirm": "Voulez-vous vraiment supprimer cette disposition ?", + "field.layout.empty": "Pas encore de rangées", + "field.layout.select": "Choisir une disposition", + + "field.pages.empty": "Pas encore de page sélectionnée", + "field.structure.delete.confirm": "Voulez-vous vraiment supprimer cette ligne?", + "field.structure.empty": "Pas encore d’entrée", + "field.users.empty": "Pas encore d’utilisateur sélectionné", + + "file.blueprint": "Ce fichier n’a pas encore de blueprint. Vous pouvez en définir les paramètres dans /site/blueprints/{template}.yml", + "file.delete.confirm": "Voulez-vous vraiment supprimer
{filename} ?", + "file.sort": "Modifier la position", + + "files": "Fichiers", + "files.empty": "Pas encore de fichier", + + "hide": "Masquer", + "hour": "Heure", + "insert": "Insérer", + "insert.after": "Insérer après", + "insert.before": "Insérer avant", + "install": "Installer", + + "installation": "Installation", + "installation.completed": "Le Panel a été installé", + "installation.disabled": "L'installation du Panel est désactivée par défaut sur les serveurs publics. Veuillez lancer l'installation sur un serveur local, ou activez-la avec l'option panel.install.", + "installation.issues.accounts": "Le dossier /site/accounts n’existe pas ou n’est pas accessible en écriture", + "installation.issues.content": "Le dossier /content n’existe pas ou n’est pas accessible en écriture", + "installation.issues.curl": "L’extension CURL est requise", + "installation.issues.headline": "Le Panel ne peut être installé", + "installation.issues.mbstring": "L’extension MB String est requise", + "installation.issues.media": "Le dossier /media n’existe pas ou n’est pas accessible en écriture", + "installation.issues.php": "Veuillez utiliser PHP 7+", + "installation.issues.server": "Kirby requiert Apache, Nginx ou Caddy", + "installation.issues.sessions": "Le dossier /site/sessions n’existe pas ou n’est pas accessible en écriture", + + "language": "Langue", + "language.code": "Code", + "language.convert": "Choisir comme langue par défaut", + "language.convert.confirm": "

Souhaitez-vous vraiment convertir {name} vers la langue par défaut ? Cette action ne peut pas être annulée.

Si {name} a un contenu non traduit, il n’y aura plus de solution de secours possible et certaines parties de votre site pourraient être vides.

", + "language.create": "Ajouter une nouvelle langue", + "language.delete.confirm": "Voulez-vous vraiment supprimer la langue {name}, ainsi que toutes ses traductions ? Cette action ne peut être annulée !", + "language.deleted": "La langue a été supprimée", + "language.direction": "Sens de lecture", + "language.direction.ltr": "De gauche à droite", + "language.direction.rtl": "De droite à gauche", + "language.locale": "Locales PHP", + "language.locale.warning": "Vous utilisez une Locale PHP personnalisée. Veuillez la modifier dans le fichier de langue situé dans /site/languages", + "language.name": "Nom", + "language.updated": "La langue a été mise à jour", + + "languages": "Langages", + "languages.default": "Langue par défaut", + "languages.empty": "Il n’y a pas encore de langues", + "languages.secondary": "Langues secondaires", + "languages.secondary.empty": "Il n’y a pas encore de langues secondaires", + + "license": "Licence", + "license.buy": "Acheter une licence", + "license.register": "S’enregistrer", + "license.register.help": "Vous avez reçu votre numéro de licence par courriel après l'achat. Veuillez le copier et le coller ici pour l'enregistrer.", + "license.register.label": "Veuillez saisir votre numéro de licence", + "license.register.success": "Merci pour votre soutien à Kirby", + "license.unregistered": "Ceci est une démo non enregistrée de Kirby", + + "link": "Lien", + "link.text": "Texte du lien", + + "loading": "Chargement", + + "lock.unsaved": "Modifications non enregistrées", + "lock.unsaved.empty": "Il n’y a plus de modifications non enregistrées", + "lock.isLocked": "Modifications non enregistrées par {email}", + "lock.file.isLocked": "Le fichier est actuellement édité par {email} et ne peut être modifié.", + "lock.page.isLocked": "La page est actuellement éditée par {email} et ne peut être modifiée.", + "lock.unlock": "Déverrouiller", + "lock.isUnlocked": "Vos modifications non enregistrées ont été écrasées pas un autre utilisateur. Vous pouvez télécharger vos modifications pour les fusionner manuellement.", + + "login": "Se connecter", + "login.code.label.login": "Code de connexion", + "login.code.label.password-reset": "Code de réinitialisation du mot de passe", + "login.code.placeholder.email": "000 000", + "login.code.text.email": "Si votre adresse de courriel est enregistrée, le code demandé vous sera envoyé par courriel.", + "login.email.login.body": "Bonjour {user.nameOrEmail},\n\nVous avez demandé récemment un code de connexion pour le Panel de Kirby.\nLe code suivant sera valide pendant {timeout} minutes:\n\n{code}\n\nSi vous n’avez pas demandé de code de connexion, veuillez ignorer cet email ou contacter l’administrateur si vous avez des questions.\nPar sécurité, ne faites PAS suivre cet email.", + "login.email.login.subject": "Votre code de connexion", + "login.email.password-reset.body": "Bonjour {user.nameOrEmail},\n\nVous avez demandé récemment un code de connexion pour le Panel de Kirby.\nLe code suivant sera valide pendant {timeout} minutes:\n\n{code}\n\nSi vous n’avez pas demandé de code de connexion, veuillez ignorer ce courriel ou contacter l’administrateur si vous avez des questions.\nPar sécurité, ne faites PAS suivre ce courriel.", + "login.email.password-reset.subject": "Votre code de réinitialisation du mot de passe", + "login.remember": "Rester connecté", + "login.reset": "Réinitialiser le mot de passe", + "login.toggleText.code.email": "Se connecter par courriel", + "login.toggleText.code.email-password": "Se connecter avec un mot de passe", + "login.toggleText.password-reset.email": "Mot de passe oublié ?", + "login.toggleText.password-reset.email-password": "← Retour à la connexion", + + "logout": "Se déconnecter", + + "menu": "Menu", + "meridiem": "AM/PM", + "mime": "Type de médias", + "minutes": "Minutes", + + "month": "Mois", + "months.april": "Avril", + "months.august": "Août", + "months.december": "Décembre", + "months.february": "Février", + "months.january": "Janvier", + "months.july": "Juillet", + "months.june": "Juin", + "months.march": "Mars", + "months.may": "Mai", + "months.november": "Novembre", + "months.october": "Octobre", + "months.september": "Septembre", + + "more": "Plus", + "name": "Nom", + "next": "Suivant", + "no": "non", + "off": "off", + "on": "on", + "open": "Ouvrir", + "open.newWindow": "Ouvrir dans une nouvelle fenêtre", + "options": "Options", + "options.none": "Pas d’options", + + "orientation": "Orientation", + "orientation.landscape": "Paysage", + "orientation.portrait": "Portrait", + "orientation.square": "Carré", + + "page.blueprint": "Cette page n’a pas encore de blueprint. Vous pouvez en définir les paramètres dans /site/blueprints/{template}.yml", + "page.changeSlug": "Modifier l’URL", + "page.changeSlug.fromTitle": "Créer à partir du titre", + "page.changeStatus": "Changer le statut", + "page.changeStatus.position": "Veuillez sélectionner une position", + "page.changeStatus.select": "Sélectionner un nouveau statut", + "page.changeTemplate": "Changer de modèle", + "page.delete.confirm": "Voulez-vous vraiment supprimer {title} ?", + "page.delete.confirm.subpages": "Cette page contient des sous-pages.
Toutes les sous-pages seront également supprimées.", + "page.delete.confirm.title": "Veuillez saisir le titre de la page pour confirmer", + "page.draft.create": "Créer un brouillon", + "page.duplicate.appendix": "Copier", + "page.duplicate.files": "Copier les fichiers", + "page.duplicate.pages": "Copier les pages", + "page.sort": "Modifier la position", + "page.status": "Statut", + "page.status.draft": "Brouillon", + "page.status.draft.description": "Cette page est un brouillon et n’est visible que pour les éditeurs connectés ou par un lien secret", + "page.status.listed": "Public", + "page.status.listed.description": "La page est publique pour tout le monde", + "page.status.unlisted": "Non listé", + "page.status.unlisted.description": "La page est uniquement accessible par son URL", + + "pages": "Pages", + "pages.empty": "Pas encore de pages", + "pages.status.draft": "Brouillons", + "pages.status.listed": "Publié", + "pages.status.unlisted": "Non listé", + + "pagination.page": "Page", + + "password": "Mot de passe", + "pixel": "Pixel", + "prev": "Précédent", + "preview": "Prévisualiser", + "remove": "Supprimer", + "rename": "Renommer", + "replace": "Remplacer", + "retry": "Essayer à nouveau", + "revert": "Revenir", + "revert.confirm": "Voulez-vous vraiment supprimer toutes les modifications non-enregistrées ?", + + "role": "Rôle", + "role.admin.description": "L’administrateur dispose de tous les droits", + "role.admin.title": "Administrateur", + "role.all": "Tous", + "role.empty": "Il n’y a aucun utilisateur avec ce rôle", + "role.description.placeholder": "Pas de description", + "role.nobody.description": "Ceci est un rôle de secours sans aucune permission.", + "role.nobody.title": "Personne", + + "save": "Enregistrer", + "search": "Rechercher", + "search.min": "Entrez {min} caractères pour rechercher", + "search.all": "Tout afficher", + "search.results.none": "Pas de résultats", + + "section.required": "Cette section est obligatoire", + + "select": "Sélectionner", + "settings": "Paramètres", + "show": "Afficher", + "size": "Poids", + "slug": "Identifiant de l’URL", + "sort": "Trier", + "title": "Titre", + "template": "Modèle", + "today": "Aujourd’hui", + + "site.blueprint": "Ce site n’a pas encore de blueprint. Vous pouvez en définir les paramètres dans /site/blueprints/site.yml", + + "toolbar.button.code": "Code", + "toolbar.button.bold": "Gras", + "toolbar.button.email": "Courriel", + "toolbar.button.headings": "Titres", + "toolbar.button.heading.1": "Titre 1", + "toolbar.button.heading.2": "Titre 2", + "toolbar.button.heading.3": "Titre 3", + "toolbar.button.italic": "Italique", + "toolbar.button.file": "Fichier", + "toolbar.button.file.select": "Sélectionner un fichier", + "toolbar.button.file.upload": "Transférer un fichier", + "toolbar.button.link": "Lien", + "toolbar.button.strike": "Barré", + "toolbar.button.ol": "Liste ordonnée", + "toolbar.button.underline": "Souligné", + "toolbar.button.ul": "Liste non-ordonnée", + + "translation.author": "Kirby Team", + "translation.direction": "ltr", + "translation.name": "Français", + "translation.locale": "fr_FR", + + "upload": "Transférer", + "upload.error.cantMove": "Le fichier transféré n’a pu être déplacé", + "upload.error.cantWrite": "Le fichier n’a pu être écrit sur le disque", + "upload.error.default": "Le fichier n’a pu être transféré", + "upload.error.extension": "Le transfert de fichier a été stoppé par une extension", + "upload.error.formSize": "Le fichier transféré excède la directive MAX_FILE_SIZE spécifiée dans le formulaire", + "upload.error.iniPostSize": "Le fichier transféré excède la directive post_max_size spécifiée dans php.ini", + "upload.error.iniSize": "Le fichier transféré excède la directive upload_max_filesize spécifiée dans php.ini", + "upload.error.noFile": "Aucun fichier n’a été transféré", + "upload.error.noFiles": "Aucun fichier n’a été transféré", + "upload.error.partial": "Le fichier n’a été que partiellement transféré", + "upload.error.tmpDir": "Un dossier temporaire est manquant", + "upload.errors": "Erreur", + "upload.progress": "Transfert en cours…", + + "url": "Url", + "url.placeholder": "https://example.com", + + "user": "Utilisateur", + "user.blueprint": "Vous pouvez définir des sections et des champs de formulaire supplémentaires pour ce rôle d’utilisateur dans /site/blueprints/users/{role}.yml", + "user.changeEmail": "Modifier le courriel", + "user.changeLanguage": "Modifier la langue", + "user.changeName": "Renommer cet utilisateur", + "user.changePassword": "Modifier le mot de passe", + "user.changePassword.new": "Nouveau mot de passe", + "user.changePassword.new.confirm": "Confirmer le nouveau mot de passe…", + "user.changeRole": "Modifier le rôle", + "user.changeRole.select": "Sélectionner un nouveau rôle", + "user.create": "Ajouter un nouvel utilisateur", + "user.delete": "Supprimer cet utilisateur", + "user.delete.confirm": "Voulez-vous vraiment supprimer
{email}?", + + "users": "Utilisateurs", + + "version": "Version", + + "view.account": "Votre compte", + "view.installation": "Installation", + "view.resetPassword": "Réinitialiser le mot de passe", + "view.settings": "Paramètres", + "view.site": "Site", + "view.users": "Utilisateurs", + + "welcome": "Bienvenue", + "year": "Année", + "yes": "oui" +} diff --git a/kirby/i18n/translations/hu.json b/kirby/i18n/translations/hu.json new file mode 100644 index 0000000..47cb39e --- /dev/null +++ b/kirby/i18n/translations/hu.json @@ -0,0 +1,527 @@ +{ + "add": "Hozz\u00e1ad", + "avatar": "Profilkép", + "back": "Vissza", + "cancel": "M\u00e9gsem", + "change": "M\u00f3dos\u00edt\u00e1s", + "close": "Bez\u00e1r", + "confirm": "Mentés", + "collapse": "Collapse", + "collapse.all": "Collapse All", + "copy": "Másol", + "create": "Létrehoz", + + "date": "Dátum", + "date.select": "Dátum kiválasztása", + + "day": "Nap", + "days.fri": "p\u00e9", + "days.mon": "h\u00e9", + "days.sat": "szo", + "days.sun": "va", + "days.thu": "cs\u00fc", + "days.tue": "ke", + "days.wed": "sze", + + "delete": "T\u00f6rl\u00e9s", + "delete.all": "Delete all", + "dimensions": "Méretek", + "disabled": "Disabled", + "discard": "Visszavon\u00e1s", + "download": "Letöltés", + "duplicate": "Másolat", + "edit": "Aloldal szerkeszt\u00e9se", + "expand": "Expand", + "expand.all": "Expand All", + + "dialog.files.empty": "Nincsenek fájlok kiválasztva", + "dialog.pages.empty": "Nincsenek oldalak kiválasztva", + "dialog.users.empty": "Nincsenek felhasználók kiválasztva", + + "email": "Email", + "email.placeholder": "mail@pelda.hu", + + "error.access.code": "Invalid code", + "error.access.login": "Érvénytelen bejelentkezés", + "error.access.panel": "Nincs jogosultságod megnyitni a panelt", + "error.access.view": "You are not allowed to access this part of the panel", + + "error.avatar.create.fail": "A profilkép feltöltése nem sikerült", + "error.avatar.delete.fail": "A profilkép nem törölhető", + "error.avatar.dimensions.invalid": "A profilkép maximális szélessége és magassága 3000 pixel lehet", + "error.avatar.mime.forbidden": "A profilkép formátuma csak JPEG vagy PNG lehet", + + "error.blueprint.notFound": "A \"{name}\" oldalsablon nem tölthető be", + + "error.blocks.max.plural": "You must not add more than {max} blocks", + "error.blocks.max.singular": "You must not add more than one block", + "error.blocks.min.plural": "You must add at least {min} blocks", + "error.blocks.min.singular": "You must add at least one block", + "error.blocks.validation": "There's an error in block {index}", + + "error.email.preset.notFound": "A \"{name}\" email-beállítás nem található", + + "error.field.converter.invalid": "Érvénytelen konverter: \"{converter}\"", + + "error.file.changeName.empty": "The name must not be empty", + "error.file.changeName.permission": "Nincs jogosultságod megváltoztatni a \"{filename}\" fájl nevét", + "error.file.duplicate": "Már létezik \"{filename}\" nevű fájl", + "error.file.extension.forbidden": "Tiltott kiterjeszt\u00e9s\u0171 f\u00e1jl", + "error.file.extension.invalid": "Invalid extension: {extension}", + "error.file.extension.missing": "Kiterjeszt\u00e9s n\u00e9lk\u00fcli f\u00e1jl nem t\u00f6lthet\u0151 fel", + "error.file.maxheight": "The height of the image must not exceed {height} pixels", + "error.file.maxsize": "The file is too large", + "error.file.maxwidth": "The width of the image must not exceed {width} pixels", + "error.file.mime.differs": "A feltöltött fájlnak azonos \"{mime}\" típusúnak kell lennie", + "error.file.mime.forbidden": "A \"{mime}\" típusú médiafájlok nem engedélyezettek", + "error.file.mime.invalid": "Invalid mime type: {mime}", + "error.file.mime.missing": "A \"{filename}\" fájl típusa nem állapítható meg", + "error.file.minheight": "The height of the image must be at least {height} pixels", + "error.file.minsize": "The file is too small", + "error.file.minwidth": "The width of the image must be at least {width} pixels", + "error.file.name.missing": "A fálj neve nem lehet üres", + "error.file.notFound": "A \"{filename}\" fájl nem található", + "error.file.orientation": "The orientation of the image must be \"{orientation}\"", + "error.file.type.forbidden": "Nem tölthetsz fel \"{type}\" típusú fájlokat", + "error.file.type.invalid": "Invalid file type: {type}", + "error.file.undefined": "A f\u00e1jl nem tal\u00e1lhat\u00f3", + + "error.form.incomplete": "Kérlek javítsd ki az összes hibát az űrlapon", + "error.form.notSaved": "Az űrlap nem menthető", + + "error.language.code": "Kérlek, add meg a nyelv érvényes kódját", + "error.language.duplicate": "A nyelv már létezik", + "error.language.name": "Kérlek, add meg a nyelv érvényes nevét", + + "error.layout.validation.block": "There's an error in block {blockIndex} in layout {layoutIndex}", + "error.layout.validation.settings": "There's an error in layout {index} settings", + + "error.license.format": "Kérlek, add meg az évényes lincensz kulcsot", + "error.license.email": "Kérlek adj meg egy valós email-címet", + "error.license.verification": "A licensz nem ellenőrizhető", + + "error.page.changeSlug.permission": "Nem változtathatod meg az URL-előtagot: \"{slug}\"", + "error.page.changeStatus.incomplete": "Az oldal hibákat tartalmaz és nem publikálható", + "error.page.changeStatus.permission": "Az oldal státusza nem változtatható meg", + "error.page.changeStatus.toDraft.invalid": "A(z) \"{slug}\" oldalt nem lehet piszkozattá alakítani", + "error.page.changeTemplate.invalid": "A \"{slug}\" oldal sablonját nem lehet megváltoztatni", + "error.page.changeTemplate.permission": "Nincs jogosultságod megváltoztatni a sablont ehhez: \"{slug}\"", + "error.page.changeTitle.empty": "A cím nem lehet üres", + "error.page.changeTitle.permission": "Nincs jogosultságod megváltoztatni a címet: \"{slug}\"", + "error.page.create.permission": "Nincs jogosultságod az oldal létrehozásához: \"{slug}\"", + "error.page.delete": "A(z) \"{slug}\" oldal nem törölhető", + "error.page.delete.confirm": "Megerősítéshez add meg az oldal címét", + "error.page.delete.hasChildren": "Az oldalnak vannak aloldalai és nem törölhető", + "error.page.delete.permission": "Nincs jogosultságod a(z) \"{slug}\" oldal törléséhez", + "error.page.draft.duplicate": "Van már egy másik oldal ezzel az URL-lel: \"{slug}\"", + "error.page.duplicate": "Van már egy másik oldal ezzel az URL-lel: \"{slug}\"", + "error.page.duplicate.permission": "Nincs engedélyed a(z) \"{slug}\" másolat keszítéséhez", + "error.page.notFound": "Az oldal nem tal\u00e1lhat\u00f3", + "error.page.num.invalid": "Kérlek megfelelő oldalszámozást adj meg. Negatív szám itt nem használható.", + "error.page.slug.invalid": "Please enter a valid URL appendix", + "error.page.slug.maxlength": "Slug length must be less than \"{length}\" characters", + "error.page.sort.permission": "A(z) \"{slug}\" oldal nem illeszthető a sorrendbe", + "error.page.status.invalid": "Kérlek add meg a megfelelő oldalstátuszt", + "error.page.undefined": "Az oldal nem tal\u00e1lhat\u00f3", + "error.page.update.permission": "Nincs jogosultságod a(z) \"{slug}\" oldal frissítéséhez", + + "error.section.files.max.plural": "Maximum {max} fájlt adhatsz hozzá a(z) \"{section}\" szekcióhoz", + "error.section.files.max.singular": "Nem adhatsz hozzá egynél több fájlt a(z) \"{section}\" szekcióhoz", + "error.section.files.min.plural": "A \"{section}\" szakasz legalább {min} fájlt igényel", + "error.section.files.min.singular": "A \"{section}\" szakasz legalább egy fájlt igényel", + + "error.section.pages.max.plural": "Maximum {max} oldalt adhatsz hozzá a(z) \"{section}\" szekcióhoz", + "error.section.pages.max.singular": "Nem adhatsz hozzá egynél több oldalt a(z) \"{section}\" szekcióhoz", + "error.section.pages.min.plural": "A \"{section}\" szakasz legalább {min} oldalt igényel", + "error.section.pages.min.singular": "A \"{section}\" szakasz legalább egy oldalt igényel", + + "error.section.notLoaded": "A(z) \"{name}\" szekció nem tölthető be", + "error.section.type.invalid": "A szekció típusa (\"{type}\") nem megfelelő", + + "error.site.changeTitle.empty": "A cím nem lehet üres", + "error.site.changeTitle.permission": "Nincs jogosultságod megváltoztatni az honlap címét", + "error.site.update.permission": "Nincs jogosultságod frissíteni a honlapot", + + "error.template.default.notFound": "Az alapértelmezett sablon nem létezik", + + "error.user.changeEmail.permission": "Nincs jogosultságod megváltoztatni \"{name}\" felhasználó email-címét", + "error.user.changeLanguage.permission": "Nincs jogosultságod megváltoztatni \"{name}\" felhasználó nyelvi beállításait", + "error.user.changeName.permission": "Nincs jogosultságod megváltoztatni \"{name}\" felhasználó nevét", + "error.user.changePassword.permission": "Nincs jogosultságod megváltoztatni \"{name}\" felhasználó jelszavát", + "error.user.changeRole.lastAdmin": "Az egyedüli adminisztrátor szerepkörét nem lehet megváltoztatni", + "error.user.changeRole.permission": "Nincs jogosultságod megváltoztatni \"{name}\" felhasználó szerepkörét", + "error.user.changeRole.toAdmin": "You are not allowed to promote someone to the admin role", + "error.user.create.permission": "Nincs jogosultságod létrehozni ezt a felhasználót", + "error.user.delete": "A felhaszn\u00e1l\u00f3 nem t\u00f6r\u00f6lhet\u0151", + "error.user.delete.lastAdmin": "Nem t\u00f6r\u00f6lheted az egyetlen adminisztr\u00e1tort", + "error.user.delete.lastUser": "Nem törölheted az egyetlen felhasználót", + "error.user.delete.permission": "Nincs jogosults\u00e1god t\u00f6r\u00f6lni ezt a felhaszn\u00e1l\u00f3t", + "error.user.duplicate": "Már létezik felhasználó \"{email}\" email-címmel", + "error.user.email.invalid": "Kérlek adj meg egy valós email-címet", + "error.user.language.invalid": "Kérlek add meg a megfelelő nyelvi beállítást", + "error.user.notFound": "A felhaszn\u00e1l\u00f3 nem tal\u00e1lhat\u00f3", + "error.user.password.invalid": "Kérlek adj meg egy megfelelő jelszót. A jelszónak legalább 8 karakter hosszúságúnak kell lennie.", + "error.user.password.notSame": "K\u00e9rlek er\u0151s\u00edtsd meg a jelsz\u00f3t", + "error.user.password.undefined": "A felhasználónak nincs jelszó megadva", + "error.user.password.wrong": "Wrong password", + "error.user.role.invalid": "Kérlek adj meg egy megfelelő szerepkört", + "error.user.update.permission": "Nincs jogosultságod frissíteni \"{name}\" felhasználó adatait", + + "error.validation.accepted": "Kérlek erősítsd meg", + "error.validation.alpha": "Kérlek csak kis betűket használj (a-z)", + "error.validation.alphanum": "Kérlek csak kis betűket és számjegyeket használj (a-z, 0-9)", + "error.validation.between": "Kérlek egy \"{min}\" és \"{max}\" közötti értéket adj meg", + "error.validation.boolean": "Kérlek erősítsd meg vagy vesd el", + "error.validation.contains": "Kérlek olyan értéket adj meg, amely tartalmazza ezt: \"{needle}\"", + "error.validation.date": "Kérlek megfelelő dátumot adj meg", + "error.validation.date.after": "Please enter a date after {date}", + "error.validation.date.before": "Please enter a date before {date}", + "error.validation.date.between": "Please enter a date between {min} and {max}", + "error.validation.denied": "Kérlek vesd el", + "error.validation.different": "Az érték nem lehet \"{other}\"", + "error.validation.email": "Kérlek adj meg egy valós email-címet", + "error.validation.endswith": "Az értéknek erre kell végződnie: \"{end}\"", + "error.validation.filename": "Kérlek megfelelő fájlnevet adj meg", + "error.validation.in": "Kérlek adj meg egyet az alábbiak közül: ({in})", + "error.validation.integer": "Kérlek valós számot adj meg", + "error.validation.ip": "Kérlek megfelelő IP-címet adj meg", + "error.validation.less": "A megadott érték kevesebb legyen, mint {max}", + "error.validation.match": "A megadott érték nem felel meg az elvárt struktúrának", + "error.validation.max": "A megadott érték egyenlő vagy kevesebb legyen, mint {max}", + "error.validation.maxlength": "Kérlek rövidebb értéket adj meg (legfeljebb {max} karakter)", + "error.validation.maxwords": "Kérlek ide legfeljebb {max} szót írj", + "error.validation.min": "A megadott érték egyenlő vagy nagyobb legyen, mint {min}", + "error.validation.minlength": "Kérlek hosszabb értéket adj meg (legalább {min} karakter)", + "error.validation.minwords": "Kérlek ide legalább {min} szót írj", + "error.validation.more": "A megadott érték legyen nagyobb, mint {min} ", + "error.validation.notcontains": "Kérlek olyan értéket adj meg, amely nem tartalmazza ezt: \"{needle}\" ", + "error.validation.notin": "Kérlek egyiket se használd az alábbiak közül: ({notIn})", + "error.validation.option": "Kérlek válassz egy megfelelő opciót", + "error.validation.num": "Kérlek adj meg egy megfelelő számot", + "error.validation.required": "Kérlek írj be valamit", + "error.validation.same": "Kérlek írd be: \"{other}\"", + "error.validation.size": "Az értéknek az alábbi méretűnek kell lennie: \"{size}\"", + "error.validation.startswith": "Az értéknek ezzel kell kezdődnie: \"{start}\"", + "error.validation.time": "Kérlek megfelelő időt adj meg", + "error.validation.time.after": "Please enter a time after {time}", + "error.validation.time.before": "Please enter a time before {time}", + "error.validation.time.between": "Please enter a time between {min} and {max}", + "error.validation.url": "Kérlek megfelelő URL-t adj meg", + + "field.required": "The field is required", + "field.blocks.changeType": "Change type", + "field.blocks.code.name": "Kód", + "field.blocks.code.language": "Nyelv", + "field.blocks.code.placeholder": "Your code …", + "field.blocks.delete.confirm": "Do you really want to delete this block?", + "field.blocks.delete.confirm.all": "Do you really want to delete all blocks?", + "field.blocks.delete.confirm.selected": "Do you really want to delete the selected blocks?", + "field.blocks.empty": "No blocks yet", + "field.blocks.fieldsets.label": "Please select a block type …", + "field.blocks.gallery.name": "Gallery", + "field.blocks.gallery.images.empty": "No images yet", + "field.blocks.gallery.images.label": "Images", + "field.blocks.heading.level": "Level", + "field.blocks.heading.name": "Heading", + "field.blocks.heading.text": "Text", + "field.blocks.heading.placeholder": "Heading …", + "field.blocks.image.alt": "Alternative text", + "field.blocks.image.caption": "Caption", + "field.blocks.image.crop": "Crop", + "field.blocks.image.link": "Link", + "field.blocks.image.location": "Location", + "field.blocks.image.name": "Kép", + "field.blocks.image.placeholder": "Select an image", + "field.blocks.image.ratio": "Ratio", + "field.blocks.image.url": "Image URL", + "field.blocks.list.name": "List", + "field.blocks.markdown.name": "Markdown", + "field.blocks.markdown.label": "Text", + "field.blocks.markdown.placeholder": "Markdown …", + "field.blocks.quote.name": "Quote", + "field.blocks.quote.text.label": "Text", + "field.blocks.quote.text.placeholder": "Quote …", + "field.blocks.quote.citation.label": "Citation", + "field.blocks.quote.citation.placeholder": "by …", + "field.blocks.text.name": "Text", + "field.blocks.text.placeholder": "Text …", + "field.blocks.video.caption": "Caption", + "field.blocks.video.name": "Video", + "field.blocks.video.placeholder": "Enter a video URL", + "field.blocks.video.url.label": "Video-URL", + "field.blocks.video.url.placeholder": "https://youtube.com/?v=", + + "field.files.empty": "Nincs fálj kiválasztva", + + "field.layout.delete": "Delete layout", + "field.layout.delete.confirm": "Do you really want to delete this layout?", + "field.layout.empty": "No rows yet", + "field.layout.select": "Select a layout", + + "field.pages.empty": "Nincs oldal kiválasztva", + "field.structure.delete.confirm": "Biztos t\u00f6r\u00f6lni szeretn\u00e9d ezt a bejegyz\u00e9st?", + "field.structure.empty": "Nincs m\u00e9g bejegyz\u00e9s", + "field.users.empty": "Nincs felhasználó kiválasztva", + + "file.blueprint": "This file has no blueprint yet. You can define the setup in /site/blueprints/{template}.yml", + "file.delete.confirm": "Biztos törölni akarod ezt a fájlt:
{filename}?", + "file.sort": "Change position", + + "files": "Fájlok", + "files.empty": "Még nincsenek fájlok", + + "hide": "Hide", + "hour": "Óra", + "insert": "Beilleszt", + "insert.after": "Insert after", + "insert.before": "Insert before", + "install": "Telepítés", + + "installation": "Telepítés", + "installation.completed": "A panel sikeresen telepítve", + "installation.disabled": "A panel telepítője alapértelmezés szerint le van tiltva a nyilvános szervereken. Kérlek, futtassd a telepítőt egy helyi gépen vagy engedélyezze a panel.install opcióval.", + "installation.issues.accounts": "A /site/accounts mappa nem létezik, vagy nem írható", + "installation.issues.content": "A /content mappa nem létezik vagy nem írható", + "installation.issues.curl": "A CURL bővítmény engedélyezése szükséges", + "installation.issues.headline": "A panel telepítése sikertelen", + "installation.issues.mbstring": "Az MB String bővítmény engedélyezése szükséges", + "installation.issues.media": "A /media mappa nem létezik vagy nem írható", + "installation.issues.php": "Bizonyosodj meg róla, hogy az általad használt PHP-verzió PHP 7+", + "installation.issues.server": "A Kirby az alábbi szervereken futtatható: Apache, Nginx vagy Caddy", + "installation.issues.sessions": "A /site/sessions könyvtár nem létezik vagy nem írható", + + "language": "Nyelv", + "language.code": "Kód", + "language.convert": "Alapértelmezettnek jelölés", + "language.convert.confirm": "

Tényleg az alaőértelmezett nyelvre szeretnéd konvertálni ezt: {name}? Ez a művelet nem vonható vissza.

Ha{name} olyat is tartalmaz, amelynek nincs megfelelő fordítása, a honlapod egyes részei az új alapértelmezett nyelv hiányosságai miatt üresek maradhatnak.

", + "language.create": "Új nyelv hozzáadása", + "language.delete.confirm": "Tényleg törölni szeretnéd a(z) {name} nyelvet, annak minden fordításával együtt? Ez a művelet nem vonható vissza!", + "language.deleted": "A nyelv törölve lett", + "language.direction": "Olvasási irány", + "language.direction.ltr": "Balról jobbra", + "language.direction.rtl": "Jobbról balra", + "language.locale": "PHP locale sztring", + "language.locale.warning": "You are using a custom locale set up. Please modify it in the language file in /site/languages", + "language.name": "Név", + "language.updated": "A nyelv frissítve lett", + + "languages": "Nyelvek", + "languages.default": "Alapértelmezett nyelv", + "languages.empty": "Nincsnek még nyelvek", + "languages.secondary": "Másodlagos nyelvek", + "languages.secondary.empty": "Nincsnek még másodlagos nyelvek", + + "license": "Kirby licenc", + "license.buy": "Licenc vásárlása", + "license.register": "Regisztráció", + "license.register.help": "A vásárlás után emailben küldjük el a licenc-kódot. Regisztrációhoz másold ide a kapott kódot.", + "license.register.label": "Kérlek írd be a licenc-kódot", + "license.register.success": "Köszönjük, hogy támogatod a Kirby-t", + "license.unregistered": "Jelenleg a Kirby nem regisztrált próbaverzióját használod", + + "link": "Link", + "link.text": "Link szövege", + + "loading": "Betöltés", + + "lock.unsaved": "Nem mentett változások", + "lock.unsaved.empty": "There are no more unsaved changes", + "lock.isLocked": "Nem mentett {email} változások", + "lock.file.isLocked": "A fájlt jelenleg {email} szerkeszti és nem módosítható.", + "lock.page.isLocked": "Az oldalt jelenleg {email} szerkeszti és nem módosítható.", + "lock.unlock": "Kinyit", + "lock.isUnlocked": "A nem mentett módosításokat egy másik felhasználó felülírta. A módosításokat manuálisan egyesítheted.", + + "login": "Bejelentkezés", + "login.code.label.login": "Login code", + "login.code.label.password-reset": "Password reset code", + "login.code.placeholder.email": "000 000", + "login.code.text.email": "If your email address is registered, the requested code was sent via email.", + "login.email.login.body": "Hi {user.nameOrEmail},\n\nYou recently requested a login code for the Kirby Panel.\nThe following login code will be valid for {timeout} minutes:\n\n{code}\n\nIf you did not request a login code, please ignore this email or contact your administrator if you have questions.\nFor security, please DO NOT forward this email.", + "login.email.login.subject": "Your login code", + "login.email.password-reset.body": "Hi {user.nameOrEmail},\n\nYou recently requested a password reset code for the Kirby Panel.\nThe following password reset code will be valid for {timeout} minutes:\n\n{code}\n\nIf you did not request a password reset code, please ignore this email or contact your administrator if you have questions.\nFor security, please DO NOT forward this email.", + "login.email.password-reset.subject": "Your password reset code", + "login.remember": "Maradjak bejelentkezve", + "login.reset": "Reset password", + "login.toggleText.code.email": "Login via email", + "login.toggleText.code.email-password": "Login with password", + "login.toggleText.password-reset.email": "Forgot your password?", + "login.toggleText.password-reset.email-password": "← Back to login", + + "logout": "Kijelentkezés", + + "menu": "Menü", + "meridiem": "DE/DU", + "mime": "Média-típus", + "minutes": "Perc", + + "month": "Hónap", + "months.april": "\u00e1prilis", + "months.august": "augusztus", + "months.december": "december", + "months.february": "február", + "months.january": "janu\u00e1r", + "months.july": "j\u00falius", + "months.june": "j\u00fanius", + "months.march": "m\u00e1rcius", + "months.may": "m\u00e1jus", + "months.november": "november", + "months.october": "okt\u00f3ber", + "months.september": "szeptember", + + "more": "Több", + "name": "Név", + "next": "Következő", + "no": "no", + "off": "ki", + "on": "be", + "open": "Megnyitás", + "open.newWindow": "Open in new window", + "options": "Beállítások", + "options.none": "No options", + + "orientation": "Tájolás", + "orientation.landscape": "Fekvő", + "orientation.portrait": "Álló", + "orientation.square": "Négyzetes", + + "page.blueprint": "This page has no blueprint yet. You can define the setup in /site/blueprints/{template}.yml", + "page.changeSlug": "URL v\u00e1ltoztat\u00e1sa", + "page.changeSlug.fromTitle": "L\u00e9trehoz\u00e1s c\u00edmb\u0151l", + "page.changeStatus": "Állapot módosítása", + "page.changeStatus.position": "Kérlek válaszd ki a pozíciót", + "page.changeStatus.select": "Új állapot kiválasztása", + "page.changeTemplate": "Sablon módosítása", + "page.delete.confirm": "Biztos vagy benne, hogy törlöd az alábbi oldalt: {title}?", + "page.delete.confirm.subpages": "Ehhez az oldalhoz aloldalak tartoznak.
Az oldal törlésekor a hozzá tartozó aloldalak is törlődnek.", + "page.delete.confirm.title": "Megerősítéshez add meg az oldal címét", + "page.draft.create": "Piszkozat létrehozása", + "page.duplicate.appendix": "Másol", + "page.duplicate.files": "Fájlok másolása", + "page.duplicate.pages": "Oldalak másolása", + "page.sort": "Change position", + "page.status": "Állapot", + "page.status.draft": "Piszkozat", + "page.status.draft.description": "The page is in draft mode and only visible for logged in editors or via secret link", + "page.status.listed": "Publikus", + "page.status.listed.description": "Az oldal mindenki számára elérhető", + "page.status.unlisted": "Nem listázott", + "page.status.unlisted.description": "Az oldal csak URL-en keresztül érhető el", + + "pages": "Oldalak", + "pages.empty": "Nincs még bejegyzés", + "pages.status.draft": "Piszkozatok", + "pages.status.listed": "Publikálva", + "pages.status.unlisted": "Nem listázott", + + "pagination.page": "Oldal", + + "password": "Jelsz\u00f3", + "pixel": "Pixel", + "prev": "Előző", + "preview": "Preview", + "remove": "Eltávolítás", + "rename": "Átnevezés", + "replace": "Cser\u00e9l", + "retry": "Próbáld újra", + "revert": "Visszavon\u00e1s", + "revert.confirm": "Do you really want to delete all unsaved changes?", + + "role": "Szerepkör", + "role.admin.description": "Az adminisztrátornak minden joga van", + "role.admin.title": "Admin", + "role.all": "Összes", + "role.empty": "Nincsenek felhasználók ilyen szerepkörrel", + "role.description.placeholder": "Nincs leírás", + "role.nobody.description": "Ez a visszatérő szabály a nem rendelkező jogosultsághoz", + "role.nobody.title": "Senki", + + "save": "Ment\u00e9s", + "search": "Keresés", + "search.min": "Enter {min} characters to search", + "search.all": "Show all", + "search.results.none": "No results", + + "section.required": "The section is required", + + "select": "Kiválasztás", + "settings": "Beállítások", + "show": "Show", + "size": "Méret", + "slug": "URL n\u00e9v", + "sort": "Rendezés", + "title": "Cím", + "template": "Sablon", + "today": "Ma", + + "site.blueprint": "The site has no blueprint yet. You can define the setup in /site/blueprints/site.yml", + + "toolbar.button.code": "Kód", + "toolbar.button.bold": "F\u00e9lk\u00f6v\u00e9r sz\u00f6veg", + "toolbar.button.email": "Email", + "toolbar.button.headings": "Cím", + "toolbar.button.heading.1": "Cím 1", + "toolbar.button.heading.2": "Cím 2", + "toolbar.button.heading.3": "Cím 3", + "toolbar.button.italic": "Dőlt szöveg", + "toolbar.button.file": "Fájl", + "toolbar.button.file.select": "Válassz egy fájlt", + "toolbar.button.file.upload": "Fájl feltöltése", + "toolbar.button.link": "Link", + "toolbar.button.strike": "Strike-through", + "toolbar.button.ol": "Rendezett lista", + "toolbar.button.underline": "Underline", + "toolbar.button.ul": "Rendezetlen lista", + + "translation.author": "A Kirby csapata", + "translation.direction": "ltr", + "translation.name": "Magyar", + "translation.locale": "hu_HU", + + "upload": "Feltöltés", + "upload.error.cantMove": "The uploaded file could not be moved", + "upload.error.cantWrite": "Failed to write file to disk", + "upload.error.default": "The file could not be uploaded", + "upload.error.extension": "File upload stopped by extension", + "upload.error.formSize": "The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the form", + "upload.error.iniPostSize": "The uploaded file exceeds the post_max_size directive in php.ini", + "upload.error.iniSize": "The uploaded file exceeds the upload_max_filesize directive in php.ini", + "upload.error.noFile": "No file was uploaded", + "upload.error.noFiles": "No files were uploaded", + "upload.error.partial": "The uploaded file was only partially uploaded", + "upload.error.tmpDir": "Missing a temporary folder", + "upload.errors": "Hiba", + "upload.progress": "Feltöltés...", + + "url": "Url", + "url.placeholder": "https://pelda.hu", + + "user": "Felhasználó", + "user.blueprint": "Ehhez a szerepkörhöz további szekciókat és mezőket vehetsz fel a /site/blueprints/users/{role}.yml fájlban", + "user.changeEmail": "Email módosítása", + "user.changeLanguage": "Nyelv módosítása", + "user.changeName": "Felhasználó átnevezése", + "user.changePassword": "Jelszó módosítása", + "user.changePassword.new": "Új jelszó", + "user.changePassword.new.confirm": "Az új jelszó megerősítése", + "user.changeRole": "Szerepkör módosítása", + "user.changeRole.select": "Új szerepkör kiválasztása", + "user.create": "Új felhasználó hozzáadása", + "user.delete": "Felhasználó törlése", + "user.delete.confirm": "Biztos törlöd ezt a felhasználót:
{email}?", + + "users": "Felhasználók", + + "version": "Kirby verzi\u00f3", + + "view.account": "Fi\u00f3kod", + "view.installation": "Telep\u00edt\u00e9s", + "view.resetPassword": "Reset password", + "view.settings": "Beállítások", + "view.site": "Weboldal", + "view.users": "Felhaszn\u00e1l\u00f3k", + + "welcome": "Üdvözlünk", + "year": "Év", + "yes": "yes" +} diff --git a/kirby/i18n/translations/id.json b/kirby/i18n/translations/id.json new file mode 100644 index 0000000..63e7031 --- /dev/null +++ b/kirby/i18n/translations/id.json @@ -0,0 +1,527 @@ +{ + "add": "Tambah", + "avatar": "Gambar profil", + "back": "Kembali", + "cancel": "Batal", + "change": "Ubah", + "close": "Tutup", + "confirm": "Oke", + "collapse": "Lipat", + "collapse.all": "Lipat Semua", + "copy": "Salin", + "create": "Buat", + + "date": "Tanggal", + "date.select": "Pilih tanggal", + + "day": "Hari", + "days.fri": "Jum", + "days.mon": "Sen", + "days.sat": "Sab", + "days.sun": "Min", + "days.thu": "Kam", + "days.tue": "Sel", + "days.wed": "Rab", + + "delete": "Hapus", + "delete.all": "Hapus semua", + "dimensions": "Dimensi", + "disabled": "Dimatikan", + "discard": "Buang", + "download": "Unduh", + "duplicate": "Duplikasi", + "edit": "Sunting", + "expand": "Luaskan", + "expand.all": "Luaskan Semua", + + "dialog.files.empty": "Tidak ada berkas untuk dipilih", + "dialog.pages.empty": "Tidak ada halaman untuk dipilih", + "dialog.users.empty": "Tidak ada pengguna untuk dipilih", + + "email": "Surel", + "email.placeholder": "surel@contoh.com", + + "error.access.code": "Kode tidak valid", + "error.access.login": "Upaya masuk tidak valid", + "error.access.panel": "Anda tidak diizinkan mengakses panel", + "error.access.view": "Anda tidak diizinkan mengakses bagian panel ini", + + "error.avatar.create.fail": "Gambar profil tidak dapat diunggah", + "error.avatar.delete.fail": "Gambar profil tidak dapat dihapus", + "error.avatar.dimensions.invalid": "Pastikan lebar dan tinggi gambar profil di bawah 3000 piksel", + "error.avatar.mime.forbidden": "Gambar profil harus berupa berkas JPEG atau PNG", + + "error.blueprint.notFound": "Cetak biru \"{name}\" tidak dapat dimuat", + + "error.blocks.max.plural": "Anda tidak boleh menambahkan lebih dari {max} blok", + "error.blocks.max.singular": "Anda tidak boleh menambahkan lebih dari satu blok", + "error.blocks.min.plural": "Anda setidaknya menambahkan {min} blok", + "error.blocks.min.singular": "Anda setidaknya menambahkan satu blok", + "error.blocks.validation": "Ada kesalahan di blok {index}", + + "error.email.preset.notFound": "Surel \"{name}\" tidak dapat ditemukan", + + "error.field.converter.invalid": "Konverter \"{converter}\" tidak valid", + + "error.file.changeName.empty": "Nama harus diisi", + "error.file.changeName.permission": "Anda tidak diizinkan mengubah nama berkas \"{filename}\"", + "error.file.duplicate": "Berkas dengan nama \"{filename}\" sudah ada", + "error.file.extension.forbidden": "Ekstensi \"{extension}\" tidak diizinkan", + "error.file.extension.invalid": "Ekstensi tidak valid: {extension}", + "error.file.extension.missing": "Berkas \"{filename}\" harus memiliki ekstensi", + "error.file.maxheight": "Tinggi gambar tidak boleh melebihi {height} piksel", + "error.file.maxsize": "Berkas terlalu besar", + "error.file.maxwidth": "Lebar gambar tidak boleh melebihi {width} piksel", + "error.file.mime.differs": "Berkas yang diunggah harus memiliki tipe mime sama \"{mime}\"", + "error.file.mime.forbidden": "Media dengan tipe mime \"{mime}\" tidak diizinkan", + "error.file.mime.invalid": "Tipe mime tidak valid: {mime}", + "error.file.mime.missing": "Tipe media untuk \"{filename}\" tidak dapat dideteksi", + "error.file.minheight": "Tinggi gambar setidaknya {height} piksel", + "error.file.minsize": "Berkas terlalu kecil", + "error.file.minwidth": "Lebar gambar setidaknya {width} piksel", + "error.file.name.missing": "Nama berkas harus diisi", + "error.file.notFound": "Berkas \"{filename}\" tidak dapat ditemukan", + "error.file.orientation": "Orientasi gambar harus \"{orientation}\"", + "error.file.type.forbidden": "Anda tidak diizinkan mengunggah berkas dengan tipe {type}", + "error.file.type.invalid": "Tipe berkas tidak valid: {type}", + "error.file.undefined": "Berkas tidak dapat ditemukan", + + "error.form.incomplete": "Pastikan semua bidang telah diisi dengan benar…", + "error.form.notSaved": "Formulir tidak dapat disimpan", + + "error.language.code": "Masukkan kode bahasa yang valid", + "error.language.duplicate": "Bahasa sudah ada", + "error.language.name": "Masukkan nama bahasa yang valid", + + "error.layout.validation.block": "Ada kesalahan di blok {blockIndex} di tata letak {layoutIndex}", + "error.layout.validation.settings": "Ada kesalahan di pengaturan tata letak {index}", + + "error.license.format": "Masukkan kode lisensi yang valid", + "error.license.email": "Masukkan surel yang valid", + "error.license.verification": "Lisensi tidak dapat diverifikasi", + + "error.page.changeSlug.permission": "Anda tidak diizinkan mengubah akhiran URL untuk \"{slug}\"", + "error.page.changeStatus.incomplete": "Halaman memiliki kesalahan dan tidak dapat diterbitkan", + "error.page.changeStatus.permission": "Status halaman ini tidak dapat diubah", + "error.page.changeStatus.toDraft.invalid": "Halaman \"{slug}\" tidak dapat dikonversi menjadi draf", + "error.page.changeTemplate.invalid": "Templat untuk halaman \"{slug}\" tidak dapat diubah", + "error.page.changeTemplate.permission": "Anda tidak diizinkan mengubah templat dari \"{slug}\"", + "error.page.changeTitle.empty": "Judul harus diisi", + "error.page.changeTitle.permission": "Anda tidak diizinkan mengubah judul dari \"{slug}\"", + "error.page.create.permission": "Anda tidak diizinkan membuat \"{slug}\"", + "error.page.delete": "Halaman \"{slug}\" tidak dapat dihapus", + "error.page.delete.confirm": "Masukkan judul halaman untuk mengonfirmasi", + "error.page.delete.hasChildren": "Halaman ini memiliki sub-halaman dan tidak dapat dihapus", + "error.page.delete.permission": "Anda tidak diizinkan menghapus \"{slug}\"", + "error.page.draft.duplicate": "Draf halaman dengan akhiran URL \"{slug}\" sudah ada", + "error.page.duplicate": "Halaman dengan akhiran URL \"{slug}\" sudah ada", + "error.page.duplicate.permission": "Anda tidak diizinkan menduplikasi \"{slug}\"", + "error.page.notFound": "Halaman \"{slug}\" tidak dapat ditemukan", + "error.page.num.invalid": "Masukkan nomor urut yang valid. Nomor tidak boleh negatif.", + "error.page.slug.invalid": "Masukkan akhiran URL yang valid", + "error.page.slug.maxlength": "Panjang slug harus kurang dari \"{length}\" karakter", + "error.page.sort.permission": "Halaman \"{slug}\" tidak dapat diurutkan", + "error.page.status.invalid": "Atur status halaman yang valid", + "error.page.undefined": "Halaman tidak dapat ditemukan", + "error.page.update.permission": "Anda tidak diizinkan memperbaharui \"{slug}\"", + + "error.section.files.max.plural": "Anda hanya boleh menambahkan maksimal {max} berkas ke bagian \"{section}\"", + "error.section.files.max.singular": "Anda hanya boleh menambahkan satu berkas ke bagian \"{section}\"", + "error.section.files.min.plural": "Bagian \"{section}\" setidaknya memiliki {min} berkas", + "error.section.files.min.singular": "Bagian \"{section}\" setidaknya memiliki satu berkas", + + "error.section.pages.max.plural": "Anda hanya boleh menambahkan maksimal {max} halaman ke bagian \"{section}\"", + "error.section.pages.max.singular": "Anda hanya boleh menambahkan satu halaman ke bagian \"{section}\"", + "error.section.pages.min.plural": "Bagian \"{section}\" setidaknya memiliki {min} halaman", + "error.section.pages.min.singular": "Bagian \"{section}\" setidaknya memiliki satu halaman", + + "error.section.notLoaded": "Bagian \"{name}\" tidak dapat dimuat", + "error.section.type.invalid": "Tipe bagian \"{type}\" tidak valid", + + "error.site.changeTitle.empty": "Judul harus diisi", + "error.site.changeTitle.permission": "Anda tidak diizinkan mengubah judul situs", + "error.site.update.permission": "Anda tidak diizinkan memperbaharui situs", + + "error.template.default.notFound": "Templat bawaan tidak ada", + + "error.user.changeEmail.permission": "Anda tidak diizinkan mengubah surel dari pengguna \"{name}\"", + "error.user.changeLanguage.permission": "Anda tidak diizinkan mengubah bahasa dari pengguna \"{name}\"", + "error.user.changeName.permission": "Anda tidak diizinkan mengubah nama dari pengguna \"{name}\"", + "error.user.changePassword.permission": "Anda tidak diizinkan mengubah sandi dari pengguna \"{name}\"", + "error.user.changeRole.lastAdmin": "Peran dari admin satu-satunya tidak dapat diubah", + "error.user.changeRole.permission": "Anda tidak diizinkan mengubah peran dari pengguna \"{name}\"", + "error.user.changeRole.toAdmin": "Anda tidak diizinkan mempromosikan seseorang menjadi admin", + "error.user.create.permission": "Anda tidak diizinkan membuat pengguna ini", + "error.user.delete": "Pengguna \"{nama}\" tidak dapat dihapus", + "error.user.delete.lastAdmin": "Admin satu-satunya tidak dapat dihapus", + "error.user.delete.lastUser": "Pengguna satu-satunya tidak dapat dihapus", + "error.user.delete.permission": "Anda tidak diizinkan menghapus pengguna \"{name}\"", + "error.user.duplicate": "Pengguna dengan surel \"{email}\" sudah ada", + "error.user.email.invalid": "Masukkan surel yang valid", + "error.user.language.invalid": "Masukkan bahasa yang valid", + "error.user.notFound": "Pengguna \"{name}\" tidak dapat ditemukan", + "error.user.password.invalid": "Masukkan sandi yang valid. Sandi setidaknya mengandung 8 karakter.", + "error.user.password.notSame": "Sandi tidak cocok", + "error.user.password.undefined": "Pengguna tidak memiliki sandi", + "error.user.password.wrong": "Kata sandi salah", + "error.user.role.invalid": "Masukkan peran yang valid", + "error.user.update.permission": "Anda tidak diizinkan memperbaharui pengguna \"{name}\"", + + "error.validation.accepted": "Mohon konfirmasi", + "error.validation.alpha": "Masukkan hanya karakter a-z", + "error.validation.alphanum": "Masukkan hanya karakter a-z atau 0-9", + "error.validation.between": "Masukkan nilai antara \"{min}\" dan \"{max}\"", + "error.validation.boolean": "Mohon konfirmasi atau tolak", + "error.validation.contains": "Masukkan nilai yang mengandung \"{needle}\"", + "error.validation.date": "Masukkan tanggal yang valid", + "error.validation.date.after": "Masukkan tanggal setelah {date}", + "error.validation.date.before": "Masukkan tanggal sebelum {date}", + "error.validation.date.between": "Masukkan tanggal antara {min} dan {max}", + "error.validation.denied": "Mohon tolak", + "error.validation.different": "Nilai harus selain \"{other}\"", + "error.validation.email": "Masukkan surel yang valid", + "error.validation.endswith": "Nilai harus diakhiri dengan \"{end}\"", + "error.validation.filename": "Masukkan nama berkas yang valid", + "error.validation.in": "Masukkan satu dari berikut: ({in})", + "error.validation.integer": "Masukkan bilangan bulat yang valid", + "error.validation.ip": "Masukkan IP yang valid", + "error.validation.less": "Masukkan nilai kurang dari {max}", + "error.validation.match": "Nilai tidak cocok dengan pola yang semestinya", + "error.validation.max": "Masukkan nilai yang sama dengan atau kurang dari {max}", + "error.validation.maxlength": "Masukkan nilai yang lebih pendek. (maksimal {max} karakter)", + "error.validation.maxwords": "Masukkan tidak lebih dari {max} kata", + "error.validation.min": "Masukkan nilai yang sama dengan atau lebih dari {min}", + "error.validation.minlength": "Masukkan nilai yang lebih panjang. (minimal {min} karakter)", + "error.validation.minwords": "Masukkan setidaknya {min} kata", + "error.validation.more": "Masukkan nilai yang lebih besar dari {min}", + "error.validation.notcontains": "Masukkan nilai yang tidak mengandung \"{needle}\"", + "error.validation.notin": "Jangan masukkan satupun: ({notIn})", + "error.validation.option": "Pilih opsi yang valid", + "error.validation.num": "Masukkan nomor yang valid", + "error.validation.required": "Masukkan sesuatu", + "error.validation.same": "Masukkan \"{other}\"", + "error.validation.size": "Ukuran dari nilai harus \"{size}\"", + "error.validation.startswith": "Nilai harus diawali dengan \"{start}\"", + "error.validation.time": "Masukkan waktu yang valid", + "error.validation.time.after": "Masukkan waktu setelah {time}", + "error.validation.time.before": "Masukkan waktu sebelum {time}", + "error.validation.time.between": "Masukkan waktu antara {min} dan {max}", + "error.validation.url": "Masukkan URL yang valid", + + "field.required": "Bidang ini wajib", + "field.blocks.changeType": "Ubah tipe", + "field.blocks.code.name": "Kode", + "field.blocks.code.language": "Bahasa", + "field.blocks.code.placeholder": "Kode Anda …", + "field.blocks.delete.confirm": "Anda yakin menghapus blok ini?", + "field.blocks.delete.confirm.all": "Anda yakin menghapus semua blok?", + "field.blocks.delete.confirm.selected": "Anda yakin menghapus blok yang dipilih?", + "field.blocks.empty": "Belum ada blok", + "field.blocks.fieldsets.label": "Pilih tipe blok …", + "field.blocks.gallery.name": "Galeri", + "field.blocks.gallery.images.empty": "Belum ada gambar", + "field.blocks.gallery.images.label": "Gambar", + "field.blocks.heading.level": "Level", + "field.blocks.heading.name": "Penajukan", + "field.blocks.heading.text": "Teks", + "field.blocks.heading.placeholder": "Penajukan …", + "field.blocks.image.alt": "Teks alternatif", + "field.blocks.image.caption": "Keterangan", + "field.blocks.image.crop": "Pangkas", + "field.blocks.image.link": "Tautan", + "field.blocks.image.location": "Lokasi", + "field.blocks.image.name": "Gambar", + "field.blocks.image.placeholder": "Pilih gambar", + "field.blocks.image.ratio": "Rasio", + "field.blocks.image.url": "URL Gambar", + "field.blocks.list.name": "Daftar", + "field.blocks.markdown.name": "Markdown", + "field.blocks.markdown.label": "Teks", + "field.blocks.markdown.placeholder": "Markdown …", + "field.blocks.quote.name": "Kutipan", + "field.blocks.quote.text.label": "Teks", + "field.blocks.quote.text.placeholder": "Kutipan …", + "field.blocks.quote.citation.label": "Sitasi", + "field.blocks.quote.citation.placeholder": "oleh …", + "field.blocks.text.name": "Teks", + "field.blocks.text.placeholder": "Teks …", + "field.blocks.video.caption": "Deskripsi", + "field.blocks.video.name": "Video", + "field.blocks.video.placeholder": "Masukkan URL video", + "field.blocks.video.url.label": "URL Video", + "field.blocks.video.url.placeholder": "https://youtube.com/?v=", + + "field.files.empty": "Belum ada berkas yang dipilih", + + "field.layout.delete": "Hapus tata letak", + "field.layout.delete.confirm": "Anda yakin menghapus tata letak ini?", + "field.layout.empty": "Belum ada baris", + "field.layout.select": "Pilih tata letak", + + "field.pages.empty": "Belum ada halaman yang dipilih", + "field.structure.delete.confirm": "Anda yakin menghapus baris ini?", + "field.structure.empty": "Belum ada entri", + "field.users.empty": "Belum ada pengguna yang dipilih", + + "file.blueprint": "Berkas ini belum memiliki cetak biru. Anda dapat mendefinisikannya di /site/blueprints/{template}.yml", + "file.delete.confirm": "Anda yakin menghapus
{filename}?", + "file.sort": "Ubah posisi", + + "files": "Berkas", + "files.empty": "Belum ada berkas", + + "hide": "Sembunyikan", + "hour": "Jam", + "insert": "Sisipkan", + "insert.after": "Sisipkan setelah", + "insert.before": "Sisipkan sebelum", + "install": "Pasang", + + "installation": "Pemasangan", + "installation.completed": "Panel sudah dipasang", + "installation.disabled": "Pemasang panel dimatikan di server publik secara bawaan. Mohon jalankan di server lokal atau ubah opsi panel.install untuk menjalankan di server saat ini.", + "installation.issues.accounts": "Folder /site/accounts tidak ada atau tidak dapat ditulis", + "installation.issues.content": "Folder /content tidak ada atau tidak dapat ditulis", + "installation.issues.curl": "Ekstensi CURL diperlukan", + "installation.issues.headline": "Panel tidak dapat dipasang", + "installation.issues.mbstring": "Ekstensi MB String diperlukan", + "installation.issues.media": "Folder /media tidak ada atau tidak dapat ditulis", + "installation.issues.php": "Pastikan Anda menggunakan PHP 7+", + "installation.issues.server": "Kirby memerlukan Apache, Nginx, atau Caddy", + "installation.issues.sessions": "Folder /site/sessions tidak ada atau tidak dapat ditulis", + + "language": "Bahasa", + "language.code": "Kode", + "language.convert": "Atur sebagai bawaan", + "language.convert.confirm": "

Anda yakin mengubah {name} menjadi bahasa bawaan? Ini tidak dapat dibatalkan.

Jika {name} memiliki konten yang tidak diterjemahkan, tidak akan ada pengganti yang valid dan dapat menyebabkan beberapa bagian dari situs Anda menjadi kosong.

", + "language.create": "Tambah bahasa baru", + "language.delete.confirm": "Anda yakin menghapus bahasa {name} termasuk semua terjemahannya? Ini tidak dapat dibatalkan!", + "language.deleted": "Bahasa sudah dihapus", + "language.direction": "Arah baca", + "language.direction.ltr": "Kiri ke kanan", + "language.direction.rtl": "Kanan ke kiri", + "language.locale": "String \"PHP locale\"", + "language.locale.warning": "Anda menggunakan pengaturan lokal ubah suaian. Ubah di berkas bahasa di /site/languages", + "language.name": "Nama", + "language.updated": "Bahasa sudah diperbaharui", + + "languages": "Bahasa", + "languages.default": "Bahasa bawaan", + "languages.empty": "Belum ada bahasa", + "languages.secondary": "Bahasa sekunder", + "languages.secondary.empty": "Belum ada bahasa sekunder", + + "license": "Lisensi Kirby", + "license.buy": "Beli lisensi", + "license.register": "Daftar", + "license.register.help": "Anda menerima kode lisensi via surel setelah pembelian. Salin dan tempel kode tersebut untuk mendaftarkan.", + "license.register.label": "Masukkan kode lisensi Anda", + "license.register.success": "Terima kasih atas dukungan untuk Kirby", + "license.unregistered": "Ini adalah demo tidak diregistrasi dari Kirby", + + "link": "Tautan", + "link.text": "Teks tautan", + + "loading": "Memuat", + + "lock.unsaved": "Perubahan belum tersimpan", + "lock.unsaved.empty": "Tidak ada lagi perubahan belum tersimpan", + "lock.isLocked": "Perubahan belum tersimpan oleh {email}", + "lock.file.isLocked": "Berkas sedang disunting oleh {email} dan tidak dapat diubah.", + "lock.page.isLocked": "Halaman sedang disunting oleh {email} dan tidak dapat diubah.", + "lock.unlock": "Buka kunci", + "lock.isUnlocked": "Perubahan Anda yang belum tersimpan telah terubah oleh pengguna lain. Anda dapat mengunduh perubahan Anda untuk menggabungkannya manual.", + + "login": "Masuk", + "login.code.label.login": "Kode masuk", + "login.code.label.password-reset": "Kode atur ulang sandi", + "login.code.placeholder.email": "000 000", + "login.code.text.email": "Jika alamat surel terdaftar, kode yang diminta dikirim via surel", + "login.email.login.body": "Hai {user.nameOrEmail},\n\nAnda baru saja meminta kode masuk untuk Kirby Panel.\nKode masuk berikut valid selama {timeout} menit:\n\n{code}\n\nJika Anda tidak meminta kode masuk, abaikan surel ini atau hubungi admin apabila ada yang ingin ditanyakan.\nUntuk alasan keamanan, JANGAN teruskan surel ini.", + "login.email.login.subject": "Kode masuk Anda", + "login.email.password-reset.body": "Hai {user.nameOrEmail},\n\nAnda baru saja meminta kode atur ulang sandi untuk Kirby Panel.\nKode atur ulang sandi berikut valid selama {timeout} menit:\n\n{code}\n\nJika Anda tidak meminta kode atur ulang sandi, abaikan surel ini atau hubungi admin apabila ada yang ingin ditanyakan.\nUntuk alasan keamanan, JANGAN teruskan surel ini.", + "login.email.password-reset.subject": "Kode atur ulang sandi Anda", + "login.remember": "Biarkan tetap masuk", + "login.reset": "Atur ulang sandi", + "login.toggleText.code.email": "Masuk via surel", + "login.toggleText.code.email-password": "Masuk dengan sandi", + "login.toggleText.password-reset.email": "Lupa sandi Anda?", + "login.toggleText.password-reset.email-password": "← Kembali ke masuk", + + "logout": "Keluar", + + "menu": "Menu", + "meridiem": "AM/PM", + "mime": "Tipe Media", + "minutes": "Menit", + + "month": "Bulan", + "months.april": "April", + "months.august": "Agustus", + "months.december": "Desember", + "months.february": "Februari", + "months.january": "Januari", + "months.july": "Juli", + "months.june": "Juni", + "months.march": "Maret", + "months.may": "Mei", + "months.november": "November", + "months.october": "Oktober", + "months.september": "September", + + "more": "Lebih lanjut", + "name": "Nama", + "next": "Selanjutnya", + "no": "tidak", + "off": "mati", + "on": "hidup", + "open": "Buka", + "open.newWindow": "Buka di jendela baru", + "options": "Opsi", + "options.none": "Tidak ada opsi", + + "orientation": "Orientasi", + "orientation.landscape": "Rebah", + "orientation.portrait": "Tegak", + "orientation.square": "Persegi", + + "page.blueprint": "Halaman ini belum memiliki cetak biru. Anda dapat mendefinisikannya di /site/blueprints/{template}.yml", + "page.changeSlug": "Ubah URL", + "page.changeSlug.fromTitle": "Buat dari judul", + "page.changeStatus": "Ubah status", + "page.changeStatus.position": "Pilih posisi", + "page.changeStatus.select": "Pilih status baru", + "page.changeTemplate": "Ubah templat", + "page.delete.confirm": "Anda yakin menghapus {title}?", + "page.delete.confirm.subpages": "Halaman ini memiliki sub-halaman.
Semua sub-halaman akan ikut dihapus.", + "page.delete.confirm.title": "Masukkan judul halaman untuk mengonfirmasi", + "page.draft.create": "Buat draf", + "page.duplicate.appendix": "Salin", + "page.duplicate.files": "Salin berkas", + "page.duplicate.pages": "Salin halaman", + "page.sort": "Ubah posisi", + "page.status": "Status", + "page.status.draft": "Draf", + "page.status.draft.description": "Halaman ini ada pada mode draf dan hanya dapat dilihat oleh penyunting atau via tautan rahasia", + "page.status.listed": "Publik", + "page.status.listed.description": "Halaman publik untuk siapapun", + "page.status.unlisted": "Tidak tercantum", + "page.status.unlisted.description": "Halaman hanya dapat diakses via URL", + + "pages": "Halaman", + "pages.empty": "Belum ada halaman", + "pages.status.draft": "Draf", + "pages.status.listed": "Dipublikasikan", + "pages.status.unlisted": "Tidak tercantum", + + "pagination.page": "Halaman", + + "password": "Sandi", + "pixel": "Piksel", + "prev": "Sebelumnya", + "preview": "Pratinjau", + "remove": "Hapus", + "rename": "Ubah nama", + "replace": "Ganti", + "retry": "Coba lagi", + "revert": "Kembalikan", + "revert.confirm": "Anda yakin menghapus semua perubahan yang belum tersimpan?", + + "role": "Peran", + "role.admin.description": "Admin memiliki semua izin", + "role.admin.title": "Admin", + "role.all": "Semua", + "role.empty": "Tidak ada pengguna dengan peran ini", + "role.description.placeholder": "Tidak ada deskripsi", + "role.nobody.description": "Ini adalah peran cadangan tanpa permisi apapun", + "role.nobody.title": "Tidak siapapun", + + "save": "Simpan", + "search": "Cari", + "search.min": "Masukkan {min} karakter untuk mencari", + "search.all": "Tampilkan semua", + "search.results.none": "Tidak ada hasil", + + "section.required": "Bagian ini wajib", + + "select": "Pilih", + "settings": "Pengaturan", + "show": "Tampilkan", + "size": "Ukuran", + "slug": "Akhiran URL", + "sort": "Urutkan", + "title": "Judul", + "template": "Templat", + "today": "Hari ini", + + "site.blueprint": "Situs ini belum memiliki cetak biru. Anda dapat mendefinisikannya di /site/blueprints/site.yml", + + "toolbar.button.code": "Kode", + "toolbar.button.bold": "Tebal", + "toolbar.button.email": "Surel", + "toolbar.button.headings": "Penajukan", + "toolbar.button.heading.1": "Penajukan 1", + "toolbar.button.heading.2": "Penajukan 2", + "toolbar.button.heading.3": "Penajukan 3", + "toolbar.button.italic": "Miring", + "toolbar.button.file": "Berkas", + "toolbar.button.file.select": "Pilih berkas", + "toolbar.button.file.upload": "Unggah berkas", + "toolbar.button.link": "Tautan", + "toolbar.button.strike": "Coret", + "toolbar.button.ol": "Daftar berurut", + "toolbar.button.underline": "Garis bawah", + "toolbar.button.ul": "Daftar tidak berurut", + + "translation.author": "Tim Kirby", + "translation.direction": "ltr", + "translation.name": "Bahasa Indonesia", + "translation.locale": "id_ID", + + "upload": "Unggah", + "upload.error.cantMove": "Berkas unggahan tidak dapat dipindahkan", + "upload.error.cantWrite": "Gagal menyimpan berkas", + "upload.error.default": "Berkas tidak dapat diunggah", + "upload.error.extension": "Unggahan berkas diblokir dengan ekstensi", + "upload.error.formSize": "Berkas unggahan mencapai acuan MAX_FILE_SIZE yang diatur di formulir", + "upload.error.iniPostSize": "Berkas unggahan mencapai acuan post_max_size di php.ini", + "upload.error.iniSize": "Berkas unggahan mencapai acuan upload_max_filesize di php.ini", + "upload.error.noFile": "Tidak ada berkas diunggah", + "upload.error.noFiles": "Tidak ada berkas diunggah", + "upload.error.partial": "Berkas unggahan hanya berhasil diunggah sebagian", + "upload.error.tmpDir": "Folder sementara tidak ada", + "upload.errors": "Kesalahan", + "upload.progress": "Mengunggah…", + + "url": "Url", + "url.placeholder": "https://contoh.com", + + "user": "Pengguna", + "user.blueprint": "Anda dapat mendefinisikan bagian tambahan dan bidang formulir untuk peran pengguna ini di /site/blueprints/users/{role}.yml", + "user.changeEmail": "Ubah surel", + "user.changeLanguage": "Ubah bahasa", + "user.changeName": "Ubah nama pengguna ini", + "user.changePassword": "Ubah sandi", + "user.changePassword.new": "Sandi baru", + "user.changePassword.new.confirm": "Konfirmasi sandi baru…", + "user.changeRole": "Ubah peran", + "user.changeRole.select": "Pilih peran baru", + "user.create": "Tambah pengguna baru", + "user.delete": "Hapus pengguna ini", + "user.delete.confirm": "Anda yakin menghapus
{email}?", + + "users": "Pengguna", + + "version": "Versi", + + "view.account": "Akun Anda", + "view.installation": "Pemasangan", + "view.resetPassword": "Atur ulang sandi", + "view.settings": "Pengaturan", + "view.site": "Situs", + "view.users": "Pengguna", + + "welcome": "Selamat datang", + "year": "Tahun", + "yes": "ya" +} diff --git a/kirby/i18n/translations/it.json b/kirby/i18n/translations/it.json new file mode 100644 index 0000000..f3bb130 --- /dev/null +++ b/kirby/i18n/translations/it.json @@ -0,0 +1,527 @@ +{ + "add": "Aggiungi", + "avatar": "Immagine del profilo", + "back": "Indietro", + "cancel": "Annulla", + "change": "Cambia", + "close": "Chiudi", + "confirm": "OK", + "collapse": "Comprimi", + "collapse.all": "Comprimi tutto", + "copy": "Copia", + "create": "Crea", + + "date": "Data", + "date.select": "Scegli una data", + + "day": "Giorno", + "days.fri": "Ve", + "days.mon": "Lu", + "days.sat": "Sa", + "days.sun": "Do", + "days.thu": "Gi", + "days.tue": "Ma", + "days.wed": "Me", + + "delete": "Elimina", + "delete.all": "Elimina tutti", + "dimensions": "Dimensioni", + "disabled": "Disabilitato", + "discard": "Abbandona", + "download": "Scarica", + "duplicate": "Duplica", + "edit": "Modifica", + "expand": "Espandi", + "expand.all": "Espandi tutto", + + "dialog.files.empty": "Nessun file selezionabile", + "dialog.pages.empty": "Nessuna pagina selezionabile", + "dialog.users.empty": "Nessuno user selezionabile", + + "email": "Email", + "email.placeholder": "mail@esempio.com", + + "error.access.code": "Codice non valido", + "error.access.login": "Login Invalido", + "error.access.panel": "Non ti è permesso accedere al pannello", + "error.access.view": "Non ti è permesso accedere a questa parte del pannello", + + "error.avatar.create.fail": "Non è stato possibile caricare l'immagine del profilo", + "error.avatar.delete.fail": "Non è stato possibile eliminare l'immagine del profilo", + "error.avatar.dimensions.invalid": "Per favore mantieni l'altezza e la larghezza dell'immagine del profilo inferiore ai 3000 pixel", + "error.avatar.mime.forbidden": "L'immagine del profilo dev'essere un file JPEG o PNG", + + "error.blueprint.notFound": "Non è stato possibile caricare il blueprint \"{name}\"", + + "error.blocks.max.plural": "Non puoi aggiungere più di {max} blocchi", + "error.blocks.max.singular": "Non puoi aggiungere più di un blocco", + "error.blocks.min.plural": "Devi aggiungere almeno {min} blocchi", + "error.blocks.min.singular": "Devi aggiungere almeno un blocco", + "error.blocks.validation": "C'è un errore nel blocco {index}", + + "error.email.preset.notFound": "Non è stato possibile trovare il preset email \"{name}\"", + + "error.field.converter.invalid": "Convertitore \"{converter}\" non valido", + + "error.file.changeName.empty": "Il nome non dev'essere vuoto", + "error.file.changeName.permission": "Non ti è permesso modificare il nome di \"{filename}\"", + "error.file.duplicate": "Un file con il nome \"{filename}\" esiste già", + "error.file.extension.forbidden": "L'estensione \"{extension}\" non è consentita", + "error.file.extension.invalid": "Invalid extension: {extension}", + "error.file.extension.missing": "Il file \"{filename}\" non ha estensione", + "error.file.maxheight": "L'immagine non dev'essere più alta di {height} pixel", + "error.file.maxsize": "Il file è troppo pesante", + "error.file.maxwidth": "L'immagine non dev'essere più larga di {width} pixel", + "error.file.mime.differs": "Il file caricato dev'essere dello stesso MIME type \"{mime}\"", + "error.file.mime.forbidden": "Il MIME type \"{mime}\" non è consentito", + "error.file.mime.invalid": "Tipo mime non valido: {mime}", + "error.file.mime.missing": "Il MIME type per \"{filename}\" non può essere rilevato", + "error.file.minheight": "L'immagine dev'essere alta almeno {height} pixel", + "error.file.minsize": "Il file è troppo leggero", + "error.file.minwidth": "L'immagine dev'essere larga almeno {height} pixel", + "error.file.name.missing": "Il nome del file non può essere vuoto", + "error.file.notFound": "Il file non \u00e8 stato trovato", + "error.file.orientation": "L'imaggine dev'essere orientata in \"{orientation}\"", + "error.file.type.forbidden": "Non ti è permesso caricare file {type}", + "error.file.type.invalid": "Invalid file type: {type}", + "error.file.undefined": "Il file non \u00e8 stato trovato", + + "error.form.incomplete": "Correggi tutti gli errori nel form...", + "error.form.notSaved": "Non è stato possibile salvare il form", + + "error.language.code": "Inserisci un codice valido per la lingua", + "error.language.duplicate": "La lingua esiste già", + "error.language.name": "Inserisci un nome valido per la lingua", + + "error.layout.validation.block": "There's an error in block {blockIndex} in layout {layoutIndex}", + "error.layout.validation.settings": "There's an error in layout {index} settings", + + "error.license.format": "Inserisci un codice di licenza valido", + "error.license.email": "Inserisci un indirizzo email valido", + "error.license.verification": "Non è stato possibile verificare la licenza", + + "error.page.changeSlug.permission": "Non ti è permesso cambiare l'URL di \"{slug}\"", + "error.page.changeStatus.incomplete": "La pagina contiene errori e non può essere pubblicata", + "error.page.changeStatus.permission": "Lo stato di questa pagina non può essere cambiato", + "error.page.changeStatus.toDraft.invalid": "La pagina \"{slug}\" non può essere convertita in bozza", + "error.page.changeTemplate.invalid": "Il template della pagina \"{slug}\" non può essere cambiato", + "error.page.changeTemplate.permission": "Non ti è permesso modificare il template di \"{slug}\"", + "error.page.changeTitle.empty": "Il titolo non può essere vuoto", + "error.page.changeTitle.permission": "Non ti è permesso modificare il titolo di \"{slug}\"", + "error.page.create.permission": "Non ti è permesso creare \"{slug}\"", + "error.page.delete": "La pagina \"{slug}\" non può essere eliminata", + "error.page.delete.confirm": "Inserisci il titolo della pagina per confermare", + "error.page.delete.hasChildren": "La pagina ha sottopagine e non può essere eliminata", + "error.page.delete.permission": "Non ti è permesso eliminare \"{slug}\"", + "error.page.draft.duplicate": "Una bozza di pagina con l'URL \"{slug}\" esiste già", + "error.page.duplicate": "Una pagina con l'URL \"{slug}\" esiste già", + "error.page.duplicate.permission": "Non ti è permesso duplicare \"{slug}\"", + "error.page.notFound": "La pagina \"{slug}\" non è stata trovata", + "error.page.num.invalid": "Inserisci un numero di ordinamento valido. I numeri non devono essere negativi", + "error.page.slug.invalid": "Please enter a valid URL appendix", + "error.page.slug.maxlength": "Lo \"slug\" dev'essere più corto di \"{length}\" caratteri", + "error.page.sort.permission": "La pagina \"{slug}\" non può essere ordinata", + "error.page.status.invalid": "Imposta uno stato valido per la pagina", + "error.page.undefined": "La pagina non \u00e8 stata trovata", + "error.page.update.permission": "Non ti è permesso modificare \"{slug}\"", + + "error.section.files.max.plural": "Non puoi aggiungere più di {max} file alla sezione \"{section}\"", + "error.section.files.max.singular": "Non puoi aggiungere più di un file alla sezione \"{section}\"", + "error.section.files.min.plural": "La sezione \"{section}\" richiede almeno {min} file", + "error.section.files.min.singular": "La sezione \"{section}\" richiede almeno un file", + + "error.section.pages.max.plural": "Non puoi aggiungere più di {max} pagine alla sezione \"{section}\"", + "error.section.pages.max.singular": "Non puoi aggiungere più di una pagina alla sezione \"{section}\"", + "error.section.pages.min.plural": "La sezione \"{section}\" richiede almeno {min} pagine", + "error.section.pages.min.singular": "La sezione \"{section}\" richiede almeno una pagina", + + "error.section.notLoaded": "Non è stato possibile caricare la sezione \"{name}\"", + "error.section.type.invalid": "Il tipo di sezione \"{type}\" non è valido", + + "error.site.changeTitle.empty": "Il titolo non può essere vuoto", + "error.site.changeTitle.permission": "Non ti è permesso modificare il titolo del sito", + "error.site.update.permission": "Non ti è permesso modificare i contenuti globali del sito", + + "error.template.default.notFound": "Il template \"default\" non esiste", + + "error.user.changeEmail.permission": "Non ti è permesso modificare l'indirizzo email di \"{name}\"", + "error.user.changeLanguage.permission": "Non ti è permesso modificare la lingua per l'utente \"{name}\"", + "error.user.changeName.permission": "Non ti è permesso modificare il nome dell'utente \"{name}\"", + "error.user.changePassword.permission": "Non ti è permesso modificare la password dell'utente \"{name}\"", + "error.user.changeRole.lastAdmin": "Il ruolo dell'ultimo amministratore non può esser cambiato", + "error.user.changeRole.permission": "Non ti è permesso modificare il ruolo dell'utente \"{name}\"", + "error.user.changeRole.toAdmin": "Non ti è permesso assegnare il ruolo di amministratore ad altri utenti", + "error.user.create.permission": "Non ti è permesso creare questo utente", + "error.user.delete": "L'utente non pu\u00f2 essere eliminato", + "error.user.delete.lastAdmin": "L'ultimo amministratore non può essere eliminato", + "error.user.delete.lastUser": "L'ultimo utente non può essere eliminato", + "error.user.delete.permission": "Non ti \u00e8 permesso eliminare questo utente ", + "error.user.duplicate": "Esiste già un utente con l'indirizzo email \"{email}\"", + "error.user.email.invalid": "Inserisci un indirizzo email valido", + "error.user.language.invalid": "Inserisci una lingua valida", + "error.user.notFound": "L'utente non \u00e8 stato trovato", + "error.user.password.invalid": "Per favore inserisci una password valida. Le password devono essere lunghe almeno 8 caratteri", + "error.user.password.notSame": "Le password non corrispondono", + "error.user.password.undefined": "L'utente non ha una password", + "error.user.password.wrong": "Wrong password", + "error.user.role.invalid": "Inserisci un ruolo valido", + "error.user.update.permission": "Non ti è permesso aggiornare l'utente \"{name}\"", + + "error.validation.accepted": "Per favore conferma", + "error.validation.alpha": "Puoi inserire solo caratteri tra a-z", + "error.validation.alphanum": "Puoi inserire solo caratteri tra a-z e numeri 0-9", + "error.validation.between": "Inserisci un valore tra \"{min}\" e \"{max}\"", + "error.validation.boolean": "Per favore conferma o nega", + "error.validation.contains": "Inserisci un valore che contiene \"{needle}\"", + "error.validation.date": "Inserisci una data valida", + "error.validation.date.after": "Inserisci una data dopo il {date}", + "error.validation.date.before": "Inserisci una data prima del {date}", + "error.validation.date.between": "Inserisci una data tra {min} e {max}", + "error.validation.denied": "Per favore nega", + "error.validation.different": "Il valore non dev'essere \"{other}\"", + "error.validation.email": "Inserisci un indirizzo email valido", + "error.validation.endswith": "Il valore non deve finire con \"{end}\"", + "error.validation.filename": "Inserisci un nome del file valido", + "error.validation.in": "Inserisci uno dei seguenti valori: ({in})", + "error.validation.integer": "Inserisci un numero intero", + "error.validation.ip": "Inserisci un indirizzo IP valido", + "error.validation.less": "Inserisci un valore inferiore a {max}", + "error.validation.match": "Il valore non corrisponde al pattern previsto", + "error.validation.max": "Inserisci un valore inferiore o uguale a {max}", + "error.validation.maxlength": "Inserisci un testo più corto. (max. {max} caratteri)", + "error.validation.maxwords": "Non inserire più di {max} parola/e", + "error.validation.min": "Inserisci un valore superiore o uguale a {min}", + "error.validation.minlength": "Inserisci un testo più lungo. (min. {min} caratteri)", + "error.validation.minwords": "Inserisci almeno {min} parola/e", + "error.validation.more": "Inserisci un valore superiore a {min}", + "error.validation.notcontains": "Inserisci un valore che non contenga \"{needle}\"", + "error.validation.notin": "Non inserire nessuno dei valori seguenti: ({notIn})", + "error.validation.option": "Seleziona un'opzione valida", + "error.validation.num": "Inserisci un numero valido", + "error.validation.required": "Inserisci qualcosa", + "error.validation.same": "Inserisci \"{other}\"", + "error.validation.size": "La dimensione del valore dev'essere \"{size}\"", + "error.validation.startswith": "Il valore deve iniziare con \"{start}\"", + "error.validation.time": "Inserisci un orario valido", + "error.validation.time.after": "Inserisci un orario dopo le {time}", + "error.validation.time.before": "Inserisci un orario prima delle {time}", + "error.validation.time.between": "Inserisci un orario tra le {min} e le {max}", + "error.validation.url": "Inserisci un URL valido", + + "field.required": "Il campo è obbligatorio", + "field.blocks.changeType": "Cambia tipo", + "field.blocks.code.name": "Codice", + "field.blocks.code.language": "Lingua", + "field.blocks.code.placeholder": "Il tuo codice …", + "field.blocks.delete.confirm": "Vuoi veramente eliminare questo blocco?", + "field.blocks.delete.confirm.all": "Vuoi veramente eliminare tutti i blocchi? ", + "field.blocks.delete.confirm.selected": "Vuoi veramente eliminare i blocchi selezionati?", + "field.blocks.empty": "Nessun blocco inserito", + "field.blocks.fieldsets.label": "Seleziona il tipo di blocco …", + "field.blocks.gallery.name": "Galleria", + "field.blocks.gallery.images.empty": "Nessuna immagine inserita", + "field.blocks.gallery.images.label": "Immagini", + "field.blocks.heading.level": "Livello", + "field.blocks.heading.name": "Titolo", + "field.blocks.heading.text": "Testo", + "field.blocks.heading.placeholder": "Titolo …", + "field.blocks.image.alt": "Testo alternativo", + "field.blocks.image.caption": "Didascalia", + "field.blocks.image.crop": "Ritaglio", + "field.blocks.image.link": "Link", + "field.blocks.image.location": "Posizione", + "field.blocks.image.name": "Immagine", + "field.blocks.image.placeholder": "Seleziona un'immagine", + "field.blocks.image.ratio": "Rapporto", + "field.blocks.image.url": "Image URL", + "field.blocks.list.name": "Lista", + "field.blocks.markdown.name": "Markdown", + "field.blocks.markdown.label": "Testo", + "field.blocks.markdown.placeholder": "Markdown …", + "field.blocks.quote.name": "Citazione", + "field.blocks.quote.text.label": "Testo", + "field.blocks.quote.text.placeholder": "Citazione …", + "field.blocks.quote.citation.label": "Fonte", + "field.blocks.quote.citation.placeholder": "di …", + "field.blocks.text.name": "Testo", + "field.blocks.text.placeholder": "Testo …", + "field.blocks.video.caption": "Didascalia", + "field.blocks.video.name": "Video", + "field.blocks.video.placeholder": "Inserisci un URL di un video", + "field.blocks.video.url.label": "URL Video", + "field.blocks.video.url.placeholder": "https://youtube.com/?v=", + + "field.files.empty": "Nessun file selezionato", + + "field.layout.delete": "Elimina layout", + "field.layout.delete.confirm": "Vuoi veramente eliminare questo layout?", + "field.layout.empty": "Nessuna riga inserita", + "field.layout.select": "Scegli un layout", + + "field.pages.empty": "Nessuna pagina selezionata", + "field.structure.delete.confirm": "Vuoi veramente eliminare questo elemento?", + "field.structure.empty": "Non ci sono ancora elementi.", + "field.users.empty": "Nessun utente selezionato", + + "file.blueprint": "Questo file non ha ancora un \"blueprint\". Puoi impostarne uno in /site/blueprints/{template}.yml", + "file.delete.confirm": "Sei sicuro di voler eliminare questo file?", + "file.sort": "Cambia posizione", + + "files": "Files", + "files.empty": "Nessun file caricato", + + "hide": "Nascondi", + "hour": "Ora", + "insert": "Inserisci", + "insert.after": "Inserisci dopo", + "insert.before": "Inserisci prima", + "install": "Installa", + + "installation": "Installazione", + "installation.completed": "Il pannello è stato installato", + "installation.disabled": "L'installazione del pannello è disabilitata di default sui server pubblici. Esegui l'installazione in locale oppure abilitala usando l'opzione panel.install.", + "installation.issues.accounts": "/site/accounts non esiste o non dispone dei permessi di scrittura", + "installation.issues.content": "La cartella /content non esiste o non dispone dei permessi di scrittura", + "installation.issues.curl": "È necessaria l'estensione CURL", + "installation.issues.headline": "Il pannello non può esser installato", + "installation.issues.mbstring": "È necessaria l'estensione MB String", + "installation.issues.media": "La cartella /media non esiste o non dispone dei permessi di scrittura", + "installation.issues.php": "Assicurati di utilizzare PHP 7.1+", + "installation.issues.server": "Kirby necessita di Apache, Nginx o Caddy", + "installation.issues.sessions": "La cartella /site/sessionsnon esiste o non dispone dei permessi di scrittura", + + "language": "Lingua", + "language.code": "Codice", + "language.convert": "Imposta come predefinito", + "language.convert.confirm": "

Sei sicuro di voler convertire {name} nella lingua predefinita? Questa operazione non può essere annullata.

Se {name} non contiene tutte le traduzioni, non ci sarà più una versione alternativa valida e parti del sito potrebbero rimanere vuote.

", + "language.create": "Aggiungi una nuova lingua", + "language.delete.confirm": "Sei sicuro di voler eliminare la lingua {name} con tutte le traduzioni? Non sarà possibile annullare!", + "language.deleted": "La lingua è stata eliminata", + "language.direction": "Direzione di lettura", + "language.direction.ltr": "Sinistra a destra", + "language.direction.rtl": "Destra a sinistra", + "language.locale": "Stringa \"PHP locale\"", + "language.locale.warning": "Stai usando una impostazione personalizzata per il locale. Modificalo nel file della lingua situato in /site/languages", + "language.name": "Nome", + "language.updated": "La lingua è stata aggiornata", + + "languages": "Lingue", + "languages.default": "Lingua di default", + "languages.empty": "Non ci sono lingue impostate", + "languages.secondary": "Lingue secondarie", + "languages.secondary.empty": "Non ci sono lingue secondarie impostate", + + "license": "Licenza di Kirby", + "license.buy": "Acquista una licenza", + "license.register": "Registra", + "license.register.help": "Hai ricevuto il codice di licenza tramite email dopo l'acquisto. Per favore inseriscilo per registrare Kirby.", + "license.register.label": "Inserisci il codice di licenza", + "license.register.success": "Ti ringraziamo per aver supportato Kirby", + "license.unregistered": "Questa è una versione demo di Kirby non registrata", + + "link": "Link", + "link.text": "Testo del link", + + "loading": "Caricamento", + + "lock.unsaved": "Modifiche non salvate", + "lock.unsaved.empty": "Non ci sono altre modifiche non salvate", + "lock.isLocked": "Modifiche non salvate di {email}", + "lock.file.isLocked": "Il file viene attualmente modificato da {email} e non può essere cambiato.", + "lock.page.isLocked": "la pagina viene attualmente modificata da {email} e non può essere cambiata.", + "lock.unlock": "Sblocca", + "lock.isUnlocked": "Un altro utente ha sovrascritto le tue modifiche non salvate. Puoi scaricarle per recuperarle e quindi incorporarle manualmente. ", + + "login": "Accedi", + "login.code.label.login": "Codice di accesso", + "login.code.label.password-reset": "Codice per reimpostare la password", + "login.code.placeholder.email": "000 000", + "login.code.text.email": "Qualora il tuo indirizzo email fosse registrato, il codice richiesto è stato inviato tramite email.", + "login.email.login.body": "Ciao {user.nameOrEmail},\n\nHai recentemente richiesto un codice di accesso per il pannello Kirby.\nIl seguente codice sarà valido per {timeout} minuti:\n\n{code}\n\nSe non hai richiesto un codice di accesso, per favore ignora questa email o contatta il tuo amministratore se hai domande. \nPer motivi di sicurezza, per favore NON inoltrare questa email.", + "login.email.login.subject": "Il tuo codice di accesso", + "login.email.password-reset.body": "Ciao {user.nameOrEmail},\n\nHai recentemente richiesto un codice per reimpostare la tua password per il pannello Kirby.\nIl seguente codice sarà valido per {timeout} minuti:\n\n{code}\n\nSe non hai richiesto questo codice, per favore ignora questa email o contatta il tuo amministratore se hai domande. \nPer motivi di sicurezza, per favore NON inoltrare questa email.", + "login.email.password-reset.subject": "Il tuo codice di reimpostazione della password", + "login.remember": "Resta collegato", + "login.reset": "Reimposta la password", + "login.toggleText.code.email": "Accedi tramite email", + "login.toggleText.code.email-password": "Accedi con la password", + "login.toggleText.password-reset.email": "Hai dimenticato la password?", + "login.toggleText.password-reset.email-password": "← Torna al login", + + "logout": "Esci", + + "menu": "Menu", + "meridiem": "AM/PM", + "mime": "MIME Type", + "minutes": "Minuti", + + "month": "Mese", + "months.april": "Aprile", + "months.august": "Agosto", + "months.december": "Dicembre", + "months.february": "Febbraio", + "months.january": "Gennaio", + "months.july": "Luglio", + "months.june": "Giugno", + "months.march": "Marzo", + "months.may": "Maggio", + "months.november": "Novembre", + "months.october": "Ottobre", + "months.september": "Settembre", + + "more": "Di più", + "name": "Nome", + "next": "Prossimo", + "no": "no", + "off": "off", + "on": "on", + "open": "Apri", + "open.newWindow": "Open in new window", + "options": "Opzioni", + "options.none": "Nessuna opzione", + + "orientation": "Orientamento", + "orientation.landscape": "Orizzontale", + "orientation.portrait": "Verticale", + "orientation.square": "Quadrato", + + "page.blueprint": "Questa pagina non ha ancora un \"blueprint\". Puoi impostarne uno in /site/blueprints/{template}.yml", + "page.changeSlug": "Modifica URL", + "page.changeSlug.fromTitle": "Crea in base al titolo", + "page.changeStatus": "Cambia stato", + "page.changeStatus.position": "Scegli una posizione", + "page.changeStatus.select": "Seleziona un nuovo stato", + "page.changeTemplate": "Cambia template", + "page.delete.confirm": "Sei sicuro di voler eliminare questa pagina?", + "page.delete.confirm.subpages": "Questa pagina ha sottopagine.
Anche tutte le sottopagine verranno eliminate.", + "page.delete.confirm.title": "Inserisci il titolo della pagina per confermare", + "page.draft.create": "Crea bozza", + "page.duplicate.appendix": "Copia", + "page.duplicate.files": "Copia file", + "page.duplicate.pages": "Copia pagine", + "page.sort": "Cambia posizione", + "page.status": "Stato", + "page.status.draft": "Bozza", + "page.status.draft.description": "Questa pagina è una bozza ed è visibile soltanto agli utenti registrati o tramite link segreto", + "page.status.listed": "Pubblico", + "page.status.listed.description": "La pagina è pubblicata per tutti", + "page.status.unlisted": "Non in elenco", + "page.status.unlisted.description": "La pagina è accessibile soltanto tramite URL", + + "pages": "Pagine", + "pages.empty": "Nessuna pagina", + "pages.status.draft": "Bozza", + "pages.status.listed": "Pubblicato", + "pages.status.unlisted": "Non in elenco", + + "pagination.page": "Pagina", + + "password": "Password", + "pixel": "Pixel", + "prev": "Precedente", + "preview": "Anteprima", + "remove": "Rimuovi", + "rename": "Rinomina", + "replace": "Sostituisci", + "retry": "Riprova", + "revert": "Abbandona", + "revert.confirm": "Sei sicuro di voler cancellare tutte le modifiche non salvate?", + + "role": "Ruolo", + "role.admin.description": "L'amministratore ha tutti i permessi", + "role.admin.title": "Amministratore", + "role.all": "Tutti", + "role.empty": "Non ci sono utenti con questo ruolo", + "role.description.placeholder": "Nessuna descrizione", + "role.nobody.description": "Questo è un ruolo \"fallback\" senza permessi", + "role.nobody.title": "Nessuno", + + "save": "Salva", + "search": "Cerca", + "search.min": "Inserisci almeno {min} caratteri per la ricerca", + "search.all": "Mostra tutti", + "search.results.none": "Nessun risultato", + + "section.required": "La sezione è obbligatoria", + + "select": "Seleziona", + "settings": "Impostazioni", + "show": "Mostra", + "size": "Dimensioni", + "slug": "URL", + "sort": "Ordina", + "title": "Titolo", + "template": "Template", + "today": "Oggi", + + "site.blueprint": "Il sito non ha ancora un \"blueprint\". Puoi impostarne uno in /site/blueprints/site.yml", + + "toolbar.button.code": "Codice", + "toolbar.button.bold": "Grassetto", + "toolbar.button.email": "Email", + "toolbar.button.headings": "Titoli", + "toolbar.button.heading.1": "Titolo 1", + "toolbar.button.heading.2": "Titolo 2", + "toolbar.button.heading.3": "Titolo 3", + "toolbar.button.italic": "Corsivo", + "toolbar.button.file": "File", + "toolbar.button.file.select": "Seleziona un file", + "toolbar.button.file.upload": "Carica un file", + "toolbar.button.link": "Link", + "toolbar.button.strike": "Strike-through", + "toolbar.button.ol": "Elenco numerato", + "toolbar.button.underline": "Underline", + "toolbar.button.ul": "Elenco puntato", + + "translation.author": "Kirby Team, Roman Steiner, Manu Moreale", + "translation.direction": "ltr", + "translation.name": "Italiano", + "translation.locale": "it_IT", + + "upload": "Carica", + "upload.error.cantMove": "Non è stato possibile spostare il file caricato", + "upload.error.cantWrite": "Impossibile scrivere il file su disco", + "upload.error.default": "Impossibile caricare il file", + "upload.error.extension": "Caricamento del file interrotto per via dell'estensione", + "upload.error.formSize": "La dimensione del file caricato supera la direttiva MAX_FILE_SIZE specificata nel form", + "upload.error.iniPostSize": "La dimensione del file caricato supera la direttiva post_max_size specificata in php.ini", + "upload.error.iniSize": "La dimensione del file caricato supera la direttiva upload_max_filesize specificata in php.ini", + "upload.error.noFile": "Il file non è stato caricato", + "upload.error.noFiles": "Nessun file è stato caricato", + "upload.error.partial": "Il file è stato caricato solo parzialmente", + "upload.error.tmpDir": "Manca la cartella temporanea", + "upload.errors": "Errore", + "upload.progress": "Caricamento...", + + "url": "URL", + "url.placeholder": "https://esempio.com", + + "user": "Utente", + "user.blueprint": "Puoi definire sezioni e campi del form aggiuntivi per questo ruolo in /site/blueprints/users/{role}.yml", + "user.changeEmail": "Modifica email", + "user.changeLanguage": "Cambia lingua", + "user.changeName": "Rinomina questo utente", + "user.changePassword": "Cambia password", + "user.changePassword.new": "Nuova password", + "user.changePassword.new.confirm": "Conferma la nuova password...", + "user.changeRole": "Cambia ruolo", + "user.changeRole.select": "Seleziona un nuovo ruolo", + "user.create": "Aggiungi nuovo utente", + "user.delete": "Elimina questo utente", + "user.delete.confirm": "Sei sicuro di voler eliminare l'utente
{email}?", + + "users": "Utenti", + + "version": "Versione di Kirby", + + "view.account": "Il tuo account", + "view.installation": "Installazione", + "view.resetPassword": "Reimposta la password", + "view.settings": "Impostazioni", + "view.site": "Sito", + "view.users": "Utenti", + + "welcome": "Benvenuto", + "year": "Anno", + "yes": "yes" +} diff --git a/kirby/i18n/translations/ko.json b/kirby/i18n/translations/ko.json new file mode 100644 index 0000000..83258df --- /dev/null +++ b/kirby/i18n/translations/ko.json @@ -0,0 +1,527 @@ +{ + "add": "\ucd94\uac00", + "avatar": "\ud504\ub85c\ud544 \uc774\ubbf8\uc9c0", + "back": "복귀", + "cancel": "\ucde8\uc18c", + "change": "\ubcc0\uacbd", + "close": "\ub2eb\uae30", + "confirm": "확인", + "collapse": "접기", + "collapse.all": "모두 접기", + "copy": "복사", + "create": "등록", + + "date": "날짜", + "date.select": "날짜 지정", + + "day": "일", + "days.fri": "\uae08", + "days.mon": "\uc6d4", + "days.sat": "\ud1a0", + "days.sun": "\uc77c", + "days.thu": "\ubaa9", + "days.tue": "\ud654", + "days.wed": "\uc218", + + "delete": "\uc0ad\uc81c", + "delete.all": "모두 삭제", + "dimensions": "크기", + "disabled": "비활성화", + "discard": "무시", + "download": "다운로드", + "duplicate": "복제", + "edit": "\ud3b8\uc9d1", + "expand": "열기", + "expand.all": "모두 열기", + + "dialog.files.empty": "선택한 파일이 없습니다.", + "dialog.pages.empty": "선택한 페이지가 없습니다.", + "dialog.users.empty": "선택한 사용자가 없습니다.", + + "email": "\uc774\uba54\uc77c \uc8fc\uc18c", + "email.placeholder": "mail@example.com", + + "error.access.code": "코드가 올바르지 않습니다.", + "error.access.login": "로그인할 수 없습니다.", + "error.access.panel": "패널에 접근할 권한이 없습니다.", + "error.access.view": "패널에 접근할 권한이 없습니다.", + + "error.avatar.create.fail": "프로필 이미지를 업로드할 수 없습니다.", + "error.avatar.delete.fail": "\ud504\ub85c\ud544 \uc774\ubbf8\uc9c0\ub97c \uc0ad\uc81c\ud560 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4.", + "error.avatar.dimensions.invalid": "프로필 이미지의 너비와 높이를 3,000픽셀 이하로 설정하세요.", + "error.avatar.mime.forbidden": "프로필 이미지의 확장자(JPG, JPEG, PNG)를 확인하세요.", + + "error.blueprint.notFound": "블루프린트({name})를 불러올 수 없습니다.", + + "error.blocks.max.plural": "블록을 {max}개 이상 추가할 수 없습니다.", + "error.blocks.max.singular": "블록을 하나 이상 추가할 수 없습니다.", + "error.blocks.min.plural": "블록을 {min}개 이상 추가하세요.", + "error.blocks.min.singular": "블록을 하나 이상 추가하세요.", + "error.blocks.validation": "블록({index})이 올바르지 않습니다.", + + "error.email.preset.notFound": "기본 이메일 주소({name})가 없습니다.", + + "error.field.converter.invalid": "컨버터({converter})가 올바르지 않습니다.", + + "error.file.changeName.empty": "이름을 입력하세요.", + "error.file.changeName.permission": "파일명({filename})을 변경할 권한이 없습니다.", + "error.file.duplicate": "파일명이 같은 파일({filename})이 있습니다.", + "error.file.extension.forbidden": "이 확장자({extension})는 업로드할 수 없습니다.", + "error.file.extension.invalid": "확장자({extension})가 올바르지 않습니다.", + "error.file.extension.missing": "파일({filename})에 확장자가 없습니다.", + "error.file.maxheight": "이미지의 높이는 {height}픽셀을 초과할 수 없습니다.", + "error.file.maxsize": "파일이 너무 큽니다.", + "error.file.maxwidth": "이미지의 너비는 {width}픽셀을 초과할 수 없습니다.", + "error.file.mime.differs": "기존 파일과 MIME 형식({mime})이 다릅니다.", + "error.file.mime.forbidden": "이 MIME 형식({mime})은 업로드할 수 없습니다.", + "error.file.mime.invalid": "MIME 형식({mime})이 올바르지 않습니다.", + "error.file.mime.missing": "파일({filename})의 MIME 형식을 확인할 수 없습니다.", + "error.file.minheight": "{height}픽셀 이상으로 이미지의 높이를 설정하세요.", + "error.file.minsize": "파일이 너무 작습니다.", + "error.file.minwidth": "이미지의 너비를 {width}픽셀 이상으로 설정하세요.", + "error.file.name.missing": "파일명을 입력하세요.", + "error.file.notFound": "파일({filename})이 없습니다.", + "error.file.orientation": "이미지의 비율({orientation})을 확인하세요.", + "error.file.type.forbidden": "이 형식({type})의 파일을 업로드할 권한이 없습니다.", + "error.file.type.invalid": "파일의 형식({type})이 올바르지 않습니다.", + "error.file.undefined": "\ud30c\uc77c\uc774 \uc5c6\uc2b5\ub2c8\ub2e4.", + + "error.form.incomplete": "항목에 오류가 있습니다.", + "error.form.notSaved": "항목을 저장할 수 없습니다.", + + "error.language.code": "올바른 언어 코드를 입력하세요.", + "error.language.duplicate": "이미 등록한 언어입니다.", + "error.language.name": "올바른 언어명을 입력하세요.", + + "error.layout.validation.block": "레이아웃({layoutIndex})의 블록({blockIndex})을 확인하세요.", + "error.layout.validation.settings": "레이아웃({index})의 옵션을 확인하세요.", + + "error.license.format": "올바른 라이선스 키를 입력하세요.", + "error.license.email": "올바른 이메일 주소를 입력하세요.", + "error.license.verification": "라이선스 키가 올바르지 않습니다.", + + "error.page.changeSlug.permission": "고유 주소({slug})를 변경할 권한이 없습니다.", + "error.page.changeStatus.incomplete": "페이지를 공개할 수 없습니다.", + "error.page.changeStatus.permission": "페이지의 상태를 변경할 수 없습니다.", + "error.page.changeStatus.toDraft.invalid": "페이지({slug})의 상태를 초안으로 변경할 수 없습니다.", + "error.page.changeTemplate.invalid": "페이지({slug})의 템플릿을 변경할 수 없습니다.", + "error.page.changeTemplate.permission": "페이지({slug})의 템플릿을 변경할 권한이 없습니다.", + "error.page.changeTitle.empty": "제목을 입력하세요.", + "error.page.changeTitle.permission": "페이지({slug})의 제목을 변경할 권한이 없습니다.", + "error.page.create.permission": "페이지({slug})를 등록할 권한이 없습니다.", + "error.page.delete": "페이지({slug})를 삭제할 수 없습니다.", + "error.page.delete.confirm": "페이지를 삭제하려면 페이지의 제목을 입력하세요.", + "error.page.delete.hasChildren": "하위 페이지가 있는 페이지는 삭제할 수 없습니다.", + "error.page.delete.permission": "페이지({slug})를 삭제할 권한이 없습니다.", + "error.page.draft.duplicate": "고유 주소({slug})가 같은 초안 페이지가 있습니다.", + "error.page.duplicate": "고유 주소({slug})가 같은 페이지가 있습니다.", + "error.page.duplicate.permission": "페이지({slug})를 복제할 권한이 없습니다.", + "error.page.notFound": "페이지({slug})가 없습니다.", + "error.page.num.invalid": "올바른 정수를 입력하세요.", + "error.page.slug.invalid": "올바른 URL을 입력하세요.", + "error.page.slug.maxlength": "{length}자 이하로 고유 주소를 입력하세요.", + "error.page.sort.permission": "페이지({slug})를 정렬할 수 없습니다.", + "error.page.status.invalid": "올바른 상태를 설정하세요.", + "error.page.undefined": "\ud398\uc774\uc9c0\uac00 \uc5c6\uc2b5\ub2c8\ub2e4.", + "error.page.update.permission": "페이지({slug})를 변경할 권한이 없습니다.", + + "error.section.files.max.plural": "이 섹션({section})에는 파일을 {max}개 이상 추가할 수 없습니다.", + "error.section.files.max.singular": "이 섹션({section})에는 파일을 하나 이상 추가할 수 없습니다.", + "error.section.files.min.plural": "이 섹션({section})에는 파일이 {min}개 이상 필요합니다.", + "error.section.files.min.singular": "이 섹션({section})에는 파일이 하나 이상 필요합니다.", + + "error.section.pages.max.plural": "이 섹션({section})에는 페이지를 {max}개 이상 추가할 수 없습니다.", + "error.section.pages.max.singular": "이 섹션({section})에는 페이지를 하나 이상 추가할 수 없습니다.", + "error.section.pages.min.plural": "이 섹션({section})에는 페이지가 {min}개 이상 필요합니다.", + "error.section.pages.min.singular": "이 섹션({section})에는 페이지가 하나 이상 필요합니다.", + + "error.section.notLoaded": "섹션({name})을 확인할 수 없습니다.", + "error.section.type.invalid": "섹션의 형식({type})이 올바르지 않습니다.", + + "error.site.changeTitle.empty": "제목을 입력하세요.", + "error.site.changeTitle.permission": "사이트명을 변경할 권한이 없습니다.", + "error.site.update.permission": "사이트의 정보를 변경할 권한이 없습니다.", + + "error.template.default.notFound": "기본 템플릿이 없습니다.", + + "error.user.changeEmail.permission": "사용자({name})의 이메일 주소를 변경할 권한이 없습니다.", + "error.user.changeLanguage.permission": "사용자({name})의 언어를 변경할 권한이 없습니다.", + "error.user.changeName.permission": "사용자명({name})을 변경할 권한이 없습니다.", + "error.user.changePassword.permission": "사용자({name})의 암호를 변경할 권한이 없습니다.", + "error.user.changeRole.lastAdmin": "최종 관리자의 역할은 변경할 수 없습니다.", + "error.user.changeRole.permission": "사용자({name})의 역할을 변경할 권한이 없습니다.", + "error.user.changeRole.toAdmin": "다른 사용자를 관리자로 지정할 권한이 없습니다.", + "error.user.create.permission": "사용자를 등록할 권한이 없습니다.", + "error.user.delete": "사용자({name})를 삭제할 수 없습니다.", + "error.user.delete.lastAdmin": "\ucd5c\uc885 \uad00\ub9ac\uc790\ub294 \uc0ad\uc81c\ud560 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4.", + "error.user.delete.lastUser": "최종 사용자는 삭제할 수 없습니다.", + "error.user.delete.permission": "사용자({name})를 삭제할 권한이 없습니다.", + "error.user.duplicate": "이메일 주소({email})가 같은 사용자가 있습니다.", + "error.user.email.invalid": "올바른 이메일 주소를 입력하세요.", + "error.user.language.invalid": "올바른 언어를 입력하세요.", + "error.user.notFound": "사용자({name})가 없습니다.", + "error.user.password.invalid": "암호를 8자 이상으로 설정하세요.", + "error.user.password.notSame": "\uc554\ud638\ub97c \ud655\uc778\ud558\uc138\uc694.", + "error.user.password.undefined": "암호가 설정되지 않았습니다.", + "error.user.password.wrong": "암호가 올바르지 않습니다.", + "error.user.role.invalid": "올바른 역할을 지정하세요.", + "error.user.update.permission": "사용자({name})의 정보를 변경할 권한이 없습니다.", + + "error.validation.accepted": "확인하세요.", + "error.validation.alpha": "로마자(a~z)만 입력할 수 있습니다.", + "error.validation.alphanum": "로마자(a~z) 또는 숫자(0~9)만 입력할 수 있습니다.", + "error.validation.between": "{min}, {max} 사이의 값을 입력하세요.", + "error.validation.boolean": "확인하거나 취소하세요.", + "error.validation.contains": "{needle}에 포함된 값을 입력하세요.", + "error.validation.date": "올바른 날짜를 입력하세요.", + "error.validation.date.after": "{date} 이후 날짜를 입력하세요.", + "error.validation.date.before": "{date} 이전 날짜를 입력하세요.", + "error.validation.date.between": "{min}, {max} 사이의 날짜를 입력하세요.", + "error.validation.denied": "취소하세요.", + "error.validation.different": "{other}에 포함된 값은 입력할 수 없습니다.", + "error.validation.email": "올바른 이메일 주소를 입력하세요.", + "error.validation.endswith": "값은 다음으로 끝나야 합니다: {end}", + "error.validation.filename": "올바른 파일명을 입력하세요.", + "error.validation.in": "{in} 중 하나를 입력하세요.", + "error.validation.integer": "올바른 정수를 입력하세요.", + "error.validation.ip": "올바른 IP 주소를 입력하세요.", + "error.validation.less": "{max} 미만의 값을 입력하세요.", + "error.validation.match": "입력한 값이 예상 패턴과 일치하지 않습니다.", + "error.validation.max": "{max} 이하의 값을 입력하세요.", + "error.validation.maxlength": "{max}자 이하의 값을 입력하세요.", + "error.validation.maxwords": "{max}자 이하를 입력하세요.", + "error.validation.min": "{min} 이상의 값을 입력하세요.", + "error.validation.minlength": "{min}자 이상의 값을 입력하세요.", + "error.validation.minwords": "{min}자 이상을 입력하세요.", + "error.validation.more": "{min} 이상의 값을 입력하세요.", + "error.validation.notcontains": "{needle}에 포함된 값은 입력할 수 없습니다.", + "error.validation.notin": "{notIn}에 포함된 값은 입력할 수 없습니다.", + "error.validation.option": "올바른 옵션을 선택하세요.", + "error.validation.num": "올바른 숫자를 입력하세요.", + "error.validation.required": "해당 항목을 확인하세요.", + "error.validation.same": "이 값({other})을 입력하세요.", + "error.validation.size": "값의 크기({size})를 확인하세요. ", + "error.validation.startswith": "값은 다음으로 시작해야 합니다: {start}", + "error.validation.time": "올바른 시각을 입력하세요.", + "error.validation.time.after": "{time} 이후 시각을 입력하세요.", + "error.validation.time.before": "{time} 이전 시각을 입력하세요.", + "error.validation.time.between": "{min}, {max} 사이의 시각을 입력하세요.", + "error.validation.url": "올바른 URL을 입력하세요.", + + "field.required": "필드를 채우세요.", + "field.blocks.changeType": "유형 변경", + "field.blocks.code.name": "코드", + "field.blocks.code.language": "언어", + "field.blocks.code.placeholder": "코드", + "field.blocks.delete.confirm": "블록을 삭제할까요?", + "field.blocks.delete.confirm.all": "모든 블록을 삭제할까요?", + "field.blocks.delete.confirm.selected": "선택한 블록을 삭제할까요?", + "field.blocks.empty": "블록이 없습니다.", + "field.blocks.fieldsets.label": "블록의 유형을 선택하세요.", + "field.blocks.gallery.name": "갤러리", + "field.blocks.gallery.images.empty": "이미지가 없습니다.", + "field.blocks.gallery.images.label": "이미지", + "field.blocks.heading.level": "단계", + "field.blocks.heading.name": "제목", + "field.blocks.heading.text": "제목", + "field.blocks.heading.placeholder": "제목", + "field.blocks.image.alt": "대체 텍스트", + "field.blocks.image.caption": "캡션", + "field.blocks.image.crop": "자르기", + "field.blocks.image.link": "링크", + "field.blocks.image.location": "위치", + "field.blocks.image.name": "이미지", + "field.blocks.image.placeholder": "이미지 선택", + "field.blocks.image.ratio": "비율", + "field.blocks.image.url": "이미지 URL", + "field.blocks.list.name": "목록", + "field.blocks.markdown.name": "마크다운", + "field.blocks.markdown.label": "마크다운", + "field.blocks.markdown.placeholder": "마크다운", + "field.blocks.quote.name": "인용문", + "field.blocks.quote.text.label": "인용문", + "field.blocks.quote.text.placeholder": "인용문", + "field.blocks.quote.citation.label": "출처", + "field.blocks.quote.citation.placeholder": "출처", + "field.blocks.text.name": "텍스트", + "field.blocks.text.placeholder": "텍스트", + "field.blocks.video.caption": "캡션", + "field.blocks.video.name": "영상", + "field.blocks.video.placeholder": "영상 URL 입력", + "field.blocks.video.url.label": "영상 URL", + "field.blocks.video.url.placeholder": "https://youtube.com/?v=", + + "field.files.empty": "선택한 파일이 없습니다.", + + "field.layout.delete": "레이아웃 삭제", + "field.layout.delete.confirm": "레이아웃을 삭제할까요?", + "field.layout.empty": "레이아웃이 없습니다.", + "field.layout.select": "레이아웃 선택", + + "field.pages.empty": "선택한 페이지가 없습니다.", + "field.structure.delete.confirm": "이 항목을 삭제할까요?", + "field.structure.empty": "항목이 없습니다.", + "field.users.empty": "선택한 사용자가 없습니다.", + + "file.blueprint": "블루프린트(/site/blueprints/{template}.yml)를 설정하세요.", + "file.delete.confirm": "파일({filename})을 삭제할까요?", + "file.sort": "순서 변경", + + "files": "파일", + "files.empty": "파일이 없습니다.", + + "hide": "숨기기", + "hour": "시", + "insert": "\uc0bd\uc785", + "insert.after": "뒤에 삽입", + "insert.before": "앞에 삽입", + "install": "설치", + + "installation": "설치", + "installation.completed": "패널을 설치했습니다.", + "installation.disabled": "패널 설치 관리자는 로컬 서버에서 실행하거나 panel.install 옵션을 설정하세요.", + "installation.issues.accounts": "폴더(/site/accounts)에 쓰기 권한이 없습니다.", + "installation.issues.content": "폴더(/content)에 쓰기 권한이 없습니다.", + "installation.issues.curl": "cURL 확장 모듈이 필요합니다.", + "installation.issues.headline": "패널을 설치할 수 없습니다.", + "installation.issues.mbstring": "MB String 확장 모듈이 필요합니다.", + "installation.issues.media": "폴더(/media)에 쓰기 권한이 없습니다.", + "installation.issues.php": "PHP 버전이 7 이상인지 확인하세요.", + "installation.issues.server": "Kirby를 실행하려면 Apache, Nginx, 또는 Caddy가 필요합니다.", + "installation.issues.sessions": "폴더(/site/sessions)에 쓰기 권한이 없습니다.", + + "language": "\uc5b8\uc5b4", + "language.code": "언어 코드", + "language.convert": "기본 언어로 지정", + "language.convert.confirm": "이 언어({name})를 기본 언어로 지정할까요? 지정한 뒤에는 복원할 수 없으며, 이 언어로 번역되지 않은 항목은 올바르게 표시되지 않을 수 있습니다.", + "language.create": "새 언어 추가", + "language.delete.confirm": "언어({name})를 삭제할까요? 삭제한 뒤에는 복원할 수 없습니다.", + "language.deleted": "언어를 삭제했습니다.", + "language.direction": "읽기 방향", + "language.direction.ltr": "왼쪽에서 오른쪽", + "language.direction.rtl": "오른쪽에서 왼쪽", + "language.locale": "PHP 로캘 문자열", + "language.locale.warning": "사용자 지정 로캘을 사용 중입니다. 폴더(/site/languages)의 언어 파일을 수정하세요.", + "language.name": "언어명", + "language.updated": "언어를 변경했습니다.", + + "languages": "언어", + "languages.default": "기본 언어", + "languages.empty": "언어가 없습니다.", + "languages.secondary": "보조 언어", + "languages.secondary.empty": "보조 언어가 없습니다.", + + "license": "라이선스", + "license.buy": "라이선스 구매", + "license.register": "등록", + "license.register.help": "Kirby를 등록하려면 이메일로 전송받은 라이선스 코드와 이메일 주소를 입력하세요.", + "license.register.label": "라이선스 코드를 입력하세요.", + "license.register.success": "Kirby와 함께해주셔서 감사합니다.", + "license.unregistered": "Kirby가 등록되지 않았습니다.", + + "link": "\uc77c\ubc18 \ub9c1\ud06c", + "link.text": "\ubb38\uc790", + + "loading": "로딩 중…", + + "lock.unsaved": "저장되지 않은 항목이 있습니다.", + "lock.unsaved.empty": "모든 페이지를 저장했습니다.", + "lock.isLocked": "다른 사용자({email})가 수정한 사항이 저장되지 않았습니다.", + "lock.file.isLocked": "파일을 편집할 수 없습니다. 다른 사용자({email})가 편집 중입니다.", + "lock.page.isLocked": "페이지를 편집할 수 없습니다. 다른 사용자({email})가 편집 중입니다.", + "lock.unlock": "잠금 해제", + "lock.isUnlocked": "다른 사용자가 이미 내용을 수정했으므로 현재 내용이 올바르게 저장되지 않았습니다. 저장되지 않은 내용은 다운로드해 수동으로 대치할 수 있습니다.", + + "login": "\ub85c\uadf8\uc778", + "login.code.label.login": "로그인 코드", + "login.code.label.password-reset": "암호 초기화 코드", + "login.code.placeholder.email": "000 000", + "login.code.text.email": "입력한 이메일 주소로 코드를 전송했습니다.", + "login.email.login.body": "{user.nameOrEmail} 님,\n\nKirby 패널에서 요청한 로그인 코드는 다음과 같습니다. 로그인 코드는 {timeout}분 동안 유효합니다.\n\n{code}\n\n로그인 코드를 요청한 적이 없다면, 이 이메일을 무시하거나 관리자에게 문의하세요. 보안을 위해 이 이메일은 다른 사람과 공유하지 마세요.", + "login.email.login.subject": "로그인 코드", + "login.email.password-reset.body": "{user.nameOrEmail} 님,\n\nKirby 패널에서 요청한 로그인 코드는 다음과 같습니다. 로그인 코드는 {timeout}분 동안 유효합니다.\n\n{code}\n\n로그인 코드를 요청한 적이 없다면, 이 이메일을 무시하거나 관리자에게 문의하세요. 보안을 위해 이 이메일은 다른 사람과 공유하지 마세요.", + "login.email.password-reset.subject": "암호 초기화 코드", + "login.remember": "로그인 유지", + "login.reset": "암호 초기화", + "login.toggleText.code.email": "이메일 주소로 로그인", + "login.toggleText.code.email-password": "암호로 로그인", + "login.toggleText.password-reset.email": "암호 찾기", + "login.toggleText.password-reset.email-password": "로그인 화면으로", + + "logout": "\ub85c\uadf8\uc544\uc6c3", + + "menu": "메뉴", + "meridiem": "오전/오후", + "mime": "MIME 형식", + "minutes": "분", + + "month": "월", + "months.april": "4\uc6d4", + "months.august": "8\uc6d4", + "months.december": "12\uc6d4", + "months.february": "2월", + "months.january": "1\uc6d4", + "months.july": "7\uc6d4", + "months.june": "6\uc6d4", + "months.march": "3\uc6d4", + "months.may": "5\uc6d4", + "months.november": "11\uc6d4", + "months.october": "10\uc6d4", + "months.september": "9\uc6d4", + + "more": "더 보기", + "name": "이름", + "next": "다음", + "no": "no", + "off": "끔", + "on": "켬", + "open": "열기", + "open.newWindow": "Open in new window", + "options": "옵션", + "options.none": "옵션이 없습니다.", + + "orientation": "비율", + "orientation.landscape": "가로로 긴 사각형", + "orientation.portrait": "세로로 긴 사각형", + "orientation.square": "정사각형", + + "page.blueprint": "블루프린트(/site/blueprints/{template}.yml)를 설정하세요.", + "page.changeSlug": "고유 주소 변경", + "page.changeSlug.fromTitle": "제목에서 가져오기", + "page.changeStatus": "상태 변경", + "page.changeStatus.position": "순서를 지정하세요.", + "page.changeStatus.select": "새 상태 선택", + "page.changeTemplate": "템플릿 변경", + "page.delete.confirm": "페이지({title})를 삭제할까요?", + "page.delete.confirm.subpages": "페이지에 하위 페이지가 있습니다. 모든 하위 페이지가 삭제됩니다.", + "page.delete.confirm.title": "페이지의 제목을 입력하세요.", + "page.draft.create": "초안 등록", + "page.duplicate.appendix": "복사", + "page.duplicate.files": "파일 복사", + "page.duplicate.pages": "페이지 복사", + "page.sort": "순서 변경", + "page.status": "상태", + "page.status.draft": "초안", + "page.status.draft.description": "로그인한 사용자나 URL을 통해 접근할 수 있습니다.", + "page.status.listed": "공개", + "page.status.listed.description": "누구나 읽을 수 있습니다.", + "page.status.unlisted": "비공개", + "page.status.unlisted.description": "URL을 통해 접근할 수 있습니다.", + + "pages": "페이지", + "pages.empty": "페이지가 없습니다.", + "pages.status.draft": "초안", + "pages.status.listed": "발행", + "pages.status.unlisted": "비공개", + + "pagination.page": "페이지", + + "password": "\uc554\ud638", + "pixel": "픽셀", + "prev": "이전", + "preview": "미리 보기", + "remove": "삭제", + "rename": "이름 변경", + "replace": "\uad50\uccb4", + "retry": "\ub2e4\uc2dc \uc2dc\ub3c4", + "revert": "복원", + "revert.confirm": "저장되지 않은 내용을 삭제할까요?", + + "role": "역할", + "role.admin.description": "관리자는 모든 권한이 있습니다.", + "role.admin.title": "관리자", + "role.all": "전체", + "role.empty": "이 역할에 해당하는 사용자가 없습니다.", + "role.description.placeholder": "설명이 없습니다.", + "role.nobody.description": "대체 사용자는 아무 권한이 없습니다.", + "role.nobody.title": "사용자가 없습니다.", + + "save": "\uc800\uc7a5", + "search": "검색", + "search.min": "{min}자 이상 입력하세요.", + "search.all": "모두 보기", + "search.results.none": "해당하는 결과가 없습니다.", + + "section.required": "섹션이 필요합니다.", + + "select": "선택", + "settings": "설정", + "show": "보기", + "size": "크기", + "slug": "고유 주소", + "sort": "정렬", + "title": "제목", + "template": "\ud15c\ud50c\ub9bf", + "today": "오늘", + + "site.blueprint": "블루프린트(/site/blueprints/site.yml)를 설정하세요.", + + "toolbar.button.code": "코드", + "toolbar.button.bold": "강조 1", + "toolbar.button.email": "이메일 주소", + "toolbar.button.headings": "제목", + "toolbar.button.heading.1": "제목 1", + "toolbar.button.heading.2": "제목 2", + "toolbar.button.heading.3": "제목 3", + "toolbar.button.italic": "강조 2", + "toolbar.button.file": "파일", + "toolbar.button.file.select": "파일 선택", + "toolbar.button.file.upload": "파일 업로드", + "toolbar.button.link": "링크", + "toolbar.button.strike": "Strike-through", + "toolbar.button.ol": "숫자 목록", + "toolbar.button.underline": "Underline", + "toolbar.button.ul": "기호 목록", + + "translation.author": "Kirby 팀", + "translation.direction": "LTR", + "translation.name": "한국어", + "translation.locale": "ko_KR", + + "upload": "업로드", + "upload.error.cantMove": "파일을 이동할 수 없습니다.", + "upload.error.cantWrite": "디스크를 읽을 수 없습니다.", + "upload.error.default": "파일을 업로드할 수 없습니다.", + "upload.error.extension": "파일 확장자를 확인하세요.", + "upload.error.formSize": "업로드한 파일이 허용된 크기(MAX_FILE_SIZE)를 초과했습니다.", + "upload.error.iniPostSize": "업로드한 파일이 PHP 환경 설정 파일(php.ini)에서 허용된 크기(post_max_size)를 초과했습니다.", + "upload.error.iniSize": "업로드한 파일이 PHP 환경 설정 파일(php.ini)에서 허용된 크기(upload_max_filesize)를 초과했습니다.", + "upload.error.noFile": "업로드한 파일이 없습니다.", + "upload.error.noFiles": "업로드한 파일이 없습니다.", + "upload.error.partial": "일부 파일을 업로드했습니다.", + "upload.error.tmpDir": "임시 폴더가 없습니다.", + "upload.errors": "오류", + "upload.progress": "업로드 중…", + + "url": "URL", + "url.placeholder": "https://example.com", + + "user": "사용자", + "user.blueprint": "블루프린트(/site/blueprints/users/{role}.yml)에 섹션과 필드를 추가할 수 있습니다.", + "user.changeEmail": "이메일 주소 변경", + "user.changeLanguage": "언어 변경", + "user.changeName": "사용자명 변경", + "user.changePassword": "암호 변경", + "user.changePassword.new": "새 암호", + "user.changePassword.new.confirm": "새 암호 확인", + "user.changeRole": "역할 변경", + "user.changeRole.select": "새 역할 선택", + "user.create": "사용자 추가", + "user.delete": "사용자 삭제", + "user.delete.confirm": "사용자({email})를 삭제할까요?", + + "users": "사용자", + + "version": "버전", + + "view.account": "계정", + "view.installation": "\uc124\uce58", + "view.resetPassword": "암호 초기화", + "view.settings": "설정", + "view.site": "사이트", + "view.users": "\uc0ac\uc6a9\uc790", + + "welcome": "반갑습니다.", + "year": "년", + "yes": "yes" +} diff --git a/kirby/i18n/translations/lt.json b/kirby/i18n/translations/lt.json new file mode 100644 index 0000000..77f8d61 --- /dev/null +++ b/kirby/i18n/translations/lt.json @@ -0,0 +1,527 @@ +{ + "add": "Pridėti", + "avatar": "Profilio nuotrauka", + "back": "Atgal", + "cancel": "Atšaukti", + "change": "Keisti", + "close": "Uždaryti", + "confirm": "Ok", + "collapse": "Sutraukti", + "collapse.all": "Sutraukti viską", + "copy": "Kopijuoti", + "create": "Sukurti", + + "date": "Data", + "date.select": "Pasirinkite datą", + + "day": "Diena", + "days.fri": "Pen", + "days.mon": "Pir", + "days.sat": "Šeš", + "days.sun": "Sek", + "days.thu": "Ket", + "days.tue": "Ant", + "days.wed": "Tre", + + "delete": "Pašalinti", + "delete.all": "Pašalinti viską", + "dimensions": "Išmatavimai", + "disabled": "Išjungta", + "discard": "Atšaukti", + "download": "Parsisiųsti", + "duplicate": "Kopijuoti", + "edit": "Redaguoti", + "expand": "Išskleisti", + "expand.all": "Išskleisti viską", + + "dialog.files.empty": "Nėra failų pasirinkimui", + "dialog.pages.empty": "Nėra puslapių pasirinkimui", + "dialog.users.empty": "Nėra vartotojų pasirinkimui", + + "email": "El. paštas", + "email.placeholder": "mail@example.com", + + "error.access.code": "Neteisinas kodas", + "error.access.login": "Neteisingas prisijungimo vardas", + "error.access.panel": "Neturite teisės prisijungti prie valdymo pulto", + "error.access.view": "Neturite teisės peržiūrėti šios valdymo pulto dalies", + + "error.avatar.create.fail": "Nepavyko įkelti profilio nuotraukos", + "error.avatar.delete.fail": "Nepavyko pašalinti profilio nuotraukos", + "error.avatar.dimensions.invalid": "Profilio nuotraukos plotis ar aukštis turėtų būti iki 3000 pikselių", + "error.avatar.mime.forbidden": "Profilio nuotrauka turi būti JPEG arba PNG", + + "error.blueprint.notFound": "Blueprint \"{name}\" negali būti užkrautas", + + "error.blocks.max.plural": "Didžiausias įmanomas blokų kiekis: {max}", + "error.blocks.max.singular": "Jūs galite pridėti daugiausiai vieną bloką", + "error.blocks.min.plural": "Minimalus blokų kiekis: {min}", + "error.blocks.min.singular": "Jūs turite pridėti bent vieną bloką", + "error.blocks.validation": "Bloke {index} yra klaida", + + "error.email.preset.notFound": "El. pašto paruoštukas \"{name}\" nerastas", + + "error.field.converter.invalid": "Neteisingas konverteris \"{converter}\"", + + "error.file.changeName.empty": "Pavadinimas negali būti tuščias", + "error.file.changeName.permission": "Neturite teisės pakeisti failo pavadinimo \"{filename}\"", + "error.file.duplicate": "Failas su pavadinimu \"{filename}\" jau yra", + "error.file.extension.forbidden": "Failo tipas (plėtinys) \"{extension}\" neleidžiamas", + "error.file.extension.invalid": "Neteisingas plėtinys: {extension}", + "error.file.extension.missing": "Failui \"{filename}\" trūksta tipo (plėtinio)", + "error.file.maxheight": "Failo aukštis neturi viršyti {height} px", + "error.file.maxsize": "Failas per didelis", + "error.file.maxwidth": "Failo plotis neturi viršyti {width} px", + "error.file.mime.differs": "Įkėliamas failas turi būti tokio pat mime tipo \"{mime}\"", + "error.file.mime.forbidden": "Media tipas \"{mime}\" neleidžiamas", + "error.file.mime.invalid": "Neteisingas mime tipas: {mime}", + "error.file.mime.missing": "Failui \"{filename}\" nepavyko atpažinti media (mime) tipo", + "error.file.minheight": "Failo aukštis turi būti bent {height} px", + "error.file.minsize": "Failas per mažas", + "error.file.minwidth": "Failo plotis turi būti bent {width} px", + "error.file.name.missing": "Failo pavadinimas negali būti tuščias", + "error.file.notFound": "Failas \"{filename}\" nerastas", + "error.file.orientation": "Failo orientacija turi būti \"{orientation}\"", + "error.file.type.forbidden": "Jūs neturite teisės įkelti {type} tipo failų", + "error.file.type.invalid": "Neteisingas failo tipas: {type}", + "error.file.undefined": "Failas nerastas", + + "error.form.incomplete": "🙏 Prašome ištaisyti visas formos klaidas…", + "error.form.notSaved": "Formos nepavyko išsaugoti", + + "error.language.code": "Prašome įrašyti teisingą kalbos kodą", + "error.language.duplicate": "Tokia kalba jau yra", + "error.language.name": "Prašome įrašyti teisingą kalbos pavadinimą", + + "error.layout.validation.block": "Yra klaida bloke {blockIndex} išdėstyme {layoutIndex}", + "error.layout.validation.settings": "Yra klaida išdėstymo {index} nustatymuose", + + "error.license.format": "Prašome įrašyti teisingą licenzijos kodą", + "error.license.email": "Prašome įrašyti teisingą el. pašto adresą", + "error.license.verification": "Nepavyko patikrinti licenzijos", + + "error.page.changeSlug.permission": "Neturite teisės pakeisti \"{slug}\" URL", + "error.page.changeStatus.incomplete": "Puslapis turi klaidų ir negali būti paskelbtas", + "error.page.changeStatus.permission": "Šiam puslapiui negalima pakeisti statuso", + "error.page.changeStatus.toDraft.invalid": "Puslapio \"{slug}\" negalima paversti juodraščiu", + "error.page.changeTemplate.invalid": "Šablono puslapiui \"{slug}\" negalima keisti", + "error.page.changeTemplate.permission": "Neturite leidimo keisti šabloną puslapiui \"{slug}\"", + "error.page.changeTitle.empty": "Pavadinimas negali būti tuščias", + "error.page.changeTitle.permission": "Neturite leidimo keisti pavadinimo puslapiui \"{slug}\"", + "error.page.create.permission": "Neturite leidimo sukurti \"{slug}\"", + "error.page.delete": "Puslapio \"{slug}\" negalima pašalinti", + "error.page.delete.confirm": "Įrašykite puslapio pavadinimą, tam kad patvirtintumėte", + "error.page.delete.hasChildren": "Puslapis turi vidinių puslapių, dėl to negalima jo pašalinti", + "error.page.delete.permission": "Neturite leidimo šalinti \"{slug}\"", + "error.page.draft.duplicate": "Puslapio juodraštis su URL pabaiga \"{slug}\" jau yra", + "error.page.duplicate": "Puslapis su URL pabaiga \"{slug}\" jau yra", + "error.page.duplicate.permission": "Neturite leidimo dubliuoti \"{slug}\"", + "error.page.notFound": "Puslapis \"{slug}\" nerastas", + "error.page.num.invalid": "Įrašykite teisingą eiliškumo numerį. Numeris negali būti neigiamas.", + "error.page.slug.invalid": "Please enter a valid URL appendix", + "error.page.slug.maxlength": "url adreso maksimalus simbolių kiekis: \"{length}\"", + "error.page.sort.permission": "Puslapiui \"{slug}\" negalima pakeisti eiliškumo", + "error.page.status.invalid": "Nustatykite teisingą puslapio statusą", + "error.page.undefined": "Puslapis nerastas", + "error.page.update.permission": "Neturite leidimo atnaujinti \"{slug}\"", + + "error.section.files.max.plural": "Į sekciją \"{section}\" negalima pridėti daugiau nei {max} failų", + "error.section.files.max.singular": "Į sekciją \"{section}\" negalima pridėti daugiau nei vieną failą", + "error.section.files.min.plural": "Sekcija \"{section}\" reikalauja bent {min} failų", + "error.section.files.min.singular": "Sekcija \"{section}\" reikalauja bent vieno failo", + + "error.section.pages.max.plural": "Į sekciją \"{section}\" negalima pridėti daugiau nei {max} puslapių", + "error.section.pages.max.singular": "Į sekciją \"{section}\" negalima pridėti daugiau nei vieną puslapį", + "error.section.pages.min.plural": "Sekcija \"{section}\" reikalauja bent {min} puslapių", + "error.section.pages.min.singular": "Sekcija \"{section}\" reikalauja bent vieno puslapio", + + "error.section.notLoaded": "Sekcija \"{name}\" negali būti užkrauta", + "error.section.type.invalid": "Sekcijos tipas \"{type}\" yra neteisingas", + + "error.site.changeTitle.empty": "Pavadinimas negali būti tuščias", + "error.site.changeTitle.permission": "Neturite leidimo keisti svetainės pavadinimo", + "error.site.update.permission": "Neturite leidimo atnaujinti svetainės", + + "error.template.default.notFound": "Nėra šablono pagal nutylėjimą", + + "error.user.changeEmail.permission": "Neturite leidimo keisti vartotojo \"{name}\" el. paštą", + "error.user.changeLanguage.permission": "Neturite leidimo keisti vartotojo \"{name}\" kalbą", + "error.user.changeName.permission": "Neturite leidimo keisti vartotojo \"{name}\" vardą", + "error.user.changePassword.permission": "Neturite leidimo keisti vartotojo \"{name}\" slaptažodį", + "error.user.changeRole.lastAdmin": "Vienintelio administratoriaus rolės negalima pakeisti", + "error.user.changeRole.permission": "Neturite leidimo pakeisti vartotojo \"{name}\" rolės", + "error.user.changeRole.toAdmin": "Jūs neturite teisių suteikti administratoriaus rolę", + "error.user.create.permission": "Neturite leidimo sukurti šį vartotoją", + "error.user.delete": "Vartotojo \"{name}\" negalima pašalinti", + "error.user.delete.lastAdmin": "Vienintelio administratoriaus negalima pašalinti", + "error.user.delete.lastUser": "Vienintelio vartotojo negalima pašalinti", + "error.user.delete.permission": "Neturite leidimo pašalinti vartotoją \"{name}\"", + "error.user.duplicate": "Vartotojas su el. paštu \"{email}\" jau yra", + "error.user.email.invalid": "Įrašykite teisingą el. pašto adresą", + "error.user.language.invalid": "Įrašykite teisingą kalbą", + "error.user.notFound": "Vartotojas \"{name}\" nerastas", + "error.user.password.invalid": "Prašome įrašyti galiojantį slaptažodį. Slaptažodį turi sudaryti bent 8 simboliai.", + "error.user.password.notSame": "Slaptažodžiai nesutampa", + "error.user.password.undefined": "Vartotojas neturi slaptažodžio", + "error.user.password.wrong": "Wrong password", + "error.user.role.invalid": "Įrašykite teisingą rolę", + "error.user.update.permission": "Neturite teisės keisti vartotojo \"{name}\"", + + "error.validation.accepted": "Prašome patvirtinti", + "error.validation.alpha": "Prašome įrašyti tik raides a-z", + "error.validation.alphanum": "Prašome įrašyti tik raides a-z arba skaičius 0-9", + "error.validation.between": "Prašome įrašyti reikšmę tarp \"{min}\" ir \"{max}\"", + "error.validation.boolean": "Patvirtinkite arba atšaukite", + "error.validation.contains": "Prašome įrašyti reikšmę, kuri turėtų \"{needle}\"", + "error.validation.date": "Prašome įrašyti korektišką datą", + "error.validation.date.after": "Įrašykite datą nuo {date}", + "error.validation.date.before": "Įrašykite datą iki {date}", + "error.validation.date.between": "Įrašykite datą tarp {min} ir {max}", + "error.validation.denied": "Prašome neleisti", + "error.validation.different": "Reikšmė neturi būti \"{other}\"", + "error.validation.email": "Prašome įrašyti korektišką el. paštą", + "error.validation.endswith": "Reikšmė turi baigtis su \"{end}\"", + "error.validation.filename": "Prašome įrašyti teisingą failo pavadinimą", + "error.validation.in": "Prašome įrašyti vieną iš šių: ({in})", + "error.validation.integer": "Prašome įrašyti teisingą sveiką skaičių", + "error.validation.ip": "Prašome įrašyti teisingą IP adresą", + "error.validation.less": "Prašome įrašyti mažiau nei {max}", + "error.validation.match": "Reikšmė nesutampa su laukiamu šablonu", + "error.validation.max": "Prašome įrašyti reikšmę lygią arba didesnę, nei {max}", + "error.validation.maxlength": "Prašome įrašyti trumpesnę reikšmę. (max. {max} characters)", + "error.validation.maxwords": "Please enter no more than {max} word(s)", + "error.validation.min": "Please enter a value equal to or greater than {min}", + "error.validation.minlength": "Prašome įrašyti ilgesnę reikšmę. (min. {min} characters)", + "error.validation.minwords": "Prašome įrašyti bent {min} žodžius", + "error.validation.more": "Prašome įrašyti daugiau nei {min}", + "error.validation.notcontains": "Prašome įrašyti reikšmę, kuri neturi \"{needle}\"", + "error.validation.notin": "Prašome neįrašyti vieną iš šių: ({notIn})", + "error.validation.option": "Prašome pasirinkti korektišką opciją", + "error.validation.num": "Prašome įrašyti teisingą numerį", + "error.validation.required": "Prašome įrašyti ką nors", + "error.validation.same": "Prašome įrašyti \"{other}\"", + "error.validation.size": "Reikšmės dydis turi būti \"{size}\"", + "error.validation.startswith": "Reikšmė turi prasidėti su \"{start}\"", + "error.validation.time": "Prašome įrašyti korektišką laiką", + "error.validation.time.after": "Įrašykite laiką po {time}", + "error.validation.time.before": "Įrašykite laiką prieš {time}", + "error.validation.time.between": "Įrašykite laiką tarp {min} ir {max}", + "error.validation.url": "Prašome įrašyti teisingą URL", + + "field.required": "Laukas privalomas", + "field.blocks.changeType": "Pakeisti tipą", + "field.blocks.code.name": "Kodas", + "field.blocks.code.language": "Kalba", + "field.blocks.code.placeholder": "Jūsų kodas ...", + "field.blocks.delete.confirm": "Ar tikrai norite pašalinti šį bloką?", + "field.blocks.delete.confirm.all": "Ar tikrai norite pašalinti visus blokus?", + "field.blocks.delete.confirm.selected": "Ar tikrai norite pašalinti pasirinktus blokus?", + "field.blocks.empty": "Dar nėra blokų", + "field.blocks.fieldsets.label": "Pasirinkite bloko tipą ...", + "field.blocks.gallery.name": "Galerija", + "field.blocks.gallery.images.empty": "Dar nėra nuotraukų", + "field.blocks.gallery.images.label": "Nuotraukos", + "field.blocks.heading.level": "Lygis", + "field.blocks.heading.name": "Antraštė", + "field.blocks.heading.text": "Tekstas", + "field.blocks.heading.placeholder": "Antraštė ...", + "field.blocks.image.alt": "Alternatyvus tekstas", + "field.blocks.image.caption": "Aprašymas", + "field.blocks.image.crop": "Kirpti", + "field.blocks.image.link": "Nuoroda", + "field.blocks.image.location": "Šaltinis", + "field.blocks.image.name": "Nuotrauka", + "field.blocks.image.placeholder": "Pasirinkite nuotrauką", + "field.blocks.image.ratio": "Proporcijos", + "field.blocks.image.url": "Nuotraukos URL", + "field.blocks.list.name": "Sąrašas", + "field.blocks.markdown.name": "Markdown", + "field.blocks.markdown.label": "Tekstas", + "field.blocks.markdown.placeholder": "Markdown ...", + "field.blocks.quote.name": "Citata", + "field.blocks.quote.text.label": "Tekstas", + "field.blocks.quote.text.placeholder": "Citata ...", + "field.blocks.quote.citation.label": "Citatos turinys", + "field.blocks.quote.citation.placeholder": "autorius", + "field.blocks.text.name": "Tekstas", + "field.blocks.text.placeholder": "Tekstas ...", + "field.blocks.video.caption": "Aprašymas", + "field.blocks.video.name": "Video", + "field.blocks.video.placeholder": "Įrašykite video URL", + "field.blocks.video.url.label": "Video-URL", + "field.blocks.video.url.placeholder": "https://youtube.com/?v=", + + "field.files.empty": "Pasirinkti", + + "field.layout.delete": "Pašalinti eilutę", + "field.layout.delete.confirm": "Ar tikrai norite pašalinti šią eilutę", + "field.layout.empty": "Dar nėra eilučių", + "field.layout.select": "Pasirinkite išdėstymą", + + "field.pages.empty": "Dar nėra puslapių", + "field.structure.delete.confirm": "Ar tikrai norite pašalinti šią eilutę?", + "field.structure.empty": "Dar nėra įrašų", + "field.users.empty": "Dar nėra vartotojų", + + "file.blueprint": "Šis failas dar neturi blueprint. Jūs galite nustatyti jį /site/blueprints/{template}.yml", + "file.delete.confirm": "Ar tikrai norite pašalinti
{filename}?", + "file.sort": "Pakeisti poziciją", + + "files": "Failai", + "files.empty": "Įkelti", + + "hide": "Paslėpti", + "hour": "Valanda", + "insert": "Įterpti", + "insert.after": "Įterpti po", + "insert.before": "Įterpti prieš", + "install": "Įdiegti", + + "installation": "Įdiegimas", + "installation.completed": "Valdymo pultas įdiegtas", + "installation.disabled": "Pagal nutylėjimą valdymo pulto įdiegimas viešuose serveriuose yra negalimas. Prašome įdiegti lokalioje aplinkoje arba įgalinkite jį su panel.install opcija.", + "installation.issues.accounts": "Katalogas /site/accounts neegzistuoja arba neturi įrašymo teisių", + "installation.issues.content": "Katalogas /content neegzistuoja arba neturi įrašymo teisių", + "installation.issues.curl": "Plėtinys CURL yra privalomas", + "installation.issues.headline": "Nepavyko įdiegti valdymo pulto", + "installation.issues.mbstring": "Plėtinys MB String yra privalomas", + "installation.issues.media": "Katalogas /media neegzistuoja arba neturi įrašymo teisių", + "installation.issues.php": "Įsitikinkite, kad naudojama PHP 7+", + "installation.issues.server": "Kirby reikalauja Apache, Nginx arba Caddy", + "installation.issues.sessions": "Katalogas /site/sessions neegzistuoja arba neturi įrašymo teisių", + + "language": "Kalba", + "language.code": "Kodas", + "language.convert": "Padaryti pagrindinį", + "language.convert.confirm": "

Do you really want to convert {name} to the default language? This cannot be undone.

If {name} has untranslated content, there will no longer be a valid fallback and parts of your site might be empty.

", + "language.create": "Pridėti naują kalbą", + "language.delete.confirm": "Ar tikrai norite pašalinti {name} kalbą, kartu su visais vertimais? Grąžinti nebus įmanoma! 🙀", + "language.deleted": "Kalba pašalinta", + "language.direction": "Skaitymo kryptis", + "language.direction.ltr": "Iš kairės į dešinę", + "language.direction.rtl": "Iš dešinės į kairę", + "language.locale": "PHP locale string", + "language.locale.warning": "Jūs naudojate pasirinktinį lokalės nustatymą. Prašome pakeisti jį faile /site/languages", + "language.name": "Pavadinimas", + "language.updated": "Kalba atnaujinta", + + "languages": "Kalbos", + "languages.default": "Pagrindinė kalba", + "languages.empty": "Dar nėra kalbų", + "languages.secondary": "Papildomos kalbos", + "languages.secondary.empty": "Dar nėra papildomų kalbų", + + "license": "Licenzija", + "license.buy": "Pirkti licenziją", + "license.register": "Registruoti", + "license.register.help": "Licenzijos kodą gavote el. paštu po apmokėjimo. Prašome įterpti čia, kad sistema būtų užregistruota.", + "license.register.label": "Prašome įrašyti jūsų licenzijos kodą", + "license.register.success": "Ačiū, kad palaikote Kirby", + "license.unregistered": "Tai neregistruota Kirby demo versija", + + "link": "Nuoroda", + "link.text": "Nuorodos tekstas", + + "loading": "Kraunasi", + + "lock.unsaved": "Neišsaugoti pakeitimai", + "lock.unsaved.empty": "Nebeliko neišsaugotų pakeitimų", + "lock.isLocked": "Vartotojo {email} neišsaugoti pakeitimai", + "lock.file.isLocked": "Šį failą dabar redaguoja kitas vartotojas {email}, tad jo negalima pekeisti.", + "lock.page.isLocked": "Šį puslapį dabar redaguoja kitas vartotojas {email}, tad jo negalima pekeisti.", + "lock.unlock": "Atrakinti", + "lock.isUnlocked": "Jūsų neišsaugoti pakeitimai buvo perrašyti kito vartotojo. Galite parsisiųsti savo pakeitimus ir įkelti juos rankiniu būdu.", + + "login": "Prisijungti", + "login.code.label.login": "Prisijungimo kodas", + "login.code.label.password-reset": "Slaptažodžio atstatymo kodas", + "login.code.placeholder.email": "000 000", + "login.code.text.email": "Jei jūsų el. paštas yra užregistruotas, užklaistas kodas buvo išsiųstas el. paštu.", + "login.email.login.body": "Sveiki, {user.nameOrEmail},\n\nJūs ką tik užklausėte prisijungimo kodą svetainės valdymui.\nŠis kodas galios {timeout} minučių:\n\n{code}\n\nJei neprašėte šio kodo, galite ignoruoti šį laišką arba kreiptis į administratorių dėl patikslinimo.\nDėl saugumo, prašome nepersiųsti šio laiško kitiems.", + "login.email.login.subject": "Jūsų prisijungimo kodas", + "login.email.password-reset.body": "Sveiki, {user.nameOrEmail},\n\njūs ką tik užklausėte slaptažodžio atstatymo kodą svetainės valdymui .\nŠis kodas galios {timeout} minučių:\n\n{code}\n\nJei neprašėte šio kodo, galite ignoruoti šį laišką arba kreiptis į administratorių dėl patikslinimo.\nDėl saugumo, prašome nepersiųsti šio laiško kitiems.", + "login.email.password-reset.subject": "Jūsų slaptažodžio atstatymo kodas ", + "login.remember": "Likti prisijungus", + "login.reset": "Sukurti naują slaptažodį", + "login.toggleText.code.email": "Prisijungti su el. paštu", + "login.toggleText.code.email-password": "Prisijungti su slaptažodžiu", + "login.toggleText.password-reset.email": "Pamiršote slaptažodį?", + "login.toggleText.password-reset.email-password": "← Atgal į prisijungimą", + + "logout": "Atsijungti", + + "menu": "Meniu", + "meridiem": "AM/PM", + "mime": "Media Tipas", + "minutes": "Minutės", + + "month": "Mėnuo", + "months.april": "Balandis", + "months.august": "August", + "months.december": "Gruodis", + "months.february": "Vasaris", + "months.january": "Sausis", + "months.july": "Liepa", + "months.june": "Birželis", + "months.march": "Kovas", + "months.may": "Gegužė", + "months.november": "Lapkritis", + "months.october": "Spalis", + "months.september": "Rugsėjis", + + "more": "Daugiau", + "name": "Pavadinimas", + "next": "Toliau", + "no": "no", + "off": "off", + "on": "on", + "open": "Atidaryti", + "open.newWindow": "Open in new window", + "options": "Pasirinkimai", + "options.none": "Nėra pasirinkimų", + + "orientation": "Orientacija", + "orientation.landscape": "Horizontali", + "orientation.portrait": "Portretas", + "orientation.square": "Kvadratas", + + "page.blueprint": "Šis puslapis dar neturi blueprint. Jūs galite nustatyti jį /site/blueprints/{template}.yml", + "page.changeSlug": "Pakeisti URL", + "page.changeSlug.fromTitle": "Sukurti URL pagal pavadinimą", + "page.changeStatus": "Pakeisti statusą", + "page.changeStatus.position": "Pasirinkite poziciją", + "page.changeStatus.select": "Pasirinkite statusą", + "page.changeTemplate": "Pakeisti šabloną", + "page.delete.confirm": "🙀 Ar tikrai norite pašalinti puslapį {title}?", + "page.delete.confirm.subpages": "Šis puslapis turi sub-puslapių.
Visi sub-puslapiai taip pat bus pašalinti.", + "page.delete.confirm.title": "Įrašykite puslapio pavadinimą tam, kad patvirtinti", + "page.draft.create": "Sukurti juodraštį", + "page.duplicate.appendix": "Kopijuoti", + "page.duplicate.files": "Kopijuoti failus", + "page.duplicate.pages": "Kopijuoti puslapius", + "page.sort": "Pakeisti poziciją", + "page.status": "Statusas", + "page.status.draft": "Juodraštis", + "page.status.draft.description": "Šis puslapis yra juodraščio režime ir prieinamas tik redaktoriams arba per slaptą nuorodą", + "page.status.listed": "Paskelbtas", + "page.status.listed.description": "Matomas viešai visiems", + "page.status.unlisted": "Nerodomas", + "page.status.unlisted.description": "Rodomas viešai visiems, bet tik per URL", + + "pages": "Puslapiai", + "pages.empty": "Dar nėra puslapių", + "pages.status.draft": "Juodraščiai", + "pages.status.listed": "Paskelbti", + "pages.status.unlisted": "Nerodomi", + + "pagination.page": "Puslapis", + + "password": "Slaptažodis", + "pixel": "Pikselis", + "prev": "Ankstesnis", + "preview": "Peržiūra", + "remove": "Pašalinti", + "rename": "Pervadinti", + "replace": "Apkeisti", + "retry": "Bandyti dar", + "revert": "Grąžinti", + "revert.confirm": "Ar tikrai norite atšaukti visus neišsaugotus pakeitimus?", + + "role": "Rolė", + "role.admin.description": "Admin turi visas teises", + "role.admin.title": "Admin", + "role.all": "Visos", + "role.empty": "Nėra vartotojų su tokia role", + "role.description.placeholder": "Be aprašymo", + "role.nobody.description": "Ši rolė bus naudojama jei nenustatytos jokios teisės", + "role.nobody.title": "Niekas", + + "save": "Išsaugoti", + "search": "Ieškoti", + "search.min": "Minimalus simbolių kiekis paieškai: {min}", + "search.all": "Rodyti viską", + "search.results.none": "Nėra rezultatų", + + "section.required": "Sekcija privaloma", + + "select": "Pasirinkti", + "settings": "Nustatymai", + "show": "Rodyti", + "size": "Dydis", + "slug": "URL pabaiga", + "sort": "Rikiuoti", + "title": "Pavadinimas", + "template": "Puslapio šablonas", + "today": "Šiandien", + + "site.blueprint": "Svetainė neturi blueprint. Jūs galite nustatyti jį /site/blueprints/site.yml", + + "toolbar.button.code": "Kodas", + "toolbar.button.bold": "Bold", + "toolbar.button.email": "El. paštas", + "toolbar.button.headings": "Antraštės", + "toolbar.button.heading.1": "Heading 1", + "toolbar.button.heading.2": "Heading 2", + "toolbar.button.heading.3": "Heading 3", + "toolbar.button.italic": "Italic", + "toolbar.button.file": "Failas", + "toolbar.button.file.select": "Pasirinkite failą", + "toolbar.button.file.upload": "Įkelti failą", + "toolbar.button.link": "Nuoroda", + "toolbar.button.strike": "Strike-through", + "toolbar.button.ol": "Sąrašas su skaičiais", + "toolbar.button.underline": "Underline", + "toolbar.button.ul": "Sąrašas su taškais", + + "translation.author": "Roman U", + "translation.direction": "ltr", + "translation.name": "Lietuvių", + "translation.locale": "lt_LT", + + "upload": "Įkelti", + "upload.error.cantMove": "Įkeltas failas negali būti perkeltas", + "upload.error.cantWrite": "Nepavyko įrašyti failo į diską", + "upload.error.default": "Nepavyko įkelti failo", + "upload.error.extension": "Neįmanoma įkelti tokio tipo failo", + "upload.error.formSize": "Įkeltas failas viršija MAX_FILE_SIZE nustatymą, kuris buvo nurodytas formoje", + "upload.error.iniPostSize": "Įkeliamas failas viršija post_max_size nustatymą iš php.ini", + "upload.error.iniSize": "Įkeltas failas viršija upload_max_filesize nustatymą faile php.ini", + "upload.error.noFile": "Failas nebuvo įkeltas", + "upload.error.noFiles": "Failai nebuvo įkelti", + "upload.error.partial": "Failas įkeltas tik iš dalies", + "upload.error.tmpDir": "Trūksta laikinojo katalogo", + "upload.errors": "Klaida", + "upload.progress": "Įkėlimas…", + + "url": "Url", + "url.placeholder": "https://example.com", + + "user": "Vartotojas", + "user.blueprint": "Galite nustatyti papildomas sekcijas ir formos laukelius šiai vartotojo rolei faile /site/blueprints/users/{role}.yml", + "user.changeEmail": "Keisti el. paštą", + "user.changeLanguage": "Keisti kalbą", + "user.changeName": "Pervadinti vartotoją", + "user.changePassword": "Keisti slaptažodį", + "user.changePassword.new": "Naujas slaptažodis", + "user.changePassword.new.confirm": "Patvirtinti naują slaptažodį…", + "user.changeRole": "Keisti rolę", + "user.changeRole.select": "Pasirinkti naują rolę", + "user.create": "Pridėti naują vartotoją", + "user.delete": "Pašalinti šį vartotoją", + "user.delete.confirm": "Ar tikrai norite pašalinti vartotoją
{email}?", + + "users": "Vartotojai", + + "version": "Versija", + + "view.account": "Jūsų paskyra", + "view.installation": "Installation", + "view.resetPassword": "Sukurti naują slaptažodį", + "view.settings": "Nustatymai", + "view.site": "Svetainė", + "view.users": "Vartotojai", + + "welcome": "Sveiki", + "year": "Metai", + "yes": "yes" +} diff --git a/kirby/i18n/translations/nb.json b/kirby/i18n/translations/nb.json new file mode 100644 index 0000000..1a7bc37 --- /dev/null +++ b/kirby/i18n/translations/nb.json @@ -0,0 +1,527 @@ +{ + "add": "Legg til", + "avatar": "Profilbilde", + "back": "Tilbake", + "cancel": "Avbryt", + "change": "Endre", + "close": "Lukk", + "confirm": "Lagre", + "collapse": "Collapse", + "collapse.all": "Collapse All", + "copy": "Kopier", + "create": "Opprett", + + "date": "Dato", + "date.select": "Velg dato", + + "day": "Dag", + "days.fri": "Fre", + "days.mon": "Man", + "days.sat": "L\u00f8r", + "days.sun": "S\u00f8n", + "days.thu": "Tor", + "days.tue": "Tir", + "days.wed": "Ons", + + "delete": "Slett", + "delete.all": "Delete all", + "dimensions": "Dimensjoner", + "disabled": "Disabled", + "discard": "Forkast", + "download": "Download", + "duplicate": "Duplicate", + "edit": "Rediger", + "expand": "Expand", + "expand.all": "Expand All", + + "dialog.files.empty": "No files to select", + "dialog.pages.empty": "No pages to select", + "dialog.users.empty": "No users to select", + + "email": "Epost", + "email.placeholder": "epost@eksempel.no", + + "error.access.code": "Invalid code", + "error.access.login": "Ugyldig innlogging", + "error.access.panel": "Du har ikke tilgang til panelet", + "error.access.view": "You are not allowed to access this part of the panel", + + "error.avatar.create.fail": "Profilbildet kunne ikke lastes opp", + "error.avatar.delete.fail": "Profil bildet kunne ikke bli slette", + "error.avatar.dimensions.invalid": "Vennligst hold profilbildets bredde og høyde under 3000 piksler", + "error.avatar.mime.forbidden": "Ugyldig MIME-type", + + "error.blueprint.notFound": "Blueprint \"{name}\" kunne ikke lastes inn", + + "error.blocks.max.plural": "You must not add more than {max} blocks", + "error.blocks.max.singular": "You must not add more than one block", + "error.blocks.min.plural": "You must add at least {min} blocks", + "error.blocks.min.singular": "You must add at least one block", + "error.blocks.validation": "There's an error in block {index}", + + "error.email.preset.notFound": "E-postinnstillingen \"{name}\" ble ikke funnet", + + "error.field.converter.invalid": "Ugyldig omformer \"{converter}\"", + + "error.file.changeName.empty": "The name must not be empty", + "error.file.changeName.permission": "Du er ikke tillatt å endre navnet til \"{filename}\"", + "error.file.duplicate": "En fil med navnet \"{filename}\" eksisterer allerede", + "error.file.extension.forbidden": "Ugyldig filtype", + "error.file.extension.invalid": "Invalid extension: {extension}", + "error.file.extension.missing": "Du kan ikke laste opp filer uten filtype", + "error.file.maxheight": "The height of the image must not exceed {height} pixels", + "error.file.maxsize": "The file is too large", + "error.file.maxwidth": "The width of the image must not exceed {width} pixels", + "error.file.mime.differs": "Den opplastede filen må være av samme MIME-type \"{mime}\"", + "error.file.mime.forbidden": "Mediatypen \"{mime}\" er ikke tillatt", + "error.file.mime.invalid": "Invalid mime type: {mime}", + "error.file.mime.missing": "Mediatypen for \"{filename}\" kan ikke gjenkjennes", + "error.file.minheight": "The height of the image must be at least {height} pixels", + "error.file.minsize": "The file is too small", + "error.file.minwidth": "The width of the image must be at least {width} pixels", + "error.file.name.missing": "Filnavnet kan ikke være tomt", + "error.file.notFound": "Filen kunne ikke bli funnet", + "error.file.orientation": "The orientation of the image must be \"{orientation}\"", + "error.file.type.forbidden": "Du har ikke lov til å laste opp filer av typen {type}", + "error.file.type.invalid": "Invalid file type: {type}", + "error.file.undefined": "Filen kunne ikke bli funnet", + + "error.form.incomplete": "Vennligst fiks alle feil…", + "error.form.notSaved": "Skjemaet kunne ikke lagres", + + "error.language.code": "Please enter a valid code for the language", + "error.language.duplicate": "The language already exists", + "error.language.name": "Please enter a valid name for the language", + + "error.layout.validation.block": "There's an error in block {blockIndex} in layout {layoutIndex}", + "error.layout.validation.settings": "There's an error in layout {index} settings", + + "error.license.format": "Please enter a valid license key", + "error.license.email": "Vennligst skriv inn en gyldig e-postadresse", + "error.license.verification": "The license could not be verified", + + "error.page.changeSlug.permission": "Du kan ikke endre URLen for denne siden", + "error.page.changeStatus.incomplete": "Siden har feil og kan ikke publiseres", + "error.page.changeStatus.permission": "Sidens status kan ikke endres", + "error.page.changeStatus.toDraft.invalid": "Siden \"{slug}\" kan ikke konverteres til et utkast", + "error.page.changeTemplate.invalid": "Malen for siden \"{slug}\" kan ikke endres", + "error.page.changeTemplate.permission": "Du har ikke tillatelse til å endre malen for \"{slug}\"", + "error.page.changeTitle.empty": "Tittelen kan ikke være tom", + "error.page.changeTitle.permission": "Du har ikke tillatelse til å endre tittelen for \"{slug}\"", + "error.page.create.permission": "Du har ikke tillatelse til å opprette \"{slug}\"", + "error.page.delete": "Siden \"{slug}\" kan ikke slettes", + "error.page.delete.confirm": "Vennligst skriv inn sidens tittel for å bekrefte", + "error.page.delete.hasChildren": "Siden har undersider og kan derfor ikke slettes", + "error.page.delete.permission": "Du har ikke tilgang til å slette \"{slug}\"", + "error.page.draft.duplicate": "Et sideutkast med URL-tillegget \"{slug}\" eksisterer allerede", + "error.page.duplicate": "En side med URL-tillegget \"{slug}\" eksisterer allerede", + "error.page.duplicate.permission": "You are not allowed to duplicate \"{slug}\"", + "error.page.notFound": "Siden \"{slug}\" ble ikke funnet", + "error.page.num.invalid": "Vennligst skriv inn et gyldig sorteringsnummer. Tallet må ikke være negativt.", + "error.page.slug.invalid": "Please enter a valid URL appendix", + "error.page.slug.maxlength": "Slug length must be less than \"{length}\" characters", + "error.page.sort.permission": "Siden \"{slug}\" kan ikke sorteres", + "error.page.status.invalid": "Vennligst angi en gyldig sidestatus", + "error.page.undefined": "Siden kunne ikke bli funnet", + "error.page.update.permission": "Du har ikke tilgang til å oppdatere \"{slug}\"", + + "error.section.files.max.plural": "Det er ikke mulig å legge til mer enn {max} filer i seksjonen \"{section}\"", + "error.section.files.max.singular": "Det er ikke mulig å legge til mer enn én fil i seksjonen \"{section}\"", + "error.section.files.min.plural": "The \"{section}\" section requires at least {min} files", + "error.section.files.min.singular": "The \"{section}\" section requires at least one file", + + "error.section.pages.max.plural": "Det er ikke mulig å legge til mer enn {max} sider i \"{section}\" seksjonen", + "error.section.pages.max.singular": "Det er ikke mulig å legge til mer enn én side i \"{section}\" seksjonen", + "error.section.pages.min.plural": "The \"{section}\" section requires at least {min} pages", + "error.section.pages.min.singular": "The \"{section}\" section requires at least one page", + + "error.section.notLoaded": "Seksjonen \"{name}\" kunne ikke lastes inn", + "error.section.type.invalid": "Seksjonstypen \"{type}\" er ikke gyldig", + + "error.site.changeTitle.empty": "Tittelen kan ikke være tom", + "error.site.changeTitle.permission": "Du har ikke tillatelse til å endre tittel på siden", + "error.site.update.permission": "Du har ikke tillatelse til å oppdatere denne siden", + + "error.template.default.notFound": "Standardmalen eksisterer ikke", + + "error.user.changeEmail.permission": "Du har ikke tillatelse til å endre e-post for brukeren \"{name}\"", + "error.user.changeLanguage.permission": "Du har ikke tillatelse til å endre språk for brukeren \"{name}\"", + "error.user.changeName.permission": "Du har ikke tillatelse til å endre navn for brukeren \"{name}\"", + "error.user.changePassword.permission": "Du har ikke tillatelse til å endre passord for brukeren \"{name}\"", + "error.user.changeRole.lastAdmin": "Rollen for den siste administratoren kan ikke endres", + "error.user.changeRole.permission": "Du har ikke tillatelse til å endre rollen for brukeren \"{name}\"", + "error.user.changeRole.toAdmin": "You are not allowed to promote someone to the admin role", + "error.user.create.permission": "Du har ikke tilgang til å opprette denne brukeren", + "error.user.delete": "Denne brukeren kunne ikke bli slettet", + "error.user.delete.lastAdmin": "Siste administrator kan ikke slettes", + "error.user.delete.lastUser": "Den siste brukeren kan ikke slettes", + "error.user.delete.permission": "Du er ikke tillat \u00e5 slette denne brukeren", + "error.user.duplicate": "En bruker med e-postadresse \"{email}\" eksisterer allerede", + "error.user.email.invalid": "Vennligst skriv inn en gyldig e-postadresse", + "error.user.language.invalid": "Vennligst skriv inn et gyldig språk", + "error.user.notFound": "Brukeren kunne ikke bli funnet", + "error.user.password.invalid": "Vennligst skriv inn et gyldig passord. Passordet må minst være 8 tegn langt.", + "error.user.password.notSame": "Vennligst bekreft passordet", + "error.user.password.undefined": "Brukeren har ikke et passord", + "error.user.password.wrong": "Wrong password", + "error.user.role.invalid": "Vennligst skriv inn en gyldig rolle", + "error.user.update.permission": "Du har ikke tillatelse til å oppdatere brukeren \"{name}\"", + + "error.validation.accepted": "Vennligst bekreft", + "error.validation.alpha": "Vennligst skriv kun tegn mellom a-z", + "error.validation.alphanum": "Vennligst skriv kun tegn mellom a-z eller tall mellom 0-9", + "error.validation.between": "Vennligst angi en verdi mellom \"{min}\" og \"{max}\"", + "error.validation.boolean": "Vennligst bekreft eller avslå", + "error.validation.contains": "Vennligst skriv inn en verdi som inneholder \"{needle}\"", + "error.validation.date": "Vennligst skriv inn en gyldig dato", + "error.validation.date.after": "Please enter a date after {date}", + "error.validation.date.before": "Please enter a date before {date}", + "error.validation.date.between": "Please enter a date between {min} and {max}", + "error.validation.denied": "Vennligst avslå", + "error.validation.different": "Verdien kan ikke være \"{other}\"", + "error.validation.email": "Vennligst skriv inn en gyldig e-postadresse", + "error.validation.endswith": "Verdien må ende med \"{end}\"", + "error.validation.filename": "Vennligst skriv inn et gyldig filnavn", + "error.validation.in": "Vennligst skriv inn en av følgende: ({in})", + "error.validation.integer": "Vennligst skriv inn et gyldig tall", + "error.validation.ip": "Vennligst skriv inn en gyldig IP-adresse", + "error.validation.less": "Vennligst angi en verdi lavere enn {max}", + "error.validation.match": "Verdien samsvarer ikke med det forventede mønsteret", + "error.validation.max": "Vennligst angi en verdi lik eller lavere enn {max}", + "error.validation.maxlength": "Vennligst angi en kortere verdi. (maks. {max} tegn)", + "error.validation.maxwords": "Vennligst ikke skriv inn mer enn {max} ord", + "error.validation.min": "Vennligst angi en verdi lik eller større enn {min}", + "error.validation.minlength": "Vennligst angi en lengre verdi. (minimum. {min} tegn)", + "error.validation.minwords": "Vennligst skriv inn minst {min} ord", + "error.validation.more": "Vennligst angi en verdi større enn {min}", + "error.validation.notcontains": "Vennligst angi en verdi som ikke inneholder \"{needle}\"", + "error.validation.notin": "Vennligst ikke angi noen av følgende:({notIn})", + "error.validation.option": "Vennligst velg et gyldig alternativ", + "error.validation.num": "Vennligst angi et gyldig nummer", + "error.validation.required": "Vennligst skriv inn noe", + "error.validation.same": "Vennligst angi \"{other}\"", + "error.validation.size": "Størrelsen på verdien må være \"{size}\"", + "error.validation.startswith": "Verdien må starte med \"{start}\"", + "error.validation.time": "Vennligst angi et gyldig tidspunkt", + "error.validation.time.after": "Please enter a time after {time}", + "error.validation.time.before": "Please enter a time before {time}", + "error.validation.time.between": "Please enter a time between {min} and {max}", + "error.validation.url": "Vennligst skriv inn en gyldig URL", + + "field.required": "The field is required", + "field.blocks.changeType": "Change type", + "field.blocks.code.name": "Kode", + "field.blocks.code.language": "Språk", + "field.blocks.code.placeholder": "Your code …", + "field.blocks.delete.confirm": "Do you really want to delete this block?", + "field.blocks.delete.confirm.all": "Do you really want to delete all blocks?", + "field.blocks.delete.confirm.selected": "Do you really want to delete the selected blocks?", + "field.blocks.empty": "No blocks yet", + "field.blocks.fieldsets.label": "Please select a block type …", + "field.blocks.gallery.name": "Gallery", + "field.blocks.gallery.images.empty": "No images yet", + "field.blocks.gallery.images.label": "Images", + "field.blocks.heading.level": "Level", + "field.blocks.heading.name": "Heading", + "field.blocks.heading.text": "Text", + "field.blocks.heading.placeholder": "Heading …", + "field.blocks.image.alt": "Alternative text", + "field.blocks.image.caption": "Caption", + "field.blocks.image.crop": "Crop", + "field.blocks.image.link": "Adresse", + "field.blocks.image.location": "Location", + "field.blocks.image.name": "Bilde", + "field.blocks.image.placeholder": "Select an image", + "field.blocks.image.ratio": "Ratio", + "field.blocks.image.url": "Image URL", + "field.blocks.list.name": "List", + "field.blocks.markdown.name": "Markdown", + "field.blocks.markdown.label": "Text", + "field.blocks.markdown.placeholder": "Markdown …", + "field.blocks.quote.name": "Quote", + "field.blocks.quote.text.label": "Text", + "field.blocks.quote.text.placeholder": "Quote …", + "field.blocks.quote.citation.label": "Citation", + "field.blocks.quote.citation.placeholder": "by …", + "field.blocks.text.name": "Text", + "field.blocks.text.placeholder": "Text …", + "field.blocks.video.caption": "Caption", + "field.blocks.video.name": "Video", + "field.blocks.video.placeholder": "Enter a video URL", + "field.blocks.video.url.label": "Video-URL", + "field.blocks.video.url.placeholder": "https://youtube.com/?v=", + + "field.files.empty": "Ingen filer har blitt valgt", + + "field.layout.delete": "Delete layout", + "field.layout.delete.confirm": "Do you really want to delete this layout?", + "field.layout.empty": "No rows yet", + "field.layout.select": "Select a layout", + + "field.pages.empty": "Ingen side har blitt valgt", + "field.structure.delete.confirm": "\u00d8nsker du virkelig \u00e5 slette denne oppf\u00f8ringen?", + "field.structure.empty": "Ingen oppf\u00f8ringer enda", + "field.users.empty": "Ingen bruker har blitt valgt", + + "file.blueprint": "This file has no blueprint yet. You can define the setup in /site/blueprints/{template}.yml", + "file.delete.confirm": "Vil du virkelig slette denne filen?", + "file.sort": "Change position", + + "files": "Filer", + "files.empty": "Ingen filer ennå", + + "hide": "Hide", + "hour": "Time", + "insert": "Sett Inn", + "insert.after": "Insert after", + "insert.before": "Insert before", + "install": "Installer", + + "installation": "Installasjon", + "installation.completed": "Panelet har blitt installert", + "installation.disabled": "Installasjonsprogrammet for Panelet er deaktivert på offentlige servere som standard. Vennligst kjør installasjonsprogrammet på en lokal maskin eller aktiver den med panel.install innstillingen.", + "installation.issues.accounts": "\/site\/accounts er ikke skrivbar", + "installation.issues.content": "Mappen content og alt av innhold m\u00e5 v\u00e6re skrivbar.", + "installation.issues.curl": "Utvidelsen CURL er nødvendig", + "installation.issues.headline": "Panelet kan ikke installeres", + "installation.issues.mbstring": "Utvidelsen MB String er nødvendig", + "installation.issues.media": "Mappen /media eksisterer ikke eller er ikke skrivbar", + "installation.issues.php": "Pass på at du bruker PHP 7+", + "installation.issues.server": "Kirby krever Apache, Nginx eller Caddy", + "installation.issues.sessions": "Mappen /site/sessions eksisterer ikke eller er ikke skrivbar", + + "language": "Spr\u00e5k", + "language.code": "Kode", + "language.convert": "Gjør til standard", + "language.convert.confirm": "

Vil du virkelig konvertere {name} til standardspråk? Dette kan ikke angres.

Dersom {name} har innhold som ikke er oversatt, vil nettstedet mangle innhold å falle tilbake på. Dette kan resultere i at deler av nettstedet fremstår som tomt.

", + "language.create": "Legg til språk", + "language.delete.confirm": "Vil du virkelig slette språket {name} inkludert alle oversettelser? Dette kan ikke angres!", + "language.deleted": "Språket har blitt slettet", + "language.direction": "Leseretning", + "language.direction.ltr": "Venstre til høyre", + "language.direction.rtl": "Høyre til venstre", + "language.locale": "PHP locale streng", + "language.locale.warning": "You are using a custom locale set up. Please modify it in the language file in /site/languages", + "language.name": "Navn", + "language.updated": "Språk har blitt oppdatert", + + "languages": "Språk", + "languages.default": "Standardspråk", + "languages.empty": "Det er ingen språk ennå", + "languages.secondary": "Sekundære språk", + "languages.secondary.empty": "Det er ingen andre språk ennå", + + "license": "Kirby lisens", + "license.buy": "Kjøp lisens", + "license.register": "Registrer", + "license.register.help": "Du skal ha mottatt din lisenskode for kjøpet via e-post. Vennligst kopier og lim inn denne for å registrere deg.", + "license.register.label": "Vennligst skriv inn din lisenskode", + "license.register.success": "Takk for at du støtter Kirby", + "license.unregistered": "Dette er en uregistrert demo av Kirby", + + "link": "Adresse", + "link.text": "Koblingstekst", + + "loading": "Laster inn", + + "lock.unsaved": "Unsaved changes", + "lock.unsaved.empty": "There are no more unsaved changes", + "lock.isLocked": "Unsaved changes by {email}", + "lock.file.isLocked": "The file is currently being edited by {email} and cannot be changed.", + "lock.page.isLocked": "The page is currently being edited by {email} and cannot be changed.", + "lock.unlock": "Unlock", + "lock.isUnlocked": "Your unsaved changes have been overwritten by another user. You can download your changes to merge them manually.", + + "login": "Logg Inn", + "login.code.label.login": "Login code", + "login.code.label.password-reset": "Password reset code", + "login.code.placeholder.email": "000 000", + "login.code.text.email": "If your email address is registered, the requested code was sent via email.", + "login.email.login.body": "Hi {user.nameOrEmail},\n\nYou recently requested a login code for the Kirby Panel.\nThe following login code will be valid for {timeout} minutes:\n\n{code}\n\nIf you did not request a login code, please ignore this email or contact your administrator if you have questions.\nFor security, please DO NOT forward this email.", + "login.email.login.subject": "Your login code", + "login.email.password-reset.body": "Hi {user.nameOrEmail},\n\nYou recently requested a password reset code for the Kirby Panel.\nThe following password reset code will be valid for {timeout} minutes:\n\n{code}\n\nIf you did not request a password reset code, please ignore this email or contact your administrator if you have questions.\nFor security, please DO NOT forward this email.", + "login.email.password-reset.subject": "Your password reset code", + "login.remember": "Hold meg innlogget", + "login.reset": "Reset password", + "login.toggleText.code.email": "Login via email", + "login.toggleText.code.email-password": "Login with password", + "login.toggleText.password-reset.email": "Forgot your password?", + "login.toggleText.password-reset.email-password": "← Back to login", + + "logout": "Logg ut", + + "menu": "Meny", + "meridiem": "AM/PM", + "mime": "Mediatype", + "minutes": "Minutter", + + "month": "Måned", + "months.april": "April", + "months.august": "August", + "months.december": "Desember", + "months.february": "Februar", + "months.january": "Januar", + "months.july": "July", + "months.june": "Juni", + "months.march": "Mars", + "months.may": "Mai", + "months.november": "November", + "months.october": "Oktober", + "months.september": "September", + + "more": "Mer", + "name": "Navn", + "next": "Neste", + "no": "no", + "off": "off", + "on": "on", + "open": "Åpne", + "open.newWindow": "Open in new window", + "options": "Alternativer", + "options.none": "No options", + + "orientation": "Orientering", + "orientation.landscape": "Landskap", + "orientation.portrait": "Portrett", + "orientation.square": "Kvadrat", + + "page.blueprint": "This page has no blueprint yet. You can define the setup in /site/blueprints/{template}.yml", + "page.changeSlug": "Endre URL", + "page.changeSlug.fromTitle": "Opprett fra tittel", + "page.changeStatus": "Endre status", + "page.changeStatus.position": "Vennligst velg en posisjon", + "page.changeStatus.select": "Velg ny status", + "page.changeTemplate": "Endre mal", + "page.delete.confirm": "Vil du virkelig slette denne siden?", + "page.delete.confirm.subpages": "Denne siden har undersider.
Alle undersider vil også bli slettet.", + "page.delete.confirm.title": "Skriv inn sidetittel for å bekrefte", + "page.draft.create": "Lag utkast", + "page.duplicate.appendix": "Kopier", + "page.duplicate.files": "Copy files", + "page.duplicate.pages": "Copy pages", + "page.sort": "Change position", + "page.status": "Status", + "page.status.draft": "Utkast", + "page.status.draft.description": "The page is in draft mode and only visible for logged in editors or via secret link", + "page.status.listed": "Offentlig", + "page.status.listed.description": "Siden er offentlig og synlig for alle", + "page.status.unlisted": "Unotert", + "page.status.unlisted.description": "Siden er ikke er oppført og er kun tilgjengelig via URL", + + "pages": "Sider", + "pages.empty": "Ingen sider ennå", + "pages.status.draft": "Utkast", + "pages.status.listed": "Publisert", + "pages.status.unlisted": "Unotert", + + "pagination.page": "Side", + + "password": "Passord", + "pixel": "Piksel", + "prev": "Forrige", + "preview": "Preview", + "remove": "Fjern", + "rename": "Endre navn", + "replace": "Erstatt", + "retry": "Pr\u00f8v p\u00e5 nytt", + "revert": "Forkast", + "revert.confirm": "Do you really want to delete all unsaved changes?", + + "role": "Rolle", + "role.admin.description": "The admin has all rights", + "role.admin.title": "Admin", + "role.all": "Alle", + "role.empty": "Det er ingen brukere med denne rollen", + "role.description.placeholder": "Ingen beskrivelse", + "role.nobody.description": "This is a fallback role without any permissions", + "role.nobody.title": "Nobody", + + "save": "Lagre", + "search": "Søk", + "search.min": "Enter {min} characters to search", + "search.all": "Show all", + "search.results.none": "No results", + + "section.required": "The section is required", + + "select": "Velg", + "settings": "Innstillinger", + "show": "Show", + "size": "Størrelse", + "slug": "URL-appendiks", + "sort": "Sortere", + "title": "Tittel", + "template": "Mal", + "today": "I dag", + + "site.blueprint": "The site has no blueprint yet. You can define the setup in /site/blueprints/site.yml", + + "toolbar.button.code": "Kode", + "toolbar.button.bold": "Tykk tekst", + "toolbar.button.email": "Epost", + "toolbar.button.headings": "Overskrifter", + "toolbar.button.heading.1": "Overskrift 1", + "toolbar.button.heading.2": "Overskrift 2", + "toolbar.button.heading.3": "Overskrift 3", + "toolbar.button.italic": "Kursiv tekst", + "toolbar.button.file": "Fil", + "toolbar.button.file.select": "Select a file", + "toolbar.button.file.upload": "Upload a file", + "toolbar.button.link": "Adresse", + "toolbar.button.strike": "Strike-through", + "toolbar.button.ol": "Ordnet liste", + "toolbar.button.underline": "Underline", + "toolbar.button.ul": "Punktliste", + + "translation.author": "Kirby Team", + "translation.direction": "ltr", + "translation.name": "Norsk Bokm\u00e5l", + "translation.locale": "nb_NO", + + "upload": "Last opp", + "upload.error.cantMove": "The uploaded file could not be moved", + "upload.error.cantWrite": "Failed to write file to disk", + "upload.error.default": "The file could not be uploaded", + "upload.error.extension": "File upload stopped by extension", + "upload.error.formSize": "The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the form", + "upload.error.iniPostSize": "The uploaded file exceeds the post_max_size directive in php.ini", + "upload.error.iniSize": "The uploaded file exceeds the upload_max_filesize directive in php.ini", + "upload.error.noFile": "No file was uploaded", + "upload.error.noFiles": "No files were uploaded", + "upload.error.partial": "The uploaded file was only partially uploaded", + "upload.error.tmpDir": "Missing a temporary folder", + "upload.errors": "Feil", + "upload.progress": "Laster opp…", + + "url": "Nettadresse", + "url.placeholder": "https://example.com", + + "user": "Bruker", + "user.blueprint": "Du kan definere flere seksjoner og skjemafelter for denne brukerrollen i /site/blueprints/users/{role}.yml", + "user.changeEmail": "Endre e-post", + "user.changeLanguage": "Endre språk", + "user.changeName": "Angi nytt navn for denne brukeren", + "user.changePassword": "Bytt passord", + "user.changePassword.new": "Nytt passord", + "user.changePassword.new.confirm": "Bekreft nytt passord…", + "user.changeRole": "Bytt rolle", + "user.changeRole.select": "Velg en ny rolle", + "user.create": "Legg til ny bruker", + "user.delete": "Slett denne brukeren", + "user.delete.confirm": "Vil du virkelig slette denne konten?", + + "users": "Brukere", + + "version": "Kirby versjon", + + "view.account": "Din konto", + "view.installation": "Installasjon", + "view.resetPassword": "Reset password", + "view.settings": "Innstillinger", + "view.site": "Side", + "view.users": "Brukere", + + "welcome": "Velkommen", + "year": "År", + "yes": "yes" +} diff --git a/kirby/i18n/translations/nl.json b/kirby/i18n/translations/nl.json new file mode 100644 index 0000000..6cc7368 --- /dev/null +++ b/kirby/i18n/translations/nl.json @@ -0,0 +1,527 @@ +{ + "add": "Voeg toe", + "avatar": "Avatar", + "back": "Terug", + "cancel": "Annuleren", + "change": "Wijzigen", + "close": "Sluiten", + "confirm": "OK", + "collapse": "Sluit", + "collapse.all": "Sluit alles", + "copy": "Kopiëren", + "create": "Aanmaken", + + "date": "Datum", + "date.select": "Selecteer een datum", + + "day": "Dag", + "days.fri": "Vr", + "days.mon": "Ma", + "days.sat": "Za", + "days.sun": "Zo", + "days.thu": "Do", + "days.tue": "Di", + "days.wed": "Wo", + + "delete": "Verwijderen", + "delete.all": "Verwijder alles", + "dimensions": "Dimensies", + "disabled": "Uitgeschakeld", + "discard": "Annuleren", + "download": "Download", + "duplicate": "Dupliceren", + "edit": "Wijzig", + "expand": "Open", + "expand.all": "Open alles", + + "dialog.files.empty": "Geen bestanden om te selecteren", + "dialog.pages.empty": "Geen pagina's om te selecteren", + "dialog.users.empty": "Geen gebruikers om te selecteren", + + "email": "E-mailadres", + "email.placeholder": "mail@voorbeeld.nl", + + "error.access.code": "Ongeldige code", + "error.access.login": "Ongeldige login", + "error.access.panel": "Je hebt geen toegang tot het Panel", + "error.access.view": "Je hebt geen toegangsrechten voor deze zone van het Panel", + + "error.avatar.create.fail": "De avatar kon niet worden geupload", + "error.avatar.delete.fail": "De avatar kan niet worden verwijderd", + "error.avatar.dimensions.invalid": "Houd de breedte en hoogte van de avatar onder 3000 pixels", + "error.avatar.mime.forbidden": "De avatar moet een JPEG of PNG bestand zijn", + + "error.blueprint.notFound": "De blueprint \"{name}\" kon niet geladen worden", + + "error.blocks.max.plural": "Je kunt niet meer dan {max} blokken toevoegen", + "error.blocks.max.singular": "Je kunt niet meer dan één blok toevoegen", + "error.blocks.min.plural": "Je moet ten minste {min} blok toevoegen", + "error.blocks.min.singular": "Je moet ten minste één blok toevoegen", + "error.blocks.validation": "Er is een fout gevonden in blok {index}", + + "error.email.preset.notFound": "De e-mailvoorinstelling \"{name}\" kan niet worden gevonden", + + "error.field.converter.invalid": "Ongeldige converter \"{converter}\"", + + "error.file.changeName.empty": "De naam mag niet leeg zijn", + "error.file.changeName.permission": "Je hebt geen rechten om de naam te wijzigen van \"{filename}\"", + "error.file.duplicate": "Er bestaat al een bestand met de naam \"{filename}\"", + "error.file.extension.forbidden": "Bestandsextensie \"{extension}\" is niet toegestaan", + "error.file.extension.invalid": "Ongeldige extensie: {extension}", + "error.file.extension.missing": "Je kunt geen bestanden uploaden zonder bestandsextensie", + "error.file.maxheight": "De hoogte van de afbeelding mag niet groter zijn dan {height} pixels", + "error.file.maxsize": "Het bestand is te groot", + "error.file.maxwidth": "De breedte van de afbeelding mag niet groter zijn dan {width} pixels", + "error.file.mime.differs": "Het geüploade bestand moet van hetzelfde mime-type zijn: \"{mime}\"", + "error.file.mime.forbidden": "Het type \"{mime}\" is niet toegestaan", + "error.file.mime.invalid": "Ongeldig media type: {mine}", + "error.file.mime.missing": "Het mediatype voor \"{filename}\" kan niet worden gedecteerd", + "error.file.minheight": "De hoogte van de afbeelding moet minimaal {height} pixels zijn", + "error.file.minsize": "Het bestand is te klein", + "error.file.minwidth": "De breedte van de afbeelding moet minimaal {width} pixels zijn", + "error.file.name.missing": "De bestandsnaam mag niet leeg zijn", + "error.file.notFound": "Het bestand kan niet worden gevonden", + "error.file.orientation": "De oriëntatie van de afbeelding moet \"{orientation}\" zijn", + "error.file.type.forbidden": "Je hebt geen rechten om {type} bestanden up te loaden", + "error.file.type.invalid": "Ongeldig bestands type: {type}", + "error.file.undefined": "Het bestand kan niet worden gevonden", + + "error.form.incomplete": "Verbeter alle fouten in het formulier", + "error.form.notSaved": "Het formulier kon niet worden opgeslagen", + + "error.language.code": "Vul een geldige code voor deze taal in", + "error.language.duplicate": "De taal bestaat al", + "error.language.name": "Vul een geldige naam voor deze taal in", + + "error.layout.validation.block": "Er is een fout gevonden in blok {blockIndex} in ontwerp {layoutIndex}", + "error.layout.validation.settings": "Er is een fout gevonden in de instellingen van ontwerp {index} ", + + "error.license.format": "Vul een gelidge licentie-key in", + "error.license.email": "Gelieve een geldig emailadres in te voeren", + "error.license.verification": "De licentie kon niet worden geverifieerd. ", + + "error.page.changeSlug.permission": "Je kunt de URL van deze pagina niet wijzigen", + "error.page.changeStatus.incomplete": "Deze pagina bevat fouten en kan niet worden gepubliceerd", + "error.page.changeStatus.permission": "De status van deze pagina kan niet worden gewijzigd", + "error.page.changeStatus.toDraft.invalid": "De pagina \"{slug}\" kan niet worden aangepast naar 'concept'", + "error.page.changeTemplate.invalid": "De template van deze pagina \"{slug}\" kan niet worden gewijzigd", + "error.page.changeTemplate.permission": "Je hebt geen rechten om het template te wijzigen van \"{slug}\"", + "error.page.changeTitle.empty": "De titel mag niet leeg zijn", + "error.page.changeTitle.permission": "Je hebt geen rechten om de titel te wijzigen van \"{slug}\"", + "error.page.create.permission": "Je hebt geen rechten om \"{slug}\" aan te maken", + "error.page.delete": "De pagina \"{slug}\" kan niet worden verwijderd", + "error.page.delete.confirm": "Voer de paginatitel in om te bevestigen", + "error.page.delete.hasChildren": "Deze pagina heeft subpagina's en kan niet worden verwijderd", + "error.page.delete.permission": "Je hebt geen rechten om \"{slug}\" te verwijderen", + "error.page.draft.duplicate": "Er bestaat al een conceptpagina met de URL-appendix \"{slug}\"", + "error.page.duplicate": "Er bestaat al een pagina met de URL-appendix \"{slug}\"", + "error.page.duplicate.permission": "Je bent niet gemachtigd om \"{slug}\" te dupliceren", + "error.page.notFound": "De pagina \"{slug}\" kan niet worden gevonden", + "error.page.num.invalid": "Vul een geldig sorteer-cijfer in. Het cijfer mag niet negatief zijn", + "error.page.slug.invalid": "Please enter a valid URL appendix", + "error.page.slug.maxlength": "Slug lengte moet minder dan \"{length}\" tekens bevatten", + "error.page.sort.permission": "De pagina \"{slug}\" kan niet worden gesorteerd", + "error.page.status.invalid": "Zorg voor een geldige paginastatus", + "error.page.undefined": "De pagina kan niet worden gevonden", + "error.page.update.permission": "Je hebt geen rechten om \"{slug}\" te updaten", + + "error.section.files.max.plural": "Voeg niet meer dan {max} bestanden toe aan de zone \"{section}\"", + "error.section.files.max.singular": "Je kunt niet meer dan 1 bestand toevoegen aan de zone \"{section}\"", + "error.section.files.min.plural": "De \"{section}\" sectie moet minimaal {min} bestanden bevatten.", + "error.section.files.min.singular": "De \"{section}\" sectie moet minimaal 1 bestand bevatten.", + + "error.section.pages.max.plural": "Je kunt niet meer dan {max} pagina's toevoegen aan de zone \"{section}\"", + "error.section.pages.max.singular": "Je kunt niet meer dan 1 pagina toevoegen aan de zone \"{section}\"", + "error.section.pages.min.plural": "De \"{section}\" sectie moet minimaal {min} pagina's bevatten.", + "error.section.pages.min.singular": "De \"{section}\" sectie moet minimaal 1 pagina bevatten.", + + "error.section.notLoaded": "De zone \"{name}\" kan niet worden geladen", + "error.section.type.invalid": "Zone-type \"{type}\" is niet geldig", + + "error.site.changeTitle.empty": "De titel mag niet leeg zijn", + "error.site.changeTitle.permission": "Je hebt geen rechten om de titel van de site te wijzigen", + "error.site.update.permission": "Je hebt geen rechten om de site te updaten", + + "error.template.default.notFound": "Het standaard template bestaat niet", + + "error.user.changeEmail.permission": "Je hebt geen rechten om het e-mailadres van gebruiker \"{name}\" te wijzigen", + "error.user.changeLanguage.permission": "Je hebt geen rechten om de taal voor gebruiker \"{name}\" te wijzigen", + "error.user.changeName.permission": "Je hebt geen rechten om de naam van gebruiker \"{name}\" te wijzigen", + "error.user.changePassword.permission": "Je hebt geen rechten om het wachtwoord van gebruiker \"{name}\" te wijzigen", + "error.user.changeRole.lastAdmin": "De rol van de laatste beheerder kan niet worden gewijzigd", + "error.user.changeRole.permission": "Je hebt geen rechten om de rol van gebruiker \"{name}\" te wijzigen", + "error.user.changeRole.toAdmin": "Je hebt geen rechten om de rol van iemand te wijzigen naar admin", + "error.user.create.permission": "Je hebt geen rechten om deze gebruiker aan te maken", + "error.user.delete": "De gebruiker \"{name}\" kan niet worden verwijderd", + "error.user.delete.lastAdmin": "Je kan de laatste admin niet verwijderen", + "error.user.delete.lastUser": "De laatste gebruiker kan niet worden verwijderd", + "error.user.delete.permission": "Je hebt geen rechten om gebruiker \"{name}\" te verwijderen", + "error.user.duplicate": "Er bestaat al een gebruiker met e-mailadres \"{email}\"", + "error.user.email.invalid": "Gelieve een geldig emailadres in te voeren", + "error.user.language.invalid": "Gelieve een geldige taal in te voeren", + "error.user.notFound": "De gebruiker \"{name}\" kan niet worden gevonden", + "error.user.password.invalid": "Gelieve een geldig wachtwoord in te voeren. Wachtwoorden moeten minstens 8 karakters lang zijn.", + "error.user.password.notSame": "De wachtwoorden komen niet overeen", + "error.user.password.undefined": "De gebruiker heeft geen wachtwoord", + "error.user.password.wrong": "Wrong password", + "error.user.role.invalid": "Gelieve een geldige rol in te voeren", + "error.user.update.permission": "Je hebt geen rechten om gebruiker \"{name}\" te updaten", + + "error.validation.accepted": "Gelieve te bevestigen", + "error.validation.alpha": "Vul alleen a-z karakters in", + "error.validation.alphanum": "Vul alleen a-z karakters of cijfers (0-9) in", + "error.validation.between": "Vul een waarde tussen \"{min}\" en \"{max}\"", + "error.validation.boolean": "Ga akkoord of weiger", + "error.validation.contains": "Vul een waarde in die \"{needle}\" bevat", + "error.validation.date": "Vul een geldige datum in", + "error.validation.date.after": "Vul een datum in na {date}", + "error.validation.date.before": "Vul een datum in voor {date}", + "error.validation.date.between": "Vul een datum in tussen {min} en {max}", + "error.validation.denied": "Weiger", + "error.validation.different": "De invoer mag niet \"{other}\" zijn", + "error.validation.email": "Gelieve een geldig emailadres in te voeren", + "error.validation.endswith": "De invoer moet eindigen met \"{end}\"", + "error.validation.filename": "Vul een geldige bestandsnaam in", + "error.validation.in": "Vul één van de volgende dingen in: ({in})", + "error.validation.integer": "Vul een geldig geheel getal in", + "error.validation.ip": "Vul een geldig IP-adres in", + "error.validation.less": "Vul een waarde in lager dan {max}", + "error.validation.match": "De invoer klopt niet met het verwachte patroon", + "error.validation.max": "Vul een waarde in die gelijk is aan of lager dan {max}", + "error.validation.maxlength": "Gebruik minder karakters (maximaal {max} karakters)", + "error.validation.maxwords": "Vul minder dan {max} woord(en) in", + "error.validation.min": "Vul een waarde in die gelijk is aan of groter dan {min}", + "error.validation.minlength": "Gebruik meer karakters (minimaal {min} karakters)", + "error.validation.minwords": "Vul minimaal {min} woord(en) in", + "error.validation.more": "Vul een grotere waarde in dan {min}", + "error.validation.notcontains": "Zorg dat de invoer niet \"{needle}\" bevat", + "error.validation.notin": "Vul de volgende dingen niet in: ({notIn})", + "error.validation.option": "Selecteer een geldige optie", + "error.validation.num": "Vul een geldig cijfer in", + "error.validation.required": "Vul iets in", + "error.validation.same": "Vul \"{other}\" in", + "error.validation.size": "De lengte van de invoer moet \"{size}\" zijn", + "error.validation.startswith": "De invoer moet beginnen met \"{start}\"", + "error.validation.time": "Vul een geldige tijd in", + "error.validation.time.after": "Voer een tijd in na {time}", + "error.validation.time.before": "Voer een tijd in voor {time}", + "error.validation.time.between": "Voer een tijd in tussen {min} en {max}", + "error.validation.url": "Vul een geldige URL in", + + "field.required": "Dit veld is verplicht", + "field.blocks.changeType": "Wijzig type", + "field.blocks.code.name": "Code", + "field.blocks.code.language": "Taal", + "field.blocks.code.placeholder": "Jouw code ...", + "field.blocks.delete.confirm": "Wil je echt dit blok wilt verwijderen?", + "field.blocks.delete.confirm.all": "Wil je echt alle blokken verwijderen?", + "field.blocks.delete.confirm.selected": "Wil je de geselecteerde blokken echt verwijderen?", + "field.blocks.empty": "Nog geen blokken", + "field.blocks.fieldsets.label": "Selecteer een bloktype ...", + "field.blocks.gallery.name": "Galerij", + "field.blocks.gallery.images.empty": "Nog geen afbeeldingen", + "field.blocks.gallery.images.label": "Afbeeldingen", + "field.blocks.heading.level": "Niveau", + "field.blocks.heading.name": "Koptekst", + "field.blocks.heading.text": "Tekst", + "field.blocks.heading.placeholder": "Koptekst ...", + "field.blocks.image.alt": "Alternatieve tekst", + "field.blocks.image.caption": "Beschrijving", + "field.blocks.image.crop": "Uitsnede", + "field.blocks.image.link": "Link", + "field.blocks.image.location": "Locatie", + "field.blocks.image.name": "Afbeelding", + "field.blocks.image.placeholder": "Selecteer een afbeelding", + "field.blocks.image.ratio": "Verhouding", + "field.blocks.image.url": "Afbeeldings-URL", + "field.blocks.list.name": "Lijst", + "field.blocks.markdown.name": "Markdown", + "field.blocks.markdown.label": "Tekst", + "field.blocks.markdown.placeholder": "Markdown ...", + "field.blocks.quote.name": "Citaat", + "field.blocks.quote.text.label": "Tekst", + "field.blocks.quote.text.placeholder": "Citaat ...", + "field.blocks.quote.citation.label": "Bron", + "field.blocks.quote.citation.placeholder": "door ...", + "field.blocks.text.name": "Tekst", + "field.blocks.text.placeholder": "Tekst ...", + "field.blocks.video.caption": "Beschrijving", + "field.blocks.video.name": "Video", + "field.blocks.video.placeholder": "Voer een video link in", + "field.blocks.video.url.label": "Video link", + "field.blocks.video.url.placeholder": "https://youtube.com/?v=", + + "field.files.empty": "Nog geen bestanden geselecteerd", + + "field.layout.delete": "Verwijder indeling", + "field.layout.delete.confirm": "Weet je zeker dat je deze indeling wilt verwijderen?", + "field.layout.empty": "Er zijn nog geen rijen", + "field.layout.select": "Selecteer een indeling", + + "field.pages.empty": "Nog geen pagina's geselecteerd", + "field.structure.delete.confirm": "Wil je deze entry verwijderen?", + "field.structure.empty": "Nog geen items.", + "field.users.empty": "Nog geen gebruikers geselecteerd", + + "file.blueprint": "Dit bestand heeft nog geen ontwerp. Je kan het ontwerp hier plaatsen /site/blueprints/{template}.yml", + "file.delete.confirm": "Wil je dit bestand
{filename} verwijderen?", + "file.sort": "Verander positie", + + "files": "Bestanden", + "files.empty": "Nog geen bestanden", + + "hide": "Verberg", + "hour": "Uur", + "insert": "Toevoegen", + "insert.after": "Voeg toe na", + "insert.before": "Voeg toe voor", + "install": "Installeren", + + "installation": "Installatie", + "installation.completed": "Het Panel is geïnstalleerd", + "installation.disabled": "Je kan geen Panel installatie uitvoeren op een openbare server. Voer het installatieprogramma uit op een lokale computer of schakel het in met de panel.install optie.", + "installation.issues.accounts": "De map /site/accounts heeft geen schrijfrechten", + "installation.issues.content": "De map /content bestaat niet of heeft geen schrijfrechten", + "installation.issues.curl": "De CURL-extensie is vereist", + "installation.issues.headline": "Het Panel kan niet worden geïnstalleerd", + "installation.issues.mbstring": "De MB String extensie is verplicht", + "installation.issues.media": "De map /mediabestaat niet of heeft geen schrijfrechten", + "installation.issues.php": "Gebruik PHP7+", + "installation.issues.server": "Kirby vereist Apache, Nginx of Caddy", + "installation.issues.sessions": "De map /site/sessions bestaat niet of heeft geen schrijfrechten", + + "language": "Taal", + "language.code": "Code", + "language.convert": "Maak standaard", + "language.convert.confirm": "

Weet je zeker dat je {name} wilt aanpassen naar de standaard taal? Dit kan niet ongedaan worden gemaakt

Als {name} nog niet vertaalde content heeft, is er geen content meer om op terug te vallen en zouden delen van je site leeg kunnen zijn.

", + "language.create": "Nieuwe taal toevoegen", + "language.delete.confirm": "Weet je zeker dat je de taal {name} inclusief alle vertalingen wilt verwijderen? Je kunt dit niet ongedaan maken!", + "language.deleted": "De taal is verwijderd", + "language.direction": "Leesrichting", + "language.direction.ltr": "Links naar rechts", + "language.direction.rtl": "Rechts naar links", + "language.locale": "PHP-locale regel", + "language.locale.warning": "Je gebruikt een aangepaste landinstelling. Wijzig het het taalbestand in /site/languages", + "language.name": "Naam", + "language.updated": "De taal is geüpdatet", + + "languages": "Talen", + "languages.default": "Standaard taal", + "languages.empty": "Er zijn nog geen talen", + "languages.secondary": "Andere talen", + "languages.secondary.empty": "Er zijn nog geen andere talen beschikbaar", + + "license": "Licentie", + "license.buy": "Koop een licentie", + "license.register": "Registreren", + "license.register.help": "Je hebt de licentie via e-mail gekregen nadat je de aankoop hebt gedaan. Kopieer en plak de licentie om te registreren. ", + "license.register.label": "Vul je licentie in", + "license.register.success": "Bedankt dat je Kirby ondersteunt", + "license.unregistered": "Dit is een niet geregistreerde demo van Kirby", + + "link": "Link", + "link.text": "Linktekst", + + "loading": "Laden", + + "lock.unsaved": "Niet opgeslagen wijzigingen", + "lock.unsaved.empty": "Er zijn geen niet opgeslagen wijzigingen meer", + "lock.isLocked": "Niet opgeslagen wijzigingen door {email}", + "lock.file.isLocked": "Dit bestand wordt momenteel bewerkt door {email} en kan niet worden gewijzigd.", + "lock.page.isLocked": "Deze pagina wordt momenteel bewerkt door {email} en kan niet worden gewijzigd.", + "lock.unlock": "Ontgrendelen", + "lock.isUnlocked": "Je niet opgeslagen wijzigingen zijn overschreven door een andere gebruiker. Je kunt je wijzigingen downloaden om ze handmatig samen te voegen.", + + "login": "Inloggen", + "login.code.label.login": "Log in code", + "login.code.label.password-reset": "Wachtwoord herstel code", + "login.code.placeholder.email": "000 000", + "login.code.text.email": "Als uw e-mailadres geregistreerd is, werd de gevraagde code per e-mail verzonden.", + "login.email.login.body": "Hallo {user.nameOrEmail},\n\nU heeft onlangs een inlogcode aangevraagd voor het Kirby Panel.\nDe volgende inlogcode is geldig voor {timeout} minuten:\n\n{code}\n\nIndien u geen inlogcode heeft aangevraagd, negeer dan alstublieft deze email of neem contact op met uw beheerder indien u vragen heeft.\nVoor de veiligheid, gelieve deze email NIET door te sturen.", + "login.email.login.subject": "Jouw log in code", + "login.email.password-reset.body": "Hallo {user.nameOrEmail},\n\nU heeft onlangs een wachtwoord reset code aangevraagd voor het Kirby Panel.\nDe volgende wachtwoord reset code is geldig voor {timeout} minuten:\n\n{code}\n\nAls u geen wachtwoord reset code heeft aangevraagd, negeer deze e-mail dan alstublieft of neem contact op met uw beheerder als u vragen heeft.\nVoor de veiligheid, gelieve deze email NIET door te sturen.", + "login.email.password-reset.subject": "Jouw wachtwoord herstel code", + "login.remember": "Houd mij ingelogd", + "login.reset": "Wachtwoord herstellen", + "login.toggleText.code.email": "Log in via email", + "login.toggleText.code.email-password": "Log in met je wachtwoord", + "login.toggleText.password-reset.email": "Wachtwoord vergeten?", + "login.toggleText.password-reset.email-password": "← Terug naar log in", + + "logout": "Uitloggen", + + "menu": "Menu", + "meridiem": "AM/PM", + "mime": "Mime-type", + "minutes": "Minuten", + + "month": "Maand", + "months.april": "april", + "months.august": "augustus", + "months.december": "december", + "months.february": "februari", + "months.january": "januari", + "months.july": "juli", + "months.june": "juni", + "months.march": "maart", + "months.may": "mei", + "months.november": "november", + "months.october": "oktober", + "months.september": "september", + + "more": "Meer", + "name": "Naam", + "next": "Volgende", + "no": "no", + "off": "uit", + "on": "aan", + "open": "Open", + "open.newWindow": "Open in new window", + "options": "Opties", + "options.none": "Geen opties beschikbaar", + + "orientation": "Oriëntatie", + "orientation.landscape": "Liggend", + "orientation.portrait": "Staand", + "orientation.square": "Vierkant", + + "page.blueprint": "Deze pagina heeft nog geen ontwerp. Je kan het ontwerp hier plaatsten /site/blueprints/{template}.yml", + "page.changeSlug": "Verander URL", + "page.changeSlug.fromTitle": "Aanmaken op basis van titel", + "page.changeStatus": "Wijzig status", + "page.changeStatus.position": "Selecteer een positie", + "page.changeStatus.select": "Selecteer een nieuwe status", + "page.changeTemplate": "Verander template", + "page.delete.confirm": "Weet je zeker dat je pagina {title} wilt verwijderen?", + "page.delete.confirm.subpages": "Deze pagina heeft subpagina's.
Alle subpagina's zullen ook worden verwijderd.", + "page.delete.confirm.title": "Voeg een paginatitel in om te bevestigen", + "page.draft.create": "Maak concept", + "page.duplicate.appendix": "Kopiëren", + "page.duplicate.files": "Kopieer bestanden", + "page.duplicate.pages": "Kopieer pagina's", + "page.sort": "Verander positie", + "page.status": "Status", + "page.status.draft": "Concept", + "page.status.draft.description": "De pagina is in concept-modus en alleen zichtbaar voor ingelogde redacteuren of via een geheime link", + "page.status.listed": "Openbaar", + "page.status.listed.description": "Deze pagina is toegankelijk voor iedereen", + "page.status.unlisted": "Niet gepubliceerd", + "page.status.unlisted.description": "Deze pagina is alleen bereikbaar via URL", + + "pages": "Pagina’s", + "pages.empty": "Nog geen pagina's", + "pages.status.draft": "Concepten", + "pages.status.listed": "Gepubliceerd", + "pages.status.unlisted": "Niet gepubliceerd", + + "pagination.page": "Pagina", + + "password": "Wachtwoord", + "pixel": "Pixel", + "prev": "Vorige", + "preview": "Voorbeeld", + "remove": "Verwijder", + "rename": "Hernoem", + "replace": "Vervang", + "retry": "Probeer opnieuw", + "revert": "Annuleren", + "revert.confirm": "Weet je zeker dat je alle niet-opgeslagen veranderingen wilt verwijderen?", + + "role": "Rol", + "role.admin.description": "De admin heeft alle rechten", + "role.admin.title": "Admin", + "role.all": "Alle", + "role.empty": "Er zijn geen gebruikers met deze rol", + "role.description.placeholder": "Geen beschrijving", + "role.nobody.description": "Dit is een fallback-rol zonder rechten", + "role.nobody.title": "Niemand", + + "save": "Opslaan", + "search": "Zoeken", + "search.min": "Voer {min} tekens in om te zoeken", + "search.all": "Toon alles", + "search.results.none": "Geen resultaten", + + "section.required": "De sectie is verplicht", + + "select": "Selecteren", + "settings": "Opties", + "show": "Toon", + "size": "Grootte", + "slug": "URL-toevoeging", + "sort": "Sorteren", + "title": "Titel", + "template": "Template", + "today": "Vandaag", + + "site.blueprint": "Deze website heeft nog geen ontwerp. Je kan het ontwerp hier plaatsen/site/blueprints/site.yml", + + "toolbar.button.code": "Code", + "toolbar.button.bold": "Dikgedrukte tekst", + "toolbar.button.email": "E-mailadres", + "toolbar.button.headings": "Kopteksten", + "toolbar.button.heading.1": "Koptekst 1", + "toolbar.button.heading.2": "Koptekst 2", + "toolbar.button.heading.3": "Koptekst 3", + "toolbar.button.italic": "Cursieve tekst", + "toolbar.button.file": "Bestand", + "toolbar.button.file.select": "Selecteer een bestand", + "toolbar.button.file.upload": "Upload bestand", + "toolbar.button.link": "Link", + "toolbar.button.strike": "Strike-through", + "toolbar.button.ol": "Genummerde lijst", + "toolbar.button.underline": "Underline", + "toolbar.button.ul": "Opsomming", + + "translation.author": "Het team van Kirby", + "translation.direction": "ltr", + "translation.name": "Nederlands", + "translation.locale": "nl_NL", + + "upload": "Upload", + "upload.error.cantMove": "Het geüploadde bestand kon niet worden verplaatst", + "upload.error.cantWrite": "Fout bij het schrijven van het bestand naar de schijf", + "upload.error.default": "Het bestand kan niet worden geüpload", + "upload.error.extension": "Kan bestand niet uploaden vanwege de extensie", + "upload.error.formSize": "Het geüploadde bestand is groter dan de MAX_FILE_SIZE die is aangegeven in het formulier", + "upload.error.iniPostSize": "Het geüploadde bestand is groter dan de post_max_size in php.ini", + "upload.error.iniSize": "Het geüploadde bestand is groter dan de upload_max_filesize in php.ini", + "upload.error.noFile": "Er is geen bestand geüpload", + "upload.error.noFiles": "Er zijn geen bestanden geüpload", + "upload.error.partial": "Het geüploadde bestand is slechts gedeeltelijk geüpload", + "upload.error.tmpDir": "Er mist een tijdelijke map", + "upload.errors": "Foutmelding", + "upload.progress": "Uploaden...", + + "url": "Url", + "url.placeholder": "https://voorbeeld.nl", + + "user": "Gebruiker", + "user.blueprint": "Je kunt extra zones en formuliervelden voor deze rol toevoegen in /site/blueprints/users/{role}.yml", + "user.changeEmail": "Email veranderen", + "user.changeLanguage": "Taal veranderen", + "user.changeName": "Gebruiker hernoemen", + "user.changePassword": "Wachtwoord wijzigen", + "user.changePassword.new": "Nieuw wachtwoord", + "user.changePassword.new.confirm": "Bevestig het nieuwe wachtwoord...", + "user.changeRole": "Verander rol", + "user.changeRole.select": "Kies een nieuwe rol", + "user.create": "Voeg een nieuwe gebruiker toe", + "user.delete": "Verwijder deze gebruiker", + "user.delete.confirm": "Weet je zeker dat je
{email} wil verwijderen?", + + "users": "Gebruikers", + + "version": "Kirby-versie", + + "view.account": "Jouw account", + "view.installation": "Installatie", + "view.resetPassword": "Wachtwoord herstellen", + "view.settings": "Opties", + "view.site": "Site", + "view.users": "Gebruikers", + + "welcome": "Welkom", + "year": "Jaar", + "yes": "yes" +} diff --git a/kirby/i18n/translations/pl.json b/kirby/i18n/translations/pl.json new file mode 100644 index 0000000..16196b2 --- /dev/null +++ b/kirby/i18n/translations/pl.json @@ -0,0 +1,527 @@ +{ + "add": "Dodaj", + "avatar": "Zdj\u0119cie profilowe", + "back": "Wróć", + "cancel": "Anuluj", + "change": "Zmie\u0144", + "close": "Zamknij", + "confirm": "Ok", + "collapse": "Zwiń", + "collapse.all": "Zwiń wszystkie", + "copy": "Kopiuj", + "create": "Utwórz", + + "date": "Data", + "date.select": "Wybierz datę", + + "day": "Dzień", + "days.fri": "Pt", + "days.mon": "Pn", + "days.sat": "Sb", + "days.sun": "Nd", + "days.thu": "Czw", + "days.tue": "Wt", + "days.wed": "\u015ar", + + "delete": "Usu\u0144", + "delete.all": "Usuń wszystkie", + "dimensions": "Wymiary", + "disabled": "Wyłączone", + "discard": "Odrzu\u0107", + "download": "Pobierz", + "duplicate": "Zduplikuj", + "edit": "Edytuj", + "expand": "Rozwiń", + "expand.all": "Rozwiń wszystkie", + + "dialog.files.empty": "Brak plików do wyboru", + "dialog.pages.empty": "Brak stron do wyboru", + "dialog.users.empty": "Brak użytkowników do wyboru", + + "email": "Email", + "email.placeholder": "mail@example.com", + + "error.access.code": "Nieprawidłowy kod", + "error.access.login": "Nieprawidłowy login", + "error.access.panel": "Nie masz uprawnień by dostać się do panelu", + "error.access.view": "Nie masz uprawnień, by dostać się do tej części panelu", + + "error.avatar.create.fail": "Nie udało się załadować zdjęcia profilowego", + "error.avatar.delete.fail": "Nie udało się usunąć zdjęcia profilowego", + "error.avatar.dimensions.invalid": "Proszę zachować szerokość i wysokość zdjęcia profilowego poniżej 3000 pikseli", + "error.avatar.mime.forbidden": "Zdjęcie profilowe musi być plikiem JPEG lub PNG", + + "error.blueprint.notFound": "Nie udało się załadować wzorca \"{name}\"", + + "error.blocks.max.plural": "Możesz dodać nie więcej niż {max} bloki/-ów", + "error.blocks.max.singular": "Możesz dodać tylko jeden blok", + "error.blocks.min.plural": "Musisz dodać co najmniej {min} bloki/-ów", + "error.blocks.min.singular": "Musisz dodać co najmniej jeden blok", + "error.blocks.validation": "W bloku {index} jest błąd", + + "error.email.preset.notFound": "Nie udało się załadować wzorca wiadomości e-mail \"{name}\"", + + "error.field.converter.invalid": "Nieprawidłowy konwerter \"{converter}\"", + + "error.file.changeName.empty": "Imię nie może być puste", + "error.file.changeName.permission": "Nie masz uprawnień, by zmienić nazwę \"{filename}\"", + "error.file.duplicate": "Istnieje już plik o nazwie \"{filename}\"", + "error.file.extension.forbidden": "Rozszerzenie \"{extension}\" jest niedozwolone", + "error.file.extension.invalid": "Nieprawidłowe rozszerzenie: {extension}", + "error.file.extension.missing": "Brak rozszerzenia pliku \"{filename}\"", + "error.file.maxheight": "Wysokość obrazka nie może być większa niż {height} pikseli", + "error.file.maxsize": "Plik jest za duży", + "error.file.maxwidth": "Szerokość obrazka nie może być większa niż {width} pikseli", + "error.file.mime.differs": "Przesłany plik musi być tego samego typu mime \"{mime}\"", + "error.file.mime.forbidden": "Typ multimediów \"{mime}\" jest niedozwolony", + "error.file.mime.invalid": "Nieprawidłowy typ MIME: {mime}", + "error.file.mime.missing": "Nie można wykryć typu multimediów dla \"{filename}\"", + "error.file.minheight": "Wysokość obrazka musi wynosić co najmniej {height} pikseli", + "error.file.minsize": "Plik jest za mały", + "error.file.minwidth": "Szerokość obrazka musi wynosić co najmniej {width} pikseli", + "error.file.name.missing": "Nazwa pliku nie może być pusta", + "error.file.notFound": "Nie można znaleźć pliku \"{filename}\"", + "error.file.orientation": "Orientacja obrazka musi być \"{orientation}\"", + "error.file.type.forbidden": "Nie możesz przesyłać plików {type}", + "error.file.type.invalid": "Nieprawidłowy typ pliku: {type}", + "error.file.undefined": "Nie można znaleźć pliku", + + "error.form.incomplete": "Popraw wszystkie błędy w formularzu…", + "error.form.notSaved": "Nie udało się zapisać formularza", + + "error.language.code": "Wprowadź poprawny kod języka.", + "error.language.duplicate": "Język już istnieje.", + "error.language.name": "Wprowadź poprawną nazwę języka.", + + "error.layout.validation.block": "W bloku {blockIndex} w układzie {layoutIndex} jest błąd", + "error.layout.validation.settings": "W ustawieniach układu {index} jest błąd", + + "error.license.format": "Wprowadź poprawny klucz licencyjny", + "error.license.email": "Wprowadź poprawny adres email", + "error.license.verification": "Nie udało się zweryfikować licencji", + + "error.page.changeSlug.permission": "Nie możesz zmienić końcówki adresu URL w \"{slug}\"", + "error.page.changeStatus.incomplete": "Strona zawiera błędy i nie można jej opublikować", + "error.page.changeStatus.permission": "Status tej strony nie może zostać zmieniony", + "error.page.changeStatus.toDraft.invalid": "Strony \"{slug}\" nie można przekonwertować na szkic", + "error.page.changeTemplate.invalid": "Nie można zmienić szablonu strony \"{slug}\"", + "error.page.changeTemplate.permission": "Nie masz uprawnień, by zmienić szablon dla \"{slug}\"", + "error.page.changeTitle.empty": "Tytuł nie może być pusty", + "error.page.changeTitle.permission": "Nie masz uprawnień, by zmienić tytuł dla \"{slug}\"", + "error.page.create.permission": "Nie masz uprawnień, by utworzyć \"{slug}\"", + "error.page.delete": "Strony \"{slug}\" nie można usunąć", + "error.page.delete.confirm": "Wprowadź tytuł strony, aby potwierdzić", + "error.page.delete.hasChildren": "Strona zawiera podstrony i nie można jej usunąć", + "error.page.delete.permission": "Nie masz uprawnień, by usunąć \"{slug}\"", + "error.page.draft.duplicate": "Istnieje już szkic z końcówką URL \"{slug}\"", + "error.page.duplicate": "Istnieje już strona z końcówką URL \"{slug}\"", + "error.page.duplicate.permission": "Nie masz uprawnień, by zduplikować \"{slug}\"", + "error.page.notFound": "Nie można znaleźć strony \"{slug}\"", + "error.page.num.invalid": "Wprowadź poprawny numer sortujący. Liczby nie mogą być ujemne.", + "error.page.slug.invalid": "Wprowadź poprawną końcówkę adresu URL", + "error.page.slug.maxlength": "Końcówka adresu musi być krótsza niż \"{length}\" znaków", + "error.page.sort.permission": "Nie można sortować strony \"{slug}\"", + "error.page.status.invalid": "Ustaw prawidłowy status strony", + "error.page.undefined": "Nie udało się znaleźć strony", + "error.page.update.permission": "Nie masz uprawnień, by zaktualizować \"{slug}\"", + + "error.section.files.max.plural": "Do sekcji \"{section}\" można dodać nie więcej niż {max} plików", + "error.section.files.max.singular": "Do sekcji \"{section}\" można dodać tylko jeden plik", + "error.section.files.min.plural": "W sekcji \"{section}\" musi być co najmniej {min} pliki/-ów", + "error.section.files.min.singular": "W sekcji \"{section}\" musi być co najmniej jeden plik", + + "error.section.pages.max.plural": "Do sekcji \"{section}\" można dodać nie więcej niż {max} stron", + "error.section.pages.max.singular": "Do sekcji \"{section}\" można dodać tylko jedną stronę", + "error.section.pages.min.plural": "W sekcji \"{section}\" musi być co najmniej {min} stron/-y", + "error.section.pages.min.singular": "W sekcji \"{section}\" musi być co najmniej jedna strona", + + "error.section.notLoaded": "Nie udało się załadować sekcji \"{name}\"", + "error.section.type.invalid": "Typ sekcji \"{type}\" jest nieprawidłowy", + + "error.site.changeTitle.empty": "Tytuł nie może być pusty", + "error.site.changeTitle.permission": "Nie masz uprawnień, by zmienić tytuł strony", + "error.site.update.permission": "Nie masz uprawnień, by zaktualizować stronę", + + "error.template.default.notFound": "Domyślny szablon nie istnieje", + + "error.user.changeEmail.permission": "Nie masz uprawnień, by zmienić adres e-mail użytkownika \"{name}\"", + "error.user.changeLanguage.permission": "Nie masz uprawnień, by zmienić język użytkownika \"{name}\"", + "error.user.changeName.permission": "Nie masz uprawnień, by zmienić nazwę użytkownika \"{name}\"", + "error.user.changePassword.permission": "Nie masz uprawnień, by zmienić hasło użytkownika \"{name}\"", + "error.user.changeRole.lastAdmin": "Nie można zmienić roli ostatniego administratora", + "error.user.changeRole.permission": "Nie masz uprawnień, by zmienić rolę użytkownika \"{name}\"", + "error.user.changeRole.toAdmin": "Nie masz uprawnień, by awansować kogoś do roli daministratora", + "error.user.create.permission": "Nie masz uprawnień, by utworzyć tego użytkownika", + "error.user.delete": "Nie można usunąć użytkownika \"{name}\"", + "error.user.delete.lastAdmin": "Nie można usunąć ostatniego administratora", + "error.user.delete.lastUser": "Nie można usunąć ostatniego użytkownika", + "error.user.delete.permission": "Nie masz uprawnień, by usunąć użytkownika \"{name}\"", + "error.user.duplicate": "Istnieje już użytkownik z adresem email \"{email}\"", + "error.user.email.invalid": "Wprowadź poprawny adres email", + "error.user.language.invalid": "Proszę podać poprawny język", + "error.user.notFound": "Nie można znaleźć użytkownika \"{name}\"", + "error.user.password.invalid": "Wprowadź prawidłowe hasło. Hasła muszą mieć co najmniej 8 znaków.", + "error.user.password.notSame": "Hasła nie są takie same", + "error.user.password.undefined": "Użytkownik nie ma hasła", + "error.user.password.wrong": "Nieprawidłowe hasło", + "error.user.role.invalid": "Wprowadź poprawną rolę", + "error.user.update.permission": "Nie masz uprawnień, by zaktualizować użytkownika \"{name}\"", + + "error.validation.accepted": "Proszę potwierdzić", + "error.validation.alpha": "Wprowadź tylko znaki między a-z", + "error.validation.alphanum": "Wprowadź tylko znaki między a-z lub cyfry 0-9", + "error.validation.between": "Wprowadź wartość między \"{min}\" i \"{max}\"", + "error.validation.boolean": "Potwierdź lub odmów", + "error.validation.contains": "Wprowadź wartość, która zawiera \"{needle}\"", + "error.validation.date": "Wprowadź poprawną datę", + "error.validation.date.after": "Wprowadź datę późniejszą niż {date}", + "error.validation.date.before": "Wprowadź datę wcześniejszą niż {date}", + "error.validation.date.between": "Wprowadź datę między {min} a {max}", + "error.validation.denied": "Proszę odmówić", + "error.validation.different": "Wartością nie może być \"{other}\"", + "error.validation.email": "Wprowadź poprawny adres email", + "error.validation.endswith": "Wartość musi kończyć się na \"{end}\"", + "error.validation.filename": "Wprowadź poprawną nazwę pliku", + "error.validation.in": "Wprowadź jedno z następujących: ({in})", + "error.validation.integer": "Wprowadź poprawną liczbę całkowitą", + "error.validation.ip": "Wprowadź poprawny adres IP", + "error.validation.less": "Wprowadź wartość mniejszą niż {max}", + "error.validation.match": "Wartość nie jest zgodna z oczekiwanym wzorcem", + "error.validation.max": "Wprowadź wartość równą lub mniejszą niż {max}", + "error.validation.maxlength": "Wprowadź krótszą wartość. (maks. {max} znaków)", + "error.validation.maxwords": "Wprowadź nie więcej niż {max} słowa/słów", + "error.validation.min": "Wprowadź wartość równą lub większą niż {min}", + "error.validation.minlength": "Wprowadź dłuższą wartość. (min. {min} znaków)", + "error.validation.minwords": "Wprowadź co najmniej {min} słowa/słów", + "error.validation.more": "Wprowadź wartość większą niż {min}", + "error.validation.notcontains": "Wprowadź wartość, która nie zawiera \"{needle}\"", + "error.validation.notin": "Nie wprowadzaj żadnego z następujących ({notIn})", + "error.validation.option": "Wybierz poprawną opcję", + "error.validation.num": "Wprowadź poprawny numer", + "error.validation.required": "Wpisz coś", + "error.validation.same": "Wprowadź \"{other}\"", + "error.validation.size": "Rozmiar wartości musi wynosić \"{size}\"", + "error.validation.startswith": "Wartość musi zaczynać się od \"{start}\"", + "error.validation.time": "Wprowadź poprawny czas", + "error.validation.time.after": "Wprowadź czas późniejszy niż {time}", + "error.validation.time.before": "Wprowadź czas wcześniejszy niż {time}", + "error.validation.time.between": "Wprowadź czas między {min} a {max}", + "error.validation.url": "Wprowadź poprawny adres URL", + + "field.required": "Pole jest wymagane", + "field.blocks.changeType": "Zmień typ", + "field.blocks.code.name": "Kod", + "field.blocks.code.language": "Język", + "field.blocks.code.placeholder": "Twój kod …", + "field.blocks.delete.confirm": "Czy na pewno chcesz usunąć ten blok?", + "field.blocks.delete.confirm.all": "Czy na pewno chcesz usunąć wszystkie bloki?", + "field.blocks.delete.confirm.selected": "Czy na pewno chcesz usunąć wszystkie wybrane bloki?", + "field.blocks.empty": "Nie ma jeszcze żadnych bloków", + "field.blocks.fieldsets.label": "Wybierz typ bloku …", + "field.blocks.gallery.name": "Galeria", + "field.blocks.gallery.images.empty": "Nie ma jeszcze żadnych obrazków", + "field.blocks.gallery.images.label": "Obrazki", + "field.blocks.heading.level": "Poziom", + "field.blocks.heading.name": "Nagłówek", + "field.blocks.heading.text": "Tekst", + "field.blocks.heading.placeholder": "Nagłówek …", + "field.blocks.image.alt": "Tekst alternatywny", + "field.blocks.image.caption": "Podpis", + "field.blocks.image.crop": "Przytnij", + "field.blocks.image.link": "Link", + "field.blocks.image.location": "Lokalizacja", + "field.blocks.image.name": "Obrazek", + "field.blocks.image.placeholder": "Wybierz obrazek", + "field.blocks.image.ratio": "Proporcje", + "field.blocks.image.url": "URL obrazka", + "field.blocks.list.name": "Lista", + "field.blocks.markdown.name": "Markdown", + "field.blocks.markdown.label": "Tekst", + "field.blocks.markdown.placeholder": "Markdown …", + "field.blocks.quote.name": "Cytat", + "field.blocks.quote.text.label": "Tekst", + "field.blocks.quote.text.placeholder": "Cytat …", + "field.blocks.quote.citation.label": "Źródło", + "field.blocks.quote.citation.placeholder": "autorstwa …", + "field.blocks.text.name": "Tekst", + "field.blocks.text.placeholder": "Tekst …", + "field.blocks.video.caption": "Podpis", + "field.blocks.video.name": "Video", + "field.blocks.video.placeholder": "Wprowadź URL video", + "field.blocks.video.url.label": "URL video", + "field.blocks.video.url.placeholder": "https://youtube.com/?v=", + + "field.files.empty": "Nie wybrano jeszcze żadnych plików", + + "field.layout.delete": "Usuń układ", + "field.layout.delete.confirm": "Czy na pewno chcesz usunąć ten układ?", + "field.layout.empty": "Nie ma jeszcze żadnych rzędów", + "field.layout.select": "Wybierz układ", + + "field.pages.empty": "Nie wybrano jeszcze żadnych stron", + "field.structure.delete.confirm": "Czy na pewno chcesz usunąć ten wiersz?", + "field.structure.empty": "Nie ma jeszcze \u017cadnych wpis\u00f3w.", + "field.users.empty": "Nie wybrano jeszcze żadnych użytkowników", + + "file.blueprint": "Ten plik nie ma jeszcze wzorca. Możesz go zdefiniować w /site/blueprints/{template}.yml", + "file.delete.confirm": "Czy na pewno chcesz usunąć
{filename}?", + "file.sort": "Zmień pozycję", + + "files": "Pliki", + "files.empty": "Nie ma jeszcze żadnych plików", + + "hide": "Ukryj", + "hour": "Godzina", + "insert": "Wstaw", + "insert.after": "Wstaw po", + "insert.before": "Wstaw przed", + "install": "Zainstaluj", + + "installation": "Instalacja", + "installation.completed": "Panel został zainstalowany", + "installation.disabled": "Instalator panelu jest domyślnie wyłączony na serwerach publicznych. Uruchom instalator na komputerze lokalnym lub włącz go za pomocą opcji panel.install.", + "installation.issues.accounts": "Folder /site/accounts nie istnieje lub nie ma uprawnień do zapisu", + "installation.issues.content": "Folder /content nie istnieje lub nie ma uprawnień do zapisu", + "installation.issues.curl": "Wymagane jest rozszerzenie CURL", + "installation.issues.headline": "Nie można zainstalować panelu", + "installation.issues.mbstring": "Wymagane jest rozszerzenie MB String", + "installation.issues.media": "Folder /media nie istnieje lub nie ma uprawnień do zapisu", + "installation.issues.php": "Upewnij się, że używasz PHP 7+", + "installation.issues.server": "Kirby wymaga Apache, Nginx lub Caddy", + "installation.issues.sessions": "Folder /site/sessions nie istnieje lub nie ma uprawnień do zapisu", + + "language": "J\u0119zyk", + "language.code": "Kod", + "language.convert": "Ustaw jako domyślny", + "language.convert.confirm": "

Czy na pewno chcesz zmienić domyślny język na {name}? Nie można tego cofnąć.

Jeżeli brakuje tłumaczenia jakichś treści na {name}, nie będzie ich czym zastąpić i części witryny mogą być puste.

", + "language.create": "Dodaj nowy język", + "language.delete.confirm": "Czy na pewno chcesz usunąć język {name} i wszystkie tłumaczenia? Tego nie da się cofnąć!", + "language.deleted": "Język został usunięty", + "language.direction": "Kierunek czytania", + "language.direction.ltr": "Od lewej do prawej", + "language.direction.rtl": "Od prawej do lewej", + "language.locale": "PHP locale string", + "language.locale.warning": "Używasz niestandardowej konfiguracji ustawień regionalnych. Zmodyfikuj to w pliku języka w /site/langugaes", + "language.name": "Nazwa", + "language.updated": "Język został zaktualizowany", + + "languages": "Języki", + "languages.default": "Domyślny język", + "languages.empty": "Nie ma jeszcze żadnych języków", + "languages.secondary": "Dodatkowe języki", + "languages.secondary.empty": "Nie ma jeszcze dodatkowych języków", + + "license": "Licencja", + "license.buy": "Kup licencję", + "license.register": "Zarejestruj", + "license.register.help": "Po zakupieniu licencji otrzymałaś/-eś mailem klucz. Skopiuj go i wklej tutaj, aby dokonać rejestracji.", + "license.register.label": "Wprowadź swój kod licencji", + "license.register.success": "Dziękujemy za wspieranie Kirby", + "license.unregistered": "To jest niezarejestrowana wersja demonstracyjna Kirby", + + "link": "Link", + "link.text": "Tekst linku", + + "loading": "Ładuję", + + "lock.unsaved": "Niezapisane zmiany", + "lock.unsaved.empty": "Nie ma już żadnych niezapisanych zmian", + "lock.isLocked": "Niezapisane zmiany autorstwa {email}", + "lock.file.isLocked": "Plik jest aktualnie edytowany przez {email} i nie może zostać zmieniony.", + "lock.page.isLocked": "Strona jest aktualnie edytowana przez {email} i nie może zostać zmieniona.", + "lock.unlock": "Odblokuj", + "lock.isUnlocked": "Twoje niezapisane zmiany zostały nadpisane przez innego użytkownika. Możesz pobrać swoje zmiany, by scalić je ręcznie.", + + "login": "Zaloguj", + "login.code.label.login": "Kod logowania się", + "login.code.label.password-reset": "Kod resetowania hasła", + "login.code.placeholder.email": "000 000", + "login.code.text.email": "Jeśli Twój adres email jest zarejestrowany, żądany kod został wysłany na Twoją skrzynkę.", + "login.email.login.body": "Witaj {user.nameOrEmail},\n\nNiedawno poprosiłeś/-aś o kod logowania do panelu Kirby.\nPoniższy kod resetowania hasła będzie ważny przez {timeout} minut:\n\n{code}\n\nJeżeli nie zażądałeś/-aś kodu logowania, zignoruj tę wiadomość e-mail lub skontaktuj się z administratorem, jeśli masz pytania.\nZe względów bezpieczeństwa NIE przesyłaj dalej tego emaila. ", + "login.email.login.subject": "Twój kod logowania się", + "login.email.password-reset.body": "Witaj {user.nameOrEmail},\n\nNiedawno poprosiłeś/-aś o kod resetowania hasła do panelu Kirby.\nPoniższy kod resetowania hasła będzie ważny przez {timeout} minut:\n\n{code}\n\nJeżeli nie zażądałeś/-aś kodu resetowania hasła, zignoruj tę wiadomość email lub skontaktuj się z administratorem, jeśli masz pytania.\nZe względów bezpieczeństwa NIE przesyłaj dalej tego emaila.", + "login.email.password-reset.subject": "Twój kod resetujący hasło", + "login.remember": "Nie wylogowuj mnie", + "login.reset": "Zresetuj hasło", + "login.toggleText.code.email": "Zaloguj się za pomocą adresu email", + "login.toggleText.code.email-password": "Zaloguj się za pomocą hasła", + "login.toggleText.password-reset.email": "Zapomniałeś/-aś hasła?", + "login.toggleText.password-reset.email-password": "← Powrót do logowania", + + "logout": "Wyloguj", + + "menu": "Menu", + "meridiem": "AM/PM", + "mime": "Typ multimediów", + "minutes": "Minuty", + + "month": "Miesiąc", + "months.april": "Kwiecie\u0144", + "months.august": "Sierpie\u0144", + "months.december": "Grudzie\u0144", + "months.february": "Luty", + "months.january": "Stycze\u0144", + "months.july": "Lipiec", + "months.june": "Czerwiec", + "months.march": "Marzec", + "months.may": "Maj", + "months.november": "Listopad", + "months.october": "Pa\u017adziernik", + "months.september": "Wrzesie\u0144", + + "more": "Więcej", + "name": "Nazwa", + "next": "Następne", + "no": "nie", + "off": "wyłączone", + "on": "włączone", + "open": "Otwórz", + "open.newWindow": "Otwórz w nowym oknie", + "options": "Opcje", + "options.none": "Brak opcji", + + "orientation": "Orientacja", + "orientation.landscape": "Pozioma", + "orientation.portrait": "Pionowa", + "orientation.square": "Kwadrat", + + "page.blueprint": "Ta strona nie ma jeszcze wzorca. Możesz go zdefiniować w /site/blueprints/{template}.yml", + "page.changeSlug": "Zmie\u0144 URL", + "page.changeSlug.fromTitle": "Utw\u00f3rz na podstawie tytu\u0142u", + "page.changeStatus": "Zmień status", + "page.changeStatus.position": "Wybierz pozycję", + "page.changeStatus.select": "Wybierz nowy status", + "page.changeTemplate": "Zmień szablon", + "page.delete.confirm": "Czy na pewno chcesz usunąć {title}?", + "page.delete.confirm.subpages": "Ta strona zawiera podstrony.
Wszystkie podstrony również zostaną usunięte.", + "page.delete.confirm.title": "Wprowadź tytuł strony, aby potwierdzić", + "page.draft.create": "Utwórz szkic", + "page.duplicate.appendix": "Kopiuj", + "page.duplicate.files": "Kopiuj pliki", + "page.duplicate.pages": "Kopiuj strony", + "page.sort": "Zmień pozycję", + "page.status": "Status", + "page.status.draft": "Szkic", + "page.status.draft.description": "Strona jest w trybie roboczym i widoczna tylko dla zalogowanych redaktorów lub pod sekretnym linkiem", + "page.status.listed": "Opublikowana", + "page.status.listed.description": "Strona jest opublikowana i widoczna dla każdego", + "page.status.unlisted": "Nie katalogowana", + "page.status.unlisted.description": "Strona jest dostępna tylko za pośrednictwem adresu URL", + + "pages": "Strony", + "pages.empty": "Nie ma jeszcze żadnych stron", + "pages.status.draft": "Szkice", + "pages.status.listed": "Opublikowane", + "pages.status.unlisted": "Nie katalogowana", + + "pagination.page": "Strona", + + "password": "Has\u0142o", + "pixel": "Piksel", + "prev": "Poprzednie", + "preview": "Podgląd", + "remove": "Usuń", + "rename": "Zmień nazwę", + "replace": "Zamie\u0144", + "retry": "Pon\u00f3w pr\u00f3b\u0119", + "revert": "Odrzu\u0107", + "revert.confirm": "Czy na pewno chcesz usunąć wszystkie niezapisane zmiany?", + + "role": "Rola", + "role.admin.description": "Administrator posiada wszystkie uprawnienia", + "role.admin.title": "Administrator", + "role.all": "Wszystkie", + "role.empty": "Nie ma użytkowników z tą rolą", + "role.description.placeholder": "Brak opisu", + "role.nobody.description": "To jest rola zastępcza bez żadnych uprawnień", + "role.nobody.title": "Nikt", + + "save": "Zapisz", + "search": "Szukaj", + "search.min": "Aby wyszukać, wprowadź co najmniej {min} znaków", + "search.all": "Pokaż wzystkie", + "search.results.none": "Brak wyników", + + "section.required": "Sekcja jest wymagana", + + "select": "Wybierz", + "settings": "Ustawienia", + "show": "Pokaż", + "size": "Rozmiar", + "slug": "Końcówka URL", + "sort": "Sortuj", + "title": "Tytuł", + "template": "Szablon", + "today": "Dzisiaj", + + "site.blueprint": "Ta strona nie ma jeszcze wzorca. Możesz go zdefiniować w /site/blueprints/site.yml", + + "toolbar.button.code": "Kod", + "toolbar.button.bold": "Pogrubienie", + "toolbar.button.email": "Email", + "toolbar.button.headings": "Nagłówki", + "toolbar.button.heading.1": "Nagłówek 1", + "toolbar.button.heading.2": "Nagłówek 2", + "toolbar.button.heading.3": "Nagłówek 3", + "toolbar.button.italic": "Kursywa", + "toolbar.button.file": "Plik", + "toolbar.button.file.select": "Wybierz plik", + "toolbar.button.file.upload": "Prześlij plik", + "toolbar.button.link": "Link", + "toolbar.button.strike": "Przekreślenie", + "toolbar.button.ol": "Lista numerowana", + "toolbar.button.underline": "Podkreślenie", + "toolbar.button.ul": "Lista wypunktowana", + + "translation.author": "Zespół Kirby", + "translation.direction": "ltr", + "translation.name": "Polski", + "translation.locale": "pl_PL", + + "upload": "Prześlij", + "upload.error.cantMove": "Przesłany plik nie mógł być przeniesiony", + "upload.error.cantWrite": "Nie udało się zapisać pliku na dysku", + "upload.error.default": "Nie udało się przesłać pliku", + "upload.error.extension": "Przesyłanie pliku zostało zastopowane przez rozszerzenie", + "upload.error.formSize": "Przesłany plik przekracza dyrektywę MAX_FILE_SIZE określoną w formularzu", + "upload.error.iniPostSize": "Przesłany plik przekracza dyrektywę post_max_size określoną w php.ini", + "upload.error.iniSize": "Przesłany plik przekracza dyrektywę upload_max_filesize określoną w php.ini", + "upload.error.noFile": "Nie został przesłany żaden plik", + "upload.error.noFiles": "Nie zostały przesłane żadne pliki", + "upload.error.partial": "Została przesłana tylko część przesyłanego pliku", + "upload.error.tmpDir": "Brak tymczasowego folderu", + "upload.errors": "Błąd", + "upload.progress": "Przesyłanie…", + + "url": "Url", + "url.placeholder": "https://example.com", + + "user": "Użytkownik", + "user.blueprint": "Możesz zdefiniować dodatkowe sekcje i pola formularza dla tej roli użytkownika w /site/blueprints/users/{role}.yml", + "user.changeEmail": "Zmień email", + "user.changeLanguage": "Zmień język", + "user.changeName": "Zmień nazwę tego użytkownika", + "user.changePassword": "Zmień hasło", + "user.changePassword.new": "Nowe hasło", + "user.changePassword.new.confirm": "Potwierdź nowe hasło…", + "user.changeRole": "Zmień rolę", + "user.changeRole.select": "Wybierz nową rolę", + "user.create": "Dodaj nowego użytkownika", + "user.delete": "Usuń tego użytkownika", + "user.delete.confirm": "Czy na pewno chcesz usunąć
{email}?", + + "users": "Użytkownicy", + + "version": "Wersja", + + "view.account": "Twoje konto", + "view.installation": "Instalacja", + "view.resetPassword": "Zresetuj hasło", + "view.settings": "Ustawienia", + "view.site": "Strona", + "view.users": "U\u017cytkownicy", + + "welcome": "Witaj", + "year": "Rok", + "yes": "tak" +} diff --git a/kirby/i18n/translations/pt_BR.json b/kirby/i18n/translations/pt_BR.json new file mode 100644 index 0000000..96fb1aa --- /dev/null +++ b/kirby/i18n/translations/pt_BR.json @@ -0,0 +1,527 @@ +{ + "add": "Adicionar", + "avatar": "Foto do perfil", + "back": "Voltar", + "cancel": "Cancelar", + "change": "Alterar", + "close": "Fechar", + "confirm": "Salvar", + "collapse": "Collapse", + "collapse.all": "Collapse All", + "copy": "Copiar", + "create": "Criar", + + "date": "Data", + "date.select": "Selecione uma data", + + "day": "Dia", + "days.fri": "Sex", + "days.mon": "Seg", + "days.sat": "S\u00e1b", + "days.sun": "Dom", + "days.thu": "Qui", + "days.tue": "Ter", + "days.wed": "Qua", + + "delete": "Excluir", + "delete.all": "Delete all", + "dimensions": "Dimensões", + "disabled": "Disabled", + "discard": "Descartar", + "download": "Download", + "duplicate": "Duplicate", + "edit": "Editar", + "expand": "Expand", + "expand.all": "Expand All", + + "dialog.files.empty": "No files to select", + "dialog.pages.empty": "No pages to select", + "dialog.users.empty": "No users to select", + + "email": "Email", + "email.placeholder": "mail@exemplo.com", + + "error.access.code": "Invalid code", + "error.access.login": "Login inválido", + "error.access.panel": "Você não tem permissão para acessar o painel", + "error.access.view": "You are not allowed to access this part of the panel", + + "error.avatar.create.fail": "A foto de perfil não pôde ser enviada", + "error.avatar.delete.fail": "A foto do perfil não pôde ser deletada", + "error.avatar.dimensions.invalid": "Por favor, use uma foto de perfil com largura e altura menores que 3000 pixels", + "error.avatar.mime.forbidden": "A foto de perfil deve ser um arquivo JPEG ou PNG", + + "error.blueprint.notFound": "O blueprint \"{name}\" não pôde ser carregado", + + "error.blocks.max.plural": "You must not add more than {max} blocks", + "error.blocks.max.singular": "You must not add more than one block", + "error.blocks.min.plural": "You must add at least {min} blocks", + "error.blocks.min.singular": "You must add at least one block", + "error.blocks.validation": "There's an error in block {index}", + + "error.email.preset.notFound": "Preset de email \"{name}\" não encontrado", + + "error.field.converter.invalid": "Conversor \"{converter}\" inválido", + + "error.file.changeName.empty": "The name must not be empty", + "error.file.changeName.permission": "Você não tem permissão para alterar o nome de \"{filename}\"", + "error.file.duplicate": "Um arquivo com o nome \"{filename}\" já existe", + "error.file.extension.forbidden": "Extensão \"{extension}\" não permitida", + "error.file.extension.invalid": "Invalid extension: {extension}", + "error.file.extension.missing": "Extensão de \"{filename}\" em falta", + "error.file.maxheight": "The height of the image must not exceed {height} pixels", + "error.file.maxsize": "The file is too large", + "error.file.maxwidth": "The width of the image must not exceed {width} pixels", + "error.file.mime.differs": "O arquivo enviado precisa ser do tipo \"{mime}\"", + "error.file.mime.forbidden": "Tipo de mídia \"{mime}\" não permitido", + "error.file.mime.invalid": "Invalid mime type: {mime}", + "error.file.mime.missing": "Tipo de mídia de \"{filename}\" não detectado", + "error.file.minheight": "The height of the image must be at least {height} pixels", + "error.file.minsize": "The file is too small", + "error.file.minwidth": "The width of the image must be at least {width} pixels", + "error.file.name.missing": "O nome do arquivo não pode ficar em branco", + "error.file.notFound": "Arquivo \"{filename}\" não encontrado", + "error.file.orientation": "The orientation of the image must be \"{orientation}\"", + "error.file.type.forbidden": "Você não tem permissão para enviar arquivos {type}", + "error.file.type.invalid": "Invalid file type: {type}", + "error.file.undefined": "Arquivo n\u00e3o encontrado", + + "error.form.incomplete": "Por favor, corrija os erros do formulário…", + "error.form.notSaved": "O formulário não pôde ser salvo", + + "error.language.code": "Please enter a valid code for the language", + "error.language.duplicate": "The language already exists", + "error.language.name": "Please enter a valid name for the language", + + "error.layout.validation.block": "There's an error in block {blockIndex} in layout {layoutIndex}", + "error.layout.validation.settings": "There's an error in layout {index} settings", + + "error.license.format": "Please enter a valid license key", + "error.license.email": "Digite um endereço de email válido", + "error.license.verification": "The license could not be verified", + + "error.page.changeSlug.permission": "Você não tem permissão para alterar a URL de \"{slug}\"", + "error.page.changeStatus.incomplete": "A página possui erros e não pode ser salva", + "error.page.changeStatus.permission": "O estado desta página não pode ser alterado", + "error.page.changeStatus.toDraft.invalid": "A página \"{slug}\" não pode ser convertida para rascunho", + "error.page.changeTemplate.invalid": "O tema da página \"{slug}\" não pode ser alterado", + "error.page.changeTemplate.permission": "Você não tem permissão para alterar o tema de \"{slug}\"", + "error.page.changeTitle.empty": "O título não pode ficar em branco", + "error.page.changeTitle.permission": "Você não tem permissão para alterar o título de \"{slug}\"", + "error.page.create.permission": "Você não tem permissão para criar \"{slug}\"", + "error.page.delete": "A página \"{slug}\" não pode ser excluída", + "error.page.delete.confirm": "Por favor, digite o título da página para confirmar", + "error.page.delete.hasChildren": "A página possui subpáginas e não pode ser excluída", + "error.page.delete.permission": "Você não tem permissão para excluir \"{slug}\"", + "error.page.draft.duplicate": "Um rascunho de página com a URL \"{slug}\" já existe", + "error.page.duplicate": "Uma página com a URL \"{slug}\" já existe", + "error.page.duplicate.permission": "You are not allowed to duplicate \"{slug}\"", + "error.page.notFound": "Página\"{slug}\" não encontrada", + "error.page.num.invalid": "Digite um número de ordenação válido. Este número não pode ser negativo.", + "error.page.slug.invalid": "Please enter a valid URL appendix", + "error.page.slug.maxlength": "Slug length must be less than \"{length}\" characters", + "error.page.sort.permission": "A página \"{slug}\" não pode ser ordenada", + "error.page.status.invalid": "Por favor, defina um estado de página válido", + "error.page.undefined": "P\u00e1gina n\u00e3o encontrada", + "error.page.update.permission": "Você não tem permissão para atualizar \"{slug}\"", + + "error.section.files.max.plural": "Você não pode adicionar mais do que {max} arquivos à seção \"{section}\"", + "error.section.files.max.singular": "Você não pode adicionar mais do que um arquivo à seção \"{section}\"", + "error.section.files.min.plural": "The \"{section}\" section requires at least {min} files", + "error.section.files.min.singular": "The \"{section}\" section requires at least one file", + + "error.section.pages.max.plural": "Você não pode adicionar mais do que {max} página à seção \"{section}\"", + "error.section.pages.max.singular": "Você não pode adicionar mais do que uma página à seção \"{section}\"", + "error.section.pages.min.plural": "The \"{section}\" section requires at least {min} pages", + "error.section.pages.min.singular": "The \"{section}\" section requires at least one page", + + "error.section.notLoaded": "A seção \"{name}\" não pôde ser carregada", + "error.section.type.invalid": "O tipo da seção \"{type}\" não é válido", + + "error.site.changeTitle.empty": "O título não pode ficar em branco", + "error.site.changeTitle.permission": "Você não tem permissão para alterar o título do site", + "error.site.update.permission": "Você não tem permissão para atualizar o site", + + "error.template.default.notFound": "O tema padrão não existe", + + "error.user.changeEmail.permission": "Você não tem permissão para alterar o email do usuário \"{name}\"", + "error.user.changeLanguage.permission": "Você não tem permissão para alterar o idioma do usuário \"{name}\"", + "error.user.changeName.permission": "Você não tem permissão para alterar o nome do usuário \"{name}\"", + "error.user.changePassword.permission": "Você não tem permissão para alterar a senha do usuário \"{name}\"", + "error.user.changeRole.lastAdmin": "O papel do último administrador não pode ser alterado", + "error.user.changeRole.permission": "Você não tem permissão para alterar o papel do usuário \"{name}\"", + "error.user.changeRole.toAdmin": "You are not allowed to promote someone to the admin role", + "error.user.create.permission": "Você não tem permissão para criar este usuário", + "error.user.delete": "O usuário \"{name}\" não pode ser excluído", + "error.user.delete.lastAdmin": "O último administrador não pode ser excluído", + "error.user.delete.lastUser": "O último usuário não pode ser excluído", + "error.user.delete.permission": "Você não tem permissão para excluir o usuário \"{name}\"", + "error.user.duplicate": "Um usuário com o email \"{email}\" já existe", + "error.user.email.invalid": "Digite um endereço de email válido", + "error.user.language.invalid": "Digite um idioma válido", + "error.user.notFound": "Usuário \"{name}\" não encontrado", + "error.user.password.invalid": "Digite uma senha válida. Sua senha deve ter pelo menos 8 caracteres.", + "error.user.password.notSame": "As senhas não combinam", + "error.user.password.undefined": "O usuário não possui uma senha", + "error.user.password.wrong": "Wrong password", + "error.user.role.invalid": "Digite um papel válido", + "error.user.update.permission": "Você não tem permissão para atualizar o usuário \"{name}\"", + + "error.validation.accepted": "Por favor, confirme", + "error.validation.alpha": "Por favor, use apenas caracteres entre a-z", + "error.validation.alphanum": "Por favor, use apenas caracteres entre a-z ou 0-9", + "error.validation.between": "Digite um valor entre \"{min}\" e \"{max}\"", + "error.validation.boolean": "Por favor, confirme ou rejeite", + "error.validation.contains": "Digite um valor que contenha \"{needle}\"", + "error.validation.date": "Escolha uma data válida", + "error.validation.date.after": "Please enter a date after {date}", + "error.validation.date.before": "Please enter a date before {date}", + "error.validation.date.between": "Please enter a date between {min} and {max}", + "error.validation.denied": "Por favor, cancele", + "error.validation.different": "O valor deve ser diferente de \"{other}\"", + "error.validation.email": "Digite um endereço de email válido", + "error.validation.endswith": "O valor deve terminar com \"{end}\"", + "error.validation.filename": "Digite um nome de arquivo válido", + "error.validation.in": "Digite um destes valores: ({in})", + "error.validation.integer": "Digite um número inteiro válido", + "error.validation.ip": "Digite um endereço de IP válido", + "error.validation.less": "Digite um valor menor que {max}", + "error.validation.match": "O valor não combina com o padrão esperado", + "error.validation.max": "Digite um valor igual ou menor que {max}", + "error.validation.maxlength": "Digite um valor curto. (no máximo {max} caracteres)", + "error.validation.maxwords": "Digite menos que {max} palavra(s)", + "error.validation.min": "Digite um valor igual ou maior que {min}", + "error.validation.minlength": "Digite um valor maior. (no mínimo {min} caracteres)", + "error.validation.minwords": "Digite ao menos {min} palavra(s)", + "error.validation.more": "Digite um valor maior que {min}", + "error.validation.notcontains": "Digite um valor que não contenha \"{needle}\"", + "error.validation.notin": "Não digite nenhum destes valores: ({notIn})", + "error.validation.option": "Escolha uma opção válida", + "error.validation.num": "Digite um número válido", + "error.validation.required": "Digite algo", + "error.validation.same": "Por favor, digite \"{other}\"", + "error.validation.size": "O tamanho do valor deve ser \"{size}\"", + "error.validation.startswith": "O valor deve começar com \"{start}\"", + "error.validation.time": "Digite uma hora válida", + "error.validation.time.after": "Please enter a time after {time}", + "error.validation.time.before": "Please enter a time before {time}", + "error.validation.time.between": "Please enter a time between {min} and {max}", + "error.validation.url": "Digite uma URL válida", + + "field.required": "The field is required", + "field.blocks.changeType": "Change type", + "field.blocks.code.name": "Código", + "field.blocks.code.language": "Idioma", + "field.blocks.code.placeholder": "Your code …", + "field.blocks.delete.confirm": "Do you really want to delete this block?", + "field.blocks.delete.confirm.all": "Do you really want to delete all blocks?", + "field.blocks.delete.confirm.selected": "Do you really want to delete the selected blocks?", + "field.blocks.empty": "No blocks yet", + "field.blocks.fieldsets.label": "Please select a block type …", + "field.blocks.gallery.name": "Gallery", + "field.blocks.gallery.images.empty": "No images yet", + "field.blocks.gallery.images.label": "Images", + "field.blocks.heading.level": "Level", + "field.blocks.heading.name": "Heading", + "field.blocks.heading.text": "Text", + "field.blocks.heading.placeholder": "Heading …", + "field.blocks.image.alt": "Alternative text", + "field.blocks.image.caption": "Caption", + "field.blocks.image.crop": "Crop", + "field.blocks.image.link": "Link", + "field.blocks.image.location": "Location", + "field.blocks.image.name": "Imagem", + "field.blocks.image.placeholder": "Select an image", + "field.blocks.image.ratio": "Ratio", + "field.blocks.image.url": "Image URL", + "field.blocks.list.name": "List", + "field.blocks.markdown.name": "Markdown", + "field.blocks.markdown.label": "Text", + "field.blocks.markdown.placeholder": "Markdown …", + "field.blocks.quote.name": "Quote", + "field.blocks.quote.text.label": "Text", + "field.blocks.quote.text.placeholder": "Quote …", + "field.blocks.quote.citation.label": "Citation", + "field.blocks.quote.citation.placeholder": "by …", + "field.blocks.text.name": "Text", + "field.blocks.text.placeholder": "Text …", + "field.blocks.video.caption": "Caption", + "field.blocks.video.name": "Video", + "field.blocks.video.placeholder": "Enter a video URL", + "field.blocks.video.url.label": "Video-URL", + "field.blocks.video.url.placeholder": "https://youtube.com/?v=", + + "field.files.empty": "Nenhum arquivo selecionado", + + "field.layout.delete": "Delete layout", + "field.layout.delete.confirm": "Do you really want to delete this layout?", + "field.layout.empty": "No rows yet", + "field.layout.select": "Select a layout", + + "field.pages.empty": "Nenhuma página selecionada", + "field.structure.delete.confirm": "Deseja realmente excluir este registro?", + "field.structure.empty": "Nenhum registro", + "field.users.empty": "Nenhum usuário selecionado", + + "file.blueprint": "This file has no blueprint yet. You can define the setup in /site/blueprints/{template}.yml", + "file.delete.confirm": "Deseja realmente excluir
{filename}?", + "file.sort": "Change position", + + "files": "Arquivos", + "files.empty": "Nenhum arquivo", + + "hide": "Hide", + "hour": "Hora", + "insert": "Inserir", + "insert.after": "Insert after", + "insert.before": "Insert before", + "install": "Instalar", + + "installation": "Instalação", + "installation.completed": "Painel instalado com sucesso", + "installation.disabled": "O instalador do painel está desabilitado em servidores públicos por padrão. Por favor, execute o instalador em uma máquina local ou habilite a opção panel.install.", + "installation.issues.accounts": "A pasta /site/accounts não existe ou não possui permissão de escrita", + "installation.issues.content": "A pasta /content não existe ou não possui permissão de escrita", + "installation.issues.curl": "A extensão CURL é necessária", + "installation.issues.headline": "O painel não pôde ser instalado", + "installation.issues.mbstring": "A extensão MB String é necessária", + "installation.issues.media": "A pasta /media não existe ou não possui permissão de escrita", + "installation.issues.php": "Certifique-se que você está usando o PHP 7+", + "installation.issues.server": "Kirby necessita do Apache, Nginx ou Caddy", + "installation.issues.sessions": "A pasta /site/sessions não existe ou não possui permissão de escrita", + + "language": "Idioma", + "language.code": "Código", + "language.convert": "Tornar padrão", + "language.convert.confirm": "

Deseja realmente converter {name} para o idioma padrão? Esta ação não poderá ser revertida.

Se {name} tiver conteúdo não traduzido, partes do seu site poderão ficar sem conteúdo.

", + "language.create": "Adicionar novo idioma", + "language.delete.confirm": "Deseja realmente excluir o idioma {name} incluíndo todas as traduções. Esta ação não poderá ser revertida!", + "language.deleted": "Idioma excluído", + "language.direction": "Direção de leitura", + "language.direction.ltr": "Esquerda para direita", + "language.direction.rtl": "Direita para esquerda", + "language.locale": "String de localização do PHP", + "language.locale.warning": "You are using a custom locale set up. Please modify it in the language file in /site/languages", + "language.name": "Nome", + "language.updated": "Idioma atualizado", + + "languages": "Idiomas", + "languages.default": "Idioma padrão", + "languages.empty": "Nenhum idioma", + "languages.secondary": "Idiomas secundários", + "languages.secondary.empty": "Nenhum idioma secundário", + + "license": "Licen\u00e7a do Kirby ", + "license.buy": "Comprar licença", + "license.register": "Registrar", + "license.register.help": "Você recebeu o código da sua licença por email após a compra. Por favor, copie e cole-a para completar o registro.", + "license.register.label": "Por favor, digite o código da sua licença", + "license.register.success": "Obrigado por apoiar o Kirby", + "license.unregistered": "Esta é uma demonstração não registrada do Kirby", + + "link": "Link", + "link.text": "Texto do link", + + "loading": "Carregando", + + "lock.unsaved": "Unsaved changes", + "lock.unsaved.empty": "There are no more unsaved changes", + "lock.isLocked": "Unsaved changes by {email}", + "lock.file.isLocked": "The file is currently being edited by {email} and cannot be changed.", + "lock.page.isLocked": "The page is currently being edited by {email} and cannot be changed.", + "lock.unlock": "Unlock", + "lock.isUnlocked": "Your unsaved changes have been overwritten by another user. You can download your changes to merge them manually.", + + "login": "Entrar", + "login.code.label.login": "Login code", + "login.code.label.password-reset": "Password reset code", + "login.code.placeholder.email": "000 000", + "login.code.text.email": "If your email address is registered, the requested code was sent via email.", + "login.email.login.body": "Hi {user.nameOrEmail},\n\nYou recently requested a login code for the Kirby Panel.\nThe following login code will be valid for {timeout} minutes:\n\n{code}\n\nIf you did not request a login code, please ignore this email or contact your administrator if you have questions.\nFor security, please DO NOT forward this email.", + "login.email.login.subject": "Your login code", + "login.email.password-reset.body": "Hi {user.nameOrEmail},\n\nYou recently requested a password reset code for the Kirby Panel.\nThe following password reset code will be valid for {timeout} minutes:\n\n{code}\n\nIf you did not request a password reset code, please ignore this email or contact your administrator if you have questions.\nFor security, please DO NOT forward this email.", + "login.email.password-reset.subject": "Your password reset code", + "login.remember": "Manter-me conectado", + "login.reset": "Reset password", + "login.toggleText.code.email": "Login via email", + "login.toggleText.code.email-password": "Login with password", + "login.toggleText.password-reset.email": "Forgot your password?", + "login.toggleText.password-reset.email-password": "← Back to login", + + "logout": "Sair", + + "menu": "Menu", + "meridiem": "AM/PM", + "mime": "Tipo de mídia", + "minutes": "Minutos", + + "month": "Mês", + "months.april": "Abril", + "months.august": "Agosto", + "months.december": "Dezembro", + "months.february": "Fevereiro", + "months.january": "Janeiro", + "months.july": "Julho", + "months.june": "Junho", + "months.march": "Mar\u00e7o", + "months.may": "Maio", + "months.november": "Novembro", + "months.october": "Outubro", + "months.september": "Setembro", + + "more": "Mais", + "name": "Nome", + "next": "Próximo", + "no": "no", + "off": "off", + "on": "on", + "open": "Abrir", + "open.newWindow": "Open in new window", + "options": "Opções", + "options.none": "No options", + + "orientation": "Orientação", + "orientation.landscape": "Paisagem", + "orientation.portrait": "Retrato", + "orientation.square": "Quadrado", + + "page.blueprint": "This page has no blueprint yet. You can define the setup in /site/blueprints/{template}.yml", + "page.changeSlug": "Alterar URL", + "page.changeSlug.fromTitle": "Criar a partir do t\u00edtulo", + "page.changeStatus": "Alterar estado", + "page.changeStatus.position": "Selecione uma posição", + "page.changeStatus.select": "Selecione um novo estado", + "page.changeTemplate": "Alterar tema", + "page.delete.confirm": "Deseja realmente excluir {title}?", + "page.delete.confirm.subpages": "Esta página possui subpáginas.
Todas as subpáginas serão excluídas também.", + "page.delete.confirm.title": "Digite o título da página para confirmar", + "page.draft.create": "Criar rascunho", + "page.duplicate.appendix": "Copiar", + "page.duplicate.files": "Copy files", + "page.duplicate.pages": "Copy pages", + "page.sort": "Change position", + "page.status": "Estado", + "page.status.draft": "Rascunho", + "page.status.draft.description": "The page is in draft mode and only visible for logged in editors or via secret link", + "page.status.listed": "Pública", + "page.status.listed.description": "A página pública é visível para todos", + "page.status.unlisted": "Não listadas", + "page.status.unlisted.description": "Esta página é acessível somente através da URL", + + "pages": "Páginas", + "pages.empty": "Nenhuma página", + "pages.status.draft": "Rascunhos", + "pages.status.listed": "Publicadas", + "pages.status.unlisted": "Não listadas", + + "pagination.page": "Página", + + "password": "Senha", + "pixel": "Pixel", + "prev": "Anterior", + "preview": "Preview", + "remove": "Remover", + "rename": "Renomear", + "replace": "Substituir", + "retry": "Tentar novamente", + "revert": "Descartar", + "revert.confirm": "Do you really want to delete all unsaved changes?", + + "role": "Papel", + "role.admin.description": "The admin has all rights", + "role.admin.title": "Admin", + "role.all": "Todos", + "role.empty": "Não há usuários com este papel", + "role.description.placeholder": "Sem descrição", + "role.nobody.description": "This is a fallback role without any permissions", + "role.nobody.title": "Nobody", + + "save": "Salvar", + "search": "Buscar", + "search.min": "Enter {min} characters to search", + "search.all": "Show all", + "search.results.none": "No results", + + "section.required": "The section is required", + + "select": "Selecionar", + "settings": "Configurações", + "show": "Show", + "size": "Tamanho", + "slug": "URL", + "sort": "Ordenar", + "title": "Título", + "template": "Tema", + "today": "Hoje", + + "site.blueprint": "The site has no blueprint yet. You can define the setup in /site/blueprints/site.yml", + + "toolbar.button.code": "Código", + "toolbar.button.bold": "Negrito", + "toolbar.button.email": "Email", + "toolbar.button.headings": "Títulos", + "toolbar.button.heading.1": "Título 1", + "toolbar.button.heading.2": "Título 2", + "toolbar.button.heading.3": "Título 3", + "toolbar.button.italic": "Itálico", + "toolbar.button.file": "Arquivo", + "toolbar.button.file.select": "Select a file", + "toolbar.button.file.upload": "Upload a file", + "toolbar.button.link": "Link", + "toolbar.button.strike": "Strike-through", + "toolbar.button.ol": "Lista ordenada", + "toolbar.button.underline": "Underline", + "toolbar.button.ul": "Lista não-ordenada", + + "translation.author": "Kirby Team", + "translation.direction": "ltr", + "translation.name": "Português (Brasileiro)", + "translation.locale": "pt_BR", + + "upload": "Enviar", + "upload.error.cantMove": "The uploaded file could not be moved", + "upload.error.cantWrite": "Failed to write file to disk", + "upload.error.default": "The file could not be uploaded", + "upload.error.extension": "File upload stopped by extension", + "upload.error.formSize": "The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the form", + "upload.error.iniPostSize": "The uploaded file exceeds the post_max_size directive in php.ini", + "upload.error.iniSize": "The uploaded file exceeds the upload_max_filesize directive in php.ini", + "upload.error.noFile": "No file was uploaded", + "upload.error.noFiles": "No files were uploaded", + "upload.error.partial": "The uploaded file was only partially uploaded", + "upload.error.tmpDir": "Missing a temporary folder", + "upload.errors": "Erro", + "upload.progress": "Enviando…", + + "url": "Url", + "url.placeholder": "https://exemplo.com", + + "user": "Usuário", + "user.blueprint": "Você pode definir seções e campos de formulário adicionais para este papel de usuário em /site/blueprints/users/{role}.yml", + "user.changeEmail": "Alterar email", + "user.changeLanguage": "Alterar idioma", + "user.changeName": "Renomear usuário", + "user.changePassword": "Alterar senha", + "user.changePassword.new": "Nova senha", + "user.changePassword.new.confirm": "Confirme a nova senha…", + "user.changeRole": "Alterar papel", + "user.changeRole.select": "Selecione um novo papel", + "user.create": "Adicionar novo usuário", + "user.delete": "Excluir este usuário", + "user.delete.confirm": "Deseja realmente excluir
{email}?", + + "users": "Usuários", + + "version": "Vers\u00e3o do Kirby", + + "view.account": "Sua conta", + "view.installation": "Instala\u00e7\u00e3o", + "view.resetPassword": "Reset password", + "view.settings": "Configurações", + "view.site": "Site", + "view.users": "Usu\u00e1rios", + + "welcome": "Bem-vindo", + "year": "Ano", + "yes": "yes" +} diff --git a/kirby/i18n/translations/pt_PT.json b/kirby/i18n/translations/pt_PT.json new file mode 100644 index 0000000..a9c0972 --- /dev/null +++ b/kirby/i18n/translations/pt_PT.json @@ -0,0 +1,527 @@ +{ + "add": "Adicionar", + "avatar": "Foto do perfil", + "back": "Voltar", + "cancel": "Cancelar", + "change": "Alterar", + "close": "Fechar", + "confirm": "Salvar", + "collapse": "Collapse", + "collapse.all": "Collapse All", + "copy": "Copiar", + "create": "Criar", + + "date": "Data", + "date.select": "Selecione uma data", + + "day": "Dia", + "days.fri": "Sex", + "days.mon": "Seg", + "days.sat": "S\u00e1b", + "days.sun": "Dom", + "days.thu": "Qui", + "days.tue": "Ter", + "days.wed": "Qua", + + "delete": "Excluir", + "delete.all": "Delete all", + "dimensions": "Dimensões", + "disabled": "Inativo", + "discard": "Descartar", + "download": "Descarregar", + "duplicate": "Duplicar", + "edit": "Editar", + "expand": "Expand", + "expand.all": "Expand All", + + "dialog.files.empty": "Sem arquivos para selecionar", + "dialog.pages.empty": "Sem páginas para selecionar", + "dialog.users.empty": "Sem utilizadores para selecionar", + + "email": "Email", + "email.placeholder": "mail@exemplo.pt", + + "error.access.code": "Invalid code", + "error.access.login": "Login inválido", + "error.access.panel": "Não tem permissões para aceder ao painel", + "error.access.view": "Não tem permissões para aceder a esta área do Painel", + + "error.avatar.create.fail": "A foto de perfil não foi enviada", + "error.avatar.delete.fail": "A foto do perfil não foi excluída", + "error.avatar.dimensions.invalid": "Por favor, use uma foto de perfil com largura e altura menores que 3000 pixels", + "error.avatar.mime.forbidden": "A foto de perfil deve ser um arquivo JPEG ou PNG", + + "error.blueprint.notFound": "O blueprint \"{name}\" não pode ser carregado", + + "error.blocks.max.plural": "You must not add more than {max} blocks", + "error.blocks.max.singular": "You must not add more than one block", + "error.blocks.min.plural": "You must add at least {min} blocks", + "error.blocks.min.singular": "You must add at least one block", + "error.blocks.validation": "There's an error in block {index}", + + "error.email.preset.notFound": "Preset de email \"{name}\" não encontrado", + + "error.field.converter.invalid": "Conversor \"{converter}\" inválido", + + "error.file.changeName.empty": "O nome não pode ficar em branco", + "error.file.changeName.permission": "Não tem permissões para alterar o nome de \"{filename}\"", + "error.file.duplicate": "Um arquivo com o nome \"{filename}\" já existe", + "error.file.extension.forbidden": "Extensão \"{extension}\" não permitida", + "error.file.extension.invalid": "Invalid extension: {extension}", + "error.file.extension.missing": "Extensão de \"{filename}\" em falta", + "error.file.maxheight": "A altura da imagem não deve exceder {height} pixels", + "error.file.maxsize": "O arquivo é muito grande", + "error.file.maxwidth": "A largura da imagem não deve exceder {width} pixels", + "error.file.mime.differs": "O arquivo enviado precisa ser do tipo \"{mime}\"", + "error.file.mime.forbidden": "Tipo de mídia \"{mime}\" não permitido", + "error.file.mime.invalid": "Tipo de mídia inválido: {mime}", + "error.file.mime.missing": "Tipo de mídia de \"{filename}\" não detectado", + "error.file.minheight": "A altura da imagem deve ser pelo menos {height} pixels", + "error.file.minsize": "O ficheiro é muito pequeno", + "error.file.minwidth": "A largura da imagem deve ser pelo menos {width} pixels", + "error.file.name.missing": "O nome do arquivo não pode ficar em branco", + "error.file.notFound": "Arquivo \"{filename}\" não encontrado", + "error.file.orientation": "A orientação da imagem deve ser \"{orientation}\"", + "error.file.type.forbidden": "Não tem permissões para enviar arquivos {type}", + "error.file.type.invalid": "Invalid file type: {type}", + "error.file.undefined": "Arquivo n\u00e3o encontrado", + + "error.form.incomplete": "Por favor, corrija os erros do formulário…", + "error.form.notSaved": "O formulário não foi guardado", + + "error.language.code": "Insira um código de idioma válido", + "error.language.duplicate": "O idioma já existe", + "error.language.name": "Insira um nome válido para o idioma", + + "error.layout.validation.block": "There's an error in block {blockIndex} in layout {layoutIndex}", + "error.layout.validation.settings": "There's an error in layout {index} settings", + + "error.license.format": "Insira uma chave de licença válida", + "error.license.email": "Digite um endereço de email válido", + "error.license.verification": "Não foi possível verificar a licença", + + "error.page.changeSlug.permission": "Não tem permissões para alterar a URL de \"{slug}\"", + "error.page.changeStatus.incomplete": "A página possui erros e não pode ser guardada", + "error.page.changeStatus.permission": "O estado desta página não pode ser alterado", + "error.page.changeStatus.toDraft.invalid": "A página \"{slug}\" não pode ser convertida para rascunho", + "error.page.changeTemplate.invalid": "O tema da página \"{slug}\" não pode ser alterado", + "error.page.changeTemplate.permission": "Não tem permissões para alterar o tema de \"{slug}\"", + "error.page.changeTitle.empty": "O título não pode ficar em branco", + "error.page.changeTitle.permission": "Não tem permissões para alterar o título de \"{slug}\"", + "error.page.create.permission": "Não tem permissões para criar \"{slug}\"", + "error.page.delete": "A página \"{slug}\" não pode ser excluída", + "error.page.delete.confirm": "Por favor, digite o título da página para confirmar", + "error.page.delete.hasChildren": "A página possui subpáginas e não pode ser excluída", + "error.page.delete.permission": "Não tem permissões para excluir \"{slug}\"", + "error.page.draft.duplicate": "Um rascunho de página com a URL \"{slug}\" já existe", + "error.page.duplicate": "Uma página com a URL \"{slug}\" já existe", + "error.page.duplicate.permission": "Não tem permissão para duplicar \"{slug}\"", + "error.page.notFound": "Página\"{slug}\" não encontrada", + "error.page.num.invalid": "Digite um número de ordenação válido. Este número não pode ser negativo.", + "error.page.slug.invalid": "Please enter a valid URL appendix", + "error.page.slug.maxlength": "O slug não pode conter mais do que \"{length}\" caracteres", + "error.page.sort.permission": "A página \"{slug}\" não pode ser ordenada", + "error.page.status.invalid": "Por favor, defina um estado de página válido", + "error.page.undefined": "P\u00e1gina n\u00e3o encontrada", + "error.page.update.permission": "Não tem permissões para atualizar \"{slug}\"", + + "error.section.files.max.plural": "Não pode adicionar mais do que {max} arquivos à seção \"{section}\"", + "error.section.files.max.singular": "Não pode adicionar mais do que um arquivo à seção \"{section}\"", + "error.section.files.min.plural": "A secção \"{section}\" requer no mínimo {min} arquivos", + "error.section.files.min.singular": "A secção \"{section}\" requer no mínimo um arquivo", + + "error.section.pages.max.plural": "Não pode adicionar mais do que {max} página à seção \"{section}\"", + "error.section.pages.max.singular": "Não pode adicionar mais do que uma página à seção \"{section}\"", + "error.section.pages.min.plural": "A secção \"{section}\" requer no mínimo {min} páginas", + "error.section.pages.min.singular": "A secção \"{section}\" requer no mínimo uma página", + + "error.section.notLoaded": "A seção \"{name}\" não pôde ser carregada", + "error.section.type.invalid": "O tipo da seção \"{type}\" não é válido", + + "error.site.changeTitle.empty": "O título não pode ficar em branco", + "error.site.changeTitle.permission": "Não tem permissões para alterar o título do site", + "error.site.update.permission": "Não tem permissões para atualizar o site", + + "error.template.default.notFound": "O tema padrão não existe", + + "error.user.changeEmail.permission": "Não tem permissões para alterar o email do utilizador \"{name}\"", + "error.user.changeLanguage.permission": "Não tem permissões para alterar o idioma do utilizador \"{name}\"", + "error.user.changeName.permission": "Não tem permissões para alterar o nome do utilizador \"{name}\"", + "error.user.changePassword.permission": "Não tem permissões para alterar a palavra-passe do utilizador \"{name}\"", + "error.user.changeRole.lastAdmin": "A função do último administrador não pode ser alterado", + "error.user.changeRole.permission": "Não tem permissões para alterar a função do utilizador \"{name}\"", + "error.user.changeRole.toAdmin": "Não tem permissões para promover utilizadores à função de administrador", + "error.user.create.permission": "Não tem permissões para criar este utilizador", + "error.user.delete": "O utilizador \"{name}\" não pode ser excluído", + "error.user.delete.lastAdmin": "O último administrador não pode ser excluído", + "error.user.delete.lastUser": "O último utilizador não pode ser excluído", + "error.user.delete.permission": "Não tem permissões para excluir o utilizador \"{name}\"", + "error.user.duplicate": "Um utilizador com o email \"{email}\" já existe", + "error.user.email.invalid": "Digite um endereço de email válido", + "error.user.language.invalid": "Digite um idioma válido", + "error.user.notFound": "Utilizador \"{name}\" não encontrado", + "error.user.password.invalid": "Digite uma palavra-passe válida. A sua palavra-passe deve ter pelo menos 8 caracteres.", + "error.user.password.notSame": "As palavras-passe não combinam", + "error.user.password.undefined": "O utilizador não possui uma palavra-passe", + "error.user.password.wrong": "Wrong password", + "error.user.role.invalid": "Digite uma função válida", + "error.user.update.permission": "Não tem permissões para atualizar o utilizador \"{name}\"", + + "error.validation.accepted": "Por favor, confirme", + "error.validation.alpha": "Por favor, use apenas caracteres entre a-z", + "error.validation.alphanum": "Por favor, use apenas caracteres entre a-z ou 0-9", + "error.validation.between": "Digite um valor entre \"{min}\" e \"{max}\"", + "error.validation.boolean": "Por favor, confirme ou rejeite", + "error.validation.contains": "Digite um valor que contenha \"{needle}\"", + "error.validation.date": "Escolha uma data válida", + "error.validation.date.after": "Escolha uma data posterior a {date}", + "error.validation.date.before": "Escolha uma data anterior a {date}", + "error.validation.date.between": "Escolha uma data compreendida entre {min} e {max}", + "error.validation.denied": "Por favor, cancele", + "error.validation.different": "O valor deve ser diferente de \"{other}\"", + "error.validation.email": "Digite um endereço de email válido", + "error.validation.endswith": "O valor deve terminar com \"{end}\"", + "error.validation.filename": "Digite um nome de arquivo válido", + "error.validation.in": "Digite um destes valores: ({in})", + "error.validation.integer": "Digite um número inteiro válido", + "error.validation.ip": "Digite um endereço de IP válido", + "error.validation.less": "Digite um valor menor que {max}", + "error.validation.match": "O valor não combina com o padrão esperado", + "error.validation.max": "Digite um valor igual ou menor que {max}", + "error.validation.maxlength": "Digite um valor curto. (no máximo {max} caracteres)", + "error.validation.maxwords": "Digite menos que {max} palavra(s)", + "error.validation.min": "Digite um valor igual ou maior que {min}", + "error.validation.minlength": "Digite um valor maior. (no mínimo {min} caracteres)", + "error.validation.minwords": "Digite ao menos {min} palavra(s)", + "error.validation.more": "Digite um valor maior que {min}", + "error.validation.notcontains": "Digite um valor que não contenha \"{needle}\"", + "error.validation.notin": "Não digite nenhum destes valores: ({notIn})", + "error.validation.option": "Escolha uma opção válida", + "error.validation.num": "Digite um número válido", + "error.validation.required": "Digite algo", + "error.validation.same": "Por favor, digite \"{other}\"", + "error.validation.size": "O tamanho do valor deve ser \"{size}\"", + "error.validation.startswith": "O valor deve começar com \"{start}\"", + "error.validation.time": "Digite uma hora válida", + "error.validation.time.after": "Please enter a time after {time}", + "error.validation.time.before": "Please enter a time before {time}", + "error.validation.time.between": "Please enter a time between {min} and {max}", + "error.validation.url": "Digite uma URL válida", + + "field.required": "Este campo é necessário", + "field.blocks.changeType": "Change type", + "field.blocks.code.name": "Código", + "field.blocks.code.language": "Idioma", + "field.blocks.code.placeholder": "Your code …", + "field.blocks.delete.confirm": "Do you really want to delete this block?", + "field.blocks.delete.confirm.all": "Do you really want to delete all blocks?", + "field.blocks.delete.confirm.selected": "Do you really want to delete the selected blocks?", + "field.blocks.empty": "No blocks yet", + "field.blocks.fieldsets.label": "Please select a block type …", + "field.blocks.gallery.name": "Gallery", + "field.blocks.gallery.images.empty": "No images yet", + "field.blocks.gallery.images.label": "Images", + "field.blocks.heading.level": "Level", + "field.blocks.heading.name": "Heading", + "field.blocks.heading.text": "Text", + "field.blocks.heading.placeholder": "Heading …", + "field.blocks.image.alt": "Alternative text", + "field.blocks.image.caption": "Caption", + "field.blocks.image.crop": "Crop", + "field.blocks.image.link": "Link", + "field.blocks.image.location": "Location", + "field.blocks.image.name": "Imagem", + "field.blocks.image.placeholder": "Select an image", + "field.blocks.image.ratio": "Ratio", + "field.blocks.image.url": "Image URL", + "field.blocks.list.name": "List", + "field.blocks.markdown.name": "Markdown", + "field.blocks.markdown.label": "Text", + "field.blocks.markdown.placeholder": "Markdown …", + "field.blocks.quote.name": "Quote", + "field.blocks.quote.text.label": "Text", + "field.blocks.quote.text.placeholder": "Quote …", + "field.blocks.quote.citation.label": "Citation", + "field.blocks.quote.citation.placeholder": "by …", + "field.blocks.text.name": "Text", + "field.blocks.text.placeholder": "Text …", + "field.blocks.video.caption": "Caption", + "field.blocks.video.name": "Video", + "field.blocks.video.placeholder": "Enter a video URL", + "field.blocks.video.url.label": "Video-URL", + "field.blocks.video.url.placeholder": "https://youtube.com/?v=", + + "field.files.empty": "Nenhum arquivo selecionado", + + "field.layout.delete": "Delete layout", + "field.layout.delete.confirm": "Do you really want to delete this layout?", + "field.layout.empty": "No rows yet", + "field.layout.select": "Select a layout", + + "field.pages.empty": "Nenhuma página selecionada", + "field.structure.delete.confirm": "Deseja realmente excluir este registro?", + "field.structure.empty": "Nenhum registro", + "field.users.empty": "Nenhum utilizador selecionado", + + "file.blueprint": "This file has no blueprint yet. You can define the setup in /site/blueprints/{template}.yml", + "file.delete.confirm": "Deseja realmente excluir
{filename}?", + "file.sort": "Change position", + + "files": "Arquivos", + "files.empty": "Nenhum arquivo", + + "hide": "Hide", + "hour": "Hora", + "insert": "Inserir", + "insert.after": "Insert after", + "insert.before": "Insert before", + "install": "Instalar", + + "installation": "Instalação", + "installation.completed": "Painel instalado com sucesso", + "installation.disabled": "Por padrão, o instalador do painel está desabilitado em servidores públicos. Por favor, execute o instalador numa máquina local ou habilite a opção panel.install.", + "installation.issues.accounts": "A pasta /site/accounts não existe ou não possui permissão de escrita", + "installation.issues.content": "A pasta /content não existe ou não possui permissão de escrita", + "installation.issues.curl": "A extensão CURL é necessária", + "installation.issues.headline": "O painel não pôde ser instalado", + "installation.issues.mbstring": "A extensão MB String é necessária", + "installation.issues.media": "A pasta /media não existe ou não possui permissão de escrita", + "installation.issues.php": "Certifique-se que está a usar o PHP 7+", + "installation.issues.server": "O Kirby necessita do Apache, Nginx ou Caddy", + "installation.issues.sessions": "A pasta /site/sessions não existe ou não possui permissão de escrita", + + "language": "Idioma", + "language.code": "Código", + "language.convert": "Tornar padrão", + "language.convert.confirm": "

Deseja realmente converter {name} para o idioma padrão? Esta ação não poderá ser revertida.

Se {name} tiver conteúdo não traduzido, partes do seu site poderão ficar sem conteúdo.

", + "language.create": "Adicionar novo idioma", + "language.delete.confirm": "Deseja realmente excluir o idioma {name} incluíndo todas as traduções? Esta ação não poderá ser revertida!", + "language.deleted": "Idioma excluído", + "language.direction": "Direção de leitura", + "language.direction.ltr": "Esquerda para direita", + "language.direction.rtl": "Direita para esquerda", + "language.locale": "String de localização do PHP", + "language.locale.warning": "Está a usar configurações de localização personalizadas. Corrija as mesmas no ficheiro /site/languages", + "language.name": "Nome", + "language.updated": "Idioma atualizado", + + "languages": "Idiomas", + "languages.default": "Idioma padrão", + "languages.empty": "Nenhum idioma", + "languages.secondary": "Idiomas secundários", + "languages.secondary.empty": "Nenhum idioma secundário", + + "license": "Licen\u00e7a do Kirby ", + "license.buy": "Comprar uma licença", + "license.register": "Registrar", + "license.register.help": "Recebeu o código da sua licença por email após a compra. Por favor, copie e cole-o para completar o registro.", + "license.register.label": "Por favor, digite o código da sua licença", + "license.register.success": "Obrigado por apoiar o Kirby", + "license.unregistered": "Esta é uma demonstração não registrada do Kirby", + + "link": "Link", + "link.text": "Texto do link", + + "loading": "A carregar", + + "lock.unsaved": "Alterações por guardar", + "lock.unsaved.empty": "Não existem alterações por guardar", + "lock.isLocked": "Alterações por guardar de {email}", + "lock.file.isLocked": "O arquivo está a ser editado por {email} e não pode ser alterado.", + "lock.page.isLocked": "A página está a ser editada por {email} e não pode ser alterada.", + "lock.unlock": "Desbloquear", + "lock.isUnlocked": "As suas alterações foram sobrepostas por outro utilizador. Pode descarregar as suas alterações e combiná-las manualmente.", + + "login": "Entrar", + "login.code.label.login": "Login code", + "login.code.label.password-reset": "Password reset code", + "login.code.placeholder.email": "000 000", + "login.code.text.email": "If your email address is registered, the requested code was sent via email.", + "login.email.login.body": "Hi {user.nameOrEmail},\n\nYou recently requested a login code for the Kirby Panel.\nThe following login code will be valid for {timeout} minutes:\n\n{code}\n\nIf you did not request a login code, please ignore this email or contact your administrator if you have questions.\nFor security, please DO NOT forward this email.", + "login.email.login.subject": "Your login code", + "login.email.password-reset.body": "Hi {user.nameOrEmail},\n\nYou recently requested a password reset code for the Kirby Panel.\nThe following password reset code will be valid for {timeout} minutes:\n\n{code}\n\nIf you did not request a password reset code, please ignore this email or contact your administrator if you have questions.\nFor security, please DO NOT forward this email.", + "login.email.password-reset.subject": "Your password reset code", + "login.remember": "Manter-me conectado", + "login.reset": "Reset password", + "login.toggleText.code.email": "Login via email", + "login.toggleText.code.email-password": "Login with password", + "login.toggleText.password-reset.email": "Forgot your password?", + "login.toggleText.password-reset.email-password": "← Back to login", + + "logout": "Sair", + + "menu": "Menu", + "meridiem": "AM/PM", + "mime": "Tipo de mídia", + "minutes": "Minutos", + + "month": "Mês", + "months.april": "Abril", + "months.august": "Agosto", + "months.december": "Dezembro", + "months.february": "Fevereiro", + "months.january": "Janeiro", + "months.july": "Julho", + "months.june": "Junho", + "months.march": "Mar\u00e7o", + "months.may": "Maio", + "months.november": "Novembro", + "months.october": "Outubro", + "months.september": "Setembro", + + "more": "Mais", + "name": "Nome", + "next": "Próximo", + "no": "no", + "off": "off", + "on": "on", + "open": "Abrir", + "open.newWindow": "Open in new window", + "options": "Opções", + "options.none": "Sem opções", + + "orientation": "Orientação", + "orientation.landscape": "Paisagem", + "orientation.portrait": "Retrato", + "orientation.square": "Quadrado", + + "page.blueprint": "This page has no blueprint yet. You can define the setup in /site/blueprints/{template}.yml", + "page.changeSlug": "Alterar URL", + "page.changeSlug.fromTitle": "Criar a partir do t\u00edtulo", + "page.changeStatus": "Alterar estado", + "page.changeStatus.position": "Selecione uma posição", + "page.changeStatus.select": "Selecione um novo estado", + "page.changeTemplate": "Alterar tema", + "page.delete.confirm": "Deseja realmente excluir {title}?", + "page.delete.confirm.subpages": "Esta página possui subpáginas.
Todas as subpáginas serão excluídas também.", + "page.delete.confirm.title": "Digite o título da página para confirmar", + "page.draft.create": "Criar rascunho", + "page.duplicate.appendix": "Copiar", + "page.duplicate.files": "Copiar arquivos", + "page.duplicate.pages": "Copiar páginas", + "page.sort": "Change position", + "page.status": "Estado", + "page.status.draft": "Rascunho", + "page.status.draft.description": "A página está em modo de rascunho e é visível somente para editores", + "page.status.listed": "Pública", + "page.status.listed.description": "A página é pública para todos", + "page.status.unlisted": "Não listadas", + "page.status.unlisted.description": "Esta página é acessível somente através da URL", + + "pages": "Páginas", + "pages.empty": "Nenhuma página", + "pages.status.draft": "Rascunhos", + "pages.status.listed": "Publicadas", + "pages.status.unlisted": "Não listadas", + + "pagination.page": "Página", + + "password": "Palavra-passe", + "pixel": "Pixel", + "prev": "Anterior", + "preview": "Preview", + "remove": "Remover", + "rename": "Renomear", + "replace": "Substituir", + "retry": "Tentar novamente", + "revert": "Descartar", + "revert.confirm": "Tem a certeza que pretende eliminar todas as alterações por guardar?", + + "role": "Função", + "role.admin.description": "O administrador tem todas as permissões.", + "role.admin.title": "Administrador", + "role.all": "Todos", + "role.empty": "Não há utilizadores com esta função", + "role.description.placeholder": "Sem descrição", + "role.nobody.description": "Esta é uma função de salvaguarda sem permissões.", + "role.nobody.title": "Ninguém", + + "save": "Salvar", + "search": "Buscar", + "search.min": "Introduza {min} caracteres para pesquisar", + "search.all": "Mostrar todos", + "search.results.none": "Sem resultados", + + "section.required": "Esta seção é necessária", + + "select": "Selecionar", + "settings": "Configurações", + "show": "Show", + "size": "Tamanho", + "slug": "URL", + "sort": "Ordenar", + "title": "Título", + "template": "Tema", + "today": "Hoje", + + "site.blueprint": "The site has no blueprint yet. You can define the setup in /site/blueprints/site.yml", + + "toolbar.button.code": "Código", + "toolbar.button.bold": "Negrito", + "toolbar.button.email": "Email", + "toolbar.button.headings": "Títulos", + "toolbar.button.heading.1": "Título 1", + "toolbar.button.heading.2": "Título 2", + "toolbar.button.heading.3": "Título 3", + "toolbar.button.italic": "Itálico", + "toolbar.button.file": "Ficheiro", + "toolbar.button.file.select": "Selecione o arquivo", + "toolbar.button.file.upload": "Carregue o arquivo", + "toolbar.button.link": "Link", + "toolbar.button.strike": "Strike-through", + "toolbar.button.ol": "Lista ordenada", + "toolbar.button.underline": "Underline", + "toolbar.button.ul": "Lista não-ordenada", + + "translation.author": "Kirby Team", + "translation.direction": "ltr", + "translation.name": "Português (Europeu)", + "translation.locale": "pt_PT", + + "upload": "Enviar", + "upload.error.cantMove": "Não foi possível mover o arquivo carregado", + "upload.error.cantWrite": "Não foi possível guardar o arquivo no sistema de ficheiros.", + "upload.error.default": "Não foi possível carregar o arquivo", + "upload.error.extension": "A extensão do arquivo não permite o carregamento", + "upload.error.formSize": "O arquivo excede o tamanho MAX_FILE_SIZE", + "upload.error.iniPostSize": "O arquivo excede o tamanho post_max_size", + "upload.error.iniSize": "O arquivo carregado excede a definição upload_max_filesize do php.ini", + "upload.error.noFile": "Nenhum arquivo carregado", + "upload.error.noFiles": "Nenhuns arquivos carregados", + "upload.error.partial": "O arquivo foi parcialmente carregado", + "upload.error.tmpDir": "Pasta temporária em falta", + "upload.errors": "Erro", + "upload.progress": "A enviar…", + + "url": "Url", + "url.placeholder": "https://exemplo.pt", + + "user": "Utilizador", + "user.blueprint": "Pode definir seções e campos de formulário adicionais para esta função de utilizador em /site/blueprints/users/{role}.yml", + "user.changeEmail": "Alterar email", + "user.changeLanguage": "Alterar idioma", + "user.changeName": "Renomear este utilizador", + "user.changePassword": "Alterar palavra-passe", + "user.changePassword.new": "Nova palavra-passe", + "user.changePassword.new.confirm": "Confirme a nova palavra-passe…", + "user.changeRole": "Alterar Função", + "user.changeRole.select": "Selecione uma nova função", + "user.create": "Adicionar novo utilizador", + "user.delete": "Excluir este utilizador", + "user.delete.confirm": "Deseja realmente excluir
{email}?", + + "users": "Utilizadores", + + "version": "Vers\u00e3o do Kirby", + + "view.account": "A sua conta", + "view.installation": "Instala\u00e7\u00e3o", + "view.resetPassword": "Reset password", + "view.settings": "Configurações", + "view.site": "Site", + "view.users": "Utilizadores", + + "welcome": "Bem-vindo", + "year": "Ano", + "yes": "yes" +} diff --git a/kirby/i18n/translations/ru.json b/kirby/i18n/translations/ru.json new file mode 100644 index 0000000..12d1f08 --- /dev/null +++ b/kirby/i18n/translations/ru.json @@ -0,0 +1,527 @@ +{ + "add": "\u0414\u043e\u0431\u0430\u0432\u0438\u0442\u044c", + "avatar": "\u0410\u0432\u0430\u0442\u0430\u0440 (\u0444\u043e\u0442\u043e)", + "back": "Назад", + "cancel": "\u041e\u0442\u043c\u0435\u043d\u0438\u0442\u044c", + "change": "\u0418\u0437\u043c\u0435\u043d\u0438\u0442\u044c", + "close": "\u0417\u0430\u043a\u0440\u044b\u0442\u044c", + "confirm": "Ок", + "collapse": "Свернуть", + "collapse.all": "Свернуть все", + "copy": "Скопировать", + "create": "Создать", + + "date": "Дата", + "date.select": "Выберите дату", + + "day": "День", + "days.fri": "\u041f\u0442", + "days.mon": "\u041f\u043d", + "days.sat": "\u0421\u0431", + "days.sun": "\u0412\u0441", + "days.thu": "\u0427\u0442", + "days.tue": "\u0412\u0442", + "days.wed": "\u0421\u0440", + + "delete": "\u0423\u0434\u0430\u043b\u0438\u0442\u044c", + "delete.all": "Удалить все", + "dimensions": "Размеры", + "disabled": "Отключено", + "discard": "\u0421\u0431\u0440\u043e\u0441", + "download": "Скачать", + "duplicate": "Дублировать", + "edit": "\u041d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c", + "expand": "Развернуть", + "expand.all": "Развернуть все", + + "dialog.files.empty": "Нет файлов для выбора", + "dialog.pages.empty": "Нет страниц для выбора", + "dialog.users.empty": "Нет пользователей для выбора", + + "email": "Email", + "email.placeholder": "mail@example.com", + + "error.access.code": "Неверный код", + "error.access.login": "Неправильный логин", + "error.access.panel": "У вас нет права доступа к панели", + "error.access.view": "У вас нет прав доступа к этой части панели", + + "error.avatar.create.fail": "Не удалось загрузить фотографию профиля", + "error.avatar.delete.fail": "\u0410\u0432\u0430\u0442\u0430\u0440 (\u0444\u043e\u0442\u043e) \u043a \u0430\u043a\u043a\u0430\u0443\u043d\u0442\u0443 \u043d\u0435 \u043c\u043e\u0436\u0435\u0442 \u0431\u044b\u0442\u044c \u0443\u0434\u0430\u043b\u0435\u043d", + "error.avatar.dimensions.invalid": "Пожалуйста, сделайте чтобы ширина или высота фотографии была меньше 3000 пикселей", + "error.avatar.mime.forbidden": "Фотография профиля должна быть JPEG или PNG", + + "error.blueprint.notFound": "Не удалось загрузить blueprint \"{name}\"", + + "error.blocks.max.plural": "Вы не можете добавить больше {max} блоков", + "error.blocks.max.singular": "Вы не можете добавить больше одного блока", + "error.blocks.min.plural": "Вы должны добавить хотя бы {min} блоков", + "error.blocks.min.singular": "Вы должны добавить хотя бы один блок", + "error.blocks.validation": "Обнаружена ошибка в блоке {index}", + + "error.email.preset.notFound": "Шаблон эл. почты \"{name}\" не найден", + + "error.field.converter.invalid": "Неверный конвертер \"{converter}\"", + + "error.file.changeName.empty": "Название не может быть пустым", + "error.file.changeName.permission": "У вас нет права поменять название \"{filename}\"", + "error.file.duplicate": "Файл с названием \"{filename}\" уже есть", + "error.file.extension.forbidden": "Расширение файла \"{extension}\" неразрешено", + "error.file.extension.invalid": "Неверное разрешение: {extension}", + "error.file.extension.missing": "Файлу \"{filename}\" не хватает расширения", + "error.file.maxheight": "Высота изображения не должна превышать {height} px", + "error.file.maxsize": "Файл слишком большой", + "error.file.maxwidth": "Ширина изображения не должна превышать {width} px", + "error.file.mime.differs": "Загруженный файл должен быть того же mime типа: \"{mime}\"", + "error.file.mime.forbidden": "Тип медиа \"{mime}\" не допустим", + "error.file.mime.invalid": "Неверный тип mime: {mime}", + "error.file.mime.missing": "Не удалось определить тип медиа для файла \"{filename}\"", + "error.file.minheight": "Высота файла должна быть хотя бы {height} px", + "error.file.minsize": "Файл слишком маленький", + "error.file.minwidth": "Ширина файла должна быть хотя бы {width} px", + "error.file.name.missing": "Название файла не может быть пустым", + "error.file.notFound": "\u0424\u0430\u0439\u043b \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d", + "error.file.orientation": "Ориентация изображения должна быть \"{orientation}\"", + "error.file.type.forbidden": "У вас нет права загружать файлы {type}", + "error.file.type.invalid": "Неверный тип файла: {type}", + "error.file.undefined": "\u0424\u0430\u0439\u043b \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d", + + "error.form.incomplete": "Пожалуйста, исправьте все ошибки в форме", + "error.form.notSaved": "Форма не может быть сохранена", + + "error.language.code": "Пожалуйста, впишите правильный код языка", + "error.language.duplicate": "Язык уже есть", + "error.language.name": "Пожалуйста, впишите правильное название языка", + + "error.layout.validation.block": "Ошибка в блоке {blockIndex} в макете {layoutIndex}", + "error.layout.validation.settings": "Ошибка в настройках макета {index}", + + "error.license.format": "Пожалуйста, введите правильный лицензионный код", + "error.license.email": "Пожалуйста, введите правильный адрес эл. почты", + "error.license.verification": "Лицензия не подтверждена", + + "error.page.changeSlug.permission": "\u0412\u044b \u043d\u0435 \u043c\u043e\u0436\u0435\u0442\u0435 \u0438\u0437\u043c\u0435\u043d\u0438\u0442\u044c URL \u044d\u0442\u043e\u0439 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u044b", + "error.page.changeStatus.incomplete": "На странице есть ошибки и поэтому ее нельзя опубликовать", + "error.page.changeStatus.permission": "Невозможно поменять статус для этой страницы", + "error.page.changeStatus.toDraft.invalid": "Невозможно конвертировать в черновик страницу \"{slug}\"", + "error.page.changeTemplate.invalid": "Невозможно поменять шаблон страницы \"{slug}\"", + "error.page.changeTemplate.permission": "У вас нет права поменять шаблон для \"{slug}\"", + "error.page.changeTitle.empty": "Название не может быть пустым", + "error.page.changeTitle.permission": "у вас нет права поменять название \"{slug}\"", + "error.page.create.permission": "У вас нет права создать \"{slug}\"", + "error.page.delete": "Невозможно удалить страницу \"{slug}\"", + "error.page.delete.confirm": "Впишите название страницы чтобы подтвердить", + "error.page.delete.hasChildren": "У страницы есть внутренние страницы, поэтому ее невозможно удалить", + "error.page.delete.permission": "У вас нет права удалить \"{slug}\"", + "error.page.draft.duplicate": "Черновик страницы с аппендиксом URL \"{slug}\" уже есть", + "error.page.duplicate": "Страница с аппендиксом URL \"{slug}\" уже есть", + "error.page.duplicate.permission": "У вас нет права дублировать \"{slug}\"", + "error.page.notFound": "\u0421\u0442\u0440\u0430\u043d\u0438\u0446\u0430 \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u0430", + "error.page.num.invalid": "Пожалуйста, впишите правильное число сортировки. Число не может быть отрицательным.", + "error.page.slug.invalid": "Пожалуйста, введите правильный URL", + "error.page.slug.maxlength": "Длина ссылки (ЧПУ) должна быть короче \"{length}\" символов", + "error.page.sort.permission": "Невозможно сортировать страницу \"{slug}\"", + "error.page.status.invalid": "Пожалуйста, установите верный статус страницы", + "error.page.undefined": "\u0421\u0442\u0440\u0430\u043d\u0438\u0446\u0430 \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u0430", + "error.page.update.permission": "У вас нет права обновить \"{slug}\"", + + "error.section.files.max.plural": "Нельзя добавить больше чем {max} файлов в секции \"{section}\"", + "error.section.files.max.singular": "Можно добавить не больше 1 файла в секции \"{section}\"", + "error.section.files.min.plural": "Секция \"{section}\" требует хотя бы {min} файлов", + "error.section.files.min.singular": "Секция \"{section}\" требует хотя бы 1 файл", + + "error.section.pages.max.plural": "Можно добавить не больше {max} страниц в секции \"{section}\"", + "error.section.pages.max.singular": "Нельзя добавить больше чем 1 страницу в секции \"{section}\"", + "error.section.pages.min.plural": "Секция \"{section}\" требует хотя бы {min} страниц", + "error.section.pages.min.singular": "Секция \"{section}\" требует хотя бы одну страницу", + + "error.section.notLoaded": "Секция \"{name}\" не может быть загружена", + "error.section.type.invalid": "Тип секции {type} неверный", + + "error.site.changeTitle.empty": "Название не может быть пустым", + "error.site.changeTitle.permission": "У вас нет права поменять название сайта", + "error.site.update.permission": "У вас нет права обновить сайт", + + "error.template.default.notFound": "Нет шаблона по умолчанию", + + "error.user.changeEmail.permission": "У вас нет права поменять эл. почту пользователя \"{name}\"", + "error.user.changeLanguage.permission": "У вас нет права поменять язык для пользователя \"{name}\"", + "error.user.changeName.permission": "У вас нет права поменять имя пользователя \"{name}\"", + "error.user.changePassword.permission": "У вас нет права поменять пароль для пользователя \"{name}\"", + "error.user.changeRole.lastAdmin": "Роль единственного администратора нельзя поменять", + "error.user.changeRole.permission": "У вас нет права поменять поль для пользователя \"{name}\"", + "error.user.changeRole.toAdmin": "У вас нет прав предоставить роль администратора", + "error.user.create.permission": "У вас нет права создать этого пользователя", + "error.user.delete": "\u0410\u043a\u043a\u0430\u0443\u043d\u0442 \u043d\u0435 \u043c\u043e\u0436\u0435\u0442 \u0431\u044b\u0442\u044c \u0443\u0434\u0430\u043b\u0435\u043d", + "error.user.delete.lastAdmin": "\u0412\u044b \u043d\u0435 \u043c\u043e\u0436\u0435\u0442\u0435 \u0443\u0434\u0430\u043b\u0438\u0442\u044c \u0435\u0434\u0438\u043d\u0441\u0442\u0432\u0435\u043d\u043d\u043e\u0433\u043e \u0430\u0434\u043c\u0438\u043d\u0438\u0441\u0442\u0440\u0430\u0442\u043e\u0440\u0430", + "error.user.delete.lastUser": "Нельзя удалить единственного пользователя", + "error.user.delete.permission": "У вас нет права удалить пользователя \"{name}\"", + "error.user.duplicate": "Пользователь с эл. почтой \"{email}\" уже есть", + "error.user.email.invalid": "Пожалуйста, введите правильный адрес эл. почты", + "error.user.language.invalid": "Введите правильный язык", + "error.user.notFound": "\u0410\u043a\u043a\u0430\u0443\u043d\u0442 \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d", + "error.user.password.invalid": "Пожалуйста, введите правильный пароль. Он должен состоять минимум из 8 символов.", + "error.user.password.notSame": "\u041f\u043e\u0436\u0430\u043b\u0443\u0439\u0441\u0442\u0430, \u043f\u043e\u0434\u0442\u0432\u0435\u0440\u0434\u0438\u0442\u0435 \u043f\u0430\u0440\u043e\u043b\u044c", + "error.user.password.undefined": "У пользователя нет пароля", + "error.user.password.wrong": "Неверный пароль", + "error.user.role.invalid": "Введите правильную роль", + "error.user.update.permission": "У вас нет права обновить пользователя \"{name}\"", + + "error.validation.accepted": "Пожалуйста, подтвердите", + "error.validation.alpha": "Пожалуйста, введите только буквы a-z", + "error.validation.alphanum": "Пожалуйста, введите только буквы a-z или числа 0-9", + "error.validation.between": "Пожалуйста, введите значение от \"{min}\" до \"{max}\"", + "error.validation.boolean": "Пожалуйста, подтвердите или отмените", + "error.validation.contains": "Пожалуйста, впишите значение, которое содержит \"{needle}\"", + "error.validation.date": "Пожалуйста, укажите правильную дату", + "error.validation.date.after": "Пожалуйста, укажите дату после {date}", + "error.validation.date.before": "Пожалуйста, укажите дату до {date}", + "error.validation.date.between": "Пожалуйста, укажите дату между {min} и {max}", + "error.validation.denied": "Пожалуйста отмените", + "error.validation.different": "Значение не может быть \"{other}\"", + "error.validation.email": "Пожалуйста, введите правильный адрес эл. почты", + "error.validation.endswith": "Значение должно заканчиваться с \"{end}\"", + "error.validation.filename": "Пожалуйста, введите правильное название файла", + "error.validation.in": "Пожалуйста, введите одно из следующих: ({in})", + "error.validation.integer": "Пожалуйста, введите правильное целое число", + "error.validation.ip": "Пожалуйста, введите правильный IP адрес", + "error.validation.less": "Пожалуйста, введите значение меньше чем {max}", + "error.validation.match": "Значение не соответствует ожидаемому шаблону", + "error.validation.max": "Пожалуйста, введите значение равное или больше чем {max}", + "error.validation.maxlength": "Пожалуйста, введите значение короче (макс. {max} символов)", + "error.validation.maxwords": "Пожалуйста, введите не более {max} слов ", + "error.validation.min": "Пожалуйста, введите значение равное или больше чем {min}", + "error.validation.minlength": "Пожалуйста, введите значение длиннее (мин. {min} символов)", + "error.validation.minwords": "Пожалуйста, введите хотя бы {min} слов", + "error.validation.more": "Пожалуйста, введите значение больше, чем {min}", + "error.validation.notcontains": "Пожалуйста, введите значение, которое не содержит \"{needle}\"", + "error.validation.notin": "Пожалуйста, не вписывайте одно из: ({notIn})", + "error.validation.option": "Пожалуйста, выберите правильную опцию ", + "error.validation.num": "Пожалуйста, введите правильный номер", + "error.validation.required": "Пожалуйста, введите что-нибудь", + "error.validation.same": "Пожалуйста, введите \"{other}\"", + "error.validation.size": "Значение размера должно быть \"{size}\"", + "error.validation.startswith": "Значение должно начинаться с \"{start}\"", + "error.validation.time": "Пожалуйста, введите правильную дату", + "error.validation.time.after": "Пожалуйста, укажите время после {time}", + "error.validation.time.before": "Пожалуйста, укажите время до {time}", + "error.validation.time.between": "Пожалуйста, укажите время между {min} и {max}", + "error.validation.url": "Пожалуйста, введите правильный URL", + + "field.required": "Поле обязательно", + "field.blocks.changeType": "Изменить тип", + "field.blocks.code.name": "Код", + "field.blocks.code.language": "Язык", + "field.blocks.code.placeholder": "Ваш код …", + "field.blocks.delete.confirm": "Вы действительно хотите удалить этот блок?", + "field.blocks.delete.confirm.all": "Вы действительно хотите удалить все блоки?", + "field.blocks.delete.confirm.selected": "Вы действительно хотите удалить эти блоки?", + "field.blocks.empty": "Еще нет блоков", + "field.blocks.fieldsets.label": "Пожалуйста, выберите тип блока…", + "field.blocks.gallery.name": "Галерея", + "field.blocks.gallery.images.empty": "Еще нет изображений", + "field.blocks.gallery.images.label": "Изображения", + "field.blocks.heading.level": "Уровень", + "field.blocks.heading.name": "Заголовок", + "field.blocks.heading.text": "Текст", + "field.blocks.heading.placeholder": "Заголовок …", + "field.blocks.image.alt": "Альтернативный текст", + "field.blocks.image.caption": "Подпись", + "field.blocks.image.crop": "Обрезать", + "field.blocks.image.link": "Ссылка", + "field.blocks.image.location": "Расположение", + "field.blocks.image.name": "Картинка", + "field.blocks.image.placeholder": "Выберите изображение", + "field.blocks.image.ratio": "Соотношение", + "field.blocks.image.url": "URL изображения", + "field.blocks.list.name": "Список", + "field.blocks.markdown.name": "Markdown", + "field.blocks.markdown.label": "Текст", + "field.blocks.markdown.placeholder": "Markdown …", + "field.blocks.quote.name": "Цитата", + "field.blocks.quote.text.label": "Текст", + "field.blocks.quote.text.placeholder": "Цитата …", + "field.blocks.quote.citation.label": "Цитирование", + "field.blocks.quote.citation.placeholder": "Автор …", + "field.blocks.text.name": "Текст", + "field.blocks.text.placeholder": "Текст …", + "field.blocks.video.caption": "Подпись", + "field.blocks.video.name": "Видео", + "field.blocks.video.placeholder": "Введите ссылку на видео", + "field.blocks.video.url.label": "Ссылка на видео", + "field.blocks.video.url.placeholder": "https://youtube.com/?v=", + + "field.files.empty": "Еще не выбраны файлы", + + "field.layout.delete": "Удалить разметку", + "field.layout.delete.confirm": "Вы действительно хотите удалить эту разметку?", + "field.layout.empty": "Еще нет строк", + "field.layout.select": "Выберите разметку", + + "field.pages.empty": "Еще не выбраны страницы", + "field.structure.delete.confirm": "Вы точно хотите удалить эту запись?", + "field.structure.empty": "Еще нет записей", + "field.users.empty": "Еще нет пользователей", + + "file.blueprint": "Вы можете определить новые секции и поля формы для этого файла в /site/blueprints/{template}.yml", + "file.delete.confirm": "Вы точно хотите удалить файл
{filename}?", + "file.sort": "Изменить позицию", + + "files": "Файлы", + "files.empty": "Еще нет файлов", + + "hide": "Скрыть", + "hour": "Час", + "insert": "\u0412\u0441\u0442\u0430\u0432\u0438\u0442\u044c", + "insert.after": "Вставить ниже", + "insert.before": "Вставить выше", + "install": "Установить", + + "installation": "Установка", + "installation.completed": "Панель установлена", + "installation.disabled": "Установка панели по умолчанию отключена на общедоступных серверах. Пожалуйста запустите установку на локальном сервере или включите такую возможность с помощью опции panel.install", + "installation.issues.accounts": "Каталог /site/accounts не существует или не имеет прав записи", + "installation.issues.content": "Каталог /content не существует или не имеет прав записи", + "installation.issues.curl": "Расширение CURL необходимо", + "installation.issues.headline": "Не удалось установить панель", + "installation.issues.mbstring": "Расширение MB String необходимо", + "installation.issues.media": "Каталог /media не существует или нет прав записи", + "installation.issues.php": "Убедитесь, что используется PHP 7+", + "installation.issues.server": "Kirby требует Apache, Nginx или Caddy ", + "installation.issues.sessions": "Каталог /site/sessions не существует или нет прав записи", + + "language": "\u042f\u0437\u044b\u043a", + "language.code": "Код", + "language.convert": "Установить по умолчанию", + "language.convert.confirm": "

Вы точно хотите конвертировать {name} в главный язык? Это нельзя будет отменить.

Если {name} имеет непереведенный контент, то больше не будет верного каскада и части вашего сайта могут быть пустыми.

", + "language.create": "Добавить новый язык", + "language.delete.confirm": "Вы точно хотите удалить {name} язык, включая все переводы? Это нельзя будет вернуть.", + "language.deleted": "Язык удален", + "language.direction": "Направление чтения", + "language.direction.ltr": "Слева направо", + "language.direction.rtl": "Справа налево", + "language.locale": "PHP locale string", + "language.locale.warning": "Вы используете кастомную локаль. Пожалуйста измените ее в файле языка в /site/languages", + "language.name": "Название", + "language.updated": "Язык обновлен", + + "languages": "Языки", + "languages.default": "Главный язык", + "languages.empty": "Еще нет языков", + "languages.secondary": "Дополнительные языки", + "languages.secondary.empty": "Еще нет дополнительных языков", + + "license": "\u041b\u0438\u0446\u0435\u043d\u0437\u0438\u044f Kirby", + "license.buy": "Купить лицензию", + "license.register": "Зарегистрировать", + "license.register.help": "После покупки вы получили по эл. почте код лицензии. Пожалуйста скопируйте и вставьте сюда чтобы зарегистрировать.", + "license.register.label": "Пожалуйста вставьте код лицензии", + "license.register.success": "Спасибо за поддержку Kirby", + "license.unregistered": "Это незарегистрированная версия Kirby", + + "link": "\u0421\u0441\u044b\u043b\u043a\u0430", + "link.text": "\u0422\u0435\u043a\u0441\u0442 \u0441\u0441\u044b\u043b\u043a\u0438", + + "loading": "Загрузка", + + "lock.unsaved": "Несохраненные изменения", + "lock.unsaved.empty": "Больше нет несохраненных изменений", + "lock.isLocked": "Несохраненные изменения пользователя {email}", + "lock.file.isLocked": "В данный момент этот файл редактирует {email}, поэтому его нельзя изменить.", + "lock.page.isLocked": "В данный момент эту страницу редактирует {email}, поэтому его нельзя изменить.", + "lock.unlock": "Разблокировать", + "lock.isUnlocked": "Ваши несохраненные изменения были перезаписаны другим пользователем. Вы можете загрузить ваши изменения и объединить их вручную.", + + "login": "Войти", + "login.code.label.login": "Код для входа", + "login.code.label.password-reset": "Код для сброса пароля", + "login.code.placeholder.email": "000 000", + "login.code.text.email": "Если ваш Email уже зарегистрирован, запрашиваемый код был отправлен на него.", + "login.email.login.body": "Добрый день, {user.nameOrEmail}.\n\nНедавно вы запросили код для авторизации в Kirby Panel.\nЭтот код будет доступен {timeout} минут:\n\n{code}\n\nЕсли вы не запрашивали код для авторизации, пожалуйста, проигнорируйте это письмо или свяжитесь с вашим администратором, если у вас возникли вопросы.\nТакже исходя из соображений безопасности просим вас НЕ ПЕРЕСЫЛАТЬ это письмо.", + "login.email.login.subject": "Ваш код для входа", + "login.email.password-reset.body": "Добрый день, {user.nameOrEmail}.\n\nНедавно вы запросили код для сброса пароля в Kirby Panel.\nЭтот код будет доступен {timeout} минут:\n\n{code}\n\nЕсли вы не запрашивали код для сброса пароля, пожалуйста, проигнорируйте это письмо или свяжитесь с вашим администратором, если у вас возникли вопросы.\nТакже исходя из соображений безопасности просим вас НЕ ПЕРЕСЫЛАТЬ это письмо.", + "login.email.password-reset.subject": "Ваш код для сброса пароля", + "login.remember": "Сохранять вход активным", + "login.reset": "Сбросить пароль", + "login.toggleText.code.email": "Вход с помощью Email", + "login.toggleText.code.email-password": "Вход с паролем", + "login.toggleText.password-reset.email": "Забыли ваш пароль?", + "login.toggleText.password-reset.email-password": "← Вернуться к форме входа", + + "logout": "Выйти", + + "menu": "Меню", + "meridiem": "До полудня / После полудня", + "mime": "Тип медиа", + "minutes": "Минуты", + + "month": "Месяц", + "months.april": "\u0410\u043f\u0440\u0435\u043b\u044c", + "months.august": "\u0410\u0432\u0433\u0443\u0441\u0442", + "months.december": "\u0414\u0435\u043a\u0430\u0431\u0440\u044c", + "months.february": "Февраль", + "months.january": "\u042f\u043d\u0432\u0430\u0440\u044c", + "months.july": "\u0418\u044e\u043b\u044c", + "months.june": "\u0418\u044e\u043d\u044c", + "months.march": "\u041c\u0430\u0440\u0442", + "months.may": "\u041c\u0430\u0439", + "months.november": "\u041d\u043e\u044f\u0431\u0440\u044c", + "months.october": "\u041e\u043a\u0442\u044f\u0431\u0440\u044c", + "months.september": "\u0421\u0435\u043d\u0442\u044f\u0431\u0440\u044c", + + "more": "Подробнее", + "name": "Название", + "next": "Дальше", + "no": "нет", + "off": "выключено", + "on": "включено", + "open": "Открыть", + "open.newWindow": "Открывать в новом окне", + "options": "Опции", + "options.none": "Нет параметров", + + "orientation": "Ориентация", + "orientation.landscape": "Горизонтальная", + "orientation.portrait": "Портретная", + "orientation.square": "Квадрат", + + "page.blueprint": "Вы можете определить новые секции и поля формы для этой страницы в /site/blueprints/{template}.yml", + "page.changeSlug": "\u0418\u0437\u043c\u0435\u043d\u0438\u0442\u044c \u0441\u0441\u044b\u043b\u043a\u0443 (\u0427\u041f\u0423)", + "page.changeSlug.fromTitle": "Создать из названия", + "page.changeStatus": "Изменить статус", + "page.changeStatus.position": "Пожалуйста, выберите позицию", + "page.changeStatus.select": "Выбрать новый статус", + "page.changeTemplate": "Поменять шаблон", + "page.delete.confirm": "Вы точно хотите удалить страницу {title}?", + "page.delete.confirm.subpages": "У этой страницы есть внутренние страницы.
Все внутренние страницы так же будут удалены.", + "page.delete.confirm.title": "Напишите название страницы, чтобы подтвердить", + "page.draft.create": "Создать черновик", + "page.duplicate.appendix": "Скопировать", + "page.duplicate.files": "Копировать файлы", + "page.duplicate.pages": "Копировать страницы", + "page.sort": "Изменить позицию", + "page.status": "Статус", + "page.status.draft": "Черновик", + "page.status.draft.description": "Страница находится в черновом режиме и видна только зарегистрированным пользователям или по секретной ссылке", + "page.status.listed": "Опубликована", + "page.status.listed.description": "Страница доступна для всех посетителей", + "page.status.unlisted": "Скрыта", + "page.status.unlisted.description": "Страница доступна только по URL", + + "pages": "Страницы", + "pages.empty": "Еще нет страниц", + "pages.status.draft": "Черновики", + "pages.status.listed": "Опубликовано", + "pages.status.unlisted": "Скрытая", + + "pagination.page": "Страница", + + "password": "\u041f\u0430\u0440\u043e\u043b\u044c", + "pixel": "Пиксель", + "prev": "Предыдущий", + "preview": "Предпросмотр", + "remove": "Удалить", + "rename": "Переназвать", + "replace": "\u0417\u0430\u043c\u0435\u043d\u0438\u0442\u044c", + "retry": "\u041f\u043e\u0432\u0442\u043e\u0440\u0438\u0442\u044c", + "revert": "\u0421\u0431\u0440\u043e\u0441", + "revert.confirm": "Вы действительно хотите удалить все несохраненные изменения?", + + "role": "\u0420\u043e\u043b\u044c", + "role.admin.description": "Администратор имеет все права", + "role.admin.title": "Администратор", + "role.all": "Все", + "role.empty": "Нет пользователей с такой ролью", + "role.description.placeholder": "Без описания", + "role.nobody.description": "Эта роль применяется если у пользователя нет никаких прав", + "role.nobody.title": "Никто", + + "save": "\u0421\u043e\u0445\u0440\u0430\u043d\u0438\u0442\u044c", + "search": "Поиск", + "search.min": "Введите хотя бы {min} символов для поиска", + "search.all": "Показать все", + "search.results.none": "Нет результатов", + + "section.required": "Секция обязательна", + + "select": "Выбрать", + "settings": "Настройка", + "show": "Показать", + "size": "Размер", + "slug": "Понятная ссылка (ЧПУ)", + "sort": "Сортировать", + "title": "Название", + "template": "\u0428\u0430\u0431\u043b\u043e\u043d", + "today": "Сегодня", + + "site.blueprint": "Вы можете определить новые секции и поля формы для этого сайта в /site/blueprints/site.yml", + + "toolbar.button.code": "Код", + "toolbar.button.bold": "\u0416\u0438\u0440\u043d\u044b\u0439 \u0448\u0440\u0438\u0444\u0442", + "toolbar.button.email": "Эл. почта", + "toolbar.button.headings": "Заголовки", + "toolbar.button.heading.1": "Заголовок 1", + "toolbar.button.heading.2": "Заголовок 2", + "toolbar.button.heading.3": "Заголовок 3", + "toolbar.button.italic": "Курсив", + "toolbar.button.file": "Файл", + "toolbar.button.file.select": "Выбрать файл", + "toolbar.button.file.upload": "Закачать файл", + "toolbar.button.link": "\u0421\u0441\u044b\u043b\u043a\u0430", + "toolbar.button.strike": "Зачёркнутый", + "toolbar.button.ol": "Нумерованный список", + "toolbar.button.underline": "Подчёркнутый", + "toolbar.button.ul": "Маркированный список", + + "translation.author": "Команда Kirby", + "translation.direction": "ltr", + "translation.name": "Русский (Russian)", + "translation.locale": "ru_RU", + + "upload": "Закачать", + "upload.error.cantMove": "Загруженный файл не может быть перемещен", + "upload.error.cantWrite": "Не получилось записать файл на диск", + "upload.error.default": "Не получилось загрузить файл", + "upload.error.extension": "Загрузка файла не удалась из за расширения", + "upload.error.formSize": "Загруженный файл больше чем MAX_FILE_SIZE настройка в форме", + "upload.error.iniPostSize": "Загружаемый файл больше чем post_max_size настройка в php.ini", + "upload.error.iniSize": "Загруженный файл больше чем upload_max_filesize настройка в php.ini", + "upload.error.noFile": "Файл не был загружен", + "upload.error.noFiles": "Файлы не были загружены", + "upload.error.partial": "Файл загружен только частично", + "upload.error.tmpDir": "Не хватает временной папки", + "upload.errors": "Ошибка", + "upload.progress": "Закачивается...", + + "url": "URL", + "url.placeholder": "https://example.com", + + "user": "Пользователь", + "user.blueprint": "Вы можете определить новые секции и поля формы для этой роли пользователя в /site/blueprints/users/{role}.yml", + "user.changeEmail": "Поменять эл. почту", + "user.changeLanguage": "Поменять язык", + "user.changeName": "Переназвать этого пользователя", + "user.changePassword": "Поменять пароль", + "user.changePassword.new": "Новый пароль", + "user.changePassword.new.confirm": "Подтвердить новый пароль…", + "user.changeRole": "Поменять роль", + "user.changeRole.select": "Выбрать новую роль", + "user.create": "Добавить нового пользователя", + "user.delete": "Удалить этого пользователя", + "user.delete.confirm": "Вы действительно хотите аккаунт
{email}?", + + "users": "Пользователи", + + "version": "\u0412\u0435\u0440\u0441\u0438\u044f Kirby", + + "view.account": "\u0412\u0430\u0448 \u0430\u043a\u043a\u0430\u0443\u043d\u0442", + "view.installation": "\u0423\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0430", + "view.resetPassword": "Сбросить пароль", + "view.settings": "Настройка", + "view.site": "Сайт", + "view.users": "\u041f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0438", + + "welcome": "Добро пожаловать", + "year": "Год", + "yes": "да" +} diff --git a/kirby/i18n/translations/sk.json b/kirby/i18n/translations/sk.json new file mode 100644 index 0000000..b08251d --- /dev/null +++ b/kirby/i18n/translations/sk.json @@ -0,0 +1,527 @@ +{ + "add": "Pridať", + "avatar": "Profilový obrázok", + "back": "Späť", + "cancel": "Zrušiť", + "change": "Zmeniť", + "close": "Zavrieť", + "confirm": "Ok", + "collapse": "Zabaliť", + "collapse.all": "Zabaliť všetky", + "copy": "Kopírovať", + "create": "Vytvoriť", + + "date": "Dátum", + "date.select": "Zvoliť dátum", + + "day": "Deň", + "days.fri": "Pia", + "days.mon": "Pon", + "days.sat": "Sob", + "days.sun": "Ned", + "days.thu": "Štv", + "days.tue": "Uto", + "days.wed": "Str", + + "delete": "Zmazať", + "delete.all": "Zmazať všetky", + "dimensions": "Rozmery", + "disabled": "Disabled", + "discard": "Zahodiť", + "download": "Stiahnuť", + "duplicate": "Duplikovať", + "edit": "Upraviť", + "expand": "Rozbaliť", + "expand.all": "Rozbaliť všetky", + + "dialog.files.empty": "No files to select", + "dialog.pages.empty": "No pages to select", + "dialog.users.empty": "No users to select", + + "email": "E-mail", + "email.placeholder": "mail@example.com", + + "error.access.code": "Neplatný kód", + "error.access.login": "Neplatné prihlásenie", + "error.access.panel": "Nemáte povolenie na prístup do Panel-u", + "error.access.view": "You are not allowed to access this part of the panel", + + "error.avatar.create.fail": "Profilový obrázok sa nepodarilo nahrať", + "error.avatar.delete.fail": "Profilový obrázok sa nepodarilo zmazať", + "error.avatar.dimensions.invalid": "Prosím, dodržte, aby šírka a výška profilového obrázka bola menšia ako 3000 pixelov.", + "error.avatar.mime.forbidden": "Profilový obrázok musí byť súbor JPEG alebo PNG.", + + "error.blueprint.notFound": "Blueprint \"{name}\" sa nepodarilo načítať", + + "error.blocks.max.plural": "You must not add more than {max} blocks", + "error.blocks.max.singular": "You must not add more than one block", + "error.blocks.min.plural": "You must add at least {min} blocks", + "error.blocks.min.singular": "You must add at least one block", + "error.blocks.validation": "There's an error in block {index}", + + "error.email.preset.notFound": "E-mailovú predvoľbu \"{name}\" nie je možné nájsť", + + "error.field.converter.invalid": "Neplatný converter \"{converter}\"", + + "error.file.changeName.empty": "Meno nesmie byť prázdne", + "error.file.changeName.permission": "Nemáte povolenie na zmenu názvu pre \"{filename}\"", + "error.file.duplicate": "Súbor s názvom \"{filename}\" už existuje", + "error.file.extension.forbidden": "Prípona \"{extension}\" nie je povolená", + "error.file.extension.invalid": "Neplatná prípona: \"{extension}\"", + "error.file.extension.missing": "Prípona pre \"{filename}\" chýba", + "error.file.maxheight": "Výška obrázku nesmie prekročiť \"{height}\" pixelov", + "error.file.maxsize": "Súbor je príliš velký", + "error.file.maxwidth": "Šírka obrázku nesmie prekročiť \"{width}\" pixelov", + "error.file.mime.differs": "Mime typ nahratého súboru msa musí zhodovať s \"{mime}\"", + "error.file.mime.forbidden": "Typ média \"{mime}\" nie je povolený", + "error.file.mime.invalid": "Neplatný mime typ: \"{mime}\"", + "error.file.mime.missing": "Typ média pre \"{filename}\" sa nepodarilo zistiť", + "error.file.minheight": "Výška obrázku musí byť aspoň \"{height}\" pixelov", + "error.file.minsize": "Súbor je príliš malý", + "error.file.minwidth": "Šírka obrázku musí byť aspoň \"{width}\" pixelov", + "error.file.name.missing": "Názov súboru nemôže byť prázdny", + "error.file.notFound": "Súbor \"{filename}\" sa nepodarilo nájsť", + "error.file.orientation": "The orientation of the image must be \"{orientation}\"", + "error.file.type.forbidden": "Nemáte povolenie na nahrávanie súborov s typom {type}", + "error.file.type.invalid": "Neplatný typ súboru: \"{type}\"", + "error.file.undefined": "Súbor nie je možné nájsť", + + "error.form.incomplete": "Prosím, opravte všetky chyby v rámci formuláru...", + "error.form.notSaved": "Formulár sa nepodarilo uložiť", + + "error.language.code": "Please enter a valid code for the language", + "error.language.duplicate": "The language already exists", + "error.language.name": "Please enter a valid name for the language", + + "error.layout.validation.block": "There's an error in block {blockIndex} in layout {layoutIndex}", + "error.layout.validation.settings": "There's an error in layout {index} settings", + + "error.license.format": "Please enter a valid license key", + "error.license.email": "Prosím, zadajte platnú e-mailovú adresu", + "error.license.verification": "The license could not be verified", + + "error.page.changeSlug.permission": "Nemáte povolenie na zmenu URL príponu pre \"{slug}\"", + "error.page.changeStatus.incomplete": "Stránka obsahuje chyby a nemôže byť zverejnená", + "error.page.changeStatus.permission": "Status tejto stránky nemôže byť zmenený", + "error.page.changeStatus.toDraft.invalid": "Stránka \"{slug}\" nemôže byť zmenená na koncept.", + "error.page.changeTemplate.invalid": "Šablónu pre stránku \"{slug}\" nie je možné zmeniť", + "error.page.changeTemplate.permission": "Nemáte povolenie na zmenu šablóny pre \"{slug}\"", + "error.page.changeTitle.empty": "Titulok nemôže byť prázdny", + "error.page.changeTitle.permission": "Nemáte povolenie na zmenu titulku pre \"{slug}\"", + "error.page.create.permission": "Nemáte povolenie na vytvorenie \"{slug}\"", + "error.page.delete": "Stránku \"{slug}\" nie je možné vymazať", + "error.page.delete.confirm": "Prosím, zadajte titulok stránky pre potvrdenie", + "error.page.delete.hasChildren": "Táto stránka obsahuje podstránky a nemôže byť zmazaná", + "error.page.delete.permission": "Nemáte povolenie na zmazanie stránky \"{slug}\"", + "error.page.draft.duplicate": "Koncept stránky s URL appendix-om \"{slug}\" už existuje", + "error.page.duplicate": "Stránka s URL appendix-om \"{slug}\" už existuje", + "error.page.duplicate.permission": "You are not allowed to duplicate \"{slug}\"", + "error.page.notFound": "Stránku \"{slug}\" nie je možné nájsť", + "error.page.num.invalid": "Prosím, zadajte platné číslo pre radenie. Čísla nemôžu byť záporné.", + "error.page.slug.invalid": "Please enter a valid URL appendix", + "error.page.slug.maxlength": "Slug length must be less than \"{length}\" characters", + "error.page.sort.permission": "Stránku \"{slug}\" nie je možné preradiť.", + "error.page.status.invalid": "Prosím, nastavte platnú status pre stránku", + "error.page.undefined": "Stránku nie je možné nájsť", + "error.page.update.permission": "Nemáte povolenie na aktualizáciu \"{slug}\"", + + "error.section.files.max.plural": "Nemôžete pridať viac ako {max} súbory/ov do sekcie \"{section}\"", + "error.section.files.max.singular": "Nemôžete pridať viac ako 1 súbor do sekcie \"{section}\"", + "error.section.files.min.plural": "The \"{section}\" section requires at least {min} files", + "error.section.files.min.singular": "The \"{section}\" section requires at least one file", + + "error.section.pages.max.plural": "Nemôžete pridať viac ako {max} stránky/ok do sekcie \"{section}\"", + "error.section.pages.max.singular": "Nemôžete pridať viac ako 1 stránku do sekcie \"{section}\"", + "error.section.pages.min.plural": "The \"{section}\" section requires at least {min} pages", + "error.section.pages.min.singular": "The \"{section}\" section requires at least one page", + + "error.section.notLoaded": "Sekciu \"{name}\" sa nepodarilo nahrať", + "error.section.type.invalid": "Typ sekcie \"{type}\" nie je platný", + + "error.site.changeTitle.empty": "Titulok nemôže byť prázdny", + "error.site.changeTitle.permission": "Nemáte povolenie na zmenu titulku pre portál", + "error.site.update.permission": "Nemáte povolenie na aktualizovanie portálu", + + "error.template.default.notFound": "Predvolená šablóna neexistuje", + + "error.user.changeEmail.permission": "Nemáte povolenie na zmenu e-mailu pre užívateľa \"{name}\"", + "error.user.changeLanguage.permission": "Nemáte povolenie na zmenu jazyka pre užívateľa \"{name}\"", + "error.user.changeName.permission": "Nemáte povolenie na zmenu mena pre užívateľa \"{name}\"", + "error.user.changePassword.permission": "Nemáte povolenie na zmenu hesla pre užívateľa \"{name}\"", + "error.user.changeRole.lastAdmin": "Rolu pre posledného administrátora nie je možné zmeniť", + "error.user.changeRole.permission": "Nemáte povolenie na zmenu role pre užívateľa \"{name}\"", + "error.user.changeRole.toAdmin": "You are not allowed to promote someone to the admin role", + "error.user.create.permission": "Nemáte povolenie na vytvorenie tohto užívateľa", + "error.user.delete": "Užívateľa \"{name}\" nie je možné zmazať", + "error.user.delete.lastAdmin": "Posledného administrátora nie je možné zmazať", + "error.user.delete.lastUser": "Posledného užívateľa nie je možné zmazať", + "error.user.delete.permission": "Nemáte povolenie na zmazanie užívateľa \"{name}\"", + "error.user.duplicate": "Užívateľ s e-mailovou adresou \"{email}\" už existuje", + "error.user.email.invalid": "Prosím, zadajte platnú e-mailovú adresu", + "error.user.language.invalid": "Prosím, zadajte platný jazyk", + "error.user.notFound": "Užívateľa \"{name}\" nie je možné nájsť", + "error.user.password.invalid": "Prosím, zadajte platné heslo. Dĺžka hesla musí byť aspoň 8 znakov.", + "error.user.password.notSame": "Heslá nie sú rovnaké", + "error.user.password.undefined": "Užívateľ nemá heslo", + "error.user.password.wrong": "Wrong password", + "error.user.role.invalid": "Prosím, zadajte platnú rolu", + "error.user.update.permission": "Nemáte povolenie na aktualizáciu užívateľa \"{name}\"", + + "error.validation.accepted": "Prosím, potvrďte", + "error.validation.alpha": "Prosím, zadajte len znaky z hlások a-z", + "error.validation.alphanum": "Prosím, zadajte len znaky z hlások a-z a čísloviek 0-9", + "error.validation.between": "Prosím, zadajte hodnotu od \"{min}\" do \"{max}\"", + "error.validation.boolean": "Prosím, potvrďte alebo odmietnite", + "error.validation.contains": "Prosím, zadajte hodnotu, ktorá obsahuje \"{needle}\"", + "error.validation.date": "Prosím, zadajte platný dátum", + "error.validation.date.after": "Please enter a date after {date}", + "error.validation.date.before": "Please enter a date before {date}", + "error.validation.date.between": "Please enter a date between {min} and {max}", + "error.validation.denied": "Prosím, odmietnite", + "error.validation.different": "Hodnota nemôže byť \"{other}\"", + "error.validation.email": "Prosím, zadajte platnú e-mailovú adresu", + "error.validation.endswith": "Hodnota musí končiť na \"{end}\"", + "error.validation.filename": "Prosím, zadajte platný názov súboru", + "error.validation.in": "Prosím, zadajte jedno z nasledujúcich: ({in})", + "error.validation.integer": "Prosím, zadajte platné celé číslo", + "error.validation.ip": "Prosím, zadajte platnú e-mailovú adresu", + "error.validation.less": "Prosím, zadajte hodnotu menšiu ako {max}", + "error.validation.match": "Hodnota nezodpovedá očakávanému vzoru", + "error.validation.max": "Prosím, zadajte hodnotu rovnú alebo menšiu ako {max}", + "error.validation.maxlength": "Prosím, zadajte kratšiu hodnotu. (max. {max} charaktery/ov)", + "error.validation.maxwords": "Prosím, nezadávajte viac ako {max} slovo/á/ov", + "error.validation.min": "Prosím, zadajte hodnotu rovnú alebo väčšiu ako {min}", + "error.validation.minlength": "Prosím, zadajte dlhšiu hodnotu. (min. {min} charaktery/ov)", + "error.validation.minwords": "Prosím, zadajte aspoň {min} slovo/á/ov", + "error.validation.more": "Prosím zadajte hodnotu väčšiu ako {min}", + "error.validation.notcontains": "Prosím, zadajte hodnotu, ktorá neobsahuje \"{needle}\"", + "error.validation.notin": "Prosím, nezadávajte ani jedno z nasledujúcich: ({notIn})", + "error.validation.option": "Prosím, zadajte platnú voľbu", + "error.validation.num": "Prosím, zadajte platné číslo", + "error.validation.required": "Prosím, zadajte niečo", + "error.validation.same": "Prosím, zadajte \"{other}\"", + "error.validation.size": "Veľkosť hodnoty musí byť \"{size}\"", + "error.validation.startswith": "Hodnota musí začínať s \"{start}\"", + "error.validation.time": "Prosím, zadajte platný čas", + "error.validation.time.after": "Please enter a time after {time}", + "error.validation.time.before": "Please enter a time before {time}", + "error.validation.time.between": "Please enter a time between {min} and {max}", + "error.validation.url": "Prosím, zadajte platnú URL", + + "field.required": "The field is required", + "field.blocks.changeType": "Change type", + "field.blocks.code.name": "Kód", + "field.blocks.code.language": "Jazyk", + "field.blocks.code.placeholder": "Váš kód ...", + "field.blocks.delete.confirm": "Naozaj chcete zmazať tento blok?", + "field.blocks.delete.confirm.all": "Naozaj chcete zmazať všetky bloky?", + "field.blocks.delete.confirm.selected": "Naozaj chcete zmazať vybrané bloky?", + "field.blocks.empty": "No blocks yet", + "field.blocks.fieldsets.label": "Please select a block type …", + "field.blocks.gallery.name": "Galéria", + "field.blocks.gallery.images.empty": "No images yet", + "field.blocks.gallery.images.label": "Obrázky", + "field.blocks.heading.level": "Level", + "field.blocks.heading.name": "Nadpis", + "field.blocks.heading.text": "Text", + "field.blocks.heading.placeholder": "Nadpis ...", + "field.blocks.image.alt": "Alternative text", + "field.blocks.image.caption": "Popis", + "field.blocks.image.crop": "Orezanie", + "field.blocks.image.link": "Odkaz", + "field.blocks.image.location": "Poloha", + "field.blocks.image.name": "Obrázok", + "field.blocks.image.placeholder": "Select an image", + "field.blocks.image.ratio": "Ratio", + "field.blocks.image.url": "Image URL", + "field.blocks.list.name": "List", + "field.blocks.markdown.name": "Markdown", + "field.blocks.markdown.label": "Text", + "field.blocks.markdown.placeholder": "Markdown …", + "field.blocks.quote.name": "Quote", + "field.blocks.quote.text.label": "Text", + "field.blocks.quote.text.placeholder": "Quote …", + "field.blocks.quote.citation.label": "Citation", + "field.blocks.quote.citation.placeholder": "by …", + "field.blocks.text.name": "Text", + "field.blocks.text.placeholder": "Text …", + "field.blocks.video.caption": "Popis", + "field.blocks.video.name": "Video", + "field.blocks.video.placeholder": "Enter a video URL", + "field.blocks.video.url.label": "Video-URL", + "field.blocks.video.url.placeholder": "https://youtube.com/?v=", + + "field.files.empty": "Žiadne súbory zatiaľ neboli zvolené", + + "field.layout.delete": "Delete layout", + "field.layout.delete.confirm": "Do you really want to delete this layout?", + "field.layout.empty": "No rows yet", + "field.layout.select": "Select a layout", + + "field.pages.empty": "Žiadne stránky zatiaľ neboli zvolené", + "field.structure.delete.confirm": "Ste si istý, že chcete zmazať tento riadok?", + "field.structure.empty": "Zatiaľ žiadne údaje", + "field.users.empty": "Žiadni užívatelia zatiaľ neboli zvolení", + + "file.blueprint": "This file has no blueprint yet. You can define the setup in /site/blueprints/{template}.yml", + "file.delete.confirm": "Ste si istý, že chcete zmazať
{filename}?", + "file.sort": "Change position", + + "files": "Súbory", + "files.empty": "Zatiaľ žiadne súbory", + + "hide": "Hide", + "hour": "Hodina", + "insert": "Vložiť", + "insert.after": "Insert after", + "insert.before": "Insert before", + "install": "Inštalovať", + + "installation": "Inštalácia", + "installation.completed": "Panel bol nainštalovaný", + "installation.disabled": "Inštalácia Panelu na verejných serveroch je štandardne zablokovaná. Prosím, spustite inštaláciu na lokálnom serveri alebo aktivujte voľbu panel.install.", + "installation.issues.accounts": "Priečinok /site/accounts neexistuje alebo nie je nastavený ako zapisovateľný", + "installation.issues.content": "Priečinok /content neexistuje alebo nie je nastavený ako zapisovateľný", + "installation.issues.curl": "CURL rozšírenie je povinné", + "installation.issues.headline": "Panel nie je možné naištalovať", + "installation.issues.mbstring": "MB String rozšírenie je povinné", + "installation.issues.media": "Priečinok /media neexistuje alebo nie je nastavený ako zapisovateľný", + "installation.issues.php": "Uistite sa, že používate PHP 7+", + "installation.issues.server": "Kirby vyžaduje Apache, Nginx alebo Caddy", + "installation.issues.sessions": "Priečinok /site/sessions neexistuje alebo nie je nastavený ako zapisovateľný", + + "language": "Jazyk", + "language.code": "Kód", + "language.convert": "Nastaviť ako predvolené", + "language.convert.confirm": "

Ste si istý, že chcete nastaviť {name} ako predvolený jazyk? Túto akciu nie je možné zvrátiť.

Ak {name} obsahuje nepreložený obsah, tak pre tento obsah nebude fungovať platné volanie a niektoré časti vašich stránok zostanú prázdne.

", + "language.create": "Pridať nový jazyk", + "language.delete.confirm": "Ste si istý, že chcete zmazať jazyk {name} vrátane všetkých prekladov? Túto akciu nie je možné zvrátiť.", + "language.deleted": "Jazyk bol zmazaný", + "language.direction": "Smer čítania", + "language.direction.ltr": "Zľava doprava", + "language.direction.rtl": "Zprava doľava", + "language.locale": "PHP locale string", + "language.locale.warning": "You are using a custom locale set up. Please modify it in the language file in /site/languages", + "language.name": "Názov", + "language.updated": "Jazyk bol aktualizovaný", + + "languages": "Jazyky", + "languages.default": "Predvolený jazyk", + "languages.empty": "Zatiaľ žiadne jazyky", + "languages.secondary": "Sekundárne jazyky", + "languages.secondary.empty": "Zatiaľ žiadne sekundárne jazyky", + + "license": "Licencia", + "license.buy": "Zakúpiť licenciu", + "license.register": "Registrovať", + "license.register.help": "Licenčný kód vám bol doručený e-mailom po úspešnom nákupe. Prosím, skopírujte a prilepte ho na uskutočnenie registrácie.", + "license.register.label": "Prosím, zadajte váš licenčný kód", + "license.register.success": "Ďakujeme za vašu podporu Kirby", + "license.unregistered": "Toto je neregistrované demo Kirby", + + "link": "Odkaz", + "link.text": "Text odkazu", + + "loading": "Načítavanie", + + "lock.unsaved": "Unsaved changes", + "lock.unsaved.empty": "There are no more unsaved changes", + "lock.isLocked": "Unsaved changes by {email}", + "lock.file.isLocked": "The file is currently being edited by {email} and cannot be changed.", + "lock.page.isLocked": "The page is currently being edited by {email} and cannot be changed.", + "lock.unlock": "Unlock", + "lock.isUnlocked": "Your unsaved changes have been overwritten by another user. You can download your changes to merge them manually.", + + "login": "Prihlásenie", + "login.code.label.login": "Login code", + "login.code.label.password-reset": "Password reset code", + "login.code.placeholder.email": "000 000", + "login.code.text.email": "If your email address is registered, the requested code was sent via email.", + "login.email.login.body": "Hi {user.nameOrEmail},\n\nYou recently requested a login code for the Kirby Panel.\nThe following login code will be valid for {timeout} minutes:\n\n{code}\n\nIf you did not request a login code, please ignore this email or contact your administrator if you have questions.\nFor security, please DO NOT forward this email.", + "login.email.login.subject": "Your login code", + "login.email.password-reset.body": "Hi {user.nameOrEmail},\n\nYou recently requested a password reset code for the Kirby Panel.\nThe following password reset code will be valid for {timeout} minutes:\n\n{code}\n\nIf you did not request a password reset code, please ignore this email or contact your administrator if you have questions.\nFor security, please DO NOT forward this email.", + "login.email.password-reset.subject": "Your password reset code", + "login.remember": "Ponechať ma prihláseného", + "login.reset": "Reset password", + "login.toggleText.code.email": "Login via email", + "login.toggleText.code.email-password": "Login with password", + "login.toggleText.password-reset.email": "Forgot your password?", + "login.toggleText.password-reset.email-password": "← Back to login", + + "logout": "Odhlásenie", + + "menu": "Menu", + "meridiem": "AM/PM", + "mime": "Typ média", + "minutes": "Minúty", + + "month": "Mesiac", + "months.april": "Apríl", + "months.august": "August", + "months.december": "December", + "months.february": "Február", + "months.january": "Január", + "months.july": "Júl", + "months.june": "Jún", + "months.march": "Marec", + "months.may": "Máj", + "months.november": "November", + "months.october": "Október", + "months.september": "September", + + "more": "Viac", + "name": "Meno", + "next": "Ďalej", + "no": "no", + "off": "off", + "on": "on", + "open": "Otvoriť", + "open.newWindow": "Open in new window", + "options": "Nastavenia", + "options.none": "No options", + + "orientation": "Orientácia", + "orientation.landscape": "Širokouhlá", + "orientation.portrait": "Portrét", + "orientation.square": "Štvorec", + + "page.blueprint": "This page has no blueprint yet. You can define the setup in /site/blueprints/{template}.yml", + "page.changeSlug": "Zmeniť URL", + "page.changeSlug.fromTitle": "Vytvoriť z titulku", + "page.changeStatus": "Zmeniť status", + "page.changeStatus.position": "Prosím, zmeňte pozíciu", + "page.changeStatus.select": "Zvoľte nový status", + "page.changeTemplate": "Zmeniť šablónu", + "page.delete.confirm": "Ste si istý, že chcete zmazať {title}?", + "page.delete.confirm.subpages": "Táto stránka obsahuje podstránky.
Všetky podstránky budú taktiež zmazané.", + "page.delete.confirm.title": "Pre potvrdenie zadajte titulok stránky", + "page.draft.create": "Vytvoriť koncept", + "page.duplicate.appendix": "Kopírovať", + "page.duplicate.files": "Copy files", + "page.duplicate.pages": "Copy pages", + "page.sort": "Change position", + "page.status": "Status", + "page.status.draft": "Koncept", + "page.status.draft.description": "The page is in draft mode and only visible for logged in editors or via secret link", + "page.status.listed": "Verejné", + "page.status.listed.description": "Stránka je prístupná pre všetkých", + "page.status.unlisted": "Skryté", + "page.status.unlisted.description": "Stránka je prístupná len prostredníctvom priamej URL", + + "pages": "Stránky", + "pages.empty": "Zatiaľ žiadne stránky", + "pages.status.draft": "Koncepty", + "pages.status.listed": "Zverejnené", + "pages.status.unlisted": "Skryté", + + "pagination.page": "Stránka", + + "password": "Heslo", + "pixel": "Pixel", + "prev": "Predchádzajúci", + "preview": "Preview", + "remove": "Odstrániť", + "rename": "Premenovať", + "replace": "Nahradiť", + "retry": "Skúsiť ešte raz", + "revert": "Vrátiť späť", + "revert.confirm": "Do you really want to delete all unsaved changes?", + + "role": "Rola", + "role.admin.description": "The admin has all rights", + "role.admin.title": "Admin", + "role.all": "Všetko", + "role.empty": "S touto rolou neexistujú žiadni užívatelia", + "role.description.placeholder": "Žiadny popis", + "role.nobody.description": "This is a fallback role without any permissions", + "role.nobody.title": "Nobody", + + "save": "Uložiť", + "search": "Hľadať", + "search.min": "Enter {min} characters to search", + "search.all": "Show all", + "search.results.none": "No results", + + "section.required": "The section is required", + + "select": "Zvoliť", + "settings": "Nastavenia", + "show": "Show", + "size": "Veľkosť", + "slug": "URL appendix", + "sort": "Zoradiť", + "title": "Titulok", + "template": "Šablóna", + "today": "Dnes", + + "site.blueprint": "The site has no blueprint yet. You can define the setup in /site/blueprints/site.yml", + + "toolbar.button.code": "Kód", + "toolbar.button.bold": "Tučný", + "toolbar.button.email": "E-mail", + "toolbar.button.headings": "Nadpisy", + "toolbar.button.heading.1": "Nadpis 1", + "toolbar.button.heading.2": "Nadpis 2", + "toolbar.button.heading.3": "Nadpis 3", + "toolbar.button.italic": "Kurzíva", + "toolbar.button.file": "Súbor", + "toolbar.button.file.select": "Select a file", + "toolbar.button.file.upload": "Upload a file", + "toolbar.button.link": "Odkaz", + "toolbar.button.strike": "Strike-through", + "toolbar.button.ol": "Číslovaný zoznam", + "toolbar.button.underline": "Underline", + "toolbar.button.ul": "Odrážkový zoznam", + + "translation.author": "Tím Kirby", + "translation.direction": "ltr", + "translation.name": "Slovensky", + "translation.locale": "sk_SK", + + "upload": "Nahrať", + "upload.error.cantMove": "The uploaded file could not be moved", + "upload.error.cantWrite": "Failed to write file to disk", + "upload.error.default": "The file could not be uploaded", + "upload.error.extension": "File upload stopped by extension", + "upload.error.formSize": "The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the form", + "upload.error.iniPostSize": "The uploaded file exceeds the post_max_size directive in php.ini", + "upload.error.iniSize": "The uploaded file exceeds the upload_max_filesize directive in php.ini", + "upload.error.noFile": "No file was uploaded", + "upload.error.noFiles": "No files were uploaded", + "upload.error.partial": "The uploaded file was only partially uploaded", + "upload.error.tmpDir": "Missing a temporary folder", + "upload.errors": "Chyba", + "upload.progress": "Nahrávanie...", + + "url": "URL", + "url.placeholder": "https://example.com", + + "user": "Užívateľ", + "user.blueprint": "Ďalšie sekcie a formulárové polia pre túto užívateľskú rolu môžete definovať v rámci /site/blueprints/users/{role}.yml", + "user.changeEmail": "Zmeniť e-mail", + "user.changeLanguage": "Zmeniť jazyk", + "user.changeName": "Premenovať tohto užívateľa", + "user.changePassword": "Zmeniť heslo", + "user.changePassword.new": "Nové heslo", + "user.changePassword.new.confirm": "Potvrdiť nové heslo...", + "user.changeRole": "Zmeniť rolu", + "user.changeRole.select": "Zvoliť novú rolu", + "user.create": "Pridať nového užívateľa", + "user.delete": "Zmazať tohto užívateľa", + "user.delete.confirm": "Ste si istý, že chcete zmazať
{email}?", + + "users": "Užívatelia", + + "version": "Verzia", + + "view.account": "Váš účet", + "view.installation": "Inštalácia", + "view.resetPassword": "Reset password", + "view.settings": "Nastavenia", + "view.site": "Portál", + "view.users": "Užívatelia", + + "welcome": "Vitajte", + "year": "Rok", + "yes": "yes" +} diff --git a/kirby/i18n/translations/sv_SE.json b/kirby/i18n/translations/sv_SE.json new file mode 100644 index 0000000..69ce3f2 --- /dev/null +++ b/kirby/i18n/translations/sv_SE.json @@ -0,0 +1,527 @@ +{ + "add": "L\u00e4gg till", + "avatar": "Profilbild", + "back": "Tillbaka", + "cancel": "Avbryt", + "change": "\u00c4ndra", + "close": "St\u00e4ng", + "confirm": "Spara", + "collapse": "Kollapsa", + "collapse.all": "Kollapsa alla", + "copy": "Kopiera", + "create": "Skapa", + + "date": "Datum", + "date.select": "Välj ett datum", + + "day": "Dag", + "days.fri": "Fre", + "days.mon": "M\u00e5n", + "days.sat": "L\u00f6r", + "days.sun": "S\u00f6n", + "days.thu": "Tor", + "days.tue": "Tis", + "days.wed": "Ons", + + "delete": "Radera", + "delete.all": "Radera allt", + "dimensions": "Dimensioner", + "disabled": "Inaktiverad", + "discard": "Kassera", + "download": "Ladda ner", + "duplicate": "Duplicera", + "edit": "Redigera", + "expand": "Expandera", + "expand.all": "Expandera alla", + + "dialog.files.empty": "Inga filer att välja", + "dialog.pages.empty": "Inga sidor att välja", + "dialog.users.empty": "Inga användare att välja", + + "email": "E-postadress", + "email.placeholder": "namn@exempel.se", + + "error.access.code": "Ogiltig kod", + "error.access.login": "Ogiltig inloggning", + "error.access.panel": "Du saknar behörighet att nå panelen", + "error.access.view": "Du saknar behörighet att nå denna del av panelen", + + "error.avatar.create.fail": "Profilbilden kunde inte laddas upp", + "error.avatar.delete.fail": "Profilbilden kunde inte raderas", + "error.avatar.dimensions.invalid": "Se till att profilbildens bredd och höjd är mindre än 3000 pixlar", + "error.avatar.mime.forbidden": "Profilbilden måste vara i formatet JPEG eller PNG", + + "error.blueprint.notFound": "Blueprint \"{name}\" kunde inte laddas", + + "error.blocks.max.plural": "Du får inte lägga till mer än {max} block", + "error.blocks.max.singular": "Du får inte lägga till mer än ett block", + "error.blocks.min.plural": "Du måste lägga till minst {min} block", + "error.blocks.min.singular": "Du måste lägga till minst ett block", + "error.blocks.validation": "Det finns ett fel i block {index}", + + "error.email.preset.notFound": "E-postförinställningen \"{name}\" kan inte hittas", + + "error.field.converter.invalid": "Ogiltig omvandlare \"{converter}\"", + + "error.file.changeName.empty": "Namnet får inte vara tomt", + "error.file.changeName.permission": "Du har inte behörighet att ändra namnet på \"{filename}\"", + "error.file.duplicate": "En fil med namnet \"{filename}\" existerar redan", + "error.file.extension.forbidden": "Filändelsen \"{extension}\" är inte tillåten", + "error.file.extension.invalid": "Ogiltig filändelse: {extension}", + "error.file.extension.missing": "Filen \"{filename}\" saknar filändelse", + "error.file.maxheight": "Bildens höjd får inte överstiga {height} pixlar", + "error.file.maxsize": "Filen är för stor", + "error.file.maxwidth": "Bildens bredd får inte överstiga {width} pixlar", + "error.file.mime.differs": "Den uppladdade filen måste vara av samma mime-typ \"{mime}\"", + "error.file.mime.forbidden": "Mediatypen \"{mime}\" är inte tillåten", + "error.file.mime.invalid": "Ogiltig mime-typ: {mime}", + "error.file.mime.missing": "Mediatypen för \"{filename}\" kan inte detekteras", + "error.file.minheight": "Bildens höjd måste vara minst {height} pixlar", + "error.file.minsize": "Filen är för liten", + "error.file.minwidth": "Bildens bredd måste vara minst {width} pixlar", + "error.file.name.missing": "Filnamnet får inte vara tomt", + "error.file.notFound": "Filen \"{filename}\" kan ej hittas", + "error.file.orientation": "Bildens orientering måste vara \"{orientation}\"", + "error.file.type.forbidden": "Du har inte behörighet att ladda upp filer av typen {type}", + "error.file.type.invalid": "Ogiltig filtyp: {type}", + "error.file.undefined": "Filen kan inte hittas", + + "error.form.incomplete": "Vänligen åtgärda alla formulärfel...", + "error.form.notSaved": "Formuläret kunde inte sparas", + + "error.language.code": "Ange en giltig kod för språket", + "error.language.duplicate": "Språket finns redan", + "error.language.name": "Ange ett giltigt namn för språket", + + "error.layout.validation.block": "Det finns ett fel i block {blockIndex} i layout {layoutIndex}", + "error.layout.validation.settings": "Det finns ett fel i inställningarna för layout {index}", + + "error.license.format": "Ange en giltig licensnyckel", + "error.license.email": "Ange en giltig e-postadress", + "error.license.verification": "Licensen kunde inte verifieras", + + "error.page.changeSlug.permission": "Du har inte behörighet att ändra URL-appendixen för \"{slug}\"", + "error.page.changeStatus.incomplete": "Sidan innehåller fel och kan inte publiceras", + "error.page.changeStatus.permission": "Statusen för denna sida kan inte ändras", + "error.page.changeStatus.toDraft.invalid": "Statusen för sidan \"{slug}\" kan inte ändras till utkast", + "error.page.changeTemplate.invalid": "Mallen för sidan \"{slug}\" kan inte ändras", + "error.page.changeTemplate.permission": "Du har inte behörighet att ändra mallen för \"{slug}\"", + "error.page.changeTitle.empty": "Titeln får inte vara tom", + "error.page.changeTitle.permission": "Du har inte behörighet att ändra titeln för \"{slug}\"", + "error.page.create.permission": "Du har inte behörighet att skapa \"{slug}\"", + "error.page.delete": "Sidan \"{slug}\" kan inte raderas", + "error.page.delete.confirm": "Fyll i sidans titel för att bekräfta", + "error.page.delete.hasChildren": "Sidan har undersidor och kan inte raderas", + "error.page.delete.permission": "Du har inte behörighet att radera \"{slug}\"", + "error.page.draft.duplicate": "Ett utkast med URL-appendixen \"{slug}\" existerar redan", + "error.page.duplicate": "En sida med URL-appendixen \"{slug}\" existerar redan", + "error.page.duplicate.permission": "Du har inte behörighet att duplicera \"{slug}\"", + "error.page.notFound": "Sidan \"{slug}\" kan inte hittas", + "error.page.num.invalid": "Ange ett giltigt nummer för sortering. Numret får inte vara negativt.", + "error.page.slug.invalid": "Ange en giltig URL-appendix", + "error.page.slug.maxlength": "Permalänkens längd måste vara kortare än \"{length}\" tecken", + "error.page.sort.permission": "Sidan \"{slug}\" kan inte sorteras", + "error.page.status.invalid": "Sätt en giltig status för sidan", + "error.page.undefined": "Sidan kan inte hittas", + "error.page.update.permission": "Du har inte behörighet att uppdatera \"{slug}\"", + + "error.section.files.max.plural": "Du får inte lägga till mer än {max} filer till sektionen \"{section}\"", + "error.section.files.max.singular": "Du får inte lägga till mer än en fil i sektionen \"{section}\"", + "error.section.files.min.plural": "Sektionen \"{section}\" kräver minst {min} filer", + "error.section.files.min.singular": "Sektionen \"{section}\" kräver minst en fil", + + "error.section.pages.max.plural": "Du får inte lägga till mer än {max} sidor till sektionen \"{section}\"", + "error.section.pages.max.singular": "Du får inte lägga till mer än en sida i sektionen \"{section}\"", + "error.section.pages.min.plural": "Sektionen \"{section}\" kräver minst {min} sidor", + "error.section.pages.min.singular": "Sektionen \"{section}\" kräver minst en sida", + + "error.section.notLoaded": "Sektionen \"{name}\" kunde inte laddas", + "error.section.type.invalid": "Sektionstypen \"{type}\" är inte giltig", + + "error.site.changeTitle.empty": "Titeln får inte vara tom", + "error.site.changeTitle.permission": "Du har inte behörighet att ändra titeln på webbplatsen", + "error.site.update.permission": "Du har inte behörighet att uppdatera webbplatsen", + + "error.template.default.notFound": "Standardmallen existerar inte", + + "error.user.changeEmail.permission": "Du har inte behörighet att ändra e-postadressen för användaren \"{name}\"", + "error.user.changeLanguage.permission": "Du har inte behörighet att ändra språket för användaren \"{name}\"", + "error.user.changeName.permission": "Du har inte behörighet att ändra namnet för användaren \"{name}\"", + "error.user.changePassword.permission": "Du har inte behörighet att ändra lösenordet för användaren \"{name}\"", + "error.user.changeRole.lastAdmin": "Rollen för den återstående adminanvändaren kan inte ändras", + "error.user.changeRole.permission": "Du har inte behörighet att ändra rollen för användaren \"{name}\"", + "error.user.changeRole.toAdmin": "Du har inte behörighet att ge någon en administratörsroll", + "error.user.create.permission": "Du har inte behörighet att skapa denna användare", + "error.user.delete": "Användaren kan inte raderas", + "error.user.delete.lastAdmin": "Den återstående administratören kan inte raderas", + "error.user.delete.lastUser": "Den återstående användaren kan inte raderas", + "error.user.delete.permission": "Du har inte behörighet att radera användaren \"{name}\"", + "error.user.duplicate": "En användare med e-postadressen \"{email}\" finns redan", + "error.user.email.invalid": "Ange en giltig e-postadress", + "error.user.language.invalid": "Ange ett giltigt språk", + "error.user.notFound": "Användaren \"{name}\" kan ej hittas", + "error.user.password.invalid": "Ange ett giltigt lösenord. Lösenordet måste vara minst 8 tecken långt.", + "error.user.password.notSame": "Lösenorden matchar inte", + "error.user.password.undefined": "Användaren har inget lösenord", + "error.user.password.wrong": "Fel lösenord", + "error.user.role.invalid": "Ange en giltig roll", + "error.user.update.permission": "Du har inte behörighet att uppdatera användaren \"{name}\"", + + "error.validation.accepted": "Vänligen bekräfta", + "error.validation.alpha": "Ange endast tecken mellan a-z", + "error.validation.alphanum": "Ange endast tecken mellan a-z eller siffror 0-9", + "error.validation.between": "Ange ett värde mellan \"{min}\" och \"{max}\"", + "error.validation.boolean": "Bekräfta eller neka", + "error.validation.contains": "Ange ett värde som innehåller \"{needle}\"", + "error.validation.date": "Ange ett giltigt datum", + "error.validation.date.after": "Ange ett datum efter {date}", + "error.validation.date.before": "Ange ett datum före {date}", + "error.validation.date.between": "Ange ett datum mellan {min} och {max}", + "error.validation.denied": "Vänligen neka", + "error.validation.different": "Värdet får inte vara \"{other}\"", + "error.validation.email": "Ange en giltig e-postadress", + "error.validation.endswith": "Värdet måste sluta med \"{end}\"", + "error.validation.filename": "Ange ett giltigt filnamn", + "error.validation.in": "Ange ett av följande: ({in})", + "error.validation.integer": "Ange en giltig heltalssiffra", + "error.validation.ip": "Ange en giltig IP-adress", + "error.validation.less": "Ange ett värde lägre än {max}", + "error.validation.match": "Värdet matchar inte det förväntade mönstret", + "error.validation.max": "Ange ett värde som är lika med eller lägre än {max}", + "error.validation.maxlength": "Ange ett kortare värde. (max {max} tecken)", + "error.validation.maxwords": "Ange inte mer än {max} ord", + "error.validation.min": "Ange ett värde som är lika med eller större än {min}", + "error.validation.minlength": "Ange ett längre värde. (minst {min} tecken)", + "error.validation.minwords": "Ange minst {min} ord", + "error.validation.more": "Ange ett större värde än {min}", + "error.validation.notcontains": "Ange ett värde som inte innehåller \"{needle}\"", + "error.validation.notin": "Ange inte något av följande: ({notIn})", + "error.validation.option": "Välj ett giltigt alternativ", + "error.validation.num": "Ange ett giltigt nummer", + "error.validation.required": "Ange någonting", + "error.validation.same": "Ange \"{other}\"", + "error.validation.size": "Storleken av värdet måste vara \"{size}\"", + "error.validation.startswith": "Värdet måste börja med \"{start}\"", + "error.validation.time": "Ange en giltig tid", + "error.validation.time.after": "Ange en tid efter {time}", + "error.validation.time.before": "Ange en tid före {time}", + "error.validation.time.between": "Ange en tid mellan {min} och {max}", + "error.validation.url": "Ange en giltig URL", + + "field.required": "Fältet krävs", + "field.blocks.changeType": "Ändra typ", + "field.blocks.code.name": "Kod", + "field.blocks.code.language": "Språk", + "field.blocks.code.placeholder": "Din kod …", + "field.blocks.delete.confirm": "Vill du verkligen radera detta block?", + "field.blocks.delete.confirm.all": "Vill du verkligen radera alla block?", + "field.blocks.delete.confirm.selected": "Vill du verkligen radera de valda blocken?", + "field.blocks.empty": "Inga block än", + "field.blocks.fieldsets.label": "Välj en typ av block …", + "field.blocks.gallery.name": "Galleri", + "field.blocks.gallery.images.empty": "Inga bilder än", + "field.blocks.gallery.images.label": "Bilder", + "field.blocks.heading.level": "Nivå", + "field.blocks.heading.name": "Rubrik", + "field.blocks.heading.text": "Text", + "field.blocks.heading.placeholder": "Rubrik …", + "field.blocks.image.alt": "Alternativ text", + "field.blocks.image.caption": "Rubrik", + "field.blocks.image.crop": "Beskär", + "field.blocks.image.link": "Länk", + "field.blocks.image.location": "Plats", + "field.blocks.image.name": "Bild", + "field.blocks.image.placeholder": "Välj en bild", + "field.blocks.image.ratio": "Bildförhållande", + "field.blocks.image.url": "Bild-URL", + "field.blocks.list.name": "Punktlista", + "field.blocks.markdown.name": "Markdown", + "field.blocks.markdown.label": "Text", + "field.blocks.markdown.placeholder": "Markdown …", + "field.blocks.quote.name": "Citat", + "field.blocks.quote.text.label": "Text", + "field.blocks.quote.text.placeholder": "Citat …", + "field.blocks.quote.citation.label": "Citat", + "field.blocks.quote.citation.placeholder": "av …", + "field.blocks.text.name": "Text", + "field.blocks.text.placeholder": "Text …", + "field.blocks.video.caption": "Rubrik", + "field.blocks.video.name": "Video", + "field.blocks.video.placeholder": "Ange en URL till en video", + "field.blocks.video.url.label": "Video-URL", + "field.blocks.video.url.placeholder": "https://youtube.com/?v=", + + "field.files.empty": "Inga filer valda än", + + "field.layout.delete": "Radera layout", + "field.layout.delete.confirm": "Vill du verkligen radera denna layout?", + "field.layout.empty": "Inga rader än", + "field.layout.select": "Välj en layout", + + "field.pages.empty": "Inga sidor valda än", + "field.structure.delete.confirm": "Vill du verkligen radera denna rad?", + "field.structure.empty": "Inga poster än", + "field.users.empty": "Inga användare valda än", + + "file.blueprint": "Denna fil har ingen blueprint än. Du kan skapa en i /site/blueprints/{template}.yml", + "file.delete.confirm": "Vill du verkligen radera
{filename}?", + "file.sort": "Ändra position", + + "files": "Filer", + "files.empty": "Inga filer än", + + "hide": "Göm", + "hour": "Timme", + "insert": "Infoga", + "insert.after": "Infoga efter", + "insert.before": "Infoga före", + "install": "Installera", + + "installation": "Installation", + "installation.completed": "Panelen har installerats", + "installation.disabled": "Installeraren för panelen är som standard inaktiverad på offentliga servrar. Kör installeraren på en lokal maskin eller aktivera den med alternativet panel.install.", + "installation.issues.accounts": "Mappen /site/accounts finns inte eller är inte skrivbar", + "installation.issues.content": "Mappen /content finns inte eller är inte skrivbar", + "installation.issues.curl": "Tillägget CURL krävs", + "installation.issues.headline": "Panelen kan inte installeras", + "installation.issues.mbstring": "Tillägget MB String krävs", + "installation.issues.media": "Mappen /media finns inte eller är inte skrivbar", + "installation.issues.php": "Se till att du använder PHP 7+", + "installation.issues.server": "Kirby kräver Apache, Nginx eller Caddy", + "installation.issues.sessions": "Mappen /site/sessions finns inte eller är inte skrivbar", + + "language": "Spr\u00e5k", + "language.code": "Kod", + "language.convert": "Ange som standard", + "language.convert.confirm": "

Vill du verkligen göra {name} till standardspråket? Detta kan inte ångras.

Om {name} har oöversatt innehåll, kommer det inte längre finnas en alternativ översättning och delar av sajten kommer kanske att vara tom.

", + "language.create": "Lägg till ett nytt språk", + "language.delete.confirm": "Vill du verkligen radera språket {name} inklusive alla översättningar? Detta kan inte ångras!", + "language.deleted": "Språket har raderats", + "language.direction": "Läsriktning", + "language.direction.ltr": "Vänster till höger", + "language.direction.rtl": "Höger till vänster", + "language.locale": "PHP locale string", + "language.locale.warning": "Du använder en anpassad språkinställning. Ändra den i språkfilen i mappen /site/languages", + "language.name": "Namn", + "language.updated": "Språket har uppdaterats", + + "languages": "Språk", + "languages.default": "Standardspråk", + "languages.empty": "Det finns inga språk ännu", + "languages.secondary": "Sekundära språk", + "languages.secondary.empty": "Det finns inga sekundära språk ännu", + + "license": "Licens", + "license.buy": "Köp en licens", + "license.register": "Registrera", + "license.register.help": "Du fick din licenskod via e-post efter inköpet. Kopiera och klistra in den för att registrera licensen.", + "license.register.label": "Ange din licenskod", + "license.register.success": "Tack för att du stödjer Kirby", + "license.unregistered": "Detta är en oregistrerad demo av Kirby", + + "link": "L\u00e4nk", + "link.text": "L\u00e4nktext", + + "loading": "Laddar", + + "lock.unsaved": "Osparade ändringar", + "lock.unsaved.empty": "Det finns inga fler osparade ändringar", + "lock.isLocked": "Osparade ändringar av {email}", + "lock.file.isLocked": "Filen redigeras just nu av {email} och kan inte redigeras.", + "lock.page.isLocked": "Sidan redigeras just nu av {email} och kan inte redigeras.", + "lock.unlock": "Lås upp", + "lock.isUnlocked": "Dina osparade ändringar har skrivits över av en annan användare. Du kan ladda ner dina ändringar för att slå ihop dem manuellt.", + + "login": "Logga in", + "login.code.label.login": "Inloggningskod", + "login.code.label.password-reset": "Kod för återställning av lösenord", + "login.code.placeholder.email": "000 000", + "login.code.text.email": "Om din e-postadress är registrerad skickades den begärda koden via e-post.", + "login.email.login.body": "Hej {user.nameOrEmail},\n\nDu begärde nyligen en inloggningskod för Kirby-panelen.\nFöljande kod är giltig i {timeout} minuter:\n\n{code}\n\nOm du inte har begärt en inloggningskod kan du ignorera det här e-postmeddelandet eller kontakta din administratör om du har frågor.\nAv säkerhetsskäl ska du INTE vidarebefordra detta e-postmeddelande.", + "login.email.login.subject": "Din inloggningskod", + "login.email.password-reset.body": "Hej {user.nameOrEmail},\n\nDu begärde nyligen en kod för återställning av lösenordet för Kirby-panelen.\nFöljande kod är giltig i {timeout} minuter:\n\n{code}\n\nOm du inte har begärt en återställning av lösenordet, ignorera detta e-postmeddelande eller kontakta din administratör om du har frågor.\nAv säkerhetsskäl ska du INTE vidarebefordra detta e-postmeddelande.", + "login.email.password-reset.subject": "Din kod för återställning av lösenord", + "login.remember": "Håll mig inloggad", + "login.reset": "Återställ lösenord", + "login.toggleText.code.email": "Logga in via e-post", + "login.toggleText.code.email-password": "Logga in med lösenord", + "login.toggleText.password-reset.email": "Glömt ditt lösenord?", + "login.toggleText.password-reset.email-password": "← Tillbaka till inloggning", + + "logout": "Logga ut", + + "menu": "Meny", + "meridiem": "a.m./p.m.", + "mime": "Mediatyp", + "minutes": "Minuter", + + "month": "Månad", + "months.april": "April", + "months.august": "Augusti", + "months.december": "December", + "months.february": "Februari", + "months.january": "Januari", + "months.july": "Juli", + "months.june": "Juni", + "months.march": "Mars", + "months.may": "Maj", + "months.november": "November", + "months.october": "Oktober", + "months.september": "September", + + "more": "Mer", + "name": "Namn", + "next": "Nästa", + "no": "nej", + "off": "av", + "on": "på", + "open": "Öppna", + "open.newWindow": "Öppna i nytt fönster", + "options": "Alternativ", + "options.none": "Inga alternativ", + + "orientation": "Orientering", + "orientation.landscape": "Liggande", + "orientation.portrait": "Stående", + "orientation.square": "Kvadrat", + + "page.blueprint": "Denna sida har ingen blueprint än. Du kan skapa en i /site/blueprints/{template}.yml", + "page.changeSlug": "Ändra URL", + "page.changeSlug.fromTitle": "Skapa utifr\u00e5n titel", + "page.changeStatus": "Ändra status", + "page.changeStatus.position": "Välj en ny position", + "page.changeStatus.select": "Välj en ny status", + "page.changeTemplate": "Ändra mall", + "page.delete.confirm": "Vill du verkligen radera {title}?", + "page.delete.confirm.subpages": "Denna sida har undersidor.
Alla undersidor kommer också att raderas.", + "page.delete.confirm.title": "Fyll i sidans titel för att bekräfta", + "page.draft.create": "Skapa utkast", + "page.duplicate.appendix": "Kopiera", + "page.duplicate.files": "Kopiera filer", + "page.duplicate.pages": "Kopiera sidor", + "page.sort": "Ändra position", + "page.status": "Status", + "page.status.draft": "Utkast", + "page.status.draft.description": "Sidan är ett utkast och endast synlig för inloggade redaktörer eller via en hemlig länk", + "page.status.listed": "Publik", + "page.status.listed.description": "Sidan är publik för vem som helst", + "page.status.unlisted": "Olistad", + "page.status.unlisted.description": "Sidan är endast åtkomlig via URL", + + "pages": "Sidor", + "pages.empty": "Inga sidor än", + "pages.status.draft": "Utkast", + "pages.status.listed": "Publicerade", + "pages.status.unlisted": "Olistade", + + "pagination.page": "Sida", + + "password": "L\u00f6senord", + "pixel": "Pixel", + "prev": "Föregående", + "preview": "Förhandsgranska", + "remove": "Ta bort", + "rename": "Byt namn", + "replace": "Ersätt", + "retry": "F\u00f6rs\u00f6k igen", + "revert": "Återgå", + "revert.confirm": "Vill du verkligen radera alla osparade ändringar?", + + "role": "Roll", + "role.admin.description": "Administratören har alla behörigheter", + "role.admin.title": "Administratör", + "role.all": "Alla", + "role.empty": "Det finns inga användare med denna roll", + "role.description.placeholder": "Ingen beskrivning", + "role.nobody.description": "Detta är en roll utan några behörigheter", + "role.nobody.title": "Ingen", + + "save": "Spara", + "search": "Sök", + "search.min": "Ange {min} tecken för att söka", + "search.all": "Visa alla", + "search.results.none": "Inga träffar", + + "section.required": "Sektionen krävs", + + "select": "Välj", + "settings": "Inställningar", + "show": "Visa", + "size": "Storlek", + "slug": "URL-appendix", + "sort": "Sortera", + "title": "Titel", + "template": "Mall", + "today": "Idag", + + "site.blueprint": "Webbplatsen har ingen blueprint än. Du kan skapa en i /site/blueprints/site.yml", + + "toolbar.button.code": "Kod", + "toolbar.button.bold": "Fet", + "toolbar.button.email": "E-post", + "toolbar.button.headings": "Rubriker", + "toolbar.button.heading.1": "Rubrik 1", + "toolbar.button.heading.2": "Rubrik 2", + "toolbar.button.heading.3": "Rubrik 3", + "toolbar.button.italic": "Kursiv", + "toolbar.button.file": "Fil", + "toolbar.button.file.select": "Välj en fil", + "toolbar.button.file.upload": "Ladda upp en fil", + "toolbar.button.link": "L\u00e4nk", + "toolbar.button.strike": "Genomstruken", + "toolbar.button.ol": "Sorterad lista", + "toolbar.button.underline": "Understruken", + "toolbar.button.ul": "Punktlista", + + "translation.author": "Kirby-teamet, Ola Christensson", + "translation.direction": "ltr", + "translation.name": "Svenska", + "translation.locale": "sv_SE", + + "upload": "Ladda upp", + "upload.error.cantMove": "Den överförda filen kunde inte flyttas", + "upload.error.cantWrite": "Det gick inte att skriva filen till hårddisken", + "upload.error.default": "Filen kunde inte laddas upp", + "upload.error.extension": "Filuppladdningen förhindrades på grund av filändelsen", + "upload.error.formSize": "Den överförda filen överskrider den maximala filstorlek som anges i formuläret (MAX_FILE_SIZE)", + "upload.error.iniPostSize": "Den överförda filen överskrider post_max_size-direktivet i php.ini", + "upload.error.iniSize": "Den överförda filen överskrider direktivet upload_max_filesize i php.ini", + "upload.error.noFile": "Ingen fil laddades upp", + "upload.error.noFiles": "Inga filer laddades upp", + "upload.error.partial": "Den överförda filen laddades bara delvis upp", + "upload.error.tmpDir": "Saknar en temporär mapp", + "upload.errors": "Fel", + "upload.progress": "Laddar upp...", + + "url": "URL", + "url.placeholder": "https://exempel.se", + + "user": "Användare", + "user.blueprint": "Du kan ange ytterligare sektioner och fält för denna användarroll i /site/blueprints/users/{role}.yml", + "user.changeEmail": "Ändra e-postadress", + "user.changeLanguage": "Ändra språk", + "user.changeName": "Byt namn på denna användare", + "user.changePassword": "Ändra lösenord", + "user.changePassword.new": "Nytt lösenord", + "user.changePassword.new.confirm": "Bekräfta det nya lösenordet...", + "user.changeRole": "Ändra roll", + "user.changeRole.select": "Välj en ny roll", + "user.create": "Lägg till en ny användare", + "user.delete": "Radera denna användare", + "user.delete.confirm": "Vill du verkligen radera
{email}?", + + "users": "Användare", + + "version": "Version", + + "view.account": "Ditt konto", + "view.installation": "Installation", + "view.resetPassword": "Återställ lösenord", + "view.settings": "Inställningar", + "view.site": "Webbplats", + "view.users": "Anv\u00e4ndare", + + "welcome": "Välkommen", + "year": "År", + "yes": "ja" +} diff --git a/kirby/i18n/translations/tr.json b/kirby/i18n/translations/tr.json new file mode 100644 index 0000000..d93718f --- /dev/null +++ b/kirby/i18n/translations/tr.json @@ -0,0 +1,527 @@ +{ + "add": "Ekle", + "avatar": "Profil resmi", + "back": "Geri", + "cancel": "\u0130ptal", + "change": "De\u011fi\u015ftir", + "close": "Kapat", + "confirm": "Tamam", + "collapse": "Daralt", + "collapse.all": "Tümünü daralt", + "copy": "Kopyala", + "create": "Oluştur", + + "date": "Tarih", + "date.select": "Bir tarih seçiniz", + + "day": "Gün", + "days.fri": "Cum", + "days.mon": "Pzt", + "days.sat": "Cmt", + "days.sun": "Paz", + "days.thu": "Per", + "days.tue": "Sal", + "days.wed": "\u00c7ar", + + "delete": "Sil", + "delete.all": "Tümünü sil", + "dimensions": "Boyutlar", + "disabled": "Devredışı", + "discard": "Vazge\u00e7", + "download": "İndir", + "duplicate": "Kopyala", + "edit": "D\u00fczenle", + "expand": "Genişlet", + "expand.all": "Tümünü genişlet", + + "dialog.files.empty": "Seçilecek dosya yok", + "dialog.pages.empty": "Seçilecek sayfa yok", + "dialog.users.empty": "Seçilecek kullanıcı yok", + + "email": "E-Posta", + "email.placeholder": "eposta@ornek.com", + + "error.access.code": "Geçersiz kod", + "error.access.login": "Geçersiz giriş", + "error.access.panel": "Panel'e erişim izniniz yok", + "error.access.view": "Panel'in bu bölümüne erişim izniniz yok", + + "error.avatar.create.fail": "Profil resmi yüklenemedi", + "error.avatar.delete.fail": "Profil resmi silinemedi", + "error.avatar.dimensions.invalid": "Lütfen profil resminin genişliğini ve yüksekliğini 3000 pikselin altında tutun", + "error.avatar.mime.forbidden": "Profil resmi JPEG veya PNG dosyaları olmalıdır", + + "error.blueprint.notFound": "\"{name}\" adlı plan yüklenemedi", + + "error.blocks.max.plural": "{max} bloktan fazlasını eklememelisiniz", + "error.blocks.max.singular": "Birden fazla blok eklememelisiniz", + "error.blocks.min.plural": "En az {min} blok eklemelisiniz", + "error.blocks.min.singular": "En az bir blok eklemelisiniz", + "error.blocks.validation": "{index} bloğunda bir hata var", + + "error.email.preset.notFound": "\"{name}\" e-posta adresi bulunamadı", + + "error.field.converter.invalid": "Geçersiz dönüştürücü \"{converter}\"", + + "error.file.changeName.empty": "İsim boş olmamalıdır", + "error.file.changeName.permission": "\"{filename}\" adını değiştiremezsiniz", + "error.file.duplicate": "\"{filename}\" isimli bir dosya zaten var", + "error.file.extension.forbidden": "\"{extension}\" dosya uzantısına izin verilmiyor", + "error.file.extension.invalid": "Geçersiz uzantı: {extension}", + "error.file.extension.missing": "\"{filename}\" dosyasının uzantısı yok", + "error.file.maxheight": "Resmin yüksekliği {height} pikselden büyük olmamalıdır", + "error.file.maxsize": "Dosya çok büyük", + "error.file.maxwidth": "Resmin genişliği {width} pikselden büyük olmamalıdır", + "error.file.mime.differs": "Yüklenen dosya aynı dosya türü \"{mime}\" olmalıdır", + "error.file.mime.forbidden": "\"{mime}\" medya türüne izin verilmiyor", + "error.file.mime.invalid": "Geçersiz medya türü: {mime}", + "error.file.mime.missing": "\"{filename}\" için medya türü tespit edilemiyor", + "error.file.minheight": "Resmin yüksekliği en az {height} piksel olmalıdır", + "error.file.minsize": "Dosya çok küçük", + "error.file.minwidth": "Resmin genişliği en az {width} piksel olmalıdır", + "error.file.name.missing": "Dosya adı boş bırakılamaz", + "error.file.notFound": "\"{filename}\" dosyası bulunamadı", + "error.file.orientation": "Resmin oryantasyonu \"{orientation}\" olmalıdır", + "error.file.type.forbidden": "{type} dosya yükleme izni yok", + "error.file.type.invalid": "Geçersiz dosya türü: {type}", + "error.file.undefined": "Dosya bulunamad\u0131", + + "error.form.incomplete": "Lütfen tüm form hatalarını düzeltin...", + "error.form.notSaved": "Form kaydedilemedi", + + "error.language.code": "Lütfen dil için geçerli bir kod girin", + "error.language.duplicate": "Bu dil zaten var", + "error.language.name": "Lütfen dil için geçerli bir isim girin", + + "error.layout.validation.block": "{layoutIndex}. düzenin {blockIndex}. bloğunda bir hata var", + "error.layout.validation.settings": "{index}. düzen ayarlarında bir hata var", + + "error.license.format": "Lütfen geçerli bir lisans anahtarı girin", + "error.license.email": "Lütfen geçerli bir e-posta adresi girin", + "error.license.verification": "Lisans doğrulanamadı", + + "error.page.changeSlug.permission": "\"{slug}\" uzantısına sahip bu sayfanın adresini değiştirilemez", + "error.page.changeStatus.incomplete": "Sayfada hatalar var ve yayınlanamadı", + "error.page.changeStatus.permission": "Bu sayfanın durumu değiştirilemez", + "error.page.changeStatus.toDraft.invalid": "\"{slug}\" sayfası bir taslak haline dönüştürülemiyor", + "error.page.changeTemplate.invalid": "\"{slug}\" sayfası için şablon değiştirilemiyor", + "error.page.changeTemplate.permission": "\"{slug}\" için şablonu değiştiremezsiniz", + "error.page.changeTitle.empty": "Başlık boş bırakılamaz", + "error.page.changeTitle.permission": "\"{slug}\" için başlığı değiştiremezsiniz", + "error.page.create.permission": "\"{slug}\" oluşturmanıza izin verilmiyor", + "error.page.delete": "\"{slug}\" sayfası silinemedi", + "error.page.delete.confirm": "Onaylamak için sayfa başlığını girin", + "error.page.delete.hasChildren": "Sayfada alt sayfalar var ve silinemiyor", + "error.page.delete.permission": "\"{slug}\" öğesini silmenize izin verilmiyor", + "error.page.draft.duplicate": "\"{slug}\" adres eki olan bir sayfa taslağı zaten mevcut", + "error.page.duplicate": "\"{slug}\" adres eki içeren bir sayfa zaten mevcut", + "error.page.duplicate.permission": "\"{slug}\" öğesini çoğaltmanıza izin verilmiyor", + "error.page.notFound": "\"{slug}\" uzantısındaki sayfa bulunamadı", + "error.page.num.invalid": "Lütfen geçerli bir sıralama numarası girin. Sayılar negatif olmamalıdır.", + "error.page.slug.invalid": "Lütfen geçerli bir URL eki girin", + "error.page.slug.maxlength": "Adres uzantısı \"{length}\" karakterden az olmalıdır", + "error.page.sort.permission": "\"{slug}\" sayfası sıralanamıyor", + "error.page.status.invalid": "Lütfen geçerli bir sayfa durumu ayarlayın", + "error.page.undefined": "Sayfa bulunamad\u0131", + "error.page.update.permission": "\"{slug}\" güncellemesine izin verilmiyor", + + "error.section.files.max.plural": "\"{section}\" bölümüne {max} dosyadan daha fazlasını eklememelisiniz", + "error.section.files.max.singular": "\"{section}\" bölümüne birden fazla dosya eklememelisiniz", + "error.section.files.min.plural": "\"{section}\" bölümü en az {min} dosya gerektiriyor", + "error.section.files.min.singular": "\"{section}\" bölümü en az bir dosya gerektiriyor", + + "error.section.pages.max.plural": "\"{section}\" bölümüne maksimum {max} sayfadan fazla ekleyemezsiniz", + "error.section.pages.max.singular": "\"{section}\" bölümüne birden fazla sayfa ekleyemezsiniz", + "error.section.pages.min.plural": "\"{section}\" bölümü en az {min} sayfa gerektiriyor", + "error.section.pages.min.singular": "\"{section}\" bölümü en az bir sayfa gerektiriyor", + + "error.section.notLoaded": "\"{name}\" bölümü yüklenemedi", + "error.section.type.invalid": "\"{type}\" tipi geçerli değil", + + "error.site.changeTitle.empty": "Başlık boş bırakılamaz", + "error.site.changeTitle.permission": "Sitenin başlığını değiştiremezsin", + "error.site.update.permission": "Siteyi güncellemenize izin verilmiyor", + + "error.template.default.notFound": "Varsayılan şablon yok", + + "error.user.changeEmail.permission": "\"{name}\" kullanıcısı için e-postayı değiştiremezsiniz", + "error.user.changeLanguage.permission": "\"{name}\" kullanıcısının dilini değiştiremezsin", + "error.user.changeName.permission": "\"{name}\" kullanıcısının adını değiştiremezsiniz", + "error.user.changePassword.permission": "\"{name}\" kullanıcısının şifresini değiştiremezsiniz", + "error.user.changeRole.lastAdmin": "Son yöneticinin rolü değiştirilemez", + "error.user.changeRole.permission": "\"{name}\" kullanıcısının rolünü değiştiremezsin", + "error.user.changeRole.toAdmin": "Birini yönetici rolüne tanıtmanıza izin verilmiyor", + "error.user.create.permission": "Bu kullanıcıyı oluşturmanıza izin verilmiyor", + "error.user.delete": "\"{name}\" kullanıcısı silinemedi", + "error.user.delete.lastAdmin": "Son y\u00f6netici kullan\u0131c\u0131y\u0131 silemezsiniz", + "error.user.delete.lastUser": "Son kullanıcı silinemez", + "error.user.delete.permission": "\"{name}\" kullanıcısını silme yetkiniz yok", + "error.user.duplicate": "\"{email}\" e-posta adresine sahip bir kullanıcı zaten var", + "error.user.email.invalid": "Lütfen geçerli bir e-posta adresi girin", + "error.user.language.invalid": "Lütfen geçerli bir dil girin", + "error.user.notFound": "\"{name}\" kullanıcısı bulunamadı", + "error.user.password.invalid": "Lütfen geçerli bir şifre giriniz. Şifreler en az 8 karakter uzunluğunda olmalıdır.", + "error.user.password.notSame": "L\u00fctfen \u015fifreyi do\u011frulay\u0131n", + "error.user.password.undefined": "Bu kullanıcının şifresi yok", + "error.user.password.wrong": "Yanlış şifre", + "error.user.role.invalid": "Lütfen geçerli bir rol girin", + "error.user.update.permission": "\"{name}\" kullanıcısını güncellemenize izin verilmiyor", + + "error.validation.accepted": "Lütfen onaylayın", + "error.validation.alpha": "Lütfen sadece a-z arasındaki karakterleri girin", + "error.validation.alphanum": "Lütfen sadece a-z veya 0-9 arasındaki rakamları girin", + "error.validation.between": "Lütfen \"{min}\" ile \"{max}\" arasında bir değer girin", + "error.validation.boolean": "Lütfen onaylayın veya reddedin", + "error.validation.contains": "Lütfen \"{needle}\" içeren bir değer girin", + "error.validation.date": "Lütfen geçerli bir tarih girin", + "error.validation.date.after": "Lütfen {date} tarihinden sonra bir tarih girin", + "error.validation.date.before": "Lütfen {date} tarihinden önce bir tarih girin", + "error.validation.date.between": "Lütfen {min} ve {max} arasında bir tarih girin", + "error.validation.denied": "Lütfen reddedin", + "error.validation.different": "Değer \"{other}\" olmamalıdır", + "error.validation.email": "Lütfen geçerli bir e-posta adresi girin", + "error.validation.endswith": "Değer \"{end}\" ile bitmelidir", + "error.validation.filename": "Lütfen geçerli bir dosya adı girin", + "error.validation.in": "Lütfen bunlardan birini girin: ({in})", + "error.validation.integer": "Lütfen geçerli bir tamsayı girin", + "error.validation.ip": "Lütfen geçerli bir ip adresi girin", + "error.validation.less": "Lütfen {max} 'dan daha düşük bir değer girin", + "error.validation.match": "Değer beklenen modelle eşleşmiyor", + "error.validation.max": "Lütfen {max} 'a eşit veya daha küçük bir değer girin", + "error.validation.maxlength": "Lütfen daha kısa bir değer girin. (maks. {max} karakter)", + "error.validation.maxwords": "Lütfen en fazla {max} kelime(ler) girin", + "error.validation.min": "Lütfen {min} ile eşit veya daha büyük bir değer girin", + "error.validation.minlength": "Lütfen daha uzun bir değer girin. (min. {min} karakter)", + "error.validation.minwords": "Lütfen en az {min} kelime(ler) girin", + "error.validation.more": "Lütfen {min} değerinden daha büyük bir değer girin", + "error.validation.notcontains": "Lütfen \"{needle}\" içermeyen bir değer girin", + "error.validation.notin": "Lütfen bunlardan herhangi birini girmeyin: ({notIn})", + "error.validation.option": "Lütfen geçerli bir seçenek girin", + "error.validation.num": "Lütfen geçerli bir sayı girin", + "error.validation.required": "Lütfen birşeyler girin", + "error.validation.same": "Lütfen \"{other}\" yazınız", + "error.validation.size": "Değerin boyutu \"{size}\" olmalıdır", + "error.validation.startswith": "Değer \"{start}\" ile başlamalıdır", + "error.validation.time": "Lütfen geçerli bir zaman girin", + "error.validation.time.after": "Lütfen {time} sonrası bir tarih girin", + "error.validation.time.before": "Lütfen {time} öncesi bir tarih girin", + "error.validation.time.between": "Lütfen {min} ile {max} arasında bir tarih girin", + "error.validation.url": "Lütfen geçerli bir adres girin", + + "field.required": "Alan gereklidir", + "field.blocks.changeType": "Türü değiştir", + "field.blocks.code.name": "Kod", + "field.blocks.code.language": "Dil", + "field.blocks.code.placeholder": "Kodunuz …", + "field.blocks.delete.confirm": "Bu bloğu gerçekten silmek istiyor musunuz?", + "field.blocks.delete.confirm.all": "Tüm blokları gerçekten silmek istiyor musunuz?", + "field.blocks.delete.confirm.selected": "Seçilen blokları gerçekten silmek istiyor musunuz?", + "field.blocks.empty": "Henüz blok yok", + "field.blocks.fieldsets.label": "Lütfen bir blok türü seçiniz …", + "field.blocks.gallery.name": "Galeri", + "field.blocks.gallery.images.empty": "Henüz görsel yok", + "field.blocks.gallery.images.label": "Görseller", + "field.blocks.heading.level": "Seviye", + "field.blocks.heading.name": "Başlık", + "field.blocks.heading.text": "Metin", + "field.blocks.heading.placeholder": "Başlık …", + "field.blocks.image.alt": "Alternatif metin", + "field.blocks.image.caption": "Altyazı", + "field.blocks.image.crop": "Kırp", + "field.blocks.image.link": "Bağlantı", + "field.blocks.image.location": "Lokasyon", + "field.blocks.image.name": "Görsel", + "field.blocks.image.placeholder": "Bir görsel seçin", + "field.blocks.image.ratio": "Oran", + "field.blocks.image.url": "Görsel URL", + "field.blocks.list.name": "Liste", + "field.blocks.markdown.name": "Markdown", + "field.blocks.markdown.label": "Metin", + "field.blocks.markdown.placeholder": "Markdown …", + "field.blocks.quote.name": "Alıntı", + "field.blocks.quote.text.label": "Metin", + "field.blocks.quote.text.placeholder": "Alıntı …", + "field.blocks.quote.citation.label": "Alıntı", + "field.blocks.quote.citation.placeholder": "yazar …", + "field.blocks.text.name": "Metin", + "field.blocks.text.placeholder": "Metin …", + "field.blocks.video.caption": "Altyazı", + "field.blocks.video.name": "Video", + "field.blocks.video.placeholder": "Bir video URL'si girin", + "field.blocks.video.url.label": "Video-URL", + "field.blocks.video.url.placeholder": "https://youtube.com/?v=", + + "field.files.empty": "Henüz dosya seçilmedi", + + "field.layout.delete": "Düzeni sil", + "field.layout.delete.confirm": "Bu düzeni gerçekten silmek istiyor musunuz?", + "field.layout.empty": "Henüz satır yok", + "field.layout.select": "Bir düzen seçin", + + "field.pages.empty": "Henüz sayfa seçilmedi", + "field.structure.delete.confirm": "Bu girdiyi silmek istedi\u011finizden emin misiniz?", + "field.structure.empty": "Hen\u00fcz bir girdi yok", + "field.users.empty": "Henüz kullanıcı seçilmedi", + + "file.blueprint": "Bu dosyanın henüz bir planı yok. Kurulumu /site/blueprints/file/{template}.yml'de tanımlayabilirsiniz.", + "file.delete.confirm": "{filename} dosyasını silmek istediğinizden emin misiniz?", + "file.sort": "Pozisyon değiştir", + + "files": "Dosyalar", + "files.empty": "Henüz dosya yok", + + "hide": "Gizle", + "hour": "Saat", + "insert": "Ekle", + "insert.after": "Sonrasına ekle", + "insert.before": "Öncesine ekle", + "install": "Kurulum", + + "installation": "Kurulum", + "installation.completed": "Panel kuruldu", + "installation.disabled": "Panel yükleyici, herkese açık sunucularda varsayılan olarak devre dışıdır. Lütfen yükleyiciyi yerel bir makinede çalıştırın veya panel.install seçeneğiyle etkinleştirin.", + "installation.issues.accounts": "/site/accounts klasörü yok yada yazılabilir değil", + "installation.issues.content": "/content klasörü yok yada yazılabilir değil", + "installation.issues.curl": "CURL eklentisi gerekli", + "installation.issues.headline": "Panel kurulamadı", + "installation.issues.mbstring": "MB String eklentisi gerekli", + "installation.issues.media": "/media klasörü yok yada yazılamaz", + "installation.issues.php": "PHP 7+ kullandığınızdan emin olun. ", + "installation.issues.server": "Kirby Apache, Nginx veya Caddy gerektirir", + "installation.issues.sessions": "/site/sessions klasörü mevcut değil veya yazılabilir değil", + + "language": "Dil", + "language.code": "Kod", + "language.convert": "Varsayılan yap", + "language.convert.confirm": "

{name}'i varsayılan dile dönüştürmek istiyor musunuz? Bu geri alınamaz.

{name} çevrilmemiş içeriğe sahipse, artık geçerli bir geri dönüş olmaz ve sitenizin bazı bölümleri boş olabilir.

", + "language.create": "Yeni bir dil ekle", + "language.delete.confirm": "Tüm çevirileri içeren {name} dilini gerçekten silmek istiyor musunuz? Bu geri alınamaz!", + "language.deleted": "Dil silindi", + "language.direction": "Okuma yönü", + "language.direction.ltr": "Soldan sağa", + "language.direction.rtl": "Sağdan sola", + "language.locale": "PHP yerel dizesi", + "language.locale.warning": "Özel bir yerel ayar kullanıyorsunuz. Lütfen /site/languages konumundaki dil dosyasından değiştirin.", + "language.name": "İsim", + "language.updated": "Dil güncellendi", + + "languages": "Diller", + "languages.default": "Varsayılan dil", + "languages.empty": "Henüz hiç dil yok", + "languages.secondary": "İkincil diller", + "languages.secondary.empty": "Henüz ikincil bir dil yok", + + "license": "Lisans", + "license.buy": "Bir lisans satın al", + "license.register": "Kayıt Ol", + "license.register.help": "Satın alma işleminden sonra e-posta yoluyla lisans kodunuzu aldınız. Lütfen kayıt olmak için kodu kopyalayıp yapıştırın.", + "license.register.label": "Lütfen lisans kodunu giriniz", + "license.register.success": "Kirby'yi desteklediğiniz için teşekkürler", + "license.unregistered": "Bu Kirby'nin kayıtsız bir demosu", + + "link": "Ba\u011flant\u0131", + "link.text": "Ba\u011flant\u0131 yaz\u0131s\u0131", + + "loading": "Yükleniyor", + + "lock.unsaved": "Kaydedilmemiş değişiklikler", + "lock.unsaved.empty": "Daha fazla kaydedilmemiş değişiklik yok", + "lock.isLocked": "{email} tarafından kaydedilmemiş değişiklikler", + "lock.file.isLocked": "Dosya şu anda {email} tarafından düzenlenmektedir ve değiştirilemez.", + "lock.page.isLocked": "Sayfa şu anda {email} tarafından düzenlenmektedir ve değiştirilemez.", + "lock.unlock": "Kilidi Aç", + "lock.isUnlocked": "Kaydedilmemiş değişikliklerin üzerine başka bir kullanıcı yazmış. Değişikliklerinizi el ile birleştirmek için değişikliklerinizi indirebilirsiniz.", + + "login": "Giri\u015f", + "login.code.label.login": "Giriş kodu", + "login.code.label.password-reset": "Şifre sıfırlama kodu", + "login.code.placeholder.email": "000 000", + "login.code.text.email": "E-posta adresiniz kayıtlıysa, istenen kod e-posta yoluyla gönderilmiştir.", + "login.email.login.body": "Merhaba {user.nameOrEmail},\n\nKısa süre önce Kirby Panel'i için bir giriş kodu istediniz.\nAşağıdaki giriş kodu {timeout} dakika boyunca geçerli olacaktır:\n\n{code}\n\nBir giriş kodu istemediyseniz, lütfen bu e-postayı dikkate almayın veya sorularınız varsa yöneticinize başvurun.\nGüvenlik için lütfen bu e-postayı İLETMEYİN.", + "login.email.login.subject": "Giriş kodunuz", + "login.email.password-reset.body": "Merhaba {user.nameOrEmail},\n\nKısa süre önce Kirby Panel'i için bir şifre sıfırlama kodu istediniz.\nAşağıdaki şifre sıfırlama kodu {timeout} dakika boyunca geçerli olacaktır:\n\n{code}\n\nŞifre sıfırlama kodu istemediyseniz, lütfen bu e-postayı dikkate almayın veya sorularınız varsa yöneticinizle iletişime geçin.\nGüvenlik için lütfen bu e-postayı İLETMEYİN.", + "login.email.password-reset.subject": "Şifre sıfırlama kodunuz", + "login.remember": "Oturumumu açık tut", + "login.reset": "Şifreyi sıfırla", + "login.toggleText.code.email": "E-posta ile giriş yapın", + "login.toggleText.code.email-password": "Şifre ile giriş yapın", + "login.toggleText.password-reset.email": "Şifrenizi mi unuttunuz?", + "login.toggleText.password-reset.email-password": "← Girişe geri dön", + + "logout": "Güvenli Çıkış", + + "menu": "Menü", + "meridiem": "AM/PM", + "mime": "Medya Türü", + "minutes": "Dakika", + + "month": "Ay", + "months.april": "Nisan", + "months.august": "A\u011fustos", + "months.december": "Aral\u0131k", + "months.february": "Şubat", + "months.january": "Ocak", + "months.july": "Temmuz", + "months.june": "Haziran", + "months.march": "Mart", + "months.may": "May\u0131s", + "months.november": "Kas\u0131m", + "months.october": "Ekim", + "months.september": "Eyl\u00fcl", + + "more": "Daha Fazla", + "name": "İsim", + "next": "Sonraki", + "no": "hayır", + "off": "kapalı", + "on": "açık", + "open": "Önizleme", + "open.newWindow": "Yeni pencerede aç", + "options": "Seçenekler", + "options.none": "Seçenek yok", + + "orientation": "Oryantasyon", + "orientation.landscape": "Yatay", + "orientation.portrait": "Dikey", + "orientation.square": "Kare", + + "page.blueprint": "Bu sayfanın henüz bir planı yok. Kurulumu /site/blueprints/{template}.yml'de tanımlayabilirsiniz.", + "page.changeSlug": "Web Adresini Değiştir", + "page.changeSlug.fromTitle": "Ba\u015fl\u0131ktan olu\u015ftur", + "page.changeStatus": "Durumu değiştir", + "page.changeStatus.position": "Lütfen bir pozisyon seçin", + "page.changeStatus.select": "Yeni bir durum seçin", + "page.changeTemplate": "Şablonu değiştir", + "page.delete.confirm": "{title} sayfasını silmek istediğinizden emin misiniz?", + "page.delete.confirm.subpages": "Bu sayfada alt sayfalar var.
Tüm alt sayfalar da silinecek.", + "page.delete.confirm.title": "Onaylamak için sayfa başlığını girin", + "page.draft.create": "Taslak oluştur", + "page.duplicate.appendix": "Kopya", + "page.duplicate.files": "Dosyaları kopyala", + "page.duplicate.pages": "Sayfaları kopyala", + "page.sort": "Pozisyon değiştir", + "page.status": "Durum", + "page.status.draft": "Taslak", + "page.status.draft.description": "Sayfa taslak halinde ve yalnızca oturum açmış editörler için veya gizli bağlantı üzerinden görülebilir", + "page.status.listed": "Herkese Açık", + "page.status.listed.description": "Bu sayfa herkese açık", + "page.status.unlisted": "Liste Dışı", + "page.status.unlisted.description": "Bu sayfa sadece bağlantı adresi ile erişilebilir", + + "pages": "Sayfalar", + "pages.empty": "Henüz sayfa yok", + "pages.status.draft": "Taslaklar", + "pages.status.listed": "Yayınlandı", + "pages.status.unlisted": "Liste Dışı", + + "pagination.page": "Sayfa", + + "password": "\u015eifre", + "pixel": "Piksel", + "prev": "Önceki", + "preview": "Önizle", + "remove": "Kaldır", + "rename": "Yeniden Adlandır", + "replace": "De\u011fi\u015ftir", + "retry": "Tekrar Dene", + "revert": "Vazge\u00e7", + "revert.confirm": "Gerçekten kaydedilmemiş tüm değişiklikleri silmek istiyor musunuz?", + + "role": "Rol", + "role.admin.description": "Yönetici tüm haklara sahiptir", + "role.admin.title": "Yönetici", + "role.all": "Tümü", + "role.empty": "Bu role ait kullanıcı bulunamadı", + "role.description.placeholder": "Açıklama yok", + "role.nobody.description": "Bu hiçbir izni olmayan bir geri dönüş rolüdür.", + "role.nobody.title": "Hiçkimse", + + "save": "Kaydet", + "search": "Arama", + "search.min": "Aramak için {min} karakter girin", + "search.all": "Tümünü göster", + "search.results.none": "Sonuç yok", + + "section.required": "Bölüm gereklidir", + + "select": "Seç", + "settings": "Ayarlar", + "show": "Göster", + "size": "Boyut", + "slug": "Web Adres Uzantısı", + "sort": "Sırala", + "title": "Başlık", + "template": "\u015eablon", + "today": "Bugün", + + "site.blueprint": "Sitenin henüz bir planı yok. Kurulumu /site/blueprints/site.yml'de tanımlayabilirsiniz.", + + "toolbar.button.code": "Kod", + "toolbar.button.bold": "Kalın Yazı", + "toolbar.button.email": "E-Posta", + "toolbar.button.headings": "Başlıklar", + "toolbar.button.heading.1": "Başlık 1", + "toolbar.button.heading.2": "Başlık 2", + "toolbar.button.heading.3": "Başlık 3", + "toolbar.button.italic": "Eğik Yazı", + "toolbar.button.file": "Dosya", + "toolbar.button.file.select": "Bir dosya seçin", + "toolbar.button.file.upload": "Bir dosya yükleyin", + "toolbar.button.link": "Ba\u011flant\u0131", + "toolbar.button.strike": "Üstü çizili", + "toolbar.button.ol": "Sıralı liste", + "toolbar.button.underline": "Altı çizili", + "toolbar.button.ul": "Madde listesi", + + "translation.author": "Kirby Takımı", + "translation.direction": "ltr", + "translation.name": "T\u00fcrk\u00e7e", + "translation.locale": "tr_TR", + + "upload": "Yükle", + "upload.error.cantMove": "Yüklenen dosya taşınamadı", + "upload.error.cantWrite": "Dosya diske yazılamadı", + "upload.error.default": "Dosya yüklenemedi", + "upload.error.extension": "Dosya yükleme uzantısı tarafından durduruldu", + "upload.error.formSize": "Yüklenen dosya, formda belirtilen MAX_FILE_SIZE yönergesini aşıyor", + "upload.error.iniPostSize": "Yüklenen dosya php.ini içindeki post_max_size yönergesini aşıyor", + "upload.error.iniSize": "Yüklenen dosya php.ini içindeki upload_max_filesize yönergesini aşıyor", + "upload.error.noFile": "Dosya yüklenmedi", + "upload.error.noFiles": "Dosyalar yüklenmedi", + "upload.error.partial": "Yüklenen dosya sadece kısmen yüklendi", + "upload.error.tmpDir": "Geçici klasör eksik", + "upload.errors": "Hata", + "upload.progress": "Yükleniyor...", + + "url": "Url", + "url.placeholder": "https://ornek.com", + + "user": "Kullanıcı", + "user.blueprint": "Bu kullanıcı rolü için /site/blueprints/users/{role}.yml içinde ek bölümler ve form alanları tanımlayabilirsiniz", + "user.changeEmail": "E-postayı değiştir", + "user.changeLanguage": "Dili değiştir", + "user.changeName": "Kullanıcıyı yeniden adlandır", + "user.changePassword": "Şifre değiştir", + "user.changePassword.new": "Yeni Şifre", + "user.changePassword.new.confirm": "Şifreyi onaylayın...", + "user.changeRole": "Rolü değiştir", + "user.changeRole.select": "Yeni bir rol seçin", + "user.create": "Yeni bir kullanıcı ekle", + "user.delete": "Bu kullanıcıyı sil", + "user.delete.confirm": "{email} kullanıcısını silmek istediğinizden emin misiniz?", + + "users": "Kullanıcılar", + + "version": "Versiyon", + + "view.account": "Hesap Bilgilerin", + "view.installation": "Kurulum", + "view.resetPassword": "Şifreyi sıfırla", + "view.settings": "Ayarlar", + "view.site": "Site", + "view.users": "Kullan\u0131c\u0131lar", + + "welcome": "Hoşgeldiniz", + "year": "Yıl", + "yes": "evet" +} diff --git a/kirby/kirby.pub b/kirby/kirby.pub new file mode 100644 index 0000000..ddf9130 --- /dev/null +++ b/kirby/kirby.pub @@ -0,0 +1,9 @@ +-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2Ux4q7LmQ5hfTYTtz3/a +mohFJMWo/iCnxVcY84PZjLwWnT+G2DTKGaEWydB77TteJQnmsgtvO5734oj3Ga3r +QCfwr2gxo/0WDEBq7C5HP+YNJiuZ/iD/tYV+gloF+Aaa3Mo8AK5DYH3dnjuyfHc1 +veIlYX1D2MXji2IRqdweAzVi1dfI4I3Ys8awhzv653vFLj5LvAtlwlYlmYeRwci7 +GkAOWw709CuKQNdPBXGFQQ/pEB5mnp8mI31j8og845u6v/Sk4+85gFORSufIRfnQ +GFYrPOeavxfAWQGjh7JQjr/sbKSXaJ3nDlrYsOPIrC0Rwn/jsQPO7OLdVwkc9ofL +GQIDAQAB +-----END PUBLIC KEY----- diff --git a/kirby/panel/dist/apple-touch-icon.png b/kirby/panel/dist/apple-touch-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..832510ed868e140c0a680449c2d38a921d850877 GIT binary patch literal 4115 zcmds)cQ9Q4yT`Q!ZHZW-cTuuxgy>~g zZy^b>tmuoVxhFHfx%dA5yZ5ghN(fohur6m9)zrFgnylkjA_^adduQD`R3c8Xp4LNy`#kR`$q6N-jo>U z{Frbt!|*T%ZZVCd*Y)AycQB1nkhzG$wN8j`I_y!N8A{D$QVJz zO2Lj6f$D}r^c3In(?=FjnzQ8L%x_E3=wQ~hEvT;hT8#I1nz^YlbCA;sO!^|^*wKvt zXT$54*Wq4XRthdd`oT5B&x}GL;2)>c_93OZv7Du~PQ7J~jg2cb^UoPixf3pbdqs$w zWBE;Z7qSw;q?vS8=hZjZ-q#BUp@BT9f<`JLE~lU)Z9%ccZ^LqOVWC3l>729TVSlPb z`8A{T5?VHK>onJ^3gm)_OdcMivtzGnb_T!?PN=p7gZkXcUlfe40Mx1y7lA`RS?6?Z+`BjZa!6h!OMpQ3>{cPrPf)ZY- zK#hXE8R8jqw3U{n;CjD*7KanAtgJ+wkkJOu&X|7Ko)G$3Zb}W!3#kBMIQ(dpWRBWnsZt;qN&xM^ioT$KA=e-Pb zD!(34Ctg}Bgc7BBl|>dQ)8*EiAOP|sT1c*=(P-z{y2psE1uRH9x8urVa~i*=-7uNH z7;`jP_(}*iFaQ~&3(ASXi=d#A2Oxzs%$)CWv znnw|FLRyQ4Mc=!k23_BwR^hMkN+&fee5#@WJt0&GS<=P%>Epv+vzB}G+;ZBvAs2zF zfqURyQ#5HwdhKbsN z?8nMGNO7kcz-_rN!5laqKt)MFTCE&R+h+{Qv<#_WGIt($wcQIo zKbW3bRp4RkaY^Al?W7y}B$FNuS!?i^9$3Q{REm2~l$h7K45O`At5;OyU~!W$cnxJ2 zw}=}im;}{-9)1 zXa2dHU-?|clA1DGmg5^YKU@c8&ys@^LXnc`1=J>p_jtC~l)V<=;B9;c<&}8_EeHB3 zjIJEy53nV~a=h~Wm^h!R!3^2NiyRNdy6@^YXJ&Gzy71U_W)=W6vkG8%VF0Glh9ZWqi4C$0ZZPH&Q+oBpbY|YlHj8j}mykW$}pzYgJ z-!Bg5y^P-M9oC)9z7r!+e zhR%JSLpR%v50zIfCD1h=pBG?czV^@W0AJsdLZb|ES020P-!BzzgDi9<30AlG+^)FF z;FL}rEKEWnRwET=l&r+GGFIB9;M2`Av6ml9t|a3vr7u+IXjh*_K&BnWyfgEx0&X+1 zSytIT@yiU=p?&iObBWlj@}jg87w!=bnSM5~1KY|C+LCWiHd=DXuSitRROEMD&CbU$ z7M1+Am~VVKPp9F~qHIde$r|&#K=ViK!3l2XQD3v)M#{LC{wi>`jcWYs2i=swiom(8 zq-FQsuQkViXm^qd^dlv!K0+7-)jzIf!M&6e6>qEzt%+%4z&0fw)oqsN7pL2X)fttf zw@R^x&K@6XYt7q!|A;mCZk6c*~P9~j77#|L#3VZSGCVF|f+85kJa zGK^tCMYwC?)FR8NVB6WoXWt=y{ns+yTwlL<(za!Qz`C6*=~N$VwvTHE<>6k%bYb*7 zJUsTc1*V0MwwIR73jA9(HZ~@^n6xV9=I1AWe2I-vYp)iF9Jrqk_z?*$7Gr^|H7U$+ zHACK@8APXXjmM6a&wg-~L8z=P$|e(aMkUhQ)HsCkgZ-s8S`{*cGQ$M8(at@#&Ke-udb?~> zlGRy!jXclET?Kp-C-eE{W`)o8!P%$F6`zL0K!;9@a$GivLs#BXOc+rN;xIau-%Qar z4V1hVb1Z)@U!wX%9S4U}|E<^IEWFwWbmn_Q3Zv2Off#QPkw}Ct&FV7>s&fz6s*JKp zeWHRJgCgXuBLekV-r#Onx8V{N{O6zc7<9g0Z;2iNWFhW4l4DR#*kW;_p{$XMi92_= z(sLD-1S!s&uCNg;&`WGYzc(*{(GC!#np3@f6EDYV0&9TMx$YCz9~)cS;uomyCpaIl7O9cZX=B!aH<|>pXXs8 z`2X6e#7*(b1nUf0drwq?-@uz2w{o1MuL+GXIngVLEVnAmbR(-Y4Kd>}y~PfIB{iL} z8!x3;lHMNwC4wghLs}wMD{%|ALCdMyHPPqe6D5%*@e2(e8Ee;K;!_!KkiymLeAn-& z1|7ohYxpGgF{p}IrS{Dv)<)bC1X!b(4eLJn50AX#%QULxp7_**jp^ch3j5JHN!jsz z+>$*x#eHeG%$d8zCjEo59a6|)Ey){O%^NbaO$_ ze{{TTLDcU7x*Lt0TqdZ1Gaou8RuX^N+P1J0J_LQ zulpun(o5h^))Neb!rLwKtO7aO>MHm(ko6^zxli+@>th;kg8CsyC0iUfCEZJ^D;F z`_bD8Q?*G=%&Us;>${{XZ^)L1E1VSz`WHgyb5$xEMqMEsAR`dpK9TE`r@XmklO4PH z@0Rs1DCaHgk$kG76H(vT_`1DA7ZcnZ_7}I|c4@##xy%4)?w>nav{display:flex;direction:ltr}.k-calendar-input>nav .k-button{padding:.5rem}.k-calendar-selects{flex-grow:1;display:flex;align-items:center;justify-content:center}[dir=ltr] .k-calendar-selects{direction:ltr}[dir=rtl] .k-calendar-selects{direction:rtl}.k-calendar-selects .k-select-input{padding:0 .5rem;font-weight:400;font-size:.875rem}.k-calendar-selects .k-select-input:focus-within{color:#7e9abf!important}.k-calendar-input th{padding:.5rem 0;color:#999;font-size:.75rem;font-weight:400;text-align:center}.k-calendar-day .k-button{width:2rem;height:2rem;margin:0 auto;color:#fff;line-height:1.75rem;display:flex;justify-content:center;border-radius:50%;border:2px solid transparent}.k-calendar-day .k-button .k-button-text{opacity:1}.k-calendar-table .k-button:hover{color:#fff}.k-calendar-day:hover .k-button:not([data-disabled]){border-color:hsla(0,0%,100%,.25)}.k-calendar-day[aria-current=date] .k-button{color:#cca000;font-weight:500}.k-calendar-day[aria-selected=date] .k-button{border-color:#7e9abf;color:#7e9abf}.k-calendar-day[data-between]{background:#333}.k-calendar-day[data-first]{border-top-left-radius:100%;border-bottom-left-radius:100%}.k-calendar-day[data-last]{border-top-right-radius:100%;border-bottom-right-radius:100%}.k-calendar-today{text-align:center;padding-top:.5rem}.k-calendar-today .k-button{color:#7e9abf;font-size:.75rem;padding:1rem}.k-calendar-today .k-button-text{opacity:1}.k-counter{font-size:.75rem;color:#111;font-weight:600}.k-counter[data-invalid]{box-shadow:none;border:0;color:#c82829}.k-counter-rules{color:#777;font-weight:400}[dir=ltr] .k-counter-rules{padding-left:.5rem}[dir=rtl] .k-counter-rules{padding-right:.5rem}.k-form-submitter{display:none}.k-form-buttons[data-theme=changes]{background:#de935f}.k-form-buttons[data-theme=lock]{background:#d16464}.k-form-buttons[data-theme=unlock]{background:#7e9abf}.k-form-buttons .k-view{display:flex;justify-content:space-between;align-items:center}.k-form-button.k-button{font-weight:500;white-space:nowrap;line-height:1;height:2.5rem;display:flex;padding:0 1rem;align-items:center}.k-form-button:first-child{margin-left:-1rem}.k-form-button:last-child{margin-right:-1rem}.k-form-lock-info{display:flex;font-size:.875rem;align-items:center;line-height:1.5em;padding:.625rem 0;margin-right:3rem}.k-form-lock-info>.k-icon{margin-right:.5rem}.k-form-lock-buttons{display:flex;flex-shrink:0}.k-form-lock-loader{animation:Spin 4s linear infinite}.k-form-lock-loader .k-icon-loader{display:flex}.k-form-indicator-icon{color:#de935f}.k-form-indicator-info{font-size:.875rem;font-weight:600;padding:.75rem 1rem .25rem;line-height:1.25em;width:15rem}.k-field-label{font-weight:600;display:block;padding:0 0 .75rem;flex-grow:1;line-height:1.25rem}.k-field-label abbr{text-decoration:none;color:#999;padding-left:.25rem}.k-field-header{position:relative;display:flex;align-items:baseline}.k-field-options{position:absolute;top:calc(-.5rem - 1px)}[dir=ltr] .k-field-options{right:0}[dir=rtl] .k-field-options{left:0}.k-field-options.k-button-group .k-dropdown{height:auto}.k-field-options.k-button-group .k-field-options-button.k-button{padding:.75rem;display:flex}.k-field[data-disabled]{cursor:not-allowed}.k-field[data-disabled] *{pointer-events:none}.k-field[data-disabled] .k-text[data-theme=help] *{pointer-events:auto}.k-field:focus-within>.k-field-header>.k-field-counter{display:block}.k-field-help{padding-top:.5rem}.k-fieldset{border:0}.k-fieldset .k-grid{grid-row-gap:2.25rem}@media screen and (min-width:30em){.k-fieldset .k-grid{grid-column-gap:1.5rem}}.k-sections>.k-column[data-width="1/3"] .k-fieldset .k-grid,.k-sections>.k-column[data-width="1/4"] .k-fieldset .k-grid{grid-template-columns:repeat(1,1fr)}.k-sections>.k-column[data-width="1/3"] .k-fieldset .k-grid .k-column,.k-sections>.k-column[data-width="1/4"] .k-fieldset .k-grid .k-column{grid-column-start:auto}.k-input{display:flex;align-items:center;line-height:1;border:0;outline:0;background:none}.k-input-element{flex-grow:1}.k-input-icon{display:flex;justify-content:center;align-items:center;line-height:0}.k-input[data-disabled]{pointer-events:none}[data-disabled] .k-input-icon{color:#777}.k-input[data-theme=field]{line-height:1;border:1px solid #ccc;background:#fff}.k-input[data-theme=field]:focus-within{border:1px solid #4271ae;box-shadow:0 0 0 2px rgba(66,113,174,.25)}.k-input[data-theme=field][data-disabled]{background:#efefef}.k-input[data-theme=field] .k-input-icon{width:2.25rem}.k-input[data-theme=field] .k-input-after,.k-input[data-theme=field] .k-input-before,.k-input[data-theme=field] .k-input-icon{align-self:stretch;display:flex;align-items:center;flex-shrink:0}.k-input[data-theme=field] .k-input-after,.k-input[data-theme=field] .k-input-before{padding:0 .5rem}.k-input[data-theme=field] .k-input-before{color:#555;padding-right:0}.k-input[data-theme=field] .k-input-after{color:#555;padding-left:0}.k-input[data-theme=field] .k-input-icon>.k-dropdown{width:100%;height:100%}.k-input[data-theme=field] .k-input-icon-button{width:100%;height:100%;display:flex;align-items:center;justify-content:center;flex-shrink:0}.k-input[data-theme=field] .k-number-input,.k-input[data-theme=field] .k-select-input,.k-input[data-theme=field] .k-text-input{padding:.5rem;line-height:1.25rem}.k-input[data-theme=field] .k-date-input .k-select-input,.k-input[data-theme=field] .k-time-input .k-select-input{padding-left:0;padding-right:0}[dir=ltr] .k-input[data-theme=field] .k-date-input .k-select-input:first-child,[dir=ltr] .k-input[data-theme=field] .k-time-input .k-select-input:first-child{padding-left:.5rem}[dir=rtl] .k-input[data-theme=field] .k-date-input .k-select-input:first-child,[dir=rtl] .k-input[data-theme=field] .k-time-input .k-select-input:first-child{padding-right:.5rem}.k-input[data-theme=field] .k-date-input .k-select-input:focus-within,.k-input[data-theme=field] .k-time-input .k-select-input:focus-within{color:#4271ae;font-weight:600}.k-input[data-theme=field] .k-time-input .k-time-input-meridiem{padding-left:.5rem}.k-input[data-theme=field][data-type=checkboxes] .k-checkboxes-input li,.k-input[data-theme=field][data-type=checkboxes] .k-radio-input li,.k-input[data-theme=field][data-type=radio] .k-checkboxes-input li,.k-input[data-theme=field][data-type=radio] .k-radio-input li{min-width:0;overflow-wrap:break-word}.k-input[data-theme=field][data-type=checkboxes] .k-input-before{border-right:1px solid #efefef}.k-input[data-theme=field][data-type=checkboxes] .k-input-element+.k-input-after,.k-input[data-theme=field][data-type=checkboxes] .k-input-element+.k-input-icon{border-left:1px solid #efefef}.k-input[data-theme=field][data-type=checkboxes] .k-input-element{overflow:hidden}.k-input[data-theme=field][data-type=checkboxes] .k-checkboxes-input{display:grid;grid-template-columns:1fr;margin-bottom:-1px;margin-right:-1px}@media screen and (min-width:65em){.k-input[data-theme=field][data-type=checkboxes] .k-checkboxes-input{grid-template-columns:repeat(var(--columns),1fr)}}.k-input[data-theme=field][data-type=checkboxes] .k-checkboxes-input li{border-right:1px solid #efefef;border-bottom:1px solid #efefef}.k-input[data-theme=field][data-type=checkboxes] .k-checkboxes-input label{display:block;line-height:1.25rem;padding:.5rem .5rem}.k-input[data-theme=field][data-type=checkboxes] .k-checkbox-input-icon{top:.625rem;left:.5rem;margin-top:0}.k-input[data-theme=field][data-type=radio] .k-input-before{border-right:1px solid #efefef}.k-input[data-theme=field][data-type=radio] .k-input-element+.k-input-after,.k-input[data-theme=field][data-type=radio] .k-input-element+.k-input-icon{border-left:1px solid #efefef}.k-input[data-theme=field][data-type=radio] .k-input-element{overflow:hidden}.k-input[data-theme=field][data-type=radio] .k-radio-input{display:grid;grid-template-columns:1fr;margin-bottom:-1px;margin-right:-1px}@media screen and (min-width:65em){.k-input[data-theme=field][data-type=radio] .k-radio-input{grid-template-columns:repeat(var(--columns),1fr)}}.k-input[data-theme=field][data-type=radio] .k-radio-input li{border-right:1px solid #efefef;border-bottom:1px solid #efefef}.k-input[data-theme=field][data-type=radio] .k-radio-input label{display:block;flex-grow:1;min-height:2.25rem;line-height:1.25rem;padding:.5rem .5rem}.k-input[data-theme=field][data-type=radio] .k-radio-input label:before{top:.625rem;left:.5rem;margin-top:-1px}.k-input[data-theme=field][data-type=radio] .k-radio-input .k-radio-input-info{display:block;font-size:.875rem;color:#777;line-height:1.25rem;padding-top:.125rem}.k-input[data-theme=field][data-type=radio] .k-radio-input .k-icon{width:2.25rem;height:2.25rem;display:flex;align-items:center;justify-content:center}.k-input[data-theme=field][data-type=range] .k-range-input{padding:.5rem}.k-input[data-theme=field][data-type=select]{position:relative}.k-input[data-theme=field][data-type=select] .k-input-icon{position:absolute;top:0;bottom:0}[dir=ltr] .k-input[data-theme=field][data-type=select] .k-input-icon{right:0}[dir=rtl] .k-input[data-theme=field][data-type=select] .k-input-icon{left:0}.k-input[data-theme=field][data-type=tags] .k-tags-input{padding:.25rem .25rem 0 .25rem}.k-input[data-theme=field][data-type=tags] .k-tag{margin-right:.25rem;margin-bottom:.25rem;height:1.75rem;font-size:.875rem}.k-input[data-theme=field][data-type=tags] .k-tags-input input{font-size:.875rem;padding:0 .25rem;height:1.75rem;line-height:1;margin-bottom:.25rem}.k-input[data-theme=field][data-type=tags] .k-tags-input .k-dropdown-content{top:calc(100% + .5rem + 2px)}.k-input[data-theme=field][data-type=tags] .k-tags-input .k-dropdown-content[data-dropup]{top:calc(100% + .5rem + 2px);bottom:auto;margin-bottom:0}.k-input[data-theme=field][data-type=multiselect]{position:relative}.k-input[data-theme=field][data-type=multiselect] .k-multiselect-input{padding:.25rem 2rem 0 .25rem;min-height:2.25rem}.k-input[data-theme=field][data-type=multiselect] .k-tag{margin-right:.25rem;margin-bottom:.25rem;height:1.75rem;font-size:.875rem}.k-input[data-theme=field][data-type=multiselect] .k-input-icon{position:absolute;top:0;right:0;bottom:0;pointer-events:none}.k-input[data-theme=field][data-type=textarea] .k-textarea-input-native{padding:.25rem .5rem;line-height:1.5rem}.k-input[data-theme=field][data-type=toggle] .k-input-before{padding-right:.25rem}.k-input[data-theme=field][data-type=toggle] .k-toggle-input{padding-left:.5rem}.k-input[data-theme=field][data-type=toggle] .k-toggle-input-label{padding:0 .5rem 0 .75rem;line-height:2.25rem}.k-login-code-form .k-user-info{height:38px;margin-bottom:2.25rem;padding:.5rem;background:#fff;border-radius:1px;box-shadow:0 1px 3px 0 rgba(0,0,0,.1),0 1px 2px 0 rgba(0,0,0,.06)}.k-upload input{position:absolute;top:0}[dir=ltr] .k-upload input{left:-3000px}[dir=rtl] .k-upload input{right:-3000px}.k-upload .k-headline{margin-bottom:.75rem}.k-upload-error-list,.k-upload-list{line-height:1.5em;font-size:.875rem}.k-upload-list-filename{color:#777}.k-upload-error-list li{padding:.75rem;background:#fff;border-radius:1px}.k-upload-error-list li:not(:last-child){margin-bottom:2px}.k-upload-error-filename{color:#c82829;font-weight:600}.k-upload-error-message{color:#777}.k-writer-toolbar{position:absolute;display:flex;background:#000;height:30px;transform:translateX(-50%) translateY(-.75rem);z-index:801;box-shadow:0 1px 3px 0 rgba(0,0,0,.1),0 1px 2px 0 rgba(0,0,0,.06);color:#fff;border-radius:.25rem}.k-writer-toolbar-button.k-button{display:flex;align-items:center;justify-content:center;height:30px;width:30px;font-size:.875rem!important;color:currentColor;line-height:1}.k-writer-toolbar-button.k-button:hover{background:hsla(0,0%,100%,.15)}.k-writer-toolbar-button.k-writer-toolbar-button-active{color:#b1c2d8}.k-writer-toolbar-button.k-writer-toolbar-nodes{width:auto;padding:0 .75rem}.k-writer-toolbar .k-dropdown+.k-writer-toolbar-button{border-left:1px solid #555}.k-writer-toolbar-button.k-writer-toolbar-nodes:after{content:"";margin-left:.5rem;border-top:4px solid #fff;border-left:4px solid transparent;border-right:4px solid transparent}.k-writer-toolbar .k-dropdown-content{color:#000;background:#fff;margin-top:.5rem}.k-writer{position:relative;width:100%}.k-writer .ProseMirror{overflow-wrap:break-word;word-wrap:break-word;word-break:break-word;white-space:pre-wrap;font-variant-ligatures:none;line-height:inherit}.k-writer .ProseMirror:focus{outline:0}.k-writer .ProseMirror *{caret-color:currentColor}.k-writer .ProseMirror a{color:#4271ae;text-decoration:underline}.k-writer .ProseMirror>:last-child{margin-bottom:0}.k-writer .ProseMirror h1,.k-writer .ProseMirror h2,.k-writer .ProseMirror h3,.k-writer .ProseMirror ol,.k-writer .ProseMirror p,.k-writer .ProseMirror ul{margin-bottom:.75rem}.k-writer .ProseMirror h1{font-size:1.875rem;line-height:1.25em}.k-writer .ProseMirror h2{font-size:1.5rem;line-height:1.25em}.k-writer .ProseMirror h3{font-size:1.25rem;line-height:1.25em}.k-writer .ProseMirror h1 strong,.k-writer .ProseMirror h2 strong,.k-writer .ProseMirror h3 strong{font-weight:700}.k-writer .ProseMirror strong{font-weight:600}.k-writer .ProseMirror code{position:relative;font-size:.925em;display:inline-block;line-height:1.325;padding:.05em .325em;background:#ddd;border-radius:.25rem;font-family:SFMono-Regular,Consolas,Liberation Mono,Menlo,Courier,monospace}.k-writer .ProseMirror ol,.k-writer .ProseMirror ul{padding-left:1rem}.k-writer .ProseMirror ul>li{list-style:disc}.k-writer .ProseMirror ul ul>li{list-style:circle}.k-writer .ProseMirror ul ul ul>li{list-style:square}.k-writer .ProseMirror ol>li{list-style:decimal}.k-writer .ProseMirror li>ol,.k-writer .ProseMirror li>p,.k-writer .ProseMirror li>ul{margin:0}.k-writer-code pre{-moz-tab-size:2;-o-tab-size:2;tab-size:2;font-size:.875rem;line-height:2em;overflow-x:auto;overflow-y:hidden;white-space:pre}.k-writer-code code{font-family:SFMono-Regular,Consolas,Liberation Mono,Menlo,Courier,monospace}.k-writer[data-placeholder][data-empty]:before{content:attr(data-placeholder);position:absolute;line-height:inherit;color:#999;pointer-events:none}.k-checkbox-input{position:relative;cursor:pointer}.k-checkbox-input-native{position:absolute;-webkit-appearance:none;-moz-appearance:none;appearance:none;width:0;height:0;opacity:0}.k-checkbox-input-label{display:block;padding-left:1.75rem}.k-checkbox-input-icon{position:absolute;left:0;width:1rem;height:1rem;border:2px solid #999}.k-checkbox-input-icon svg{position:absolute;width:12px;height:12px;display:none}.k-checkbox-input-icon path{stroke:#fff}.k-checkbox-input-native:checked+.k-checkbox-input-icon{border-color:#111;background:#111}[data-disabled] .k-checkbox-input-native:checked+.k-checkbox-input-icon{border-color:#777;background:#777}.k-checkbox-input-native:checked+.k-checkbox-input-icon svg{display:block}.k-checkbox-input-native:focus+.k-checkbox-input-icon{border-color:#4271ae}.k-checkbox-input-native:focus:checked+.k-checkbox-input-icon{background:#4271ae}.k-datetime-input{display:flex}.k-datetime-input .k-time-input{padding-left:.5rem}.k-text-input{width:100%;border:0;background:none;font:inherit;color:inherit}.k-text-input::-moz-placeholder{color:#999}.k-text-input::placeholder{color:#999}.k-text-input:focus{outline:0}.k-text-input:invalid{box-shadow:none;outline:0}.k-list-input .ProseMirror{line-height:1.5em}.k-list-input .ProseMirror ol>li::marker{font-size:.875rem;color:#999}.k-multiselect-input{display:flex;flex-wrap:wrap;position:relative;font-size:.875rem;min-height:2.25rem;line-height:1}.k-multiselect-input .k-sortable-ghost{background:#4271ae}.k-multiselect-input .k-dropdown-content{width:100%}.k-multiselect-search{margin-top:0!important;color:#fff;background:#111;border-bottom:1px dashed hsla(0,0%,100%,.2)}.k-multiselect-search>.k-button-text{flex:1;opacity:1!important}.k-multiselect-search input{width:100%;color:#fff;background:none;border:none;outline:none;padding:.25rem 0;font:inherit}.k-multiselect-options{position:relative;max-height:275px;overflow-y:auto;padding:.5rem 0}.k-multiselect-option{position:relative}.k-multiselect-option.selected{color:#a7bd68}.k-multiselect-option.disabled:not(.selected) .k-icon{opacity:0}.k-multiselect-option b{color:#7e9abf;font-weight:700}.k-multiselect-value{color:#999;margin-left:.25rem}.k-multiselect-value:before{content:" ("}.k-multiselect-value:after{content:")"}.k-multiselect-input[data-layout=list] .k-tag{width:100%;margin-right:0!important}.k-multiselect-more{width:100%;padding:.75rem;color:hsla(0,0%,100%,.8);text-align:center;border-top:1px dashed hsla(0,0%,100%,.2)}.k-multiselect-more:hover{color:#fff}.k-number-input{width:100%;border:0;background:none;font:inherit;color:inherit}.k-number-input::-moz-placeholder{color:$color-light-grey}.k-number-input::placeholder{color:$color-light-grey}.k-number-input:focus{outline:0}.k-number-input:invalid{box-shadow:none;outline:0}.k-radio-input li{position:relative;line-height:1.5rem;padding-left:1.75rem}.k-radio-input input{position:absolute;width:0;height:0;-webkit-appearance:none;-moz-appearance:none;appearance:none;opacity:0}.k-radio-input label{cursor:pointer;align-items:center}.k-radio-input label:before{position:absolute;top:.175em;left:0;content:"";width:1rem;height:1rem;border-radius:50%;border:2px solid #999;box-shadow:inset 0 0 0 2px #fff}.k-radio-input input:checked+label:before{border-color:#111;background:#111}[data-disabled] .k-radio-input input:checked+label:before{border-color:#777;background:#777}.k-radio-input input:focus+label:before{border-color:#4271ae}.k-radio-input input:focus:checked+label:before{background:#4271ae}.k-radio-input-text{display:block}.k-range-input{display:flex;align-items:center}.k-range-input-native{--min:0;--max:100;--value:0;--range:calc(var(--max) - var(--min));--ratio:calc((var(--value) - var(--min))/var(--range));--position:calc(8px + var(--ratio)*(100% - 16px));-webkit-appearance:none;-moz-appearance:none;appearance:none;width:100%;height:16px;background:transparent;font-size:.875rem;line-height:1}.k-range-input-native::-webkit-slider-thumb{-webkit-appearance:none;appearance:none}.k-range-input-native::-webkit-slider-runnable-track{border:none;border-radius:4px;width:100%;height:4px;background:#ccc;background:linear-gradient(#111,#111) 0/var(--position) 100% no-repeat #ccc}.k-range-input-native::-moz-range-track{border:none;border-radius:4px;width:100%;height:4px;background:#ccc}.k-range-input-native::-ms-track{border:none;border-radius:4px;width:100%;height:4px;background:#ccc}.k-range-input-native::-moz-range-progress{height:4px;background:#111}.k-range-input-native::-ms-fill-lower{height:4px;background:#111}.k-range-input-native::-webkit-slider-thumb{margin-top:-6px;box-sizing:border-box;width:16px;height:16px;background:#efefef;border:4px solid #111;border-radius:50%;cursor:pointer}.k-range-input-native::-moz-range-thumb{box-sizing:border-box;width:16px;height:16px;background:#efefef;border:4px solid #111;border-radius:50%;cursor:pointer}.k-range-input-native::-ms-thumb{margin-top:0;box-sizing:border-box;width:16px;height:16px;background:#efefef;border:4px solid #111;border-radius:50%;cursor:pointer}.k-range-input-native::-ms-tooltip{display:none}.k-range-input-native:focus{outline:none}.k-range-input-native:focus::-webkit-slider-runnable-track{border:none;border-radius:4px;width:100%;height:4px;background:#ccc;background:linear-gradient(#4271ae,#4271ae) 0/var(--position) 100% no-repeat #ccc}.k-range-input-native:focus::-moz-range-progress{height:4px;background:#4271ae}.k-range-input-native:focus::-ms-fill-lower{height:4px;background:#4271ae}.k-range-input-native:focus::-webkit-slider-thumb{background:#efefef;border:4px solid #4271ae}.k-range-input-native:focus::-moz-range-thumb{background:#efefef;border:4px solid #4271ae}.k-range-input-native:focus::-ms-thumb{background:#efefef;border:4px solid #4271ae}.k-range-input-tooltip{position:relative;max-width:20%;display:flex;align-items:center;color:#fff;font-size:.75rem;line-height:1;text-align:center;border-radius:1px;background:#111;margin-left:1rem;padding:0 .25rem;white-space:nowrap}.k-range-input-tooltip:after{position:absolute;top:50%;left:-5px;width:0;height:0;transform:translateY(-50%);border-top:5px solid transparent;border-right:5px solid #111;border-bottom:5px solid transparent;content:""}.k-range-input-tooltip>*{padding:4px}[data-disabled] .k-range-input-native::-webkit-slider-runnable-track{background:linear-gradient(#777,#777) 0/var(--position) 100% no-repeat #ccc}[data-disabled] .k-range-input-native::-moz-range-progress{height:4px;background:#777}[data-disabled] .k-range-input-native::-ms-fill-lower{height:4px;background:#777}[data-disabled] .k-range-input-native::-webkit-slider-thumb{border:4px solid #777}[data-disabled] .k-range-input-native::-moz-range-thumb{border:4px solid #777}[data-disabled] .k-range-input-native::-ms-thumb{border:4px solid #777}[data-disabled] .k-range-input-tooltip{background:#777}[data-disabled] .k-range-input-tooltip:after{border-right:5px solid #777}.k-select-input{position:relative;display:block;cursor:pointer;overflow:hidden}.k-select-input-native{position:absolute;top:0;right:0;bottom:0;left:0;opacity:0;width:100%;font:inherit;z-index:1;cursor:pointer;-webkit-appearance:none;-moz-appearance:none;appearance:none}.k-select-input-native[disabled]{cursor:default}.k-select-input-native{font-weight:400}.k-tags-input{display:flex;flex-wrap:wrap}.k-tags-input .k-sortable-ghost{background:#4271ae}.k-tags-input-element{flex-grow:1;flex-basis:0;min-width:0}.k-tags-input:focus-within .k-tags-input-element{flex-basis:4rem}.k-tags-input-element input{font:inherit;border:0;width:100%;background:none}.k-tags-input-element input:focus{outline:0}.k-tags-input[data-layout=list] .k-tag{width:100%;margin-right:0!important}.k-textarea-input-wrapper{position:relative}.k-textarea-input-native{resize:none;border:0;width:100%;background:none;font:inherit;line-height:1.5em;color:inherit}.k-textarea-input-native::-moz-placeholder{color:#999}.k-textarea-input-native::placeholder{color:#999}.k-textarea-input-native:focus{outline:0}.k-textarea-input-native:invalid{box-shadow:none;outline:0}.k-textarea-input-native[data-size=small]{min-height:7.5rem}.k-textarea-input-native[data-size=medium]{min-height:15rem}.k-textarea-input-native[data-size=large]{min-height:30rem}.k-textarea-input-native[data-size=huge]{min-height:45rem}.k-textarea-input-native[data-font=monospace]{font-family:SFMono-Regular,Consolas,Liberation Mono,Menlo,Courier,monospace}.k-toolbar{margin-bottom:.25rem;color:#aaa}.k-textarea-input:focus-within .k-toolbar{position:sticky;top:0;right:0;left:0;z-index:1;box-shadow:0 2px 5px rgba(0,0,0,.05);border-bottom:1px solid rgba(0,0,0,.1);color:#000}.k-toggle-input{display:flex;align-items:center}.k-toggle-input-native{position:relative;height:16px;width:32px;border-radius:16px;border:2px solid #999;box-shadow:inset 0 0 0 2px #fff,inset -16px 0 0 2px #fff;background-color:#999;outline:0;transition:all .1s ease-in-out;-webkit-appearance:none;-moz-appearance:none;appearance:none;cursor:pointer;flex-shrink:0}.k-toggle-input-native:checked{border-color:#111;box-shadow:inset 0 0 0 2px #fff,inset 16px 0 0 2px #fff;background-color:#111}.k-toggle-input-native[disabled]{border-color:#ccc;box-shadow:inset 0 0 0 2px #efefef,inset -16px 0 0 2px #efefef;background-color:#ccc}.k-toggle-input-native[disabled]:checked{box-shadow:inset 0 0 0 2px #efefef,inset 16px 0 0 2px #efefef}.k-toggle-input-native:focus:checked{border:2px solid #4271ae;background-color:#4271ae}.k-toggle-input-native::-ms-check{opacity:0}.k-toggle-input-label{cursor:pointer;flex-grow:1}.k-blocks-field{position:relative}.k-files-field[data-disabled] *{pointer-events:all!important}body{counter-reset:headline-counter}.k-headline-field{position:relative;padding-top:1.5rem}.k-fieldset>.k-grid .k-column:first-child .k-headline-field{padding-top:0}.k-headline-field .k-headline[data-numbered]:before{counter-increment:headline-counter;content:counter(headline-counter,decimal-leading-zero);color:#4271ae;font-weight:400;padding-right:.25rem}.k-info-field .k-headline{padding-bottom:.75rem;line-height:1.25rem}.k-layout-column{position:relative;height:100%;display:flex;flex-direction:column;background:#fff;min-height:6rem}.k-layout-column:focus{outline:0}.k-layout-column .k-blocks{background:none;box-shadow:none;padding:0;height:100%;background:#fff}.k-layout-column .k-blocks-list{display:flex;flex-direction:column;height:100%}.k-layout-column .k-blocks .k-block-container:last-of-type{flex-grow:1}.k-layout-column .k-blocks .k-empty{position:absolute;top:0;left:0;bottom:0;right:0;display:flex;align-items:center;justify-content:center;border:0;opacity:0;transition:opacity .3s}.k-layout-column .k-blocks .k-empty:hover{opacity:1}.k-layout-column .k-blocks .k-empty .k-icon{border-right:0}.k-layout{position:relative;padding-right:2rem;background:#fff;box-shadow:0 1px 3px 0 rgba(0,0,0,.1),0 1px 2px 0 rgba(0,0,0,.06)}[data-disabled] .k-layout{padding-right:0}.k-layout:not(:last-of-type){margin-bottom:1px}.k-layout:focus{outline:0}.k-layout-toolbar{position:absolute;right:0;top:0;bottom:0;width:2rem;display:flex;flex-direction:column;font-size:.875rem;background:#f7f7f7;border-left:1px solid #efefef;color:#999}.k-layout-toolbar:hover{color:#000}.k-layout-toolbar-button{width:2rem;height:2rem}.k-layout-toolbar .k-sort-handle{margin-top:auto;color:currentColor}.k-layout-columns.k-grid{grid-gap:1px;background:#ddd}.k-layout:not(:first-child) .k-layout-columns.k-grid{border-top:0}.k-layouts .k-sortable-ghost{position:relative;box-shadow:0 5px 10px rgba(17,17,17,.25);outline:2px solid #4271ae;cursor:grabbing;cursor:-webkit-grabbing;z-index:1}.k-layout-selector.k-dialog{background:#313740;color:#fff}.k-layout-selector .k-headline{margin-bottom:1.5rem;line-height:1;margin-top:-.25rem}.k-layout-selector ul{display:grid;grid-template-columns:repeat(3,1fr);grid-gap:1.5rem}.k-layout-selector-option .k-grid{height:5rem;grid-gap:2px;box-shadow:0 1px 3px 0 rgba(0,0,0,.1),0 1px 2px 0 rgba(0,0,0,.06);cursor:pointer}.k-layout-selector-option:hover{outline:2px solid #c6d49d;outline-offset:2px}.k-layout-selector-option:last-child{margin-bottom:0}.k-layout-selector-option .k-column{display:flex;background:hsla(0,0%,100%,.2);justify-content:center;font-size:.75rem;align-items:center}.k-layout-add-button{display:flex;align-items:center;width:100%;color:#999;justify-content:center;padding:.75rem 0}.k-layout-add-button:hover{color:#000}.k-line-field{position:relative;border:0;height:3rem;width:auto}.k-line-field:after{position:absolute;content:"";top:50%;margin-top:-1px;left:0;right:0;height:1px;background:#ccc}.k-list-field .k-list-input{padding:.375rem .5rem .375rem .75rem}.k-pages-field[data-disabled] *{pointer-events:all!important}.k-structure-table{position:relative;table-layout:fixed;width:100%;background:#fff;font-size:.875rem;border-spacing:0;box-shadow:0 1px 3px 0 rgba(0,0,0,.1),0 1px 2px 0 rgba(0,0,0,.06)}.k-structure-table td,.k-structure-table th{border-bottom:1px solid #efefef;line-height:1.25em;overflow:hidden;text-overflow:ellipsis}[dir=ltr] .k-structure-table td,[dir=ltr] .k-structure-table th{border-right:1px solid #efefef}[dir=rtl] .k-structure-table td,[dir=rtl] .k-structure-table th{border-left:1px solid #efefef}.k-structure-table td:last-child{overflow:visible}.k-structure-table th{position:sticky;top:0;right:0;left:0;width:100%;background:#fff;font-weight:400;z-index:1;color:#777;padding:0 .75rem;height:38px}[dir=ltr] .k-structure-table th{text-align:left}[dir=rtl] .k-structure-table th{text-align:right}.k-structure-table td:last-child,.k-structure-table th:last-child{width:38px}[dir=ltr] .k-structure-table td:last-child,[dir=ltr] .k-structure-table th:last-child{border-right:0}[dir=rtl] .k-structure-table td:last-child,[dir=rtl] .k-structure-table th:last-child{border-left:0}.k-structure-table tr:last-child td{border-bottom:0}.k-structure-table tbody tr:hover td{background:hsla(0,0%,93.7%,.25)}@media screen and (max-width:65em){.k-structure-table td,.k-structure-table th{display:none}.k-structure-table td:first-child,.k-structure-table td:last-child,.k-structure-table td:nth-child(2),.k-structure-table th:first-child,.k-structure-table th:last-child,.k-structure-table th:nth-child(2){display:table-cell}}.k-structure-table .k-structure-table-column[data-align=center]{text-align:center}[dir=ltr] .k-structure-table .k-structure-table-column[data-align=right]{text-align:right}[dir=rtl] .k-structure-table .k-structure-table-column[data-align=right]{text-align:left}.k-structure-table .k-structure-table-column[data-align=right]>.k-input{flex-direction:column;align-items:flex-end}.k-structure-table .k-structure-table-column[data-width="1/2"]{width:50%}.k-structure-table .k-structure-table-column[data-width="1/3"]{width:33.33%}.k-structure-table .k-structure-table-column[data-width="1/4"]{width:25%}.k-structure-table .k-structure-table-column[data-width="1/5"]{width:20%}.k-structure-table .k-structure-table-column[data-width="1/6"]{width:16.66%}.k-structure-table .k-structure-table-column[data-width="1/8"]{width:12.5%}.k-structure-table .k-structure-table-column[data-width="1/9"]{width:11.11%}.k-structure-table .k-structure-table-column[data-width="2/3"]{width:66.66%}.k-structure-table .k-structure-table-column[data-width="3/4"]{width:75%}.k-structure-table .k-structure-table-index{width:38px;height:38px;text-align:center}.k-structure-table .k-structure-table-index-number{font-size:.75rem;color:#999;padding-top:.15rem}.k-structure-table .k-sort-handle{width:38px;height:38px;display:none}.k-structure-table[data-sortable] tr:hover .k-structure-table-index-number{display:none}.k-structure-table[data-sortable] tr:hover .k-sort-handle{display:flex!important}.k-structure-table .k-structure-table-options{position:relative;width:38px;text-align:center;height:38px}.k-structure-table .k-structure-table-options-button{width:38px;height:38px}.k-structure-table .k-structure-table-text{padding:0 .75rem;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.k-structure-table .k-sortable-ghost{background:#fff;box-shadow:0 5px 10px rgba(17,17,17,.25);outline:2px solid #4271ae;margin-bottom:2px;cursor:grabbing;cursor:-webkit-grabbing}[data-disabled] .k-structure-table{background:#efefef}[data-disabled] .k-structure-table td,[data-disabled] .k-structure-table th{background:#efefef;border-bottom:1px solid #ccc}[dir=ltr] [data-disabled] .k-structure-table td,[dir=ltr] [data-disabled] .k-structure-table th{border-right:1px solid #ccc}[dir=rtl] [data-disabled] .k-structure-table td,[dir=rtl] [data-disabled] .k-structure-table th{border-left:1px solid #ccc}[data-disabled] .k-structure-table td:last-child{overflow:hidden;text-overflow:ellipsis}.k-sortable-row-fallback{opacity:0!important}.k-structure-backdrop{position:absolute;top:0;right:0;bottom:0;left:0;z-index:2;height:100vh}.k-structure-form{position:relative;z-index:3;border-radius:1px;margin-bottom:1px;box-shadow:0 0 0 3px rgba(17,17,17,.05);border:1px solid #ccc;background:#efefef}.k-structure-form-fields{padding:1.5rem 1.5rem 2rem}.k-structure-form-buttons{border-top:1px solid #ccc;display:flex;justify-content:space-between}.k-structure-form-buttons .k-pagination{display:none}@media screen and (min-width:65em){.k-structure-form-buttons .k-pagination{display:flex}}.k-structure-form-buttons .k-pagination>.k-button,.k-structure-form-buttons .k-pagination>span{padding:.875rem 1rem!important}.k-structure-form-cancel-button,.k-structure-form-submit-button{padding:.875rem 1.5rem;line-height:1rem;display:flex}.k-field-counter{display:none}.k-text-field:focus-within .k-field-counter{display:block}.k-users-field[data-disabled] *{pointer-events:all!important}.k-writer-field-input{line-height:1.5em;padding:.375rem .5rem}.k-toolbar{background:#fff;border-bottom:1px solid #efefef;height:38px}.k-toolbar-wrapper{position:absolute;top:0;right:0;left:0;max-width:100%}.k-toolbar-buttons{display:flex}.k-toolbar-divider{width:1px;background:#efefef}.k-toolbar-button{width:36px;height:36px}.k-toolbar-button:hover{background:hsla(0,0%,93.7%,.5)}.k-date-field-preview{padding:0 .75rem}.k-url-field-preview{padding:0 .75rem;overflow:hidden;text-overflow:ellipsis}.k-url-field-preview a{color:#4271ae;text-decoration:underline;transition:color .3s;white-space:nowrap;max-width:100%}.k-url-field-preview a:hover{color:#000}.k-files-field-preview{display:grid;grid-gap:.5rem;grid-template-columns:repeat(auto-fill,1.525rem);padding:0 .75rem}.k-files-field-preview li{line-height:0}.k-files-field-preview li .k-icon{height:100%}.k-list-field-preview{padding:.325rem .75rem;line-height:1.5em}.k-list-field-preview ol,.k-list-field-preview ul{margin-left:1rem}.k-list-field-preview ul>li{list-style:disc}.k-list-field-preview ol ul>li,.k-list-field-preview ul ul>li{list-style:circle}.k-list-field-preview ol>li{list-style:decimal}.k-list-field-preview ol>li::marker{color:#999;font-size:.75rem}.k-pages-field-preview{padding:0 .25rem 0 .75rem;display:flex}.k-pages-field-preview li{line-height:0;margin-right:.5rem}.k-pages-field-preview .k-link{display:flex;align-items:stretch;background:#efefef;box-shadow:0 1px 3px 0 rgba(0,0,0,.1),0 1px 2px 0 rgba(0,0,0,.06)}.k-pages-field-preview-image{width:1.525rem;height:1.525rem;color:#999!important}.k-pages-field-preview figcaption{flex-grow:1;line-height:1.5em;padding:0 .5rem;border:1px solid #ccc;border-left:0;border-radius:1px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.k-time-field-preview{padding:0 .75rem}.k-toggle-field-preview label{padding:0 .25rem 0 .75rem;display:flex;height:38px;cursor:pointer;overflow:hidden;white-space:nowrap}[dir=ltr] .k-toggle-field-preview .k-toggle-input-label{padding-left:.5rem}[dir=ltr] [data-align=right] .k-toggle-field-preview .k-toggle-input-label,[dir=rtl] .k-toggle-field-preview .k-toggle-input-label{padding-right:.5rem}[dir=rtl] [data-align=right] .k-toggle-field-preview .k-toggle-input-label{padding-left:.5rem}[dir=ltr] .k-toggle-field-preview .k-toggle-input{padding:0 .25rem 0 .75rem}[dir=rtl] .k-toggle-field-preview .k-toggle-input{padding:0 .75rem 0 .25rem}[data-align=right] .k-toggle-field-preview .k-toggle-input{flex-direction:row-reverse}[dir=ltr] [data-align=right] .k-toggle-field-preview .k-toggle-input{padding:0 .75rem 0 .25rem}.k-users-field-preview,[dir=rtl] [data-align=right] .k-toggle-field-preview .k-toggle-input{padding:0 .25rem 0 .75rem}.k-users-field-preview{display:flex}.k-users-field-preview li{line-height:0;margin-right:.5rem}.k-users-field-preview .k-link{display:flex;align-items:stretch;background:#efefef;box-shadow:0 1px 3px 0 rgba(0,0,0,.1),0 1px 2px 0 rgba(0,0,0,.06)}.k-users-field-preview-avatar{width:1.525rem;height:1.525rem;color:#999!important}.k-users-field-preview-avatar.k-image{display:block}.k-users-field-preview figcaption{flex-grow:1;line-height:1.5em;padding:0 .5rem;border:1px solid #ccc;border-left:0;border-radius:1px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.k-writer-field-preview{padding:.325rem .75rem;line-height:1.5em}.k-writer-field-preview p:not(:last-child){margin-bottom:1.5em}.k-aspect-ratio{position:relative;display:block;overflow:hidden;padding-bottom:100%}.k-aspect-ratio>*{position:absolute;top:0;left:0;bottom:0;right:0;height:100%;width:100%;-o-object-fit:contain;object-fit:contain}.k-aspect-ratio[data-cover]>*{-o-object-fit:cover;object-fit:cover}.k-bar{display:flex;align-items:center;justify-content:space-between;line-height:1}.k-bar-slot{flex-grow:1}.k-bar-slot[data-position=center]{text-align:center}[dir=ltr] .k-bar-slot[data-position=right]{text-align:right}[dir=rtl] .k-bar-slot[data-position=right]{text-align:left}.k-box{word-wrap:break-word;font-size:.875rem}.k-box:not([data-theme=none]){background:#d9d9d9;border-radius:1px;padding:.375rem .75rem;line-height:1.25rem;border-left:2px solid #999;padding:.5rem 1.5rem}.k-box[data-theme=code]{background:#111;border:1px solid #000;color:#efefef;font-family:Input,Menlo,monospace;font-size:.875rem;line-height:1.5}.k-box[data-theme=button]{padding:0}.k-box[data-theme=button] .k-button{padding:0 .75rem;height:2.25rem;width:100%;display:flex;align-items:center;line-height:2rem;text-align:left}.k-box[data-theme=positive]{background:#dbe4c1;border:0;border-left:2px solid #a7bd68;padding:.5rem 1.5rem}.k-box[data-theme=negative]{background:#eec6c6;border:0;border-left:2px solid #d16464;padding:.5rem 1.5rem}.k-box[data-theme=notice]{background:#f4dac9;border:0;border-left:2px solid #de935f;padding:.5rem 1.5rem}.k-box[data-theme=info]{background:#d3dde9;border:0;border-left:2px solid #7e9abf;padding:.5rem 1.5rem}.k-box[data-theme=empty]{text-align:center;border-left:0;padding:3rem 1.5rem;display:flex;justify-content:center;align-items:center;flex-direction:column;background:#efefef;border-radius:1px;color:#777;border:1px dashed #ccc}.k-box[data-theme=empty] .k-icon{margin-bottom:.5rem;color:#999}.k-box[data-theme=empty] p{color:#777}.k-card{position:relative;border-radius:1px;box-shadow:0 1px 3px 0 rgba(0,0,0,.1),0 1px 2px 0 rgba(0,0,0,.06)}.k-card,.k-card a{min-width:0;background:#fff}.k-card:focus-within{box-shadow:0 0 0 2px #4271ae}.k-card a:focus{outline:0}.k-card .k-sort-handle{position:absolute;top:.75rem;width:2rem;height:2rem;border-radius:1px;background:#fff;opacity:0;color:#111;z-index:1;will-change:opacity;transition:opacity .3s}[dir=ltr] .k-card .k-sort-handle{right:.75rem}[dir=rtl] .k-card .k-sort-handle{left:.75rem}.k-cards:hover .k-sort-handle{opacity:.25}.k-card:hover .k-sort-handle{opacity:1}.k-card.k-sortable-ghost{outline:2px solid #4271ae;border-radius:0}.k-card-icon,.k-card-image{border-top-left-radius:1px;border-top-right-radius:1px;overflow:hidden}.k-card-icon{position:relative;display:block}.k-card-icon .k-icon{position:absolute;top:0;right:0;bottom:0;left:0}.k-card-icon .k-icon-emoji{font-size:3rem}.k-card-icon .k-icon svg{width:3rem;height:3rem}.k-card-content{line-height:1.25rem;border-bottom-left-radius:1px;border-bottom-right-radius:1px;min-height:2.25rem;padding:.5rem .75rem;overflow-wrap:break-word;word-wrap:break-word}.k-card-text{display:block;font-weight:400;text-overflow:ellipsis;font-size:.875rem}.k-card-text[data-noinfo]:after{content:" ";height:1em;width:5rem;display:inline-block}.k-card-info{color:#777;display:block;font-size:.875rem;text-overflow:ellipsis;overflow:hidden}[dir=ltr] .k-card-info{margin-right:4rem}[dir=rtl] .k-card-info{margin-left:4rem}.k-card-options{position:absolute;bottom:0}[dir=ltr] .k-card-options{right:0}[dir=rtl] .k-card-options{left:0}.k-card-options>.k-button{position:relative;float:left;height:2.25rem;padding:0 .75rem;line-height:1}.k-card-options-dropdown{top:2.25rem}.k-cards{display:grid;grid-gap:1.5rem;grid-template-columns:repeat(auto-fit,minmax(12rem,1fr));grid-template-columns:repeat(auto-fill,minmax(min(12rem,100%),1fr))}@media screen and (min-width:30em){.k-cards[data-size=tiny]{grid-template-columns:repeat(auto-fill,minmax(10rem,1fr));grid-template-columns:repeat(auto-fill,minmax(min(10rem,100%),1fr))}.k-cards[data-size=small]{grid-template-columns:repeat(auto-fill,minmax(16rem,1fr));grid-template-columns:repeat(auto-fill,minmax(min(16rem,100%),1fr))}.k-cards[data-size=medium]{grid-template-columns:repeat(auto-fill,minmax(24rem,1fr));grid-template-columns:repeat(auto-fill,minmax(min(24rem,100%),1fr))}.k-cards[data-size=huge],.k-cards[data-size=large]{grid-template-columns:1fr}}@media screen and (min-width:65em){.k-cards[data-size=large]{grid-template-columns:repeat(auto-fill,minmax(32rem,1fr));grid-template-columns:repeat(auto-fill,minmax(min(32rem,100%),1fr))}}.k-collection-help{padding:.5rem .75rem}.k-collection-footer{display:flex;justify-content:space-between;margin-right:-.75rem;margin-left:-.75rem}.k-collection-pagination{line-height:1.25rem;flex-shrink:0;min-height:2.75rem}.k-collection-pagination .k-pagination .k-button{padding:.5rem .75rem;line-height:1.125rem}.k-column{min-width:0;grid-column-start:span 12}.k-column[data-sticky]>div{position:sticky;top:4vh;z-index:2}@media screen and (min-width:65em){.k-column[data-width="1/1"],.k-column[data-width="2/2"],.k-column[data-width="3/3"],.k-column[data-width="4/4"],.k-column[data-width="6/6"],.k-column[data-width="12/12"]{grid-column-start:span 12}.k-column[data-width="11/12"]{grid-column-start:span 11}.k-column[data-width="5/6"],.k-column[data-width="10/12"]{grid-column-start:span 10}.k-column[data-width="3/4"],.k-column[data-width="9/12"]{grid-column-start:span 9}.k-column[data-width="2/3"],.k-column[data-width="4/6"],.k-column[data-width="8/12"]{grid-column-start:span 8}.k-column[data-width="7/12"]{grid-column-start:span 7}.k-column[data-width="1/2"],.k-column[data-width="2/4"],.k-column[data-width="3/6"],.k-column[data-width="6/12"]{grid-column-start:span 6}.k-column[data-width="5/12"]{grid-column-start:span 5}.k-column[data-width="1/3"],.k-column[data-width="2/6"],.k-column[data-width="4/12"]{grid-column-start:span 4}.k-column[data-width="1/4"],.k-column[data-width="3/12"]{grid-column-start:span 3}.k-column[data-width="1/6"],.k-column[data-width="2/12"]{grid-column-start:span 2}.k-column[data-width="1/12"]{grid-column-start:span 1}}.k-column[data-disabled]{cursor:not-allowed;opacity:.4}.k-column[data-disabled] *{pointer-events:none}.k-column[data-disabled] .k-text[data-theme=help] *{pointer-events:auto}.k-dropzone{position:relative}.k-dropzone:after{content:"";position:absolute;top:0;right:0;bottom:0;left:0;display:none;pointer-events:none;z-index:1}.k-dropzone[data-over]:after{display:block;outline:1px solid #4271ae;box-shadow:0 0 0 3px rgba(66,113,174,.25)}.k-empty{display:flex;align-items:stretch;border-radius:1px;color:#777;border:1px dashed #ccc}button.k-empty{width:100%}button.k-empty:focus{outline:none}.k-empty p{font-size:.875rem;color:#777}.k-empty>.k-icon{color:#999}.k-empty[data-layout=cards]{text-align:center;padding:1.5rem;justify-content:center;flex-direction:column}.k-empty[data-layout=cards] .k-icon{margin-bottom:1rem}.k-empty[data-layout=cards] .k-icon svg{width:2rem;height:2rem}.k-empty[data-layout=list]{min-height:38px}.k-empty[data-layout=list]>.k-icon{width:36px;min-height:36px;border-right:1px solid rgba(0,0,0,.05)}.k-empty[data-layout=list]>p{line-height:1.25rem;padding:.5rem .75rem}.k-file-preview{background:#2b2b2b}.k-file-preview-layout{display:grid}@media screen and (max-width:65em){.k-file-preview-layout{padding:0!important}}@media screen and (min-width:30em){.k-file-preview-layout{grid-template-columns:50% auto}}@media screen and (min-width:65em){.k-file-preview-layout{display:flex;align-items:center}}.k-file-preview-layout>*{min-width:0}.k-file-preview-image{position:relative;background:url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjxwYXR0ZXJuIGlkPSJhIiB3aWR0aD0iMjAiIGhlaWdodD0iMjAiIHBhdHRlcm5Vbml0cz0idXNlclNwYWNlT25Vc2UiPjxwYXRoIGZpbGw9InJnYmEoMCwgMCwgMCwgMC4yKSIgZD0iTTAgMGgxMHYxMEgwem0xMCAxMGgxMHYxMEgxMHoiLz48L3BhdHRlcm4+PHJlY3QgZmlsbD0idXJsKCNhKSIgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIvPjwvc3ZnPg==")}@media screen and (min-width:65em){.k-file-preview-image{width:33.33%}}@media screen and (min-width:90em){.k-file-preview-image{width:25%}}.k-file-preview-image .k-image span{overflow:hidden;padding-bottom:66.66%}@media screen and (min-width:30em)and (max-width:65em){.k-file-preview-image .k-image span{position:absolute;top:0;left:0;bottom:0;right:0;padding-bottom:0!important}}@media screen and (min-width:65em){.k-file-preview-image .k-image span{padding-bottom:100%}}.k-file-preview-placeholder{display:block;padding-bottom:100%}.k-file-preview-image img{padding:3rem}.k-file-preview-image-link{display:block;outline:0}.k-file-preview-image-link.k-link[data-tabbed]{box-shadow:none;outline:2px solid #4271ae;outline-offset:-2px}.k-file-preview-icon{position:relative;display:block;padding-bottom:100%;overflow:hidden;color:hsla(0,0%,100%,.5)}.k-file-preview-icon svg{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%) scale(4)}.k-file-preview-details{padding:1.5rem;flex-grow:1}@media screen and (min-width:65em){.k-file-preview-details{padding:3rem}}.k-file-preview-details ul{line-height:1.5em;max-width:50rem;display:grid;grid-gap:1.5rem 3rem;grid-template-columns:repeat(auto-fill,minmax(100px,1fr))}@media screen and (min-width:30em){.k-file-preview-details ul{grid-template-columns:repeat(auto-fill,minmax(200px,1fr))}}.k-file-preview-details h3{font-size:.875rem;font-weight:500;color:#999}.k-file-preview-details p{white-space:nowrap;overflow:hidden;text-overflow:ellipsis;color:hsla(0,0%,100%,.75);font-size:.875rem}.k-file-preview-details p a{display:block;width:100%;overflow:hidden;text-overflow:ellipsis}.k-grid{--columns:12;display:grid;grid-column-gap:0;grid-row-gap:0;grid-template-columns:1fr}@media screen and (min-width:30em){.k-grid[data-gutter=small]{grid-column-gap:1rem;grid-row-gap:1rem}.k-grid[data-gutter=huge],.k-grid[data-gutter=large],.k-grid[data-gutter=medium]{grid-column-gap:1.5rem;grid-row-gap:1.5rem}}@media screen and (min-width:65em){.k-grid{grid-template-columns:repeat(var(--columns),1fr)}.k-grid[data-gutter=large]{grid-column-gap:3rem}.k-grid[data-gutter=huge]{grid-column-gap:4.5rem}}@media screen and (min-width:90em){.k-grid[data-gutter=large]{grid-column-gap:4.5rem}.k-grid[data-gutter=huge]{grid-column-gap:6rem}}@media screen and (min-width:120em){.k-grid[data-gutter=large]{grid-column-gap:6rem}.k-grid[data-gutter=huge]{grid-column-gap:7.5rem}}.k-header{border-bottom:1px solid #ccc;margin-bottom:2rem;padding-top:4vh}.k-header .k-headline{min-height:1.25em;margin-bottom:.5rem;word-wrap:break-word}.k-header .k-header-buttons{margin-top:-.5rem;height:3.25rem}.k-header .k-headline-editable{cursor:pointer}.k-header .k-headline-editable .k-icon{color:#999;opacity:0;transition:opacity .3s;display:inline-block}[dir=ltr] .k-header .k-headline-editable .k-icon{margin-left:.5rem}[dir=rtl] .k-header .k-headline-editable .k-icon{margin-right:.5rem}.k-header .k-headline-editable:hover .k-icon{opacity:1}.k-list .k-list-item:not(:last-child){margin-bottom:2px}.k-list-item{position:relative;display:flex;align-items:center;background:#fff;border-radius:1px;box-shadow:0 1px 3px 0 rgba(0,0,0,.1),0 1px 2px 0 rgba(0,0,0,.06)}[data-disabled] .k-list-item{background:#efefef}.k-list-item .k-sort-handle{position:absolute;left:-1.5rem;width:1.5rem;height:38px;opacity:0}.k-list:hover .k-sort-handle{opacity:.25}.k-list-item:hover .k-sort-handle{opacity:1}.k-list-item.k-sortable-ghost{position:relative;outline:2px solid #4271ae;z-index:1;box-shadow:0 5px 10px rgba(17,17,17,.25)}.k-list-item.k-sortable-fallback{opacity:.25!important;overflow:hidden}.k-list-item-image{width:38px;height:38px;overflow:hidden;flex-shrink:0;line-height:0}.k-list-item-image .k-image{width:38px;height:38px;-o-object-fit:contain;object-fit:contain}.k-list-item-image .k-icon{width:38px;height:38px}.k-list-item-image .k-icon svg{opacity:.5}.k-list-item-content{display:flex;align-items:center;flex-grow:1;flex-shrink:1;overflow:hidden;outline:none}.k-list-item-content[data-tabbed]{outline:none;box-shadow:0 0 0 2px currentColor}.k-list-item-text{display:flex;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;align-items:baseline;width:100%;line-height:1.25rem;padding:.5rem .75rem}.k-list-item-text em{font-style:normal;margin-right:1rem;flex-grow:1;font-size:.875rem;color:#111}.k-list-item-text em,.k-list-item-text small{min-width:0;overflow:hidden;text-overflow:ellipsis}.k-list-item-text small{color:#999;font-size:.75rem;font-variant-numeric:tabular-nums;color:#777;display:none}@media screen and (min-width:30em){.k-list-item-text small{display:block}}.k-list-item-status{height:auto!important}.k-list-item-options{position:relative;flex-shrink:0}.k-list-item-options .k-dropdown-content{top:38px}.k-list-item-options>.k-button{height:38px;padding:0 12px}.k-list-item-options>.k-button>.k-button-icon{height:38px}.k-overlay{position:fixed;top:0;right:0;bottom:0;left:0;width:100%;height:100%;z-index:700;transform:translateZ(0)}.k-overlay[data-centered]{display:flex;align-items:center;justify-content:center}.k-overlay[data-dimmed]{background:rgba(0,0,0,.6)}.k-overlay-loader{color:#fff}.k-tabs{position:relative;background:#e9e9e9;border-top:1px solid #ccc;border-left:1px solid #ccc;border-right:1px solid #ccc}.k-tabs nav{display:flex;justify-content:center;margin-left:-1px;margin-right:-1px}.k-tab-button.k-button{position:relative;z-index:1;display:inline-flex;justify-content:center;align-items:center;padding:.625rem .75rem;font-size:.75rem;text-transform:uppercase;text-align:center;font-weight:500;border-left:1px solid transparent;border-right:1px solid #ccc;flex-grow:1;flex-shrink:1;flex-direction:column;max-width:15rem}@media screen and (min-width:30em){.k-tab-button.k-button{flex-direction:row}}@media screen and (min-width:30em){.k-tab-button.k-button .k-icon{margin-right:.5rem}}.k-tab-button.k-button>.k-button-text{padding-top:.375rem;font-size:10px;overflow:hidden;max-width:10rem;text-overflow:ellipsis}[dir=ltr] .k-tab-button.k-button>.k-button-text{padding-left:0}[dir=rtl] .k-tab-button.k-button>.k-button-text{padding-right:0}@media screen and (min-width:30em){.k-tab-button.k-button>.k-button-text{font-size:.75rem;padding-top:0}}.k-tab-button:last-child{border-right:1px solid transparent}.k-tab-button[aria-current]{position:relative;background:#efefef;border-right:1px solid #ccc;pointer-events:none}.k-tab-button[aria-current]:first-child{border-left:1px solid #ccc}.k-tab-button[aria-current]:after,.k-tab-button[aria-current]:before{position:absolute;content:""}.k-tab-button[aria-current]:before{left:-1px;right:-1px;height:2px;top:-1px;background:#000}.k-tab-button[aria-current]:after{left:0;right:0;height:1px;bottom:-1px;background:#efefef}.k-tabs-dropdown{top:100%;right:0}[dir=ltr] .k-tabs-badge{padding-left:.25rem}[dir=rtl] .k-tabs-badge{padding-right:.25rem}.k-tabs[data-theme=notice] .k-tabs-badge{color:#f4861f}.k-view{padding-left:1.5rem;padding-right:1.5rem;margin:0 auto;max-width:100rem}@media screen and (min-width:30em){.k-view{padding-left:3rem;padding-right:3rem}}@media screen and (min-width:90em){.k-view{padding-left:6rem;padding-right:6rem}}.k-view[data-align=center]{height:calc(100vh - 6rem);display:flex;align-items:center;justify-content:center;padding:0 3rem;overflow:auto}.k-view[data-align=center]>*{flex-basis:22.5rem}.k-headline{font-size:1rem;font-weight:600;line-height:1.5em}.k-headline[data-size=small]{font-size:.875rem}.k-headline[data-size=large]{font-size:1.25rem;font-weight:400}@media screen and (min-width:65em){.k-headline[data-size=large]{font-size:1.5rem}}.k-headline[data-size=huge]{font-size:1.5rem;line-height:1.15em}@media screen and (min-width:65em){.k-headline[data-size=huge]{font-size:1.875rem}}.k-headline[data-theme=negative]{color:#c82829}.k-headline[data-theme=positive]{color:#5d800d}.k-headline abbr{color:#999;padding-left:.25rem;text-decoration:none}.k-icon{position:relative;line-height:0;display:flex;align-items:center;justify-content:center;flex-shrink:0}.k-icon svg{width:1rem;height:1rem;-moz-transform:scale(1)}.k-icon svg *{fill:currentColor}.k-icon[data-back=black]{background:#111;color:#fff}.k-icon[data-back=white]{background:#fff;color:#111}.k-icon[data-back=pattern]{background:#2b2b2b url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjxwYXR0ZXJuIGlkPSJhIiB3aWR0aD0iMjAiIGhlaWdodD0iMjAiIHBhdHRlcm5Vbml0cz0idXNlclNwYWNlT25Vc2UiPjxwYXRoIGZpbGw9InJnYmEoMCwgMCwgMCwgMC4yKSIgZD0iTTAgMGgxMHYxMEgwem0xMCAxMGgxMHYxMEgxMHoiLz48L3BhdHRlcm4+PHJlY3QgZmlsbD0idXJsKCNhKSIgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIvPjwvc3ZnPg==");color:#fff}[data-disabled] .k-icon[data-back=black]{background-color:#777}[data-disabled] .k-icon[data-back=pattern]{background:#848484 url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjxwYXR0ZXJuIGlkPSJhIiB3aWR0aD0iMjAiIGhlaWdodD0iMjAiIHBhdHRlcm5Vbml0cz0idXNlclNwYWNlT25Vc2UiPjxwYXRoIGZpbGw9InJnYmEoMCwgMCwgMCwgMC4yKSIgZD0iTTAgMGgxMHYxMEgwem0xMCAxMGgxMHYxMEgxMHoiLz48L3BhdHRlcm4+PHJlY3QgZmlsbD0idXJsKCNhKSIgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIvPjwvc3ZnPg==")}[data-disabled] .k-icon[data-back=pattern] svg{opacity:1}.k-icon[data-size=medium] svg{width:2rem;height:2rem}.k-icon[data-size=large] svg{width:3rem;height:3rem}.k-icon-emoji{display:block;line-height:1;font-style:normal;font-size:1rem}@media not all,only screen and (-webkit-min-device-pixel-ratio:2),only screen and (min-resolution:2dppx),only screen and (min-resolution:192dpi){.k-icon-emoji{font-size:1.25rem}}.k-image span{position:relative;display:block;line-height:0;padding-bottom:100%}.k-image img{position:absolute;top:0;right:0;bottom:0;left:0;width:100%;height:100%;-o-object-fit:contain;object-fit:contain}.k-image-error{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);color:#fff;font-size:.9em}.k-image-error svg *{fill:hsla(0,0%,100%,.3)}.k-image[data-cover] img{-o-object-fit:cover;object-fit:cover}.k-image[data-back=black] span{background:#111}.k-image[data-back=white] span{background:#fff;color:#111}.k-image[data-back=white] .k-image-error{background:#111;color:#fff}.k-image[data-back=pattern] span{background:#2b2b2b url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjxwYXR0ZXJuIGlkPSJhIiB3aWR0aD0iMjAiIGhlaWdodD0iMjAiIHBhdHRlcm5Vbml0cz0idXNlclNwYWNlT25Vc2UiPjxwYXRoIGZpbGw9InJnYmEoMCwgMCwgMCwgMC4yKSIgZD0iTTAgMGgxMHYxMEgwem0xMCAxMGgxMHYxMEgxMHoiLz48L3BhdHRlcm4+PHJlY3QgZmlsbD0idXJsKCNhKSIgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIvPjwvc3ZnPg==")}.k-loader{z-index:1}.k-loader svg{animation:Spin .9s linear infinite}.k-progress{-webkit-appearance:none;width:100%;height:.5rem;border-radius:5rem;background:#ccc;overflow:hidden;border:none}.k-progress::-webkit-progress-bar{border:none;background:#ccc;height:.5rem;border-radius:20px}.k-progress::-webkit-progress-value{border-radius:inherit;background:#4271ae;-webkit-transition:width .3s;transition:width .3s}.k-progress::-moz-progress-bar{border-radius:inherit;background:#4271ae;-moz-transition:width .3s;transition:width .3s}.k-sort-handle{cursor:move;cursor:grab;cursor:-webkit-grab;color:#111;justify-content:center;align-items:center;line-height:0;width:2rem;height:2rem;display:flex;will-change:opacity,color;transition:opacity .3s;z-index:1}.k-sort-handle svg{width:1rem}.k-sort-handle:active{cursor:grabbing;cursor:-webkit-grabbing}.k-status-icon svg{width:14px;height:14px}.k-status-icon-listed .k-icon{color:#a7bd68}.k-status-icon-unlisted .k-icon{color:#7e9abf}.k-status-icon-draft .k-icon{color:#d16464}.k-status-icon[data-disabled]{opacity:1!important}.k-status-icon[data-disabled] .k-icon{color:#ccc;opacity:.5}.k-text{line-height:1.5em}.k-text ol,.k-text ul{margin-left:1rem}.k-text li{list-style:inherit}.k-text>ol,.k-text>ul,.k-text p{margin-bottom:1.5em}.k-text a{text-decoration:underline}.k-text>:last-child{margin-bottom:0}.k-text[data-align=center]{text-align:center}.k-text[data-align=right]{text-align:right}.k-text[data-size=tiny]{font-size:.75rem}.k-text[data-size=small]{font-size:.875rem}.k-text[data-size=medium]{font-size:1rem}.k-text[data-size=large]{font-size:1.25rem}.k-text[data-theme=help]{font-size:.875rem;color:#777;line-height:1.25rem}.k-dialog-body .k-text{word-wrap:break-word}.k-user-info{display:flex;align-items:center;line-height:1;font-size:.875rem}.k-user-info .k-icon,.k-user-info .k-image{width:1.5rem;margin-right:.75rem}.k-user-info .k-icon{height:1.5rem;background:#000;color:#fff}button{line-height:inherit;border:0;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica,Arial,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol;font-size:1rem;color:currentColor;background:none;cursor:pointer}button::-moz-focus-inner{padding:0;border:0}.k-button{display:inline-block;position:relative;font-size:.875rem;transition:color .3s}.k-button,.k-button:focus,.k-button:hover{outline:none}.k-button[data-tabbed]{outline:none;box-shadow:0 0 0 2px currentColor}.k-button *{vertical-align:middle}.k-button[data-responsive] .k-button-text{display:none}@media screen and (min-width:30em){.k-button[data-responsive] .k-button-text{display:inline}}.k-button[data-theme=positive]{color:#5d800d}.k-button[data-theme=negative]{color:#c82829}.k-button-icon{display:inline-flex;align-items:center;line-height:0}[dir=ltr] .k-button-icon~.k-button-text{padding-left:.5rem}[dir=rtl] .k-button-icon~.k-button-text{padding-right:.5rem}.k-button-text{opacity:.75}.k-button:focus .k-button-text,.k-button:hover .k-button-text{opacity:1}.k-button-text b,.k-button-text span{vertical-align:baseline}.k-button[data-disabled]{opacity:.5;cursor:default}.k-card-options>.k-button[data-disabled]{display:inline-flex}.k-button[data-disabled]:focus .k-button-text,.k-button[data-disabled]:hover .k-button-text{opacity:.75}.k-button-group{font-size:0;margin-left:-.75rem;margin-right:-.75rem}.k-button-group>.k-dropdown{height:3rem;display:inline-block}.k-button-group>.k-button,.k-button-group>.k-dropdown>.k-button{padding:1rem .75rem;line-height:1rem}.k-button-group .k-dropdown-content{top:calc(100% + 1px);margin:0 .75rem}.k-dropdown{position:relative}.k-dropdown-content{position:absolute;top:100%;background:#111;color:#fff;z-index:800;box-shadow:0 10px 15px -3px rgba(0,0,0,.1),0 4px 6px -2px rgba(0,0,0,.05);border-radius:1px;text-align:left;margin-bottom:6rem}[dir=ltr] .k-dropdown-content{left:0}[dir=rtl] .k-dropdown-content{right:0}[dir=ltr] .k-dropdown-content[data-align=right]{left:auto;right:0}[dir=rtl] .k-dropdown-content[data-align=right]{left:0;right:auto}.k-dropdown-content>.k-dropdown-item:first-child{margin-top:.5rem}.k-dropdown-content>.k-dropdown-item:last-child{margin-bottom:.5rem}.k-dropdown-content[data-dropup]{top:auto;bottom:100%;margin-bottom:.5rem}.k-dropdown-content hr{position:relative;padding:.5rem 0;border:0}.k-dropdown-content hr:after{position:absolute;top:.5rem;left:1rem;right:1rem;content:"";height:1px;background:currentColor;opacity:.2}.k-dropdown-item{white-space:nowrap;line-height:1;display:flex;width:100%;align-items:center;font-size:.875rem;padding:6px 16px}.k-dropdown-item:focus{outline:none;box-shadow:0 0 0 2px currentColor}.k-dropdown-item .k-button-figure{text-align:center;padding-right:.5rem}.k-link{outline:none}.k-link[data-tabbed]{outline:none;box-shadow:0 0 0 2px currentColor}.k-pagination{-webkit-user-select:none;-moz-user-select:none;user-select:none;direction:ltr}.k-pagination .k-button{padding:1rem}.k-pagination-details{white-space:nowrap}.k-pagination>span{font-size:.875rem}.k-pagination[data-align=center]{text-align:center}.k-pagination[data-align=right]{text-align:right}.k-dropdown-content.k-pagination-selector{position:absolute;top:100%;left:50%;transform:translateX(-50%);background:#000}[dir=ltr] .k-dropdown-content.k-pagination-selector{direction:ltr}[dir=rtl] .k-dropdown-content.k-pagination-selector{direction:rtl}.k-pagination-settings{display:flex;align-items:center;justify-content:space-between}.k-pagination-settings .k-button{line-height:1}.k-pagination-settings label{display:flex;border-right:1px solid hsla(0,0%,100%,.35);align-items:center;padding:.625rem 1rem;font-size:.75rem}.k-pagination-settings label span{margin-right:.5rem}.k-prev-next{direction:ltr}.k-search{max-width:30rem;margin:0 auto;box-shadow:0 10px 15px -3px rgba(0,0,0,.1),0 4px 6px -2px rgba(0,0,0,.05)}@media screen and (min-width:65em){.k-search{margin:2.5rem auto}}.k-search-input{background:#efefef;display:flex}.k-search-types{flex-shrink:0;display:flex}.k-search-types>.k-button{padding:0 0 0 1rem;font-size:1rem;line-height:1;height:2.5rem}.k-search-types>.k-button .k-icon{height:2.5rem}.k-search-types>.k-button .k-button-text{opacity:1;font-weight:500}.k-search-input input{background:none;flex-grow:1;font:inherit;padding:.75rem;border:0;height:2.5rem}.k-search-close{width:3rem;line-height:1}.k-search-close .k-icon-loader{animation:Spin 2s linear infinite}.k-search input:focus{outline:0}.k-search-results{padding:.5rem 1rem 1rem;background:#efefef}.k-search li{background:#fff;display:flex;box-shadow:0 1px 3px 0 rgba(0,0,0,.1),0 1px 2px 0 rgba(0,0,0,.06)}.k-search li:not(:last-child){margin-bottom:.25rem}.k-search li[data-selected]{outline:2px solid #4271ae}.k-search li .k-link{display:flex;align-items:center;flex-grow:1}.k-search-item-image,.k-search-item-image>*{height:50px;width:50px}.k-search-item-info{padding:.5rem .75rem;line-height:1.125}.k-search li strong{display:block;font-size:.875rem;font-weight:400}.k-search-empty,.k-search li small{font-size:.75rem;color:#777}.k-search-empty{text-align:center}.k-tag{position:relative;font-size:.875rem;line-height:1;cursor:pointer;background-color:#111;color:#efefef;border-radius:1px;display:flex;align-items:center;justify-content:space-between;-webkit-user-select:none;-moz-user-select:none;user-select:none}.k-tag:focus{outline:0;background-color:#4271ae;border-color:#4271ae;color:#fff}.k-tag-text{padding:0 .75rem}.k-tag-toggle{color:hsla(0,0%,100%,.7);width:2rem;height:100%;display:flex;align-items:center;justify-content:center;border-left:1px solid hsla(0,0%,100%,.15)}.k-tag-toggle:hover{background:hsla(0,0%,100%,.2);color:#fff}[data-disabled] .k-tag{background-color:#777}[data-disabled] .k-tag .k-tag-toggle{display:none}.k-topbar{position:relative;color:#fff;flex-shrink:0;height:2.5rem;line-height:1;background:#111}.k-topbar-wrapper{position:relative;display:flex;align-items:center;margin-left:-.75rem;margin-right:-.75rem}.k-topbar-loader{position:absolute;top:0;right:0;bottom:0;height:2.5rem;width:2.5rem;padding:.75rem;background:#111;z-index:1;display:flex;align-items:center;justify-content:center}.k-topbar-loader svg{height:18px;width:18px;animation:Spin .9s linear infinite}.k-topbar-menu{flex-shrink:0}.k-topbar-menu ul{padding:.5rem 0}.k-topbar-menu-button{display:flex;align-items:center}.k-topbar-menu-button .k-button-text{opacity:1}.k-topbar-button,.k-topbar-signals-button{padding:.75rem;line-height:1;font-size:.875rem}.k-topbar-signals .k-button .k-button-text{opacity:1}.k-topbar-button .k-button-text{display:flex;opacity:1}.k-topbar-view-button{flex-shrink:0;display:flex;align-items:center}[dir=ltr] .k-topbar-view-button{padding-right:0}[dir=rtl] .k-topbar-view-button{padding-left:0}[dir=ltr] .k-topbar-view-button .k-icon{margin-right:.5rem}[dir=rtl] .k-topbar-view-button .k-icon{margin-left:.5rem}.k-topbar-crumbs{flex-grow:1;display:flex;overflow-y:hidden}.k-topbar-crumbs a{position:relative;font-size:.875rem;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;display:none;padding-top:.75rem;padding-bottom:.75rem;line-height:1;transition:opacity .3s;outline:none}.k-topbar-crumbs a:before{content:"/";padding:0 .5rem;opacity:.25}.k-topbar-crumbs a:focus,.k-topbar-crumbs a:hover{opacity:1}.k-topbar-crumbs a[data-tabbed]{outline:none;box-shadow:0 0 0 2px currentColor}.k-topbar-crumbs a:not(:last-child){max-width:15vw}.k-topbar-breadcrumb-menu{flex-shrink:0}@media screen and (min-width:30em){.k-topbar-crumbs a{display:block}.k-topbar-breadcrumb-menu{display:none}}.k-topbar-signals{position:absolute;top:0;background:#111;height:2.5rem;display:flex;align-items:center}[dir=ltr] .k-topbar-signals{right:0}[dir=rtl] .k-topbar-signals{left:0}.k-topbar-signals:before{position:absolute;content:"";top:0;bottom:0;width:.5rem}[dir=ltr] .k-topbar-signals:before{left:-.5rem;background:-webkit-linear-gradient(left,rgba(17,17,17,0),#111)}[dir=rtl] .k-topbar-signals:before{right:-.5rem;background:-webkit-linear-gradient(right,rgba(17,17,17,0),#111)}.k-topbar-signals .k-button{line-height:1}.k-topbar-notification{font-weight:600;line-height:1;display:flex}.k-topbar .k-button[data-theme=positive]{color:#a7bd68}.k-topbar .k-button[data-theme=negative]{color:#d16464}.k-topbar .k-button[data-theme=negative] .k-button-text{display:none}@media screen and (min-width:30em){.k-topbar .k-button[data-theme=negative] .k-button-text{display:inline}}.k-topbar .k-button[data-theme] .k-button-text{opacity:1}.k-topbar .k-dropdown-content{color:#111;background:#fff}.k-topbar .k-dropdown-content hr:after{opacity:.1}.k-topbar-menu [aria-current] .k-link{color:#4271ae;font-weight:500}.k-registration{display:inline-block;margin-right:1rem;display:flex;align-items:center}.k-registration p{color:#d16464;font-size:.875rem;margin-right:1rem;font-weight:600;display:none}@media screen and (min-width:90em){.k-registration p{display:block}}.k-registration .k-button{color:#fff}.k-section,.k-sections{padding-bottom:3rem}.k-section-header{position:relative;display:flex;align-items:baseline;z-index:1}.k-section-header .k-headline{line-height:1.25rem;padding-bottom:.75rem;min-height:2rem}.k-section-header .k-button-group{position:absolute;top:-.875rem}[dir=ltr] .k-section-header .k-button-group{right:0}[dir=rtl] .k-section-header .k-button-group{left:0}.k-info-section-headline{margin-bottom:.5rem}.k-files-section[data-processing],.k-pages-section[data-processing]{pointer-events:none}.k-fields-issue-headline{margin-bottom:.5rem}.k-fields-section input[type=submit]{display:none}[data-locked] .k-fields-section{opacity:.2;pointer-events:none}.k-browser-view .k-error-view-content{text-align:left}.k-error-view{position:absolute;top:0;right:0;bottom:0;left:0;display:flex;align-items:center;justify-content:center}.k-error-view-content{line-height:1.5em;max-width:25rem;text-align:center}.k-error-view-icon{color:#c82829;display:inline-block}.k-error-view-content p:not(:last-child){margin-bottom:.75rem}.k-installation-view .k-button{display:block;margin-top:1.5rem}.k-installation-view .k-headline{margin-bottom:.75rem}.k-installation-issues{line-height:1.5em;font-size:.875rem}.k-installation-issues li{position:relative;padding:1.5rem;background:#fff}[dir=ltr] .k-installation-issues li{padding-left:3.5rem}[dir=rtl] .k-installation-issues li{padding-right:3.5rem}.k-installation-issues .k-icon{position:absolute;top:calc(1.5rem + 2px)}[dir=ltr] .k-installation-issues .k-icon{left:1.5rem}[dir=rtl] .k-installation-issues .k-icon{right:1.5rem}.k-installation-issues .k-icon svg *{fill:#c82829}.k-installation-issues li:not(:last-child){margin-bottom:2px}.k-installation-issues li code{font:inherit;color:#c82829}.k-installation-view .k-button[type=submit]{padding:1rem}[dir=ltr] .k-installation-view .k-button[type=submit]{margin-left:-1rem}[dir=rtl] .k-installation-view .k-button[type=submit]{margin-right:-1rem}.k-login-fields{position:relative}.k-login-toggler{position:absolute;top:0;right:0;z-index:1;text-decoration:underline;font-size:.875rem}.k-login-form label abbr{visibility:hidden}.k-login-buttons{display:flex;align-items:center;justify-content:flex-end;padding:1.5rem 0}.k-login-button{padding:.5rem 1rem;font-weight:500;transition:opacity .3s}[dir=ltr] .k-login-button{margin-right:-1rem}[dir=rtl] .k-login-button{margin-left:-1rem}.k-login-button span{opacity:1}.k-login-button[disabled]{opacity:.25}.k-login-back-button,.k-login-checkbox{display:flex;align-items:center;flex-grow:1}[dir=ltr] .k-login-back-button{margin-left:-1rem}[dir=rtl] .k-login-back-button{margin-right:-1rem}.k-login-checkbox{padding:.5rem 0;font-size:.875rem;cursor:pointer}.k-login-checkbox .k-checkbox-text{opacity:.75;transition:opacity .3s}.k-login-checkbox:focus span,.k-login-checkbox:hover span{opacity:1}.k-login-alert{padding:.5rem .75rem;display:flex;justify-content:space-between;align-items:center;min-height:38px;margin-bottom:2rem;background:#c82829;color:#fff;font-size:.875rem;border-radius:1px;box-shadow:0 10px 15px -3px rgba(0,0,0,.1),0 4px 6px -2px rgba(0,0,0,.05);cursor:pointer}.k-password-reset-view .k-user-info{height:38px;margin-bottom:2.25rem;padding:.5rem;background:#fff;border-radius:1px;box-shadow:0 1px 3px 0 rgba(0,0,0,.1),0 1px 2px 0 rgba(0,0,0,.06)}.k-settings-view section{margin-bottom:3rem}.k-settings-view .k-header{margin-bottom:1.5rem}.k-settings-view header{margin-bottom:.5rem;display:flex;justify-content:space-between}.k-system-info-box{background:#fff;padding:.75rem;display:flex}.k-system-info-box li{flex-shrink:0;flex-grow:1;flex-basis:0}.k-system-info-box dt{font-size:.875rem;color:#777;margin-bottom:.25rem}.k-system-unregistered{color:#c82829}.k-languages-section{margin-bottom:2rem}.k-user-profile{background:#fff}.k-user-profile>.k-view{padding-top:3rem;padding-bottom:3rem;display:flex;align-items:center;line-height:0}.k-user-profile .k-button-group{overflow:hidden}[dir=ltr] .k-user-profile .k-button-group{margin-left:.75rem}[dir=rtl] .k-user-profile .k-button-group{margin-right:.75rem}.k-user-profile .k-button-group .k-button{display:block;padding-top:.25rem;padding-bottom:.25rem;overflow:hidden;white-space:nowrap}.k-user-profile .k-button-group .k-button[disabled]{opacity:1}.k-user-profile .k-dropdown-content{margin-top:.5rem;left:50%;transform:translateX(-50%)}.k-user-view-image .k-image{display:block;width:4rem;height:4rem;line-height:0}.k-user-view-image .k-button-text{opacity:1}.k-user-view-image .k-icon{width:4rem;height:4rem;background:#111;color:#999}.k-user-name-placeholder{color:#999;transition:color .3s}.k-header[data-editable] .k-user-name-placeholder:hover{color:#111}.k-block-container{position:relative;padding:.75rem;border-bottom:1px dashed rgba(0,0,0,.1);background:#fff}.k-block-container:last-of-type{border-bottom:0}.k-block-container:focus{outline:0}.k-block-container[data-batched]{z-index:2;border-bottom-color:transparent}.k-block-container[data-batched]:after{position:absolute;content:"";top:0;right:0;bottom:0;left:0;background:rgba(203,215,229,.375);mix-blend-mode:multiply;border:1px solid #4271ae}.k-block-container[data-selected]{z-index:2;box-shadow:0 0 0 1px #4271ae,0 0 0 3px rgba(66,113,174,.25);border-bottom-color:transparent}.k-block-container .k-block-options{position:absolute;top:0;right:.75rem;margin-top:calc(-1.75rem + 2px);display:none}.k-block-container[data-last-in-batch] .k-block-options,.k-block-container[data-selected] .k-block-options{display:block}.k-block-container[data-hidden] .k-block{opacity:.25}.k-drawer-options .k-button[data-disabled]{vertical-align:middle;display:inline-grid}[data-disabled] .k-block-container{background:#efefef}.k-blocks{background:#fff;box-shadow:0 1px 3px 0 rgba(0,0,0,.1),0 1px 2px 0 rgba(0,0,0,.06);border-radius:.25rem}[data-disabled] .k-blocks{background:#efefef}.k-blocks[data-alt] .k-block-container>*{pointer-events:none}.k-blocks[data-empty]{padding:0;background:none;box-shadow:none}.k-blocks .k-sortable-ghost{outline:2px solid #4271ae;box-shadow:0 5px 10px rgba(17,17,17,.25);cursor:grabbing;cursor:-webkit-grabbing}.k-blocks-empty.k-empty{cursor:pointer;display:flex;align-items:center}.k-blocks-list>.k-blocks-empty:not(:only-child){display:none}.k-block-figure{cursor:pointer}.k-block-figure iframe{border:0;pointer-events:none;background:#000}.k-block-figure figcaption{padding-top:.5rem;color:#777;font-size:.875rem;text-align:center}.k-block-figure-empty.k-button{display:flex;width:100%;height:6rem;border-radius:.125rem;align-items:center;justify-content:center;color:#777;background:#efefef}.k-block-options{display:flex;align-items:center;background:#fff;z-index:800;box-shadow:-2px 0 5px rgba(0,0,0,.1),0 1px 3px 0 rgba(0,0,0,.1),0 1px 2px 0 rgba(0,0,0,.06),0 20px 25px -5px rgba(0,0,0,.1),0 10px 10px -5px rgba(0,0,0,.04);color:#000;border-radius:.25rem}.k-block-options-button{width:30px;height:30px;line-height:1;display:inline-flex;align-items:center;justify-content:center;border-right:1px solid #efefef}.k-block-options-button:first-child{border-top-left-radius:.25rem;border-bottom-left-radius:.25rem}.k-block-options-button:last-child{border-top-right-radius:.25rem;border-bottom-right-radius:.25rem}.k-block-options-button:last-of-type{border-right:0}.k-block-options-button[aria-current]{color:#4271ae}.k-block-options-button:hover{background:#f7f7f7}.k-block-options .k-dropdown-content{margin-top:.5rem}.k-block-selector.k-dialog{background:#313740;color:#fff}.k-block-selector .k-headline{margin-bottom:1rem}.k-block-selector details:not(:last-of-type){margin-bottom:1.5rem}.k-block-selector summary{font-size:.75rem;cursor:pointer;color:#ccc}.k-block-selector details:only-child summary{pointer-events:none}.k-block-selector summary:focus{outline:0}.k-block-selector summary:focus-visible{color:#a7bd68}.k-block-types{display:grid;grid-gap:2px;margin-top:.75rem;grid-template-columns:repeat(1,1fr)}.k-block-types .k-button{display:flex;align-items:top;background:rgba(0,0,0,.5);width:100%;text-align:left;padding:0 .75rem 0 0;line-height:1.5em}.k-block-types .k-button:focus{outline:2px solid #c6d49d}.k-block-types .k-button .k-button-text{padding:.5rem 0 .5rem .5rem}.k-block-types .k-button .k-icon{width:38px;height:38px}.k-block-title{display:flex;align-items:center;min-width:0;padding-right:.75rem;font-size:.875rem;line-height:1}.k-block-icon{width:1rem;color:#999}.k-block-icon,.k-block-name{margin-right:.5rem}.k-block-label{color:#777;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.k-block-type-code-editor{position:relative;font-size:.875rem;line-height:1.5em;background:#000;border-radius:.25rem;padding:.5rem .75rem 3rem;color:#fff;font-family:SFMono-Regular,Consolas,Liberation Mono,Menlo,Courier,monospace}.k-block-type-code-editor .k-editor{white-space:pre-wrap;line-height:1.75em}.k-block-type-code-editor-language{font-size:.875rem;position:absolute;right:0;bottom:0}.k-block-type-code-editor-language .k-icon{position:absolute;top:0;left:0;height:1.5rem;display:flex;width:2rem;z-index:0}.k-block-type-code-editor-language .k-select-input{position:relative;padding:.325rem .75rem .5rem 2rem;z-index:1;font-size:.75rem}.k-block-type-default .k-block-title{line-height:1.5em}.k-block-type-gallery ul{display:grid;grid-gap:.75rem;grid-template-columns:repeat(auto-fit,minmax(6rem,1fr));line-height:0;align-items:center;justify-content:center;cursor:pointer}.k-block-type-gallery li:empty{padding-bottom:100%;background:#efefef}.k-block-type-gallery li{display:flex;position:relative;align-items:center;justify-content:center}.k-block-type-gallery li img{flex-grow:1;max-width:100%}.k-block-type-heading-input{font-weight:600;line-height:1.25em}.k-block-type-heading-input[data-level=h1]{font-size:1.875rem;line-height:1.125em}.k-block-type-heading-input[data-level=h2]{font-size:1.5rem}.k-block-type-heading-input[data-level=h3]{font-size:1.25rem}.k-block-type-heading-input[data-level=h4]{font-size:1.125rem}.k-block-type-heading-input[data-level=h5]{line-height:1.5em;font-size:1rem}.k-block-type-heading-input[data-level=h6]{line-height:1.5em;font-size:.875rem}.k-block-type-heading-input .ProseMirror strong{font-weight:700}.k-block-type-image .k-block-figure-container{display:block;text-align:center;line-height:0}.k-block-type-image-auto{max-width:100%;max-height:30rem}.k-block-type-markdown-input{position:relative;font-size:.875rem;line-height:1.5em;background:#efefef;border-radius:.25rem;padding:.5rem .5rem 0;font-family:SFMono-Regular,Consolas,Liberation Mono,Menlo,Courier,monospace}.k-block-type-quote-editor{padding-left:1rem;border-left:2px solid #000}.k-block-type-quote-text{font-size:1.25rem;margin-bottom:.25rem;line-height:1.25em}.k-block-type-quote-citation{font-style:italic;font-size:.875rem;color:#777}.k-block-type-table-preview{cursor:pointer;width:100%;border:1px solid #ddd;border-spacing:0;border-radius:.125rem;overflow:hidden;table-layout:fixed}.k-block-type-table-preview td,.k-block-type-table-preview th{text-align:left;line-height:1.5em;padding:.5rem .75rem;font-size:.875rem;border-bottom:1px solid #ddd}.k-block-type-table-preview th{background:#f7f7f7;font-family:SFMono-Regular,Consolas,Liberation Mono,Menlo,Courier,monospace;font-size:.75rem}.k-block-type-table-preview tr:last-child td{border-bottom:0}.k-block-type-table-preview [data-align=left]{text-align:left}.k-block-type-table-preview [data-align=right]{text-align:right}.k-block-type-table-preview [data-align=center]{text-align:center}.k-block-type-table-preview-empty{color:#777;font-size:.875rem}.k-block-type-text-input{font-size:1rem;line-height:1.5em} \ No newline at end of file diff --git a/kirby/panel/dist/favicon.png b/kirby/panel/dist/favicon.png new file mode 100644 index 0000000000000000000000000000000000000000..ecf0cf8d60e963463e558d3aefbbd532d676566e GIT binary patch literal 539 zcmV+$0_6RPP)Fp|=ykZ#eI$qt+0+(tJc@4BUyZNbtdTnW7Ue zj`Lb*_W2_5Tx=kw**3Vd^FhyE1y^2TprZL8M>ro&OrP(BlAjb@aMnLvJNdawU;7E)&0!b~~Qh?=C z%B_HJYw=s;T42I`DG!4Ey*|ozV5iKQoIu^rM7aXIzwBD)ww@HwrUfP`y7pc`_SYD^ z=0;%aY>G}`uQO|&U@o_ZYpQsDojZA+);n-5{2ljxA>p-{iQcYc6kzG?QtAS{a;4^K z-JFl;x)xo#h=rrGFdxku?oc*b2GF_fxKMnFDJr={xx$Jjuaiad!KXVCqSn-7i|X`t dTw&!t{{jk>)XmVGi~axr002ovPDHLkV1jM>_2d8m literal 0 HcmV?d00001 diff --git a/kirby/panel/dist/favicon.svg b/kirby/panel/dist/favicon.svg new file mode 100644 index 0000000..5b59d61 --- /dev/null +++ b/kirby/panel/dist/favicon.svg @@ -0,0 +1,9 @@ + + + + \ No newline at end of file diff --git a/kirby/panel/dist/img/icons.svg b/kirby/panel/dist/img/icons.svg new file mode 100644 index 0000000..34bb1f0 --- /dev/null +++ b/kirby/panel/dist/img/icons.svg @@ -0,0 +1,449 @@ + diff --git a/kirby/panel/dist/js/app.js b/kirby/panel/dist/js/app.js new file mode 100644 index 0000000..6ff15a9 --- /dev/null +++ b/kirby/panel/dist/js/app.js @@ -0,0 +1 @@ +(function(t){function e(e){for(var i,r,o=e[0],l=e[1],c=e[2],d=0,p=[];d{this.$events.$emit(t)})),!1!==Object.prototype.hasOwnProperty.call(t,"emit")&&!1===t.emit||this.$emit("success")}}},f={mixins:[m],data(){return{registration:{license:null,email:null}}},computed:{fields(){return{license:{label:this.$t("license.register.label"),type:"text",required:!0,counter:!1,placeholder:"K3-",help:this.$t("license.register.help")},email:{label:this.$t("email"),type:"email",required:!0,counter:!1}}}},methods:{async submit(){try{await this.$api.system.register(this.registration),this.$store.dispatch("system/register",this.registration.license),this.success({message:this.$t("license.register.success")})}catch(t){this.$refs.dialog.error(t.message)}}}},g=f,b=Object(c["a"])(g,p,h,!1,null,null,null),k=b.exports;const v=window.panel||{},$={assets:"@/assets",api:"/api",site:Object({NODE_ENV:"production",BASE_URL:"/"}).VUE_APP_DEV_SERVER,url:"/",debug:!0,translation:"en",search:{limit:10}};var y,_,w,x,O={...$,...v},C=t=>{const e=async({endpoint:e,query:s,limit:i,fields:n,map:a})=>{const r=await t.$api.get(e,{q:s,limit:i||10,select:["id",...n,"panelIcon","panelImage"]});return r.data.map(t=>({id:t.id,icon:{...t.panelIcon,back:"black",color:"#fff"},image:{...t.panelImage,back:"pattern",cover:!0},...a(t)}))};return{pages:{label:t.$t("pages"),icon:"page",search:async s=>e({...s,endpoint:"site/search",fields:["title"],map:e=>({title:e.title,link:t.$api.pages.link(e.id),info:e.id})})},files:{label:t.$t("files"),icon:"image",search:async s=>e({...s,endpoint:"files/search",fields:["filename","parent"],map:e=>({title:e.filename,link:t.$api.files.link(t.$api.pages.url(e.parent.id),e.filename),info:e.id})})},users:{label:t.$t("users"),icon:"users",search:async s=>e({...s,endpoint:"users/search",fields:["name","email"],map:e=>({title:e.name||e.email,link:t.$api.users.link(e.id),info:e.email,icon:{back:"black",type:"user"}})})}}},S={name:"App",components:{"k-icons":d,"k-registration":k},data(){return{offline:!1,dragging:!1,debug:O.debug}},computed:{inside(){return!(this.$route.meta.outside||!this.$store.state.user.current)},defaultTranslation(){return!!this.$store.state.languages.current&&this.$store.state.languages.current===this.$store.state.languages.default},fatal(){return this.$store.state.fatal},searchType(){return"users"===this.$store.state.view?"users":"pages"},searchTypes(){return C(this)},translation(){return!!this.$store.state.languages.current&&this.$store.state.languages.current.code}},watch:{fatal(t){null!==t&&this.$nextTick(()=>{try{let e=this.$refs.fatal.contentWindow.document;e.open(),e.write(t),e.close()}catch(e){console.error(e)}})}},created(){this.$events.$on("offline",this.isOffline),this.$events.$on("online",this.isOnline),this.$events.$on("drop",this.drop)},destroyed(){this.$events.$off("offline",this.isOffline),this.$events.$off("online",this.isOnline),this.$events.$off("drop",this.drop)},methods:{drop(){this.$store.dispatch("drag",null)},isOnline(){this.offline=!1},isOffline(){!1===this.$store.state.system.info.isLocal&&(this.offline=!0)}}},E=S,j=(s("5c0b"),Object(c["a"])(E,i,n,!1,null,null,null)),T=j.exports,L=t=>({async login(e){const s={long:e.remember||!1,email:e.email,password:e.password};return await t.post("auth/login",s)},async logout(){return t.post("auth/logout")},async user(e){return t.get("auth",e)},async verifyCode(e){return await t.post("auth/code",{code:e})}}),I=s("a026"),A=t=>({breadcrumb(e,s){let i=null,n=[];switch(s){case"UserFile":n.push({label:e.parent.username,link:t.users.link(e.parent.id)}),i="users/"+e.parent.id;break;case"SiteFile":i="site";break;case"PageFile":n=e.parents.map(e=>({label:e.title,link:t.pages.link(e.id)})),i=t.pages.url(e.parent.id);break}return n.push({label:e.filename,link:this.link(i,e.filename)}),n},async changeName(e,s,i){return t.patch(e+"/files/"+s+"/name",{name:i})},async delete(e,s){return t.delete(e+"/files/"+s)},async get(e,s,i){let n=await t.get(e+"/files/"+s,i);return!0===Array.isArray(n.content)&&(n.content={}),n},link(t,e,s){return"/"+this.url(t,e,s)},async options(e,s,i,n=!0){const a=await t.get(this.url(e,s),{select:"options"}),r=a.options;let o=[];return"list"===i&&(o.push({click:"download",icon:"open",text:I["a"].i18n.translate("open")}),o.push("-")),o.push({click:"rename",icon:"title",text:I["a"].i18n.translate("rename"),disabled:!r.changeName}),o.push({click:"replace",icon:"upload",text:I["a"].i18n.translate("replace"),disabled:!r.replace}),"list"===i&&(o.push("-"),o.push({click:"sort",icon:"sort",text:I["a"].i18n.translate("file.sort"),disabled:!(r.update&&n)})),o.push("-"),o.push({click:"remove",icon:"trash",text:I["a"].i18n.translate("delete"),disabled:!r.delete}),o},async update(e,s,i){return t.patch(e+"/files/"+s,i)},url(t,e,s){let i=t+"/files/"+e;return s&&(i+="/"+s),i}}),B=t=>({async create(e){return await t.post("languages",e)},async delete(e){return t.delete("languages/"+e)},async get(e){return await t.get("languages/"+e)},async list(){return await t.get("languages")},async update(e,s){return t.patch("languages/"+e,s)}}),M=t=>({async blueprint(e){return t.get("pages/"+this.id(e)+"/blueprint")},async blueprints(e,s){return t.get("pages/"+this.id(e)+"/blueprints",{section:s})},breadcrumb(t,e=!0){var s=t.parents.map(t=>({label:t.title,link:this.link(t.id)}));return!0===e&&s.push({label:t.title,link:this.link(t.id)}),s},async changeSlug(e,s){return t.patch("pages/"+this.id(e)+"/slug",{slug:s})},async changeStatus(e,s,i){return t.patch("pages/"+this.id(e)+"/status",{status:s,position:i})},async changeTemplate(e,s){return t.patch("pages/"+this.id(e)+"/template",{template:s})},async changeTitle(e,s){return t.patch("pages/"+this.id(e)+"/title",{title:s})},async children(e,s){return t.post("pages/"+this.id(e)+"/children/search",s)},async create(e,s){return null===e||"/"===e?t.post("site/children",s):t.post("pages/"+this.id(e)+"/children",s)},async delete(e,s){return t.delete("pages/"+this.id(e),s)},async duplicate(e,s,i){return t.post("pages/"+this.id(e)+"/duplicate",{slug:s,children:i.children||!1,files:i.files||!1})},async get(e,s){let i=await t.get("pages/"+this.id(e),s);return!0===Array.isArray(i.content)&&(i.content={}),i},id(t){return t.replace(/\//g,"+")},async files(e,s){return t.post("pages/"+this.id(e)+"/files/search",s)},link(t){return"/"+this.url(t)},async options(e,s="view",i=!0){const n=await t.get(this.url(e),{select:"options"}),a=n.options;let r=[];return"list"===s&&(r.push({click:"preview",icon:"open",text:I["a"].i18n.translate("open"),disabled:!1===a.preview}),r.push("-")),r.push({click:"rename",icon:"title",text:I["a"].i18n.translate("rename"),disabled:!a.changeTitle}),r.push({click:"duplicate",icon:"copy",text:I["a"].i18n.translate("duplicate"),disabled:!a.duplicate}),r.push("-"),r.push({click:"url",icon:"url",text:I["a"].i18n.translate("page.changeSlug"),disabled:!a.changeSlug}),r.push({click:"status",icon:"preview",text:I["a"].i18n.translate("page.changeStatus"),disabled:!a.changeStatus}),"list"===s&&r.push({click:"sort",icon:"sort",text:I["a"].i18n.translate("page.sort"),disabled:!(a.sort&&i)}),r.push({click:"template",icon:"template",text:I["a"].i18n.translate("page.changeTemplate"),disabled:!a.changeTemplate}),r.push("-"),r.push({click:"remove",icon:"trash",text:I["a"].i18n.translate("delete"),disabled:!a.delete}),r},async preview(t){const e=await this.get(this.id(t),{select:"previewUrl"});return e.previewUrl},async search(e,s){return e?t.post("pages/"+this.id(e)+"/children/search?select=id,title,hasChildren",s):t.post("site/children/search?select=id,title,hasChildren",s)},async update(e,s){return t.patch("pages/"+this.id(e),s)},url(t,e){let s=null===t?"pages":"pages/"+t.replace(/\//g,"+");return e&&(s+="/"+e),s}}),D=t=>({running:0,async request(e,s,i=!1){s=Object.assign(s||{},{credentials:"same-origin",cache:"no-store",headers:{"x-requested-with":"xmlhttprequest","content-type":"application/json",...s.headers}}),t.methodOverwrite&&"GET"!==s.method&&"POST"!==s.method&&(s.headers["x-http-method-override"]=s.method,s.method="POST"),s=t.onPrepare(s);const n=e+"/"+JSON.stringify(s);t.onStart(n,i),this.running++;const a=await fetch([t.endpoint,e].join(t.endpoint.endsWith("/")||e.startsWith("/")?"":"/"),s),r=await a.text();try{let e;try{e=JSON.parse(r)}catch(o){return void t.onParserError(r)}if(a.status<200||a.status>299)throw e;if(e.status&&"error"===e.status)throw e;let s=e;return e.data&&e.type&&"model"===e.type&&(s=e.data),this.running--,t.onComplete(n),t.onSuccess(e),s}catch(o){throw this.running--,t.onComplete(n),t.onError(o),o}},async get(t,e,s,i=!1){return e&&(t+="?"+Object.keys(e).filter(t=>void 0!==e[t]&&null!==e[t]).map(t=>t+"="+e[t]).join("&")),this.request(t,Object.assign(s||{},{method:"GET"}),i)},async post(t,e,s,i="POST",n=!1){return this.request(t,Object.assign(s||{},{method:i,body:JSON.stringify(e)}),n)},async patch(t,e,s,i=!1){return this.post(t,e,s,"PATCH",i)},async delete(t,e,s,i=!1){return this.post(t,e,s,"DELETE",i)}}),N=t=>({async list(e){return t.get("roles",e)},async get(e){return t.get("roles/"+e)},async options(t){const e=await this.list(t);return e.data.map(t=>({info:t.description||`(${I["a"].i18n.translate("role.description.placeholder")})`,text:t.title,value:t.name}))}}),P=t=>({async get(e={view:"panel"}){return t.get("system",e)},async install(e){const s=await t.post("system/install",e);return s.user},async register(e){return t.post("system/register",e)}}),R=t=>({async blueprint(){return t.get("site/blueprint")},async blueprints(){return t.get("site/blueprints")},async changeTitle(e){return t.patch("site/title",{title:e})},async children(e){return t.post("site/children/search",e)},async get(e={view:"panel"}){return t.get("site",e)},async options(){const e=await t.get("site",{select:"options"}),s=e.options;let i=[];return i.push({click:"rename",icon:"title",text:I["a"].i18n.translate("rename"),disabled:!s.changeTitle}),i},async update(e){return t.post("site",e)}}),q=t=>({async list(){return t.get("translations")},async get(e){return t.get("translations/"+e)},async options(){const t=await this.list(),e=t.data.map(t=>({value:t.id,text:t.name}));return e}}),F=t=>({async blueprint(e){return t.get("users/"+e+"/blueprint")},async blueprints(e,s){return t.get("users/"+e+"/blueprints",{section:s})},breadcrumb(t){return[{link:"/users/"+t.id,label:t.username}]},async changeEmail(e,s){return t.patch("users/"+e+"/email",{email:s})},async changeLanguage(e,s){return t.patch("users/"+e+"/language",{language:s})},async changeName(e,s){return t.patch("users/"+e+"/name",{name:s})},async changePassword(e,s){return t.patch("users/"+e+"/password",{password:s})},async changeRole(e,s){return t.patch("users/"+e+"/role",{role:s})},async create(e){return t.post("users",e)},async delete(e){return t.delete("users/"+e)},async deleteAvatar(e){return t.delete("users/"+e+"/avatar")},link(t,e){return"/"+this.url(t,e)},async list(e){return t.post(this.url(null,"search"),e)},async get(e,s){return t.get("users/"+e,s)},async options(e){const s=await t.get(this.url(e),{select:"options"}),i=s.options;let n=[];return n.push({click:"rename",icon:"title",text:I["a"].i18n.translate("user.changeName"),disabled:!i.changeName}),n.push({click:"email",icon:"email",text:I["a"].i18n.translate("user.changeEmail"),disabled:!i.changeEmail}),n.push({click:"role",icon:"bolt",text:I["a"].i18n.translate("user.changeRole"),disabled:!i.changeRole}),n.push({click:"password",icon:"key",text:I["a"].i18n.translate("user.changePassword"),disabled:!i.changePassword}),n.push({click:"language",icon:"globe",text:I["a"].i18n.translate("user.changeLanguage"),disabled:!i.changeLanguage}),n.push({click:"remove",icon:"trash",text:I["a"].i18n.translate("user.delete"),disabled:!i.delete}),n},async roles(e){const s=await t.get(this.url(e,"roles"));return s.data.map(t=>({info:t.description||`(${I["a"].i18n.translate("role.description.placeholder")})`,text:t.title,value:t.name}))},async search(e){return t.post("users/search",e)},async update(e,s){return t.patch("users/"+e,s)},url(t,e){let s=t?"users/"+t:"users";return e&&(s+="/"+e),s}}),U=(t={})=>{const e={endpoint:"/api",methodOverwrite:!0,onPrepare(t){return t},onStart(){},onComplete(){},onSuccess(){},onError(t){throw window.console.log(t.message),t}},s={...e,...t.config||{}};let i={...s,...D(s),...t};return i.auth=L(i),i.files=A(i),i.languages=B(i),i.pages=M(i),i.roles=N(i),i.system=P(i),i.site=R(i),i.translations=q(i),i.users=F(i),i.files.rename=i.files.changeName,i.pages.slug=i.pages.changeSlug,i.pages.status=i.pages.changeStatus,i.pages.template=i.pages.changeTemplate,i.pages.title=i.pages.changeTitle,i.site.title=i.site.changeTitle,i.system.info=i.system.get,i},H={install(t,e){t.prototype.$api=t.$api=U({config:{endpoint:O.api,onComplete:s=>{t.$api.requests=t.$api.requests.filter(t=>t!==s),0===t.$api.requests.length&&e.dispatch("isLoading",!1)},onError:t=>{O.debug&&window.console.error(t),403!==t.code||"Unauthenticated"!==t.message&&"access.panel"!==t.key||e.dispatch("user/logout",!0)},onParserError:t=>{throw e.dispatch("fatal",t),new Error("The JSON response from the API could not be parsed")},onPrepare:t=>(e.state.languages.current&&(t.headers["x-language"]=e.state.languages.current.code),t.headers["x-csrf"]=window.panel.csrf,t),onStart:(s,i=!1)=>{!1===i&&e.dispatch("isLoading",!0),t.$api.requests.push(s)},onSuccess:()=>{clearInterval(t.$api.ping),t.$api.ping=setInterval(t.$api.auth.user,3e5)}},ping:null,requests:[]}),t.$api.ping=setInterval(t.$api.auth.user,3e5)}},z={install(t){t.filter("t",(function(t){return t}))}},K={install(t){t.prototype.$events=new t({data(){return{entered:null}},created(){window.addEventListener("online",this.online),window.addEventListener("offline",this.offline),window.addEventListener("dragenter",this.dragenter,!1),window.addEventListener("dragover",this.prevent,!1),window.addEventListener("dragexit",this.prevent,!1),window.addEventListener("dragleave",this.dragleave,!1),window.addEventListener("drop",this.drop,!1),window.addEventListener("keydown",this.keydown,!1),window.addEventListener("keyup",this.keyup,!1),document.addEventListener("click",this.click,!1)},destroyed(){window.removeEventListener("online",this.online),window.removeEventListener("offline",this.offline),window.removeEventListener("dragenter",this.dragenter,!1),window.removeEventListener("dragover",this.prevent,!1),window.removeEventListener("dragexit",this.prevent,!1),window.removeEventListener("dragleave",this.dragleave,!1),window.removeEventListener("drop",this.drop,!1),window.removeEventListener("keydown",this.keydown,!1),window.removeEventListener("keyup",this.keyup,!1),document.removeEventListener("click",this.click,!1)},methods:{click(t){this.$emit("click",t)},drop(t){this.prevent(t),this.$emit("drop",t)},dragenter(t){this.entered=t.target,this.prevent(t),this.$emit("dragenter",t)},dragleave(t){this.prevent(t),this.entered===t.target&&this.$emit("dragleave",t)},keydown(t){let e=["keydown"];(t.metaKey||t.ctrlKey)&&e.push("cmd"),!0===t.altKey&&e.push("alt"),!0===t.shiftKey&&e.push("shift");let s=this.$helper.string.lcfirst(t.key);const i={escape:"esc",arrowUp:"up",arrowDown:"down",arrowLeft:"left",arrowRight:"right"};i[s]&&(s=i[s]),!1===["alt","control","shift","meta"].includes(s)&&e.push(s),this.$emit(e.join("."),t),this.$emit("keydown",t)},keyup(t){this.$emit("keyup",t)},online(t){this.$emit("online",t)},offline(t){this.$emit("offline",t)},prevent(t){t.stopPropagation(),t.preventDefault()}}})}},G=s("1dce"),W=s.n(G),Y=function(t){if(void 0!==t)return JSON.parse(JSON.stringify(t))},V=(t,e)=>{var s=null;return function(){var i=this,n=arguments;clearTimeout(s),s=setTimeout((function(){t.apply(i,n)}),e)}},J=t=>void 0!==I["a"].options.components[t],Z=t=>!!t.dataTransfer&&(!!t.dataTransfer.types&&(!0===t.dataTransfer.types.includes("Files")&&!1===t.dataTransfer.types.includes("text/plain"))),X=(t,e)=>{t=String(t);let s="";e=(e||2)-t.length;while(s.length{const e=String(t).split("/");if(2!==e.length)return"100%";const s=Number(e[0]),i=Number(e[1]);let n=100;return 0!==s&&0!==i&&(n=100/s*i),n+"%"},tt=(s("86c7"),(t,e=[],s="")=>{const i="-";return s="a-z0-9"+s,t=t.trim().toLowerCase(),e.forEach(e=>{e&&Object.keys(e).forEach(s=>{const i="/"!==s.substr(0,1),n=s.substring(1,s.length-1),a=i?s:n;t=t.replace(new RegExp(RegExp.escape(a),"g"),e[s])})}),t=t.replace("/[^\t\n\r -~]/",""),t=t.replace(new RegExp("[^"+s+"]","ig"),i),t=t.replace(new RegExp("["+RegExp.escape(i)+"]{2,}","g"),i),t=t.replace("/",i),t=t.replace(new RegExp("^[^"+s+"]+","g"),""),t=t.replace(new RegExp("[^"+s+"]+$","g"),""),t}),et=t=>{t=t||{};var e=t.desc?-1:1,s=-e,i=/^0/,n=/\s+/g,a=/^\s+|\s+$/g,r=/[^\x00-\x80]/,o=/^0x[0-9a-f]+$/i,l=/(0x[\da-fA-F]+|(^[\+\-]?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?(?=\D|\s|$))|\d+)/g,c=/(^([\w ]+,?[\w ]+)?[\w ]+,?[\w ]+\d+:\d+(:\d+)?[\w ]?|^\d{1,4}[\/\-]\d{1,4}[\/\-]\d{1,4}|^\w+, \w+ \d+, \d{4})/,u=t.insensitive?function(t){return d(""+t).replace(a,"")}:function(t){return(""+t).replace(a,"")};function d(t){return t.toLocaleLowerCase?t.toLocaleLowerCase():t.toLowerCase()}function p(t){return t.replace(l,"\0$1\0").replace(/\0$/,"").replace(/^\0/,"").split("\0")}function h(t,e){return(!t.match(i)||1===e)&&parseFloat(t)||t.replace(n," ").replace(a,"")||0}return function(t,i){var n=u(t),a=u(i);if(!n&&!a)return 0;if(!n&&a)return s;if(n&&!a)return e;var l=p(n),d=p(a),m=parseInt(n.match(o),16)||1!==l.length&&Date.parse(n),f=parseInt(a.match(o),16)||m&&a.match(c)&&Date.parse(a)||null;if(f){if(mf)return e}for(var g=l.length,b=d.length,k=0,v=Math.max(g,b);k0)return e;if(_<0)return s;if(k===v-1)return 0}else{if($y)return e}}return 0}},st={camelToKebab(t){return t.replace(/([a-z0-9])([A-Z])/g,"$1-$2").toLowerCase()},escapeHTML(t){const e={"&":"&","<":"<",">":">",'"':""","'":"'","/":"/","`":"`","=":"="};return String(t).replace(/[&<>"'`=/]/g,t=>e[t])},hasEmoji(t){if("string"!==typeof t)return!1;const e=t.match(/(?:[\u2700-\u27bf]|(?:\ud83c[\udde6-\uddff]){2}|[\ud800-\udbff][\udc00-\udfff]|[\u0023-\u0039]\ufe0f?\u20e3|\u3299|\u3297|\u303d|\u3030|\u24c2|\ud83c[\udd70-\udd71]|\ud83c[\udd7e-\udd7f]|\ud83c\udd8e|\ud83c[\udd91-\udd9a]|\ud83c[\udde6-\uddff]|[\ud83c\ude01-\ude02]|\ud83c\ude1a|\ud83c\ude2f|[\ud83c\ude32-\ude3a]|[\ud83c\ude50-\ude51]|\u203c|\u2049|[\u25aa-\u25ab]|\u25b6|\u25c0|[\u25fb-\u25fe]|\u00a9|\u00ae|\u2122|\u2139|\ud83c\udc04|[\u2600-\u26FF]|\u2b05|\u2b06|\u2b07|\u2b1b|\u2b1c|\u2b50|\u2b55|\u231a|\u231b|\u2328|\u23cf|[\u23e9-\u23f3]|[\u23f8-\u23fa]|\ud83c\udccf|\u2934|\u2935|[\u2190-\u21ff])/i);return null!==e&&null!==e.length},lcfirst(t){const e=String(t);return e.charAt(0).toLowerCase()+e.substr(1)},random(t){let e="";const s="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789",i=s.length;for(var n=0;n]+)>)/gi,"")},template(t,e={}){const s="{{[ ]{0,}",i="[ ]{0,}}}";return Object.keys(e).forEach(n=>{t=t.replace(new RegExp(`${s}${n}${i}`,"gi"),e[n]||"…"),!0===Array.isArray(e[n])&&(t=t.replace(new RegExp(`${s}${n}.count${i}|${s}${n}.length${i}`,"gi"),e[n].length||0))}),t.replace(/{{.*}}/gi,"…")},ucfirst(t){const e=String(t);return e.charAt(0).toUpperCase()+e.substr(1)},ucwords(t){const e=String(t);return e.split(/ /g).map(t=>this.ucfirst(t)).join(" ")}},it=(t,e)=>{const s={url:"/",field:"file",method:"POST",accept:"text",attributes:{},complete:function(){},error:function(){},success:function(){},progress:function(){}},i=Object.assign(s,e),n=new FormData;n.append(i.field,t,t.name),i.attributes&&Object.keys(i.attributes).forEach(t=>{n.append(t,i.attributes[t])});const a=new XMLHttpRequest,r=e=>{if(!e.lengthComputable||!i.progress)return;let s=Math.max(0,Math.min(100,e.loaded/e.total*100));i.progress(a,t,Math.ceil(s))};a.upload.addEventListener("loadstart",r),a.upload.addEventListener("progress",r),a.addEventListener("load",e=>{let s=null;try{s=JSON.parse(e.target.response)}catch(n){s={status:"error",message:"The file could not be uploaded"}}s.status&&"error"===s.status?i.error(a,t,s):(i.success(a,t,s),i.progress(a,t,100))}),a.addEventListener("error",e=>{const s=JSON.parse(e.target.response);i.error(a,t,s),i.progress(a,t,100)}),a.open("POST",i.url,!0),i.headers&&Object.keys(i.headers).forEach(t=>{const e=i.headers[t];a.setRequestHeader(t,e)}),a.send(n)},nt=function(){var t,e,s="";for(t=0;t<32;t++)e=16*Math.random()|0,8!=t&&12!=t&&16!=t&&20!=t||(s+="-"),s+=(12==t?4:16==t?3&e|8:e).toString(16);return s},at={datetime(t,e,s,i,n="day"){let a=t.$library.dayjs.utc(e);return a.isValid()||(a=t.$library.dayjs.utc(e,"HH:mm:ss")),s?!e||!a.isValid()||(s=t.$library.dayjs.utc(s),a.isSame(s,n)||a[i](s,n)):e&&a.isValid()}},rt={install(t){Array.prototype.sortBy=function(e){const s=t.prototype.$helper.sort(),i=e.split(" "),n=i[0],a=i[1]||"asc";return this.sort((t,e)=>{const i=String(t[n]).toLowerCase(),r=String(e[n]).toLowerCase();return"desc"===a?s(r,i):s(i,r)})},t.prototype.$helper={clone:Y,isComponent:J,isUploadEvent:Z,debounce:V,pad:X,ratio:Q,slug:tt,sort:et,string:st,upload:it,uuid:nt,validate:at},t.prototype.$esc=st.escapeHTML}},ot=(s("64e4"),{}),lt=Object(c["a"])(ot,y,_,!1,null,null,null),ct=(lt.exports,function(){var t=this,e=t.$createElement,s=t._self._c||e;return s("k-overlay",{ref:"overlay",attrs:{autofocus:t.autofocus,centered:!0},on:{ready:function(e){return t.$emit("ready")}}},[s("div",{ref:"dialog",staticClass:"k-dialog",class:t.$vnode.data.staticClass,attrs:{"data-size":t.size},on:{mousedown:function(t){t.stopPropagation()}}},[t.notification?s("div",{staticClass:"k-dialog-notification",attrs:{"data-theme":t.notification.type}},[s("p",[t._v(t._s(t.notification.message))]),s("k-button",{attrs:{icon:"cancel"},on:{click:function(e){t.notification=null}}})],1):t._e(),s("div",{staticClass:"k-dialog-body"},[t._t("default")],2),t.$slots["footer"]||t.cancelButton||t.submitButton?s("footer",{staticClass:"k-dialog-footer"},[t._t("footer",[s("k-button-group",[s("span",[t.cancelButton?s("k-button",{staticClass:"k-dialog-button-cancel",attrs:{icon:"cancel"},on:{click:t.cancel}},[t._v(" "+t._s(t.cancelButtonLabel)+" ")]):t._e()],1),s("span",[t.submitButtonConfig?s("k-button",{staticClass:"k-dialog-button-submit",attrs:{icon:t.icon,theme:t.theme},on:{click:t.submit}},[t._v(" "+t._s(t.submitButtonLabel)+" ")]):t._e()],1)])])],2):t._e()])])}),ut=[],dt={props:{autofocus:{type:Boolean,default:!0},cancelButton:{type:[String,Boolean],default:!0},icon:{type:String,default:"check"},size:{type:String,default:"default"},submitButton:{type:[String,Boolean],default:!0},theme:String,visible:Boolean},data(){return{notification:null}},computed:{cancelButtonLabel(){return!1!==this.cancelButton&&(!0===this.cancelButton||0===this.cancelButton.length?this.$t("cancel"):this.cancelButton)},submitButtonConfig(){return void 0!==this.$attrs["button"]?this.$attrs["button"]:void 0===this.submitButton||this.submitButton},submitButtonLabel(){return!0===this.submitButton||0===this.submitButton.length?this.$t("confirm"):this.submitButton}},created(){this.$events.$on("keydown.esc",this.close,!1)},destroyed(){this.$events.$off("keydown.esc",this.close,!1)},mounted(){this.visible&&this.$nextTick(this.open)},methods:{open(){this.$store.dispatch("dialog",!0),this.notification=null,this.$refs.overlay.open(),this.$emit("open"),this.$events.$on("keydown.esc",this.close)},close(){this.notification=null,this.$refs.overlay&&this.$refs.overlay.close(),this.$emit("close"),this.$events.$off("keydown.esc",this.close),this.$store.dispatch("dialog",!1)},cancel(){this.$emit("cancel"),this.close()},focus(){if(this.$refs.dialog&&this.$refs.dialog.querySelector){const t=this.$refs.dialog.querySelector(".k-dialog-button-cancel");t&&"function"===typeof t.focus&&t.focus()}},error(t){this.notification={message:t,type:"error"}},submit(){this.$emit("submit")},success(t){this.notification={message:t,type:"success"}}}},pt=dt,ht=(s("a5f3"),Object(c["a"])(pt,ct,ut,!1,null,null,null)),mt=ht.exports,ft=function(){var t=this,e=t.$createElement,s=t._self._c||e;return t.notification?s("k-dialog",{ref:"dialog",staticClass:"k-error-dialog",attrs:{"cancel-button":!1,visible:!0},on:{close:t.exit,submit:function(e){return t.$refs.dialog.close()}}},[s("k-text",[t._v(t._s(t.notification.message))]),t.notification.details&&Object.keys(t.notification.details).length?s("dl",{staticClass:"k-error-details"},[t._l(t.notification.details,(function(e,i){return[s("dt",{key:"detail-label-"+i},[t._v(" "+t._s(e.label)+" ")]),s("dd",{key:"detail-message-"+i},["object"===typeof e.message?[s("ul",t._l(e.message,(function(e,i){return s("li",{key:i},[t._v(" "+t._s(e)+" ")])})),0)]:[t._v(" "+t._s(e.message)+" ")]],2)]}))],2):t._e()],1):t._e()},gt=[],bt={mixins:[m],computed:{notification(){let t=this.$store.state.notification;return"error"===t.type?t:null}},methods:{enter(){this.$nextTick(()=>{this.$el&&this.$el.querySelector&&this.$el.querySelector(".k-dialog-footer .k-button").focus()})},exit(){this.$store.dispatch("notification/close")}}},kt=bt,vt=(s("7737"),Object(c["a"])(kt,ft,gt,!1,null,null,null)),$t=vt.exports,yt=function(){var t=this,e=t.$createElement,s=t._self._c||e;return s("k-remove-dialog",{ref:"dialog",attrs:{text:t.$t("file.delete.confirm",{filename:t.$esc(t.filename)})},on:{submit:t.submit}})},_t=[],wt={mixins:[m],data(){return{id:null,parent:null,filename:null}},methods:{async open(t,e){try{const s=await this.$api.files.get(t,e);this.id=s.id,this.filename=s.filename,this.parent=t,this.$refs.dialog.open()}catch(s){this.$store.dispatch("notification/error",s)}},async submit(){try{await this.$api.files.delete(this.parent,this.filename),this.$store.dispatch("content/remove","files/"+this.id),this.$store.dispatch("notification/success",":)"),this.$events.$emit("file.delete",this.id),this.$emit("success"),this.$refs.dialog.close()}catch(t){this.$refs.dialog.error(t.message)}}}},xt=wt,Ot=Object(c["a"])(xt,yt,_t,!1,null,null,null),Ct=Ot.exports,St=function(){var t=this,e=t.$createElement,s=t._self._c||e;return s("k-form-dialog",{ref:"dialog",attrs:{fields:t.fields,"submit-button":t.$t("rename")},on:{input:function(e){t.file.name=t.sluggify(t.file.name)},submit:t.submit},model:{value:t.file,callback:function(e){t.file=e},expression:"file"}})},Et=[],jt={mixins:[m],data(){return{parent:null,file:{id:null,name:null,filename:null,extension:null}}},computed:{fields(){return{name:{label:this.$t("name"),type:"text",required:!0,icon:"title",after:"."+this.file.extension,preselect:!0}}},slugs(){return this.$store.state.languages.default?this.$store.state.languages.default.rules:this.system.slugs},system(){return this.$store.state.system.info}},methods:{async open(t,e){try{this.file=await this.$api.files.get(t,e,{select:["id","filename","name","extension"]}),this.parent=t,this.$refs.dialog.open()}catch(s){this.$store.dispatch("notification/error",s)}},sluggify(t){return this.$helper.slug(t,[this.slugs,this.system.ascii],"@._-")},async submit(){if(this.file.name=this.file.name.trim(),0!==this.file.name.length)try{const t=await this.$api.files.changeName(this.parent,this.file.filename,this.file.name);this.$store.dispatch("content/move",["files/"+this.file.id,"files/"+t.id]),this.$store.dispatch("notification/success",":)"),this.$emit("success",t),this.$events.$emit("file.changeName",t),this.$refs.dialog.close()}catch(t){this.$refs.dialog.error(t.message)}else this.$refs.dialog.error(this.$t("error.file.changeName.empty"))}}},Tt=jt,Lt=Object(c["a"])(Tt,St,Et,!1,null,null,null),It=Lt.exports,At=function(){var t=this,e=t.$createElement,s=t._self._c||e;return s("k-form-dialog",{ref:"dialog",attrs:{fields:t.fields,"submit-button":t.$t("change")},on:{submit:t.submit},model:{value:t.form,callback:function(e){t.form=e},expression:"form"}})},Bt=[],Mt={mixins:[m],data(){return{file:{id:null},form:{position:null},siblings:[],api:null}},computed:{fields(){return{position:{name:"position",label:this.$t("file.sort"),type:"select",empty:!1,options:this.sortingOptions}}},sortingOptions(){let t=[],e=0;return this.siblings.forEach(s=>{if(s.id===this.file.id||s.num<1)return!1;e++,t.push({value:e,text:e}),t.push({value:s.id,text:s.filename,disabled:!0})}),t.push({value:e+1,text:e+1}),t}},methods:{async open(t,e,s){try{this.file=e;const i=await this.$api.post(t+"/files/search");this.siblings=i.data,this.form.position=this.siblings.findIndex(t=>t.id===e.id)+1,this.api=s,this.$refs.dialog.open()}catch(i){this.$store.dispatch("notification/error",i)}},async submit(){try{const t=this.siblings.findIndex(t=>t.id===this.file.id),e=this.form.position-1,s=this.$helper.clone(this.siblings);s.splice(t,1),s.splice(e,0,this.file),await this.$api.patch(this.api+"/files/sort",{files:s.map(t=>t.id),index:0}),this.success({message:":)",event:"file.sort"})}catch(t){this.$refs.dialog.error(t.message)}}}},Dt=Mt,Nt=Object(c["a"])(Dt,At,Bt,!1,null,null,null),Pt=Nt.exports,Rt=function(){var t=this,e=t.$createElement,s=t._self._c||e;return s("k-dialog",{ref:"dialog",staticClass:"k-files-dialog",attrs:{size:"medium"},on:{cancel:function(e){return t.$emit("cancel")},submit:t.submit}},[t.issue?[s("k-box",{attrs:{text:t.issue,html:!1,theme:"negative"}})]:[t.options.search?s("k-input",{staticClass:"k-dialog-search",attrs:{autofocus:!0,placeholder:t.$t("search")+" …",type:"text",icon:"search"},model:{value:t.search,callback:function(e){t.search=e},expression:"search"}}):t._e(),t.models.length?[s("k-list",t._l(t.models,(function(e){return s("k-list-item",{key:e.id,attrs:{text:e.text,info:e.info,image:e.image,icon:e.icon},on:{click:function(s){return t.toggle(e)}}},[t.isSelected(e)?s("k-button",{attrs:{slot:"options",autofocus:!0,icon:t.checkedIcon,tooltip:t.$t("remove"),theme:"positive"},slot:"options"}):s("k-button",{attrs:{slot:"options",autofocus:!0,tooltip:t.$t("select"),icon:"circle-outline"},slot:"options"})],1)})),1),s("k-pagination",t._b({staticClass:"k-dialog-pagination",attrs:{details:!0,dropdown:!1,align:"center"},on:{paginate:t.paginate}},"k-pagination",t.pagination,!1))]:s("k-empty",{attrs:{icon:"image"}},[t._v(" "+t._s(t.$t("dialog.files.empty"))+" ")])]],2)},qt=[],Ft={data(){return{models:[],issue:null,selected:{},options:{endpoint:null,max:null,multiple:!0,parent:null,selected:[],search:!0},search:null,pagination:{limit:20,page:1,total:0}}},computed:{multiple(){return!0===this.options.multiple&&1!==this.options.max},checkedIcon(){return!0===this.multiple?"check":"circle-filled"}},watch:{search(){this.updateSearch()}},created(){this.updateSearch=V(this.updateSearch,200)},methods:{fetch(){const t={page:this.pagination.page,search:this.search,...this.fetchData||{}};return this.$api.get(this.options.endpoint,t).then(t=>{this.models=t.data,this.pagination=t.pagination,this.onFetched&&this.onFetched(t)}).catch(t=>{this.models=[],this.issue=t.message})},open(t,e){this.pagination.page=0,this.search=null;let s=!0;Array.isArray(t)?(this.models=t,s=!1):(this.models=[],e=t),this.options={...this.options,...e},this.selected={},this.options.selected.forEach(t=>{this.$set(this.selected,t,{id:t})}),s?this.fetch().then(()=>{this.$refs.dialog.open()}):this.$refs.dialog.open()},paginate(t){this.pagination.page=t.page,this.pagination.limit=t.limit,this.fetch()},submit(){this.$emit("submit",Object.values(this.selected)),this.$refs.dialog.close()},isSelected(t){return void 0!==this.selected[t.id]},toggle(t){!1!==this.options.multiple&&1!==this.options.max||(this.selected={}),!0!==this.isSelected(t)?this.options.max&&this.options.max<=Object.keys(this.selected).length||this.$set(this.selected,t.id,t):this.$delete(this.selected,t.id)},updateSearch(){this.pagination.page=0,this.fetch()}}},Ut={mixins:[Ft]},Ht=Ut,zt=(s("bf53"),Object(c["a"])(Ht,Rt,qt,!1,null,null,null)),Kt=zt.exports,Gt=function(){var t=this,e=t.$createElement,s=t._self._c||e;return s("k-dialog",t._g(t._b({ref:"dialog"},"k-dialog",t.$props,!1),t.$listeners),[s("k-form",{ref:"form",attrs:{fields:t.fields,novalidate:t.novalidate},on:{input:function(e){return t.$emit("input",e)},submit:function(e){return t.$emit("submit",e)}},model:{value:t.value,callback:function(e){t.value=e},expression:"value"}})],1)},Wt=[],Yt={mixins:[m],props:{fields:{type:[Array,Object],default(){return[]}},novalidate:{type:Boolean,default:!0},size:{type:String,default:"medium"},submitButton:{type:[String,Boolean],default(){return this.$t("save")}},theme:{type:String,default:"positive"},value:{type:Object,default(){return{}}}}},Vt=Yt,Jt=Object(c["a"])(Vt,Gt,Wt,!1,null,null,null),Zt=Jt.exports,Xt=function(){var t=this,e=t.$createElement,s=t._self._c||e;return s("k-form-dialog",{ref:"dialog",attrs:{fields:t.fields,"submit-button":t.$t("language.create")},on:{submit:t.submit},model:{value:t.language,callback:function(e){t.language=e},expression:"language"}})},Qt=[],te={mixins:[m],data(){return{language:{name:"",code:"",direction:"ltr",locale:""}}},computed:{fields(){return{name:{label:this.$t("language.name"),type:"text",required:!0,icon:"title"},code:{label:this.$t("language.code"),type:"text",required:!0,counter:!1,icon:"globe",width:"1/2"},direction:{label:this.$t("language.direction"),type:"select",required:!0,empty:!1,options:[{value:"ltr",text:this.$t("language.direction.ltr")},{value:"rtl",text:this.$t("language.direction.rtl")}],width:"1/2"},locale:{label:this.$t("language.locale"),type:"text"}}},system(){return this.$store.state.system.info}},watch:{"language.name"(t){this.onNameChanges(t)},"language.code"(t){this.language.code=this.$helper.slug(t,[this.system.ascii]),this.onCodeChanges(this.language.code)}},methods:{onCodeChanges(t){if(!t)return this.language.locale=null;if(t.length>=2)if(-1!==t.indexOf("-")){let e=t.split("-"),s=[e[0],e[1].toUpperCase()];this.language.locale=s.join("_")}else{let e=this.$store.state.system.info.locales||[];e&&e[t]?this.language.locale=e[t]:this.language.locale=null}},onNameChanges(t){this.language.code=this.$helper.slug(t,[this.language.rules,this.system.ascii]).substr(0,2)},open(){this.language={name:"",code:"",direction:"ltr"},this.$refs.dialog.open()},async submit(){this.language.locale&&(this.language.locale=this.language.locale.trim()||null);try{await this.$api.languages.create({name:this.language.name,code:this.language.code,direction:this.language.direction,locale:this.language.locale}),this.$store.dispatch("languages/load"),this.success({message:":)",event:"language.create"})}catch(t){this.$refs.dialog.error(t.message)}}}},ee=te,se=Object(c["a"])(ee,Xt,Qt,!1,null,null,null),ie=se.exports,ne=function(){var t=this,e=t.$createElement,s=t._self._c||e;return s("k-remove-dialog",{ref:"dialog",attrs:{text:t.$t("language.delete.confirm",{name:t.$esc(t.language.name)})},on:{submit:t.submit}})},ae=[],re={mixins:[m],data(){return{language:{name:null}}},methods:{async open(t){try{this.language=await this.$api.languages.get(t),this.$refs.dialog.open()}catch(e){this.$store.dispatch("notification/error",e)}},async submit(){try{await this.$api.languages.delete(this.language.code),this.$store.dispatch("languages/load"),this.success({message:this.$t("language.deleted"),event:"language.delete"})}catch(t){this.$refs.dialog.error(t.message)}}}},oe=re,le=Object(c["a"])(oe,ne,ae,!1,null,null,null),ce=le.exports,ue=function(){var t=this,e=t.$createElement,s=t._self._c||e;return s("k-form-dialog",{ref:"dialog",attrs:{fields:t.fields,size:"medium"},on:{submit:t.submit},model:{value:t.language,callback:function(e){t.language=e},expression:"language"}})},de=[],pe={mixins:[ie],computed:{fields(){let t=ie.computed.fields.apply(this);return t.code.disabled=!0,"object"===typeof this.language.locale&&(t.locale={label:t.locale.label,type:"info",text:this.$t("language.locale.warning")}),t}},methods:{onCodeChanges(){return!1},onNameChanges(){return!1},async open(t){try{this.language=await this.$api.languages.get(t);const e=Object.keys(this.language.locale);1===e.length&&(this.language.locale=this.language.locale[e[0]]),this.$refs.dialog.open()}catch(e){this.$store.dispatch("notification/error",e)}},async submit(){if(0===this.language.name.length)return this.$refs.dialog.error(this.$t("error.language.name"));"string"===typeof this.language.locale&&(this.language.locale=this.language.locale.trim()||null);try{await this.$api.languages.update(this.language.code,{name:this.language.name,direction:this.language.direction,locale:this.language.locale}),this.$store.dispatch("languages/load"),this.success({message:this.$t("language.updated"),event:"language.update"})}catch(t){this.$refs.dialog.error(t.message)}}}},he=pe,me=Object(c["a"])(he,ue,de,!1,null,null,null),fe=me.exports,ge=function(){var t=this,e=t.$createElement,s=t._self._c||e;return s("k-form-dialog",{ref:"dialog",attrs:{fields:t.fields,"submit-button":t.$t("page.draft.create")},on:{submit:t.submit,close:t.reset},model:{value:t.page,callback:function(e){t.page=e},expression:"page"}})},be=[],ke={mixins:[m],data(){return{parent:null,section:null,templates:[],page:this.emptyForm()}},computed:{fields(){let t={title:{label:this.$t("title"),type:"text",required:!0,icon:"title"},slug:{label:this.$t("slug"),type:"text",required:!0,counter:!1,icon:"url"}};return(this.templates.length>1||O.debug)&&(t.template={name:"template",label:this.$t("template"),type:"select",disabled:1===this.templates.length,required:!0,icon:"code",empty:!1,options:this.templates}),t},slugs(){return this.$store.state.languages.default?this.$store.state.languages.default.rules:this.system.slugs},system(){return this.$store.state.system.info}},watch:{"page.title"(t){this.page.slug=this.$helper.slug(t,[this.slugs,this.system.ascii])}},methods:{emptyForm(){return{title:"",slug:"",template:null}},async open(t,e,s){this.parent=t,this.section=s;try{const t=await this.$api.get(e,{section:s});this.templates=t.map(t=>({value:t.name,text:t.title})),this.templates[0]&&(this.page.template=this.templates[0].value),this.$refs.dialog.open()}catch(i){this.$store.dispatch("notification/error",i)}},async submit(){if(this.page.title=this.page.title.trim(),0===this.page.title.length)return void this.$refs.dialog.error(this.$t("error.page.changeTitle.empty"));const t={template:this.page.template,slug:this.page.slug,content:{title:this.page.title}};try{const e=await this.$api.post(this.parent+"/children",t);this.success({route:this.$api.pages.link(e.id),message:":)",event:"page.create"})}catch(e){this.$refs.dialog.error(e.message)}},reset(){this.page=this.emptyForm()}}},ve=ke,$e=Object(c["a"])(ve,ge,be,!1,null,null,null),ye=$e.exports,_e=function(){var t=this,e=t.$createElement,s=t._self._c||e;return s("k-form-dialog",{ref:"dialog",attrs:{fields:t.fields,"submit-button":t.$t("duplicate")},on:{submit:t.submit},model:{value:t.page,callback:function(e){t.page=e},expression:"page"}})},we=[],xe={mixins:[m],data(){return{page:{children:!1,files:!1,hasChildren:!1,hasDrafts:!1,hasFiles:!1,id:null,slug:""}}},computed:{fields(){const t=this.page.hasChildren||this.page.hasDrafts,e=this.page.hasFiles;let s={slug:{label:this.$t("slug"),type:"text",required:!0,counter:!1,spellcheck:!1,icon:"url"}};return e&&(s.files={label:this.$t("page.duplicate.files"),type:"toggle",required:!0,width:t?"1/2":null}),t&&(s.children={label:this.$t("page.duplicate.pages"),type:"toggle",required:!0,width:e?"1/2":null}),s},slugs(){return this.$store.state.languages.default?this.$store.state.languages.default.rules:this.system.slugs},system(){return this.$store.state.system.info}},watch:{"page.slug"(t){this.page.slug=this.$helper.slug(t,[this.slugs,this.system.ascii])}},methods:{async open(t){try{const e=await this.$api.pages.get(t,{language:"@default",select:"id,slug,hasChildren,hasDrafts,hasFiles,title"});this.page.id=e.id,this.page.slug=e.slug+"-"+this.$helper.slug(this.$t("page.duplicate.appendix")),this.page.hasChildren=e.hasChildren,this.page.hasDrafts=e.hasDrafts,this.page.hasFiles=e.hasFiles,this.$refs.dialog.open()}catch(e){this.$store.dispatch("notification/error",e)}},async submit(){try{const t=await this.$api.pages.duplicate(this.page.id,this.page.slug,{children:this.page.children,files:this.page.files});this.success({route:this.$api.pages.link(t.id),message:":)",event:"page.duplicate"})}catch(t){this.$refs.dialog.error(t.message)}}}},Oe=xe,Ce=Object(c["a"])(Oe,_e,we,!1,null,null,null),Se=Ce.exports,Ee=function(){var t=this,e=t.$createElement,s=t._self._c||e;return s("k-remove-dialog",{ref:"dialog",attrs:{size:t.hasSubpages?"medium":"small"},on:{submit:t.submit,close:t.reset}},[t.page.hasChildren||t.page.hasDrafts?[s("k-text",{domProps:{innerHTML:t._s(t.$t("page.delete.confirm",{title:t.$esc(t.page.title)}))}}),s("div",{staticClass:"k-page-remove-warning"},[s("k-box",{attrs:{theme:"negative"},domProps:{innerHTML:t._s(t.$t("page.delete.confirm.subpages"))}})],1),t.hasSubpages?s("k-form",{attrs:{fields:t.fields},on:{submit:t.submit},model:{value:t.model,callback:function(e){t.model=e},expression:"model"}}):t._e()]:[s("k-text",{domProps:{innerHTML:t._s(t.$t("page.delete.confirm",{title:t.$esc(t.page.title)}))},on:{keydown:function(e){return!e.type.indexOf("key")&&t._k(e.keyCode,"enter",13,e.key,"Enter")?null:t.submit(e)}}})]],2)},je=[],Te={mixins:[m],data(){return{page:{title:null,hasChildren:!1,hasDrafts:!1},model:this.emptyForm()}},computed:{hasSubpages(){return this.page.hasChildren||this.page.hasDrafts},fields(){return{check:{label:this.$t("page.delete.confirm.title"),type:"text",counter:!1}}}},methods:{emptyForm(){return{check:null}},async open(t){try{this.page=await this.$api.pages.get(t,{select:"id, title, hasChildren, hasDrafts, parent"}),this.$refs.dialog.open()}catch(e){this.$store.dispatch("notification/error",e)}},async submit(){if(this.hasSubpages&&this.model.check!==this.page.title)return this.$refs.dialog.error(this.$t("error.page.delete.confirm"));try{await this.$api.pages.delete(this.page.id,{force:!0}),this.$store.dispatch("content/remove","pages/"+this.page.id);const t={message:":)",event:"page.delete"};this.$route.params.path&&this.page.id===this.$route.params.path.replace(/\+/g,"/")&&(this.page.parent?t.route=this.$api.pages.link(this.page.parent.id):t.route="/pages"),this.success(t)}catch(t){this.$refs.dialog.error(t.message)}},reset(){this.model=this.emptyForm()}}},Le=Te,Ie=(s("12fb"),Object(c["a"])(Le,Ee,je,!1,null,null,null)),Ae=Ie.exports,Be=function(){var t=this,e=t.$createElement,s=t._self._c||e;return s("k-dialog",{ref:"dialog",staticClass:"k-page-rename-dialog",attrs:{autofocus:!1,"submit-button":t.$t("change"),size:"medium",theme:"positive"},on:{ready:t.onReady,submit:function(e){return t.$refs.form.submit()}}},[s("k-form",{ref:"form",on:{submit:t.submit}},[s("k-text-field",t._b({ref:"title",model:{value:t.title,callback:function(e){t.title=e},expression:"title"}},"k-text-field",t.fields.title,!1)),s("k-text-field",t._b({ref:"slug",attrs:{value:t.slug},on:{input:function(e){return t.sluggify(e)}}},"k-text-field",t.fields.slug,!1),[s("k-button",{attrs:{slot:"options",disabled:t.fields.slug.disabled,icon:"wand"},on:{click:function(e){return t.sluggify(t.title)}},slot:"options"},[t._v(" "+t._s(t.$t("page.changeSlug.fromTitle"))+" ")])],1)],1)],1)},Me=[],De={mixins:[m],data(){return{page:{id:null,parent:null,title:null},permissions:{},select:"title",slug:null,title:null,url:null}},computed:{fields(){return{title:{label:this.$t("title"),type:"text",required:!0,icon:"title",disabled:!1===this.permissions.changeTitle},slug:{name:"slug",label:this.$t("slug"),type:"text",required:!0,icon:"url",help:"/"+this.url,counter:!1,disabled:!1===this.permissions.changeSlug}}},slugs(){return this.$store.state.languages.current?this.$store.state.languages.current.rules:this.system.slugs},system(){return this.$store.state.system.info}},methods:{onReady(){this.$refs[this.select]&&this.$refs[this.select].select()},async open(t,e,s="title"){try{this.page=await this.$api.pages.get(t,{view:"panel"}),this.select=s,this.title=this.page.title,this.sluggify(this.page.slug),this.permissions=e,this.$refs.dialog.open()}catch(i){this.$store.dispatch("notification/error",i)}},sluggify(t){t=t.trim(),this.slug=this.$helper.slug(t,[this.slugs,this.system.ascii]),this.page.parents?this.url=this.page.parents.map(t=>t.slug).concat([this.slug]).join("/"):this.url=this.slug},async submit(){if(this.title=this.title.trim(),this.title===this.page.title&&this.slug===this.page.slug)return this.$refs.dialog.close(),void this.$store.dispatch("notification/success",":)");if(0===this.title.length)return this.$refs.dialog.error(this.$t("error.page.changeTitle.empty"));if(0===this.slug.length)return this.$refs.dialog.error(this.$t("error.page.slug.invalid"));try{let t={message:":)",event:[]};if(this.title!==this.page.title&&(await this.$api.pages.changeTitle(this.page.id,this.title),t.event.push("page.changeTitle")),this.slug!==this.page.slug){const e=await this.$api.pages.changeSlug(this.page.id,this.slug);this.$store.dispatch("content/move",["pages/"+this.page.id,"pages/"+e.id]),t.event.push("page.changeSlug"),!this.$route.params.path||this.page.id!==this.$route.params.path.replace(/\+/g,"/")||this.$store.state.languages.current&&!0!==this.$store.state.languages.current.default||(t.route=this.$api.pages.link(e.id),t.emit=!1,delete t.event)}this.success(t)}catch(t){this.$refs.dialog.error(t.message)}}}},Ne=De,Pe=(s("5f52"),Object(c["a"])(Ne,Be,Me,!1,null,null,null)),Re=Pe.exports,qe=function(){var t=this,e=t.$createElement,s=t._self._c||e;return s("k-form-dialog",{ref:"dialog",attrs:{fields:t.fields,"submit-button":t.$t("change")},on:{submit:t.submit},model:{value:t.form,callback:function(e){t.form=e},expression:"form"}})},Fe=[],Ue={mixins:[m],data(){return{page:{id:null,siblings:[]},isBlocked:!1,isIncomplete:!1,form:{status:null,position:null},states:{}}},computed:{fields(){let t={status:{name:"status",label:this.$t("page.changeStatus.select"),type:"radio",required:!0,options:Object.keys(this.states).map(t=>({value:t,text:this.states[t].label,info:this.states[t].text}))}};return"listed"===this.form.status&&"default"===this.page.blueprint.num&&(t.position={name:"position",label:this.$t("page.changeStatus.position"),type:"select",empty:!1,options:this.sortingOptions}),t},sortingOptions(){let t=[],e=0;return this.page.siblings.forEach(s=>{if(s.id===this.page.id||s.num<1)return!1;e++,t.push({value:e,text:e}),t.push({value:s.id,text:s.title,disabled:!0})}),t.push({value:e+1,text:e+1}),t}},methods:{async open(t){try{const s=await this.$api.pages.get(t,{select:["id","status","num","errors","blueprint"]});if(!1===s.blueprint.options.changeStatus)return this.$store.dispatch("notification/error",{message:this.$t("error.page.changeStatus.permission")});if("draft"===s.status&&Object.keys(s.errors).length>0)return this.$store.dispatch("notification/error",{message:this.$t("error.page.changeStatus.incomplete"),details:s.errors});if("default"===s.blueprint.num)try{const e=await this.$api.pages.get(t,{select:["siblings"]});this.setup({...s,siblings:e.siblings})}catch(e){this.$store.dispatch("notification/error",e)}else this.setup({...s,siblings:[]})}catch(e){this.$store.dispatch("notification/error",e)}},setup(t){this.page=t,this.form.position=t.num||t.siblings.length+1,this.form.status=t.status,this.states=t.blueprint.status,this.$refs.dialog.open()},async submit(){try{await this.$api.pages.changeStatus(this.page.id,this.form.status,this.form.position||1),this.success({message:":)",event:"page.changeStatus"})}catch(t){this.$refs.dialog.error(t.message)}}}},He=Ue,ze=Object(c["a"])(He,qe,Fe,!1,null,null,null),Ke=ze.exports,Ge={extends:Ke,computed:{fields(){return{position:{name:"position",label:this.$t("page.changeStatus.position"),type:"select",empty:!1,options:this.sortingOptions}}}},methods:{async open(t){try{const e=await this.$api.pages.get(t,{select:["id","status","num","errors","blueprint","siblings"]});this.setup(e)}catch(e){this.$store.dispatch("notification/error",e)}},async submit(){try{await this.$api.pages.status(this.page.id,"listed",this.form.position||1),this.success({message:":)",event:"page.sort"})}catch(t){this.$refs.dialog.error(t.message)}}}},We=Ge,Ye=Object(c["a"])(We,w,x,!1,null,null,null),Ve=Ye.exports,Je=function(){var t=this,e=t.$createElement,s=t._self._c||e;return s("k-form-dialog",{ref:"dialog",attrs:{fields:t.fields,"submit-button":t.$t("change")},on:{submit:t.submit},model:{value:t.page,callback:function(e){t.page=e},expression:"page"}})},Ze=[],Xe={mixins:[m],data(){return{blueprints:[],page:{id:null,template:null}}},computed:{fields(){return{template:{label:this.$t("template"),type:"select",required:!0,empty:!1,options:this.page.blueprints,icon:"template"}}}},methods:{async open(t){try{const e=await this.$api.pages.get(t,{select:["id","template","blueprints"]});if(e.blueprints.length<=1)return this.$store.dispatch("notification/error",{message:this.$t("error.page.changeTemplate.invalid",{slug:e.id})});this.page=e,this.page.blueprints=this.page.blueprints.map(t=>({text:t.title,value:t.name})),this.$refs.dialog.open()}catch(e){this.$store.dispatch("notification/error",e)}},async submit(){this.$events.$emit("keydown.cmd.s");try{await this.$api.pages.changeTemplate(this.page.id,this.page.template),this.success({message:":)",event:"page.changeTemplate"})}catch(t){this.$refs.dialog.error(t.message)}}}},Qe=Xe,ts=Object(c["a"])(Qe,Je,Ze,!1,null,null,null),es=ts.exports,ss=function(){var t=this,e=t.$createElement,s=t._self._c||e;return s("k-dialog",{ref:"dialog",staticClass:"k-pages-dialog",attrs:{size:"medium"},on:{cancel:function(e){return t.$emit("cancel")},submit:t.submit}},[t.issue?[s("k-box",{attrs:{text:t.issue,html:!1,theme:"negative"}})]:[t.model?s("header",{staticClass:"k-pages-dialog-navbar"},[s("k-button",{attrs:{disabled:!t.model.id,tooltip:t.$t("back"),icon:"angle-left"},on:{click:t.back}}),s("k-headline",[t._v(t._s(t.model.title))])],1):t._e(),t.options.search?s("k-input",{staticClass:"k-dialog-search",attrs:{autofocus:!0,placeholder:t.$t("search")+" …",type:"text",icon:"search"},model:{value:t.search,callback:function(e){t.search=e},expression:"search"}}):t._e(),t.models.length?[s("k-list",t._l(t.models,(function(e){return s("k-list-item",{key:e.id,attrs:{text:e.text,info:e.info,image:e.image,icon:e.icon},on:{click:function(s){return t.toggle(e)}}},[s("template",{slot:"options"},[t.isSelected(e)?s("k-button",{attrs:{slot:"options",autofocus:!0,icon:t.checkedIcon,tooltip:t.$t("remove"),theme:"positive"},slot:"options"}):s("k-button",{attrs:{slot:"options",autofocus:!0,tooltip:t.$t("select"),icon:"circle-outline"},slot:"options"}),t.model?s("k-button",{attrs:{disabled:!e.hasChildren,tooltip:t.$t("open"),icon:"angle-right"},on:{click:function(s){return s.stopPropagation(),t.go(e)}}}):t._e()],1)],2)})),1),s("k-pagination",t._b({staticClass:"k-dialog-pagination",attrs:{details:!0,dropdown:!1,align:"center"},on:{paginate:t.paginate}},"k-pagination",t.pagination,!1))]:s("k-empty",{attrs:{icon:"page"}},[t._v(" "+t._s(t.$t("dialog.pages.empty"))+" ")])]],2)},is=[],ns={mixins:[Ft],data(){const t=Ft.data();return{...t,model:{title:null,parent:null},options:{...t.options,parent:null}}},computed:{fetchData(){return{parent:this.options.parent}}},methods:{back(){this.options.parent=this.model.parent,this.pagination.page=1,this.fetch()},go(t){this.options.parent=t.id,this.pagination.page=1,this.fetch()},onFetched(t){this.model=t.model}}},as=ns,rs=(s("ac27"),Object(c["a"])(as,ss,is,!1,null,null,null)),os=rs.exports,ls=function(){var t=this,e=t.$createElement,s=t._self._c||e;return s("k-text-dialog",t._g(t._b({ref:"dialog"},"k-text-dialog",t.$props,!1),t.$listeners),[t._t("default")],2)},cs=[],us={mixins:[m],props:{icon:{type:String,default:"trash"},submitButton:{type:[String,Boolean],default(){return this.$t("delete")}},text:String,theme:{type:String,default:"negative"}}},ds=us,ps=Object(c["a"])(ds,ls,cs,!1,null,null,null),hs=ps.exports,ms=function(){var t=this,e=t.$createElement,s=t._self._c||e;return s("k-form-dialog",{ref:"dialog",attrs:{fields:t.fields,"submit-button":t.$t("rename")},on:{submit:t.submit},model:{value:t.site,callback:function(e){t.site=e},expression:"site"}})},fs=[],gs={mixins:[m],data(){return{site:{id:null,title:null}}},computed:{fields(){return{title:{label:this.$t("title"),type:"text",required:!0,icon:"title",preselect:!0}}}},methods:{async open(){try{this.site=await this.$api.site.get({select:["title"]}),this.$refs.dialog.open()}catch(t){this.$store.dispatch("notification/error",t)}},async submit(){if(this.site.title=this.site.title.trim(),0!==this.site.title.length)try{await this.$api.site.changeTitle(this.site.title),this.$store.dispatch("system/title",this.site.title),this.success({message:":)",event:"site.changeTitle"})}catch(t){this.$refs.dialog.error(t.message)}else this.$refs.dialog.error(this.$t("error.site.changeTitle.empty"))}}},bs=gs,ks=Object(c["a"])(bs,ms,fs,!1,null,null,null),vs=ks.exports,$s=function(){var t=this,e=t.$createElement,s=t._self._c||e;return s("k-dialog",t._g(t._b({ref:"dialog"},"k-dialog",t.$props,!1),t.$listeners),[t._t("default",[s("k-text",{domProps:{innerHTML:t._s(t.text)}})])],2)},ys=[],_s={mixins:[m],props:{text:String}},ws=_s,xs=Object(c["a"])(ws,$s,ys,!1,null,null,null),Os=xs.exports,Cs=function(){var t=this,e=t.$createElement,s=t._self._c||e;return s("k-form-dialog",{ref:"dialog",attrs:{fields:t.fields,"submit-button":t.$t("create")},on:{submit:t.create,close:t.reset},model:{value:t.user,callback:function(e){t.user=e},expression:"user"}})},Ss=[],Es={mixins:[m],data(){return{user:this.emptyForm(),languages:[],roles:[]}},computed:{fields(){return{name:{label:this.$t("name"),type:"text",icon:"user"},email:{label:this.$t("email"),type:"email",icon:"email",link:!1,required:!0},password:{label:this.$t("password"),type:"password",icon:"key"},language:{label:this.$t("language"),type:"select",icon:"globe",options:this.languages,required:!0,empty:!1},role:{label:this.$t("role"),type:1===this.roles.length?"hidden":"radio",required:!0,options:this.roles}}}},methods:{async create(){try{await this.$api.users.create(this.user),this.success({message:":)",event:"user.create"})}catch(t){this.$refs.dialog.error(t.message)}},emptyForm(){return{name:"",email:"",password:"",language:this.$store.state.system.info.defaultLanguage||"en",role:this.$user.role.name}},async open(){try{this.roles=await this.$api.roles.options({canBe:"created"}),"admin"!==this.$user.role.name&&(this.roles=this.roles.filter(t=>"admin"!==t.value))}catch(t){this.$store.dispatch("notification/error",t)}try{this.languages=await this.$api.translations.options()}catch(t){this.$store.dispatch("notification/error",t)}this.$refs.dialog.open()},reset(){this.user=this.emptyForm()}}},js=Es,Ts=Object(c["a"])(js,Cs,Ss,!1,null,null,null),Ls=Ts.exports,Is=function(){var t=this,e=t.$createElement,s=t._self._c||e;return s("k-form-dialog",{ref:"dialog",attrs:{fields:t.fields,"submit-button":t.$t("change")},on:{submit:t.submit},model:{value:t.user,callback:function(e){t.user=e},expression:"user"}})},As=[],Bs={mixins:[m],data(){return{user:{id:null,email:null}}},computed:{fields(){return{email:{label:this.$t("email"),preselect:!0,required:!0,type:"email"}}}},methods:{async open(t){try{this.user=await this.$api.users.get(t,{select:["id","email"]}),this.$refs.dialog.open()}catch(e){this.$store.dispatch("notification/error",e)}},async submit(){try{await this.$api.users.changeEmail(this.user.id,this.user.email),this.$store.dispatch("content/revert","users/"+this.user.id),this.$user.id===this.user.id&&this.$store.dispatch("user/email",this.user.email);let t={message:":)",event:"user.changeEmail"};this.success(t)}catch(t){this.$refs.dialog.error(t.message)}}}},Ms=Bs,Ds=Object(c["a"])(Ms,Is,As,!1,null,null,null),Ns=Ds.exports,Ps=function(){var t=this,e=t.$createElement,s=t._self._c||e;return s("k-form-dialog",{ref:"dialog",attrs:{fields:t.fields,"submit-button":t.$t("change")},on:{submit:t.submit},model:{value:t.user,callback:function(e){t.user=e},expression:"user"}})},Rs=[],qs={mixins:[m],data(){return{user:{language:"en"},languages:[]}},computed:{fields(){return{language:{label:this.$t("language"),type:"select",icon:"globe",options:this.languages,required:!0,empty:!1}}}},async created(){this.languages=await this.$api.translations.options()},methods:{async open(t){try{this.user=await this.$api.users.get(t,{view:"compact"}),this.$refs.dialog.open()}catch(e){this.$store.dispatch("notification/error",e)}},async submit(){try{this.user=await this.$api.users.changeLanguage(this.user.id,this.user.language),this.$user.id===this.user.id&&this.$store.dispatch("user/language",this.user.language),this.success({message:":)",event:"user.changeLanguage"})}catch(t){this.$refs.dialog.error(t.message)}}}},Fs=qs,Us=Object(c["a"])(Fs,Ps,Rs,!1,null,null,null),Hs=Us.exports,zs=function(){var t=this,e=t.$createElement,s=t._self._c||e;return s("k-form-dialog",{ref:"dialog",attrs:{fields:t.fields,"submit-button":t.$t("change")},on:{submit:t.submit},model:{value:t.values,callback:function(e){t.values=e},expression:"values"}})},Ks=[],Gs={mixins:[m],data(){return{user:null,values:{password:null,passwordConfirmation:null}}},computed:{fields(){return{password:{label:this.$t("user.changePassword.new"),type:"password",icon:"key"},passwordConfirmation:{label:this.$t("user.changePassword.new.confirm"),icon:"key",type:"password"}}}},methods:{async open(t){try{this.user=await this.$api.users.get(t),this.$refs.dialog.open()}catch(e){this.$store.dispatch("notification/error",e)}},async submit(){if(!this.values.password||this.values.password.length<8)return this.$refs.dialog.error(this.$t("error.user.password.invalid")),!1;if(this.values.password!==this.values.passwordConfirmation)return this.$refs.dialog.error(this.$t("error.user.password.notSame")),!1;try{await this.$api.users.changePassword(this.user.id,this.values.password),this.success({message:":)",event:"user.changePassword"})}catch(t){this.$refs.dialog.error(t.message)}}}},Ws=Gs,Ys=Object(c["a"])(Ws,zs,Ks,!1,null,null,null),Vs=Ys.exports,Js=function(){var t=this,e=t.$createElement,s=t._self._c||e;return s("k-remove-dialog",{ref:"dialog",attrs:{text:t.$t("user.delete.confirm",{email:t.$esc(t.user.email)})},on:{submit:t.submit}})},Zs=[],Xs={mixins:[m],data(){return{user:{email:null}}},methods:{async open(t){try{this.user=await this.$api.users.get(t),this.$refs.dialog.open()}catch(e){this.$store.dispatch("notification/error",e)}},async submit(){try{await this.$api.users.delete(this.user.id),this.$store.dispatch("content/remove","users/"+this.user.id),this.success({message:":)",event:"user.delete"}),"User"===this.$route.name&&this.$go("/users")}catch(t){this.$refs.dialog.error(t.message)}}}},Qs=Xs,ti=Object(c["a"])(Qs,Js,Zs,!1,null,null,null),ei=ti.exports,si=function(){var t=this,e=t.$createElement,s=t._self._c||e;return s("k-form-dialog",{ref:"dialog",attrs:{fields:t.fields,"submit-button":t.$t("rename")},on:{submit:t.submit},model:{value:t.user,callback:function(e){t.user=e},expression:"user"}})},ii=[],ni={mixins:[m],data(){return{user:{id:null,name:null}}},computed:{fields(){return{name:{label:this.$t("name"),type:"text",icon:"user",preselect:!0}}}},methods:{async open(t){try{this.user=await this.$api.users.get(t,{select:["id","name"]}),this.$refs.dialog.open()}catch(e){this.$store.dispatch("notification/error",e)}},async submit(){this.user.name=this.user.name.trim();try{await this.$api.users.changeName(this.user.id,this.user.name),this.$user.id===this.user.id&&this.$store.dispatch("user/name",this.user.name),this.success({message:":)",event:"user.changeName"})}catch(t){this.$refs.dialog.error(t.message)}}}},ai=ni,ri=Object(c["a"])(ai,si,ii,!1,null,null,null),oi=ri.exports,li=function(){var t=this,e=t.$createElement,s=t._self._c||e;return s("k-form-dialog",{ref:"dialog",attrs:{fields:t.fields,"submit-button":t.$t("user.changeRole")},on:{submit:t.submit},model:{value:t.user,callback:function(e){t.user=e},expression:"user"}})},ci=[],ui={mixins:[m],data(){return{roles:[],user:{id:null,role:"visitor"}}},computed:{fields(){return{role:{label:this.$t("user.changeRole.select"),type:"radio",required:!0,options:this.roles}}}},methods:{async open(t){this.id=t;try{this.user=await this.$api.users.get(t),this.user.role=this.user.role.name,this.roles=await this.$api.users.roles(t),"admin"!==this.$user.role.name&&(this.roles=this.roles.filter(t=>"admin"!==t.value)),this.$refs.dialog.open()}catch(e){this.$store.dispatch("notification/error",e)}},async submit(){try{await this.$api.users.changeRole(this.user.id,this.user.role),this.$user.id===this.user.id&&this.$store.dispatch("user/load"),this.success({message:":)",event:"user.changeRole"})}catch(t){this.$refs.dialog.error(t.message)}}}},di=ui,pi=Object(c["a"])(di,li,ci,!1,null,null,null),hi=pi.exports,mi=function(){var t=this,e=t.$createElement,s=t._self._c||e;return s("k-dialog",{ref:"dialog",staticClass:"k-users-dialog",attrs:{size:"medium"},on:{cancel:function(e){return t.$emit("cancel")},submit:t.submit}},[t.issue?[s("k-box",{attrs:{text:t.issue,html:!1,theme:"negative"}})]:[t.options.search?s("k-input",{staticClass:"k-dialog-search",attrs:{autofocus:!0,placeholder:t.$t("search")+" …",type:"text",icon:"search"},model:{value:t.search,callback:function(e){t.search=e},expression:"search"}}):t._e(),t.models.length?[s("k-list",t._l(t.models,(function(e){return s("k-list-item",{key:e.email,attrs:{text:e.text,info:e.info!==e.text?e.info:null,image:e.image,icon:e.icon},on:{click:function(s){return t.toggle(e)}}},[t.isSelected(e)?s("k-button",{attrs:{slot:"options",autofocus:!0,icon:t.checkedIcon,tooltip:t.$t("remove"),theme:"positive"},slot:"options"}):s("k-button",{attrs:{slot:"options",autofocus:!0,tooltip:t.$t("select"),icon:"circle-outline"},slot:"options"})],1)})),1),s("k-pagination",t._b({staticClass:"k-dialog-pagination",attrs:{details:!0,dropdown:!1,align:"center"},on:{paginate:t.paginate}},"k-pagination",t.pagination,!1))]:s("k-empty",{attrs:{icon:"users"}},[t._v(" "+t._s(t.$t("dialog.users.empty"))+" ")])]],2)},fi=[],gi={mixins:[Ft]},bi=gi,ki=(s("7568"),Object(c["a"])(bi,mi,fi,!1,null,null,null)),vi=ki.exports,$i=function(){var t=this,e=t.$createElement,s=t._self._c||e;return s("k-overlay",{ref:"overlay",attrs:{dimmed:!1},on:{close:t.onClose,open:t.onOpen}},[s("div",{staticClass:"k-drawer",attrs:{"data-nested":t.nested},on:{mousedown:function(e){t.click=!0},mouseup:t.mouseup}},[s("div",{staticClass:"k-drawer-box",on:{mousedown:function(e){e.stopPropagation(),t.click=!1}}},[s("header",{staticClass:"k-drawer-header"},[1===t.breadcrumb.length?s("h2",{staticClass:"k-drawer-title"},[s("k-icon",{attrs:{type:t.icon}}),t._v(" "+t._s(t.title)+" ")],1):s("ul",{staticClass:"k-drawer-breadcrumb"},t._l(t.breadcrumb,(function(e){return s("li",{key:e.id},[s("k-button",{attrs:{icon:e.icon},on:{click:function(s){return t.goTo(e.id)}}},[t._v(" "+t._s(e.title)+" ")])],1)})),0),t.hasTabs?s("nav",{staticClass:"k-drawer-tabs"},t._l(t.tabs,(function(e){return s("k-button",{key:e.name,staticClass:"k-drawer-tab",attrs:{current:t.tab==e.name},on:{click:function(s){return s.stopPropagation(),t.$emit("tab",e.name)}}},[t._v(" "+t._s(e.label)+" ")])})),1):t._e(),s("nav",{staticClass:"k-drawer-options"},[t._t("options"),s("k-button",{staticClass:"k-drawer-option",attrs:{icon:"check"},on:{click:t.close}})],2)]),s("div",{staticClass:"k-drawer-body"},[t._t("default")],2)])])])},yi=[],_i={inheritAttrs:!1,props:{icon:String,tab:String,tabs:Object,title:String},data(){return{click:!1}},computed:{breadcrumb(){return this.$store.state.drawers.open},hasTabs(){return this.tabs&&Object.keys(this.tabs).length>1},index(){return this.breadcrumb.findIndex(t=>t.id===this._uid)},nested(){return this.index>0}},watch:{index(){-1===this.index&&this.close()}},destroyed(){this.$store.dispatch("drawers/close",this._uid)},methods:{close(){this.$refs.overlay.close()},goTo(t){if(t===this._uid)return!0;this.$store.dispatch("drawers/goto",t)},mouseup(){!0===this.click&&this.close(),this.click=!1},onClose(){this.$store.dispatch("drawers/close",this._uid),this.$emit("close")},onOpen(){this.$store.dispatch("drawers/open",{id:this._uid,icon:this.icon,title:this.title}),this.$emit("open")},open(){this.$refs.overlay.open()}}},wi=_i,xi=(s("5332"),Object(c["a"])(wi,$i,yi,!1,null,null,null)),Oi=xi.exports,Ci=function(){var t=this,e=t.$createElement,s=t._self._c||e;return s("k-drawer",{ref:"drawer",staticClass:"k-form-drawer",attrs:{icon:t.icon,tabs:t.tabs,tab:t.tab,title:t.title},on:{close:function(e){return t.$emit("close")},open:function(e){return t.$emit("open")},tab:function(e){t.tab=e}},scopedSlots:t._u([{key:"options",fn:function(){return[t._t("options")]},proxy:!0},{key:"default",fn:function(){return[0===Object.keys(t.fields).length?s("k-box",{attrs:{theme:"info"}},[t._v(" "+t._s(t.empty)+" ")]):s("k-form",{ref:"form",attrs:{autofocus:!0,fields:t.fields,value:t.$helper.clone(t.value)},on:{input:function(e){return t.$emit("input",e)}}})]},proxy:!0}],null,!0)})},Si=[],Ei={inheritAttrs:!1,props:{empty:{type:String,default(){return"Missing field setup"}},icon:String,tabs:Object,title:String,type:String,value:Object},data(){return{tab:null}},computed:{fields(){const t=this.tab||null,e=this.tabs,s=e[t]||this.firstTab,i=s.fields||{};return i},firstTab(){return Object.values(this.tabs)[0]}},methods:{close(){this.$refs.drawer.close()},focus(t){this.$refs.form&&"function"===typeof this.$refs.form.focus&&this.$refs.form.focus(t)},open(t,e=!0){this.$refs.drawer.open(),this.tab=t||this.firstTab.name,!1!==e&&setTimeout(()=>{let t=Object.values(this.fields).filter(t=>!0===t.autofocus)[0]||null;this.focus(t)},1)}}},ji=Ei,Ti=Object(c["a"])(ji,Ci,Si,!1,null,null,null),Li=Ti.exports,Ii=function(){var t=this,e=t.$createElement,s=t._self._c||e;return s("k-dropdown",{staticClass:"k-autocomplete"},[t._t("default"),s("k-dropdown-content",t._g({ref:"dropdown",attrs:{autofocus:!0}},t.$listeners),t._l(t.matches,(function(e,i){return s("k-dropdown-item",t._b({key:i,on:{mousedown:function(s){return t.onSelect(e)},keydown:[function(s){return!s.type.indexOf("key")&&t._k(s.keyCode,"tab",9,s.key,"Tab")?null:(s.preventDefault(),t.onSelect(e))},function(s){return!s.type.indexOf("key")&&t._k(s.keyCode,"enter",13,s.key,"Enter")?null:(s.preventDefault(),t.onSelect(e))},function(e){return!e.type.indexOf("key")&&t._k(e.keyCode,"left",37,e.key,["Left","ArrowLeft"])||"button"in e&&0!==e.button?null:(e.preventDefault(),t.close(e))},function(e){return!e.type.indexOf("key")&&t._k(e.keyCode,"backspace",void 0,e.key,void 0)?null:(e.preventDefault(),t.close(e))},function(e){return!e.type.indexOf("key")&&t._k(e.keyCode,"delete",[8,46],e.key,["Backspace","Delete","Del"])?null:(e.preventDefault(),t.close(e))}]}},"k-dropdown-item",e,!1),[s("span",{domProps:{innerHTML:t._s(t.html?e.text:t.$esc(e.text))}})])})),1),t._v(" "+t._s(t.query)+" ")],2)},Ai=[],Bi={props:{html:{type:Boolean,default:!1},limit:{type:Number,default:10},skip:{type:Array,default(){return[]}},options:Array,query:String},data(){return{matches:[],selected:{text:null}}},methods:{close(){this.$refs.dropdown.close()},onSelect(t){this.$emit("select",t),this.$refs.dropdown.close()},search(t){if(t.length<1)return;const e=new RegExp(RegExp.escape(t),"ig");this.matches=this.options.filter(t=>!!t.text&&(-1===this.skip.indexOf(t.value)&&null!==t.text.match(e))).slice(0,this.limit),this.$emit("search",t,this.matches),this.$refs.dropdown.open()}}},Mi=Bi,Di=Object(c["a"])(Mi,Ii,Ai,!1,null,null,null),Ni=Di.exports,Pi=function(){var t=this,e=t.$createElement,s=t._self._c||e;return s("div",{staticClass:"k-calendar-input"},[s("nav",[s("k-button",{attrs:{icon:"angle-left"},on:{click:t.prev}}),s("span",{staticClass:"k-calendar-selects"},[s("k-select-input",{attrs:{options:t.months,disabled:t.disabled,required:!0},model:{value:t.view.month,callback:function(e){t.$set(t.view,"month",t._n(e))},expression:"view.month"}}),s("k-select-input",{attrs:{options:t.years,disabled:t.disabled,required:!0},model:{value:t.view.year,callback:function(e){t.$set(t.view,"year",t._n(e))},expression:"view.year"}})],1),s("k-button",{attrs:{icon:"angle-right"},on:{click:t.next}})],1),s("table",{staticClass:"k-calendar-table"},[s("thead",[s("tr",t._l(t.weekdays,(function(e){return s("th",{key:"weekday_"+e},[t._v(" "+t._s(e)+" ")])})),0)]),s("tbody",t._l(t.numberOfWeeks,(function(e){return s("tr",{key:"week_"+e},t._l(t.days(e),(function(e,i){return s("td",{key:"day_"+i,staticClass:"k-calendar-day",attrs:{"aria-current":!!t.isToday(e)&&"date","aria-selected":!!t.isSelected(e)&&"date","data-between":t.isBetween(e),"data-first":t.isFirst(e),"data-last":t.isLast(e)}},[e?s("k-button",{attrs:{disabled:t.isDisabled(e)},on:{click:function(s){return t.select(e)}}},[t._v(" "+t._s(e)+" ")]):t._e()],1)})),0)})),0),s("tfoot",[s("tr",[s("td",{staticClass:"k-calendar-today",attrs:{colspan:"7"}},[s("k-button",{on:{click:function(e){return t.select("today")}}},[t._v(" "+t._s(t.$t("today"))+" ")])],1)])])])])},Ri=[],qi={props:{disabled:Boolean,multiple:Boolean,max:String,min:String,value:[Array,String]},data(){return this.toData(this.value)},computed:{numberOfDays(){return this.viewDt.daysInMonth()},numberOfWeeks(){return Math.ceil((this.numberOfDays+this.firstWeekday-1)/7)},firstWeekday(){const t=this.viewDt.day();return t>0?t:7},weekdays(){return[this.$t("days.mon"),this.$t("days.tue"),this.$t("days.wed"),this.$t("days.thu"),this.$t("days.fri"),this.$t("days.sat"),this.$t("days.sun")]},monthnames(){return[this.$t("months.january"),this.$t("months.february"),this.$t("months.march"),this.$t("months.april"),this.$t("months.may"),this.$t("months.june"),this.$t("months.july"),this.$t("months.august"),this.$t("months.september"),this.$t("months.october"),this.$t("months.november"),this.$t("months.december")]},months(){var t=[];return this.monthnames.forEach((e,s)=>{const i=this.toDate(1,s);t.push({value:s,text:e,disabled:i.isBefore(this.view.min,"month")||i.isAfter(this.view.max,"month")})}),t},years(){var t=[];const e=this.view.min?this.view.min.get("year"):this.view.year-20,s=this.view.max?this.view.max.get("year"):this.view.year+20;for(var i=e;i<=s;i++)t.push({value:i,text:this.$helper.pad(i)});return t},viewDt(){const t=`${this.view.year}-${this.view.month+1}-01 00:00:00`;return this.$library.dayjs.utc(t)}},watch:{value(t){const e=this.toData(t);this.datetimes=e.datetimes,this.view=e.view}},methods:{days(t){let e=[];const s=7*(t-1)+1;for(let i=s;ithis.numberOfDays?e.push(""):e.push(t)}return e},isBetween(t){if(""===t||0==this.multiple||this.datetimes.length<2)return!1;const e=this.toDate(t);return this.isFirst(t)||this.isLast(t)||e.isAfter(this.datetimes[0],"day")&&e.isBefore(this.datetimes[1],"day")},isDisabled(t){const e=this.toDate(t);return e.isBefore(this.view.min,"day")||e.isAfter(this.view.max,"day")},isFirst(t){if(""===t||0==this.multiple||this.datetimes.length<2)return!1;const e=this.toDate(t);return e.isSame(this.datetimes[0],"day")},isLast(t){if(""===t||0==this.multiple||this.datetimes.length<2)return!1;const e=this.toDate(t);return e.isSame(this.datetimes[1],"day")},isSelected(t){if(""===t)return!1;const e=this.toDate(t);return this.datetimes.some(t=>e.isSame(t,"day"))},isToday(t){return this.toDate(t).isSame(this.toToday(),"day")},next(){let t=this.viewDt.clone().add(1,"month");this.show(t)},prev(){let t=this.viewDt.clone().subtract(1,"month");this.show(t)},mergeTime(t,e){return t.clone().set("second",e.get("second")).set("minute",e.get("minute")).set("hour",e.get("hour"))},select(t){const e=this.datetimes[0]||this.toToday();if("today"===t){const t=this.mergeTime(this.$library.dayjs(),e);this.datetimes=[t],this.show(t)}else{let s=this.toDate(t);s=this.mergeTime(s,e),!1===this.multiple||0===this.datetimes.length||2===this.datetimes.length||s.isBefore(this.datetimes[0])?this.datetimes=[s]:this.datetimes.push(s)}const s=this.multiple?this.datetimes.map(t=>this.toISO(t)):this.toISO(this.datetimes[0]);this.$emit("input",s)},show(t){this.view.year=t.year(),this.view.month=t.month()},toData(t){const e=this.toToday(),s=this.toDatetimes(t);return{datetimes:s,view:{month:(s[0]||e).month(),year:(s[0]||e).year(),min:this.min?this.$library.dayjs.utc(this.min):null,max:this.max?this.$library.dayjs.utc(this.max):null}}},toDate(t,e=this.view.month,s=this.view.year){return this.$library.dayjs.utc(`${s}-${e+1}-${t} 00:00:00`)},toDatetimes(t){return t?"string"===typeof t?[this.$library.dayjs.utc(t)]:t.map(t=>this.$library.dayjs.utc(t)):[]},toISO(t){return t.format("YYYY-MM-DD HH:mm:ss")},toToday(){return this.$library.dayjs.utc()}}},Fi=qi,Ui=(s("ee15"),Object(c["a"])(Fi,Pi,Ri,!1,null,null,null)),Hi=Ui.exports,zi=function(){var t=this,e=t.$createElement,s=t._self._c||e;return s("span",{staticClass:"k-counter",attrs:{"data-invalid":!t.valid}},[s("span",[t._v(t._s(t.count))]),t.min&&t.max?s("span",{staticClass:"k-counter-rules"},[t._v("("+t._s(t.min)+"–"+t._s(t.max)+")")]):t.min?s("span",{staticClass:"k-counter-rules"},[t._v("≥ "+t._s(t.min))]):t.max?s("span",{staticClass:"k-counter-rules"},[t._v("≤ "+t._s(t.max))]):t._e()])},Ki=[],Gi={props:{count:Number,min:Number,max:Number,required:{type:Boolean,default:!1}},computed:{valid(){return!1===this.required&&0===this.count||(!0!==this.required||0!==this.count)&&(!(this.min&&this.countthis.max))}}},Wi=Gi,Yi=(s("fc0f"),Object(c["a"])(Wi,zi,Ki,!1,null,null,null)),Vi=Yi.exports,Ji=function(){var t=this,e=t.$createElement,s=t._self._c||e;return s("form",{ref:"form",staticClass:"k-form",attrs:{method:"POST",autocomplete:"off",novalidate:""},on:{submit:function(e){return e.preventDefault(),t.onSubmit(e)}}},[t._t("header"),t._t("default",[s("k-fieldset",t._g({ref:"fields",attrs:{disabled:t.disabled,fields:t.fields,novalidate:t.novalidate},model:{value:t.value,callback:function(e){t.value=e},expression:"value"}},t.listeners))]),t._t("footer"),s("input",{ref:"submitter",staticClass:"k-form-submitter",attrs:{type:"submit"}})],2)},Zi=[],Xi={props:{disabled:Boolean,config:Object,fields:{type:[Array,Object],default(){return{}}},novalidate:{type:Boolean,default:!1},value:{type:Object,default(){return{}}}},data(){return{errors:{},listeners:{...this.$listeners,submit:this.onSubmit}}},methods:{focus(t){this.$refs.fields&&this.$refs.fields.focus&&this.$refs.fields.focus(t)},onSubmit(){this.$emit("submit",this.value)},submit(){this.$refs.submitter.click()}}},Qi=Xi,tn=(s("5d33"),Object(c["a"])(Qi,Ji,Zi,!1,null,null,null)),en=tn.exports,sn=function(){var t=this,e=t.$createElement,s=t._self._c||e;return s("nav",{staticClass:"k-form-buttons",attrs:{"data-theme":t.mode}},["unlock"===t.mode?s("k-view",[s("p",{staticClass:"k-form-lock-info"},[t._v(" "+t._s(t.$t("lock.isUnlocked"))+" ")]),s("span",{staticClass:"k-form-lock-buttons"},[s("k-button",{staticClass:"k-form-button",attrs:{icon:"download"},on:{click:t.onDownload}},[t._v(" "+t._s(t.$t("download"))+" ")]),s("k-button",{staticClass:"k-form-button",attrs:{icon:"check"},on:{click:t.onResolve}},[t._v(" "+t._s(t.$t("confirm"))+" ")])],1)]):"lock"===t.mode?s("k-view",[s("p",{staticClass:"k-form-lock-info"},[s("k-icon",{attrs:{type:"lock"}}),s("span",{domProps:{innerHTML:t._s(t.$t("lock.isLocked",{email:t.$esc(t.form.lock.email)}))}})],1),t.form.lock.unlockable?s("k-button",{staticClass:"k-form-button",attrs:{icon:"unlock"},on:{click:t.setUnlock}},[t._v(" "+t._s(t.$t("lock.unlock"))+" ")]):s("k-icon",{staticClass:"k-form-lock-loader",attrs:{type:"loader"}})],1):"changes"===t.mode?s("k-view",[s("k-button",{staticClass:"k-form-button",attrs:{disabled:t.isDisabled,icon:"undo"},on:{click:function(e){return t.$refs.revert.open()}}},[t._v(" "+t._s(t.$t("revert"))+" ")]),s("k-button",{staticClass:"k-form-button",attrs:{disabled:t.isDisabled,icon:"check"},on:{click:t.onSave}},[t._v(" "+t._s(t.$t("save"))+" ")])],1):t._e(),s("k-dialog",{ref:"revert",attrs:{"submit-button":t.$t("revert"),icon:"undo",theme:"negative"},on:{submit:t.onRevert}},[s("k-text",{domProps:{innerHTML:t._s(t.$t("revert.confirm"))}})],1)],1)},nn=[],an={data(){return{supportsLocking:!0}},computed:{api(){return{lock:[this.$route.path+"/lock",null,null,!0],unlock:[this.$route.path+"/unlock",null,null,!0]}},hasChanges(){return this.$store.getters["content/hasChanges"]()},form(){return{lock:this.$store.state.content.status.lock,unlock:this.$store.state.content.status.unlock}},id(){return this.$store.state.content.current},isDisabled(){return!1===this.$store.state.content.status.enabled},isLocked(){return null!==this.form.lock},isUnlocked(){return null!==this.form.unlock},mode(){return!0===this.isUnlocked?"unlock":!0===this.isLocked?"lock":!0===this.hasChanges?"changes":null}},watch:{hasChanges(t,e){if(!1===e&&!0===t)return this.$store.dispatch("heartbeat/remove",this.getLock),void this.$store.dispatch("heartbeat/add",[this.setLock,30]);this.id&&!0===e&&!1===t&&this.removeLock()},id(){this.id&&!1===this.hasChanges&&this.$store.dispatch("heartbeat/add",[this.getLock,10])}},created(){this.$events.$on("keydown.cmd.s",this.onSave)},destroyed(){this.$events.$off("keydown.cmd.s",this.onSave)},methods:{getLock(){return this.$api.get(...this.api.lock).then(t=>{if(!1===t.supported)return this.supportsLocking=!1,void this.$store.dispatch("heartbeat/remove",this.getLock);!1===t.locked?(this.isLocked&&this.form.lock.user!==this.$store.state.user.current.id&&this.$events.$emit("model.reload"),this.$store.dispatch("content/lock",null)):this.$store.dispatch("content/lock",t.locked)}).catch(()=>{})},setLock(){!0===this.supportsLocking&&this.$api.patch(...this.api.lock).catch(t=>{if("error.lock.notImplemented"===t.key)return this.supportsLocking=!1,this.$store.dispatch("heartbeat/remove",this.setLock),!1;this.$store.dispatch("content/revert",this.id),this.$store.dispatch("heartbeat/remove",this.setLock),this.$store.dispatch("heartbeat/add",[this.getLock,10])})},removeLock(){!0===this.supportsLocking&&(this.$store.dispatch("heartbeat/remove",this.setLock),this.$api.delete(...this.api.lock).then(()=>{this.$store.dispatch("content/lock",null),this.$store.dispatch("heartbeat/add",[this.getLock,10])}).catch(()=>{}))},setUnlock(){!0===this.supportsLocking&&(this.$store.dispatch("heartbeat/remove",this.setLock),this.$api.patch(...this.api.unlock).then(()=>{this.$store.dispatch("content/lock",null),this.$store.dispatch("heartbeat/add",[this.getLock,10])}).catch(()=>{}))},removeUnlock(){!0===this.supportsLocking&&(this.$store.dispatch("heartbeat/remove",this.setLock),this.$api.delete(...this.api.unlock).then(()=>{this.$store.dispatch("content/unlock",null),this.$store.dispatch("heartbeat/add",[this.getLock,10])}).catch(()=>{}))},onDownload(){let t="";Object.keys(this.form.unlock).forEach(e=>{t+=e+": \n\n"+this.form.unlock[e],t+="\n\n----\n\n"});let e=document.createElement("a");e.setAttribute("href","data:text/plain;charset=utf-8,"+encodeURIComponent(t)),e.setAttribute("download",this.id+".txt"),e.style.display="none",document.body.appendChild(e),e.click(),document.body.removeChild(e)},onResolve(){this.$store.dispatch("content/revert"),this.removeUnlock()},onRevert(){this.$store.dispatch("content/revert"),this.$refs.revert.close()},onSave(t){return!!t&&(t.preventDefault&&t.preventDefault(),!1===this.hasChanges||void this.$store.dispatch("content/save").then(()=>{this.$events.$emit("model.update"),this.$store.dispatch("notification/success",":)")}).catch(t=>{403!==t.code&&(t.details&&Object.keys(t.details).length>0?this.$store.dispatch("notification/error",{message:this.$t("error.form.incomplete"),details:t.details}):this.$store.dispatch("notification/error",{message:this.$t("error.form.notSaved"),details:[{label:"Exception: "+t.exception,message:t.message}]}))}))}}},rn=an,on=(s("18dd"),Object(c["a"])(rn,sn,nn,!1,null,null,null)),ln=on.exports,cn=function(){var t=this,e=t.$createElement,s=t._self._c||e;return t.hasChanges?s("k-dropdown",{staticClass:"k-form-indicator"},[s("k-button",{staticClass:"k-topbar-button",on:{click:t.toggle}},[s("k-icon",{staticClass:"k-form-indicator-icon",attrs:{type:"edit"}})],1),s("k-dropdown-content",{ref:"list",attrs:{align:"right"}},[s("p",{staticClass:"k-form-indicator-info"},[t._v(" "+t._s(t.$t("lock.unsaved"))+": ")]),s("hr"),t._l(t.entries,(function(e){return s("k-dropdown-item",{key:e.id,attrs:{icon:e.icon},nativeOn:{click:function(s){return s.stopPropagation(),t.go(e.target)}}},[t._v(" "+t._s(e.label)+" ")])}))],2)],1):t._e()},un=[],dn={data(){return{isOpen:!1,entries:[]}},computed:{store(){return this.$store.state.content.models},models(){const t=Object.keys(this.store).filter(t=>!!this.store[t]);let e=t.map(t=>({id:t,...this.store[t]}));return e.filter(t=>Object.keys(t.changes).length>0)},hasChanges(){return this.models.length>0}},methods:{go(t){if(t.language&&this.$store.state.languages.current.code!==t.language){const e=this.$store.state.languages.all.filter(e=>e.code===t.language)[0];this.$store.dispatch("languages/current",e)}this.$go(t.link)},load(){const t=this.models.map(t=>this.$api.get(t.api,{view:"compact"},null,!0).then(e=>{let s;if(s=!0===t.id.startsWith("pages/")?{icon:"page",label:e.title,target:{link:this.$api.pages.link(e.id)}}:!0===t.id.startsWith("files/")?{icon:"image",label:e.filename,target:{link:e.link}}:!0===t.id.startsWith("users/")?{icon:"user",label:e.email,target:{link:this.$api.users.link(e.id)}}:{icon:"home",label:e.title,target:{link:"/site"}},this.$store.state.languages.current){const e=t.id.split("/").pop();s.label=s.label+" ("+e+")",s.target.language=e}return s}).catch(()=>(this.$store.dispatch("content/remove",t.id),null)));return Promise.all(t).then(t=>{this.entries=t.filter(t=>null!==t),0===this.entries.length&&this.$store.dispatch("notification/success",this.$t("lock.unsaved.empty"))})},toggle(){!1===this.$refs.list.isOpen?this.load().then(()=>{this.$refs.list&&this.$refs.list.toggle()}):this.$refs.list.toggle()}}},pn=dn,hn=(s("9e26"),Object(c["a"])(pn,cn,un,!1,null,null,null)),mn=hn.exports,fn=s("38b6"),gn=function(){var t=this,e=t.$createElement,s=t._self._c||e;return s("fieldset",{staticClass:"k-fieldset"},[s("k-grid",[t._l(t.fields,(function(e,i){return["hidden"!==e.type&&t.meetsCondition(e)?s("k-column",{key:e.signature,attrs:{width:e.width}},[s("k-error-boundary",[t.hasFieldType(e.type)?s("k-"+e.type+"-field",t._b({ref:i,refInFor:!0,tag:"component",attrs:{name:i,novalidate:t.novalidate,disabled:t.disabled||e.disabled},on:{input:function(s){return t.$emit("input",t.value,e,i)},focus:function(s){return t.$emit("focus",s,e,i)},invalid:function(s,n){return t.onInvalid(s,n,e,i)},submit:function(s){return t.$emit("submit",s,e,i)}},model:{value:t.value[i],callback:function(e){t.$set(t.value,i,e)},expression:"value[fieldName]"}},"component",e,!1)):s("k-box",{attrs:{theme:"negative"}},[s("k-text",{attrs:{size:"small"}},[t._v(" The field type "),s("strong",[t._v('"'+t._s(i)+'"')]),t._v(" does not exist ")])],1)],1)],1):t._e()]}))],2)],1)},bn=[],kn={props:{config:Object,disabled:Boolean,fields:{type:[Array,Object],default(){return[]}},novalidate:{type:Boolean,default:!1},value:{type:Object,default(){return{}}}},data(){return{errors:{}}},methods:{focus(t){if(t)return void(this.hasField(t)&&"function"===typeof this.$refs[t][0].focus&&this.$refs[t][0].focus());const e=Object.keys(this.$refs)[0];this.focus(e)},hasFieldType(t){return this.$helper.isComponent(`k-${t}-field`)},hasField(t){return this.$refs[t]&&this.$refs[t][0]},meetsCondition(t){if(!t.when)return!0;let e=!0;return Object.keys(t.when).forEach(s=>{const i=this.value[s.toLowerCase()],n=t.when[s];i!==n&&(e=!1)}),e},onInvalid(t,e,s,i){this.errors[i]=e,this.$emit("invalid",this.errors)},hasErrors(){return Object.keys(this.errors).length}}},vn=kn,$n=(s("862b"),Object(c["a"])(vn,gn,bn,!1,null,null,null)),yn=$n.exports,_n=function(){var t=this,e=t.$createElement,s=t._self._c||e;return s("div",{staticClass:"k-input",attrs:{"data-disabled":t.disabled,"data-invalid":!t.novalidate&&t.isInvalid,"data-theme":t.theme,"data-type":t.type}},[t.$slots.before||t.before?s("span",{staticClass:"k-input-before",on:{click:t.focus}},[t._t("before",[t._v(t._s(t.before))])],2):t._e(),s("span",{staticClass:"k-input-element",on:{click:function(e){return e.stopPropagation(),t.focus(e)}}},[t._t("default",[s("k-"+t.type+"-input",t._g(t._b({ref:"input",tag:"component",attrs:{value:t.value}},"component",t.inputProps,!1),t.listeners))])],2),t.$slots.after||t.after?s("span",{staticClass:"k-input-after",on:{click:t.focus}},[t._t("after",[t._v(t._s(t.after))])],2):t._e(),t.$slots.icon||t.icon?s("span",{staticClass:"k-input-icon",on:{click:t.focus}},[t._t("icon",[s("k-icon",{attrs:{type:t.icon}})])],2):t._e()])},wn=[],xn={inheritAttrs:!1,props:{after:String,autofocus:Boolean,before:String,disabled:Boolean,type:String,icon:[String,Boolean],invalid:Boolean,theme:String,novalidate:{type:Boolean,default:!1},value:{type:[String,Boolean,Number,Object,Array],default:null}},data(){return{isInvalid:this.invalid,listeners:{...this.$listeners,invalid:(t,e)=>{this.isInvalid=t,this.$emit("invalid",t,e)}}}},computed:{inputProps(){return{...this.$props,...this.$attrs}}},methods:{blur(t){t&&t.relatedTarget&&!1===this.$el.contains(t.relatedTarget)&&this.trigger(null,"blur")},focus(t){this.trigger(t,"focus")},select(t){this.trigger(t,"select")},trigger(t,e){if(t&&t.target&&"INPUT"===t.target.tagName&&"function"===typeof t.target[e])return void t.target[e]();if(this.$refs.input&&"function"===typeof this.$refs.input[e])return void this.$refs.input[e]();const s=this.$el.querySelector("input, select, textarea");s&&"function"===typeof s[e]&&s[e]()}}},On=xn,Cn=(s("c7c8"),Object(c["a"])(On,_n,wn,!1,null,null,null)),Sn=Cn.exports,En=function(){var t=this,e=t.$createElement,s=t._self._c||e;return s("form",{staticClass:"k-login-form",on:{submit:function(e){return e.preventDefault(),t.login(e)}}},[s("h1",{staticClass:"k-offscreen"},[t._v(" "+t._s(t.$t("login"))+" ")]),t.issue?s("div",{staticClass:"k-login-alert",on:{click:function(e){t.issue=null}}},[s("span",[t._v(t._s(t.issue))]),s("k-icon",{attrs:{type:"alert"}})],1):t._e(),s("div",{staticClass:"k-login-fields"},[!0===t.canToggle?s("button",{staticClass:"k-login-toggler",attrs:{type:"button"},on:{click:t.toggleForm}},[t._v(" "+t._s(t.toggleText)+" ")]):t._e(),s("k-fieldset",{ref:"fieldset",attrs:{novalidate:!0,fields:t.fields},model:{value:t.user,callback:function(e){t.user=e},expression:"user"}})],1),s("div",{staticClass:"k-login-buttons"},[!1===t.isResetForm?s("span",{staticClass:"k-login-checkbox"},[s("k-checkbox-input",{attrs:{value:t.user.remember,label:t.$t("login.remember")},on:{input:function(e){t.user.remember=e}}})],1):t._e(),s("k-button",{staticClass:"k-login-button",attrs:{icon:"check",type:"submit"}},[t._v(" "+t._s(t.$t("login"+(t.isResetForm?".reset":"")))+" "),t.isLoading?[t._v(" … ")]:t._e()],2)],1)])},jn=[],Tn={data(){return{currentForm:null,isLoading:!1,issue:"",user:{email:"",password:"",remember:!1}}},computed:{canToggle(){let t=this.$store.state.system.info.loginMethods;return null!==this.codeMode&&!0===t.includes("password")&&(!0===t.includes("password-reset")||!0===t.includes("code"))},codeMode(){let t=this.$store.state.system.info.loginMethods;return!0===t.includes("password-reset")?"password-reset":!0===t.includes("code")?"code":null},fields(){let t={email:{autofocus:!0,label:this.$t("email"),type:"email",required:!0,link:!1}};return"email-password"===this.form&&(t.password={label:this.$t("password"),type:"password",minLength:8,required:!0,autocomplete:"current-password",counter:!1}),t},form(){return this.currentForm?this.currentForm:"password"===this.$store.state.system.info.loginMethods[0]?"email-password":"email"},isResetForm(){return"password-reset"===this.codeMode&&"email"===this.form},toggleText(){return this.$t("login.toggleText."+this.codeMode+"."+this.formOpposite(this.form))}},methods:{formOpposite(t){return"email-password"===t?"email":"email-password"},async login(){this.issue=null,this.isLoading=!0;let t=Object.assign({},this.user);"email"===this.currentForm&&(t.password=null),!0===this.isResetForm&&(t.remember=!1);try{const e=await this.$api.auth.login(t);e.challenge?this.$store.dispatch("user/pending",{email:t.email,challenge:e.challenge}):(this.$store.dispatch("user/login",e.user),await this.$store.dispatch("system/load",!0),this.$store.dispatch("notification/success",this.$t("welcome")))}catch(e){this.issue=e.message}finally{this.isLoading=!1}},toggleForm(){this.currentForm=this.formOpposite(this.form),this.$refs.fieldset.focus("email")}}},Ln=Tn,In=Object(c["a"])(Ln,En,jn,!1,null,null,null),An=In.exports,Bn=function(){var t=this,e=t.$createElement,s=t._self._c||e;return s("form",{staticClass:"k-login-form k-login-code-form",on:{submit:function(e){return e.preventDefault(),t.login(e)}}},[s("h1",{staticClass:"k-offscreen"},[t._v(" "+t._s(t.$t("login"))+" ")]),t.issue?s("div",{staticClass:"k-login-alert",on:{click:function(e){t.issue=null}}},[s("span",[t._v(t._s(t.issue))]),s("k-icon",{attrs:{type:"alert"}})],1):t._e(),s("k-user-info",{attrs:{user:t.$store.state.user.pendingEmail}}),s("k-text-field",{attrs:{autofocus:!0,counter:!1,help:t.$t("login.code.text."+t.$store.state.user.pendingChallenge),label:t.$t("login.code.label."+t.mode),novalidate:!0,placeholder:t.$t("login.code.placeholder."+t.$store.state.user.pendingChallenge),required:!0,autocomplete:"one-time-code",icon:"unlock",name:"code"},model:{value:t.code,callback:function(e){t.code=e},expression:"code"}}),s("div",{staticClass:"k-login-buttons"},[s("k-button",{staticClass:"k-login-button k-login-back-button",attrs:{icon:"angle-left"},on:{click:t.back}},[t._v(" "+t._s(t.$t("back"))+" "),t.isLoadingBack?[t._v(" … ")]:t._e()],2),s("k-button",{staticClass:"k-login-button",attrs:{icon:"check",type:"submit"}},[t._v(" "+t._s(t.$t("login"+("password-reset"===t.mode?".reset":"")))+" "),t.isLoadingLogin?[t._v(" … ")]:t._e()],2)],1)],1)},Mn=[],Dn={data(){return{code:"",isLoadingBack:!1,isLoadingLogin:!1,issue:""}},computed:{mode(){return!0===this.$store.state.system.info.loginMethods.includes("password-reset")?"password-reset":"login"}},methods:{async back(){this.isLoadingBack=!0,await this.$store.dispatch("user/logout"),this.isLoadingBack=!1},async login(){this.issue=null,this.isLoadingLogin=!0;try{const t=await this.$api.auth.verifyCode(this.code);"password-reset"===this.mode&&this.$store.dispatch("user/visit","/reset-password"),this.$store.dispatch("user/login",t.user),await this.$store.dispatch("system/load",!0),this.$store.dispatch("notification/success",this.$t("welcome"))}catch(t){this.issue=t.message}finally{this.isLoadingLogin=!1}}}},Nn=Dn,Pn=(s("e1f3"),Object(c["a"])(Nn,Bn,Mn,!1,null,null,null)),Rn=Pn.exports,qn=function(){var t=this,e=t.$createElement,s=t._self._c||e;return s("div",{staticClass:"k-upload"},[s("input",{ref:"input",attrs:{accept:t.options.accept,multiple:t.options.multiple,"aria-hidden":"true",type:"file",tabindex:"-1"},on:{change:t.select,click:function(t){t.stopPropagation()}}}),s("k-dialog",{ref:"dialog",attrs:{"cancel-button":!1,"submit-button":!1,size:"medium"}},[t.errors.length>0?[s("k-headline",[t._v(t._s(t.$t("upload.errors")))]),s("ul",{staticClass:"k-upload-error-list"},t._l(t.errors,(function(e,i){return s("li",{key:"error-"+i},[s("p",{staticClass:"k-upload-error-filename"},[t._v(" "+t._s(e.file.name)+" ")]),s("p",{staticClass:"k-upload-error-message"},[t._v(" "+t._s(e.message)+" ")])])})),0)]:[s("k-headline",[t._v(t._s(t.$t("upload.progress")))]),s("ul",{staticClass:"k-upload-list"},t._l(t.files,(function(e,i){return s("li",{key:"file-"+i},[s("k-progress",{ref:e.name,refInFor:!0}),s("p",{staticClass:"k-upload-list-filename"},[t._v(" "+t._s(e.name)+" ")]),s("p",[t._v(t._s(t.errors[e.name]))])],1)})),0)],s("template",{slot:"footer"},[t.errors.length>0?[s("k-button-group",[s("k-button",{attrs:{icon:"check"},on:{click:function(e){return t.$refs.dialog.close()}}},[t._v(" "+t._s(t.$t("confirm"))+" ")])],1)]:t._e()],2)],2)],1)},Fn=[],Un={props:{url:{type:String},accept:{type:String,default:"*"},attributes:{type:Object},multiple:{type:Boolean,default:!0},max:{type:Number}},data(){return{options:this.$props,completed:{},errors:[],files:[],total:0}},methods:{open(t){this.params(t),setTimeout(()=>{this.$refs.input.click()},1)},params(t){this.options=Object.assign({},this.$props,t)},select(t){this.upload(t.target.files)},drop(t,e){this.params(e),this.upload(t)},upload(t){this.$refs.dialog.open(),this.files=[...t],this.completed={},this.errors=[],this.hasErrors=!1,this.options.max&&(this.files=this.files.slice(0,this.options.max)),this.total=this.files.length,this.files.forEach(t=>{this.$helper.upload(t,{url:this.options.url,attributes:this.options.attributes,headers:{"X-CSRF":window.panel.csrf},progress:(t,e,s)=>{this.$refs[e.name]&&this.$refs[e.name][0]&&this.$refs[e.name][0].set(s)},success:(t,e,s)=>{this.complete(e,s.data)},error:(t,e,s)=>{this.errors.push({file:e,message:s.message}),this.complete(e,s.data)}})})},complete(t,e){if(this.completed[t.name]=e,Object.keys(this.completed).length==this.total){if(this.$refs.input.value="",this.errors.length>0)return this.$forceUpdate(),void this.$emit("error",this.files);setTimeout(()=>{this.$refs.dialog.close(),this.$emit("success",this.files,Object.values(this.completed))},250)}}}},Hn=Un,zn=(s("5aee"),Object(c["a"])(Hn,qn,Fn,!1,null,null,null)),Kn=zn.exports,Gn=function(){var t=this,e=t.$createElement,s=t._self._c||e;return s("div",{ref:"editor",staticClass:"k-writer",attrs:{"data-empty":t.isEmpty,"data-placeholder":t.placeholder,spellcheck:t.spellcheck}},[t.editor?[t.toolbar.visible?s("k-writer-toolbar",{ref:"toolbar",style:{bottom:t.toolbar.position.bottom+"px",left:t.toolbar.position.left+"px"},attrs:{editor:t.editor,"active-marks":t.toolbar.marks,"active-nodes":t.toolbar.nodes},on:{command:function(e){return t.editor.command(e)}}}):t._e(),s("k-writer-link-dialog",{ref:"linkDialog",on:{close:function(e){return t.editor.focus()},submit:function(e){return t.editor.command("toggleLink",e)}}})]:t._e()],2)},Wn=[],Yn=(s("bf19"),s("5313")),Vn=s("576a"),Jn=s("304a"),Zn=s("7f06"),Xn=s("0010"),Qn=s("f95e"),ta=s("665f");function ea(t,e){const{from:s,to:i}=t.selection;let n=[];t.doc.nodesBetween(s,i,t=>{n=[...n,...t.marks]});const a=n.find(t=>t.type.name===e.name);return a?a.attrs:{}}function sa(t,e,s){let i=[];return s.doc.nodesBetween(t,e,(t,e)=>{i=[...i,...t.marks.map(s=>({start:e,end:e+t.nodeSize,mark:s}))]}),i}var ia=function(t,e,s){return new Qn["a"](t,(t,i,n,a)=>{const r=s instanceof Function?s(i):s,{tr:o}=t,l=i.length-1;let c=a,u=n;if(i[l]){const s=n+i[0].indexOf(i[l-1]),r=s+i[l-1].length-1,d=s+i[l-1].lastIndexOf(i[l]),p=d+i[l].length,h=sa(n,a,t).filter(t=>{const{excluded:s}=t.mark.type;return s.find(t=>t.name===e.name)}).filter(t=>t.end>s);if(h.length)return!1;ps&&o.delete(s,d),u=s,c=u+i[l].length}return o.addMark(u,c,e.create(r)),o.removeStoredMark(e),o})};function na(t,e){const{from:s,$from:i,to:n,empty:a}=t.selection;return a?!!e.isInSet(t.storedMarks||i.marks()):!!t.doc.rangeHasMark(s,n,e)}var aa=function(t,e,s){const i=(n,a)=>{const r=[];return n.forEach(n=>{if(n.isText){const{text:i,marks:o}=n;let l,c=0;const u=!!o.filter(t=>"link"===t.type.name)[0];while(!u&&null!==(l=t.exec(i)))if(a&&a.type.allowsMarkType(e)&&l[1]){const t=l.index,i=t+l[0].length,a=t+l[0].indexOf(l[1]),o=a+l[1].length,u=s instanceof Function?s(l):s;t>0&&r.push(n.cut(c,t)),r.push(n.cut(a,o).mark(e.create(u).addToSet(n.marks))),c=i}cnew Jn["j"](i(t.content),t.openStart,t.openEnd)}})};function ra(t=0,e=0,s=0){return Math.min(Math.max(parseInt(t,10),e),s)}var oa=function(t,e,s){return new Qn["a"](t,(t,i,n,a)=>{const r=s instanceof Function?s(i):s,{tr:o}=t;return i[0]&&o.replaceWith(n-1,a,e.create(r)),o})},la=(t,e)=>{for(let s=t.depth;s>0;s--){const i=t.node(s);if(e(i))return{pos:s>0?t.before(s):0,start:t.start(s),depth:s,node:i}}},ca=t=>({$from:e})=>la(e,t),ua=t=>t instanceof Yn["c"],da=(t,e)=>Array.isArray(t)&&t.indexOf(e.type)>-1||e.type===t,pa=t=>e=>{if(ua(e)){const{node:s,$from:i}=e;if(da(t,s))return{node:s,pos:i.pos,depth:i.depth}}},ha=(t,e,s={})=>{const i=t=>t.type===e,n=pa(e)(t.selection)||ca(i)(t.selection);return Object.keys(s).length&&n?n.node.hasMarkup(e,{...n.node.attrs,...s}):!!n};function ma(t=null,e=null){if(!t||!e)return!1;const s=t.parent.childAfter(t.parentOffset);if(!s.node)return!1;const i=s.node.marks.find(t=>t.type===e);if(!i)return!1;let n=t.index(),a=t.start()+s.offset,r=n+1,o=a+s.node.nodeSize;while(n>0&&i.isInSet(t.parent.child(n-1).marks))n-=1,a-=t.parent.child(n).nodeSize;while(r{const{tr:i,selection:n}=e;let{from:a,to:r}=n;const{$from:o,empty:l}=n;if(l){const e=ma(o,t);a=e.from,r=e.to}return i.removeMark(a,r,t),s(i)}},ga=function(t,e,s){const i=n=>{const a=[];return n.forEach(n=>{if(n.isText){const{text:i}=n;let r,o=0;do{if(r=t.exec(i),r){const t=r.index,i=t+r[0].length,l=s instanceof Function?s(r[0]):s;t>0&&a.push(n.cut(o,t)),a.push(n.cut(t,i).mark(e.create(l).addToSet(n.marks))),o=i}}while(r);onew Jn["j"](i(t.content),t.openStart,t.openEnd)}})},ba=function(t,e,s={}){return(i,n,a)=>{const r=ha(i,t,s);return r?Object(Xn["d"])(e)(i,n,a):Object(Xn["d"])(t,s)(i,n,a)}};function ka(t,e){return t.type===e.nodes.bulletList||t.type===e.nodes.orderedList||t.type===e.nodes.todoList}function va(t,e){return(s,i,n)=>{const{schema:a,selection:r}=s,{$from:o,$to:l}=r,c=o.blockRange(l);if(!c)return!1;const u=ca(t=>ka(t,a))(r);if(c.depth>=1&&u&&c.depth-u.depth<=1){if(u.node.type===t)return Object(ta["b"])(e)(s,i,n);if(ka(u.node,a)&&t.validContent(u.node.content)){const{tr:e}=s;return e.setNodeMarkup(u.pos,t),i&&i(e),!1}}return Object(ta["e"])(t)(s,i,n)}}var $a=function(t,e){return(s,i)=>{const{tr:n,selection:a,doc:r}=s,{ranges:o,empty:l}=a;if(l){const{from:s,to:i}=ma(a.$from,t);r.rangeHasMark(s,i,t)&&n.removeMark(s,i,t),n.addMark(s,i,t.create(e))}else o.forEach(s=>{const{$to:i,$from:a}=s;r.rangeHasMark(a.pos,i.pos,t)&&n.removeMark(a.pos,i.pos,t),n.addMark(a.pos,i.pos,t.create(e))});return i(n)}},ya={chainCommands:Xn["b"],exitCode:Xn["c"],setBlockType:Xn["d"],toggleMark:Xn["e"],wrappingInputRule:Qn["e"],textblockTypeInputRule:Qn["c"],addListNodes:ta["a"],wrapInList:ta["e"],splitListItem:ta["d"],liftListItem:ta["b"],sinkListItem:ta["c"],getMarkAttrs:ea,markInputRule:ia,markIsActive:na,markPasteRule:aa,minMax:ra,nodeIsActive:ha,nodeInputRule:oa,pasteRule:ga,removeMark:fa,toggleBlockType:ba,toggleList:va,updateMark:$a};class _a{emit(t,...e){this._callbacks=this._callbacks||{};const s=this._callbacks[t];return s&&s.forEach(t=>t.apply(this,e)),this}off(t,e){if(arguments.length){const s=this._callbacks?this._callbacks[t]:null;s&&(e?this._callbacks[t]=s.filter(t=>t!==e):delete this._callbacks[t])}else this._callbacks={};return this}on(t,e){return this._callbacks=this._callbacks||{},this._callbacks[t]=this._callbacks[t]||[],this._callbacks[t].push(e),this}}class wa{constructor(t=[],e){t.forEach(t=>{t.bindEditor(e),t.init()}),this.extensions=t}commands({schema:t,view:e}){return this.extensions.filter(t=>t.commands).reduce((s,i)=>{const{name:n,type:a}=i,r={},o=i.commands({schema:t,utils:ya,...["node","mark"].includes(a)?{type:t[a+"s"][n]}:{}}),l=(t,s)=>{r[t]=t=>{if("function"!==typeof s||!e.editable)return!1;e.focus();const i=s(t);return"function"===typeof i?i(e.state,e.dispatch,e):i}};return"object"===typeof o?Object.entries(o).forEach(([t,e])=>{l(t,e)}):l(n,o),{...s,...r}},{})}buttons(t="mark"){const e={};return this.extensions.filter(e=>e.type===t).filter(t=>t.button).forEach(t=>{Array.isArray(t.button)?t.button.forEach((s,i)=>{e[t.name+"-"+i]=s}):e[t.name]=t.button}),e}getAllowedExtensions(t){return t instanceof Array||!t?t instanceof Array?this.extensions.filter(e=>!t.includes(e.name)):this.extensions:[]}getFromExtensions(t,e,s=this.extensions){return s.filter(t=>["extension"].includes(t.type)).filter(e=>e[t]).map(s=>s[t]({...e,utils:ya}))}getFromNodesAndMarks(t,e,s=this.extensions){return s.filter(t=>["node","mark"].includes(t.type)).filter(e=>e[t]).map(s=>s[t]({...e,type:e.schema[s.type+"s"][s.name],utils:ya}))}inputRules({schema:t,excludedExtensions:e}){const s=this.getAllowedExtensions(e),i=this.getFromExtensions("inputRules",{schema:t},s),n=this.getFromNodesAndMarks("inputRules",{schema:t},s);return[...i,...n].reduce((t,e)=>[...t,...e],[])}keymaps({schema:t}){const e=this.getFromExtensions("keys",{schema:t}),s=this.getFromNodesAndMarks("keys",{schema:t});return[...e,...s].map(t=>Object(Zn["a"])(t))}get marks(){return this.extensions.filter(t=>"mark"===t.type).reduce((t,{name:e,schema:s})=>({...t,[e]:s}),{})}get nodes(){return this.extensions.filter(t=>"node"===t.type).reduce((t,{name:e,schema:s})=>({...t,[e]:s}),{})}get options(){const{view:t}=this;return this.extensions.reduce((e,s)=>({...e,[s.name]:new Proxy(s.options,{set(e,s,i){const n=e[s]!==i;return Object.assign(e,{[s]:i}),n&&t.updateState(t.state),!0}})}),{})}pasteRules({schema:t,excludedExtensions:e}){const s=this.getAllowedExtensions(e),i=this.getFromExtensions("pasteRules",{schema:t},s),n=this.getFromNodesAndMarks("pasteRules",{schema:t},s);return[...i,...n].reduce((t,e)=>[...t,...e],[])}plugins({schema:t}){const e=this.getFromExtensions("plugins",{schema:t}),s=this.getFromNodesAndMarks("plugins",{schema:t});return[...e,...s].reduce((t,e)=>[...t,...e],[]).map(t=>t instanceof Yn["d"]?t:new Yn["d"](t))}}class xa{constructor(t={}){this.options={...this.defaults,...t}}init(){return null}bindEditor(t=null){this.editor=t}get name(){return null}get type(){return"extension"}get defaults(){return{}}plugins(){return[]}inputRules(){return[]}pasteRules(){return[]}keys(){return{}}}class Oa extends xa{constructor(t={}){super(t)}get type(){return"node"}get schema(){return null}commands(){return{}}}class Ca extends Oa{get defaults(){return{inline:!1}}get name(){return"doc"}get schema(){return{content:this.options.inline?"paragraph+":"block+"}}}class Sa extends Oa{commands({utils:t,type:e}){return{paragraph:()=>t.setBlockType(e)}}get schema(){return{content:"inline*",group:"block",draggable:!1,parseDOM:[{tag:"p"}],toDOM:()=>["p",0]}}get name(){return"paragraph"}}class Ea extends Oa{get name(){return"text"}get schema(){return{group:"inline"}}}class ja extends _a{constructor(t={}){super(),this.defaults={autofocus:!1,content:"",disableInputRules:!1,disablePasteRules:!1,editable:!0,element:null,extensions:[],emptyDocument:{type:"doc",content:[]},events:{},inline:!1,parseOptions:{},topNode:"doc",useBuiltInExtensions:!0},this.init(t)}blur(){this.view.dom.blur()}get builtInExtensions(){return this.options.useBuiltInExtensions?[new Ca({inline:this.options.inline}),new Ea,new Sa]:[]}buttons(t){return this.extensions.buttons(t)}clearContent(t=!1){this.setContent(this.options.emptyDocument,t)}command(t,...e){this.commands[t]&&this.commands[t](...e)}createCommands(){return this.extensions.commands({schema:this.schema,view:this.view})}createDocument(t,e=this.options.parseOptions){if(null===t)return this.schema.nodeFromJSON(this.options.emptyDocument);if("object"===typeof t)try{return this.schema.nodeFromJSON(t)}catch(s){return window.console.warn("Invalid content.","Passed value:",t,"Error:",s),this.schema.nodeFromJSON(this.options.emptyDocument)}if("string"===typeof t){const s=`
${t}
`,i=new window.DOMParser,n=i.parseFromString(s,"text/html").body.firstElementChild;return Jn["a"].fromSchema(this.schema).parse(n,e)}return!1}createEvents(){const t=this.options.events||{};return Object.entries(t).forEach(([t,e])=>{this.on(t,e)}),t}createExtensions(){return new wa([...this.builtInExtensions,...this.options.extensions],this)}createFocusEvents(){const t=(t,e,s=!0)=>{this.focused=s,this.emit(s?"focus":"blur",{event:e,state:t.state,view:t});const i=this.state.tr.setMeta("focused",s);this.view.dispatch(i)};return new Yn["d"]({props:{attributes:{tabindex:0},handleDOMEvents:{focus:(e,s)=>{t(e,s,!0)},blur:(e,s)=>{t(e,s,!1)}}}})}createInputRules(){return this.extensions.inputRules({schema:this.schema,excludedExtensions:this.options.disableInputRules})}createKeymaps(){return this.extensions.keymaps({schema:this.schema})}createMarks(){return this.extensions.marks}createNodes(){return this.extensions.nodes}createPasteRules(){return this.extensions.pasteRules({schema:this.schema,excludedExtensions:this.options.disablePasteRules})}createPlugins(){return this.extensions.plugins({schema:this.schema})}createSchema(){return new Jn["i"]({topNode:this.options.topNode,nodes:this.nodes,marks:this.marks})}createState(){return Yn["b"].create({schema:this.schema,doc:this.createDocument(this.options.content),plugins:[...this.plugins,Object(Qn["b"])({rules:this.inputRules}),...this.pasteRules,...this.keymaps,Object(Zn["a"])({Backspace:Qn["d"]}),Object(Zn["a"])(Xn["a"]),this.createFocusEvents()]})}createView(){return new Vn["a"](this.element,{dispatchTransaction:this.dispatchTransaction.bind(this),editable:()=>this.options.editable,handlePaste:(...t)=>{this.emit("paste",...t)},handleDrop:(...t)=>{this.emit("drop",...t)},state:this.createState()})}destroy(){this.view&&this.view.destroy()}dispatchTransaction(t){const e=this.state,s=this.state.apply(t);this.view.updateState(s),this.selection={from:this.state.selection.from,to:this.state.selection.to},this.setActiveNodesAndMarks();const i={editor:this,getHTML:this.getHTML.bind(this),getJSON:this.getJSON.bind(this),state:this.state,transaction:t};this.emit("transaction",i),!t.docChanged&&t.getMeta("preventUpdate")||this.emit("update",i);const{from:n,to:a}=this.state.selection,r=!e||!e.selection.eq(s.selection);this.emit(s.selection.empty?"deselect":"select",{...i,from:n,hasChanged:r,to:a})}focus(t=null){if(this.view.focused&&null===t||!1===t)return;const{from:e,to:s}=this.selectionAtPosition(t);this.setSelection(e,s),setTimeout(()=>this.view.focus(),10)}getHTML(){const t=document.createElement("div"),e=Jn["b"].fromSchema(this.schema).serializeFragment(this.state.doc.content);return t.appendChild(e),this.options.inline&&t.querySelector("p")?t.querySelector("p").innerHTML:t.innerHTML}getJSON(){return this.state.doc.toJSON()}getMarkAttrs(t=null){return this.activeMarkAttrs[t]}getSchemaJSON(){return JSON.parse(JSON.stringify({nodes:this.nodes,marks:this.marks}))}init(t={}){this.options={...this.defaults,...t},this.element=this.options.element,this.focused=!1,this.selection={from:0,to:0},this.events=this.createEvents(),this.extensions=this.createExtensions(),this.nodes=this.createNodes(),this.marks=this.createMarks(),this.schema=this.createSchema(),this.keymaps=this.createKeymaps(),this.inputRules=this.createInputRules(),this.pasteRules=this.createPasteRules(),this.plugins=this.createPlugins(),this.view=this.createView(),this.commands=this.createCommands(),this.setActiveNodesAndMarks(),!1!==this.options.autofocus&&this.focus(this.options.autofocus),this.emit("init",{view:this.view,state:this.state}),this.extensions.view=this.view}isEditable(){return this.options.editable}isEmpty(){if(this.state)return 0===this.state.doc.textContent.length}get isActive(){return Object.entries({...this.activeMarks,...this.activeNodes}).reduce((t,[e,s])=>({...t,[e]:(t={})=>s(t)}),{})}removeMark(t){if(this.schema.marks[t])return ya.removeMark(this.schema.marks[t])(this.state,this.view.dispatch)}selectionAtPosition(t=null){if(this.selection&&null===t)return this.selection;if("start"===t||!0===t)return{from:0,to:0};if("end"===t){const{doc:t}=this.state;return{from:t.content.size,to:t.content.size}}return{from:t,to:t}}setActiveNodesAndMarks(){this.activeMarks=Object.values(this.schema.marks).filter(t=>ya.markIsActive(this.state,t)).map(t=>t.name),this.activeMarkAttrs=Object.entries(this.schema.marks).reduce((t,[e,s])=>({...t,[e]:ya.getMarkAttrs(this.state,s)}),{}),this.activeNodes=Object.values(this.schema.nodes).filter(t=>ya.nodeIsActive(this.state,t)).map(t=>t.name)}setContent(t={},e=!1,s){const{doc:i,tr:n}=this.state,a=this.createDocument(t,s),r=Yn["g"].create(i,0,i.content.size),o=n.setSelection(r).replaceSelectionWith(a,!1).setMeta("preventUpdate",!e);this.view.dispatch(o)}setSelection(t=0,e=0){const{doc:s,tr:i}=this.state,n=ya.minMax(t,0,s.content.size),a=ya.minMax(e,0,s.content.size),r=Yn["g"].create(s,n,a),o=i.setSelection(r);this.view.dispatch(o)}get state(){return this.view?this.view.state:null}toggleMark(t){if(this.schema.marks[t])return ya.toggleMark(this.schema.marks[t])(this.state,this.view.dispatch)}updateMark(t,e){if(this.schema.marks[t])return ya.updateMark(this.schema.marks[t],e)(this.state,this.view.dispatch)}}var Ta=function(){var t=this,e=t.$createElement,s=t._self._c||e;return s("k-form-dialog",{ref:"dialog",attrs:{fields:t.fields,"submit-button":t.$t("confirm"),size:"medium"},on:{close:function(e){return t.$emit("close")},submit:t.submit},model:{value:t.link,callback:function(e){t.link=e},expression:"link"}})},La=[],Ia={data(){return{link:{href:null,title:null,target:!1}}},computed:{fields(){return{href:{label:this.$t("url"),type:"text",icon:"url"},title:{label:this.$t("title"),type:"text",icon:"title"},target:{label:this.$t("open.newWindow"),type:"toggle",text:[this.$t("no"),this.$t("yes")]}}}},methods:{open(t){this.link={title:null,target:!1,...t},this.link.target=Boolean(this.link.target),this.$refs.dialog.open()},submit(){this.$emit("submit",{...this.link,target:this.link.target?"_blank":null}),this.$refs.dialog.close()}}},Aa=Ia,Ba=Object(c["a"])(Aa,Ta,La,!1,null,null,null),Ma=Ba.exports;class Da extends xa{constructor(t={}){super(t)}command(){return()=>{}}remove(){this.editor.removeMark(this.name)}get schema(){return null}get type(){return"mark"}toggle(){return this.editor.toggleMark(this.name)}update(t){this.editor.updateMark(this.name,t)}}class Na extends Da{get button(){return{icon:"code",label:I["a"].$t("toolbar.button.code")}}commands(){return()=>this.toggle()}inputRules({type:t,utils:e}){return[e.markInputRule(/(?:`)([^`]+)(?:`)$/,t)]}keys(){return{"Mod-`":()=>this.toggle()}}get name(){return"code"}pasteRules({type:t,utils:e}){return[e.markPasteRule(/(?:`)([^`]+)(?:`)/g,t)]}get schema(){return{excludes:"_",parseDOM:[{tag:"code"}],toDOM:()=>["code",0]}}}class Pa extends Da{get button(){return{icon:"bold",label:I["a"].$t("toolbar.button.bold")}}commands(){return()=>this.toggle()}inputRules({type:t,utils:e}){return[e.markInputRule(/(?:\*\*|__)([^*_]+)(?:\*\*|__)$/,t)]}keys(){return{"Mod-b":()=>this.toggle()}}get name(){return"bold"}pasteRules({type:t,utils:e}){return[e.markPasteRule(/(?:\*\*|__)([^*_]+)(?:\*\*|__)/g,t)]}get schema(){return{parseDOM:[{tag:"strong"},{tag:"b",getAttrs:t=>"normal"!==t.style.fontWeight&&null},{style:"font-weight",getAttrs:t=>/^(bold(er)?|[5-9]\d{2,})$/.test(t)&&null}],toDOM:()=>["strong",0]}}}class Ra extends Da{get button(){return{icon:"italic",label:I["a"].$t("toolbar.button.italic")}}commands(){return()=>this.toggle()}inputRules({type:t,utils:e}){return[e.markInputRule(/(?:^|[^_])(_([^_]+)_)$/,t),e.markInputRule(/(?:^|[^*])(\*([^*]+)\*)$/,t)]}keys(){return{"Mod-i":()=>this.toggle()}}get name(){return"italic"}pasteRules({type:t,utils:e}){return[e.markPasteRule(/_([^_]+)_/g,t),e.markPasteRule(/\*([^*]+)\*/g,t)]}get schema(){return{parseDOM:[{tag:"i"},{tag:"em"},{style:"font-style=italic"}],toDOM:()=>["em",0]}}}class qa extends Da{get button(){return{icon:"url",label:I["a"].$t("toolbar.button.link")}}commands(){return{link:()=>{this.editor.emit("link",this.editor)},insertLink:(t={})=>{if(t.href)return this.update(t)},removeLink:()=>this.remove(),toggleLink:(t={})=>{t.href&&t.href.length>0?this.editor.command("insertLink",t):this.editor.command("removeLink")}}}get defaults(){return{target:null}}get name(){return"link"}pasteRules({type:t,utils:e}){return[e.pasteRule(/https?:\/\/(www\.)?[-a-zA-Z0-9@:%._+~#=]{1,256}\.[a-zA-Z]{2,}\b([-a-zA-Z0-9@:%_+.~#?&//=,]*)/gi,t,t=>({href:t}))]}plugins(){return[{props:{handleClick:(t,e,s)=>{const i=this.editor.getMarkAttrs("link");i.href&&!0===s.altKey&&s.target instanceof HTMLAnchorElement&&(s.stopPropagation(),window.open(i.href,i.target))}}}]}get schema(){return{attrs:{href:{default:null},target:{default:null},title:{default:null}},inclusive:!1,parseDOM:[{tag:"a[href]",getAttrs:t=>({href:t.getAttribute("href"),target:t.getAttribute("target"),title:t.getAttribute("title")})}],toDOM:t=>["a",{...t.attrs,rel:"noopener noreferrer nofollow"},0]}}}class Fa extends Da{get button(){return{icon:"strikethrough",label:I["a"].$t("toolbar.button.strike")}}commands(){return()=>this.toggle()}inputRules({type:t,utils:e}){return[e.markInputRule(/~([^~]+)~$/,t)]}keys(){return{"Mod-d":()=>this.toggle()}}get name(){return"strike"}pasteRules({type:t,utils:e}){return[e.markPasteRule(/~([^~]+)~/g,t)]}get schema(){return{parseDOM:[{tag:"s"},{tag:"del"},{tag:"strike"},{style:"text-decoration",getAttrs:t=>"line-through"===t}],toDOM:()=>["s",0]}}}class Ua extends Da{get button(){return{icon:"underline",label:I["a"].$t("toolbar.button.underline")}}commands(){return()=>this.toggle()}keys(){return{"Mod-u":()=>this.toggle()}}get name(){return"underline"}get schema(){return{parseDOM:[{tag:"u"},{style:"text-decoration",getAttrs:t=>"underline"===t}],toDOM:()=>["u",0]}}}class Ha extends Oa{get button(){return{icon:"list-bullet",label:I["a"].$t("toolbar.button.ul")}}commands({type:t,schema:e,utils:s}){return()=>s.toggleList(t,e.nodes.listItem)}inputRules({type:t,utils:e}){return[e.wrappingInputRule(/^\s*([-+*])\s$/,t)]}keys({type:t,schema:e,utils:s}){return{"Shift-Ctrl-8":s.toggleList(t,e.nodes.listItem)}}get name(){return"bulletList"}get schema(){return{content:"listItem+",group:"block",parseDOM:[{tag:"ul"}],toDOM:()=>["ul",0]}}}class za extends Oa{commands({utils:t,type:e}){return()=>this.createHardBreak(t,e)}createHardBreak(t,e){return t.chainCommands(t.exitCode,(t,s)=>(s(t.tr.replaceSelectionWith(e.create()).scrollIntoView()),!0))}get defaults(){return{enter:!1,text:!1}}keys({utils:t,type:e}){const s=this.createHardBreak(t,e);let i={"Mod-Enter":s,"Shift-Enter":s};return this.options.enter&&(i["Enter"]=s),i}get name(){return"hardBreak"}get schema(){return{inline:!0,group:"inline",selectable:!1,parseDOM:[{tag:"br"}],toDOM:()=>["br"]}}}class Ka extends Oa{get button(){return this.options.levels.map(t=>({command:"h"+t,icon:"title",label:"Heading "+t}))}commands({type:t,schema:e,utils:s}){let i={toggleHeading:i=>s.toggleBlockType(t,e.nodes.paragraph,i)};return this.options.levels.forEach(e=>{i["h"+e]=()=>s.setBlockType(t,{level:e})}),i}get defaults(){return{levels:[1,2,3]}}inputRules({type:t,utils:e}){return this.options.levels.map(s=>e.textblockTypeInputRule(new RegExp(`^(#{1,${s}})\\s$`),t,()=>({level:s})))}keys({type:t,utils:e}){return this.options.levels.reduce((s,i)=>({...s,["Shift-Ctrl-"+i]:e.setBlockType(t,{level:i})}),{})}get name(){return"heading"}get schema(){return{attrs:{level:{default:1}},content:"inline*",group:"block",defining:!0,draggable:!1,parseDOM:this.options.levels.map(t=>({tag:"h"+t,attrs:{level:t}})),toDOM:t=>["h"+t.attrs.level,0]}}}class Ga extends Oa{commands({type:t}){return()=>(e,s)=>s(e.tr.replaceSelectionWith(t.create()))}inputRules({type:t,utils:e}){return[e.nodeInputRule(/^(?:---|___\s|\*\*\*\s)$/,t)]}get name(){return"horizontalRule"}get schema(){return{group:"block",parseDOM:[{tag:"hr"}],toDOM:()=>["hr"]}}}class Wa extends Oa{keys({type:t,utils:e}){return{Enter:e.splitListItem(t),"Shift-Tab":e.liftListItem(t),Tab:e.sinkListItem(t)}}get name(){return"listItem"}get schema(){return{content:"paragraph block*",defining:!0,draggable:!1,parseDOM:[{tag:"li"}],toDOM:()=>["li",0]}}}class Ya extends Oa{get button(){return{icon:"list-numbers",label:I["a"].$t("toolbar.button.ol")}}commands({type:t,schema:e,utils:s}){return()=>s.toggleList(t,e.nodes.listItem)}inputRules({type:t,utils:e}){return[e.wrappingInputRule(/^(\d+)\.\s$/,t,t=>({order:+t[1]}),(t,e)=>e.childCount+e.attrs.order===+t[1])]}keys({type:t,schema:e,utils:s}){return{"Shift-Ctrl-9":s.toggleList(t,e.nodes.listItem)}}get name(){return"orderedList"}get schema(){return{attrs:{order:{default:1}},content:"listItem+",group:"block",parseDOM:[{tag:"ol",getAttrs:t=>({order:t.hasAttribute("start")?+t.getAttribute("start"):1})}],toDOM:t=>1===t.attrs.order?["ol",0]:["ol",{start:t.attrs.order},0]}}}var Va=s("8726");class Ja extends xa{commands(){return{undo:()=>Va["d"],redo:()=>Va["b"],undoDepth:()=>Va["e"],redoDepth:()=>Va["c"]}}get defaults(){return{depth:"",newGroupDelay:""}}keys(){return{"Mod-z":Va["d"],"Mod-y":Va["b"],"Shift-Mod-z":Va["b"],"Mod-я":Va["d"],"Shift-Mod-я":Va["b"]}}get name(){return"history"}plugins(){return[Object(Va["a"])({depth:this.options.depth,newGroupDelay:this.options.newGroupDelay})]}}class Za extends xa{constructor(t={}){super(t)}close(){this.visible=!1,this.emit()}emit(){this.editor.emit("toolbar",{marks:this.marks,nodes:this.nodes,position:this.position,visible:this.visible})}init(){this.position={left:0,bottom:0},this.visible=!1,this.editor.on("blur",()=>{this.close()}),this.editor.on("deselect",()=>{this.close()}),this.editor.on("select",({hasChanged:t})=>{!1!==t?this.open():this.emit()})}get marks(){return this.editor.activeMarks}get nodes(){return this.editor.activeNodes}open(){this.visible=!0,this.reposition(),this.emit()}reposition(){const{from:t,to:e}=this.editor.selection,s=this.editor.view.coordsAtPos(t),i=this.editor.view.coordsAtPos(e,!0),n=this.editor.element.getBoundingClientRect();let a=(s.left+i.left)/2-n.left,r=Math.round(n.bottom-s.top);return this.position={bottom:r,left:a}}get type(){return"toolbar"}}var Xa,Qa,tr=function(){var t=this,e=t.$createElement,s=t._self._c||e;return s("div",{staticClass:"k-writer-toolbar"},[Object.keys(t.nodeButtons).length>1&&t.activeNode?s("k-dropdown",{nativeOn:{mousedown:function(t){t.preventDefault()}}},[s("k-button",{staticClass:"k-writer-toolbar-button k-writer-toolbar-nodes",attrs:{icon:t.activeNode.icon},on:{click:function(e){return t.$refs.nodes.toggle()}}}),s("k-dropdown-content",{ref:"nodes"},t._l(t.nodeButtons,(function(e,i){return s("k-dropdown-item",{key:i,attrs:{icon:e.icon},on:{click:function(s){return t.command(e.command||i)}}},[t._v(" "+t._s(e.label)+" ")])})),1)],1):t._e(),t._l(t.markButtons,(function(e,i){return s("k-button",{key:i,class:{"k-writer-toolbar-button":!0,"k-writer-toolbar-button-active":t.activeMarks.includes(i)},attrs:{icon:e.icon},on:{mousedown:function(s){return s.preventDefault(),t.command(e.command||i)}}})}))],2)},er=[],sr={props:{activeMarks:{type:Array,default(){return[]}},activeNodes:{type:Array,default(){return[]}},editor:{type:Object,required:!0},marks:{type:Array}},computed:{activeNode(){const t=Object.keys(this.nodeButtons).find(t=>this.activeNodes.includes(t));return!!t&&this.nodeButtons[t]},markButtons(){return this.buttons("mark")},nodeButtons(){return this.buttons("node")}},methods:{buttons(t){const e=this.editor.buttons(t);let s=this.sorting;!1!==s&&!1!==Array.isArray(s)||(s=Object.keys(e));let i={};return s.forEach(t=>{e[t]&&(i[t]=e[t])}),i},command(t,...e){this.$emit("command",t,...e)}}},ir=sr,nr=(s("6a16"),Object(c["a"])(ir,tr,er,!1,null,null,null)),ar=nr.exports,rr={components:{"k-writer-link-dialog":Ma,"k-writer-toolbar":ar},props:{autofocus:Boolean,breaks:Boolean,code:Boolean,disabled:Boolean,emptyDocument:{type:Object,default(){return{type:"doc",content:[]}}},headings:[Array,Boolean],inline:{type:Boolean,default:!1},marks:{type:[Array,Boolean],default:!0},nodes:{type:[Array,Boolean],default(){return["heading","bulletList","orderedList"]}},placeholder:String,spellcheck:Boolean,extensions:Array,value:{type:String,default:""}},data(){return{editor:null,html:this.value,isEmpty:!0,toolbar:!1}},watch:{value(t,e){t!==e&&t!==this.html&&(this.html=t,this.editor.setContent(this.html))}},mounted(){this.editor=new ja({autofocus:this.autofocus,content:this.value,editable:!this.disabled,element:this.$el,emptyDocument:this.emptyDocument,events:{link:t=>{this.$refs.linkDialog.open(t.getMarkAttrs("link"))},toolbar:t=>{this.toolbar=t,this.toolbar.visible&&this.$nextTick(()=>{this.onToolbarOpen()})},update:t=>{this.html=t.editor.getHTML(),this.isEmpty=t.editor.isEmpty(),this.isEmpty&&(0===t.editor.activeNodes.length||t.editor.activeNodes.includes("paragraph"))&&(this.html=""),this.$emit("input",this.html)}},extensions:[...this.createMarks(),...this.createNodes(),new Ja,new Za,...this.extensions||[]],inline:this.inline}),this.isEmpty=this.editor.isEmpty()},beforeDestroy(){this.editor.destroy()},methods:{filterExtensions(t,e,s){!1===e?e=[]:!0!==e&&!1!==Array.isArray(e)||(e=Object.keys(t));let i=[];return e.forEach(e=>{t[e]&&i.push(t[e])}),"function"===typeof s&&(i=s(e,i)),i},command(t,...e){this.editor.command(t,...e)},createMarks(){return this.filterExtensions({bold:new Pa,italic:new Ra,strike:new Fa,underline:new Ua,code:new Na,link:new qa},this.marks)},createNodes(){const t=new za({text:!0,enter:this.inline});return!0===this.inline?[t]:this.filterExtensions({bulletList:new Ha,orderedList:new Ya,heading:new Ka,horizontalRule:new Ga,listItem:new Wa},this.nodes,(e,s)=>((e.includes("bulletList")||e.includes("orderedList"))&&s.push(new Wa),s.push(t),s))},getHTML(){return this.editor.getHTML()},focus(){this.editor.focus()},onToolbarOpen(){if(this.$refs.toolbar){const t=this.$el.clientWidth,e=this.$refs.toolbar.$el.clientWidth;let s=this.toolbar.position.left;s-e/2<0&&(s=s+(e/2-s)-20),s+e/2>t&&(s=s-(s+e/2-t)+20),s!==this.toolbar.position.left&&(this.$refs.toolbar.$el.style.left=s+"px")}}}},or=rr,lr=(s("4041"),Object(c["a"])(or,Gn,Wn,!1,null,null,null)),cr=lr.exports,ur=function(){var t=this,e=t.$createElement,s=t._self._c||e;return s("label",{staticClass:"k-checkbox-input",on:{click:function(t){t.stopPropagation()}}},[s("input",{ref:"input",staticClass:"k-checkbox-input-native",attrs:{id:t.id,disabled:t.disabled,type:"checkbox"},domProps:{checked:t.value},on:{change:function(e){return t.onChange(e.target.checked)}}}),s("span",{staticClass:"k-checkbox-input-icon",attrs:{"aria-hidden":"true"}},[s("svg",{attrs:{width:"12",height:"10",viewBox:"0 0 12 10",xmlns:"http://www.w3.org/2000/svg"}},[s("path",{attrs:{d:"M1 5l3.3 3L11 1","stroke-width":"2",fill:"none","fill-rule":"evenodd"}})])]),s("span",{staticClass:"k-checkbox-input-label",domProps:{innerHTML:t._s(t.label)}})])},dr=[],pr=s("b5ae"),hr={inheritAttrs:!1,props:{autofocus:{type:Boolean,default:!1},disabled:{type:Boolean,default:!1},id:[Number,String],label:String,required:{type:Boolean,default:!1},value:Boolean},watch:{value(){this.onInvalid()}},mounted(){this.onInvalid(),this.$props.autofocus&&this.focus()},methods:{focus(){this.$refs.input.focus()},onChange(t){this.$emit("input",t)},onInvalid(){this.$emit("invalid",this.$v.$invalid,this.$v)},select(){this.focus()}},validations(){return{value:{required:!this.required||pr["required"]}}}},mr=hr,fr=(s("42e4"),Object(c["a"])(mr,ur,dr,!1,null,null,null)),gr=fr.exports,br=function(){var t=this,e=t.$createElement,s=t._self._c||e;return s("ul",{staticClass:"k-checkboxes-input",style:"--columns:"+t.columns},t._l(t.options,(function(e,i){return s("li",{key:i},[s("k-checkbox-input",{attrs:{id:t.id+"-"+i,label:e.text,value:-1!==t.selected.indexOf(e.value)},on:{input:function(s){return t.onInput(e.value,s)}}})],1)})),0)},kr=[],vr={inheritAttrs:!1,props:{autofocus:Boolean,columns:Number,disabled:Boolean,id:{type:[Number,String],default(){return this._uid}},max:Number,min:Number,options:Array,required:Boolean,value:{type:[Array,Object],default(){return[]}}},data(){return{selected:this.valueToArray(this.value)}},watch:{value(t){this.selected=this.valueToArray(t)},selected(){this.onInvalid()}},mounted(){this.onInvalid(),this.$props.autofocus&&this.focus()},methods:{focus(){this.$el.querySelector("input").focus()},onInput(t,e){if(!0===e)this.selected.push(t);else{const e=this.selected.indexOf(t);-1!==e&&this.selected.splice(e,1)}this.$emit("input",this.selected)},onInvalid(){this.$emit("invalid",this.$v.$invalid,this.$v)},select(){this.focus()},valueToArray(t){return!0===Array.isArray(t)?t:"string"===typeof t?String(t).split(","):"object"===typeof t?Object.values(t):void 0}},validations(){return{selected:{required:!this.required||pr["required"],min:!this.min||Object(pr["minLength"])(this.min),max:!this.max||Object(pr["maxLength"])(this.max)}}}},$r=vr,yr=Object(c["a"])($r,br,kr,!1,null,null,null),_r=yr.exports,wr=function(){var t=this,e=t.$createElement,s=t._self._c||e;return s("k-text-input",t._b({ref:"input",class:"k-"+t.type+"-input",attrs:{placeholder:t.display,spellcheck:!1,type:"text"},on:{blur:t.onBlur,input:t.onInput,invalid:t.onInvalid,focus:function(e){return t.$emit("focus")},keydown:[function(e){return!e.type.indexOf("key")&&t._k(e.keyCode,"down",40,e.key,["Down","ArrowDown"])?null:(e.stopPropagation(),e.preventDefault(),t.onDown(e))},function(e){return!e.type.indexOf("key")&&t._k(e.keyCode,"up",38,e.key,["Up","ArrowUp"])?null:(e.stopPropagation(),e.preventDefault(),t.onUp(e))},function(e){return!e.type.indexOf("key")&&t._k(e.keyCode,"enter",13,e.key,"Enter")?null:t.onEnter(e)},function(e){return!e.type.indexOf("key")&&t._k(e.keyCode,"tab",9,e.key,"Tab")?null:t.onTab(e)}]},model:{value:t.input,callback:function(e){t.input=e},expression:"input"}},"k-text-input",t.$props,!1))},xr=[],Or={inheritAttrs:!1,props:{autofocus:Boolean,disabled:Boolean,display:{type:String,default:"DD.MM.YYYY"},id:[String,Number],max:String,min:String,required:Boolean,step:{type:Object,default(){return{size:1,unit:"day"}}},type:{type:String,default:"date"},value:String},data(){return{input:this.toFormat(this.value),selected:null}},computed:{map(){return{day:["D","DD"],month:["M","MM","MMM","MMMM"],year:["YY","YYYY"]}},parsed(){if(this.input)for(let t=0;t[t]):i.forEach(t=>{s=s.concat(e.map(e=>e.concat([t])))}),t=t.concat(s),e=s,s=[]}}return t.map(t=>t.join(this.separator)).reverse()},separator(){return this.display.match(/[\W]/)[0]},tokens(){return this.display.split(/\W/)}},watch:{value(t){this.input=this.toFormat(t),this.onInvalid()}},mounted(){this.onInvalid()},methods:{emit(t){const e=this.toFormat(this.parsed,"YYYY-MM-DD HH:mm:ss")||"";this.$emit(t,e)},focus(){this.$refs.input.focus()},manipulate(t){let e;if(this.parsed){let s=this.step.unit,i=this.step.size;if(this.selected=this.toCursorIndex(),null!==this.selected){const e=this.tokens[this.selected];"a"===e.toLowerCase()?(s="hour",i=12,t="pm"===this.parts[this.selected]?"subtract":"add"):(s=this.toUnit(e),s!==this.step.unit&&(i=1))}e=this.parsed.clone()[t](i,s)}else e=this.toNearest(this.$library.dayjs()),this.selected=this.toIndex();this.input=this.toFormat(e),this.emit("update"),this.$nextTick(()=>{this.select()})},onBlur(){this.parsed||(this.input=null),this.selected=null,this.emit("update")},onDown(){this.manipulate("subtract")},onEnter(){this.onBlur(),this.emit("enter")},onInput(){this.emit("input")},onInvalid(t,e){this.$emit("invalid",t||this.$v.$invalid,e||this.$v)},onTab(t){const e=this.toCursorIndex();null===this.selected?this.selected=e||0:e!==this.selected?this.selected=e:this.selected++,this.selected>=this.parts.length?this.selected=null:(t.preventDefault(),t.stopPropagation(),this.select())},onUp(){this.manipulate("add")},select(){if(null!==this.selected){const t=this.toRange(this.selected);this.selected>0&&t.start++,this.$refs.input.$refs.input.setSelectionRange(t.start,t.end)}},toCursorIndex(){if(0===this.$refs.input.$refs.input.selectionStart&&this.$refs.input.$refs.input.selectionEnd===String(this.input).length)return null;for(let t=0;t=this.$refs.input.$refs.input.selectionEnd)return t}},toDatetime(t){return this.$library.dayjs.utc(t)},toFormat(t,e=this.display){return t?("string"==typeof t&&(t=this.toDatetime(t)),!1===t.isValid()?null:this.toNearest(t).format(e)):null},toNearest(t,e=this.step.unit,s=this.step.size){"day"===e&&(e="date");const i=t.get(e),n=Math.round(i/s)*s;return t.set(e,n).startOf(e)},toIndex(t=this.step.unit){const e=this.map[t];for(let s=0;se.includes(t));return s[0]},toUnit(t,e=!0){const s=Object.keys(this.map),i=Object.values(this.map);let n=i.findIndex(e=>e.includes(t));const a=this.step.unit;return!0===e&&nthis.$helper.validate.datetime(this,t,this.min,"isAfter",this.step.unit)),max:!this.max||(t=>this.$helper.validate.datetime(this,t,this.max,"isBefore",this.step.unit)),required:!this.required||pr["required"]}}}},Cr=Or,Sr=Object(c["a"])(Cr,wr,xr,!1,null,null,null),Er=Sr.exports,jr=function(){var t=this,e=t.$createElement,s=t._self._c||e;return s("div",{staticClass:"k-datetime-input"},[s("k-date-input",t._b({ref:"dateInput",on:{input:function(e){return t.onInput(e,"date")},update:function(e){return t.onUpdate(e,"date")},enter:function(e){return t.onEnter(e,"date")},focus:function(e){return t.$emit("focus")}}},"k-date-input",t.dateOptions,!1)),t.time?[s("k-time-input",t._b({ref:"timeInput",on:{input:function(e){return t.onInput(e,"time")},update:function(e){return t.onUpdate(e,"time")},enter:function(e){return t.onEnter(e,"time")},focus:function(e){return t.$emit("focus")}}},"k-time-input",t.timeOptions,!1))]:t._e()],2)},Tr=[],Lr={inheritAttrs:!1,props:{...Er.props,time:{type:[Boolean,Object],default(){return{}}},value:String},data(){return{input:this.toDatetime(this.value)}},computed:{dateOptions(){return{autofocus:this.autofocus,disabled:this.disabled,display:this.display,id:this.id,required:this.required,value:this.value}},timeOptions(){return{...this.time,disabled:this.disabled,required:this.required,value:this.value?this.toDatetime(this.value).format("HH:mm:ss"):null}}},watch:{value(){this.input=this.toDatetime(this.value),this.onInvalid()}},mounted(){this.onInvalid()},methods:{emit(t,e=this.input){e?this.$emit(t,e.format("YYYY-MM-DD HH:mm:ss")):this.$emit(t,"")},focus(){this.$refs.dateInput.focus()},onUpdate(t,e){const s=this.toDatetime(this.value);e=this.toDatetime(t,e,s),this.emit("update",e)},onEnter(t,e){this.onUpdate(e,t),this.emit("enter")},onInput(t,e){this.input=this.toDatetime(t,e,this.input),this.emit("input")},onInvalid(){this.$emit("invalid",this.$v.$invalid,this.$v)},toDatetime(t,e,s){if(!t)return null;let i=this.$library.dayjs.utc(t);return"time"===e&&(i=this.$library.dayjs.utc(t,"HH:mm:ss")),!1===i.isValid()?null:e&&s?"date"===e?s.clone().utc().set("year",i.get("year")).set("month",i.get("month")).set("date",i.get("date")):"time"===e?s.clone().utc().set("hour",i.get("hour")).set("minute",i.get("minute")).set("second",i.get("second")):void 0:i}},validations(){return{value:{min:!this.min||(t=>this.$helper.validate.datetime(this,t,this.min,"isAfter",this.step.unit)),max:!this.max||(t=>this.$helper.validate.datetime(this,t,this.max,"isBefore",this.step.unit)),required:!this.required||pr["required"]}}}},Ir=Lr,Ar=(s("4433"),Object(c["a"])(Ir,jr,Tr,!1,null,null,null)),Br=Ar.exports,Mr=function(){var t=this,e=t.$createElement,s=t._self._c||e;return s("input",t._g(t._b({ref:"input",staticClass:"k-text-input",attrs:{dir:t.direction}},"input",{autocomplete:t.autocomplete,autofocus:t.autofocus,disabled:t.disabled,id:t.id,minlength:t.minlength,name:t.name,pattern:t.pattern,placeholder:t.placeholder,required:t.required,spellcheck:t.spellcheck,type:t.type,value:t.value},!1),t.listeners))},Dr=[],Nr=function(t){const e=t.$store.state.languages.default||null,s=t.$store.state.languages.current||null,i=t.$store.state.system.info.multilang||!1,n=t.$store.state.system.info.user?t.$store.state.system.info.user.language:null,a=s?s.direction:null;if(i&&s&&!1===t.disabled&&(s.direction!==e.direction||n!==s.code))return a},Pr={inheritAttrs:!1,class:"k-text-input",props:{autocomplete:{type:[Boolean,String],default:"off"},autofocus:Boolean,disabled:Boolean,id:[Number,String],maxlength:Number,minlength:Number,name:[Number,String],pattern:String,placeholder:String,preselect:Boolean,required:Boolean,spellcheck:{type:[Boolean,String],default:"off"},type:{type:String,default:"text"},value:String},data(){return{listeners:{...this.$listeners,input:t=>this.onInput(t.target.value)}}},computed:{direction(){return Nr(this)}},watch:{value(){this.onInvalid()}},mounted(){this.onInvalid(),this.$props.autofocus&&this.focus(),this.$props.preselect&&this.select()},methods:{focus(){this.$refs.input.focus()},onInput(t){this.$emit("input",t)},onInvalid(){this.$emit("invalid",this.$v.$invalid,this.$v)},select(){this.$refs.input.select()}},validations(){const t=t=>!this.required&&!t||!this.$refs.input.validity.patternMismatch;return{value:{required:!this.required||pr["required"],minLength:!this.minlength||Object(pr["minLength"])(this.minlength),maxLength:!this.maxlength||Object(pr["maxLength"])(this.maxlength),email:"email"!==this.type||pr["email"],url:"url"!==this.type||pr["url"],pattern:!this.pattern||t}}}},Rr=Pr,qr=(s("cb8f"),Object(c["a"])(Rr,Mr,Dr,!1,null,null,null)),Fr=qr.exports,Ur={extends:Fr,props:{...Fr.props,autocomplete:{type:String,default:"email"},placeholder:{type:String,default(){return this.$t("email.placeholder")}},type:{type:String,default:"email"}}},Hr=Ur,zr=Object(c["a"])(Hr,Xa,Qa,!1,null,null,null),Kr=zr.exports,Gr=function(){var t=this,e=t.$createElement,s=t._self._c||e;return s("k-writer",t._b({ref:"input",staticClass:"k-list-input",attrs:{extensions:t.extensions,nodes:["bulletList","orderedList"],value:t.list},on:{input:t.onInput}},"k-writer",t.$props,!1))},Wr=[];class Yr extends Ca{get schema(){return{content:"bulletList|orderedList"}}}var Vr,Jr,Zr,Xr,Qr,to,eo,so,io={inheritAttrs:!1,props:{autofocus:Boolean,marks:{type:[Array,Boolean],default:!0},value:String},data(){return{list:this.value,html:this.value}},computed:{extensions(){return[new Yr({inline:!0})]}},watch:{value(t){t!==this.html&&(this.list=t,this.html=t)}},methods:{focus(){this.$refs.input.focus()},onInput(t){let e=(new DOMParser).parseFromString(t,"text/html"),s=e.querySelector("ul, ol");if(!s)return void this.$emit("input",this.list="");let i=s.textContent.trim();0!==i.length?(this.list=t,this.html=t.replace(/(

|<\/p>)/gi,""),this.$emit("input",this.html)):this.$emit("input",this.list="")}}},no=io,ao=(s("82c9"),Object(c["a"])(no,Gr,Wr,!1,null,null,null)),ro=ao.exports,oo=function(){var t=this,e=t.$createElement,s=t._self._c||e;return s("k-draggable",{staticClass:"k-multiselect-input",attrs:{list:t.state,options:t.dragOptions,"data-layout":t.layout,element:"k-dropdown"},on:{end:t.onInput},nativeOn:{click:function(e){return t.$refs.dropdown.toggle(e)}}},[t._l(t.sorted,(function(e){return s("k-tag",{key:e.value,ref:e.value,refInFor:!0,attrs:{removable:!0},on:{remove:function(s){return t.remove(e)}},nativeOn:{click:function(t){t.stopPropagation()},keydown:[function(e){return!e.type.indexOf("key")&&t._k(e.keyCode,"left",37,e.key,["Left","ArrowLeft"])||"button"in e&&0!==e.button?null:t.navigate("prev")},function(e){return!e.type.indexOf("key")&&t._k(e.keyCode,"right",39,e.key,["Right","ArrowRight"])||"button"in e&&2!==e.button?null:t.navigate("next")},function(e){return!e.type.indexOf("key")&&t._k(e.keyCode,"down",40,e.key,["Down","ArrowDown"])?null:t.$refs.dropdown.open(e)}]}},[s("span",{domProps:{innerHTML:t._s(e.text)}})])})),s("k-dropdown-content",{ref:"dropdown",attrs:{slot:"footer"},on:{open:t.onOpen,close:t.onClose},nativeOn:{keydown:function(e){return!e.type.indexOf("key")&&t._k(e.keyCode,"esc",27,e.key,["Esc","Escape"])?null:(e.stopPropagation(),t.close(e))}},slot:"footer"},[t.search?s("k-dropdown-item",{staticClass:"k-multiselect-search",attrs:{icon:"search"}},[s("input",{directives:[{name:"model",rawName:"v-model",value:t.q,expression:"q"}],ref:"search",attrs:{placeholder:t.search.min?t.$t("search.min",{min:t.search.min}):t.$t("search")+" …"},domProps:{value:t.q},on:{keydown:function(e){return!e.type.indexOf("key")&&t._k(e.keyCode,"esc",27,e.key,["Esc","Escape"])?null:(e.stopPropagation(),t.escape(e))},input:function(e){e.target.composing||(t.q=e.target.value)}}})]):t._e(),s("div",{staticClass:"k-multiselect-options"},[t._l(t.visible,(function(e){return s("k-dropdown-item",{key:e.value,class:{"k-multiselect-option":!0,selected:t.isSelected(e),disabled:!t.more},attrs:{icon:t.isSelected(e)?"check":"circle-outline"},on:{click:function(s){return s.preventDefault(),t.select(e)}},nativeOn:{keydown:[function(s){return!s.type.indexOf("key")&&t._k(s.keyCode,"enter",13,s.key,"Enter")?null:(s.preventDefault(),s.stopPropagation(),t.select(e))},function(s){return!s.type.indexOf("key")&&t._k(s.keyCode,"space",32,s.key,[" ","Spacebar"])?null:(s.preventDefault(),s.stopPropagation(),t.select(e))}]}},[s("span",{domProps:{innerHTML:t._s(e.display)}}),s("span",{staticClass:"k-multiselect-value",domProps:{innerHTML:t._s(e.info)}})])})),0===t.filtered.length?s("k-dropdown-item",{staticClass:"k-multiselect-option",attrs:{disabled:!0}},[t._v(" "+t._s(t.emptyLabel)+" ")]):t._e()],2),t.visible.length1&&!this.sort},dragOptions(){return{disabled:!this.draggable,draggable:".k-tag",delay:1}},emptyLabel(){return this.q?this.$t("search.results.none"):this.$t("options.none")},filtered(){return this.q&&this.q.length>=(this.search.min||0)?this.options.filter(t=>this.isFiltered(t)).map(t=>({...t,display:this.toHighlightedString(t.text),info:this.toHighlightedString(t.value)})):this.options.map(t=>({...t,display:t.text,info:t.value}))},more(){return!this.max||this.state.lengththis.options.findIndex(e=>e.value===t.value);return t.sort((t,s)=>e(t)-e(s))},visible(){return this.limit?this.filtered.slice(0,this.search.display||this.filtered.length):this.filtered}},watch:{value(t){this.state=t,this.onInvalid()}},mounted(){this.onInvalid(),this.$events.$on("click",this.close),this.$events.$on("keydown.cmd.s",this.close)},destroyed(){this.$events.$off("click",this.close),this.$events.$off("keydown.cmd.s",this.close)},methods:{add(t){!0===this.more&&(this.state.push(t),this.onInput())},blur(){this.close()},close(){!0===this.$refs.dropdown.isOpen&&(this.$refs.dropdown.close(),this.limit=!0)},escape(){this.q?this.q=null:this.close()},focus(){this.$refs.dropdown.open()},index(t){return this.state.findIndex(e=>e.value===t.value)},isFiltered(t){return String(t.text).match(this.regex)||String(t.value).match(this.regex)},isSelected(t){return-1!==this.index(t)},navigate(t){let e=document.activeElement;switch(t){case"prev":e&&e.previousSibling&&e.previousSibling.focus&&e.previousSibling.focus();break;case"next":e&&e.nextSibling&&e.nextSibling.focus&&e.nextSibling.focus();break}},onClose(){!1===this.$refs.dropdown.isOpen&&(document.activeElement===this.$parent.$el&&(this.q=null),this.$parent.$el.focus())},onInput(){this.$emit("input",this.sorted)},onInvalid(){this.$emit("invalid",this.$v.$invalid,this.$v)},onOpen(){this.$nextTick(()=>{this.$refs.search&&this.$refs.search.focus&&this.$refs.search.focus(),this.$refs.dropdown.$el.querySelector(".k-multiselect-options").scrollTop=this.scrollTop})},remove(t){this.state.splice(this.index(t),1),this.onInput()},select(t){this.scrollTop=this.$refs.dropdown.$el.querySelector(".k-multiselect-options").scrollTop,t={text:t.text,value:t.value},this.isSelected(t)?this.remove(t):this.add(t)},toHighlightedString(t){return t=this.$helper.string.stripHTML(t),t.replace(this.regex,"$1")}},validations(){return{state:{required:!this.required||pr["required"],minLength:!this.min||Object(pr["minLength"])(this.min),maxLength:!this.max||Object(pr["maxLength"])(this.max)}}}},uo=co,po=(s("11ae"),Object(c["a"])(uo,oo,lo,!1,null,null,null)),ho=po.exports,mo=function(){var t=this,e=t.$createElement,s=t._self._c||e;return s("input",t._g(t._b({ref:"input",staticClass:"k-number-input",attrs:{step:t.stepNumber,type:"number"},domProps:{value:t.number},on:{keydown:[function(e){return!e.type.indexOf("key")&&t._k(e.keyCode,"s",void 0,e.key,void 0)?null:e.ctrlKey?t.clean(e):null},function(e){return!e.type.indexOf("key")&&t._k(e.keyCode,"s",void 0,e.key,void 0)?null:e.metaKey?t.clean(e):null}]}},"input",{autofocus:t.autofocus,disabled:t.disabled,id:t.id,max:t.max,min:t.min,name:t.name,placeholder:t.placeholder,required:t.required},!1),t.listeners))},fo=[],go={inheritAttrs:!1,props:{autofocus:Boolean,disabled:Boolean,id:[Number,String],max:Number,min:Number,name:[Number,String],placeholder:String,preselect:Boolean,required:Boolean,step:Number,value:{type:[Number,String],default:null}},data(){return{number:this.format(this.value),stepNumber:this.format(this.step),timeout:null,listeners:{...this.$listeners,input:t=>this.onInput(t.target.value),blur:this.onBlur}}},watch:{value(t){this.number=t},number:{immediate:!0,handler(){this.onInvalid()}}},mounted(){this.$props.autofocus&&this.focus(),this.$props.preselect&&this.select()},methods:{decimals(){const t=Number(this.step||0);return Math.floor(t)===t?0:-1!==t.toString().indexOf("e")?parseInt(t.toFixed(16).split(".")[1].split("").reverse().join("")).toString().length:t.toString().split(".")[1].length||0},format(t){if(isNaN(t)||""===t)return"";const e=this.decimals();return t=e?parseFloat(t).toFixed(e):Number.isInteger(this.step)?parseInt(t):parseFloat(t),t},clean(){this.number=this.format(this.number)},emit(t){t=parseFloat(t),isNaN(t)&&(t=""),t!==this.value&&this.$emit("input",t)},focus(){this.$refs.input.focus()},onInvalid(){this.$emit("invalid",this.$v.$invalid,this.$v)},onInput(t){this.number=t,this.emit(t)},onBlur(){this.clean(),this.emit(this.number)},select(){this.$refs.input.select()}},validations(){return{value:{required:!this.required||pr["required"],min:!this.min||Object(pr["minValue"])(this.min),max:!this.max||Object(pr["maxValue"])(this.max)}}}},bo=go,ko=(s("6018"),Object(c["a"])(bo,mo,fo,!1,null,null,null)),vo=ko.exports,$o={extends:Fr,props:{...Fr.props,autocomplete:{type:String,default:"new-password"},type:{type:String,default:"password"}}},yo=$o,_o=Object(c["a"])(yo,Vr,Jr,!1,null,null,null),wo=_o.exports,xo=function(){var t=this,e=t.$createElement,s=t._self._c||e;return s("ul",{staticClass:"k-radio-input",style:"--columns:"+t.columns},t._l(t.options,(function(e,i){return s("li",{key:i},[s("input",{staticClass:"k-radio-input-native",attrs:{id:t.id+"-"+i,name:t.id,type:"radio"},domProps:{value:e.value,checked:t.value===e.value},on:{change:function(s){return t.onInput(e.value)}}}),e.info?s("label",{attrs:{for:t.id+"-"+i}},[s("span",{staticClass:"k-radio-input-text",domProps:{innerHTML:t._s(e.text)}}),s("span",{staticClass:"k-radio-input-info"},[t._v(t._s(e.info))])]):s("label",{attrs:{for:t.id+"-"+i},domProps:{innerHTML:t._s(e.text)}}),e.icon?s("k-icon",{attrs:{type:e.icon}}):t._e()],1)})),0)},Oo=[],Co={inheritAttrs:!1,props:{autofocus:Boolean,columns:Number,disabled:Boolean,id:{type:[Number,String],default(){return this._uid}},options:Array,required:Boolean,value:[String,Number,Boolean]},watch:{value(){this.onInvalid()}},mounted(){this.onInvalid(),this.$props.autofocus&&this.focus()},methods:{focus(){this.$el.querySelector("input").focus()},onInput(t){this.$emit("input",t)},onInvalid(){this.$emit("invalid",this.$v.$invalid,this.$v)},select(){this.focus()}},validations(){return{value:{required:!this.required||pr["required"]}}}},So=Co,Eo=(s("893d"),Object(c["a"])(So,xo,Oo,!1,null,null,null)),jo=Eo.exports,To=function(){var t=this,e=t.$createElement,s=t._self._c||e;return s("label",{staticClass:"k-range-input"},[s("input",t._g(t._b({ref:"input",staticClass:"k-range-input-native",style:"--min: "+t.min+"; --max: "+t.max+"; --value: "+t.position,attrs:{type:"range"},domProps:{value:t.position}},"input",{autofocus:t.autofocus,disabled:t.disabled,id:t.id,max:t.max,min:t.min,name:t.name,required:t.required,step:t.step},!1),t.listeners)),t.tooltip?s("span",{staticClass:"k-range-input-tooltip"},[t.tooltip.before?s("span",{staticClass:"k-range-input-tooltip-before"},[t._v(t._s(t.tooltip.before))]):t._e(),s("span",{staticClass:"k-range-input-tooltip-text"},[t._v(t._s(t.label))]),t.tooltip.after?s("span",{staticClass:"k-range-input-tooltip-after"},[t._v(t._s(t.tooltip.after))]):t._e()]):t._e()])},Lo=[],Io={inheritAttrs:!1,props:{autofocus:Boolean,disabled:Boolean,id:[String,Number],default:[Number,String],max:{type:Number,default:100},min:{type:Number,default:0},name:[String,Number],required:Boolean,step:{type:Number,default:1},tooltip:{type:[Boolean,Object],default(){return{before:null,after:null}}},value:[Number,String]},data(){return{listeners:{...this.$listeners,input:t=>this.onInput(t.target.value)}}},computed:{baseline(){return this.min<0?0:this.min},label(){return this.required||this.value||0===this.value?this.format(this.position):"–"},position(){return this.value||0===this.value?this.value:this.default||this.baseline}},watch:{position(){this.onInvalid()}},mounted(){this.onInvalid(),this.$props.autofocus&&this.focus()},methods:{focus(){this.$refs.input.focus()},format(t){const e=document.lang?document.lang.replace("_","-"):"en",s=this.step.toString().split("."),i=s.length>1?s[1].length:0;return new Intl.NumberFormat(e,{minimumFractionDigits:i}).format(t)},onInvalid(){this.$emit("invalid",this.$v.$invalid,this.$v)},onInput(t){this.$emit("input",t)}},validations(){return{position:{required:!this.required||pr["required"],min:!this.min||Object(pr["minValue"])(this.min),max:!this.max||Object(pr["maxValue"])(this.max)}}}},Ao=Io,Bo=(s("b5d2"),Object(c["a"])(Ao,To,Lo,!1,null,null,null)),Mo=Bo.exports,Do=function(){var t=this,e=t.$createElement,s=t._self._c||e;return s("span",{staticClass:"k-select-input",attrs:{"data-disabled":t.disabled,"data-empty":""===t.selected}},[s("select",t._g({ref:"input",staticClass:"k-select-input-native",attrs:{id:t.id,autofocus:t.autofocus,"aria-label":t.ariaLabel,disabled:t.disabled,name:t.name,required:t.required},domProps:{value:t.selected}},t.listeners),[t.hasEmptyOption?s("option",{attrs:{disabled:t.required,value:""}},[t._v(" "+t._s(t.emptyOption)+" ")]):t._e(),t._l(t.options,(function(e){return s("option",{key:e.value,attrs:{disabled:e.disabled},domProps:{value:e.value}},[t._v(" "+t._s(e.text)+" ")])}))],2),t._v(" "+t._s(t.label)+" ")])},No=[],Po={inheritAttrs:!1,props:{autofocus:Boolean,ariaLabel:String,default:String,disabled:Boolean,empty:{type:[Boolean,String],default:!0},id:[Number,String],name:[Number,String],placeholder:String,options:{type:Array,default:()=>[]},required:Boolean,value:{type:[String,Number,Boolean],default:""}},data(){return{selected:this.value,listeners:{...this.$listeners,click:t=>this.onClick(t),change:t=>this.onInput(t.target.value),input:()=>{}}}},computed:{emptyOption(){return this.placeholder||"—"},hasEmptyOption(){return!1!==this.empty&&!(this.required&&this.default)},label(){const t=this.text(this.selected);return""===this.selected||null===this.selected||null===t?this.emptyOption:t}},watch:{value(t){this.selected=t,this.onInvalid()}},mounted(){this.onInvalid(),this.$props.autofocus&&this.focus()},methods:{focus(){this.$refs.input.focus()},onClick(t){t.stopPropagation(),this.$emit("click",t)},onInvalid(){this.$emit("invalid",this.$v.$invalid,this.$v)},onInput(t){this.selected=t,this.$emit("input",this.selected)},select(){this.focus()},text(t){let e=null;return this.options.forEach(s=>{s.value==t&&(e=s.text)}),e}},validations(){return{selected:{required:!this.required||pr["required"]}}}},Ro=Po,qo=(s("6a18"),Object(c["a"])(Ro,Do,No,!1,null,null,null)),Fo=qo.exports,Uo=function(){var t=this,e=t.$createElement,s=t._self._c||e;return s("k-draggable",{ref:"box",staticClass:"k-tags-input",attrs:{list:t.tags,"data-layout":t.layout,options:t.dragOptions,dir:t.direction},on:{end:t.onInput}},[t._l(t.tags,(function(e,i){return s("k-tag",{key:i,ref:e.value,refInFor:!0,attrs:{removable:!t.disabled,name:"tag"},on:{remove:function(s){return t.remove(e)}},nativeOn:{click:function(t){t.stopPropagation()},blur:function(e){return t.selectTag(null)},focus:function(s){return t.selectTag(e)},keydown:[function(e){return!e.type.indexOf("key")&&t._k(e.keyCode,"left",37,e.key,["Left","ArrowLeft"])||"button"in e&&0!==e.button?null:t.navigate("prev")},function(e){return!e.type.indexOf("key")&&t._k(e.keyCode,"right",39,e.key,["Right","ArrowRight"])||"button"in e&&2!==e.button?null:t.navigate("next")}],dblclick:function(s){return t.edit(e)}}},[s("span",{domProps:{innerHTML:t._s(e.text)}})])})),s("span",{staticClass:"k-tags-input-element",attrs:{slot:"footer"},slot:"footer"},[s("k-autocomplete",{ref:"autocomplete",attrs:{html:!0,options:t.options,skip:t.skip},on:{select:t.addTag,leave:function(e){return t.$refs.input.focus()}}},[s("input",{directives:[{name:"model",rawName:"v-model.trim",value:t.newTag,expression:"newTag",modifiers:{trim:!0}}],ref:"input",attrs:{id:t.id,autofocus:t.autofocus,disabled:t.disabled||t.max&&t.tags.length>=t.max,name:t.name,autocomplete:"off",type:"text"},domProps:{value:t.newTag},on:{input:[function(e){e.target.composing||(t.newTag=e.target.value.trim())},function(e){return t.type(e.target.value)}],blur:[t.blurInput,function(e){return t.$forceUpdate()}],keydown:[function(e){return!e.type.indexOf("key")&&t._k(e.keyCode,"s",void 0,e.key,void 0)?null:e.metaKey?t.blurInput(e):null},function(e){return!e.type.indexOf("key")&&t._k(e.keyCode,"left",37,e.key,["Left","ArrowLeft"])||"button"in e&&0!==e.button||e.ctrlKey||e.shiftKey||e.altKey||e.metaKey?null:t.leaveInput(e)},function(e){return!e.type.indexOf("key")&&t._k(e.keyCode,"enter",13,e.key,"Enter")||e.ctrlKey||e.shiftKey||e.altKey||e.metaKey?null:t.enter(e)},function(e){return!e.type.indexOf("key")&&t._k(e.keyCode,"tab",9,e.key,"Tab")||e.ctrlKey||e.shiftKey||e.altKey||e.metaKey?null:t.tab(e)},function(e){return!e.type.indexOf("key")&&t._k(e.keyCode,"backspace",void 0,e.key,void 0)||e.ctrlKey||e.shiftKey||e.altKey||e.metaKey?null:t.leaveInput(e)}]}})])],1)],2)},Ho=[],zo={inheritAttrs:!1,props:{autofocus:Boolean,accept:{type:String,default:"all"},disabled:Boolean,icon:{type:[String,Boolean],default:"tag"},id:[Number,String],layout:String,max:Number,min:Number,name:[Number,String],options:{type:Array,default(){return[]}},required:Boolean,separator:{type:String,default:","},value:{type:Array,default(){return[]}}},data(){return{tags:this.prepareTags(this.value),selected:null,newTag:null,tagOptions:this.options.map(t=>(this.icon&&this.icon.length>0&&(t.icon=this.icon),t),this)}},computed:{direction(){return Nr(this)},dragOptions(){return{delay:1,disabled:!this.draggable,draggable:".k-tag"}},draggable(){return this.tags.length>1},skip(){return this.tags.map(t=>t.value)}},watch:{value(t){this.tags=this.prepareTags(t),this.onInvalid()}},mounted(){this.onInvalid(),this.$props.autofocus&&this.focus()},methods:{addString(t){if(t)if(t=t.trim(),t.includes(this.separator))t.split(this.separator).forEach(t=>{this.addString(t)});else if(0!==t.length)if("options"===this.accept){const e=this.options.filter(e=>e.text===t)[0];if(!e)return;this.addTag(e)}else this.addTag({text:t,value:t})},addTag(t){this.addTagToIndex(t),this.$refs.autocomplete.close(),this.$refs.input.focus()},addTagToIndex(t){if("options"===this.accept){const e=this.options.filter(e=>e.value===t.value)[0];if(!e)return}-1===this.index(t)&&(!this.max||this.tags.length=this.tags.length)return;break;case"first":e=0;break;case"last":e=this.tags.length-1;break;default:e=t;break}let i=this.tags[e];if(i){let t=this.$refs[i.value];if(t&&t[0])return{ref:t[0],tag:i,index:e}}return!1},index(t){return this.tags.findIndex(e=>e.value===t.value)},onInput(){this.$emit("input",this.tags)},onInvalid(){this.$emit("invalid",this.$v.$invalid,this.$v)},leaveInput(t){0===t.target.selectionStart&&t.target.selectionStart===t.target.selectionEnd&&0!==this.tags.length&&(this.$refs.autocomplete.close(),this.navigate("last"),t.preventDefault())},navigate(t){var e=this.get(t);e?(e.ref.focus(),this.selectTag(e.tag)):"next"===t&&(this.$refs.input.focus(),this.selectTag(null))},prepareTags(t){return!1===Array.isArray(t)?[]:t.map(t=>"string"===typeof t?{text:t,value:t}:t)},remove(t){const e=this.get("prev"),s=this.get("next");this.tags.splice(this.index(t),1),this.onInput(),e?(this.selectTag(e.tag),e.ref.focus()):s?this.selectTag(s.tag):(this.selectTag(null),this.$refs.input.focus())},select(){this.focus()},selectTag(t){this.selected=t},tab(t){this.newTag&&this.newTag.length>0&&(t.preventDefault(),this.addString(this.newTag))},type(t){this.newTag=t,this.$refs.autocomplete.search(t)}},validations(){return{tags:{required:!this.required||pr["required"],minLength:!this.min||Object(pr["minLength"])(this.min),maxLength:!this.max||Object(pr["maxLength"])(this.max)}}}},Ko=zo,Go=(s("27c1"),Object(c["a"])(Ko,Uo,Ho,!1,null,null,null)),Wo=Go.exports,Yo={extends:Fr,props:{...Fr.props,autocomplete:{type:String,default:"tel"},type:{type:String,default:"tel"}}},Vo=Yo,Jo=Object(c["a"])(Vo,Zr,Xr,!1,null,null,null),Zo=Jo.exports,Xo=function(){var t=this,e=t.$createElement,s=t._self._c||e;return s("div",{staticClass:"k-textarea-input",attrs:{"data-theme":t.theme,"data-over":t.over}},[s("div",{staticClass:"k-textarea-input-wrapper"},[t.buttons&&!t.disabled?s("k-toolbar",{ref:"toolbar",attrs:{buttons:t.buttons,disabled:t.disabled,uploads:t.uploads},on:{command:t.onCommand},nativeOn:{mousedown:function(t){t.preventDefault()}}}):t._e(),s("textarea",t._b({ref:"input",staticClass:"k-textarea-input-native",attrs:{"data-font":t.font,"data-size":t.size,dir:t.direction},on:{click:t.onClick,focus:t.onFocus,input:t.onInput,keydown:[function(e){return!e.type.indexOf("key")&&t._k(e.keyCode,"enter",13,e.key,"Enter")?null:e.metaKey?t.onSubmit(e):null},function(e){return!e.type.indexOf("key")&&t._k(e.keyCode,"enter",13,e.key,"Enter")?null:e.ctrlKey?t.onSubmit(e):null},function(e){return e.metaKey?t.onShortcut(e):null},function(e){return e.ctrlKey?t.onShortcut(e):null}],dragover:t.onOver,dragleave:t.onOut,drop:t.onDrop}},"textarea",{autofocus:t.autofocus,disabled:t.disabled,id:t.id,minlength:t.minlength,name:t.name,placeholder:t.placeholder,required:t.required,spellcheck:t.spellcheck,value:t.value},!1))],1),s("k-toolbar-email-dialog",{ref:"emailDialog",on:{cancel:t.cancel,submit:function(e){return t.insert(e)}}}),s("k-toolbar-link-dialog",{ref:"linkDialog",on:{cancel:t.cancel,submit:function(e){return t.insert(e)}}}),s("k-files-dialog",{ref:"fileDialog",on:{cancel:t.cancel,submit:function(e){return t.insertFile(e)}}}),t.uploads?s("k-upload",{ref:"fileUpload",on:{success:t.insertUpload}}):t._e()],1)},Qo=[],tl={inheritAttrs:!1,props:{autofocus:Boolean,buttons:{type:[Boolean,Array],default:!0},disabled:Boolean,endpoints:Object,font:String,id:[Number,String],name:[Number,String],maxlength:Number,minlength:Number,placeholder:String,preselect:Boolean,required:Boolean,size:String,spellcheck:{type:[Boolean,String],default:"off"},theme:String,uploads:[Boolean,Object,Array],value:String},data(){return{over:!1}},computed:{direction(){return Nr(this)}},watch:{value(){this.onInvalid(),this.$nextTick(()=>{this.resize()})}},mounted(){this.$nextTick(()=>{this.$library.autosize(this.$refs.input)}),this.onInvalid(),this.$props.autofocus&&this.focus(),this.$props.preselect&&this.select()},methods:{cancel(){this.$refs.input.focus()},dialog(t){if(!this.$refs[t+"Dialog"])throw"Invalid toolbar dialog";this.$refs[t+"Dialog"].open(this.$refs.input,this.selection())},focus(){this.$refs.input.focus()},insert(t){const e=this.$refs.input,s=e.value;setTimeout(()=>{if(e.focus(),document.execCommand("insertText",!1,t),e.value===s){const s=e.value.slice(0,e.selectionStart)+t+e.value.slice(e.selectionEnd);e.value=s,this.$emit("input",s)}}),this.resize()},insertFile(t){t&&t.length>0&&this.insert(t.map(t=>t.dragText).join("\n\n"))},insertUpload(t,e){this.insert(e.map(t=>t.dragText).join("\n\n")),this.$events.$emit("model.update")},onClick(){this.$refs.toolbar&&this.$refs.toolbar.close()},onCommand(t,e){"function"===typeof this[t]?"function"===typeof e?this[t](e(this.$refs.input,this.selection())):this[t](e):window.console.warn(t+" is not a valid command")},onDrop(t){if(this.uploads&&this.$helper.isUploadEvent(t))return this.$refs.fileUpload.drop(t.dataTransfer.files,{url:O.api+"/"+this.endpoints.field+"/upload",multiple:!1});const e=this.$store.state.drag;e&&"text"===e.type&&(this.focus(),this.insert(e.data))},onFocus(t){this.$emit("focus",t)},onInput(t){this.$emit("input",t.target.value)},onInvalid(){this.$emit("invalid",this.$v.$invalid,this.$v)},onOut(){this.$refs.input.blur(),this.over=!1},onOver(t){if(this.uploads&&this.$helper.isUploadEvent(t))return t.dataTransfer.dropEffect="copy",this.focus(),void(this.over=!0);const e=this.$store.state.drag;e&&"text"===e.type&&(t.dataTransfer.dropEffect="copy",this.focus(),this.over=!0)},onShortcut(t){!1!==this.buttons&&"Meta"!==t.key&&"Control"!==t.key&&this.$refs.toolbar&&this.$refs.toolbar.shortcut(t.key,t)},onSubmit(t){return this.$emit("submit",t)},prepend(t){this.insert(t+" "+this.selection())},resize(){this.$library.autosize.update(this.$refs.input)},select(){this.$refs.select()},selectFile(){this.$refs.fileDialog.open({endpoint:this.endpoints.field+"/files",multiple:!1})},selection(){const t=this.$refs.input,e=t.selectionStart,s=t.selectionEnd;return t.value.substring(e,s)},uploadFile(){this.$refs.fileUpload.open({url:O.api+"/"+this.endpoints.field+"/upload",multiple:!1})},wrap(t){this.insert(t+this.selection()+t)}},validations(){return{value:{required:!this.required||pr["required"],minLength:!this.minlength||Object(pr["minLength"])(this.minlength),maxLength:!this.maxlength||Object(pr["maxLength"])(this.maxlength)}}}},el=tl,sl=(s("cca8"),Object(c["a"])(el,Xo,Qo,!1,null,null,null)),il=sl.exports,nl={extends:Er,props:{display:{type:String,default:"HH:mm"},max:String,min:String,step:{type:Object,default(){return{size:5,unit:"minute"}}},type:{type:String,default:"time"}},computed:{is12HourFormat(){return this.display.toLowerCase().includes("a")},map(){return{second:["s","ss"],minute:["m","mm"],hour:this.is12HourFormat?["h","hh"]:["H","HH"]}},patterns(){let t=Er.computed.patterns.apply(this);return this.is12HourFormat&&(t=t.map(t=>t+"a").concat(t)),t}},methods:{emit(t){const e=this.toFormat(this.parsed,"HH:mm:ss")||"";this.$emit(t,e)},toDatetime(t){return this.$library.dayjs.utc(t,"HH:mm:ss")}}},al=nl,rl=Object(c["a"])(al,Qr,to,!1,null,null,null),ol=rl.exports,ll=function(){var t=this,e=t.$createElement,s=t._self._c||e;return s("label",{staticClass:"k-toggle-input",attrs:{"data-disabled":t.disabled}},[s("input",{ref:"input",staticClass:"k-toggle-input-native",attrs:{id:t.id,disabled:t.disabled,type:"checkbox"},domProps:{checked:t.value},on:{change:function(e){return t.onInput(e.target.checked)}}}),s("span",{staticClass:"k-toggle-input-label",domProps:{innerHTML:t._s(t.label)}})])},cl=[],ul={inheritAttrs:!1,props:{autofocus:Boolean,disabled:Boolean,id:[Number,String],text:{type:[Array,String],default(){return[this.$t("off"),this.$t("on")]}},required:Boolean,value:Boolean},computed:{label(){return Array.isArray(this.text)?this.value?this.text[1]:this.text[0]:this.text}},watch:{value(){this.onInvalid()}},mounted(){this.onInvalid(),this.$props.autofocus&&this.focus()},methods:{focus(){this.$refs.input.focus()},onEnter(t){"Enter"===t.key&&this.$refs.input.click()},onInput(t){this.$emit("input",t)},onInvalid(){this.$emit("invalid",this.$v.$invalid,this.$v)},select(){this.$refs.input.focus()}},validations(){return{value:{required:!this.required||pr["required"]}}}},dl=ul,pl=(s("bb41"),Object(c["a"])(dl,ll,cl,!1,null,null,null)),hl=pl.exports,ml={extends:Fr,props:{...Fr.props,autocomplete:{type:String,default:"url"},type:{type:String,default:"url"}}},fl=ml,gl=Object(c["a"])(fl,eo,so,!1,null,null,null),bl=gl.exports,kl=function(){var t=this,e=t.$createElement,s=t._self._c||e;return s("k-field",t._b({staticClass:"k-blocks-field"},"k-field",t.$props,!1),[t.hasFieldsets?s("k-dropdown",{attrs:{slot:"options"},slot:"options"},[s("k-button",{attrs:{icon:"dots"},on:{click:function(e){return t.$refs.options.toggle()}}}),s("k-dropdown-content",{ref:"options",attrs:{align:"right"}},[s("k-dropdown-item",{attrs:{disabled:t.isFull,icon:"add"},on:{click:function(e){return t.$refs.blocks.choose(t.value.length)}}},[t._v(" "+t._s(t.$t("add"))+" ")]),s("k-dropdown-item",{attrs:{disabled:t.isEmpty,icon:"trash"},on:{click:function(e){return t.$refs.blocks.confirmToRemoveAll()}}},[t._v(" "+t._s(t.$t("delete.all"))+" ")])],1)],1):t._e(),s("k-blocks",t._g({ref:"blocks",attrs:{compact:!1,empty:t.empty,endpoints:t.endpoints,fieldsets:t.fieldsets,"fieldset-groups":t.fieldsetGroups,group:t.group,max:t.max,value:t.value},on:{close:function(e){t.opened=e},open:function(e){t.opened=e}}},t.$listeners))],1)},vl=[],$l={inheritAttrs:!1,props:{...fn["a"].props,empty:String,fieldsets:Object,fieldsetGroups:Object,group:String,max:{type:Number,default:null},value:{type:Array,default(){return[]}}},data(){return{opened:[]}},computed:{hasFieldsets(){return Object.keys(this.fieldsets).length},isEmpty(){return 0===this.value.length},isFull(){return null!==this.max&&this.value.length>=this.max}}},yl=$l,_l=(s("ae14"),Object(c["a"])(yl,kl,vl,!1,null,null,null)),wl=_l.exports,xl=function(){var t=this,e=t.$createElement,s=t._self._c||e;return s("k-field",t._b({staticClass:"k-checkboxes-field",attrs:{counter:t.counterOptions}},"k-field",t.$props,!1),[s("k-input",t._g(t._b({ref:"input",attrs:{id:t._uid,theme:"field",type:"checkboxes"}},"k-input",t.$props,!1),t.$listeners))],1)},Ol=[],Cl={inheritAttrs:!1,props:{...fn["a"].props,...Sn.props,..._r.props,counter:{type:Boolean,default:!0}},computed:{counterOptions(){return null!==this.value&&!this.disabled&&!1!==this.counter&&{count:this.value&&Array.isArray(this.value)?this.value.length:0,min:this.min,max:this.max}}},methods:{focus(){this.$refs.input.focus()}}},Sl=Cl,El=Object(c["a"])(Sl,xl,Ol,!1,null,null,null),jl=El.exports,Tl=function(){var t=this,e=t.$createElement,s=t._self._c||e;return s("k-field",t._b({staticClass:"k-date-field",attrs:{input:t._uid}},"k-field",t.$props,!1),[s("k-input",t._g(t._b({ref:"input",attrs:{id:t._uid,type:t.inputType,value:t.value,theme:"field"},scopedSlots:t._u([t.calendar?{key:"icon",fn:function(){return[s("k-dropdown",[s("k-button",{staticClass:"k-input-icon-button",attrs:{icon:t.icon,tooltip:t.$t("date.select"),tabindex:"-1"},on:{click:t.onFocus}}),s("k-dropdown-content",{ref:"calendar",attrs:{align:"right"}},[s("k-calendar",{attrs:{value:t.datetime,min:t.min,max:t.max},on:{input:t.onSelect}})],1)],1)]},proxy:!0}:null],null,!0)},"k-input",t.$props,!1),t.listeners))],1)},Ll=[],Il={inheritAttrs:!1,props:{...fn["a"].props,...Sn.props,...Br.props,calendar:{type:Boolean,default:!0},icon:{type:String,default:"calendar"}},data(){return{datetime:this.value}},computed:{inputType(){return!1===this.time?"date":"datetime"},listeners(){return{...this.$listeners,enter:this.onSelect,focus:this.onFocus,input:this.onInput,update:this.onUpdate}}},watch:{value(t){this.datetime=t}},methods:{focus(){this.$refs.input.focus()},onUpdate(t){this.$emit("input",t)},onFocus(){this.$refs.calendar&&this.$refs.calendar.open()},onInput(t){this.datetime=t},onSelect(t){this.onUpdate(t),this.$refs.calendar&&this.$refs.calendar.close()}}},Al=Il,Bl=Object(c["a"])(Al,Tl,Ll,!1,null,null,null),Ml=Bl.exports,Dl=function(){var t=this,e=t.$createElement,s=t._self._c||e;return s("k-field",t._b({staticClass:"k-email-field",attrs:{input:t._uid}},"k-field",t.$props,!1),[s("k-input",t._g(t._b({ref:"input",attrs:{id:t._uid,theme:"field",type:"email"}},"k-input",t.$props,!1),t.$listeners),[t.link?s("k-button",{staticClass:"k-input-icon-button",attrs:{slot:"icon",icon:t.icon,link:t.mailto,tooltip:t.$t("open"),tabindex:"-1",target:"_blank"},slot:"icon"}):t._e()],1)],1)},Nl=[],Pl={inheritAttrs:!1,props:{...fn["a"].props,...Sn.props,...Kr.props,link:{type:Boolean,default:!0},icon:{type:String,default:"email"}},computed:{mailto(){return this.value&&this.value.length>0?"mailto:"+this.value:null}},methods:{focus(){this.$refs.input.focus()}}},Rl=Pl,ql=Object(c["a"])(Rl,Dl,Nl,!1,null,null,null),Fl=ql.exports,Ul=function(){var t=this,e=t.$createElement,s=t._self._c||e;return s("k-field",t._b({staticClass:"k-files-field"},"k-field",t.$props,!1),[t.more&&!t.disabled?s("template",{slot:"options"},[s("k-button-group",{staticClass:"k-field-options"},[t.uploads?[s("k-dropdown",[s("k-button",{ref:"pickerToggle",staticClass:"k-field-options-button",attrs:{icon:t.btnIcon},on:{click:t.prompt}},[t._v(" "+t._s(t.btnLabel)+" ")]),s("k-dropdown-content",{ref:"picker",attrs:{align:"right"}},[s("k-dropdown-item",{attrs:{icon:"check"},on:{click:t.open}},[t._v(" "+t._s(t.$t("select"))+" ")]),s("k-dropdown-item",{attrs:{icon:"upload"},on:{click:t.upload}},[t._v(" "+t._s(t.$t("upload"))+" ")])],1)],1)]:[s("k-button",{staticClass:"k-field-options-button",attrs:{icon:"check"},on:{click:t.open}},[t._v(" "+t._s(t.$t("select"))+" ")])]],2)],1):t._e(),t.selected.length?[s("k-draggable",{attrs:{element:t.elements.list,list:t.selected,"data-size":t.size,handle:!0,"data-invalid":t.isInvalid},on:{end:t.onInput}},t._l(t.selected,(function(e,i){return s(t.elements.item,{key:e.id,tag:"component",attrs:{sortable:!t.disabled&&t.selected.length>1,text:e.text,link:t.link?e.link:null,info:e.info,image:e.image,icon:e.icon}},[t.disabled?t._e():s("k-button",{attrs:{slot:"options",tooltip:t.$t("remove"),icon:"remove"},on:{click:function(e){return t.remove(i)}},slot:"options"})],1)})),1)]:s("k-empty",{attrs:{layout:t.layout,"data-invalid":t.isInvalid,icon:"image"},on:{click:t.prompt}},[t._v(" "+t._s(t.empty||t.$t("field.files.empty"))+" ")]),s("k-files-dialog",{ref:"selector",on:{submit:t.select}}),s("k-upload",{ref:"fileUpload",on:{success:t.selectUpload}})],2)},Hl=[],zl={inheritAttrs:!1,props:{...fn["a"].props,empty:String,info:String,link:Boolean,layout:{type:String,default:"list"},max:Number,multiple:Boolean,parent:String,search:Boolean,size:String,text:String,value:{type:Array,default(){return[]}}},data(){return{selected:this.value}},computed:{btnIcon(){return!this.multiple&&this.selected.length>0?"refresh":"add"},btnLabel(){return!this.multiple&&this.selected.length>0?this.$t("change"):this.$t("add")},elements(){const t={cards:{list:"k-cards",item:"k-card"},list:{list:"k-list",item:"k-list-item"}};return t[this.layout]?t[this.layout]:t["list"]},isInvalid(){return!(!this.required||0!==this.selected.length)||(!!(this.min&&this.selected.lengththis.max))},more(){return!this.max||this.max>this.selected.length}},watch:{value(t){this.selected=t}},methods:{focus(){},onInput(){this.$emit("input",this.selected)},remove(t){this.selected.splice(t,1),this.onInput()},removeById(t){this.selected=this.selected.filter(e=>e.id!==t),this.onInput()},select(t){0!==t.length?(this.selected=this.selected.filter(e=>t.filter(t=>t.id===e.id).length>0),t.forEach(t=>{0===this.selected.filter(e=>t.id===e.id).length&&this.selected.push(t)}),this.onInput()):this.selected=[]}}},Kl={mixins:[zl],props:{uploads:[Boolean,Object,Array]},created(){this.$events.$on("file.delete",this.removeById)},destroyed(){this.$events.$off("file.delete",this.removeById)},methods:{prompt(t){if(t.stopPropagation(),this.disabled)return!1;this.more&&this.uploads?this.$refs.picker.toggle():this.open()},open(){if(this.disabled)return!1;this.$refs.selector.open({endpoint:this.endpoints.field,max:this.max,multiple:this.multiple,search:this.search,selected:this.selected.map(t=>t.id)})},selectUpload(t,e){!1===this.multiple&&(this.selected=[]),e.forEach(t=>{this.selected.push(t)}),this.onInput(),this.$events.$emit("model.update")},upload(){this.$refs.fileUpload.open({url:O.api+"/"+this.endpoints.field+"/upload",multiple:this.multiple,accept:this.uploads.accept})}}},Gl=Kl,Wl=(s("4a4b"),Object(c["a"])(Gl,Ul,Hl,!1,null,null,null)),Yl=Wl.exports,Vl=function(){var t=this,e=t.$createElement,s=t._self._c||e;return s("div",{staticClass:"k-field k-gap-field"})},Jl=[],Zl={},Xl=Object(c["a"])(Zl,Vl,Jl,!1,null,null,null),Ql=Xl.exports,tc=function(){var t=this,e=t.$createElement,s=t._self._c||e;return s("div",{staticClass:"k-headline-field"},[s("k-headline",{attrs:{"data-numbered":t.numbered,size:"large"}},[t._v(" "+t._s(t.label)+" ")]),t.help?s("footer",{staticClass:"k-field-footer"},[t.help?s("k-text",{staticClass:"k-field-help",attrs:{theme:"help"},domProps:{innerHTML:t._s(t.help)}}):t._e()],1):t._e()],1)},ec=[],sc={props:{help:String,label:String,numbered:Boolean}},ic=sc,nc=(s("19d7"),Object(c["a"])(ic,tc,ec,!1,null,null,null)),ac=nc.exports,rc=function(){var t=this,e=t.$createElement,s=t._self._c||e;return s("div",{staticClass:"k-field k-info-field"},[s("k-headline",[t._v(t._s(t.label))]),s("k-box",{attrs:{theme:t.theme}},[s("k-text",{domProps:{innerHTML:t._s(t.text)}})],1),t.help?s("footer",{staticClass:"k-field-footer"},[t.help?s("k-text",{staticClass:"k-field-help",attrs:{theme:"help"},domProps:{innerHTML:t._s(t.help)}}):t._e()],1):t._e()],1)},oc=[],lc={props:{help:String,label:String,text:String,theme:{type:String,default:"info"}}},cc=lc,uc=(s("ddfd"),Object(c["a"])(cc,rc,oc,!1,null,null,null)),dc=uc.exports,pc=function(){var t=this,e=t.$createElement,s=t._self._c||e;return s("k-field",t._b({staticClass:"k-layout-field"},"k-field",t.$props,!1),[s("k-block-layouts",t._b({on:{input:function(e){return t.$emit("input",e)}}},"k-block-layouts",t.$props,!1))],1)},hc=[],mc=function(){var t=this,e=t.$createElement,s=t._self._c||e;return s("div",[t.rows.length?[s("k-draggable",t._b({staticClass:"k-layouts",on:{sort:t.save}},"k-draggable",t.draggableOptions,!1),t._l(t.rows,(function(e,i){return s("k-layout",t._b({key:e.id,attrs:{disabled:t.disabled,endpoints:t.endpoints,"fieldset-groups":t.fieldsetGroups,fieldsets:t.fieldsets,"is-selected":t.selected===e.id,settings:t.settings},on:{append:function(e){return t.selectLayout(i+1)},duplicate:function(s){return t.duplicateLayout(i,e)},prepend:function(e){return t.selectLayout(i)},remove:function(s){return t.removeLayout(e)},select:function(s){t.selected=e.id},updateAttrs:function(e){return t.updateAttrs(i,e)},updateColumn:function(s){return t.updateColumn(Object.assign({},{layout:e,layoutIndex:i},s))}}},"k-layout",e,!1))})),1),t.disabled?t._e():s("k-button",{staticClass:"k-layout-add-button",attrs:{icon:"add"},on:{click:function(e){return t.selectLayout(t.rows.length)}}})]:[s("k-empty",{staticClass:"k-layout-empty",attrs:{icon:"dashboard"},on:{click:function(e){return t.selectLayout(0)}}},[t._v(" "+t._s(t.empty||t.$t("field.layout.empty"))+" ")])],s("k-dialog",{ref:"selector",staticClass:"k-layout-selector",attrs:{"cancel-button":!1,"submit-button":!1,size:"medium"}},[s("k-headline",[t._v(t._s(t.$t("field.layout.select")))]),s("ul",t._l(t.layouts,(function(e,i){return s("li",{key:i,staticClass:"k-layout-selector-option"},[s("k-grid",{nativeOn:{click:function(s){return t.addLayout(e)}}},t._l(e,(function(t,e){return s("k-column",{key:e,attrs:{width:t}})})),1)],1)})),0)],1)],2)},fc=[],gc=function(){var t=this,e=t.$createElement,s=t._self._c||e;return s("section",{staticClass:"k-layout",attrs:{"data-selected":t.isSelected,tabindex:"0"},on:{click:function(e){return t.$emit("select")}}},[s("k-grid",{staticClass:"k-layout-columns"},t._l(t.columns,(function(e,i){return s("k-layout-column",t._b({key:e.id,attrs:{endpoints:t.endpoints,"fieldset-groups":t.fieldsetGroups,fieldsets:t.fieldsets},on:{input:function(s){return t.$emit("updateColumn",{column:e,columnIndex:i,blocks:s})}}},"k-layout-column",e,!1))})),1),t.disabled?t._e():s("nav",{staticClass:"k-layout-toolbar"},[t.settings?s("k-button",{staticClass:"k-layout-toolbar-button",attrs:{tooltip:t.$t("settings"),icon:"settings"},on:{click:function(e){return t.$refs.drawer.open()}}}):t._e(),s("k-dropdown",[s("k-button",{staticClass:"k-layout-toolbar-button",attrs:{icon:"angle-down"},on:{click:function(e){return t.$refs.options.toggle()}}}),s("k-dropdown-content",{ref:"options",attrs:{align:"right"}},[s("k-dropdown-item",{attrs:{icon:"angle-up"},on:{click:function(e){return t.$emit("prepend")}}},[t._v(" "+t._s(t.$t("insert.before"))+" ")]),s("k-dropdown-item",{attrs:{icon:"angle-down"},on:{click:function(e){return t.$emit("append")}}},[t._v(" "+t._s(t.$t("insert.after"))+" ")]),s("hr"),t.settings?s("k-dropdown-item",{attrs:{icon:"settings"},on:{click:function(e){return t.$refs.drawer.open()}}},[t._v(" "+t._s(t.$t("settings"))+" ")]):t._e(),s("k-dropdown-item",{attrs:{icon:"copy"},on:{click:function(e){return t.$emit("duplicate")}}},[t._v(" "+t._s(t.$t("duplicate"))+" ")]),s("hr"),s("k-dropdown-item",{attrs:{icon:"trash"},on:{click:function(e){return t.$refs.confirmRemoveDialog.open()}}},[t._v(" "+t._s(t.$t("field.layout.delete"))+" ")])],1)],1),s("k-sort-handle")],1),t.settings?s("k-form-drawer",{ref:"drawer",staticClass:"k-layout-drawer",attrs:{tabs:t.tabs,title:t.$t("settings"),value:t.attrs,icon:"settings"},on:{input:function(e){return t.$emit("updateAttrs",e)}}}):t._e(),s("k-remove-dialog",{ref:"confirmRemoveDialog",attrs:{text:t.$t("field.layout.delete.confirm")},on:{submit:function(e){return t.$emit("remove")}}})],1)},bc=[],kc=function(){var t=this,e=t.$createElement,s=t._self._c||e;return s("div",{staticClass:"k-column k-layout-column",attrs:{id:t.id,"data-width":t.width,tabindex:"0"},on:{dblclick:function(e){return t.$refs.blocks.choose(t.blocks.length)}}},[s("k-blocks",{ref:"blocks",attrs:{endpoints:t.endpoints,"fieldset-groups":t.fieldsetGroups,fieldsets:t.fieldsets,value:t.blocks,group:"layout"},on:{input:function(e){return t.$emit("input",e)}},nativeOn:{dblclick:function(t){t.stopPropagation()}}})],1)},vc=[],$c={props:{blocks:Array,endpoints:Object,fieldsetGroups:Object,fieldsets:Object,id:String,isSelected:Boolean,width:String}},yc=$c,_c=(s("e791"),Object(c["a"])(yc,kc,vc,!1,null,null,null)),wc=_c.exports,xc={components:{"k-layout-column":wc},props:{attrs:[Array,Object],columns:Array,disabled:Boolean,endpoints:Object,fieldsetGroups:Object,fieldsets:Object,id:String,isSelected:Boolean,settings:Object},computed:{tabs(){let t=this.settings.tabs;return Object.entries(t).forEach(([e,s])=>{Object.entries(s.fields).forEach(([s])=>{t[e].fields[s].endpoints={field:this.endpoints.field+"/fields/"+s,section:this.endpoints.section,model:this.endpoints.model}})}),t}}},Oc=xc,Cc=(s("170b"),Object(c["a"])(Oc,gc,bc,!1,null,null,null)),Sc=Cc.exports,Ec={components:{"k-layout":Sc},props:{disabled:Boolean,empty:String,endpoints:Object,fieldsetGroups:Object,fieldsets:Object,layouts:Array,max:Number,settings:Object,value:Array},data(){return{currentLayout:null,nextIndex:null,rows:this.value,selected:null}},computed:{draggableOptions(){return{id:this._uid,handle:!0,list:this.rows}}},watch:{value(){this.rows=this.value}},methods:{async addLayout(t){let e=await this.$api.post(this.endpoints.field+"/layout",{columns:t});this.rows.splice(this.nextIndex,0,e),this.layouts.length>1&&this.$refs.selector.close(),this.save()},duplicateLayout(t,e){let s={...this.$helper.clone(e),id:this.$helper.uuid()};s.columns=s.columns.map(t=>(t.id=this.$helper.uuid(),t.blocks=t.blocks.map(t=>(t.id=this.$helper.uuid(),t)),t)),this.rows.splice(t+1,0,s),this.save()},removeLayout(t){const e=this.rows.findIndex(e=>e.id===t.id);-1!==e&&this.$delete(this.rows,e),this.save()},save(){this.$emit("input",this.rows)},selectLayout(t){this.nextIndex=t,1!==this.layouts.length?this.$refs.selector.open():this.addLayout(this.layouts[0])},updateColumn(t){this.rows[t.layoutIndex].columns[t.columnIndex].blocks=t.blocks,this.save()},updateAttrs(t,e){this.rows[t].attrs=e,this.save()}}},jc=Ec,Tc=(s("a659"),Object(c["a"])(jc,mc,fc,!1,null,null,null)),Lc=Tc.exports,Ic={components:{"k-block-layouts":Lc},inheritAttrs:!1,props:{...fn["a"].props,empty:String,fieldsetGroups:Object,fieldsets:Object,layouts:{type:Array,default(){return[["1/1"]]}},settings:Object,value:{type:Array,default(){return[]}}}},Ac=Ic,Bc=Object(c["a"])(Ac,pc,hc,!1,null,null,null),Mc=Bc.exports,Dc=function(){var t=this,e=t.$createElement,s=t._self._c||e;return s("hr",{staticClass:"k-line-field"})},Nc=[],Pc={},Rc=Pc,qc=(s("718c"),Object(c["a"])(Rc,Dc,Nc,!1,null,null,null)),Fc=qc.exports,Uc=function(){var t=this,e=t.$createElement,s=t._self._c||e;return s("k-field",t._b({staticClass:"k-list-field",attrs:{input:t._uid,counter:!1}},"k-field",t.$props,!1),[s("k-input",t._b({ref:"input",attrs:{id:t._uid,marks:t.marks,value:t.value,type:"list",theme:"field"},on:{input:function(e){return t.$emit("input",e)}}},"k-input",t.$props,!1))],1)},Hc=[],zc={inheritAttrs:!1,props:{...fn["a"].props,...Sn.props,marks:[Array,Boolean],value:String},methods:{focus(){this.$refs.input.focus()}}},Kc=zc,Gc=(s("116a"),Object(c["a"])(Kc,Uc,Hc,!1,null,null,null)),Wc=Gc.exports,Yc=function(){var t=this,e=t.$createElement,s=t._self._c||e;return s("k-field",t._b({staticClass:"k-multiselect-field",attrs:{input:t._uid,counter:t.counterOptions},on:{blur:t.blur},nativeOn:{keydown:function(e){return!e.type.indexOf("key")&&t._k(e.keyCode,"enter",13,e.key,"Enter")?null:(e.preventDefault(),t.focus(e))}}},"k-field",t.$props,!1),[s("k-input",t._g(t._b({ref:"input",attrs:{id:t._uid,theme:"field",type:"multiselect"}},"k-input",t.$props,!1),t.$listeners))],1)},Vc=[],Jc={inheritAttrs:!1,props:{...fn["a"].props,...Sn.props,...ho.props,counter:{type:Boolean,default:!0},icon:{type:String,default:"angle-down"}},computed:{counterOptions(){return null!==this.value&&!this.disabled&&!1!==this.counter&&{count:this.value&&Array.isArray(this.value)?this.value.length:0,min:this.min,max:this.max}}},mounted(){this.$refs.input.$el.setAttribute("tabindex",0)},methods:{blur(t){this.$refs.input.blur(t)},focus(){this.$refs.input.focus()}}},Zc=Jc,Xc=Object(c["a"])(Zc,Yc,Vc,!1,null,null,null),Qc=Xc.exports,tu=function(){var t=this,e=t.$createElement,s=t._self._c||e;return s("k-field",t._b({staticClass:"k-number-field",attrs:{input:t._uid}},"k-field",t.$props,!1),[s("k-input",t._g(t._b({ref:"input",attrs:{id:t._uid,theme:"field",type:"number"}},"k-input",t.$props,!1),t.$listeners))],1)},eu=[],su={inheritAttrs:!1,props:{...fn["a"].props,...Sn.props,...vo.props},methods:{focus(){this.$refs.input.focus()}}},iu=su,nu=Object(c["a"])(iu,tu,eu,!1,null,null,null),au=nu.exports,ru=function(){var t=this,e=t.$createElement,s=t._self._c||e;return s("k-field",t._b({staticClass:"k-pages-field"},"k-field",t.$props,!1),[s("k-button-group",{staticClass:"k-field-options",attrs:{slot:"options"},slot:"options"},[t.more&&!t.disabled?s("k-button",{staticClass:"k-field-options-button",attrs:{icon:t.btnIcon},on:{click:t.open}},[t._v(" "+t._s(t.btnLabel)+" ")]):t._e()],1),t.selected.length?[s("k-draggable",{attrs:{element:t.elements.list,handle:!0,list:t.selected,"data-size":t.size,"data-invalid":t.isInvalid},on:{end:t.onInput}},t._l(t.selected,(function(e,i){return s(t.elements.item,{key:e.id,tag:"component",attrs:{sortable:!t.disabled&&t.selected.length>1,text:e.text,info:e.info,link:t.link?e.link:null,icon:e.icon,image:e.image}},[t.disabled?t._e():s("k-button",{attrs:{slot:"options",icon:"remove"},on:{click:function(e){return t.remove(i)}},slot:"options"})],1)})),1)]:s("k-empty",{attrs:{layout:t.layout,"data-invalid":t.isInvalid,icon:"page"},on:{click:t.open}},[t._v(" "+t._s(t.empty||t.$t("field.pages.empty"))+" ")]),s("k-pages-dialog",{ref:"selector",on:{submit:t.select}})],2)},ou=[],lu={mixins:[zl],methods:{open(){if(this.disabled)return!1;this.$refs.selector.open({endpoint:this.endpoints.field,max:this.max,multiple:this.multiple,search:this.search,selected:this.selected.map(t=>t.id)})}}},cu=lu,uu=(s("7e85"),Object(c["a"])(cu,ru,ou,!1,null,null,null)),du=uu.exports,pu=function(){var t=this,e=t.$createElement,s=t._self._c||e;return s("k-field",t._b({staticClass:"k-password-field",attrs:{input:t._uid,counter:t.counterOptions}},"k-field",t.$props,!1),[t._t("options",null,{slot:"options"}),s("k-input",t._g(t._b({ref:"input",attrs:{id:t._uid,theme:"field",type:"password"}},"k-input",t.$props,!1),t.$listeners))],2)},hu=[],mu={inheritAttrs:!1,props:{...fn["a"].props,...Sn.props,...wo.props,counter:{type:Boolean,default:!0},minlength:{type:Number,default:8},icon:{type:String,default:"key"}},computed:{counterOptions(){return null!==this.value&&!this.disabled&&!1!==this.counter&&{count:this.value?String(this.value).length:0,min:this.minlength,max:this.maxlength}}},methods:{focus(){this.$refs.input.focus()}}},fu=mu,gu=Object(c["a"])(fu,pu,hu,!1,null,null,null),bu=gu.exports,ku=function(){var t=this,e=t.$createElement,s=t._self._c||e;return s("k-field",t._b({staticClass:"k-radio-field"},"k-field",t.$props,!1),[s("k-input",t._g(t._b({ref:"input",attrs:{id:t._uid,theme:"field",type:"radio"}},"k-input",t.$props,!1),t.$listeners))],1)},vu=[],$u={inheritAttrs:!1,props:{...fn["a"].props,...Sn.props,...jo.props},methods:{focus(){this.$refs.input.focus()}}},yu=$u,_u=Object(c["a"])(yu,ku,vu,!1,null,null,null),wu=_u.exports,xu=function(){var t=this,e=t.$createElement,s=t._self._c||e;return s("k-field",t._b({staticClass:"k-range-field",attrs:{input:t._uid}},"k-field",t.$props,!1),[s("k-input",t._g(t._b({ref:"input",attrs:{id:t._uid,theme:"field",type:"range"}},"k-input",t.$props,!1),t.$listeners))],1)},Ou=[],Cu={inheritAttrs:!1,props:{...fn["a"].props,...Sn.props,...Mo.props},methods:{focus(){this.$refs.input.focus()}}},Su=Cu,Eu=Object(c["a"])(Su,xu,Ou,!1,null,null,null),ju=Eu.exports,Tu=function(){var t=this,e=t.$createElement,s=t._self._c||e;return s("k-field",t._b({staticClass:"k-select-field",attrs:{input:t._uid}},"k-field",t.$props,!1),[s("k-input",t._g(t._b({ref:"input",attrs:{id:t._uid,theme:"field",type:"select"}},"k-input",t.$props,!1),t.$listeners))],1)},Lu=[],Iu={inheritAttrs:!1,props:{...fn["a"].props,...Sn.props,...Fo.props,icon:{type:String,default:"angle-down"}},methods:{focus(){this.$refs.input.focus()}}},Au=Iu,Bu=Object(c["a"])(Au,Tu,Lu,!1,null,null,null),Mu=Bu.exports,Du=function(){var t=this,e=t.$createElement,s=t._self._c||e;return s("k-field",t._b({staticClass:"k-structure-field",nativeOn:{click:function(t){t.stopPropagation()}}},"k-field",t.$props,!1),[s("template",{slot:"options"},[t.more&&null===t.currentIndex?s("k-button",{ref:"add",attrs:{id:t._uid,icon:"add"},on:{click:t.add}},[t._v(" "+t._s(t.$t("add"))+" ")]):t._e()],1),null!==t.currentIndex?[s("div",{staticClass:"k-structure-backdrop",on:{click:t.escape}}),s("section",{staticClass:"k-structure-form"},[s("k-form",{ref:"form",staticClass:"k-structure-form-fields",attrs:{fields:t.formFields},on:{input:t.onInput,submit:t.submit},model:{value:t.currentModel,callback:function(e){t.currentModel=e},expression:"currentModel"}}),s("footer",{staticClass:"k-structure-form-buttons"},[s("k-button",{staticClass:"k-structure-form-cancel-button",attrs:{icon:"cancel"},on:{click:t.close}},[t._v(" "+t._s(t.$t("cancel"))+" ")]),"new"!==t.currentIndex?s("k-pagination",{attrs:{dropdown:!1,total:t.items.length,limit:1,page:t.currentIndex+1,details:!0,validate:t.beforePaginate},on:{paginate:t.paginate}}):t._e(),s("k-button",{staticClass:"k-structure-form-submit-button",attrs:{icon:"check"},on:{click:t.submit}},[t._v(" "+t._s(t.$t("new"!==t.currentIndex?"confirm":"add"))+" ")])],1)],1)]:0===t.items.length?s("k-empty",{attrs:{"data-invalid":t.isInvalid,icon:"list-bullet"},on:{click:t.add}},[t._v(" "+t._s(t.empty||t.$t("field.structure.empty"))+" ")]):[s("table",{staticClass:"k-structure-table",attrs:{"data-invalid":t.isInvalid,"data-sortable":t.isSortable}},[s("thead",[s("tr",[s("th",{staticClass:"k-structure-table-index"},[t._v(" # ")]),t._l(t.columns,(function(e,i){return s("th",{key:i+"-header",staticClass:"k-structure-table-column",style:"width:"+t.width(e.width),attrs:{"data-align":e.align}},[t._v(" "+t._s(e.label)+" ")])})),t.disabled?t._e():s("th")],2)]),s("k-draggable",{attrs:{list:t.items,"data-disabled":t.disabled,options:t.dragOptions,handle:!0,dir:t.direction,element:"tbody"},on:{end:t.onInput}},t._l(t.paginatedItems,(function(e,i){return s("tr",{key:i,on:{click:function(t){t.stopPropagation()}}},[s("td",{staticClass:"k-structure-table-index"},[t.isSortable?s("k-sort-handle"):t._e(),s("span",{staticClass:"k-structure-table-index-number"},[t._v(t._s(t.indexOf(i)))])],1),t._l(t.columns,(function(n,a){return s("td",{key:a,staticClass:"k-structure-table-column",style:"width:"+t.width(n.width),attrs:{title:n.label,"data-align":n.align},on:{click:function(e){return t.jump(i,a)}}},[!1===t.columnIsEmpty(e[a])?[t.previewExists(n.type)?s("k-"+n.type+"-field-preview",{tag:"component",attrs:{value:e[a],column:n,field:t.fields[a]},on:{input:function(e){return t.update(i,a,e)}}}):[s("p",{staticClass:"k-structure-table-text"},[t._v(" "+t._s(n.before)+" "+t._s(t.displayText(t.fields[a],e[a])||"–")+" "+t._s(n.after)+" ")])]]:t._e()],2)})),t.disabled?t._e():s("td",{staticClass:"k-structure-table-options"},[t.duplicate&&t.more&&null===t.currentIndex?[s("k-button",{key:i,ref:"actionsToggle",refInFor:!0,staticClass:"k-structure-table-options-button",attrs:{icon:"dots"},on:{click:function(e){t.$refs[i+"-actions"][0].toggle()}}}),s("k-dropdown-content",{ref:i+"-actions",refInFor:!0,attrs:{align:"right"}},[s("k-dropdown-item",{attrs:{icon:"copy"},on:{click:function(e){return t.duplicateItem(i)}}},[t._v(" "+t._s(t.$t("duplicate"))+" ")]),s("k-dropdown-item",{attrs:{icon:"remove"},on:{click:function(e){return t.confirmRemove(i)}}},[t._v(" "+t._s(t.$t("remove"))+" ")])],1)]:[s("k-button",{staticClass:"k-structure-table-options-button",attrs:{tooltip:t.$t("remove"),icon:"remove"},on:{click:function(e){return t.confirmRemove(i)}}})]],2)],2)})),0)],1),t.limit?s("k-pagination",t._b({on:{paginate:t.paginateItems}},"k-pagination",t.pagination,!1)):t._e(),t.disabled?t._e():s("k-dialog",{ref:"remove",attrs:{"submit-button":t.$t("delete"),theme:"negative"},on:{submit:t.remove}},[s("k-text",[t._v(t._s(t.$t("field.structure.delete.confirm")))])],1)]],2)},Nu=[],Pu=s("9fc0"),Ru={mixins:[Pu["a"]],inheritAttrs:!1,props:{columns:Object,duplicate:{type:Boolean,default:!0},empty:String,fields:Object,limit:Number,max:Number,min:Number,prepend:{type:Boolean,default:!1},sortable:{type:Boolean,default:!0},sortBy:String,value:{type:Array,default(){return[]}}},data(){return{autofocus:null,items:this.makeItems(this.value),currentIndex:null,currentModel:null,trash:null,page:1}},computed:{direction(){return Nr(this)},dragOptions(){return{disabled:!this.isSortable,fallbackClass:"k-sortable-row-fallback"}},formFields(){let t={};return Object.keys(this.fields).forEach(e=>{let s=this.fields[e];s.section=this.name,s.endpoints={field:this.endpoints.field+"+"+e,section:this.endpoints.section,model:this.endpoints.model},null===this.autofocus&&!0===s.autofocus&&(this.autofocus=e),t[e]=s}),t},more(){return!0!==this.disabled&&!(this.max&&this.items.length>=this.max)},isInvalid(){return!0!==this.disabled&&(!!(this.min&&this.items.lengththis.max))},isSortable(){return!this.sortBy&&(!this.limit&&(!0!==this.disabled&&(!(this.items.length<=1)&&!1!==this.sortable)))},pagination(){let t=0;return this.limit&&(t=(this.page-1)*this.limit),{page:this.page,offset:t,limit:this.limit,total:this.items.length,align:"center",details:!0}},paginatedItems(){return this.limit?this.items.slice(this.pagination.offset,this.pagination.offset+this.limit):this.items}},watch:{value(t){t!=this.items&&(this.items=this.makeItems(t))}},methods:{add(){if(!0===this.disabled)return!1;if(null!==this.currentIndex)return this.escape(),!1;let t={};Object.keys(this.fields).forEach(e=>{const s=this.fields[e];null!==s.default?t[e]=this.$helper.clone(s.default):t[e]=null}),this.currentIndex="new",this.currentModel=t,this.createForm()},addItem(t){!0===this.prepend?this.items.unshift(t):this.items.push(t)},beforePaginate(){return this.save(this.currentModel)},close(){this.currentIndex=null,this.currentModel=null,this.$events.$off("keydown.esc",this.escape),this.$events.$off("keydown.cmd.s",this.submit),this.$store.dispatch("content/enable")},columnIsEmpty(t){return void 0===t||null===t||""===t||("object"===typeof t&&0===Object.keys(t).length&&t.constructor===Object||void 0!==t.length&&0===t.length)},confirmRemove(t){this.close(),this.trash=t+this.pagination.offset,this.$refs.remove.open()},createForm(t){this.$events.$on("keydown.esc",this.escape),this.$events.$on("keydown.cmd.s",this.submit),this.$store.dispatch("content/disable"),this.$nextTick(()=>{this.$refs.form&&this.$refs.form.focus(t||this.autofocus)})},duplicateItem(t){this.addItem(this.items[t+this.pagination.offset]),this.onInput()},escape(){if("new"===this.currentIndex){let t=Object.values(this.currentModel),e=!0;if(t.forEach(t=>{!1===this.columnIsEmpty(t)&&(e=!1)}),!0===e)return void this.close()}this.submit()},focus(){this.$refs.add&&this.$refs.add.focus&&this.$refs.add.focus()},indexOf(t){return this.limit?(this.page-1)*this.limit+t+1:t+1},isActive(t){return this.currentIndex===t},jump(t,e){this.open(t+this.pagination.offset,e)},makeItems(t){return!1===Array.isArray(t)?[]:this.sort(t)},onInput(){this.$emit("input",this.items)},open(t,e){this.currentIndex=t,this.currentModel=this.$helper.clone(this.items[t]),this.createForm(e)},paginate(t){this.open(t.offset)},paginateItems(t){this.page=t.page},remove(){if(null===this.trash)return!1;this.items.splice(this.trash,1),this.trash=null,this.$refs.remove.close(),this.onInput(),0===this.paginatedItems.length&&this.page>1&&this.page--,this.items=this.sort(this.items)},sort(t){return this.sortBy?t.sortBy(this.sortBy):t},save(){return null!==this.currentIndex&&void 0!==this.currentIndex?this.validate(this.currentModel).then(()=>("new"===this.currentIndex?this.addItem(this.currentModel):this.items[this.currentIndex]=this.currentModel,this.items=this.sort(this.items),this.onInput(),!0)).catch(t=>{throw this.$store.dispatch("notification/error",{message:this.$t("error.form.incomplete"),details:t}),t}):Promise.resolve()},submit(){this.save().then(this.close).catch(()=>{})},validate(t){return this.$api.post(this.endpoints.field+"/validate",t).then(t=>{if(t.length>0)throw t;return!0})},update(t,e,s){this.items[t][e]=s,this.onInput()}}},qu=Ru,Fu=(s("088c"),Object(c["a"])(qu,Du,Nu,!1,null,null,null)),Uu=Fu.exports,Hu=function(){var t=this,e=t.$createElement,s=t._self._c||e;return s("k-field",t._b({staticClass:"k-tags-field",attrs:{input:t._uid,counter:t.counterOptions}},"k-field",t.$props,!1),[s("k-input",t._g(t._b({ref:"input",attrs:{id:t._uid,theme:"field",type:"tags"}},"k-input",t.$props,!1),t.$listeners))],1)},zu=[],Ku={inheritAttrs:!1,props:{...fn["a"].props,...Sn.props,...Wo.props,counter:{type:Boolean,default:!0}},computed:{counterOptions(){return null!==this.value&&!this.disabled&&!1!==this.counter&&{count:this.value&&Array.isArray(this.value)?this.value.length:0,min:this.min,max:this.max}}},methods:{focus(){this.$refs.input.focus()}}},Gu=Ku,Wu=Object(c["a"])(Gu,Hu,zu,!1,null,null,null),Yu=Wu.exports,Vu=function(){var t=this,e=t.$createElement,s=t._self._c||e;return s("k-field",t._b({staticClass:"k-tel-field",attrs:{input:t._uid}},"k-field",t.$props,!1),[s("k-input",t._g(t._b({ref:"input",attrs:{id:t._uid,theme:"field",type:"tel"}},"k-input",t.$props,!1),t.$listeners))],1)},Ju=[],Zu={inheritAttrs:!1,props:{...fn["a"].props,...Sn.props,...Zo.props,icon:{type:String,default:"phone"}},methods:{focus(){this.$refs.input.focus()}}},Xu=Zu,Qu=Object(c["a"])(Xu,Vu,Ju,!1,null,null,null),td=Qu.exports,ed=function(){var t=this,e=t.$createElement,s=t._self._c||e;return s("k-field",t._b({staticClass:"k-text-field",attrs:{input:t._uid,counter:t.counterOptions}},"k-field",t.$props,!1),[t._t("options",null,{slot:"options"}),s("k-input",t._g(t._b({ref:"input",attrs:{id:t._uid,theme:"field"}},"k-input",t.$props,!1),t.$listeners))],2)},sd=[],id={inheritAttrs:!1,props:{...fn["a"].props,...Sn.props,...Fr.props,counter:{type:Boolean,default:!0}},computed:{counterOptions(){return null!==this.value&&!this.disabled&&!1!==this.counter&&{count:this.value?String(this.value).length:0,min:this.minlength,max:this.maxlength}}},methods:{focus(){this.$refs.input.focus()},select(){this.$refs.input.select()}}},nd=id,ad=(s("b746"),Object(c["a"])(nd,ed,sd,!1,null,null,null)),rd=ad.exports,od=function(){var t=this,e=t.$createElement,s=t._self._c||e;return s("k-field",t._b({staticClass:"k-textarea-field",attrs:{input:t._uid,counter:t.counterOptions}},"k-field",t.$props,!1),[s("k-input",t._g(t._b({ref:"input",attrs:{id:t._uid,type:"textarea",theme:"field"}},"k-input",t.$props,!1),t.$listeners))],1)},ld=[],cd={inheritAttrs:!1,props:{...fn["a"].props,...Sn.props,...il.props,counter:{type:Boolean,default:!0}},computed:{counterOptions(){return null!==this.value&&!this.disabled&&!1!==this.counter&&{count:this.value?this.value.length:0,min:this.minlength,max:this.maxlength}}},methods:{focus(){this.$refs.input.focus()}}},ud=cd,dd=Object(c["a"])(ud,od,ld,!1,null,null,null),pd=dd.exports,hd=function(){var t=this,e=t.$createElement,s=t._self._c||e;return s("k-field",t._b({staticClass:"k-time-field",attrs:{input:t._uid}},"k-field",t.$props,!1),[s("k-input",t._g(t._b({ref:"input",attrs:{id:t._uid,theme:"field",type:"time"}},"k-input",t.$props,!1),t.listeners))],1)},md=[],fd={inheritAttrs:!1,props:{...fn["a"].props,...Sn.props,...ol.props,icon:{type:String,default:"clock"}},computed:{listeners(){return{...this.$listeners,update:t=>this.$emit("input",t),input:()=>{}}}},methods:{focus(){this.$refs.input.focus()}}},gd=fd,bd=Object(c["a"])(gd,hd,md,!1,null,null,null),kd=bd.exports,vd=function(){var t=this,e=t.$createElement,s=t._self._c||e;return s("k-field",t._b({staticClass:"k-toggle-field",attrs:{input:t._uid}},"k-field",t.$props,!1),[s("k-input",t._g(t._b({ref:"input",attrs:{id:t._uid,theme:"field",type:"toggle"}},"k-input",t.$props,!1),t.$listeners))],1)},$d=[],yd={inheritAttrs:!1,props:{...fn["a"].props,...Sn.props,...hl.props},methods:{focus(){this.$refs.input.focus()}}},_d=yd,wd=Object(c["a"])(_d,vd,$d,!1,null,null,null),xd=wd.exports,Od=function(){var t=this,e=t.$createElement,s=t._self._c||e;return s("k-field",t._b({staticClass:"k-url-field",attrs:{input:t._uid}},"k-field",t.$props,!1),[s("k-input",t._g(t._b({ref:"input",attrs:{id:t._uid,theme:"field",type:"url"}},"k-input",t.$props,!1),t.$listeners),[t.link?s("k-button",{staticClass:"k-input-icon-button",attrs:{slot:"icon",icon:t.icon,link:t.value,tooltip:t.$t("open"),tabindex:"-1",target:"_blank"},slot:"icon"}):t._e()],1)],1)},Cd=[],Sd={inheritAttrs:!1,props:{...fn["a"].props,...Sn.props,...bl.props,link:{type:Boolean,default:!0},icon:{type:String,default:"url"}},methods:{focus(){this.$refs.input.focus()}}},Ed=Sd,jd=Object(c["a"])(Ed,Od,Cd,!1,null,null,null),Td=jd.exports,Ld=function(){var t=this,e=t.$createElement,s=t._self._c||e;return s("k-field",t._b({staticClass:"k-users-field"},"k-field",t.$props,!1),[s("k-button-group",{staticClass:"k-field-options",attrs:{slot:"options"},slot:"options"},[t.more&&!t.disabled?s("k-button",{staticClass:"k-field-options-button",attrs:{icon:t.btnIcon},on:{click:t.open}},[t._v(" "+t._s(t.btnLabel)+" ")]):t._e()],1),t.selected.length?[s("k-draggable",{attrs:{element:t.elements.list,list:t.selected,handle:!0,"data-invalid":t.isInvalid},on:{end:t.onInput}},t._l(t.selected,(function(e,i){return s(t.elements.item,{key:e.email,tag:"component",attrs:{sortable:!t.disabled&&t.selected.length>1,text:e.text,info:e.info,link:t.link?t.$api.users.link(e.id):null,image:e.image,icon:e.icon}},[t.disabled?t._e():s("k-button",{attrs:{slot:"options",icon:"remove"},on:{click:function(e){return t.remove(i)}},slot:"options"})],1)})),1)]:s("k-empty",{attrs:{"data-invalid":t.isInvalid,icon:"users"},on:{click:t.open}},[t._v(" "+t._s(t.empty||t.$t("field.users.empty"))+" ")]),s("k-users-dialog",{ref:"selector",on:{submit:t.select}})],2)},Id=[],Ad={mixins:[zl],methods:{open(){if(this.disabled)return!1;this.$refs.selector.open({endpoint:this.endpoints.field,max:this.max,multiple:this.multiple,search:this.search,selected:this.selected.map(t=>t.id)})}}},Bd=Ad,Md=(s("7f6e"),Object(c["a"])(Bd,Ld,Id,!1,null,null,null)),Dd=Md.exports,Nd=function(){var t=this,e=t.$createElement,s=t._self._c||e;return s("k-field",t._b({staticClass:"k-writer-field",attrs:{input:t._uid,counter:!1}},"k-field",t.$props,!1),[s("k-input",t._b({attrs:{after:t.after,before:t.before,icon:t.icon,theme:"field"}},"k-input",t.$props,!1),[s("k-writer",t._b({ref:"input",staticClass:"k-writer-field-input",attrs:{value:t.value},on:{input:function(e){return t.$emit("input",e)}}},"k-writer",t.$props,!1))],1)],1)},Pd=[],Rd={inheritAttrs:!1,props:{...fn["a"].props,...Sn.props,...cr.props},methods:{focus(){this.$refs.input.focus()}}},qd=Rd,Fd=(s("1267"),Object(c["a"])(qd,Nd,Pd,!1,null,null,null)),Ud=Fd.exports,Hd=function(){var t=this,e=t.$createElement,s=t._self._c||e;return s("nav",{staticClass:"k-toolbar"},[s("div",{staticClass:"k-toolbar-wrapper"},[s("div",{staticClass:"k-toolbar-buttons"},[t._l(t.layout,(function(e,i){return[e.divider?[s("span",{key:i,staticClass:"k-toolbar-divider"})]:e.dropdown?[s("k-dropdown",{key:i},[s("k-button",{key:i,staticClass:"k-toolbar-button",attrs:{icon:e.icon,tooltip:e.label,tabindex:"-1"},on:{click:function(e){t.$refs[i+"-dropdown"][0].toggle()}}}),s("k-dropdown-content",{ref:i+"-dropdown",refInFor:!0},t._l(e.dropdown,(function(e,i){return s("k-dropdown-item",{key:i,attrs:{icon:e.icon},on:{click:function(s){return t.command(e.command,e.args)}}},[t._v(" "+t._s(e.label)+" ")])})),1)],1)]:[s("k-button",{key:i,staticClass:"k-toolbar-button",attrs:{icon:e.icon,tooltip:e.label,tabindex:"-1"},on:{click:function(s){return t.command(e.command,e.args)}}})]]}))],2)])])},zd=[];const Kd=function(t){this.command("insert",(e,s)=>{let i=[];return s.split("\n").forEach((e,s)=>{let n="ol"===t?s+1+".":"-";i.push(n+" "+e)}),i.join("\n")})};var Gd,Wd,Yd={layout:["headlines","bold","italic","|","link","email","file","|","code","ul","ol"],props:{buttons:{type:[Boolean,Array],default:!0},uploads:[Boolean,Object,Array]},data(){let t={},e={},s=[],i=this.commands();return!1===this.buttons?t:(Array.isArray(this.buttons)&&(s=this.buttons),!0!==Array.isArray(this.buttons)&&(s=this.$options.layout),s.forEach((s,n)=>{if("|"===s)t["divider-"+n]={divider:!0};else if(i[s]){let n=i[s];t[s]=n,n.shortcut&&(e[n.shortcut]=s)}}),{layout:t,shortcuts:e})},methods:{command(t,e){"function"===typeof t?t.apply(this):this.$emit("command",t,e)},close(){Object.keys(this.$refs).forEach(t=>{const e=this.$refs[t][0];e.close&&"function"===typeof e.close&&e.close()})},fileCommandSetup(){let t={label:this.$t("toolbar.button.file"),icon:"attachment"};return!1===this.uploads?t.command="selectFile":t.dropdown={select:{label:this.$t("toolbar.button.file.select"),icon:"check",command:"selectFile"},upload:{label:this.$t("toolbar.button.file.upload"),icon:"upload",command:"uploadFile"}},t},commands(){return{headlines:{label:this.$t("toolbar.button.headings"),icon:"title",dropdown:{h1:{label:this.$t("toolbar.button.heading.1"),icon:"title",command:"prepend",args:"#"},h2:{label:this.$t("toolbar.button.heading.2"),icon:"title",command:"prepend",args:"##"},h3:{label:this.$t("toolbar.button.heading.3"),icon:"title",command:"prepend",args:"###"}}},bold:{label:this.$t("toolbar.button.bold"),icon:"bold",command:"wrap",args:"**",shortcut:"b"},italic:{label:this.$t("toolbar.button.italic"),icon:"italic",command:"wrap",args:"*",shortcut:"i"},link:{label:this.$t("toolbar.button.link"),icon:"url",shortcut:"k",command:"dialog",args:"link"},email:{label:this.$t("toolbar.button.email"),icon:"email",shortcut:"e",command:"dialog",args:"email"},file:this.fileCommandSetup(),code:{label:this.$t("toolbar.button.code"),icon:"code",command:"wrap",args:"`"},ul:{label:this.$t("toolbar.button.ul"),icon:"list-bullet",command(){return Kd.apply(this,["ul"])}},ol:{label:this.$t("toolbar.button.ol"),icon:"list-numbers",command(){return Kd.apply(this,["ol"])}}}},shortcut(t,e){if(this.shortcuts[t]){const s=this.layout[this.shortcuts[t]];if(!s)return!1;e.preventDefault(),this.command(s.command,s.args)}}}},Vd=Yd,Jd=(s("df0d"),Object(c["a"])(Vd,Hd,zd,!1,null,null,null)),Zd=Jd.exports,Xd=function(){var t=this,e=t.$createElement,s=t._self._c||e;return s("k-dialog",{ref:"dialog",attrs:{"submit-button":t.$t("insert")},on:{close:t.cancel,submit:function(e){return t.$refs.form.submit()}}},[s("k-form",{ref:"form",attrs:{fields:t.fields},on:{submit:t.submit},model:{value:t.value,callback:function(e){t.value=e},expression:"value"}})],1)},Qd=[],tp={data(){return{value:{email:null,text:null},fields:{email:{label:this.$t("email"),type:"email"},text:{label:this.$t("link.text"),type:"text"}}}},computed:{kirbytext(){return this.$store.state.system.info.kirbytext}},methods:{open(t,e){this.value.text=e,this.$refs.dialog.open()},cancel(){this.$emit("cancel")},createKirbytext(){const t=this.value.email||"";return this.value.text&&this.value.text.length>0?`(email: ${t} text: ${this.value.text})`:`(email: ${t})`},createMarkdown(){const t=this.value.email||"";return this.value.text&&this.value.text.length>0?`[${this.value.text}](mailto:${t})`:`<${t}>`},submit(){this.$emit("submit",this.kirbytext?this.createKirbytext():this.createMarkdown()),this.value={email:null,text:null},this.$refs.dialog.close()}}},ep=tp,sp=Object(c["a"])(ep,Xd,Qd,!1,null,null,null),ip=sp.exports,np=function(){var t=this,e=t.$createElement,s=t._self._c||e;return s("k-dialog",{ref:"dialog",attrs:{"submit-button":t.$t("insert")},on:{close:t.cancel,submit:function(e){return t.$refs.form.submit()}}},[s("k-form",{ref:"form",attrs:{fields:t.fields},on:{submit:t.submit},model:{value:t.value,callback:function(e){t.value=e},expression:"value"}})],1)},ap=[],rp={data(){return{value:{url:null,text:null},fields:{url:{label:this.$t("link"),type:"text",placeholder:this.$t("url.placeholder"),icon:"url"},text:{label:this.$t("link.text"),type:"text"}}}},computed:{kirbytext(){return this.$store.state.system.info.kirbytext}},methods:{open(t,e){this.value.text=e,this.$refs.dialog.open()},cancel(){this.$emit("cancel")},createKirbytext(){return this.value.text.length>0?`(link: ${this.value.url} text: ${this.value.text})`:`(link: ${this.value.url})`},createMarkdown(){return this.value.text.length>0?`[${this.value.text}](${this.value.url})`:`<${this.value.url}>`},submit(){this.$emit("submit",this.kirbytext?this.createKirbytext():this.createMarkdown()),this.value={url:null,text:null},this.$refs.dialog.close()}}},op=rp,lp=Object(c["a"])(op,np,ap,!1,null,null,null),cp=lp.exports,up=function(){var t=this,e=t.$createElement,s=t._self._c||e;return s("div",[s("p",{staticClass:"k-date-field-preview"},[t._v(" "+t._s(t.text)+" ")])])},dp=[],pp={props:{field:Object,value:String},computed:{text(){const t=Er.methods.toDatetime.call(this,this.value);return t.format(this.field.display)}}},hp=pp,mp=(s("96a5"),Object(c["a"])(hp,up,dp,!1,null,null,null)),fp=mp.exports,gp=function(){var t=this,e=t.$createElement,s=t._self._c||e;return s("p",{staticClass:"k-url-field-preview"},[t._v(" "+t._s(t.column.before)+" "),s("k-link",{attrs:{to:t.link,target:"_blank"},nativeOn:{click:function(t){t.stopPropagation()}}},[t._v(" "+t._s(t.value)+" ")]),t._v(" "+t._s(t.column.after)+" ")],1)},bp=[],kp={props:{column:{type:Object,default(){return{}}},value:String},computed:{link(){return this.value}}},vp=kp,$p=(s("977f"),Object(c["a"])(vp,gp,bp,!1,null,null,null)),yp=$p.exports,_p={extends:yp,computed:{link(){return this.value&&this.value.length>0?"mailto:"+this.value:null}}},wp=_p,xp=Object(c["a"])(wp,Gd,Wd,!1,null,null,null),Op=xp.exports,Cp=function(){var t=this,e=t.$createElement,s=t._self._c||e;return t.value?s("ul",{staticClass:"k-files-field-preview"},t._l(t.value,(function(e){return s("li",{key:e.url},[s("k-link",{attrs:{title:e.filename,to:e.link},nativeOn:{click:function(t){t.stopPropagation()}}},["image"===e.type?s("k-image",t._b({},"k-image",t.imageOptions(e),!1)):s("k-icon",t._b({},"k-icon",e.icon,!1))],1)],1)})),0):t._e()},Sp=[],Ep=function(t,e="list",s="1/1"){if(!t||0===t.length)return!1;let i=null,n=null;if(t.list?(i=t[e].url,n=t[e].srcset):(i=t.url,n=t.srcset),!i)return!1;let a={src:i,srcset:n,back:t.back||"black",cover:t.cover};return"cards"===e&&(a.ratio=t.ratio||"3/2",a.sizes=jp(s)),a};function jp(t){switch(t){case"1/2":case"2/4":return"(min-width: 30em) and (max-width: 65em) 59em, (min-width: 65em) 44em, 27em";case"1/3":return"(min-width: 30em) and (max-width: 65em) 59em, (min-width: 65em) 29.333em, 27em";case"1/4":return"(min-width: 30em) and (max-width: 65em) 59em, (min-width: 65em) 22em, 27em";case"2/3":return"(min-width: 30em) and (max-width: 65em) 59em, (min-width: 65em) 27em, 27em";case"3/4":return"(min-width: 30em) and (max-width: 65em) 59em, (min-width: 65em) 66em, 27em";default:return"(min-width: 30em) and (max-width: 65em) 59em, (min-width: 65em) 88em, 27em"}}var Tp,Lp,Ip={props:{value:Array,field:Object},methods:{imageOptions(t){const e=Ep(t.image);return e.src?{...e,back:"pattern",cover:!1,...this.field.image||{}}:{src:t.url}}}},Ap=Ip,Bp=(s("21dc"),Object(c["a"])(Ap,Cp,Sp,!1,null,null,null)),Mp=Bp.exports,Dp=function(){var t=this,e=t.$createElement,s=t._self._c||e;return s("div",{staticClass:"k-list-field-preview",domProps:{innerHTML:t._s(t.value)}})},Np=[],Pp={inheritAttrs:!1,props:{value:String}},Rp=Pp,qp=(s("200d"),Object(c["a"])(Rp,Dp,Np,!1,null,null,null)),Fp=qp.exports,Up=function(){var t=this,e=t.$createElement,s=t._self._c||e;return t.value?s("ul",{staticClass:"k-pages-field-preview"},t._l(t.value,(function(e){return s("li",{key:e.id},[s("figure",[s("k-link",{attrs:{title:e.id,to:t.$api.pages.link(e.id)},nativeOn:{click:function(t){t.stopPropagation()}}},[s("k-icon",{staticClass:"k-pages-field-preview-image",attrs:{type:"page",back:"pattern"}}),s("figcaption",[t._v(" "+t._s(e.text)+" ")])],1)],1)])})),0):t._e()},Hp=[],zp={props:{value:Array}},Kp=zp,Gp=(s("d0c1"),Object(c["a"])(Kp,Up,Hp,!1,null,null,null)),Wp=Gp.exports,Yp=function(){var t=this,e=t.$createElement,s=t._self._c||e;return s("div",[s("p",{staticClass:"k-time-field-preview"},[t._v(" "+t._s(t.text)+" ")])])},Vp=[],Jp={props:{field:Object,value:String},computed:{text(){const t=ol.methods.toDatetime.call(this,this.value);return t.format(this.field.display)}}},Zp=Jp,Xp=(s("f9aa"),Object(c["a"])(Zp,Yp,Vp,!1,null,null,null)),Qp=Xp.exports,th=function(){var t=this,e=t.$createElement,s=t._self._c||e;return s("k-input",{staticClass:"k-toggle-field-preview",attrs:{text:t.text,type:"toggle"},on:{input:function(e){return t.$emit("input",e)}},model:{value:t.value,callback:function(e){t.value=e},expression:"value"}})},eh=[],sh={props:{field:Object,value:Boolean,column:Object},computed:{text(){return!1!==this.column.text?this.field.text:null}}},ih=sh,nh=(s("1c4e"),Object(c["a"])(ih,th,eh,!1,null,null,null)),ah=nh.exports,rh=function(){var t=this,e=t.$createElement,s=t._self._c||e;return t.value?s("ul",{staticClass:"k-users-field-preview"},t._l(t.value,(function(e){return s("li",{key:e.email},[s("figure",[s("k-link",{attrs:{title:e.email,to:t.$api.users.link(e.id)},nativeOn:{click:function(t){t.stopPropagation()}}},[e.avatar?s("k-image",{staticClass:"k-users-field-preview-avatar",attrs:{src:e.avatar.url,back:"pattern"}}):s("k-icon",{staticClass:"k-users-field-preview-avatar",attrs:{type:"user",back:"pattern"}}),s("figcaption",[t._v(" "+t._s(e.username)+" ")])],1)],1)])})),0):t._e()},oh=[],lh={props:{value:Array}},ch=lh,uh=(s("3a85"),Object(c["a"])(ch,rh,oh,!1,null,null,null)),dh=uh.exports,ph=function(){var t=this,e=t.$createElement,s=t._self._c||e;return s("div",{staticClass:"k-writer-field-preview",domProps:{innerHTML:t._s(t.value)}})},hh=[],mh={inheritAttrs:!1,props:{value:String}},fh=mh,gh=(s("5e9a"),Object(c["a"])(fh,ph,hh,!1,null,null,null)),bh=gh.exports,kh=function(){var t=this,e=t.$createElement,s=t._self._c||e;return s("span",{staticClass:"k-aspect-ratio",style:{paddingBottom:t.ratioPadding},attrs:{"data-cover":t.cover}},[t._t("default")],2)},vh=[],$h={props:{cover:Boolean,ratio:String},computed:{ratioPadding(){return this.$helper.ratio(this.ratio)}}},yh=$h,_h=(s("8d8b"),Object(c["a"])(yh,kh,vh,!1,null,null,null)),wh=_h.exports,xh=function(){var t=this,e=t.$createElement,s=t._self._c||e;return s("div",{staticClass:"k-bar"},[t.$slots.left?s("div",{staticClass:"k-bar-slot",attrs:{"data-position":"left"}},[t._t("left")],2):t._e(),t.$slots.center?s("div",{staticClass:"k-bar-slot",attrs:{"data-position":"center"}},[t._t("center")],2):t._e(),t.$slots.right?s("div",{staticClass:"k-bar-slot",attrs:{"data-position":"right"}},[t._t("right")],2):t._e()])},Oh=[],Ch={},Sh=Ch,Eh=(s("6f7b"),Object(c["a"])(Sh,xh,Oh,!1,null,null,null)),jh=Eh.exports,Th=function(){var t=this,e=t.$createElement,s=t._self._c||e;return s("div",t._g({staticClass:"k-box",attrs:{"data-theme":t.theme}},t.$listeners),[t._t("default",[t.html?s("k-text",{domProps:{innerHTML:t._s(t.text)}}):s("k-text",[t._v(" "+t._s(t.text)+" ")])])],2)},Lh=[],Ih={props:{theme:{type:String,default:"none"},text:String,html:{type:Boolean,default:!0}}},Ah=Ih,Bh=(s("7dc7"),Object(c["a"])(Ah,Th,Lh,!1,null,null,null)),Mh=Bh.exports,Dh=function(){var t=this,e=t.$createElement,s=t._self._c||e;return s("figure",t._g({staticClass:"k-card"},t.$listeners),[t.sortable?s("k-sort-handle"):t._e(),s(t.wrapper,{tag:"component",attrs:{to:t.link,target:t.target}},[t.imageOptions?s("k-image",t._b({staticClass:"k-card-image"},"k-image",t.imageOptions,!1)):s("span",{staticClass:"k-card-icon",style:"padding-bottom:"+t.ratioPadding},[s("k-icon",t._b({},"k-icon",t.icon,!1))],1),s("figcaption",{staticClass:"k-card-content"},[s("span",{staticClass:"k-card-text",attrs:{"data-noinfo":!t.info},domProps:{innerHTML:t._s(t.text)}}),t.info?s("span",{staticClass:"k-card-info",domProps:{innerHTML:t._s(t.info)}}):t._e()])],1),s("nav",{staticClass:"k-card-options"},[t._t("options",[t.flag?s("k-button",t._b({staticClass:"k-card-options-button",on:{click:t.flag.click}},"k-button",t.flag,!1)):t.statusIcon?s("k-status-icon",t._b({staticClass:"k-card-options-button"},"k-status-icon",t.statusIcon,!1)):t._e(),t.options?s("k-button",{staticClass:"k-card-options-button",attrs:{tooltip:t.$t("options"),icon:"dots"},on:{click:function(e){return e.stopPropagation(),t.$refs.dropdown.toggle()}}}):t._e(),s("k-dropdown-content",{ref:"dropdown",staticClass:"k-card-options-dropdown",attrs:{options:t.options,align:"right"},on:{action:function(e){return t.$emit("action",e)}}})])],2)],1)},Nh=[],Ph={inheritAttrs:!1,props:{column:String,flag:Object,icon:{type:Object,default(){return{type:"file",back:"black"}}},image:[Object,Boolean],info:String,link:[String,Function],options:[Array,Function],sortable:Boolean,statusIcon:Object,target:String,text:String},computed:{wrapper(){return this.link?"k-link":"div"},ratioPadding(){return this.icon&&this.icon.ratio?this.$helper.ratio(this.icon.ratio):this.$helper.ratio("3/2")},imageOptions(){return Ep(this.image,"cards",this.column)}}},Rh=Ph,qh=(s("c119"),Object(c["a"])(Rh,Dh,Nh,!1,null,null,null)),Fh=qh.exports,Uh=function(){var t=this,e=t.$createElement,s=t._self._c||e;return s("div",{staticClass:"k-cards"},[t._t("default",t._l(t.cards,(function(e,i){return s("k-card",t._g(t._b({key:i},"k-card",e,!1),t.$listeners))})))],2)},Hh=[],zh={props:{cards:Array}},Kh=zh,Gh=(s("f56d"),Object(c["a"])(Kh,Uh,Hh,!1,null,null,null)),Wh=Gh.exports,Yh=function(){var t=this,e=t.$createElement,s=t._self._c||e;return s("div",{staticClass:"k-collection",attrs:{"data-layout":t.layout}},[s("k-draggable",{attrs:{list:t.items,options:t.dragOptions,element:t.elements.list,"data-size":t.size,handle:!0},on:{change:function(e){return t.$emit("change",e)},end:t.onEnd}},t._l(t.items,(function(e,i){return s(t.elements.item,t._b({key:i,tag:"component",class:{"k-draggable-item":e.sortable},on:{action:function(s){return t.$emit("action",e,s)},dragstart:function(s){return t.onDragStart(s,e.dragText)}}},"component",e,!1))})),1),t.hasFooter?s("footer",{staticClass:"k-collection-footer"},[t.help?s("k-text",{staticClass:"k-collection-help",attrs:{theme:"help"},domProps:{innerHTML:t._s(t.help)}}):t._e(),s("div",{staticClass:"k-collection-pagination"},[t.hasPagination?s("k-pagination",t._b({on:{paginate:function(e){return t.$emit("paginate",e)}}},"k-pagination",t.paginationOptions,!1)):t._e()],1)],1):t._e()],1)},Vh=[],Jh={props:{help:String,items:{type:[Array,Object],default(){return[]}},layout:{type:String,default:"list"},size:String,sortable:Boolean,pagination:{type:[Boolean,Object],default(){return!1}}},computed:{hasPagination(){return!1!==this.pagination&&(!0!==this.paginationOptions.hide&&!(this.pagination.total<=this.pagination.limit))},hasFooter(){return!(!this.hasPagination&&!this.help)},dragOptions(){return{sort:this.sortable,disabled:!1===this.sortable,draggable:".k-draggable-item"}},elements(){const t={cards:{list:"k-cards",item:"k-card"},list:{list:"k-list",item:"k-list-item"}};return t[this.layout]?t[this.layout]:t["list"]},paginationOptions(){const t="object"!==typeof this.pagination?{}:this.pagination;return{limit:10,details:!0,keys:!1,total:0,hide:!1,...t}}},watch:{$props(){this.$forceUpdate()}},over:null,methods:{onEnd(){this.over&&this.over.removeAttribute("data-over"),this.$emit("sort",this.items)},onDragStart(t,e){this.$store.dispatch("drag",{type:"text",data:e})}}},Zh=Jh,Xh=(s("8c28"),Object(c["a"])(Zh,Yh,Vh,!1,null,null,null)),Qh=Xh.exports,tm=function(){var t=this,e=t.$createElement,s=t._self._c||e;return s("div",{staticClass:"k-column",attrs:{"data-width":t.width,"data-sticky":t.sticky}},[s("div",[t._t("default")],2)])},em=[],sm={props:{width:String,sticky:Boolean}},im=sm,nm=(s("c9cb"),Object(c["a"])(im,tm,em,!1,null,null,null)),am=nm.exports,rm=function(){var t=this,e=t.$createElement,s=t._self._c||e;return s("div",{staticClass:"k-dropzone",attrs:{"data-dragging":t.dragging,"data-over":t.over},on:{dragenter:t.onEnter,dragleave:t.onLeave,dragover:t.onOver,drop:t.onDrop}},[t._t("default")],2)},om=[],lm={props:{disabled:{type:Boolean,default:!1}},data(){return{files:[],dragging:!1,over:!1}},methods:{cancel(){this.reset()},reset(){this.dragging=!1,this.over=!1},onDrop(t){return!0===this.disabled||!1===this.$helper.isUploadEvent(t)?this.reset():(this.$events.$emit("dropzone.drop"),this.files=t.dataTransfer.files,this.$emit("drop",this.files),void this.reset())},onEnter(t){!1===this.disabled&&this.$helper.isUploadEvent(t)&&(this.dragging=!0)},onLeave(){this.reset()},onOver(t){!1===this.disabled&&this.$helper.isUploadEvent(t)&&(t.dataTransfer.dropEffect="copy",this.over=!0)}}},cm=lm,um=(s("414d"),Object(c["a"])(cm,rm,om,!1,null,null,null)),dm=um.exports,pm=function(){var t=this,e=t.$createElement,s=t._self._c||e;return s(t.element,t._g({tag:"component",staticClass:"k-empty",attrs:{"data-layout":t.layout,type:"button"===t.element&&"button"}},t.$listeners),[t.icon?s("k-icon",{attrs:{type:t.icon}}):t._e(),s("p",[t._t("default")],2)],1)},hm=[],mm={props:{text:String,icon:String,layout:{type:String,default:"list"}},computed:{element(){return void 0!==this.$listeners["click"]?"button":"div"}}},fm=mm,gm=(s("ba8f"),Object(c["a"])(fm,pm,hm,!1,null,null,null)),bm=gm.exports,km=function(){var t=this,e=t.$createElement,s=t._self._c||e;return s("div",{staticClass:"k-file-preview"},[s("k-view",{staticClass:"k-file-preview-layout"},[s("div",{staticClass:"k-file-preview-image"},[s("k-link",{staticClass:"k-file-preview-image-link",attrs:{to:t.file.url,title:t.$t("open"),target:"_blank"}},[t.file.panelImage&&t.file.panelImage.cards&&t.file.panelImage.cards.url?s("k-image",{attrs:{src:t.file.panelImage.cards.url,srcset:t.file.panelImage.cards.srcset,back:"none"}}):t.file.panelIcon?s("k-icon",{staticClass:"k-file-preview-icon",style:{color:t.file.panelIcon.color},attrs:{type:t.file.panelIcon.type}}):s("span",{staticClass:"k-file-preview-placeholder"})],1)],1),s("div",{staticClass:"k-file-preview-details"},[s("ul",[s("li",[s("h3",[t._v(t._s(t.$t("template")))]),s("p",[t._v(t._s(t.file.template||"—"))])]),s("li",[s("h3",[t._v(t._s(t.$t("mime")))]),s("p",[t._v(t._s(t.file.mime))])]),s("li",[s("h3",[t._v(t._s(t.$t("url")))]),s("p",[s("k-link",{attrs:{to:t.file.url,tabindex:"-1",target:"_blank"}},[t._v(" /"+t._s(t.file.id)+" ")])],1)]),s("li",[s("h3",[t._v(t._s(t.$t("size")))]),s("p",[t._v(t._s(t.file.niceSize))])]),s("li",[s("h3",[t._v(t._s(t.$t("dimensions")))]),t.file.dimensions&&(t.file.dimensions.width||t.file.dimensions.height)?s("p",[t._v(" "+t._s(t.file.dimensions.width)+"×"+t._s(t.file.dimensions.height)+" "+t._s(t.$t("pixel"))+" ")]):s("p",[t._v(" — ")])]),s("li",[s("h3",[t._v(t._s(t.$t("orientation")))]),t.file.dimensions&&t.file.dimensions.orientation?s("p",[t._v(" "+t._s(t.$t("orientation."+t.file.dimensions.orientation))+" ")]):s("p",[t._v(" — ")])])])])])],1)},vm=[],$m={props:{file:Object}},ym=$m,_m=(s("696b"),Object(c["a"])(ym,km,vm,!1,null,null,null)),wm=_m.exports,xm=function(){var t=this,e=t.$createElement,s=t._self._c||e;return s("div",{staticClass:"k-grid",attrs:{"data-gutter":t.gutter}},[t._t("default")],2)},Om=[],Cm={props:{gutter:String}},Sm=Cm,Em=(s("5b23"),Object(c["a"])(Sm,xm,Om,!1,null,null,null)),jm=Em.exports,Tm=function(){var t=this,e=t.$createElement,s=t._self._c||e;return s("header",{staticClass:"k-header",attrs:{"data-editable":t.editable}},[s("k-headline",{attrs:{tag:"h1",size:"huge"}},[t.editable&&t.$listeners.edit?s("span",{staticClass:"k-headline-editable",on:{click:function(e){return t.$emit("edit")}}},[t._t("default"),s("k-icon",{attrs:{type:"edit"}})],2):t._t("default")],2),t.$slots.left||t.$slots.right?s("k-bar",{staticClass:"k-header-buttons"},[t._t("left",null,{slot:"left"}),t._t("right",null,{slot:"right"})],2):t._e(),s("k-tabs",{attrs:{tab:t.tab,tabs:t.tabsWithBadges,theme:"notice"}})],1)},Lm=[],Im={props:{editable:Boolean,tab:String,tabs:{type:Array,default(){return[]}}},computed:{tabsWithBadges(){const t=Object.keys(this.$store.getters["content/changes"]());return this.tabs.map(e=>{let s=[];return Object.values(e.columns).forEach(t=>{Object.values(t.sections).forEach(t=>{"fields"===t.type&&Object.keys(t.fields).forEach(t=>{s.push(t)})})}),e.badge=s.filter(e=>t.includes(e.toLowerCase())).length,e})}}},Am=Im,Bm=(s("53c5"),Object(c["a"])(Am,Tm,Lm,!1,null,null,null)),Mm=Bm.exports,Dm=function(){var t=this,e=t.$createElement,s=t._self._c||e;return s("ul",{staticClass:"k-list"},[t._t("default",t._l(t.items,(function(e,i){return s("k-list-item",t._g(t._b({key:i},"k-list-item",e,!1),t.$listeners))})))],2)},Nm=[],Pm={props:{items:Array}},Rm=Pm,qm=(s("c857"),Object(c["a"])(Rm,Dm,Nm,!1,null,null,null)),Fm=qm.exports,Um=function(){var t=this,e=t.$createElement,s=t._self._c||e;return s(t.element,t._g({tag:"component",staticClass:"k-list-item"},t.$listeners),[t.sortable?s("k-sort-handle"):t._e(),s("k-link",{staticClass:"k-list-item-content",attrs:{to:t.link,target:t.target}},[t.image?s("span",{staticClass:"k-list-item-image"},[t._t("image",[t.imageOptions?s("k-image",t._b({},"k-image",t.imageOptions,!1)):s("k-icon",t._b({},"k-icon",t.icon,!1))])],2):t._e(),s("span",{staticClass:"k-list-item-text"},[t._t("default",[s("em",{domProps:{innerHTML:t._s(t.text)}}),t.info?s("small",{domProps:{innerHTML:t._s(t.info)}}):t._e()])],2)]),s("nav",{staticClass:"k-list-item-options"},[t._t("options",[t.flag?s("k-button",t._b({staticClass:"k-list-item-status",on:{click:t.flag.click}},"k-button",t.flag,!1)):t.statusIcon?s("k-status-icon",t._b({staticClass:"k-list-item-status"},"k-status-icon",t.statusIcon,!1)):t._e(),t.options?s("k-button",{staticClass:"k-list-item-toggle",attrs:{tooltip:t.$t("options"),icon:"dots",alt:"Options"},on:{click:function(e){return e.stopPropagation(),t.$refs.options.toggle()}}}):t._e(),s("k-dropdown-content",{ref:"options",attrs:{options:t.options,align:"right"},on:{action:function(e){return t.$emit("action",e)}}})])],2)],1)},Hm=[],zm={inheritAttrs:!1,props:{element:{type:String,default:"li"},image:[Object,Boolean],icon:{type:Object,default(){return{type:"file",back:"black"}}},sortable:Boolean,text:String,target:String,info:String,link:[String,Function],flag:Object,options:[Array,Function],statusIcon:Object},computed:{imageOptions(){return Ep(this.image)}}},Km=zm,Gm=(s("fa6a"),Object(c["a"])(Km,Um,Hm,!1,null,null,null)),Wm=Gm.exports,Ym=function(){var t=this,e=t.$createElement,s=t._self._c||e;return t.isOpen?s("portal",[s("div",t._g({ref:"overlay",staticClass:"k-overlay",class:t.$vnode.data.staticClass,attrs:{"data-centered":t.loading||t.centered,"data-dimmed":t.dimmed,"data-loading":t.loading},on:{mousedown:t.close}},t.$listeners),[t.loading?s("k-loader",{staticClass:"k-overlay-loader"}):t._t("default",null,{close:t.close,isOpen:t.isOpen})],2)]):t._e()},Vm=[],Jm={inheritAttrs:!0,props:{autofocus:{type:Boolean,default:!0},centered:{type:Boolean,default:!1},dimmed:{type:Boolean,default:!0},loading:{type:Boolean,default:!1}},data(){return{isOpen:!1,scrollTop:0}},methods:{close(){this.isOpen=!1,this.$emit("close"),this.restoreScrollPosition(),this.$events.$off("keydown.esc",this.close),document.removeEventListener("focus",this.focustrap)},focus(){let t=this.$refs.overlay.querySelector("\n [autofocus],\n [data-autofocus]\n ");null===t&&(t=this.$refs.overlay.querySelector("\n input,\n textarea,\n select,\n button\n ")),t&&"function"===typeof t.focus?t.focus():this.$slots.default[0]&&this.$slots.default[0].context&&"function"===typeof this.$slots.default[0].context.focus&&this.$slots.default[0].context.focus()},focustrap(t){this.$refs.overlay&&!1===this.$refs.overlay.contains(t.target)&&this.focus()},open(){this.storeScrollPosition(),this.isOpen=!0,this.$emit("open"),this.$events.$on("keydown.esc",this.close),setTimeout(()=>{!0===this.autofocus&&this.focus(),document.querySelector(".k-overlay > *").addEventListener("mousedown",t=>t.stopPropagation()),this.$emit("ready")},1)},restoreScrollPosition(){const t=document.querySelector(".k-panel-view");t&&t.scrollTop&&(t.scrollTop=this.scrollTop)},storeScrollPosition(){const t=document.querySelector(".k-panel-view");t&&t.scrollTop?this.scrollTop=t.scrollTop:this.scrollTop=0}}},Zm=Jm,Xm=(s("f7f5"),Object(c["a"])(Zm,Ym,Vm,!1,null,null,null)),Qm=Xm.exports,tf=function(){var t=this,e=t.$createElement,s=t._self._c||e;return t.tabs&&t.tabs.length>1?s("div",{staticClass:"k-tabs",attrs:{"data-theme":t.theme}},[s("nav",[t._l(t.visibleTabs,(function(e){return s("k-button",{key:e.name,staticClass:"k-tab-button",attrs:{link:"#"+e.name,current:t.current===e.name,icon:e.icon,tooltip:e.label}},[t._v(" "+t._s(e.label||e.text||e.name)+" "),e.badge?s("span",{staticClass:"k-tabs-badge"},[t._v(" "+t._s(e.badge)+" ")]):t._e()])})),t.invisibleTabs.length?s("k-button",{staticClass:"k-tab-button k-tabs-dropdown-button",attrs:{text:t.$t("more"),icon:"dots"},on:{click:function(e){return e.stopPropagation(),t.$refs.more.toggle()}}}):t._e()],2),t.invisibleTabs.length?s("k-dropdown-content",{ref:"more",staticClass:"k-tabs-dropdown",attrs:{align:"right"}},t._l(t.invisibleTabs,(function(e){return s("k-dropdown-item",{key:"more-"+e.name,attrs:{link:"#"+e.name,current:t.tab===e.name,icon:e.icon,tooltip:e.label}},[t._v(" "+t._s(e.label||e.name)+" ")])})),1):t._e()],1):t._e()},ef=[],sf={props:{tab:String,tabs:Array,theme:String},data(){return{size:null,visibleTabs:this.tabs,invisibleTabs:[]}},computed:{current(){const t=this.tabs.find(t=>t.name===this.tab)||this.tabs[0]||{};return t.name}},watch:{tabs(t){this.visibleTabs=t,this.invisibleTabs=[],this.resize(!0)}},created(){window.addEventListener("resize",this.resize)},destroyed(){window.removeEventListener("resize",this.resize)},methods:{resize(t){if(this.tabs&&!(this.tabs.length<=1)){if(this.tabs.length<=3)return this.visibleTabs=this.tabs,void(this.invisibleTabs=[]);if(window.innerWidth>=700){if("large"===this.size&&!t)return;this.visibleTabs=this.tabs,this.invisibleTabs=[],this.size="large"}else{if("small"===this.size&&!t)return;this.visibleTabs=this.tabs.slice(0,2),this.invisibleTabs=this.tabs.slice(2),this.size="small"}}}}},nf=sf,af=(s("8f7d"),Object(c["a"])(nf,tf,ef,!1,null,null,null)),rf=af.exports,of=function(){var t=this,e=t.$createElement,s=t._self._c||e;return s("div",{staticClass:"k-view",attrs:{"data-align":t.align}},[t._t("default")],2)},lf=[],cf={props:{align:String}},uf=cf,df=(s("daa8"),Object(c["a"])(uf,of,lf,!1,null,null,null)),pf=df.exports,hf=function(){var t=this,e=t.$createElement,s=t._self._c||e;return s("draggable",t._g(t._b({staticClass:"k-draggable",attrs:{"component-data":t.data,tag:t.element,list:t.list,move:t.move}},"draggable",t.dragOptions,!1),t.listeners),[t._t("default"),t._t("footer",null,{slot:"footer"})],2)},mf=[],ff=s("b76a"),gf=s.n(ff),bf={components:{draggable:gf.a},props:{data:Object,element:String,handle:[String,Boolean],list:[Array,Object],move:Function,options:Object},data(){return{listeners:{...this.$listeners,start:t=>{this.$store.dispatch("drag",{}),this.$listeners.start&&this.$listeners.start(t)},end:t=>{this.$store.dispatch("drag",null),this.$listeners.end&&this.$listeners.end(t)}}}},computed:{dragOptions(){let t=!1;return t=!0===this.handle?".k-sort-handle":this.handle,{fallbackClass:"k-sortable-fallback",fallbackOnBody:!0,forceFallback:!0,ghostClass:"k-sortable-ghost",handle:t,scroll:document.querySelector(".k-panel-view"),...this.options}}}},kf=bf,vf=Object(c["a"])(kf,hf,mf,!1,null,null,null),$f=vf.exports,yf={data(){return{error:null}},errorCaptured(t){return O.debug&&window.console.warn(t),this.error=t,!1},render(t){return this.error?this.$slots.error?this.$slots.error[0]:this.$scopedSlots.error?this.$scopedSlots.error({error:this.error}):t("k-box",{attrs:{theme:"negative"}},this.error.message||this.error):this.$slots.default[0]}},_f=yf,wf=Object(c["a"])(_f,Tp,Lp,!1,null,null,null),xf=wf.exports,Of=function(){var t=this,e=t.$createElement,s=t._self._c||e;return s(t.tag,t._g({tag:"component",staticClass:"k-headline",attrs:{"data-theme":t.theme,"data-size":t.size}},t.$listeners),[t.link?s("k-link",{attrs:{to:t.link}},[t._t("default")],2):t._t("default")],2)},Cf=[],Sf={props:{link:String,size:{type:String},tag:{type:String,default:"h2"},theme:{type:String}}},Ef=Sf,jf=(s("f8a7"),Object(c["a"])(Ef,Of,Cf,!1,null,null,null)),Tf=jf.exports,Lf=function(){var t=this,e=t.$createElement,s=t._self._c||e;return s("span",{class:"k-icon k-icon-"+t.type,attrs:{"aria-label":t.alt,role:t.alt?"img":null,"aria-hidden":!t.alt,"data-back":t.back,"data-size":t.size}},[t.isEmoji?s("span",{staticClass:"k-icon-emoji"},[t._v(t._s(t.type))]):s("svg",{style:{color:t.color},attrs:{viewBox:"0 0 16 16"}},[s("use",{attrs:{"xlink:href":"#icon-"+t.type}})])])},If=[],Af={props:{alt:String,color:String,back:String,size:String,type:String},computed:{isEmoji(){return this.$helper.string.hasEmoji(this.type)}}},Bf=Af,Mf=(s("3342"),Object(c["a"])(Bf,Lf,If,!1,null,null,null)),Df=Mf.exports,Nf=function(){var t=this,e=t.$createElement,s=t._self._c||e;return s("span",t._g({staticClass:"k-image",attrs:{"data-ratio":t.ratio,"data-back":t.back,"data-cover":t.cover}},t.$listeners),[s("span",{style:"padding-bottom:"+t.ratioPadding},[t.loaded?s("img",{key:t.src,attrs:{alt:t.alt||"",src:t.src,srcset:t.srcset,sizes:t.sizes},on:{dragstart:function(t){t.preventDefault()}}}):t._e(),t.loaded||t.error?t._e():s("k-loader",{attrs:{position:"center",theme:"light"}}),!t.loaded&&t.error?s("k-icon",{staticClass:"k-image-error",attrs:{type:"cancel"}}):t._e()],1)])},Pf=[],Rf={props:{alt:String,back:String,cover:Boolean,ratio:String,sizes:String,src:String,srcset:String},data(){return{loaded:{type:Boolean,default:!1},error:{type:Boolean,default:!1}}},computed:{ratioPadding(){return this.$helper.ratio(this.ratio||"1/1")}},created(){let t=new Image;t.onload=()=>{this.loaded=!0,this.$emit("load")},t.onerror=()=>{this.error=!0,this.$emit("error")},t.src=this.src}},qf=Rf,Ff=(s("0d56"),Object(c["a"])(qf,Nf,Pf,!1,null,null,null)),Uf=Ff.exports,Hf=function(){var t=this,e=t.$createElement,s=t._self._c||e;return s("k-icon",{staticClass:"k-loader",attrs:{type:"loader"}})},zf=[],Kf=(s("3cf3"),{}),Gf=Object(c["a"])(Kf,Hf,zf,!1,null,null,null),Wf=Gf.exports,Yf=function(){var t=this,e=t.$createElement,s=t._self._c||e;return s("progress",{staticClass:"k-progress",attrs:{max:"100"},domProps:{value:t.state}},[t._v(" "+t._s(t.state)+"% ")])},Vf=[],Jf={props:{value:{type:Number,default:0}},data(){return{state:this.value}},methods:{set(t){this.state=t}}},Zf=Jf,Xf=(s("9799"),Object(c["a"])(Zf,Yf,Vf,!1,null,null,null)),Qf=Xf.exports,tg=function(){var t=this,e=t.$createElement,s=t._self._c||e;return s("k-icon",{staticClass:"k-sort-handle",attrs:{type:t.icon,"aria-hidden":"true"}})},eg=[],sg={props:{icon:{type:String,default:"sort"}}},ig=sg,ng=(s("35cb"),Object(c["a"])(ig,tg,eg,!1,null,null,null)),ag=ng.exports,rg=function(){var t=this,e=t.$createElement,s=t._self._c||e;return s("k-button",{class:"k-status-icon k-status-icon-"+t.status,attrs:{disabled:t.disabled,icon:t.icon,responsive:t.responsive,tooltip:t.title},on:{click:t.onClick}},[t.text?[t._v(" "+t._s(t.text)+" ")]:t._e()],2)},og=[],lg={props:{click:{type:Function,default:()=>{}},disabled:Boolean,responsive:Boolean,status:String,text:String,tooltip:String},computed:{icon(){return"draft"===this.status?"circle-outline":"unlisted"===this.status?"circle-half":"circle"},title(){let t=this.tooltip||this.text;return this.disabled&&(t+=` (${this.$t("disabled")})`),t}},methods:{onClick(){this.click(),this.$emit("click")}}},cg=lg,ug=(s("dd70"),Object(c["a"])(cg,rg,og,!1,null,null,null)),dg=ug.exports,pg=function(){var t=this,e=t.$createElement,s=t._self._c||e;return s("div",{staticClass:"k-text",attrs:{"data-align":t.align,"data-size":t.size,"data-theme":t.theme}},[t._t("default")],2)},hg=[],mg={props:{align:String,size:String,theme:String}},fg=mg,gg=(s("b0d6"),Object(c["a"])(fg,pg,hg,!1,null,null,null)),bg=gg.exports,kg=function(){var t=this,e=t.$createElement,s=t._self._c||e;return s("div",{staticClass:"k-user-info"},[t.user.avatar?s("k-image",{attrs:{cover:!0,src:t.user.avatar.url,ratio:"1/1"}}):s("k-icon",{attrs:{type:"user"}}),t._v(" "+t._s(t.user.name||t.user.email||t.user)+" ")],1)},vg=[],$g={props:{user:[Object,String]}},yg=$g,_g=(s("217b"),Object(c["a"])(yg,kg,vg,!1,null,null,null)),wg=_g.exports,xg=function(){var t=this,e=t.$createElement,s=t._self._c||e;return s(t.component,t._g(t._b({ref:"button",tag:"component"},"component",t.$props,!1),t.$listeners),[t._t("default")],2)},Og=[],Cg={inheritAttrs:!1,props:{autofocus:Boolean,current:[String,Boolean],disabled:Boolean,icon:String,id:[String,Number],link:String,responsive:Boolean,rel:String,role:String,target:String,tabindex:String,theme:String,tooltip:String,type:{type:String,default:"button"}},computed:{component(){return!0===this.disabled?"k-button-disabled":this.link?"k-button-link":"k-button-native"}},methods:{focus(){this.$refs.button.focus&&this.$refs.button.focus()},tab(){this.$refs.button.tab&&this.$refs.button.tab()},untab(){this.$refs.button.untab&&this.$refs.button.untab()}}},Sg=Cg,Eg=(s("3787"),Object(c["a"])(Sg,xg,Og,!1,null,null,null)),jg=Eg.exports,Tg=function(){var t=this,e=t.$createElement,s=t._self._c||e;return s("span",{staticClass:"k-button",attrs:{id:t.id,"data-disabled":!0,"data-responsive":t.responsive,"data-theme":t.theme,title:t.tooltip}},[t.icon?s("k-icon",{staticClass:"k-button-icon",attrs:{type:t.icon,alt:t.tooltip}}):t._e(),t.$slots.default?s("span",{staticClass:"k-button-text"},[t._t("default")],2):t._e()],1)},Lg=[],Ig={inheritAttrs:!1,props:{icon:String,id:[String,Number],responsive:Boolean,theme:String,tooltip:String}},Ag=Ig,Bg=(s("16eb"),Object(c["a"])(Ag,Tg,Lg,!1,null,null,null)),Mg=Bg.exports,Dg=function(){var t=this,e=t.$createElement,s=t._self._c||e;return s("div",{staticClass:"k-button-group"},[t._t("default")],2)},Ng=[],Pg={},Rg=Pg,qg=(s("a567"),Object(c["a"])(Rg,Dg,Ng,!1,null,null,null)),Fg=qg.exports,Ug=function(){var t=this,e=t.$createElement,s=t._self._c||e;return s("k-link",t._g({staticClass:"k-button",attrs:{id:t.id,"aria-current":t.current,autofocus:t.autofocus,"data-theme":t.theme,"data-responsive":t.responsive,rel:t.rel,role:t.role,tabindex:t.tabindex,target:t.target,title:t.tooltip,to:t.link}},t.$listeners),[t.icon?s("k-icon",{staticClass:"k-button-icon",attrs:{type:t.icon,alt:t.tooltip}}):t._e(),t.$slots.default?s("span",{staticClass:"k-button-text"},[t._t("default")],2):t._e()],1)},Hg=[],zg={inheritAttrs:!1,props:{autofocus:Boolean,current:[String,Boolean],icon:String,id:[String,Number],link:String,rel:String,responsive:Boolean,role:String,target:String,tabindex:String,theme:String,tooltip:String}},Kg=zg,Gg=Object(c["a"])(Kg,Ug,Hg,!1,null,null,null),Wg=Gg.exports,Yg=function(){var t=this,e=t.$createElement,s=t._self._c||e;return s("button",t._g({staticClass:"k-button",attrs:{id:t.id,"aria-current":t.current,autofocus:t.autofocus,"data-theme":t.theme,"data-responsive":t.responsive,role:t.role,tabindex:t.tabindex,title:t.tooltip,type:t.type}},t.$listeners),[t.icon?s("k-icon",{staticClass:"k-button-icon",attrs:{type:t.icon,alt:t.tooltip}}):t._e(),t.$slots.default?s("span",{staticClass:"k-button-text"},[t._t("default")],2):t._e()],1)},Vg=[],Jg={mounted(){this.$el.addEventListener("keyup",this.onTab,!0),this.$el.addEventListener("blur",this.onUntab,!0)},destroyed(){this.$el.removeEventListener("keyup",this.onTab,!0),this.$el.removeEventListener("blur",this.onUntab,!0)},methods:{focus(){this.$el.focus&&this.$el.focus()},onTab(t){9===t.keyCode&&this.$el.setAttribute("data-tabbed",!0)},onUntab(){this.$el.removeAttribute("data-tabbed")},tab(){this.$el.focus(),this.$el.setAttribute("data-tabbed",!0)},untab(){this.$el.removeAttribute("data-tabbed")}}},Zg={mixins:[Jg],inheritAttrs:!1,props:{autofocus:Boolean,current:[String,Boolean],icon:String,id:[String,Number],responsive:Boolean,role:String,tabindex:String,theme:String,tooltip:String,type:{type:String,default:"button"}}},Xg=Zg,Qg=Object(c["a"])(Xg,Yg,Vg,!1,null,null,null),tb=Qg.exports,eb=function(){var t=this,e=t.$createElement,s=t._self._c||e;return s("span",{staticClass:"k-dropdown",on:{click:function(t){t.stopPropagation()}}},[t._t("default")],2)},sb=[],ib={},nb=ib,ab=(s("f95f"),Object(c["a"])(nb,eb,sb,!1,null,null,null)),rb=ab.exports,ob=function(){var t=this,e=t.$createElement,s=t._self._c||e;return t.isOpen?s("div",{staticClass:"k-dropdown-content",attrs:{"data-align":t.align,"data-dropup":t.dropup}},[t._t("default",[t._l(t.items,(function(e,i){return["-"===e?s("hr",{key:t._uid+"-item-"+i}):s("k-dropdown-item",t._b({key:t._uid+"-item-"+i,ref:t._uid+"-item-"+i,refInFor:!0,on:{click:function(s){return t.$emit("action",e.click)}}},"k-dropdown-item",e,!1),[t._v(" "+t._s(e.text)+" ")])]}))])],2):t._e()},lb=[];let cb=null;var ub,db,pb={props:{options:[Array,Function],align:{type:String,default:"left"}},data(){return{current:-1,dropup:!1,isOpen:!1,items:[]}},methods:{async fetchOptions(t){if(!this.options)return t(this.items);if("string"===typeof this.options){const e=await fetch(this.options),s=await e.json();return t(s)}"function"===typeof this.options?this.options(t):Array.isArray(this.options)&&t(this.options)},open(){this.reset(),cb&&cb!==this&&cb.close(),this.fetchOptions(t=>{this.$events.$on("keydown",this.navigate),this.$events.$on("click",this.close),this.items=t,this.isOpen=!0,cb=this,this.onOpen(),this.$emit("open")})},reset(){this.current=-1,this.$events.$off("keydown",this.navigate),this.$events.$off("click",this.close)},close(){this.reset(),this.isOpen=cb=!1,this.$emit("close")},toggle(){this.isOpen?this.close():this.open()},focus(t=0){this.$children[t]&&this.$children[t].focus&&(this.current=t,this.$children[t].focus())},onOpen(){this.dropup=!1,this.$nextTick(()=>{const t=document.querySelector(".k-panel-view");if(t&&this.$el){let e=t.clientHeight-40,s=this.$el.getBoundingClientRect().top||0,i=this.$el.clientHeight;this.dropup=s+i>e}})},navigate(t){switch(t.code){case"Escape":case"ArrowLeft":this.close(),this.$emit("leave",t.code);break;case"ArrowUp":t.preventDefault();while(1){if(this.current--,this.current<0){this.close(),this.$emit("leave",t.code);break}if(this.$children[this.current]&&!1===this.$children[this.current].disabled){this.focus(this.current);break}}break;case"ArrowDown":t.preventDefault();while(1){if(this.current++,this.current>this.$children.length-1){const t=this.$children.filter(t=>!1===t.disabled);this.current=this.$children.indexOf(t[t.length-1]);break}if(this.$children[this.current]&&!1===this.$children[this.current].disabled){this.focus(this.current);break}}break;case"Tab":while(1){if(this.current++,this.current>this.$children.length-1){this.close(),this.$emit("leave",t.code);break}if(this.$children[this.current]&&!1===this.$children[this.current].disabled)break}break}}}},hb=pb,mb=(s("98a1"),Object(c["a"])(hb,ob,lb,!1,null,null,null)),fb=mb.exports,gb=function(){var t=this,e=t.$createElement,s=t._self._c||e;return s("k-button",t._g(t._b({ref:"button",staticClass:"k-dropdown-item"},"k-button",t.$props,!1),t.listeners),[t._t("default")],2)},bb=[],kb={inheritAttrs:!1,props:{disabled:Boolean,icon:String,image:[String,Object],link:String,target:String,theme:String,upload:String,current:[String,Boolean]},data(){return{listeners:{...this.$listeners,click:t=>{this.$parent.close(),this.$emit("click",t)}}}},methods:{focus(){this.$refs.button.focus()},tab(){this.$refs.button.tab()}}},vb=kb,$b=(s("580a"),Object(c["a"])(vb,gb,bb,!1,null,null,null)),yb=$b.exports,_b=function(){var t=this,e=t.$createElement,s=t._self._c||e;return t.to&&!t.disabled?s("a",t._g({ref:"link",staticClass:"k-link",attrs:{href:t.href,rel:t.relAttr,tabindex:t.tabindex,target:t.target,title:t.title}},t.listeners),[t._t("default")],2):s("span",{staticClass:"k-link",attrs:{title:t.title,"data-disabled":""}},[t._t("default")],2)},wb=[],xb={mixins:[Jg],props:{disabled:Boolean,rel:String,tabindex:[String,Number],target:String,title:String,to:[String,Function]},data(){return{relAttr:"_blank"===this.target?"noreferrer noopener":this.rel,listeners:{...this.$listeners,click:this.onClick}}},computed:{href(){return"function"===typeof this.to?"":void 0===this.$route||"/"!==this.to[0]||this.target?this.to:(this.$router.options.url||"")+this.to}},methods:{isRoutable(t){return void 0!==this.$route&&(!(t.metaKey||t.altKey||t.ctrlKey||t.shiftKey)&&(!t.defaultPrevented&&((void 0===t.button||0===t.button)&&!this.target)))},onClick(t){if(!0===this.disabled)return t.preventDefault(),!1;"function"===typeof this.to&&(t.preventDefault(),this.to()),this.isRoutable(t)&&(t.preventDefault(),this.$go(this.to)),this.$emit("click",t)}}},Ob=xb,Cb=(s("cc79"),Object(c["a"])(Ob,_b,wb,!1,null,null,null)),Sb=Cb.exports,Eb=function(){var t=this,e=t.$createElement,s=t._self._c||e;return t.languages.length?s("k-dropdown",[s("k-button",{attrs:{responsive:!0,icon:"globe"},on:{click:function(e){return t.$refs.languages.toggle()}}},[t._v(" "+t._s(t.language.name)+" ")]),t.languages?s("k-dropdown-content",{ref:"languages"},[s("k-dropdown-item",{on:{click:function(e){return t.change(t.defaultLanguage)}}},[t._v(" "+t._s(t.defaultLanguage.name)+" ")]),s("hr"),t._l(t.languages,(function(e){return s("k-dropdown-item",{key:e.code,on:{click:function(s){return t.change(e)}}},[t._v(" "+t._s(e.name)+" ")])}))],2):t._e()],1):t._e()},jb=[],Tb={computed:{defaultLanguage(){return this.$store.state.languages.default},language(){return this.$store.state.languages.current},languages(){return this.$store.state.languages.all.filter(t=>!1===t.default)}},methods:{change(t){this.$store.dispatch("languages/current",t),this.$emit("change",t)}}},Lb=Tb,Ib=Object(c["a"])(Lb,Eb,jb,!1,null,null,null),Ab=Ib.exports,Bb=function(){var t=this,e=t.$createElement,s=t._self._c||e;return t.show?s("nav",{staticClass:"k-pagination",attrs:{"data-align":t.align}},[t.show?s("k-button",{attrs:{disabled:!t.hasPrev,tooltip:t.prevLabel,icon:"angle-left"},on:{click:t.prev}}):t._e(),t.details?[t.dropdown?[s("k-dropdown",[s("k-button",{staticClass:"k-pagination-details",attrs:{disabled:!t.hasPages},on:{click:function(e){return t.$refs.dropdown.toggle()}}},[t.total>1?[t._v(" "+t._s(t.detailsText)+" ")]:t._e(),t._v(t._s(t.total)+" ")],2),s("k-dropdown-content",{ref:"dropdown",staticClass:"k-pagination-selector",on:{open:function(e){t.$nextTick((function(){return t.$refs.page.focus()}))}}},[s("div",{staticClass:"k-pagination-settings"},[s("label",{attrs:{for:"k-pagination-page"}},[s("span",[t._v(t._s(t.pageLabel)+":")]),s("select",{ref:"page",attrs:{id:"k-pagination-page"}},t._l(t.pages,(function(e){return s("option",{key:e,domProps:{selected:t.page===e,value:e}},[t._v(" "+t._s(e)+" ")])})),0)]),s("k-button",{attrs:{icon:"check"},on:{click:function(e){return t.goTo(t.$refs.page.value)}}})],1)])],1)]:[s("span",{staticClass:"k-pagination-details"},[t.total>1?[t._v(t._s(t.detailsText))]:t._e(),t._v(t._s(t.total)+" ")],2)]]:t._e(),t.show?s("k-button",{attrs:{disabled:!t.hasNext,tooltip:t.nextLabel,icon:"angle-right"},on:{click:t.next}}):t._e()],2):t._e()},Mb=[],Db={props:{align:{type:String,default:"left"},details:{type:Boolean,default:!1},dropdown:{type:Boolean,default:!0},keys:{type:Boolean,default:!1},limit:{type:Number,default:10},page:{type:Number,default:1},pageLabel:{type:String,default(){return this.$t("pagination.page")}},total:{type:Number,default:0},prevLabel:{type:String,default(){return this.$t("prev")}},nextLabel:{type:String,default(){return this.$t("next")}},validate:{type:Function,default(){return Promise.resolve()}}},data(){return{currentPage:this.page}},computed:{show(){return this.pages>1},start(){return(this.currentPage-1)*this.limit+1},end(){let t=this.start-1+this.limit;return t>this.total?this.total:t},detailsText(){return 1===this.limit?this.start+" / ":this.start+"-"+this.end+" / "},pages(){return Math.ceil(this.total/this.limit)},hasPrev(){return this.start>1},hasNext(){return this.endthis.limit},offset(){return this.start-1}},watch:{page(t){this.currentPage=parseInt(t)}},created(){!0===this.keys&&window.addEventListener("keydown",this.navigate,!1)},destroyed(){window.removeEventListener("keydown",this.navigate,!1)},methods:{goTo(t){this.validate(t).then(()=>{t<1&&(t=1),t>this.pages&&(t=this.pages),this.currentPage=t,this.$refs.dropdown&&this.$refs.dropdown.close(),this.$emit("paginate",{page:this.currentPage,start:this.start,end:this.end,limit:this.limit,offset:this.offset})}).catch(()=>{})},prev(){this.goTo(this.currentPage-1)},next(){this.goTo(this.currentPage+1)},navigate(t){switch(t.code){case"ArrowLeft":this.prev();break;case"ArrowRight":this.next();break}}}},Nb=Db,Pb=(s("a66d"),Object(c["a"])(Nb,Bb,Mb,!1,null,null,null)),Rb=Pb.exports,qb=function(){var t=this,e=t.$createElement,s=t._self._c||e;return s("k-button-group",{staticClass:"k-prev-next"},[s("k-button",t._b({attrs:{icon:"angle-left"}},"k-button",t.button(t.prev),!1)),s("k-button",t._b({attrs:{icon:"angle-right"}},"k-button",t.button(t.next),!1))],1)},Fb=[],Ub={props:{prev:{type:[Boolean,Object],default:!1},next:{type:[Boolean,Object],default:!1}},methods:{button(t){return t||{disabled:!0,link:"#"}}}},Hb=Ub,zb=(s("7a7d"),Object(c["a"])(Hb,qb,Fb,!1,null,null,null)),Kb=zb.exports,Gb=function(){var t=this,e=t.$createElement,s=t._self._c||e;return s("k-overlay",{ref:"overlay"},[s("div",{staticClass:"k-search",attrs:{role:"search"}},[s("div",{staticClass:"k-search-input"},[s("k-dropdown",{staticClass:"k-search-types"},[s("k-button",{attrs:{icon:t.currentType.icon},on:{click:function(e){return t.$refs.types.toggle()}}},[t._v(" "+t._s(t.currentType.label)+": ")]),s("k-dropdown-content",{ref:"types"},t._l(t.types,(function(e,i){return s("k-dropdown-item",{key:i,attrs:{icon:e.icon},on:{click:function(e){return t.changeType(i)}}},[t._v(" "+t._s(e.label)+" ")])})),1)],1),s("input",{directives:[{name:"model",rawName:"v-model",value:t.q,expression:"q"}],ref:"input",attrs:{placeholder:t.$t("search")+" …","aria-label":t.$t("search"),autofocus:!0,type:"text"},domProps:{value:t.q},on:{input:[function(e){e.target.composing||(t.q=e.target.value)},function(e){t.hasResults=!0}],keydown:[function(e){return!e.type.indexOf("key")&&t._k(e.keyCode,"down",40,e.key,["Down","ArrowDown"])?null:(e.preventDefault(),t.onDown(e))},function(e){return!e.type.indexOf("key")&&t._k(e.keyCode,"up",38,e.key,["Up","ArrowUp"])?null:(e.preventDefault(),t.onUp(e))},function(e){return!e.type.indexOf("key")&&t._k(e.keyCode,"tab",9,e.key,"Tab")?null:(e.preventDefault(),t.onTab(e))},function(e){return!e.type.indexOf("key")&&t._k(e.keyCode,"enter",13,e.key,"Enter")?null:t.onEnter(e)},function(e){return!e.type.indexOf("key")&&t._k(e.keyCode,"esc",27,e.key,["Esc","Escape"])?null:t.close(e)}]}}),s("k-button",{staticClass:"k-search-close",attrs:{icon:t.isLoading?"loader":"cancel",tooltip:t.$t("close")},on:{click:t.close}})],1),!t.q||t.hasResults&&!t.items.length?t._e():s("div",{staticClass:"k-search-results"},[t.items.length?s("ul",{on:{mouseout:function(e){t.selected=-1}}},t._l(t.items,(function(e,i){return s("li",{key:e.id,attrs:{"data-selected":t.selected===i},on:{mouseover:function(e){t.selected=i}}},[s("k-link",{attrs:{to:e.link},on:{click:t.close}},[s("span",{staticClass:"k-search-item-image"},[t.imageOptions(e.image)?s("k-image",t._b({},"k-image",t.imageOptions(e.image),!1)):s("k-icon",t._b({},"k-icon",e.icon,!1))],1),s("span",{staticClass:"k-search-item-info"},[s("strong",[t._v(t._s(e.title))]),s("small",[t._v(t._s(e.info))])])])],1)})),0):t.hasResults?t._e():s("p",{staticClass:"k-search-empty"},[t._v(" "+t._s(t.$t("search.results.none"))+" ")])])])])},Wb=[],Yb={props:{types:{type:Object,default(){return{}}},type:{type:String}},data(){return{isLoading:!1,hasResults:!0,items:[],currentType:this.getType(this.type),q:null,selected:-1}},watch:{q(){this.search(this.q)},currentType(){this.search(this.q)},type(){this.currentType=this.getType(this.type)},types(){this.currentType=this.getType(this.type)}},created(){this.search=V(this.search,250),this.$events.$on("keydown.cmd.shift.f",this.open)},destroyed(){this.$events.$off("keydown.cmd.shift.f",this.open)},methods:{changeType(t){this.currentType=this.getType(t),this.$nextTick(()=>{this.$refs.input.focus()})},close(){this.$refs.overlay.close(),this.hasResults=!0,this.items=[],this.q=null},getType(t){return this.types[t]||this.types[Object.keys(this.types)[0]]},imageOptions(t){return Ep(t)},navigate(t){this.$go(t.link),this.close()},onDown(){this.selected=0&&this.selected--},open(){this.$refs.overlay.open()},async search(t){this.isLoading=!0,this.$refs.types&&this.$refs.types.close();try{if(""===t)throw new Error;this.items=await this.currentType.search({query:t,limit:O.search.limit})}catch(e){this.items=[]}finally{this.selected=-1,this.isLoading=!1,this.hasResults=this.items.length>0}}}},Vb=Yb,Jb=(s("4cb2"),Object(c["a"])(Vb,Gb,Wb,!1,null,null,null)),Zb=Jb.exports,Xb=function(){var t=this,e=t.$createElement,s=t._self._c||e;return s("span",{ref:"button",staticClass:"k-tag",attrs:{tabindex:"0"},on:{keydown:function(e){return!e.type.indexOf("key")&&t._k(e.keyCode,"delete",[8,46],e.key,["Backspace","Delete","Del"])?null:(e.preventDefault(),t.remove(e))}}},[s("span",{staticClass:"k-tag-text"},[t._t("default")],2),t.removable?s("span",{staticClass:"k-tag-toggle",on:{click:t.remove}},[t._v("×")]):t._e()])},Qb=[],tk={props:{removable:Boolean},methods:{remove(){this.removable&&this.$emit("remove")},focus(){this.$refs.button.focus()}}},ek=tk,sk=(s("021f"),Object(c["a"])(ek,Xb,Qb,!1,null,null,null)),ik=sk.exports,nk=function(){var t=this,e=t.$createElement,s=t._self._c||e;return t.user&&t.view?s("div",{staticClass:"k-topbar"},[s("k-view",[s("div",{staticClass:"k-topbar-wrapper"},[s("k-dropdown",{staticClass:"k-topbar-menu"},[s("k-button",{staticClass:"k-topbar-button k-topbar-menu-button",attrs:{tooltip:t.$t("menu"),icon:"bars"},on:{click:function(e){return t.$refs.menu.toggle()}}},[s("k-icon",{attrs:{type:"angle-down"}})],1),s("k-dropdown-content",{ref:"menu",staticClass:"k-topbar-menu"},[s("ul",[t._l(t.views,(function(e,i){return[!1!==t.viewEntryInMenu(i,e)?s("li",{key:"menu-item-"+i,attrs:{"aria-current":t.$store.state.view===i}},[s("k-dropdown-item",{attrs:{disabled:"disabled"===t.viewEntryInMenu(i,e),icon:e.icon,link:e.link}},[t._v(" "+t._s(t.menuTitle(e,i))+" ")])],1):t._e()]})),s("li",[s("hr")]),s("li",{attrs:{"aria-current":"account"===t.$route.meta.view}},[s("k-dropdown-item",{attrs:{icon:"account",link:"/account"}},[t._v(" "+t._s(t.$t("view.account"))+" ")])],1),s("li",[s("hr")]),s("li",[s("k-dropdown-item",{attrs:{icon:"logout",link:"/logout"}},[t._v(" "+t._s(t.$t("logout"))+" ")])],1)],2)])],1),t.view?s("k-link",{staticClass:"k-topbar-button k-topbar-view-button",attrs:{to:t.view.link}},[s("k-icon",{attrs:{type:t.view.icon}}),t._v(" "+t._s(t.breadcrumbTitle)+" ")],1):t._e(),t.$store.state.breadcrumb.length>1?s("k-dropdown",{staticClass:"k-topbar-breadcrumb-menu"},[s("k-button",{staticClass:"k-topbar-button",on:{click:function(e){return t.$refs.crumb.toggle()}}},[t._v(" … "),s("k-icon",{attrs:{type:"angle-down"}})],1),s("k-dropdown-content",{ref:"crumb"},[s("k-dropdown-item",{attrs:{icon:t.view.icon,link:t.view.link}},[t._v(" "+t._s(t.$t("view."+t.$store.state.view,t.view.label))+" ")]),t._l(t.$store.state.breadcrumb,(function(e,i){return s("k-dropdown-item",{key:"crumb-"+i+"-dropdown",attrs:{icon:t.view.icon,link:e.link}},[t._v(" "+t._s(e.label)+" ")])}))],2)],1):t._e(),s("nav",{staticClass:"k-topbar-crumbs"},t._l(t.$store.state.breadcrumb,(function(e,i){return s("k-link",{key:"crumb-"+i,attrs:{to:e.link}},[t._v(" "+t._s(e.label)+" ")])})),1),s("div",{staticClass:"k-topbar-signals"},[s("span",{directives:[{name:"show",rawName:"v-show",value:t.$store.state.isLoading,expression:"$store.state.isLoading"}],staticClass:"k-topbar-loader"},[s("svg",{attrs:{viewBox:"0 0 16 18"}},[s("path",{attrs:{fill:"white",d:"M8,0 L16,4.50265232 L16,13.5112142 L8,18.0138665 L0,13.5112142 L0,4.50265232 L8,0 Z M2.10648757,5.69852516 L2.10648757,12.3153414 L8,15.632396 L13.8935124,12.3153414 L13.8935124,5.69852516 L8,2.38147048 L2.10648757,5.69852516 Z"}})])]),t.notification?[s("k-button",{staticClass:"k-topbar-notification k-topbar-signals-button",attrs:{theme:"positive"},on:{click:function(e){return t.$store.dispatch("notification/close")}}},[t._v(" "+t._s(t.notification.message)+" ")])]:t.unregistered?[s("div",{staticClass:"k-registration"},[s("p",[t._v(t._s(t.$t("license.unregistered")))]),s("k-button",{staticClass:"k-topbar-signals-button",attrs:{responsive:!0,tooltip:t.$t("license.unregistered"),icon:"key"},on:{click:function(e){return t.$emit("register")}}},[t._v(" "+t._s(t.$t("license.register"))+" ")]),s("k-button",{staticClass:"k-topbar-signals-button",attrs:{responsive:!0,link:"https://getkirby.com/buy",target:"_blank",icon:"cart"}},[t._v(" "+t._s(t.$t("license.buy"))+" ")])],1)]:t._e(),[s("k-form-indicator")],s("k-button",{staticClass:"k-topbar-signals-button",attrs:{tooltip:t.$t("search"),icon:"search"},on:{click:function(e){return t.$emit("search")}}})],2)],1)])],1):t._e()},ak=[],rk={site:{link:"/site",icon:"page",menu:!0},users:{link:"/users",icon:"users",menu:!0},settings:{link:"/settings",icon:"settings",menu:!0},account:{link:"/account",icon:"users",menu:!1},resetPassword:{link:"/reset-password",icon:"key",menu:!1},...window.panel.plugins.views},ok={computed:{breadcrumbTitle(){let t=this.$t("view."+this.$store.state.view,this.view.label);return"site"===this.$store.state.view&&this.$store.state.system.info.title||t},view(){return rk[this.$store.state.view]},views(){return rk},user(){return this.$store.state.user.current},notification(){return this.$store.state.notification.type&&"error"!==this.$store.state.notification.type?this.$store.state.notification:null},unregistered(){return!this.$store.state.system.info.license}},methods:{menuTitle(t,e){let s=this.$t("view."+e,t.label);return"site"===e&&this.$store.state.system.info.site||s},viewEntryInMenu(t,e){let s=e.menu;return"function"===typeof s&&(s=s(this)),[!0,!1,"disabled"].indexOf(s)>=0?s:!1!==this.$permissions.access[t]||"disabled"}}},lk=ok,ck=(s("1e3b"),Object(c["a"])(lk,nk,ak,!1,null,null,null)),uk=ck.exports,dk=function(){var t=this,e=t.$createElement,s=t._self._c||e;return 0===t.tabs.length?s("k-box",{attrs:{text:t.empty,theme:"info"}}):s("k-grid",{staticClass:"k-sections",attrs:{gutter:"large"}},t._l(t.currentTab.columns,(function(e,i){return s("k-column",{key:t.parent+"-column-"+i,attrs:{width:e.width,sticky:e.sticky}},[t._l(e.sections,(function(n,a){return[t.meetsCondition(n)?[t.exists(n.type)?s("k-"+n.type+"-section",t._b({key:t.parent+"-column-"+i+"-section-"+a+"-"+t.blueprint,tag:"component",class:"k-section k-section-name-"+n.name,attrs:{name:n.name,parent:t.parent,blueprint:t.blueprint,column:e.width},on:{submit:function(e){return t.$emit("submit",e)}}},"component",n,!1)):[s("k-box",{key:t.parent+"-column-"+i+"-section-"+a,attrs:{text:t.$t("error.section.type.invalid",{type:n.type}),theme:"negative"}})]]:t._e()]}))],2)})),1)},pk=[],hk={props:{empty:String,blueprint:String,parent:String,tab:String,tabs:Array},computed:{currentTab(){return this.tabs.find(t=>t.name===this.tab)||this.tabs[0]||{}},content(){return this.$store.getters["content/values"]()}},methods:{exists(t){return this.$helper.isComponent(`k-${t}-section`)},meetsCondition(t){if(!t.when)return!0;let e=!0;return Object.keys(t.when).forEach(s=>{const i=this.content[s.toLowerCase()],n=t.when[s];i!==n&&(e=!1)}),e}}},mk=hk,fk=(s("6bcd"),Object(c["a"])(mk,dk,pk,!1,null,null,null)),gk=fk.exports,bk=function(){var t=this,e=t.$createElement,s=t._self._c||e;return s("section",{staticClass:"k-info-section"},[s("k-headline",{staticClass:"k-info-section-headline"},[t._v(" "+t._s(t.headline)+" ")]),s("k-box",{attrs:{theme:t.theme}},[s("k-text",{domProps:{innerHTML:t._s(t.text)}})],1)],1)},kk=[],vk={props:{blueprint:String,help:String,name:String,parent:String},methods:{load(){return this.$api.get(this.parent+"/sections/"+this.name)}}},$k={mixins:[vk],data(){return{headline:null,text:null,theme:null}},created(){this.load().then(t=>{this.headline=t.options.headline,this.text=t.options.text,this.theme=t.options.theme||"info"})}},yk=$k,_k=(s("4333"),Object(c["a"])(yk,bk,kk,!1,null,null,null)),wk=_k.exports,xk=function(){var t=this,e=t.$createElement,s=t._self._c||e;return!1===t.isLoading?s("section",{staticClass:"k-pages-section",attrs:{"data-processing":t.isProcessing}},[s("header",{staticClass:"k-section-header"},[s("k-headline",{attrs:{link:t.options.link}},[t._v(" "+t._s(t.headline)+" "),t.options.min?s("abbr",{attrs:{title:t.$t("section.required")}},[t._v("*")]):t._e()]),t.add?s("k-button-group",[s("k-button",{attrs:{icon:"add"},on:{click:t.create}},[t._v(" "+t._s(t.$t("add"))+" ")])],1):t._e()],1),t.error?[s("k-box",{attrs:{theme:"negative"}},[s("k-text",{attrs:{size:"small"}},[s("strong",[t._v(" "+t._s(t.$t("error.section.notLoaded",{name:t.name}))+": ")]),t._v(" "+t._s(t.error)+" ")])],1)]:[t.data.length?s("k-collection",{attrs:{layout:t.options.layout,help:t.help,items:t.data,pagination:t.pagination,sortable:!t.isProcessing&&t.options.sortable,size:t.options.size,"data-invalid":t.isInvalid},on:{change:t.sort,paginate:t.paginate,action:t.action}}):[s("k-empty",{attrs:{layout:t.options.layout,"data-invalid":t.isInvalid,icon:"page"},on:{click:t.create}},[t._v(" "+t._s(t.options.empty||t.$t("pages.empty"))+" ")]),s("footer",{staticClass:"k-collection-footer"},[t.help?s("k-text",{staticClass:"k-collection-help",attrs:{theme:"help"},domProps:{innerHTML:t._s(t.help)}}):t._e()],1)],s("k-page-create-dialog",{ref:"create"}),s("k-page-duplicate-dialog",{ref:"duplicate"}),s("k-page-rename-dialog",{ref:"rename",on:{success:t.update}}),s("k-page-sort-dialog",{ref:"sort",on:{success:t.update}}),s("k-page-status-dialog",{ref:"status",on:{success:t.update}}),s("k-page-template-dialog",{ref:"template",on:{success:t.update}}),s("k-page-remove-dialog",{ref:"remove",on:{success:t.update}})]],2):t._e()},Ok=[],Ck={inheritAttrs:!1,props:{blueprint:String,column:String,parent:String,name:String},data(){return{data:[],error:null,isLoading:!1,isProcessing:!1,options:{empty:null,headline:null,help:null,layout:"list",link:null,max:null,min:null,size:null,sortable:null},pagination:{page:null}}},computed:{headline(){return this.options.headline||" "},help(){return this.options.help},isInvalid(){return!!(this.options.min&&this.data.lengththis.options.max)},language(){return this.$store.state.languages.current},paginationId(){return"kirby$pagination$"+this.parent+"/"+this.name}},watch:{language(){this.reload()}},methods:{items(t){return t},async load(t){t||(this.isLoading=!0),this.isProcessing=!0,null===this.pagination.page&&(this.pagination.page=localStorage.getItem(this.paginationId)||1);try{const t=await this.$api.get(this.parent+"/sections/"+this.name,{page:this.pagination.page});this.options=t.options,this.pagination=t.pagination,this.data=this.items(t.data)}catch(e){this.error=e.message}finally{this.isProcessing=!1,this.isLoading=!1}},paginate(t){localStorage.setItem(this.paginationId,t.page),this.pagination=t,this.reload()},async reload(){await this.load(!0)}}},Sk={mixins:[Ck],computed:{add(){return this.options.add&&this.$permissions.pages.create}},created(){this.load(),this.$events.$on("page.changeStatus",this.reload),this.$events.$on("page.sort",this.reload)},destroyed(){this.$events.$off("page.changeStatus",this.reload),this.$events.$off("page.sort",this.reload)},methods:{create(){this.add&&this.$refs.create.open(this.options.link||this.parent,this.parent+"/blueprints",this.name)},action(t,e){switch(e){case"duplicate":this.$refs.duplicate.open(t.id);break;case"preview":{let e=window.open("","_blank");e.document.write="...",this.$api.pages.preview(t.id).then(t=>{e.location.href=t}).catch(t=>{this.$store.dispatch("notification/error",t)});break}case"rename":this.$refs.rename.open(t.id,t.permissions,"title");break;case"url":this.$refs.rename.open(t.id,t.permissions,"slug");break;case"sort":this.$refs.sort.open(t.id);break;case"status":this.$refs.status.open(t.id);break;case"template":this.$refs.template.open(t.id);break;case"remove":if(this.data.length<=this.options.min){const t=this.options.min>1?"plural":"singular";this.$store.dispatch("notification/error",{message:this.$t("error.section.pages.min."+t,{section:this.options.headline||this.name,min:this.options.min})});break}this.$refs.remove.open(t.id);break;default:throw new Error("Invalid action")}},items(t){return t.map(t=>{const e=!1!==t.permissions.changeStatus;return t.statusIcon={status:t.status,tooltip:this.$t("page.status"),disabled:!e,click:()=>{this.action(t,"status")}},t.sortable=t.permissions.sort&&this.options.sortable,t.column=this.column,t.options=async e=>{try{const s=await this.$api.pages.options(t.id,"list",t.sortable);e(s)}catch(s){this.$store.dispatch("notification/error",s)}},t})},async sort(t){let e=null;if(t.added&&(e="added"),t.moved&&(e="moved"),e){this.isProcessing=!0;const i=t[e].element,n=t[e].newIndex+1+this.pagination.offset;try{await this.$api.pages.status(i.id,"listed",n),this.$store.dispatch("notification/success",":)"),this.$events.$emit("page.sort",i)}catch(s){this.$store.dispatch("notification/error",{message:s.message,details:s.details}),await this.reload()}finally{this.isProcessing=!1}}},update(){this.reload(),this.$events.$emit("model.update")}}},Ek=Sk,jk=(s("224d"),Object(c["a"])(Ek,xk,Ok,!1,null,null,null)),Tk=jk.exports,Lk=function(){var t=this,e=t.$createElement,s=t._self._c||e;return!1===t.isLoading?s("section",{staticClass:"k-files-section",attrs:{"data-processing":t.isProcessing}},[s("header",{staticClass:"k-section-header"},[s("k-headline",[t._v(" "+t._s(t.headline)+" "),t.options.min?s("abbr",{attrs:{title:t.$t("section.required")}},[t._v("*")]):t._e()]),t.add?s("k-button-group",[s("k-button",{attrs:{icon:"upload"},on:{click:t.upload}},[t._v(" "+t._s(t.$t("add"))+" ")])],1):t._e()],1),t.error?[s("k-box",{attrs:{theme:"negative"}},[s("k-text",{attrs:{size:"small"}},[s("strong",[t._v(t._s(t.$t("error.section.notLoaded",{name:t.name}))+":")]),t._v(" "+t._s(t.error)+" ")])],1)]:[s("k-dropzone",{attrs:{disabled:!1===t.add},on:{drop:t.drop}},[t.data.length?s("k-collection",{attrs:{help:t.help,items:t.data,layout:t.options.layout,pagination:t.pagination,sortable:!t.isProcessing&&t.options.sortable,size:t.options.size,"data-invalid":t.isInvalid},on:{sort:t.sort,paginate:t.paginate,action:t.action}}):[s("k-empty",{attrs:{layout:t.options.layout,"data-invalid":t.isInvalid,icon:"image"},on:{click:function(e){t.add&&t.upload()}}},[t._v(" "+t._s(t.options.empty||t.$t("files.empty"))+" ")]),s("footer",{staticClass:"k-collection-footer"},[t.help?s("k-text",{staticClass:"k-collection-help",attrs:{theme:"help"},domProps:{innerHTML:t._s(t.help)}}):t._e()],1)]],2),s("k-file-rename-dialog",{ref:"rename",on:{success:t.update}}),s("k-file-remove-dialog",{ref:"remove",on:{success:t.update}}),s("k-file-sort-dialog",{ref:"sort",on:{success:t.reload}}),s("k-upload",{ref:"upload",on:{success:t.uploaded,error:t.reload}})]],2):t._e()},Ik=[],Ak={mixins:[Ck],computed:{add(){return!(!this.$permissions.files.create||!1===this.options.upload)&&this.options.upload}},created(){this.load(),this.$events.$on("model.update",this.reload),this.$events.$on("file.sort",this.reload)},destroyed(){this.$events.$off("model.update",this.reload),this.$events.$off("file.sort",this.reload)},methods:{action(t,e){switch(e){case"edit":this.$go(t.link);break;case"download":window.open(t.url);break;case"rename":this.$refs.rename.open(t.parent,t.filename);break;case"replace":this.$refs.upload.open({url:O.api+"/"+this.$api.files.url(t.parent,t.filename),accept:"."+t.extension+","+t.mime,multiple:!1});break;case"remove":if(this.data.length<=this.options.min){const t=this.options.min>1?"plural":"singular";this.$store.dispatch("notification/error",{message:this.$t("error.section.files.min."+t,{section:this.options.headline||this.name,min:this.options.min})});break}this.$refs.remove.open(t.parent,t.filename);break;case"sort":this.$refs.sort.open(t.parent,t,this.options.apiUrl);break}},drop(t){if(!1===this.add)return!1;this.$refs.upload.drop(t,{...this.add,url:O.api+"/"+this.add.api})},items(t){return t.map(t=>(t.sortable=this.options.sortable,t.column=this.column,t.options=async e=>{try{const s=await this.$api.files.options(t.parent,t.filename,"list",this.options.sortable);e(s)}catch(s){this.$store.dispatch("notification/error",s)}},t))},replace(t){this.$refs.upload.open({url:O.api+"/"+this.$api.files.url(t.parent,t.filename),accept:t.mime,multiple:!1})},async sort(t){if(!1===this.options.sortable)return!1;this.isProcessing=!0,t=t.map(t=>t.id);try{await this.$api.patch(this.options.apiUrl+"/files/sort",{files:t,index:this.pagination.offset}),this.$store.dispatch("notification/success",":)"),this.$events.$emit("file.sort")}catch(e){this.reload(),this.$store.dispatch("notification/error",e.message)}finally{this.isProcessing=!1}},update(){this.$events.$emit("model.update")},upload(){if(!1===this.add)return!1;this.$refs.upload.open({...this.add,url:O.api+"/"+this.add.api})},uploaded(){this.$events.$emit("file.create"),this.$events.$emit("model.update"),this.$store.dispatch("notification/success",":)")}}},Bk=Ak,Mk=(s("b7a8"),Object(c["a"])(Bk,Lk,Ik,!1,null,null,null)),Dk=Mk.exports,Nk=function(){var t=this,e=t.$createElement,s=t._self._c||e;return t.isLoading?t._e():s("section",{staticClass:"k-fields-section"},[t.issue?[s("k-headline",{staticClass:"k-fields-issue-headline"},[t._v(" Error ")]),s("k-box",{attrs:{text:t.issue.message,html:!1,theme:"negative"}})]:t._e(),s("k-form",{attrs:{fields:t.fields,validate:!0,value:t.values,disabled:null!==t.$store.state.content.status.lock},on:{input:t.input,submit:t.onSubmit}})],2)},Pk=[],Rk={mixins:[vk],inheritAttrs:!1,data(){return{fields:{},isLoading:!0,issue:null}},computed:{language(){return this.$store.state.languages.current},values(){return this.$store.getters["content/values"]()}},watch:{language(){this.fetch()}},created(){this.input=V(this.input,50),this.fetch()},methods:{input(t,e,s){this.$store.dispatch("content/update",[s,t[s]])},async fetch(){try{const t=await this.load();this.fields=t.fields,Object.keys(this.fields).forEach(t=>{this.fields[t].section=this.name,this.fields[t].endpoints={field:this.parent+"/fields/"+t,section:this.parent+"/sections/"+this.name,model:this.parent}})}catch(t){this.issue=t}finally{this.isLoading=!1}},onSubmit(t){this.$events.$emit("keydown.cmd.s",t)}}},qk=Rk,Fk=(s("7d5d"),Object(c["a"])(qk,Nk,Pk,!1,null,null,null)),Uk=Fk.exports,Hk=function(){var t=this,e=t.$createElement,s=t._self._c||e;return s("k-error-view",{staticClass:"k-browser-view"},[s("p",[t._v(" We are really sorry, but your browser does not support all features required for the Kirby Panel. ")]),!1===t.hasFetchSupport?[s("p",[s("strong",[t._v("Fetch")]),s("br"),t._v(" We use Javascript's new Fetch API. You can find a list of supported browsers for this feature on "),s("strong",[s("a",{attrs:{href:"https://caniuse.com/#feat=fetch"}},[t._v("caniuse.com")])])])]:t._e(),!1===t.hasGridSupport?[s("p",[s("strong",[t._v("CSS Grid")]),s("br"),t._v(" We use CSS Grids for all our layouts. You can find a list of supported browsers for this feature on "),s("strong",[s("a",{attrs:{href:"https://caniuse.com/#feat=css-grid"}},[t._v("caniuse.com")])])])]:t._e()],2)},zk=[],Kk={grid(){return!(!window.CSS||!window.CSS.supports("display","grid"))},fetch(){return void 0!==window.fetch},all(){return this.fetch()&&this.grid()}},Gk={computed:{hasFetchSupport(){return Kk.fetch()},hasGridSupport(){return Kk.grid()}},created(){this.$store.dispatch("content/current",null),Kk.all()&&this.$go("/")}},Wk=Gk,Yk=(s("d6fc"),Object(c["a"])(Wk,Hk,zk,!1,null,null,null)),Vk=Yk.exports,Jk=function(){var t=this,e=t.$createElement,s=t._self._c||e;return s("k-error-boundary",{key:t.plugin,scopedSlots:t._u([{key:"error",fn:function(e){var i=e.error;return s("k-error-view",{},[t._v(" "+t._s(i.message||i)+" ")])}}])},[s("k-"+t.plugin+"-plugin-view",t._b({tag:"component"},"component",t.$props,!1))],1)},Zk=[],Xk={props:{plugin:String,hash:String},beforeRouteEnter(t,e,s){s(t=>{t.$store.dispatch("breadcrumb",[]),t.$store.dispatch("content/current",null)})}},Qk=Xk,tv=Object(c["a"])(Qk,Jk,Zk,!1,null,null,null),ev=tv.exports,sv=function(){var t=this,e=t.$createElement,s=t._self._c||e;return s("k-view",{staticClass:"k-error-view"},[s("div",{staticClass:"k-error-view-content"},[s("k-text",[s("p",[s("k-icon",{staticClass:"k-error-view-icon",attrs:{type:"alert"}})],1),s("p",[t._t("default")],2)])],1)])},iv=[],nv=(s("d221"),{}),av=Object(c["a"])(nv,sv,iv,!1,null,null,null),rv=av.exports,ov=function(){var t=this,e=t.$createElement,s=t._self._c||e;return t.issue?s("k-error-view",[t._v(" "+t._s(t.issue.message)+" ")]):null!==t.file.id?s("div",{staticClass:"k-file-view"},[s("k-file-preview",{attrs:{file:t.file}}),s("k-view",{staticClass:"k-file-content",attrs:{"data-locked":t.isLocked}},[s("k-header",{attrs:{editable:t.permissions.changeName&&!t.isLocked,tab:t.tab,tabs:t.tabs},on:{edit:function(e){return t.action("rename")}}},[t._v(" "+t._s(t.file.filename)+" "),s("k-button-group",{attrs:{slot:"left"},slot:"left"},[s("k-button",{attrs:{link:t.file.url,responsive:!0,icon:"open",target:"_blank"}},[t._v(" "+t._s(t.$t("open"))+" ")]),s("k-dropdown",[s("k-button",{attrs:{responsive:!0,disabled:t.isLocked,icon:"cog"},on:{click:function(e){return t.$refs.settings.toggle()}}},[t._v(" "+t._s(t.$t("settings"))+" ")]),s("k-dropdown-content",{ref:"settings",attrs:{options:t.options},on:{action:t.action}})],1),s("k-languages-dropdown")],1),t.file.id?s("k-prev-next",{attrs:{slot:"right",prev:t.prev,next:t.next},slot:"right"}):t._e()],1),t.file.id?s("k-sections",{attrs:{blueprint:t.file.blueprint.name,empty:t.$t("file.blueprint",{template:t.$esc(t.file.blueprint.name)}),parent:t.parent,tab:t.tab,tabs:t.tabs}}):t._e(),s("k-file-rename-dialog",{ref:"rename",on:{success:t.renamed}}),s("k-file-remove-dialog",{ref:"remove",on:{success:t.deleted}}),s("k-upload",{ref:"upload",attrs:{url:t.uploadApi,accept:t.file.mime,multiple:!1},on:{success:t.uploaded}})],1)],1):t._e()},lv=[],cv={computed:{isLocked(){return null!==this.$store.state.content.status.lock}},created(){this.fetch(),this.$events.$on("model.reload",this.fetch),this.$events.$on("keydown.left",this.toPrev),this.$events.$on("keydown.right",this.toNext)},destroyed(){this.$events.$off("model.reload",this.fetch),this.$events.$off("keydown.left",this.toPrev),this.$events.$off("keydown.right",this.toNext)},methods:{toPrev(t){this.prev&&"body"===t.target.localName&&this.$router.push(this.prev.link)},toNext(t){this.next&&"body"===t.target.localName&&this.$router.push(this.next.link)}}},uv={mixins:[cv],props:{path:{type:String},filename:{type:String,required:!0},tab:{type:String,required:!0}},data(){return{file:{id:null,parent:null,filename:"",url:"",prev:null,next:null,panelIcon:null,panelImage:null,mime:null,content:{}},parent:null,permissions:{changeName:!1,delete:!1},issue:null,tabs:[],options:null}},computed:{uploadApi(){return O.api+"/"+this.path+"/files/"+this.filename},prev(){return this.file.prev?{link:this.$api.files.link(this.path,this.file.prev.filename),tooltip:this.file.prev.filename}:null},language(){return this.$store.state.languages.current},next(){return this.file.next?{link:this.$api.files.link(this.path,this.file.next.filename),tooltip:this.file.next.filename}:null}},watch:{language(){this.fetch()},filename(){this.fetch()}},methods:{async fetch(){try{const t=await this.$api.files.get(this.path,this.filename,{view:"panel"});this.file={...t,next:t.nextWithTemplate,prev:t.prevWithTemplate,url:t.url},this.parent=this.$api.files.url(this.path,t.filename),this.tabs=t.blueprint.tabs,this.permissions=t.options,this.options=async t=>{const e=await this.$api.files.options(this.path,this.file.filename);t(e)},this.$store.dispatch("breadcrumb",this.$api.files.breadcrumb(this.file,this.$route.name)),this.$store.dispatch("title",this.filename),this.$store.dispatch("content/create",{id:"files/"+t.id,api:this.$api.files.link(this.path,this.filename),content:t.content})}catch(t){window.console.error(t),this.issue=t}},action(t){switch(t){case"rename":this.$refs.rename.open(this.path,this.file.filename);break;case"replace":this.$refs.upload.open({url:O.api+"/"+this.$api.files.url(this.path,this.file.filename),accept:"."+this.file.extension+","+this.file.mime});break;case"remove":this.$refs.remove.open(this.path,this.file.filename);break}},deleted(){this.path?this.$go("/"+this.path):this.$go("/site")},renamed(t){this.$go(this.$api.files.link(this.path,t.filename))},uploaded(){this.fetch(),this.$store.dispatch("notification/success",":)")}}},dv=uv,pv=Object(c["a"])(dv,ov,lv,!1,null,null,null),hv=pv.exports,mv=function(){var t=this,e=t.$createElement,s=t._self._c||e;return t.system?s("k-view",{staticClass:"k-installation-view",attrs:{align:"center"}},["install"===t.state?s("form",{on:{submit:function(e){return e.preventDefault(),t.install(e)}}},[s("h1",{staticClass:"k-offscreen"},[t._v(" "+t._s(t.$t("installation"))+" ")]),s("k-fieldset",{attrs:{fields:t.fields,novalidate:!0},model:{value:t.user,callback:function(e){t.user=e},expression:"user"}}),s("k-button",{attrs:{type:"submit",icon:"check"}},[t._v(" "+t._s(t.$t("install"))+" ")])],1):"completed"===t.state?s("k-text",[s("k-headline",[t._v(t._s(t.$t("installation.completed")))]),s("k-link",{attrs:{to:"/login"}},[t._v(" "+t._s(t.$t("login"))+" ")])],1):s("div",[t.system.isInstalled?t._e():s("k-headline",[t._v(" "+t._s(t.$t("installation.issues.headline"))+" ")]),s("ul",{staticClass:"k-installation-issues"},[!1===t.system.isInstallable?s("li",[s("k-icon",{attrs:{type:"alert"}}),s("span",{domProps:{innerHTML:t._s(t.$t("installation.disabled"))}})],1):t._e(),!1===t.requirements.php?s("li",[s("k-icon",{attrs:{type:"alert"}}),s("span",{domProps:{innerHTML:t._s(t.$t("installation.issues.php"))}})],1):t._e(),!1===t.requirements.server?s("li",[s("k-icon",{attrs:{type:"alert"}}),s("span",{domProps:{innerHTML:t._s(t.$t("installation.issues.server"))}})],1):t._e(),!1===t.requirements.mbstring?s("li",[s("k-icon",{attrs:{type:"alert"}}),s("span",{domProps:{innerHTML:t._s(t.$t("installation.issues.mbstring"))}})],1):t._e(),!1===t.requirements.curl?s("li",[s("k-icon",{attrs:{type:"alert"}}),s("span",{domProps:{innerHTML:t._s(t.$t("installation.issues.curl"))}})],1):t._e(),!1===t.requirements.accounts?s("li",[s("k-icon",{attrs:{type:"alert"}}),s("span",{domProps:{innerHTML:t._s(t.$t("installation.issues.accounts"))}})],1):t._e(),!1===t.requirements.content?s("li",[s("k-icon",{attrs:{type:"alert"}}),s("span",{domProps:{innerHTML:t._s(t.$t("installation.issues.content"))}})],1):t._e(),!1===t.requirements.media?s("li",[s("k-icon",{attrs:{type:"alert"}}),s("span",{domProps:{innerHTML:t._s(t.$t("installation.issues.media"))}})],1):t._e(),!1===t.requirements.sessions?s("li",[s("k-icon",{attrs:{type:"alert"}}),s("span",{domProps:{innerHTML:t._s(t.$t("installation.issues.sessions"))}})],1):t._e()]),s("k-button",{attrs:{icon:"refresh"},on:{click:t.check}},[t._v(" "+t._s(t.$t("retry"))+" ")])],1)],1):t._e()},fv=[],gv={data(){return{user:{name:"",email:"",language:"",password:"",role:"admin"},languages:[],system:null}},computed:{state(){return this.system.isOk&&this.system.isInstallable&&!this.system.isInstalled?"install":this.system.isOk&&this.system.isInstallable&&this.system.isInstalled?"completed":null},translation(){return this.$store.state.translation.current},requirements(){return this.system&&this.system.requirements?this.system.requirements:{}},fields(){return{email:{label:this.$t("email"),type:"email",link:!1,required:!0},password:{label:this.$t("password"),type:"password",placeholder:this.$t("password")+" …",required:!0},language:{label:this.$t("language"),type:"select",options:this.languages,icon:"globe",empty:!1,required:!0}}}},watch:{translation:{handler(t){this.user.language=t},immediate:!0},"user.language"(t){this.$store.dispatch("translation/activate",t)}},created(){this.$store.dispatch("content/current",null),this.check()},methods:{install(){this.$api.system.install(this.user).then(t=>{this.$store.dispatch("user/current",t),this.$store.dispatch("notification/success",this.$t("welcome")+"!"),this.$go("/")}).catch(t=>{this.$store.dispatch("notification/error",t)})},check(){this.$store.dispatch("system/load",!0).then(t=>{!0===t.isInstalled&&t.isReady?this.$go("/login"):this.$api.translations.options().then(e=>{this.languages=e,this.system=t,this.$store.dispatch("title",this.$t("view.installation"))})})}}},bv=gv,kv=(s("146c"),Object(c["a"])(bv,mv,fv,!1,null,null,null)),vv=kv.exports,$v=function(){var t=this,e=t.$createElement,s=t._self._c||e;return t.issue?s("k-error-view",[t._v(" "+t._s(t.issue.message)+" ")]):t.ready&&"login"===t.form?s("k-view",{staticClass:"k-login-view",attrs:{align:"center"}},[s("k-login-plugin")],1):t.ready&&"code"===t.form?s("k-view",{staticClass:"k-login-code-view",attrs:{align:"center"}},[s("k-login-code")],1):t._e()},yv=[],_v={components:{"k-login-plugin":window.panel.plugins.login||An},data(){return{ready:!1,issue:null}},computed:{form(){return this.$store.state.user.pendingEmail?"code":this.$store.state.user.current?null:"login"}},created(){this.$store.dispatch("content/current",null),this.$store.dispatch("system/load").then(t=>{t.isReady||this.$go("/installation"),t.user&&t.user.id&&this.$go("/"),"pending"===t.authStatus.status&&this.$store.dispatch("user/pending",t.authStatus),this.ready=!0,this.$store.dispatch("title",this.$t("login"))}).catch(t=>{this.issue=t})}},wv=_v,xv=(s("24c1"),Object(c["a"])(wv,$v,yv,!1,null,null,null)),Ov=xv.exports,Cv=function(){var t=this,e=t.$createElement,s=t._self._c||e;return t.issue?s("k-error-view",[t._v(" "+t._s(t.issue.message)+" ")]):s("k-view",{staticClass:"k-page-view",attrs:{"data-locked":t.isLocked}},[s("k-header",{attrs:{editable:t.permissions.changeTitle&&!t.isLocked,tab:t.tab,tabs:t.tabs},on:{edit:function(e){return t.action("rename")}}},[t._v(" "+t._s(t.page.title)+" "),s("k-button-group",{attrs:{slot:"left"},slot:"left"},[t.permissions.preview&&t.page.previewUrl?s("k-button",{attrs:{responsive:!0,link:t.page.previewUrl,target:"_blank",icon:"open"}},[t._v(" "+t._s(t.$t("open"))+" ")]):t._e(),t.status?s("k-status-icon",{attrs:{status:t.page.status,disabled:!t.permissions.changeStatus||t.isLocked,responsive:!0,text:t.status.label},on:{click:function(e){return t.action("status")}}}):t._e(),s("k-dropdown",[s("k-button",{attrs:{responsive:!0,disabled:!0===t.isLocked,icon:"cog"},on:{click:function(e){return t.$refs.settings.toggle()}}},[t._v(" "+t._s(t.$t("settings"))+" ")]),s("k-dropdown-content",{ref:"settings",attrs:{options:t.options},on:{action:t.action}})],1),s("k-languages-dropdown")],1),t.page.id?s("k-prev-next",{attrs:{slot:"right",prev:t.prev,next:t.next},slot:"right"}):t._e()],1),t.page.id?s("k-sections",{attrs:{blueprint:t.blueprint,empty:t.$t("page.blueprint",{template:t.$esc(t.blueprint)}),parent:t.$api.pages.url(t.page.id),tab:t.tab,tabs:t.tabs}}):t._e(),s("k-page-rename-dialog",{ref:"rename",on:{success:t.update}}),s("k-page-duplicate-dialog",{ref:"duplicate"}),s("k-page-status-dialog",{ref:"status",on:{success:t.update}}),s("k-page-template-dialog",{ref:"template",on:{success:t.update}}),s("k-page-remove-dialog",{ref:"remove"})],1)},Sv=[],Ev={mixins:[cv],props:{path:{type:String,required:!0},tab:{type:String}},data(){return{page:{title:"",id:null,prev:null,next:null,status:null},blueprint:null,preview:!0,permissions:{changeTitle:!1,changeStatus:!1},icon:"page",issue:null,tabs:[],options:null}},computed:{language(){return this.$store.state.languages.current},next(){return this.page.next?{link:this.$api.pages.link(this.page.next.id),tooltip:this.page.next.title}:null},prev(){return this.page.prev?{link:this.$api.pages.link(this.page.prev.id),tooltip:this.page.prev.title}:null},status(){return null!==this.page.status?this.page.blueprint.status[this.page.status]:null}},watch:{language(){this.fetch()},path(){this.fetch()}},created(){this.$events.$on("page.changeSlug",this.update)},destroyed(){this.$events.$off("page.changeSlug",this.update)},methods:{action(t){switch(t){case"duplicate":this.$refs.duplicate.open(this.page.id);break;case"rename":this.$refs.rename.open(this.page.id,this.permissions,"title");break;case"url":this.$refs.rename.open(this.page.id,this.permissions,"slug");break;case"status":this.$refs.status.open(this.page.id);break;case"template":this.$refs.template.open(this.page.id);break;case"remove":this.$refs.remove.open(this.page.id);break;default:this.$store.dispatch("notification/error",this.$t("notification.notImplemented"));break}},async fetch(){try{this.page=await this.$api.pages.get(this.path,{view:"panel"}),this.blueprint=this.page.blueprint.name,this.permissions=this.page.options,this.tabs=this.page.blueprint.tabs,this.options=async t=>{const e=await this.$api.pages.options(this.page.id);t(e)},this.$store.dispatch("breadcrumb",this.$api.pages.breadcrumb(this.page)),this.$store.dispatch("title",this.page.title),this.$store.dispatch("content/create",{id:"pages/"+this.page.id,api:this.$api.pages.link(this.page.id),content:this.page.content})}catch(t){this.issue=t}},update(){this.fetch(),this.$emit("model.update")}}},jv=Ev,Tv=Object(c["a"])(jv,Cv,Sv,!1,null,null,null),Lv=Tv.exports,Iv=function(){var t=this,e=t.$createElement,s=t._self._c||e;return s("k-view",{staticClass:"k-password-reset-view",attrs:{align:"center"}},[s("k-form",{attrs:{fields:t.fields,"submit-button":t.$t("change")},on:{submit:t.submit},model:{value:t.values,callback:function(e){t.values=e},expression:"values"}},[s("template",{slot:"header"},[s("h1",{staticClass:"k-offscreen"},[t._v(" "+t._s(t.$t("view.resetPassword"))+" ")]),t.issue?s("div",{staticClass:"k-login-alert",on:{click:function(e){t.issue=null}}},[s("span",[t._v(t._s(t.issue))]),s("k-icon",{attrs:{type:"alert"}})],1):t._e(),s("k-user-info",{attrs:{user:t.$user}})],1),s("div",{staticClass:"k-login-buttons",attrs:{slot:"footer"},slot:"footer"},[s("k-button",{staticClass:"k-login-button",attrs:{icon:"check",type:"submit"}},[t._v(" "+t._s(t.$t("change"))+" "),t.isLoading?[t._v(" … ")]:t._e()],2)],1)],2)],1)},Av=[],Bv={data(){return{isLoading:!1,issue:"",values:{password:null,passwordConfirmation:null}}},computed:{fields(){return{password:{autofocus:!0,label:this.$t("user.changePassword.new"),icon:"key",type:"password"},passwordConfirmation:{label:this.$t("user.changePassword.new.confirm"),icon:"key",type:"password"}}}},mounted(){this.$store.dispatch("title",this.$t("view.resetPassword"))},methods:{async submit(){if(!this.values.password||this.values.password.length<8)return this.issue=this.$t("error.user.password.invalid"),!1;if(this.values.password!==this.values.passwordConfirmation)return this.issue=this.$t("error.user.password.notSame"),!1;this.isLoading=!0;try{await this.$api.users.changePassword(this.$user.id,this.values.password),this.$store.dispatch("notification/success",":)"),this.$go("/")}catch(t){this.issue=t.message}finally{this.isLoading=!1}}}},Mv=Bv,Dv=(s("310e"),Object(c["a"])(Mv,Iv,Av,!1,null,null,null)),Nv=Dv.exports,Pv=function(){var t=this,e=t.$createElement,s=t._self._c||e;return s("k-view",{staticClass:"k-settings-view"},[s("k-header",[t._v(" "+t._s(t.$t("view.settings"))+" ")]),s("section",{staticClass:"k-system-info"},[s("header",[s("k-headline",[t._v("Kirby")])],1),s("ul",{staticClass:"k-system-info-box"},[s("li",[s("dl",[s("dt",[t._v(t._s(t.$t("license")))]),s("dd",[t.license?[t._v(" "+t._s(t.license)+" ")]:s("p",[s("strong",{staticClass:"k-system-unregistered"},[t._v(t._s(t.$t("license.unregistered")))])])],2)])]),s("li",[s("dl",[s("dt",[t._v(t._s(t.$t("version")))]),s("dd",[t._v(t._s(t.$store.state.system.info.version))])])])])]),t.multilang?s("section",{staticClass:"k-languages"},[t.languages.length>0?[s("section",{staticClass:"k-languages-section"},[s("header",[s("k-headline",[t._v(t._s(t.$t("languages.default")))])],1),s("k-collection",{attrs:{items:t.defaultLanguage},on:{action:t.action}})],1),s("section",{staticClass:"k-languages-section"},[s("header",[s("k-headline",[t._v(t._s(t.$t("languages.secondary")))]),s("k-button",{attrs:{icon:"add"},on:{click:function(e){return t.$refs.create.open()}}},[t._v(" "+t._s(t.$t("language.create"))+" ")])],1),t.translations.length?s("k-collection",{attrs:{items:t.translations},on:{action:t.action}}):s("k-empty",{attrs:{icon:"globe"},on:{click:function(e){return t.$refs.create.open()}}},[t._v(" "+t._s(t.$t("languages.secondary.empty"))+" ")])],1)]:0===t.languages.length?[s("header",[s("k-headline",[t._v(t._s(t.$t("languages")))]),s("k-button",{attrs:{icon:"add"},on:{click:function(e){return t.$refs.create.open()}}},[t._v(" "+t._s(t.$t("language.create"))+" ")])],1),s("k-empty",{attrs:{icon:"globe"},on:{click:function(e){return t.$refs.create.open()}}},[t._v(" "+t._s(t.$t("languages.empty"))+" ")])]:t._e(),s("k-language-create-dialog",{ref:"create",on:{success:t.fetch}}),s("k-language-update-dialog",{ref:"update",on:{success:t.fetch}}),s("k-language-remove-dialog",{ref:"remove",on:{success:t.fetch}})],2):t._e()],1)},Rv=[],qv={data(){return{languages:[]}},computed:{defaultLanguage(){return this.languages.filter(t=>t.default)},multilang(){return this.$store.state.system.info.multilang},license(){return this.$store.state.system.info.license},translations(){return this.languages.filter(t=>!1===t.default)}},created(){this.$store.dispatch("content/current",null),this.$store.dispatch("title",this.$t("view.settings")),this.$store.dispatch("breadcrumb",[]),this.fetch()},methods:{fetch(){!0===this.multilang?this.$api.get("languages").then(t=>{this.languages=t.data.map(e=>({id:e.code,default:e.default,icon:{type:"globe",back:"black"},image:!0,text:this.$esc(e.name),info:this.$esc(e.code),link:()=>{this.$refs.update.open(e.code)},options:[{icon:"edit",text:this.$t("edit"),click:"update"},{icon:"trash",text:this.$t("delete"),disabled:e.default&&1!==t.data.length,click:"remove"}]}))}):this.languages=[]},action(t,e){switch(e){case"update":this.$refs.update.open(t.id);break;case"remove":this.$refs.remove.open(t.id);break}}}},Fv=qv,Uv=(s("9bd5"),Object(c["a"])(Fv,Pv,Rv,!1,null,null,null)),Hv=Uv.exports,zv=function(){var t=this,e=t.$createElement,s=t._self._c||e;return t.issue?s("k-error-view",[t._v(" "+t._s(t.issue.message)+" ")]):s("k-view",{key:"site-view",staticClass:"k-site-view",attrs:{"data-locked":t.isLocked}},[s("k-header",{attrs:{editable:t.permissions.changeTitle&&!t.isLocked,tab:t.tab,tabs:t.tabs},on:{edit:function(e){return t.action("rename")}}},[t._v(" "+t._s(t.site.title)+" "),s("k-button-group",{attrs:{slot:"left"},slot:"left"},[s("k-button",{attrs:{responsive:!0,link:t.site.previewUrl,target:"_blank",icon:"open"}},[t._v(" "+t._s(t.$t("open"))+" ")]),s("k-languages-dropdown")],1)],1),t.site.url?s("k-sections",{attrs:{blueprint:t.site.blueprint.name,empty:t.$t("site.blueprint"),tab:t.tab,tabs:t.tabs,parent:"site"},on:{submit:function(e){return t.$emit("submit",e)}}}):t._e(),s("k-site-rename-dialog",{ref:"rename",on:{success:t.fetch}})],1)},Kv=[],Gv={props:{tab:String},data(){return{site:{title:null,url:null},issue:null,tabs:[],options:null,permissions:{changeTitle:!0}}},computed:{isLocked(){return null!==this.$store.state.content.status.lock},language(){return this.$store.state.languages.current}},watch:{language(){this.fetch()}},created(){this.fetch()},methods:{fetch(){this.$api.site.get({view:"panel"}).then(t=>{this.site=t,this.tabs=t.blueprint.tabs,this.permissions=t.options,this.options=t=>{this.$api.site.options().then(e=>{t(e)})},this.$store.dispatch("breadcrumb",[]),this.$store.dispatch("title",null),this.$store.dispatch("content/create",{id:"site",api:"site",content:t.content})}).catch(t=>{this.issue=t})},action(t){switch(t){case"languages":this.$refs.languages.open();break;case"rename":this.$refs.rename.open();break;default:this.$store.dispatch("notification/error",this.$t("notification.notImplemented"));break}}}},Wv=Gv,Yv=Object(c["a"])(Wv,zv,Kv,!1,null,null,null),Vv=Yv.exports,Jv=function(){var t=this,e=t.$createElement,s=t._self._c||e;return t.issue?s("k-error-view",[t._v(" "+t._s(t.issue.message)+" ")]):s("k-view",{staticClass:"k-users-view"},[s("k-header",[t._v(" "+t._s(t.$t("view.users"))+" "),s("k-button-group",{attrs:{slot:"left"},slot:"left"},[s("k-button",{attrs:{disabled:!1===t.$permissions.users.create,icon:"add"},on:{click:function(e){return t.$refs.create.open()}}},[t._v(" "+t._s(t.$t("user.create"))+" ")])],1),s("k-button-group",{attrs:{slot:"right"},slot:"right"},[s("k-dropdown",[s("k-button",{attrs:{responsive:!0,icon:"funnel"},on:{click:function(e){return t.$refs.roles.toggle()}}},[t._v(" "+t._s(t.$t("role"))+": "+t._s(t.role?t.role.text:t.$t("role.all"))+" ")]),s("k-dropdown-content",{ref:"roles",attrs:{align:"right"}},[s("k-dropdown-item",{attrs:{icon:"bolt"},on:{click:function(e){return t.filter(!1)}}},[t._v(" "+t._s(t.$t("role.all"))+" ")]),s("hr"),t._l(t.roles,(function(e){return s("k-dropdown-item",{key:e.value,attrs:{icon:"bolt"},on:{click:function(s){return t.filter(e)}}},[t._v(" "+t._s(e.text)+" ")])}))],2)],1)],1)],1),t.users.length>0?[s("k-collection",{attrs:{items:t.users,pagination:t.pagination},on:{paginate:t.paginate,action:t.action}})]:0===t.total?[s("k-empty",{attrs:{icon:"users"}},[t._v(" "+t._s(t.$t("role.empty"))+" ")])]:t._e(),s("k-user-create-dialog",{ref:"create",on:{success:t.fetch}}),s("k-user-email-dialog",{ref:"email",on:{success:t.fetch}}),s("k-user-language-dialog",{ref:"language",on:{success:t.fetch}}),s("k-user-password-dialog",{ref:"password"}),s("k-user-remove-dialog",{ref:"remove",on:{success:t.fetch}}),s("k-user-rename-dialog",{ref:"rename",on:{success:t.fetch}}),s("k-user-role-dialog",{ref:"role",on:{success:t.fetch}})],2)},Zv=[],Xv={data(){return{page:1,limit:20,total:null,users:[],roles:[],issue:null}},computed:{pagination(){return{page:this.page,limit:this.limit,total:this.total}},role(){let t=null;return this.$route.params.role&&this.roles.forEach(e=>{e.value===this.$route.params.role&&(t=e)}),t}},watch:{$route(){this.fetch()}},created(){this.$store.dispatch("content/current",null),this.$api.roles.options().then(t=>{this.roles=t,this.fetch()})},methods:{fetch(){this.$store.dispatch("title",this.$t("view.users"));let t={paginate:{page:this.page,limit:this.limit},sortBy:"username asc"};this.role&&(t.filterBy=[{field:"role",operator:"==",value:this.role.value}]),this.$api.users.list(t).then(t=>{this.users=t.data.map(t=>{let e={id:t.id,icon:{type:"user",back:"black"},text:this.$esc(t.name||t.email),info:this.$esc(t.role.title),link:"/users/"+t.id,options:e=>{this.$api.users.options(t.id,"list").then(t=>e(t)).catch(t=>{this.$store.dispatch("notification/error",t)})},image:!0};return t.avatar&&(e.image={url:t.avatar.url,cover:!0}),e}),this.role?this.$store.dispatch("breadcrumb",[{link:"/users/role/"+this.role.value,label:this.$t("role")+": "+this.role.text}]):this.$store.dispatch("breadcrumb",[]),this.total=t.pagination.total}).catch(t=>{this.issue=t})},paginate(t){this.page=t.page,this.limit=t.limit,this.fetch()},action(t,e){switch(e){case"edit":this.$go("/users/"+t.id);break;case"email":this.$refs.email.open(t.id);break;case"role":this.$refs.role.open(t.id);break;case"rename":this.$refs.rename.open(t.id);break;case"password":this.$refs.password.open(t.id);break;case"language":this.$refs.language.open(t.id);break;case"remove":this.$refs.remove.open(t.id);break}},filter(t){!1===t?this.$go("/users"):this.$go("/users/role/"+t.value),this.$refs.roles.close()}}},Qv=Xv,t$=Object(c["a"])(Qv,Jv,Zv,!1,null,null,null),e$=t$.exports,s$=function(){var t=this,e=t.$createElement,s=t._self._c||e;return t.issue?s("k-error-view",[t._v(" "+t._s(t.issue.message)+" ")]):t.ready?s("div",{staticClass:"k-user-view",attrs:{"data-locked":t.isLocked}},[s("div",{staticClass:"k-user-profile"},[s("k-view",[t.avatar?[s("k-dropdown",[s("k-button",{staticClass:"k-user-view-image",attrs:{tooltip:t.$t("avatar"),disabled:t.isLocked},on:{click:function(e){return t.$refs.picture.toggle()}}},[t.avatar?s("k-image",{attrs:{cover:!0,src:t.avatar,ratio:"1/1"}}):t._e()],1),s("k-dropdown-content",{ref:"picture"},[s("k-dropdown-item",{attrs:{icon:"upload"},on:{click:function(e){return t.$refs.upload.open()}}},[t._v(" "+t._s(t.$t("change"))+" ")]),s("k-dropdown-item",{attrs:{icon:"trash"},on:{click:function(e){return t.action("picture.delete")}}},[t._v(" "+t._s(t.$t("delete"))+" ")])],1)],1)]:[s("k-button",{staticClass:"k-user-view-image",attrs:{tooltip:t.$t("avatar")},on:{click:function(e){return t.$refs.upload.open()}}},[s("k-icon",{attrs:{type:"user"}})],1)],s("k-button-group",[s("k-button",{attrs:{disabled:!t.permissions.changeEmail||t.isLocked,icon:"email"},on:{click:function(e){return t.action("email")}}},[t._v(" "+t._s(t.$t("email"))+": "+t._s(t.user.email)+" ")]),s("k-button",{attrs:{disabled:!t.permissions.changeRole||t.isLocked,icon:"bolt"},on:{click:function(e){return t.action("role")}}},[t._v(" "+t._s(t.$t("role"))+": "+t._s(t.user.role.title)+" ")]),s("k-button",{attrs:{disabled:!t.permissions.changeLanguage||t.isLocked,icon:"globe"},on:{click:function(e){return t.action("language")}}},[t._v(" "+t._s(t.$t("language"))+": "+t._s(t.user.language)+" ")])],1)],2)],1),s("k-view",[s("k-header",{attrs:{editable:t.permissions.changeName&&!t.isLocked,tab:t.tab,tabs:t.tabs},on:{edit:function(e){return t.action("rename")}}},[t.user.name&&0!==t.user.name.length?[t._v(" "+t._s(t.user.name)+" ")]:s("span",{staticClass:"k-user-name-placeholder"},[t._v(t._s(t.$t("name"))+" …")]),s("k-button-group",{attrs:{slot:"left"},slot:"left"},[s("k-dropdown",[s("k-button",{attrs:{disabled:t.isLocked,icon:"cog"},on:{click:function(e){return t.$refs.settings.toggle()}}},[t._v(" "+t._s(t.$t("settings"))+" ")]),s("k-dropdown-content",{ref:"settings",attrs:{options:t.options},on:{action:t.action}})],1),s("k-languages-dropdown")],1),t.user.id&&"User"===t.$route.name?s("k-prev-next",{attrs:{slot:"right",prev:t.prev,next:t.next},slot:"right"}):t._e()],2),t.user?s("k-sections",{attrs:{blueprint:t.user.blueprint.name,empty:t.$t("user.blueprint",{role:t.$esc(t.user.role.name)}),parent:"users/"+t.user.id,tab:t.tab,tabs:t.tabs}}):t._e(),s("k-user-email-dialog",{ref:"email",on:{success:t.fetch}}),s("k-user-language-dialog",{ref:"language",on:{success:t.fetch}}),s("k-user-password-dialog",{ref:"password"}),s("k-user-remove-dialog",{ref:"remove"}),s("k-user-rename-dialog",{ref:"rename",on:{success:t.fetch}}),s("k-user-role-dialog",{ref:"role",on:{success:t.fetch}}),s("k-upload",{ref:"upload",attrs:{url:t.uploadApi,multiple:!1,accept:"image/*"},on:{success:t.uploadedAvatar}})],1)],1):t._e()},i$=[],n$={mixins:[cv],props:{id:{type:[Boolean,String],required:!0},tab:String},data(){return{tabs:[],ready:!1,user:{role:{name:null},name:null,language:null,prev:null,next:null},permissions:{changeEmail:!0,changeName:!0,changeLanguage:!0,changeRole:!0},issue:null,avatar:null,options:null}},computed:{language(){return this.$store.state.languages.current},next(){return this.user.next?{link:this.$api.users.link(this.user.next.id),tooltip:this.user.next.name}:null},prev(){return this.user.prev?{link:this.$api.users.link(this.user.prev.id),tooltip:this.user.prev.name}:null},uploadApi(){return O.api+"/users/"+this.user.id+"/avatar"}},watch:{"$route.name":{handler(t){"Account"===t&&this.$store.dispatch("breadcrumb",[])},immediate:!0},language(){this.fetch()},id(){this.fetch()}},methods:{async action(t){switch(t){case"email":this.$refs.email.open(this.user.id);break;case"language":this.$refs.language.open(this.user.id);break;case"password":this.$refs.password.open(this.user.id);break;case"picture.delete":await this.$api.users.deleteAvatar(this.id),this.avatar=null,this.$store.dispatch("notification/success",":)");break;case"remove":this.$refs.remove.open(this.user.id);break;case"rename":this.$refs.rename.open(this.user.id);break;case"role":this.$refs.role.open(this.user.id);break;default:this.$store.dispatch("notification/error","Not yet implemented")}},async fetch(){if(this.id)try{this.user=await this.$api.users.get(this.id,{view:"panel"}),this.tabs=this.user.blueprint.tabs,this.ready=!0,this.permissions=this.user.options,this.options=async t=>{const e=await this.$api.users.options(this.user.id);t(e)},this.user.avatar?this.avatar=this.user.avatar.url:this.avatar=null,"User"===this.$route.name&&this.$store.dispatch("breadcrumb",this.$api.users.breadcrumb(this.user)),this.$store.dispatch("title",this.user.name||this.user.email),this.$store.dispatch("content/create",{id:"users/"+this.user.id,api:this.$api.users.link(this.user.id),content:this.user.content})}catch(t){window.console.error(t),this.issue=t}},uploadedAvatar(){this.$store.dispatch("notification/success",":)"),this.fetch()}}},a$=n$,r$=(s("bd96"),Object(c["a"])(a$,s$,i$,!1,null,null,null)),o$=r$.exports,l$=function(){var t=this,e=t.$createElement,s=t._self._c||e;return s("div",{ref:"container",staticClass:"k-block-container",class:"k-block-container-type-"+t.type,attrs:{"data-batched":t.isBatched,"data-disabled":t.fieldset.disabled,"data-hidden":t.isHidden,"data-last-in-batch":t.isLastInBatch,"data-selected":t.isSelected,"data-translate":t.fieldset.translate,tabindex:"0"},on:{keydown:[function(e){return!e.type.indexOf("key")&&t._k(e.keyCode,"down",40,e.key,["Down","ArrowDown"])?null:e.ctrlKey&&e.shiftKey?(e.preventDefault(),t.$emit("sortDown")):null},function(e){return!e.type.indexOf("key")&&t._k(e.keyCode,"up",38,e.key,["Up","ArrowUp"])?null:e.ctrlKey&&e.shiftKey?(e.preventDefault(),t.$emit("sortUp")):null}],focus:function(e){return t.$emit("focus")},focusin:function(e){return t.$emit("focus")}}},[s("div",{staticClass:"k-block",class:t.className},[s(t.customComponent,t._g(t._b({ref:"editor",tag:"component"},"component",t.$props,!1),t.listeners))],1),s("k-block-options",t._g({ref:"options",attrs:{"is-batched":t.isBatched,"is-full":t.isFull,"is-hidden":t.isHidden}},t.listeners)),t.isBatched?t._e():s("k-form-drawer",{ref:"drawer",staticClass:"k-block-drawer",attrs:{icon:t.fieldset.icon||"box",tabs:t.tabs,title:t.fieldset.name,value:t.content},on:{close:function(e){return t.focus()},input:function(e){return t.$emit("update",e)}},scopedSlots:t._u([{key:"options",fn:function(){return[t.isHidden?s("k-button",{staticClass:"k-drawer-option",attrs:{icon:"hidden"},on:{click:function(e){return t.$emit("show")}}}):t._e(),s("k-button",{staticClass:"k-drawer-option",attrs:{disabled:!t.prev,icon:"angle-left"},on:{click:function(e){return e.preventDefault(),e.stopPropagation(),t.goTo(t.prev)}}}),s("k-button",{staticClass:"k-drawer-option",attrs:{disabled:!t.next,icon:"angle-right"},on:{click:function(e){return e.preventDefault(),e.stopPropagation(),t.goTo(t.next)}}}),s("k-button",{staticClass:"k-drawer-option",attrs:{icon:"trash"},on:{click:function(e){return e.preventDefault(),e.stopPropagation(),t.confirmToRemove(e)}}})]},proxy:!0}],null,!1,2458022885)}),s("k-remove-dialog",{ref:"removeDialog",attrs:{text:t.$t("field.blocks.delete.confirm")},on:{submit:t.remove}})],1)},c$=[],u$={inheritAttrs:!1,props:{attrs:[Array,Object],content:[Array,Object],endpoints:Object,fieldset:Object,id:String,isBatched:Boolean,isFull:Boolean,isHidden:Boolean,isLastInBatch:Boolean,isSelected:Boolean,name:String,next:Object,prev:Object,type:String},data(){return{skipFocus:!1}},computed:{className(){let t=["k-block-type-"+this.type];return this.fieldset.preview&&this.fieldset.preview!==this.type&&t.push("k-block-type-"+this.fieldset.preview),!1===this.wysiwyg&&t.push("k-block-type-default"),t},customComponent(){return this.wysiwyg?this.wysiwygComponent:"k-block-type-default"},listeners(){return{...this.$listeners,confirmToRemove:this.confirmToRemove,open:this.open}},tabs(){let t=this.fieldset.tabs;return Object.entries(t).forEach(([e,s])=>{Object.entries(s.fields).forEach(([s])=>{t[e].fields[s].section=this.name,t[e].fields[s].endpoints={field:this.endpoints.field+"/fieldsets/"+this.type+"/fields/"+s,section:this.endpoints.section,model:this.endpoints.model}})}),t},wysiwyg(){return!1!==this.wysiwygComponent},wysiwygComponent(){if(!1===this.fieldset.preview)return!1;let t="k-block-type-"+this.type;return(this.$helper.isComponent(t)||!(!this.fieldset.preview||(t="k-block-type-"+this.fieldset.preview,!this.$helper.isComponent(t))))&&t}},methods:{close(){this.$refs.drawer.close()},confirmToRemove(){this.$refs.removeDialog.open()},focus(){!0!==this.skipFocus&&("function"===typeof this.$refs.editor.focus?this.$refs.editor.focus():this.$refs.container.focus())},goTo(t){t&&(this.skipFocus=!0,this.close(),this.$nextTick(()=>{t.$refs.container.focus(),t.open(),this.skipFocus=!1}))},open(){this.$refs.drawer.open()},remove(){this.$refs.removeDialog.close(),this.$emit("remove",this.id)}}},d$=u$,p$=(s("bbdf"),Object(c["a"])(d$,l$,c$,!1,null,null,null)),h$=p$.exports,m$=function(){var t=this,e=t.$createElement,s=t._self._c||e;return s("div",{staticClass:"k-blocks",attrs:{"data-empty":0===t.blocks.length,"data-alt":t.altKey}},[t.hasFieldsets?[s("k-draggable",t._b({staticClass:"k-blocks-list",on:{sort:t.save},scopedSlots:t._u([{key:"footer",fn:function(){return[s("k-empty",{staticClass:"k-blocks-empty",attrs:{icon:"box"},on:{click:function(e){return t.choose(t.blocks.length)}}},[t._v(" "+t._s(t.empty||t.$t("field.blocks.empty"))+" ")])]},proxy:!0}],null,!1,2413899928)},"k-draggable",t.draggableOptions,!1),t._l(t.blocks,(function(e,i){return s("k-block",t._b({key:e.id,ref:"block-"+e.id,refInFor:!0,attrs:{endpoints:t.endpoints,fieldset:t.fieldset(e),"is-batched":t.isBatched(e),"is-last-in-batch":t.isLastInBatch(e),"is-full":t.isFull,"is-hidden":!0===e.isHidden,"is-selected":t.isSelected(e),next:t.prevNext(i+1),prev:t.prevNext(i-1)},on:{append:function(e){return t.add(e,i+1)},blur:function(e){return t.select(null)},choose:function(e){return t.choose(e)},chooseToAppend:function(e){return t.choose(i+1)},chooseToConvert:function(s){return t.chooseToConvert(e)},chooseToPrepend:function(e){return t.choose(i)},confirmToRemoveSelected:t.confirmToRemoveSelected,duplicate:function(s){return t.duplicate(e,i)},focus:function(s){return t.select(e)},hide:function(s){return t.hide(e)},prepend:function(e){return t.add(e,i)},remove:function(s){return t.remove(e)},sortDown:function(s){return t.sort(e,i,i+1)},sortUp:function(s){return t.sort(e,i,i-1)},show:function(s){return t.show(e)},update:function(s){return t.update(e,s)}}},"k-block",e,!1))})),1),s("k-block-selector",{ref:"selector",attrs:{fieldsets:t.fieldsets,"fieldset-groups":t.fieldsetGroups},on:{add:t.add,convert:t.convert}}),s("k-remove-dialog",{ref:"removeAll",attrs:{text:t.$t("field.blocks.delete.confirm.all")},on:{submit:t.removeAll}}),s("k-remove-dialog",{ref:"removeSelected",attrs:{text:t.$t("field.blocks.delete.confirm.selected")},on:{submit:t.removeSelected}})]:[s("k-box",{attrs:{theme:"info"}},[t._v(" No fieldsets yet ")])]],2)},f$=[],g$={inheritAttrs:!1,props:{empty:String,endpoints:Object,fieldsets:Object,fieldsetGroups:Object,group:String,max:{type:Number,default:null},value:{type:Array,default(){return[]}}},data(){return{batch:[],blocks:this.value,altKey:!1}},computed:{draggableOptions(){return{id:this._uid,handle:".k-block-handle",list:this.blocks,move:this.move,delay:10,data:{fieldsets:this.fieldsets,isFull:this.isFull},options:{group:this.group}}},hasFieldsets(){return Object.keys(this.fieldsets).length},isEmpty(){return 0===this.blocks.length},isFull(){return null!==this.max&&this.blocks.length>=this.max},selected(){return this.$store.state.blocks.current}},watch:{value(){this.blocks=this.value}},created(){this.outsideFocus=t=>{const e=document.querySelector(".k-overlay:last-of-type");!1!==this.$el.contains(t.target)||e&&!1!==e.contains(t.target)||this.select(null)},document.addEventListener("focus",this.outsideFocus,!0),this.onAlt=t=>{t.altKey?this.altKey=!0:this.altKey=!1},document.addEventListener("keydown",this.onAlt,!0),document.addEventListener("keyup",this.onAlt,!0)},destroyed(){document.removeEventListener("focus",this.outsideFocus),document.removeEventListener("keydown",this.onAlt),document.removeEventListener("keyup",this.onAlt)},methods:{async add(t="text",e){const s=await this.$api.get(this.endpoints.field+"/fieldsets/"+t);this.blocks.splice(e,0,s),this.save(),this.$nextTick(()=>{this.focusOrOpen(s)})},addToBatch(t){null!==this.selected&&!1===this.batch.includes(this.selected)&&(this.batch.push(this.selected),this.$store.dispatch("blocks/current",null)),!1===this.batch.includes(t.id)&&this.batch.push(t.id)},choose(t){if(1===Object.keys(this.fieldsets).length){const e=Object.values(this.fieldsets)[0].type;this.add(e,t)}else this.$refs.selector.open(t)},chooseToConvert(t){this.$refs.selector.open(t,{disabled:[t.type],headline:this.$t("field.blocks.changeType"),event:"convert"})},click(t){this.$emit("click",t)},confirmToRemoveAll(){this.$refs.removeAll.open()},confirmToRemoveSelected(){this.$refs.removeSelected.open()},async convert(t,e){const s=this.blocks.findIndex(t=>t.id===e.id);if(-1===s)return!1;const i=t=>{let e={};return Object.values(t.tabs).forEach(t=>{e={...e,...t.fields}}),e},n=this.blocks[s],a=await this.$api.get(this.endpoints.field+"/fieldsets/"+t),r=this.fieldsets[n.type],o=this.fieldsets[t];if(!o)return!1;let l=a.content;const c=i(r),u=i(o);Object.entries(u).forEach(([t,e])=>{const s=c[t];s&&s.type===e.type&&n.content[t]&&(l[t]=n.content[t])}),this.blocks[s]={...a,id:n.id,content:l},this.save()},async duplicate(t,e){const s={...this.$helper.clone(t),id:this.$helper.uuid()};this.blocks.splice(e+1,0,s),this.save()},fieldset(t){return this.fieldsets[t.type]||{icon:"box",name:t.type,tabs:{content:{fields:{}}},type:t.type}},focus(t){this.$refs["block-"+t.id]&&this.$refs["block-"+t.id][0].focus()},focusOrOpen(t){this.fieldsets[t.type].wysiwyg?this.focus(t):this.open(t)},hide(t){this.$set(t,"isHidden",!0),this.save()},isBatched(t){return this.batch.includes(t.id)},isLastInBatch(t){const[e]=this.batch.slice(-1);return e&&t.id===e},isSelected(t){return this.selected&&this.selected===t.id},move(t){if(t.from!==t.to){const e=t.draggedContext.element,s=t.relatedContext.component.componentData||t.relatedContext.component.$parent.componentData;if(!1===Object.keys(s.fieldsets).includes(e.type))return!1;if(!0===s.isFull)return!1}return!0},open(t){this.$refs["block-"+t.id]&&this.$refs["block-"+t.id][0].open()},prevNext(t){if(this.blocks[t]){let e=this.blocks[t];if(this.$refs["block-"+e.id])return this.$refs["block-"+e.id][0]}},remove(t){const e=this.blocks.findIndex(e=>e.id===t.id);-1!==e&&(this.selected&&this.selected.id===t.id&&this.select(null),this.$delete(this.blocks,e),this.save())},removeAll(){this.blocks=[],this.save(),this.$refs.removeAll.close()},removeSelected(){this.batch.forEach(t=>{const e=this.blocks.findIndex(e=>e.id===t);-1!==e&&this.$delete(this.blocks,e)}),this.batch=[],this.$store.dispatch("blocks/current",null),this.save(),this.$refs.removeSelected.close()},save(){this.$emit("input",this.blocks)},select(t){t&&this.altKey?this.addToBatch(t):(this.batch=[],this.$store.dispatch("blocks/current",t?t.id:null))},show(t){this.$set(t,"isHidden",!1),this.save()},sort(t,e,s){if(s<0)return;let i=this.$helper.clone(this.blocks);i.splice(e,1),i.splice(s,0,t),this.blocks=i,this.save(),this.$nextTick(()=>{this.focus(t)})},update(t,e){const s=this.blocks.findIndex(e=>e.id===t.id);-1!==s&&Object.entries(e).forEach(([t,e])=>{this.$set(this.blocks[s].content,t,e)}),this.save()}}},b$=g$,k$=(s("f470"),Object(c["a"])(b$,m$,f$,!1,null,null,null)),v$=k$.exports,$$=function(){var t=this,e=t.$createElement,s=t._self._c||e;return s("figure",{staticClass:"k-block-figure"},[t.isEmpty?s("k-button",{staticClass:"k-block-figure-empty",attrs:{icon:t.emptyIcon},on:{click:function(e){return t.$emit("open")}}},[t._v(" "+t._s(t.emptyText)+" ")]):s("span",{staticClass:"k-block-figure-container",on:{dblclick:function(e){return t.$emit("open")}}},[t._t("default")],2),t.caption?s("figcaption",[s("k-writer",{attrs:{inline:!0,marks:t.captionMarks,value:t.caption},on:{input:function(e){return t.$emit("update",{caption:e})}}})],1):t._e()],1)},y$=[],_$={inheritAttrs:!1,props:{caption:String,captionMarks:[Boolean,Array],cover:{type:Boolean,default:!0},isEmpty:Boolean,emptyIcon:String,emptyText:String,ratio:String},computed:{ratioPadding(){return this.$helper.ratio(this.ratio||"16/9")}}},w$=_$,x$=(s("0069"),Object(c["a"])(w$,$$,y$,!1,null,null,null)),O$=x$.exports,C$=function(){var t=this,e=t.$createElement,s=t._self._c||e;return s("k-dropdown",{staticClass:"k-block-options"},[t.isBatched?[s("k-button",{staticClass:"k-block-options-button",attrs:{tooltip:t.$t("remove"),icon:"trash"},nativeOn:{mousedown:function(e){return e.preventDefault(),t.$emit("confirmToRemoveSelected")}}})]:[s("k-button",{staticClass:"k-block-options-button",attrs:{tooltip:t.$t("edit"),icon:"edit"},on:{click:function(e){return t.$emit("open")}}}),s("k-button",{staticClass:"k-block-options-button",attrs:{disabled:t.isFull,tooltip:t.$t("insert.after"),icon:"add"},on:{click:function(e){return t.$emit("chooseToAppend")}}}),s("k-button",{staticClass:"k-block-options-button",attrs:{tooltip:t.$t("delete"),icon:"trash"},on:{click:function(e){return t.$emit("confirmToRemove")}}}),s("k-button",{staticClass:"k-block-options-button",attrs:{tooltip:t.$t("more"),icon:"dots"},on:{click:function(e){return t.$refs.options.toggle()}}}),s("k-button",{staticClass:"k-block-options-button k-block-handle",attrs:{tooltip:t.$t("sort"),icon:"sort"},on:{keydown:[function(e){return!e.type.indexOf("key")&&t._k(e.keyCode,"up",38,e.key,["Up","ArrowUp"])?null:(e.preventDefault(),t.$emit("sortUp"))},function(e){return!e.type.indexOf("key")&&t._k(e.keyCode,"down",40,e.key,["Down","ArrowDown"])?null:(e.preventDefault(),t.$emit("sortDown"))}]}}),s("k-dropdown-content",{ref:"options",attrs:{align:"right"}},[s("k-dropdown-item",{attrs:{disabled:t.isFull,icon:"angle-up"},on:{click:function(e){return t.$emit("chooseToPrepend")}}},[t._v(" "+t._s(t.$t("insert.before"))+" ")]),s("k-dropdown-item",{attrs:{disabled:t.isFull,icon:"angle-down"},on:{click:function(e){return t.$emit("chooseToAppend")}}},[t._v(" "+t._s(t.$t("insert.after"))+" ")]),s("hr"),s("k-dropdown-item",{attrs:{icon:"edit"},on:{click:function(e){return t.$emit("open")}}},[t._v(" "+t._s(t.$t("edit"))+" ")]),s("k-dropdown-item",{attrs:{icon:"refresh"},on:{click:function(e){return t.$emit("chooseToConvert")}}},[t._v(" "+t._s(t.$t("field.blocks.changeType"))+" ")]),s("hr"),s("k-dropdown-item",{attrs:{icon:t.isHidden?"preview":"hidden"},on:{click:function(e){return t.$emit(t.isHidden?"show":"hide")}}},[t._v(" "+t._s(!0===t.isHidden?t.$t("show"):t.$t("hide"))+" ")]),s("k-dropdown-item",{attrs:{disabled:t.isFull,icon:"copy"},on:{click:function(e){return t.$emit("duplicate")}}},[t._v(" "+t._s(t.$t("duplicate"))+" ")]),s("hr"),s("k-dropdown-item",{attrs:{icon:"trash"},on:{click:function(e){return t.$emit("confirmToRemove")}}},[t._v(" "+t._s(t.$t("delete"))+" ")])],1)]],2)},S$=[],E$={props:{isBatched:Boolean,isFull:Boolean,isHidden:Boolean},methods:{open(){this.$refs.options.open()}}},j$=E$,T$=(s("9c44"),Object(c["a"])(j$,C$,S$,!1,null,null,null)),L$=T$.exports,I$=function(){var t=this,e=t.$createElement,s=t._self._c||e;return s("k-dialog",{ref:"dialog",staticClass:"k-block-selector",attrs:{"cancel-button":!1,"submit-button":!1,size:"medium"}},[t.headline?s("k-headline",[t._v(" "+t._s(t.headline)+" ")]):t._e(),t._l(t.groups,(function(e,i){return s("details",{key:i,attrs:{open:e.open}},[s("summary",[t._v(t._s(e.label))]),s("div",{staticClass:"k-block-types"},t._l(e.fieldsets,(function(e){return s("k-button",{key:e.name,ref:"fieldset-"+e.index,refInFor:!0,attrs:{disabled:t.disabled.includes(e.type),icon:e.icon||"box"},on:{keydown:[function(s){return!s.type.indexOf("key")&&t._k(s.keyCode,"up",38,s.key,["Up","ArrowUp"])?null:t.navigate(e.index-1)},function(s){return!s.type.indexOf("key")&&t._k(s.keyCode,"down",40,s.key,["Down","ArrowDown"])?null:t.navigate(e.index+1)}],click:function(s){return t.add(e.type)}}},[t._v(" "+t._s(e.name)+" ")])})),1)])}))],2)},A$=[],B$={inheritAttrs:!1,props:{endpoint:String,fieldsets:Object,fieldsetGroups:Object},data(){return{disabled:[],headline:null,payload:null,event:"add",groups:this.createGroups()}},methods:{add(t){this.$emit(this.event,t,this.payload),this.$refs.dialog.close()},createGroups(){let t={},e=0;const s=this.fieldsetGroups||{blocks:{label:this.$t("field.blocks.fieldsets.label"),sets:Object.keys(this.fieldsets)}};return Object.keys(s).forEach(i=>{let n=s[i];n.open=!1!==n.open,n.fieldsets=n.sets.filter(t=>this.fieldsets[t]).map(t=>(e++,{...this.fieldsets[t],index:e})),0!==n.fieldsets.length&&(t[i]=n)}),t},navigate(t){const e=this.$refs["fieldset-"+t];e&&e[0]&&e[0].focus()},open(t,e={}){const s={event:"add",disabled:[],headline:null,...e};this.event=s.event,this.disabled=s.disabled,this.headline=s.headline,this.payload=t,this.$refs.dialog.open()}}},M$=B$,D$=(s("f6c2"),Object(c["a"])(M$,I$,A$,!1,null,null,null)),N$=D$.exports,P$=function(){var t=this,e=t.$createElement,s=t._self._c||e;return s("div",t._g({staticClass:"k-block-title"},t.$listeners),[s("k-icon",{staticClass:"k-block-icon",attrs:{type:t.icon}}),s("span",{staticClass:"k-block-name"},[t._v(" "+t._s(t.name)+" ")]),t.label?s("span",{staticClass:"k-block-label"},[t._v(" "+t._s(t.label)+" ")]):t._e()],1)},R$=[],q$={inheritAttrs:!1,props:{fieldset:Object,content:Object},computed:{icon(){return this.fieldset.icon||"box"},label(){if(!this.fieldset.label||0===this.fieldset.label.length)return!1;if(this.fieldset.label===this.fieldset.name)return!1;const t=this.$helper.string.template(this.fieldset.label,this.content);return"…"!==t&&t},name(){return this.fieldset.name}}},F$=q$,U$=(s("c21a"),Object(c["a"])(F$,P$,R$,!1,null,null,null)),H$=U$.exports,z$={inheritAttrs:!1,props:{content:Object,fieldset:Object},methods:{field(t,e=null){let s=null;return Object.values(this.fieldset.tabs).forEach(e=>{e.fields[t]&&(s=e.fields[t])}),s||e},open(){this.$emit("open")},update(t){this.$emit("update",{...this.content,...t})}}},K$=z$,G$=Object(c["a"])(K$,ub,db,!1,null,null,null),W$=G$.exports;I["a"].component("k-block",h$),I["a"].component("k-blocks",v$),I["a"].component("k-block-figure",O$),I["a"].component("k-block-options",L$),I["a"].component("k-block-selector",N$),I["a"].component("k-block-title",H$),I["a"].component("k-block-type",W$);const Y$=s("9711");Y$.keys().map(t=>{const e=t.match(/\w+/)[0],s=e.toLowerCase();let i=Y$(t).default;i.extends=W$,I["a"].component("k-block-type-"+s,i)}),I["a"].component("k-dialog",mt),I["a"].component("k-error-dialog",$t),I["a"].component("k-file-rename-dialog",It),I["a"].component("k-file-remove-dialog",Ct),I["a"].component("k-file-sort-dialog",Pt),I["a"].component("k-files-dialog",Kt),I["a"].component("k-form-dialog",Zt),I["a"].component("k-language-create-dialog",ie),I["a"].component("k-language-remove-dialog",ce),I["a"].component("k-language-update-dialog",fe),I["a"].component("k-page-create-dialog",ye),I["a"].component("k-page-duplicate-dialog",Se),I["a"].component("k-page-remove-dialog",Ae),I["a"].component("k-page-sort-dialog",Ve),I["a"].component("k-page-rename-dialog",Re),I["a"].component("k-page-status-dialog",Ke),I["a"].component("k-page-template-dialog",es),I["a"].component("k-pages-dialog",os),I["a"].component("k-remove-dialog",hs),I["a"].component("k-site-rename-dialog",vs),I["a"].component("k-text-dialog",Os),I["a"].component("k-user-create-dialog",Ls),I["a"].component("k-user-email-dialog",Ns),I["a"].component("k-user-language-dialog",Hs),I["a"].component("k-user-password-dialog",Vs),I["a"].component("k-user-remove-dialog",ei),I["a"].component("k-user-rename-dialog",oi),I["a"].component("k-user-role-dialog",hi),I["a"].component("k-users-dialog",vi),I["a"].component("k-drawer",Oi),I["a"].component("k-form-drawer",Li),I["a"].component("k-calendar",Hi),I["a"].component("k-counter",Vi),I["a"].component("k-autocomplete",Ni),I["a"].component("k-form",en),I["a"].component("k-form-buttons",ln),I["a"].component("k-form-indicator",mn),I["a"].component("k-field",fn["a"]),I["a"].component("k-fieldset",yn),I["a"].component("k-input",Sn),I["a"].component("k-login",An),I["a"].component("k-login-code",Rn),I["a"].component("k-upload",Kn),I["a"].component("k-writer",cr),I["a"].component("k-checkbox-input",gr),I["a"].component("k-checkboxes-input",_r),I["a"].component("k-date-input",Er),I["a"].component("k-datetime-input",Br),I["a"].component("k-email-input",Kr),I["a"].component("k-list-input",ro),I["a"].component("k-multiselect-input",ho),I["a"].component("k-number-input",vo),I["a"].component("k-password-input",wo),I["a"].component("k-radio-input",jo),I["a"].component("k-range-input",Mo),I["a"].component("k-select-input",Fo),I["a"].component("k-tags-input",Wo),I["a"].component("k-tel-input",Zo),I["a"].component("k-text-input",Fr),I["a"].component("k-textarea-input",il),I["a"].component("k-time-input",ol),I["a"].component("k-toggle-input",hl),I["a"].component("k-url-input",bl),I["a"].component("k-blocks-field",wl),I["a"].component("k-checkboxes-field",jl),I["a"].component("k-date-field",Ml),I["a"].component("k-email-field",Fl),I["a"].component("k-files-field",Yl),I["a"].component("k-gap-field",Ql),I["a"].component("k-headline-field",ac),I["a"].component("k-info-field",dc),I["a"].component("k-layout-field",Mc),I["a"].component("k-line-field",Fc),I["a"].component("k-list-field",Wc),I["a"].component("k-multiselect-field",Qc),I["a"].component("k-number-field",au),I["a"].component("k-pages-field",du),I["a"].component("k-password-field",bu),I["a"].component("k-radio-field",wu),I["a"].component("k-range-field",ju),I["a"].component("k-select-field",Mu),I["a"].component("k-structure-field",Uu),I["a"].component("k-tags-field",Yu),I["a"].component("k-text-field",rd),I["a"].component("k-textarea-field",pd),I["a"].component("k-tel-field",td),I["a"].component("k-time-field",kd),I["a"].component("k-toggle-field",xd),I["a"].component("k-url-field",Td),I["a"].component("k-users-field",Dd),I["a"].component("k-writer-field",Ud),I["a"].component("k-toolbar",Zd),I["a"].component("k-toolbar-email-dialog",ip),I["a"].component("k-toolbar-link-dialog",cp),I["a"].component("k-date-field-preview",fp),I["a"].component("k-email-field-preview",Op),I["a"].component("k-files-field-preview",Mp),I["a"].component("k-list-field-preview",Fp),I["a"].component("k-pages-field-preview",Wp),I["a"].component("k-toggle-field-preview",ah),I["a"].component("k-time-field-preview",Qp),I["a"].component("k-url-field-preview",yp),I["a"].component("k-users-field-preview",dh),I["a"].component("k-writer-field-preview",bh),I["a"].component("k-aspect-ratio",wh),I["a"].component("k-bar",jh),I["a"].component("k-box",Mh),I["a"].component("k-card",Fh),I["a"].component("k-cards",Wh),I["a"].component("k-collection",Qh),I["a"].component("k-column",am),I["a"].component("k-dropzone",dm),I["a"].component("k-empty",bm),I["a"].component("k-file-preview",wm),I["a"].component("k-grid",jm),I["a"].component("k-header",Mm),I["a"].component("k-list",Fm),I["a"].component("k-list-item",Wm),I["a"].component("k-overlay",Qm),I["a"].component("k-tabs",rf),I["a"].component("k-view",pf),I["a"].component("k-draggable",$f),I["a"].component("k-error-boundary",xf),I["a"].component("k-headline",Tf),I["a"].component("k-icon",Df),I["a"].component("k-image",Uf),I["a"].component("k-loader",Wf),I["a"].component("k-progress",Qf),I["a"].component("k-status-icon",dg),I["a"].component("k-sort-handle",ag),I["a"].component("k-text",bg),I["a"].component("k-user-info",wg),I["a"].component("k-button",jg),I["a"].component("k-button-disabled",Mg),I["a"].component("k-button-group",Fg),I["a"].component("k-button-link",Wg),I["a"].component("k-button-native",tb),I["a"].component("k-dropdown",rb),I["a"].component("k-dropdown-content",fb),I["a"].component("k-dropdown-item",yb),I["a"].component("k-languages-dropdown",Ab),I["a"].component("k-link",Sb),I["a"].component("k-pagination",Rb),I["a"].component("k-prev-next",Kb),I["a"].component("k-search",Zb),I["a"].component("k-tag",ik),I["a"].component("k-topbar",uk),I["a"].component("k-sections",gk),I["a"].component("k-info-section",wk),I["a"].component("k-pages-section",Tk),I["a"].component("k-files-section",Dk),I["a"].component("k-fields-section",Uk),I["a"].component("k-browser-view",Vk),I["a"].component("k-custom-view",ev),I["a"].component("k-error-view",rv),I["a"].component("k-file-view",hv),I["a"].component("k-installation-view",vv),I["a"].component("k-login-view",Ov),I["a"].component("k-page-view",Lv),I["a"].component("k-reset-password-view",Nv),I["a"].component("k-settings-view",Hv),I["a"].component("k-site-view",Vv),I["a"].component("k-users-view",e$),I["a"].component("k-user-view",o$);var V$=s("2f62"),J$={namespaced:!0,state:{current:null},mutations:{CURRENT(t,e){t.current=e}},actions:{current(t,e){t.commit("CURRENT",e)}}};const Z$=(t,e)=>{localStorage.setItem("kirby$content$"+t,JSON.stringify(e))};var X$={namespaced:!0,state:{current:null,models:{},status:{enabled:!0,lock:null,unlock:null}},getters:{exists:t=>e=>Object.prototype.hasOwnProperty.call(t.models,e),hasChanges:(t,e)=>t=>{const s=e.model(t).changes;return Object.keys(s).length>0},isCurrent:t=>e=>t.current===e,id:(t,e,s)=>e=>(e=e||t.current,s.languages.current?e+"/"+s.languages.current.code:e),model:(t,e)=>s=>(s=s||t.current,!0===e.exists(s)?t.models[s]:{api:null,originals:{},values:{},changes:{}}),originals:(t,e)=>t=>Y(e.model(t).originals),values:(t,e)=>t=>({...e.originals(t),...e.changes(t)}),changes:(t,e)=>t=>Y(e.model(t).changes)},mutations:{CREATE(t,[e,s]){if(!s)return!1;let i=t.models[e]?t.models[e].changes:s.changes;I["a"].set(t.models,e,{api:s.api,originals:s.originals,changes:i||{}})},CURRENT(t,e){t.current=e},LOCK(t,e){I["a"].set(t.status,"lock",e)},MOVE(t,[e,s]){const i=Y(t.models[e]);I["a"].delete(t.models,e),I["a"].set(t.models,s,i);const n=localStorage.getItem("kirby$content$"+e);localStorage.removeItem("kirby$content$"+e),localStorage.setItem("kirby$content$"+s,n)},REMOVE(t,e){I["a"].delete(t.models,e),localStorage.removeItem("kirby$content$"+e)},REVERT(t,e){t.models[e]&&(I["a"].set(t.models[e],"changes",{}),localStorage.removeItem("kirby$content$"+e))},STATUS(t,e){I["a"].set(t.status,"enabled",e)},UNLOCK(t,e){e&&I["a"].set(t.models[t.current],"changes",{}),I["a"].set(t.status,"unlock",e)},UPDATE(t,[e,s,i]){if(!t.models[e])return!1;i=Y(i);const n=JSON.stringify(i),a=JSON.stringify(t.models[e].originals[s]);a==n?I["a"].delete(t.models[e].changes,s):I["a"].set(t.models[e].changes,s,i),Z$(e,{api:t.models[e].api,originals:t.models[e].originals,changes:t.models[e].changes})}},actions:{init(t){Object.keys(localStorage).filter(t=>t.startsWith("kirby$content$")).map(t=>t.split("kirby$content$")[1]).forEach(e=>{const s=localStorage.getItem("kirby$content$"+e);t.commit("CREATE",[e,JSON.parse(s)])}),Object.keys(localStorage).filter(t=>t.startsWith("kirby$form$")).map(t=>t.split("kirby$form$")[1]).forEach(e=>{const s=localStorage.getItem("kirby$form$"+e);let i=null;try{i=JSON.parse(s)}catch(a){}if(!i||!i.api)return localStorage.removeItem("kirby$form$"+e),!1;const n={api:i.api,originals:i.originals,changes:i.values};t.commit("CREATE",[e,n]),Z$(e,n),localStorage.removeItem("kirby$form$"+e)})},create(t,e){e.id=t.getters.id(e.id),(e.id.startsWith("pages/")||e.id.startsWith("site"))&&delete e.content.title;const s={api:e.api,originals:Y(e.content),changes:{}};I["a"].$api.get(e.api+"/unlock").then(s=>{!0===s.supported&&!0===s.unlocked&&t.commit("UNLOCK",t.state.models[e.id].changes)}).catch(()=>{}),t.commit("CREATE",[e.id,s]),t.dispatch("current",e.id)},current(t,e){t.commit("CURRENT",e)},disable(t){t.commit("STATUS",!1)},enable(t){t.commit("STATUS",!0)},lock(t,e){t.commit("LOCK",e)},move(t,[e,s]){e=t.getters.id(e),s=t.getters.id(s),t.commit("MOVE",[e,s])},remove(t,e){t.commit("REMOVE",e),t.getters.isCurrent(e)&&t.commit("CURRENT",null)},revert(t,e){e=e||t.state.current,t.commit("REVERT",e)},save(t,e){if(e=e||t.state.current,t.getters.isCurrent(e)&&!1===t.state.status.enabled)return!1;t.dispatch("disable");const s=t.getters.model(e),i={...s.originals,...s.changes};return I["a"].$api.patch(s.api,i).then(()=>{t.commit("CREATE",[e,{...s,originals:i}]),t.dispatch("revert",e),t.dispatch("enable")}).catch(e=>{throw t.dispatch("enable"),e})},unlock(t,e){t.commit("UNLOCK",e)},update(t,[e,s,i]){i=i||t.state.current,t.commit("UPDATE",[i,e,s])}}},Q$={namespaced:!0,state:{open:[]},mutations:{CLOSE(t,e){t.open=t.open.filter(t=>t.id!==e)},GOTO(t,e){t.open=t.open.filter(t=>t.id===e)},OPEN(t,e){t.open.push(e)}},actions:{close(t,e){t.commit("CLOSE",e)},goto(t,e){t.commit("GOTO",e)},open(t,e){t.commit("OPEN",e)}}},ty={namespaced:!0,state:{instance:null,clock:0,step:5,beats:[]},mutations:{ADD(t,e){t.beats.push(e)},CLEAR(t){clearInterval(t.instance),t.clock=0},CLOCK(t){t.clock+=t.step},INITIALIZE(t,e){t.instance=e},REMOVE(t,e){const s=t.beats.map(t=>t.handler).indexOf(e);-1!==s&&I["a"].delete(t.beats,s)}},actions:{add(t,e){e={handler:e[0]||e,interval:e[1]||t.state.step},e.handler(),t.commit("ADD",e),1===t.state.beats.length&&t.dispatch("run")},clear(t){t.commit("CLEAR")},remove(t,e){t.commit("REMOVE",e),t.state.beats.length<1&&t.commit("CLEAR")},run(t){t.commit("CLEAR"),t.commit("INITIALIZE",setInterval(()=>{t.commit("CLOCK"),t.state.beats.forEach(e=>{t.state.clock%e.interval===0&&e.handler()})},1e3*t.state.step))}}},ey={namespaced:!0,state:{all:[],current:null,default:null},mutations:{SET_ALL(t,e){t.all=e.map(t=>({code:t.code,default:t.default,direction:t.direction,locale:t.locale,name:t.name,rules:t.rules,url:t.url}))},SET_CURRENT(t,e){t.current=e,e&&e.code&&localStorage.setItem("kirby$language",e.code)},SET_DEFAULT(t,e){t.default=e}},actions:{current(t,e){t.commit("SET_CURRENT",e)},install(t,e){const s=e.filter(t=>t.default)[0];t.commit("SET_ALL",e),t.commit("SET_DEFAULT",s);const i=localStorage.getItem("kirby$language");if(i){const s=e.filter(t=>t.code===i)[0];if(s)return void t.dispatch("current",s)}t.dispatch("current",s||e[0]||null)},async load(t){const e=await I["a"].$api.languages.list();t.dispatch("install",e.data)}}},sy={timer:null,namespaced:!0,state:{type:null,message:null,details:null,timeout:null},mutations:{SET(t,e){t.type=e.type,t.message=e.message,t.details=e.details,t.timeout=e.timeout},UNSET(t){t.type=null,t.message=null,t.details=null,t.timeout=null}},actions:{close(t){clearTimeout(this.timer),t.commit("UNSET")},open(t,e){t.dispatch("close"),t.commit("SET",e),e.timeout&&(this.timer=setTimeout(()=>{t.dispatch("close")},e.timeout))},success(t,e){"string"===typeof e&&(e={message:e}),t.dispatch("open",{type:"success",timeout:4e3,...e})},error(t,e){"string"===typeof e&&(e={message:e}),t.dispatch("open",{type:"error",...e})}}},iy={namespaced:!0,state:{info:{title:null}},mutations:{SET_INFO(t,e){t.info=e},SET_LICENSE(t,e){t.info.license=e},SET_TITLE(t,e){t.info.title=e}},actions:{async load(t,e){if(!e&&t.state.info.isReady&&t.rootState.user.current)return new Promise(e=>{e(t.state.info)});try{const e=await I["a"].$api.system.get();return t.commit("SET_INFO",{isReady:e.isInstalled&&e.isOk,...e}),e.languages&&t.dispatch("languages/install",e.languages,{root:!0}),t.dispatch("translation/install",e.translation,{root:!0}),t.dispatch("translation/activate",e.translation.id,{root:!0}),e.user&&t.dispatch("user/current",e.user,{root:!0}),t.state.info}catch(s){t.commit("SET_INFO",{isBroken:!0,error:s.message})}},register(t,e){t.commit("SET_LICENSE",e)},title(t,e){t.commit("SET_TITLE",e)}}},ny={namespaced:!0,state:{current:null,installed:[]},mutations:{SET_CURRENT(t,e){t.current=e},INSTALL(t,e){t.installed[e.id]=e}},actions:{load(t,e){return I["a"].$api.translations.get(e)},install(t,e){t.commit("INSTALL",e),I["a"].i18n.add(e.id,e.data)},async activate(t,e){const s=t.state.installed[e];if(!s){const s=await t.dispatch("load",e);return t.dispatch("install",s),void t.dispatch("activate",e)}I["a"].i18n.set(e),t.commit("SET_CURRENT",e),document.dir=s.direction,document.documentElement.lang=e}}},ay={namespaced:!0,state:{current:null,path:null,pendingEmail:null,pendingChallenge:null},mutations:{SET_CURRENT(t,e){t.current=e,t.pendingEmail=null,t.pendingChallenge=null,e&&e.permissions?(I["a"].prototype.$user=e,I["a"].prototype.$permissions=e.permissions):(I["a"].prototype.$user=null,I["a"].prototype.$permissions=null)},SET_PATH(t,e){t.path=e},SET_PENDING(t,{email:e,challenge:s}){t.pendingEmail=e,t.pendingChallenge=s,t.user=null,I["a"].prototype.$user=null,I["a"].prototype.$permissions=null}},actions:{current(t,e){t.commit("SET_CURRENT",e)},email(t,e){t.commit("SET_CURRENT",{...t.state.current,email:e})},language(t,e){t.dispatch("translation/activate",e,{root:!0}),t.commit("SET_CURRENT",{...t.state.current,language:e})},async load(t){const e=await I["a"].$api.auth.user();return t.commit("SET_CURRENT",e),e},login(t,e){return t.commit("SET_CURRENT",e),t.dispatch("translation/activate",e.language,{root:!0}),I["a"].prototype.$go(t.state.path||"/"),e},async logout(t,e){if(t.commit("SET_CURRENT",null),e)window.location.href=(window.panel.url||"")+"/login";else try{await I["a"].$api.auth.logout()}finally{I["a"].prototype.$go("/login")}},name(t,e){t.commit("SET_CURRENT",{...t.state.current,name:e})},pending(t,e){t.commit("SET_PENDING",e)},visit(t,e){t.commit("SET_PATH",e)}}};I["a"].use(V$["a"]);var ry=new V$["a"].Store({strict:!1,state:{breadcrumb:[],dialog:null,drag:null,fatal:null,isLoading:!1,title:null,view:null},mutations:{SET_BREADCRUMB(t,e){t.breadcrumb=e},SET_DIALOG(t,e){t.dialog=e},SET_DRAG(t,e){t.drag=e},SET_FATAL(t,e){t.fatal=e},SET_TITLE(t,e){t.title=e},SET_VIEW(t,e){t.view=e},START_LOADING(t){t.isLoading=!0},STOP_LOADING(t){t.isLoading=!1}},actions:{breadcrumb(t,e){t.commit("SET_BREADCRUMB",e)},dialog(t,e){t.commit("SET_DIALOG",e)},drag(t,e){t.commit("SET_DRAG",e)},fatal(t,e){t.commit("SET_FATAL",e)},isLoading(t,e){t.commit(!0===e?"START_LOADING":"STOP_LOADING")},title(t,e){let s;s=t.state.user.current?I["a"].$api.site.get(["title"]):new Promise(e=>{e(t.state.system.info)}),s.then(s=>{t.commit("SET_TITLE",e),t.dispatch("system/title",s.title),document.title=e||"",document.title+=null!==e?" | "+s.title:s.title})},view(t,e){t.commit("SET_VIEW",e)}},modules:{blocks:J$,content:X$,drawers:Q$,heartbeat:ty,languages:ey,notification:sy,system:iy,translation:ny,user:ay}});I["a"].config.errorHandler=t=>{O.debug&&window.console.error(t),ry.dispatch("notification/error",{message:t.message||"An error occurred. Please reload the Panel."})},window.panel=window.panel||{},window.panel.error=(t,e)=>{O.debug&&window.console.error(t+": "+e),ry.dispatch("error",t+". See the console for more information.")};var oy=s("f2f3");I["a"].use(oy["a"].plugin,ry);var ly=s("19e9"),cy=s.n(ly),uy=s("5a0c"),dy=s.n(uy),py=s("f906"),hy=s.n(py),my=s("0ecf"),fy=s.n(my);dy.a.extend(hy.a),dy.a.extend(fy.a),I["a"].prototype.$library={autosize:cy.a,dayjs:dy.a};let gy={};for(var by in I["a"].options.components)gy[by]=I["a"].options.components[by];let ky={section:vk};Object.entries(window.panel.plugins.components).forEach(([t,e])=>{e.template||e.render||e.extends?(e.extends&&"string"===typeof e.extends&&(e.extends=gy[e.extends].extend({options:e,components:{...gy,...e.components||{}}}),e.template&&(e.render=null)),e.mixins&&(e.mixins=e.mixins.map(t=>"string"===typeof t?ky[t]:t)),gy[t]&&window.console.warn(`Plugin is replacing "${t}"`),I["a"].component(t,e),gy[t]=I["a"].options.components[t]):ry.dispatch("notification/error",`Neither template or render method provided nor extending a component when loading plugin component "${t}". The component has not been registered.`)}),Object.entries(window.panel.plugins.views).forEach(([t,e])=>{if(!e.component)return ry.dispatch("notification/error",`No view component provided when loading view "${t}". The view has not been registered.`),void delete window.panel.plugins.views[t];e.link="/plugins/"+t,void 0===e.icon&&(e.icon="page"),void 0===e.menu&&(e.menu=!0),window.panel.plugins.views[t]={id:t,text:e.text||e.label,link:e.link,icon:e.icon,menu:e.menu},I["a"].component("k-"+t+"-plugin-view",e.component)}),window.panel.plugins.use.forEach(t=>{I["a"].use(t)});var vy=s("82f1"),$y=s("8c4f"),yy=function(t,e,s){ry.dispatch("system/load").then(()=>{const e=ry.state.user.current;if(!e)return ry.dispatch("user/visit",t.path),ry.dispatch("user/logout"),s(!1);const i=e.permissions.access;return!1===i.panel?(window.location.href=O.site,s(!1)):!1===i[t.meta.view]?(ry.dispatch("notification/error",{message:I["a"].i18n.translate("error.access.view")}),s(!1===i.site?"/account":"/")):void s()})};let _y=[{path:"/",name:"Home",redirect:"/site"},{path:"/browser",name:"Browser",component:I["a"].component("k-browser-view"),meta:{outside:!0}},{path:"/login",component:I["a"].component("k-login-view"),meta:{outside:!0}},{path:"/logout",beforeEnter(){Object.keys(localStorage).forEach(t=>{t.startsWith("kirby$content$")&&localStorage.removeItem(t)}),ry.dispatch("user/logout")},meta:{outside:!0}},{path:"/installation",component:I["a"].component("k-installation-view"),meta:{outside:!0}},{path:"/site",name:"Site",meta:{view:"site"},component:I["a"].component("k-site-view"),beforeEnter:yy,props:t=>({tab:t.hash.slice(1)||"main"})},{path:"/site/files/:filename",name:"SiteFile",meta:{view:"site"},component:I["a"].component("k-file-view"),beforeEnter:yy,props:t=>({filename:t.params.filename,path:"site",tab:t.hash.slice(1)||"main"})},{path:"/pages/:path/files/:filename",name:"PageFile",meta:{view:"site"},component:I["a"].component("k-file-view"),beforeEnter:yy,props:t=>({filename:t.params.filename,path:"pages/"+t.params.path,tab:t.hash.slice(1)||"main"})},{path:"/users/:path/files/:filename",name:"UserFile",meta:{view:"users"},component:I["a"].component("k-file-view"),beforeEnter:yy,props:t=>({filename:t.params.filename,path:"users/"+t.params.path,tab:t.hash.slice(1)||"main"})},{path:"/pages/:path",name:"Page",meta:{view:"site"},component:I["a"].component("k-page-view"),beforeEnter:yy,props:t=>({path:t.params.path,tab:t.hash.slice(1)||"main"})},{path:"/settings",name:"Settings",meta:{view:"settings"},component:I["a"].component("k-settings-view"),beforeEnter:yy},{path:"/users/role/:role",name:"UsersByRole",meta:{view:"users"},component:I["a"].component("k-users-view"),beforeEnter:yy,props:t=>({role:t.params.role})},{path:"/users",name:"Users",meta:{view:"users"},beforeEnter:yy,component:I["a"].component("k-users-view")},{path:"/users/:id",name:"User",meta:{view:"users"},component:I["a"].component("k-user-view"),beforeEnter:yy,props:t=>({id:t.params.id,tab:t.hash.slice(1)||"main"})},{path:"/account",name:"Account",meta:{view:"account"},component:I["a"].component("k-user-view"),beforeEnter:yy,props:t=>({id:!!ry.state.user.current&&ry.state.user.current.id,tab:t.hash.slice(1)||"main"})},{path:"/reset-password",name:"Reset password",meta:{view:"resetPassword"},component:I["a"].component("k-reset-password-view"),beforeEnter:yy},{path:"/plugins/:id",name:"Plugin",meta:{view:"plugin"},props:t=>({plugin:t.params.id,hash:t.hash.slice(1)}),beforeEnter:yy,component:I["a"].component("k-custom-view")}];_y.push({path:"*",name:"NotFound",beforeEnter:(t,e,s)=>{s("/")}});var wy=_y;I["a"].use($y["a"]);const xy=new $y["a"]({mode:"history",routes:wy,url:"/"===O.url?"":O.url});xy.beforeEach((t,e,s)=>{"Browser"!==t.name&&!1===Kk.all()&&s("/browser"),t.meta.outside||ry.dispatch("user/visit",t.path),"plugin"===t.meta.view?ry.dispatch("view",t.params.id):ry.dispatch("view",t.meta.view),ry.dispatch("content/lock",null),ry.dispatch("content/unlock",null),ry.dispatch("heartbeat/clear"),s()});var Oy=xy;I["a"].config.productionTip=!1,I["a"].config.devtools=!0,I["a"].use(rt),I["a"].use(K),I["a"].use(z),I["a"].use(W.a),I["a"].use(vy["a"]),I["a"].use(H,ry),I["a"].$t=I["a"].prototype.$t,I["a"].prototype.$go=t=>{t=t.split("#"),t={path:t[0],hash:t[1]||null},Oy.push(t).catch(t=>{if(t&&t.name&&"NavigationDuplicated"===t.name)return!0;throw t})},new I["a"]({router:Oy,store:ry,created(){window.panel.app=this,window.panel.plugins.created.forEach(t=>{t(this)}),this.$store.dispatch("content/init")},render:t=>t(T)}).$mount("#app")},"56f2":function(t,e,s){},"577a":function(t,e,s){},"580a":function(t,e,s){"use strict";s("ae29")},"5aee":function(t,e,s){"use strict";s("931e")},"5b23":function(t,e,s){"use strict";s("05be")},"5c0b":function(t,e,s){"use strict";s("9c0c")},"5c47":function(t,e,s){},"5c57":function(t,e,s){},"5cbe":function(t,e,s){},"5d33":function(t,e,s){"use strict";s("4e0b")},"5e2d":function(t,e,s){},"5e77":function(t,e,s){},"5e9a":function(t,e,s){"use strict";s("aa34")},"5f34":function(t,e,s){"use strict";s("b231")},"5f43":function(t,e,s){},"5f52":function(t,e,s){"use strict";s("26c3")},6018:function(t,e,s){"use strict";s("2a08")},"625a":function(t,e,s){"use strict";s("d3bb")},6284:function(t,e,s){"use strict";s.r(e);var i=function(){var t=this,e=t.$createElement,s=t._self._c||e;return s("k-input",{ref:"input",staticClass:"k-block-type-markdown-input",attrs:{buttons:!1,placeholder:t.placeholder,spellcheck:!1,value:t.content.text,type:"textarea"},on:{input:function(e){return t.update({text:e})}}})},n=[],a={computed:{placeholder(){return this.field("text",{}).placeholder}},methods:{focus(){this.$refs.input.focus()}}},r=a,o=(s("af44"),s("2877")),l=Object(o["a"])(r,i,n,!1,null,null,null);e["default"]=l.exports},"64e4":function(t,e,s){"use strict";s("99ed")},"653a":function(t,e,s){},"696b":function(t,e,s){"use strict";s("40a6")},6992:function(t,e,s){},"6a16":function(t,e,s){"use strict";s("ca47")},"6a18":function(t,e,s){"use strict";s("c57e")},"6ae1":function(t,e,s){},"6ae8":function(t,e,s){},"6bc0":function(t,e,s){},"6bcd":function(t,e,s){"use strict";s("d49c")},"6f7b":function(t,e,s){"use strict";s("f13c")},7041:function(t,e,s){},7086:function(t,e,s){},7187:function(t,e,s){},"718c":function(t,e,s){"use strict";s("43bf")},7491:function(t,e,s){},7568:function(t,e,s){"use strict";s("6bc0")},"772f":function(t,e,s){},7737:function(t,e,s){"use strict";s("2609")},7740:function(t,e,s){},7997:function(t,e,s){},"7a7d":function(t,e,s){"use strict";s("53fd")},"7b55":function(t,e,s){},"7d5d":function(t,e,s){"use strict";s("1e4a")},"7dc7":function(t,e,s){"use strict";s("6992")},"7e85":function(t,e,s){"use strict";s("f239")},"7f51":function(t,e,s){"use strict";s.r(e);var i=function(){var t=this,e=t.$createElement,s=t._self._c||e;return s("div",{staticClass:"k-block-type-heading-input",attrs:{"data-level":t.content.level}},[s("k-writer",{ref:"input",attrs:{inline:!0,marks:t.textField.marks,placeholder:t.textField.placeholder,value:t.content.text},on:{input:function(e){return t.update({text:e})}}})],1)},n=[],a={computed:{textField(){return this.field("text",{marks:!0})}},methods:{focus(){this.$refs.input.focus()}}},r=a,o=(s("455b"),s("2877")),l=Object(o["a"])(r,i,n,!1,null,null,null);e["default"]=l.exports},"7f6e":function(t,e,s){"use strict";s("772f")},8065:function(t,e,s){},8152:function(t,e,s){},8216:function(t,e,s){},"82b4":function(t,e,s){},"82c9":function(t,e,s){"use strict";s("e661")},"862b":function(t,e,s){"use strict";s("f0f6")},"86ab":function(t,e,s){},"86c7":function(t,e){RegExp.escape=function(t){return t.replace(new RegExp("[-/\\\\^$*+?.()[\\]{}]","gu"),"\\$&")}},"86eb":function(t,e,s){},"877d":function(t,e,s){},"893d":function(t,e,s){"use strict";s("fdbf")},"8a02":function(t,e,s){},"8abf":function(t,e,s){},"8c28":function(t,e,s){"use strict";s("4f8f")},"8d8b":function(t,e,s){"use strict";s("e292")},"8f7d":function(t,e,s){"use strict";s("c82a")},"907a":function(t,e,s){},"91a7":function(t,e,s){"use strict";s("653a")},"931e":function(t,e,s){},"96a5":function(t,e,s){"use strict";s("5c57")},9711:function(t,e,s){var i={"./Code.vue":"1bde","./Default.vue":"367b","./Gallery.vue":"3273","./Heading.vue":"7f51","./Image.vue":"9978","./List.vue":"1991","./Markdown.vue":"6284","./Quote.vue":"d55e","./Table.vue":"e54a","./Text.vue":"dc40","./Video.vue":"a08f"};function n(t){var e=a(t);return s(e)}function a(t){if(!s.o(i,t)){var e=new Error("Cannot find module '"+t+"'");throw e.code="MODULE_NOT_FOUND",e}return i[t]}n.keys=function(){return Object.keys(i)},n.resolve=a,t.exports=n,n.id="9711"},"977f":function(t,e,s){"use strict";s("ccf4")},9799:function(t,e,s){"use strict";s("8152")},"98a1":function(t,e,s){"use strict";s("20f8")},9978:function(t,e,s){"use strict";s.r(e);var i=function(){var t=this,e=t.$createElement,s=t._self._c||e;return s("k-block-figure",{attrs:{caption:t.content.caption,"caption-marks":t.captionMarks,"empty-text":t.$t("field.blocks.image.placeholder")+" …","is-empty":!t.src,"empty-icon":"image"},on:{open:t.open,update:t.update}},[t.src?[t.ratio?s("k-aspect-ratio",{attrs:{ratio:t.ratio,cover:t.crop}},[s("img",{attrs:{alt:t.content.alt,src:t.src}})]):s("img",{staticClass:"k-block-type-image-auto",attrs:{alt:t.content.alt,src:t.src}})]:t._e()],2)},n=[],a={computed:{captionMarks(){return this.field("caption",{marks:!0}).marks},crop(){return this.content.crop||!1},src(){return"web"===this.content.location?this.content.src:!(!this.content.image[0]||!this.content.image[0].url)&&this.content.image[0].url},ratio(){return this.content.ratio||!1}}},r=a,o=(s("625a"),s("2877")),l=Object(o["a"])(r,i,n,!1,null,null,null);e["default"]=l.exports},"99ed":function(t,e,s){},"9aff":function(t,e,s){},"9bd5":function(t,e,s){"use strict";s("3e1e")},"9c0c":function(t,e,s){},"9c44":function(t,e,s){"use strict";s("56f2")},"9d40":function(t,e,s){},"9d48":function(t,e,s){},"9d9f":function(t,e,s){},"9e26":function(t,e,s){"use strict";s("9d9f")},"9efc":function(t,e,s){},"9fc0":function(t,e,s){"use strict";var i=s("38b6");e["a"]={props:{...i["a"].props},methods:{displayText(t,e){switch(t.type){case"user":return e.email;case"date":{const s=this.$library.dayjs(e),i=!0===t.time?"YYYY-MM-DD HH:mm":"YYYY-MM-DD";return s.isValid()?s.format(i):""}case"tags":case"multiselect":return e.map(t=>t.text).join(", ");case"checkboxes":return e.map(e=>{let s=e;return t.options.forEach(t=>{t.value===e&&(s=t.text)}),s}).join(", ");case"radio":case"select":{const s=t.options.filter(t=>t.value===e)[0];return s?s.text:null}}return"object"===typeof e&&null!==e?"…":e.toString()},previewExists(t){return this.$helper.isComponent(`k-${t}-field-preview`)},width(t){if(!t)return"auto";const e=t.toString().split("/");if(2!==e.length)return"auto";const s=Number(e[0]),i=Number(e[1]);return parseFloat(String(100/i*s)).toFixed(2)+"%"}}}},a08f:function(t,e,s){"use strict";s.r(e);var i=function(){var t=this,e=t.$createElement,s=t._self._c||e;return s("k-block-figure",{attrs:{caption:t.content.caption,"caption-marks":t.captionMarks,"empty-text":t.$t("field.blocks.video.placeholder")+" …","is-empty":!t.video,"empty-icon":"video"},on:{open:t.open,update:t.update}},[s("k-aspect-ratio",{attrs:{ratio:"16/9"}},[t.video?s("iframe",{attrs:{src:t.video}}):t._e()])],1)},n=[],a={computed:{captionMarks(){return this.field("caption",{marks:!0}).marks},video(){var t=this.content.url;if(!t)return!1;var e=/^.*(youtu.be\/|v\/|u\/\w\/|embed\/|watch\?v=|&v=)([^#&?]*).*/,s=t.match(e);if(s)return"https://www.youtube.com/embed/"+s[2];var i=/vimeo\.com\/([0-9]+)/,n=t.match(i);return!!n&&"https://player.vimeo.com/video/"+n[1]}}},r=a,o=s("2877"),l=Object(o["a"])(r,i,n,!1,null,null,null);e["default"]=l.exports},a11f:function(t,e,s){},a134:function(t,e,s){"use strict";s("b329")},a2eb:function(t,e,s){},a567:function(t,e,s){"use strict";s("6ae8")},a5f3:function(t,e,s){"use strict";s("1174")},a659:function(t,e,s){"use strict";s("86eb")},a66d:function(t,e,s){"use strict";s("f464")},a901:function(t,e,s){},aa34:function(t,e,s){},ac27:function(t,e,s){"use strict";s("3dcf")},ae14:function(t,e,s){"use strict";s("7997")},ae1b:function(t,e,s){"use strict";s("e928")},ae29:function(t,e,s){},af44:function(t,e,s){"use strict";s("907a")},b0d6:function(t,e,s){"use strict";s("c6d0")},b231:function(t,e,s){},b329:function(t,e,s){},b5d2:function(t,e,s){"use strict";s("526c")},b746:function(t,e,s){"use strict";s("7187")},b7a8:function(t,e,s){"use strict";s("9efc")},b846:function(t,e,s){},ba8f:function(t,e,s){"use strict";s("0643")},bad3:function(t,e,s){},bb41:function(t,e,s){"use strict";s("49b6")},bbdf:function(t,e,s){"use strict";s("e729")},bd96:function(t,e,s){"use strict";s("e947")},bf53:function(t,e,s){"use strict";s("ecf0")},c08c:function(t,e,s){},c119:function(t,e,s){"use strict";s("8065")},c130:function(t,e,s){},c21a:function(t,e,s){"use strict";s("c577")},c24c:function(t,e,s){},c362:function(t,e,s){},c577:function(t,e,s){},c57e:function(t,e,s){},c6d0:function(t,e,s){},c7c8:function(t,e,s){"use strict";s("7086")},c82a:function(t,e,s){},c857:function(t,e,s){"use strict";s("022d")},c9cb:function(t,e,s){"use strict";s("577a")},ca47:function(t,e,s){},cb5b:function(t,e,s){},cb8f:function(t,e,s){"use strict";s("82b4")},cc79:function(t,e,s){"use strict";s("0de1")},cca8:function(t,e,s){"use strict";s("b846")},ccf4:function(t,e,s){},d0c1:function(t,e,s){"use strict";s("d4e5")},d221:function(t,e,s){"use strict";s("1a8b")},d3bb:function(t,e,s){},d49c:function(t,e,s){},d4e5:function(t,e,s){},d55e:function(t,e,s){"use strict";s.r(e);var i=function(){var t=this,e=t.$createElement,s=t._self._c||e;return s("div",{staticClass:"k-block-type-quote-editor"},[s("k-writer",{ref:"text",staticClass:"k-block-type-quote-text",attrs:{inline:!0,marks:t.textField.marks,placeholder:t.textField.placeholder,value:t.content.text},on:{input:function(e){return t.update({text:e})}}}),s("k-writer",{ref:"citation",staticClass:"k-block-type-quote-citation",attrs:{inline:!0,marks:t.citationField.marks,placeholder:t.citationField.placeholder,value:t.content.citation},on:{input:function(e){return t.update({citation:e})}}})],1)},n=[],a={computed:{citationField(){return this.field("citation",{})},textField(){return this.field("text",{})}},methods:{focus(){this.$refs.text.focus()}}},r=a,o=(s("91a7"),s("2877")),l=Object(o["a"])(r,i,n,!1,null,null,null);e["default"]=l.exports},d6fc:function(t,e,s){"use strict";s("877d")},d733:function(t,e,s){},daa8:function(t,e,s){"use strict";s("04f3")},dc40:function(t,e,s){"use strict";s.r(e);var i=function(){var t=this,e=t.$createElement,s=t._self._c||e;return s("k-writer",{ref:"input",staticClass:"k-block-type-text-input",attrs:{inline:t.textField.inline,marks:t.textField.marks,nodes:t.textField.nodes,placeholder:t.textField.placeholder,value:t.content.text},on:{input:function(e){return t.update({text:e})}}})},n=[],a={computed:{textField(){return this.field("text",{})}},methods:{focus(){this.$refs.input.focus()}}},r=a,o=(s("f293"),s("2877")),l=Object(o["a"])(r,i,n,!1,null,null,null);e["default"]=l.exports},dd70:function(t,e,s){"use strict";s("a901")},ddfd:function(t,e,s){"use strict";s("7491")},df0d:function(t,e,s){"use strict";s("5c47")},e1f3:function(t,e,s){"use strict";s("3b07")},e292:function(t,e,s){},e2d7:function(t,e,s){},e54a:function(t,e,s){"use strict";s.r(e);var i=function(){var t=this,e=t.$createElement,s=t._self._c||e;return s("table",{staticClass:"k-block-type-table-preview",on:{dblclick:t.open}},[s("tr",t._l(t.columns,(function(e,i){return s("th",{key:i,style:"width:"+t.width(e.width),attrs:{"data-align":e.align}},[t._v(" "+t._s(e.label)+" ")])})),0),0===t.rows.length?s("tr",[s("td",{attrs:{colspan:t.columnsCount}},[s("small",{staticClass:"k-block-type-table-preview-empty"},[t._v(t._s(t.$t("field.structure.empty")))])])]):t._l(t.rows,(function(e,i){return s("tr",{key:i},t._l(t.columns,(function(n,a){return s("td",{key:i+"-"+a,style:"width:"+t.width(n.width),attrs:{"data-align":n.align}},[t.previewExists(n.type)?s("k-"+n.type+"-field-preview",{tag:"component",attrs:{value:e[a],column:n,field:t.fields[a]}}):[s("p",{staticClass:"k-structure-table-text"},[t._v(" "+t._s(n.before)+" "+t._s(t.displayText(t.fields[a],e[a])||"–")+" "+t._s(n.after)+" ")])]],2)})),0)}))],2)},n=[],a=s("9fc0"),r={mixins:[a["a"]],inheritAttrs:!1,computed:{columns(){return this.table.columns||this.fields},columnsCount(){return Object.keys(this.columns).length},fields(){return this.table.fields||{}},rows(){return this.content.rows||[]},table(){let t=null;return Object.values(this.fieldset.tabs).forEach(e=>{e.fields.rows&&(t=e.fields.rows)}),t||{}}}},o=r,l=(s("f635"),s("2877")),c=Object(l["a"])(o,i,n,!1,null,null,null);e["default"]=c.exports},e661:function(t,e,s){},e729:function(t,e,s){},e791:function(t,e,s){"use strict";s("2283")},e863:function(t,e,s){},e928:function(t,e,s){},e947:function(t,e,s){},ecf0:function(t,e,s){},ee15:function(t,e,s){"use strict";s("4c2e")},ee92:function(t,e,s){},efe1:function(t,e,s){},f0f6:function(t,e,s){},f13c:function(t,e,s){},f239:function(t,e,s){},f293:function(t,e,s){"use strict";s("c24c")},f3da:function(t,e,s){},f464:function(t,e,s){},f470:function(t,e,s){"use strict";s("cb5b")},f56d:function(t,e,s){"use strict";s("7041")},f635:function(t,e,s){"use strict";s("4bcb")},f6c2:function(t,e,s){"use strict";s("0c44")},f7f5:function(t,e,s){"use strict";s("7b55")},f8a7:function(t,e,s){"use strict";s("4d5b")},f95f:function(t,e,s){"use strict";s("0756")},f9aa:function(t,e,s){"use strict";s("5f43")},f9c1:function(t,e,s){},fa6a:function(t,e,s){"use strict";s("5e77")},fc0f:function(t,e,s){"use strict";s("a11f")},fdbf:function(t,e,s){}}); \ No newline at end of file diff --git a/kirby/panel/dist/js/plugins.js b/kirby/panel/dist/js/plugins.js new file mode 100644 index 0000000..aeafda1 --- /dev/null +++ b/kirby/panel/dist/js/plugins.js @@ -0,0 +1,86 @@ + +window.panel = window.panel || {}; +window.panel.plugins = { + components: {}, + created: [], + icons: {}, + routes: [], + use: [], + views: {}, + thirdParty: {} +}; + +window.panel.plugin = function (plugin, parts) { + // Blocks + resolve(parts, "blocks", function (name, options) { + if (typeof options === "string") { + options = { template: options }; + } + + window.panel.plugins["components"][`k-block-type-${name}`] = { + extends: "k-block-type", + ...options + }; + }); + + // Components + resolve(parts, "components", function (name, options) { + window.panel.plugins["components"][name] = options; + }); + + // Fields + resolve(parts, "fields", function (name, options) { + window.panel.plugins["components"][`k-${name}-field`] = options; + }); + + // Icons + resolve(parts, "icons", function (name, options) { + window.panel.plugins["icons"][name] = options; + }); + + // Sections + resolve(parts, "sections", function (name, options) { + window.panel.plugins["components"][`k-${name}-section`] = { + ...options, + mixins: ["section"].concat(options.mixins || []) + }; + }); + + // Vue.use + resolve(parts, "use", function (name, options) { + window.panel.plugins["use"].push(options); + }); + + // created callback + if (parts["created"]) { + window.panel.plugins["created"].push(parts["created"]); + } + + // Views + resolve(parts, "views", function (name, options) { + window.panel.plugins["views"][name] = options; + }); + + // Login + if (parts.login) { + window.panel.plugins.login = parts.login; + } + + // Third-party plugins + resolve(parts, "thirdParty", function(name, options) { + window.panel.plugins["thirdParty"][name] = options; + }); + +}; + +function resolve(object, type, callback) { + if (object[type]) { + + if (Object.entries) { + Object.entries(object[type]).forEach(function ([name, options]) { + callback(name, options); + }); + } + + } +} diff --git a/kirby/panel/dist/js/vendor.js b/kirby/panel/dist/js/vendor.js new file mode 100644 index 0000000..4b975f1 --- /dev/null +++ b/kirby/panel/dist/js/vendor.js @@ -0,0 +1,30 @@ +(window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-vendors"],{"0010":function(t,e,n){"use strict";n.d(e,"a",(function(){return N})),n.d(e,"b",(function(){return C})),n.d(e,"c",(function(){return m})),n.d(e,"d",(function(){return O})),n.d(e,"e",(function(){return k}));var r=n("0ac0"),o=n("304a"),i=n("5313");function a(t,e){return!t.selection.empty&&(e&&e(t.tr.deleteSelection().scrollIntoView()),!0)}function s(t,e,n){var o=t.selection,a=o.$cursor;if(!a||(n?!n.endOfTextblock("backward",t):a.parentOffset>0))return!1;var s=l(a);if(!s){var u=a.blockRange(),f=u&&Object(r["j"])(u);return null!=f&&(e&&e(t.tr.lift(u,f).scrollIntoView()),!0)}var p=s.nodeBefore;if(!p.type.spec.isolating&&S(t,s,e))return!0;if(0==a.parent.content.size&&(c(p,"end")||i["c"].isSelectable(p))){if(e){var d=t.tr.deleteRange(a.before(),a.after());d.setSelection(c(p,"end")?i["f"].findFrom(d.doc.resolve(d.mapping.map(s.pos,-1)),-1):i["c"].create(d.doc,s.pos-p.nodeSize)),e(d.scrollIntoView())}return!0}return!(!p.isAtom||s.depth!=a.depth-1)&&(e&&e(t.tr.delete(s.pos-p.nodeSize,s.pos).scrollIntoView()),!0)}function c(t,e){for(;t;t="start"==e?t.firstChild:t.lastChild)if(t.isTextblock)return!0;return!1}function u(t,e,n){var r=t.selection,o=r.$head,a=r.empty,s=o;if(!a)return!1;if(o.parent.isTextblock){if(n?!n.endOfTextblock("backward",t):o.parentOffset>0)return!1;s=l(o)}var c=s&&s.nodeBefore;return!(!c||!i["c"].isSelectable(c))&&(e&&e(t.tr.setSelection(i["c"].create(t.doc,s.pos-c.nodeSize)).scrollIntoView()),!0)}function l(t){if(!t.parent.type.spec.isolating)for(var e=t.depth-1;e>=0;e--){if(t.index(e)>0)return t.doc.resolve(t.before(e+1));if(t.node(e).type.spec.isolating)break}return null}function f(t,e,n){var r=t.selection,o=r.$cursor;if(!o||(n?!n.endOfTextblock("forward",t):o.parentOffset=0;e--){var n=t.node(e);if(t.index(e)+11&&o.after()!=o.end(-1)){var i=o.before();if(Object(r["f"])(t.doc,i))return e&&e(t.tr.split(i).scrollIntoView()),!0}var a=o.blockRange(),s=a&&Object(r["j"])(a);return null!=s&&(e&&e(t.tr.lift(a,s).scrollIntoView()),!0)}function b(t,e){var n=t.selection,a=n.$from,s=n.$to;if(t.selection instanceof i["c"]&&t.selection.node.isBlock)return!(!a.parentOffset||!Object(r["f"])(t.doc,a.pos))&&(e&&e(t.tr.split(a.pos).scrollIntoView()),!0);if(!a.parent.isBlock)return!1;if(e){var c=s.parentOffset==s.parent.content.size,u=t.tr;t.selection instanceof i["g"]&&u.deleteSelection();var l=0==a.depth?null:v(a.node(-1).contentMatchAt(a.indexAfter(-1))),f=c&&l?[{type:l}]:null,p=Object(r["f"])(u.doc,u.mapping.map(a.pos),1,f);f||p||!Object(r["f"])(u.doc,u.mapping.map(a.pos),1,l&&[{type:l}])||(f=[{type:l}],p=!0),p&&(u.split(u.mapping.map(a.pos),1,f),c||a.parentOffset||a.parent.type==l||!a.node(-1).canReplace(a.index(-1),a.indexAfter(-1),o["c"].from(l.create(),a.parent))||u.setNodeMarkup(u.mapping.map(a.before()),l)),e(u.scrollIntoView())}return!0}function w(t,e){return e&&e(t.tr.setSelection(new i["a"](t.doc))),!0}function x(t,e,n){var o=e.nodeBefore,i=e.nodeAfter,a=e.index();return!!(o&&i&&o.type.compatibleContent(i.type))&&(!o.content.size&&e.parent.canReplace(a-1,a)?(n&&n(t.tr.delete(e.pos-o.nodeSize,e.pos).scrollIntoView()),!0):!(!e.parent.canReplace(a,a+1)||!i.isTextblock&&!Object(r["e"])(t.doc,e.pos))&&(n&&n(t.tr.clearIncompatible(e.pos,o.type,o.contentMatchAt(o.childCount)).join(e.pos).scrollIntoView()),!0))}function S(t,e,n){var a,s,c=e.nodeBefore,u=e.nodeAfter;if(c.type.spec.isolating||u.type.spec.isolating)return!1;if(x(t,e,n))return!0;if(e.parent.canReplace(e.index(),e.index()+1)&&(a=(s=c.contentMatchAt(c.childCount)).findWrapping(u.type))&&s.matchType(a[0]||u.type).validEnd){if(n){for(var l=e.pos+u.nodeSize,f=o["c"].empty,p=a.length-1;p>=0;p--)f=o["c"].from(a[p].create(null,f));f=o["c"].from(c.copy(f));var d=t.tr.step(new r["b"](e.pos-1,l,e.pos,l,new o["j"](f,1,0),a.length,!0)),h=l+2*a.length;Object(r["e"])(d.doc,h)&&d.join(h),n(d.scrollIntoView())}return!0}var v=i["f"].findFrom(e,1),m=v&&v.$from.blockRange(v.$to),g=m&&Object(r["j"])(m);return null!=g&&g>=e.depth&&(n&&n(t.tr.lift(m,g).scrollIntoView()),!0)}function O(t,e){return function(n,r){var o=n.selection,i=o.from,a=o.to,s=!1;return n.doc.nodesBetween(i,a,(function(r,o){if(s)return!1;if(r.isTextblock&&!r.hasMarkup(t,e))if(r.type==t)s=!0;else{var i=n.doc.resolve(o),a=i.index();s=i.parent.canReplaceWith(a,a+1,t)}})),!!s&&(r&&r(n.tr.setBlockType(i,a,t,e).scrollIntoView()),!0)}}function _(t,e,n){for(var r=function(r){var o=e[r],i=o.$from,a=o.$to,s=0==i.depth&&t.type.allowsMarkType(n);if(t.nodesBetween(i.pos,a.pos,(function(t){if(s)return!1;s=t.inlineContent&&t.type.allowsMarkType(n)})),s)return{v:!0}},o=0;ot)break;var l=this.ranges[s+o],f=this.ranges[s+i],p=c+l;if(t<=p){var d=l?t==c?-1:t==p?1:e:e,h=c+r+(d<0?0:f);if(n)return h;var v=t==(e<0?c:p)?null:a(s/3,t-c);return new u(h,e<0?t!=c:t!=p,v)}r+=f-l}return n?t+r:new u(t+r)},l.prototype.touches=function(t,e){for(var n=0,r=s(e),o=this.inverted?2:1,i=this.inverted?1:2,a=0;at)break;var u=this.ranges[a+o],l=c+u;if(t<=l&&a==3*r)return!0;n+=this.ranges[a+i]-u}return!1},l.prototype.forEach=function(t){for(var e=this.inverted?2:1,n=this.inverted?1:2,r=0,o=0;r=0;e--){var r=t.getMirror(e);this.appendMap(t.maps[e].invert(),null!=r&&r>e?n-r-1:null)}},f.prototype.invert=function(){var t=new f;return t.appendMappingInverted(this),t},f.prototype.map=function(t,e){if(void 0===e&&(e=1),this.mirror)return this._map(t,e,!0);for(var n=this.from;no&&s0},d.prototype.addStep=function(t,e){this.docs.push(this.doc),this.steps.push(t),this.mapping.appendMap(t.getMap()),this.doc=e},Object.defineProperties(d.prototype,h);var m=Object.create(null),g=function(){};g.prototype.apply=function(t){return v()},g.prototype.getMap=function(){return l.empty},g.prototype.invert=function(t){return v()},g.prototype.map=function(t){return v()},g.prototype.merge=function(t){return null},g.prototype.toJSON=function(){return v()},g.fromJSON=function(t,e){if(!e||!e.stepType)throw new RangeError("Invalid input for Step.fromJSON");var n=m[e.stepType];if(!n)throw new RangeError("No step type "+e.stepType+" defined");return n.fromJSON(t,e)},g.jsonID=function(t,e){if(t in m)throw new RangeError("Duplicate use of step JSON ID "+t);return m[t]=e,e.prototype.jsonID=t,e};var y=function(t,e){this.doc=t,this.failed=e};y.ok=function(t){return new y(t,null)},y.fail=function(t){return new y(null,t)},y.fromReplace=function(t,e,n,o){try{return y.ok(t.replace(e,n,o))}catch(i){if(i instanceof r["h"])return y.fail(i.message);throw i}};var b=function(t){function e(e,n,r,o){t.call(this),this.from=e,this.to=n,this.slice=r,this.structure=!!o}return t&&(e.__proto__=t),e.prototype=Object.create(t&&t.prototype),e.prototype.constructor=e,e.prototype.apply=function(t){return this.structure&&x(t,this.from,this.to)?y.fail("Structure replace would overwrite content"):y.fromReplace(t,this.from,this.to,this.slice)},e.prototype.getMap=function(){return new l([this.from,this.to-this.from,this.slice.size])},e.prototype.invert=function(t){return new e(this.from,this.from+this.slice.size,t.slice(this.from,this.to))},e.prototype.map=function(t){var n=t.mapResult(this.from,1),r=t.mapResult(this.to,-1);return n.deleted&&r.deleted?null:new e(n.pos,Math.max(n.pos,r.pos),this.slice)},e.prototype.merge=function(t){if(!(t instanceof e)||t.structure!=this.structure)return null;if(this.from+this.slice.size!=t.from||this.slice.openEnd||t.slice.openStart){if(t.to!=this.from||this.slice.openStart||t.slice.openEnd)return null;var n=this.slice.size+t.slice.size==0?r["j"].empty:new r["j"](t.slice.content.append(this.slice.content),t.slice.openStart,this.slice.openEnd);return new e(t.from,this.to,n,this.structure)}var o=this.slice.size+t.slice.size==0?r["j"].empty:new r["j"](this.slice.content.append(t.slice.content),this.slice.openStart,t.slice.openEnd);return new e(this.from,this.to+(t.to-t.from),o,this.structure)},e.prototype.toJSON=function(){var t={stepType:"replace",from:this.from,to:this.to};return this.slice.size&&(t.slice=this.slice.toJSON()),this.structure&&(t.structure=!0),t},e.fromJSON=function(t,n){if("number"!=typeof n.from||"number"!=typeof n.to)throw new RangeError("Invalid input for ReplaceStep.fromJSON");return new e(n.from,n.to,r["j"].fromJSON(t,n.slice),!!n.structure)},e}(g);g.jsonID("replace",b);var w=function(t){function e(e,n,r,o,i,a,s){t.call(this),this.from=e,this.to=n,this.gapFrom=r,this.gapTo=o,this.slice=i,this.insert=a,this.structure=!!s}return t&&(e.__proto__=t),e.prototype=Object.create(t&&t.prototype),e.prototype.constructor=e,e.prototype.apply=function(t){if(this.structure&&(x(t,this.from,this.gapFrom)||x(t,this.gapTo,this.to)))return y.fail("Structure gap-replace would overwrite content");var e=t.slice(this.gapFrom,this.gapTo);if(e.openStart||e.openEnd)return y.fail("Gap is not a flat range");var n=this.slice.insertAt(this.insert,e.content);return n?y.fromReplace(t,this.from,this.to,n):y.fail("Content does not fit in gap")},e.prototype.getMap=function(){return new l([this.from,this.gapFrom-this.from,this.insert,this.gapTo,this.to-this.gapTo,this.slice.size-this.insert])},e.prototype.invert=function(t){var n=this.gapTo-this.gapFrom;return new e(this.from,this.from+this.slice.size+n,this.from+this.insert,this.from+this.insert+n,t.slice(this.from,this.to).removeBetween(this.gapFrom-this.from,this.gapTo-this.from),this.gapFrom-this.from,this.structure)},e.prototype.map=function(t){var n=t.mapResult(this.from,1),r=t.mapResult(this.to,-1),o=t.map(this.gapFrom,-1),i=t.map(this.gapTo,1);return n.deleted&&r.deleted||or.pos?null:new e(n.pos,r.pos,o,i,this.slice,this.insert,this.structure)},e.prototype.toJSON=function(){var t={stepType:"replaceAround",from:this.from,to:this.to,gapFrom:this.gapFrom,gapTo:this.gapTo,insert:this.insert};return this.slice.size&&(t.slice=this.slice.toJSON()),this.structure&&(t.structure=!0),t},e.fromJSON=function(t,n){if("number"!=typeof n.from||"number"!=typeof n.to||"number"!=typeof n.gapFrom||"number"!=typeof n.gapTo||"number"!=typeof n.insert)throw new RangeError("Invalid input for ReplaceAroundStep.fromJSON");return new e(n.from,n.to,n.gapFrom,n.gapTo,r["j"].fromJSON(t,n.slice),n.insert,!!n.structure)},e}(g);function x(t,e,n){var r=t.resolve(e),o=n-e,i=r.depth;while(o>0&&i>0&&r.indexAfter(i)==r.node(i).childCount)i--,o--;if(o>0){var a=r.node(i).maybeChild(r.indexAfter(i));while(o>0){if(!a||a.isLeaf)return!0;a=a.firstChild,o--}}return!1}function S(t,e,n){return(0==e||t.canReplace(e,t.childCount))&&(n==t.childCount||t.canReplace(0,n))}function O(t){for(var e=t.parent,n=e.content.cutByIndex(t.startIndex,t.endIndex),r=t.depth;;--r){var o=t.$from.node(r),i=t.$from.index(r),a=t.$to.indexAfter(r);if(ri;s--,c--){var u=o.node(s),l=o.index(s);if(u.type.spec.isolating)return!1;var f=u.content.cutByIndex(l,u.childCount),p=r&&r[c]||u;if(p!=u&&(f=f.replaceChild(0,p.type.create(p.attrs))),!u.canReplace(l+1,u.childCount)||!p.type.validContent(f))return!1}var d=o.indexAfter(i),h=r&&r[0];return o.node(i).canReplaceWith(d,d,h?h.type:o.node(i+1).type)}function T(t,e){var n=t.resolve(e),r=n.index();return $(n.nodeBefore,n.nodeAfter)&&n.parent.canReplace(r,r+1)}function $(t,e){return t&&e&&!t.isLeaf&&t.canAppend(e)}function D(t,e,n){void 0===n&&(n=-1);for(var r=t.resolve(e),o=r.depth;;o--){var i=void 0,a=void 0,s=r.index(o);if(o==r.depth?(i=r.nodeBefore,a=r.nodeAfter):n>0?(i=r.node(o+1),s++,a=r.node(o).maybeChild(s)):(i=r.node(o).maybeChild(s-1),a=r.node(o+1)),i&&!i.isTextblock&&$(i,a)&&r.node(o).canReplace(s,s+1))return e;if(0==o)break;e=n<0?r.before(o):r.after(o)}}function N(t,e,n){var r=t.resolve(e);if(r.parent.canReplaceWith(r.index(),r.index(),n))return e;if(0==r.parentOffset)for(var o=r.depth-1;o>=0;o--){var i=r.index(o);if(r.node(o).canReplaceWith(i,i,n))return r.before(o+1);if(i>0)return null}if(r.parentOffset==r.parent.content.size)for(var a=r.depth-1;a>=0;a--){var s=r.indexAfter(a);if(r.node(a).canReplaceWith(s,s,n))return r.after(a+1);if(s=0;s--){var c=s==r.depth?0:r.pos<=(r.start(s+1)+r.end(s+1))/2?-1:1,u=r.index(s)+(c>0?1:0);if(1==a?r.node(s).canReplace(u,u,o):r.node(s).contentMatchAt(u).findWrapping(o.firstChild.type))return 0==c?r.pos:c<0?r.before(s+1):r.after(s+1)}return null}function j(t,e,n){for(var o=[],i=0;ie;p--)d||n.index(p)>0?(d=!0,l=r["c"].from(n.node(p).copy(l)),f++):c--;for(var h=r["c"].empty,v=0,m=i,g=!1;m>e;m--)g||o.after(m+1)=0;o--)n=r["c"].from(e[o].type.create(e[o].attrs,n));var i=t.start,a=t.end;return this.step(new w(i,a,i,a,new r["j"](n,0,0),e.length,!0))},d.prototype.setBlockType=function(t,e,n,o){var i=this;if(void 0===e&&(e=t),!n.isTextblock)throw new RangeError("Type given to setBlockType should be a textblock");var a=this.steps.length;return this.doc.nodesBetween(t,e,(function(t,e){if(t.isTextblock&&!t.hasMarkup(n,o)&&E(i.doc,i.mapping.slice(a).map(e),n)){i.clearIncompatible(i.mapping.slice(a).map(e,1),n);var s=i.mapping.slice(a),c=s.map(e,1),u=s.map(e+t.nodeSize,1);return i.step(new w(c,u,c+1,u-1,new r["j"](r["c"].from(n.create(o,null,t.marks)),0,0),1,!0)),!1}})),this},d.prototype.setNodeMarkup=function(t,e,n,o){var i=this.doc.nodeAt(t);if(!i)throw new RangeError("No node at given position");e||(e=i.type);var a=e.create(n,null,o||i.marks);if(i.isLeaf)return this.replaceWith(t,t+i.nodeSize,a);if(!e.validContent(i.content))throw new RangeError("Invalid content for node type "+e.name);return this.step(new w(t,t+i.nodeSize,t+1,t+i.nodeSize-1,new r["j"](r["c"].from(a),0,0),1,!0))},d.prototype.split=function(t,e,n){void 0===e&&(e=1);for(var o=this.doc.resolve(t),i=r["c"].empty,a=r["c"].empty,s=o.depth,c=o.depth-e,u=e-1;s>c;s--,u--){i=r["c"].from(o.node(s).copy(i));var l=n&&n[u];a=r["c"].from(l?l.type.create(l.attrs,a):o.node(s).copy(a))}return this.step(new b(t,t,new r["j"](i.append(a),e,e),!0))},d.prototype.join=function(t,e){void 0===e&&(e=1);var n=new b(t-e,t+e,r["j"].empty,!0);return this.step(n)};var I=function(t){function e(e,n,r){t.call(this),this.from=e,this.to=n,this.mark=r}return t&&(e.__proto__=t),e.prototype=Object.create(t&&t.prototype),e.prototype.constructor=e,e.prototype.apply=function(t){var e=this,n=t.slice(this.from,this.to),o=t.resolve(this.from),i=o.node(o.sharedDepth(this.to)),a=new r["j"](j(n.content,(function(t,n){return n.type.allowsMarkType(e.mark.type)?t.mark(e.mark.addToSet(t.marks)):t}),i),n.openStart,n.openEnd);return y.fromReplace(t,this.from,this.to,a)},e.prototype.invert=function(){return new R(this.from,this.to,this.mark)},e.prototype.map=function(t){var n=t.mapResult(this.from,1),r=t.mapResult(this.to,-1);return n.deleted&&r.deleted||n.pos>=r.pos?null:new e(n.pos,r.pos,this.mark)},e.prototype.merge=function(t){if(t instanceof e&&t.mark.eq(this.mark)&&this.from<=t.to&&this.to>=t.from)return new e(Math.min(this.from,t.from),Math.max(this.to,t.to),this.mark)},e.prototype.toJSON=function(){return{stepType:"addMark",mark:this.mark.toJSON(),from:this.from,to:this.to}},e.fromJSON=function(t,n){if("number"!=typeof n.from||"number"!=typeof n.to)throw new RangeError("Invalid input for AddMarkStep.fromJSON");return new e(n.from,n.to,t.markFromJSON(n.mark))},e}(g);g.jsonID("addMark",I);var R=function(t){function e(e,n,r){t.call(this),this.from=e,this.to=n,this.mark=r}return t&&(e.__proto__=t),e.prototype=Object.create(t&&t.prototype),e.prototype.constructor=e,e.prototype.apply=function(t){var e=this,n=t.slice(this.from,this.to),o=new r["j"](j(n.content,(function(t){return t.mark(e.mark.removeFromSet(t.marks))})),n.openStart,n.openEnd);return y.fromReplace(t,this.from,this.to,o)},e.prototype.invert=function(){return new I(this.from,this.to,this.mark)},e.prototype.map=function(t){var n=t.mapResult(this.from,1),r=t.mapResult(this.to,-1);return n.deleted&&r.deleted||n.pos>=r.pos?null:new e(n.pos,r.pos,this.mark)},e.prototype.merge=function(t){if(t instanceof e&&t.mark.eq(this.mark)&&this.from<=t.to&&this.to>=t.from)return new e(Math.min(this.from,t.from),Math.max(this.to,t.to),this.mark)},e.prototype.toJSON=function(){return{stepType:"removeMark",mark:this.mark.toJSON(),from:this.from,to:this.to}},e.fromJSON=function(t,n){if("number"!=typeof n.from||"number"!=typeof n.to)throw new RangeError("Invalid input for RemoveMarkStep.fromJSON");return new e(n.from,n.to,t.markFromJSON(n.mark))},e}(g);function L(t,e,n,o){if(void 0===n&&(n=e),void 0===o&&(o=r["j"].empty),e==n&&!o.size)return null;var i=t.resolve(e),a=t.resolve(n);return z(i,a,o)?new b(e,n,o):new F(i,a,o).fit()}function z(t,e,n){return!n.openStart&&!n.openEnd&&t.start()==e.start()&&t.parent.canReplace(t.index(),e.index(),n.content)}g.jsonID("removeMark",R),d.prototype.addMark=function(t,e,n){var r=this,o=[],i=[],a=null,s=null;return this.doc.nodesBetween(t,e,(function(r,c,u){if(r.isInline){var l=r.marks;if(!n.isInSet(l)&&u.type.allowsMarkType(n.type)){for(var f=Math.max(c,t),p=Math.min(c+r.nodeSize,e),d=n.addToSet(l),h=0;h=0;d--)this.step(i[d]);return this},d.prototype.replace=function(t,e,n){void 0===e&&(e=t),void 0===n&&(n=r["j"].empty);var o=L(this.doc,t,e,n);return o&&this.step(o),this},d.prototype.replaceWith=function(t,e,n){return this.replace(t,e,new r["j"](r["c"].from(n),0,0))},d.prototype.delete=function(t,e){return this.replace(t,e,r["j"].empty)},d.prototype.insert=function(t,e){return this.replaceWith(t,t,e)};var F=function(t,e,n){this.$to=e,this.$from=t,this.unplaced=n,this.frontier=[];for(var o=0;o<=t.depth;o++){var i=t.node(o);this.frontier.push({type:i.type,match:i.contentMatchAt(t.indexAfter(o))})}this.placed=r["c"].empty;for(var a=t.depth;a>0;a--)this.placed=r["c"].from(t.node(a).copy(this.placed))},B={depth:{configurable:!0}};function V(t,e,n){return 0==e?t.cutByIndex(n):t.replaceChild(0,t.firstChild.copy(V(t.firstChild.content,e-1,n)))}function q(t,e,n){return 0==e?t.append(n):t.replaceChild(t.childCount-1,t.lastChild.copy(q(t.lastChild.content,e-1,n)))}function H(t,e){for(var n=0;n1&&(o=o.replaceChild(0,W(o.firstChild,e-1,1==o.childCount?n-1:0))),e>0&&(o=t.type.contentMatch.fillBefore(o).append(o),n<=0&&(o=o.append(t.type.contentMatch.matchFragment(o).fillBefore(r["c"].empty,!0)))),t.copy(o)}function U(t,e,n,r,o){var i=t.node(e),a=o?t.indexAfter(e):t.index(e);if(a==i.childCount&&!n.compatibleContent(i.type))return null;var s=r.fillBefore(i.content,!0,a);return s&&!K(n,i.content,a)?s:null}function K(t,e,n){for(var r=n;ro){var s=i.contentMatchAt(0),c=s.fillBefore(t).append(t);t=c.append(s.matchFragment(c).fillBefore(r["c"].empty,!0))}return t}function Y(t,e){for(var n=[],r=Math.min(t.depth,e.depth),o=r;o>=0;o--){var i=t.start(o);if(ie.pos+(e.depth-o)||t.node(o).type.spec.isolating||e.node(o).type.spec.isolating)break;i==e.start(o)&&n.push(o)}return n}B.depth.get=function(){return this.frontier.length-1},F.prototype.fit=function(){while(this.unplaced.size){var t=this.findFittable();t?this.placeNodes(t):this.openMore()||this.dropNode()}var e=this.mustMoveInline(),n=this.placed.size-this.depth-this.$from.depth,o=this.$from,i=this.close(e<0?this.$to:o.doc.resolve(e));if(!i)return null;var a=this.placed,s=o.depth,c=i.depth;while(s&&c&&1==a.childCount)a=a.firstChild.content,s--,c--;var u=new r["j"](a,s,c);return e>-1?new w(o.pos,e,this.$to.pos,this.$to.end(),u,n):u.size||o.pos!=this.$to.pos?new b(o.pos,i.pos,u):void 0},F.prototype.findFittable=function(){for(var t=1;t<=2;t++)for(var e=this.unplaced.openStart;e>=0;e--){var n=void 0,o=void 0;e?(o=H(this.unplaced.content,e-1).firstChild,n=o.content):n=this.unplaced.content;for(var i=n.firstChild,a=this.depth;a>=0;a--){var s=this.frontier[a],c=s.type,u=s.match,l=void 0,f=void 0;if(1==t&&(i?u.matchType(i.type)||(f=u.fillBefore(r["c"].from(i),!1)):c.compatibleContent(o.type)))return{sliceDepth:e,frontierDepth:a,parent:o,inject:f};if(2==t&&i&&(l=u.findWrapping(i.type)))return{sliceDepth:e,frontierDepth:a,parent:o,wrap:l};if(o&&u.matchType(o.type))break}}},F.prototype.openMore=function(){var t=this.unplaced,e=t.content,n=t.openStart,o=t.openEnd,i=H(e,n);return!(!i.childCount||i.firstChild.isLeaf)&&(this.unplaced=new r["j"](e,n+1,Math.max(o,i.size+n>=e.size-o?n+1:0)),!0)},F.prototype.dropNode=function(){var t=this.unplaced,e=t.content,n=t.openStart,o=t.openEnd,i=H(e,n);if(i.childCount<=1&&n>0){var a=e.size-n<=n+i.size;this.unplaced=new r["j"](V(e,n-1,1),n-1,a?n-1:o)}else this.unplaced=new r["j"](V(e,n,1),n,o)},F.prototype.placeNodes=function(t){var e=t.sliceDepth,n=t.frontierDepth,o=t.parent,i=t.inject,a=t.wrap;while(this.depth>n)this.closeFrontierNode();if(a)for(var s=0;s1||0==l||y.content.size)&&(h=b,p.push(W(y.mark(v.allowedMarks(y.marks)),1==f?l:0,f==u.childCount?g:-1)))}var w=f==u.childCount;w||(g=-1),this.placed=q(this.placed,n,r["c"].from(p)),this.frontier[n].match=h,w&&g<0&&o&&o.type==this.frontier[this.depth].type&&this.frontier.length>1&&this.closeFrontierNode();for(var x=0,S=u;x1&&o==this.$to.end(--r))++o;return o},F.prototype.findCloseLevel=function(t){t:for(var e=Math.min(this.depth,t.depth);e>=0;e--){var n=this.frontier[e],r=n.match,o=n.type,i=e=0;s--){var c=this.frontier[s],u=c.match,l=c.type,f=U(t,s,l,u,!0);if(!f||f.childCount)continue t}return{depth:e,fit:a,move:i?t.doc.resolve(t.after(e+1)):t}}}},F.prototype.close=function(t){var e=this.findCloseLevel(t);if(!e)return null;while(this.depth>e.depth)this.closeFrontierNode();e.fit.childCount&&(this.placed=q(this.placed,e.depth,e.fit)),t=e.move;for(var n=e.depth+1;n<=t.depth;n++){var r=t.node(n),o=r.type.contentMatch.fillBefore(r.content,!0,t.index(n));this.openFrontierNode(r.type,r.attrs,o)}return t},F.prototype.openFrontierNode=function(t,e,n){var o=this.frontier[this.depth];o.match=o.match.matchType(t),this.placed=q(this.placed,this.depth,r["c"].from(t.create(e,n))),this.frontier.push({type:t,match:t.contentMatch})},F.prototype.closeFrontierNode=function(){var t=this.frontier.pop(),e=t.match.fillBefore(r["c"].empty,!0);e.childCount&&(this.placed=q(this.placed,this.frontier.length,e))},Object.defineProperties(F.prototype,B),d.prototype.replaceRange=function(t,e,n){if(!n.size)return this.deleteRange(t,e);var o=this.doc.resolve(t),i=this.doc.resolve(e);if(z(o,i,n))return this.step(new b(t,e,n));var a=Y(o,this.doc.resolve(e));0==a[a.length-1]&&a.pop();var s=-(o.depth+1);a.unshift(s);for(var c=o.depth,u=o.pos-1;c>0;c--,u--){var l=o.node(c).type.spec;if(l.defining||l.isolating)break;a.indexOf(c)>-1?s=c:o.before(c)==u&&a.splice(1,0,-c)}for(var f=a.indexOf(s),p=[],d=n.openStart,h=n.content,v=0;;v++){var m=h.firstChild;if(p.push(m),v==n.openStart)break;h=m.content}d>0&&p[d-1].type.spec.defining&&o.node(f).type!=p[d-1].type?d-=1:d>=2&&p[d-1].isTextblock&&p[d-2].type.spec.defining&&o.node(f).type!=p[d-2].type&&(d-=2);for(var g=n.openStart;g>=0;g--){var y=(g+d+1)%(n.openStart+1),w=p[y];if(w)for(var x=0;x=0;M--){if(this.replace(t,e,n),this.steps.length>C)break;var E=a[M];M<0||(t=o.before(E),e=i.after(E))}return this},d.prototype.replaceRangeWith=function(t,e,n){if(!n.isInline&&t==e&&this.doc.resolve(t).parent.content.size){var o=N(this.doc,t,n.type);null!=o&&(t=e=o)}return this.replaceRange(t,e,new r["j"](r["c"].from(n),0,0))},d.prototype.deleteRange=function(t,e){for(var n=this.doc.resolve(t),r=this.doc.resolve(e),o=Y(n,r),i=0;i0&&(s||n.node(a-1).canReplace(n.index(a-1),r.indexAfter(a-1))))return this.delete(n.before(a),r.after(a))}for(var c=1;c<=n.depth&&c<=r.depth;c++)if(t-n.start(c)==n.depth-c&&e>n.end(c)&&r.end(c)-e!=r.depth-c)return this.delete(n.before(c),e);return this.delete(t,e)}},"0cfb":function(t,e,n){var r=n("83ab"),o=n("d039"),i=n("cc12");t.exports=!r&&!o((function(){return 7!=Object.defineProperty(i("div"),"a",{get:function(){return 7}}).a}))},"0ecf":function(t,e,n){!function(e,n){t.exports=n()}(0,(function(){"use strict";return function(t,e,n){var r=e.prototype;n.utc=function(t){return new e({date:t,utc:!0,args:arguments})},r.utc=function(t){var e=n(this.toDate(),{locale:this.$L,utc:!0});return t?e.add(this.utcOffset(),"minute"):e},r.local=function(){return n(this.toDate(),{locale:this.$L,utc:!1})};var o=r.parse;r.parse=function(t){t.utc&&(this.$u=!0),this.$utils().u(t.$offset)||(this.$offset=t.$offset),o.call(this,t)};var i=r.init;r.init=function(){if(this.$u){var t=this.$d;this.$y=t.getUTCFullYear(),this.$M=t.getUTCMonth(),this.$D=t.getUTCDate(),this.$W=t.getUTCDay(),this.$H=t.getUTCHours(),this.$m=t.getUTCMinutes(),this.$s=t.getUTCSeconds(),this.$ms=t.getUTCMilliseconds()}else i.call(this)};var a=r.utcOffset;r.utcOffset=function(t,e){var n=this.$utils().u;if(n(t))return this.$u?0:n(this.$offset)?a.call(this):this.$offset;var r=Math.abs(t)<=16?60*t:t,o=this;if(e)return o.$offset=r,o.$u=0===t,o;if(0!==t){var i=this.$u?this.toDate().getTimezoneOffset():-1*this.utcOffset();(o=this.local().add(r+i,"minute")).$offset=r,o.$x.$localOffset=i}else o=this.utc();return o};var s=r.format;r.format=function(t){var e=t||(this.$u?"YYYY-MM-DDTHH:mm:ss[Z]":"");return s.call(this,e)},r.valueOf=function(){var t=this.$utils().u(this.$offset)?0:this.$offset+(this.$x.$localOffset||(new Date).getTimezoneOffset());return this.$d.valueOf()-6e4*t},r.isUTC=function(){return!!this.$u},r.toISOString=function(){return this.toDate().toISOString()},r.toString=function(){return this.toDate().toUTCString()};var c=r.toDate;r.toDate=function(t){return"s"===t&&this.$offset?n(this.format("YYYY-MM-DD HH:mm:ss:SSS")).toDate():c.call(this)};var u=r.diff;r.diff=function(t,e,r){if(this.$u===t.$u)return u.call(this,t,e,r);var o=this.local(),i=n(t).local();return u.call(o,i,e,r)}}}))},1331:function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0;var r=n("78ef"),o=(0,r.regex)("integer",/(^[0-9]*$)|(^-[0-9]+$)/);e.default=o},"19e9":function(t,e,n){var r,o,i; +/*! + autosize 4.0.2 + license: MIT + http://www.jacklmoore.com/autosize +*/(function(n,a){o=[t,e],r=a,i="function"===typeof r?r.apply(e,o):r,void 0===i||(t.exports=i)})(0,(function(t,e){"use strict";var n="function"===typeof Map?new Map:function(){var t=[],e=[];return{has:function(e){return t.indexOf(e)>-1},get:function(n){return e[t.indexOf(n)]},set:function(n,r){-1===t.indexOf(n)&&(t.push(n),e.push(r))},delete:function(n){var r=t.indexOf(n);r>-1&&(t.splice(r,1),e.splice(r,1))}}}(),r=function(t){return new Event(t,{bubbles:!0})};try{new Event("test")}catch(c){r=function(t){var e=document.createEvent("Event");return e.initEvent(t,!0,!1),e}}function o(t){if(t&&t.nodeName&&"TEXTAREA"===t.nodeName&&!n.has(t)){var e=null,o=null,i=null,a=function(){t.clientWidth!==o&&p()},s=function(e){window.removeEventListener("resize",a,!1),t.removeEventListener("input",p,!1),t.removeEventListener("keyup",p,!1),t.removeEventListener("autosize:destroy",s,!1),t.removeEventListener("autosize:update",p,!1),Object.keys(e).forEach((function(n){t.style[n]=e[n]})),n.delete(t)}.bind(t,{height:t.style.height,resize:t.style.resize,overflowY:t.style.overflowY,overflowX:t.style.overflowX,wordWrap:t.style.wordWrap});t.addEventListener("autosize:destroy",s,!1),"onpropertychange"in t&&"oninput"in t&&t.addEventListener("keyup",p,!1),window.addEventListener("resize",a,!1),t.addEventListener("input",p,!1),t.addEventListener("autosize:update",p,!1),t.style.overflowX="hidden",t.style.wordWrap="break-word",n.set(t,{destroy:s,update:p}),c()}function c(){var n=window.getComputedStyle(t,null);"vertical"===n.resize?t.style.resize="none":"both"===n.resize&&(t.style.resize="horizontal"),e="content-box"===n.boxSizing?-(parseFloat(n.paddingTop)+parseFloat(n.paddingBottom)):parseFloat(n.borderTopWidth)+parseFloat(n.borderBottomWidth),isNaN(e)&&(e=0),p()}function u(e){var n=t.style.width;t.style.width="0px",t.offsetWidth,t.style.width=n,t.style.overflowY=e}function l(t){var e=[];while(t&&t.parentNode&&t.parentNode instanceof Element)t.parentNode.scrollTop&&e.push({node:t.parentNode,scrollTop:t.parentNode.scrollTop}),t=t.parentNode;return e}function f(){if(0!==t.scrollHeight){var n=l(t),r=document.documentElement&&document.documentElement.scrollTop;t.style.height="",t.style.height=t.scrollHeight+e+"px",o=t.clientWidth,n.forEach((function(t){t.node.scrollTop=t.scrollTop})),r&&(document.documentElement.scrollTop=r)}}function p(){f();var e=Math.round(parseFloat(t.style.height)),n=window.getComputedStyle(t,null),o="content-box"===n.boxSizing?Math.round(parseFloat(n.height)):t.offsetHeight;if(o1?a:a.$sub[0]:null;return{output:i,params:s}}},computed:{run:function(){var t=this,e=this.lazyParentModel(),n=Array.isArray(e)&&e.__ob__;if(n){var r=e.__ob__.dep;r.depend();var o=r.constructor.target;if(!this._indirectWatcher){var i=o.constructor;this._indirectWatcher=new i(this,(function(){return t.runRule(e)}),null,{lazy:!0})}var a=this.getModel();if(!this._indirectWatcher.dirty&&this._lastModel===a)return this._indirectWatcher.depend(),o.value;this._lastModel=a,this._indirectWatcher.evaluate(),this._indirectWatcher.depend()}else this._indirectWatcher&&(this._indirectWatcher.teardown(),this._indirectWatcher=null);return this._indirectWatcher?this._indirectWatcher.value:this.runRule(e)},$params:function(){return this.run.params},proxy:function(){var t=this.run.output;return t[y]?!!t.v:!!t},$pending:function(){var t=this.run.output;return!!t[y]&&t.p}},destroyed:function(){this._indirectWatcher&&(this._indirectWatcher.teardown(),this._indirectWatcher=null)}}),a=e.extend({data:function(){return{dirty:!1,validations:null,lazyModel:null,model:null,prop:null,lazyParentModel:null,rootModel:null}},methods:u({},S,{refProxy:function(t){return this.getRef(t).proxy},getRef:function(t){return this.refs[t]},isNested:function(t){return"function"!==typeof this.validations[t]}}),computed:u({},w,{nestedKeys:function(){return this.keys.filter(this.isNested)},ruleKeys:function(){var t=this;return this.keys.filter((function(e){return!t.isNested(e)}))},keys:function(){return Object.keys(this.validations).filter((function(t){return"$params"!==t}))},proxy:function(){var t=this,e=d(this.keys,(function(e){return{enumerable:!0,configurable:!0,get:function(){return t.refProxy(e)}}})),n=d(O,(function(e){return{enumerable:!0,configurable:!0,get:function(){return t[e]}}})),r=d(_,(function(e){return{enumerable:!1,configurable:!0,get:function(){return t[e]}}})),o=this.hasIter()?{$iter:{enumerable:!0,value:Object.defineProperties({},u({},e))}}:{};return Object.defineProperties({},u({},e,o,{$model:{enumerable:!0,get:function(){var e=t.lazyParentModel();return null!=e?e[t.prop]:null},set:function(e){var n=t.lazyParentModel();null!=n&&(n[t.prop]=e,t.$touch())}}},n,r))},children:function(){var t=this;return i(this.nestedKeys.map((function(e){return l(t,e)}))).concat(i(this.ruleKeys.map((function(e){return f(t,e)})))).filter(Boolean)}})}),s=a.extend({methods:{isNested:function(t){return"undefined"!==typeof this.validations[t]()},getRef:function(t){var e=this;return{get proxy(){return e.validations[t]()||!1}}}}}),c=a.extend({computed:{keys:function(){var t=this.getModel();return v(t)?Object.keys(t):[]},tracker:function(){var t=this,e=this.validations.$trackBy;return e?function(n){return"".concat(g(t.rootModel,t.getModelKey(n),e))}:function(t){return"".concat(t)}},getModelLazy:function(){var t=this;return function(){return t.getModel()}},children:function(){var t=this,e=this.validations,n=this.getModel(),o=u({},e);delete o["$trackBy"];var i={};return this.keys.map((function(e){var s=t.tracker(e);return i.hasOwnProperty(s)?null:(i[s]=!0,(0,r.h)(a,s,{validations:o,prop:e,lazyParentModel:t.getModelLazy,model:n[e],rootModel:t.rootModel}))})).filter(Boolean)}},methods:{isNested:function(){return!0},getRef:function(t){return this.refs[this.tracker(t)]},hasIter:function(){return!0}}}),l=function(t,e){if("$each"===e)return(0,r.h)(c,e,{validations:t.validations[e],lazyParentModel:t.lazyParentModel,prop:e,lazyModel:t.getModel,rootModel:t.rootModel});var n=t.validations[e];if(Array.isArray(n)){var o=t.rootModel,i=d(n,(function(t){return function(){return g(o,o.$v,t)}}),(function(t){return Array.isArray(t)?t.join("."):t}));return(0,r.h)(s,e,{validations:i,lazyParentModel:p,prop:e,lazyModel:p,rootModel:o})}return(0,r.h)(a,e,{validations:n,lazyParentModel:t.getModel,prop:e,lazyModel:t.getModelKey,rootModel:t.rootModel})},f=function(t,e){return(0,r.h)(n,e,{rule:t.validations[e],lazyParentModel:t.lazyParentModel,lazyModel:t.getModel,rootModel:t.rootModel})};return k={VBase:e,Validation:a},k},M=null;function E(t){if(M)return M;var e=t.constructor;while(e.super)e=e.super;return M=e,e}var A=function(t,e){var n=E(t),o=C(n),i=o.Validation,a=o.VBase,s=new a({computed:{children:function(){var n="function"===typeof e?e.call(t):e;return[(0,r.h)(i,"$v",{validations:n,lazyParentModel:p,prop:"$v",model:t,rootModel:t})]}}});return s},T={data:function(){var t=this.$options.validations;return t&&(this._vuelidate=A(this,t)),{}},beforeCreate:function(){var t=this.$options,e=t.validations;e&&(t.computed||(t.computed={}),t.computed.$v||(t.computed.$v=function(){return this._vuelidate?this._vuelidate.refs.$v.proxy:null}))},beforeDestroy:function(){this._vuelidate&&(this._vuelidate.$destroy(),this._vuelidate=null)}};function $(t){t.mixin(T)}e.validationMixin=T;var D=$;e.default=D},"23cb":function(t,e,n){var r=n("a691"),o=Math.max,i=Math.min;t.exports=function(t,e){var n=r(t);return n<0?o(n+e,0):i(n,e)}},"23e7":function(t,e,n){var r=n("da84"),o=n("06cf").f,i=n("9112"),a=n("6eeb"),s=n("ce4e"),c=n("e893"),u=n("94ca");t.exports=function(t,e){var n,l,f,p,d,h,v=t.target,m=t.global,g=t.stat;if(l=m?r:g?r[v]||s(v,{}):(r[v]||{}).prototype,l)for(f in e){if(d=e[f],t.noTargetGet?(h=o(l,f),p=h&&h.value):p=l[f],n=u(m?f:v+(g?".":"#")+f,t.forced),!n&&void 0!==p){if(typeof d===typeof p)continue;c(d,p)}(t.sham||p&&p.sham)&&i(d,"sham",!0),a(l,f,d,t)}}},"241c":function(t,e,n){var r=n("ca84"),o=n("7839"),i=o.concat("length","prototype");e.f=Object.getOwnPropertyNames||function(t){return r(t,i)}},2877:function(t,e,n){"use strict";function r(t,e,n,r,o,i,a,s){var c,u="function"===typeof t?t.options:t;if(e&&(u.render=e,u.staticRenderFns=n,u._compiled=!0),r&&(u.functional=!0),i&&(u._scopeId="data-v-"+i),a?(c=function(t){t=t||this.$vnode&&this.$vnode.ssrContext||this.parent&&this.parent.$vnode&&this.parent.$vnode.ssrContext,t||"undefined"===typeof __VUE_SSR_CONTEXT__||(t=__VUE_SSR_CONTEXT__),o&&o.call(this,t),t&&t._registeredComponents&&t._registeredComponents.add(a)},u._ssrRegister=c):o&&(c=s?function(){o.call(this,(u.functional?this.parent:this).$root.$options.shadowRoot)}:o),c)if(u.functional){u._injectStyles=c;var l=u.render;u.render=function(t,e){return c.call(e),l(t,e)}}else{var f=u.beforeCreate;u.beforeCreate=f?[].concat(f,c):[c]}return{exports:t,options:u}}n.d(e,"a",(function(){return r}))},"2a12":function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0;var r=n("78ef"),o=function(t){return(0,r.withParams)({type:"maxLength",max:t},(function(e){return!(0,r.req)(e)||(0,r.len)(e)<=t}))};e.default=o},"2f62":function(t,e,n){"use strict";(function(t){ +/*! + * vuex v3.5.1 + * (c) 2020 Evan You + * @license MIT + */ +function n(t){var e=Number(t.version.split(".")[0]);if(e>=2)t.mixin({beforeCreate:r});else{var n=t.prototype._init;t.prototype._init=function(t){void 0===t&&(t={}),t.init=t.init?[r].concat(t.init):r,n.call(this,t)}}function r(){var t=this.$options;t.store?this.$store="function"===typeof t.store?t.store():t.store:t.parent&&t.parent.$store&&(this.$store=t.parent.$store)}}var r="undefined"!==typeof window?window:"undefined"!==typeof t?t:{},o=r.__VUE_DEVTOOLS_GLOBAL_HOOK__;function i(t){o&&(t._devtoolHook=o,o.emit("vuex:init",t),o.on("vuex:travel-to-state",(function(e){t.replaceState(e)})),t.subscribe((function(t,e){o.emit("vuex:mutation",t,e)}),{prepend:!0}),t.subscribeAction((function(t,e){o.emit("vuex:action",t,e)}),{prepend:!0}))}function a(t,e){return t.filter(e)[0]}function s(t,e){if(void 0===e&&(e=[]),null===t||"object"!==typeof t)return t;var n=a(e,(function(e){return e.original===t}));if(n)return n.copy;var r=Array.isArray(t)?[]:{};return e.push({original:t,copy:r}),Object.keys(t).forEach((function(n){r[n]=s(t[n],e)})),r}function c(t,e){Object.keys(t).forEach((function(n){return e(t[n],n)}))}function u(t){return null!==t&&"object"===typeof t}function l(t){return t&&"function"===typeof t.then}function f(t,e){return function(){return t(e)}}var p=function(t,e){this.runtime=e,this._children=Object.create(null),this._rawModule=t;var n=t.state;this.state=("function"===typeof n?n():n)||{}},d={namespaced:{configurable:!0}};d.namespaced.get=function(){return!!this._rawModule.namespaced},p.prototype.addChild=function(t,e){this._children[t]=e},p.prototype.removeChild=function(t){delete this._children[t]},p.prototype.getChild=function(t){return this._children[t]},p.prototype.hasChild=function(t){return t in this._children},p.prototype.update=function(t){this._rawModule.namespaced=t.namespaced,t.actions&&(this._rawModule.actions=t.actions),t.mutations&&(this._rawModule.mutations=t.mutations),t.getters&&(this._rawModule.getters=t.getters)},p.prototype.forEachChild=function(t){c(this._children,t)},p.prototype.forEachGetter=function(t){this._rawModule.getters&&c(this._rawModule.getters,t)},p.prototype.forEachAction=function(t){this._rawModule.actions&&c(this._rawModule.actions,t)},p.prototype.forEachMutation=function(t){this._rawModule.mutations&&c(this._rawModule.mutations,t)},Object.defineProperties(p.prototype,d);var h=function(t){this.register([],t,!1)};function v(t,e,n){if(e.update(n),n.modules)for(var r in n.modules){if(!e.getChild(r))return void 0;v(t.concat(r),e.getChild(r),n.modules[r])}}h.prototype.get=function(t){return t.reduce((function(t,e){return t.getChild(e)}),this.root)},h.prototype.getNamespace=function(t){var e=this.root;return t.reduce((function(t,n){return e=e.getChild(n),t+(e.namespaced?n+"/":"")}),"")},h.prototype.update=function(t){v([],this.root,t)},h.prototype.register=function(t,e,n){var r=this;void 0===n&&(n=!0);var o=new p(e,n);if(0===t.length)this.root=o;else{var i=this.get(t.slice(0,-1));i.addChild(t[t.length-1],o)}e.modules&&c(e.modules,(function(e,o){r.register(t.concat(o),e,n)}))},h.prototype.unregister=function(t){var e=this.get(t.slice(0,-1)),n=t[t.length-1],r=e.getChild(n);r&&r.runtime&&e.removeChild(n)},h.prototype.isRegistered=function(t){var e=this.get(t.slice(0,-1)),n=t[t.length-1];return e.hasChild(n)};var m;var g=function(t){var e=this;void 0===t&&(t={}),!m&&"undefined"!==typeof window&&window.Vue&&$(window.Vue);var n=t.plugins;void 0===n&&(n=[]);var r=t.strict;void 0===r&&(r=!1),this._committing=!1,this._actions=Object.create(null),this._actionSubscribers=[],this._mutations=Object.create(null),this._wrappedGetters=Object.create(null),this._modules=new h(t),this._modulesNamespaceMap=Object.create(null),this._subscribers=[],this._watcherVM=new m,this._makeLocalGettersCache=Object.create(null);var o=this,a=this,s=a.dispatch,c=a.commit;this.dispatch=function(t,e){return s.call(o,t,e)},this.commit=function(t,e,n){return c.call(o,t,e,n)},this.strict=r;var u=this._modules.root.state;S(this,u,[],this._modules.root),x(this,u),n.forEach((function(t){return t(e)}));var l=void 0!==t.devtools?t.devtools:m.config.devtools;l&&i(this)},y={state:{configurable:!0}};function b(t,e,n){return e.indexOf(t)<0&&(n&&n.prepend?e.unshift(t):e.push(t)),function(){var n=e.indexOf(t);n>-1&&e.splice(n,1)}}function w(t,e){t._actions=Object.create(null),t._mutations=Object.create(null),t._wrappedGetters=Object.create(null),t._modulesNamespaceMap=Object.create(null);var n=t.state;S(t,n,[],t._modules.root,!0),x(t,n,e)}function x(t,e,n){var r=t._vm;t.getters={},t._makeLocalGettersCache=Object.create(null);var o=t._wrappedGetters,i={};c(o,(function(e,n){i[n]=f(e,t),Object.defineProperty(t.getters,n,{get:function(){return t._vm[n]},enumerable:!0})}));var a=m.config.silent;m.config.silent=!0,t._vm=new m({data:{$$state:e},computed:i}),m.config.silent=a,t.strict&&E(t),r&&(n&&t._withCommit((function(){r._data.$$state=null})),m.nextTick((function(){return r.$destroy()})))}function S(t,e,n,r,o){var i=!n.length,a=t._modules.getNamespace(n);if(r.namespaced&&(t._modulesNamespaceMap[a],t._modulesNamespaceMap[a]=r),!i&&!o){var s=A(e,n.slice(0,-1)),c=n[n.length-1];t._withCommit((function(){m.set(s,c,r.state)}))}var u=r.context=O(t,a,n);r.forEachMutation((function(e,n){var r=a+n;k(t,r,e,u)})),r.forEachAction((function(e,n){var r=e.root?n:a+n,o=e.handler||e;C(t,r,o,u)})),r.forEachGetter((function(e,n){var r=a+n;M(t,r,e,u)})),r.forEachChild((function(r,i){S(t,e,n.concat(i),r,o)}))}function O(t,e,n){var r=""===e,o={dispatch:r?t.dispatch:function(n,r,o){var i=T(n,r,o),a=i.payload,s=i.options,c=i.type;return s&&s.root||(c=e+c),t.dispatch(c,a)},commit:r?t.commit:function(n,r,o){var i=T(n,r,o),a=i.payload,s=i.options,c=i.type;s&&s.root||(c=e+c),t.commit(c,a,s)}};return Object.defineProperties(o,{getters:{get:r?function(){return t.getters}:function(){return _(t,e)}},state:{get:function(){return A(t.state,n)}}}),o}function _(t,e){if(!t._makeLocalGettersCache[e]){var n={},r=e.length;Object.keys(t.getters).forEach((function(o){if(o.slice(0,r)===e){var i=o.slice(r);Object.defineProperty(n,i,{get:function(){return t.getters[o]},enumerable:!0})}})),t._makeLocalGettersCache[e]=n}return t._makeLocalGettersCache[e]}function k(t,e,n,r){var o=t._mutations[e]||(t._mutations[e]=[]);o.push((function(e){n.call(t,r.state,e)}))}function C(t,e,n,r){var o=t._actions[e]||(t._actions[e]=[]);o.push((function(e){var o=n.call(t,{dispatch:r.dispatch,commit:r.commit,getters:r.getters,state:r.state,rootGetters:t.getters,rootState:t.state},e);return l(o)||(o=Promise.resolve(o)),t._devtoolHook?o.catch((function(e){throw t._devtoolHook.emit("vuex:error",e),e})):o}))}function M(t,e,n,r){t._wrappedGetters[e]||(t._wrappedGetters[e]=function(t){return n(r.state,r.getters,t.state,t.getters)})}function E(t){t._vm.$watch((function(){return this._data.$$state}),(function(){0}),{deep:!0,sync:!0})}function A(t,e){return e.reduce((function(t,e){return t[e]}),t)}function T(t,e,n){return u(t)&&t.type&&(n=e,e=t,t=t.type),{type:t,payload:e,options:n}}function $(t){m&&t===m||(m=t,n(m))}y.state.get=function(){return this._vm._data.$$state},y.state.set=function(t){0},g.prototype.commit=function(t,e,n){var r=this,o=T(t,e,n),i=o.type,a=o.payload,s=(o.options,{type:i,payload:a}),c=this._mutations[i];c&&(this._withCommit((function(){c.forEach((function(t){t(a)}))})),this._subscribers.slice().forEach((function(t){return t(s,r.state)})))},g.prototype.dispatch=function(t,e){var n=this,r=T(t,e),o=r.type,i=r.payload,a={type:o,payload:i},s=this._actions[o];if(s){try{this._actionSubscribers.slice().filter((function(t){return t.before})).forEach((function(t){return t.before(a,n.state)}))}catch(u){0}var c=s.length>1?Promise.all(s.map((function(t){return t(i)}))):s[0](i);return new Promise((function(t,e){c.then((function(e){try{n._actionSubscribers.filter((function(t){return t.after})).forEach((function(t){return t.after(a,n.state)}))}catch(u){0}t(e)}),(function(t){try{n._actionSubscribers.filter((function(t){return t.error})).forEach((function(e){return e.error(a,n.state,t)}))}catch(u){0}e(t)}))}))}},g.prototype.subscribe=function(t,e){return b(t,this._subscribers,e)},g.prototype.subscribeAction=function(t,e){var n="function"===typeof t?{before:t}:t;return b(n,this._actionSubscribers,e)},g.prototype.watch=function(t,e,n){var r=this;return this._watcherVM.$watch((function(){return t(r.state,r.getters)}),e,n)},g.prototype.replaceState=function(t){var e=this;this._withCommit((function(){e._vm._data.$$state=t}))},g.prototype.registerModule=function(t,e,n){void 0===n&&(n={}),"string"===typeof t&&(t=[t]),this._modules.register(t,e),S(this,this.state,t,this._modules.get(t),n.preserveState),x(this,this.state)},g.prototype.unregisterModule=function(t){var e=this;"string"===typeof t&&(t=[t]),this._modules.unregister(t),this._withCommit((function(){var n=A(e.state,t.slice(0,-1));m.delete(n,t[t.length-1])})),w(this)},g.prototype.hasModule=function(t){return"string"===typeof t&&(t=[t]),this._modules.isRegistered(t)},g.prototype.hotUpdate=function(t){this._modules.update(t),w(this,!0)},g.prototype._withCommit=function(t){var e=this._committing;this._committing=!0,t(),this._committing=e},Object.defineProperties(g.prototype,y);var D=z((function(t,e){var n={};return R(e).forEach((function(e){var r=e.key,o=e.val;n[r]=function(){var e=this.$store.state,n=this.$store.getters;if(t){var r=F(this.$store,"mapState",t);if(!r)return;e=r.context.state,n=r.context.getters}return"function"===typeof o?o.call(this,e,n):e[o]},n[r].vuex=!0})),n})),N=z((function(t,e){var n={};return R(e).forEach((function(e){var r=e.key,o=e.val;n[r]=function(){var e=[],n=arguments.length;while(n--)e[n]=arguments[n];var r=this.$store.commit;if(t){var i=F(this.$store,"mapMutations",t);if(!i)return;r=i.context.commit}return"function"===typeof o?o.apply(this,[r].concat(e)):r.apply(this.$store,[o].concat(e))}})),n})),P=z((function(t,e){var n={};return R(e).forEach((function(e){var r=e.key,o=e.val;o=t+o,n[r]=function(){if(!t||F(this.$store,"mapGetters",t))return this.$store.getters[o]},n[r].vuex=!0})),n})),j=z((function(t,e){var n={};return R(e).forEach((function(e){var r=e.key,o=e.val;n[r]=function(){var e=[],n=arguments.length;while(n--)e[n]=arguments[n];var r=this.$store.dispatch;if(t){var i=F(this.$store,"mapActions",t);if(!i)return;r=i.context.dispatch}return"function"===typeof o?o.apply(this,[r].concat(e)):r.apply(this.$store,[o].concat(e))}})),n})),I=function(t){return{mapState:D.bind(null,t),mapGetters:P.bind(null,t),mapMutations:N.bind(null,t),mapActions:j.bind(null,t)}};function R(t){return L(t)?Array.isArray(t)?t.map((function(t){return{key:t,val:t}})):Object.keys(t).map((function(e){return{key:e,val:t[e]}})):[]}function L(t){return Array.isArray(t)||u(t)}function z(t){return function(e,n){return"string"!==typeof e?(n=e,e=""):"/"!==e.charAt(e.length-1)&&(e+="/"),t(e,n)}}function F(t,e,n){var r=t._modulesNamespaceMap[n];return r}function B(t){void 0===t&&(t={});var e=t.collapsed;void 0===e&&(e=!0);var n=t.filter;void 0===n&&(n=function(t,e,n){return!0});var r=t.transformer;void 0===r&&(r=function(t){return t});var o=t.mutationTransformer;void 0===o&&(o=function(t){return t});var i=t.actionFilter;void 0===i&&(i=function(t,e){return!0});var a=t.actionTransformer;void 0===a&&(a=function(t){return t});var c=t.logMutations;void 0===c&&(c=!0);var u=t.logActions;void 0===u&&(u=!0);var l=t.logger;return void 0===l&&(l=console),function(t){var f=s(t.state);"undefined"!==typeof l&&(c&&t.subscribe((function(t,i){var a=s(i);if(n(t,f,a)){var c=H(),u=o(t),p="mutation "+t.type+c;V(l,p,e),l.log("%c prev state","color: #9E9E9E; font-weight: bold",r(f)),l.log("%c mutation","color: #03A9F4; font-weight: bold",u),l.log("%c next state","color: #4CAF50; font-weight: bold",r(a)),q(l)}f=a})),u&&t.subscribeAction((function(t,n){if(i(t,n)){var r=H(),o=a(t),s="action "+t.type+r;V(l,s,e),l.log("%c action","color: #03A9F4; font-weight: bold",o),q(l)}})))}}function V(t,e,n){var r=n?t.groupCollapsed:t.group;try{r.call(t,e)}catch(o){t.log(e)}}function q(t){try{t.groupEnd()}catch(e){t.log("—— log end ——")}}function H(){var t=new Date;return" @ "+U(t.getHours(),2)+":"+U(t.getMinutes(),2)+":"+U(t.getSeconds(),2)+"."+U(t.getMilliseconds(),3)}function W(t,e){return new Array(e+1).join(t)}function U(t,e){return W("0",e-t.toString().length)+t}var K={Store:g,install:$,version:"3.5.1",mapState:D,mapMutations:N,mapGetters:P,mapActions:j,createNamespacedHelpers:I,createLogger:B};e["a"]=K}).call(this,n("c8ba"))},"304a":function(t,e,n){"use strict";function r(t){this.content=t}n.d(e,"a",(function(){return dt})),n.d(e,"b",(function(){return Tt})),n.d(e,"c",(function(){return c})),n.d(e,"d",(function(){return d})),n.d(e,"e",(function(){return lt})),n.d(e,"f",(function(){return R})),n.d(e,"g",(function(){return P})),n.d(e,"h",(function(){return h})),n.d(e,"i",(function(){return ft})),n.d(e,"j",(function(){return v})),r.prototype={constructor:r,find:function(t){for(var e=0;e>1}},r.from=function(t){if(t instanceof r)return t;var e=[];if(t)for(var n in t)e.push(n,t[n]);return new r(e)};var o=r,i=o;function a(t,e,n){for(var r=0;;r++){if(r==t.childCount||r==e.childCount)return t.childCount==e.childCount?null:n;var o=t.child(r),i=e.child(r);if(o!=i){if(!o.sameMarkup(i))return n;if(o.isText&&o.text!=i.text){for(var s=0;o.text[s]==i.text[s];s++)n++;return n}if(o.content.size||i.content.size){var c=a(o.content,i.content,n+1);if(null!=c)return c}n+=o.nodeSize}else n+=o.nodeSize}}function s(t,e,n,r){for(var o=t.childCount,i=e.childCount;;){if(0==o||0==i)return o==i?null:{a:n,b:r};var a=t.child(--o),c=e.child(--i),u=a.nodeSize;if(a!=c){if(!a.sameMarkup(c))return{a:n,b:r};if(a.isText&&a.text!=c.text){var l=0,f=Math.min(a.text.length,c.text.length);while(lt&&!1!==n(s,r+a,o,i)&&s.content.size){var u=a+1;s.nodesBetween(Math.max(0,t-u),Math.min(s.content.size,e-u),n,r+u)}a=c}},c.prototype.descendants=function(t){this.nodesBetween(0,this.size,t)},c.prototype.textBetween=function(t,e,n,r){var o="",i=!0;return this.nodesBetween(t,e,(function(a,s){a.isText?(o+=a.text.slice(Math.max(t,s)-s,e-s),i=!n):a.isLeaf&&r?(o+=r,i=!n):!i&&a.isBlock&&(o+=n,i=!0)}),0),o},c.prototype.append=function(t){if(!t.size)return this;if(!this.size)return t;var e=this.lastChild,n=t.firstChild,r=this.content.slice(),o=0;for(e.isText&&e.sameMarkup(n)&&(r[r.length-1]=e.withText(e.text+n.text),o=1);ot)for(var o=0,i=0;it&&((ie)&&(a=a.isText?a.cut(Math.max(0,t-i),Math.min(a.text.length,e-i)):a.cut(Math.max(0,t-i-1),Math.min(a.content.size,e-i-1))),n.push(a),r+=a.nodeSize),i=s}return new c(n,r)},c.prototype.cutByIndex=function(t,e){return t==e?c.empty:0==t&&e==this.content.length?this:new c(this.content.slice(t,e))},c.prototype.replaceChild=function(t,e){var n=this.content[t];if(n==e)return this;var r=this.content.slice(),o=this.size+e.nodeSize-n.nodeSize;return r[t]=e,new c(r,o)},c.prototype.addToStart=function(t){return new c([t].concat(this.content),this.size+t.nodeSize)},c.prototype.addToEnd=function(t){return new c(this.content.concat(t),this.size+t.nodeSize)},c.prototype.eq=function(t){if(this.content.length!=t.content.length)return!1;for(var e=0;ethis.size||t<0)throw new RangeError("Position "+t+" outside of fragment ("+this+")");for(var n=0,r=0;;n++){var o=this.child(n),i=r+o.nodeSize;if(i>=t)return i==t||e>0?f(n+1,i):f(n,r);r=i}},c.prototype.toString=function(){return"<"+this.toStringInner()+">"},c.prototype.toStringInner=function(){return this.content.join(", ")},c.prototype.toJSON=function(){return this.content.length?this.content.map((function(t){return t.toJSON()})):null},c.fromJSON=function(t,e){if(!e)return c.empty;if(!Array.isArray(e))throw new RangeError("Invalid input for Fragment.fromJSON");return new c(e.map(t.nodeFromJSON))},c.fromArray=function(t){if(!t.length)return c.empty;for(var e,n=0,r=0;rthis.type.rank&&(e||(e=t.slice(0,r)),e.push(this),n=!0),e&&e.push(o)}}return e||(e=t.slice()),n||e.push(this),e},d.prototype.removeFromSet=function(t){for(var e=0;et.depth)throw new h("Inserted content deeper than insertion position");if(t.depth-n.openStart!=e.depth-n.openEnd)throw new h("Inconsistent open depths");return w(t,e,n,0)}function w(t,e,n,r){var o=t.index(r),i=t.node(r);if(o==e.index(r)&&r=0&&t.isText&&t.sameMarkup(e[n])?e[n]=t.withText(e[n].text+t.text):e.push(t)}function _(t,e,n,r){var o=(e||t).node(n),i=0,a=e?e.index(n):o.childCount;t&&(i=t.index(n),t.depth>n?i++:t.textOffset&&(O(t.nodeAfter,r),i++));for(var s=i;so&&S(t,e,o+1),a=r.depth>o&&S(n,r,o+1),s=[];return _(null,t,o,s),i&&a&&e.index(o)==n.index(o)?(x(i,a),O(k(i,C(t,e,n,r,o+1)),s)):(i&&O(k(i,M(t,e,o+1)),s),_(e,n,o,s),a&&O(k(a,M(n,r,o+1)),s)),_(r,null,o,s),new c(s)}function M(t,e,n){var r=[];if(_(null,t,n,r),t.depth>n){var o=S(t,e,n+1);O(k(o,M(t,e,n+1)),r)}return _(e,null,n,r),new c(r)}function E(t,e){for(var n=e.depth-t.openStart,r=e.node(n),o=r.copy(t.content),i=n-1;i>=0;i--)o=e.node(i).copy(c.from(o));return{start:o.resolveNoCache(t.openStart+n),end:o.resolveNoCache(o.content.size-t.openEnd-n)}}m.size.get=function(){return this.content.size-this.openStart-this.openEnd},v.prototype.insertAt=function(t,e){var n=y(this.content,t+this.openStart,e,null);return n&&new v(n,this.openStart,this.openEnd)},v.prototype.removeBetween=function(t,e){return new v(g(this.content,t+this.openStart,e+this.openStart),this.openStart,this.openEnd)},v.prototype.eq=function(t){return this.content.eq(t.content)&&this.openStart==t.openStart&&this.openEnd==t.openEnd},v.prototype.toString=function(){return this.content+"("+this.openStart+","+this.openEnd+")"},v.prototype.toJSON=function(){if(!this.content.size)return null;var t={content:this.content.toJSON()};return this.openStart>0&&(t.openStart=this.openStart),this.openEnd>0&&(t.openEnd=this.openEnd),t},v.fromJSON=function(t,e){if(!e)return v.empty;var n=e.openStart||0,r=e.openEnd||0;if("number"!=typeof n||"number"!=typeof r)throw new RangeError("Invalid input for Slice.fromJSON");return new v(c.fromJSON(t,e.content),n,r)},v.maxOpen=function(t,e){void 0===e&&(e=!0);for(var n=0,r=0,o=t.firstChild;o&&!o.isLeaf&&(e||!o.type.spec.isolating);o=o.firstChild)n++;for(var i=t.lastChild;i&&!i.isLeaf&&(e||!i.type.spec.isolating);i=i.lastChild)r++;return new v(t,n,r)},Object.defineProperties(v.prototype,m),v.empty=new v(c.empty,0,0);var A=function(t,e,n){this.pos=t,this.path=e,this.depth=e.length/3-1,this.parentOffset=n},T={parent:{configurable:!0},doc:{configurable:!0},textOffset:{configurable:!0},nodeAfter:{configurable:!0},nodeBefore:{configurable:!0}};A.prototype.resolveDepth=function(t){return null==t?this.depth:t<0?this.depth+t:t},T.parent.get=function(){return this.node(this.depth)},T.doc.get=function(){return this.node(0)},A.prototype.node=function(t){return this.path[3*this.resolveDepth(t)]},A.prototype.index=function(t){return this.path[3*this.resolveDepth(t)+1]},A.prototype.indexAfter=function(t){return t=this.resolveDepth(t),this.index(t)+(t!=this.depth||this.textOffset?1:0)},A.prototype.start=function(t){return t=this.resolveDepth(t),0==t?0:this.path[3*t-1]+1},A.prototype.end=function(t){return t=this.resolveDepth(t),this.start(t)+this.node(t).content.size},A.prototype.before=function(t){if(t=this.resolveDepth(t),!t)throw new RangeError("There is no position before the top-level node");return t==this.depth+1?this.pos:this.path[3*t-1]},A.prototype.after=function(t){if(t=this.resolveDepth(t),!t)throw new RangeError("There is no position after the top-level node");return t==this.depth+1?this.pos:this.path[3*t-1]+this.path[3*t].nodeSize},T.textOffset.get=function(){return this.pos-this.path[this.path.length-1]},T.nodeAfter.get=function(){var t=this.parent,e=this.index(this.depth);if(e==t.childCount)return null;var n=this.pos-this.path[this.path.length-1],r=t.child(e);return n?t.child(e).cut(n):r},T.nodeBefore.get=function(){var t=this.index(this.depth),e=this.pos-this.path[this.path.length-1];return e?this.parent.child(t).cut(0,e):0==t?null:this.parent.child(t-1)},A.prototype.posAtIndex=function(t,e){e=this.resolveDepth(e);for(var n=this.path[3*e],r=0==e?0:this.path[3*e-1]+1,o=0;o0;e--)if(this.start(e)<=t&&this.end(e)>=t)return e;return 0},A.prototype.blockRange=function(t,e){if(void 0===t&&(t=this),t.pos=0;n--)if(t.pos<=this.end(n)&&(!e||e(this.node(n))))return new P(this,t,n)},A.prototype.sameParent=function(t){return this.pos-this.parentOffset==t.pos-t.parentOffset},A.prototype.max=function(t){return t.pos>this.pos?t:this},A.prototype.min=function(t){return t.pos=0&&e<=t.content.size))throw new RangeError("Position "+e+" out of range");for(var n=[],r=0,o=e,i=t;;){var a=i.content.findIndex(o),s=a.index,c=a.offset,u=o-c;if(n.push(i,s,r+c),!u)break;if(i=i.child(s),i.isText)break;o=u-1,r+=c+1}return new A(e,n,o)},A.resolveCached=function(t,e){for(var n=0;n<$.length;n++){var r=$[n];if(r.pos==e&&r.doc==t)return r}var o=$[D]=A.resolve(t,e);return D=(D+1)%N,o},Object.defineProperties(A.prototype,T);var $=[],D=0,N=12,P=function(t,e,n){this.$from=t,this.$to=e,this.depth=n},j={start:{configurable:!0},end:{configurable:!0},parent:{configurable:!0},startIndex:{configurable:!0},endIndex:{configurable:!0}};j.start.get=function(){return this.$from.before(this.depth+1)},j.end.get=function(){return this.$to.after(this.depth+1)},j.parent.get=function(){return this.$from.node(this.depth)},j.startIndex.get=function(){return this.$from.index(this.depth)},j.endIndex.get=function(){return this.$to.indexAfter(this.depth)},Object.defineProperties(P.prototype,j);var I=Object.create(null),R=function(t,e,n,r){this.type=t,this.attrs=e,this.content=n||c.empty,this.marks=r||d.none},L={nodeSize:{configurable:!0},childCount:{configurable:!0},textContent:{configurable:!0},firstChild:{configurable:!0},lastChild:{configurable:!0},isBlock:{configurable:!0},isTextblock:{configurable:!0},inlineContent:{configurable:!0},isInline:{configurable:!0},isText:{configurable:!0},isLeaf:{configurable:!0},isAtom:{configurable:!0}};L.nodeSize.get=function(){return this.isLeaf?1:2+this.content.size},L.childCount.get=function(){return this.content.childCount},R.prototype.child=function(t){return this.content.child(t)},R.prototype.maybeChild=function(t){return this.content.maybeChild(t)},R.prototype.forEach=function(t){this.content.forEach(t)},R.prototype.nodesBetween=function(t,e,n,r){void 0===r&&(r=0),this.content.nodesBetween(t,e,n,r,this)},R.prototype.descendants=function(t){this.nodesBetween(0,this.content.size,t)},L.textContent.get=function(){return this.textBetween(0,this.content.size,"")},R.prototype.textBetween=function(t,e,n,r){return this.content.textBetween(t,e,n,r)},L.firstChild.get=function(){return this.content.firstChild},L.lastChild.get=function(){return this.content.lastChild},R.prototype.eq=function(t){return this==t||this.sameMarkup(t)&&this.content.eq(t.content)},R.prototype.sameMarkup=function(t){return this.hasMarkup(t.type,t.attrs,t.marks)},R.prototype.hasMarkup=function(t,e,n){return this.type==t&&p(this.attrs,e||t.defaultAttrs||I)&&d.sameSet(this.marks,n||d.none)},R.prototype.copy=function(t){return void 0===t&&(t=null),t==this.content?this:new this.constructor(this.type,this.attrs,t,this.marks)},R.prototype.mark=function(t){return t==this.marks?this:new this.constructor(this.type,this.attrs,this.content,t)},R.prototype.cut=function(t,e){return 0==t&&e==this.content.size?this:this.copy(this.content.cut(t,e))},R.prototype.slice=function(t,e,n){if(void 0===e&&(e=this.content.size),void 0===n&&(n=!1),t==e)return v.empty;var r=this.resolve(t),o=this.resolve(e),i=n?0:r.sharedDepth(e),a=r.start(i),s=r.node(i),c=s.content.cut(r.pos-a,o.pos-a);return new v(c,r.depth-i,o.depth-i)},R.prototype.replace=function(t,e,n){return b(this.resolve(t),this.resolve(e),n)},R.prototype.nodeAt=function(t){for(var e=this;;){var n=e.content.findIndex(t),r=n.index,o=n.offset;if(e=e.maybeChild(r),!e)return null;if(o==t||e.isText)return e;t-=o+1}},R.prototype.childAfter=function(t){var e=this.content.findIndex(t),n=e.index,r=e.offset;return{node:this.content.maybeChild(n),index:n,offset:r}},R.prototype.childBefore=function(t){if(0==t)return{node:null,index:0,offset:0};var e=this.content.findIndex(t),n=e.index,r=e.offset;if(rt&&this.nodesBetween(t,e,(function(t){return n.isInSet(t.marks)&&(r=!0),!r})),r},L.isBlock.get=function(){return this.type.isBlock},L.isTextblock.get=function(){return this.type.isTextblock},L.inlineContent.get=function(){return this.type.inlineContent},L.isInline.get=function(){return this.type.isInline},L.isText.get=function(){return this.type.isText},L.isLeaf.get=function(){return this.type.isLeaf},L.isAtom.get=function(){return this.type.isAtom},R.prototype.toString=function(){if(this.type.spec.toDebugString)return this.type.spec.toDebugString(this);var t=this.type.name;return this.content.size&&(t+="("+this.content.toStringInner()+")"),F(this.marks,t)},R.prototype.contentMatchAt=function(t){var e=this.type.contentMatch.matchFragment(this.content,0,t);if(!e)throw new Error("Called contentMatchAt on a node with invalid content");return e},R.prototype.canReplace=function(t,e,n,r,o){void 0===n&&(n=c.empty),void 0===r&&(r=0),void 0===o&&(o=n.childCount);var i=this.contentMatchAt(t).matchFragment(n,r,o),a=i&&i.matchFragment(this.content,e);if(!a||!a.validEnd)return!1;for(var s=r;s=0;n--)e=t[n].type.name+"("+e+")";return e}var B=function(t){this.validEnd=t,this.next=[],this.wrapCache=[]},V={inlineContent:{configurable:!0},defaultType:{configurable:!0},edgeCount:{configurable:!0}};B.parse=function(t,e){var n=new q(t,e);if(null==n.next)return B.empty;var r=W(n);n.next&&n.err("Unexpected trailing text");var o=et(Z(r));return nt(o,n),o},B.prototype.matchType=function(t){for(var e=0;e>1},B.prototype.edge=function(t){var e=t<<1;if(e>=this.next.length)throw new RangeError("There's no "+t+"th edge in this content match");return{type:this.next[e],next:this.next[e+1]}},B.prototype.toString=function(){var t=[];function e(n){t.push(n);for(var r=1;r"+t.indexOf(e.next[o+1]);return r})).join("\n")},Object.defineProperties(B.prototype,V),B.empty=new B(!0);var q=function(t,e){this.string=t,this.nodeTypes=e,this.inline=null,this.pos=0,this.tokens=t.split(/\s*(?=\b|\W|$)/),""==this.tokens[this.tokens.length-1]&&this.tokens.pop(),""==this.tokens[0]&&this.tokens.unshift()},H={next:{configurable:!0}};function W(t){var e=[];do{e.push(U(t))}while(t.eat("|"));return 1==e.length?e[0]:{type:"choice",exprs:e}}function U(t){var e=[];do{e.push(K(t))}while(t.next&&")"!=t.next&&"|"!=t.next);return 1==e.length?e[0]:{type:"seq",exprs:e}}function K(t){for(var e=X(t);;)if(t.eat("+"))e={type:"plus",expr:e};else if(t.eat("*"))e={type:"star",expr:e};else if(t.eat("?"))e={type:"opt",expr:e};else{if(!t.eat("{"))break;e=Y(t,e)}return e}function J(t){/\D/.test(t.next)&&t.err("Expected number, got '"+t.next+"'");var e=Number(t.next);return t.pos++,e}function Y(t,e){var n=J(t),r=n;return t.eat(",")&&(r="}"!=t.next?J(t):-1),t.eat("}")||t.err("Unclosed braced range"),{type:"range",min:n,max:r,expr:e}}function G(t,e){var n=t.nodeTypes,r=n[e];if(r)return[r];var o=[];for(var i in n){var a=n[i];a.groups.indexOf(e)>-1&&o.push(a)}return 0==o.length&&t.err("No node type or group '"+e+"' found"),o}function X(t){if(t.eat("(")){var e=W(t);return t.eat(")")||t.err("Missing closing paren"),e}if(!/\W/.test(t.next)){var n=G(t,t.next).map((function(e){return null==t.inline?t.inline=e.isInline:t.inline!=e.isInline&&t.err("Mixing inline and block content"),{type:"name",value:e}}));return t.pos++,1==n.length?n[0]:{type:"choice",exprs:n}}t.err("Unexpected token '"+t.next+"'")}function Z(t){var e=[[]];return o(i(t,0),n()),e;function n(){return e.push([])-1}function r(t,n,r){var o={term:r,to:n};return e[t].push(o),o}function o(t,e){t.forEach((function(t){return t.to=e}))}function i(t,e){if("choice"==t.type)return t.exprs.reduce((function(t,n){return t.concat(i(n,e))}),[]);if("seq"==t.type)for(var a=0;;a++){var s=i(t.exprs[a],e);if(a==t.exprs.length-1)return s;o(s,e=n())}else{if("star"==t.type){var c=n();return r(e,c),o(i(t.expr,c),c),[r(c)]}if("plus"==t.type){var u=n();return o(i(t.expr,e),u),o(i(t.expr,u),u),[r(u)]}if("opt"==t.type)return[r(e)].concat(i(t.expr,e));if("range"==t.type){for(var l=e,f=0;f-1&&o[i+1];tt(t,r).forEach((function(t){a||o.push(n,a=[]),-1==a.indexOf(t)&&a.push(t)}))}}))}));for(var i=e[r.join(",")]=new B(r.indexOf(t.length-1)>-1),a=0;a-1},at.prototype.allowsMarks=function(t){if(null==this.markSet)return!0;for(var e=0;e-1};var ft=function(t){for(var e in this.spec={},t)this.spec[e]=t[e];this.spec.nodes=i.from(t.nodes),this.spec.marks=i.from(t.marks),this.nodes=at.compile(this.spec.nodes,this),this.marks=lt.compile(this.spec.marks,this);var n=Object.create(null);for(var r in this.nodes){if(r in this.marks)throw new RangeError(r+" can not be both a node and a mark");var o=this.nodes[r],a=o.spec.content||"",s=o.spec.marks;o.contentMatch=n[a]||(n[a]=B.parse(a,this.nodes)),o.inlineContent=o.contentMatch.inlineContent,o.markSet="_"==s?null:s?pt(this,s.split(" ")):""!=s&&o.inlineContent?null:[]}for(var c in this.marks){var u=this.marks[c],l=u.spec.excludes;u.excluded=null==l?[u]:""==l?[]:pt(this,l.split(" "))}this.nodeFromJSON=this.nodeFromJSON.bind(this),this.markFromJSON=this.markFromJSON.bind(this),this.topNodeType=this.nodes[this.spec.topNode||"doc"],this.cached=Object.create(null),this.cached.wrappings=Object.create(null)};function pt(t,e){for(var n=[],r=0;r-1)&&n.push(a=c)}if(!a)throw new SyntaxError("Unknown mark type: '"+e[r]+"'")}return n}ft.prototype.node=function(t,e,n,r){if("string"==typeof t)t=this.nodeType(t);else{if(!(t instanceof at))throw new RangeError("Invalid node type: "+t);if(t.schema!=this)throw new RangeError("Node type from different schema used ("+t.name+")")}return t.createChecked(e,n,r)},ft.prototype.text=function(t,e){var n=this.nodes.text;return new z(n,n.defaultAttrs,t,d.setFrom(e))},ft.prototype.mark=function(t,e){return"string"==typeof t&&(t=this.marks[t]),t.create(e)},ft.prototype.nodeFromJSON=function(t){return R.fromJSON(this,t)},ft.prototype.markFromJSON=function(t){return d.fromJSON(this,t)},ft.prototype.nodeType=function(t){var e=this.nodes[t];if(!e)throw new RangeError("Unknown node type: "+t);return e};var dt=function(t,e){var n=this;this.schema=t,this.rules=e,this.tags=[],this.styles=[],e.forEach((function(t){t.tag?n.tags.push(t):t.style&&n.styles.push(t)})),this.normalizeLists=!this.tags.some((function(e){if(!/^(ul|ol)\b/.test(e.tag)||!e.node)return!1;var n=t.nodes[e.node];return n.contentMatch.matchType(n)}))};dt.prototype.parse=function(t,e){void 0===e&&(e={});var n=new St(this,e,!1);return n.addAll(t,null,e.from,e.to),n.finish()},dt.prototype.parseSlice=function(t,e){void 0===e&&(e={});var n=new St(this,e,!0);return n.addAll(t,null,e.from,e.to),v.maxOpen(n.finish())},dt.prototype.matchTag=function(t,e){for(var n=0;nt.length&&(61!=o.style.charCodeAt(t.length)||o.style.slice(t.length+1)!=e))){if(o.getAttrs){var i=o.getAttrs(e);if(!1===i)continue;o.attrs=i}return o}}},dt.schemaRules=function(t){var e=[];function n(t){for(var n=null==t.priority?50:t.priority,r=0;r=0;e--)if(this.stashMarks[e].type==t)return this.stashMarks.splice(e,1)[0]},xt.prototype.applyPending=function(t){for(var e=0,n=this.pendingMarks;e=0;r--){var o=this.nodes[r],i=o.findWrapping(t);if(i&&(!e||e.length>i.length)&&(e=i,n=o,!i.length))break;if(o.solid)break}if(!e)return!1;this.sync(n);for(var a=0;athis.open){for(;e>this.open;e--)this.nodes[e-1].content.push(this.nodes[e].finish(t));this.nodes.length=this.open+1}},St.prototype.finish=function(){return this.open=0,this.closeExtra(this.isOpen),this.nodes[0].finish(this.isOpen||this.options.topOpen)},St.prototype.sync=function(t){for(var e=this.open;e>=0;e--)if(this.nodes[e]==t)return void(this.open=e)},Ot.currentPos.get=function(){this.closeExtra();for(var t=0,e=this.open;e>=0;e--){for(var n=this.nodes[e].content,r=n.length-1;r>=0;r--)t+=n[r].nodeSize;e&&t++}return t},St.prototype.findAtPoint=function(t,e){if(this.find)for(var n=0;n-1)return t.split(/\s*\|\s*/).some(this.matchesContext,this);var n=t.split("/"),r=this.options.context,o=!this.isOpen&&(!r||r.parent.type==this.nodes[0].type),i=-(r?r.depth+1:0)+(o?0:1),a=function(t,s){for(;t>=0;t--){var c=n[t];if(""==c){if(t==n.length-1||0==t)continue;for(;s>=i;s--)if(a(t-1,s))return!0;return!1}var u=s>0||0==s&&o?e.nodes[s].type:r&&s>=i?r.node(s-i).type:null;if(!u||u.name!=c&&-1==u.groups.indexOf(c))return!1;s--}return!0};return a(n.length-1,this.open)},St.prototype.textblockFromContext=function(){var t=this.options.context;if(t)for(var e=t.depth;e>=0;e--){var n=t.node(e).contentMatchAt(t.indexAfter(e)).defaultType;if(n&&n.isTextblock&&n.defaultAttrs)return n}for(var r in this.parser.schema.nodes){var o=this.parser.schema.nodes[r];if(o.isTextblock&&o.defaultAttrs)return o}},St.prototype.addPendingMark=function(t){var e=At(t,this.top.pendingMarks);e&&this.top.stashMarks.push(e),this.top.pendingMarks=t.addToSet(this.top.pendingMarks)},St.prototype.removePendingMark=function(t,e){for(var n=this.open;n>=0;n--){var r=this.nodes[n],o=r.pendingMarks.lastIndexOf(t);if(o>-1)r.pendingMarks=t.removeFromSet(r.pendingMarks);else{r.activeMarks=t.removeFromSet(r.activeMarks);var i=r.popFromStashMark(t.type);i&&(r.activeMarks=i.addToSet(r.activeMarks))}if(r==e)break}},Object.defineProperties(St.prototype,Ot);var Tt=function(t,e){this.nodes=t||{},this.marks=e||{}};function $t(t){var e={};for(var n in t){var r=t[n].spec.toDOM;r&&(e[n]=r)}return e}function Dt(t){return t.document||window.document}Tt.prototype.serializeFragment=function(t,e,n){var r=this;void 0===e&&(e={}),n||(n=Dt(e).createDocumentFragment());var o=n,i=null;return t.forEach((function(t){if(i||t.marks.length){i||(i=[]);var n=0,a=0;while(n=0;r--){var o=this.serializeMark(t.marks[r],t.isInline,e);o&&((o.contentDOM||o.dom).appendChild(n),n=o.dom)}return n},Tt.prototype.serializeMark=function(t,e,n){void 0===n&&(n={});var r=this.marks[t.type.name];return r&&Tt.renderSpec(Dt(n),r(t,e))},Tt.renderSpec=function(t,e,n){if(void 0===n&&(n=null),"string"==typeof e)return{dom:t.createTextNode(e)};if(null!=e.nodeType)return{dom:e};if(e.dom&&null!=e.dom.nodeType)return e;var r=e[0],o=r.indexOf(" ");o>0&&(n=r.slice(0,o),r=r.slice(o+1));var i=null,a=n?t.createElementNS(n,r):t.createElement(r),s=e[1],c=1;if(s&&"object"==typeof s&&null==s.nodeType&&!Array.isArray(s))for(var u in c=2,s)if(null!=s[u]){var l=u.indexOf(" ");l>0?a.setAttributeNS(u.slice(0,l),u.slice(l+1),s[u]):a.setAttribute(u,s[u])}for(var f=c;fc)throw new RangeError("Content hole must be the only child of its parent node");return{dom:a,contentDOM:a}}var d=Tt.renderSpec(t,p,n),h=d.dom,v=d.contentDOM;if(a.appendChild(h),v){if(i)throw new RangeError("Multiple content holes");i=v}}return{dom:a,contentDOM:i}},Tt.fromSchema=function(t){return t.cached.domSerializer||(t.cached.domSerializer=new Tt(this.nodesFromSchema(t),this.marksFromSchema(t)))},Tt.nodesFromSchema=function(t){var e=$t(t.nodes);return e.text||(e.text=function(t){return t.text}),e},Tt.marksFromSchema=function(t){return $t(t.marks)}},3360:function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0;var r=n("78ef"),o=function(){for(var t=arguments.length,e=new Array(t),n=0;n0&&e.reduce((function(e,n){return e&&n.apply(t,r)}),!0)}))};e.default=o},"3a54":function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0;var r=n("78ef"),o=(0,r.regex)("alphaNum",/^[a-zA-Z0-9]*$/);e.default=o},"428f":function(t,e,n){var r=n("da84");t.exports=r},"44ad":function(t,e,n){var r=n("d039"),o=n("c6b6"),i="".split;t.exports=r((function(){return!Object("z").propertyIsEnumerable(0)}))?function(t){return"String"==o(t)?i.call(t,""):Object(t)}:Object},"45b8":function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0;var r=n("78ef"),o=(0,r.regex)("numeric",/^[0-9]*$/);e.default=o},"46bc":function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0;var r=n("78ef"),o=function(t){return(0,r.withParams)({type:"maxValue",max:t},(function(e){return!(0,r.req)(e)||(!/\s/.test(e)||e instanceof Date)&&+e<=+t}))};e.default=o},"4d64":function(t,e,n){var r=n("fc6a"),o=n("50c4"),i=n("23cb"),a=function(t){return function(e,n,a){var s,c=r(e),u=o(c.length),l=i(a,u);if(t&&n!=n){while(u>l)if(s=c[l++],s!=s)return!0}else for(;u>l;l++)if((t||l in c)&&c[l]===n)return t||l||0;return!t&&-1}};t.exports={includes:a(!0),indexOf:a(!1)}},"50c4":function(t,e,n){var r=n("a691"),o=Math.min;t.exports=function(t){return t>0?o(r(t),9007199254740991):0}},5135:function(t,e){var n={}.hasOwnProperty;t.exports=function(t,e){return n.call(t,e)}},5313:function(t,e,n){"use strict";n.d(e,"a",(function(){return d})),n.d(e,"b",(function(){return k})),n.d(e,"c",(function(){return f})),n.d(e,"d",(function(){return A})),n.d(e,"e",(function(){return D})),n.d(e,"f",(function(){return a})),n.d(e,"g",(function(){return u}));var r=n("304a"),o=n("0ac0"),i=Object.create(null),a=function(t,e,n){this.ranges=n||[new c(t.min(e),t.max(e))],this.$anchor=t,this.$head=e},s={anchor:{configurable:!0},head:{configurable:!0},from:{configurable:!0},to:{configurable:!0},$from:{configurable:!0},$to:{configurable:!0},empty:{configurable:!0}};s.anchor.get=function(){return this.$anchor.pos},s.head.get=function(){return this.$head.pos},s.from.get=function(){return this.$from.pos},s.to.get=function(){return this.$to.pos},s.$from.get=function(){return this.ranges[0].$from},s.$to.get=function(){return this.ranges[0].$to},s.empty.get=function(){for(var t=this.ranges,e=0;e=0;o--){var i=e<0?v(t.node(0),t.node(o),t.before(o+1),t.index(o),e,n):v(t.node(0),t.node(o),t.after(o+1),t.index(o)+1,e,n);if(i)return i}},a.near=function(t,e){return void 0===e&&(e=1),this.findFrom(t,e)||this.findFrom(t,-e)||new d(t.node(0))},a.atStart=function(t){return v(t,t,0,0,1)||new d(t)},a.atEnd=function(t){return v(t,t,t.content.size,t.childCount,-1)||new d(t)},a.fromJSON=function(t,e){if(!e||!e.type)throw new RangeError("Invalid input for Selection.fromJSON");var n=i[e.type];if(!n)throw new RangeError("No selection type "+e.type+" defined");return n.fromJSON(t,e)},a.jsonID=function(t,e){if(t in i)throw new RangeError("Duplicate use of selection JSON ID "+t);return i[t]=e,e.prototype.jsonID=t,e},a.prototype.getBookmark=function(){return u.between(this.$anchor,this.$head).getBookmark()},Object.defineProperties(a.prototype,s),a.prototype.visible=!0;var c=function(t,e){this.$from=t,this.$to=e},u=function(t){function e(e,n){void 0===n&&(n=e),t.call(this,e,n)}t&&(e.__proto__=t),e.prototype=Object.create(t&&t.prototype),e.prototype.constructor=e;var n={$cursor:{configurable:!0}};return n.$cursor.get=function(){return this.$anchor.pos==this.$head.pos?this.$head:null},e.prototype.map=function(n,r){var o=n.resolve(r.map(this.head));if(!o.parent.inlineContent)return t.near(o);var i=n.resolve(r.map(this.anchor));return new e(i.parent.inlineContent?i:o,o)},e.prototype.replace=function(e,n){if(void 0===n&&(n=r["j"].empty),t.prototype.replace.call(this,e,n),n==r["j"].empty){var o=this.$from.marksAcross(this.$to);o&&e.ensureMarks(o)}},e.prototype.eq=function(t){return t instanceof e&&t.anchor==this.anchor&&t.head==this.head},e.prototype.getBookmark=function(){return new l(this.anchor,this.head)},e.prototype.toJSON=function(){return{type:"text",anchor:this.anchor,head:this.head}},e.fromJSON=function(t,n){if("number"!=typeof n.anchor||"number"!=typeof n.head)throw new RangeError("Invalid input for TextSelection.fromJSON");return new e(t.resolve(n.anchor),t.resolve(n.head))},e.create=function(t,e,n){void 0===n&&(n=e);var r=t.resolve(e);return new this(r,n==e?r:t.resolve(n))},e.between=function(n,r,o){var i=n.pos-r.pos;if(o&&!i||(o=i>=0?1:-1),!r.parent.inlineContent){var a=t.findFrom(r,o,!0)||t.findFrom(r,-o,!0);if(!a)return t.near(r,o);r=a.$head}return n.parent.inlineContent||(0==i?n=r:(n=(t.findFrom(n,-o,!0)||t.findFrom(n,o,!0)).$anchor,n.pos0?0:1);o>0?a=0;a+=o){var s=e.child(a);if(s.isAtom){if(!i&&f.isSelectable(s))return f.create(t,n-(o<0?s.nodeSize:0))}else{var c=v(t,s,n+o,o<0?s.childCount:0,o,i);if(c)return c}n+=s.nodeSize*o}}function m(t,e,n){var r=t.steps.length-1;if(!(r0},e.prototype.setStoredMarks=function(t){return this.storedMarks=t,this.updated|=y,this},e.prototype.ensureMarks=function(t){return r["d"].sameSet(this.storedMarks||this.selection.$from.marks(),t)||this.setStoredMarks(t),this},e.prototype.addStoredMark=function(t){return this.ensureMarks(t.addToSet(this.storedMarks||this.selection.$head.marks()))},e.prototype.removeStoredMark=function(t){return this.ensureMarks(t.removeFromSet(this.storedMarks||this.selection.$head.marks()))},n.storedMarksSet.get=function(){return(this.updated&y)>0},e.prototype.addStep=function(e,n){t.prototype.addStep.call(this,e,n),this.updated=this.updated&~y,this.storedMarks=null},e.prototype.setTime=function(t){return this.time=t,this},e.prototype.replaceSelection=function(t){return this.selection.replace(this,t),this},e.prototype.replaceSelectionWith=function(t,e){var n=this.selection;return!1!==e&&(t=t.mark(this.storedMarks||(n.empty?n.$from.marks():n.$from.marksAcross(n.$to)||r["d"].none))),n.replaceWith(this,t),this},e.prototype.deleteSelection=function(){return this.selection.replace(this),this},e.prototype.insertText=function(t,e,n){void 0===n&&(n=e);var r=this.doc.type.schema;if(null==e)return t?this.replaceSelectionWith(r.text(t),!0):this.deleteSelection();if(!t)return this.deleteRange(e,n);var o=this.storedMarks;if(!o){var i=this.doc.resolve(e);o=n==e?i.marks():i.marksAcross(this.doc.resolve(n))}return this.replaceRangeWith(e,n,r.text(t,o)),this.selection.empty||this.setSelection(a.near(this.selection.$to)),this},e.prototype.setMeta=function(t,e){return this.meta["string"==typeof t?t:t.key]=e,this},e.prototype.getMeta=function(t){return this.meta["string"==typeof t?t:t.key]},n.isGeneric.get=function(){for(var t in this.meta)return!1;return!0},e.prototype.scrollIntoView=function(){return this.updated|=b,this},n.scrolledIntoView.get=function(){return(this.updated&b)>0},Object.defineProperties(e.prototype,n),e}(o["d"]);function x(t,e){return e&&t?t.bind(e):t}var S=function(t,e,n){this.name=t,this.init=x(e.init,n),this.apply=x(e.apply,n)},O=[new S("doc",{init:function(t){return t.doc||t.schema.topNodeType.createAndFill()},apply:function(t){return t.doc}}),new S("selection",{init:function(t,e){return t.selection||a.atStart(e.doc)},apply:function(t){return t.selection}}),new S("storedMarks",{init:function(t){return t.storedMarks||null},apply:function(t,e,n,r){return r.selection.$cursor?t.storedMarks:null}}),new S("scrollToSelection",{init:function(){return 0},apply:function(t,e){return t.scrolledIntoView?e+1:e}})],_=function(t,e){var n=this;this.schema=t,this.fields=O.concat(),this.plugins=[],this.pluginsByKey=Object.create(null),e&&e.forEach((function(t){if(n.pluginsByKey[t.key])throw new RangeError("Adding different instances of a keyed plugin ("+t.key+")");n.plugins.push(t),n.pluginsByKey[t.key]=t,t.spec.state&&n.fields.push(new S(t.key,t.spec.state,t))}))},k=function(t){this.config=t},C={schema:{configurable:!0},plugins:{configurable:!0},tr:{configurable:!0}};C.schema.get=function(){return this.config.schema},C.plugins.get=function(){return this.config.plugins},k.prototype.apply=function(t){return this.applyTransaction(t).state},k.prototype.filterTransaction=function(t,e){void 0===e&&(e=-1);for(var n=0;n-1&&M.splice(e,1)},Object.defineProperties(k.prototype,C);var M=[];function E(t,e,n){for(var r in t){var o=t[r];o instanceof Function?o=o.bind(e):"handleDOMEvents"==r&&(o=E(o,e,{})),n[r]=o}return n}var A=function(t){this.props={},t.props&&E(t.props,this,this.props),this.spec=t,this.key=t.key?t.key.key:$("plugin")};A.prototype.getState=function(t){return t[this.key]};var T=Object.create(null);function $(t){return t in T?t+"$"+ ++T[t]:(T[t]=0,t+"$")}var D=function(t){void 0===t&&(t="key"),this.key=$(t)};D.prototype.get=function(t){return t.config.pluginsByKey[this.key]},D.prototype.getState=function(t){return t[this.key]}},5692:function(t,e,n){var r=n("c430"),o=n("c6cd");(t.exports=function(t,e){return o[t]||(o[t]=void 0!==e?e:{})})("versions",[]).push({version:"3.7.0",mode:r?"pure":"global",copyright:"© 2020 Denis Pushkarev (zloirock.ru)"})},"56ef":function(t,e,n){var r=n("d066"),o=n("241c"),i=n("7418"),a=n("825a");t.exports=r("Reflect","ownKeys")||function(t){var e=o.f(a(t)),n=i.f;return n?e.concat(n(t)):e}},"576a":function(t,e,n){"use strict";n.d(e,"a",(function(){return Ln}));var r=n("5313"),o=n("304a"),i=n("0ac0"),a={};if("undefined"!=typeof navigator&&"undefined"!=typeof document){var s=/Edge\/(\d+)/.exec(navigator.userAgent),c=/MSIE \d/.test(navigator.userAgent),u=/Trident\/(?:[7-9]|\d{2,})\..*rv:(\d+)/.exec(navigator.userAgent);a.mac=/Mac/.test(navigator.platform);var l=a.ie=!!(c||u||s);a.ie_version=c?document.documentMode||6:u?+u[1]:s?+s[1]:null,a.gecko=!l&&/gecko\/(\d+)/i.test(navigator.userAgent),a.gecko_version=a.gecko&&+(/Firefox\/(\d+)/.exec(navigator.userAgent)||[0,0])[1];var f=!l&&/Chrome\/(\d+)/.exec(navigator.userAgent);a.chrome=!!f,a.chrome_version=f&&+f[1],a.ios=!l&&/AppleWebKit/.test(navigator.userAgent)&&/Mobile\/\w+/.test(navigator.userAgent),a.android=/Android \d/.test(navigator.userAgent),a.webkit="webkitFontSmoothing"in document.documentElement.style,a.safari=/Apple Computer/.test(navigator.vendor),a.webkit_version=a.webkit&&+(/\bAppleWebKit\/(\d+)/.exec(navigator.userAgent)||[0,0])[1]}var p=function(t){for(var e=0;;e++)if(t=t.previousSibling,!t)return e},d=function(t){var e=t.parentNode;return e&&11==e.nodeType?e.host:e},h=null,v=function(t,e,n){var r=h||(h=document.createRange());return r.setEnd(t,null==n?t.nodeValue.length:n),r.setStart(t,e||0),r},m=function(t,e,n,r){return n&&(y(t,e,n,r,-1)||y(t,e,n,r,1))},g=/^(img|br|input|textarea|hr)$/i;function y(t,e,n,r,o){for(;;){if(t==n&&e==r)return!0;if(e==(o<0?0:b(t))){var i=t.parentNode;if(1!=i.nodeType||x(t)||g.test(t.nodeName)||"false"==t.contentEditable)return!1;e=p(t)+(o<0?0:1),t=i}else{if(1!=t.nodeType)return!1;if(t=t.childNodes[e+(o<0?-1:0)],"false"==t.contentEditable)return!1;e=o<0?b(t):0}}}function b(t){return 3==t.nodeType?t.nodeValue.length:t.childNodes.length}function w(t,e,n){for(var r=0==e,o=e==b(t);r||o;){if(t==n)return!0;var i=p(t);if(t=t.parentNode,!t)return!1;r=r&&0==i,o=o&&i==b(t)}}function x(t){for(var e,n=t;n;n=n.parentNode)if(e=n.pmViewDesc)break;return e&&e.node&&e.node.isBlock&&(e.dom==t||e.contentDOM==t)}var S=function(t){var e=t.isCollapsed;return e&&a.chrome&&t.rangeCount&&!t.getRangeAt(0).collapsed&&(e=!1),e};function O(t,e){var n=document.createEvent("Event");return n.initEvent("keydown",!0,!0),n.keyCode=t,n.key=n.code=e,n}function _(t){return{left:0,right:t.documentElement.clientWidth,top:0,bottom:t.documentElement.clientHeight}}function k(t,e){return"number"==typeof t?t:t[e]}function C(t){var e=t.getBoundingClientRect();return{left:e.left,right:e.left+t.clientWidth,top:e.top,bottom:e.top+t.clientHeight}}function M(t,e,n){for(var r=t.someProp("scrollThreshold")||0,o=t.someProp("scrollMargin")||5,i=t.dom.ownerDocument,a=n||t.dom;;a=d(a)){if(!a)break;if(1==a.nodeType){var s=a==i.body||1!=a.nodeType,c=s?_(i):C(a),u=0,l=0;if(e.topc.bottom-k(r,"bottom")&&(l=e.bottom-c.bottom+k(o,"bottom")),e.leftc.right-k(r,"right")&&(u=e.right-c.right+k(o,"right")),u||l)if(s)i.defaultView.scrollBy(u,l);else{var f=a.scrollLeft,p=a.scrollTop;l&&(a.scrollTop+=l),u&&(a.scrollLeft+=u);var h=a.scrollLeft-f,v=a.scrollTop-p;e={left:e.left-h,top:e.top-v,right:e.right-h,bottom:e.bottom-v}}if(s)break}}}function E(t){for(var e,n,r=t.dom.getBoundingClientRect(),o=Math.max(0,r.top),i=(r.left+r.right)/2,a=o+1;a=o-20){e=s,n=c.top;break}}}return{refDOM:e,refTop:n,stack:A(t.dom)}}function A(t){for(var e=[],n=t.ownerDocument;t;t=d(t))if(e.push({dom:t,top:t.scrollTop,left:t.scrollLeft}),t==n)break;return e}function T(t){var e=t.refDOM,n=t.refTop,r=t.stack,o=e?e.getBoundingClientRect().top:0;$(r,0==o?0:o-n)}function $(t,e){for(var n=0;n=s){a=Math.max(p.bottom,a),s=Math.min(p.top,s);var d=p.left>e.left?p.left-e.left:p.right=(p.left+p.right)/2?1:0));continue}}!n&&(e.left>=p.right&&e.top>=p.top||e.left>=p.left&&e.top>=p.bottom)&&(i=u+1)}}return n&&3==n.nodeType?j(n,r):!n||o&&1==n.nodeType?{node:t,offset:i}:P(n,r)}function j(t,e){for(var n=t.nodeValue.length,r=document.createRange(),o=0;o=(i.left+i.right)/2?1:0)}}return{node:t,offset:0}}function I(t,e){return t.left>=e.left-1&&t.left<=e.right+1&&t.top>=e.top-1&&t.top<=e.bottom+1}function R(t,e){var n=t.parentNode;return n&&/^li$/i.test(n.nodeName)&&e.left(s.left+s.right)/2?1:-1}return t.docView.posFromDOM(o,i,a)}function z(t,e,n,r){for(var o=-1,i=e;;){if(i==t.dom)break;var a=t.docView.nearestDesc(i,!0);if(!a)return null;if(a.node.isBlock&&a.parent){var s=a.dom.getBoundingClientRect();if(s.left>r.left||s.top>r.top)o=a.posBefore;else{if(!(s.right-1?o:t.docView.posFromDOM(e,n)}function F(t,e,n){var r=t.childNodes.length;if(r&&n.tope.top&&i++}o==t.dom&&i==o.childNodes.length-1&&1==o.lastChild.nodeType&&e.top>o.lastChild.getBoundingClientRect().bottom?l=t.state.doc.content.size:0!=i&&1==o.nodeType&&"BR"==o.childNodes[i-1].nodeName||(l=z(t,o,i,e))}null==l&&(l=L(t,f,e));var v=t.docView.nearestDesc(f,!0);return{pos:l,inside:v?v.posAtStart-v.border:-1}}function V(t,e){var n=t.getClientRects();return n.length?n[e<0?0:n.length-1]:t.getBoundingClientRect()}var q=/[\u0590-\u05f4\u0600-\u06ff\u0700-\u08ac]/;function H(t,e,n){var r=t.docView.domFromPos(e),o=r.node,i=r.offset,s=t.state.doc.resolve(e),c=s.parent.inlineContent,u=a.webkit||a.gecko;if(3==o.nodeType&&u&&q.test(o.nodeValue)){var l=V(v(o,i,i),n);if(a.gecko&&i&&/\s/.test(o.nodeValue[i-1])&&i=0&&i==b(o)&&o!=h)i=p(o)+1,o=o.parentNode}if(3==o.nodeType)return n<0?W(V(v(o,i-1,i),1),!1):W(V(v(o,i,i+1),-1),!0);if(!c){if(i&&(n<0||i==b(o))){var m=o.childNodes[i-1];if(1==m.nodeType)return U(m.getBoundingClientRect(),!1)}if(i=0)}if(i&&(n<0||i==b(o))){var y=o.childNodes[i-1],w=3==y.nodeType?v(y,b(y)-(u?0:1)):1==y.nodeType&&"BR"!=y.nodeName?y:null;if(w)return W(V(w,1),!1)}if(i=0)}function W(t,e){if(0==t.width)return t;var n=e?t.left:t.right;return{top:t.top,bottom:t.bottom,left:n,right:n}}function U(t,e){if(0==t.height)return t;var n=e?t.top:t.bottom;return{top:n,bottom:n,left:t.left,right:t.right}}function K(t,e,n){var r=t.state,o=t.root.activeElement;r!=e&&t.updateState(e),o!=t.dom&&t.focus();try{return n()}finally{r!=e&&t.updateState(r),o!=t.dom&&o&&o.focus()}}function J(t,e,n){var r=e.selection,o="up"==n?r.$anchor.min(r.$head):r.$anchor.max(r.$head);return K(t,e,(function(){for(var e=t.docView.domFromPos(o.pos),r=e.node;;){var i=t.docView.nearestDesc(r,!0);if(!i)break;if(i.node.isBlock){r=i.dom;break}r=i.dom.parentNode}for(var a=H(t,o.pos,1),s=r.firstChild;s;s=s.nextSibling){var c=void 0;if(1==s.nodeType)c=s.getClientRects();else{if(3!=s.nodeType)continue;c=v(s,0,s.nodeValue.length).getClientRects()}for(var u=0;ul.top&&("up"==n?l.bottoma.bottom-1))return!1}}return!0}))}var Y=/[\u0590-\u08ac]/;function G(t,e,n){var r=e.selection,o=r.$head;if(!o.parent.isTextblock)return!1;var i=o.parentOffset,a=!i,s=i==o.parent.content.size,c=getSelection();return Y.test(o.parent.textContent)&&c.modify?K(t,e,(function(){var e=c.getRangeAt(0),r=c.focusNode,i=c.focusOffset,a=c.caretBidiLevel;c.modify("move",n,"character");var s=o.depth?t.docView.domAfterPos(o.before()):t.dom,u=!s.contains(1==c.focusNode.nodeType?c.focusNode:c.focusNode.parentNode)||r==c.focusNode&&i==c.focusOffset;return c.removeAllRanges(),c.addRange(e),null!=a&&(c.caretBidiLevel=a),u})):"left"==n||"backward"==n?a:s}var X=null,Z=null,Q=!1;function tt(t,e,n){return X==e&&Z==n?Q:(X=e,Z=n,Q="up"==n||"down"==n?J(t,e,n):G(t,e,n))}var et=0,nt=1,rt=2,ot=3,it=function(t,e,n,r){this.parent=t,this.children=e,this.dom=n,n.pmViewDesc=this,this.contentDOM=r,this.dirty=et},at={beforePosition:{configurable:!0},size:{configurable:!0},border:{configurable:!0},posBefore:{configurable:!0},posAtStart:{configurable:!0},posAfter:{configurable:!0},posAtEnd:{configurable:!0},contentLost:{configurable:!0}};it.prototype.matchesWidget=function(){return!1},it.prototype.matchesMark=function(){return!1},it.prototype.matchesNode=function(){return!1},it.prototype.matchesHack=function(){return!1},at.beforePosition.get=function(){return!1},it.prototype.parseRule=function(){return null},it.prototype.stopEvent=function(){return!1},at.size.get=function(){for(var t=0,e=0;e0:s)?this.posAtEnd:this.posAtStart},it.prototype.nearestDesc=function(t,e){for(var n=!0,r=t;r;r=r.parentNode){var o=this.getDesc(r);if(o&&(!e||o.node)){if(!n||!o.nodeDOM||(1==o.nodeDOM.nodeType?o.nodeDOM.contains(1==t.nodeType?t:t.parentNode):o.nodeDOM==t))return o;n=!1}}},it.prototype.getDesc=function(t){for(var e=t.pmViewDesc,n=e;n;n=n.parent)if(n==this)return e},it.prototype.posFromDOM=function(t,e,n){for(var r=t;r;r=r.parentNode){var o=this.getDesc(r);if(o)return o.localPosFromDOM(t,e,n)}return-1},it.prototype.descAt=function(t){for(var e=0,n=0;e=u&&e<=c-s.border&&s.node&&s.contentDOM&&this.contentDOM.contains(s.contentDOM))return s.parseRange(t,e,u);t=i;for(var l=a;l>0;l--){var f=this.children[l-1];if(f.size&&f.dom.parentNode==this.contentDOM&&!f.emptyChildAt(1)){r=p(f.dom)+1;break}t-=f.size}-1==r&&(r=0)}if(r>-1&&(c>e||a==this.children.length-1)){e=c;for(var d=a+1;dc&&ie){var y=f;f=p,p=y}var b=document.createRange();b.setEnd(p.node,p.offset),b.setStart(f.node,f.offset),d.removeAllRanges(),d.addRange(b)}}},it.prototype.ignoreMutation=function(t){return!this.contentDOM&&"selection"!=t.type},at.contentLost.get=function(){return this.contentDOM&&this.contentDOM!=this.dom&&!this.dom.contains(this.contentDOM)},it.prototype.markDirty=function(t,e){for(var n=0,r=0;r=n:tn){var a=n+o.border,s=i-o.border;if(t>=a&&e<=s)return this.dirty=t==n||e==i?rt:nt,void(t!=a||e!=s||!o.contentLost&&o.dom.parentNode==this.contentDOM?o.markDirty(t-a,e-a):o.dirty=ot);o.dirty=ot}n=i}this.dirty=rt},it.prototype.markParentsDirty=function(){for(var t=1,e=this.parent;e;e=e.parent,t++){var n=1==t?rt:nt;e.dirty0&&(i=Dt(i,0,t,r));for(var s=0;s=0&&!s&&c.syncToMarks(a==n.node.childCount?o["d"].none:n.node.child(a).marks,r,t),c.placeWidget(e,t,i)}),(function(e,n,o,a){c.syncToMarks(e.marks,r,t),c.findNodeMatch(e,n,o,a)||c.updateNextNode(e,n,o,t,a)||c.addNode(e,n,o,t,i),i+=e.nodeSize})),c.syncToMarks(st,r,t),this.node.isTextblock&&c.addTextblockHacks(),c.destroyRest(),(c.changed||this.dirty==rt)&&(s&&this.protectLocalComposition(t,s),mt(this.contentDOM,this.children,t),a.ios&&At(this.dom))},e.prototype.localCompositionNode=function(t,e){var n=t.state.selection,o=n.from,i=n.to;if(!(!(t.state.selection instanceof r["g"])||oe+this.node.content.size)){var a=t.root.getSelection(),s=Tt(a.focusNode,a.focusOffset);if(s&&this.dom.contains(s.parentNode)){var c=s.nodeValue,u=$t(this.node.content,c,o-e,i-e);return u<0?null:{node:s,pos:u,text:c}}}},e.prototype.protectLocalComposition=function(t,e){var n=e.node,r=e.pos,o=e.text;if(!this.getDesc(n)){for(var i=n;;i=i.parentNode){if(i.parentNode==this.contentDOM)break;while(i.previousSibling)i.parentNode.removeChild(i.previousSibling);while(i.nextSibling)i.parentNode.removeChild(i.nextSibling);i.pmViewDesc&&(i.pmViewDesc=null)}var a=new ut(this,i,n,o);t.compositionNodes.push(a),this.children=Dt(this.children,r,r+o.length,t,a)}},e.prototype.update=function(t,e,n,r){return!(this.dirty==ot||!t.sameMarkup(this.node))&&(this.updateInner(t,e,n,r),!0)},e.prototype.updateInner=function(t,e,n,r){this.updateOuterDeco(e),this.node=t,this.innerDeco=n,this.contentDOM&&this.updateChildren(r,this.posAtStart),this.dirty=et},e.prototype.updateOuterDeco=function(t){if(!Ot(t,this.outerDeco)){var e=1!=this.nodeDOM.nodeType,n=this.dom;this.dom=wt(this.dom,this.nodeDOM,bt(this.outerDeco,this.node,e),bt(t,this.node,e)),this.dom!=n&&(n.pmViewDesc=null,this.dom.pmViewDesc=this),this.outerDeco=t}},e.prototype.selectNode=function(){this.nodeDOM.classList.add("ProseMirror-selectednode"),!this.contentDOM&&this.node.type.spec.draggable||(this.dom.draggable=!0)},e.prototype.deselectNode=function(){this.nodeDOM.classList.remove("ProseMirror-selectednode"),!this.contentDOM&&this.node.type.spec.draggable||this.dom.removeAttribute("draggable")},Object.defineProperties(e.prototype,n),e}(it);function pt(t,e,n,r,o){return St(r,e,t),new ft(null,t,e,n,r,r,r,o,0)}var dt=function(t){function e(e,n,r,o,i,a,s){t.call(this,e,n,r,o,i,null,a,s)}return t&&(e.__proto__=t),e.prototype=Object.create(t&&t.prototype),e.prototype.constructor=e,e.prototype.parseRule=function(){var t=this.nodeDOM.parentNode;while(t&&t!=this.dom&&!t.pmIsDeco)t=t.parentNode;return{skip:t||!0}},e.prototype.update=function(t,e,n,r){return!(this.dirty==ot||this.dirty!=et&&!this.inParent()||!t.sameMarkup(this.node))&&(this.updateOuterDeco(e),this.dirty==et&&t.text==this.node.text||t.text==this.nodeDOM.nodeValue||(this.nodeDOM.nodeValue=t.text,r.trackWrites==this.nodeDOM&&(r.trackWrites=null)),this.node=t,this.dirty=et,!0)},e.prototype.inParent=function(){for(var t=this.parent.contentDOM,e=this.nodeDOM;e;e=e.parentNode)if(e==t)return!0;return!1},e.prototype.domFromPos=function(t){return{node:this.nodeDOM,offset:t}},e.prototype.localPosFromDOM=function(e,n,r){return e==this.nodeDOM?this.posAtStart+Math.min(n,this.node.text.length):t.prototype.localPosFromDOM.call(this,e,n,r)},e.prototype.ignoreMutation=function(t){return"characterData"!=t.type&&"selection"!=t.type},e.prototype.slice=function(t,n,r){var o=this.node.cut(t,n),i=document.createTextNode(o.text);return new e(this.parent,o,this.outerDeco,this.innerDeco,i,i,r)},e}(ft),ht=function(t){function e(){t.apply(this,arguments)}return t&&(e.__proto__=t),e.prototype=Object.create(t&&t.prototype),e.prototype.constructor=e,e.prototype.parseRule=function(){return{ignore:!0}},e.prototype.matchesHack=function(){return this.dirty==et},e}(it),vt=function(t){function e(e,n,r,o,i,a,s,c,u,l){t.call(this,e,n,r,o,i,a,s,u,l),this.spec=c}return t&&(e.__proto__=t),e.prototype=Object.create(t&&t.prototype),e.prototype.constructor=e,e.prototype.update=function(e,n,r,o){if(this.dirty==ot)return!1;if(this.spec.update){var i=this.spec.update(e,n);return i&&this.updateInner(e,n,r,o),i}return!(!this.contentDOM&&!e.isLeaf)&&t.prototype.update.call(this,e,n,r,o)},e.prototype.selectNode=function(){this.spec.selectNode?this.spec.selectNode():t.prototype.selectNode.call(this)},e.prototype.deselectNode=function(){this.spec.deselectNode?this.spec.deselectNode():t.prototype.deselectNode.call(this)},e.prototype.setSelection=function(e,n,r,o){this.spec.setSelection?this.spec.setSelection(e,n,r):t.prototype.setSelection.call(this,e,n,r,o)},e.prototype.destroy=function(){this.spec.destroy&&this.spec.destroy(),t.prototype.destroy.call(this)},e.prototype.stopEvent=function(t){return!!this.spec.stopEvent&&this.spec.stopEvent(t)},e.prototype.ignoreMutation=function(e){return this.spec.ignoreMutation?this.spec.ignoreMutation(e):t.prototype.ignoreMutation.call(this,e)},e}(ft);function mt(t,e,n){for(var r=t.firstChild,o=!1,i=0;i0&&o>=0;o--){var i=e[o],a=i.node;if(a){if(a!=t.child(r-1))break;n.push(i),--r}}return{nodes:n.reverse(),offset:r}}function Mt(t,e){return t.type.side-e.type.side}function Et(t,e,n,r){var o=e.locals(t),i=0;if(0!=o.length)for(var a=0,s=[],c=null,u=0;;){if(ai)s.push(o[a++]);var m=i+d.nodeSize;if(d.isText){var g=m;a0){if(t.childNodes.length>e&&3==t.childNodes[e].nodeType)return t.childNodes[e];t=t.childNodes[e-1],e=b(t)}else{if(!(1==t.nodeType&&e=n){var l=c.lastIndexOf(e,r-s);if(l>=0&&l+e.length+s>=n)return s+l}}}return-1}function Dt(t,e,n,r,o){for(var i=[],a=0,s=0;a=n||l<=e?i.push(c):(un&&i.push(c.slice(n-u,c.size,r)))}return i}function Nt(t,e){var n=t.root.getSelection(),o=t.state.doc;if(!n.focusNode)return null;var i=t.docView.nearestDesc(n.focusNode),a=i&&0==i.size,s=t.docView.posFromDOM(n.focusNode,n.focusOffset);if(s<0)return null;var c,u,l=o.resolve(s);if(S(n)){c=l;while(i&&!i.node)i=i.parent;if(i&&i.node.isAtom&&r["c"].isSelectable(i.node)&&i.parent&&(!i.node.isInline||!w(n.focusNode,n.focusOffset,i.dom))){var f=i.posBefore;u=new r["c"](s==f?l:o.resolve(f))}}else{var p=t.docView.posFromDOM(n.anchorNode,n.anchorOffset);if(p<0)return null;c=o.resolve(p)}if(!u){var d="pointer"==e||t.state.selection.head=this.preMatchOffset?this.preMatched[t-this.preMatchOffset]:null},kt.prototype.destroyBetween=function(t,e){if(t!=e){for(var n=t;n>1,i=Math.min(o,t.length);while(r-1)a>this.index&&(this.changed=!0,this.destroyBetween(this.index,a)),this.top=this.top.children[this.index];else{var c=lt.create(this.top,t[o],e,n);this.top.children.splice(this.index,0,c),this.top=c,this.changed=!0}this.index=0,o++}},kt.prototype.findNodeMatch=function(t,e,n,r){var o=-1,i=r<0?void 0:this.getPreMatch(r),a=this.top.children;if(i&&i.matchesNode(t,e,n))o=a.indexOf(i);else for(var s=this.index,c=Math.min(a.length,s+5);s-1&&s+this.preMatchOffset!=o)return!1;var c=a.dom,u=this.lock&&(c==this.lock||1==c.nodeType&&c.contains(this.lock.parentNode))&&!(t.isText&&a.node&&a.node.isText&&a.nodeDOM.nodeValue==t.text&&a.dirty!=ot&&Ot(e,a.outerDeco));if(!u&&a.update(t,e,n,r))return this.destroyBetween(this.index,i),a.dom!=c&&(this.changed=!0),this.index++,!0;break}}return!1},kt.prototype.addNode=function(t,e,n,r,o){this.top.children.splice(this.index++,0,ft.create(this.top,t,e,n,r,o)),this.changed=!0},kt.prototype.placeWidget=function(t,e,n){var r=this.index0?o.max(i):o.min(i),s=a.parent.inlineContent?a.depth?t.doc.resolve(e>0?a.after():a.before()):null:a;return s&&r["f"].findFrom(s,e)}function Jt(t,e){return t.dispatch(t.state.tr.setSelection(e).scrollIntoView()),!0}function Yt(t,e,n){var o=t.state.selection;if(!(o instanceof r["g"])){if(o instanceof r["c"]&&o.node.isInline)return Jt(t,new r["g"](e>0?o.$to:o.$from));var i=Kt(t.state,e);return!!i&&Jt(t,i)}if(!o.empty||n.indexOf("s")>-1)return!1;if(t.endOfTextblock(e>0?"right":"left")){var s=Kt(t.state,e);return!!(s&&s instanceof r["c"])&&Jt(t,s)}if(!(a.mac&&n.indexOf("m")>-1)){var c,u=o.$head,l=u.textOffset?null:e<0?u.nodeBefore:u.nodeAfter;if(!l||l.isText)return!1;var f=e<0?u.pos-l.nodeSize:u.pos;return!!(l.isAtom||(c=t.docView.descAt(f))&&!c.contentDOM)&&(r["c"].isSelectable(l)?Jt(t,new r["c"](e<0?t.state.doc.resolve(u.pos-l.nodeSize):u)):!!a.webkit&&Jt(t,new r["g"](t.state.doc.resolve(e<0?f:f+l.nodeSize))))}}function Gt(t){return 3==t.nodeType?t.nodeValue.length:t.childNodes.length}function Xt(t){var e=t.pmViewDesc;return e&&0==e.size&&(t.nextSibling||"BR"!=t.nodeName)}function Zt(t){var e=t.root.getSelection(),n=e.focusNode,r=e.focusOffset;if(n){var o,i,s=!1;for(a.gecko&&1==n.nodeType&&r0){if(1!=n.nodeType)break;var c=n.childNodes[r-1];if(Xt(c))o=n,i=--r;else{if(3!=c.nodeType)break;n=c,r=n.nodeValue.length}}else{if(te(n))break;var u=n.previousSibling;while(u&&Xt(u))o=n.parentNode,i=p(u),u=u.previousSibling;if(u)n=u,r=Gt(n);else{if(n=n.parentNode,n==t.dom)break;r=0}}s?ee(t,e,n,r):o&&ee(t,e,o,i)}}function Qt(t){var e=t.root.getSelection(),n=e.focusNode,r=e.focusOffset;if(n){for(var o,i,a=Gt(n);;)if(r-1)return!1;if(a.mac&&n.indexOf("m")>-1)return!1;var i=o.$from,s=o.$to;if(!i.parent.inlineContent||t.endOfTextblock(e<0?"up":"down")){var c=Kt(t.state,e);if(c&&c instanceof r["c"])return Jt(t,c)}if(!i.parent.inlineContent){var u=r["f"].findFrom(e<0?i:s,e);return!u||Jt(t,u)}return!1}function re(t,e){if(!(t.state.selection instanceof r["g"]))return!0;var n=t.state.selection,o=n.$head,i=n.$anchor,a=n.empty;if(!o.sameParent(i))return!0;if(!a)return!1;if(t.endOfTextblock(e>0?"forward":"backward"))return!0;var s=!o.textOffset&&(e<0?o.nodeBefore:o.nodeAfter);if(s&&!s.isText){var c=t.state.tr;return e<0?c.delete(o.pos-s.nodeSize,o.pos):c.delete(o.pos,o.pos+s.nodeSize),t.dispatch(c),!0}return!1}function oe(t,e,n){t.domObserver.stop(),e.contentEditable=n,t.domObserver.start()}function ie(t){if(a.safari&&!(t.state.selection.$head.parentOffset>0)){var e=t.root.getSelection(),n=e.focusNode,r=e.focusOffset;if(n&&1==n.nodeType&&0==r&&n.firstChild&&"false"==n.firstChild.contentEditable){var o=n.firstChild;oe(t,o,!0),setTimeout((function(){return oe(t,o,!1)}),20)}}}function ae(t){var e="";return t.ctrlKey&&(e+="c"),t.metaKey&&(e+="m"),t.altKey&&(e+="a"),t.shiftKey&&(e+="s"),e}function se(t,e){var n=e.keyCode,r=ae(e);return 8==n||a.mac&&72==n&&"c"==r?re(t,-1)||Zt(t):46==n||a.mac&&68==n&&"c"==r?re(t,1)||Qt(t):13==n||27==n||(37==n?Yt(t,-1,r)||Zt(t):39==n?Yt(t,1,r)||Qt(t):38==n?ne(t,-1,r)||Zt(t):40==n?ie(t)||ne(t,1,r)||Qt(t):r==(a.mac?"m":"c")&&(66==n||73==n||89==n||90==n))}function ce(t,e,n){var r=t.docView.parseRange(e,n),i=r.node,s=r.fromOffset,c=r.toOffset,u=r.from,l=r.to,f=t.root.getSelection(),p=null,d=f.anchorNode;if(d&&t.dom.contains(1==d.nodeType?d:d.parentNode)&&(p=[{node:d,offset:f.anchorOffset}],S(f)||p.push({node:f.focusNode,offset:f.focusOffset})),a.chrome&&8===t.lastKeyCode)for(var h=c;h>s;h--){var v=i.childNodes[h-1],m=v.pmViewDesc;if("BR"==v.nodeType&&!m){c=h;break}if(!m||m.size)break}var g=t.state.doc,y=t.someProp("domParser")||o["a"].fromSchema(t.state.schema),b=g.resolve(u),w=null,x=y.parse(i,{topNode:b.parent,topMatch:b.parent.contentMatchAt(b.index()),topOpen:!0,from:s,to:c,preserveWhitespace:!b.parent.type.spec.code||"full",editableContent:!0,findPositions:p,ruleFromNode:ue,context:b});if(p&&null!=p[0].pos){var O=p[0].pos,_=p[1]&&p[1].pos;null==_&&(_=O),w={anchor:O+u,head:_+u}}return{doc:x,sel:w,from:u,to:l}}function ue(t){var e=t.pmViewDesc;if(e)return e.parseRule();if("BR"==t.nodeName&&t.parentNode){if(a.safari&&/^(ul|ol)$/i.test(t.parentNode.nodeName)){var n=document.createElement("div");return n.appendChild(document.createElement("li")),{skip:n}}if(t.parentNode.lastChild==t||a.safari&&/^(tr|table)$/i.test(t.parentNode.nodeName))return{ignore:!0}}else if("IMG"==t.nodeName&&t.getAttribute("mark-placeholder"))return{ignore:!0}}function le(t,e,n,o,i){if(e<0){var s=t.lastSelectionTime>Date.now()-50?t.lastSelectionOrigin:null,c=Nt(t,s);if(c&&!t.state.selection.eq(c)){var u=t.state.tr.setSelection(c);"pointer"==s?u.setMeta("pointer",!0):"key"==s&&u.scrollIntoView(),t.dispatch(u)}}else{var l=t.state.doc.resolve(e),f=l.sharedDepth(n);e=l.before(f+1),n=t.state.doc.resolve(n).after(f+1);var p,d,h=t.state.selection,v=ce(t,e,n),m=t.state.doc,g=m.slice(v.from,v.to);8===t.lastKeyCode&&Date.now()-100t.state.selection.from&&y.start<=t.state.selection.from+2?y.start=t.state.selection.from:y.endA=t.state.selection.to-2&&(y.endB+=t.state.selection.to-y.endA,y.endA=t.state.selection.to)),a.ie&&a.ie_version<=11&&y.endB==y.start+1&&y.endA==y.start&&y.start>v.from&&"  "==v.doc.textBetween(y.start-v.from-1,y.start-v.from+1)&&(y.start--,y.endA--,y.endB--);var w,x=v.doc.resolveNoCache(y.start-v.from),S=v.doc.resolveNoCache(y.endB-v.from),_=x.sameParent(S)&&x.parent.inlineContent;if((a.ios&&t.lastIOSEnter>Date.now()-225&&(!_||i.some((function(t){return"DIV"==t.nodeName||"P"==t.nodeName})))||!_&&x.posy.start&&de(m,y.start,y.endA,x,S)&&t.someProp("handleKeyDown",(function(e){return e(t,O(8,"Backspace"))})))a.android&&a.chrome&&t.domObserver.suppressSelectionUpdates();else{a.android&&!_&&x.start()!=S.start()&&0==S.parentOffset&&x.depth==S.depth&&v.sel&&v.sel.anchor==v.sel.head&&v.sel.head==y.endA&&(y.endB-=2,S=v.doc.resolveNoCache(y.endB-v.from),setTimeout((function(){t.someProp("handleKeyDown",(function(e){return e(t,O(13,"Enter"))}))}),20));var k,C,M,E,A=y.start,T=y.endA;if(_)if(x.pos==S.pos)a.ie&&a.ie_version<=11&&0==x.parentOffset&&(t.domObserver.suppressSelectionUpdates(),setTimeout((function(){return Pt(t)}),20)),k=t.state.tr.delete(A,T),C=m.resolve(y.start).marksAcross(m.resolve(y.endA));else if(y.endA==y.endB&&(E=m.resolve(y.start))&&(M=pe(x.parent.content.cut(x.parentOffset,S.parentOffset),E.parent.content.cut(E.parentOffset,y.endA-E.start()))))k=t.state.tr,"add"==M.type?k.addMark(A,T,M.mark):k.removeMark(A,T,M.mark);else if(x.parent.child(x.index()).isText&&x.index()==S.index()-(S.textOffset?0:1)){var $=x.parent.textBetween(x.parentOffset,S.parentOffset);if(t.someProp("handleTextInput",(function(e){return e(t,A,T,$)})))return;k=t.state.tr.insertText($,A,T)}if(k||(k=t.state.tr.replace(A,T,v.doc.slice(y.start-v.from,y.endB-v.from))),v.sel){var D=fe(t,k.doc,v.sel);D&&!(a.chrome&&a.android&&t.composing&&D.empty&&(D.head==A||D.head==k.mapping.map(T)-1)||a.ie&&D.empty&&D.head==A)&&k.setSelection(D)}C&&k.ensureMarks(C),t.dispatch(k.scrollIntoView())}}}function fe(t,e,n){return Math.max(n.anchor,n.head)>e.content.size?null:qt(t,e.resolve(n.anchor),e.resolve(n.head))}function pe(t,e){for(var n,r,i,a=t.firstChild.marks,s=e.firstChild.marks,c=a,u=s,l=0;ln||he(a,!0,!1)0&&(e||t.indexAfter(r)==t.node(r).childCount))r--,o++,e=!1;if(n){var i=t.node(r).maybeChild(t.indexAfter(r));while(i&&!i.isLeaf)i=i.firstChild,o++}return o}function ve(t,e,n,r,o){var i=t.findDiffStart(e,n);if(null==i)return null;var a=t.findDiffEnd(e,n+t.size,n+e.size),s=a.a,c=a.b;if("end"==o){var u=Math.max(0,i-Math.min(s,c));r-=s+u-i}if(s=s?i-r:0;i-=l,c=i+(c-s),s=i}else if(c=c?i-r:0;i-=f,s=i+(s-c),c=i}return{start:i,endA:s,endB:c}}function me(t,e){var n=[],r=e.content,i=e.openStart,a=e.openEnd;while(i>1&&a>1&&1==r.childCount&&1==r.firstChild.childCount){i--,a--;var s=r.firstChild;n.push(s.type.name,s.attrs!=s.type.defaultAttrs?s.attrs:null),r=s.content}var c=t.someProp("clipboardSerializer")||o["b"].fromSchema(t.state.schema),u=Ce(),l=u.createElement("div");l.appendChild(c.serializeFragment(r,{document:u}));var f,p=l.firstChild;while(p&&1==p.nodeType&&(f=_e[p.nodeName.toLowerCase()])){for(var d=f.length-1;d>=0;d--){var h=u.createElement(f[d]);while(l.firstChild)h.appendChild(l.firstChild);l.appendChild(h)}p=l.firstChild}p&&1==p.nodeType&&p.setAttribute("data-pm-slice",i+" "+a+" "+JSON.stringify(n));var v=t.someProp("clipboardTextSerializer",(function(t){return t(e)}))||e.content.textBetween(0,e.content.size,"\n\n");return{dom:l,text:v}}function ge(t,e,n,r,i){var a,s,c=i.parent.type.spec.code;if(!n&&!e)return null;var u=e&&(r||c||!n);if(u){if(t.someProp("transformPastedText",(function(t){e=t(e,c||r)})),c)return new o["j"](o["c"].from(t.state.schema.text(e)),0,0);var l=t.someProp("clipboardTextParser",(function(t){return t(e,i,r)}));l?s=l:(a=document.createElement("div"),e.trim().split(/(?:\r\n?|\n)+/).forEach((function(t){a.appendChild(document.createElement("p")).textContent=t})))}else t.someProp("transformPastedHTML",(function(t){n=t(n)})),a=Me(n);var f=a&&a.querySelector("[data-pm-slice]"),p=f&&/^(\d+) (\d+) (.*)/.exec(f.getAttribute("data-pm-slice"));if(!s){var d=t.someProp("clipboardParser")||t.someProp("domParser")||o["a"].fromSchema(t.state.schema);s=d.parseSlice(a,{preserveWhitespace:!(!u&&!p),context:i})}return s=p?Ee(Oe(s,+p[1],+p[2]),p[3]):o["j"].maxOpen(ye(s.content,i),!1),t.someProp("transformPasted",(function(t){s=t(s)})),s}function ye(t,e){if(t.childCount<2)return t;for(var n=function(n){var r=e.node(n),i=r.contentMatchAt(e.index(n)),a=void 0,s=[];if(t.forEach((function(t){if(s){var e,n=i.findWrapping(t.type);if(!n)return s=null;if(e=s.length&&a.length&&we(n,a,t,s[s.length-1],0))s[s.length-1]=e;else{s.length&&(s[s.length-1]=xe(s[s.length-1],a.length));var r=be(t,n);s.push(r),i=i.matchType(r.type,r.attrs),a=n}}})),s)return{v:o["c"].from(s)}},r=e.depth;r>=0;r--){var i=n(r);if(i)return i.v}return t}function be(t,e,n){void 0===n&&(n=0);for(var r=e.length-1;r>=n;r--)t=e[r].create(null,o["c"].from(t));return t}function we(t,e,n,r,i){if(i=n&&(c=e<0?s.contentMatchAt(0).fillBefore(c,t.childCount>1||a<=i).append(c):c.append(s.contentMatchAt(s.childCount).fillBefore(o["c"].empty,!0))),t.replaceChild(e<0?0:t.childCount-1,s.copy(c))}function Oe(t,e,n){return e]*>)*/.exec(t);e&&(t=t.slice(e[0].length));var n,r=Ce().createElement("div"),o=/(?:]*>)*<([a-z][^>\s]+)/i.exec(t),i=0;(n=o&&_e[o[1].toLowerCase()])&&(t=n.map((function(t){return"<"+t+">"})).join("")+t+n.map((function(t){return""})).reverse().join(""),i=n.length),r.innerHTML=t;for(var a=0;a=0;c-=2){var u=r.nodes[n[c]];if(!u||u.hasRequiredAttrs())break;i=o["c"].from(u.create(n[c+1],i)),a++,s++}return new o["j"](i,a,s)}var Ae={childList:!0,characterData:!0,characterDataOldValue:!0,attributes:!0,attributeOldValue:!0,subtree:!0},Te=a.ie&&a.ie_version<=11,$e=function(){this.anchorNode=this.anchorOffset=this.focusNode=this.focusOffset=null};$e.prototype.set=function(t){this.anchorNode=t.anchorNode,this.anchorOffset=t.anchorOffset,this.focusNode=t.focusNode,this.focusOffset=t.focusOffset},$e.prototype.eq=function(t){return t.anchorNode==this.anchorNode&&t.anchorOffset==this.anchorOffset&&t.focusNode==this.focusNode&&t.focusOffset==this.focusOffset};var De=function(t,e){var n=this;this.view=t,this.handleDOMChange=e,this.queue=[],this.flushingSoon=-1,this.observer=window.MutationObserver&&new window.MutationObserver((function(t){for(var e=0;et.target.nodeValue.length}))?n.flushSoon():n.flush()})),this.currentSelection=new $e,Te&&(this.onCharData=function(t){n.queue.push({target:t.target,type:"characterData",oldValue:t.prevValue}),n.flushSoon()}),this.onSelectionChange=this.onSelectionChange.bind(this),this.suppressingSelectionUpdates=!1};De.prototype.flushSoon=function(){var t=this;this.flushingSoon<0&&(this.flushingSoon=window.setTimeout((function(){t.flushingSoon=-1,t.flush()}),20))},De.prototype.forceFlush=function(){this.flushingSoon>-1&&(window.clearTimeout(this.flushingSoon),this.flushingSoon=-1,this.flush())},De.prototype.start=function(){this.observer&&this.observer.observe(this.view.dom,Ae),Te&&this.view.dom.addEventListener("DOMCharacterDataModified",this.onCharData),this.connectSelection()},De.prototype.stop=function(){var t=this;if(this.observer){var e=this.observer.takeRecords();if(e.length){for(var n=0;n-1)){var t=this.observer?this.observer.takeRecords():[];this.queue.length&&(t=this.queue.concat(t),this.queue.length=0);var e=this.view.root.getSelection(),n=!this.suppressingSelectionUpdates&&!this.currentSelection.eq(e)&&Wt(this.view)&&!this.ignoreSelectionChange(e),r=-1,o=-1,i=!1,s=[];if(this.view.editable)for(var c=0;c1){var l=s.filter((function(t){return"BR"==t.nodeName}));if(2==l.length){var f=l[0],p=l[1];f.parentNode&&f.parentNode.parentNode==p.parentNode?p.remove():f.remove()}}(r>-1||n)&&(r>-1&&(this.view.docView.markDirty(r,o),Pe(this.view)),this.handleDOMChange(r,o,i,s),this.view.docView.dirty?this.view.updateState(this.view.state):this.currentSelection.eq(e)||Pt(this.view))}},De.prototype.registerMutation=function(t,e){if(e.indexOf(t.target)>-1)return null;var n=this.view.docView.nearestDesc(t.target);if("attributes"==t.type&&(n==this.view.docView||"contenteditable"==t.attributeName||"style"==t.attributeName&&!t.oldValue&&!t.target.getAttribute("style")))return null;if(!n||n.ignoreMutation(t))return null;if("childList"==t.type){var r=t.previousSibling,o=t.nextSibling;if(a.ie&&a.ie_version<=11&&t.addedNodes.length)for(var i=0;ii.depth?e(t,n,i.nodeAfter,i.before(r),o,!0):e(t,n,i.node(r),i.before(r),o,!1)})))return{v:!0}},s=i.depth+1;s>0;s--){var c=a(s);if(c)return c.v}return!1}function Ke(t,e,n){t.focused||t.focus();var r=t.state.tr.setSelection(e);"pointer"==n&&r.setMeta("pointer",!0),t.dispatch(r)}function Je(t,e){if(-1==e)return!1;var n=t.state.doc.resolve(e),o=n.nodeAfter;return!!(o&&o.isAtom&&r["c"].isSelectable(o))&&(Ke(t,new r["c"](n),"pointer"),!0)}function Ye(t,e){if(-1==e)return!1;var n,o,i=t.state.selection;i instanceof r["c"]&&(n=i.node);for(var a=t.state.doc.resolve(e),s=a.depth+1;s>0;s--){var c=s>a.depth?a.nodeAfter:a.node(s);if(r["c"].isSelectable(c)){o=n&&i.$from.depth>0&&s>=i.$from.depth&&a.before(i.$from.depth+1)==i.$from.pos?a.before(i.$from.depth):a.before(s);break}}return null!=o&&(Ke(t,r["c"].create(t.state.doc,o),"pointer"),!0)}function Ge(t,e,n,r,o){return Ue(t,"handleClickOn",e,n,r)||t.someProp("handleClick",(function(n){return n(t,e,r)}))||(o?Ye(t,n):Je(t,n))}function Xe(t,e,n,r){return Ue(t,"handleDoubleClickOn",e,n,r)||t.someProp("handleDoubleClick",(function(n){return n(t,e,r)}))}function Ze(t,e,n,r){return Ue(t,"handleTripleClickOn",e,n,r)||t.someProp("handleTripleClick",(function(n){return n(t,e,r)}))||Qe(t,n)}function Qe(t,e){var n=t.state.doc;if(-1==e)return!!n.inlineContent&&(Ke(t,r["g"].create(n,0,n.content.size),"pointer"),!0);for(var o=n.resolve(e),i=o.depth+1;i>0;i--){var a=i>o.depth?o.nodeAfter:o.node(i),s=o.before(i);if(a.inlineContent)Ke(t,r["g"].create(n,s+1,s+1+a.content.size),"pointer");else{if(!r["c"].isSelectable(a))continue;Ke(t,r["c"].create(n,s),"pointer")}return!0}}function tn(t){return cn(t)}Ie.keydown=function(t,e){if(t.shiftKey=16==e.keyCode||e.shiftKey,!rn(t,e))if(t.domObserver.forceFlush(),t.lastKeyCode=e.keyCode,t.lastKeyCodeTime=Date.now(),!a.ios||13!=e.keyCode||e.ctrlKey||e.altKey||e.metaKey)t.someProp("handleKeyDown",(function(n){return n(t,e)}))||se(t,e)?e.preventDefault():Le(t,"key");else{var n=Date.now();t.lastIOSEnter=n,t.lastIOSEnterFallbackTimeout=setTimeout((function(){t.lastIOSEnter==n&&(t.someProp("handleKeyDown",(function(e){return e(t,O(13,"Enter"))})),t.lastIOSEnter=0)}),200)}},Ie.keyup=function(t,e){16==e.keyCode&&(t.shiftKey=!1)},Ie.keypress=function(t,e){if(!(rn(t,e)||!e.charCode||e.ctrlKey&&!e.altKey||a.mac&&e.metaKey))if(t.someProp("handleKeyPress",(function(n){return n(t,e)})))e.preventDefault();else{var n=t.state.selection;if(!(n instanceof r["g"])||!n.$from.sameParent(n.$to)){var o=String.fromCharCode(e.charCode);t.someProp("handleTextInput",(function(e){return e(t,n.$from.pos,n.$to.pos,o)}))||t.dispatch(t.state.tr.insertText(o).scrollIntoView()),e.preventDefault()}}};var en=a.mac?"metaKey":"ctrlKey";je.mousedown=function(t,e){t.shiftKey=e.shiftKey;var n=tn(t),r=Date.now(),o="singleClick";r-t.lastClick.time<500&&We(e,t.lastClick)&&!e[en]&&("singleClick"==t.lastClick.type?o="doubleClick":"doubleClick"==t.lastClick.type&&(o="tripleClick")),t.lastClick={time:r,x:e.clientX,y:e.clientY,type:o};var i=t.posAtCoords(He(e));i&&("singleClick"==o?t.mouseDown=new nn(t,i,e,n):("doubleClick"==o?Xe:Ze)(t,i.pos,i.inside,e)?e.preventDefault():Le(t,"pointer"))};var nn=function(t,e,n,o){var i,s,c=this;if(this.view=t,this.startDoc=t.state.doc,this.pos=e,this.event=n,this.flushed=o,this.selectNode=n[en],this.allowDefault=n.shiftKey,e.inside>-1)i=t.state.doc.nodeAt(e.inside),s=e.inside;else{var u=t.state.doc.resolve(e.pos);i=u.parent,s=u.depth?u.before():0}this.mightDrag=null;var l=o?null:n.target,f=l?t.docView.nearestDesc(l,!0):null;this.target=f?f.dom:null,(i.type.spec.draggable&&!1!==i.type.spec.selectable||t.state.selection instanceof r["c"]&&s==t.state.selection.from)&&(this.mightDrag={node:i,pos:s,addAttr:this.target&&!this.target.draggable,setUneditable:this.target&&a.gecko&&!this.target.hasAttribute("contentEditable")}),this.target&&this.mightDrag&&(this.mightDrag.addAttr||this.mightDrag.setUneditable)&&(this.view.domObserver.stop(),this.mightDrag.addAttr&&(this.target.draggable=!0),this.mightDrag.setUneditable&&setTimeout((function(){return c.target.setAttribute("contentEditable","false")}),20),this.view.domObserver.start()),t.root.addEventListener("mouseup",this.up=this.up.bind(this)),t.root.addEventListener("mousemove",this.move=this.move.bind(this)),Le(t,"pointer")};function rn(t,e){return!!t.composing||!!(a.safari&&Math.abs(e.timeStamp-t.compositionEndedAt)<500)&&(t.compositionEndedAt=-2e8,!0)}nn.prototype.done=function(){this.view.root.removeEventListener("mouseup",this.up),this.view.root.removeEventListener("mousemove",this.move),this.mightDrag&&this.target&&(this.view.domObserver.stop(),this.mightDrag.addAttr&&this.target.removeAttribute("draggable"),this.mightDrag.setUneditable&&this.target.removeAttribute("contentEditable"),this.view.domObserver.start()),this.view.mouseDown=null},nn.prototype.up=function(t){if(this.done(),this.view.dom.contains(3==t.target.nodeType?t.target.parentNode:t.target)){var e=this.pos;this.view.state.doc!=this.startDoc&&(e=this.view.posAtCoords(He(t))),this.allowDefault||!e?Le(this.view,"pointer"):Ge(this.view,e.pos,e.inside,t,this.selectNode)?t.preventDefault():this.flushed||a.safari&&this.mightDrag&&!this.mightDrag.node.isAtom||a.chrome&&!(this.view.state.selection instanceof r["g"])&&(e.pos==this.view.state.selection.from||e.pos==this.view.state.selection.to)?(Ke(this.view,r["f"].near(this.view.state.doc.resolve(e.pos)),"pointer"),t.preventDefault()):Le(this.view,"pointer")}},nn.prototype.move=function(t){!this.allowDefault&&(Math.abs(this.event.x-t.clientX)>4||Math.abs(this.event.y-t.clientY)>4)&&(this.allowDefault=!0),Le(this.view,"pointer")},je.touchdown=function(t){tn(t),Le(t,"pointer")},je.contextmenu=function(t){return tn(t)};var on=a.android?5e3:-1;function an(t,e){clearTimeout(t.composingTimeout),e>-1&&(t.composingTimeout=setTimeout((function(){return cn(t)}),e))}function sn(t){t.composing=!1;while(t.compositionNodes.length>0)t.compositionNodes.pop().markParentsDirty()}function cn(t,e){if(t.domObserver.forceFlush(),sn(t),e||t.docView.dirty){var n=Nt(t);return n&&!n.eq(t.state.selection)?t.dispatch(t.state.tr.setSelection(n)):t.updateState(t.state),!0}return!1}function un(t,e){if(t.dom.parentNode){var n=t.dom.parentNode.appendChild(document.createElement("div"));n.appendChild(e),n.style.cssText="position: fixed; left: -10000px; top: 10px";var r=getSelection(),o=document.createRange();o.selectNodeContents(e),t.dom.blur(),r.removeAllRanges(),r.addRange(o),setTimeout((function(){n.parentNode&&n.parentNode.removeChild(n),t.focus()}),50)}}Ie.compositionstart=Ie.compositionupdate=function(t){if(!t.composing){t.domObserver.flush();var e=t.state,n=e.selection.$from;if(e.selection.empty&&(e.storedMarks||!n.textOffset&&n.parentOffset&&n.nodeBefore.marks.some((function(t){return!1===t.type.spec.inclusive}))))t.markCursor=t.state.storedMarks||n.marks(),cn(t,!0),t.markCursor=null;else if(cn(t),a.gecko&&e.selection.empty&&n.parentOffset&&!n.textOffset&&n.nodeBefore.marks.length)for(var r=t.root.getSelection(),o=r.focusNode,i=r.focusOffset;o&&1==o.nodeType&&0!=i;){var s=i<0?o.lastChild:o.childNodes[i-1];if(!s)break;if(3==s.nodeType){r.collapse(s,s.nodeValue.length);break}o=s,i=-1}t.composing=!0}an(t,on)},Ie.compositionend=function(t,e){t.composing&&(t.composing=!1,t.compositionEndedAt=e.timeStamp,an(t,20))};var ln=a.ie&&a.ie_version<15||a.ios&&a.webkit_version<604;function fn(t){return 0==t.openStart&&0==t.openEnd&&1==t.content.childCount?t.content.firstChild:null}function pn(t,e){if(t.dom.parentNode){var n=t.shiftKey||t.state.selection.$from.parent.type.spec.code,r=t.dom.parentNode.appendChild(document.createElement(n?"textarea":"div"));n||(r.contentEditable="true"),r.style.cssText="position: fixed; left: -10000px; top: 10px",r.focus(),setTimeout((function(){t.focus(),r.parentNode&&r.parentNode.removeChild(r),n?dn(t,r.value,null,e):dn(t,r.textContent,r.innerHTML,e)}),50)}}function dn(t,e,n,r){var i=ge(t,e,n,t.shiftKey,t.state.selection.$from);if(!t.someProp("handlePaste",(function(e){return e(t,r,i||o["j"].empty)}))&&i){var a=fn(i),s=a?t.state.tr.replaceSelectionWith(a,t.shiftKey):t.state.tr.replaceSelection(i);t.dispatch(s.scrollIntoView().setMeta("paste",!0).setMeta("uiEvent","paste"))}}je.copy=Ie.cut=function(t,e){var n=t.state.selection,r="cut"==e.type;if(!n.empty){var o=ln?null:e.clipboardData,i=n.content(),a=me(t,i),s=a.dom,c=a.text;o?(e.preventDefault(),o.clearData(),o.setData("text/html",s.innerHTML),o.setData("text/plain",c)):un(t,s),r&&t.dispatch(t.state.tr.deleteSelection().scrollIntoView().setMeta("uiEvent","cut"))}},Ie.paste=function(t,e){var n=ln?null:e.clipboardData,r=n&&n.getData("text/html"),o=n&&n.getData("text/plain");n&&(r||o||n.files.length)?(dn(t,o,r,e),e.preventDefault()):pn(t,e)};var hn=function(t,e){this.slice=t,this.move=e},vn=a.mac?"altKey":"ctrlKey";for(var mn in je.dragstart=function(t,e){var n=t.mouseDown;if(n&&n.done(),e.dataTransfer){var o=t.state.selection,i=o.empty?null:t.posAtCoords(He(e));if(i&&i.pos>=o.from&&i.pos<=(o instanceof r["c"]?o.to-1:o.to));else if(n&&n.mightDrag)t.dispatch(t.state.tr.setSelection(r["c"].create(t.state.doc,n.mightDrag.pos)));else if(e.target&&1==e.target.nodeType){var a=t.docView.nearestDesc(e.target,!0);if(!a||!a.node.type.spec.draggable||a==t.docView)return;t.dispatch(t.state.tr.setSelection(r["c"].create(t.state.doc,a.posBefore)))}var s=t.state.selection.content(),c=me(t,s),u=c.dom,l=c.text;e.dataTransfer.clearData(),e.dataTransfer.setData(ln?"Text":"text/html",u.innerHTML),ln||e.dataTransfer.setData("text/plain",l),t.dragging=new hn(s,!e[vn])}},je.dragend=function(t){var e=t.dragging;window.setTimeout((function(){t.dragging==e&&(t.dragging=null)}),50)},Ie.dragover=Ie.dragenter=function(t,e){return e.preventDefault()},Ie.drop=function(t,e){var n=t.dragging;if(t.dragging=null,e.dataTransfer){var a=t.posAtCoords(He(e));if(a){var s=t.state.doc.resolve(a.pos);if(s){var c=n&&n.slice||ge(t,e.dataTransfer.getData(ln?"Text":"text/plain"),ln?null:e.dataTransfer.getData("text/html"),!1,s),u=n&&!e[vn];if(t.someProp("handleDrop",(function(n){return n(t,e,c||o["j"].empty,u)})))e.preventDefault();else if(c){e.preventDefault();var l=c?Object(i["g"])(t.state.doc,s.pos,c):s.pos;null==l&&(l=s.pos);var f=t.state.tr;u&&f.deleteSelection();var p=f.mapping.map(l),d=0==c.openStart&&0==c.openEnd&&1==c.content.childCount,h=f.doc;if(d?f.replaceRangeWith(p,p,c.content.firstChild):f.replaceRange(p,p,c),!f.doc.eq(h)){var v=f.doc.resolve(p);if(d&&r["c"].isSelectable(c.content.firstChild)&&v.nodeAfter&&v.nodeAfter.sameMarkup(c.content.firstChild))f.setSelection(new r["c"](v));else{var m=f.mapping.map(l);f.mapping.maps[f.mapping.maps.length-1].forEach((function(t,e,n,r){return m=r})),f.setSelection(qt(t,v,f.doc.resolve(m)))}t.focus(),t.dispatch(f.setMeta("uiEvent","drop"))}}}}}},je.focus=function(t){t.focused||(t.domObserver.stop(),t.dom.classList.add("ProseMirror-focused"),t.domObserver.start(),t.focused=!0,setTimeout((function(){t.docView&&t.hasFocus()&&!t.domObserver.currentSelection.eq(t.root.getSelection())&&Pt(t)}),20))},je.blur=function(t){t.focused&&(t.domObserver.stop(),t.dom.classList.remove("ProseMirror-focused"),t.domObserver.start(),t.domObserver.currentSelection.set({}),t.focused=!1)},je.beforeinput=function(t,e){if(a.chrome&&a.android&&"deleteContentBackward"==e.inputType){var n=t.domChangeCount;setTimeout((function(){if(t.domChangeCount==n&&(t.dom.blur(),t.focus(),!t.someProp("handleKeyDown",(function(e){return e(t,O(8,"Backspace"))})))){var e=t.state.selection,r=e.$cursor;r&&r.pos>0&&t.dispatch(t.state.tr.delete(r.pos-1,r.pos).scrollIntoView())}}),50)}},Ie)je[mn]=Ie[mn];function gn(t,e){if(t==e)return!0;for(var n in t)if(t[n]!==e[n])return!1;for(var r in e)if(!(r in t))return!1;return!0}var yn=function(t,e){this.spec=e||_n,this.side=this.spec.side||0,this.toDOM=t};yn.prototype.map=function(t,e,n,r){var o=t.mapResult(e.from+r,this.side<0?-1:1),i=o.pos,a=o.deleted;return a?null:new xn(i-n,i-n,this)},yn.prototype.valid=function(){return!0},yn.prototype.eq=function(t){return this==t||t instanceof yn&&(this.spec.key&&this.spec.key==t.spec.key||this.toDOM==t.toDOM&&gn(this.spec,t.spec))};var bn=function(t,e){this.spec=e||_n,this.attrs=t};bn.prototype.map=function(t,e,n,r){var o=t.map(e.from+r,this.spec.inclusiveStart?-1:1)-n,i=t.map(e.to+r,this.spec.inclusiveEnd?1:-1)-n;return o>=i?null:new xn(o,i,this)},bn.prototype.valid=function(t,e){return e.from=t&&(!o||o(a.spec))&&n.push(a.copy(a.from+r,a.to+r))}for(var s=0;st){var c=this.children[s]+1;this.children[s+2].findInner(t-c,e-c,n,r+c,o)}},kn.prototype.map=function(t,e,n){return this==Cn||0==t.maps.length?this:this.mapInner(t,e,0,0,n||_n)},kn.prototype.mapInner=function(t,e,n,r,o){for(var i,a=0;aa&&u.to=t){this.children[o]==t&&(n=this.children[o+2]);break}for(var i=t+1,a=i+e.content.size,s=0;si&&c.type instanceof bn){var u=Math.max(i,c.from)-i,l=Math.min(a,c.to)-i;uc+i||(e>=s[a]+i?s[a+1]=-1:n>=o&&(u=r-n-(e-t))&&(s[a]+=u,s[a+1]+=u))}},u=0;u=r.content.size){l=!0;continue}var h=n.map(t[f+1]+i,-1),v=h-o,m=r.content.findIndex(d),g=m.index,y=m.offset,b=r.maybeChild(g);if(b&&y==d&&y+b.nodeSize==v){var w=s[f+2].mapInner(n,b,p+1,t[f]+i+1,a);w!=Cn?(s[f]=d,s[f+1]=v,s[f+2]=w):(s[f+1]=-2,l=!0)}else l=!0}if(l){var x=Tn(s,t,e||[],n,o,i,a),S=Nn(x,r,0,a);e=S.local;for(var O=0;On&&a.to0)e++;t.splice(e,0,n)}function Rn(t){var e=[];return t.someProp("decorations",(function(n){var r=n(t.state);r&&r!=Cn&&e.push(r)})),t.cursorWrapper&&e.push(kn.create(t.state.doc,[t.cursorWrapper.deco])),Mn.from(e)}Mn.prototype.forChild=function(t,e){if(e.isLeaf)return kn.empty;for(var n=[],r=0;ro.scrollToSelection?"to selection":"preserve",p=i||!this.docView.matchesNode(t.doc,l,u);!p&&t.selection.eq(o.selection)||(s=!0);var d="preserve"==f&&s&&null==this.dom.style.overflowAnchor&&E(this);if(s){this.domObserver.stop();var h=p&&(a.ie||a.chrome)&&!this.composing&&!o.selection.empty&&!t.selection.empty&&qn(o.selection,t.selection);if(p){var v=a.chrome?this.trackWrites=this.root.getSelection().focusNode:null;!i&&this.docView.update(t.doc,l,u,this)||(this.docView.updateOuterDeco([]),this.docView.destroy(),this.docView=pt(t.doc,l,u,this.dom,this)),v&&!this.trackWrites&&(h=!0)}h||!(this.mouseDown&&this.domObserver.currentSelection.eq(this.root.getSelection())&&Ut(this))?Pt(this,h):(Bt(this,t.selection),this.domObserver.setCurSelection()),this.domObserver.start()}if(this.updatePluginViews(o),"reset"==f)this.dom.scrollTop=0;else if("to selection"==f){var m=this.root.getSelection().focusNode;this.someProp("handleScrollToSelection",(function(t){return t(n)}))||(t.selection instanceof r["c"]?M(this,this.docView.domAfterPos(t.selection.from).getBoundingClientRect(),m):M(this,this.coordsAtPos(t.selection.head,1),m))}else d&&T(d)},Ln.prototype.destroyPluginViews=function(){var t;while(t=this.pluginViews.pop())t.destroy&&t.destroy()},Ln.prototype.updatePluginViews=function(t){if(t&&t.plugins==this.state.plugins)for(var e=0;e=e?t:""+Array(e+1-r.length).join(n)+t},h={s:d,z:function(t){var e=-t.utcOffset(),n=Math.abs(e),r=Math.floor(n/60),o=n%60;return(e<=0?"+":"-")+d(r,2,"0")+":"+d(o,2,"0")},m:function t(e,n){if(e.date()=t}))};e.default=o},6235:function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0;var r=n("78ef"),o=(0,r.regex)("alpha",/^[a-zA-Z]*$/);e.default=o},6417:function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0;var r=n("78ef"),o=function(t){return(0,r.withParams)({type:"not"},(function(e,n){return!(0,r.req)(e)||!t.call(this,e,n)}))};e.default=o},"665f":function(t,e,n){"use strict";n.d(e,"a",(function(){return p})),n.d(e,"b",(function(){return m})),n.d(e,"c",(function(){return b})),n.d(e,"d",(function(){return v})),n.d(e,"e",(function(){return d}));var r=n("0ac0"),o=n("304a"),i=["ol",0],a=["ul",0],s=["li",0],c={attrs:{order:{default:1}},parseDOM:[{tag:"ol",getAttrs:function(t){return{order:t.hasAttribute("start")?+t.getAttribute("start"):1}}}],toDOM:function(t){return 1==t.attrs.order?i:["ol",{start:t.attrs.order},0]}},u={parseDOM:[{tag:"ul"}],toDOM:function(){return a}},l={parseDOM:[{tag:"li"}],toDOM:function(){return s},defining:!0};function f(t,e){var n={};for(var r in t)n[r]=t[r];for(var o in e)n[o]=e[o];return n}function p(t,e,n){return t.append({ordered_list:f(c,{content:"list_item+",group:n}),bullet_list:f(u,{content:"list_item+",group:n}),list_item:f(l,{content:e})})}function d(t,e){return function(n,i){var a=n.selection,s=a.$from,c=a.$to,u=s.blockRange(c),l=!1,f=u;if(!u)return!1;if(u.depth>=2&&s.node(u.depth-1).type.compatibleContent(t)&&0==u.startIndex){if(0==s.index(u.depth-1))return!1;var p=n.doc.resolve(u.start-2);f=new o["g"](p,p,u.depth),u.endIndex=0;c--)s=o["c"].from(n[c].type.create(n[c].attrs,s));t.step(new r["b"](e.start-(i?2:0),e.end,e.start,e.end,new o["j"](s,0,0),n.length,!0));for(var u=0,l=0;l0,p=a.depth-(f?1:2);p>=a.depth-3;p--)l=o["c"].from(a.node(p).copy(l));l=l.append(o["c"].from(t.createAndFill()));var d=e.tr.replace(a.before(f?null:-1),a.after(-3),new o["j"](l,f?3:2,2));d.setSelection(e.selection.constructor.near(d.doc.resolve(a.pos+(f?3:2)))),n(d.scrollIntoView())}return!0}var h=s.pos==a.end()?u.contentMatchAt(0).defaultType:null,v=e.tr.delete(a.pos,s.pos),m=h&&[null,{type:h}];return!!Object(r["f"])(v.doc,a.pos,2,m)&&(n&&n(v.split(a.pos,2,m).scrollIntoView()),!0)}}function m(t){return function(e,n){var r=e.selection,o=r.$from,i=r.$to,a=o.blockRange(i,(function(e){return e.childCount&&e.firstChild.type==t}));return!!a&&(!n||(o.node(a.depth-1).type==t?g(e,n,t,a):y(e,n,a)))}}function g(t,e,n,i){var a=t.tr,s=i.end,c=i.$to.end(i.depth);return su;c--)s-=a.child(c).nodeSize,i.delete(s-1,s+1);var l=i.doc.resolve(n.start),f=l.nodeAfter,p=0==n.startIndex,d=n.endIndex==a.childCount,h=l.node(-1),v=l.index(-1);if(!h.canReplace(v+(p?0:1),v+1,f.content.append(d?o["c"].empty:o["c"].from(a))))return!1;var m=l.pos,g=m+f.nodeSize;return i.step(new r["b"](m-(p?1:0),g+(d?1:0),m+1,g-1,new o["j"]((p?o["c"].empty:o["c"].from(a.copy(o["c"].empty))).append(d?o["c"].empty:o["c"].from(a.copy(o["c"].empty))),p?0:1,d?0:1),p?0:1)),e(i.scrollIntoView()),!0}function b(t){return function(e,n){var i=e.selection,a=i.$from,s=i.$to,c=a.blockRange(s,(function(e){return e.childCount&&e.firstChild.type==t}));if(!c)return!1;var u=c.startIndex;if(0==u)return!1;var l=c.parent,f=l.child(u-1);if(f.type!=t)return!1;if(n){var p=f.lastChild&&f.lastChild.type==l.type,d=o["c"].from(p?t.create():null),h=new o["j"](o["c"].from(t.create(null,o["c"].from(l.type.create(null,d)))),p?3:1,0),v=c.start,m=c.end;n(e.tr.step(new r["b"](v-(p?3:1),m,v,m,h,1,!0)).scrollIntoView())}return!0}}},"69f3":function(t,e,n){var r,o,i,a=n("7f9a"),s=n("da84"),c=n("861d"),u=n("9112"),l=n("5135"),f=n("c6cd"),p=n("f772"),d=n("d012"),h=s.WeakMap,v=function(t){return i(t)?o(t):r(t,{})},m=function(t){return function(e){var n;if(!c(e)||(n=o(e)).type!==t)throw TypeError("Incompatible receiver, "+t+" required");return n}};if(a){var g=f.state||(f.state=new h),y=g.get,b=g.has,w=g.set;r=function(t,e){return e.facade=t,w.call(g,t,e),e},o=function(t){return y.call(g,t)||{}},i=function(t){return b.call(g,t)}}else{var x=p("state");d[x]=!0,r=function(t,e){return e.facade=t,u(t,x,e),e},o=function(t){return l(t,x)?t[x]:{}},i=function(t){return l(t,x)}}t.exports={set:r,get:o,has:i,enforce:v,getterFor:m}},"6eeb":function(t,e,n){var r=n("da84"),o=n("9112"),i=n("5135"),a=n("ce4e"),s=n("8925"),c=n("69f3"),u=c.get,l=c.enforce,f=String(String).split("String");(t.exports=function(t,e,n,s){var c,u=!!s&&!!s.unsafe,p=!!s&&!!s.enumerable,d=!!s&&!!s.noTargetGet;"function"==typeof n&&("string"!=typeof e||i(n,"name")||o(n,"name",e),c=l(n),c.source||(c.source=f.join("string"==typeof e?e:""))),t!==r?(u?!d&&t[e]&&(p=!0):delete t[e],p?t[e]=n:o(t,e,n)):p?t[e]=n:a(e,n)})(Function.prototype,"toString",(function(){return"function"==typeof this&&u(this).source||s(this)}))},7418:function(t,e){e.f=Object.getOwnPropertySymbols},"772d":function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0;var r=n("78ef"),o=/^(?:(?:https?|ftp):\/\/)(?:\S+(?::\S*)?@)?(?:(?!(?:10|127)(?:\.\d{1,3}){3})(?!(?:169\.254|192\.168)(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)(?:\.(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)*(?:\.(?:[a-z\u00a1-\uffff]{2,})))(?::\d{2,5})?(?:[/?#]\S*)?$/i,i=(0,r.regex)("url",o);e.default=i},7839:function(t,e){t.exports=["constructor","hasOwnProperty","isPrototypeOf","propertyIsEnumerable","toLocaleString","toString","valueOf"]},"78ef":function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"withParams",{enumerable:!0,get:function(){return r.default}}),e.regex=e.ref=e.len=e.req=void 0;var r=o(n("8750"));function o(t){return t&&t.__esModule?t:{default:t}}function i(t){return i="function"===typeof Symbol&&"symbol"===typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"===typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t},i(t)}var a=function(t){if(Array.isArray(t))return!!t.length;if(void 0===t||null===t)return!1;if(!1===t)return!0;if(t instanceof Date)return!isNaN(t.getTime());if("object"===i(t)){for(var e in t)return!0;return!1}return!!String(t).length};e.req=a;var s=function(t){return Array.isArray(t)?t.length:"object"===i(t)?Object.keys(t).length:String(t).length};e.len=s;var c=function(t,e,n){return"function"===typeof t?t.call(e,n):n[t]};e.ref=c;var u=function(t,e){return(0,r.default)({type:t},(function(t){return!a(t)||e.test(t)}))};e.regex=u},"7f06":function(t,e,n){"use strict";n.d(e,"a",(function(){return b}));for(var r={8:"Backspace",9:"Tab",10:"Enter",12:"NumLock",13:"Enter",16:"Shift",17:"Control",18:"Alt",20:"CapsLock",27:"Escape",32:" ",33:"PageUp",34:"PageDown",35:"End",36:"Home",37:"ArrowLeft",38:"ArrowUp",39:"ArrowRight",40:"ArrowDown",44:"PrintScreen",45:"Insert",46:"Delete",59:";",61:"=",91:"Meta",92:"Meta",106:"*",107:"+",108:",",109:"-",110:".",111:"/",144:"NumLock",145:"ScrollLock",160:"Shift",161:"Shift",162:"Control",163:"Control",164:"Alt",165:"Alt",173:"-",186:";",187:"=",188:",",189:"-",190:".",191:"/",192:"`",219:"[",220:"\\",221:"]",222:"'",229:"q"},o={48:")",49:"!",50:"@",51:"#",52:"$",53:"%",54:"^",55:"&",56:"*",57:"(",59:":",61:"+",173:"_",186:":",187:"+",188:"<",189:"_",190:">",191:"?",192:"~",219:"{",220:"|",221:"}",222:'"',229:"Q"},i="undefined"!=typeof navigator&&/Chrome\/(\d+)/.exec(navigator.userAgent),a="undefined"!=typeof navigator&&/Apple Computer/.test(navigator.vendor),s="undefined"!=typeof navigator&&/Gecko\/\d+/.test(navigator.userAgent),c="undefined"!=typeof navigator&&/Mac/.test(navigator.platform),u="undefined"!=typeof navigator&&/MSIE \d|Trident\/(?:[7-9]|\d{2,})\..*rv:(\d+)/.exec(navigator.userAgent),l=i&&(c||+i[1]<57)||s&&c,f=0;f<10;f++)r[48+f]=r[96+f]=String(f);for(f=1;f<=24;f++)r[f+111]="F"+f;for(f=65;f<=90;f++)r[f]=String.fromCharCode(f+32),o[f]=String.fromCharCode(f);for(var p in r)o.hasOwnProperty(p)||(o[p]=r[p]);function d(t){var e=l&&(t.ctrlKey||t.altKey||t.metaKey)||(a||u)&&t.shiftKey&&t.key&&1==t.key.length,n=!e&&t.key||(t.shiftKey?o:r)[t.keyCode]||t.key||"Unidentified";return"Esc"==n&&(n="Escape"),"Del"==n&&(n="Delete"),"Left"==n&&(n="ArrowLeft"),"Up"==n&&(n="ArrowUp"),"Right"==n&&(n="ArrowRight"),"Down"==n&&(n="ArrowDown"),n}var h=n("5313"),v="undefined"!=typeof navigator&&/Mac/.test(navigator.platform);function m(t){var e,n,r,o,i=t.split(/-(?!$)/),a=i[i.length-1];"Space"==a&&(a=" ");for(var s=0;s127)&&(o=r[n.keyCode])&&o!=i){var c=e[y(o,n,!0)];if(c&&c(t.state,t.dispatch,t))return!0}else if(a&&n.shiftKey){var u=e[y(i,n,!0)];if(u&&u(t.state,t.dispatch,t))return!0}return!1}}},"7f9a":function(t,e,n){var r=n("da84"),o=n("8925"),i=r.WeakMap;t.exports="function"===typeof i&&/native code/.test(o(i))},"825a":function(t,e,n){var r=n("861d");t.exports=function(t){if(!r(t))throw TypeError(String(t)+" is not an object");return t}},"82f1":function(t,e,n){"use strict";var r=n("a026"),o=n("ee2b");function i(t){return i="function"===typeof Symbol&&"symbol"===typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"===typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t},i(t)}var a={selector:"vue-portal-target-".concat(o())},s=function(t){return a.selector=t},c="undefined"!==typeof window&&void 0!==("undefined"===typeof document?"undefined":i(document)),u=r["a"].extend({abstract:!0,name:"PortalOutlet",props:["nodes","tag"],data:function(t){return{updatedNodes:t.nodes}},render:function(t){var e=this.updatedNodes&&this.updatedNodes();return e?e.length<2&&!e[0].text?e:t(this.tag||"DIV",e):t()},destroyed:function(){var t=this.$el;t.parentNode.removeChild(t)}}),l=r["a"].extend({name:"VueSimplePortal",props:{disabled:{type:Boolean},prepend:{type:Boolean},selector:{type:String,default:function(){return"#".concat(a.selector)}},tag:{type:String,default:"DIV"}},render:function(t){if(this.disabled){var e=this.$scopedSlots&&this.$scopedSlots.default();return e?e.length<2&&!e[0].text?e:t(this.tag,e):t()}return t()},created:function(){this.getTargetEl()||this.insertTargetEl()},updated:function(){var t=this;this.$nextTick((function(){t.disabled||t.slotFn===t.$scopedSlots.default||(t.container.updatedNodes=t.$scopedSlots.default),t.slotFn=t.$scopedSlots.default}))},beforeDestroy:function(){this.unmount()},watch:{disabled:{immediate:!0,handler:function(t){t?this.unmount():this.$nextTick(this.mount)}}},methods:{getTargetEl:function(){if(c)return document.querySelector(this.selector)},insertTargetEl:function(){if(c){var t=document.querySelector("body"),e=document.createElement(this.tag);e.id=this.selector.substring(1),t.appendChild(e)}},mount:function(){var t=this.getTargetEl(),e=document.createElement("DIV");this.prepend&&t.firstChild?t.insertBefore(e,t.firstChild):t.appendChild(e),this.container=new u({el:e,parent:this,propsData:{tag:this.tag,nodes:this.$scopedSlots.default}})},unmount:function(){this.container&&(this.container.$destroy(),delete this.container)}}});function f(t){var e=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};t.component(e.name||"portal",l),e.defaultSelector&&s(e.defaultSelector)}"undefined"!==typeof window&&window.Vue&&window.Vue===r["a"]&&r["a"].use(f),e["a"]=f},"83ab":function(t,e,n){var r=n("d039");t.exports=!r((function(){return 7!=Object.defineProperty({},1,{get:function(){return 7}})[1]}))},"861d":function(t,e){t.exports=function(t){return"object"===typeof t?null!==t:"function"===typeof t}},8726:function(t,e,n){"use strict";n.d(e,"a",(function(){return M})),n.d(e,"b",(function(){return A})),n.d(e,"c",(function(){return $})),n.d(e,"d",(function(){return E})),n.d(e,"e",(function(){return T}));var r=200,o=function(){};o.prototype.append=function(t){return t.length?(t=o.from(t),!this.length&&t||t.length=e?o.empty:this.sliceInner(Math.max(0,t),Math.min(this.length,e))},o.prototype.get=function(t){if(!(t<0||t>=this.length))return this.getInner(t)},o.prototype.forEach=function(t,e,n){void 0===e&&(e=0),void 0===n&&(n=this.length),e<=n?this.forEachInner(t,e,n,0):this.forEachInvertedInner(t,e,n,0)},o.prototype.map=function(t,e,n){void 0===e&&(e=0),void 0===n&&(n=this.length);var r=[];return this.forEach((function(e,n){return r.push(t(e,n))}),e,n),r},o.from=function(t){return t instanceof o?t:t&&t.length?new i(t):o.empty};var i=function(t){function e(e){t.call(this),this.values=e}t&&(e.__proto__=t),e.prototype=Object.create(t&&t.prototype),e.prototype.constructor=e;var n={length:{configurable:!0},depth:{configurable:!0}};return e.prototype.flatten=function(){return this.values},e.prototype.sliceInner=function(t,n){return 0==t&&n==this.length?this:new e(this.values.slice(t,n))},e.prototype.getInner=function(t){return this.values[t]},e.prototype.forEachInner=function(t,e,n,r){for(var o=e;o=n;o--)if(!1===t(this.values[o],r+o))return!1},e.prototype.leafAppend=function(t){if(this.length+t.length<=r)return new e(this.values.concat(t.flatten()))},e.prototype.leafPrepend=function(t){if(this.length+t.length<=r)return new e(t.flatten().concat(this.values))},n.length.get=function(){return this.values.length},n.depth.get=function(){return 0},Object.defineProperties(e.prototype,n),e}(o);o.empty=new i([]);var a=function(t){function e(e,n){t.call(this),this.left=e,this.right=n,this.length=e.length+n.length,this.depth=Math.max(e.depth,n.depth)+1}return t&&(e.__proto__=t),e.prototype=Object.create(t&&t.prototype),e.prototype.constructor=e,e.prototype.flatten=function(){return this.left.flatten().concat(this.right.flatten())},e.prototype.getInner=function(t){return to&&!1===this.right.forEachInner(t,Math.max(e-o,0),Math.min(this.length,n)-o,r+o))&&void 0)},e.prototype.forEachInvertedInner=function(t,e,n,r){var o=this.left.length;return!(e>o&&!1===this.right.forEachInvertedInner(t,e-o,Math.max(n,o)-o,r+o))&&(!(n=n?this.right.slice(t-n,e-n):this.left.slice(t,n).append(this.right.slice(0,e-n))},e.prototype.leafAppend=function(t){var n=this.right.leafAppend(t);if(n)return new e(this.left,n)},e.prototype.leafPrepend=function(t){var n=this.left.leafPrepend(t);if(n)return new e(n,this.right)},e.prototype.appendInner=function(t){return this.left.depth>=Math.max(this.right.depth,t.depth)+1?new e(this.left,new e(this.right,t)):new e(this,t)},e}(o),s=o,c=s,u=n("0ac0"),l=n("5313"),f=500,p=function(t,e){this.items=t,this.eventCount=e};function d(t,e){var n;return t.forEach((function(t,r){if(t.selection&&0==e--)return n=r,!1})),t.slice(n)}p.prototype.popEvent=function(t,e){var n=this;if(0==this.eventCount)return null;for(var r,o,i=this.items.length;;i--){var a=this.items.get(i-1);if(a.selection){--i;break}}e&&(r=this.remapping(i,this.items.length),o=r.maps.length);var s,c,u=t.tr,l=[],f=[];return this.items.forEach((function(t,e){if(!t.step)return r||(r=n.remapping(i,e+1),o=r.maps.length),o--,void f.push(t);if(r){f.push(new h(t.map));var a,d=t.step.map(r.slice(o));d&&u.maybeStep(d).doc&&(a=u.mapping.maps[u.mapping.maps.length-1],l.push(new h(a,null,null,l.length+f.length))),o--,a&&r.appendMap(a,o)}else u.maybeStep(t.step);return t.selection?(s=r?t.selection.map(r.slice(o)):t.selection,c=new p(n.items.slice(0,i).append(f.reverse().concat(l)),n.eventCount-1),!1):void 0}),this.items.length,0),{remaining:c,transform:u,selection:s}},p.prototype.addTransform=function(t,e,n,r){for(var o=[],i=this.eventCount,a=this.items,s=!r&&a.length?a.get(a.length-1):null,c=0;cm&&(a=d(a,v),i-=v),new p(a.append(o),i)},p.prototype.remapping=function(t,e){var n=new u["a"];return this.items.forEach((function(e,r){var o=null!=e.mirrorOffset&&r-e.mirrorOffset>=t?n.maps.length-e.mirrorOffset:null;n.appendMap(e.map,o)}),t,e),n},p.prototype.addMaps=function(t){return 0==this.eventCount?this:new p(this.items.append(t.map((function(t){return new h(t)}))),this.eventCount)},p.prototype.rebased=function(t,e){if(!this.eventCount)return this;var n=[],r=Math.max(0,this.items.length-e),o=t.mapping,i=t.steps.length,a=this.eventCount;this.items.forEach((function(t){t.selection&&a--}),r);var s=e;this.items.forEach((function(e){var r=o.getMirror(--s);if(null!=r){i=Math.min(i,r);var c=o.maps[r];if(e.step){var u=t.steps[r].invert(t.docs[r]),l=e.selection&&e.selection.map(o.slice(s+1,r));l&&a++,n.push(new h(c,u,l))}else n.push(new h(c))}}),r);for(var c=[],u=e;uf&&(d=d.compress(this.items.length-n.length)),d},p.prototype.emptyItemCount=function(){var t=0;return this.items.forEach((function(e){e.step||t++})),t},p.prototype.compress=function(t){void 0===t&&(t=this.items.length);var e=this.remapping(0,t),n=e.maps.length,r=[],o=0;return this.items.forEach((function(i,a){if(a>=t)r.push(i),i.selection&&o++;else if(i.step){var s=i.step.map(e.slice(n)),c=s&&s.getMap();if(n--,c&&e.appendMap(c,n),s){var u=i.selection&&i.selection.map(e.slice(n));u&&o++;var l,f=new h(c.invert(),s,u),p=r.length-1;(l=r.length&&r[p].merge(f))?r[p]=l:r.push(f)}}else i.map&&n--}),this.items.length,0),new p(c.from(r.reverse()),o)},p.empty=new p(c.empty,0);var h=function(t,e,n,r){this.map=t,this.step=e,this.selection=n,this.mirrorOffset=r};h.prototype.merge=function(t){if(this.step&&t.step&&!t.selection){var e=t.step.merge(this.step);if(e)return new h(e.getMap().invert(),e,this.selection)}};var v=function(t,e,n,r){this.done=t,this.undone=e,this.prevRanges=n,this.prevTime=r},m=20;function g(t,e,n,r){var o,i=n.getMeta(k);if(i)return i.historyState;n.getMeta(C)&&(t=new v(t.done,t.undone,null,0));var a=n.getMeta("appendedTransaction");if(0==n.steps.length)return t;if(a&&a.getMeta(k))return a.getMeta(k).redo?new v(t.done.addTransform(n,null,r,_(e)),t.undone,b(n.mapping.maps[n.steps.length-1]),t.prevTime):new v(t.done,t.undone.addTransform(n,null,r,_(e)),null,t.prevTime);if(!1===n.getMeta("addToHistory")||a&&!1===a.getMeta("addToHistory"))return(o=n.getMeta("rebased"))?new v(t.done.rebased(n,o),t.undone.rebased(n,o),w(t.prevRanges,n.mapping),t.prevTime):new v(t.done.addMaps(n.mapping.maps),t.undone.addMaps(n.mapping.maps),w(t.prevRanges,n.mapping),t.prevTime);var s=0==t.prevTime||!a&&(t.prevTime<(n.time||0)-r.newGroupDelay||!y(n,t.prevRanges)),c=a?w(t.prevRanges,n.mapping):b(n.mapping.maps[n.steps.length-1]);return new v(t.done.addTransform(n,s?e.selection.getBookmark():null,r,_(e)),p.empty,c,n.time)}function y(t,e){if(!e)return!1;if(!t.docChanged)return!0;var n=!1;return t.mapping.maps[0].forEach((function(t,r){for(var o=0;o=e[o]&&(n=!0)})),n}function b(t){var e=[];return t.forEach((function(t,n,r,o){return e.push(r,o)})),e}function w(t,e){if(!t)return null;for(var n=[],r=0;r=0&&(e=t.slice(r),t=t.slice(0,r));var o=t.indexOf("?");return o>=0&&(n=t.slice(o+1),t=t.slice(0,o)),{path:t,query:n,hash:e}}function T(t){return t.replace(/\/\//g,"/")}var $=Array.isArray||function(t){return"[object Array]"==Object.prototype.toString.call(t)},D=X,N=L,P=z,j=V,I=G,R=new RegExp(["(\\\\.)","([\\/.])?(?:(?:\\:(\\w+)(?:\\(((?:\\\\.|[^\\\\()])+)\\))?|\\(((?:\\\\.|[^\\\\()])+)\\))([+*?])?|(\\*))"].join("|"),"g");function L(t,e){var n,r=[],o=0,i=0,a="",s=e&&e.delimiter||"/";while(null!=(n=R.exec(t))){var c=n[0],u=n[1],l=n.index;if(a+=t.slice(i,l),i=l+c.length,u)a+=u[1];else{var f=t[i],p=n[2],d=n[3],h=n[4],v=n[5],m=n[6],g=n[7];a&&(r.push(a),a="");var y=null!=p&&null!=f&&f!==p,b="+"===m||"*"===m,w="?"===m||"*"===m,x=n[2]||s,S=h||v;r.push({name:d||o++,prefix:p||"",delimiter:x,optional:w,repeat:b,partial:y,asterisk:!!g,pattern:S?H(S):g?".*":"[^"+q(x)+"]+?"})}}return i1||!_.length)return 0===_.length?t():t("span",{},_)}if("a"===this.tag)O.on=x,O.attrs={href:c,"aria-current":y};else{var k=st(this.$slots.default);if(k){k.isStatic=!1;var C=k.data=o({},k.data);for(var M in C.on=C.on||{},C.on){var E=C.on[M];M in x&&(C.on[M]=Array.isArray(E)?E:[E])}for(var A in x)A in C.on?C.on[A].push(x[A]):C.on[A]=b;var T=k.data.attrs=o({},k.data.attrs);T.href=c,T["aria-current"]=y}else O.on=x}return t(this.tag,O,this.$slots.default)}};function at(t){if(!(t.metaKey||t.altKey||t.ctrlKey||t.shiftKey)&&!t.defaultPrevented&&(void 0===t.button||0===t.button)){if(t.currentTarget&&t.currentTarget.getAttribute){var e=t.currentTarget.getAttribute("target");if(/\b_blank\b/i.test(e))return}return t.preventDefault&&t.preventDefault(),!0}}function st(t){if(t)for(var e,n=0;n-1&&(s.params[p]=n.params[p]);return s.path=Q(u.path,s.params,'named route "'+c+'"'),l(u,s,a)}if(s.path){s.params={};for(var d=0;d=t.length?n():t[o]?e(t[o],(function(){r(o+1)})):r(o+1)};r(0)}var zt={redirected:2,aborted:4,cancelled:8,duplicated:16};function Ft(t,e){return Ht(t,e,zt.redirected,'Redirected when going from "'+t.fullPath+'" to "'+Ut(e)+'" via a navigation guard.')}function Bt(t,e){var n=Ht(t,e,zt.duplicated,'Avoided redundant navigation to current location: "'+t.fullPath+'".');return n.name="NavigationDuplicated",n}function Vt(t,e){return Ht(t,e,zt.cancelled,'Navigation cancelled from "'+t.fullPath+'" to "'+e.fullPath+'" with a new navigation.')}function qt(t,e){return Ht(t,e,zt.aborted,'Navigation aborted from "'+t.fullPath+'" to "'+e.fullPath+'" via a navigation guard.')}function Ht(t,e,n,r){var o=new Error(r);return o._isRouter=!0,o.from=t,o.to=e,o.type=n,o}var Wt=["params","query","hash"];function Ut(t){if("string"===typeof t)return t;if("path"in t)return t.path;var e={};return Wt.forEach((function(n){n in t&&(e[n]=t[n])})),JSON.stringify(e,null,2)}function Kt(t){return Object.prototype.toString.call(t).indexOf("Error")>-1}function Jt(t,e){return Kt(t)&&t._isRouter&&(null==e||t.type===e)}function Yt(t){return function(e,n,r){var o=!1,i=0,a=null;Gt(t,(function(t,e,n,s){if("function"===typeof t&&void 0===t.cid){o=!0,i++;var c,u=te((function(e){Qt(e)&&(e=e.default),t.resolved="function"===typeof e?e:et.extend(e),n.components[s]=e,i--,i<=0&&r()})),l=te((function(t){var e="Failed to resolve async component "+s+": "+t;a||(a=Kt(t)?t:new Error(e),r(a))}));try{c=t(u,l)}catch(p){l(p)}if(c)if("function"===typeof c.then)c.then(u,l);else{var f=c.component;f&&"function"===typeof f.then&&f.then(u,l)}}})),o||r()}}function Gt(t,e){return Xt(t.map((function(t){return Object.keys(t.components).map((function(n){return e(t.components[n],t.instances[n],t,n)}))})))}function Xt(t){return Array.prototype.concat.apply([],t)}var Zt="function"===typeof Symbol&&"symbol"===typeof Symbol.toStringTag;function Qt(t){return t.__esModule||Zt&&"Module"===t[Symbol.toStringTag]}function te(t){var e=!1;return function(){var n=[],r=arguments.length;while(r--)n[r]=arguments[r];if(!e)return e=!0,t.apply(this,n)}}var ee=function(t,e){this.router=t,this.base=ne(e),this.current=g,this.pending=null,this.ready=!1,this.readyCbs=[],this.readyErrorCbs=[],this.errorCbs=[],this.listeners=[]};function ne(t){if(!t)if(ut){var e=document.querySelector("base");t=e&&e.getAttribute("href")||"/",t=t.replace(/^https?:\/\/[^\/]+/,"")}else t="/";return"/"!==t.charAt(0)&&(t="/"+t),t.replace(/\/$/,"")}function re(t,e){var n,r=Math.max(t.length,e.length);for(n=0;n0)){var e=this.router,n=e.options.scrollBehavior,r=jt&&n;r&&this.listeners.push(Ot());var o=function(){var n=t.current,o=pe(t.base);t.current===g&&o===t._startLocation||t.transitionTo(o,(function(t){r&&_t(e,t,n,!0)}))};window.addEventListener("popstate",o),this.listeners.push((function(){window.removeEventListener("popstate",o)}))}},e.prototype.go=function(t){window.history.go(t)},e.prototype.push=function(t,e,n){var r=this,o=this,i=o.current;this.transitionTo(t,(function(t){It(T(r.base+t.fullPath)),_t(r.router,t,i,!1),e&&e(t)}),n)},e.prototype.replace=function(t,e,n){var r=this,o=this,i=o.current;this.transitionTo(t,(function(t){Rt(T(r.base+t.fullPath)),_t(r.router,t,i,!1),e&&e(t)}),n)},e.prototype.ensureURL=function(t){if(pe(this.base)!==this.current.fullPath){var e=T(this.base+this.current.fullPath);t?It(e):Rt(e)}},e.prototype.getCurrentLocation=function(){return pe(this.base)},e}(ee);function pe(t){var e=window.location.pathname;return t&&0===e.toLowerCase().indexOf(t.toLowerCase())&&(e=e.slice(t.length)),(e||"/")+window.location.search+window.location.hash}var de=function(t){function e(e,n,r){t.call(this,e,n),r&&he(this.base)||ve()}return t&&(e.__proto__=t),e.prototype=Object.create(t&&t.prototype),e.prototype.constructor=e,e.prototype.setupListeners=function(){var t=this;if(!(this.listeners.length>0)){var e=this.router,n=e.options.scrollBehavior,r=jt&&n;r&&this.listeners.push(Ot());var o=function(){var e=t.current;ve()&&t.transitionTo(me(),(function(n){r&&_t(t.router,n,e,!0),jt||be(n.fullPath)}))},i=jt?"popstate":"hashchange";window.addEventListener(i,o),this.listeners.push((function(){window.removeEventListener(i,o)}))}},e.prototype.push=function(t,e,n){var r=this,o=this,i=o.current;this.transitionTo(t,(function(t){ye(t.fullPath),_t(r.router,t,i,!1),e&&e(t)}),n)},e.prototype.replace=function(t,e,n){var r=this,o=this,i=o.current;this.transitionTo(t,(function(t){be(t.fullPath),_t(r.router,t,i,!1),e&&e(t)}),n)},e.prototype.go=function(t){window.history.go(t)},e.prototype.ensureURL=function(t){var e=this.current.fullPath;me()!==e&&(t?ye(e):be(e))},e.prototype.getCurrentLocation=function(){return me()},e}(ee);function he(t){var e=pe(t);if(!/^\/#/.test(e))return window.location.replace(T(t+"/#"+e)),!0}function ve(){var t=me();return"/"===t.charAt(0)||(be("/"+t),!1)}function me(){var t=window.location.href,e=t.indexOf("#");return e<0?"":(t=t.slice(e+1),t)}function ge(t){var e=window.location.href,n=e.indexOf("#"),r=n>=0?e.slice(0,n):e;return r+"#"+t}function ye(t){jt?It(ge(t)):window.location.hash=t}function be(t){jt?Rt(ge(t)):window.location.replace(ge(t))}var we=function(t){function e(e,n){t.call(this,e,n),this.stack=[],this.index=-1}return t&&(e.__proto__=t),e.prototype=Object.create(t&&t.prototype),e.prototype.constructor=e,e.prototype.push=function(t,e,n){var r=this;this.transitionTo(t,(function(t){r.stack=r.stack.slice(0,r.index+1).concat(t),r.index++,e&&e(t)}),n)},e.prototype.replace=function(t,e,n){var r=this;this.transitionTo(t,(function(t){r.stack=r.stack.slice(0,r.index).concat(t),e&&e(t)}),n)},e.prototype.go=function(t){var e=this,n=this.index+t;if(!(n<0||n>=this.stack.length)){var r=this.stack[n];this.confirmTransition(r,(function(){var t=e.current;e.index=n,e.updateRoute(r),e.router.afterHooks.forEach((function(e){e&&e(r,t)}))}),(function(t){Jt(t,zt.duplicated)&&(e.index=n)}))}},e.prototype.getCurrentLocation=function(){var t=this.stack[this.stack.length-1];return t?t.fullPath:"/"},e.prototype.ensureURL=function(){},e}(ee),xe=function(t){void 0===t&&(t={}),this.app=null,this.apps=[],this.options=t,this.beforeHooks=[],this.resolveHooks=[],this.afterHooks=[],this.matcher=ht(t.routes||[],this);var e=t.mode||"hash";switch(this.fallback="history"===e&&!jt&&!1!==t.fallback,this.fallback&&(e="hash"),ut||(e="abstract"),this.mode=e,e){case"history":this.history=new fe(this,t.base);break;case"hash":this.history=new de(this,t.base,this.fallback);break;case"abstract":this.history=new we(this,t.base);break;default:0}},Se={currentRoute:{configurable:!0}};function Oe(t,e){return t.push(e),function(){var n=t.indexOf(e);n>-1&&t.splice(n,1)}}function _e(t,e,n){var r="hash"===n?"#"+e:e;return t?T(t+"/"+r):r}xe.prototype.match=function(t,e,n){return this.matcher.match(t,e,n)},Se.currentRoute.get=function(){return this.history&&this.history.current},xe.prototype.init=function(t){var e=this;if(this.apps.push(t),t.$once("hook:destroyed",(function(){var n=e.apps.indexOf(t);n>-1&&e.apps.splice(n,1),e.app===t&&(e.app=e.apps[0]||null),e.app||e.history.teardown()})),!this.app){this.app=t;var n=this.history;if(n instanceof fe||n instanceof de){var r=function(t){var r=n.current,o=e.options.scrollBehavior,i=jt&&o;i&&"fullPath"in t&&_t(e,t,r,!1)},o=function(t){n.setupListeners(),r(t)};n.transitionTo(n.getCurrentLocation(),o,o)}n.listen((function(t){e.apps.forEach((function(e){e._route=t}))}))}},xe.prototype.beforeEach=function(t){return Oe(this.beforeHooks,t)},xe.prototype.beforeResolve=function(t){return Oe(this.resolveHooks,t)},xe.prototype.afterEach=function(t){return Oe(this.afterHooks,t)},xe.prototype.onReady=function(t,e){this.history.onReady(t,e)},xe.prototype.onError=function(t){this.history.onError(t)},xe.prototype.push=function(t,e,n){var r=this;if(!e&&!n&&"undefined"!==typeof Promise)return new Promise((function(e,n){r.history.push(t,e,n)}));this.history.push(t,e,n)},xe.prototype.replace=function(t,e,n){var r=this;if(!e&&!n&&"undefined"!==typeof Promise)return new Promise((function(e,n){r.history.replace(t,e,n)}));this.history.replace(t,e,n)},xe.prototype.go=function(t){this.history.go(t)},xe.prototype.back=function(){this.go(-1)},xe.prototype.forward=function(){this.go(1)},xe.prototype.getMatchedComponents=function(t){var e=t?t.matched?t:this.resolve(t).route:this.currentRoute;return e?[].concat.apply([],e.matched.map((function(t){return Object.keys(t.components).map((function(e){return t.components[e]}))}))):[]},xe.prototype.resolve=function(t,e,n){e=e||this.history.current;var r=tt(t,e,n,this),o=this.match(r,e),i=o.redirectedFrom||o.fullPath,a=this.history.base,s=_e(a,i,this.mode);return{location:r,route:o,href:s,normalizedTo:r,resolved:o}},xe.prototype.addRoutes=function(t){this.matcher.addRoutes(t),this.history.current!==g&&this.history.transitionTo(this.history.getCurrentLocation())},Object.defineProperties(xe.prototype,Se),xe.install=ct,xe.version="3.4.9",xe.isNavigationFailure=Jt,xe.NavigationFailureType=zt,ut&&window.Vue&&window.Vue.use(xe),e["a"]=xe},"90e3":function(t,e){var n=0,r=Math.random();t.exports=function(t){return"Symbol("+String(void 0===t?"":t)+")_"+(++n+r).toString(36)}},9112:function(t,e,n){var r=n("83ab"),o=n("9bf2"),i=n("5c6c");t.exports=r?function(t,e,n){return o.f(t,e,i(1,n))}:function(t,e,n){return t[e]=n,t}},"91d3":function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0;var r=n("78ef"),o=function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:":";return(0,r.withParams)({type:"macAddress"},(function(e){if(!(0,r.req)(e))return!0;if("string"!==typeof e)return!1;var n="string"===typeof t&&""!==t?e.split(t):12===e.length||16===e.length?e.match(/.{2}/g):null;return null!==n&&(6===n.length||8===n.length)&&n.every(i)}))};e.default=o;var i=function(t){return t.toLowerCase().match(/^[0-9a-f]{2}$/)}},"94ca":function(t,e,n){var r=n("d039"),o=/#|\.prototype\./,i=function(t,e){var n=s[a(t)];return n==u||n!=c&&("function"==typeof e?r(e):!!e)},a=i.normalize=function(t){return String(t).replace(o,".").toLowerCase()},s=i.data={},c=i.NATIVE="N",u=i.POLYFILL="P";t.exports=i},"9bf2":function(t,e,n){var r=n("83ab"),o=n("0cfb"),i=n("825a"),a=n("c04e"),s=Object.defineProperty;e.f=r?s:function(t,e,n){if(i(t),e=a(e,!0),i(n),o)try{return s(t,e,n)}catch(r){}if("get"in n||"set"in n)throw TypeError("Accessors not supported");return"value"in n&&(t[e]=n.value),t}},a026:function(t,e,n){"use strict";(function(t){ +/*! + * Vue.js v2.6.12 + * (c) 2014-2020 Evan You + * Released under the MIT License. + */ +var n=Object.freeze({});function r(t){return void 0===t||null===t}function o(t){return void 0!==t&&null!==t}function i(t){return!0===t}function a(t){return!1===t}function s(t){return"string"===typeof t||"number"===typeof t||"symbol"===typeof t||"boolean"===typeof t}function c(t){return null!==t&&"object"===typeof t}var u=Object.prototype.toString;function l(t){return"[object Object]"===u.call(t)}function f(t){return"[object RegExp]"===u.call(t)}function p(t){var e=parseFloat(String(t));return e>=0&&Math.floor(e)===e&&isFinite(t)}function d(t){return o(t)&&"function"===typeof t.then&&"function"===typeof t.catch}function h(t){return null==t?"":Array.isArray(t)||l(t)&&t.toString===u?JSON.stringify(t,null,2):String(t)}function v(t){var e=parseFloat(t);return isNaN(e)?t:e}function m(t,e){for(var n=Object.create(null),r=t.split(","),o=0;o-1)return t.splice(n,1)}}var w=Object.prototype.hasOwnProperty;function x(t,e){return w.call(t,e)}function S(t){var e=Object.create(null);return function(n){var r=e[n];return r||(e[n]=t(n))}}var O=/-(\w)/g,_=S((function(t){return t.replace(O,(function(t,e){return e?e.toUpperCase():""}))})),k=S((function(t){return t.charAt(0).toUpperCase()+t.slice(1)})),C=/\B([A-Z])/g,M=S((function(t){return t.replace(C,"-$1").toLowerCase()}));function E(t,e){function n(n){var r=arguments.length;return r?r>1?t.apply(e,arguments):t.call(e,n):t.call(e)}return n._length=t.length,n}function A(t,e){return t.bind(e)}var T=Function.prototype.bind?A:E;function $(t,e){e=e||0;var n=t.length-e,r=new Array(n);while(n--)r[n]=t[n+e];return r}function D(t,e){for(var n in e)t[n]=e[n];return t}function N(t){for(var e={},n=0;n0,ot=et&&et.indexOf("edge/")>0,it=(et&&et.indexOf("android"),et&&/iphone|ipad|ipod|ios/.test(et)||"ios"===tt),at=(et&&/chrome\/\d+/.test(et),et&&/phantomjs/.test(et),et&&et.match(/firefox\/(\d+)/)),st={}.watch,ct=!1;if(Z)try{var ut={};Object.defineProperty(ut,"passive",{get:function(){ct=!0}}),window.addEventListener("test-passive",null,ut)}catch(Xu){}var lt=function(){return void 0===G&&(G=!Z&&!Q&&"undefined"!==typeof t&&(t["process"]&&"server"===t["process"].env.VUE_ENV)),G},ft=Z&&window.__VUE_DEVTOOLS_GLOBAL_HOOK__;function pt(t){return"function"===typeof t&&/native code/.test(t.toString())}var dt,ht="undefined"!==typeof Symbol&&pt(Symbol)&&"undefined"!==typeof Reflect&&pt(Reflect.ownKeys);dt="undefined"!==typeof Set&&pt(Set)?Set:function(){function t(){this.set=Object.create(null)}return t.prototype.has=function(t){return!0===this.set[t]},t.prototype.add=function(t){this.set[t]=!0},t.prototype.clear=function(){this.set=Object.create(null)},t}();var vt=P,mt=0,gt=function(){this.id=mt++,this.subs=[]};gt.prototype.addSub=function(t){this.subs.push(t)},gt.prototype.removeSub=function(t){b(this.subs,t)},gt.prototype.depend=function(){gt.target&>.target.addDep(this)},gt.prototype.notify=function(){var t=this.subs.slice();for(var e=0,n=t.length;e-1)if(i&&!x(o,"default"))a=!1;else if(""===a||a===M(t)){var c=ne(String,o.type);(c<0||s0&&(a=Ae(a,(e||"")+"_"+n),Ee(a[0])&&Ee(u)&&(l[c]=_t(u.text+a[0].text),a.shift()),l.push.apply(l,a)):s(a)?Ee(u)?l[c]=_t(u.text+a):""!==a&&l.push(_t(a)):Ee(a)&&Ee(u)?l[c]=_t(u.text+a.text):(i(t._isVList)&&o(a.tag)&&r(a.key)&&o(e)&&(a.key="__vlist"+e+"_"+n+"__"),l.push(a)));return l}function Te(t){var e=t.$options.provide;e&&(t._provided="function"===typeof e?e.call(t):e)}function $e(t){var e=De(t.$options.inject,t);e&&($t(!1),Object.keys(e).forEach((function(n){It(t,n,e[n])})),$t(!0))}function De(t,e){if(t){for(var n=Object.create(null),r=ht?Reflect.ownKeys(t):Object.keys(t),o=0;o0,a=t?!!t.$stable:!i,s=t&&t.$key;if(t){if(t._normalized)return t._normalized;if(a&&r&&r!==n&&s===r.$key&&!i&&!r.$hasNormal)return r;for(var c in o={},t)t[c]&&"$"!==c[0]&&(o[c]=Ie(e,c,t[c]))}else o={};for(var u in e)u in o||(o[u]=Re(e,u));return t&&Object.isExtensible(t)&&(t._normalized=o),K(o,"$stable",a),K(o,"$key",s),K(o,"$hasNormal",i),o}function Ie(t,e,n){var r=function(){var t=arguments.length?n.apply(null,arguments):n({});return t=t&&"object"===typeof t&&!Array.isArray(t)?[t]:Me(t),t&&(0===t.length||1===t.length&&t[0].isComment)?void 0:t};return n.proxy&&Object.defineProperty(t,e,{get:r,enumerable:!0,configurable:!0}),r}function Re(t,e){return function(){return t[e]}}function Le(t,e){var n,r,i,a,s;if(Array.isArray(t)||"string"===typeof t)for(n=new Array(t.length),r=0,i=t.length;r1?$(n):n;for(var r=$(arguments,1),o='event handler for "'+t+'"',i=0,a=n.length;idocument.createEvent("Event").timeStamp&&(Gn=function(){return Xn.now()})}function Zn(){var t,e;for(Yn=Gn(),Un=!0,Vn.sort((function(t,e){return t.id-e.id})),Kn=0;KnKn&&Vn[n].id>t.id)n--;Vn.splice(n+1,0,t)}else Vn.push(t);Wn||(Wn=!0,me(Zn))}}var rr=0,or=function(t,e,n,r,o){this.vm=t,o&&(t._watcher=this),t._watchers.push(this),r?(this.deep=!!r.deep,this.user=!!r.user,this.lazy=!!r.lazy,this.sync=!!r.sync,this.before=r.before):this.deep=this.user=this.lazy=this.sync=!1,this.cb=n,this.id=++rr,this.active=!0,this.dirty=this.lazy,this.deps=[],this.newDeps=[],this.depIds=new dt,this.newDepIds=new dt,this.expression="","function"===typeof e?this.getter=e:(this.getter=Y(e),this.getter||(this.getter=P)),this.value=this.lazy?void 0:this.get()};or.prototype.get=function(){var t;bt(this);var e=this.vm;try{t=this.getter.call(e,e)}catch(Xu){if(!this.user)throw Xu;re(Xu,e,'getter for watcher "'+this.expression+'"')}finally{this.deep&&ye(t),wt(),this.cleanupDeps()}return t},or.prototype.addDep=function(t){var e=t.id;this.newDepIds.has(e)||(this.newDepIds.add(e),this.newDeps.push(t),this.depIds.has(e)||t.addSub(this))},or.prototype.cleanupDeps=function(){var t=this.deps.length;while(t--){var e=this.deps[t];this.newDepIds.has(e.id)||e.removeSub(this)}var n=this.depIds;this.depIds=this.newDepIds,this.newDepIds=n,this.newDepIds.clear(),n=this.deps,this.deps=this.newDeps,this.newDeps=n,this.newDeps.length=0},or.prototype.update=function(){this.lazy?this.dirty=!0:this.sync?this.run():nr(this)},or.prototype.run=function(){if(this.active){var t=this.get();if(t!==this.value||c(t)||this.deep){var e=this.value;if(this.value=t,this.user)try{this.cb.call(this.vm,t,e)}catch(Xu){re(Xu,this.vm,'callback for watcher "'+this.expression+'"')}else this.cb.call(this.vm,t,e)}}},or.prototype.evaluate=function(){this.value=this.get(),this.dirty=!1},or.prototype.depend=function(){var t=this.deps.length;while(t--)this.deps[t].depend()},or.prototype.teardown=function(){if(this.active){this.vm._isBeingDestroyed||b(this.vm._watchers,this);var t=this.deps.length;while(t--)this.deps[t].removeSub(this);this.active=!1}};var ir={enumerable:!0,configurable:!0,get:P,set:P};function ar(t,e,n){ir.get=function(){return this[e][n]},ir.set=function(t){this[e][n]=t},Object.defineProperty(t,n,ir)}function sr(t){t._watchers=[];var e=t.$options;e.props&&cr(t,e.props),e.methods&&mr(t,e.methods),e.data?ur(t):jt(t._data={},!0),e.computed&&pr(t,e.computed),e.watch&&e.watch!==st&&gr(t,e.watch)}function cr(t,e){var n=t.$options.propsData||{},r=t._props={},o=t.$options._propKeys=[],i=!t.$parent;i||$t(!1);var a=function(i){o.push(i);var a=Zt(i,e,n,t);It(r,i,a),i in t||ar(t,"_props",i)};for(var s in e)a(s);$t(!0)}function ur(t){var e=t.$options.data;e=t._data="function"===typeof e?lr(e,t):e||{},l(e)||(e={});var n=Object.keys(e),r=t.$options.props,o=(t.$options.methods,n.length);while(o--){var i=n[o];0,r&&x(r,i)||U(i)||ar(t,"_data",i)}jt(e,!0)}function lr(t,e){bt();try{return t.call(e,e)}catch(Xu){return re(Xu,e,"data()"),{}}finally{wt()}}var fr={lazy:!0};function pr(t,e){var n=t._computedWatchers=Object.create(null),r=lt();for(var o in e){var i=e[o],a="function"===typeof i?i:i.get;0,r||(n[o]=new or(t,a||P,P,fr)),o in t||dr(t,o,i)}}function dr(t,e,n){var r=!lt();"function"===typeof n?(ir.get=r?hr(e):vr(n),ir.set=P):(ir.get=n.get?r&&!1!==n.cache?hr(e):vr(n.get):P,ir.set=n.set||P),Object.defineProperty(t,e,ir)}function hr(t){return function(){var e=this._computedWatchers&&this._computedWatchers[t];if(e)return e.dirty&&e.evaluate(),gt.target&&e.depend(),e.value}}function vr(t){return function(){return t.call(this,this)}}function mr(t,e){t.$options.props;for(var n in e)t[n]="function"!==typeof e[n]?P:T(e[n],t)}function gr(t,e){for(var n in e){var r=e[n];if(Array.isArray(r))for(var o=0;o-1)return this;var n=$(arguments,1);return n.unshift(this),"function"===typeof t.install?t.install.apply(t,n):"function"===typeof t&&t.apply(null,n),e.push(t),this}}function Mr(t){t.mixin=function(t){return this.options=Gt(this.options,t),this}}function Er(t){t.cid=0;var e=1;t.extend=function(t){t=t||{};var n=this,r=n.cid,o=t._Ctor||(t._Ctor={});if(o[r])return o[r];var i=t.name||n.options.name;var a=function(t){this._init(t)};return a.prototype=Object.create(n.prototype),a.prototype.constructor=a,a.cid=e++,a.options=Gt(n.options,t),a["super"]=n,a.options.props&&Ar(a),a.options.computed&&Tr(a),a.extend=n.extend,a.mixin=n.mixin,a.use=n.use,V.forEach((function(t){a[t]=n[t]})),i&&(a.options.components[i]=a),a.superOptions=n.options,a.extendOptions=t,a.sealedOptions=D({},a.options),o[r]=a,a}}function Ar(t){var e=t.options.props;for(var n in e)ar(t.prototype,"_props",n)}function Tr(t){var e=t.options.computed;for(var n in e)dr(t.prototype,n,e[n])}function $r(t){V.forEach((function(e){t[e]=function(t,n){return n?("component"===e&&l(n)&&(n.name=n.name||t,n=this.options._base.extend(n)),"directive"===e&&"function"===typeof n&&(n={bind:n,update:n}),this.options[e+"s"][t]=n,n):this.options[e+"s"][t]}}))}function Dr(t){return t&&(t.Ctor.options.name||t.tag)}function Nr(t,e){return Array.isArray(t)?t.indexOf(e)>-1:"string"===typeof t?t.split(",").indexOf(e)>-1:!!f(t)&&t.test(e)}function Pr(t,e){var n=t.cache,r=t.keys,o=t._vnode;for(var i in n){var a=n[i];if(a){var s=Dr(a.componentOptions);s&&!e(s)&&jr(n,i,r,o)}}}function jr(t,e,n,r){var o=t[e];!o||r&&o.tag===r.tag||o.componentInstance.$destroy(),t[e]=null,b(n,e)}xr(kr),br(kr),$n(kr),jn(kr),wn(kr);var Ir=[String,RegExp,Array],Rr={name:"keep-alive",abstract:!0,props:{include:Ir,exclude:Ir,max:[String,Number]},created:function(){this.cache=Object.create(null),this.keys=[]},destroyed:function(){for(var t in this.cache)jr(this.cache,t,this.keys)},mounted:function(){var t=this;this.$watch("include",(function(e){Pr(t,(function(t){return Nr(e,t)}))})),this.$watch("exclude",(function(e){Pr(t,(function(t){return!Nr(e,t)}))}))},render:function(){var t=this.$slots.default,e=kn(t),n=e&&e.componentOptions;if(n){var r=Dr(n),o=this,i=o.include,a=o.exclude;if(i&&(!r||!Nr(i,r))||a&&r&&Nr(a,r))return e;var s=this,c=s.cache,u=s.keys,l=null==e.key?n.Ctor.cid+(n.tag?"::"+n.tag:""):e.key;c[l]?(e.componentInstance=c[l].componentInstance,b(u,l),u.push(l)):(c[l]=e,u.push(l),this.max&&u.length>parseInt(this.max)&&jr(c,u[0],u,this._vnode)),e.data.keepAlive=!0}return e||t&&t[0]}},Lr={KeepAlive:Rr};function zr(t){var e={get:function(){return H}};Object.defineProperty(t,"config",e),t.util={warn:vt,extend:D,mergeOptions:Gt,defineReactive:It},t.set=Rt,t.delete=Lt,t.nextTick=me,t.observable=function(t){return jt(t),t},t.options=Object.create(null),V.forEach((function(e){t.options[e+"s"]=Object.create(null)})),t.options._base=t,D(t.options.components,Lr),Cr(t),Mr(t),Er(t),$r(t)}zr(kr),Object.defineProperty(kr.prototype,"$isServer",{get:lt}),Object.defineProperty(kr.prototype,"$ssrContext",{get:function(){return this.$vnode&&this.$vnode.ssrContext}}),Object.defineProperty(kr,"FunctionalRenderContext",{value:Qe}),kr.version="2.6.12";var Fr=m("style,class"),Br=m("input,textarea,option,select,progress"),Vr=function(t,e,n){return"value"===n&&Br(t)&&"button"!==e||"selected"===n&&"option"===t||"checked"===n&&"input"===t||"muted"===n&&"video"===t},qr=m("contenteditable,draggable,spellcheck"),Hr=m("events,caret,typing,plaintext-only"),Wr=function(t,e){return Gr(e)||"false"===e?"false":"contenteditable"===t&&Hr(e)?e:"true"},Ur=m("allowfullscreen,async,autofocus,autoplay,checked,compact,controls,declare,default,defaultchecked,defaultmuted,defaultselected,defer,disabled,enabled,formnovalidate,hidden,indeterminate,inert,ismap,itemscope,loop,multiple,muted,nohref,noresize,noshade,novalidate,nowrap,open,pauseonexit,readonly,required,reversed,scoped,seamless,selected,sortable,translate,truespeed,typemustmatch,visible"),Kr="http://www.w3.org/1999/xlink",Jr=function(t){return":"===t.charAt(5)&&"xlink"===t.slice(0,5)},Yr=function(t){return Jr(t)?t.slice(6,t.length):""},Gr=function(t){return null==t||!1===t};function Xr(t){var e=t.data,n=t,r=t;while(o(r.componentInstance))r=r.componentInstance._vnode,r&&r.data&&(e=Zr(r.data,e));while(o(n=n.parent))n&&n.data&&(e=Zr(e,n.data));return Qr(e.staticClass,e.class)}function Zr(t,e){return{staticClass:to(t.staticClass,e.staticClass),class:o(t.class)?[t.class,e.class]:e.class}}function Qr(t,e){return o(t)||o(e)?to(t,eo(e)):""}function to(t,e){return t?e?t+" "+e:t:e||""}function eo(t){return Array.isArray(t)?no(t):c(t)?ro(t):"string"===typeof t?t:""}function no(t){for(var e,n="",r=0,i=t.length;r-1?lo[t]=e.constructor===window.HTMLUnknownElement||e.constructor===window.HTMLElement:lo[t]=/HTMLUnknownElement/.test(e.toString())}var po=m("text,number,password,search,email,tel,url");function ho(t){if("string"===typeof t){var e=document.querySelector(t);return e||document.createElement("div")}return t}function vo(t,e){var n=document.createElement(t);return"select"!==t||e.data&&e.data.attrs&&void 0!==e.data.attrs.multiple&&n.setAttribute("multiple","multiple"),n}function mo(t,e){return document.createElementNS(oo[t],e)}function go(t){return document.createTextNode(t)}function yo(t){return document.createComment(t)}function bo(t,e,n){t.insertBefore(e,n)}function wo(t,e){t.removeChild(e)}function xo(t,e){t.appendChild(e)}function So(t){return t.parentNode}function Oo(t){return t.nextSibling}function _o(t){return t.tagName}function ko(t,e){t.textContent=e}function Co(t,e){t.setAttribute(e,"")}var Mo=Object.freeze({createElement:vo,createElementNS:mo,createTextNode:go,createComment:yo,insertBefore:bo,removeChild:wo,appendChild:xo,parentNode:So,nextSibling:Oo,tagName:_o,setTextContent:ko,setStyleScope:Co}),Eo={create:function(t,e){Ao(e)},update:function(t,e){t.data.ref!==e.data.ref&&(Ao(t,!0),Ao(e))},destroy:function(t){Ao(t,!0)}};function Ao(t,e){var n=t.data.ref;if(o(n)){var r=t.context,i=t.componentInstance||t.elm,a=r.$refs;e?Array.isArray(a[n])?b(a[n],i):a[n]===i&&(a[n]=void 0):t.data.refInFor?Array.isArray(a[n])?a[n].indexOf(i)<0&&a[n].push(i):a[n]=[i]:a[n]=i}}var To=new xt("",{},[]),$o=["create","activate","update","remove","destroy"];function Do(t,e){return t.key===e.key&&(t.tag===e.tag&&t.isComment===e.isComment&&o(t.data)===o(e.data)&&No(t,e)||i(t.isAsyncPlaceholder)&&t.asyncFactory===e.asyncFactory&&r(e.asyncFactory.error))}function No(t,e){if("input"!==t.tag)return!0;var n,r=o(n=t.data)&&o(n=n.attrs)&&n.type,i=o(n=e.data)&&o(n=n.attrs)&&n.type;return r===i||po(r)&&po(i)}function Po(t,e,n){var r,i,a={};for(r=e;r<=n;++r)i=t[r].key,o(i)&&(a[i]=r);return a}function jo(t){var e,n,a={},c=t.modules,u=t.nodeOps;for(e=0;e<$o.length;++e)for(a[$o[e]]=[],n=0;nv?(f=r(n[y+1])?null:n[y+1].elm,O(t,f,n,h,y,i)):h>y&&k(e,p,v)}function E(t,e,n,r){for(var i=n;i-1?Uo(t,e,n):Ur(e)?Gr(n)?t.removeAttribute(e):(n="allowfullscreen"===e&&"EMBED"===t.tagName?"true":e,t.setAttribute(e,n)):qr(e)?t.setAttribute(e,Wr(e,n)):Jr(e)?Gr(n)?t.removeAttributeNS(Kr,Yr(e)):t.setAttributeNS(Kr,e,n):Uo(t,e,n)}function Uo(t,e,n){if(Gr(n))t.removeAttribute(e);else{if(nt&&!rt&&"TEXTAREA"===t.tagName&&"placeholder"===e&&""!==n&&!t.__ieph){var r=function(e){e.stopImmediatePropagation(),t.removeEventListener("input",r)};t.addEventListener("input",r),t.__ieph=!0}t.setAttribute(e,n)}}var Ko={create:Ho,update:Ho};function Jo(t,e){var n=e.elm,i=e.data,a=t.data;if(!(r(i.staticClass)&&r(i.class)&&(r(a)||r(a.staticClass)&&r(a.class)))){var s=Xr(e),c=n._transitionClasses;o(c)&&(s=to(s,eo(c))),s!==n._prevClass&&(n.setAttribute("class",s),n._prevClass=s)}}var Yo,Go,Xo,Zo,Qo,ti,ei={create:Jo,update:Jo},ni=/[\w).+\-_$\]]/;function ri(t){var e,n,r,o,i,a=!1,s=!1,c=!1,u=!1,l=0,f=0,p=0,d=0;for(r=0;r=0;h--)if(v=t.charAt(h)," "!==v)break;v&&ni.test(v)||(u=!0)}}else void 0===o?(d=r+1,o=t.slice(0,r).trim()):m();function m(){(i||(i=[])).push(t.slice(d,r).trim()),d=r+1}if(void 0===o?o=t.slice(0,r).trim():0!==d&&m(),i)for(r=0;r-1?{exp:t.slice(0,Zo),key:'"'+t.slice(Zo+1)+'"'}:{exp:t,key:null};Go=t,Zo=Qo=ti=0;while(!Si())Xo=xi(),Oi(Xo)?ki(Xo):91===Xo&&_i(Xo);return{exp:t.slice(0,Qo),key:t.slice(Qo+1,ti)}}function xi(){return Go.charCodeAt(++Zo)}function Si(){return Zo>=Yo}function Oi(t){return 34===t||39===t}function _i(t){var e=1;Qo=Zo;while(!Si())if(t=xi(),Oi(t))ki(t);else if(91===t&&e++,93===t&&e--,0===e){ti=Zo;break}}function ki(t){var e=t;while(!Si())if(t=xi(),t===e)break}var Ci,Mi="__r",Ei="__c";function Ai(t,e,n){n;var r=e.value,o=e.modifiers,i=t.tag,a=t.attrsMap.type;if(t.component)return yi(t,r,o),!1;if("select"===i)Di(t,r,o);else if("input"===i&&"checkbox"===a)Ti(t,r,o);else if("input"===i&&"radio"===a)$i(t,r,o);else if("input"===i||"textarea"===i)Ni(t,r,o);else{if(!H.isReservedTag(i))return yi(t,r,o),!1}return!0}function Ti(t,e,n){var r=n&&n.number,o=hi(t,"value")||"null",i=hi(t,"true-value")||"true",a=hi(t,"false-value")||"false";si(t,"checked","Array.isArray("+e+")?_i("+e+","+o+")>-1"+("true"===i?":("+e+")":":_q("+e+","+i+")")),pi(t,"change","var $$a="+e+",$$el=$event.target,$$c=$$el.checked?("+i+"):("+a+");if(Array.isArray($$a)){var $$v="+(r?"_n("+o+")":o)+",$$i=_i($$a,$$v);if($$el.checked){$$i<0&&("+bi(e,"$$a.concat([$$v])")+")}else{$$i>-1&&("+bi(e,"$$a.slice(0,$$i).concat($$a.slice($$i+1))")+")}}else{"+bi(e,"$$c")+"}",null,!0)}function $i(t,e,n){var r=n&&n.number,o=hi(t,"value")||"null";o=r?"_n("+o+")":o,si(t,"checked","_q("+e+","+o+")"),pi(t,"change",bi(e,o),null,!0)}function Di(t,e,n){var r=n&&n.number,o='Array.prototype.filter.call($event.target.options,function(o){return o.selected}).map(function(o){var val = "_value" in o ? o._value : o.value;return '+(r?"_n(val)":"val")+"})",i="$event.target.multiple ? $$selectedVal : $$selectedVal[0]",a="var $$selectedVal = "+o+";";a=a+" "+bi(e,i),pi(t,"change",a,null,!0)}function Ni(t,e,n){var r=t.attrsMap.type,o=n||{},i=o.lazy,a=o.number,s=o.trim,c=!i&&"range"!==r,u=i?"change":"range"===r?Mi:"input",l="$event.target.value";s&&(l="$event.target.value.trim()"),a&&(l="_n("+l+")");var f=bi(e,l);c&&(f="if($event.target.composing)return;"+f),si(t,"value","("+e+")"),pi(t,u,f,null,!0),(s||a)&&pi(t,"blur","$forceUpdate()")}function Pi(t){if(o(t[Mi])){var e=nt?"change":"input";t[e]=[].concat(t[Mi],t[e]||[]),delete t[Mi]}o(t[Ei])&&(t.change=[].concat(t[Ei],t.change||[]),delete t[Ei])}function ji(t,e,n){var r=Ci;return function o(){var i=e.apply(null,arguments);null!==i&&Li(t,o,n,r)}}var Ii=ce&&!(at&&Number(at[1])<=53);function Ri(t,e,n,r){if(Ii){var o=Yn,i=e;e=i._wrapper=function(t){if(t.target===t.currentTarget||t.timeStamp>=o||t.timeStamp<=0||t.target.ownerDocument!==document)return i.apply(this,arguments)}}Ci.addEventListener(t,e,ct?{capture:n,passive:r}:n)}function Li(t,e,n,r){(r||Ci).removeEventListener(t,e._wrapper||e,n)}function zi(t,e){if(!r(t.data.on)||!r(e.data.on)){var n=e.data.on||{},o=t.data.on||{};Ci=e.elm,Pi(n),Se(n,o,Ri,Li,ji,e.context),Ci=void 0}}var Fi,Bi={create:zi,update:zi};function Vi(t,e){if(!r(t.data.domProps)||!r(e.data.domProps)){var n,i,a=e.elm,s=t.data.domProps||{},c=e.data.domProps||{};for(n in o(c.__ob__)&&(c=e.data.domProps=D({},c)),s)n in c||(a[n]="");for(n in c){if(i=c[n],"textContent"===n||"innerHTML"===n){if(e.children&&(e.children.length=0),i===s[n])continue;1===a.childNodes.length&&a.removeChild(a.childNodes[0])}if("value"===n&&"PROGRESS"!==a.tagName){a._value=i;var u=r(i)?"":String(i);qi(a,u)&&(a.value=u)}else if("innerHTML"===n&&ao(a.tagName)&&r(a.innerHTML)){Fi=Fi||document.createElement("div"),Fi.innerHTML=""+i+"";var l=Fi.firstChild;while(a.firstChild)a.removeChild(a.firstChild);while(l.firstChild)a.appendChild(l.firstChild)}else if(i!==s[n])try{a[n]=i}catch(Xu){}}}}function qi(t,e){return!t.composing&&("OPTION"===t.tagName||Hi(t,e)||Wi(t,e))}function Hi(t,e){var n=!0;try{n=document.activeElement!==t}catch(Xu){}return n&&t.value!==e}function Wi(t,e){var n=t.value,r=t._vModifiers;if(o(r)){if(r.number)return v(n)!==v(e);if(r.trim)return n.trim()!==e.trim()}return n!==e}var Ui={create:Vi,update:Vi},Ki=S((function(t){var e={},n=/;(?![^(]*\))/g,r=/:(.+)/;return t.split(n).forEach((function(t){if(t){var n=t.split(r);n.length>1&&(e[n[0].trim()]=n[1].trim())}})),e}));function Ji(t){var e=Yi(t.style);return t.staticStyle?D(t.staticStyle,e):e}function Yi(t){return Array.isArray(t)?N(t):"string"===typeof t?Ki(t):t}function Gi(t,e){var n,r={};if(e){var o=t;while(o.componentInstance)o=o.componentInstance._vnode,o&&o.data&&(n=Ji(o.data))&&D(r,n)}(n=Ji(t.data))&&D(r,n);var i=t;while(i=i.parent)i.data&&(n=Ji(i.data))&&D(r,n);return r}var Xi,Zi=/^--/,Qi=/\s*!important$/,ta=function(t,e,n){if(Zi.test(e))t.style.setProperty(e,n);else if(Qi.test(n))t.style.setProperty(M(e),n.replace(Qi,""),"important");else{var r=na(e);if(Array.isArray(n))for(var o=0,i=n.length;o-1?e.split(ia).forEach((function(e){return t.classList.add(e)})):t.classList.add(e);else{var n=" "+(t.getAttribute("class")||"")+" ";n.indexOf(" "+e+" ")<0&&t.setAttribute("class",(n+e).trim())}}function sa(t,e){if(e&&(e=e.trim()))if(t.classList)e.indexOf(" ")>-1?e.split(ia).forEach((function(e){return t.classList.remove(e)})):t.classList.remove(e),t.classList.length||t.removeAttribute("class");else{var n=" "+(t.getAttribute("class")||"")+" ",r=" "+e+" ";while(n.indexOf(r)>=0)n=n.replace(r," ");n=n.trim(),n?t.setAttribute("class",n):t.removeAttribute("class")}}function ca(t){if(t){if("object"===typeof t){var e={};return!1!==t.css&&D(e,ua(t.name||"v")),D(e,t),e}return"string"===typeof t?ua(t):void 0}}var ua=S((function(t){return{enterClass:t+"-enter",enterToClass:t+"-enter-to",enterActiveClass:t+"-enter-active",leaveClass:t+"-leave",leaveToClass:t+"-leave-to",leaveActiveClass:t+"-leave-active"}})),la=Z&&!rt,fa="transition",pa="animation",da="transition",ha="transitionend",va="animation",ma="animationend";la&&(void 0===window.ontransitionend&&void 0!==window.onwebkittransitionend&&(da="WebkitTransition",ha="webkitTransitionEnd"),void 0===window.onanimationend&&void 0!==window.onwebkitanimationend&&(va="WebkitAnimation",ma="webkitAnimationEnd"));var ga=Z?window.requestAnimationFrame?window.requestAnimationFrame.bind(window):setTimeout:function(t){return t()};function ya(t){ga((function(){ga(t)}))}function ba(t,e){var n=t._transitionClasses||(t._transitionClasses=[]);n.indexOf(e)<0&&(n.push(e),aa(t,e))}function wa(t,e){t._transitionClasses&&b(t._transitionClasses,e),sa(t,e)}function xa(t,e,n){var r=Oa(t,e),o=r.type,i=r.timeout,a=r.propCount;if(!o)return n();var s=o===fa?ha:ma,c=0,u=function(){t.removeEventListener(s,l),n()},l=function(e){e.target===t&&++c>=a&&u()};setTimeout((function(){c0&&(n=fa,l=a,f=i.length):e===pa?u>0&&(n=pa,l=u,f=c.length):(l=Math.max(a,u),n=l>0?a>u?fa:pa:null,f=n?n===fa?i.length:c.length:0);var p=n===fa&&Sa.test(r[da+"Property"]);return{type:n,timeout:l,propCount:f,hasTransform:p}}function _a(t,e){while(t.length1}function Ta(t,e){!0!==e.data.show&&Ca(e)}var $a=Z?{create:Ta,activate:Ta,remove:function(t,e){!0!==t.data.show?Ma(t,e):e()}}:{},Da=[Ko,ei,Bi,Ui,oa,$a],Na=Da.concat(qo),Pa=jo({nodeOps:Mo,modules:Na});rt&&document.addEventListener("selectionchange",(function(){var t=document.activeElement;t&&t.vmodel&&Va(t,"input")}));var ja={inserted:function(t,e,n,r){"select"===n.tag?(r.elm&&!r.elm._vOptions?Oe(n,"postpatch",(function(){ja.componentUpdated(t,e,n)})):Ia(t,e,n.context),t._vOptions=[].map.call(t.options,za)):("textarea"===n.tag||po(t.type))&&(t._vModifiers=e.modifiers,e.modifiers.lazy||(t.addEventListener("compositionstart",Fa),t.addEventListener("compositionend",Ba),t.addEventListener("change",Ba),rt&&(t.vmodel=!0)))},componentUpdated:function(t,e,n){if("select"===n.tag){Ia(t,e,n.context);var r=t._vOptions,o=t._vOptions=[].map.call(t.options,za);if(o.some((function(t,e){return!L(t,r[e])}))){var i=t.multiple?e.value.some((function(t){return La(t,o)})):e.value!==e.oldValue&&La(e.value,o);i&&Va(t,"change")}}}};function Ia(t,e,n){Ra(t,e,n),(nt||ot)&&setTimeout((function(){Ra(t,e,n)}),0)}function Ra(t,e,n){var r=e.value,o=t.multiple;if(!o||Array.isArray(r)){for(var i,a,s=0,c=t.options.length;s-1,a.selected!==i&&(a.selected=i);else if(L(za(a),r))return void(t.selectedIndex!==s&&(t.selectedIndex=s));o||(t.selectedIndex=-1)}}function La(t,e){return e.every((function(e){return!L(e,t)}))}function za(t){return"_value"in t?t._value:t.value}function Fa(t){t.target.composing=!0}function Ba(t){t.target.composing&&(t.target.composing=!1,Va(t.target,"input"))}function Va(t,e){var n=document.createEvent("HTMLEvents");n.initEvent(e,!0,!0),t.dispatchEvent(n)}function qa(t){return!t.componentInstance||t.data&&t.data.transition?t:qa(t.componentInstance._vnode)}var Ha={bind:function(t,e,n){var r=e.value;n=qa(n);var o=n.data&&n.data.transition,i=t.__vOriginalDisplay="none"===t.style.display?"":t.style.display;r&&o?(n.data.show=!0,Ca(n,(function(){t.style.display=i}))):t.style.display=r?i:"none"},update:function(t,e,n){var r=e.value,o=e.oldValue;if(!r!==!o){n=qa(n);var i=n.data&&n.data.transition;i?(n.data.show=!0,r?Ca(n,(function(){t.style.display=t.__vOriginalDisplay})):Ma(n,(function(){t.style.display="none"}))):t.style.display=r?t.__vOriginalDisplay:"none"}},unbind:function(t,e,n,r,o){o||(t.style.display=t.__vOriginalDisplay)}},Wa={model:ja,show:Ha},Ua={name:String,appear:Boolean,css:Boolean,mode:String,type:String,enterClass:String,leaveClass:String,enterToClass:String,leaveToClass:String,enterActiveClass:String,leaveActiveClass:String,appearClass:String,appearActiveClass:String,appearToClass:String,duration:[Number,String,Object]};function Ka(t){var e=t&&t.componentOptions;return e&&e.Ctor.options.abstract?Ka(kn(e.children)):t}function Ja(t){var e={},n=t.$options;for(var r in n.propsData)e[r]=t[r];var o=n._parentListeners;for(var i in o)e[_(i)]=o[i];return e}function Ya(t,e){if(/\d-keep-alive$/.test(e.tag))return t("keep-alive",{props:e.componentOptions.propsData})}function Ga(t){while(t=t.parent)if(t.data.transition)return!0}function Xa(t,e){return e.key===t.key&&e.tag===t.tag}var Za=function(t){return t.tag||_n(t)},Qa=function(t){return"show"===t.name},ts={name:"transition",props:Ua,abstract:!0,render:function(t){var e=this,n=this.$slots.default;if(n&&(n=n.filter(Za),n.length)){0;var r=this.mode;0;var o=n[0];if(Ga(this.$vnode))return o;var i=Ka(o);if(!i)return o;if(this._leaving)return Ya(t,o);var a="__transition-"+this._uid+"-";i.key=null==i.key?i.isComment?a+"comment":a+i.tag:s(i.key)?0===String(i.key).indexOf(a)?i.key:a+i.key:i.key;var c=(i.data||(i.data={})).transition=Ja(this),u=this._vnode,l=Ka(u);if(i.data.directives&&i.data.directives.some(Qa)&&(i.data.show=!0),l&&l.data&&!Xa(i,l)&&!_n(l)&&(!l.componentInstance||!l.componentInstance._vnode.isComment)){var f=l.data.transition=D({},c);if("out-in"===r)return this._leaving=!0,Oe(f,"afterLeave",(function(){e._leaving=!1,e.$forceUpdate()})),Ya(t,o);if("in-out"===r){if(_n(i))return u;var p,d=function(){p()};Oe(c,"afterEnter",d),Oe(c,"enterCancelled",d),Oe(f,"delayLeave",(function(t){p=t}))}}return o}}},es=D({tag:String,moveClass:String},Ua);delete es.mode;var ns={props:es,beforeMount:function(){var t=this,e=this._update;this._update=function(n,r){var o=Nn(t);t.__patch__(t._vnode,t.kept,!1,!0),t._vnode=t.kept,o(),e.call(t,n,r)}},render:function(t){for(var e=this.tag||this.$vnode.data.tag||"span",n=Object.create(null),r=this.prevChildren=this.children,o=this.$slots.default||[],i=this.children=[],a=Ja(this),s=0;sc&&(s.push(i=t.slice(c,o)),a.push(JSON.stringify(i)));var u=ri(r[1].trim());a.push("_s("+u+")"),s.push({"@binding":u}),c=o+r[0].length}return c\/=]+)(?:\s*(=)\s*(?:"([^"]*)"+|'([^']*)'+|([^\s"'=<>`]+)))?/,Os=/^\s*((?:v-[\w-]+:|@|:|#)\[[^=]+\][^\s"'<>\/=]*)(?:\s*(=)\s*(?:"([^"]*)"+|'([^']*)'+|([^\s"'=<>`]+)))?/,_s="[a-zA-Z_][\\-\\.0-9_a-zA-Z"+W.source+"]*",ks="((?:"+_s+"\\:)?"+_s+")",Cs=new RegExp("^<"+ks),Ms=/^\s*(\/?)>/,Es=new RegExp("^<\\/"+ks+"[^>]*>"),As=/^]+>/i,Ts=/^",""":'"',"&":"&"," ":"\n"," ":"\t","'":"'"},js=/&(?:lt|gt|quot|amp|#39);/g,Is=/&(?:lt|gt|quot|amp|#39|#10|#9);/g,Rs=m("pre,textarea",!0),Ls=function(t,e){return t&&Rs(t)&&"\n"===e[0]};function zs(t,e){var n=e?Is:js;return t.replace(n,(function(t){return Ps[t]}))}function Fs(t,e){var n,r,o=[],i=e.expectHTML,a=e.isUnaryTag||j,s=e.canBeLeftOpenTag||j,c=0;while(t){if(n=t,r&&Ds(r)){var u=0,l=r.toLowerCase(),f=Ns[l]||(Ns[l]=new RegExp("([\\s\\S]*?)(]*>)","i")),p=t.replace(f,(function(t,n,r){return u=r.length,Ds(l)||"noscript"===l||(n=n.replace(//g,"$1").replace(//g,"$1")),Ls(l,n)&&(n=n.slice(1)),e.chars&&e.chars(n),""}));c+=t.length-p.length,t=p,C(l,c-u,c)}else{var d=t.indexOf("<");if(0===d){if(Ts.test(t)){var h=t.indexOf("--\x3e");if(h>=0){e.shouldKeepComment&&e.comment(t.substring(4,h),c,c+h+3),O(h+3);continue}}if($s.test(t)){var v=t.indexOf("]>");if(v>=0){O(v+2);continue}}var m=t.match(As);if(m){O(m[0].length);continue}var g=t.match(Es);if(g){var y=c;O(g[0].length),C(g[1],y,c);continue}var b=_();if(b){k(b),Ls(b.tagName,t)&&O(1);continue}}var w=void 0,x=void 0,S=void 0;if(d>=0){x=t.slice(d);while(!Es.test(x)&&!Cs.test(x)&&!Ts.test(x)&&!$s.test(x)){if(S=x.indexOf("<",1),S<0)break;d+=S,x=t.slice(d)}w=t.substring(0,d)}d<0&&(w=t),w&&O(w.length),e.chars&&w&&e.chars(w,c-w.length,c)}if(t===n){e.chars&&e.chars(t);break}}function O(e){c+=e,t=t.substring(e)}function _(){var e=t.match(Cs);if(e){var n,r,o={tagName:e[1],attrs:[],start:c};O(e[0].length);while(!(n=t.match(Ms))&&(r=t.match(Os)||t.match(Ss)))r.start=c,O(r[0].length),r.end=c,o.attrs.push(r);if(n)return o.unarySlash=n[1],O(n[0].length),o.end=c,o}}function k(t){var n=t.tagName,c=t.unarySlash;i&&("p"===r&&xs(n)&&C(r),s(n)&&r===n&&C(n));for(var u=a(n)||!!c,l=t.attrs.length,f=new Array(l),p=0;p=0;a--)if(o[a].lowerCasedTag===s)break}else a=0;if(a>=0){for(var u=o.length-1;u>=a;u--)e.end&&e.end(o[u].tag,n,i);o.length=a,r=a&&o[a-1].tag}else"br"===s?e.start&&e.start(t,[],!0,n,i):"p"===s&&(e.start&&e.start(t,[],!1,n,i),e.end&&e.end(t,n,i))}C()}var Bs,Vs,qs,Hs,Ws,Us,Ks,Js,Ys=/^@|^v-on:/,Gs=/^v-|^@|^:|^#/,Xs=/([\s\S]*?)\s+(?:in|of)\s+([\s\S]*)/,Zs=/,([^,\}\]]*)(?:,([^,\}\]]*))?$/,Qs=/^\(|\)$/g,tc=/^\[.*\]$/,ec=/:(.*)$/,nc=/^:|^\.|^v-bind:/,rc=/\.[^.\]]+(?=[^\]]*$)/g,oc=/^v-slot(:|$)|^#/,ic=/[\r\n]/,ac=/\s+/g,sc=S(ys.decode),cc="_empty_";function uc(t,e,n){return{type:1,tag:t,attrsList:e,attrsMap:Tc(e),rawAttrsMap:{},parent:n,children:[]}}function lc(t,e){Bs=e.warn||ii,Us=e.isPreTag||j,Ks=e.mustUseProp||j,Js=e.getTagNamespace||j;var n=e.isReservedTag||j;(function(t){return!!t.component||!n(t.tag)}),qs=ai(e.modules,"transformNode"),Hs=ai(e.modules,"preTransformNode"),Ws=ai(e.modules,"postTransformNode"),Vs=e.delimiters;var r,o,i=[],a=!1!==e.preserveWhitespace,s=e.whitespace,c=!1,u=!1;function l(t){if(f(t),c||t.processed||(t=dc(t,e)),i.length||t===r||r.if&&(t.elseif||t.else)&&xc(r,{exp:t.elseif,block:t}),o&&!t.forbidden)if(t.elseif||t.else)bc(t,o);else{if(t.slotScope){var n=t.slotTarget||'"default"';(o.scopedSlots||(o.scopedSlots={}))[n]=t}o.children.push(t),t.parent=o}t.children=t.children.filter((function(t){return!t.slotScope})),f(t),t.pre&&(c=!1),Us(t.tag)&&(u=!1);for(var a=0;a|^function(?:\s+[\w$]+)?\s*\(/,tu=/\([^)]*?\);*$/,eu=/^[A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*|\['[^']*?']|\["[^"]*?"]|\[\d+]|\[[A-Za-z_$][\w$]*])*$/,nu={esc:27,tab:9,enter:13,space:32,up:38,left:37,right:39,down:40,delete:[8,46]},ru={esc:["Esc","Escape"],tab:"Tab",enter:"Enter",space:[" ","Spacebar"],up:["Up","ArrowUp"],left:["Left","ArrowLeft"],right:["Right","ArrowRight"],down:["Down","ArrowDown"],delete:["Backspace","Delete","Del"]},ou=function(t){return"if("+t+")return null;"},iu={stop:"$event.stopPropagation();",prevent:"$event.preventDefault();",self:ou("$event.target !== $event.currentTarget"),ctrl:ou("!$event.ctrlKey"),shift:ou("!$event.shiftKey"),alt:ou("!$event.altKey"),meta:ou("!$event.metaKey"),left:ou("'button' in $event && $event.button !== 0"),middle:ou("'button' in $event && $event.button !== 1"),right:ou("'button' in $event && $event.button !== 2")};function au(t,e){var n=e?"nativeOn:":"on:",r="",o="";for(var i in t){var a=su(t[i]);t[i]&&t[i].dynamic?o+=i+","+a+",":r+='"'+i+'":'+a+","}return r="{"+r.slice(0,-1)+"}",o?n+"_d("+r+",["+o.slice(0,-1)+"])":n+r}function su(t){if(!t)return"function(){}";if(Array.isArray(t))return"["+t.map((function(t){return su(t)})).join(",")+"]";var e=eu.test(t.value),n=Qc.test(t.value),r=eu.test(t.value.replace(tu,""));if(t.modifiers){var o="",i="",a=[];for(var s in t.modifiers)if(iu[s])i+=iu[s],nu[s]&&a.push(s);else if("exact"===s){var c=t.modifiers;i+=ou(["ctrl","shift","alt","meta"].filter((function(t){return!c[t]})).map((function(t){return"$event."+t+"Key"})).join("||"))}else a.push(s);a.length&&(o+=cu(a)),i&&(o+=i);var u=e?"return "+t.value+"($event)":n?"return ("+t.value+")($event)":r?"return "+t.value:t.value;return"function($event){"+o+u+"}"}return e||n?t.value:"function($event){"+(r?"return "+t.value:t.value)+"}"}function cu(t){return"if(!$event.type.indexOf('key')&&"+t.map(uu).join("&&")+")return null;"}function uu(t){var e=parseInt(t,10);if(e)return"$event.keyCode!=="+e;var n=nu[t],r=ru[t];return"_k($event.keyCode,"+JSON.stringify(t)+","+JSON.stringify(n)+",$event.key,"+JSON.stringify(r)+")"}function lu(t,e){t.wrapListeners=function(t){return"_g("+t+","+e.value+")"}}function fu(t,e){t.wrapData=function(n){return"_b("+n+",'"+t.tag+"',"+e.value+","+(e.modifiers&&e.modifiers.prop?"true":"false")+(e.modifiers&&e.modifiers.sync?",true":"")+")"}}var pu={on:lu,bind:fu,cloak:P},du=function(t){this.options=t,this.warn=t.warn||ii,this.transforms=ai(t.modules,"transformCode"),this.dataGenFns=ai(t.modules,"genData"),this.directives=D(D({},pu),t.directives);var e=t.isReservedTag||j;this.maybeComponent=function(t){return!!t.component||!e(t.tag)},this.onceId=0,this.staticRenderFns=[],this.pre=!1};function hu(t,e){var n=new du(e),r=t?vu(t,n):'_c("div")';return{render:"with(this){return "+r+"}",staticRenderFns:n.staticRenderFns}}function vu(t,e){if(t.parent&&(t.pre=t.pre||t.parent.pre),t.staticRoot&&!t.staticProcessed)return mu(t,e);if(t.once&&!t.onceProcessed)return gu(t,e);if(t.for&&!t.forProcessed)return wu(t,e);if(t.if&&!t.ifProcessed)return yu(t,e);if("template"!==t.tag||t.slotTarget||e.pre){if("slot"===t.tag)return Pu(t,e);var n;if(t.component)n=ju(t.component,t,e);else{var r;(!t.plain||t.pre&&e.maybeComponent(t))&&(r=xu(t,e));var o=t.inlineTemplate?null:Eu(t,e,!0);n="_c('"+t.tag+"'"+(r?","+r:"")+(o?","+o:"")+")"}for(var i=0;i>>0}function Cu(t){return 1===t.type&&("slot"===t.tag||t.children.some(Cu))}function Mu(t,e){var n=t.attrsMap["slot-scope"];if(t.if&&!t.ifProcessed&&!n)return yu(t,e,Mu,"null");if(t.for&&!t.forProcessed)return wu(t,e,Mu);var r=t.slotScope===cc?"":String(t.slotScope),o="function("+r+"){return "+("template"===t.tag?t.if&&n?"("+t.if+")?"+(Eu(t,e)||"undefined")+":undefined":Eu(t,e)||"undefined":vu(t,e))+"}",i=r?"":",proxy:true";return"{key:"+(t.slotTarget||'"default"')+",fn:"+o+i+"}"}function Eu(t,e,n,r,o){var i=t.children;if(i.length){var a=i[0];if(1===i.length&&a.for&&"template"!==a.tag&&"slot"!==a.tag){var s=n?e.maybeComponent(a)?",1":",0":"";return""+(r||vu)(a,e)+s}var c=n?Au(i,e.maybeComponent):0,u=o||$u;return"["+i.map((function(t){return u(t,e)})).join(",")+"]"+(c?","+c:"")}}function Au(t,e){for(var n=0,r=0;r':'

',Bu.innerHTML.indexOf(" ")>0}var Uu=!!Z&&Wu(!1),Ku=!!Z&&Wu(!0),Ju=S((function(t){var e=ho(t);return e&&e.innerHTML})),Yu=kr.prototype.$mount;function Gu(t){if(t.outerHTML)return t.outerHTML;var e=document.createElement("div");return e.appendChild(t.cloneNode(!0)),e.innerHTML}kr.prototype.$mount=function(t,e){if(t=t&&ho(t),t===document.body||t===document.documentElement)return this;var n=this.$options;if(!n.render){var r=n.template;if(r)if("string"===typeof r)"#"===r.charAt(0)&&(r=Ju(r));else{if(!r.nodeType)return this;r=r.innerHTML}else t&&(r=Gu(t));if(r){0;var o=Hu(r,{outputSourceRange:!1,shouldDecodeNewlines:Uu,shouldDecodeNewlinesForHref:Ku,delimiters:n.delimiters,comments:n.comments},this),i=o.render,a=o.staticRenderFns;n.render=i,n.staticRenderFns=a}}return Yu.call(this,t,e)},kr.compile=Hu,e["a"]=kr}).call(this,n("c8ba"))},a691:function(t,e){var n=Math.ceil,r=Math.floor;t.exports=function(t){return isNaN(t=+t)?0:(t>0?r:n)(t)}},aa47:function(t,e,n){"use strict"; +/**! + * Sortable 1.10.2 + * @author RubaXa + * @author owenm + * @license MIT + */ +function r(t){return r="function"===typeof Symbol&&"symbol"===typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"===typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t},r(t)}function o(t,e,n){return e in t?Object.defineProperty(t,e,{value:n,enumerable:!0,configurable:!0,writable:!0}):t[e]=n,t}function i(){return i=Object.assign||function(t){for(var e=1;e=0||(o[n]=t[n]);return o}function c(t,e){if(null==t)return{};var n,r,o=s(t,e);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(t);for(r=0;r=0||Object.prototype.propertyIsEnumerable.call(t,n)&&(o[n]=t[n])}return o}function u(t){return l(t)||f(t)||p()}function l(t){if(Array.isArray(t)){for(var e=0,n=new Array(t.length);e"===e[0]&&(e=e.substring(1)),t)try{if(t.matches)return t.matches(e);if(t.msMatchesSelector)return t.msMatchesSelector(e);if(t.webkitMatchesSelector)return t.webkitMatchesSelector(e)}catch(n){return!1}return!1}}function k(t){return t.host&&t!==document&&t.host.nodeType?t.host:t.parentNode}function C(t,e,n,r){if(t){n=n||document;do{if(null!=e&&(">"===e[0]?t.parentNode===n&&_(t,e):_(t,e))||r&&t===n)return t;if(t===n)break}while(t=k(t))}return null}var M,E=/\s+/g;function A(t,e,n){if(t&&e)if(t.classList)t.classList[n?"add":"remove"](e);else{var r=(" "+t.className+" ").replace(E," ").replace(" "+e+" "," ");t.className=(r+(n?" "+e:"")).replace(E," ")}}function T(t,e,n){var r=t&&t.style;if(r){if(void 0===n)return document.defaultView&&document.defaultView.getComputedStyle?n=document.defaultView.getComputedStyle(t,""):t.currentStyle&&(n=t.currentStyle),void 0===e?n:n[e];e in r||-1!==e.indexOf("webkit")||(e="-webkit-"+e),r[e]=n+("string"===typeof n?"":"px")}}function $(t,e){var n="";if("string"===typeof t)n=t;else do{var r=T(t,"transform");r&&"none"!==r&&(n=r+" "+n)}while(!e&&(t=t.parentNode));var o=window.DOMMatrix||window.WebKitCSSMatrix||window.CSSMatrix||window.MSCSSMatrix;return o&&new o(n)}function D(t,e,n){if(t){var r=t.getElementsByTagName(e),o=0,i=r.length;if(n)for(;o=i:o<=i,!a)return r;if(r===N())break;r=B(r,!1)}return!1}function I(t,e,n){var r=0,o=0,i=t.children;while(o2&&void 0!==arguments[2]?arguments[2]:{},r=n.evt,o=c(n,["evt"]);nt.pluginEvent.bind(Zt)(t,e,a({dragEl:at,parentEl:st,ghostEl:ct,rootEl:ut,nextEl:lt,lastDownEl:ft,cloneEl:pt,cloneHidden:dt,dragStarted:Ct,putSortable:bt,activeSortable:Zt.active,originalEvent:r,oldIndex:ht,oldDraggableIndex:mt,newIndex:vt,newDraggableIndex:gt,hideGhostForTarget:Jt,unhideGhostForTarget:Yt,cloneNowHidden:function(){dt=!0},cloneNowShown:function(){dt=!1},dispatchSortableEvent:function(t){it({sortable:e,name:t,originalEvent:r})}},o))};function it(t){rt(a({putSortable:bt,cloneEl:pt,targetEl:at,rootEl:ut,oldIndex:ht,oldDraggableIndex:mt,newIndex:vt,newDraggableIndex:gt},t))}var at,st,ct,ut,lt,ft,pt,dt,ht,vt,mt,gt,yt,bt,wt,xt,St,Ot,_t,kt,Ct,Mt,Et,At,Tt,$t=!1,Dt=!1,Nt=[],Pt=!1,jt=!1,It=[],Rt=!1,Lt=[],zt="undefined"!==typeof document,Ft=b,Bt=m||v?"cssFloat":"float",Vt=zt&&!w&&!b&&"draggable"in document.createElement("div"),qt=function(){if(zt){if(v)return!1;var t=document.createElement("x");return t.style.cssText="pointer-events:auto","auto"===t.style.pointerEvents}}(),Ht=function(t,e){var n=T(t),r=parseInt(n.width)-parseInt(n.paddingLeft)-parseInt(n.paddingRight)-parseInt(n.borderLeftWidth)-parseInt(n.borderRightWidth),o=I(t,0,e),i=I(t,1,e),a=o&&T(o),s=i&&T(i),c=a&&parseInt(a.marginLeft)+parseInt(a.marginRight)+P(o).width,u=s&&parseInt(s.marginLeft)+parseInt(s.marginRight)+P(i).width;if("flex"===n.display)return"column"===n.flexDirection||"column-reverse"===n.flexDirection?"vertical":"horizontal";if("grid"===n.display)return n.gridTemplateColumns.split(" ").length<=1?"vertical":"horizontal";if(o&&a["float"]&&"none"!==a["float"]){var l="left"===a["float"]?"left":"right";return!i||"both"!==s.clear&&s.clear!==l?"horizontal":"vertical"}return o&&("block"===a.display||"flex"===a.display||"table"===a.display||"grid"===a.display||c>=r&&"none"===n[Bt]||i&&"none"===n[Bt]&&c+u>r)?"vertical":"horizontal"},Wt=function(t,e,n){var r=n?t.left:t.top,o=n?t.right:t.bottom,i=n?t.width:t.height,a=n?e.left:e.top,s=n?e.right:e.bottom,c=n?e.width:e.height;return r===a||o===s||r+i/2===a+c/2},Ut=function(t,e){var n;return Nt.some((function(r){if(!R(r)){var o=P(r),i=r[G].options.emptyInsertThreshold,a=t>=o.left-i&&t<=o.right+i,s=e>=o.top-i&&e<=o.bottom+i;return i&&a&&s?n=r:void 0}})),n},Kt=function(t){function e(t,n){return function(r,o,i,a){var s=r.options.group.name&&o.options.group.name&&r.options.group.name===o.options.group.name;if(null==t&&(n||s))return!0;if(null==t||!1===t)return!1;if(n&&"clone"===t)return t;if("function"===typeof t)return e(t(r,o,i,a),n)(r,o,i,a);var c=(n?r:o).options.group.name;return!0===t||"string"===typeof t&&t===c||t.join&&t.indexOf(c)>-1}}var n={},o=t.group;o&&"object"==r(o)||(o={name:o}),n.name=o.name,n.checkPull=e(o.pull,!0),n.checkPut=e(o.put),n.revertClone=o.revertClone,t.group=n},Jt=function(){!qt&&ct&&T(ct,"display","none")},Yt=function(){!qt&&ct&&T(ct,"display","")};zt&&document.addEventListener("click",(function(t){if(Dt)return t.preventDefault(),t.stopPropagation&&t.stopPropagation(),t.stopImmediatePropagation&&t.stopImmediatePropagation(),Dt=!1,!1}),!0);var Gt=function(t){if(at){t=t.touches?t.touches[0]:t;var e=Ut(t.clientX,t.clientY);if(e){var n={};for(var r in t)t.hasOwnProperty(r)&&(n[r]=t[r]);n.target=n.rootEl=e,n.preventDefault=void 0,n.stopPropagation=void 0,e[G]._onDragOver(n)}}},Xt=function(t){at&&at.parentNode[G]._isOutsideThisEl(t.target)};function Zt(t,e){if(!t||!t.nodeType||1!==t.nodeType)throw"Sortable: `el` must be an HTMLElement, not ".concat({}.toString.call(t));this.el=t,this.options=e=i({},e),t[G]=this;var n={group:null,sort:!0,disabled:!1,store:null,handle:null,draggable:/^[uo]l$/i.test(t.nodeName)?">li":">*",swapThreshold:1,invertSwap:!1,invertedSwapThreshold:null,removeCloneOnHide:!0,direction:function(){return Ht(t,this.options)},ghostClass:"sortable-ghost",chosenClass:"sortable-chosen",dragClass:"sortable-drag",ignore:"a, img",filter:null,preventOnFilter:!0,animation:0,easing:null,setData:function(t,e){t.setData("Text",e.textContent)},dropBubble:!1,dragoverBubble:!1,dataIdAttr:"data-id",delay:0,delayOnTouchOnly:!1,touchStartThreshold:(Number.parseInt?Number:window).parseInt(window.devicePixelRatio,10)||1,forceFallback:!1,fallbackClass:"sortable-fallback",fallbackOnBody:!1,fallbackTolerance:0,fallbackOffset:{x:0,y:0},supportPointer:!1!==Zt.supportPointer&&"PointerEvent"in window,emptyInsertThreshold:5};for(var r in nt.initializePlugins(this,t,n),n)!(r in e)&&(e[r]=n[r]);for(var o in Kt(e),this)"_"===o.charAt(0)&&"function"===typeof this[o]&&(this[o]=this[o].bind(this));this.nativeDraggable=!e.forceFallback&&Vt,this.nativeDraggable&&(this.options.touchStartThreshold=1),e.supportPointer?S(t,"pointerdown",this._onTapStart):(S(t,"mousedown",this._onTapStart),S(t,"touchstart",this._onTapStart)),this.nativeDraggable&&(S(t,"dragover",this),S(t,"dragenter",this)),Nt.push(this.el),e.store&&e.store.get&&this.sort(e.store.get(this)||[]),i(this,X())}function Qt(t){t.dataTransfer&&(t.dataTransfer.dropEffect="move"),t.cancelable&&t.preventDefault()}function te(t,e,n,r,o,i,a,s){var c,u,l=t[G],f=l.options.onMove;return!window.CustomEvent||v||m?(c=document.createEvent("Event"),c.initEvent("move",!0,!0)):c=new CustomEvent("move",{bubbles:!0,cancelable:!0}),c.to=e,c.from=t,c.dragged=n,c.draggedRect=r,c.related=o||e,c.relatedRect=i||P(e),c.willInsertAfter=s,c.originalEvent=a,t.dispatchEvent(c),f&&(u=f.call(l,c,a)),u}function ee(t){t.draggable=!1}function ne(){Rt=!1}function re(t,e,n){var r=P(R(n.el,n.options.draggable)),o=10;return e?t.clientX>r.right+o||t.clientX<=r.right&&t.clientY>r.bottom&&t.clientX>=r.left:t.clientX>r.right&&t.clientY>r.top||t.clientX<=r.right&&t.clientY>r.bottom+o}function oe(t,e,n,r,o,i,a,s){var c=r?t.clientY:t.clientX,u=r?n.height:n.width,l=r?n.top:n.left,f=r?n.bottom:n.right,p=!1;if(!a)if(s&&Atl+u*i/2:cf-At)return-Et}else if(c>l+u*(1-o)/2&&cf-u*i/2)?c>l+u/2?1:-1:0}function ie(t){return L(at)=Math.floor(this.options.touchStartThreshold/(this.nativeDraggable&&window.devicePixelRatio||1))&&this._disableDelayedDrag()},_disableDelayedDrag:function(){at&&ee(at),clearTimeout(this._dragStartTimer),this._disableDelayedDragEvents()},_disableDelayedDragEvents:function(){var t=this.el.ownerDocument;O(t,"mouseup",this._disableDelayedDrag),O(t,"touchend",this._disableDelayedDrag),O(t,"touchcancel",this._disableDelayedDrag),O(t,"mousemove",this._delayedDragTouchMoveHandler),O(t,"touchmove",this._delayedDragTouchMoveHandler),O(t,"pointermove",this._delayedDragTouchMoveHandler)},_triggerDragStart:function(t,e){e=e||"touch"==t.pointerType&&t,!this.nativeDraggable||e?this.options.supportPointer?S(document,"pointermove",this._onTouchMove):S(document,e?"touchmove":"mousemove",this._onTouchMove):(S(at,"dragend",this),S(ut,"dragstart",this._onDragStart));try{document.selection?ce((function(){document.selection.empty()})):window.getSelection().removeAllRanges()}catch(n){}},_dragStarted:function(t,e){if($t=!1,ut&&at){ot("dragStarted",this,{evt:e}),this.nativeDraggable&&S(document,"dragover",Xt);var n=this.options;!t&&A(at,n.dragClass,!1),A(at,n.ghostClass,!0),Zt.active=this,t&&this._appendGhost(),it({sortable:this,name:"start",originalEvent:e})}else this._nulling()},_emulateDragOver:function(){if(xt){this._lastX=xt.clientX,this._lastY=xt.clientY,Jt();var t=document.elementFromPoint(xt.clientX,xt.clientY),e=t;while(t&&t.shadowRoot){if(t=t.shadowRoot.elementFromPoint(xt.clientX,xt.clientY),t===e)break;e=t}if(at.parentNode[G]._isOutsideThisEl(t),e)do{if(e[G]){var n=void 0;if(n=e[G]._onDragOver({clientX:xt.clientX,clientY:xt.clientY,target:t,rootEl:e}),n&&!this.options.dragoverBubble)break}t=e}while(e=e.parentNode);Yt()}},_onTouchMove:function(t){if(wt){var e=this.options,n=e.fallbackTolerance,r=e.fallbackOffset,o=t.touches?t.touches[0]:t,i=ct&&$(ct,!0),a=ct&&i&&i.a,s=ct&&i&&i.d,c=Ft&&Tt&&z(Tt),u=(o.clientX-wt.clientX+r.x)/(a||1)+(c?c[0]-It[0]:0)/(a||1),l=(o.clientY-wt.clientY+r.y)/(s||1)+(c?c[1]-It[1]:0)/(s||1);if(!Zt.active&&!$t){if(n&&Math.max(Math.abs(o.clientX-this._lastX),Math.abs(o.clientY-this._lastY))=0&&(it({rootEl:st,name:"add",toEl:st,fromEl:ut,originalEvent:t}),it({sortable:this,name:"remove",toEl:st,originalEvent:t}),it({rootEl:st,name:"sort",toEl:st,fromEl:ut,originalEvent:t}),it({sortable:this,name:"sort",toEl:st,originalEvent:t})),bt&&bt.save()):vt!==ht&&vt>=0&&(it({sortable:this,name:"update",toEl:st,originalEvent:t}),it({sortable:this,name:"sort",toEl:st,originalEvent:t})),Zt.active&&(null!=vt&&-1!==vt||(vt=ht,gt=mt),it({sortable:this,name:"end",toEl:st,originalEvent:t}),this.save())))),this._nulling()},_nulling:function(){ot("nulling",this),ut=at=st=ct=lt=pt=ft=dt=wt=xt=Ct=vt=gt=ht=mt=Mt=Et=bt=yt=Zt.dragged=Zt.ghost=Zt.clone=Zt.active=null,Lt.forEach((function(t){t.checked=!0})),Lt.length=St=Ot=0},handleEvent:function(t){switch(t.type){case"drop":case"dragend":this._onDrop(t);break;case"dragenter":case"dragover":at&&(this._onDragOver(t),Qt(t));break;case"selectstart":t.preventDefault();break}},toArray:function(){for(var t,e=[],n=this.el.children,r=0,o=n.length,i=this.options;r1&&(Ne.forEach((function(t){r.addAnimationState({target:t,rect:Ie?P(t):o}),Y(t),t.fromRect=o,e.removeAnimationState(t)})),Ie=!1,ze(!this.options.removeCloneOnHide,n))},dragOverCompleted:function(t){var e=t.sortable,n=t.isOwner,r=t.insertion,o=t.activeSortable,i=t.parentEl,a=t.putSortable,s=this.options;if(r){if(n&&o._hideClone(),je=!1,s.animation&&Ne.length>1&&(Ie||!n&&!o.options.sort&&!a)){var c=P(Te,!1,!0,!0);Ne.forEach((function(t){t!==Te&&(J(t,c),i.appendChild(t))})),Ie=!0}if(!n)if(Ie||Be(),Ne.length>1){var u=De;o._showClone(e),o.options.animation&&!De&&u&&Pe.forEach((function(t){o.addAnimationState({target:t,rect:$e}),t.fromRect=$e,t.thisAnimationDuration=null}))}else o._showClone(e)}},dragOverAnimationCapture:function(t){var e=t.dragRect,n=t.isOwner,r=t.activeSortable;if(Ne.forEach((function(t){t.thisAnimationDuration=null})),r.options.animation&&!n&&r.multiDrag.isMultiDrag){$e=i({},e);var o=$(Te,!0);$e.top-=o.f,$e.left-=o.e}},dragOverAnimationComplete:function(){Ie&&(Ie=!1,Be())},drop:function(t){var e=t.originalEvent,n=t.rootEl,r=t.parentEl,o=t.sortable,i=t.dispatchSortableEvent,a=t.oldIndex,s=t.putSortable,c=s||this.sortable;if(e){var u=this.options,l=r.children;if(!Re)if(u.multiDragKey&&!this.multiDragKeyDown&&this._deselectMultiDrag(),A(Te,u.selectedClass,!~Ne.indexOf(Te)),~Ne.indexOf(Te))Ne.splice(Ne.indexOf(Te),1),Ee=null,rt({sortable:o,rootEl:n,name:"deselect",targetEl:Te,originalEvt:e});else{if(Ne.push(Te),rt({sortable:o,rootEl:n,name:"select",targetEl:Te,originalEvt:e}),e.shiftKey&&Ee&&o.el.contains(Ee)){var f,p,d=L(Ee),h=L(Te);if(~d&&~h&&d!==h)for(h>d?(p=d,f=h):(p=h,f=d+1);p1){var v=P(Te),m=L(Te,":not(."+this.options.selectedClass+")");if(!je&&u.animation&&(Te.thisAnimationDuration=null),c.captureAnimationState(),!je&&(u.animation&&(Te.fromRect=v,Ne.forEach((function(t){if(t.thisAnimationDuration=null,t!==Te){var e=Ie?P(t):v;t.fromRect=e,c.addAnimationState({target:t,rect:e})}}))),Be(),Ne.forEach((function(t){l[m]?r.insertBefore(t,l[m]):r.appendChild(t),m++})),a===L(Te))){var g=!1;Ne.forEach((function(t){t.sortableIndex===L(t)||(g=!0)})),g&&i("update")}Ne.forEach((function(t){Y(t)})),c.animateAll()}Ae=c}(n===r||s&&"clone"!==s.lastPutMode)&&Pe.forEach((function(t){t.parentNode&&t.parentNode.removeChild(t)}))}},nullingGlobal:function(){this.isMultiDrag=Re=!1,Pe.length=0},destroyGlobal:function(){this._deselectMultiDrag(),O(document,"pointerup",this._deselectMultiDrag),O(document,"mouseup",this._deselectMultiDrag),O(document,"touchend",this._deselectMultiDrag),O(document,"keydown",this._checkKeyDown),O(document,"keyup",this._checkKeyUp)},_deselectMultiDrag:function(t){if(("undefined"===typeof Re||!Re)&&Ae===this.sortable&&(!t||!C(t.target,this.options.draggable,this.sortable.el,!1))&&(!t||0===t.button))while(Ne.length){var e=Ne[0];A(e,this.options.selectedClass,!1),Ne.shift(),rt({sortable:this.sortable,rootEl:this.sortable.el,name:"deselect",targetEl:e,originalEvt:t})}},_checkKeyDown:function(t){t.key===this.options.multiDragKey&&(this.multiDragKeyDown=!0)},_checkKeyUp:function(t){t.key===this.options.multiDragKey&&(this.multiDragKeyDown=!1)}},i(t,{pluginName:"multiDrag",utils:{select:function(t){var e=t.parentNode[G];e&&e.options.multiDrag&&!~Ne.indexOf(t)&&(Ae&&Ae!==e&&(Ae.multiDrag._deselectMultiDrag(),Ae=e),A(t,e.options.selectedClass,!0),Ne.push(t))},deselect:function(t){var e=t.parentNode[G],n=Ne.indexOf(t);e&&e.options.multiDrag&&~n&&(A(t,e.options.selectedClass,!1),Ne.splice(n,1))}},eventProperties:function(){var t=this,e=[],n=[];return Ne.forEach((function(r){var o;e.push({multiDragElement:r,index:r.sortableIndex}),o=Ie&&r!==Te?-1:Ie?L(r,":not(."+t.options.selectedClass+")"):L(r),n.push({multiDragElement:r,index:o})})),{items:u(Ne),clones:[].concat(Pe),oldIndicies:e,newIndicies:n}},optionListeners:{multiDragKey:function(t){return t=t.toLowerCase(),"ctrl"===t?t="Control":t.length>1&&(t=t.charAt(0).toUpperCase()+t.substr(1)),t}}})}function ze(t,e){Ne.forEach((function(n,r){var o=e.children[n.sortableIndex+(t?Number(r):0)];o?e.insertBefore(n,o):e.appendChild(n)}))}function Fe(t,e){Pe.forEach((function(n,r){var o=e.children[n.sortableIndex+(t?Number(r):0)];o?e.insertBefore(n,o):e.appendChild(n)}))}function Be(){Ne.forEach((function(t){t!==Te&&t.parentNode&&t.parentNode.removeChild(t)}))}Zt.mount(new ye),Zt.mount(ke,_e),e["default"]=Zt},aa82:function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0;var r=n("78ef"),o=function(t){return(0,r.withParams)({type:"requiredIf",prop:t},(function(e,n){return!(0,r.ref)(t,this,n)||(0,r.req)(e)}))};e.default=o},b5ae:function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"alpha",{enumerable:!0,get:function(){return r.default}}),Object.defineProperty(e,"alphaNum",{enumerable:!0,get:function(){return o.default}}),Object.defineProperty(e,"numeric",{enumerable:!0,get:function(){return i.default}}),Object.defineProperty(e,"between",{enumerable:!0,get:function(){return a.default}}),Object.defineProperty(e,"email",{enumerable:!0,get:function(){return s.default}}),Object.defineProperty(e,"ipAddress",{enumerable:!0,get:function(){return c.default}}),Object.defineProperty(e,"macAddress",{enumerable:!0,get:function(){return u.default}}),Object.defineProperty(e,"maxLength",{enumerable:!0,get:function(){return l.default}}),Object.defineProperty(e,"minLength",{enumerable:!0,get:function(){return f.default}}),Object.defineProperty(e,"required",{enumerable:!0,get:function(){return p.default}}),Object.defineProperty(e,"requiredIf",{enumerable:!0,get:function(){return d.default}}),Object.defineProperty(e,"requiredUnless",{enumerable:!0,get:function(){return h.default}}),Object.defineProperty(e,"sameAs",{enumerable:!0,get:function(){return v.default}}),Object.defineProperty(e,"url",{enumerable:!0,get:function(){return m.default}}),Object.defineProperty(e,"or",{enumerable:!0,get:function(){return g.default}}),Object.defineProperty(e,"and",{enumerable:!0,get:function(){return y.default}}),Object.defineProperty(e,"not",{enumerable:!0,get:function(){return b.default}}),Object.defineProperty(e,"minValue",{enumerable:!0,get:function(){return w.default}}),Object.defineProperty(e,"maxValue",{enumerable:!0,get:function(){return x.default}}),Object.defineProperty(e,"integer",{enumerable:!0,get:function(){return S.default}}),Object.defineProperty(e,"decimal",{enumerable:!0,get:function(){return O.default}}),e.helpers=void 0;var r=C(n("6235")),o=C(n("3a54")),i=C(n("45b8")),a=C(n("ec11")),s=C(n("5d75")),c=C(n("c99d")),u=C(n("91d3")),l=C(n("2a12")),f=C(n("5db3")),p=C(n("d4f4")),d=C(n("aa82")),h=C(n("e652")),v=C(n("b6cb")),m=C(n("772d")),g=C(n("d294")),y=C(n("3360")),b=C(n("6417")),w=C(n("eb66")),x=C(n("46bc")),S=C(n("1331")),O=C(n("c301")),_=k(n("78ef"));function k(t){if(t&&t.__esModule)return t;var e={};if(null!=t)for(var n in t)if(Object.prototype.hasOwnProperty.call(t,n)){var r=Object.defineProperty&&Object.getOwnPropertyDescriptor?Object.getOwnPropertyDescriptor(t,n):{};r.get||r.set?Object.defineProperty(e,n,r):e[n]=t[n]}return e.default=t,e}function C(t){return t&&t.__esModule?t:{default:t}}e.helpers=_},b6cb:function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0;var r=n("78ef"),o=function(t){return(0,r.withParams)({type:"sameAs",eq:t},(function(e,n){return e===(0,r.ref)(t,this,n)}))};e.default=o},b76a:function(t,e,n){(function(e,r){t.exports=r(n("aa47"))})("undefined"!==typeof self&&self,(function(t){return function(t){var e={};function n(r){if(e[r])return e[r].exports;var o=e[r]={i:r,l:!1,exports:{}};return t[r].call(o.exports,o,o.exports,n),o.l=!0,o.exports}return n.m=t,n.c=e,n.d=function(t,e,r){n.o(t,e)||Object.defineProperty(t,e,{enumerable:!0,get:r})},n.r=function(t){"undefined"!==typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})},n.t=function(t,e){if(1&e&&(t=n(t)),8&e)return t;if(4&e&&"object"===typeof t&&t&&t.__esModule)return t;var r=Object.create(null);if(n.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:t}),2&e&&"string"!=typeof t)for(var o in t)n.d(r,o,function(e){return t[e]}.bind(null,o));return r},n.n=function(t){var e=t&&t.__esModule?function(){return t["default"]}:function(){return t};return n.d(e,"a",e),e},n.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},n.p="",n(n.s="fb15")}({"01f9":function(t,e,n){"use strict";var r=n("2d00"),o=n("5ca1"),i=n("2aba"),a=n("32e9"),s=n("84f2"),c=n("41a0"),u=n("7f20"),l=n("38fd"),f=n("2b4c")("iterator"),p=!([].keys&&"next"in[].keys()),d="@@iterator",h="keys",v="values",m=function(){return this};t.exports=function(t,e,n,g,y,b,w){c(n,e,g);var x,S,O,_=function(t){if(!p&&t in E)return E[t];switch(t){case h:return function(){return new n(this,t)};case v:return function(){return new n(this,t)}}return function(){return new n(this,t)}},k=e+" Iterator",C=y==v,M=!1,E=t.prototype,A=E[f]||E[d]||y&&E[y],T=A||_(y),$=y?C?_("entries"):T:void 0,D="Array"==e&&E.entries||A;if(D&&(O=l(D.call(new t)),O!==Object.prototype&&O.next&&(u(O,k,!0),r||"function"==typeof O[f]||a(O,f,m))),C&&A&&A.name!==v&&(M=!0,T=function(){return A.call(this)}),r&&!w||!p&&!M&&E[f]||a(E,f,T),s[e]=T,s[k]=m,y)if(x={values:C?T:_(v),keys:b?T:_(h),entries:$},w)for(S in x)S in E||i(E,S,x[S]);else o(o.P+o.F*(p||M),e,x);return x}},"02f4":function(t,e,n){var r=n("4588"),o=n("be13");t.exports=function(t){return function(e,n){var i,a,s=String(o(e)),c=r(n),u=s.length;return c<0||c>=u?t?"":void 0:(i=s.charCodeAt(c),i<55296||i>56319||c+1===u||(a=s.charCodeAt(c+1))<56320||a>57343?t?s.charAt(c):i:t?s.slice(c,c+2):a-56320+(i-55296<<10)+65536)}}},"0390":function(t,e,n){"use strict";var r=n("02f4")(!0);t.exports=function(t,e,n){return e+(n?r(t,e).length:1)}},"0bfb":function(t,e,n){"use strict";var r=n("cb7c");t.exports=function(){var t=r(this),e="";return t.global&&(e+="g"),t.ignoreCase&&(e+="i"),t.multiline&&(e+="m"),t.unicode&&(e+="u"),t.sticky&&(e+="y"),e}},"0d58":function(t,e,n){var r=n("ce10"),o=n("e11e");t.exports=Object.keys||function(t){return r(t,o)}},1495:function(t,e,n){var r=n("86cc"),o=n("cb7c"),i=n("0d58");t.exports=n("9e1e")?Object.defineProperties:function(t,e){o(t);var n,a=i(e),s=a.length,c=0;while(s>c)r.f(t,n=a[c++],e[n]);return t}},"214f":function(t,e,n){"use strict";n("b0c5");var r=n("2aba"),o=n("32e9"),i=n("79e5"),a=n("be13"),s=n("2b4c"),c=n("520a"),u=s("species"),l=!i((function(){var t=/./;return t.exec=function(){var t=[];return t.groups={a:"7"},t},"7"!=="".replace(t,"$")})),f=function(){var t=/(?:)/,e=t.exec;t.exec=function(){return e.apply(this,arguments)};var n="ab".split(t);return 2===n.length&&"a"===n[0]&&"b"===n[1]}();t.exports=function(t,e,n){var p=s(t),d=!i((function(){var e={};return e[p]=function(){return 7},7!=""[t](e)})),h=d?!i((function(){var e=!1,n=/a/;return n.exec=function(){return e=!0,null},"split"===t&&(n.constructor={},n.constructor[u]=function(){return n}),n[p](""),!e})):void 0;if(!d||!h||"replace"===t&&!l||"split"===t&&!f){var v=/./[p],m=n(a,p,""[t],(function(t,e,n,r,o){return e.exec===c?d&&!o?{done:!0,value:v.call(e,n,r)}:{done:!0,value:t.call(n,e,r)}:{done:!1}})),g=m[0],y=m[1];r(String.prototype,t,g),o(RegExp.prototype,p,2==e?function(t,e){return y.call(t,this,e)}:function(t){return y.call(t,this)})}}},"230e":function(t,e,n){var r=n("d3f4"),o=n("7726").document,i=r(o)&&r(o.createElement);t.exports=function(t){return i?o.createElement(t):{}}},"23c6":function(t,e,n){var r=n("2d95"),o=n("2b4c")("toStringTag"),i="Arguments"==r(function(){return arguments}()),a=function(t,e){try{return t[e]}catch(n){}};t.exports=function(t){var e,n,s;return void 0===t?"Undefined":null===t?"Null":"string"==typeof(n=a(e=Object(t),o))?n:i?r(e):"Object"==(s=r(e))&&"function"==typeof e.callee?"Arguments":s}},2621:function(t,e){e.f=Object.getOwnPropertySymbols},"2aba":function(t,e,n){var r=n("7726"),o=n("32e9"),i=n("69a8"),a=n("ca5a")("src"),s=n("fa5b"),c="toString",u=(""+s).split(c);n("8378").inspectSource=function(t){return s.call(t)},(t.exports=function(t,e,n,s){var c="function"==typeof n;c&&(i(n,"name")||o(n,"name",e)),t[e]!==n&&(c&&(i(n,a)||o(n,a,t[e]?""+t[e]:u.join(String(e)))),t===r?t[e]=n:s?t[e]?t[e]=n:o(t,e,n):(delete t[e],o(t,e,n)))})(Function.prototype,c,(function(){return"function"==typeof this&&this[a]||s.call(this)}))},"2aeb":function(t,e,n){var r=n("cb7c"),o=n("1495"),i=n("e11e"),a=n("613b")("IE_PROTO"),s=function(){},c="prototype",u=function(){var t,e=n("230e")("iframe"),r=i.length,o="<",a=">";e.style.display="none",n("fab2").appendChild(e),e.src="javascript:",t=e.contentWindow.document,t.open(),t.write(o+"script"+a+"document.F=Object"+o+"/script"+a),t.close(),u=t.F;while(r--)delete u[c][i[r]];return u()};t.exports=Object.create||function(t,e){var n;return null!==t?(s[c]=r(t),n=new s,s[c]=null,n[a]=t):n=u(),void 0===e?n:o(n,e)}},"2b4c":function(t,e,n){var r=n("5537")("wks"),o=n("ca5a"),i=n("7726").Symbol,a="function"==typeof i,s=t.exports=function(t){return r[t]||(r[t]=a&&i[t]||(a?i:o)("Symbol."+t))};s.store=r},"2d00":function(t,e){t.exports=!1},"2d95":function(t,e){var n={}.toString;t.exports=function(t){return n.call(t).slice(8,-1)}},"2fdb":function(t,e,n){"use strict";var r=n("5ca1"),o=n("d2c8"),i="includes";r(r.P+r.F*n("5147")(i),"String",{includes:function(t){return!!~o(this,t,i).indexOf(t,arguments.length>1?arguments[1]:void 0)}})},"32e9":function(t,e,n){var r=n("86cc"),o=n("4630");t.exports=n("9e1e")?function(t,e,n){return r.f(t,e,o(1,n))}:function(t,e,n){return t[e]=n,t}},"38fd":function(t,e,n){var r=n("69a8"),o=n("4bf8"),i=n("613b")("IE_PROTO"),a=Object.prototype;t.exports=Object.getPrototypeOf||function(t){return t=o(t),r(t,i)?t[i]:"function"==typeof t.constructor&&t instanceof t.constructor?t.constructor.prototype:t instanceof Object?a:null}},"41a0":function(t,e,n){"use strict";var r=n("2aeb"),o=n("4630"),i=n("7f20"),a={};n("32e9")(a,n("2b4c")("iterator"),(function(){return this})),t.exports=function(t,e,n){t.prototype=r(a,{next:o(1,n)}),i(t,e+" Iterator")}},"456d":function(t,e,n){var r=n("4bf8"),o=n("0d58");n("5eda")("keys",(function(){return function(t){return o(r(t))}}))},4588:function(t,e){var n=Math.ceil,r=Math.floor;t.exports=function(t){return isNaN(t=+t)?0:(t>0?r:n)(t)}},4630:function(t,e){t.exports=function(t,e){return{enumerable:!(1&t),configurable:!(2&t),writable:!(4&t),value:e}}},"4bf8":function(t,e,n){var r=n("be13");t.exports=function(t){return Object(r(t))}},5147:function(t,e,n){var r=n("2b4c")("match");t.exports=function(t){var e=/./;try{"/./"[t](e)}catch(n){try{return e[r]=!1,!"/./"[t](e)}catch(o){}}return!0}},"520a":function(t,e,n){"use strict";var r=n("0bfb"),o=RegExp.prototype.exec,i=String.prototype.replace,a=o,s="lastIndex",c=function(){var t=/a/,e=/b*/g;return o.call(t,"a"),o.call(e,"a"),0!==t[s]||0!==e[s]}(),u=void 0!==/()??/.exec("")[1],l=c||u;l&&(a=function(t){var e,n,a,l,f=this;return u&&(n=new RegExp("^"+f.source+"$(?!\\s)",r.call(f))),c&&(e=f[s]),a=o.call(f,t),c&&a&&(f[s]=f.global?a.index+a[0].length:e),u&&a&&a.length>1&&i.call(a[0],n,(function(){for(l=1;l1?arguments[1]:void 0)}}),n("9c6c")("includes")},6821:function(t,e,n){var r=n("626a"),o=n("be13");t.exports=function(t){return r(o(t))}},"69a8":function(t,e){var n={}.hasOwnProperty;t.exports=function(t,e){return n.call(t,e)}},"6a99":function(t,e,n){var r=n("d3f4");t.exports=function(t,e){if(!r(t))return t;var n,o;if(e&&"function"==typeof(n=t.toString)&&!r(o=n.call(t)))return o;if("function"==typeof(n=t.valueOf)&&!r(o=n.call(t)))return o;if(!e&&"function"==typeof(n=t.toString)&&!r(o=n.call(t)))return o;throw TypeError("Can't convert object to primitive value")}},7333:function(t,e,n){"use strict";var r=n("0d58"),o=n("2621"),i=n("52a7"),a=n("4bf8"),s=n("626a"),c=Object.assign;t.exports=!c||n("79e5")((function(){var t={},e={},n=Symbol(),r="abcdefghijklmnopqrst";return t[n]=7,r.split("").forEach((function(t){e[t]=t})),7!=c({},t)[n]||Object.keys(c({},e)).join("")!=r}))?function(t,e){var n=a(t),c=arguments.length,u=1,l=o.f,f=i.f;while(c>u){var p,d=s(arguments[u++]),h=l?r(d).concat(l(d)):r(d),v=h.length,m=0;while(v>m)f.call(d,p=h[m++])&&(n[p]=d[p])}return n}:c},7726:function(t,e){var n=t.exports="undefined"!=typeof window&&window.Math==Math?window:"undefined"!=typeof self&&self.Math==Math?self:Function("return this")();"number"==typeof __g&&(__g=n)},"77f1":function(t,e,n){var r=n("4588"),o=Math.max,i=Math.min;t.exports=function(t,e){return t=r(t),t<0?o(t+e,0):i(t,e)}},"79e5":function(t,e){t.exports=function(t){try{return!!t()}catch(e){return!0}}},"7f20":function(t,e,n){var r=n("86cc").f,o=n("69a8"),i=n("2b4c")("toStringTag");t.exports=function(t,e,n){t&&!o(t=n?t:t.prototype,i)&&r(t,i,{configurable:!0,value:e})}},8378:function(t,e){var n=t.exports={version:"2.6.5"};"number"==typeof __e&&(__e=n)},"84f2":function(t,e){t.exports={}},"86cc":function(t,e,n){var r=n("cb7c"),o=n("c69a"),i=n("6a99"),a=Object.defineProperty;e.f=n("9e1e")?Object.defineProperty:function(t,e,n){if(r(t),e=i(e,!0),r(n),o)try{return a(t,e,n)}catch(s){}if("get"in n||"set"in n)throw TypeError("Accessors not supported!");return"value"in n&&(t[e]=n.value),t}},"9b43":function(t,e,n){var r=n("d8e8");t.exports=function(t,e,n){if(r(t),void 0===e)return t;switch(n){case 1:return function(n){return t.call(e,n)};case 2:return function(n,r){return t.call(e,n,r)};case 3:return function(n,r,o){return t.call(e,n,r,o)}}return function(){return t.apply(e,arguments)}}},"9c6c":function(t,e,n){var r=n("2b4c")("unscopables"),o=Array.prototype;void 0==o[r]&&n("32e9")(o,r,{}),t.exports=function(t){o[r][t]=!0}},"9def":function(t,e,n){var r=n("4588"),o=Math.min;t.exports=function(t){return t>0?o(r(t),9007199254740991):0}},"9e1e":function(t,e,n){t.exports=!n("79e5")((function(){return 7!=Object.defineProperty({},"a",{get:function(){return 7}}).a}))},a352:function(e,n){e.exports=t},a481:function(t,e,n){"use strict";var r=n("cb7c"),o=n("4bf8"),i=n("9def"),a=n("4588"),s=n("0390"),c=n("5f1b"),u=Math.max,l=Math.min,f=Math.floor,p=/\$([$&`']|\d\d?|<[^>]*>)/g,d=/\$([$&`']|\d\d?)/g,h=function(t){return void 0===t?t:String(t)};n("214f")("replace",2,(function(t,e,n,v){return[function(r,o){var i=t(this),a=void 0==r?void 0:r[e];return void 0!==a?a.call(r,i,o):n.call(String(i),r,o)},function(t,e){var o=v(n,t,this,e);if(o.done)return o.value;var f=r(t),p=String(this),d="function"===typeof e;d||(e=String(e));var g=f.global;if(g){var y=f.unicode;f.lastIndex=0}var b=[];while(1){var w=c(f,p);if(null===w)break;if(b.push(w),!g)break;var x=String(w[0]);""===x&&(f.lastIndex=s(p,i(f.lastIndex),y))}for(var S="",O=0,_=0;_=O&&(S+=p.slice(O,C)+$,O=C+k.length)}return S+p.slice(O)}];function m(t,e,r,i,a,s){var c=r+t.length,u=i.length,l=d;return void 0!==a&&(a=o(a),l=p),n.call(s,l,(function(n,o){var s;switch(o.charAt(0)){case"$":return"$";case"&":return t;case"`":return e.slice(0,r);case"'":return e.slice(c);case"<":s=a[o.slice(1,-1)];break;default:var l=+o;if(0===l)return n;if(l>u){var p=f(l/10);return 0===p?n:p<=u?void 0===i[p-1]?o.charAt(1):i[p-1]+o.charAt(1):n}s=i[l-1]}return void 0===s?"":s}))}}))},aae3:function(t,e,n){var r=n("d3f4"),o=n("2d95"),i=n("2b4c")("match");t.exports=function(t){var e;return r(t)&&(void 0!==(e=t[i])?!!e:"RegExp"==o(t))}},ac6a:function(t,e,n){for(var r=n("cadf"),o=n("0d58"),i=n("2aba"),a=n("7726"),s=n("32e9"),c=n("84f2"),u=n("2b4c"),l=u("iterator"),f=u("toStringTag"),p=c.Array,d={CSSRuleList:!0,CSSStyleDeclaration:!1,CSSValueList:!1,ClientRectList:!1,DOMRectList:!1,DOMStringList:!1,DOMTokenList:!0,DataTransferItemList:!1,FileList:!1,HTMLAllCollection:!1,HTMLCollection:!1,HTMLFormElement:!1,HTMLSelectElement:!1,MediaList:!0,MimeTypeArray:!1,NamedNodeMap:!1,NodeList:!0,PaintRequestList:!1,Plugin:!1,PluginArray:!1,SVGLengthList:!1,SVGNumberList:!1,SVGPathSegList:!1,SVGPointList:!1,SVGStringList:!1,SVGTransformList:!1,SourceBufferList:!1,StyleSheetList:!0,TextTrackCueList:!1,TextTrackList:!1,TouchList:!1},h=o(d),v=0;vl)if(s=c[l++],s!=s)return!0}else for(;u>l;l++)if((t||l in c)&&c[l]===n)return t||l||0;return!t&&-1}}},c649:function(t,e,n){"use strict";(function(t){n.d(e,"c",(function(){return u})),n.d(e,"a",(function(){return s})),n.d(e,"b",(function(){return o})),n.d(e,"d",(function(){return c}));n("a481");function r(){return"undefined"!==typeof window?window.console:t.console}var o=r();function i(t){var e=Object.create(null);return function(n){var r=e[n];return r||(e[n]=t(n))}}var a=/-(\w)/g,s=i((function(t){return t.replace(a,(function(t,e){return e?e.toUpperCase():""}))}));function c(t){null!==t.parentElement&&t.parentElement.removeChild(t)}function u(t,e,n){var r=0===n?t.children[0]:t.children[n-1].nextSibling;t.insertBefore(e,r)}}).call(this,n("c8ba"))},c69a:function(t,e,n){t.exports=!n("9e1e")&&!n("79e5")((function(){return 7!=Object.defineProperty(n("230e")("div"),"a",{get:function(){return 7}}).a}))},c8ba:function(t,e){var n;n=function(){return this}();try{n=n||new Function("return this")()}catch(r){"object"===typeof window&&(n=window)}t.exports=n},ca5a:function(t,e){var n=0,r=Math.random();t.exports=function(t){return"Symbol(".concat(void 0===t?"":t,")_",(++n+r).toString(36))}},cadf:function(t,e,n){"use strict";var r=n("9c6c"),o=n("d53b"),i=n("84f2"),a=n("6821");t.exports=n("01f9")(Array,"Array",(function(t,e){this._t=a(t),this._i=0,this._k=e}),(function(){var t=this._t,e=this._k,n=this._i++;return!t||n>=t.length?(this._t=void 0,o(1)):o(0,"keys"==e?n:"values"==e?t[n]:[n,t[n]])}),"values"),i.Arguments=i.Array,r("keys"),r("values"),r("entries")},cb7c:function(t,e,n){var r=n("d3f4");t.exports=function(t){if(!r(t))throw TypeError(t+" is not an object!");return t}},ce10:function(t,e,n){var r=n("69a8"),o=n("6821"),i=n("c366")(!1),a=n("613b")("IE_PROTO");t.exports=function(t,e){var n,s=o(t),c=0,u=[];for(n in s)n!=a&&r(s,n)&&u.push(n);while(e.length>c)r(s,n=e[c++])&&(~i(u,n)||u.push(n));return u}},d2c8:function(t,e,n){var r=n("aae3"),o=n("be13");t.exports=function(t,e,n){if(r(e))throw TypeError("String#"+n+" doesn't accept regex!");return String(o(t))}},d3f4:function(t,e){t.exports=function(t){return"object"===typeof t?null!==t:"function"===typeof t}},d53b:function(t,e){t.exports=function(t,e){return{value:e,done:!!t}}},d8e8:function(t,e){t.exports=function(t){if("function"!=typeof t)throw TypeError(t+" is not a function!");return t}},e11e:function(t,e){t.exports="constructor,hasOwnProperty,isPrototypeOf,propertyIsEnumerable,toLocaleString,toString,valueOf".split(",")},f559:function(t,e,n){"use strict";var r=n("5ca1"),o=n("9def"),i=n("d2c8"),a="startsWith",s=""[a];r(r.P+r.F*n("5147")(a),"String",{startsWith:function(t){var e=i(this,t,a),n=o(Math.min(arguments.length>1?arguments[1]:void 0,e.length)),r=String(t);return s?s.call(e,r,n):e.slice(n,n+r.length)===r}})},f6fd:function(t,e){(function(t){var e="currentScript",n=t.getElementsByTagName("script");e in t||Object.defineProperty(t,e,{get:function(){try{throw new Error}catch(r){var t,e=(/.*at [^\(]*\((.*):.+:.+\)$/gi.exec(r.stack)||[!1])[1];for(t in n)if(n[t].src==e||"interactive"==n[t].readyState)return n[t];return null}}})})(document)},f751:function(t,e,n){var r=n("5ca1");r(r.S+r.F,"Object",{assign:n("7333")})},fa5b:function(t,e,n){t.exports=n("5537")("native-function-to-string",Function.toString)},fab2:function(t,e,n){var r=n("7726").document;t.exports=r&&r.documentElement},fb15:function(t,e,n){"use strict";var r;(n.r(e),"undefined"!==typeof window)&&(n("f6fd"),(r=window.document.currentScript)&&(r=r.src.match(/(.+\/)[^/]+\.js(\?.*)?$/))&&(n.p=r[1]));n("f751"),n("f559"),n("ac6a"),n("cadf"),n("456d");function o(t){if(Array.isArray(t))return t}function i(t,e){if("undefined"!==typeof Symbol&&Symbol.iterator in Object(t)){var n=[],r=!0,o=!1,i=void 0;try{for(var a,s=t[Symbol.iterator]();!(r=(a=s.next()).done);r=!0)if(n.push(a.value),e&&n.length===e)break}catch(c){o=!0,i=c}finally{try{r||null==s["return"]||s["return"]()}finally{if(o)throw i}}return n}}function a(t,e){(null==e||e>t.length)&&(e=t.length);for(var n=0,r=new Array(e);n=i?o.length:o.indexOf(t)}));return n?a.filter((function(t){return-1!==t})):a}function w(t,e){var n=this;this.$nextTick((function(){return n.$emit(t.toLowerCase(),e)}))}function x(t){var e=this;return function(n){null!==e.realList&&e["onDrag"+t](n),w.call(e,t,n)}}function S(t){return["transition-group","TransitionGroup"].includes(t)}function O(t){if(!t||1!==t.length)return!1;var e=u(t,1),n=e[0].componentOptions;return!!n&&S(n.tag)}function _(t,e,n){return t[n]||(e[n]?e[n]():void 0)}function k(t,e,n){var r=0,o=0,i=_(e,n,"header");i&&(r=i.length,t=t?[].concat(d(i),d(t)):d(i));var a=_(e,n,"footer");return a&&(o=a.length,t=t?[].concat(d(t),d(a)):d(a)),{children:t,headerOffset:r,footerOffset:o}}function C(t,e){var n=null,r=function(t,e){n=g(n,t,e)},o=Object.keys(t).filter((function(t){return"id"===t||t.startsWith("data-")})).reduce((function(e,n){return e[n]=t[n],e}),{});if(r("attrs",o),!e)return n;var i=e.on,a=e.props,s=e.attrs;return r("on",i),r("props",a),Object.assign(n.attrs,s),n}var M=["Start","Add","Remove","Update","End"],E=["Choose","Unchoose","Sort","Filter","Clone"],A=["Move"].concat(M,E).map((function(t){return"on"+t})),T=null,$={options:Object,list:{type:Array,required:!1,default:null},value:{type:Array,required:!1,default:null},noTransitionOnDrag:{type:Boolean,default:!1},clone:{type:Function,default:function(t){return t}},element:{type:String,default:"div"},tag:{type:String,default:null},move:{type:Function,default:null},componentData:{type:Object,required:!1,default:null}},D={name:"draggable",inheritAttrs:!1,props:$,data:function(){return{transitionMode:!1,noneFunctionalComponentMode:!1}},render:function(t){var e=this.$slots.default;this.transitionMode=O(e);var n=k(e,this.$slots,this.$scopedSlots),r=n.children,o=n.headerOffset,i=n.footerOffset;this.headerOffset=o,this.footerOffset=i;var a=C(this.$attrs,this.componentData);return t(this.getTag(),a,r)},created:function(){null!==this.list&&null!==this.value&&m["b"].error("Value and list props are mutually exclusive! Please set one or another."),"div"!==this.element&&m["b"].warn("Element props is deprecated please use tag props instead. See https://github.com/SortableJS/Vue.Draggable/blob/master/documentation/migrate.md#element-props"),void 0!==this.options&&m["b"].warn("Options props is deprecated, add sortable options directly as vue.draggable item, or use v-bind. See https://github.com/SortableJS/Vue.Draggable/blob/master/documentation/migrate.md#options-props")},mounted:function(){var t=this;if(this.noneFunctionalComponentMode=this.getTag().toLowerCase()!==this.$el.nodeName.toLowerCase()&&!this.getIsFunctional(),this.noneFunctionalComponentMode&&this.transitionMode)throw new Error("Transition-group inside component is not supported. Please alter tag value or remove transition-group. Current tag value: ".concat(this.getTag()));var e={};M.forEach((function(n){e["on"+n]=x.call(t,n)})),E.forEach((function(n){e["on"+n]=w.bind(t,n)}));var n=Object.keys(this.$attrs).reduce((function(e,n){return e[Object(m["a"])(n)]=t.$attrs[n],e}),{}),r=Object.assign({},this.options,n,e,{onMove:function(e,n){return t.onDragMove(e,n)}});!("draggable"in r)&&(r.draggable=">*"),this._sortable=new v.a(this.rootContainer,r),this.computeIndexes()},beforeDestroy:function(){void 0!==this._sortable&&this._sortable.destroy()},computed:{rootContainer:function(){return this.transitionMode?this.$el.children[0]:this.$el},realList:function(){return this.list?this.list:this.value}},watch:{options:{handler:function(t){this.updateOptions(t)},deep:!0},$attrs:{handler:function(t){this.updateOptions(t)},deep:!0},realList:function(){this.computeIndexes()}},methods:{getIsFunctional:function(){var t=this._vnode.fnOptions;return t&&t.functional},getTag:function(){return this.tag||this.element},updateOptions:function(t){for(var e in t){var n=Object(m["a"])(e);-1===A.indexOf(n)&&this._sortable.option(n,t[e])}},getChildrenNodes:function(){if(this.noneFunctionalComponentMode)return this.$children[0].$slots.default;var t=this.$slots.default;return this.transitionMode?t[0].child.$slots.default:t},computeIndexes:function(){var t=this;this.$nextTick((function(){t.visibleIndexes=b(t.getChildrenNodes(),t.rootContainer.children,t.transitionMode,t.footerOffset)}))},getUnderlyingVm:function(t){var e=y(this.getChildrenNodes()||[],t);if(-1===e)return null;var n=this.realList[e];return{index:e,element:n}},getUnderlyingPotencialDraggableComponent:function(t){var e=t.__vue__;return e&&e.$options&&S(e.$options._componentTag)?e.$parent:!("realList"in e)&&1===e.$children.length&&"realList"in e.$children[0]?e.$children[0]:e},emitChanges:function(t){var e=this;this.$nextTick((function(){e.$emit("change",t)}))},alterList:function(t){if(this.list)t(this.list);else{var e=d(this.value);t(e),this.$emit("input",e)}},spliceList:function(){var t=arguments,e=function(e){return e.splice.apply(e,d(t))};this.alterList(e)},updatePosition:function(t,e){var n=function(n){return n.splice(e,0,n.splice(t,1)[0])};this.alterList(n)},getRelatedContextFromMoveEvent:function(t){var e=t.to,n=t.related,r=this.getUnderlyingPotencialDraggableComponent(e);if(!r)return{component:r};var o=r.realList,i={list:o,component:r};if(e!==n&&o&&r.getUnderlyingVm){var a=r.getUnderlyingVm(n);if(a)return Object.assign(a,i)}return i},getVmIndex:function(t){var e=this.visibleIndexes,n=e.length;return t>n-1?n:e[t]},getComponent:function(){return this.$slots.default[0].componentInstance},resetTransitionData:function(t){if(this.noTransitionOnDrag&&this.transitionMode){var e=this.getChildrenNodes();e[t].data=null;var n=this.getComponent();n.children=[],n.kept=void 0}},onDragStart:function(t){this.context=this.getUnderlyingVm(t.item),t.item._underlying_vm_=this.clone(this.context.element),T=t.item},onDragAdd:function(t){var e=t.item._underlying_vm_;if(void 0!==e){Object(m["d"])(t.item);var n=this.getVmIndex(t.newIndex);this.spliceList(n,0,e),this.computeIndexes();var r={element:e,newIndex:n};this.emitChanges({added:r})}},onDragRemove:function(t){if(Object(m["c"])(this.rootContainer,t.item,t.oldIndex),"clone"!==t.pullMode){var e=this.context.index;this.spliceList(e,1);var n={element:this.context.element,oldIndex:e};this.resetTransitionData(e),this.emitChanges({removed:n})}else Object(m["d"])(t.clone)},onDragUpdate:function(t){Object(m["d"])(t.item),Object(m["c"])(t.from,t.item,t.oldIndex);var e=this.context.index,n=this.getVmIndex(t.newIndex);this.updatePosition(e,n);var r={element:this.context.element,oldIndex:e,newIndex:n};this.emitChanges({moved:r})},updateProperty:function(t,e){t.hasOwnProperty(e)&&(t[e]+=this.headerOffset)},computeFutureIndex:function(t,e){if(!t.element)return 0;var n=d(e.to.children).filter((function(t){return"none"!==t.style["display"]})),r=n.indexOf(e.related),o=t.component.getVmIndex(r),i=-1!==n.indexOf(T);return i||!e.willInsertAfter?o:o+1},onDragMove:function(t,e){var n=this.move;if(!n||!this.realList)return!0;var r=this.getRelatedContextFromMoveEvent(t),o=this.context,i=this.computeFutureIndex(r,t);Object.assign(o,{futureIndex:i});var a=Object.assign({},t,{relatedContext:r,draggedContext:o});return n(a,e)},onDragEnd:function(){this.computeIndexes(),T=null}}};"undefined"!==typeof window&&"Vue"in window&&window.Vue.component("draggable",D);var N=D;e["default"]=N}})["default"]}))},bf19:function(t,e,n){"use strict";var r=n("23e7");r({target:"URL",proto:!0,enumerable:!0},{toJSON:function(){return URL.prototype.toString.call(this)}})},c04e:function(t,e,n){var r=n("861d");t.exports=function(t,e){if(!r(t))return t;var n,o;if(e&&"function"==typeof(n=t.toString)&&!r(o=n.call(t)))return o;if("function"==typeof(n=t.valueOf)&&!r(o=n.call(t)))return o;if(!e&&"function"==typeof(n=t.toString)&&!r(o=n.call(t)))return o;throw TypeError("Can't convert object to primitive value")}},c301:function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0;var r=n("78ef"),o=(0,r.regex)("decimal",/^[-]?\d*(\.\d+)?$/);e.default=o},c430:function(t,e){t.exports=!1},c6b6:function(t,e){var n={}.toString;t.exports=function(t){return n.call(t).slice(8,-1)}},c6cd:function(t,e,n){var r=n("da84"),o=n("ce4e"),i="__core-js_shared__",a=r[i]||o(i,{});t.exports=a},c8ba:function(t,e){var n;n=function(){return this}();try{n=n||new Function("return this")()}catch(r){"object"===typeof window&&(n=window)}t.exports=n},c99d:function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0;var r=n("78ef"),o=(0,r.withParams)({type:"ipAddress"},(function(t){if(!(0,r.req)(t))return!0;if("string"!==typeof t)return!1;var e=t.split(".");return 4===e.length&&e.every(i)}));e.default=o;var i=function(t){if(t.length>3||0===t.length)return!1;if("0"===t[0]&&"0"!==t)return!1;if(!t.match(/^\d+$/))return!1;var e=0|+t;return e>=0&&e<=255}},ca84:function(t,e,n){var r=n("5135"),o=n("fc6a"),i=n("4d64").indexOf,a=n("d012");t.exports=function(t,e){var n,s=o(t),c=0,u=[];for(n in s)!r(a,n)&&r(s,n)&&u.push(n);while(e.length>c)r(s,n=e[c++])&&(~i(u,n)||u.push(n));return u}},cb69:function(t,e,n){"use strict";(function(t){function n(t){return n="function"===typeof Symbol&&"symbol"===typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"===typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t},n(t)}Object.defineProperty(e,"__esModule",{value:!0}),e.withParams=void 0;var r="undefined"!==typeof window?window:"undefined"!==typeof t?t:{},o=function(t,e){return"object"===n(t)&&void 0!==e?e:t((function(){}))},i=r.vuelidate?r.vuelidate.withParams:o;e.withParams=i}).call(this,n("c8ba"))},cc12:function(t,e,n){var r=n("da84"),o=n("861d"),i=r.document,a=o(i)&&o(i.createElement);t.exports=function(t){return a?i.createElement(t):{}}},ce4e:function(t,e,n){var r=n("da84"),o=n("9112");t.exports=function(t,e){try{o(r,t,e)}catch(n){r[t]=e}return e}},d012:function(t,e){t.exports={}},d039:function(t,e){t.exports=function(t){try{return!!t()}catch(e){return!0}}},d066:function(t,e,n){var r=n("428f"),o=n("da84"),i=function(t){return"function"==typeof t?t:void 0};t.exports=function(t,e){return arguments.length<2?i(r[t])||i(o[t]):r[t]&&r[t][e]||o[t]&&o[t][e]}},d1e7:function(t,e,n){"use strict";var r={}.propertyIsEnumerable,o=Object.getOwnPropertyDescriptor,i=o&&!r.call({1:2},1);e.f=i?function(t){var e=o(this,t);return!!e&&e.enumerable}:r},d294:function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0;var r=n("78ef"),o=function(){for(var t=arguments.length,e=new Array(t),n=0;n0&&e.reduce((function(e,n){return e||n.apply(t,r)}),!1)}))};e.default=o},d4f4:function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0;var r=n("78ef"),o=(0,r.withParams)({type:"required"},(function(t){return"string"===typeof t?(0,r.req)(t.trim()):(0,r.req)(t)}));e.default=o},da84:function(t,e,n){(function(e){var n=function(t){return t&&t.Math==Math&&t};t.exports=n("object"==typeof globalThis&&globalThis)||n("object"==typeof window&&window)||n("object"==typeof self&&self)||n("object"==typeof e&&e)||function(){return this}()||Function("return this")()}).call(this,n("c8ba"))},e652:function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0;var r=n("78ef"),o=function(t){return(0,r.withParams)({type:"requiredUnless",prop:t},(function(e,n){return!!(0,r.ref)(t,this,n)||(0,r.req)(e)}))};e.default=o},e893:function(t,e,n){var r=n("5135"),o=n("56ef"),i=n("06cf"),a=n("9bf2");t.exports=function(t,e){for(var n=o(e),s=a.f,c=i.f,u=0;u=+t}))};e.default=o},ec11:function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0;var r=n("78ef"),o=function(t,e){return(0,r.withParams)({type:"between",min:t,max:e},(function(n){return!(0,r.req)(n)||(!/\s/.test(n)||n instanceof Date)&&+t<=+n&&+e>=+n}))};e.default=o},ee2b:function(t,e){var n="-_",r=36;while(r--)n+=r.toString(36);r=36;while(r---10)n+=r.toString(36).toUpperCase();t.exports=function(t){var e="";r=t||21;while(r--)e+=n[64*Math.random()|0];return e}},f2f3:function(t,e,n){"use strict";function r(t){return r="function"===typeof Symbol&&"symbol"===typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"===typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t},r(t)}var o={namespaced:!0,state:{locale:null,fallback:null,translations:{}},mutations:{SET_LOCALE:function(t,e){t.locale=e.locale},ADD_LOCALE:function(t,e){var n=i(e.translations);if(t.translations.hasOwnProperty(e.locale)){var r=t.translations[e.locale];t.translations[e.locale]=Object.assign({},r,n)}else t.translations[e.locale]=n;try{t.translations.__ob__&&t.translations.__ob__.dep.notify()}catch(o){}},REPLACE_LOCALE:function(t,e){var n=i(e.translations);t.translations[e.locale]=n;try{t.translations.__ob__&&t.translations.__ob__.dep.notify()}catch(r){}},REMOVE_LOCALE:function(t,e){if(t.translations.hasOwnProperty(e.locale)){t.locale===e.locale&&(t.locale=null);var n=Object.assign({},t.translations);delete n[e.locale],t.translations=n}},SET_FALLBACK_LOCALE:function(t,e){t.fallback=e.locale}},actions:{setLocale:function(t,e){t.commit({type:"SET_LOCALE",locale:e.locale})},addLocale:function(t,e){t.commit({type:"ADD_LOCALE",locale:e.locale,translations:e.translations})},replaceLocale:function(t,e){t.commit({type:"REPLACE_LOCALE",locale:e.locale,translations:e.translations})},removeLocale:function(t,e){t.commit({type:"REMOVE_LOCALE",locale:e.locale,translations:e.translations})},setFallbackLocale:function(t,e){t.commit({type:"SET_FALLBACK_LOCALE",locale:e.locale})}}},i=function t(e){var n={};for(var o in e)if(e.hasOwnProperty(o)){var i=r(e[o]);if(a(e[o])){for(var s=e[o].length,c=0;c1?1:0;case"lv":return e%10===1&&e%100!==11?0:0!==e?1:2;case"lt":return e%10===1&&e%100!==11?0:e%10>=2&&(e%100<10||e%100>=20)?1:2;case"be":case"bs":case"hr":case"ru":case"sr":case"uk":return e%10===1&&e%100!==11?0:e%10>=2&&e%10<=4&&(e%100<10||e%100>=20)?1:2;case"mnk":return 0===e?0:1===e?1:2;case"ro":return 1===e?0:0===e||e%100>0&&e%100<20?1:2;case"pl":return 1===e?0:e%10>=2&&e%10<=4&&(e%100<10||e%100>=20)?1:2;case"cs":case"sk":return 1===e?0:e>=2&&e<=4?1:2;case"csb":return 1===e?0:e%10>=2&&e%10<=4&&(e%100<10||e%100>=20)?1:2;case"sl":return e%100===1?0:e%100===2?1:e%100===3||e%100===4?2:3;case"mt":return 1===e?0:0===e||e%100>1&&e%100<11?1:e%100>10&&e%100<20?2:3;case"gd":return 1===e||11===e?0:2===e||12===e?1:e>2&&e<20?2:3;case"cy":return 1===e?0:2===e?1:8!==e&&11!==e?2:3;case"kw":return 1===e?0:2===e?1:3===e?2:3;case"ga":return 1===e?0:2===e?1:e>2&&e<7?2:e>6&&e<11?3:4;case"ar":return 0===e?0:1===e?1:2===e?2:e%100>=3&&e%100<=10?3:e%100>=11?4:5;default:return 1!==e?1:0}}},c={install:function(t,e,n){"string"!==typeof arguments[2]&&"string"!==typeof arguments[3]||(console.warn("i18n: Registering the plugin vuex-i18n with a string for `moduleName` or `identifiers` is deprecated. Use a configuration object instead.","https://github.com/dkfbasel/vuex-i18n#setup"),n={moduleName:arguments[2],identifiers:arguments[3]}),n=Object.assign({warnings:!0,moduleName:"i18n",identifiers:["{","}"],preserveState:!1,translateFilterName:"translate",translateInFilterName:"translateIn",onTranslationNotFound:function(){}},n);var r=n.moduleName,i=n.identifiers,a=n.translateFilterName,s=n.translateInFilterName,c=n.onTranslationNotFound;if("function"!==typeof c&&(console.error("i18n: i18n config option onTranslationNotFound must be a function"),c=function(){}),e.registerModule(r,o,{preserveState:n.preserveState}),!1===e.state.hasOwnProperty(r))return console.error("i18n: i18n vuex module is not correctly initialized. Please check the module name:",r),t.prototype.$i18n=function(t){return t},t.prototype.$getLanguage=function(){return null},void(t.prototype.$setLanguage=function(){console.error("i18n: i18n vuex module is not correctly initialized")});var l=u(i,n.warnings),f=function(){var t=e.state[r].locale;return p.apply(void 0,[t].concat(Array.prototype.slice.call(arguments)))},p=function(t){var o=arguments,i="",a="",s={},u=null,f=o.length;if(f>=3&&"string"===typeof o[2]?(i=o[1],a=o[2],f>3&&(s=o[3]),f>4&&(u=o[4])):(i=o[1],a=i,f>2&&(s=o[2]),f>3&&(u=o[3])),!t)return n.warnings&&console.warn("i18n: i18n locale is not set when trying to access translations:",i),a;var p=e.state[r].translations,d=e.state[r].fallback,h=t.split("-"),v=!0;if((!1===p.hasOwnProperty(t)||!1===p[t].hasOwnProperty(i))&&(v=!1),!0===v)return l(t,p[t][i],s,u);if(h.length>1&&!0===p.hasOwnProperty(h[0])&&!0===p[h[0]].hasOwnProperty(i))return l(h[0],p[h[0]][i],s,u);var m=c(t,i,a);return m&&Promise.resolve(m).then((function(e){var n={};n[i]=e,b(t,n)})),!1===p.hasOwnProperty(d)?l(t,a,s,u):!1===p[d].hasOwnProperty(i)?l(d,a,s,u):l(t,p[d][i],s,u)},d=function(t,e){for(var n=arguments.length,r=new Array(n>2?n-2:0),o=2;o1&&void 0!==arguments[1]?arguments[1]:"fallback",o=e.state[r].locale,i=e.state[r].fallback,a=e.state[r].translations;if(a.hasOwnProperty(o)&&a[o].hasOwnProperty(t))return!0;if("strict"==n)return!1;var s=o.split("-");return!!(s.length>1&&a.hasOwnProperty(s[0])&&a[s[0]].hasOwnProperty(t))||"locale"!=n&&!(!a.hasOwnProperty(i)||!a[i].hasOwnProperty(t))},v=function(t){e.dispatch({type:"".concat(r,"/setFallbackLocale"),locale:t})},m=function(t){e.dispatch({type:"".concat(r,"/setLocale"),locale:t})},g=function(){return e.state[r].locale},y=function(){return Object.keys(e.state[r].translations)},b=function(t,n){return e.dispatch({type:"".concat(r,"/addLocale"),locale:t,translations:n})},w=function(t,n){return e.dispatch({type:"".concat(r,"/replaceLocale"),locale:t,translations:n})},x=function(t){e.state[r].translations.hasOwnProperty(t)&&e.dispatch({type:"".concat(r,"/removeLocale"),locale:t})},S=function(t){return n.warnings&&console.warn("i18n: $i18n.exists is depreceated. Please use $i18n.localeExists instead. It provides exactly the same functionality."),O(t)},O=function(t){return e.state[r].translations.hasOwnProperty(t)};t.prototype.$i18n={locale:g,locales:y,set:m,add:b,replace:w,remove:x,fallback:v,localeExists:O,keyExists:h,translate:f,translateIn:p,exists:S},t.i18n={locale:g,locales:y,set:m,add:b,replace:w,remove:x,fallback:v,translate:f,translateIn:p,localeExists:O,keyExists:h,exists:S},t.prototype.$t=f,t.prototype.$tlang=p,t.filter(a,f),t.filter(s,d)}},u=function(t){var e=!(arguments.length>1&&void 0!==arguments[1])||arguments[1];null!=t&&2==t.length||console.warn("i18n: You must specify the start and end character identifying variable substitutions");var n=new RegExp(t[0]+"{1}(\\w{1}|\\w.+?)"+t[1]+"{1}","g"),o=function(r,o){return r.replace?r.replace(n,(function(n){var i=n.replace(t[0],"").replace(t[1],"");return void 0!==o[i]?o[i]:(e&&(console.group?console.group("i18n: Not all placeholders found"):console.warn("i18n: Not all placeholders found"),console.warn("Text:",r),console.warn("Placeholder:",n),console.groupEnd&&console.groupEnd()),n)})):r},i=function(t,n){var i=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{},a=arguments.length>3&&void 0!==arguments[3]?arguments[3]:null,c=r(n),u=r(a),f=function(){return l(n)?n.map((function(t){return o(t,i,!1)})):"string"===c?o(n,i,!0):void 0};if(null===a)return f();if("number"!==u)return e&&console.warn("i18n: pluralization is not a number"),f();var p=f(),d=null;d=l(p)&&p.length>0?p:p.split(":::");var h=s.getTranslationIndex(t,a);return"undefined"===typeof d[h]?(e&&console.warn("i18n: pluralization not provided in locale",n,t,h),d[0].trim()):d[h].trim()};return i};function l(t){return!!t&&Array===t.constructor}var f={store:o,plugin:c};e["a"]=f},f772:function(t,e,n){var r=n("5692"),o=n("90e3"),i=r("keys");t.exports=function(t){return i[t]||(i[t]=o(t))}},f906:function(t,e,n){!function(e,n){t.exports=n()}(0,(function(){"use strict";var t,e={LTS:"h:mm:ss A",LT:"h:mm A",L:"MM/DD/YYYY",LL:"MMMM D, YYYY",LLL:"MMMM D, YYYY h:mm A",LLLL:"dddd, MMMM D, YYYY h:mm A"},n=function(t,n){return t.replace(/(\[[^\]]+])|(LTS?|l{1,4}|L{1,4})/g,(function(t,r,o){var i=o&&o.toUpperCase();return r||n[o]||e[o]||n[i].replace(/(\[[^\]]+])|(MMMM|MM|DD|dddd)/g,(function(t,e,n){return e||n.slice(1)}))}))},r=/(\[[^[]*\])|([-:/.()\s]+)|(A|a|YYYY|YY?|MM?M?M?|Do|DD?|hh?|HH?|mm?|ss?|S{1,3}|z|ZZ?)/g,o=/\d\d/,i=/\d\d?/,a=/\d*[^\s\d-:/()]+/,s=function(t){return function(e){this[t]=+e}},c=[/[+-]\d\d:?\d\d/,function(t){var e,n;(this.zone||(this.zone={})).offset=(e=t.match(/([+-]|\d\d)/g),0===(n=60*e[1]+ +e[2])?0:"+"===e[0]?-n:n)}],u=function(e){var n=t[e];return n&&(n.indexOf?n:n.s.concat(n.f))},l=function(e,n){var r,o=t.meridiem;if(o){for(var i=1;i<=24;i+=1)if(e.indexOf(o(i,0,n))>-1){r=i>12;break}}else r=e===(n?"pm":"PM");return r},f={A:[a,function(t){this.afternoon=l(t,!1)}],a:[a,function(t){this.afternoon=l(t,!0)}],S:[/\d/,function(t){this.milliseconds=100*+t}],SS:[o,function(t){this.milliseconds=10*+t}],SSS:[/\d{3}/,function(t){this.milliseconds=+t}],s:[i,s("seconds")],ss:[i,s("seconds")],m:[i,s("minutes")],mm:[i,s("minutes")],H:[i,s("hours")],h:[i,s("hours")],HH:[i,s("hours")],hh:[i,s("hours")],D:[i,s("day")],DD:[o,s("day")],Do:[a,function(e){var n=t.ordinal,r=e.match(/\d+/);if(this.day=r[0],n)for(var o=1;o<=31;o+=1)n(o).replace(/\[|\]/g,"")===e&&(this.day=o)}],M:[i,s("month")],MM:[o,s("month")],MMM:[a,function(t){var e=u("months"),n=(u("monthsShort")||e.map((function(t){return t.substr(0,3)}))).indexOf(t)+1;if(n<1)throw new Error;this.month=n%12||n}],MMMM:[a,function(t){var e=u("months").indexOf(t)+1;if(e<1)throw new Error;this.month=e%12||e}],Y:[/[+-]?\d+/,s("year")],YY:[o,function(t){t=+t,this.year=t+(t>68?1900:2e3)}],YYYY:[/\d{4}/,s("year")],Z:c,ZZ:c},p=function(e,o,i){try{var a=function(e){for(var o=(e=n(e,t&&t.formats)).match(r),i=o.length,a=0;a0?c-1:m.getMonth());var w=l||0,x=p||0,S=d||0,O=h||0;return v?new Date(Date.UTC(y,b,g,w,x,S,O+60*v.offset*1e3)):i?new Date(Date.UTC(y,b,g,w,x,S,O)):new Date(y,b,g,w,x,S,O)}catch(t){return new Date("")}};return function(e,n,r){r.p.customParseFormat=!0;var o=n.prototype,i=o.parse;o.parse=function(e){var n=e.date,o=e.utc,a=e.args;this.$u=o;var s=a[1];if("string"==typeof s){var c=!0===a[2],u=!0===a[3],l=c||u,f=a[2];u&&(f=a[2]),c||(t=f?r.Ls[f]:this.$locale()),this.$d=p(n,s,o),this.init(),f&&!0!==f&&(this.$L=this.locale(f).$L),l&&n!==this.format(s)&&(this.$d=new Date("")),t=void 0}else if(s instanceof Array)for(var d=s.length,h=1;h<=d;h+=1){a[1]=s[h-1];var v=r.apply(this,a);if(v.isValid()){this.$d=v.$d,this.$L=v.$L,this.init();break}h===d&&(this.$d=new Date(""))}else i.call(this,e)}}}))},f95e:function(t,e,n){"use strict";n.d(e,"a",(function(){return i})),n.d(e,"b",(function(){return c})),n.d(e,"c",(function(){return p})),n.d(e,"d",(function(){return l})),n.d(e,"e",(function(){return f}));var r=n("5313"),o=n("0ac0"),i=function(t,e){this.match=t,this.handler="string"==typeof e?a(e):e};function a(t){return function(e,n,r,o){var i=t;if(n[1]){var a=n[0].lastIndexOf(n[1]);i+=n[0].slice(a+n[1].length),r+=a;var s=r-o;s>0&&(i=n[0].slice(a-s,a)+i,r=o)}return e.tr.insertText(i,r,o)}}var s=500;function c(t){var e=t.rules,n=new r["d"]({state:{init:function(){return null},apply:function(t,e){var n=t.getMeta(this);return n||(t.selectionSet||t.docChanged?null:e)}},props:{handleTextInput:function(t,r,o,i){return u(t,r,o,i,e,n)},handleDOMEvents:{compositionend:function(t){setTimeout((function(){var r=t.state.selection,o=r.$cursor;o&&u(t,o.pos,o.pos,"",e,n)}))}}},isInputRules:!0});return n}function u(t,e,n,r,o,i){if(t.composing)return!1;var a=t.state,c=a.doc.resolve(e);if(c.parent.type.spec.code)return!1;for(var u=c.parent.textBetween(Math.max(0,c.parentOffset-s),c.parentOffset,null,"")+r,l=0;l=0;c--)a.step(s.steps[c].invert(s.docs[c]));if(i.text){var u=a.doc.resolve(i.from).marks();a.replaceWith(i.from,i.to,t.schema.text(i.text,u))}else a.delete(i.from,i.to);e(a)}return!0}}return!1}new i(/--$/,"—"),new i(/\.\.\.$/,"…"),new i(/(?:^|[\s\{\[\(\<'"\u2018\u201C])(")$/,"“"),new i(/"$/,"”"),new i(/(?:^|[\s\{\[\(\<'"\u2018\u201C])(')$/,"‘"),new i(/'$/,"’");function f(t,e,n,r){return new i(t,(function(t,i,a,s){var c=n instanceof Function?n(i):n,u=t.tr.delete(a,s),l=u.doc.resolve(a),f=l.blockRange(),p=f&&Object(o["h"])(f,e,c);if(!p)return null;u.wrap(f,p);var d=u.doc.resolve(a-1).nodeBefore;return d&&d.type==e&&Object(o["e"])(u.doc,a-1)&&(!r||r(i,d))&&u.join(a-1),u}))}function p(t,e,n){return new i(t,(function(t,r,o,i){var a=t.doc.resolve(o),s=n instanceof Function?n(r):n;return a.node(-1).canReplaceWith(a.index(-1),a.indexAfter(-1),e)?t.tr.delete(o,i).setBlockType(o,o,e,s):null}))}},fbf4:function(t,e,n){"use strict";function r(t){return null===t||void 0===t}function o(t){return null!==t&&void 0!==t}function i(t,e){return e.tag===t.tag&&e.key===t.key}function a(t){var e=t.tag;t.vm=new e({data:t.args})}function s(t){for(var e=Object.keys(t.args),n=0;nv?l(e,h,y):h>y&&f(t,d,v)}function l(t,e,n){for(;e<=n;++e)a(t[e])}function f(t,e,n){for(;e<=n;++e){var r=t[e];o(r)&&(r.vm.$destroy(),r.vm=null)}}function p(t,e){t!==e&&(e.vm=t.vm,s(e))}function d(t,e){o(t)&&o(e)?t!==e&&u(t,e):o(e)?l(e,0,e.length-1):o(t)&&f(t,0,t.length-1)}function h(t,e,n){return{tag:t,key:e,args:n}}Object.defineProperty(e,"__esModule",{value:!0}),e.patchChildren=d,e.h=h},fc6a:function(t,e,n){var r=n("44ad"),o=n("1d80");t.exports=function(t){return r(o(t))}}}]); \ No newline at end of file diff --git a/kirby/router.php b/kirby/router.php new file mode 100644 index 0000000..ba27ee0 --- /dev/null +++ b/kirby/router.php @@ -0,0 +1,12 @@ + + * @link https://getkirby.com + * @copyright Bastian Allgeier GmbH + * @license https://getkirby.com/license + */ +class Api +{ + use Properties; + + /** + * Authentication callback + * + * @var \Closure + */ + protected $authentication; + + /** + * Debugging flag + * + * @var bool + */ + protected $debug = false; + + /** + * Collection definition + * + * @var array + */ + protected $collections = []; + + /** + * Injected data/dependencies + * + * @var array + */ + protected $data = []; + + /** + * Model definitions + * + * @var array + */ + protected $models = []; + + /** + * The current route + * + * @var \Kirby\Http\Route + */ + protected $route; + + /** + * The Router instance + * + * @var \Kirby\Http\Router + */ + protected $router; + + /** + * Route definition + * + * @var array + */ + protected $routes = []; + + /** + * Request data + * [query, body, files] + * + * @var array + */ + protected $requestData = []; + + /** + * The applied request method + * (GET, POST, PATCH, etc.) + * + * @var string + */ + protected $requestMethod; + + /** + * Magic accessor for any given data + * + * @param string $method + * @param array $args + * @return mixed + * @throws \Kirby\Exception\NotFoundException + */ + public function __call(string $method, array $args = []) + { + return $this->data($method, ...$args); + } + + /** + * Creates a new API instance + * + * @param array $props + */ + public function __construct(array $props) + { + $this->setProperties($props); + } + + /** + * Runs the authentication method + * if set + * + * @return mixed + */ + public function authenticate() + { + if ($auth = $this->authentication()) { + return $auth->call($this); + } + + return true; + } + + /** + * Returns the authentication callback + * + * @return \Closure|null + */ + public function authentication() + { + return $this->authentication; + } + + /** + * Execute an API call for the given path, + * request method and optional request data + * + * @param string|null $path + * @param string $method + * @param array $requestData + * @return mixed + * @throws \Kirby\Exception\NotFoundException + * @throws \Exception + */ + public function call(string $path = null, string $method = 'GET', array $requestData = []) + { + $path = rtrim($path, '/'); + + $this->setRequestMethod($method); + $this->setRequestData($requestData); + + $this->router = new Router($this->routes()); + $this->route = $this->router->find($path, $method); + $auth = $this->route->attributes()['auth'] ?? true; + + if ($auth !== false) { + $user = $this->authenticate(); + + // set PHP locales based on *user* language + // so that e.g. strftime() gets formatted correctly + if (is_a($user, 'Kirby\Cms\User') === true) { + $language = $user->language(); + + // get the locale from the translation + $translation = $user->kirby()->translation($language); + $locale = ($translation !== null)? $translation->locale() : $language; + + // provide some variants as fallbacks to be + // compatible with as many systems as possible + $locales = [ + $locale . '.UTF-8', + $locale . '.UTF8', + $locale . '.ISO8859-1', + $locale, + $language, + setlocale(LC_ALL, 0) // fall back to the previously defined locale + ]; + + // set the locales that are relevant for string formatting + // *don't* set LC_CTYPE to avoid breaking other parts of the system + setlocale(LC_MONETARY, $locales); + setlocale(LC_NUMERIC, $locales); + setlocale(LC_TIME, $locales); + } + } + + // don't throw pagination errors if pagination + // page is out of bounds + $validate = Pagination::$validate; + Pagination::$validate = false; + + $output = $this->route->action()->call($this, ...$this->route->arguments()); + + // restore old pagination validation mode + Pagination::$validate = $validate; + + if ( + is_object($output) === true && + is_a($output, 'Kirby\\Http\\Response') !== true + ) { + return $this->resolve($output)->toResponse(); + } + + return $output; + } + + /** + * Setter and getter for an API collection + * + * @param string $name + * @param array|null $collection + * @return \Kirby\Api\Collection + * @throws \Kirby\Exception\NotFoundException If no collection for `$name` exists + * @throws \Exception + */ + public function collection(string $name, $collection = null) + { + if (isset($this->collections[$name]) === false) { + throw new NotFoundException(sprintf('The collection "%s" does not exist', $name)); + } + + return new Collection($this, $collection, $this->collections[$name]); + } + + /** + * Returns the collections definition + * + * @return array + */ + public function collections(): array + { + return $this->collections; + } + + /** + * Returns the injected data array + * or certain parts of it by key + * + * @param string|null $key + * @param mixed ...$args + * @return mixed + * + * @throws \Kirby\Exception\NotFoundException If no data for `$key` exists + */ + public function data($key = null, ...$args) + { + if ($key === null) { + return $this->data; + } + + if ($this->hasData($key) === false) { + throw new NotFoundException(sprintf('Api data for "%s" does not exist', $key)); + } + + // lazy-load data wrapped in Closures + if (is_a($this->data[$key], 'Closure') === true) { + return $this->data[$key]->call($this, ...$args); + } + + return $this->data[$key]; + } + + /** + * Returns the debugging flag + * + * @return bool + */ + public function debug(): bool + { + return $this->debug; + } + + /** + * Checks if injected data exists for the given key + * + * @param string $key + * @return bool + */ + public function hasData(string $key): bool + { + return isset($this->data[$key]) === true; + } + + /** + * Matches an object with an array item + * based on the `type` field + * + * @param array models or collections + * @param mixed $object + * + * @return string key of match + */ + protected function match(array $array, $object = null) + { + foreach ($array as $definition => $model) { + if (is_a($object, $model['type']) === true) { + return $definition; + } + } + } + + /** + * Returns an API model instance by name + * + * @param string|null $name + * @param mixed $object + * @return \Kirby\Api\Model + * + * @throws \Kirby\Exception\NotFoundException If no model for `$name` exists + */ + public function model(string $name = null, $object = null) + { + // Try to auto-match object with API models + if ($name === null) { + if ($model = $this->match($this->models, $object)) { + $name = $model; + } + } + + if (isset($this->models[$name]) === false) { + throw new NotFoundException(sprintf('The model "%s" does not exist', $name)); + } + + return new Model($this, $object, $this->models[$name]); + } + + /** + * Returns all model definitions + * + * @return array + */ + public function models(): array + { + return $this->models; + } + + /** + * Getter for request data + * Can either get all the data + * or certain parts of it. + * + * @param string|null $type + * @param string|null $key + * @param mixed $default + * @return mixed + */ + public function requestData(string $type = null, string $key = null, $default = null) + { + if ($type === null) { + return $this->requestData; + } + + if ($key === null) { + return $this->requestData[$type] ?? []; + } + + $data = array_change_key_case($this->requestData($type)); + $key = strtolower($key); + + return $data[$key] ?? $default; + } + + /** + * Returns the request body if available + * + * @param string|null $key + * @param mixed $default + * @return mixed + */ + public function requestBody(string $key = null, $default = null) + { + return $this->requestData('body', $key, $default); + } + + /** + * Returns the files from the request if available + * + * @param string|null $key + * @param mixed $default + * @return mixed + */ + public function requestFiles(string $key = null, $default = null) + { + return $this->requestData('files', $key, $default); + } + + /** + * Returns all headers from the request if available + * + * @param string|null $key + * @param mixed $default + * @return mixed + */ + public function requestHeaders(string $key = null, $default = null) + { + return $this->requestData('headers', $key, $default); + } + + /** + * Returns the request method + * + * @return string + */ + public function requestMethod(): string + { + return $this->requestMethod; + } + + /** + * Returns the request query if available + * + * @param string|null $key + * @param mixed $default + * @return mixed + */ + public function requestQuery(string $key = null, $default = null) + { + return $this->requestData('query', $key, $default); + } + + /** + * Turns a Kirby object into an + * API model or collection representation + * + * @param mixed $object + * @return \Kirby\Api\Model|\Kirby\Api\Collection + * + * @throws \Kirby\Exception\NotFoundException If `$object` cannot be resolved + */ + public function resolve($object) + { + if (is_a($object, 'Kirby\Api\Model') === true || is_a($object, 'Kirby\Api\Collection') === true) { + return $object; + } + + if ($model = $this->match($this->models, $object)) { + return $this->model($model, $object); + } + + if ($collection = $this->match($this->collections, $object)) { + return $this->collection($collection, $object); + } + + throw new NotFoundException(sprintf('The object "%s" cannot be resolved', get_class($object))); + } + + /** + * Returns all defined routes + * + * @return array + */ + public function routes(): array + { + return $this->routes; + } + + /** + * Setter for the authentication callback + * + * @param \Closure|null $authentication + * @return $this + */ + protected function setAuthentication(Closure $authentication = null) + { + $this->authentication = $authentication; + return $this; + } + + /** + * Setter for the collections definition + * + * @param array|null $collections + * @return $this + */ + protected function setCollections(array $collections = null) + { + if ($collections !== null) { + $this->collections = array_change_key_case($collections); + } + return $this; + } + + /** + * Setter for the injected data + * + * @param array|null $data + * @return $this + */ + protected function setData(array $data = null) + { + $this->data = $data ?? []; + return $this; + } + + /** + * Setter for the debug flag + * + * @param bool $debug + * @return $this + */ + protected function setDebug(bool $debug = false) + { + $this->debug = $debug; + return $this; + } + + /** + * Setter for the model definitions + * + * @param array|null $models + * @return $this + */ + protected function setModels(array $models = null) + { + if ($models !== null) { + $this->models = array_change_key_case($models); + } + + return $this; + } + + /** + * Setter for the request data + * + * @param array|null $requestData + * @return $this + */ + protected function setRequestData(array $requestData = null) + { + $defaults = [ + 'query' => [], + 'body' => [], + 'files' => [] + ]; + + $this->requestData = array_merge($defaults, (array)$requestData); + return $this; + } + + /** + * Setter for the request method + * + * @param string|null $requestMethod + * @return $this + */ + protected function setRequestMethod(string $requestMethod = null) + { + $this->requestMethod = $requestMethod ?? 'GET'; + return $this; + } + + /** + * Setter for the route definitions + * + * @param array|null $routes + * @return $this + */ + protected function setRoutes(array $routes = null) + { + $this->routes = $routes ?? []; + return $this; + } + + /** + * Renders the API call + * + * @param string $path + * @param string $method + * @param array $requestData + * @return mixed + */ + public function render(string $path, $method = 'GET', array $requestData = []) + { + try { + $result = $this->call($path, $method, $requestData); + } catch (Throwable $e) { + $result = $this->responseForException($e); + } + + if ($result === null) { + $result = $this->responseFor404(); + } elseif ($result === false) { + $result = $this->responseFor400(); + } elseif ($result === true) { + $result = $this->responseFor200(); + } + + if (is_array($result) === false) { + return $result; + } + + // pretty print json data + $pretty = (bool)($requestData['query']['pretty'] ?? false) === true; + + if (($result['status'] ?? 'ok') === 'error') { + $code = $result['code'] ?? 400; + + // sanitize the error code + if ($code < 400 || $code > 599) { + $code = 500; + } + + return Response::json($result, $code, $pretty); + } + + return Response::json($result, 200, $pretty); + } + + /** + * Returns a 200 - ok + * response array. + * + * @return array + */ + public function responseFor200(): array + { + return [ + 'status' => 'ok', + 'message' => 'ok', + 'code' => 200 + ]; + } + + /** + * Returns a 400 - bad request + * response array. + * + * @return array + */ + public function responseFor400(): array + { + return [ + 'status' => 'error', + 'message' => 'bad request', + 'code' => 400, + ]; + } + + /** + * Returns a 404 - not found + * response array. + * + * @return array + */ + public function responseFor404(): array + { + return [ + 'status' => 'error', + 'message' => 'not found', + 'code' => 404, + ]; + } + + /** + * Creates the response array for + * an exception. Kirby exceptions will + * have more information + * + * @param \Throwable $e + * @return array + */ + public function responseForException(Throwable $e): array + { + // prepare the result array for all exception types + $result = [ + 'status' => 'error', + 'message' => $e->getMessage(), + 'code' => empty($e->getCode()) === true ? 500 : $e->getCode(), + 'exception' => get_class($e), + 'key' => null, + 'file' => F::relativepath($e->getFile(), $_SERVER['DOCUMENT_ROOT'] ?? null), + 'line' => $e->getLine(), + 'details' => [], + 'route' => $this->route ? $this->route->pattern() : null + ]; + + // extend the information for Kirby Exceptions + if (is_a($e, 'Kirby\Exception\Exception') === true) { + $result['key'] = $e->getKey(); + $result['details'] = $e->getDetails(); + $result['code'] = $e->getHttpCode(); + } + + // remove critical info from the result set if + // debug mode is switched off + if ($this->debug !== true) { + unset( + $result['file'], + $result['exception'], + $result['line'], + $result['route'] + ); + } + + return $result; + } + + /** + * Upload helper method + * + * move_uploaded_file() not working with unit test + * Added debug parameter for testing purposes as we did in the Email class + * + * @param \Closure $callback + * @param bool $single + * @param bool $debug + * @return array + * + * @throws \Exception If request has no files or there was an error with the upload + */ + public function upload(Closure $callback, $single = false, $debug = false): array + { + $trials = 0; + $uploads = []; + $errors = []; + $files = $this->requestFiles(); + + // get error messages from translation + $errorMessages = [ + UPLOAD_ERR_INI_SIZE => t('upload.error.iniSize'), + UPLOAD_ERR_FORM_SIZE => t('upload.error.formSize'), + UPLOAD_ERR_PARTIAL => t('upload.error.partial'), + UPLOAD_ERR_NO_FILE => t('upload.error.noFile'), + UPLOAD_ERR_NO_TMP_DIR => t('upload.error.tmpDir'), + UPLOAD_ERR_CANT_WRITE => t('upload.error.cantWrite'), + UPLOAD_ERR_EXTENSION => t('upload.error.extension') + ]; + + if (empty($files) === true) { + $postMaxSize = Str::toBytes(ini_get('post_max_size')); + $uploadMaxFileSize = Str::toBytes(ini_get('upload_max_filesize')); + + if ($postMaxSize < $uploadMaxFileSize) { + throw new Exception(t('upload.error.iniPostSize')); + } else { + throw new Exception(t('upload.error.noFiles')); + } + } + + foreach ($files as $upload) { + if (isset($upload['tmp_name']) === false && is_array($upload)) { + continue; + } + + $trials++; + + try { + if ($upload['error'] !== 0) { + $errorMessage = $errorMessages[$upload['error']] ?? t('upload.error.default'); + throw new Exception($errorMessage); + } + + // get the extension of the uploaded file + $extension = F::extension($upload['name']); + + // try to detect the correct mime and add the extension + // accordingly. This will avoid .tmp filenames + if (empty($extension) === true || in_array($extension, ['tmp', 'temp'])) { + $mime = F::mime($upload['tmp_name']); + $extension = F::mimeToExtension($mime); + $filename = F::name($upload['name']) . '.' . $extension; + } else { + $filename = basename($upload['name']); + } + + $source = dirname($upload['tmp_name']) . '/' . uniqid() . '.' . $filename; + + // move the file to a location including the extension, + // for better mime detection + if ($debug === false && move_uploaded_file($upload['tmp_name'], $source) === false) { + throw new Exception(t('upload.error.cantMove')); + } + + $data = $callback($source, $filename); + + if (is_object($data) === true) { + $data = $this->resolve($data)->toArray(); + } + + $uploads[$upload['name']] = $data; + } catch (Exception $e) { + $errors[$upload['name']] = $e->getMessage(); + } + + if ($single === true) { + break; + } + } + + // return a single upload response + if ($trials === 1) { + if (empty($errors) === false) { + return [ + 'status' => 'error', + 'message' => current($errors) + ]; + } + + return [ + 'status' => 'ok', + 'data' => current($uploads) + ]; + } + + if (empty($errors) === false) { + return [ + 'status' => 'error', + 'errors' => $errors + ]; + } + + return [ + 'status' => 'ok', + 'data' => $uploads + ]; + } +} diff --git a/kirby/src/Api/Collection.php b/kirby/src/Api/Collection.php new file mode 100644 index 0000000..877ca01 --- /dev/null +++ b/kirby/src/Api/Collection.php @@ -0,0 +1,178 @@ + + * @link https://getkirby.com + * @copyright Bastian Allgeier GmbH + * @license https://getkirby.com/license + */ +class Collection +{ + /** + * @var \Kirby\Api\Api + */ + protected $api; + + /** + * @var mixed|null + */ + protected $data; + + /** + * @var mixed|null + */ + protected $model; + + /** + * @var mixed|null + */ + protected $select; + + /** + * @var mixed|null + */ + protected $view; + + /** + * Collection constructor + * + * @param \Kirby\Api\Api $api + * @param mixed|null $data + * @param array $schema + * @throws \Exception + */ + public function __construct(Api $api, $data, array $schema) + { + $this->api = $api; + $this->data = $data; + $this->model = $schema['model'] ?? null; + $this->view = $schema['view'] ?? null; + + if ($data === null) { + if (is_a($schema['default'] ?? null, 'Closure') === false) { + throw new Exception('Missing collection data'); + } + + $this->data = $schema['default']->call($this->api); + } + + if ( + isset($schema['type']) === true && + is_a($this->data, $schema['type']) === false + ) { + throw new Exception('Invalid collection type'); + } + } + + /** + * @param string|array|null $keys + * @return $this + * @throws \Exception + */ + public function select($keys = null) + { + if ($keys === false) { + return $this; + } + + if (is_string($keys)) { + $keys = Str::split($keys); + } + + if ($keys !== null && is_array($keys) === false) { + throw new Exception('Invalid select keys'); + } + + $this->select = $keys; + return $this; + } + + /** + * @return array + * @throws \Kirby\Exception\NotFoundException + * @throws \Exception + */ + public function toArray(): array + { + $result = []; + + foreach ($this->data as $item) { + $model = $this->api->model($this->model, $item); + + if ($this->view !== null) { + $model = $model->view($this->view); + } + + if ($this->select !== null) { + $model = $model->select($this->select); + } + + $result[] = $model->toArray(); + } + + return $result; + } + + /** + * @return array + * @throws \Kirby\Exception\NotFoundException + * @throws \Exception + */ + public function toResponse(): array + { + if ($query = $this->api->requestQuery('query')) { + $this->data = $this->data->query($query); + } + + if (!$this->data->pagination()) { + $this->data = $this->data->paginate([ + 'page' => $this->api->requestQuery('page', 1), + 'limit' => $this->api->requestQuery('limit', 100) + ]); + } + + $pagination = $this->data->pagination(); + + if ($select = $this->api->requestQuery('select')) { + $this->select($select); + } + + if ($view = $this->api->requestQuery('view')) { + $this->view($view); + } + + return [ + 'code' => 200, + 'data' => $this->toArray(), + 'pagination' => [ + 'page' => $pagination->page(), + 'total' => $pagination->total(), + 'offset' => $pagination->offset(), + 'limit' => $pagination->limit(), + ], + 'status' => 'ok', + 'type' => 'collection' + ]; + } + + /** + * @param string $view + * @return $this + */ + public function view(string $view) + { + $this->view = $view; + return $this; + } +} diff --git a/kirby/src/Api/Model.php b/kirby/src/Api/Model.php new file mode 100644 index 0000000..b8ef9e2 --- /dev/null +++ b/kirby/src/Api/Model.php @@ -0,0 +1,248 @@ + + * @link https://getkirby.com + * @copyright Bastian Allgeier GmbH + * @license https://getkirby.com/license + */ +class Model +{ + /** + * @var \Kirby\Api\Api + */ + protected $api; + + /** + * @var mixed|null + */ + protected $data; + + /** + * @var array|mixed + */ + protected $fields; + + /** + * @var mixed|null + */ + protected $select; + + /** + * @var array|mixed + */ + protected $views; + + /** + * Model constructor + * + * @param \Kirby\Api\Api $api + * @param mixed $data + * @param array $schema + * @throws \Exception + */ + public function __construct(Api $api, $data, array $schema) + { + $this->api = $api; + $this->data = $data; + $this->fields = $schema['fields'] ?? []; + $this->select = $schema['select'] ?? null; + $this->views = $schema['views'] ?? []; + + if ($this->select === null && array_key_exists('default', $this->views)) { + $this->view('default'); + } + + if ($data === null) { + if (is_a($schema['default'] ?? null, 'Closure') === false) { + throw new Exception('Missing model data'); + } + + $this->data = $schema['default']->call($this->api); + } + + if ( + isset($schema['type']) === true && + is_a($this->data, $schema['type']) === false + ) { + throw new Exception(sprintf('Invalid model type "%s" expected: "%s"', get_class($this->data), $schema['type'])); + } + } + + /** + * @param null $keys + * @return $this + * @throws \Exception + */ + public function select($keys = null) + { + if ($keys === false) { + return $this; + } + + if (is_string($keys)) { + $keys = Str::split($keys); + } + + if ($keys !== null && is_array($keys) === false) { + throw new Exception('Invalid select keys'); + } + + $this->select = $keys; + return $this; + } + + /** + * @return array + * @throws \Exception + */ + public function selection(): array + { + $select = $this->select; + + if ($select === null) { + $select = array_keys($this->fields); + } + + $selection = []; + + foreach ($select as $key => $value) { + if (is_int($key) === true) { + $selection[$value] = [ + 'view' => null, + 'select' => null + ]; + continue; + } + + if (is_string($value) === true) { + if ($value === 'any') { + throw new Exception('Invalid sub view: "any"'); + } + + $selection[$key] = [ + 'view' => $value, + 'select' => null + ]; + + continue; + } + + if (is_array($value) === true) { + $selection[$key] = [ + 'view' => null, + 'select' => $value + ]; + } + } + + return $selection; + } + + /** + * @return array + * @throws \Kirby\Exception\NotFoundException + * @throws \Exception + */ + public function toArray(): array + { + $select = $this->selection(); + $result = []; + + foreach ($this->fields as $key => $resolver) { + if (array_key_exists($key, $select) === false || is_a($resolver, 'Closure') === false) { + continue; + } + + $value = $resolver->call($this->api, $this->data); + + if (is_object($value)) { + $value = $this->api->resolve($value); + } + + if ( + is_a($value, 'Kirby\Api\Collection') === true || + is_a($value, 'Kirby\Api\Model') === true + ) { + $selection = $select[$key]; + + if ($subview = $selection['view']) { + $value->view($subview); + } + + if ($subselect = $selection['select']) { + $value->select($subselect); + } + + $value = $value->toArray(); + } + + $result[$key] = $value; + } + + ksort($result); + + return $result; + } + + /** + * @return array + * @throws \Kirby\Exception\NotFoundException + * @throws \Exception + */ + public function toResponse(): array + { + $model = $this; + + if ($select = $this->api->requestQuery('select')) { + $model = $model->select($select); + } + + if ($view = $this->api->requestQuery('view')) { + $model = $model->view($view); + } + + return [ + 'code' => 200, + 'data' => $model->toArray(), + 'status' => 'ok', + 'type' => 'model' + ]; + } + + /** + * @param string $name + * @return $this + * @throws \Exception + */ + public function view(string $name) + { + if ($name === 'any') { + return $this->select(null); + } + + if (isset($this->views[$name]) === false) { + $name = 'default'; + + // try to fall back to the default view at least + if (isset($this->views[$name]) === false) { + throw new Exception(sprintf('The view "%s" does not exist', $name)); + } + } + + return $this->select($this->views[$name]); + } +} diff --git a/kirby/src/Cache/ApcuCache.php b/kirby/src/Cache/ApcuCache.php new file mode 100644 index 0000000..ebae697 --- /dev/null +++ b/kirby/src/Cache/ApcuCache.php @@ -0,0 +1,86 @@ + + * @link https://getkirby.com + * @copyright Bastian Allgeier GmbH + * @license https://opensource.org/licenses/MIT + */ +class ApcuCache extends Cache +{ + /** + * Determines if an item exists in the cache + * + * @param string $key + * @return bool + */ + public function exists(string $key): bool + { + return apcu_exists($this->key($key)); + } + + /** + * Flushes the entire cache and returns + * whether the operation was successful + * + * @return bool + */ + public function flush(): bool + { + if (empty($this->options['prefix']) === false) { + return apcu_delete(new APCUIterator('!^' . preg_quote($this->options['prefix']) . '!')); + } else { + return apcu_clear_cache(); + } + } + + /** + * Removes an item from the cache and returns + * whether the operation was successful + * + * @param string $key + * @return bool + */ + public function remove(string $key): bool + { + return apcu_delete($this->key($key)); + } + + /** + * Internal method to retrieve the raw cache value; + * needs to return a Value object or null if not found + * + * @param string $key + * @return \Kirby\Cache\Value|null + */ + public function retrieve(string $key) + { + return Value::fromJson(apcu_fetch($this->key($key))); + } + + /** + * Writes an item to the cache for a given number of minutes and + * returns whether the operation was successful + * + * + * // put an item in the cache for 15 minutes + * $cache->set('value', 'my value', 15); + * + * + * @param string $key + * @param mixed $value + * @param int $minutes + * @return bool + */ + public function set(string $key, $value, int $minutes = 0): bool + { + return apcu_store($this->key($key), (new Value($value, $minutes))->toJson(), $this->expiration($minutes)); + } +} diff --git a/kirby/src/Cache/Cache.php b/kirby/src/Cache/Cache.php new file mode 100644 index 0000000..729d61b --- /dev/null +++ b/kirby/src/Cache/Cache.php @@ -0,0 +1,242 @@ + + * @link https://getkirby.com + * @copyright Bastian Allgeier GmbH + * @license https://opensource.org/licenses/MIT + */ +abstract class Cache +{ + /** + * Stores all options for the driver + * @var array + */ + protected $options = []; + + /** + * Sets all parameters which are needed to connect to the cache storage + * + * @param array $options + */ + public function __construct(array $options = []) + { + $this->options = $options; + } + + /** + * Writes an item to the cache for a given number of minutes and + * returns whether the operation was successful; + * this needs to be defined by the driver + * + * + * // put an item in the cache for 15 minutes + * $cache->set('value', 'my value', 15); + * + * + * @param string $key + * @param mixed $value + * @param int $minutes + * @return bool + */ + abstract public function set(string $key, $value, int $minutes = 0): bool; + + /** + * Adds the prefix to the key if given + * + * @param string $key + * @return string + */ + protected function key(string $key): string + { + if (empty($this->options['prefix']) === false) { + $key = $this->options['prefix'] . '/' . $key; + } + + return $key; + } + + /** + * Internal method to retrieve the raw cache value; + * needs to return a Value object or null if not found; + * this needs to be defined by the driver + * + * @param string $key + * @return \Kirby\Cache\Value|null + */ + abstract public function retrieve(string $key); + + /** + * Gets an item from the cache + * + * + * // get an item from the cache driver + * $value = $cache->get('value'); + * + * // return a default value if the requested item isn't cached + * $value = $cache->get('value', 'default value'); + * + * + * @param string $key + * @param mixed $default + * @return mixed + */ + public function get(string $key, $default = null) + { + // get the Value + $value = $this->retrieve($key); + + // check for a valid cache value + if (!is_a($value, 'Kirby\Cache\Value')) { + return $default; + } + + // remove the item if it is expired + if ($value->expires() > 0 && time() >= $value->expires()) { + $this->remove($key); + return $default; + } + + // return the pure value + return $value->value(); + } + + /** + * Calculates the expiration timestamp + * + * @param int $minutes + * @return int + */ + protected function expiration(int $minutes = 0): int + { + // 0 = keep forever + if ($minutes === 0) { + return 0; + } + + // calculate the time + return time() + ($minutes * 60); + } + + /** + * Checks when an item in the cache expires; + * returns the expiry timestamp on success, null if the + * item never expires and false if the item does not exist + * + * @param string $key + * @return int|null|false + */ + public function expires(string $key) + { + // get the Value object + $value = $this->retrieve($key); + + // check for a valid Value object + if (!is_a($value, 'Kirby\Cache\Value')) { + return false; + } + + // return the expires timestamp + return $value->expires(); + } + + /** + * Checks if an item in the cache is expired + * + * @param string $key + * @return bool + */ + public function expired(string $key): bool + { + $expires = $this->expires($key); + + if ($expires === null) { + return false; + } elseif (!is_int($expires)) { + return true; + } else { + return time() >= $expires; + } + } + + /** + * Checks when the cache has been created; + * returns the creation timestamp on success + * and false if the item does not exist + * + * @param string $key + * @return int|false + */ + public function created(string $key) + { + // get the Value object + $value = $this->retrieve($key); + + // check for a valid Value object + if (!is_a($value, 'Kirby\Cache\Value')) { + return false; + } + + // return the expires timestamp + return $value->created(); + } + + /** + * Alternate version for Cache::created($key) + * + * @param string $key + * @return int|false + */ + public function modified(string $key) + { + return static::created($key); + } + + /** + * Determines if an item exists in the cache + * + * @param string $key + * @return bool + */ + public function exists(string $key): bool + { + return $this->expired($key) === false; + } + + /** + * Removes an item from the cache and returns + * whether the operation was successful; + * this needs to be defined by the driver + * + * @param string $key + * @return bool + */ + abstract public function remove(string $key): bool; + + /** + * Flushes the entire cache and returns + * whether the operation was successful; + * this needs to be defined by the driver + * + * @return bool + */ + abstract public function flush(): bool; + + /** + * Returns all passed cache options + * + * @return array + */ + public function options(): array + { + return $this->options; + } +} diff --git a/kirby/src/Cache/FileCache.php b/kirby/src/Cache/FileCache.php new file mode 100644 index 0000000..a0adf74 --- /dev/null +++ b/kirby/src/Cache/FileCache.php @@ -0,0 +1,234 @@ + + * @link https://getkirby.com + * @copyright Bastian Allgeier GmbH + * @license https://opensource.org/licenses/MIT + */ +class FileCache extends Cache +{ + /** + * Full root including prefix + * + * @var string + */ + protected $root; + + /** + * Sets all parameters which are needed for the file cache + * + * @param array $options 'root' (required) + * 'prefix' (default: none) + * 'extension' (file extension for cache files, default: none) + */ + public function __construct(array $options) + { + $defaults = [ + 'root' => null, + 'prefix' => null, + 'extension' => null + ]; + + parent::__construct(array_merge($defaults, $options)); + + // build the full root including prefix + $this->root = $this->options['root']; + if (empty($this->options['prefix']) === false) { + $this->root .= '/' . $this->options['prefix']; + } + + // try to create the directory + Dir::make($this->root, true); + } + + /** + * Returns the full root including prefix + * + * @return string + */ + public function root(): string + { + return $this->root; + } + + /** + * Returns the full path to a file for a given key + * + * @param string $key + * @return string + */ + protected function file(string $key): string + { + // strip out invalid characters in each path segment + // split by slash or backslash + $keyParts = []; + foreach (preg_split('#([\/\\\\])#', $key, 0, PREG_SPLIT_DELIM_CAPTURE) as $part) { + switch ($part) { + // forward slashes don't need special treatment + case '/': + break; + + // backslashes get their own marker in the path + // to differentiate the cache key from one with forward slashes + case '\\': + $keyParts[] = '_backslash'; + break; + + // empty part means two slashes in a row; + // special marker like for backslashes + case '': + $keyParts[] = '_empty'; + break; + + // an actual path segment + default: + // check if the segment only contains safe characters; + // underscores are *not* safe to guarantee uniqueness + // as they are used in the special cases + if (preg_match('/^[a-zA-Z0-9-]+$/', $part) === 1) { + $keyParts[] = $part; + } else { + $keyParts[] = Str::slug($part) . '_' . sha1($part); + } + } + } + + $file = $this->root . '/' . implode('/', $keyParts); + + if (isset($this->options['extension'])) { + return $file . '.' . $this->options['extension']; + } else { + return $file; + } + } + + /** + * Writes an item to the cache for a given number of minutes and + * returns whether the operation was successful + * + * + * // put an item in the cache for 15 minutes + * $cache->set('value', 'my value', 15); + * + * + * @param string $key + * @param mixed $value + * @param int $minutes + * @return bool + */ + public function set(string $key, $value, int $minutes = 0): bool + { + $file = $this->file($key); + + return F::write($file, (new Value($value, $minutes))->toJson()); + } + + /** + * Internal method to retrieve the raw cache value; + * needs to return a Value object or null if not found + * + * @param string $key + * @return \Kirby\Cache\Value|null + */ + public function retrieve(string $key) + { + $file = $this->file($key); + $value = F::read($file); + + return $value ? Value::fromJson($value) : null; + } + + /** + * Checks when the cache has been created; + * returns the creation timestamp on success + * and false if the item does not exist + * + * @param string $key + * @return mixed + */ + public function created(string $key) + { + // use the modification timestamp + // as indicator when the cache has been created/overwritten + clearstatcache(); + + // get the file for this cache key + $file = $this->file($key); + return file_exists($file) ? filemtime($this->file($key)) : false; + } + + /** + * Removes an item from the cache and returns + * whether the operation was successful + * + * @param string $key + * @return bool + */ + public function remove(string $key): bool + { + $file = $this->file($key); + + if (is_file($file) === true && F::remove($file) === true) { + $this->removeEmptyDirectories(dirname($file)); + return true; + } + + return false; + } + + /** + * Removes empty directories safely by checking each directory + * up to the root directory + * + * @param string $dir + * @return void + */ + protected function removeEmptyDirectories(string $dir): void + { + try { + // ensure the path doesn't end with a slash for the next comparison + $dir = rtrim($dir, '/\/'); + + // checks all directory segments until reaching the root directory + while (Str::startsWith($dir, $this->root()) === true && $dir !== $this->root()) { + $files = array_diff(scandir($dir) ?? [], ['.', '..']); + + if (empty($files) === true && Dir::remove($dir) === true) { + // continue with the next level up + $dir = dirname($dir); + } else { + // no need to continue with the next level up as `$dir` was not deleted + break; + } + } + } catch (Exception $e) { // @codeCoverageIgnore + // silently stops the process + } + } + + /** + * Flushes the entire cache and returns + * whether the operation was successful + * + * @return bool + */ + public function flush(): bool + { + if (Dir::remove($this->root) === true && Dir::make($this->root) === true) { + return true; + } + + return false; // @codeCoverageIgnore + } +} diff --git a/kirby/src/Cache/MemCached.php b/kirby/src/Cache/MemCached.php new file mode 100644 index 0000000..2f9c2bc --- /dev/null +++ b/kirby/src/Cache/MemCached.php @@ -0,0 +1,99 @@ + + * @link https://getkirby.com + * @copyright Bastian Allgeier GmbH + * @license https://opensource.org/licenses/MIT + */ +class MemCached extends Cache +{ + /** + * store for the memache connection + * @var \Memcached + */ + protected $connection; + + /** + * Sets all parameters which are needed to connect to Memcached + * + * @param array $options 'host' (default: localhost) + * 'port' (default: 11211) + * 'prefix' (default: null) + */ + public function __construct(array $options = []) + { + $defaults = [ + 'host' => 'localhost', + 'port' => 11211, + 'prefix' => null, + ]; + + parent::__construct(array_merge($defaults, $options)); + + $this->connection = new MemcachedExt(); + $this->connection->addServer($this->options['host'], $this->options['port']); + } + + /** + * Writes an item to the cache for a given number of minutes and + * returns whether the operation was successful + * + * + * // put an item in the cache for 15 minutes + * $cache->set('value', 'my value', 15); + * + * + * @param string $key + * @param mixed $value + * @param int $minutes + * @return bool + */ + public function set(string $key, $value, int $minutes = 0): bool + { + return $this->connection->set($this->key($key), (new Value($value, $minutes))->toJson(), $this->expiration($minutes)); + } + + /** + * Internal method to retrieve the raw cache value; + * needs to return a Value object or null if not found + * + * @param string $key + * @return \Kirby\Cache\Value|null + */ + public function retrieve(string $key) + { + return Value::fromJson($this->connection->get($this->key($key))); + } + + /** + * Removes an item from the cache and returns + * whether the operation was successful + * + * @param string $key + * @return bool + */ + public function remove(string $key): bool + { + return $this->connection->delete($this->key($key)); + } + + /** + * Flushes the entire cache and returns + * whether the operation was successful; + * WARNING: Memcached only supports flushing the whole cache at once! + * + * @return bool + */ + public function flush(): bool + { + return $this->connection->flush(); + } +} diff --git a/kirby/src/Cache/MemoryCache.php b/kirby/src/Cache/MemoryCache.php new file mode 100644 index 0000000..7f2d098 --- /dev/null +++ b/kirby/src/Cache/MemoryCache.php @@ -0,0 +1,82 @@ + + * @link https://getkirby.com + * @copyright Bastian Allgeier GmbH + * @license https://opensource.org/licenses/MIT + */ +class MemoryCache extends Cache +{ + /** + * Cache data + * @var array + */ + protected $store = []; + + /** + * Writes an item to the cache for a given number of minutes and + * returns whether the operation was successful + * + * + * // put an item in the cache for 15 minutes + * $cache->set('value', 'my value', 15); + * + * + * @param string $key + * @param mixed $value + * @param int $minutes + * @return bool + */ + public function set(string $key, $value, int $minutes = 0): bool + { + $this->store[$key] = new Value($value, $minutes); + return true; + } + + /** + * Internal method to retrieve the raw cache value; + * needs to return a Value object or null if not found + * + * @param string $key + * @return \Kirby\Cache\Value|null + */ + public function retrieve(string $key) + { + return $this->store[$key] ?? null; + } + + /** + * Removes an item from the cache and returns + * whether the operation was successful + * + * @param string $key + * @return bool + */ + public function remove(string $key): bool + { + if (isset($this->store[$key])) { + unset($this->store[$key]); + return true; + } else { + return false; + } + } + + /** + * Flushes the entire cache and returns + * whether the operation was successful + * + * @return bool + */ + public function flush(): bool + { + $this->store = []; + return true; + } +} diff --git a/kirby/src/Cache/NullCache.php b/kirby/src/Cache/NullCache.php new file mode 100644 index 0000000..a33fc9c --- /dev/null +++ b/kirby/src/Cache/NullCache.php @@ -0,0 +1,69 @@ + + * @link https://getkirby.com + * @copyright Bastian Allgeier GmbH + * @license https://opensource.org/licenses/MIT + */ +class NullCache extends Cache +{ + /** + * Writes an item to the cache for a given number of minutes and + * returns whether the operation was successful + * + * + * // put an item in the cache for 15 minutes + * $cache->set('value', 'my value', 15); + * + * + * @param string $key + * @param mixed $value + * @param int $minutes + * @return bool + */ + public function set(string $key, $value, int $minutes = 0): bool + { + return true; + } + + /** + * Internal method to retrieve the raw cache value; + * needs to return a Value object or null if not found + * + * @param string $key + * @return \Kirby\Cache\Value|null + */ + public function retrieve(string $key) + { + return null; + } + + /** + * Removes an item from the cache and returns + * whether the operation was successful + * + * @param string $key + * @return bool + */ + public function remove(string $key): bool + { + return true; + } + + /** + * Flushes the entire cache and returns + * whether the operation was successful + * + * @return bool + */ + public function flush(): bool + { + return true; + } +} diff --git a/kirby/src/Cache/Value.php b/kirby/src/Cache/Value.php new file mode 100644 index 0000000..d59e2e9 --- /dev/null +++ b/kirby/src/Cache/Value.php @@ -0,0 +1,152 @@ + + * @link https://getkirby.com + * @copyright Bastian Allgeier GmbH + * @license https://opensource.org/licenses/MIT + */ +class Value +{ + /** + * Cached value + * @var mixed + */ + protected $value; + + /** + * the number of minutes until the value expires + * @todo Rename this property to $expiry to reflect + * both minutes and absolute timestamps + * @var int + */ + protected $minutes; + + /** + * Creation timestamp + * @var int + */ + protected $created; + + /** + * Constructor + * + * @param mixed $value + * @param int $minutes the number of minutes until the value expires + * or an absolute UNIX timestamp + * @param int $created the UNIX timestamp when the value has been created + */ + public function __construct($value, int $minutes = 0, int $created = null) + { + $this->value = $value; + $this->minutes = $minutes ?? 0; + $this->created = $created ?? time(); + } + + /** + * Returns the creation date as UNIX timestamp + * + * @return int + */ + public function created(): int + { + return $this->created; + } + + /** + * Returns the expiration date as UNIX timestamp or + * null if the value never expires + * + * @return int|null + */ + public function expires(): ?int + { + // 0 = keep forever + if ($this->minutes === 0) { + return null; + } + + if ($this->minutes > 1000000000) { + // absolute timestamp + return $this->minutes; + } + + return $this->created + ($this->minutes * 60); + } + + /** + * Creates a value object from an array + * + * @param array $array + * @return static + */ + public static function fromArray(array $array) + { + return new static($array['value'] ?? null, $array['minutes'] ?? 0, $array['created'] ?? null); + } + + /** + * Creates a value object from a JSON string; + * returns null on error + * + * @param string $json + * @return static|null + */ + public static function fromJson(string $json) + { + try { + $array = json_decode($json, true); + + if (is_array($array)) { + return static::fromArray($array); + } else { + return null; + } + } catch (Throwable $e) { + return null; + } + } + + /** + * Converts the object to a JSON string + * + * @return string + */ + public function toJson(): string + { + return json_encode($this->toArray()); + } + + /** + * Converts the object to an array + * + * @return array + */ + public function toArray(): array + { + return [ + 'created' => $this->created, + 'minutes' => $this->minutes, + 'value' => $this->value, + ]; + } + + /** + * Returns the pure value + * + * @return mixed + */ + public function value() + { + return $this->value; + } +} diff --git a/kirby/src/Cms/Api.php b/kirby/src/Cms/Api.php new file mode 100644 index 0000000..f6dfe88 --- /dev/null +++ b/kirby/src/Cms/Api.php @@ -0,0 +1,317 @@ + + * @link https://getkirby.com + * @copyright Bastian Allgeier GmbH + * @license https://getkirby.com/license + */ +class Api extends BaseApi +{ + /** + * @var App + */ + protected $kirby; + + /** + * Execute an API call for the given path, + * request method and optional request data + * + * @param string|null $path + * @param string $method + * @param array $requestData + * @return mixed + */ + public function call(string $path = null, string $method = 'GET', array $requestData = []) + { + $this->setRequestMethod($method); + $this->setRequestData($requestData); + + $this->kirby->setCurrentLanguage($this->language()); + + $allowImpersonation = $this->kirby()->option('api.allowImpersonation', false); + if ($user = $this->kirby->user(null, $allowImpersonation)) { + $translation = $user->language(); + } else { + $translation = $this->kirby->panelLanguage(); + } + $this->kirby->setCurrentTranslation($translation); + + return parent::call($path, $method, $requestData); + } + + /** + * @param mixed $model + * @param string $name + * @param string|null $path + * @return mixed + * @throws \Kirby\Exception\NotFoundException if the field type cannot be found or the field cannot be loaded + */ + public function fieldApi($model, string $name, string $path = null) + { + $field = Form::for($model)->field($name); + + $fieldApi = $this->clone([ + 'routes' => $field->api(), + 'data' => array_merge($this->data(), ['field' => $field]) + ]); + + return $fieldApi->call($path, $this->requestMethod(), $this->requestData()); + } + + /** + * Returns the file object for the given + * parent path and filename + * + * @param string|null $path Path to file's parent model + * @param string $filename Filename + * @return \Kirby\Cms\File|null + * @throws \Kirby\Exception\NotFoundException if the file cannot be found + */ + public function file(string $path = null, string $filename) + { + $filename = urldecode($filename); + $file = $this->parent($path)->file($filename); + + if ($file && $file->isReadable() === true) { + return $file; + } + + throw new NotFoundException([ + 'key' => 'file.notFound', + 'data' => [ + 'filename' => $filename + ] + ]); + } + + /** + * Returns the model's object for the given path + * + * @param string $path Path to parent model + * @return \Kirby\Cms\Model|null + * @throws \Kirby\Exception\InvalidArgumentException if the model type is invalid + * @throws \Kirby\Exception\NotFoundException if the model cannot be found + */ + public function parent(string $path) + { + $modelType = in_array($path, ['site', 'account']) ? $path : trim(dirname($path), '/'); + $modelTypes = [ + 'site' => 'site', + 'users' => 'user', + 'pages' => 'page', + 'account' => 'account' + ]; + $modelName = $modelTypes[$modelType] ?? null; + + if (Str::endsWith($modelType, '/files') === true) { + $modelName = 'file'; + } + + $kirby = $this->kirby(); + + switch ($modelName) { + case 'site': + $model = $kirby->site(); + break; + case 'account': + $model = $kirby->user(null, $kirby->option('api.allowImpersonation', false)); + break; + case 'page': + $id = str_replace(['+', ' '], '/', basename($path)); + $model = $kirby->page($id); + break; + case 'file': + $model = $this->file(...explode('/files/', $path)); + break; + case 'user': + $model = $kirby->user(basename($path)); + break; + default: + throw new InvalidArgumentException('Invalid model type: ' . $modelType); + } + + if ($model) { + return $model; + } + + throw new NotFoundException([ + 'key' => $modelName . '.undefined' + ]); + } + + /** + * Returns the Kirby instance + * + * @return \Kirby\Cms\App + */ + public function kirby() + { + return $this->kirby; + } + + /** + * Returns the language request header + * + * @return string|null + */ + public function language(): ?string + { + return get('language') ?? $this->requestHeaders('x-language'); + } + + /** + * Returns the page object for the given id + * + * @param string $id Page's id + * @return \Kirby\Cms\Page|null + * @throws \Kirby\Exception\NotFoundException if the page cannot be found + */ + public function page(string $id) + { + $id = str_replace('+', '/', $id); + $page = $this->kirby->page($id); + + if ($page && $page->isReadable() === true) { + return $page; + } + + throw new NotFoundException([ + 'key' => 'page.notFound', + 'data' => [ + 'slug' => $id + ] + ]); + } + + /** + * Returns the subpages for the given + * parent. The subpages can be filtered + * by status (draft, listed, unlisted, published, all) + * + * @param string|null $parentId + * @param string|null $status + * @return \Kirby\Cms\Pages + */ + public function pages(string $parentId = null, string $status = null) + { + $parent = $parentId === null ? $this->site() : $this->page($parentId); + + switch ($status) { + case 'all': + return $parent->childrenAndDrafts(); + case 'draft': + case 'drafts': + return $parent->drafts(); + case 'listed': + return $parent->children()->listed(); + case 'unlisted': + return $parent->children()->unlisted(); + case 'published': + default: + return $parent->children(); + } + } + + /** + * Search for direct subpages of the + * given parent + * + * @param string|null $parent + * @return \Kirby\Cms\Pages + */ + public function searchPages(string $parent = null) + { + $pages = $this->pages($parent, $this->requestQuery('status')); + + if ($this->requestMethod() === 'GET') { + return $pages->search($this->requestQuery('q')); + } + + return $pages->query($this->requestBody()); + } + + /** + * Returns the current Session instance + * + * @param array $options Additional options, see the session component + * @return \Kirby\Session\Session + */ + public function session(array $options = []) + { + return $this->kirby->session(array_merge([ + 'detect' => true + ], $options)); + } + + /** + * Setter for the parent Kirby instance + * + * @param \Kirby\Cms\App $kirby + * @return $this + */ + protected function setKirby(App $kirby) + { + $this->kirby = $kirby; + return $this; + } + + /** + * Returns the site object + * + * @return \Kirby\Cms\Site + */ + public function site() + { + return $this->kirby->site(); + } + + /** + * Returns the user object for the given id or + * returns the current authenticated user if no + * id is passed + * + * @param string|null $id User's id + * @return \Kirby\Cms\User|null + * @throws \Kirby\Exception\NotFoundException if the user for the given id cannot be found + */ + public function user(string $id = null) + { + // get the authenticated user + if ($id === null) { + return $this->kirby->auth()->user(null, $this->kirby()->option('api.allowImpersonation', false)); + } + + // get a specific user by id + if ($user = $this->kirby->users()->find($id)) { + return $user; + } + + throw new NotFoundException([ + 'key' => 'user.notFound', + 'data' => [ + 'name' => $id + ] + ]); + } + + /** + * Returns the users collection + * + * @return \Kirby\Cms\Users + */ + public function users() + { + return $this->kirby->users(); + } +} diff --git a/kirby/src/Cms/App.php b/kirby/src/Cms/App.php new file mode 100644 index 0000000..a887e5f --- /dev/null +++ b/kirby/src/Cms/App.php @@ -0,0 +1,1554 @@ + + * @link https://getkirby.com + * @copyright Bastian Allgeier GmbH + * @license https://getkirby.com/license + */ +class App +{ + const CLASS_ALIAS = 'kirby'; + + use AppCaches; + use AppErrors; + use AppPlugins; + use AppTranslations; + use AppUsers; + use Properties; + + protected static $instance; + protected static $version; + + public $data = []; + + protected $api; + protected $collections; + protected $defaultLanguage; + protected $language; + protected $languages; + protected $locks; + protected $multilang; + protected $nonce; + protected $options; + protected $path; + protected $request; + protected $response; + protected $roles; + protected $roots; + protected $routes; + protected $router; + protected $server; + protected $sessionHandler; + protected $site; + protected $system; + protected $urls; + protected $user; + protected $users; + protected $visitor; + + /** + * Creates a new App instance + * + * @param array $props + * @param bool $setInstance If false, the instance won't be set globally + */ + public function __construct(array $props = [], bool $setInstance = true) + { + // register all roots to be able to load stuff afterwards + $this->bakeRoots($props['roots'] ?? []); + + // stuff from config and additional options + $this->optionsFromConfig(); + $this->optionsFromProps($props['options'] ?? []); + + // register the Whoops error handler + $this->handleErrors(); + + // set the path to make it available for the url bakery + $this->setPath($props['path'] ?? null); + + // create all urls after the config, so possible + // options can be taken into account + $this->bakeUrls($props['urls'] ?? []); + + // configurable properties + $this->setOptionalProperties($props, [ + 'languages', + 'request', + 'roles', + 'site', + 'user', + 'users' + ]); + + // set the singleton + if (static::$instance === null || $setInstance === true) { + Model::$kirby = static::$instance = $this; + } + + // setup the I18n class with the translation loader + $this->i18n(); + + // load all extensions + $this->extensionsFromSystem(); + $this->extensionsFromProps($props); + $this->extensionsFromPlugins(); + $this->extensionsFromOptions(); + $this->extensionsFromFolders(); + + // trigger hook for use in plugins + $this->trigger('system.loadPlugins:after'); + + // execute a ready callback from the config + $this->optionsFromReadyCallback(); + + // bake config + $this->bakeOptions(); + } + + /** + * Improved `var_dump` output + * + * @return array + */ + public function __debugInfo(): array + { + return [ + 'languages' => $this->languages(), + 'options' => $this->options(), + 'request' => $this->request(), + 'roots' => $this->roots(), + 'site' => $this->site(), + 'urls' => $this->urls(), + 'version' => $this->version(), + ]; + } + + /** + * Returns the Api instance + * + * @internal + * @return \Kirby\Cms\Api + */ + public function api() + { + if ($this->api !== null) { + return $this->api; + } + + $root = $this->root('kirby') . '/config/api'; + $extensions = $this->extensions['api'] ?? []; + $routes = (include $root . '/routes.php')($this); + + $api = [ + 'debug' => $this->option('debug', false), + 'authentication' => $extensions['authentication'] ?? include $root . '/authentication.php', + 'data' => $extensions['data'] ?? [], + 'collections' => array_merge($extensions['collections'] ?? [], include $root . '/collections.php'), + 'models' => array_merge($extensions['models'] ?? [], include $root . '/models.php'), + 'routes' => array_merge($routes, $extensions['routes'] ?? []), + 'kirby' => $this, + ]; + + return $this->api = new Api($api); + } + + /** + * Applies a hook to the given value + * + * @internal + * @param string $name Full event name + * @param array $args Associative array of named event arguments + * @param string $modify Key in $args that is modified by the hooks + * @param \Kirby\Cms\Event|null $originalEvent Event object (internal use) + * @return mixed Resulting value as modified by the hooks + */ + public function apply(string $name, array $args, string $modify, ?Event $originalEvent = null) + { + $event = $originalEvent ?? new Event($name, $args); + + if ($functions = $this->extension('hooks', $name)) { + foreach ($functions as $function) { + // bind the App object to the hook + $newValue = $event->call($this, $function); + + // update value if one was returned + if ($newValue !== null) { + $event->updateArgument($modify, $newValue); + } + } + } + + // apply wildcard hooks if available + $nameWildcards = $event->nameWildcards(); + if ($originalEvent === null && count($nameWildcards) > 0) { + foreach ($nameWildcards as $nameWildcard) { + // the $event object is passed by reference + // and will be modified down the chain + $this->apply($nameWildcard, $event->arguments(), $modify, $event); + } + } + + return $event->argument($modify); + } + + /** + * Normalizes and globally sets the configured options + * + * @return $this + */ + protected function bakeOptions() + { + // convert the old plugin option syntax to the new one + foreach ($this->options as $key => $value) { + // detect option keys with the `vendor.plugin.option` format + if (preg_match('/^([a-z0-9-]+\.[a-z0-9-]+)\.(.*)$/i', $key, $matches) === 1) { + list(, $plugin, $option) = $matches; + + // verify that it's really a plugin option + if (isset(static::$plugins[str_replace('.', '/', $plugin)]) !== true) { + continue; + } + + // ensure that the target option array exists + // (which it will if the plugin has any options) + if (isset($this->options[$plugin]) !== true) { + $this->options[$plugin] = []; // @codeCoverageIgnore + } + + // move the option to the plugin option array + // don't overwrite nested arrays completely but merge them + $this->options[$plugin] = array_replace_recursive($this->options[$plugin], [$option => $value]); + unset($this->options[$key]); + } + } + + Config::$data = $this->options; + return $this; + } + + /** + * Sets the directory structure + * + * @param array|null $roots + * @return $this + */ + protected function bakeRoots(array $roots = null) + { + $roots = array_merge(require dirname(__DIR__, 2) . '/config/roots.php', (array)$roots); + $this->roots = Ingredients::bake($roots); + return $this; + } + + /** + * Sets the Url structure + * + * @param array|null $urls + * @return $this + */ + protected function bakeUrls(array $urls = null) + { + // inject the index URL from the config + if (isset($this->options['url']) === true) { + $urls['index'] = $this->options['url']; + } + + $urls = array_merge(require $this->root('kirby') . '/config/urls.php', (array)$urls); + $this->urls = Ingredients::bake($urls); + return $this; + } + + /** + * Returns all available blueprints for this installation + * + * @param string $type + * @return array + */ + public function blueprints(string $type = 'pages'): array + { + $blueprints = []; + + foreach ($this->extensions('blueprints') as $name => $blueprint) { + if (dirname($name) === $type) { + $name = basename($name); + $blueprints[$name] = $name; + } + } + + foreach (glob($this->root('blueprints') . '/' . $type . '/*.yml') as $blueprint) { + $name = F::name($blueprint); + $blueprints[$name] = $name; + } + + ksort($blueprints); + + return array_values($blueprints); + } + + /** + * Calls any Kirby route + * + * @param string|null $path + * @param string|null $method + * @return mixed + */ + public function call(string $path = null, string $method = null) + { + $router = $this->router(); + + $router::$beforeEach = function ($route, $path, $method) { + $this->trigger('route:before', compact('route', 'path', 'method')); + }; + + $router::$afterEach = function ($route, $path, $method, $result, $final) { + return $this->apply('route:after', compact('route', 'path', 'method', 'result', 'final'), 'result'); + }; + + return $router->call($path ?? $this->path(), $method ?? $this->request()->method()); + } + + /** + * Creates an instance with the same + * initial properties + * + * @param array $props + * @param bool $setInstance If false, the instance won't be set globally + * @return static + */ + public function clone(array $props = [], bool $setInstance = true) + { + $props = array_replace_recursive($this->propertyData, $props); + + $clone = new static($props, $setInstance); + $clone->data = $this->data; + + return $clone; + } + + /** + * Returns a specific user-defined collection + * by name. All relevant dependencies are + * automatically injected + * + * @param string $name + * @return \Kirby\Cms\Collection|null + */ + public function collection(string $name) + { + return $this->collections()->get($name, [ + 'kirby' => $this, + 'site' => $this->site(), + 'pages' => $this->site()->children(), + 'users' => $this->users() + ]); + } + + /** + * Returns all user-defined collections + * + * @return \Kirby\Cms\Collections + */ + public function collections() + { + return $this->collections = $this->collections ?? new Collections(); + } + + /** + * Returns a core component + * + * @internal + * @param string $name + * @return mixed + */ + public function component($name) + { + return $this->extensions['components'][$name] ?? null; + } + + /** + * Returns the content extension + * + * @internal + * @return string + */ + public function contentExtension(): string + { + return $this->options['content']['extension'] ?? 'txt'; + } + + /** + * Returns files that should be ignored when scanning folders + * + * @internal + * @return array + */ + public function contentIgnore(): array + { + return $this->options['content']['ignore'] ?? Dir::$ignore; + } + + /** + * Generates a non-guessable token based on model + * data and a configured salt + * + * @param mixed $model Object to pass to the salt callback if configured + * @param string $value Model data to include in the generated token + * @return string + */ + public function contentToken($model, string $value): string + { + if (method_exists($model, 'root') === true) { + $default = $model->root(); + } else { + $default = $this->root('content'); + } + + $salt = $this->option('content.salt', $default); + + if (is_a($salt, 'Closure') === true) { + $salt = $salt($model); + } + + return hash_hmac('sha1', $value, $salt); + } + + /** + * Calls a page controller by name + * and with the given arguments + * + * @internal + * @param string $name + * @param array $arguments + * @param string $contentType + * @return array + */ + public function controller(string $name, array $arguments = [], string $contentType = 'html'): array + { + $name = basename(strtolower($name)); + + if ($controller = $this->controllerLookup($name, $contentType)) { + return (array)$controller->call($this, $arguments); + } + + if ($contentType !== 'html') { + + // no luck for a specific representation controller? + // let's try the html controller instead + if ($controller = $this->controllerLookup($name)) { + return (array)$controller->call($this, $arguments); + } + } + + // still no luck? Let's take the site controller + if ($controller = $this->controllerLookup('site')) { + return (array)$controller->call($this, $arguments); + } + + return []; + } + + /** + * Try to find a controller by name + * + * @param string $name + * @param string $contentType + * @return \Kirby\Toolkit\Controller|null + */ + protected function controllerLookup(string $name, string $contentType = 'html') + { + if ($contentType !== null && $contentType !== 'html') { + $name .= '.' . $contentType; + } + + // controller on disk + if ($controller = Controller::load($this->root('controllers') . '/' . $name . '.php')) { + return $controller; + } + + // registry controller + if ($controller = $this->extension('controllers', $name)) { + return is_a($controller, 'Kirby\Toolkit\Controller') ? $controller : new Controller($controller); + } + + return null; + } + + /** + * Returns the default language object + * + * @return \Kirby\Cms\Language|null + */ + public function defaultLanguage() + { + return $this->defaultLanguage = $this->defaultLanguage ?? $this->languages()->default(); + } + + /** + * Destroy the instance singleton and + * purge other static props + * + * @internal + */ + public static function destroy(): void + { + static::$plugins = []; + static::$instance = null; + } + + /** + * Detect the preferred language from the visitor object + * + * @return \Kirby\Cms\Language + */ + public function detectedLanguage() + { + $languages = $this->languages(); + $visitor = $this->visitor(); + + foreach ($visitor->acceptedLanguages() as $lang) { + if ($language = $languages->findBy('locale', $lang->locale(LC_ALL))) { + return $language; + } + } + + foreach ($visitor->acceptedLanguages() as $lang) { + if ($language = $languages->findBy('code', $lang->code())) { + return $language; + } + } + + return $this->defaultLanguage(); + } + + /** + * Returns the Email singleton + * + * @param mixed $preset + * @param array $props + * @return \Kirby\Email\PHPMailer + */ + public function email($preset = [], array $props = []) + { + return new Emailer((new Email($preset, $props))->toArray(), $props['debug'] ?? false); + } + + /** + * Finds any file in the content directory + * + * @param string $path + * @param mixed $parent + * @param bool $drafts + * @return \Kirby\Cms\File|null + */ + public function file(string $path, $parent = null, bool $drafts = true) + { + $parent = $parent ?? $this->site(); + $id = dirname($path); + $filename = basename($path); + + if (is_a($parent, 'Kirby\Cms\User') === true) { + return $parent->file($filename); + } + + if (is_a($parent, 'Kirby\Cms\File') === true) { + $parent = $parent->parent(); + } + + if ($id === '.') { + if ($file = $parent->file($filename)) { + return $file; + } elseif ($file = $this->site()->file($filename)) { + return $file; + } else { + return null; + } + } + + if ($page = $this->page($id, $parent, $drafts)) { + return $page->file($filename); + } + + if ($page = $this->page($id, null, $drafts)) { + return $page->file($filename); + } + + return null; + } + + /** + * Returns the current App instance + * + * @param \Kirby\Cms\App|null $instance + * @param bool $lazy If `true`, the instance is only returned if already existing + * @return static|null + */ + public static function instance(self $instance = null, bool $lazy = false) + { + if ($instance === null) { + if ($lazy === true) { + return static::$instance; + } else { + return static::$instance ?? new static(); + } + } + + return static::$instance = $instance; + } + + /** + * Takes almost any kind of input and + * tries to convert it into a valid response + * + * @internal + * @param mixed $input + * @return \Kirby\Http\Response + */ + public function io($input) + { + // use the current response configuration + $response = $this->response(); + + // any direct exception will be turned into an error page + if (is_a($input, 'Throwable') === true) { + if (is_a($input, 'Kirby\Exception\Exception') === true) { + $code = $input->getHttpCode(); + $message = $input->getMessage(); + } else { + $code = $input->getCode(); + $message = $input->getMessage(); + } + + if ($code < 400 || $code > 599) { + $code = 500; + } + + if ($errorPage = $this->site()->errorPage()) { + return $response->code($code)->send($errorPage->render([ + 'errorCode' => $code, + 'errorMessage' => $message, + 'errorType' => get_class($input) + ])); + } + + return $response + ->code($code) + ->type('text/html') + ->send($message); + } + + // Empty input + if (empty($input) === true) { + return $this->io(new NotFoundException()); + } + + // Response Configuration + if (is_a($input, 'Kirby\Cms\Responder') === true) { + return $input->send(); + } + + // Responses + if (is_a($input, 'Kirby\Http\Response') === true) { + return $input; + } + + // Pages + if (is_a($input, 'Kirby\Cms\Page')) { + try { + $html = $input->render(); + } catch (ErrorPageException $e) { + return $this->io($e); + } + + if ($input->isErrorPage() === true) { + if ($response->code() === null) { + $response->code(404); + } + } + + return $response->send($html); + } + + // Files + if (is_a($input, 'Kirby\Cms\File')) { + return $response->redirect($input->mediaUrl(), 307)->send(); + } + + // Simple HTML response + if (is_string($input) === true) { + return $response->send($input); + } + + // array to json conversion + if (is_array($input) === true) { + return $response->json($input)->send(); + } + + throw new InvalidArgumentException('Unexpected input'); + } + + /** + * Renders a single KirbyTag with the given attributes + * + * @internal + * @param string $type + * @param string|null $value + * @param array $attr + * @param array $data + * @return string + */ + public function kirbytag(string $type, string $value = null, array $attr = [], array $data = []): string + { + $data['kirby'] = $data['kirby'] ?? $this; + $data['site'] = $data['site'] ?? $data['kirby']->site(); + $data['parent'] = $data['parent'] ?? $data['site']->page(); + + return (new KirbyTag($type, $value, $attr, $data, $this->options))->render(); + } + + /** + * KirbyTags Parser + * + * @internal + * @param string|null $text + * @param array $data + * @return string + */ + public function kirbytags(string $text = null, array $data = []): string + { + $data['kirby'] = $data['kirby'] ?? $this; + $data['site'] = $data['site'] ?? $data['kirby']->site(); + $data['parent'] = $data['parent'] ?? $data['site']->page(); + + return KirbyTags::parse($text, $data, $this->options, $this); + } + + /** + * Parses KirbyTags first and Markdown afterwards + * + * @internal + * @param string|null $text + * @param array $data + * @param bool $inline + * @return string + */ + public function kirbytext(string $text = null, array $data = [], bool $inline = false): string + { + $text = $this->apply('kirbytext:before', compact('text'), 'text'); + $text = $this->kirbytags($text, $data); + $text = $this->markdown($text, $inline); + + if ($this->option('smartypants', false) !== false) { + $text = $this->smartypants($text); + } + + $text = $this->apply('kirbytext:after', compact('text'), 'text'); + + return $text; + } + + /** + * Returns the current language + * + * @param string|null $code + * @return \Kirby\Cms\Language|null + */ + public function language(string $code = null) + { + if ($this->multilang() === false) { + return null; + } + + if ($code === 'default') { + return $this->languages()->default(); + } + + if ($code !== null) { + return $this->languages()->find($code); + } + + return $this->language = $this->language ?? $this->languages()->default(); + } + + /** + * Returns the current language code + * + * @internal + * @param string|null $languageCode + * @return string|null + */ + public function languageCode(string $languageCode = null): ?string + { + if ($language = $this->language($languageCode)) { + return $language->code(); + } + + return null; + } + + /** + * Returns all available site languages + * + * @return \Kirby\Cms\Languages + */ + public function languages() + { + if ($this->languages !== null) { + return clone $this->languages; + } + + return $this->languages = Languages::load(); + } + + /** + * Returns the app's locks object + * + * @return \Kirby\Cms\ContentLocks + */ + public function locks(): ContentLocks + { + if ($this->locks !== null) { + return $this->locks; + } + + return $this->locks = new ContentLocks(); + } + + /** + * Parses Markdown + * + * @internal + * @param string|null $text + * @param bool $inline + * @return string + */ + public function markdown(string $text = null, bool $inline = false): string + { + return ($this->component('markdown'))($this, $text, $this->options['markdown'] ?? [], $inline); + } + + /** + * Check for a multilang setup + * + * @return bool + */ + public function multilang(): bool + { + if ($this->multilang !== null) { + return $this->multilang; + } + + return $this->multilang = $this->languages()->count() !== 0; + } + + /** + * Returns the nonce, which is used + * in the panel for inline scripts + * @since 3.3.0 + * + * @return string + */ + public function nonce(): string + { + return $this->nonce = $this->nonce ?? base64_encode(random_bytes(20)); + } + + /** + * Load a specific configuration option + * + * @param string $key + * @param mixed $default + * @return mixed + */ + public function option(string $key, $default = null) + { + return A::get($this->options, $key, $default); + } + + /** + * Returns all configuration options + * + * @return array + */ + public function options(): array + { + return $this->options; + } + + /** + * Load all options from files in site/config + * + * @return array + */ + protected function optionsFromConfig(): array + { + $server = $this->server(); + $root = $this->root('config'); + + Config::$data = []; + + $main = F::load($root . '/config.php', []); + $host = F::load($root . '/config.' . basename($server->host()) . '.php', []); + $addr = F::load($root . '/config.' . basename($server->address()) . '.php', []); + + $config = Config::$data; + + return $this->options = array_replace_recursive($config, $main, $host, $addr); + } + + /** + * Inject options from Kirby instance props + * + * @param array $options + * @return array + */ + protected function optionsFromProps(array $options = []): array + { + return $this->options = array_replace_recursive($this->options, $options); + } + + /** + * Merge last-minute options from ready callback + * + * @return array + */ + protected function optionsFromReadyCallback(): array + { + if (isset($this->options['ready']) === true && is_callable($this->options['ready']) === true) { + // fetch last-minute options from the callback + $options = (array)$this->options['ready']($this); + + // inject all last-minute options recursively + $this->options = array_replace_recursive($this->options, $options); + + // update the system with changed options + if ( + isset($options['debug']) === true || + isset($options['whoops']) === true || + isset($options['editor']) === true + ) { + $this->handleErrors(); + } + + if (isset($options['debug']) === true) { + $this->api = null; + } + + if (isset($options['home']) === true || isset($options['error']) === true) { + $this->site = null; + } + + // checks custom language definition for slugs + if ($slugsOption = $this->option('slugs')) { + // slugs option must be set to string or "slugs" => ["language" => "de"] as array + if (is_string($slugsOption) === true || isset($slugsOption['language']) === true) { + $this->i18n(); + } + } + } + + return $this->options; + } + + /** + * Returns any page from the content folder + * + * @param string|null $id + * @param \Kirby\Cms\Page|\Kirby\Cms\Site|null $parent + * @param bool $drafts + * @return \Kirby\Cms\Page|null + */ + public function page(?string $id = null, $parent = null, bool $drafts = true) + { + if ($id === null) { + return null; + } + + $parent = $parent ?? $this->site(); + + if ($page = $parent->find($id)) { + return $page; + } + + if ($drafts === true && $draft = $parent->draft($id)) { + return $draft; + } + + return null; + } + + /** + * Returns the request path + * + * @return string + */ + public function path(): string + { + if (is_string($this->path) === true) { + return $this->path; + } + + $requestUri = '/' . $this->request()->url()->path(); + $scriptName = $_SERVER['SCRIPT_NAME']; + $scriptFile = basename($scriptName); + $scriptDir = dirname($scriptName); + $scriptPath = $scriptFile === 'index.php' ? $scriptDir : $scriptName; + $requestPath = preg_replace('!^' . preg_quote($scriptPath) . '!', '', $requestUri); + + return $this->setPath($requestPath)->path; + } + + /** + * Returns the Response object for the + * current request + * + * @param string|null $path + * @param string|null $method + * @return \Kirby\Http\Response + */ + public function render(string $path = null, string $method = null) + { + return $this->io($this->call($path, $method)); + } + + /** + * Returns the Request singleton + * + * @return \Kirby\Http\Request + */ + public function request() + { + return $this->request = $this->request ?? new Request(); + } + + /** + * Path resolver for the router + * + * @internal + * @param string|null $path + * @param string|null $language + * @return mixed + * @throws \Kirby\Exception\NotFoundException if the home page cannot be found + */ + public function resolve(string $path = null, string $language = null) + { + // set the current translation + $this->setCurrentTranslation($language); + + // set the current locale + $this->setCurrentLanguage($language); + + // the site is needed a couple times here + $site = $this->site(); + + // use the home page + if ($path === null) { + if ($homePage = $site->homePage()) { + return $homePage; + } + + throw new NotFoundException('The home page does not exist'); + } + + // search for the page by path + $page = $site->find($path); + + // search for a draft if the page cannot be found + if (!$page && $draft = $site->draft($path)) { + if ($this->user() || $draft->isVerified(get('token'))) { + $page = $draft; + } + } + + // try to resolve content representations if the path has an extension + $extension = F::extension($path); + + // no content representation? then return the page + if (empty($extension) === true) { + return $page; + } + + // only try to return a representation + // when the page has been found + if ($page) { + try { + $response = $this->response(); + $output = $page->render([], $extension); + + // attach a MIME type based on the representation + // only if no custom MIME type was set + if ($response->type() === null) { + $response->type($extension); + } + + return $response->body($output); + } catch (NotFoundException $e) { + return null; + } + } + + $id = dirname($path); + $filename = basename($path); + + // try to resolve image urls for pages and drafts + if ($page = $site->findPageOrDraft($id)) { + return $page->file($filename); + } + + // try to resolve site files at least + return $site->file($filename); + } + + /** + * Response configuration + * + * @return \Kirby\Cms\Responder + */ + public function response() + { + return $this->response = $this->response ?? new Responder(); + } + + /** + * Returns all user roles + * + * @return \Kirby\Cms\Roles + */ + public function roles() + { + return $this->roles = $this->roles ?? Roles::load($this->root('roles')); + } + + /** + * Returns a system root + * + * @param string $type + * @return string + */ + public function root(string $type = 'index'): string + { + return $this->roots->__get($type); + } + + /** + * Returns the directory structure + * + * @return \Kirby\Cms\Ingredients + */ + public function roots() + { + return $this->roots; + } + + /** + * Returns the currently active route + * + * @return \Kirby\Http\Route|null + */ + public function route() + { + return $this->router()->route(); + } + + /** + * Returns the Router singleton + * + * @internal + * @return \Kirby\Http\Router + */ + public function router() + { + $routes = $this->routes(); + + if ($this->multilang() === true) { + foreach ($routes as $index => $route) { + if (empty($route['language']) === false) { + unset($routes[$index]); + } + } + } + + return $this->router = $this->router ?? new Router($routes); + } + + /** + * Returns all defined routes + * + * @internal + * @return array + */ + public function routes(): array + { + if (is_array($this->routes) === true) { + return $this->routes; + } + + $registry = $this->extensions('routes'); + $system = (include $this->root('kirby') . '/config/routes.php')($this); + $routes = array_merge($system['before'], $registry, $system['after']); + + return $this->routes = $routes; + } + + /** + * Returns the current session object + * + * @param array $options Additional options, see the session component + * @return \Kirby\Session\Session + */ + public function session(array $options = []) + { + // never cache responses that depend on the session + $this->response()->cache(false); + $this->response()->header('Cache-Control', 'no-store', true); + + return $this->sessionHandler()->get($options); + } + + /** + * Returns the session handler + * + * @return \Kirby\Session\AutoSession + */ + public function sessionHandler() + { + $this->sessionHandler = $this->sessionHandler ?? new AutoSession($this->root('sessions'), $this->option('session', [])); + return $this->sessionHandler; + } + + /** + * Create your own set of languages + * + * @param array|null $languages + * @return $this + */ + protected function setLanguages(array $languages = null) + { + if ($languages !== null) { + $objects = []; + + foreach ($languages as $props) { + $objects[] = new Language($props); + } + + $this->languages = new Languages($objects); + } + + return $this; + } + + /** + * Sets the request path that is + * used for the router + * + * @param string|null $path + * @return $this + */ + protected function setPath(string $path = null) + { + $this->path = $path !== null ? trim($path, '/') : null; + return $this; + } + + /** + * Sets the request + * + * @param array|null $request + * @return $this + */ + protected function setRequest(array $request = null) + { + if ($request !== null) { + $this->request = new Request($request); + } + + return $this; + } + + /** + * Create your own set of roles + * + * @param array|null $roles + * @return $this + */ + protected function setRoles(array $roles = null) + { + if ($roles !== null) { + $this->roles = Roles::factory($roles, [ + 'kirby' => $this + ]); + } + + return $this; + } + + /** + * Sets a custom Site object + * + * @param \Kirby\Cms\Site|array|null $site + * @return $this + */ + protected function setSite($site = null) + { + if (is_array($site) === true) { + $site = new Site($site + [ + 'kirby' => $this + ]); + } + + $this->site = $site; + return $this; + } + + /** + * Returns the Server object + * + * @return \Kirby\Http\Server + */ + public function server() + { + return $this->server = $this->server ?? new Server(); + } + + /** + * Initializes and returns the Site object + * + * @return \Kirby\Cms\Site + */ + public function site() + { + return $this->site = $this->site ?? new Site([ + 'errorPageId' => $this->options['error'] ?? 'error', + 'homePageId' => $this->options['home'] ?? 'home', + 'kirby' => $this, + 'url' => $this->url('index'), + ]); + } + + /** + * Applies the smartypants rule on the text + * + * @internal + * @param string|null $text + * @return string + */ + public function smartypants(string $text = null): string + { + $options = $this->option('smartypants', []); + + if ($options === false) { + return $text; + } elseif (is_array($options) === false) { + $options = []; + } + + if ($this->multilang() === true) { + $languageSmartypants = $this->language()->smartypants() ?? []; + + if (empty($languageSmartypants) === false) { + $options = array_merge($options, $languageSmartypants); + } + } + + return ($this->component('smartypants'))($this, $text, $options); + } + + /** + * Uses the snippet component to create + * and return a template snippet + * + * @internal + * @param mixed $name + * @param array $data + * @return string|null + */ + public function snippet($name, array $data = []): ?string + { + return ($this->component('snippet'))($this, $name, array_merge($this->data, $data)); + } + + /** + * System check class + * + * @return \Kirby\Cms\System + */ + public function system() + { + return $this->system = $this->system ?? new System($this); + } + + /** + * Uses the template component to initialize + * and return the Template object + * + * @internal + * @return \Kirby\Cms\Template + * @param string $name + * @param string $type + * @param string $defaultType + */ + public function template(string $name, string $type = 'html', string $defaultType = 'html') + { + return ($this->component('template'))($this, $name, $type, $defaultType); + } + + /** + * Thumbnail creator + * + * @param string $src + * @param string $dst + * @param array $options + * @return string + */ + public function thumb(string $src, string $dst, array $options = []): string + { + return ($this->component('thumb'))($this, $src, $dst, $options); + } + + /** + * Trigger a hook by name + * + * @internal + * @param string $name Full event name + * @param array $args Associative array of named event arguments + * @param \Kirby\Cms\Event|null $originalEvent Event object (internal use) + * @return void + */ + public function trigger(string $name, array $args = [], ?Event $originalEvent = null) + { + $event = $originalEvent ?? new Event($name, $args); + + if ($functions = $this->extension('hooks', $name)) { + static $level = 0; + static $triggered = []; + $level++; + + foreach ($functions as $index => $function) { + if (in_array($function, $triggered[$name] ?? []) === true) { + continue; + } + + // mark the hook as triggered, to avoid endless loops + $triggered[$name][] = $function; + + // bind the App object to the hook + $event->call($this, $function); + } + + $level--; + + if ($level === 0) { + $triggered = []; + } + } + + // trigger wildcard hooks if available + $nameWildcards = $event->nameWildcards(); + if ($originalEvent === null && count($nameWildcards) > 0) { + foreach ($nameWildcards as $nameWildcard) { + $this->trigger($nameWildcard, $args, $event); + } + } + } + + /** + * Returns a system url + * + * @param string $type + * @param bool $object If set to `true`, the URL is converted to an object + * @return string|\Kirby\Http\Uri + */ + public function url(string $type = 'index', bool $object = false) + { + $url = $this->urls->__get($type); + + if ($object === true) { + if (Url::isAbsolute($url)) { + return Url::toObject($url); + } + + // index URL was configured without host, use the current host + return Uri::current([ + 'path' => $url, + 'query' => null + ]); + } + + return $url; + } + + /** + * Returns the url structure + * + * @return \Kirby\Cms\Ingredients + */ + public function urls() + { + return $this->urls; + } + + /** + * Returns the current version number from + * the composer.json (Keep that up to date! :)) + * + * @return string|null + * @throws \Kirby\Exception\LogicException if the Kirby version cannot be detected + */ + public static function version(): ?string + { + try { + return static::$version = static::$version ?? Data::read(dirname(__DIR__, 2) . '/composer.json')['version'] ?? null; + } catch (Throwable $e) { + throw new LogicException('The Kirby version cannot be detected. The composer.json is probably missing or not readable.'); + } + } + + /** + * Creates a hash of the version number + * + * @return string + */ + public static function versionHash(): string + { + return md5(static::version()); + } + + /** + * Returns the visitor object + * + * @return \Kirby\Http\Visitor + */ + public function visitor() + { + return $this->visitor = $this->visitor ?? new Visitor(); + } +} diff --git a/kirby/src/Cms/AppCaches.php b/kirby/src/Cms/AppCaches.php new file mode 100644 index 0000000..d1219bf --- /dev/null +++ b/kirby/src/Cms/AppCaches.php @@ -0,0 +1,137 @@ + + * @link https://getkirby.com + * @copyright Bastian Allgeier GmbH + * @license https://getkirby.com/license + */ +trait AppCaches +{ + protected $caches = []; + + /** + * Returns a cache instance by key + * + * @param string $key + * @return \Kirby\Cache\Cache + */ + public function cache(string $key) + { + if (isset($this->caches[$key]) === true) { + return $this->caches[$key]; + } + + // get the options for this cache type + $options = $this->cacheOptions($key); + + if ($options['active'] === false) { + // use a dummy cache that does nothing + return $this->caches[$key] = new NullCache(); + } + + $type = strtolower($options['type']); + $types = $this->extensions['cacheTypes'] ?? []; + + if (array_key_exists($type, $types) === false) { + throw new InvalidArgumentException([ + 'key' => 'app.invalid.cacheType', + 'data' => ['type' => $type] + ]); + } + + $className = $types[$type]; + + // initialize the cache class + $cache = new $className($options); + + // check if it is a useable cache object + if (is_a($cache, 'Kirby\Cache\Cache') !== true) { + throw new InvalidArgumentException([ + 'key' => 'app.invalid.cacheType', + 'data' => ['type' => $type] + ]); + } + + return $this->caches[$key] = $cache; + } + + /** + * Returns the cache options by key + * + * @param string $key + * @return array + */ + protected function cacheOptions(string $key): array + { + $options = $this->option($this->cacheOptionsKey($key), false); + + if ($options === false) { + return [ + 'active' => false + ]; + } + + $prefix = str_replace(['/', ':'], '_', $this->system()->indexUrl()) . + '/' . + str_replace('.', '/', $key); + + $defaults = [ + 'active' => true, + 'type' => 'file', + 'extension' => 'cache', + 'root' => $this->root('cache'), + 'prefix' => $prefix + ]; + + if ($options === true) { + return $defaults; + } else { + return array_merge($defaults, $options); + } + } + + /** + * Takes care of converting prefixed plugin cache setups + * to the right cache key, while leaving regular cache + * setups untouched. + * + * @param string $key + * @return string + */ + protected function cacheOptionsKey(string $key): string + { + $prefixedKey = 'cache.' . $key; + + if (isset($this->options[$prefixedKey])) { + return $prefixedKey; + } + + // plain keys without dots don't need further investigation + // since they can never be from a plugin. + if (strpos($key, '.') === false) { + return $prefixedKey; + } + + // try to extract the plugin name + $parts = explode('.', $key); + $pluginName = implode('/', array_slice($parts, 0, 2)); + $pluginPrefix = implode('.', array_slice($parts, 0, 2)); + $cacheName = implode('.', array_slice($parts, 2)); + + // check if such a plugin exists + if ($this->plugin($pluginName)) { + return empty($cacheName) === true ? $pluginPrefix . '.cache' : $pluginPrefix . '.cache.' . $cacheName; + } + + return $prefixedKey; + } +} diff --git a/kirby/src/Cms/AppErrors.php b/kirby/src/Cms/AppErrors.php new file mode 100644 index 0000000..28a2c34 --- /dev/null +++ b/kirby/src/Cms/AppErrors.php @@ -0,0 +1,189 @@ + + * @link https://getkirby.com + * @copyright Bastian Allgeier GmbH + * @license https://getkirby.com/license + */ +trait AppErrors +{ + /** + * Whoops instance cache + * + * @var \Whoops\Run + */ + protected $whoops; + + /** + * Registers the PHP error handler for CLI usage + * + * @return void + */ + protected function handleCliErrors(): void + { + $this->setWhoopsHandler(new PlainTextHandler()); + } + + /** + * Registers the PHP error handler + * based on the environment + * + * @return void + */ + protected function handleErrors(): void + { + if ($this->request()->cli() === true) { + $this->handleCliErrors(); + return; + } + + if ($this->visitor()->prefersJson() === true) { + $this->handleJsonErrors(); + return; + } + + $this->handleHtmlErrors(); + } + + /** + * Registers the PHP error handler for HTML output + * + * @return void + */ + protected function handleHtmlErrors(): void + { + $handler = null; + + if ($this->option('debug') === true) { + if ($this->option('whoops', true) === true) { + $handler = new PrettyPageHandler(); + $handler->setPageTitle('Kirby CMS Debugger'); + $handler->setResourcesPath(dirname(__DIR__, 2) . '/assets'); + $handler->addCustomCss('whoops.css'); + + if ($editor = $this->option('editor')) { + $handler->setEditor($editor); + } + } + } else { + $handler = new CallbackHandler(function ($exception, $inspector, $run) { + $fatal = $this->option('fatal'); + + if (is_a($fatal, 'Closure') === true) { + echo $fatal($this, $exception); + } else { + include $this->root('kirby') . '/views/fatal.php'; + } + + return Handler::QUIT; + }); + } + + if ($handler !== null) { + $this->setWhoopsHandler($handler); + } else { + $this->unsetWhoopsHandler(); + } + } + + /** + * Registers the PHP error handler for JSON output + * + * @return void + */ + protected function handleJsonErrors(): void + { + $handler = new CallbackHandler(function ($exception, $inspector, $run) { + if (is_a($exception, 'Kirby\Exception\Exception') === true) { + $httpCode = $exception->getHttpCode(); + $code = $exception->getCode(); + $details = $exception->getDetails(); + } elseif (is_a($exception, '\Throwable') === true) { + $httpCode = 500; + $code = $exception->getCode(); + $details = null; + } else { + $httpCode = 500; + $code = 500; + $details = null; + } + + if ($this->option('debug') === true) { + echo Response::json([ + 'status' => 'error', + 'exception' => get_class($exception), + 'code' => $code, + 'message' => $exception->getMessage(), + 'details' => $details, + 'file' => ltrim($exception->getFile(), $_SERVER['DOCUMENT_ROOT'] ?? null), + 'line' => $exception->getLine(), + ], $httpCode); + } else { + echo Response::json([ + 'status' => 'error', + 'code' => $code, + 'details' => $details, + 'message' => 'An unexpected error occurred! Enable debug mode for more info: https://getkirby.com/docs/reference/system/options/debug', + ], $httpCode); + } + + return Handler::QUIT; + }); + + $this->setWhoopsHandler($handler); + $this->whoops()->sendHttpCode(false); + } + + /** + * Enables Whoops with the specified handler + * + * @param Callable|\Whoops\Handler\HandlerInterface $handler + * @return void + */ + protected function setWhoopsHandler($handler): void + { + $whoops = $this->whoops(); + $whoops->clearHandlers(); + $whoops->pushHandler($handler); + $whoops->register(); // will only do something if not already registered + } + + /** + * Clears the Whoops handlers and disables Whoops + * + * @return void + */ + protected function unsetWhoopsHandler(): void + { + $whoops = $this->whoops(); + $whoops->clearHandlers(); + $whoops->unregister(); // will only do something if currently registered + } + + /** + * Returns the Whoops error handler instance + * + * @return \Whoops\Run + */ + protected function whoops() + { + if ($this->whoops !== null) { + return $this->whoops; + } + + return $this->whoops = new Whoops(); + } +} diff --git a/kirby/src/Cms/AppPlugins.php b/kirby/src/Cms/AppPlugins.php new file mode 100644 index 0000000..03fca7c --- /dev/null +++ b/kirby/src/Cms/AppPlugins.php @@ -0,0 +1,816 @@ + + * @link https://getkirby.com + * @copyright Bastian Allgeier GmbH + * @license https://getkirby.com/license + */ +trait AppPlugins +{ + /** + * A list of all registered plugins + * + * @var array + */ + protected static $plugins = []; + + /** + * Cache for system extensions + * + * @var array + */ + protected static $systemExtensions = null; + + /** + * The extension registry + * + * @var array + */ + protected $extensions = [ + // load options first to make them available for the rest + 'options' => [], + + // other plugin types + 'api' => [], + 'authChallenges' => [], + 'blueprints' => [], + 'cacheTypes' => [], + 'collections' => [], + 'components' => [], + 'controllers' => [], + 'collectionFilters' => [], + 'collectionMethods' => [], + 'fieldMethods' => [], + 'fileMethods' => [], + 'filesMethods' => [], + 'fields' => [], + 'hooks' => [], + 'pages' => [], + 'pageMethods' => [], + 'pagesMethods' => [], + 'pageModels' => [], + 'permissions' => [], + 'routes' => [], + 'sections' => [], + 'siteMethods' => [], + 'snippets' => [], + 'tags' => [], + 'templates' => [], + 'thirdParty' => [], + 'translations' => [], + 'userMethods' => [], + 'userModels' => [], + 'usersMethods' => [], + 'validators' => [] + ]; + + /** + * Flag when plugins have been loaded + * to not load them again + * + * @var bool + */ + protected $pluginsAreLoaded = false; + + /** + * Register all given extensions + * + * @internal + * @param array $extensions + * @param \Kirby\Cms\Plugin $plugin|null The plugin which defined those extensions + * @return array + */ + public function extend(array $extensions, Plugin $plugin = null): array + { + foreach ($this->extensions as $type => $registered) { + if (isset($extensions[$type]) === true) { + $this->{'extend' . $type}($extensions[$type], $plugin); + } + } + + return $this->extensions; + } + + /** + * Registers API extensions + * + * @param array|bool $api + * @return array + */ + protected function extendApi($api): array + { + if (is_array($api) === true) { + if (is_a($api['routes'] ?? [], 'Closure') === true) { + $api['routes'] = $api['routes']($this); + } + + return $this->extensions['api'] = A::merge($this->extensions['api'], $api, A::MERGE_APPEND); + } else { + return $this->extensions['api']; + } + } + + /** + * Registers additional authentication challenges + * + * @param array $challenges + * @return array + */ + protected function extendAuthChallenges(array $challenges): array + { + return $this->extensions['authChallenges'] = Auth::$challenges = array_merge(Auth::$challenges, $challenges); + } + + /** + * Registers additional blueprints + * + * @param array $blueprints + * @return array + */ + protected function extendBlueprints(array $blueprints): array + { + return $this->extensions['blueprints'] = array_merge($this->extensions['blueprints'], $blueprints); + } + + /** + * Registers additional cache types + * + * @param array $cacheTypes + * @return array + */ + protected function extendCacheTypes(array $cacheTypes): array + { + return $this->extensions['cacheTypes'] = array_merge($this->extensions['cacheTypes'], $cacheTypes); + } + + /** + * Registers additional collection filters + * + * @param array $filters + * @return array + */ + protected function extendCollectionFilters(array $filters): array + { + return $this->extensions['collectionFilters'] = ToolkitCollection::$filters = array_merge(ToolkitCollection::$filters, $filters); + } + + /** + * Registers additional collection methods + * + * @param array $methods + * @return array + */ + protected function extendCollectionMethods(array $methods): array + { + return $this->extensions['collectionMethods'] = Collection::$methods = array_merge(Collection::$methods, $methods); + } + + /** + * Registers additional collections + * + * @param array $collections + * @return array + */ + protected function extendCollections(array $collections): array + { + return $this->extensions['collections'] = array_merge($this->extensions['collections'], $collections); + } + + /** + * Registers core components + * + * @param array $components + * @return array + */ + protected function extendComponents(array $components): array + { + return $this->extensions['components'] = array_merge($this->extensions['components'], $components); + } + + /** + * Registers additional controllers + * + * @param array $controllers + * @return array + */ + protected function extendControllers(array $controllers): array + { + return $this->extensions['controllers'] = array_merge($this->extensions['controllers'], $controllers); + } + + /** + * Registers additional file methods + * + * @param array $methods + * @return array + */ + protected function extendFileMethods(array $methods): array + { + return $this->extensions['fileMethods'] = File::$methods = array_merge(File::$methods, $methods); + } + + /** + * Registers additional files methods + * + * @param array $methods + * @return array + */ + protected function extendFilesMethods(array $methods): array + { + return $this->extensions['filesMethods'] = Files::$methods = array_merge(Files::$methods, $methods); + } + + /** + * Registers additional field methods + * + * @param array $methods + * @return array + */ + protected function extendFieldMethods(array $methods): array + { + return $this->extensions['fieldMethods'] = Field::$methods = array_merge(Field::$methods, array_change_key_case($methods)); + } + + /** + * Registers Panel fields + * + * @param array $fields + * @return array + */ + protected function extendFields(array $fields): array + { + return $this->extensions['fields'] = FormField::$types = array_merge(FormField::$types, $fields); + } + + /** + * Registers hooks + * + * @param array $hooks + * @return array + */ + protected function extendHooks(array $hooks): array + { + foreach ($hooks as $name => $callbacks) { + if (isset($this->extensions['hooks'][$name]) === false) { + $this->extensions['hooks'][$name] = []; + } + + if (is_array($callbacks) === false) { + $callbacks = [$callbacks]; + } + + foreach ($callbacks as $callback) { + $this->extensions['hooks'][$name][] = $callback; + } + } + + return $this->extensions['hooks']; + } + + /** + * Registers markdown component + * + * @param Closure $markdown + * @return Closure + */ + protected function extendMarkdown(Closure $markdown) + { + return $this->extensions['markdown'] = $markdown; + } + + /** + * Registers additional options + * + * @param array $options + * @param \Kirby\Cms\Plugin|null $plugin + * @return array + */ + protected function extendOptions(array $options, Plugin $plugin = null): array + { + if ($plugin !== null) { + $options = [$plugin->prefix() => $options]; + } + + return $this->extensions['options'] = $this->options = A::merge($options, $this->options, A::MERGE_REPLACE); + } + + /** + * Registers additional page methods + * + * @param array $methods + * @return array + */ + protected function extendPageMethods(array $methods): array + { + return $this->extensions['pageMethods'] = Page::$methods = array_merge(Page::$methods, $methods); + } + + /** + * Registers additional pages methods + * + * @param array $methods + * @return array + */ + protected function extendPagesMethods(array $methods): array + { + return $this->extensions['pagesMethods'] = Pages::$methods = array_merge(Pages::$methods, $methods); + } + + /** + * Registers additional page models + * + * @param array $models + * @return array + */ + protected function extendPageModels(array $models): array + { + return $this->extensions['pageModels'] = Page::$models = array_merge(Page::$models, $models); + } + + /** + * Registers pages + * + * @param array $pages + * @return array + */ + protected function extendPages(array $pages): array + { + return $this->extensions['pages'] = array_merge($this->extensions['pages'], $pages); + } + + /** + * Registers additional permissions + * + * @param array $permissions + * @param \Kirby\Cms\Plugin|null $plugin + * @return array + */ + protected function extendPermissions(array $permissions, Plugin $plugin = null): array + { + if ($plugin !== null) { + $permissions = [$plugin->prefix() => $permissions]; + } + + return $this->extensions['permissions'] = Permissions::$extendedActions = array_merge(Permissions::$extendedActions, $permissions); + } + + /** + * Registers additional routes + * + * @param array|\Closure $routes + * @return array + */ + protected function extendRoutes($routes): array + { + if (is_a($routes, 'Closure') === true) { + $routes = $routes($this); + } + + return $this->extensions['routes'] = array_merge($this->extensions['routes'], $routes); + } + + /** + * Registers Panel sections + * + * @param array $sections + * @return array + */ + protected function extendSections(array $sections): array + { + return $this->extensions['sections'] = Section::$types = array_merge(Section::$types, $sections); + } + + /** + * Registers additional site methods + * + * @param array $methods + * @return array + */ + protected function extendSiteMethods(array $methods): array + { + return $this->extensions['siteMethods'] = Site::$methods = array_merge(Site::$methods, $methods); + } + + /** + * Registers SmartyPants component + * + * @param \Closure $smartypants + * @return \Closure + */ + protected function extendSmartypants(Closure $smartypants) + { + return $this->extensions['smartypants'] = $smartypants; + } + + /** + * Registers additional snippets + * + * @param array $snippets + * @return array + */ + protected function extendSnippets(array $snippets): array + { + return $this->extensions['snippets'] = array_merge($this->extensions['snippets'], $snippets); + } + + /** + * Registers additional KirbyTags + * + * @param array $tags + * @return array + */ + protected function extendTags(array $tags): array + { + return $this->extensions['tags'] = KirbyTag::$types = array_merge(KirbyTag::$types, array_change_key_case($tags)); + } + + /** + * Registers additional templates + * + * @param array $templates + * @return array + */ + protected function extendTemplates(array $templates): array + { + return $this->extensions['templates'] = array_merge($this->extensions['templates'], $templates); + } + + /** + * Registers translations + * + * @param array $translations + * @return array + */ + protected function extendTranslations(array $translations): array + { + return $this->extensions['translations'] = array_replace_recursive($this->extensions['translations'], $translations); + } + + /** + * Add third party extensions to the registry + * so they can be used as plugins for plugins + * for example. + * + * @param array $extensions + * @return array + */ + protected function extendThirdParty(array $extensions): array + { + return $this->extensions['thirdParty'] = array_replace_recursive($this->extensions['thirdParty'], $extensions); + } + + /** + * Registers additional user methods + * + * @param array $methods + * @return array + */ + protected function extendUserMethods(array $methods): array + { + return $this->extensions['userMethods'] = User::$methods = array_merge(User::$methods, $methods); + } + + /** + * Registers additional user models + * + * @param array $models + * @return array + */ + protected function extendUserModels(array $models): array + { + return $this->extensions['userModels'] = User::$models = array_merge(User::$models, $models); + } + + /** + * Registers additional users methods + * + * @param array $methods + * @return array + */ + protected function extendUsersMethods(array $methods): array + { + return $this->extensions['usersMethods'] = Users::$methods = array_merge(Users::$methods, $methods); + } + + /** + * Registers additional custom validators + * + * @param array $validators + * @return array + */ + protected function extendValidators(array $validators): array + { + return $this->extensions['validators'] = V::$validators = array_merge(V::$validators, $validators); + } + + /** + * Returns a given extension by type and name + * + * @internal + * @param string $type i.e. `'hooks'` + * @param string $name i.e. `'page.delete:before'` + * @param mixed $fallback + * @return mixed + */ + public function extension(string $type, string $name, $fallback = null) + { + return $this->extensions($type)[$name] ?? $fallback; + } + + /** + * Returns the extensions registry + * + * @internal + * @param string|null $type + * @return array + */ + public function extensions(string $type = null) + { + if ($type === null) { + return $this->extensions; + } + + return $this->extensions[$type] ?? []; + } + + /** + * Load extensions from site folders. + * This is only used for models for now, but + * could be extended later + */ + protected function extensionsFromFolders() + { + $models = []; + + foreach (glob($this->root('models') . '/*.php') as $model) { + $name = F::name($model); + $class = str_replace(['.', '-', '_'], '', $name) . 'Page'; + + // load the model class + F::loadOnce($model); + + if (class_exists($class) === true) { + $models[$name] = $class; + } + } + + $this->extendPageModels($models); + } + + /** + * Register extensions that could be located in + * the options array. I.e. hooks and routes can be + * setup from the config. + * + * @return void + */ + protected function extensionsFromOptions() + { + // register routes and hooks from options + $this->extend([ + 'api' => $this->options['api'] ?? [], + 'routes' => $this->options['routes'] ?? [], + 'hooks' => $this->options['hooks'] ?? [] + ]); + } + + /** + * Apply all plugin extensions + * + * @return void + */ + protected function extensionsFromPlugins() + { + // register all their extensions + foreach ($this->plugins() as $plugin) { + $extends = $plugin->extends(); + + if (empty($extends) === false) { + $this->extend($extends, $plugin); + } + } + } + + /** + * Apply all passed extensions + * + * @param array $props + * @return void + */ + protected function extensionsFromProps(array $props) + { + $this->extend($props); + } + + /** + * Apply all default extensions + * + * @return void + */ + protected function extensionsFromSystem() + { + $root = $this->root('kirby'); + + // load static extensions only once + if (static::$systemExtensions === null) { + // Form Field Mixins + FormField::$mixins['datetime'] = include $root . '/config/fields/mixins/datetime.php'; + FormField::$mixins['filepicker'] = include $root . '/config/fields/mixins/filepicker.php'; + FormField::$mixins['min'] = include $root . '/config/fields/mixins/min.php'; + FormField::$mixins['options'] = include $root . '/config/fields/mixins/options.php'; + FormField::$mixins['pagepicker'] = include $root . '/config/fields/mixins/pagepicker.php'; + FormField::$mixins['picker'] = include $root . '/config/fields/mixins/picker.php'; + FormField::$mixins['upload'] = include $root . '/config/fields/mixins/upload.php'; + FormField::$mixins['userpicker'] = include $root . '/config/fields/mixins/userpicker.php'; + + // Tag Aliases + KirbyTag::$aliases = [ + 'youtube' => 'video', + 'vimeo' => 'video' + ]; + + // Field method aliases + Field::$aliases = [ + 'bool' => 'toBool', + 'esc' => 'escape', + 'excerpt' => 'toExcerpt', + 'float' => 'toFloat', + 'h' => 'html', + 'int' => 'toInt', + 'kt' => 'kirbytext', + 'kti' => 'kirbytextinline', + 'link' => 'toLink', + 'md' => 'markdown', + 'sp' => 'smartypants', + 'v' => 'isValid', + 'x' => 'xml' + ]; + + // blueprint presets + PageBlueprint::$presets['pages'] = include $root . '/config/presets/pages.php'; + PageBlueprint::$presets['page'] = include $root . '/config/presets/page.php'; + PageBlueprint::$presets['files'] = include $root . '/config/presets/files.php'; + + // section mixins + Section::$mixins['empty'] = include $root . '/config/sections/mixins/empty.php'; + Section::$mixins['headline'] = include $root . '/config/sections/mixins/headline.php'; + Section::$mixins['help'] = include $root . '/config/sections/mixins/help.php'; + Section::$mixins['layout'] = include $root . '/config/sections/mixins/layout.php'; + Section::$mixins['max'] = include $root . '/config/sections/mixins/max.php'; + Section::$mixins['min'] = include $root . '/config/sections/mixins/min.php'; + Section::$mixins['pagination'] = include $root . '/config/sections/mixins/pagination.php'; + Section::$mixins['parent'] = include $root . '/config/sections/mixins/parent.php'; + + // section types + Section::$types['info'] = include $root . '/config/sections/info.php'; + Section::$types['pages'] = include $root . '/config/sections/pages.php'; + Section::$types['files'] = include $root . '/config/sections/files.php'; + Section::$types['fields'] = include $root . '/config/sections/fields.php'; + + static::$systemExtensions = [ + 'components' => include $root . '/config/components.php', + 'blueprints' => include $root . '/config/blueprints.php', + 'fields' => include $root . '/config/fields.php', + 'fieldMethods' => include $root . '/config/methods.php', + 'snippets' => include $root . '/config/snippets.php', + 'tags' => include $root . '/config/tags.php', + 'templates' => include $root . '/config/templates.php' + ]; + } + + // default auth challenges + $this->extendAuthChallenges([ + 'email' => 'Kirby\Cms\Auth\EmailChallenge' + ]); + + // default cache types + $this->extendCacheTypes([ + 'apcu' => 'Kirby\Cache\ApcuCache', + 'file' => 'Kirby\Cache\FileCache', + 'memcached' => 'Kirby\Cache\MemCached', + 'memory' => 'Kirby\Cache\MemoryCache', + ]); + + $this->extendComponents(static::$systemExtensions['components']); + $this->extendBlueprints(static::$systemExtensions['blueprints']); + $this->extendFields(static::$systemExtensions['fields']); + $this->extendFieldMethods((static::$systemExtensions['fieldMethods'])($this)); + $this->extendSnippets(static::$systemExtensions['snippets']); + $this->extendTags(static::$systemExtensions['tags']); + $this->extendTemplates(static::$systemExtensions['templates']); + } + + /** + * Returns the native implementation + * of a core component + * + * @param string $component + * @return \Closure|false + */ + public function nativeComponent(string $component) + { + return static::$systemExtensions['components'][$component] ?? false; + } + + /** + * Kirby plugin factory and getter + * + * @param string $name + * @param array|null $extends If null is passed it will be used as getter. Otherwise as factory. + * @return \Kirby\Cms\Plugin|null + * @throws \Kirby\Exception\DuplicateException + */ + public static function plugin(string $name, array $extends = null) + { + if ($extends === null) { + return static::$plugins[$name] ?? null; + } + + // get the correct root for the plugin + $extends['root'] = $extends['root'] ?? dirname(debug_backtrace()[0]['file']); + + $plugin = new Plugin($name, $extends); + $name = $plugin->name(); + + if (isset(static::$plugins[$name]) === true) { + throw new DuplicateException('The plugin "' . $name . '" has already been registered'); + } + + return static::$plugins[$name] = $plugin; + } + + /** + * Loads and returns all plugins in the site/plugins directory + * Loading only happens on the first call. + * + * @internal + * @param array|null $plugins Can be used to overwrite the plugins registry + * @return array + */ + public function plugins(array $plugins = null): array + { + // overwrite the existing plugins registry + if ($plugins !== null) { + $this->pluginsAreLoaded = true; + return static::$plugins = $plugins; + } + + // don't load plugins twice + if ($this->pluginsAreLoaded === true) { + return static::$plugins; + } + + // load all plugins from site/plugins + $this->pluginsLoader(); + + // mark plugins as loaded to stop doing it twice + $this->pluginsAreLoaded = true; + return static::$plugins; + } + + /** + * Loads all plugins from site/plugins + * + * @return array Array of loaded directories + */ + protected function pluginsLoader(): array + { + $root = $this->root('plugins'); + $loaded = []; + + foreach (Dir::read($root) as $dirname) { + if (in_array(substr($dirname, 0, 1), ['.', '_']) === true) { + continue; + } + + $dir = $root . '/' . $dirname; + $entry = $dir . '/index.php'; + + if (is_dir($dir) !== true || is_file($entry) !== true) { + continue; + } + + F::loadOnce($entry); + + $loaded[] = $dir; + } + + return $loaded; + } +} diff --git a/kirby/src/Cms/AppTranslations.php b/kirby/src/Cms/AppTranslations.php new file mode 100644 index 0000000..a174f85 --- /dev/null +++ b/kirby/src/Cms/AppTranslations.php @@ -0,0 +1,234 @@ + + * @link https://getkirby.com + * @copyright Bastian Allgeier GmbH + * @license https://getkirby.com/license + */ +trait AppTranslations +{ + protected $translations; + + /** + * Setup internationalization + * + * @return void + */ + protected function i18n(): void + { + I18n::$load = function ($locale): array { + $data = []; + + if ($translation = $this->translation($locale)) { + $data = $translation->data(); + } + + // inject translations from the current language + if ( + $this->multilang() === true && + $language = $this->languages()->find($locale) + ) { + $data = array_merge($data, $language->translations()); + } + + + return $data; + }; + + // the actual locale is set using $app->setCurrentTranslation() + I18n::$locale = function (): string { + if ($this->multilang() === true) { + return $this->defaultLanguage()->code(); + } else { + return 'en'; + } + }; + + I18n::$fallback = function (): array { + if ($this->multilang() === true) { + // first try to fall back to the configured default language + $defaultCode = $this->defaultLanguage()->code(); + $fallback = [$defaultCode]; + + // if the default language is specified with a country code + // (e.g. `en-us`), also try with just the language code + if (preg_match('/^([a-z]{2})-[a-z]+$/i', $defaultCode, $matches) === 1) { + $fallback[] = $matches[1]; + } + + // fall back to the complete English translation + // as a last resort + $fallback[] = 'en'; + + return $fallback; + } else { + return ['en']; + } + }; + + I18n::$translations = []; + + // add slug rules based on config option + if ($slugs = $this->option('slugs')) { + // two ways that the option can be defined: + // "slugs" => "de" or "slugs" => ["language" => "de"] + if ($slugs = $slugs['language'] ?? $slugs ?? null) { + Str::$language = Language::loadRules($slugs); + } + } + } + + /** + * Returns the language code that will be used + * for the Panel if no user is logged in or if + * no language is configured for the user + * + * @return string + */ + public function panelLanguage(): string + { + if ($this->multilang() === true) { + $defaultCode = $this->defaultLanguage()->code(); + + // extract the language code from a language that + // contains the country code (e.g. `en-us`) + if (preg_match('/^([a-z]{2})-[a-z]+$/i', $defaultCode, $matches) === 1) { + $defaultCode = $matches[1]; + } + } else { + $defaultCode = 'en'; + } + + return $this->option('panel.language', $defaultCode); + } + + /** + * Load and set the current language if it exists + * Otherwise fall back to the default language + * + * @internal + * @param string|null $languageCode + * @return \Kirby\Cms\Language|null + */ + public function setCurrentLanguage(string $languageCode = null) + { + if ($this->multilang() === false) { + Locale::set($this->option('locale', 'en_US.utf-8')); + return $this->language = null; + } + + if ($language = $this->language($languageCode)) { + $this->language = $language; + } else { + $this->language = $this->defaultLanguage(); + } + + if ($this->language) { + Locale::set($this->language->locale()); + } + + // add language slug rules to Str class + Str::$language = $this->language->rules(); + + return $this->language; + } + + /** + * Set the current translation + * + * @internal + * @param string|null $translationCode + * @return void + */ + public function setCurrentTranslation(string $translationCode = null): void + { + I18n::$locale = $translationCode ?? 'en'; + } + + /** + * Set locale settings + * + * @deprecated 3.5.0 Use `\Kirby\Toolkit\Locale::set()` instead + * @todo Remove in 3.6.0 + * + * @param string|array $locale + */ + public function setLocale($locale): void + { + Locale::set($locale); + } + + /** + * Load a specific translation by locale + * + * @param string|null $locale Locale name or `null` for the current locale + * @return \Kirby\Cms\Translation|null + */ + public function translation(?string $locale = null) + { + $locale = $locale ?? I18n::locale(); + $locale = basename($locale); + + // prefer loading them from the translations collection + if (is_a($this->translations, 'Kirby\Cms\Translations') === true) { + if ($translation = $this->translations()->find($locale)) { + return $translation; + } + } + + // get injected translation data from plugins etc. + $inject = $this->extensions['translations'][$locale] ?? []; + + // inject current language translations + if ($language = $this->language($locale)) { + $inject = array_merge($inject, $language->translations()); + } + + // load from disk instead + return Translation::load($locale, $this->root('i18n:translations') . '/' . $locale . '.json', $inject); + } + + /** + * Returns all available translations + * + * @return \Kirby\Cms\Translations + */ + public function translations() + { + if (is_a($this->translations, 'Kirby\Cms\Translations') === true) { + return $this->translations; + } + + $translations = $this->extensions['translations'] ?? []; + + // injects languages translations + if ($languages = $this->languages()) { + foreach ($languages as $language) { + $languageCode = $language->code(); + $languageTranslations = $language->translations(); + + // merges language translations with extensions translations + if (empty($languageTranslations) === false) { + $translations[$languageCode] = array_merge( + $translations[$languageCode] ?? [], + $languageTranslations + ); + } + } + } + + $this->translations = Translations::load($this->root('i18n:translations'), $translations); + + return $this->translations; + } +} diff --git a/kirby/src/Cms/AppUsers.php b/kirby/src/Cms/AppUsers.php new file mode 100644 index 0000000..0d9fae1 --- /dev/null +++ b/kirby/src/Cms/AppUsers.php @@ -0,0 +1,143 @@ + + * @link https://getkirby.com + * @copyright Bastian Allgeier GmbH + * @license https://getkirby.com/license + */ +trait AppUsers +{ + /** + * Cache for the auth auth layer + * + * @var Auth + */ + protected $auth; + + /** + * Returns the Authentication layer class + * + * @internal + * @return \Kirby\Cms\Auth + */ + public function auth() + { + return $this->auth = $this->auth ?? new Auth($this); + } + + /** + * Become any existing user or disable the current user + * + * @param string|null $who User ID or email address, + * `null` to use the actual user again, + * `'kirby'` for a virtual admin user or + * `'nobody'` to disable the actual user + * @param Closure|null $callback Optional action function that will be run with + * the permissions of the impersonated user; the + * impersonation will be reset afterwards + * @return mixed If called without callback: User that was impersonated; + * if called with callback: Return value from the callback + * @throws \Throwable + */ + public function impersonate(?string $who = null, ?Closure $callback = null) + { + $auth = $this->auth(); + + $userBefore = $auth->currentUserFromImpersonation(); + $userAfter = $auth->impersonate($who); + + if ($callback === null) { + return $userAfter; + } + + try { + // bind the App object to the callback + return $callback->call($this, $userAfter); + } catch (Throwable $e) { + throw $e; + } finally { + // ensure that the impersonation is *always* reset + // to the original value, even if an error occurred + $auth->impersonate($userBefore !== null ? $userBefore->id() : null); + } + } + + /** + * Set the currently active user id + * + * @param \Kirby\Cms\User|string $user + * @return \Kirby\Cms\App + */ + protected function setUser($user = null) + { + $this->user = $user; + return $this; + } + + /** + * Create your own set of app users + * + * @param array|null $users + * @return \Kirby\Cms\App + */ + protected function setUsers(array $users = null) + { + if ($users !== null) { + $this->users = Users::factory($users, [ + 'kirby' => $this + ]); + } + + return $this; + } + + /** + * Returns a specific user by id + * or the current user if no id is given + * + * @param string|null $id + * @param bool $allowImpersonation If set to false, only the actually + * logged in user will be returned + * (when `$id` is passed as `null`) + * @return \Kirby\Cms\User|null + */ + public function user(?string $id = null, bool $allowImpersonation = true) + { + if ($id !== null) { + return $this->users()->find($id); + } + + if ($allowImpersonation === true && is_string($this->user) === true) { + return $this->auth()->impersonate($this->user); + } else { + try { + return $this->auth()->user(null, $allowImpersonation); + } catch (Throwable $e) { + return null; + } + } + } + + /** + * Returns all users + * + * @return \Kirby\Cms\Users + */ + public function users() + { + if (is_a($this->users, 'Kirby\Cms\Users') === true) { + return $this->users; + } + + return $this->users = Users::load($this->root('accounts'), ['kirby' => $this]); + } +} diff --git a/kirby/src/Cms/Asset.php b/kirby/src/Cms/Asset.php new file mode 100644 index 0000000..9c68460 --- /dev/null +++ b/kirby/src/Cms/Asset.php @@ -0,0 +1,126 @@ + + * @link https://getkirby.com + * @copyright Bastian Allgeier GmbH + * @license https://getkirby.com/license + */ +class Asset +{ + use FileFoundation; + use FileModifications; + use Properties; + + /** + * @var string + */ + protected $path; + + /** + * Creates a new Asset object + * for the given path. + * + * @param string $path + */ + public function __construct(string $path) + { + $this->setPath(dirname($path)); + $this->setRoot($this->kirby()->root('index') . '/' . $path); + $this->setUrl($this->kirby()->url('index') . '/' . $path); + } + + /** + * Returns the alternative text for the asset + * + * @return null + */ + public function alt() + { + return null; + } + + /** + * Returns a unique id for the asset + * + * @return string + */ + public function id(): string + { + return $this->root(); + } + + /** + * Create a unique media hash + * + * @return string + */ + public function mediaHash(): string + { + return crc32($this->filename()) . '-' . $this->modified(); + } + + /** + * Returns the relative path starting at the media folder + * + * @return string + */ + public function mediaPath(): string + { + return 'assets/' . $this->path() . '/' . $this->mediaHash() . '/' . $this->filename(); + } + + /** + * Returns the absolute path to the file in the public media folder + * + * @return string + */ + public function mediaRoot(): string + { + return $this->kirby()->root('media') . '/' . $this->mediaPath(); + } + + /** + * Returns the absolute Url to the file in the public media folder + * + * @return string + */ + public function mediaUrl(): string + { + return $this->kirby()->url('media') . '/' . $this->mediaPath(); + } + + /** + * Returns the path of the file from the web root, + * excluding the filename + * + * @return string + */ + public function path(): string + { + return $this->path; + } + + /** + * Setter for the path + * + * @param string $path + * @return $this + */ + protected function setPath(string $path) + { + $this->path = $path === '.' ? '' : $path; + return $this; + } +} diff --git a/kirby/src/Cms/Auth.php b/kirby/src/Cms/Auth.php new file mode 100644 index 0000000..5d04c84 --- /dev/null +++ b/kirby/src/Cms/Auth.php @@ -0,0 +1,872 @@ + + * @link https://getkirby.com + * @copyright Bastian Allgeier GmbH + * @license https://getkirby.com/license + */ +class Auth +{ + /** + * Available auth challenge classes + * from the core and plugins + * + * @var array + */ + public static $challenges = []; + + /** + * Currently impersonated user + * + * @var \Kirby\Cms\User|null + */ + protected $impersonate; + + /** + * Kirby instance + * + * @var \Kirby\Cms\App + */ + protected $kirby; + + /** + * Cache of the auth status object + * + * @var \Kirby\Cms\Auth\Status + */ + protected $status; + + /** + * Instance of the currently logged in user or + * `false` if the user was not yet determined + * + * @var \Kirby\Cms\User|null|false + */ + protected $user = false; + + /** + * Exception that was thrown while + * determining the current user + * + * @var \Throwable + */ + protected $userException; + + /** + * @param \Kirby\Cms\App $kirby + * @codeCoverageIgnore + */ + public function __construct(App $kirby) + { + $this->kirby = $kirby; + } + + /** + * Creates an authentication challenge + * (one-time auth code) + * @since 3.5.0 + * + * @param string $email + * @param bool $long If `true`, a long session will be created + * @param string $mode Either 'login' or 'password-reset' + * @return \Kirby\Cms\Auth\Status + * + * @throws \Kirby\Exception\LogicException If there is no suitable authentication challenge (only in debug mode) + * @throws \Kirby\Exception\NotFoundException If the user does not exist (only in debug mode) + * @throws \Kirby\Exception\PermissionException If the rate limit is exceeded + */ + public function createChallenge(string $email, bool $long = false, string $mode = 'login') + { + // ensure that email addresses with IDN domains are in Unicode format + $email = Idn::decodeEmail($email); + + if ($this->isBlocked($email) === true) { + $this->kirby->trigger('user.login:failed', compact('email')); + + if ($this->kirby->option('debug') === true) { + $message = 'Rate limit exceeded'; + } else { + // avoid leaking security-relevant information + $message = ['key' => 'access.login']; + } + + throw new PermissionException($message); + } + + // rate-limit the number of challenges for DoS/DDoS protection + $this->track($email, false); + + $session = $this->kirby->session([ + 'createMode' => 'cookie', + 'long' => $long === true + ]); + + $challenge = null; + if ($user = $this->kirby->users()->find($email)) { + $timeout = $this->kirby->option('auth.challenge.timeout', 10 * 60); + + foreach ($this->enabledChallenges() as $name) { + $class = static::$challenges[$name] ?? null; + if ( + $class && + class_exists($class) === true && + is_subclass_of($class, 'Kirby\Cms\Auth\Challenge') === true && + $class::isAvailable($user, $mode) === true + ) { + $challenge = $name; + $code = $class::create($user, compact('mode', 'timeout')); + + $session->set('kirby.challenge.type', $challenge); + + if ($code !== null) { + $session->set('kirby.challenge.code', password_hash($code, PASSWORD_DEFAULT)); + $session->set('kirby.challenge.timeout', time() + $timeout); + } + + break; + } + } + + // if no suitable challenge was found, `$challenge === null` at this point; + // only leak this in debug mode + if ($challenge === null && $this->kirby->option('debug') === true) { + throw new LogicException('Could not find a suitable authentication challenge'); + } + } else { + $this->kirby->trigger('user.login:failed', compact('email')); + + // only leak the non-existing user in debug mode + if ($this->kirby->option('debug') === true) { + throw new NotFoundException([ + 'key' => 'user.notFound', + 'data' => [ + 'name' => $email + ] + ]); + } + } + + // always set the email, even if the challenge won't be + // created to avoid leaking whether the user exists + $session->set('kirby.challenge.email', $email); + + // sleep for a random amount of milliseconds + // to make automated attacks harder and to + // avoid leaking whether the user exists + usleep(random_int(1000, 300000)); + + // clear the status cache + $this->status = null; + + return $this->status($session, false); + } + + /** + * Returns the csrf token if it exists and if it is valid + * + * @return string|false + */ + public function csrf() + { + // get the csrf from the header + $fromHeader = $this->kirby->request()->csrf(); + + // check for a predefined csrf or use the one from session + $fromSession = $this->kirby->option('api.csrf', csrf()); + + // compare both tokens + if (hash_equals((string)$fromSession, (string)$fromHeader) !== true) { + return false; + } + + return $fromSession; + } + + /** + * Returns the logged in user by checking + * for a basic authentication header with + * valid credentials + * + * @param \Kirby\Http\Request\Auth\BasicAuth|null $auth + * @return \Kirby\Cms\User|null + * @throws \Kirby\Exception\InvalidArgumentException if the authorization header is invalid + * @throws \Kirby\Exception\PermissionException if basic authentication is not allowed + */ + public function currentUserFromBasicAuth(BasicAuth $auth = null) + { + if ($this->kirby->option('api.basicAuth', false) !== true) { + throw new PermissionException('Basic authentication is not activated'); + } + + // if logging in with password is disabled, basic auth cannot be possible either + $loginMethods = $this->kirby->system()->loginMethods(); + if (isset($loginMethods['password']) !== true) { + throw new PermissionException('Login with password is not enabled'); + } + + // if any login method requires 2FA, basic auth without 2FA would be a weakness + foreach ($loginMethods as $method) { + if (isset($method['2fa']) === true && $method['2fa'] === true) { + throw new PermissionException('Basic authentication cannot be used with 2FA'); + } + } + + $request = $this->kirby->request(); + $auth = $auth ?? $request->auth(); + + if (!$auth || $auth->type() !== 'basic') { + throw new InvalidArgumentException('Invalid authorization header'); + } + + // only allow basic auth when https is enabled or insecure requests permitted + if ($request->ssl() === false && $this->kirby->option('api.allowInsecure', false) !== true) { + throw new PermissionException('Basic authentication is only allowed over HTTPS'); + } + + return $this->validatePassword($auth->username(), $auth->password()); + } + + /** + * Returns the currently impersonated user + * + * @return \Kirby\Cms\User|null + */ + public function currentUserFromImpersonation() + { + return $this->impersonate; + } + + /** + * Returns the logged in user by checking + * the current session and finding a valid + * valid user id in there + * + * @param \Kirby\Session\Session|array|null $session + * @return \Kirby\Cms\User|null + */ + public function currentUserFromSession($session = null) + { + $session = $this->session($session); + + $id = $session->data()->get('kirby.userId'); + + if (is_string($id) !== true) { + return null; + } + + if ($user = $this->kirby->users()->find($id)) { + // in case the session needs to be updated, do it now + // for better performance + $session->commit(); + return $user; + } + + return null; + } + + /** + * Returns the list of enabled challenges in the + * configured order + * @since 3.5.1 + * + * @return array + */ + public function enabledChallenges(): array + { + return A::wrap($this->kirby->option('auth.challenges', ['email'])); + } + + /** + * Become any existing user or disable the current user + * + * @param string|null $who User ID or email address, + * `null` to use the actual user again, + * `'kirby'` for a virtual admin user or + * `'nobody'` to disable the actual user + * @return \Kirby\Cms\User|null + * @throws \Kirby\Exception\NotFoundException if the given user cannot be found + */ + public function impersonate(?string $who = null) + { + // clear the status cache + $this->status = null; + + switch ($who) { + case null: + return $this->impersonate = null; + case 'kirby': + return $this->impersonate = new User([ + 'email' => 'kirby@getkirby.com', + 'id' => 'kirby', + 'role' => 'admin', + ]); + case 'nobody': + return $this->impersonate = new User([ + 'email' => 'nobody@getkirby.com', + 'id' => 'nobody', + 'role' => 'nobody', + ]); + default: + if ($user = $this->kirby->users()->find($who)) { + return $this->impersonate = $user; + } + + throw new NotFoundException('The user "' . $who . '" cannot be found'); + } + } + + /** + * Returns the hashed ip of the visitor + * which is used to track invalid logins + * + * @return string + */ + public function ipHash(): string + { + $hash = hash('sha256', $this->kirby->visitor()->ip()); + + // only use the first 50 chars to ensure privacy + return substr($hash, 0, 50); + } + + /** + * Check if logins are blocked for the current ip or email + * + * @param string $email + * @return bool + */ + public function isBlocked(string $email): bool + { + $ip = $this->ipHash(); + $log = $this->log(); + $trials = $this->kirby->option('auth.trials', 10); + + if ($entry = ($log['by-ip'][$ip] ?? null)) { + if ($entry['trials'] >= $trials) { + return true; + } + } + + if ($this->kirby->users()->find($email)) { + if ($entry = ($log['by-email'][$email] ?? null)) { + if ($entry['trials'] >= $trials) { + return true; + } + } + } + + return false; + } + + /** + * Login a user by email and password + * + * @param string $email + * @param string $password + * @param bool $long + * @return \Kirby\Cms\User + * + * @throws \Kirby\Exception\PermissionException If the rate limit was exceeded or if any other error occured with debug mode off + * @throws \Kirby\Exception\NotFoundException If the email was invalid + * @throws \Kirby\Exception\InvalidArgumentException If the password is not valid (via `$user->login()`) + */ + public function login(string $email, string $password, bool $long = false) + { + // session options + $options = [ + 'createMode' => 'cookie', + 'long' => $long === true + ]; + + // validate the user and log in to the session + $user = $this->validatePassword($email, $password); + $user->loginPasswordless($options); + + // clear the status cache + $this->status = null; + + return $user; + } + + /** + * Login a user by email, password and auth challenge + * @since 3.5.0 + * + * @param string $email + * @param string $password + * @param bool $long + * @return \Kirby\Cms\Auth\Status + * + * @throws \Kirby\Exception\PermissionException If the rate limit was exceeded or if any other error occured with debug mode off + * @throws \Kirby\Exception\NotFoundException If the email was invalid + * @throws \Kirby\Exception\InvalidArgumentException If the password is not valid (via `$user->login()`) + */ + public function login2fa(string $email, string $password, bool $long = false) + { + $this->validatePassword($email, $password); + return $this->createChallenge($email, $long, '2fa'); + } + + /** + * Sets a user object as the current user in the cache + * @internal + * + * @param \Kirby\Cms\User $user + * @return void + */ + public function setUser(User $user): void + { + // stop impersonating + $this->impersonate = null; + + $this->user = $user; + + // clear the status cache + $this->status = null; + } + + /** + * Returns the authentication status object + * @since 3.5.1 + * + * @param \Kirby\Session\Session|array|null $session + * @param bool $allowImpersonation If set to false, only the actually + * logged in user will be returned + * @return \Kirby\Cms\Auth\Status + */ + public function status($session = null, bool $allowImpersonation = true) + { + // try to return from cache + if ($this->status && $session === null && $allowImpersonation === true) { + return $this->status; + } + + $sessionObj = $this->session($session); + + $props = ['kirby' => $this->kirby]; + if ($user = $this->user($sessionObj, $allowImpersonation)) { + // a user is currently logged in + if ($allowImpersonation === true && $this->impersonate !== null) { + $props['status'] = 'impersonated'; + } else { + $props['status'] = 'active'; + } + + $props['email'] = $user->email(); + } elseif ($email = $sessionObj->get('kirby.challenge.email')) { + // a challenge is currently pending + $props['status'] = 'pending'; + $props['email'] = $email; + $props['challenge'] = $sessionObj->get('kirby.challenge.type'); + $props['challengeFallback'] = A::last($this->enabledChallenges()); + } else { + // no active authentication + $props['status'] = 'inactive'; + } + + $status = new Status($props); + + // only cache the default object + if ($session === null && $allowImpersonation === true) { + $this->status = $status; + } + + return $status; + } + + /** + * Validates the user credentials and returns the user object on success; + * otherwise logs the failed attempt + * + * @param string $email + * @param string $password + * @return \Kirby\Cms\User + * + * @throws \Kirby\Exception\PermissionException If the rate limit was exceeded or if any other error occured with debug mode off + * @throws \Kirby\Exception\NotFoundException If the email was invalid + * @throws \Kirby\Exception\InvalidArgumentException If the password is not valid (via `$user->login()`) + */ + public function validatePassword(string $email, string $password) + { + // ensure that email addresses with IDN domains are in Unicode format + $email = Idn::decodeEmail($email); + + // check for blocked ips + if ($this->isBlocked($email) === true) { + $this->kirby->trigger('user.login:failed', compact('email')); + + if ($this->kirby->option('debug') === true) { + $message = 'Rate limit exceeded'; + } else { + // avoid leaking security-relevant information + $message = ['key' => 'access.login']; + } + + throw new PermissionException($message); + } + + // validate the user + try { + if ($user = $this->kirby->users()->find($email)) { + if ($user->validatePassword($password) === true) { + return $user; + } + } + + throw new NotFoundException([ + 'key' => 'user.notFound', + 'data' => [ + 'name' => $email + ] + ]); + } catch (Throwable $e) { + // log invalid login trial + $this->track($email); + + // sleep for a random amount of milliseconds + // to make automated attacks harder + usleep(random_int(1000, 2000000)); + + // keep throwing the original error in debug mode, + // otherwise hide it to avoid leaking security-relevant information + if ($this->kirby->option('debug') === true) { + throw $e; + } else { + throw new PermissionException(['key' => 'access.login']); + } + } + } + + /** + * Returns the absolute path to the logins log + * + * @return string + */ + public function logfile(): string + { + return $this->kirby->root('accounts') . '/.logins'; + } + + /** + * Read all tracked logins + * + * @return array + */ + public function log(): array + { + try { + $log = Data::read($this->logfile(), 'json'); + $read = true; + } catch (Throwable $e) { + $log = []; + $read = false; + } + + // ensure that the category arrays are defined + $log['by-ip'] = $log['by-ip'] ?? []; + $log['by-email'] = $log['by-email'] ?? []; + + // remove all elements on the top level with different keys (old structure) + $log = array_intersect_key($log, array_flip(['by-ip', 'by-email'])); + + // remove entries that are no longer needed + $originalLog = $log; + $time = time() - $this->kirby->option('auth.timeout', 3600); + foreach ($log as $category => $entries) { + $log[$category] = array_filter($entries, function ($entry) use ($time) { + return $entry['time'] > $time; + }); + } + + // write new log to the file system if it changed + if ($read === false || $log !== $originalLog) { + if (count($log['by-ip']) === 0 && count($log['by-email']) === 0) { + F::remove($this->logfile()); + } else { + Data::write($this->logfile(), $log, 'json'); + } + } + + return $log; + } + + /** + * Logout the current user + * + * @return void + */ + public function logout(): void + { + // stop impersonating; + // ensures that we log out the actually logged in user + $this->impersonate = null; + + // logout the current user if it exists + if ($user = $this->user()) { + $user->logout(); + } + + // clear the pending challenge + $session = $this->kirby->session(); + $session->remove('kirby.challenge.code'); + $session->remove('kirby.challenge.email'); + $session->remove('kirby.challenge.timeout'); + $session->remove('kirby.challenge.type'); + + // clear the status cache + $this->status = null; + } + + /** + * Clears the cached user data after logout + * @internal + * + * @return void + */ + public function flush(): void + { + $this->impersonate = null; + $this->status = null; + $this->user = null; + } + + /** + * Tracks a login + * + * @param string|null $email + * @param bool $triggerHook If `false`, no user.login:failed hook is triggered + * @return bool + */ + public function track(?string $email, bool $triggerHook = true): bool + { + if ($triggerHook === true) { + $this->kirby->trigger('user.login:failed', compact('email')); + } + + $ip = $this->ipHash(); + $log = $this->log(); + $time = time(); + + if (isset($log['by-ip'][$ip]) === true) { + $log['by-ip'][$ip] = [ + 'time' => $time, + 'trials' => ($log['by-ip'][$ip]['trials'] ?? 0) + 1 + ]; + } else { + $log['by-ip'][$ip] = [ + 'time' => $time, + 'trials' => 1 + ]; + } + + if ($email !== null && $this->kirby->users()->find($email)) { + if (isset($log['by-email'][$email]) === true) { + $log['by-email'][$email] = [ + 'time' => $time, + 'trials' => ($log['by-email'][$email]['trials'] ?? 0) + 1 + ]; + } else { + $log['by-email'][$email] = [ + 'time' => $time, + 'trials' => 1 + ]; + } + } + + return Data::write($this->logfile(), $log, 'json'); + } + + /** + * Returns the current authentication type + * + * @param bool $allowImpersonation If set to false, 'impersonate' won't + * be returned as authentication type + * even if an impersonation is active + * @return string + */ + public function type(bool $allowImpersonation = true): string + { + $basicAuth = $this->kirby->option('api.basicAuth', false); + $auth = $this->kirby->request()->auth(); + + if ($basicAuth === true && $auth && $auth->type() === 'basic') { + return 'basic'; + } elseif ($allowImpersonation === true && $this->impersonate !== null) { + return 'impersonate'; + } else { + return 'session'; + } + } + + /** + * Validates the currently logged in user + * + * @param \Kirby\Session\Session|array|null $session + * @param bool $allowImpersonation If set to false, only the actually + * logged in user will be returned + * @return \Kirby\Cms\User|null + * + * @throws \Throwable If an authentication error occured + */ + public function user($session = null, bool $allowImpersonation = true) + { + if ($allowImpersonation === true && $this->impersonate !== null) { + return $this->impersonate; + } + + // return from cache + if ($this->user === null) { + // throw the same Exception again if one was captured before + if ($this->userException !== null) { + throw $this->userException; + } + + return null; + } elseif ($this->user !== false) { + return $this->user; + } + + try { + if ($this->type() === 'basic') { + return $this->user = $this->currentUserFromBasicAuth(); + } else { + return $this->user = $this->currentUserFromSession($session); + } + } catch (Throwable $e) { + $this->user = null; + + // capture the Exception for future calls + $this->userException = $e; + + throw $e; + } + } + + /** + * Verifies an authentication code that was + * requested with the `createChallenge()` method; + * if successful, the user is automatically logged in + * @since 3.5.0 + * + * @param string $code User-provided auth code to verify + * @return \Kirby\Cms\User User object of the logged-in user + * + * @throws \Kirby\Exception\PermissionException If the rate limit was exceeded, the challenge timed out, the code + * is incorrect or if any other error occured with debug mode off + * @throws \Kirby\Exception\NotFoundException If the user from the challenge doesn't exist + * @throws \Kirby\Exception\InvalidArgumentException If no authentication challenge is active + * @throws \Kirby\Exception\LogicException If the authentication challenge is invalid + */ + public function verifyChallenge(string $code) + { + try { + $session = $this->kirby->session(); + + // first check if we have an active challenge at all + $email = $session->get('kirby.challenge.email'); + $challenge = $session->get('kirby.challenge.type'); + if (is_string($email) !== true || is_string($challenge) !== true) { + throw new InvalidArgumentException('No authentication challenge is active'); + } + + $user = $this->kirby->users()->find($email); + if ($user === null) { + throw new NotFoundException([ + 'key' => 'user.notFound', + 'data' => [ + 'name' => $email + ] + ]); + } + + // rate-limiting + if ($this->isBlocked($email) === true) { + $this->kirby->trigger('user.login:failed', compact('email')); + throw new PermissionException('Rate limit exceeded'); + } + + // time-limiting + $timeout = $session->get('kirby.challenge.timeout'); + if ($timeout !== null && time() > $timeout) { + throw new PermissionException('Authentication challenge timeout'); + } + + if ( + isset(static::$challenges[$challenge]) === true && + class_exists(static::$challenges[$challenge]) === true && + is_subclass_of(static::$challenges[$challenge], 'Kirby\Cms\Auth\Challenge') === true + ) { + $class = static::$challenges[$challenge]; + if ($class::verify($user, $code) === true) { + $this->logout(); + $user->loginPasswordless(); + + // clear the status cache + $this->status = null; + + return $user; + } else { + throw new PermissionException(['key' => 'access.code']); + } + } + + throw new LogicException('Invalid authentication challenge: ' . $challenge); + } catch (Throwable $e) { + if ($e->getMessage() !== 'Rate limit exceeded') { + $this->track($email); + } + + // sleep for a random amount of milliseconds + // to make automated attacks harder and to + // avoid leaking whether the user exists + usleep(random_int(1000, 2000000)); + + // keep throwing the original error in debug mode, + // otherwise hide it to avoid leaking security-relevant information + if ($this->kirby->option('debug') === true) { + throw $e; + } else { + throw new PermissionException(['key' => 'access.code']); + } + } + } + + /** + * Creates a session object from the passed options + * + * @param \Kirby\Session\Session|array|null $session + * @return \Kirby\Session\Session + */ + protected function session($session = null) + { + // use passed session options or session object if set + if (is_array($session) === true) { + return $this->kirby->session($session); + } + + // try session in header or cookie + if (is_a($session, 'Kirby\Session\Session') === false) { + return $this->kirby->session(['detect' => true]); + } + + return $session; + } +} diff --git a/kirby/src/Cms/Auth/Challenge.php b/kirby/src/Cms/Auth/Challenge.php new file mode 100644 index 0000000..bc4adf3 --- /dev/null +++ b/kirby/src/Cms/Auth/Challenge.php @@ -0,0 +1,63 @@ + + * @link https://getkirby.com + * @copyright Bastian Allgeier GmbH + * @license https://getkirby.com/license + */ +abstract class Challenge +{ + /** + * Checks whether the challenge is available + * for the passed user and purpose + * + * @param \Kirby\Cms\User $user User the code will be generated for + * @param string $mode Purpose of the code ('login', 'reset' or '2fa') + * @return bool + */ + abstract public static function isAvailable(User $user, string $mode): bool; + + /** + * Generates a random one-time auth code and returns that code + * for later verification + * + * @param \Kirby\Cms\User $user User to generate the code for + * @param array $options Details of the challenge request: + * - 'mode': Purpose of the code ('login', 'reset' or '2fa') + * - 'timeout': Number of seconds the code will be valid for + * @return string|null The generated and sent code or `null` in case + * there was no code to generate by this algorithm + */ + abstract public static function create(User $user, array $options): ?string; + + /** + * Verifies the provided code against the created one; + * default implementation that checks the code that was + * returned from the `create()` method + * + * @param \Kirby\Cms\User $user User to check the code for + * @param string $code Code to verify + * @return bool + */ + public static function verify(User $user, string $code): bool + { + $hash = $user->kirby()->session()->get('kirby.challenge.code'); + if (is_string($hash) !== true) { + return false; + } + + // normalize the formatting in the user-provided code + $code = str_replace(' ', '', $code); + + return password_verify($code, $hash); + } +} diff --git a/kirby/src/Cms/Auth/EmailChallenge.php b/kirby/src/Cms/Auth/EmailChallenge.php new file mode 100644 index 0000000..f6e540d --- /dev/null +++ b/kirby/src/Cms/Auth/EmailChallenge.php @@ -0,0 +1,76 @@ + + * @link https://getkirby.com + * @copyright Bastian Allgeier GmbH + * @license https://getkirby.com/license + */ +class EmailChallenge extends Challenge +{ + /** + * Checks whether the challenge is available + * for the passed user and purpose + * + * @param \Kirby\Cms\User $user User the code will be generated for + * @param string $mode Purpose of the code ('login', 'reset' or '2fa') + * @return bool + */ + public static function isAvailable(User $user, string $mode): bool + { + return true; + } + + /** + * Generates a random one-time auth code and returns that code + * for later verification + * + * @param \Kirby\Cms\User $user User to generate the code for + * @param array $options Details of the challenge request: + * - 'mode': Purpose of the code ('login', 'reset' or '2fa') + * - 'timeout': Number of seconds the code will be valid for + * @return string The generated and sent code + */ + public static function create(User $user, array $options): string + { + $code = Str::random(6, 'num'); + + // insert a space in the middle for easier readability + $formatted = substr($code, 0, 3) . ' ' . substr($code, 3, 3); + + // use the login templates for 2FA + $mode = $options['mode']; + if ($mode === '2fa') { + $mode = 'login'; + } + + $kirby = $user->kirby(); + $kirby->email([ + 'from' => $kirby->option('auth.challenge.email.from', 'noreply@' . $kirby->url('index', true)->host()), + 'fromName' => $kirby->option('auth.challenge.email.fromName', $kirby->site()->title()), + 'to' => $user, + 'subject' => $kirby->option( + 'auth.challenge.email.subject', + I18n::translate('login.email.' . $mode . '.subject', null, $user->language()) + ), + 'template' => 'auth/' . $mode, + 'data' => [ + 'user' => $user, + 'code' => $formatted, + 'timeout' => round($options['timeout'] / 60) + ] + ]); + + return $code; + } +} diff --git a/kirby/src/Cms/Auth/Status.php b/kirby/src/Cms/Auth/Status.php new file mode 100644 index 0000000..23de1cc --- /dev/null +++ b/kirby/src/Cms/Auth/Status.php @@ -0,0 +1,219 @@ + + * @link https://getkirby.com + * @copyright Bastian Allgeier GmbH + * @license https://getkirby.com/license + */ +class Status +{ + use Properties; + + /** + * Type of the active challenge + * + * @var string|null + */ + protected $challenge = null; + + /** + * Challenge type to use as a fallback + * when $challenge is `null` + * + * @var string|null + */ + protected $challengeFallback = null; + + /** + * Email address of the current/pending user + * + * @var string|null + */ + protected $email = null; + + /** + * Kirby instance for user lookup + * + * @var \Kirby\Cms\App + */ + protected $kirby; + + /** + * Authentication status: + * `active|impersonated|pending|inactive` + * + * @var string + */ + protected $status; + + /** + * Class constructor + * + * @param array $props + */ + public function __construct(array $props) + { + $this->setProperties($props); + } + + /** + * Returns the authentication status + * + * @return string + */ + public function __toString(): string + { + return $this->status(); + } + + /** + * Returns the type of the active challenge + * + * @param bool $automaticFallback If set to `false`, no faked challenge is returned; + * WARNING: never send the resulting `null` value to the + * user to avoid leaking whether the pending user exists + * @return string|null + */ + public function challenge(bool $automaticFallback = true): ?string + { + // never return a challenge type if the status doesn't match + if ($this->status() !== 'pending') { + return null; + } + + if ($automaticFallback === false) { + return $this->challenge; + } else { + return $this->challenge ?? $this->challengeFallback; + } + } + + /** + * Returns the email address of the current/pending user + * + * @return string|null + */ + public function email(): ?string + { + return $this->email; + } + + /** + * Returns the authentication status + * + * @return string `active|impersonated|pending|inactive` + */ + public function status(): string + { + return $this->status; + } + + /** + * Returns an array with all public status data + * + * @return array + */ + public function toArray(): array + { + return [ + 'challenge' => $this->challenge(), + 'email' => $this->email(), + 'status' => $this->status() + ]; + } + + /** + * Returns the currently logged in user + * + * @return \Kirby\Cms\User + */ + public function user() + { + // for security, only return the user if they are + // already logged in + if (in_array($this->status(), ['active', 'impersonated']) !== true) { + return null; + } + + return $this->kirby->user($this->email()); + } + + /** + * Sets the type of the active challenge + * + * @param string|null $challenge + * @return $this + */ + protected function setChallenge(?string $challenge = null) + { + $this->challenge = $challenge; + return $this; + } + + /** + * Sets the challenge type to use as + * a fallback when $challenge is `null` + * + * @param string|null $challengeFallback + * @return $this + */ + protected function setChallengeFallback(?string $challengeFallback = null) + { + $this->challengeFallback = $challengeFallback; + return $this; + } + + /** + * Sets the email address of the current/pending user + * + * @param string|null $email + * @return $this + */ + protected function setEmail(?string $email = null) + { + $this->email = $email; + return $this; + } + + /** + * Sets the Kirby instance for user lookup + * + * @param \Kirby\Cms\App $kirby + * @return $this + */ + protected function setKirby(App $kirby) + { + $this->kirby = $kirby; + return $this; + } + + /** + * Sets the authentication status + * + * @param string $status `active|impersonated|pending|inactive` + * @return $this + */ + protected function setStatus(string $status) + { + if (in_array($status, ['active', 'impersonated', 'pending', 'inactive']) !== true) { + throw new InvalidArgumentException([ + 'data' => ['argument' => '$props[\'status\']', 'method' => 'Status::__construct'] + ]); + } + + $this->status = $status; + return $this; + } +} diff --git a/kirby/src/Cms/Block.php b/kirby/src/Cms/Block.php new file mode 100644 index 0000000..da5e34d --- /dev/null +++ b/kirby/src/Cms/Block.php @@ -0,0 +1,240 @@ + + * @link https://getkirby.com + * @copyright Bastian Allgeier GmbH + * @license https://getkirby.com/license + */ +class Block extends Item +{ + const ITEMS_CLASS = '\Kirby\Cms\Blocks'; + + /** + * @var \Kirby\Cms\Content + */ + protected $content; + + /** + * @var bool + */ + protected $isHidden; + + /** + * @var string + */ + protected $type; + + /** + * Proxy for content fields + * + * @param string $method + * @param array $args + * @return \Kirby\Cms\Field + */ + public function __call(string $method, array $args = []) + { + return $this->content()->get($method); + } + + /** + * Creates a new block object + * + * @param array $params + * @param \Kirby\Cms\Blocks $siblings + */ + public function __construct(array $params) + { + parent::__construct($params); + + // import old builder format + $params = BlockConverter::builderBlock($params); + $params = BlockConverter::editorBlock($params); + + if (isset($params['type']) === false) { + throw new InvalidArgumentException('The block type is missing'); + } + + $this->content = $params['content'] ?? []; + $this->isHidden = $params['isHidden'] ?? false; + $this->type = $params['type'] ?? null; + + // create the content object + $this->content = new Content($this->content, $this->parent); + } + + /** + * Converts the object to a string + * + * @return string + */ + public function __toString(): string + { + return $this->toHtml(); + } + + /** + * Deprecated method to return the block type + * + * @deprecated 3.5.0 Use `\Kirby\Cms\Block::type()` instead + * @todo Add deprecated() helper warning in 3.6.0 + * @todo Remove in 3.7.0 + * + * @return string + */ + public function _key(): string + { + return $this->type(); + } + + /** + * Deprecated method to return the block id + * + * @deprecated 3.5.0 Use `\Kirby\Cms\Block::id()` instead + * @todo Add deprecated() helper warning in 3.6.0 + * @todo Remove in 3.7.0 + * + * @return string + */ + public function _uid(): string + { + return $this->id(); + } + + /** + * Returns the content object + * + * @return \Kirby\Cms\Content + */ + public function content() + { + return $this->content; + } + + /** + * Controller for the block snippet + * + * @return array + */ + public function controller(): array + { + return [ + 'block' => $this, + 'content' => $this->content(), + // deprecated block data + 'data' => $this, + 'id' => $this->id(), + 'prev' => $this->prev(), + 'next' => $this->next() + ]; + } + + /** + * Converts the block to HTML and then + * uses the Str::excerpt method to create + * a non-formatted, shortened excerpt from it + * + * @param mixed ...$args + * @return string + */ + public function excerpt(...$args) + { + return Str::excerpt($this->toHtml(), ...$args); + } + + /** + * Checks if the block is empty + * + * @return bool + */ + public function isEmpty(): bool + { + return empty($this->content()->toArray()); + } + + /** + * Checks if the block is hidden + * from being rendered in the frontend + * + * @return bool + */ + public function isHidden(): bool + { + return $this->isHidden; + } + + /** + * Checks if the block is not empty + * + * @return bool + */ + public function isNotEmpty(): bool + { + return $this->isEmpty() === false; + } + + /** + * Returns the block type + * + * @return string + */ + public function type(): string + { + return $this->type; + } + + /** + * The result is being sent to the editor + * via the API in the panel + * + * @return array + */ + public function toArray(): array + { + return [ + 'content' => $this->content()->toArray(), + 'id' => $this->id(), + 'isHidden' => $this->isHidden(), + 'type' => $this->type(), + ]; + } + + /** + * Converts the block to html first + * and then places that inside a field + * object. This can be used further + * with all available field methods + * + * @return \Kirby\Cms\Field + */ + public function toField() + { + return new Field($this->parent(), $this->id(), $this->toHtml()); + } + + /** + * Converts the block to HTML + * + * @return string + */ + public function toHtml(): string + { + try { + return (string)snippet('blocks/' . $this->type(), $this->controller(), true); + } catch (Throwable $e) { + return '

Block error: "' . $e->getMessage() . '" in block type: "' . $this->type() . '"

'; + } + } +} diff --git a/kirby/src/Cms/BlockConverter.php b/kirby/src/Cms/BlockConverter.php new file mode 100644 index 0000000..8b98e0c --- /dev/null +++ b/kirby/src/Cms/BlockConverter.php @@ -0,0 +1,272 @@ + + * @link https://getkirby.com + * @copyright Bastian Allgeier GmbH + * @license https://getkirby.com/license + */ +class BlockConverter +{ + public static function builderBlock(array $params): array + { + if (isset($params['_key']) === false) { + return $params; + } + + $params['type'] = $params['_key']; + $params['content'] = $params; + unset($params['_uid']); + + return $params; + } + + public static function editorBlock(array $params): array + { + if (static::isEditorBlock($params) === false) { + return $params; + } + + $method = 'editor' . $params['type']; + + if (method_exists(static::class, $method) === true) { + $params = static::$method($params); + } else { + $params = static::editorCustom($params); + } + + return $params; + } + + public static function editorBlocks(array $blocks = []): array + { + if (empty($blocks) === true) { + return $blocks; + } + + if (static::isEditorBlock($blocks[0]) === false) { + return $blocks; + } + + $list = []; + $listStart = null; + + foreach ($blocks as $index => $block) { + if (in_array($block['type'], ['ul', 'ol']) === true) { + $prev = $blocks[$index-1] ?? null; + $next = $blocks[$index+1] ?? null; + + // new list starts here + if (!$prev || $prev['type'] !== $block['type']) { + $listStart = $index; + } + + // add the block to the list + $list[] = $block; + + // list ends here + if (!$next || $next['type'] !== $block['type']) { + $blocks[$listStart] = [ + 'content' => [ + 'text' => + '<' . $block['type'] . '>' . + implode(array_map(function ($item) { + return '
  • ' . $item['content'] . '
  • '; + }, $list)) . + '', + ], + 'type' => 'list' + ]; + + $start = $listStart + 1; + $end = $listStart + count($list); + + for ($x = $start; $x <= $end; $x++) { + $blocks[$x] = false; + } + + $listStart = null; + $list = []; + } + } else { + $blocks[$index] = static::editorBlock($block); + } + } + + return array_filter($blocks); + } + + public static function editorBlockquote(array $params): array + { + return [ + 'content' => [ + 'text' => $params['content'] + ], + 'type' => 'quote' + ]; + } + + public static function editorCode(array $params): array + { + return [ + 'content' => [ + 'language' => $params['attrs']['language'] ?? null, + 'code' => $params['content'] + ], + 'type' => 'code' + ]; + } + + public static function editorCustom(array $params): array + { + return [ + 'content' => array_merge( + $params['attrs'] ?? [], + [ + 'body' => $params['content'] ?? null + ] + ), + 'type' => $params['type'] ?? 'unknown' + ]; + } + + public static function editorH1(array $params): array + { + return static::editorHeading($params, 'h1'); + } + + public static function editorH2(array $params): array + { + return static::editorHeading($params, 'h2'); + } + + public static function editorH3(array $params): array + { + return static::editorHeading($params, 'h3'); + } + + public static function editorH4(array $params): array + { + return static::editorHeading($params, 'h4'); + } + + public static function editorH5(array $params): array + { + return static::editorHeading($params, 'h5'); + } + + public static function editorH6(array $params): array + { + return static::editorHeading($params, 'h6'); + } + + public static function editorHeading(array $params, string $level): array + { + return [ + 'content' => [ + 'level' => $level, + 'text' => $params['content'] + ], + 'type' => 'heading' + ]; + } + + public static function editorImage(array $params): array + { + // internal image + if (isset($params['attrs']['id']) === true) { + return [ + 'content' => [ + 'alt' => $params['attrs']['alt'] ?? null, + 'caption' => $params['attrs']['caption'] ?? null, + 'image' => $params['attrs']['id'] ?? $params['attrs']['src'] ?? null, + 'location' => 'kirby', + 'ratio' => $params['attrs']['ratio'] ?? null, + ], + 'type' => 'image' + ]; + } + + return [ + 'content' => [ + 'alt' => $params['attrs']['alt'] ?? null, + 'caption' => $params['attrs']['caption'] ?? null, + 'src' => $params['attrs']['src'] ?? null, + 'location' => 'web', + 'ratio' => $params['attrs']['ratio'] ?? null, + ], + 'type' => 'image' + ]; + } + + public static function editorKirbytext(array $params): array + { + return [ + 'content' => [ + 'text' => $params['content'] + ], + 'type' => 'markdown' + ]; + } + + public static function editorOl(array $params): array + { + return [ + 'content' => [ + 'text' => $params['content'] + ], + 'type' => 'list' + ]; + } + + public static function editorParagraph(array $params): array + { + return [ + 'content' => [ + 'text' => '

    ' . $params['content'] . '

    ' + ], + 'type' => 'text' + ]; + } + + public static function editorUl(array $params): array + { + return [ + 'content' => [ + 'text' => $params['content'] + ], + 'type' => 'list' + ]; + } + + public static function editorVideo(array $params): array + { + return [ + 'content' => [ + 'caption' => $params['attrs']['caption'] ?? null, + 'url' => $params['attrs']['src'] ?? null + ], + 'type' => 'video' + ]; + } + + public static function isEditorBlock(array $params): bool + { + if (isset($params['attrs']) === true) { + return true; + } + + if (is_string($params['content'] ?? null) === true) { + return true; + } + + return false; + } +} diff --git a/kirby/src/Cms/Blocks.php b/kirby/src/Cms/Blocks.php new file mode 100644 index 0000000..ac6830e --- /dev/null +++ b/kirby/src/Cms/Blocks.php @@ -0,0 +1,154 @@ + + * @link https://getkirby.com + * @copyright Bastian Allgeier GmbH + * @license https://getkirby.com/license + */ +class Blocks extends Items +{ + const ITEM_CLASS = '\Kirby\Cms\Block'; + + /** + * Return HTML when the collection is + * converted to a string + * + * @return string + */ + public function __toString(): string + { + return $this->toHtml(); + } + + /** + * Converts the blocks to HTML and then + * uses the Str::excerpt method to create + * a non-formatted, shortened excerpt from it + * + * @param mixed ...$args + * @return string + */ + public function excerpt(...$args) + { + return Str::excerpt($this->toHtml(), ...$args); + } + + /** + * Wrapper around the factory to + * catch blocks from layouts + * + * @param array $items + * @param array $params + * @return \Kirby\Cms\Blocks + */ + public static function factory(array $items = null, array $params = []) + { + $items = static::extractFromLayouts($items); + $items = BlockConverter::editorBlocks($items); + + return parent::factory($items, $params); + } + + /** + * Pull out blocks from layouts + * + * @param array $input + * @return array + */ + protected static function extractFromLayouts(array $input): array + { + if (empty($input) === true) { + return []; + } + + if ( + // no columns = no layout + array_key_exists('columns', $input[0]) === false || + // checks if this is a block for the builder plugin + array_key_exists('_key', $input[0]) === true + ) { + return $input; + } + + $blocks = []; + + foreach ($input as $layout) { + foreach (($layout['columns'] ?? []) as $column) { + foreach (($column['blocks'] ?? []) as $block) { + $blocks[] = $block; + } + } + } + + return $blocks; + } + + /** + * Parse and sanitize various block formats + * + * @param array|string $input + * @return array + */ + public static function parse($input): array + { + if (empty($input) === false && is_array($input) === false) { + try { + $input = Json::decode((string)$input); + } catch (Throwable $e) { + try { + // try to import the old YAML format + $yaml = Yaml::decode((string)$input); + $first = A::first($yaml); + + // check for valid yaml + if (empty($yaml) === true || (isset($first['_key']) === false && isset($first['type']) === false)) { + throw new Exception('Invalid YAML'); + } else { + $input = $yaml; + } + } catch (Throwable $e) { + $parser = new Parsley((string)$input, new BlockSchema()); + $input = $parser->blocks(); + } + } + } + + if (empty($input) === true) { + return []; + } + + return $input; + } + + /** + * Convert all blocks to HTML + * + * @return string + */ + public function toHtml(): string + { + $html = []; + + foreach ($this->data as $block) { + $html[] = $block->toHtml(); + } + + return implode($html); + } +} diff --git a/kirby/src/Cms/Blueprint.php b/kirby/src/Cms/Blueprint.php new file mode 100644 index 0000000..8659b4a --- /dev/null +++ b/kirby/src/Cms/Blueprint.php @@ -0,0 +1,800 @@ + + * @link https://getkirby.com + * @copyright Bastian Allgeier GmbH + * @license https://getkirby.com/license + */ +class Blueprint +{ + public static $presets = []; + public static $loaded = []; + + protected $fields = []; + protected $model; + protected $props; + protected $sections = []; + protected $tabs = []; + + /** + * Magic getter/caller for any blueprint prop + * + * @param string $key + * @param array|null $arguments + * @return mixed + */ + public function __call(string $key, array $arguments = null) + { + return $this->props[$key] ?? null; + } + + /** + * Creates a new blueprint object with the given props + * + * @param array $props + * @throws \Kirby\Exception\InvalidArgumentException If the blueprint model is missing + */ + public function __construct(array $props) + { + if (empty($props['model']) === true) { + throw new InvalidArgumentException('A blueprint model is required'); + } + + $this->model = $props['model']; + + // the model should not be included in the props array + unset($props['model']); + + // extend the blueprint in general + $props = $this->extend($props); + + // apply any blueprint preset + $props = $this->preset($props); + + // normalize the name + $props['name'] = $props['name'] ?? 'default'; + + // normalize and translate the title + $props['title'] = $this->i18n($props['title'] ?? ucfirst($props['name'])); + + // convert all shortcuts + $props = $this->convertFieldsToSections('main', $props); + $props = $this->convertSectionsToColumns('main', $props); + $props = $this->convertColumnsToTabs('main', $props); + + // normalize all tabs + $props['tabs'] = $this->normalizeTabs($props['tabs'] ?? []); + + $this->props = $props; + } + + /** + * Improved `var_dump` output + * + * @return array + */ + public function __debugInfo(): array + { + return $this->props ?? []; + } + + /** + * Converts all column definitions, that + * are not wrapped in a tab, into a generic tab + * + * @param string $tabName + * @param array $props + * @return array + */ + protected function convertColumnsToTabs(string $tabName, array $props): array + { + if (isset($props['columns']) === false) { + return $props; + } + + // wrap everything in a main tab + $props['tabs'] = [ + $tabName => [ + 'columns' => $props['columns'] + ] + ]; + + unset($props['columns']); + + return $props; + } + + /** + * Converts all field definitions, that are not + * wrapped in a fields section into a generic + * fields section. + * + * @param string $tabName + * @param array $props + * @return array + */ + protected function convertFieldsToSections(string $tabName, array $props): array + { + if (isset($props['fields']) === false) { + return $props; + } + + // wrap all fields in a section + $props['sections'] = [ + $tabName . '-fields' => [ + 'type' => 'fields', + 'fields' => $props['fields'] + ] + ]; + + unset($props['fields']); + + return $props; + } + + /** + * Converts all sections that are not wrapped in + * columns, into a single generic column. + * + * @param string $tabName + * @param array $props + * @return array + */ + protected function convertSectionsToColumns(string $tabName, array $props): array + { + if (isset($props['sections']) === false) { + return $props; + } + + // wrap everything in one big column + $props['columns'] = [ + [ + 'width' => '1/1', + 'sections' => $props['sections'] + ] + ]; + + unset($props['sections']); + + return $props; + } + + /** + * Extends the props with props from a given + * mixin, when an extends key is set or the + * props is just a string + * + * @param array|string $props + * @return array + */ + public static function extend($props): array + { + if (is_string($props) === true) { + $props = [ + 'extends' => $props + ]; + } + + $extends = $props['extends'] ?? null; + + if ($extends === null) { + return $props; + } + + try { + $mixin = static::find($extends); + $mixin = static::extend($mixin); + $props = A::merge($mixin, $props, A::MERGE_REPLACE); + } catch (Exception $e) { + // keep the props unextended if the snippet wasn't found + } + + // remove the extends flag + unset($props['extends']); + + return $props; + } + + /** + * Create a new blueprint for a model + * + * @param string $name + * @param string|null $fallback + * @param \Kirby\Cms\Model $model + * @return static|null + */ + public static function factory(string $name, string $fallback = null, Model $model) + { + try { + $props = static::load($name); + } catch (Exception $e) { + $props = $fallback !== null ? static::load($fallback) : null; + } + + if ($props === null) { + return null; + } + + // inject the parent model + $props['model'] = $model; + + return new static($props); + } + + /** + * Returns a single field definition by name + * + * @param string $name + * @return array|null + */ + public function field(string $name): ?array + { + return $this->fields[$name] ?? null; + } + + /** + * Returns all field definitions + * + * @return array + */ + public function fields(): array + { + return $this->fields; + } + + /** + * Find a blueprint by name + * + * @param string $name + * @return array + * @throws \Kirby\Exception\NotFoundException If the blueprint cannot be found + */ + public static function find(string $name): array + { + if (isset(static::$loaded[$name]) === true) { + return static::$loaded[$name]; + } + + $kirby = App::instance(); + $root = $kirby->root('blueprints'); + $file = $root . '/' . $name . '.yml'; + + // first try to find a site blueprint, + // then check in the plugin extensions + if (F::exists($file, $root) !== true) { + $file = $kirby->extension('blueprints', $name); + } + + // now ensure that we always return the data array + if (is_string($file) === true && F::exists($file) === true) { + return static::$loaded[$name] = Data::read($file); + } elseif (is_array($file) === true) { + return static::$loaded[$name] = $file; + } + + // neither a valid file nor array data + throw new NotFoundException([ + 'key' => 'blueprint.notFound', + 'data' => ['name' => $name] + ]); + } + + /** + * Used to translate any label, heading, etc. + * + * @param mixed $value + * @param mixed $fallback + * @return mixed + */ + protected function i18n($value, $fallback = null) + { + return I18n::translate($value, $fallback ?? $value); + } + + /** + * Checks if this is the default blueprint + * + * @return bool + */ + public function isDefault(): bool + { + return $this->name() === 'default'; + } + + /** + * Loads a blueprint from file or array + * + * @param string $name + * @return array + */ + public static function load(string $name): array + { + $props = static::find($name); + + $normalize = function ($props) use ($name) { + // inject the filename as name if no name is set + $props['name'] = $props['name'] ?? $name; + + // normalize the title + $title = $props['title'] ?? ucfirst($props['name']); + + // translate the title + $props['title'] = I18n::translate($title, $title); + + return $props; + }; + + return $normalize($props); + } + + /** + * Returns the parent model + * + * @return \Kirby\Cms\Model + */ + public function model() + { + return $this->model; + } + + /** + * Returns the blueprint name + * + * @return string + */ + public function name(): string + { + return $this->props['name']; + } + + /** + * Normalizes all required props in a column setup + * + * @param string $tabName + * @param array $columns + * @return array + */ + protected function normalizeColumns(string $tabName, array $columns): array + { + foreach ($columns as $columnKey => $columnProps) { + // unset/remove column if its property is not array + if (is_array($columnProps) === false) { + unset($columns[$columnKey]); + continue; + } + + $columnProps = $this->convertFieldsToSections($tabName . '-col-' . $columnKey, $columnProps); + + // inject getting started info, if the sections are empty + if (empty($columnProps['sections']) === true) { + $columnProps['sections'] = [ + $tabName . '-info-' . $columnKey => [ + 'headline' => 'Column (' . ($columnProps['width'] ?? '1/1') . ')', + 'type' => 'info', + 'text' => 'No sections yet' + ] + ]; + } + + $columns[$columnKey] = array_merge($columnProps, [ + 'width' => $columnProps['width'] ?? '1/1', + 'sections' => $this->normalizeSections($tabName, $columnProps['sections'] ?? []) + ]); + } + + return $columns; + } + + /** + * @param array $items + * @return string + */ + public static function helpList(array $items): string + { + $md = []; + + foreach ($items as $item) { + $md[] = '- *' . $item . '*'; + } + + return PHP_EOL . implode(PHP_EOL, $md); + } + + /** + * Normalize field props for a single field + * + * @param array|string $props + * @return array + * @throws \Kirby\Exception\InvalidArgumentException If the filed name is missing or the field type is invalid + */ + public static function fieldProps($props): array + { + $props = static::extend($props); + + if (isset($props['name']) === false) { + throw new InvalidArgumentException('The field name is missing'); + } + + $name = $props['name']; + $type = $props['type'] ?? $name; + + if ($type !== 'group' && isset(Field::$types[$type]) === false) { + throw new InvalidArgumentException('Invalid field type ("' . $type . '")'); + } + + // support for nested fields + if (isset($props['fields']) === true) { + $props['fields'] = static::fieldsProps($props['fields']); + } + + // groups don't need all the crap + if ($type === 'group') { + return [ + 'fields' => $props['fields'], + 'name' => $name, + 'type' => $type, + ]; + } + + // add some useful defaults + return array_merge($props, [ + 'label' => $props['label'] ?? ucfirst($name), + 'name' => $name, + 'type' => $type, + 'width' => $props['width'] ?? '1/1', + ]); + } + + /** + * Creates an error field with the given error message + * + * @param string $name + * @param string $message + * @return array + */ + public static function fieldError(string $name, string $message): array + { + return [ + 'label' => 'Error', + 'name' => $name, + 'text' => strip_tags($message), + 'theme' => 'negative', + 'type' => 'info', + ]; + } + + /** + * Normalizes all fields and adds automatic labels, + * types and widths. + * + * @param array $fields + * @return array + */ + public static function fieldsProps($fields): array + { + if (is_array($fields) === false) { + $fields = []; + } + + foreach ($fields as $fieldName => $fieldProps) { + + // extend field from string + if (is_string($fieldProps) === true) { + $fieldProps = [ + 'extends' => $fieldProps, + 'name' => $fieldName + ]; + } + + // use the name as type definition + if ($fieldProps === true) { + $fieldProps = []; + } + + // unset / remove field if its property is false + if ($fieldProps === false) { + unset($fields[$fieldName]); + continue; + } + + // inject the name + $fieldProps['name'] = $fieldName; + + // create all props + try { + $fieldProps = static::fieldProps($fieldProps); + } catch (Throwable $e) { + $fieldProps = static::fieldError($fieldName, $e->getMessage()); + } + + // resolve field groups + if ($fieldProps['type'] === 'group') { + if (empty($fieldProps['fields']) === false && is_array($fieldProps['fields']) === true) { + $index = array_search($fieldName, array_keys($fields)); + $before = array_slice($fields, 0, $index); + $after = array_slice($fields, $index + 1); + $fields = array_merge($before, $fieldProps['fields'] ?? [], $after); + } else { + unset($fields[$fieldName]); + } + } else { + $fields[$fieldName] = $fieldProps; + } + } + + return $fields; + } + + /** + * Normalizes blueprint options. This must be used in the + * constructor of an extended class, if you want to make use of it. + * + * @param array|true|false|null|string $options + * @param array $defaults + * @param array $aliases + * @return array + */ + protected function normalizeOptions($options, array $defaults, array $aliases = []): array + { + // return defaults when options are not defined or set to true + if ($options === true) { + return $defaults; + } + + // set all options to false + if ($options === false) { + return array_map(function () { + return false; + }, $defaults); + } + + // extend options if possible + $options = $this->extend($options); + + foreach ($options as $key => $value) { + $alias = $aliases[$key] ?? null; + + if ($alias !== null) { + $options[$alias] = $options[$alias] ?? $value; + unset($options[$key]); + } + } + + return array_merge($defaults, $options); + } + + /** + * Normalizes all required keys in sections + * + * @param string $tabName + * @param array $sections + * @return array + */ + protected function normalizeSections(string $tabName, array $sections): array + { + foreach ($sections as $sectionName => $sectionProps) { + + // unset / remove section if its property is false + if ($sectionProps === false) { + unset($sections[$sectionName]); + continue; + } + + // fallback to default props when true is passed + if ($sectionProps === true) { + $sectionProps = []; + } + + // inject all section extensions + $sectionProps = $this->extend($sectionProps); + + $sections[$sectionName] = $sectionProps = array_merge($sectionProps, [ + 'name' => $sectionName, + 'type' => $type = $sectionProps['type'] ?? $sectionName + ]); + + if (empty($type) === true || is_string($type) === false) { + $sections[$sectionName] = [ + 'name' => $sectionName, + 'headline' => 'Invalid section type for section "' . $sectionName . '"', + 'type' => 'info', + 'text' => 'The following section types are available: ' . $this->helpList(array_keys(Section::$types)) + ]; + } elseif (isset(Section::$types[$type]) === false) { + $sections[$sectionName] = [ + 'name' => $sectionName, + 'headline' => 'Invalid section type ("' . $type . '")', + 'type' => 'info', + 'text' => 'The following section types are available: ' . $this->helpList(array_keys(Section::$types)) + ]; + } + + if ($sectionProps['type'] === 'fields') { + $fields = Blueprint::fieldsProps($sectionProps['fields'] ?? []); + + // inject guide fields guide + if (empty($fields) === true) { + $fields = [ + $tabName . '-info' => [ + 'label' => 'Fields', + 'text' => 'No fields yet', + 'type' => 'info' + ] + ]; + } else { + foreach ($fields as $fieldName => $fieldProps) { + if (isset($this->fields[$fieldName]) === true) { + $this->fields[$fieldName] = $fields[$fieldName] = [ + 'type' => 'info', + 'label' => $fieldProps['label'] ?? 'Error', + 'text' => 'The field name "' . $fieldName . '" already exists in your blueprint.', + 'theme' => 'negative' + ]; + } else { + $this->fields[$fieldName] = $fieldProps; + } + } + } + + $sections[$sectionName]['fields'] = $fields; + } + } + + // store all normalized sections + $this->sections = array_merge($this->sections, $sections); + + return $sections; + } + + /** + * Normalizes all required keys in tabs + * + * @param array $tabs + * @return array + */ + protected function normalizeTabs($tabs): array + { + if (is_array($tabs) === false) { + $tabs = []; + } + + foreach ($tabs as $tabName => $tabProps) { + + // unset / remove tab if its property is false + if ($tabProps === false) { + unset($tabs[$tabName]); + continue; + } + + // inject all tab extensions + $tabProps = $this->extend($tabProps); + + // inject a preset if available + $tabProps = $this->preset($tabProps); + + $tabProps = $this->convertFieldsToSections($tabName, $tabProps); + $tabProps = $this->convertSectionsToColumns($tabName, $tabProps); + + $tabs[$tabName] = array_merge($tabProps, [ + 'columns' => $this->normalizeColumns($tabName, $tabProps['columns'] ?? []), + 'icon' => $tabProps['icon'] ?? null, + 'label' => $this->i18n($tabProps['label'] ?? ucfirst($tabName)), + 'name' => $tabName, + ]); + } + + return $this->tabs = $tabs; + } + + /** + * Injects a blueprint preset + * + * @param array $props + * @return array + */ + protected function preset(array $props): array + { + if (isset($props['preset']) === false) { + return $props; + } + + if (isset(static::$presets[$props['preset']]) === false) { + return $props; + } + + return static::$presets[$props['preset']]($props); + } + + /** + * Returns a single section by name + * + * @param string $name + * @return \Kirby\Cms\Section|null + */ + public function section(string $name) + { + if (empty($this->sections[$name]) === true) { + return null; + } + + // get all props + $props = $this->sections[$name]; + + // inject the blueprint model + $props['model'] = $this->model(); + + // create a new section object + return new Section($props['type'], $props); + } + + /** + * Returns all sections + * + * @return array + */ + public function sections(): array + { + return array_map(function ($section) { + return $this->section($section['name']); + }, $this->sections); + } + + /** + * Returns a single tab by name + * + * @param string $name + * @return array|null + */ + public function tab(string $name): ?array + { + return $this->tabs[$name] ?? null; + } + + /** + * Returns all tabs + * + * @return array + */ + public function tabs(): array + { + return array_values($this->tabs); + } + + /** + * Returns the blueprint title + * + * @return string + */ + public function title(): string + { + return $this->props['title']; + } + + /** + * Converts the blueprint object to a plain array + * + * @return array + */ + public function toArray(): array + { + return $this->props; + } +} diff --git a/kirby/src/Cms/Collection.php b/kirby/src/Cms/Collection.php new file mode 100644 index 0000000..d727054 --- /dev/null +++ b/kirby/src/Cms/Collection.php @@ -0,0 +1,340 @@ + + * @link https://getkirby.com + * @copyright Bastian Allgeier GmbH + * @license https://getkirby.com/license + */ +class Collection extends BaseCollection +{ + use HasMethods; + + /** + * Stores the parent object, which is needed + * in some collections to get the finder methods right. + * + * @var object + */ + protected $parent; + + /** + * Magic getter function + * + * @param string $key + * @param mixed $arguments + * @return mixed + */ + public function __call(string $key, $arguments) + { + // collection methods + if ($this->hasMethod($key) === true) { + return $this->callMethod($key, $arguments); + } + } + + /** + * Creates a new Collection with the given objects + * + * @param array $objects + * @param object|null $parent + */ + public function __construct($objects = [], $parent = null) + { + $this->parent = $parent; + + foreach ($objects as $object) { + $this->add($object); + } + } + + /** + * Internal setter for each object in the Collection. + * This takes care of Component validation and of setting + * the collection prop on each object correctly. + * + * @param string $id + * @param object $object + */ + public function __set(string $id, $object) + { + $this->data[$id] = $object; + } + + /** + * Adds a single object or + * an entire second collection to the + * current collection + * + * @param mixed $object + */ + public function add($object) + { + if (is_a($object, self::class) === true) { + $this->data = array_merge($this->data, $object->data); + } elseif (is_object($object) === true && method_exists($object, 'id') === true) { + $this->__set($object->id(), $object); + } else { + $this->append($object); + } + + return $this; + } + + /** + * Appends an element to the data array + * + * @param mixed ...$args + * @param mixed $key Optional collection key, will be determined from the item if not given + * @param mixed $item + * @return \Kirby\Cms\Collection + */ + public function append(...$args) + { + if (count($args) === 1) { + // try to determine the key from the provided item + if (is_object($args[0]) === true && is_callable([$args[0], 'id']) === true) { + return parent::append($args[0]->id(), $args[0]); + } else { + return parent::append($args[0]); + } + } + + return parent::append(...$args); + } + + /** + * Groups the items by a given field or callback. Returns a collection + * with an item for each group and a collection for each group. + * + * @param string|Closure $field + * @param bool $i Ignore upper/lowercase for group names + * @return \Kirby\Cms\Collection + * @throws \Kirby\Exception\Exception + */ + public function group($field, bool $i = true) + { + if (is_string($field) === true) { + $groups = new Collection([], $this->parent()); + + foreach ($this->data as $key => $item) { + $value = $this->getAttribute($item, $field); + + // make sure that there's always a proper value to group by + if (!$value) { + throw new InvalidArgumentException('Invalid grouping value for key: ' . $key); + } + + // ignore upper/lowercase for group names + if ($i) { + $value = Str::lower($value); + } + + if (isset($groups->data[$value]) === false) { + // create a new entry for the group if it does not exist yet + $groups->data[$value] = new static([$key => $item]); + } else { + // add the item to an existing group + $groups->data[$value]->set($key, $item); + } + } + + return $groups; + } + + return parent::group($field, $i); + } + + /** + * Checks if the given object or id + * is in the collection + * + * @param string|object $id + * @return bool + */ + public function has($id): bool + { + if (is_object($id) === true) { + $id = $id->id(); + } + + return parent::has($id); + } + + /** + * Correct position detection for objects. + * The method will automatically detect objects + * or ids and then search accordingly. + * + * @param string|object $object + * @return int + */ + public function indexOf($object): int + { + if (is_string($object) === true) { + return array_search($object, $this->keys()); + } + + return array_search($object->id(), $this->keys()); + } + + /** + * Returns a Collection without the given element(s) + * + * @param mixed ...$keys any number of keys, passed as individual arguments + * @return \Kirby\Cms\Collection + */ + public function not(...$keys) + { + $collection = $this->clone(); + + foreach ($keys as $key) { + if (is_array($key) === true) { + return $this->not(...$key); + } elseif (is_a($key, 'Kirby\Toolkit\Collection') === true) { + $collection = $collection->not(...$key->keys()); + } elseif (is_object($key) === true) { + $key = $key->id(); + } + + unset($collection->{$key}); + } + + return $collection; + } + + /** + * Add pagination and return a sliced set of data. + * + * @param mixed ...$arguments + * @return \Kirby\Cms\Collection + */ + public function paginate(...$arguments) + { + $this->pagination = Pagination::for($this, ...$arguments); + + // slice and clone the collection according to the pagination + return $this->slice($this->pagination->offset(), $this->pagination->limit()); + } + + /** + * Returns the parent model + * + * @return \Kirby\Cms\Model + */ + public function parent() + { + return $this->parent; + } + + /** + * Prepends an element to the data array + * + * @param mixed ...$args + * @param mixed $key Optional collection key, will be determined from the item if not given + * @param mixed $item + * @return \Kirby\Cms\Collection + */ + public function prepend(...$args) + { + if (count($args) === 1) { + // try to determine the key from the provided item + if (is_object($args[0]) === true && is_callable([$args[0], 'id']) === true) { + return parent::prepend($args[0]->id(), $args[0]); + } else { + return parent::prepend($args[0]); + } + } + + return parent::prepend(...$args); + } + + /** + * Runs a combination of filter, sort, not, + * offset, limit, search and paginate on the collection. + * Any part of the query is optional. + * + * @param array $query + * @return static + */ + public function query(array $query = []) + { + $paginate = $query['paginate'] ?? null; + $search = $query['search'] ?? null; + + unset($query['paginate']); + + $result = parent::query($query); + + if (empty($search) === false) { + if (is_array($search) === true) { + $result = $result->search($search['query'] ?? null, $search['options'] ?? []); + } else { + $result = $result->search($search); + } + } + + if (empty($paginate) === false) { + $result = $result->paginate($paginate); + } + + return $result; + } + + /** + * Removes an object + * + * @param mixed $key the name of the key + */ + public function remove($key) + { + if (is_object($key) === true) { + $key = $key->id(); + } + + return parent::remove($key); + } + + /** + * Searches the collection + * + * @param string|null $query + * @param array $params + * @return self + */ + public function search(string $query = null, $params = []) + { + return Search::collection($this, $query, $params); + } + + /** + * Converts all objects in the collection + * to an array. This can also take a callback + * function to further modify the array result. + * + * @param \Closure|null $map + * @return array + */ + public function toArray(Closure $map = null): array + { + return parent::toArray($map ?? function ($object) { + return $object->toArray(); + }); + } +} diff --git a/kirby/src/Cms/Collections.php b/kirby/src/Cms/Collections.php new file mode 100644 index 0000000..2b49e6f --- /dev/null +++ b/kirby/src/Cms/Collections.php @@ -0,0 +1,141 @@ +collection()` + * method to provide easy access to registered collections + * + * @package Kirby Cms + * @author Bastian Allgeier + * @link https://getkirby.com + * @copyright Bastian Allgeier GmbH + * @license https://getkirby.com/license + */ +class Collections +{ + /** + * Each collection is cached once it + * has been called, to avoid further + * processing on sequential calls to + * the same collection. + * + * @var array + */ + protected $cache = []; + + /** + * Store of all collections + * + * @var array + */ + protected $collections = []; + + /** + * Magic caller to enable something like + * `$collections->myCollection()` + * + * @param string $name + * @param array $arguments + * @return \Kirby\Cms\Collection|null + */ + public function __call(string $name, array $arguments = []) + { + return $this->get($name, ...$arguments); + } + + /** + * Loads a collection by name if registered + * + * @param string $name + * @param array $data + * @return \Kirby\Cms\Collection|null + */ + public function get(string $name, array $data = []) + { + // if not yet loaded + if (isset($this->collections[$name]) === false) { + $this->collections[$name] = $this->load($name); + } + + // if not yet cached + if ( + isset($this->cache[$name]) === false || + $this->cache[$name]['data'] !== $data + ) { + $controller = new Controller($this->collections[$name]); + $this->cache[$name] = [ + 'result' => $controller->call(null, $data), + 'data' => $data + ]; + } + + // return cloned object + if (is_object($this->cache[$name]['result']) === true) { + return clone $this->cache[$name]['result']; + } + + return $this->cache[$name]['result']; + } + + /** + * Checks if a collection exists + * + * @param string $name + * @return bool + */ + public function has(string $name): bool + { + if (isset($this->collections[$name]) === true) { + return true; + } + + try { + $this->load($name); + return true; + } catch (NotFoundException $e) { + return false; + } + } + + /** + * Loads collection from php file in a + * given directory or from plugin extension. + * + * @param string $name + * @return mixed + * @throws \Kirby\Exception\NotFoundException + */ + public function load(string $name) + { + $kirby = App::instance(); + + // first check for collection file + $file = $kirby->root('collections') . '/' . $name . '.php'; + + if (is_file($file) === true) { + $collection = F::load($file); + + if (is_a($collection, 'Closure')) { + return $collection; + } + } + + // fallback to collections from plugins + $collections = $kirby->extensions('collections'); + + if (isset($collections[$name]) === true) { + return $collections[$name]; + } + + throw new NotFoundException('The collection cannot be found'); + } +} diff --git a/kirby/src/Cms/Content.php b/kirby/src/Cms/Content.php new file mode 100644 index 0000000..d810934 --- /dev/null +++ b/kirby/src/Cms/Content.php @@ -0,0 +1,266 @@ + + * @link https://getkirby.com + * @copyright Bastian Allgeier GmbH + * @license https://getkirby.com/license + */ +class Content +{ + /** + * The raw data array + * + * @var array + */ + protected $data = []; + + /** + * Cached field objects + * Once a field is being fetched + * it is added to this array for + * later reuse + * + * @var array + */ + protected $fields = []; + + /** + * A potential parent object. + * Not necessarily needed. Especially + * for testing, but field methods might + * need it. + * + * @var Model + */ + protected $parent; + + /** + * Magic getter for content fields + * + * @param string $name + * @param array $arguments + * @return \Kirby\Cms\Field + */ + public function __call(string $name, array $arguments = []) + { + return $this->get($name); + } + + /** + * Creates a new Content object + * + * @param array|null $data + * @param object|null $parent + */ + public function __construct(array $data = [], $parent = null) + { + $this->data = $data; + $this->parent = $parent; + } + + /** + * Same as `self::data()` to improve + * `var_dump` output + * + * @see self::data() + * @return array + */ + public function __debugInfo(): array + { + return $this->toArray(); + } + + /** + * Converts the content to a new blueprint + * + * @param string $to + * @return array + */ + public function convertTo(string $to): array + { + // prepare data + $data = []; + $content = $this; + + // blueprints + $old = $this->parent->blueprint(); + $subfolder = dirname($old->name()); + $new = Blueprint::factory($subfolder . '/' . $to, $subfolder . '/default', $this->parent); + + // forms + $oldForm = new Form(['fields' => $old->fields(), 'model' => $this->parent]); + $newForm = new Form(['fields' => $new->fields(), 'model' => $this->parent]); + + // fields + $oldFields = $oldForm->fields(); + $newFields = $newForm->fields(); + + // go through all fields of new template + foreach ($newFields as $newField) { + $name = $newField->name(); + $oldField = $oldFields->get($name); + + // field name and type matches with old template + if ($oldField && $oldField->type() === $newField->type()) { + $data[$name] = $content->get($name)->value(); + } else { + $data[$name] = $newField->default(); + } + } + + // preserve existing fields + return array_merge($this->data, $data); + } + + /** + * Returns the raw data array + * + * @return array + */ + public function data(): array + { + return $this->data; + } + + /** + * Returns all registered field objects + * + * @return array + */ + public function fields(): array + { + foreach ($this->data as $key => $value) { + $this->get($key); + } + return $this->fields; + } + + /** + * Returns either a single field object + * or all registered fields + * + * @param string|null $key + * @return \Kirby\Cms\Field|array + */ + public function get(string $key = null) + { + if ($key === null) { + return $this->fields(); + } + + $key = strtolower($key); + + if (isset($this->fields[$key])) { + return $this->fields[$key]; + } + + // fetch the value no matter the case + $data = $this->data(); + $value = $data[$key] ?? array_change_key_case($data)[$key] ?? null; + + return $this->fields[$key] = new Field($this->parent, $key, $value); + } + + /** + * Checks if a content field is set + * + * @param string $key + * @return bool + */ + public function has(string $key): bool + { + $key = strtolower($key); + $data = array_change_key_case($this->data); + + return isset($data[$key]) === true; + } + + /** + * Returns all field keys + * + * @return array + */ + public function keys(): array + { + return array_keys($this->data()); + } + + /** + * Returns a clone of the content object + * without the fields, specified by the + * passed key(s) + * + * @param string ...$keys + * @return static + */ + public function not(...$keys) + { + $copy = clone $this; + $copy->fields = null; + + foreach ($keys as $key) { + unset($copy->data[$key]); + } + + return $copy; + } + + /** + * Returns the parent + * Site, Page, File or User object + * + * @return \Kirby\Cms\Model + */ + public function parent() + { + return $this->parent; + } + + /** + * Set the parent model + * + * @param \Kirby\Cms\Model $parent + * @return $this + */ + public function setParent(Model $parent) + { + $this->parent = $parent; + return $this; + } + + /** + * Returns the raw data array + * + * @see self::data() + * @return array + */ + public function toArray(): array + { + return $this->data(); + } + + /** + * Updates the content and returns + * a cloned object + * + * @param array|null $content + * @param bool $overwrite + * @return $this + */ + public function update(array $content = null, bool $overwrite = false) + { + $this->data = $overwrite === true ? (array)$content : array_merge($this->data, (array)$content); + + // clear cache of Field objects + $this->fields = []; + + return $this; + } +} diff --git a/kirby/src/Cms/ContentLock.php b/kirby/src/Cms/ContentLock.php new file mode 100644 index 0000000..7769630 --- /dev/null +++ b/kirby/src/Cms/ContentLock.php @@ -0,0 +1,232 @@ + + * @link https://getkirby.com + * @copyright Bastian Allgeier GmbH + * @license https://getkirby.com/license + */ +class ContentLock +{ + /** + * Lock data + * + * @var array + */ + protected $data; + + /** + * The model to manage locking/unlocking for + * + * @var ModelWithContent + */ + protected $model; + + /** + * @param \Kirby\Cms\ModelWithContent $model + */ + public function __construct(ModelWithContent $model) + { + $this->model = $model; + $this->data = $this->kirby()->locks()->get($model); + } + + /** + * Clears the lock unconditionally + * + * @return bool + */ + protected function clearLock(): bool + { + // if no lock exists, skip + if (isset($this->data['lock']) === false) { + return true; + } + + // remove lock + unset($this->data['lock']); + + return $this->kirby()->locks()->set($this->model, $this->data); + } + + /** + * Sets lock with the current user + * + * @return bool + * @throws \Kirby\Exception\DuplicateException + */ + public function create(): bool + { + // check if model is already locked by another user + if ( + isset($this->data['lock']) === true && + $this->data['lock']['user'] !== $this->user()->id() + ) { + $id = ContentLocks::id($this->model); + throw new DuplicateException($id . ' is already locked'); + } + + $this->data['lock'] = [ + 'user' => $this->user()->id(), + 'time' => time() + ]; + + return $this->kirby()->locks()->set($this->model, $this->data); + } + + /** + * Returns either `false` or array with `user`, `email`, + * `time` and `unlockable` keys + * + * @return array|bool + */ + public function get() + { + $data = $this->data['lock'] ?? []; + + if (empty($data) === false && $data['user'] !== $this->user()->id()) { + if ($user = $this->kirby()->user($data['user'])) { + $time = (int)($data['time']); + + return [ + 'user' => $user->id(), + 'email' => $user->email(), + 'time' => $time, + 'unlockable' => ($time + 60) <= time() + ]; + } + + // clear lock if user not found + $this->clearLock(); + } + + return false; + } + + /** + * Returns if the model is locked by another user + * + * @return bool + */ + public function isLocked(): bool + { + $lock = $this->get(); + + if ($lock !== false && $lock['user'] !== $this->user()->id()) { + return true; + } + + return false; + } + + /** + * Returns if the current user's lock has been removed by another user + * + * @return bool + */ + public function isUnlocked(): bool + { + $data = $this->data['unlock'] ?? []; + + return in_array($this->user()->id(), $data) === true; + } + + /** + * Returns the app instance + * + * @return \Kirby\Cms\App + */ + protected function kirby(): App + { + return $this->model->kirby(); + } + + /** + * Removes lock of current user + * + * @return bool + * @throws \Kirby\Exception\LogicException + */ + public function remove(): bool + { + // if no lock exists, skip + if (isset($this->data['lock']) === false) { + return true; + } + + // check if lock was set by another user + if ($this->data['lock']['user'] !== $this->user()->id()) { + throw new LogicException([ + 'fallback' => 'The content lock can only be removed by the user who created it. Use unlock instead.', + 'httpCode' => 409 + ]); + } + + return $this->clearLock(); + } + + /** + * Removes unlock information for current user + * + * @return bool + */ + public function resolve(): bool + { + // if no unlocks exist, skip + if (isset($this->data['unlock']) === false) { + return true; + } + + // remove user from unlock array + $this->data['unlock'] = array_diff( + $this->data['unlock'], + [$this->user()->id()] + ); + + return $this->kirby()->locks()->set($this->model, $this->data); + } + + /** + * Removes current lock and adds lock user to unlock data + * + * @return bool + */ + public function unlock(): bool + { + // if no lock exists, skip + if (isset($this->data['lock']) === false) { + return true; + } + + // add lock user to unlocked data + $this->data['unlock'] = $this->data['unlock'] ?? []; + $this->data['unlock'][] = $this->data['lock']['user']; + + return $this->clearLock(); + } + + /** + * Returns currently authenticated user; + * throws exception if none is authenticated + * + * @return \Kirby\Cms\User + * @throws \Kirby\Exception\PermissionException + */ + protected function user(): User + { + if ($user = $this->kirby()->user()) { + return $user; + } + + throw new PermissionException('No user authenticated.'); + } +} diff --git a/kirby/src/Cms/ContentLocks.php b/kirby/src/Cms/ContentLocks.php new file mode 100644 index 0000000..bd67918 --- /dev/null +++ b/kirby/src/Cms/ContentLocks.php @@ -0,0 +1,228 @@ +, + * Lukas Bestle + * @link https://getkirby.com + * @copyright Bastian Allgeier GmbH + * @license https://getkirby.com/license + */ +class ContentLocks +{ + /** + * Data from the `.lock` files + * that have been read so far + * cached by `.lock` file path + * + * @var array + */ + protected $data = []; + + /** + * PHP file handles for all currently + * open `.lock` files + * + * @var array + */ + protected $handles = []; + + /** + * Closes the open file handles + * + * @codeCoverageIgnore + */ + public function __destruct() + { + foreach ($this->handles as $file => $handle) { + $this->closeHandle($file); + } + } + + /** + * Removes the file lock and closes the file handle + * + * @param string $file + * @return void + * @throws \Kirby\Exception\Exception + */ + protected function closeHandle(string $file) + { + if (isset($this->handles[$file]) === false) { + return; + } + + $handle = $this->handles[$file]; + $result = flock($handle, LOCK_UN) && fclose($handle); + + if ($result !== true) { + throw new Exception('Unexpected file system error.'); // @codeCoverageIgnore + } + + unset($this->handles[$file]); + } + + /** + * Returns the path to a model's lock file + * + * @param \Kirby\Cms\ModelWithContent $model + * @return string + */ + public static function file(ModelWithContent $model): string + { + return $model->contentFileDirectory() . '/.lock'; + } + + /** + * Returns the lock/unlock data for the specified model + * + * @param \Kirby\Cms\ModelWithContent $model + * @return array + */ + public function get(ModelWithContent $model): array + { + $file = static::file($model); + $id = static::id($model); + + // return from cache if file was already loaded + if (isset($this->data[$file]) === true) { + return $this->data[$file][$id] ?? []; + } + + // first get a handle to ensure a file system lock + $handle = $this->handle($file); + + if (is_resource($handle) === true) { + // read data from file + clearstatcache(); + $filesize = filesize($file); + + if ($filesize > 0) { + // always read the whole file + rewind($handle); + $string = fread($handle, $filesize); + $data = Data::decode($string, 'yaml'); + } + } + + $this->data[$file] = $data ?? []; + + return $this->data[$file][$id] ?? []; + } + + /** + * Returns the file handle to a `.lock` file + * + * @param string $file + * @param bool $create Whether to create the file if it does not exist + * @return resource|null File handle + * @throws \Kirby\Exception\Exception + */ + protected function handle(string $file, bool $create = false) + { + // check for an already open handle + if (isset($this->handles[$file]) === true) { + return $this->handles[$file]; + } + + // don't create a file if not requested + if (is_file($file) !== true && $create !== true) { + return null; + } + + $handle = @fopen($file, 'c+b'); + if (is_resource($handle) === false) { + throw new Exception('Lock file ' . $file . ' could not be opened.'); // @codeCoverageIgnore + } + + // lock the lock file exclusively to prevent changes by other threads + $result = flock($handle, LOCK_EX); + if ($result !== true) { + throw new Exception('Unexpected file system error.'); // @codeCoverageIgnore + } + + return $this->handles[$file] = $handle; + } + + /** + * Returns model ID used as the key for the data array; + * prepended with a slash because the $site otherwise won't have an ID + * + * @param \Kirby\Cms\ModelWithContent $model + * @return string + */ + public static function id(ModelWithContent $model): string + { + return '/' . $model->id(); + } + + /** + * Sets and writes the lock/unlock data for the specified model + * + * @param \Kirby\Cms\ModelWithContent $model + * @param array $data + * @return bool + * @throws \Kirby\Exception\Exception + */ + public function set(ModelWithContent $model, array $data): bool + { + $file = static::file($model); + $id = static::id($model); + $handle = $this->handle($file, true); + + $this->data[$file][$id] = $data; + + // make sure to unset model id entries, + // if no lock data for the model exists + foreach ($this->data[$file] as $id => $data) { + // there is no data for that model whatsoever + if ( + isset($data['lock']) === false && + (isset($data['unlock']) === false || + count($data['unlock']) === 0) + ) { + unset($this->data[$file][$id]); + + // there is empty unlock data, but still lock data + } elseif ( + isset($data['unlock']) === true && + count($data['unlock']) === 0 + ) { + unset($this->data[$file][$id]['unlock']); + } + } + + // there is no data left in the file whatsoever, delete the file + if (count($this->data[$file]) === 0) { + unset($this->data[$file]); + + // close the file handle, otherwise we can't delete it on Windows + $this->closeHandle($file); + + return F::remove($file); + } + + $yaml = Data::encode($this->data[$file], 'yaml'); + + // delete all file contents first + if (rewind($handle) !== true || ftruncate($handle, 0) !== true) { + throw new Exception('Could not write lock file ' . $file . '.'); // @codeCoverageIgnore + } + + // write the new contents + $result = fwrite($handle, $yaml); + if (is_int($result) === false || $result === 0) { + throw new Exception('Could not write lock file ' . $file . '.'); // @codeCoverageIgnore + } + + return true; + } +} diff --git a/kirby/src/Cms/ContentTranslation.php b/kirby/src/Cms/ContentTranslation.php new file mode 100644 index 0000000..906e79c --- /dev/null +++ b/kirby/src/Cms/ContentTranslation.php @@ -0,0 +1,242 @@ + + * @link https://getkirby.com + * @copyright Bastian Allgeier GmbH + * @license https://getkirby.com/license + */ +class ContentTranslation +{ + use Properties; + + /** + * @var string + */ + protected $code; + + /** + * @var array + */ + protected $content; + + /** + * @var string + */ + protected $contentFile; + + /** + * @var Model + */ + protected $parent; + + /** + * @var string + */ + protected $slug; + + /** + * Creates a new translation object + * + * @param array $props + */ + public function __construct(array $props) + { + $this->setRequiredProperties($props, ['parent', 'code']); + $this->setOptionalProperties($props, ['slug', 'content']); + } + + /** + * Improve `var_dump` output + * + * @return array + */ + public function __debugInfo(): array + { + return $this->toArray(); + } + + /** + * Returns the language code of the + * translation + * + * @return string + */ + public function code(): string + { + return $this->code; + } + + /** + * Returns the translation content + * as plain array + * + * @return array + */ + public function content(): array + { + $parent = $this->parent(); + + if ($this->content === null) { + $this->content = $parent->readContent($this->code()); + } + + $content = $this->content; + + // merge with the default content + if ($this->isDefault() === false && $defaultLanguage = $parent->kirby()->defaultLanguage()) { + $default = []; + + if ($defaultTranslation = $parent->translation($defaultLanguage->code())) { + $default = $defaultTranslation->content(); + } + + $content = array_merge($default, $content); + } + + return $content; + } + + /** + * Absolute path to the translation content file + * + * @return string + */ + public function contentFile(): string + { + return $this->contentFile = $this->parent->contentFile($this->code, true); + } + + /** + * Checks if the translation file exists + * + * @return bool + */ + public function exists(): bool + { + return file_exists($this->contentFile()) === true; + } + + /** + * Returns the translation code as id + * + * @return string + */ + public function id(): string + { + return $this->code(); + } + + /** + * Checks if the this is the default translation + * of the model + * + * @return bool + */ + public function isDefault(): bool + { + if ($defaultLanguage = $this->parent->kirby()->defaultLanguage()) { + return $this->code() === $defaultLanguage->code(); + } + + return false; + } + + /** + * Returns the parent page, file or site object + * + * @return \Kirby\Cms\Model + */ + public function parent() + { + return $this->parent; + } + + /** + * @param string $code + * @return $this + */ + protected function setCode(string $code) + { + $this->code = $code; + return $this; + } + + /** + * @param array|null $content + * @return $this + */ + protected function setContent(array $content = null) + { + $this->content = $content; + return $this; + } + + /** + * @param \Kirby\Cms\Model $parent + * @return $this + */ + protected function setParent(Model $parent) + { + $this->parent = $parent; + return $this; + } + + /** + * @param string|null $slug + * @return $this + */ + protected function setSlug(string $slug = null) + { + $this->slug = $slug; + return $this; + } + + /** + * Returns the custom translation slug + * + * @return string|null + */ + public function slug(): ?string + { + return $this->slug = $this->slug ?? ($this->content()['slug'] ?? null); + } + + /** + * Merge the old and new data + * + * @param array|null $data + * @param bool $overwrite + * @return $this + */ + public function update(array $data = null, bool $overwrite = false) + { + $this->content = $overwrite === true ? (array)$data : array_merge($this->content(), (array)$data); + return $this; + } + + /** + * Converts the most important translation + * props to an array + * + * @return array + */ + public function toArray(): array + { + return [ + 'code' => $this->code(), + 'content' => $this->content(), + 'exists' => $this->exists(), + 'slug' => $this->slug(), + ]; + } +} diff --git a/kirby/src/Cms/Dir.php b/kirby/src/Cms/Dir.php new file mode 100644 index 0000000..7c42c62 --- /dev/null +++ b/kirby/src/Cms/Dir.php @@ -0,0 +1,180 @@ + + * @link https://getkirby.com + * @copyright Bastian Allgeier GmbH + * @license https://getkirby.com/license + */ +class Dir extends \Kirby\Toolkit\Dir +{ + public static $numSeparator = '_'; + + /** + * Scans the directory and analyzes files, + * content, meta info and children. This is used + * in Page, Site and User objects to fetch all + * relevant information. + * + * @param string $dir + * @param string $contentExtension + * @param array|null $contentIgnore + * @param bool $multilang + * @return array + */ + public static function inventory(string $dir, string $contentExtension = 'txt', array $contentIgnore = null, bool $multilang = false): array + { + $dir = realpath($dir); + + $inventory = [ + 'children' => [], + 'files' => [], + 'template' => 'default', + ]; + + if ($dir === false) { + return $inventory; + } + + $items = Dir::read($dir, $contentIgnore); + + // a temporary store for all content files + $content = []; + + // sort all items naturally to avoid sorting issues later + natsort($items); + + foreach ($items as $item) { + + // ignore all items with a leading dot + if (in_array(substr($item, 0, 1), ['.', '_']) === true) { + continue; + } + + $root = $dir . '/' . $item; + + if (is_dir($root) === true) { + + // extract the slug and num of the directory + if (preg_match('/^([0-9]+)' . static::$numSeparator . '(.*)$/', $item, $match)) { + $num = $match[1]; + $slug = $match[2]; + } else { + $num = null; + $slug = $item; + } + + $inventory['children'][] = [ + 'dirname' => $item, + 'model' => null, + 'num' => $num, + 'root' => $root, + 'slug' => $slug, + ]; + } else { + $extension = pathinfo($item, PATHINFO_EXTENSION); + + switch ($extension) { + case 'htm': + case 'html': + case 'php': + // don't track those files + break; + case $contentExtension: + $content[] = pathinfo($item, PATHINFO_FILENAME); + break; + default: + $inventory['files'][$item] = [ + 'filename' => $item, + 'extension' => $extension, + 'root' => $root, + ]; + } + } + } + + // remove the language codes from all content filenames + if ($multilang === true) { + foreach ($content as $key => $filename) { + $content[$key] = pathinfo($filename, PATHINFO_FILENAME); + } + + $content = array_unique($content); + } + + $inventory = static::inventoryContent($inventory, $content); + $inventory = static::inventoryModels($inventory, $contentExtension, $multilang); + + return $inventory; + } + + /** + * Take all content files, + * remove those who are meta files and + * detect the main content file + * + * @param array $inventory + * @param array $content + * @return array + */ + protected static function inventoryContent(array $inventory, array $content): array + { + + // filter meta files from the content file + if (empty($content) === true) { + $inventory['template'] = 'default'; + return $inventory; + } + + foreach ($content as $contentName) { + + // could be a meta file. i.e. cover.jpg + if (isset($inventory['files'][$contentName]) === true) { + continue; + } + + // it's most likely the template + $inventory['template'] = $contentName; + } + + return $inventory; + } + + /** + * Go through all inventory children + * and inject a model for each + * + * @param array $inventory + * @param string $contentExtension + * @param bool $multilang + * @return array + */ + protected static function inventoryModels(array $inventory, string $contentExtension, bool $multilang = false): array + { + // inject models + if (empty($inventory['children']) === false && empty(Page::$models) === false) { + if ($multilang === true) { + $contentExtension = App::instance()->defaultLanguage()->code() . '.' . $contentExtension; + } + + foreach ($inventory['children'] as $key => $child) { + foreach (Page::$models as $modelName => $modelClass) { + if (file_exists($child['root'] . '/' . $modelName . '.' . $contentExtension) === true) { + $inventory['children'][$key]['model'] = $modelName; + break; + } + } + } + } + + return $inventory; + } +} diff --git a/kirby/src/Cms/Email.php b/kirby/src/Cms/Email.php new file mode 100644 index 0000000..4b78858 --- /dev/null +++ b/kirby/src/Cms/Email.php @@ -0,0 +1,255 @@ + + * @link https://getkirby.com + * @copyright Bastian Allgeier GmbH + * @license https://getkirby.com/license + */ +class Email +{ + /** + * Options configured through the `email` CMS option + * + * @var array + */ + protected $options; + + /** + * Props for the email object; will be passed to the + * Kirby\Email\Email class + * + * @var array + */ + protected $props; + + /** + * Class constructor + * + * @param string|array $preset Preset name from the config or a simple props array + * @param array $props Props array to override the $preset + */ + public function __construct($preset = [], array $props = []) + { + $this->options = App::instance()->option('email'); + + // build a prop array based on preset and props + $preset = $this->preset($preset); + $this->props = array_merge($preset, $props); + + // add transport settings + if (isset($this->props['transport']) === false) { + $this->props['transport'] = $this->options['transport'] ?? []; + } + + // add predefined beforeSend option + if (isset($this->props['beforeSend']) === false) { + $this->props['beforeSend'] = $this->options['beforeSend'] ?? null; + } + + // transform model objects to values + $this->transformUserSingle('from', 'fromName'); + $this->transformUserSingle('replyTo', 'replyToName'); + $this->transformUserMultiple('to'); + $this->transformUserMultiple('cc'); + $this->transformUserMultiple('bcc'); + $this->transformFile('attachments'); + + // load template for body text + $this->template(); + } + + /** + * Grabs a preset from the options; supports fixed + * prop arrays in case a preset is not needed + * + * @param string|array $preset Preset name or simple prop array + * @return array + * @throws \Kirby\Exception\NotFoundException + */ + protected function preset($preset): array + { + // only passed props, not preset name + if (is_array($preset) === true) { + return $preset; + } + + // preset does not exist + if (isset($this->options['presets'][$preset]) !== true) { + throw new NotFoundException([ + 'key' => 'email.preset.notFound', + 'data' => ['name' => $preset] + ]); + } + + return $this->options['presets'][$preset]; + } + + /** + * Renders the email template(s) and sets the body props + * to the result + * + * @return void + * @throws \Kirby\Exception\NotFoundException + */ + protected function template(): void + { + if (isset($this->props['template']) === true) { + + // prepare data to be passed to template + $data = $this->props['data'] ?? []; + + // check if html/text templates exist + $html = $this->getTemplate($this->props['template'], 'html'); + $text = $this->getTemplate($this->props['template'], 'text'); + + if ($html->exists()) { + $this->props['body'] = [ + 'html' => $html->render($data) + ]; + + if ($text->exists()) { + $this->props['body']['text'] = $text->render($data); + } + + // fallback to single email text template + } elseif ($text->exists()) { + $this->props['body'] = $text->render($data); + } else { + throw new NotFoundException('The email template "' . $this->props['template'] . '" cannot be found'); + } + } + } + + /** + * Returns an email template by name and type + * + * @param string $name Template name + * @param string|null $type `html` or `text` + * @return \Kirby\Cms\Template + */ + protected function getTemplate(string $name, string $type = null) + { + return App::instance()->template('emails/' . $name, $type, 'text'); + } + + /** + * Returns the prop array + * + * @return array + */ + public function toArray(): array + { + return $this->props; + } + + /** + * Transforms file object(s) to an array of file roots; + * supports simple strings, file objects or collections/arrays of either + * + * @param string $prop Prop to transform + * @return void + */ + protected function transformFile(string $prop): void + { + $this->props[$prop] = $this->transformModel($prop, 'Kirby\Cms\File', 'root'); + } + + /** + * Transforms Kirby models to a simplified collection + * + * @param string $prop Prop to transform + * @param string $class Fully qualified class name of the supported model + * @param string $contentValue Model method that returns the array value + * @param string|null $contentKey Optional model method that returns the array key; + * returns a simple value-only array if not given + * @return array Simple key-value or just value array with the transformed prop data + */ + protected function transformModel(string $prop, string $class, string $contentValue, string $contentKey = null): array + { + $value = $this->props[$prop] ?? []; + + // ensure consistent input by making everything an iterable value + if (is_iterable($value) !== true) { + $value = [$value]; + } + + $result = []; + foreach ($value as $key => $item) { + if (is_string($item) === true) { + // value is already a string + if ($contentKey !== null && is_string($key) === true) { + $result[$key] = $item; + } else { + $result[] = $item; + } + } elseif (is_a($item, $class) === true) { + // value is a model object, get value through content method(s) + if ($contentKey !== null) { + $result[(string)$item->$contentKey()] = (string)$item->$contentValue(); + } else { + $result[] = (string)$item->$contentValue(); + } + } else { + // invalid input + throw new InvalidArgumentException('Invalid input for prop "' . $prop . '", expected string or "' . $class . '" object or collection'); + } + } + + return $result; + } + + /** + * Transforms an user object to the email address and name; + * supports simple strings, user objects or collections/arrays of either + * (note: only the first item in a collection/array will be used) + * + * @param string $addressProp Prop with the email address + * @param string $nameProp Prop with the name corresponding to the $addressProp + * @return void + */ + protected function transformUserSingle(string $addressProp, string $nameProp): void + { + $result = $this->transformModel($addressProp, 'Kirby\Cms\User', 'name', 'email'); + + $address = array_keys($result)[0] ?? null; + $name = $result[$address] ?? null; + + // if the array is non-associative, the value is the address + if (is_int($address) === true) { + $address = $name; + $name = null; + } + + // always use the address as we have transformed that prop above + $this->props[$addressProp] = $address; + + // only use the name from the user if no custom name was set + if (isset($this->props[$nameProp]) === false || $this->props[$nameProp] === null) { + $this->props[$nameProp] = $name; + } + } + + /** + * Transforms user object(s) to the email address(es) and name(s); + * supports simple strings, user objects or collections/arrays of either + * + * @param string $prop Prop to transform + * @return void + */ + protected function transformUserMultiple(string $prop): void + { + $this->props[$prop] = $this->transformModel($prop, 'Kirby\Cms\User', 'name', 'email'); + } +} diff --git a/kirby/src/Cms/Event.php b/kirby/src/Cms/Event.php new file mode 100644 index 0000000..73fa2c3 --- /dev/null +++ b/kirby/src/Cms/Event.php @@ -0,0 +1,290 @@ +trigger()` + * or `$kirby->apply()` methods are called. It collects all + * event information and handles calling the individual hooks. + * @since 3.4.0 + * + * @package Kirby Cms + * @author Lukas Bestle , + * Ahmet Bora + * @link https://getkirby.com + * @copyright Bastian Allgeier GmbH + * @license https://getkirby.com/license + */ +class Event +{ + /** + * The full event name + * (e.g. `page.create:after`) + * + * @var string + */ + protected $name; + + /** + * The event type + * (e.g. `page` in `page.create:after`) + * + * @var string + */ + protected $type; + + /** + * The event action + * (e.g. `create` in `page.create:after`) + * + * @var string|null + */ + protected $action; + + /** + * The event state + * (e.g. `after` in `page.create:after`) + * + * @var string|null + */ + protected $state; + + /** + * The event arguments + * + * @var array + */ + protected $arguments = []; + + /** + * Class constructor + * + * @param string $name Full event name + * @param array $arguments Associative array of named event arguments + */ + public function __construct(string $name, array $arguments = []) + { + // split the event name into `$type.$action:$state` + // $action and $state are optional; + // if there is more than one dot, $type will be greedy + $regex = '/^(?.+?)(?:\.(?[^.]*?))?(?:\:(?.*))?$/'; + preg_match($regex, $name, $matches, PREG_UNMATCHED_AS_NULL); + + $this->name = $name; + $this->type = $matches['type']; + $this->action = $matches['action'] ?? null; + $this->state = $matches['state'] ?? null; + $this->arguments = $arguments; + } + + /** + * Magic caller for event arguments + * + * @param string $method + * @param array $arguments + * @return mixed + */ + public function __call(string $method, array $arguments = []) + { + return $this->argument($method); + } + + /** + * Improved `var_dump` output + * + * @return array + */ + public function __debugInfo(): array + { + return $this->toArray(); + } + + /** + * Makes it possible to simply echo + * or stringify the entire object + * + * @return string + */ + public function __toString(): string + { + return $this->toString(); + } + + /** + * Returns the action of the event (e.g. `create`) + * or `null` if the event name does not include an action + * + * @return string|null + */ + public function action(): ?string + { + return $this->action; + } + + /** + * Returns a specific event argument + * + * @param string $name + * @return mixed + */ + public function argument(string $name) + { + if (isset($this->arguments[$name]) === true) { + return $this->arguments[$name]; + } + + return null; + } + + /** + * Returns the arguments of the event + * + * @return array + */ + public function arguments(): array + { + return $this->arguments; + } + + /** + * Calls a hook with the event data and returns + * the hook's return value + * + * @param object|null $bind Optional object to bind to the hook function + * @param \Closure $hook + * @return mixed + */ + public function call(?object $bind, Closure $hook) + { + // collect the list of possible hook arguments + $data = $this->arguments(); + $data['event'] = $this; + + // magically call the hook with the arguments it requested + $hook = new Controller($hook); + return $hook->call($bind, $data); + } + + /** + * Returns the full name of the event + * + * @return string + */ + public function name(): string + { + return $this->name; + } + + /** + * Returns the full list of possible wildcard + * event names based on the current event name + * + * @return array + */ + public function nameWildcards(): array + { + // if the event is already a wildcard event, no further variation is possible + if ($this->type === '*' || $this->action === '*' || $this->state === '*') { + return []; + } + + if ($this->action !== null && $this->state !== null) { + // full $type.$action:$state event + + return [ + $this->type . '.*:' . $this->state, + $this->type . '.' . $this->action . ':*', + $this->type . '.*:*', + '*.' . $this->action . ':' . $this->state, + '*.' . $this->action . ':*', + '*:' . $this->state, + '*' + ]; + } elseif ($this->state !== null) { + // event without action: $type:$state + + return [ + $this->type . ':*', + '*:' . $this->state, + '*' + ]; + } elseif ($this->action !== null) { + // event without state: $type.$action + + return [ + $this->type . '.*', + '*.' . $this->action, + '*' + ]; + } else { + // event with a simple name + + return ['*']; + } + } + + /** + * Returns the state of the event (e.g. `after`) + * + * @return string|null + */ + public function state(): ?string + { + return $this->state; + } + + /** + * Returns the event data as array + * + * @return array + */ + public function toArray(): array + { + return [ + 'name' => $this->name, + 'arguments' => $this->arguments + ]; + } + + /** + * Returns the event name as string + * + * @return string + */ + public function toString(): string + { + return $this->name; + } + + /** + * Returns the type of the event (e.g. `page`) + * + * @return string + */ + public function type(): string + { + return $this->type; + } + + /** + * Updates a given argument with a new value + * + * @internal + * @param string $name + * @param mixed $value + * @return void + * @throws \Kirby\Exception\InvalidArgumentException + */ + public function updateArgument(string $name, $value): void + { + if (array_key_exists($name, $this->arguments) !== true) { + throw new InvalidArgumentException('The argument ' . $name . ' does not exist'); + } + + $this->arguments[$name] = $value; + } +} diff --git a/kirby/src/Cms/Field.php b/kirby/src/Cms/Field.php new file mode 100644 index 0000000..5a2caf4 --- /dev/null +++ b/kirby/src/Cms/Field.php @@ -0,0 +1,257 @@ +myField()->lower(); + * ``` + * + * @package Kirby Cms + * @author Bastian Allgeier + * @link https://getkirby.com + * @copyright Bastian Allgeier GmbH + * @license https://getkirby.com/license + */ +class Field +{ + /** + * Field method aliases + * + * @var array + */ + public static $aliases = []; + + /** + * The field name + * + * @var string + */ + protected $key; + + /** + * Registered field methods + * + * @var array + */ + public static $methods = []; + + /** + * The parent object if available. + * This will be the page, site, user or file + * to which the content belongs + * + * @var Model + */ + protected $parent; + + /** + * The value of the field + * + * @var mixed + */ + public $value; + + /** + * Magic caller for field methods + * + * @param string $method + * @param array $arguments + * @return mixed + */ + public function __call(string $method, array $arguments = []) + { + $method = strtolower($method); + + if (isset(static::$methods[$method]) === true) { + return static::$methods[$method](clone $this, ...$arguments); + } + + if (isset(static::$aliases[$method]) === true) { + $method = strtolower(static::$aliases[$method]); + + if (isset(static::$methods[$method]) === true) { + return static::$methods[$method](clone $this, ...$arguments); + } + } + + return $this; + } + + /** + * Creates a new field object + * + * @param object|null $parent + * @param string $key + * @param mixed $value + */ + public function __construct(?object $parent, string $key, $value) + { + $this->key = $key; + $this->value = $value; + $this->parent = $parent; + } + + /** + * Simplifies the var_dump result + * + * @see Field::toArray + * @return array + */ + public function __debugInfo() + { + return $this->toArray(); + } + + /** + * Makes it possible to simply echo + * or stringify the entire object + * + * @see Field::toString + * @return string + */ + public function __toString(): string + { + return $this->toString(); + } + + /** + * Checks if the field exists in the content data array + * + * @return bool + */ + public function exists(): bool + { + return $this->parent->content()->has($this->key); + } + + /** + * Checks if the field content is empty + * + * @return bool + */ + public function isEmpty(): bool + { + return empty($this->value) === true && in_array($this->value, [0, '0', false], true) === false; + } + + /** + * Checks if the field content is not empty + * + * @return bool + */ + public function isNotEmpty(): bool + { + return $this->isEmpty() === false; + } + + /** + * Returns the name of the field + * + * @return string + */ + public function key(): string + { + return $this->key; + } + + /** + * @see Field::parent() + * @return \Kirby\Cms\Model|null + */ + public function model() + { + return $this->parent; + } + + /** + * Provides a fallback if the field value is empty + * + * @param mixed $fallback + * @return $this|static + */ + public function or($fallback = null) + { + if ($this->isNotEmpty()) { + return $this; + } + + if (is_a($fallback, 'Kirby\Cms\Field') === true) { + return $fallback; + } + + $field = clone $this; + $field->value = $fallback; + return $field; + } + + /** + * Returns the parent object of the field + * + * @return \Kirby\Cms\Model|null + */ + public function parent() + { + return $this->parent; + } + + /** + * Converts the Field object to an array + * + * @return array + */ + public function toArray(): array + { + return [$this->key => $this->value]; + } + + /** + * Returns the field value as string + * + * @return string + */ + public function toString(): string + { + return (string)$this->value; + } + + /** + * Returns the field content. If a new value is passed, + * the modified field will be returned. Otherwise it + * will return the field value. + * + * @param string|\Closure $value + * @return mixed + * @throws \Kirby\Exception\InvalidArgumentException + */ + public function value($value = null) + { + if ($value === null) { + return $this->value; + } + + if (is_scalar($value)) { + $value = (string)$value; + } elseif (is_callable($value)) { + $value = (string)$value->call($this, $this->value); + } else { + throw new InvalidArgumentException('Invalid field value type: ' . gettype($value)); + } + + $clone = clone $this; + $clone->value = $value; + + return $clone; + } +} diff --git a/kirby/src/Cms/Fieldset.php b/kirby/src/Cms/Fieldset.php new file mode 100644 index 0000000..7b5ff2a --- /dev/null +++ b/kirby/src/Cms/Fieldset.php @@ -0,0 +1,212 @@ + + * @link https://getkirby.com + * @copyright Bastian Allgeier GmbH + * @license https://getkirby.com/license + */ +class Fieldset extends Item +{ + const ITEMS_CLASS = '\Kirby\Cms\Fieldsets'; + + protected $disabled; + protected $fields = []; + protected $icon; + protected $label; + protected $model; + protected $name; + protected $preview; + protected $tabs; + protected $translate; + protected $type; + protected $unset; + protected $wysiwyg; + + /** + * Creates a new Fieldset object + * + * @param array $params + */ + public function __construct(array $params = []) + { + if (empty($params['type']) === true) { + throw new InvalidArgumentException('The fieldset type is missing'); + } + + $this->type = $params['id'] = $params['type']; + + parent::__construct($params); + + $this->disabled = $params['disabled'] ?? false; + $this->icon = $params['icon'] ?? null; + $this->model = $this->parent; + $this->name = $this->createName($params['name'] ?? Str::ucfirst($this->type)); + $this->label = $this->createLabel($params['label'] ?? null); + $this->preview = $params['preview'] ?? null; + $this->tabs = $this->createTabs($params); + $this->translate = $params['translate'] ?? true; + $this->unset = $params['unset'] ?? false; + $this->wysiwyg = $params['wysiwyg'] ?? false; + + if ( + $this->translate === false && + $this->kirby()->multilang() === true && + $this->kirby()->language()->isDefault() === false + ) { + // disable and unset the fieldset if it's not translatable + $this->unset = true; + $this->disabled = true; + } + } + + protected function createFields(array $fields = []): array + { + $fields = Blueprint::fieldsProps($fields); + $fields = $this->form($fields)->fields()->toArray(); + + // collect all fields + $this->fields = array_merge($this->fields, $fields); + + return $fields; + } + + protected function createName($name): string + { + return I18n::translate($name, $name); + } + + protected function createLabel($label = null): ?string + { + return I18n::translate($label, $label); + } + + protected function createTabs(array $params = []): array + { + $tabs = $params['tabs'] ?? []; + + // return a single tab if there are only fields + if (empty($tabs) === true) { + return [ + 'content' => [ + 'fields' => $this->createFields($params['fields'] ?? []), + ] + ]; + } + + // normalize tabs props + foreach ($tabs as $name => $tab) { + // unset/remove tab if its property is false + if ($tab === false) { + unset($tabs[$name]); + continue; + } + + $tab = Blueprint::extend($tab); + + $tab['fields'] = $this->createFields($tab['fields'] ?? []); + $tab['label'] = $this->createLabel($tab['label'] ?? Str::ucfirst($name)); + $tab['name'] = $name; + + $tabs[$name] = $tab; + } + + return $tabs; + } + + public function disabled(): bool + { + return $this->disabled; + } + + public function fields(): array + { + return $this->fields; + } + + /** + * Creates a form for the given fields + * + * @param array $fields + * @param array $input + * @return \Kirby\Cms\Form + */ + public function form(array $fields, array $input = []) + { + return new Form([ + 'fields' => $fields, + 'model' => $this->model, + 'strict' => true, + 'values' => $input, + ]); + } + + public function icon(): ?string + { + return $this->icon; + } + + public function label(): ?string + { + return $this->label; + } + + public function model() + { + return $this->model; + } + + public function name(): string + { + return $this->name; + } + + public function tabs(): array + { + return $this->tabs; + } + + public function translate(): bool + { + return $this->translate; + } + + public function type(): string + { + return $this->type; + } + + /** + * @return array + */ + public function toArray(): array + { + return [ + 'disabled' => $this->disabled, + 'icon' => $this->icon, + 'label' => $this->label, + 'name' => $this->name, + 'preview' => $this->preview, + 'tabs' => $this->tabs, + 'translate' => $this->translate, + 'type' => $this->type, + 'unset' => $this->unset, + 'wysiwyg' => $this->wysiwyg, + ]; + } + + public function unset(): bool + { + return $this->unset; + } +} diff --git a/kirby/src/Cms/Fieldsets.php b/kirby/src/Cms/Fieldsets.php new file mode 100644 index 0000000..14ccfea --- /dev/null +++ b/kirby/src/Cms/Fieldsets.php @@ -0,0 +1,100 @@ + + * @link https://getkirby.com + * @copyright Bastian Allgeier GmbH + * @license https://getkirby.com/license + */ +class Fieldsets extends Items +{ + const ITEM_CLASS = '\Kirby\Cms\Fieldset'; + + protected static function createFieldsets($params) + { + $fieldsets = []; + $groups = []; + + foreach ($params as $type => $fieldset) { + if (is_int($type) === true && is_string($fieldset)) { + $type = $fieldset; + $fieldset = 'blocks/' . $type; + } + + if ($fieldset === false) { + continue; + } + + if ($fieldset === true) { + $fieldset = 'blocks/' . $type; + } + + $fieldset = Blueprint::extend($fieldset); + + // make sure the type is always set + $fieldset['type'] = $fieldset['type'] ?? $type; + + // extract groups + if ($fieldset['type'] === 'group') { + $result = static::createFieldsets($fieldset['fieldsets'] ?? []); + $fieldsets = array_merge($fieldsets, $result['fieldsets']); + $label = $fieldset['label'] ?? Str::ucfirst($type); + + $groups[$type] = [ + 'label' => I18n::translate($label, $label), + 'name' => $type, + 'open' => $fieldset['open'] ?? true, + 'sets' => array_column($result['fieldsets'], 'type'), + ]; + } else { + $fieldsets[$fieldset['type']] = $fieldset; + } + } + + return [ + 'fieldsets' => $fieldsets, + 'groups' => $groups + ]; + } + + public static function factory(array $items = null, array $params = []) + { + $items = $items ?? option('blocks.fieldsets', [ + 'code' => 'blocks/code', + 'gallery' => 'blocks/gallery', + 'heading' => 'blocks/heading', + 'image' => 'blocks/image', + 'list' => 'blocks/list', + 'markdown' => 'blocks/markdown', + 'quote' => 'blocks/quote', + 'text' => 'blocks/text', + 'video' => 'blocks/video', + ]); + + $result = static::createFieldsets($items); + + return parent::factory($result['fieldsets'], ['groups' => $result['groups']] + $params); + } + + public function groups(): array + { + return $this->options['groups'] ?? []; + } + + public function toArray(?Closure $map = null): array + { + return array_map($map ?? function ($fieldset) { + return $fieldset->toArray(); + }, $this->data); + } +} diff --git a/kirby/src/Cms/File.php b/kirby/src/Cms/File.php new file mode 100644 index 0000000..61ab082 --- /dev/null +++ b/kirby/src/Cms/File.php @@ -0,0 +1,809 @@ + + * @link https://getkirby.com + * @copyright Bastian Allgeier GmbH + * @license https://getkirby.com/license + */ +class File extends ModelWithContent +{ + const CLASS_ALIAS = 'file'; + + use FileActions; + use FileFoundation; + use FileModifications; + use HasMethods; + use HasSiblings; + + /** + * The parent asset object + * This is used to do actual file + * method calls, like size, mime, etc. + * + * @var \Kirby\Image\Image + */ + protected $asset; + + /** + * Cache for the initialized blueprint object + * + * @var \Kirby\Cms\FileBlueprint + */ + protected $blueprint; + + /** + * @var string + */ + protected $id; + + /** + * @var string + */ + protected $filename; + + /** + * All registered file methods + * + * @var array + */ + public static $methods = []; + + /** + * The parent object + * + * @var \Kirby\Cms\Model + */ + protected $parent; + + /** + * The absolute path to the file + * + * @var string|null + */ + protected $root; + + /** + * @var string + */ + protected $template; + + /** + * The public file Url + * + * @var string + */ + protected $url; + + /** + * Magic caller for file methods + * and content fields. (in this order) + * + * @param string $method + * @param array $arguments + * @return mixed + */ + public function __call(string $method, array $arguments = []) + { + // public property access + if (isset($this->$method) === true) { + return $this->$method; + } + + // asset method proxy + if (method_exists($this->asset(), $method)) { + return $this->asset()->$method(...$arguments); + } + + // file methods + if ($this->hasMethod($method)) { + return $this->callMethod($method, $arguments); + } + + // content fields + return $this->content()->get($method, $arguments); + } + + /** + * Creates a new File object + * + * @param array $props + */ + public function __construct(array $props) + { + // properties + $this->setProperties($props); + } + + /** + * Improved `var_dump` output + * + * @return array + */ + public function __debugInfo(): array + { + return array_merge($this->toArray(), [ + 'content' => $this->content(), + 'siblings' => $this->siblings(), + ]); + } + + /** + * Returns the url to api endpoint + * + * @internal + * @param bool $relative + * @return string + */ + public function apiUrl(bool $relative = false): string + { + return $this->parent()->apiUrl($relative) . '/files/' . $this->filename(); + } + + /** + * Returns the Image object + * + * @internal + * @return \Kirby\Image\Image + */ + public function asset() + { + return $this->asset = $this->asset ?? new Image($this->root()); + } + + /** + * Returns the FileBlueprint object for the file + * + * @return \Kirby\Cms\FileBlueprint + */ + public function blueprint() + { + if (is_a($this->blueprint, 'Kirby\Cms\FileBlueprint') === true) { + return $this->blueprint; + } + + return $this->blueprint = FileBlueprint::factory('files/' . $this->template(), 'files/default', $this); + } + + /** + * Store the template in addition to the + * other content. + * + * @internal + * @param array $data + * @param string|null $languageCode + * @return array + */ + public function contentFileData(array $data, string $languageCode = null): array + { + return A::append($data, [ + 'template' => $this->template(), + ]); + } + + /** + * Returns the directory in which + * the content file is located + * + * @internal + * @return string + */ + public function contentFileDirectory(): string + { + return dirname($this->root()); + } + + /** + * Filename for the content file + * + * @internal + * @return string + */ + public function contentFileName(): string + { + return $this->filename(); + } + + /** + * Provides a kirbytag or markdown + * tag for the file, which will be + * used in the panel, when the file + * gets dragged onto a textarea + * + * @internal + * @param string|null $type (null|auto|kirbytext|markdown) + * @param bool $absolute + * @return string + */ + public function dragText(string $type = null, bool $absolute = false): string + { + $type = $this->dragTextType($type); + $url = $absolute ? $this->id() : $this->filename(); + + if ($dragTextFromCallback = $this->dragTextFromCallback($type, $url)) { + return $dragTextFromCallback; + } + + if ($type === 'markdown') { + if ($this->type() === 'image') { + return '![' . $this->alt() . '](' . $url . ')'; + } else { + return '[' . $this->filename() . '](' . $url . ')'; + } + } else { + if ($this->type() === 'image') { + return '(image: ' . $url . ')'; + } elseif ($this->type() === 'video') { + return '(video: ' . $url . ')'; + } else { + return '(file: ' . $url . ')'; + } + } + } + + /** + * Constructs a File object + * + * @internal + * @param mixed $props + * @return static + */ + public static function factory($props) + { + return new static($props); + } + + /** + * Returns the filename with extension + * + * @return string + */ + public function filename(): string + { + return $this->filename; + } + + /** + * Returns the parent Files collection + * + * @return \Kirby\Cms\Files + */ + public function files() + { + return $this->siblingsCollection(); + } + + /** + * Returns the id + * + * @return string + */ + public function id(): string + { + if ($this->id !== null) { + return $this->id; + } + + if (is_a($this->parent(), 'Kirby\Cms\Page') === true) { + return $this->id = $this->parent()->id() . '/' . $this->filename(); + } elseif (is_a($this->parent(), 'Kirby\Cms\User') === true) { + return $this->id = $this->parent()->id() . '/' . $this->filename(); + } + + return $this->id = $this->filename(); + } + + /** + * Compares the current object with the given file object + * + * @param \Kirby\Cms\File $file + * @return bool + */ + public function is(File $file): bool + { + return $this->id() === $file->id(); + } + + /** + * Check if the file can be read by the current user + * + * @return bool + */ + public function isReadable(): bool + { + static $readable = []; + + $template = $this->template(); + + if (isset($readable[$template]) === true) { + return $readable[$template]; + } + + return $readable[$template] = $this->permissions()->can('read'); + } + + /** + * Creates a unique media hash + * + * @internal + * @return string + */ + public function mediaHash(): string + { + return $this->mediaToken() . '-' . $this->modifiedFile(); + } + + /** + * Returns the absolute path to the file in the public media folder + * + * @internal + * @return string + */ + public function mediaRoot(): string + { + return $this->parent()->mediaRoot() . '/' . $this->mediaHash() . '/' . $this->filename(); + } + + /** + * Creates a non-guessable token string for this file + * + * @internal + * @return string + */ + public function mediaToken(): string + { + $token = $this->kirby()->contentToken($this, $this->id()); + return substr($token, 0, 10); + } + + /** + * Returns the absolute Url to the file in the public media folder + * + * @internal + * @return string + */ + public function mediaUrl(): string + { + return $this->parent()->mediaUrl() . '/' . $this->mediaHash() . '/' . $this->filename(); + } + + /** + * Get the file's last modification time. + * + * @param string|null $format + * @param string|null $handler date or strftime + * @param string|null $languageCode + * @return mixed + */ + public function modified(string $format = null, string $handler = null, string $languageCode = null) + { + $file = $this->modifiedFile(); + $content = $this->modifiedContent($languageCode); + $modified = max($file, $content); + + if (is_null($format) === true) { + return $modified; + } + + $handler = $handler ?? $this->kirby()->option('date.handler', 'date'); + + return $handler($format, $modified); + } + + /** + * Timestamp of the last modification + * of the content file + * + * @param string|null $languageCode + * @return int + */ + protected function modifiedContent(string $languageCode = null): int + { + return F::modified($this->contentFile($languageCode)); + } + + /** + * Timestamp of the last modification + * of the source file + * + * @return int + */ + protected function modifiedFile(): int + { + return F::modified($this->root()); + } + + /** + * Returns the parent Page object + * + * @return \Kirby\Cms\Page|null + */ + public function page() + { + return is_a($this->parent(), 'Kirby\Cms\Page') === true ? $this->parent() : null; + } + + /** + * Panel icon definition + * + * @internal + * @param array|null $params + * @return array + */ + public function panelIcon(array $params = null): array + { + $colorBlue = '#81a2be'; + $colorPurple = '#b294bb'; + $colorOrange = '#de935f'; + $colorGreen = '#a7bd68'; + $colorAqua = '#8abeb7'; + $colorYellow = '#f0c674'; + $colorRed = '#d16464'; + $colorWhite = '#c5c9c6'; + + $types = [ + 'image' => ['color' => $colorOrange, 'type' => 'file-image'], + 'video' => ['color' => $colorYellow, 'type' => 'file-video'], + 'document' => ['color' => $colorRed, 'type' => 'file-document'], + 'audio' => ['color' => $colorAqua, 'type' => 'file-audio'], + 'code' => ['color' => $colorBlue, 'type' => 'file-code'], + 'archive' => ['color' => $colorWhite, 'type' => 'file-zip'], + ]; + + $extensions = [ + 'indd' => ['color' => $colorPurple], + 'xls' => ['color' => $colorGreen, 'type' => 'file-spreadsheet'], + 'xlsx' => ['color' => $colorGreen, 'type' => 'file-spreadsheet'], + 'csv' => ['color' => $colorGreen, 'type' => 'file-spreadsheet'], + 'docx' => ['color' => $colorBlue, 'type' => 'file-word'], + 'doc' => ['color' => $colorBlue, 'type' => 'file-word'], + 'rtf' => ['color' => $colorBlue, 'type' => 'file-word'], + 'mdown' => ['type' => 'file-text'], + 'md' => ['type' => 'file-text'] + ]; + + $definition = array_merge($types[$this->type()] ?? [], $extensions[$this->extension()] ?? []); + + $params['type'] = $definition['type'] ?? 'file'; + $params['color'] = $definition['color'] ?? $colorWhite; + + return parent::panelIcon($params); + } + + /** + * Returns the image file object based on provided query + * + * @internal + * @param string|null $query + * @return \Kirby\Cms\File|\Kirby\Cms\Asset|null + */ + protected function panelImageSource(string $query = null) + { + if ($query === null && $this->isViewable()) { + return $this; + } + + return parent::panelImageSource($query); + } + + /** + * Returns an array of all actions + * that can be performed in the Panel + * + * @since 3.3.0 This also checks for the lock status + * @since 3.5.1 This also checks for matching accept settings + * + * @param array $unlock An array of options that will be force-unlocked + * @return array + */ + public function panelOptions(array $unlock = []): array + { + $options = parent::panelOptions($unlock); + + try { + // check if the file type is allowed at all, + // otherwise it cannot be replaced + $this->match($this->blueprint()->accept()); + } catch (Throwable $e) { + $options['replace'] = false; + } + + return $options; + } + + /** + * Returns the full path without leading slash + * + * @internal + * @return string + */ + public function panelPath(): string + { + return 'files/' . $this->filename(); + } + + /** + * Prepares the response data for file pickers + * and file fields + * + * @param array|null $params + * @return array + */ + public function panelPickerData(array $params = []): array + { + $image = $this->panelImage($params['image'] ?? []); + $icon = $this->panelIcon($image); + $uuid = $this->id(); + + if (empty($params['model']) === false) { + $uuid = $this->parent() === $params['model'] ? $this->filename() : $this->id(); + $absolute = $this->parent() !== $params['model']; + } + + // escape the default text + // TODO: no longer needed in 3.6 + $textQuery = $params['text'] ?? '{{ file.filename }}'; + $text = $this->toString($textQuery); + if ($textQuery === '{{ file.filename }}') { + $text = Escape::html($text); + } + + return [ + 'filename' => $this->filename(), + 'dragText' => $this->dragText('auto', $absolute ?? false), + 'icon' => $icon, + 'id' => $this->id(), + 'image' => $image, + 'info' => $this->toString($params['info'] ?? false), + 'link' => $this->panelUrl(true), + 'text' => $text, + 'type' => $this->type(), + 'url' => $this->url(), + 'uuid' => $uuid, + ]; + } + + /** + * Returns the url to the editing view + * in the panel + * + * @internal + * @param bool $relative + * @return string + */ + public function panelUrl(bool $relative = false): string + { + return $this->parent()->panelUrl($relative) . '/' . $this->panelPath(); + } + + /** + * Returns the parent Model object + * + * @return \Kirby\Cms\Model + */ + public function parent() + { + return $this->parent = $this->parent ?? $this->kirby()->site(); + } + + /** + * Returns the parent id if a parent exists + * + * @internal + * @return string|null + */ + public function parentId(): ?string + { + if ($parent = $this->parent()) { + return $parent->id(); + } + + return null; + } + + /** + * Returns a collection of all parent pages + * + * @return \Kirby\Cms\Pages + */ + public function parents() + { + if (is_a($this->parent(), 'Kirby\Cms\Page') === true) { + return $this->parent()->parents()->prepend($this->parent()->id(), $this->parent()); + } + + return new Pages(); + } + + /** + * Returns the permissions object for this file + * + * @return \Kirby\Cms\FilePermissions + */ + public function permissions() + { + return new FilePermissions($this); + } + + /** + * Returns the absolute root to the file + * + * @return string|null + */ + public function root(): ?string + { + return $this->root = $this->root ?? $this->parent()->root() . '/' . $this->filename(); + } + + /** + * Returns the FileRules class to + * validate any important action. + * + * @return \Kirby\Cms\FileRules + */ + protected function rules() + { + return new FileRules(); + } + + /** + * Sets the Blueprint object + * + * @param array|null $blueprint + * @return $this + */ + protected function setBlueprint(array $blueprint = null) + { + if ($blueprint !== null) { + $blueprint['model'] = $this; + $this->blueprint = new FileBlueprint($blueprint); + } + + return $this; + } + + /** + * Sets the filename + * + * @param string $filename + * @return $this + */ + protected function setFilename(string $filename) + { + $this->filename = $filename; + return $this; + } + + /** + * Sets the parent model object + * + * @param \Kirby\Cms\Model|null $parent + * @return $this + */ + protected function setParent(Model $parent = null) + { + $this->parent = $parent; + return $this; + } + + /** + * Always set the root to null, to invoke + * auto root detection + * + * @param string|null $root + * @return $this + */ + protected function setRoot(string $root = null) + { + $this->root = null; + return $this; + } + + /** + * @param string|null $template + * @return $this + */ + protected function setTemplate(string $template = null) + { + $this->template = $template; + return $this; + } + + /** + * Sets the url + * + * @param string|null $url + * @return $this + */ + protected function setUrl(string $url = null) + { + $this->url = $url; + return $this; + } + + /** + * Returns the parent Files collection + * @internal + * + * @return \Kirby\Cms\Files + */ + protected function siblingsCollection() + { + return $this->parent()->files(); + } + + /** + * Returns the parent Site object + * + * @return \Kirby\Cms\Site + */ + public function site() + { + return is_a($this->parent(), 'Kirby\Cms\Site') === true ? $this->parent() : $this->kirby()->site(); + } + + /** + * Returns the final template + * + * @return string|null + */ + public function template(): ?string + { + return $this->template = $this->template ?? $this->content()->get('template')->value(); + } + + /** + * Returns siblings with the same template + * + * @param bool $self + * @return \Kirby\Cms\Files + */ + public function templateSiblings(bool $self = true) + { + return $this->siblings($self)->filter('template', $this->template()); + } + + /** + * Extended info for the array export + * by injecting the information from + * the asset. + * + * @return array + */ + public function toArray(): array + { + return array_merge($this->asset()->toArray(), parent::toArray()); + } + + /** + * Returns the Url + * + * @return string + */ + public function url(): string + { + return $this->url ?? $this->url = ($this->kirby()->component('file::url'))($this->kirby(), $this); + } +} diff --git a/kirby/src/Cms/FileActions.php b/kirby/src/Cms/FileActions.php new file mode 100644 index 0000000..83f9c7d --- /dev/null +++ b/kirby/src/Cms/FileActions.php @@ -0,0 +1,305 @@ + + * @link https://getkirby.com + * @copyright Bastian Allgeier GmbH + * @license https://getkirby.com/license + */ +trait FileActions +{ + /** + * Renames the file without touching the extension + * The store is used to actually execute this. + * + * @param string $name + * @param bool $sanitize + * @return $this|static + * @throws \Kirby\Exception\LogicException + */ + public function changeName(string $name, bool $sanitize = true) + { + if ($sanitize === true) { + $name = F::safeName($name); + } + + // don't rename if not necessary + if ($name === $this->name()) { + return $this; + } + + return $this->commit('changeName', ['file' => $this, 'name' => $name], function ($oldFile, $name) { + $newFile = $oldFile->clone([ + 'filename' => $name . '.' . $oldFile->extension(), + ]); + + if ($oldFile->exists() === false) { + return $newFile; + } + + if ($newFile->exists() === true) { + throw new LogicException('The new file exists and cannot be overwritten'); + } + + // remove the lock of the old file + if ($lock = $oldFile->lock()) { + $lock->remove(); + } + + // remove all public versions + $oldFile->unpublish(); + + // rename the main file + F::move($oldFile->root(), $newFile->root()); + + if ($newFile->kirby()->multilang() === true) { + foreach ($newFile->translations() as $translation) { + $translationCode = $translation->code(); + + // rename the content file + F::move($oldFile->contentFile($translationCode), $newFile->contentFile($translationCode)); + } + } else { + // rename the content file + F::move($oldFile->contentFile(), $newFile->contentFile()); + } + + + return $newFile; + }); + } + + /** + * Changes the file's sorting number in the meta file + * + * @param int $sort + * @return static + */ + public function changeSort(int $sort) + { + return $this->commit('changeSort', ['file' => $this, 'position' => $sort], function ($file, $sort) { + return $file->save(['sort' => $sort]); + }); + } + + /** + * Commits a file action, by following these steps + * + * 1. checks the action rules + * 2. sends the before hook + * 3. commits the store action + * 4. sends the after hook + * 5. returns the result + * + * @param string $action + * @param array $arguments + * @param Closure $callback + * @return mixed + */ + protected function commit(string $action, array $arguments, Closure $callback) + { + $old = $this->hardcopy(); + $kirby = $this->kirby(); + $argumentValues = array_values($arguments); + + $this->rules()->$action(...$argumentValues); + $kirby->trigger('file.' . $action . ':before', $arguments); + + $result = $callback(...$argumentValues); + + if ($action === 'create') { + $argumentsAfter = ['file' => $result]; + } elseif ($action === 'delete') { + $argumentsAfter = ['status' => $result, 'file' => $old]; + } else { + $argumentsAfter = ['newFile' => $result, 'oldFile' => $old]; + } + $kirby->trigger('file.' . $action . ':after', $argumentsAfter); + + $kirby->cache('pages')->flush(); + return $result; + } + + /** + * Copy the file to the given page + * + * @param \Kirby\Cms\Page $page + * @return \Kirby\Cms\File + */ + public function copy(Page $page) + { + F::copy($this->root(), $page->root() . '/' . $this->filename()); + + if ($this->kirby()->multilang() === true) { + foreach ($this->kirby()->languages() as $language) { + $contentFile = $this->contentFile($language->code()); + F::copy($contentFile, $page->root() . '/' . basename($contentFile)); + } + } else { + $contentFile = $this->contentFile(); + F::copy($contentFile, $page->root() . '/' . basename($contentFile)); + } + + return $page->clone()->file($this->filename()); + } + + /** + * Creates a new file on disk and returns the + * File object. The store is used to handle file + * writing, so it can be replaced by any other + * way of generating files. + * + * @param array $props + * @return static + * @throws \Kirby\Exception\InvalidArgumentException + * @throws \Kirby\Exception\LogicException + */ + public static function create(array $props) + { + if (isset($props['source'], $props['parent']) === false) { + throw new InvalidArgumentException('Please provide the "source" and "parent" props for the File'); + } + + // prefer the filename from the props + $props['filename'] = F::safeName($props['filename'] ?? basename($props['source'])); + + $props['model'] = strtolower($props['template'] ?? 'default'); + + // create the basic file and a test upload object + $file = static::factory($props); + $upload = new Image($props['source']); + + // create a form for the file + $form = Form::for($file, [ + 'values' => $props['content'] ?? [] + ]); + + // inject the content + $file = $file->clone(['content' => $form->strings(true)]); + + // run the hook + return $file->commit('create', compact('file', 'upload'), function ($file, $upload) { + + // delete all public versions + $file->unpublish(); + + // overwrite the original + if (F::copy($upload->root(), $file->root(), true) !== true) { + throw new LogicException('The file could not be created'); + } + + // always create pages in the default language + if ($file->kirby()->multilang() === true) { + $languageCode = $file->kirby()->defaultLanguage()->code(); + } else { + $languageCode = null; + } + + // store the content if necessary + $file->save($file->content()->toArray(), $languageCode); + + // add the file to the list of siblings + $file->siblings()->append($file->id(), $file); + + // return a fresh clone + return $file->clone(); + }); + } + + /** + * Deletes the file. The store is used to + * manipulate the filesystem or whatever you prefer. + * + * @return bool + */ + public function delete(): bool + { + return $this->commit('delete', ['file' => $this], function ($file) { + + // remove all versions in the media folder + $file->unpublish(); + + // remove the lock of the old file + if ($lock = $file->lock()) { + $lock->remove(); + } + + if ($file->kirby()->multilang() === true) { + foreach ($file->translations() as $translation) { + F::remove($file->contentFile($translation->code())); + } + } else { + F::remove($file->contentFile()); + } + + F::remove($file->root()); + + // remove the file from the sibling collection + $file->parent()->files()->remove($file); + + return true; + }); + } + + /** + * Move the file to the public media folder + * if it's not already there. + * + * @return $this + */ + public function publish() + { + Media::publish($this, $this->mediaRoot()); + return $this; + } + + /** + * Replaces the file. The source must + * be an absolute path to a file or a Url. + * The store handles the replacement so it + * finally decides what it will support as + * source. + * + * @param string $source + * @return static + * @throws \Kirby\Exception\LogicException + */ + public function replace(string $source) + { + return $this->commit('replace', ['file' => $this, 'upload' => new Image($source)], function ($file, $upload) { + + // delete all public versions + $file->unpublish(); + + // overwrite the original + if (F::copy($upload->root(), $file->root(), true) !== true) { + throw new LogicException('The file could not be created'); + } + + // return a fresh clone + return $file->clone(); + }); + } + + /** + * Remove all public versions of this file + * + * @return $this + */ + public function unpublish() + { + Media::unpublish($this->parent()->mediaRoot(), $this); + return $this; + } +} diff --git a/kirby/src/Cms/FileBlueprint.php b/kirby/src/Cms/FileBlueprint.php new file mode 100644 index 0000000..ee2e0f9 --- /dev/null +++ b/kirby/src/Cms/FileBlueprint.php @@ -0,0 +1,181 @@ + + * @link https://getkirby.com + * @copyright Bastian Allgeier GmbH + * @license https://getkirby.com/license + */ +class FileBlueprint extends Blueprint +{ + /** + * `true` if the default accepted + * types are being used + * + * @var bool + */ + protected $defaultTypes = false; + + public function __construct(array $props) + { + parent::__construct($props); + + // normalize all available page options + $this->props['options'] = $this->normalizeOptions( + $this->props['options'] ?? true, + // defaults + [ + 'changeName' => null, + 'create' => null, + 'delete' => null, + 'read' => null, + 'replace' => null, + 'update' => null, + ] + ); + + // normalize the accept settings + $this->props['accept'] = $this->normalizeAccept($this->props['accept'] ?? []); + } + + /** + * @return array + */ + public function accept(): array + { + return $this->props['accept']; + } + + /** + * Returns the list of all accepted MIME types for + * file upload or `*` if all MIME types are allowed + * + * @return string + */ + public function acceptMime(): string + { + // don't disclose the specific default types + if ($this->defaultTypes === true) { + return '*'; + } + + $accept = $this->accept(); + $restrictions = []; + + if (is_array($accept['mime']) === true) { + $restrictions[] = $accept['mime']; + } else { + // only fall back to the extension or type if + // no explicit MIME types were defined + // (allows to set custom MIME types for the frontend + // check but still restrict the extension and/or type) + + if (is_array($accept['extension']) === true) { + // determine the main MIME type for each extension + $restrictions[] = array_map(['Kirby\Toolkit\Mime', 'fromExtension'], $accept['extension']); + } + + if (is_array($accept['type']) === true) { + // determine the MIME types of each file type + $mimes = []; + foreach ($accept['type'] as $type) { + if ($extensions = F::typeToExtensions($type)) { + $mimes[] = array_map(['Kirby\Toolkit\Mime', 'fromExtension'], $extensions); + } + } + + $restrictions[] = array_merge(...$mimes); + } + } + + if ($restrictions !== []) { + if (count($restrictions) > 1) { + // only return the MIME types that are allowed by all restrictions + $mimes = array_intersect(...$restrictions); + } else { + $mimes = $restrictions[0]; + } + + // filter out empty MIME types and duplicates + return implode(', ', array_filter(array_unique($mimes))); + } + + // no restrictions, accept everything + return '*'; + } + + /** + * @param mixed $accept + * @return array + */ + protected function normalizeAccept($accept = null): array + { + if (is_string($accept) === true) { + $accept = [ + 'mime' => $accept + ]; + } elseif ($accept === true) { + // explicitly no restrictions at all + $accept = [ + 'mime' => null + ]; + } elseif (empty($accept) === true) { + // no custom restrictions + $accept = []; + } + + $accept = array_change_key_case($accept); + + $defaults = [ + 'extension' => null, + 'mime' => null, + 'maxheight' => null, + 'maxsize' => null, + 'maxwidth' => null, + 'minheight' => null, + 'minsize' => null, + 'minwidth' => null, + 'orientation' => null, + 'type' => null + ]; + + // default type restriction if none are configured; + // this ensures that no unexpected files are uploaded + if ( + array_key_exists('mime', $accept) === false && + array_key_exists('extension', $accept) === false && + array_key_exists('type', $accept) === false + ) { + $defaults['type'] = ['image', 'document', 'archive', 'audio', 'video']; + $this->defaultTypes = true; + } + + $accept = array_merge($defaults, $accept); + + // normalize the MIME, extension and type from strings into arrays + if (is_string($accept['mime']) === true) { + $accept['mime'] = array_map(function ($mime) { + return $mime['value']; + }, Str::accepted($accept['mime'])); + } + + if (is_string($accept['extension']) === true) { + $accept['extension'] = array_map('trim', explode(',', $accept['extension'])); + } + + if (is_string($accept['type']) === true) { + $accept['type'] = array_map('trim', explode(',', $accept['type'])); + } + + return $accept; + } +} diff --git a/kirby/src/Cms/FileFoundation.php b/kirby/src/Cms/FileFoundation.php new file mode 100644 index 0000000..6529f40 --- /dev/null +++ b/kirby/src/Cms/FileFoundation.php @@ -0,0 +1,248 @@ + + * @link https://getkirby.com + * @copyright Bastian Allgeier GmbH + * @license https://getkirby.com/license + */ +trait FileFoundation +{ + protected $asset; + protected $root; + protected $url; + + /** + * Magic caller for asset methods + * + * @param string $method + * @param array $arguments + * @return mixed + * @throws \Kirby\Exception\BadMethodCallException + */ + public function __call(string $method, array $arguments = []) + { + // public property access + if (isset($this->$method) === true) { + return $this->$method; + } + + // asset method proxy + if (method_exists($this->asset(), $method)) { + return $this->asset()->$method(...$arguments); + } + + throw new BadMethodCallException('The method: "' . $method . '" does not exist'); + } + + /** + * Constructor sets all file properties + * + * @param array $props + */ + public function __construct(array $props) + { + $this->setProperties($props); + } + + /** + * Converts the file object to a string + * In case of an image, it will create an image tag + * Otherwise it will return the url + * + * @return string + */ + public function __toString(): string + { + if ($this->type() === 'image') { + return $this->html(); + } + + return $this->url(); + } + + /** + * Returns the Image object + * + * @return \Kirby\Image\Image + */ + public function asset() + { + return $this->asset = $this->asset ?? new Image($this->root()); + } + + /** + * Checks if the file exists on disk + * + * @return bool + */ + public function exists(): bool + { + return file_exists($this->root()) === true; + } + + /** + * Returns the file extension + * + * @return string + */ + public function extension(): string + { + return F::extension($this->root()); + } + + /** + * Converts the file to html + * + * @param array $attr + * @return string + */ + public function html(array $attr = []): string + { + if ($this->type() === 'image') { + return Html::img($this->url(), array_merge(['alt' => $this->alt()], $attr)); + } else { + return Html::a($this->url(), $attr); + } + } + + /** + * Checks if the file is a resizable image + * + * @return bool + */ + public function isResizable(): bool + { + $resizable = [ + 'jpg', + 'jpeg', + 'gif', + 'png', + 'webp' + ]; + + return in_array($this->extension(), $resizable) === true; + } + + /** + * Checks if a preview can be displayed for the file + * in the panel or in the frontend + * + * @return bool + */ + public function isViewable(): bool + { + $viewable = [ + 'jpg', + 'jpeg', + 'gif', + 'png', + 'svg', + 'webp' + ]; + + return in_array($this->extension(), $viewable) === true; + } + + /** + * Returns the app instance + * + * @return \Kirby\Cms\App + */ + public function kirby() + { + return App::instance(); + } + + /** + * Get the file's last modification time. + * + * @param string $format + * @param string|null $handler date or strftime + * @return mixed + */ + public function modified(string $format = null, string $handler = null) + { + return F::modified($this->root(), $format, $handler ?? $this->kirby()->option('date.handler', 'date')); + } + + /** + * Returns the absolute path to the file root + * + * @return string|null + */ + public function root(): ?string + { + return $this->root; + } + + /** + * Setter for the root + * + * @param string|null $root + * @return $this + */ + protected function setRoot(string $root = null) + { + $this->root = $root; + return $this; + } + + /** + * Setter for the file url + * + * @param string $url + * @return $this + */ + protected function setUrl(string $url) + { + $this->url = $url; + return $this; + } + + /** + * Convert the object to an array + * + * @return array + */ + public function toArray(): array + { + $array = array_merge($this->asset()->toArray(), [ + 'isResizable' => $this->isResizable(), + 'url' => $this->url(), + ]); + + ksort($array); + + return $array; + } + + /** + * Returns the file type + * + * @return string|null + */ + public function type(): ?string + { + return F::type($this->root()); + } + + /** + * Returns the absolute url for the file + * + * @return string + */ + public function url(): string + { + return $this->url; + } +} diff --git a/kirby/src/Cms/FileModifications.php b/kirby/src/Cms/FileModifications.php new file mode 100644 index 0000000..8166d45 --- /dev/null +++ b/kirby/src/Cms/FileModifications.php @@ -0,0 +1,202 @@ + + * @link https://getkirby.com + * @copyright Bastian Allgeier GmbH + * @license https://getkirby.com/license + */ +trait FileModifications +{ + /** + * Blurs the image by the given amount of pixels + * + * @param bool $pixels + * @return \Kirby\Cms\FileVersion|\Kirby\Cms\File + */ + public function blur($pixels = true) + { + return $this->thumb(['blur' => $pixels]); + } + + /** + * Converts the image to black and white + * + * @return \Kirby\Cms\FileVersion|\Kirby\Cms\File + */ + public function bw() + { + return $this->thumb(['grayscale' => true]); + } + + /** + * Crops the image by the given width and height + * + * @param int $width + * @param int|null $height + * @param string|array $options + * @return \Kirby\Cms\FileVersion|\Kirby\Cms\File + */ + public function crop(int $width, int $height = null, $options = null) + { + $quality = null; + $crop = 'center'; + + if (is_int($options) === true) { + $quality = $options; + } elseif (is_string($options)) { + $crop = $options; + } elseif (is_a($options, 'Kirby\Cms\Field') === true) { + $crop = $options->value(); + } elseif (is_array($options)) { + $quality = $options['quality'] ?? $quality; + $crop = $options['crop'] ?? $crop; + } + + return $this->thumb([ + 'width' => $width, + 'height' => $height, + 'quality' => $quality, + 'crop' => $crop + ]); + } + + /** + * Alias for File::bw() + * + * @return \Kirby\Cms\FileVersion|\Kirby\Cms\File + */ + public function grayscale() + { + return $this->thumb(['grayscale' => true]); + } + + /** + * Alias for File::bw() + * + * @return \Kirby\Cms\FileVersion|\Kirby\Cms\File + */ + public function greyscale() + { + return $this->thumb(['grayscale' => true]); + } + + /** + * Sets the JPEG compression quality + * + * @param int $quality + * @return \Kirby\Cms\FileVersion|\Kirby\Cms\File + */ + public function quality(int $quality) + { + return $this->thumb(['quality' => $quality]); + } + + /** + * Resizes the file with the given width and height + * while keeping the aspect ratio. + * + * @param int|null $width + * @param int|null $height + * @param int|null $quality + * @return \Kirby\Cms\FileVersion|\Kirby\Cms\File + * @throws \Kirby\Exception\InvalidArgumentException + */ + public function resize(int $width = null, int $height = null, int $quality = null) + { + return $this->thumb([ + 'width' => $width, + 'height' => $height, + 'quality' => $quality + ]); + } + + /** + * Create a srcset definition for the given sizes + * Sizes can be defined as a simple array. They can + * also be set up in the config with the thumbs.srcsets option. + * @since 3.1.0 + * + * @param array|string|null $sizes + * @return string|null + */ + public function srcset($sizes = null): ?string + { + if (empty($sizes) === true) { + $sizes = $this->kirby()->option('thumbs.srcsets.default', []); + } + + if (is_string($sizes) === true) { + $sizes = $this->kirby()->option('thumbs.srcsets.' . $sizes, []); + } + + if (is_array($sizes) === false || empty($sizes) === true) { + return null; + } + + $set = []; + + foreach ($sizes as $key => $value) { + if (is_array($value)) { + $options = $value; + $condition = $key; + } elseif (is_string($value) === true) { + $options = [ + 'width' => $key + ]; + $condition = $value; + } else { + $options = [ + 'width' => $value + ]; + $condition = $value . 'w'; + } + + $set[] = $this->thumb($options)->url() . ' ' . $condition; + } + + return implode(', ', $set); + } + + /** + * Creates a modified version of images + * The media manager takes care of generating + * those modified versions and putting them + * in the right place. This is normally the + * `/media` folder of your installation, but + * could potentially also be a CDN or any other + * place. + * + * @param array|null|string $options + * @return \Kirby\Cms\FileVersion|\Kirby\Cms\File + * @throws \Kirby\Exception\InvalidArgumentException + */ + public function thumb($options = null) + { + // thumb presets + if (empty($options) === true) { + $options = $this->kirby()->option('thumbs.presets.default'); + } elseif (is_string($options) === true) { + $options = $this->kirby()->option('thumbs.presets.' . $options); + } + + if (empty($options) === true || is_array($options) === false) { + return $this; + } + + $result = ($this->kirby()->component('file::version'))($this->kirby(), $this, $options); + + if (is_a($result, 'Kirby\Cms\FileVersion') === false && is_a($result, 'Kirby\Cms\File') === false) { + throw new InvalidArgumentException('The file::version component must return a File or FileVersion object'); + } + + return $result; + } +} diff --git a/kirby/src/Cms/FilePermissions.php b/kirby/src/Cms/FilePermissions.php new file mode 100644 index 0000000..91fca71 --- /dev/null +++ b/kirby/src/Cms/FilePermissions.php @@ -0,0 +1,17 @@ + + * @link https://getkirby.com + * @copyright Bastian Allgeier GmbH + * @license https://getkirby.com/license + */ +class FilePermissions extends ModelPermissions +{ + protected $category = 'files'; +} diff --git a/kirby/src/Cms/FilePicker.php b/kirby/src/Cms/FilePicker.php new file mode 100644 index 0000000..4385679 --- /dev/null +++ b/kirby/src/Cms/FilePicker.php @@ -0,0 +1,74 @@ + + * @link https://getkirby.com + * @copyright Bastian Allgeier GmbH + * @license https://getkirby.com/license + */ +class FilePicker extends Picker +{ + /** + * Extends the basic defaults + * + * @return array + */ + public function defaults(): array + { + $defaults = parent::defaults(); + $defaults['text'] = '{{ file.filename }}'; + + return $defaults; + } + + /** + * Search all files for the picker + * + * @return \Kirby\Cms\Files|null + * @throws \Kirby\Exception\InvalidArgumentException + */ + public function items() + { + $model = $this->options['model']; + + // find the right default query + if (empty($this->options['query']) === false) { + $query = $this->options['query']; + } elseif (is_a($model, 'Kirby\Cms\File') === true) { + $query = 'file.siblings'; + } else { + $query = $model::CLASS_ALIAS . '.files'; + } + + // fetch all files for the picker + $files = $model->query($query); + + // help mitigate some typical query usage issues + // by converting site and page objects to proper + // pages by returning their children + if (is_a($files, 'Kirby\Cms\Site') === true) { + $files = $files->files(); + } elseif (is_a($files, 'Kirby\Cms\Page') === true) { + $files = $files->files(); + } elseif (is_a($files, 'Kirby\Cms\User') === true) { + $files = $files->files(); + } elseif (is_a($files, 'Kirby\Cms\Files') === false) { + throw new InvalidArgumentException('Your query must return a set of files'); + } + + // search + $files = $this->search($files); + + // paginate + return $this->paginate($files); + } +} diff --git a/kirby/src/Cms/FileRules.php b/kirby/src/Cms/FileRules.php new file mode 100644 index 0000000..3929b07 --- /dev/null +++ b/kirby/src/Cms/FileRules.php @@ -0,0 +1,302 @@ + + * @link https://getkirby.com + * @copyright Bastian Allgeier GmbH + * @license https://getkirby.com/license + */ +class FileRules +{ + /** + * Validates if the filename can be changed + * + * @param \Kirby\Cms\File $file + * @param string $name + * @return bool + * @throws \Kirby\Exception\DuplicateException If a file with this name exists + * @throws \Kirby\Exception\PermissionException If the user is not allowed to rename the file + */ + public static function changeName(File $file, string $name): bool + { + if ($file->permissions()->changeName() !== true) { + throw new PermissionException([ + 'key' => 'file.changeName.permission', + 'data' => ['filename' => $file->filename()] + ]); + } + + $parent = $file->parent(); + $duplicate = $parent->files()->not($file)->findBy('filename', $name . '.' . $file->extension()); + + if ($duplicate) { + throw new DuplicateException([ + 'key' => 'file.duplicate', + 'data' => ['filename' => $duplicate->filename()] + ]); + } + + return true; + } + + /** + * Validates if the file can be sorted + * + * @param \Kirby\Cms\File $file + * @param int $sort + * @return bool + */ + public static function changeSort(File $file, int $sort): bool + { + return true; + } + + /** + * Validates if the file can be created + * + * @param \Kirby\Cms\File $file + * @param \Kirby\Image\Image $upload + * @return bool + * @throws \Kirby\Exception\DuplicateException If a file with the same name exists + * @throws \Kirby\Exception\PermissionException If the user is not allowed to create the file + */ + public static function create(File $file, Image $upload): bool + { + if ($file->exists() === true) { + throw new DuplicateException('The file exists and cannot be overwritten'); + } + + if ($file->permissions()->create() !== true) { + throw new PermissionException('The file cannot be created'); + } + + static::validFile($file, $upload->mime()); + + $upload->match($file->blueprint()->accept()); + $upload->validateContents(true); + + return true; + } + + /** + * Validates if the file can be deleted + * + * @param \Kirby\Cms\File $file + * @return bool + * @throws \Kirby\Exception\PermissionException If the user is not allowed to delete the file + */ + public static function delete(File $file): bool + { + if ($file->permissions()->delete() !== true) { + throw new PermissionException('The file cannot be deleted'); + } + + return true; + } + + /** + * Validates if the file can be replaced + * + * @param \Kirby\Cms\File $file + * @param \Kirby\Image\Image $upload + * @return bool + * @throws \Kirby\Exception\PermissionException If the user is not allowed to replace the file + * @throws \Kirby\Exception\InvalidArgumentException If the file type of the new file is different + */ + public static function replace(File $file, Image $upload): bool + { + if ($file->permissions()->replace() !== true) { + throw new PermissionException('The file cannot be replaced'); + } + + static::validMime($file, $upload->mime()); + + if ( + (string)$upload->mime() !== (string)$file->mime() && + (string)$upload->extension() !== (string)$file->extension() + ) { + throw new InvalidArgumentException([ + 'key' => 'file.mime.differs', + 'data' => ['mime' => $file->mime()] + ]); + } + + $upload->match($file->blueprint()->accept()); + $upload->validateContents(true); + + return true; + } + + /** + * Validates if the file can be updated + * + * @param \Kirby\Cms\File $file + * @param array $content + * @return bool + * @throws \Kirby\Exception\PermissionException If the user is not allowed to update the file + */ + public static function update(File $file, array $content = []): bool + { + if ($file->permissions()->update() !== true) { + throw new PermissionException('The file cannot be updated'); + } + + return true; + } + + /** + * Validates the file extension + * + * @param \Kirby\Cms\File $file + * @param string $extension + * @return bool + * @throws \Kirby\Exception\InvalidArgumentException If the extension is missing or forbidden + */ + public static function validExtension(File $file, string $extension): bool + { + // make it easier to compare the extension + $extension = strtolower($extension); + + if (empty($extension)) { + throw new InvalidArgumentException([ + 'key' => 'file.extension.missing', + 'data' => ['filename' => $file->filename()] + ]); + } + + if (V::in($extension, ['php', 'phar', 'html', 'htm', 'exe', App::instance()->contentExtension()])) { + throw new InvalidArgumentException([ + 'key' => 'file.extension.forbidden', + 'data' => ['extension' => $extension] + ]); + } + + if (Str::contains($extension, 'php') || Str::contains($extension, 'phar')) { + throw new InvalidArgumentException([ + 'key' => 'file.type.forbidden', + 'data' => ['type' => 'PHP'] + ]); + } + + if (Str::contains($extension, 'htm')) { + throw new InvalidArgumentException([ + 'key' => 'file.type.forbidden', + 'data' => ['type' => 'HTML'] + ]); + } + + return true; + } + + /** + * Validates the extension, MIME type and filename + * + * @param \Kirby\Cms\File $file + * @param string|null|false $mime If not passed, the MIME type is detected from the file, + * if `false`, the MIME type is not validated for performance reasons + * @return bool + * @throws \Kirby\Exception\InvalidArgumentException If the extension, MIME type or filename is missing or forbidden + */ + public static function validFile(File $file, $mime = null): bool + { + if ($mime === false) { + // request to skip the MIME check for performance reasons + $validMime = true; + } else { + $validMime = static::validMime($file, $mime ?? $file->mime()); + } + + return + $validMime && + static::validExtension($file, $file->extension()) && + static::validFilename($file, $file->filename()); + } + + /** + * Validates the filename + * + * @param \Kirby\Cms\File $file + * @param string $filename + * @return bool + * @throws \Kirby\Exception\InvalidArgumentException If the filename is missing or forbidden + */ + public static function validFilename(File $file, string $filename): bool + { + // make it easier to compare the filename + $filename = strtolower($filename); + + // check for missing filenames + if (empty($filename)) { + throw new InvalidArgumentException([ + 'key' => 'file.name.missing' + ]); + } + + // Block htaccess files + if (Str::startsWith($filename, '.ht')) { + throw new InvalidArgumentException([ + 'key' => 'file.type.forbidden', + 'data' => ['type' => 'Apache config'] + ]); + } + + // Block invisible files + if (Str::startsWith($filename, '.')) { + throw new InvalidArgumentException([ + 'key' => 'file.type.forbidden', + 'data' => ['type' => 'invisible'] + ]); + } + + return true; + } + + /** + * Validates the MIME type + * + * @param \Kirby\Cms\File $file + * @param string|null $mime + * @return bool + * @throws \Kirby\Exception\InvalidArgumentException If the MIME type is missing or forbidden + */ + public static function validMime(File $file, string $mime = null): bool + { + // make it easier to compare the mime + $mime = strtolower($mime); + + if (empty($mime)) { + throw new InvalidArgumentException([ + 'key' => 'file.mime.missing', + 'data' => ['filename' => $file->filename()] + ]); + } + + if (Str::contains($mime, 'php')) { + throw new InvalidArgumentException([ + 'key' => 'file.type.forbidden', + 'data' => ['type' => 'PHP'] + ]); + } + + if (V::in($mime, ['text/html', 'application/x-msdownload'])) { + throw new InvalidArgumentException([ + 'key' => 'file.mime.forbidden', + 'data' => ['mime' => $mime] + ]); + } + + return true; + } +} diff --git a/kirby/src/Cms/FileVersion.php b/kirby/src/Cms/FileVersion.php new file mode 100644 index 0000000..ce0ead9 --- /dev/null +++ b/kirby/src/Cms/FileVersion.php @@ -0,0 +1,143 @@ + + * @link https://getkirby.com + * @copyright Bastian Allgeier GmbH + * @license https://getkirby.com/license + */ +class FileVersion +{ + use FileFoundation { + toArray as parentToArray; + } + use Properties; + + protected $modifications; + protected $original; + + /** + * Proxy for public properties, asset methods + * and content field getters + * + * @param string $method + * @param array $arguments + * @return mixed + */ + public function __call(string $method, array $arguments = []) + { + // public property access + if (isset($this->$method) === true) { + return $this->$method; + } + + // asset method proxy + if (method_exists($this->asset(), $method)) { + if ($this->exists() === false) { + $this->save(); + } + + return $this->asset()->$method(...$arguments); + } + + if (is_a($this->original(), 'Kirby\Cms\File') === true) { + // content fields + return $this->original()->content()->get($method, $arguments); + } + } + + /** + * Returns the unique ID + * + * @return string + */ + public function id(): string + { + return dirname($this->original()->id()) . '/' . $this->filename(); + } + + /** + * Returns the parent Kirby App instance + * + * @return \Kirby\Cms\App + */ + public function kirby() + { + return $this->original()->kirby(); + } + + /** + * Returns an array with all applied modifications + * + * @return array + */ + public function modifications(): array + { + return $this->modifications ?? []; + } + + /** + * Returns the instance of the original File object + * + * @return mixed + */ + public function original() + { + return $this->original; + } + + /** + * Applies the stored modifications and + * saves the file on disk + * + * @return $this + */ + public function save() + { + $this->kirby()->thumb($this->original()->root(), $this->root(), $this->modifications()); + return $this; + } + + /** + * Setter for modifications + * + * @param array|null $modifications + */ + protected function setModifications(array $modifications = null) + { + $this->modifications = $modifications; + } + + /** + * Setter for the original File object + * + * @param $original + */ + protected function setOriginal($original) + { + $this->original = $original; + } + + /** + * Converts the object to an array + * + * @return array + */ + public function toArray(): array + { + $array = array_merge($this->parentToArray(), [ + 'modifications' => $this->modifications(), + ]); + + ksort($array); + + return $array; + } +} diff --git a/kirby/src/Cms/Filename.php b/kirby/src/Cms/Filename.php new file mode 100644 index 0000000..b5e1867 --- /dev/null +++ b/kirby/src/Cms/Filename.php @@ -0,0 +1,303 @@ + 'top left', + * 'width' => 300, + * 'height' => 200 + * 'quality' => 80 + * ]); + * + * echo $filename->toString(); + * // result: some-file-300x200-crop-top-left-q80.jpg + * + * @package Kirby Cms + * @author Bastian Allgeier + * @link https://getkirby.com + * @copyright Bastian Allgeier GmbH + * @license https://getkirby.com/license + */ +class Filename +{ + /** + * List of all applicable attributes + * + * @var array + */ + protected $attributes; + + /** + * The sanitized file extension + * + * @var string + */ + protected $extension; + + /** + * The source original filename + * + * @var string + */ + protected $filename; + + /** + * The sanitized file name + * + * @var string + */ + protected $name; + + /** + * The template for the final name + * + * @var string + */ + protected $template; + + /** + * Creates a new Filename object + * + * @param string $filename + * @param string $template + * @param array $attributes + */ + public function __construct(string $filename, string $template, array $attributes = []) + { + $this->filename = $filename; + $this->template = $template; + $this->attributes = $attributes; + $this->extension = $this->sanitizeExtension(pathinfo($filename, PATHINFO_EXTENSION)); + $this->name = $this->sanitizeName(pathinfo($filename, PATHINFO_FILENAME)); + } + + /** + * Converts the entire object to a string + * + * @return string + */ + public function __toString(): string + { + return $this->toString(); + } + + /** + * Converts all processed attributes + * to an array. The array keys are already + * the shortened versions for the filename + * + * @return array + */ + public function attributesToArray(): array + { + $array = [ + 'dimensions' => implode('x', $this->dimensions()), + 'crop' => $this->crop(), + 'blur' => $this->blur(), + 'bw' => $this->grayscale(), + 'q' => $this->quality(), + ]; + + $array = array_filter($array, function ($item) { + return $item !== null && $item !== false && $item !== ''; + }); + + return $array; + } + + /** + * Converts all processed attributes + * to a string, that can be used in the + * new filename + * + * @param string|null $prefix The prefix will be used in the filename creation + * @return string + */ + public function attributesToString(string $prefix = null): string + { + $array = $this->attributesToArray(); + $result = []; + + foreach ($array as $key => $value) { + if ($value === true) { + $value = ''; + } + + switch ($key) { + case 'dimensions': + $result[] = $value; + break; + case 'crop': + $result[] = ($value === 'center') ? null : $key . '-' . $value; + break; + default: + $result[] = $key . $value; + } + } + + $result = array_filter($result); + $attributes = implode('-', $result); + + if (empty($attributes) === true) { + return ''; + } + + return $prefix . $attributes; + } + + /** + * Normalizes the blur option value + * + * @return false|int + */ + public function blur() + { + $value = $this->attributes['blur'] ?? false; + + if ($value === false) { + return false; + } + + return (int)$value; + } + + /** + * Normalizes the crop option value + * + * @return false|string + */ + public function crop() + { + // get the crop value + $crop = $this->attributes['crop'] ?? false; + + if ($crop === false) { + return false; + } + + return Str::slug($crop); + } + + /** + * Returns a normalized array + * with width and height values + * if available + * + * @return array + */ + public function dimensions() + { + if (empty($this->attributes['width']) === true && empty($this->attributes['height']) === true) { + return []; + } + + return [ + 'width' => $this->attributes['width'] ?? null, + 'height' => $this->attributes['height'] ?? null + ]; + } + + /** + * Returns the sanitized extension + * + * @return string + */ + public function extension(): string + { + return $this->extension; + } + + /** + * Normalizes the grayscale option value + * and also the available ways to write + * the option. You can use `grayscale`, + * `greyscale` or simply `bw`. The function + * will always return `grayscale` + * + * @return bool + */ + public function grayscale(): bool + { + // normalize options + $value = $this->attributes['grayscale'] ?? $this->attributes['greyscale'] ?? $this->attributes['bw'] ?? false; + + // turn anything into boolean + return filter_var($value, FILTER_VALIDATE_BOOLEAN); + } + + /** + * Returns the filename without extension + * + * @return string + */ + public function name(): string + { + return $this->name; + } + + /** + * Normalizes the quality option value + * + * @return false|int + */ + public function quality() + { + $value = $this->attributes['quality'] ?? false; + + if ($value === false || $value === true) { + return false; + } + + return (int)$value; + } + + /** + * Sanitizes the file extension. + * The extension will be converted + * to lowercase and `jpeg` will be + * replaced with `jpg` + * + * @param string $extension + * @return string + */ + protected function sanitizeExtension(string $extension): string + { + $extension = strtolower($extension); + $extension = str_replace('jpeg', 'jpg', $extension); + return $extension; + } + + /** + * Sanitizes the name with Kirby's + * Str::slug function + * + * @param string $name + * @return string + */ + protected function sanitizeName(string $name): string + { + return Str::slug($name); + } + + /** + * Returns the converted filename as string + * + * @return string + */ + public function toString(): string + { + return Str::template($this->template, [ + 'name' => $this->name(), + 'attributes' => $this->attributesToString('-'), + 'extension' => $this->extension() + ], ''); + } +} diff --git a/kirby/src/Cms/Files.php b/kirby/src/Cms/Files.php new file mode 100644 index 0000000..302423c --- /dev/null +++ b/kirby/src/Cms/Files.php @@ -0,0 +1,145 @@ + + * @link https://getkirby.com + * @copyright Bastian Allgeier GmbH + * @license https://getkirby.com/license + */ +class Files extends Collection +{ + /** + * All registered files methods + * + * @var array + */ + public static $methods = []; + + /** + * Adds a single file or + * an entire second collection to the + * current collection + * + * @param mixed $object + * @return $this + */ + public function add($object) + { + // add a page collection + if (is_a($object, self::class) === true) { + $this->data = array_merge($this->data, $object->data); + + // add a file by id + } elseif (is_string($object) === true && $file = App::instance()->file($object)) { + $this->__set($file->id(), $file); + + // add a file object + } elseif (is_a($object, 'Kirby\Cms\File') === true) { + $this->__set($object->id(), $object); + } + + return $this; + } + + /** + * Sort all given files by the + * order in the array + * + * @param array $files List of file ids + * @param int $offset Sorting offset + * @return $this + */ + public function changeSort(array $files, int $offset = 0) + { + foreach ($files as $filename) { + if ($file = $this->get($filename)) { + $offset++; + $file->changeSort($offset); + } + } + + return $this; + } + + /** + * Creates a files collection from an array of props + * + * @param array $files + * @param \Kirby\Cms\Model $parent + * @return static + */ + public static function factory(array $files, Model $parent) + { + $collection = new static([], $parent); + $kirby = $parent->kirby(); + + foreach ($files as $props) { + $props['collection'] = $collection; + $props['kirby'] = $kirby; + $props['parent'] = $parent; + + $file = File::factory($props); + + $collection->data[$file->id()] = $file; + } + + return $collection; + } + + /** + * Tries to find a file by id/filename + * + * @param string $id + * @return \Kirby\Cms\File|null + */ + public function findById(string $id) + { + return $this->get(ltrim($this->parent->id() . '/' . $id, '/')); + } + + /** + * Alias for FilesFinder::findById() which is + * used internally in the Files collection to + * map the get method correctly. + * + * @param string $key + * @return \Kirby\Cms\File|null + */ + public function findByKey(string $key) + { + return $this->findById($key); + } + + /** + * Filter all files by the given template + * + * @param null|string|array $template + * @return $this|static + */ + public function template($template) + { + if (empty($template) === true) { + return $this; + } + + if ($template === 'default') { + $template = ['default', '']; + } + + return $this->filter( + 'template', + is_array($template) ? 'in' : '==', + $template + ); + } +} diff --git a/kirby/src/Cms/Form.php b/kirby/src/Cms/Form.php new file mode 100644 index 0000000..522b7ae --- /dev/null +++ b/kirby/src/Cms/Form.php @@ -0,0 +1,130 @@ + + * @link https://getkirby.com + * @copyright Bastian Allgeier GmbH + * @license https://getkirby.com/license + */ +class Form extends BaseForm +{ + protected $errors; + protected $fields; + protected $values = []; + + /** + * Form constructor. + * + * @param array $props + */ + public function __construct(array $props) + { + $kirby = App::instance(); + + if ($kirby->multilang() === true) { + $fields = $props['fields'] ?? []; + $languageCode = $props['language'] ?? $kirby->language()->code(); + $isDefaultLanguage = $languageCode === $kirby->defaultLanguage()->code(); + + foreach ($fields as $fieldName => $fieldProps) { + // switch untranslatable fields to readonly + if (($fieldProps['translate'] ?? true) === false && $isDefaultLanguage === false) { + $fields[$fieldName]['unset'] = true; + $fields[$fieldName]['disabled'] = true; + } + } + + $props['fields'] = $fields; + } + + parent::__construct($props); + } + + /** + * Get the field object by name + * and handle nested fields correctly + * + * @param string $name + * @throws \Kirby\Exception\NotFoundException + * @return \Kirby\Form\Field + */ + public function field(string $name) + { + $form = $this; + $fieldNames = Str::split($name, '+'); + $index = 0; + $count = count($fieldNames); + $field = null; + + foreach ($fieldNames as $fieldName) { + $index++; + + if ($field = $form->fields()->get($fieldName)) { + if ($count !== $index) { + $form = $field->form(); + } + } else { + throw new NotFoundException('The field "' . $fieldName . '" could not be found'); + } + } + + // it can get this error only if $name is an empty string as $name = '' + if ($field === null) { + throw new NotFoundException('No field could be loaded'); + } + + return $field; + } + + /** + * @param \Kirby\Cms\Model $model + * @param array $props + * @return static + */ + public static function for(Model $model, array $props = []) + { + // get the original model data + $original = $model->content($props['language'] ?? null)->toArray(); + $values = $props['values'] ?? []; + + // convert closures to values + foreach ($values as $key => $value) { + if (is_a($value, 'Closure') === true) { + $values[$key] = $value($original[$key] ?? null); + } + } + + // set a few defaults + $props['values'] = array_merge($original, $values); + $props['fields'] = $props['fields'] ?? []; + $props['model'] = $model; + + // search for the blueprint + if (method_exists($model, 'blueprint') === true && $blueprint = $model->blueprint()) { + $props['fields'] = $blueprint->fields(); + } + + $ignoreDisabled = $props['ignoreDisabled'] ?? false; + + // REFACTOR: this could be more elegant + if ($ignoreDisabled === true) { + $props['fields'] = array_map(function ($field) { + $field['disabled'] = false; + return $field; + }, $props['fields']); + } + + return new static($props); + } +} diff --git a/kirby/src/Cms/HasChildren.php b/kirby/src/Cms/HasChildren.php new file mode 100644 index 0000000..5a6808c --- /dev/null +++ b/kirby/src/Cms/HasChildren.php @@ -0,0 +1,241 @@ + + * @link https://getkirby.com + * @copyright Bastian Allgeier GmbH + * @license https://getkirby.com/license + */ +trait HasChildren +{ + /** + * The Pages collection + * + * @var \Kirby\Cms\Pages + */ + public $children; + + /** + * The list of available drafts + * + * @var \Kirby\Cms\Pages + */ + public $drafts; + + /** + * Returns the Pages collection + * + * @return \Kirby\Cms\Pages + */ + public function children() + { + if (is_a($this->children, 'Kirby\Cms\Pages') === true) { + return $this->children; + } + + return $this->children = Pages::factory($this->inventory()['children'], $this); + } + + /** + * Returns all children and drafts at the same time + * + * @return \Kirby\Cms\Pages + */ + public function childrenAndDrafts() + { + return $this->children()->merge($this->drafts()); + } + + /** + * Return a list of ids for the model's + * toArray method + * + * @return array + */ + protected function convertChildrenToArray(): array + { + return $this->children()->keys(); + } + + /** + * Searches for a child draft by id + * + * @param string $path + * @return \Kirby\Cms\Page|null + */ + public function draft(string $path) + { + $path = str_replace('_drafts/', '', $path); + + if (Str::contains($path, '/') === false) { + return $this->drafts()->find($path); + } + + $parts = explode('/', $path); + $parent = $this; + + foreach ($parts as $slug) { + if ($page = $parent->find($slug)) { + $parent = $page; + continue; + } + + if ($draft = $parent->drafts()->find($slug)) { + $parent = $draft; + continue; + } + + return null; + } + + return $parent; + } + + /** + * Return all drafts of the model + * + * @return \Kirby\Cms\Pages + */ + public function drafts() + { + if (is_a($this->drafts, 'Kirby\Cms\Pages') === true) { + return $this->drafts; + } + + $kirby = $this->kirby(); + + // create the inventory for all drafts + $inventory = Dir::inventory( + $this->root() . '/_drafts', + $kirby->contentExtension(), + $kirby->contentIgnore(), + $kirby->multilang() + ); + + return $this->drafts = Pages::factory($inventory['children'], $this, true); + } + + /** + * Finds one or multiple children by id + * + * @param string ...$arguments + * @return \Kirby\Cms\Page|\Kirby\Cms\Pages|null + */ + public function find(...$arguments) + { + return $this->children()->find(...$arguments); + } + + /** + * Finds a single page or draft + * + * @param string $path + * @return \Kirby\Cms\Page|null + */ + public function findPageOrDraft(string $path) + { + return $this->children()->find($path) ?? $this->drafts()->find($path); + } + + /** + * Returns a collection of all children of children + * + * @return \Kirby\Cms\Pages + */ + public function grandChildren() + { + return $this->children()->children(); + } + + /** + * Checks if the model has any children + * + * @return bool + */ + public function hasChildren(): bool + { + return $this->children()->count() > 0; + } + + /** + * Checks if the model has any drafts + * + * @return bool + */ + public function hasDrafts(): bool + { + return $this->drafts()->count() > 0; + } + + /** + * Checks if the page has any listed children + * + * @return bool + */ + public function hasListedChildren(): bool + { + return $this->children()->listed()->count() > 0; + } + + /** + * Checks if the page has any unlisted children + * + * @return bool + */ + public function hasUnlistedChildren(): bool + { + return $this->children()->unlisted()->count() > 0; + } + + /** + * Creates a flat child index + * + * @param bool $drafts + * @return \Kirby\Cms\Pages + */ + public function index(bool $drafts = false) + { + if ($drafts === true) { + return $this->childrenAndDrafts()->index($drafts); + } else { + return $this->children()->index(); + } + } + + /** + * Sets the Children collection + * + * @param array|null $children + * @return $this + */ + protected function setChildren(array $children = null) + { + if ($children !== null) { + $this->children = Pages::factory($children, $this); + } + + return $this; + } + + /** + * Sets the Drafts collection + * + * @param array|null $drafts + * @return $this + */ + protected function setDrafts(array $drafts = null) + { + if ($drafts !== null) { + $this->drafts = Pages::factory($drafts, $this, true); + } + + return $this; + } +} diff --git a/kirby/src/Cms/HasFiles.php b/kirby/src/Cms/HasFiles.php new file mode 100644 index 0000000..c648ec8 --- /dev/null +++ b/kirby/src/Cms/HasFiles.php @@ -0,0 +1,226 @@ + + * @link https://getkirby.com + * @copyright Bastian Allgeier GmbH + * @license https://getkirby.com/license + */ +trait HasFiles +{ + /** + * The Files collection + * + * @var \Kirby\Cms\Files + */ + protected $files; + + /** + * Filters the Files collection by type audio + * + * @return \Kirby\Cms\Files + */ + public function audio() + { + return $this->files()->filter('type', '==', 'audio'); + } + + /** + * Filters the Files collection by type code + * + * @return \Kirby\Cms\Files + */ + public function code() + { + return $this->files()->filter('type', '==', 'code'); + } + + /** + * Returns a list of file ids + * for the toArray method of the model + * + * @return array + */ + protected function convertFilesToArray(): array + { + return $this->files()->keys(); + } + + /** + * Creates a new file + * + * @param array $props + * @return \Kirby\Cms\File + */ + public function createFile(array $props) + { + $props = array_merge($props, [ + 'parent' => $this, + 'url' => null + ]); + + return File::create($props); + } + + /** + * Filters the Files collection by type documents + * + * @return \Kirby\Cms\Files + */ + public function documents() + { + return $this->files()->filter('type', '==', 'document'); + } + + /** + * Returns a specific file by filename or the first one + * + * @param string|null $filename + * @param string $in + * @return \Kirby\Cms\File|null + */ + public function file(string $filename = null, string $in = 'files') + { + if ($filename === null) { + return $this->$in()->first(); + } + + if (strpos($filename, '/') !== false) { + $path = dirname($filename); + $filename = basename($filename); + + if ($page = $this->find($path)) { + return $page->$in()->find($filename); + } + + return null; + } + + return $this->$in()->find($filename); + } + + /** + * Returns the Files collection + * + * @return \Kirby\Cms\Files + */ + public function files() + { + if (is_a($this->files, 'Kirby\Cms\Files') === true) { + return $this->files; + } + + return $this->files = Files::factory($this->inventory()['files'], $this); + } + + /** + * Checks if the Files collection has any audio files + * + * @return bool + */ + public function hasAudio(): bool + { + return $this->audio()->count() > 0; + } + + /** + * Checks if the Files collection has any code files + * + * @return bool + */ + public function hasCode(): bool + { + return $this->code()->count() > 0; + } + + /** + * Checks if the Files collection has any document files + * + * @return bool + */ + public function hasDocuments(): bool + { + return $this->documents()->count() > 0; + } + + /** + * Checks if the Files collection has any files + * + * @return bool + */ + public function hasFiles(): bool + { + return $this->files()->count() > 0; + } + + /** + * Checks if the Files collection has any images + * + * @return bool + */ + public function hasImages(): bool + { + return $this->images()->count() > 0; + } + + /** + * Checks if the Files collection has any videos + * + * @return bool + */ + public function hasVideos(): bool + { + return $this->videos()->count() > 0; + } + + /** + * Returns a specific image by filename or the first one + * + * @param string|null $filename + * @return \Kirby\Cms\File|null + */ + public function image(string $filename = null) + { + return $this->file($filename, 'images'); + } + + /** + * Filters the Files collection by type image + * + * @return \Kirby\Cms\Files + */ + public function images() + { + return $this->files()->filter('type', '==', 'image'); + } + + /** + * Sets the Files collection + * + * @param \Kirby\Cms\Files|null $files + * @return $this + */ + protected function setFiles(array $files = null) + { + if ($files !== null) { + $this->files = Files::factory($files, $this); + } + + return $this; + } + + /** + * Filters the Files collection by type videos + * + * @return \Kirby\Cms\Files + */ + public function videos() + { + return $this->files()->filter('type', '==', 'video'); + } +} diff --git a/kirby/src/Cms/HasMethods.php b/kirby/src/Cms/HasMethods.php new file mode 100644 index 0000000..9abe109 --- /dev/null +++ b/kirby/src/Cms/HasMethods.php @@ -0,0 +1,80 @@ + + * @link https://getkirby.com + * @copyright Bastian Allgeier GmbH + * @license https://getkirby.com/license + */ +trait HasMethods +{ + /** + * All registered methods + * + * @var array + */ + public static $methods = []; + + /** + * Calls a registered method class with the + * passed arguments + * + * @internal + * @param string $method + * @param array $args + * @return mixed + * @throws \Kirby\Exception\BadMethodCallException + */ + public function callMethod(string $method, array $args = []) + { + $closure = $this->getMethod($method); + + if ($closure === null) { + throw new BadMethodCallException('The method ' . $method . ' does not exist'); + } + + return $closure->call($this, ...$args); + } + + /** + * Checks if the object has a registered method + * + * @internal + * @param string $method + * @return bool + */ + public function hasMethod(string $method): bool + { + return $this->getMethod($method) !== null; + } + + /** + * Returns a registered method by name, either from + * the current class or from a parent class ordered by + * inheritance order (top to bottom) + * + * @param string $method + * @return \Closure|null + */ + protected function getMethod(string $method) + { + if (isset(static::$methods[$method]) === true) { + return static::$methods[$method]; + } + + foreach (class_parents($this) as $parent) { + if (isset($parent::$methods[$method]) === true) { + return $parent::$methods[$method]; + } + } + + return null; + } +} diff --git a/kirby/src/Cms/HasSiblings.php b/kirby/src/Cms/HasSiblings.php new file mode 100644 index 0000000..3bc162f --- /dev/null +++ b/kirby/src/Cms/HasSiblings.php @@ -0,0 +1,182 @@ + + * @link https://getkirby.com + * @copyright Bastian Allgeier GmbH + * @license https://getkirby.com/license + */ +trait HasSiblings +{ + /** + * Returns the position / index in the collection + * + * @param \Kirby\Cms\Collection|null $collection + * + * @return int + */ + public function indexOf($collection = null): int + { + if ($collection === null) { + $collection = $this->siblingsCollection(); + } + + return $collection->indexOf($this); + } + + /** + * Returns the next item in the collection if available + * + * @param \Kirby\Cms\Collection|null $collection + * + * @return \Kirby\Cms\Model|null + */ + public function next($collection = null) + { + if ($collection === null) { + $collection = $this->siblingsCollection(); + } + + return $collection->nth($this->indexOf($collection) + 1); + } + + /** + * Returns the end of the collection starting after the current item + * + * @param \Kirby\Cms\Collection|null $collection + * + * @return \Kirby\Cms\Collection + */ + public function nextAll($collection = null) + { + if ($collection === null) { + $collection = $this->siblingsCollection(); + } + + return $collection->slice($this->indexOf($collection) + 1); + } + + /** + * Returns the previous item in the collection if available + * + * @param \Kirby\Cms\Collection|null $collection + * + * @return \Kirby\Cms\Model|null + */ + public function prev($collection = null) + { + if ($collection === null) { + $collection = $this->siblingsCollection(); + } + + return $collection->nth($this->indexOf($collection) - 1); + } + + /** + * Returns the beginning of the collection before the current item + * + * @param \Kirby\Cms\Collection|null $collection + * + * @return \Kirby\Cms\Collection + */ + public function prevAll($collection = null) + { + if ($collection === null) { + $collection = $this->siblingsCollection(); + } + + return $collection->slice(0, $this->indexOf($collection)); + } + + /** + * Returns all sibling elements + * + * @param bool $self + * @return \Kirby\Cms\Collection + */ + public function siblings(bool $self = true) + { + $siblings = $this->siblingsCollection(); + + if ($self === false) { + return $siblings->not($this); + } + + return $siblings; + } + + /** + * Checks if there's a next item in the collection + * + * @param \Kirby\Cms\Collection|null $collection + * + * @return bool + */ + public function hasNext($collection = null): bool + { + return $this->next($collection) !== null; + } + + /** + * Checks if there's a previous item in the collection + * + * @param \Kirby\Cms\Collection|null $collection + * + * @return bool + */ + public function hasPrev($collection = null): bool + { + return $this->prev($collection) !== null; + } + + /** + * Checks if the item is the first in the collection + * + * @param \Kirby\Cms\Collection|null $collection + * + * @return bool + */ + public function isFirst($collection = null): bool + { + if ($collection === null) { + $collection = $this->siblingsCollection(); + } + + return $collection->first()->is($this); + } + + /** + * Checks if the item is the last in the collection + * + * @param \Kirby\Cms\Collection|null $collection + * + * @return bool + */ + public function isLast($collection = null): bool + { + if ($collection === null) { + $collection = $this->siblingsCollection(); + } + + return $collection->last()->is($this); + } + + /** + * Checks if the item is at a certain position + * + * @param \Kirby\Cms\Collection|null $collection + * @param int $n + * + * @return bool + */ + public function isNth(int $n, $collection = null): bool + { + return $this->indexOf($collection) === $n; + } +} diff --git a/kirby/src/Cms/Html.php b/kirby/src/Cms/Html.php new file mode 100644 index 0000000..634f8de --- /dev/null +++ b/kirby/src/Cms/Html.php @@ -0,0 +1,30 @@ + + * @link https://getkirby.com + * @copyright Bastian Allgeier GmbH + * @license https://getkirby.com/license + */ +class Html extends \Kirby\Toolkit\Html +{ + /** + * Generates an `a` tag with an absolute Url + * + * @param string|null $href Relative or absolute Url + * @param string|array|null $text If `null`, the link will be used as link text. If an array is passed, each element will be added unencoded + * @param array $attr Additional attributes for the a tag. + * @return string + */ + public static function link(string $href = null, $text = null, array $attr = []): string + { + return parent::link(Url::to($href), $text, $attr); + } +} diff --git a/kirby/src/Cms/Ingredients.php b/kirby/src/Cms/Ingredients.php new file mode 100644 index 0000000..6a121a2 --- /dev/null +++ b/kirby/src/Cms/Ingredients.php @@ -0,0 +1,95 @@ +urls()` and `$kirby->roots()` objects. + * Those are configured in `kirby/config/urls.php` + * and `kirby/config/roots.php` + * + * @package Kirby Cms + * @author Bastian Allgeier + * @link https://getkirby.com + * @copyright Bastian Allgeier GmbH + * @license https://getkirby.com/license + */ +class Ingredients +{ + /** + * @var array + */ + protected $ingredients = []; + + /** + * Creates a new ingredient collection + * + * @param array $ingredients + */ + public function __construct(array $ingredients) + { + $this->ingredients = $ingredients; + } + + /** + * Magic getter for single ingredients + * + * @param string $method + * @param array|null $args + * @return mixed + */ + public function __call(string $method, array $args = null) + { + return $this->ingredients[$method] ?? null; + } + + /** + * Improved `var_dump` output + * + * @return array + */ + public function __debugInfo(): array + { + return $this->ingredients; + } + + /** + * Get a single ingredient by key + * + * @param string $key + * @return mixed + */ + public function __get(string $key) + { + return $this->ingredients[$key] ?? null; + } + + /** + * Resolves all ingredient callbacks + * and creates a plain array + * + * @internal + * @param array $ingredients + * @return static + */ + public static function bake(array $ingredients) + { + foreach ($ingredients as $name => $ingredient) { + if (is_a($ingredient, 'Closure') === true) { + $ingredients[$name] = $ingredient($ingredients); + } + } + + return new static($ingredients); + } + + /** + * Returns all ingredients as plain array + * + * @return array + */ + public function toArray(): array + { + return $this->ingredients; + } +} diff --git a/kirby/src/Cms/Item.php b/kirby/src/Cms/Item.php new file mode 100644 index 0000000..57bad44 --- /dev/null +++ b/kirby/src/Cms/Item.php @@ -0,0 +1,137 @@ + + * @link https://getkirby.com + * @copyright Bastian Allgeier GmbH + * @license https://getkirby.com/license + */ +class Item +{ + const ITEMS_CLASS = '\Kirby\Cms\Items'; + + use HasSiblings; + + /** + * @var string + */ + protected $id; + + /** + * @var array + */ + protected $params; + + /** + * @var \Kirby\Cms\Page|\Kirby\Cms\Site|\Kirby\Cms\File|\Kirby\Cms\User + */ + protected $parent; + + /** + * @var \Kirby\Cms\Items + */ + protected $siblings; + + /** + * Creates a new item + * + * @param array $params + */ + public function __construct(array $params = []) + { + $siblingsClass = static::ITEMS_CLASS; + + $this->id = $params['id'] ?? uuid(); + $this->params = $params; + $this->parent = $params['parent'] ?? site(); + $this->siblings = $params['siblings'] ?? new $siblingsClass(); + } + + /** + * Static Item factory + * + * @param array $params + * @return \Kirby\Cms\Item + */ + public static function factory(array $params) + { + return new static($params); + } + + /** + * Returns the unique item id (UUID v4) + * + * @return string + */ + public function id(): string + { + return $this->id; + } + + /** + * Compares the item to another one + * + * @param \Kirby\Cms\Item $item + * @return bool + */ + public function is(Item $item): bool + { + return $this->id() === $item->id(); + } + + /** + * Returns the Kirby instance + * + * @return \Kirby\Cms\App + */ + public function kirby() + { + return $this->parent()->kirby(); + } + + /** + * Returns the parent model + * + * @return \Kirby\Cms\Page|\Kirby\Cms\Site|\Kirby\Cms\File|\Kirby\Cms\User + */ + public function parent() + { + return $this->parent; + } + + /** + * Returns the sibling collection + * This is required by the HasSiblings trait + * + * @return \Kirby\Cms\Items + * @psalm-return self::ITEMS_CLASS + */ + protected function siblingsCollection() + { + return $this->siblings; + } + + /** + * Converts the item to an array + * + * @return array + */ + public function toArray(): array + { + return [ + 'id' => $this->id(), + ]; + } +} diff --git a/kirby/src/Cms/Items.php b/kirby/src/Cms/Items.php new file mode 100644 index 0000000..9bec303 --- /dev/null +++ b/kirby/src/Cms/Items.php @@ -0,0 +1,97 @@ + + * @link https://getkirby.com + * @copyright Bastian Allgeier GmbH + * @license https://getkirby.com/license + */ +class Items extends Collection +{ + const ITEM_CLASS = '\Kirby\Cms\Item'; + + /** + * @var array + */ + protected $options; + + /** + * @var \Kirby\Cms\ModelWithContent + */ + protected $parent; + + /** + * Constructor + * + * @param array $objects + * @param array $options + */ + public function __construct($objects = [], array $options = []) + { + $this->options = $options; + $this->parent = $options['parent'] ?? site(); + + parent::__construct($objects, $this->parent); + } + + /** + * Creates a new item collection from a + * an array of item props + * + * @param array $items + * @param array $params + * @return \Kirby\Cms\Items + */ + public static function factory(array $items = null, array $params = []) + { + $options = array_merge([ + 'options' => [], + 'parent' => site(), + ], $params); + + if (empty($items) === true || is_array($items) === false) { + return new static(); + } + + if (is_array($options) === false) { + throw new Exception('Invalid item options'); + } + + // create a new collection of blocks + $collection = new static([], $options); + + foreach ($items as $params) { + if (is_array($params) === false) { + continue; + } + + $params['options'] = $options['options']; + $params['parent'] = $options['parent']; + $params['siblings'] = $collection; + $class = static::ITEM_CLASS; + $item = $class::factory($params); + $collection->append($item->id(), $item); + } + + return $collection; + } + + /** + * Convert the items to an array + * + * @return array + */ + public function toArray(Closure $map = null): array + { + return array_values(parent::toArray($map)); + } +} diff --git a/kirby/src/Cms/KirbyTag.php b/kirby/src/Cms/KirbyTag.php new file mode 100644 index 0000000..4a676eb --- /dev/null +++ b/kirby/src/Cms/KirbyTag.php @@ -0,0 +1,64 @@ + + * @link https://getkirby.com + * @copyright Bastian Allgeier GmbH + * @license https://getkirby.com/license + */ +class KirbyTag extends \Kirby\Text\KirbyTag +{ + /** + * Finds a file for the given path. + * The method first searches the file + * in the current parent, if it's a page. + * Afterwards it uses Kirby's global file finder. + * + * @param string $path + * @return \Kirby\Cms\File|null + */ + public function file(string $path) + { + $parent = $this->parent(); + + if ( + is_object($parent) === true && + method_exists($parent, 'file') === true && + $file = $parent->file($path) + ) { + return $file; + } + + if (is_a($parent, 'Kirby\Cms\File') === true && $file = $parent->page()->file($path)) { + return $file; + } + + return $this->kirby()->file($path, null, true); + } + + /** + * Returns the current Kirby instance + * + * @return \Kirby\Cms\App + */ + public function kirby() + { + return $this->data['kirby'] ?? App::instance(); + } + + /** + * Returns the parent model + * + * @return \Kirby\Cms\Model|null + */ + public function parent() + { + return $this->data['parent']; + } +} diff --git a/kirby/src/Cms/KirbyTags.php b/kirby/src/Cms/KirbyTags.php new file mode 100644 index 0000000..d92d719 --- /dev/null +++ b/kirby/src/Cms/KirbyTags.php @@ -0,0 +1,45 @@ + + * @link https://getkirby.com + * @copyright Bastian Allgeier GmbH + * @license https://getkirby.com/license + */ +class KirbyTags extends \Kirby\Text\KirbyTags +{ + /** + * The KirbyTag rendering class + * + * @var string + */ + protected static $tagClass = 'Kirby\Cms\KirbyTag'; + + /** + * @param string|null $text + * @param array $data + * @param array $options + * @param \Kirby\Cms\App|null $app + * @return string + */ + public static function parse(string $text = null, array $data = [], array $options = [], ?App $app = null): string + { + if ($app !== null) { + $text = $app->apply('kirbytags:before', compact('text', 'data', 'options'), 'text'); + } + + $text = parent::parse($text, $data, $options); + + if ($app !== null) { + $text = $app->apply('kirbytags:after', compact('text', 'data', 'options'), 'text'); + } + + return $text; + } +} diff --git a/kirby/src/Cms/Language.php b/kirby/src/Cms/Language.php new file mode 100644 index 0000000..97ca7b7 --- /dev/null +++ b/kirby/src/Cms/Language.php @@ -0,0 +1,684 @@ + + * @link https://getkirby.com + * @copyright Bastian Allgeier GmbH + * @license https://getkirby.com/license + */ +class Language extends Model +{ + /** + * @var string + */ + protected $code; + + /** + * @var bool + */ + protected $default; + + /** + * @var string + */ + protected $direction; + + /** + * @var array + */ + protected $locale; + + /** + * @var string + */ + protected $name; + + /** + * @var array|null + */ + protected $slugs; + + /** + * @var array|null + */ + protected $smartypants; + + /** + * @var array|null + */ + protected $translations; + + /** + * @var string + */ + protected $url; + + /** + * Creates a new language object + * + * @param array $props + */ + public function __construct(array $props) + { + $this->setRequiredProperties($props, [ + 'code' + ]); + + $this->setOptionalProperties($props, [ + 'default', + 'direction', + 'locale', + 'name', + 'slugs', + 'smartypants', + 'translations', + 'url', + ]); + } + + /** + * Improved `var_dump` output + * + * @return array + */ + public function __debugInfo(): array + { + return $this->toArray(); + } + + /** + * Returns the language code + * when the language is converted to a string + * + * @return string + */ + public function __toString(): string + { + return $this->code(); + } + + /** + * Returns the base Url for the language + * without the path or other cruft + * + * @return string + */ + public function baseUrl(): string + { + $kirbyUrl = $this->kirby()->url(); + $languageUrl = $this->url(); + + if (empty($this->url)) { + return $kirbyUrl; + } + + if (Str::startsWith($languageUrl, $kirbyUrl) === true) { + return $kirbyUrl; + } + + return Url::base($languageUrl) ?? $kirbyUrl; + } + + /** + * Returns the language code/id. + * The language code is used in + * text file names as appendix. + * + * @return string + */ + public function code(): string + { + return $this->code; + } + + /** + * Internal converter to create or remove + * translation files. + * + * @param string $from + * @param string $to + * @return bool + */ + protected static function converter(string $from, string $to): bool + { + $kirby = App::instance(); + $site = $kirby->site(); + + // convert site + foreach ($site->files() as $file) { + F::move($file->contentFile($from, true), $file->contentFile($to, true)); + } + + F::move($site->contentFile($from, true), $site->contentFile($to, true)); + + // convert all pages + foreach ($kirby->site()->index(true) as $page) { + foreach ($page->files() as $file) { + F::move($file->contentFile($from, true), $file->contentFile($to, true)); + } + + F::move($page->contentFile($from, true), $page->contentFile($to, true)); + } + + // convert all users + foreach ($kirby->users() as $user) { + foreach ($user->files() as $file) { + F::move($file->contentFile($from, true), $file->contentFile($to, true)); + } + + F::move($user->contentFile($from, true), $user->contentFile($to, true)); + } + + return true; + } + + /** + * Creates a new language object + * + * @internal + * @param array $props + * @return static + */ + public static function create(array $props) + { + $props['code'] = Str::slug($props['code'] ?? null); + $kirby = App::instance(); + $languages = $kirby->languages(); + + // make the first language the default language + if ($languages->count() === 0) { + $props['default'] = true; + } + + $language = new static($props); + + // validate the new language + LanguageRules::create($language); + + $language->save(); + + if ($languages->count() === 0) { + static::converter('', $language->code()); + } + + return $language; + } + + /** + * Delete the current language and + * all its translation files + * + * @internal + * @return bool + * @throws \Kirby\Exception\Exception + */ + public function delete(): bool + { + if ($this->exists() === false) { + return true; + } + + $kirby = App::instance(); + $languages = $kirby->languages(); + $code = $this->code(); + + if (F::remove($this->root()) !== true) { + throw new Exception('The language could not be deleted'); + } + + if ($languages->count() === 1) { + return $this->converter($code, ''); + } else { + return $this->deleteContentFiles($code); + } + } + + /** + * When the language is deleted, all content files with + * the language code must be removed as well. + * + * @param mixed $code + * @return bool + */ + protected function deleteContentFiles($code): bool + { + $kirby = App::instance(); + $site = $kirby->site(); + + F::remove($site->contentFile($code, true)); + + foreach ($kirby->site()->index(true) as $page) { + foreach ($page->files() as $file) { + F::remove($file->contentFile($code, true)); + } + + F::remove($page->contentFile($code, true)); + } + + foreach ($kirby->users() as $user) { + foreach ($user->files() as $file) { + F::remove($file->contentFile($code, true)); + } + + F::remove($user->contentFile($code, true)); + } + + return true; + } + + /** + * Reading direction of this language + * + * @return string + */ + public function direction(): string + { + return $this->direction; + } + + /** + * Check if the language file exists + * + * @return bool + */ + public function exists(): bool + { + return file_exists($this->root()); + } + + /** + * Checks if this is the default language + * for the site. + * + * @return bool + */ + public function isDefault(): bool + { + return $this->default; + } + + /** + * The id is required for collections + * to work properly. The code is used as id + * + * @return string + */ + public function id(): string + { + return $this->code; + } + + /** + * Loads the language rules for provided locale code + * + * @param string $code + */ + public static function loadRules(string $code) + { + $kirby = kirby(); + $code = Str::contains($code, '.') ? Str::before($code, '.') : $code; + $file = $kirby->root('i18n:rules') . '/' . $code . '.json'; + + if (F::exists($file) === false) { + $file = $kirby->root('i18n:rules') . '/' . Str::before($code, '_') . '.json'; + } + + try { + return Data::read($file); + } catch (\Exception $e) { + return []; + } + } + + /** + * Returns the PHP locale setting array + * + * @param int $category If passed, returns the locale for the specified category (e.g. LC_ALL) as string + * @return array|string + */ + public function locale(int $category = null) + { + if ($category !== null) { + return $this->locale[$category] ?? $this->locale[LC_ALL] ?? null; + } else { + return $this->locale; + } + } + + /** + * Returns the human-readable name + * of the language + * + * @return string + */ + public function name(): string + { + return $this->name; + } + + /** + * Returns the URL path for the language + * + * @return string + */ + public function path(): string + { + if ($this->url === null) { + return $this->code; + } + + return Url::path($this->url); + } + + /** + * Returns the routing pattern for the language + * + * @return string + */ + public function pattern(): string + { + $path = $this->path(); + + if (empty($path) === true) { + return '(:all)'; + } + + return $path . '/(:all?)'; + } + + /** + * Returns the absolute path to the language file + * + * @return string + */ + public function root(): string + { + return App::instance()->root('languages') . '/' . $this->code() . '.php'; + } + + /** + * Returns the LanguageRouter instance + * which is used to handle language specific + * routes. + * + * @return \Kirby\Cms\LanguageRouter + */ + public function router() + { + return new LanguageRouter($this); + } + + /** + * Get slug rules for language + * + * @internal + * @return array + */ + public function rules(): array + { + $code = $this->locale(LC_CTYPE); + $data = static::loadRules($code); + return array_merge($data, $this->slugs()); + } + + /** + * Saves the language settings in the languages folder + * + * @internal + * @return $this + */ + public function save() + { + try { + $existingData = Data::read($this->root()); + } catch (Throwable $e) { + $existingData = []; + } + + $props = [ + 'code' => $this->code(), + 'default' => $this->isDefault(), + 'direction' => $this->direction(), + 'locale' => Locale::export($this->locale()), + 'name' => $this->name(), + 'translations' => $this->translations(), + 'url' => $this->url, + ]; + + $data = array_merge($existingData, $props); + + ksort($data); + + Data::write($this->root(), $data); + + return $this; + } + + /** + * @param string $code + * @return $this + */ + protected function setCode(string $code) + { + $this->code = trim($code); + return $this; + } + + /** + * @param bool $default + * @return $this + */ + protected function setDefault(bool $default = false) + { + $this->default = $default; + return $this; + } + + /** + * @param string $direction + * @return $this + */ + protected function setDirection(string $direction = 'ltr') + { + $this->direction = $direction === 'rtl' ? 'rtl' : 'ltr'; + return $this; + } + + /** + * @param string|array $locale + * @return $this + */ + protected function setLocale($locale = null) + { + if ($locale === null) { + $this->locale = [LC_ALL => $this->code]; + } else { + $this->locale = Locale::normalize($locale); + } + + return $this; + } + + /** + * @param string $name + * @return $this + */ + protected function setName(string $name = null) + { + $this->name = trim($name ?? $this->code); + return $this; + } + + /** + * @param array $slugs + * @return $this + */ + protected function setSlugs(array $slugs = null) + { + $this->slugs = $slugs ?? []; + return $this; + } + + /** + * @param array $smartypants + * @return $this + */ + protected function setSmartypants(array $smartypants = null) + { + $this->smartypants = $smartypants ?? []; + return $this; + } + + /** + * @param array $translations + * @return $this + */ + protected function setTranslations(array $translations = null) + { + $this->translations = $translations ?? []; + return $this; + } + + /** + * @param string $url + * @return $this + */ + protected function setUrl(string $url = null) + { + $this->url = $url; + return $this; + } + + /** + * Returns the custom slug rules for this language + * + * @return array + */ + public function slugs(): array + { + return $this->slugs; + } + + /** + * Returns the custom SmartyPants options for this language + * + * @return array + */ + public function smartypants(): array + { + return $this->smartypants; + } + + /** + * Returns the most important + * properties as array + * + * @return array + */ + public function toArray(): array + { + return [ + 'code' => $this->code(), + 'default' => $this->isDefault(), + 'direction' => $this->direction(), + 'locale' => $this->locale(), + 'name' => $this->name(), + 'rules' => $this->rules(), + 'url' => $this->url() + ]; + } + + /** + * Returns the translation strings for this language + * + * @return array + */ + public function translations(): array + { + return $this->translations; + } + + /** + * Returns the absolute Url for the language + * + * @return string + */ + public function url(): string + { + $url = $this->url; + + if ($url === null) { + $url = '/' . $this->code; + } + + return Url::makeAbsolute($url, $this->kirby()->url()); + } + + /** + * Update language properties and save them + * + * @internal + * @param array $props + * @return static + */ + public function update(array $props = null) + { + // don't change the language code + unset($props['code']); + + // make sure the slug is nice and clean + $props['slug'] = Str::slug($props['slug'] ?? null); + + $kirby = App::instance(); + $updated = $this->clone($props); + + // validate the updated language + LanguageRules::update($updated); + + // convert the current default to a non-default language + if ($updated->isDefault() === true) { + if ($oldDefault = $kirby->defaultLanguage()) { + $oldDefault->clone(['default' => false])->save(); + } + + $code = $this->code(); + $site = $kirby->site(); + + touch($site->contentFile($code)); + + foreach ($kirby->site()->index(true) as $page) { + $files = $page->files(); + + foreach ($files as $file) { + touch($file->contentFile($code)); + } + + touch($page->contentFile($code)); + } + } elseif ($this->isDefault() === true) { + throw new PermissionException('Please select another language to be the primary language'); + } + + return $updated->save(); + } +} diff --git a/kirby/src/Cms/LanguageRouter.php b/kirby/src/Cms/LanguageRouter.php new file mode 100644 index 0000000..a770c35 --- /dev/null +++ b/kirby/src/Cms/LanguageRouter.php @@ -0,0 +1,135 @@ + + * @link https://getkirby.com + * @copyright Bastian Allgeier GmbH + * @license https://getkirby.com/license + */ +class LanguageRouter +{ + /** + * The parent language + * + * @var Language + */ + protected $language; + + /** + * The router instance + * + * @var Router + */ + protected $router; + + /** + * Creates a new language router instance + * for the given language + * + * @param \Kirby\Cms\Language $language + */ + public function __construct(Language $language) + { + $this->language = $language; + } + + /** + * Fetches all scoped routes for the + * current language from the Kirby instance + * + * @return array + * @throws \Kirby\Exception\NotFoundException + */ + public function routes(): array + { + $language = $this->language; + $kirby = $language->kirby(); + $routes = $kirby->routes(); + + // only keep the scoped language routes + $routes = array_values(array_filter($routes, function ($route) use ($language) { + + // no language scope + if (empty($route['language']) === true) { + return false; + } + + // wildcard + if ($route['language'] === '*') { + return true; + } + + // get all applicable languages + $languages = Str::split(strtolower($route['language']), '|'); + + // validate the language + return in_array($language->code(), $languages) === true; + })); + + // add the page-scope if necessary + foreach ($routes as $index => $route) { + if ($pageId = ($route['page'] ?? null)) { + if ($page = $kirby->page($pageId)) { + + // convert string patterns to arrays + $patterns = A::wrap($route['pattern']); + + // prefix all patterns with the page slug + $patterns = array_map(function ($pattern) use ($page, $language) { + return $page->uri($language) . '/' . $pattern; + }, $patterns); + + // reinject the pattern and the full page object + $routes[$index]['pattern'] = $patterns; + $routes[$index]['page'] = $page; + } else { + throw new NotFoundException('The page "' . $pageId . '" does not exist'); + } + } + } + + return $routes; + } + + /** + * Wrapper around the Router::call method + * that injects the Language instance and + * if needed also the Page as arguments. + * + * @param string|null $path + * @return mixed + */ + public function call(string $path = null) + { + $language = $this->language; + $kirby = $language->kirby(); + $router = new Router($this->routes()); + + try { + return $router->call($path, $kirby->request()->method(), function ($route) use ($kirby, $language) { + $kirby->setCurrentTranslation($language); + $kirby->setCurrentLanguage($language); + + if ($page = $route->page()) { + return $route->action()->call($route, $language, $page, ...$route->arguments()); + } else { + return $route->action()->call($route, $language, ...$route->arguments()); + } + }); + } catch (Exception $e) { + return $kirby->resolve($path, $language->code()); + } + } +} diff --git a/kirby/src/Cms/LanguageRoutes.php b/kirby/src/Cms/LanguageRoutes.php new file mode 100644 index 0000000..ad98bc9 --- /dev/null +++ b/kirby/src/Cms/LanguageRoutes.php @@ -0,0 +1,155 @@ +url(); + + foreach ($kirby->languages() as $language) { + + // ignore languages with a different base url + if ($language->baseurl() !== $baseurl) { + continue; + } + + $routes[] = [ + 'pattern' => $language->pattern(), + 'method' => 'ALL', + 'env' => 'site', + 'action' => function ($path = null) use ($language) { + if ($result = $language->router()->call($path)) { + return $result; + } + + // jump through to the fallback if nothing + // can be found for this language + /** @var \Kirby\Http\Route $this */ + $this->next(); + } + ]; + } + + $routes[] = static::fallback($kirby); + + return $routes; + } + + + /** + * Create the fallback route + * for unprefixed default language URLs. + * + * @param \Kirby\Cms\App $kirby + * @return array + */ + public static function fallback(App $kirby): array + { + return [ + 'pattern' => '(:all)', + 'method' => 'ALL', + 'env' => 'site', + 'action' => function (string $path) use ($kirby) { + + // check for content representations or files + $extension = F::extension($path); + + // try to redirect prefixed pages + if (empty($extension) === true && $page = $kirby->page($path)) { + $url = $kirby->request()->url([ + 'query' => null, + 'params' => null, + 'fragment' => null + ]); + + if ($url->toString() !== $page->url()) { + // redirect to translated page directly + // if translation is exists and languages detect is enabled + if ( + $kirby->option('languages.detect') === true && + $page->translation($kirby->detectedLanguage()->code())->exists() === true + ) { + return $kirby + ->response() + ->redirect($page->url($kirby->detectedLanguage()->code())); + } + + return $kirby + ->response() + ->redirect($page->url()); + } + } + + return $kirby->language()->router()->call($path); + } + ]; + } + + /** + * Create the multi-language home page route + * + * @param \Kirby\Cms\App $kirby + * @return array + */ + public static function home(App $kirby): array + { + // Multi-language home + return [ + 'pattern' => '', + 'method' => 'ALL', + 'env' => 'site', + 'action' => function () use ($kirby) { + + // find all languages with the same base url as the current installation + $languages = $kirby->languages()->filter('baseurl', $kirby->url()); + + // if there's no language with a matching base url, + // redirect to the default language + if ($languages->count() === 0) { + return $kirby + ->response() + ->redirect($kirby->defaultLanguage()->url()); + } + + // if there's just one language, we take that to render the home page + if ($languages->count() === 1) { + $currentLanguage = $languages->first(); + } else { + $currentLanguage = $kirby->defaultLanguage(); + } + + // language detection on the home page with / as URL + if ($kirby->url() !== $currentLanguage->url()) { + if ($kirby->option('languages.detect') === true) { + return $kirby + ->response() + ->redirect($kirby->detectedLanguage()->url()); + } + + return $kirby + ->response() + ->redirect($currentLanguage->url()); + } + + // render the home page of the current language + return $currentLanguage->router()->call(); + } + ]; + } +} diff --git a/kirby/src/Cms/LanguageRules.php b/kirby/src/Cms/LanguageRules.php new file mode 100644 index 0000000..4588f1b --- /dev/null +++ b/kirby/src/Cms/LanguageRules.php @@ -0,0 +1,98 @@ + + * @link https://getkirby.com + * @copyright Bastian Allgeier GmbH + * @license https://getkirby.com/license + */ +class LanguageRules +{ + /** + * Validates if the language can be created + * + * @param \Kirby\Cms\Language $language + * @return bool + * @throws \Kirby\Exception\DuplicateException If the language already exists + */ + public static function create(Language $language): bool + { + static::validLanguageCode($language); + static::validLanguageName($language); + + if ($language->exists() === true) { + throw new DuplicateException([ + 'key' => 'language.duplicate', + 'data' => [ + 'code' => $language->code() + ] + ]); + } + + return true; + } + + /** + * Validates if the language can be updated + * + * @param \Kirby\Cms\Language $language + */ + public static function update(Language $language) + { + static::validLanguageCode($language); + static::validLanguageName($language); + } + + /** + * Validates if the language code is formatted correctly + * + * @param \Kirby\Cms\Language $language + * @return bool + * @throws \Kirby\Exception\InvalidArgumentException If the language code is not valid + */ + public static function validLanguageCode(Language $language): bool + { + if (Str::length($language->code()) < 2) { + throw new InvalidArgumentException([ + 'key' => 'language.code', + 'data' => [ + 'code' => $language->code(), + 'name' => $language->name() + ] + ]); + } + + return true; + } + + /** + * Validates if the language name is formatted correctly + * + * @param \Kirby\Cms\Language $language + * @return bool + * @throws \Kirby\Exception\InvalidArgumentException If the language name is invalid + */ + public static function validLanguageName(Language $language): bool + { + if (Str::length($language->name()) < 1) { + throw new InvalidArgumentException([ + 'key' => 'language.name', + 'data' => [ + 'code' => $language->code(), + 'name' => $language->name() + ] + ]); + } + + return true; + } +} diff --git a/kirby/src/Cms/Languages.php b/kirby/src/Cms/Languages.php new file mode 100644 index 0000000..1b84359 --- /dev/null +++ b/kirby/src/Cms/Languages.php @@ -0,0 +1,99 @@ + + * @link https://getkirby.com + * @copyright Bastian Allgeier GmbH + * @license https://getkirby.com/license + */ +class Languages extends Collection +{ + /** + * Creates a new collection with the given language objects + * + * @param array $objects `Kirby\Cms\Language` objects + * @param null $parent + * @throws \Kirby\Exception\DuplicateException + */ + public function __construct($objects = [], $parent = null) + { + $defaults = array_filter($objects, function ($language) { + return $language->isDefault() === true; + }); + + if (count($defaults) > 1) { + throw new DuplicateException('You cannot have multiple default languages. Please check your language config files.'); + } + + parent::__construct($objects, $parent); + } + + /** + * Returns all language codes as array + * + * @return array + */ + public function codes(): array + { + return $this->keys(); + } + + /** + * Creates a new language with the given props + * + * @internal + * @param array $props + * @return \Kirby\Cms\Language + */ + public function create(array $props) + { + return Language::create($props); + } + + /** + * Returns the default language + * + * @return \Kirby\Cms\Language|null + */ + public function default() + { + if ($language = $this->findBy('isDefault', true)) { + return $language; + } else { + return $this->first(); + } + } + + /** + * Convert all defined languages to a collection + * + * @internal + * @return static + */ + public static function load() + { + $languages = []; + $files = glob(App::instance()->root('languages') . '/*.php'); + + foreach ($files as $file) { + $props = F::load($file); + + if (is_array($props) === true) { + // inject the language code from the filename if it does not exist + $props['code'] = $props['code'] ?? F::name($file); + + $languages[] = new Language($props); + } + } + + return new static($languages); + } +} diff --git a/kirby/src/Cms/Layout.php b/kirby/src/Cms/Layout.php new file mode 100644 index 0000000..d65caee --- /dev/null +++ b/kirby/src/Cms/Layout.php @@ -0,0 +1,120 @@ + + * @link https://getkirby.com + * @copyright Bastian Allgeier GmbH + * @license https://getkirby.com/license + */ +class Layout extends Item +{ + const ITEMS_CLASS = '\Kirby\Cms\Layouts'; + + /** + * @var \Kirby\Cms\Content + */ + protected $attrs; + + /** + * @var \Kirby\Cms\LayoutColumns + */ + protected $columns; + + /** + * Proxy for attrs + * + * @param string $method + * @param array $args + * @return \Kirby\Cms\Field + */ + public function __call(string $method, array $args = []) + { + return $this->attrs()->get($method); + } + + /** + * Creates a new Layout object + * + * @param array $params + */ + public function __construct(array $params = []) + { + parent::__construct($params); + + $this->columns = LayoutColumns::factory($params['columns'] ?? [], [ + 'parent' => $this->parent + ]); + + // create the attrs object + $this->attrs = new Content($params['attrs'] ?? [], $this->parent); + } + + /** + * Returns the attrs object + * + * @return \Kirby\Cms\Content + */ + public function attrs() + { + return $this->attrs; + } + + /** + * Returns the columns in this layout + * + * @return \Kirby\Cms\LayoutColumns + */ + public function columns() + { + return $this->columns; + } + + /** + * Checks if the layout is empty + * @since 3.5.2 + * + * @return bool + */ + public function isEmpty(): bool + { + return $this + ->columns() + ->filter(function ($column) { + return $column->isNotEmpty(); + }) + ->count() === 0; + } + + /** + * Checks if the layout is not empty + * @since 3.5.2 + * + * @return bool + */ + public function isNotEmpty(): bool + { + return $this->isEmpty() === false; + } + + /** + * The result is being sent to the editor + * via the API in the panel + * + * @return array + */ + public function toArray(): array + { + return [ + 'attrs' => $this->attrs()->toArray(), + 'columns' => $this->columns()->toArray(), + 'id' => $this->id(), + ]; + } +} diff --git a/kirby/src/Cms/LayoutColumn.php b/kirby/src/Cms/LayoutColumn.php new file mode 100644 index 0000000..a5aa2cd --- /dev/null +++ b/kirby/src/Cms/LayoutColumn.php @@ -0,0 +1,122 @@ + + * @link https://getkirby.com + * @copyright Bastian Allgeier GmbH + * @license https://getkirby.com/license + */ +class LayoutColumn extends Item +{ + const ITEMS_CLASS = '\Kirby\Cms\LayoutColumns'; + + /** + * @var \Kirby\Cms\Blocks + */ + protected $blocks; + + /** + * @var string + */ + protected $width; + + /** + * Creates a new LayoutColumn object + * + * @param array $params + */ + public function __construct(array $params = []) + { + parent::__construct($params); + + $this->blocks = Blocks::factory($params['blocks'] ?? [], [ + 'parent' => $this->parent + ]); + + $this->width = $params['width'] ?? '1/1'; + } + + /** + * Returns the blocks collection + * + * @return \Kirby\Cms\Blocks + */ + public function blocks() + { + return $this->blocks; + } + + /** + * Checks if the column is empty + * @since 3.5.2 + * + * @return bool + */ + public function isEmpty(): bool + { + return $this + ->blocks() + ->filter('isHidden', false) + ->count() === 0; + } + + /** + * Checks if the column is not empty + * @since 3.5.2 + * + * @return bool + */ + public function isNotEmpty(): bool + { + return $this->isEmpty() === false; + } + + /** + * Returns the number of columns this column spans + * + * @param int $columns + * @return int + */ + public function span(int $columns = 12): int + { + $fraction = Str::split($this->width, '/'); + $a = $fraction[0] ?? 1; + $b = $fraction[1] ?? 1; + + return $columns * $a / $b; + } + + /** + * The result is being sent to the editor + * via the API in the panel + * + * @return array + */ + public function toArray(): array + { + return [ + 'blocks' => $this->blocks()->toArray(), + 'id' => $this->id(), + 'width' => $this->width(), + ]; + } + + /** + * Returns the width of the column + * + * @return string + */ + public function width(): string + { + return $this->width; + } +} diff --git a/kirby/src/Cms/LayoutColumns.php b/kirby/src/Cms/LayoutColumns.php new file mode 100644 index 0000000..efb9a7c --- /dev/null +++ b/kirby/src/Cms/LayoutColumns.php @@ -0,0 +1,18 @@ + + * @link https://getkirby.com + * @copyright Bastian Allgeier GmbH + * @license https://getkirby.com/license + */ +class LayoutColumns extends Items +{ + const ITEM_CLASS = '\Kirby\Cms\LayoutColumn'; +} diff --git a/kirby/src/Cms/Layouts.php b/kirby/src/Cms/Layouts.php new file mode 100644 index 0000000..2f6bbe8 --- /dev/null +++ b/kirby/src/Cms/Layouts.php @@ -0,0 +1,66 @@ + + * @link https://getkirby.com + * @copyright Bastian Allgeier GmbH + * @license https://getkirby.com/license + */ +class Layouts extends Items +{ + const ITEM_CLASS = '\Kirby\Cms\Layout'; + + public static function factory(array $items = null, array $params = []) + { + $first = $items[0] ?? []; + + // if there are no wrapping layouts for blocks yet … + if (array_key_exists('content', $first) === true || array_key_exists('type', $first) === true) { + $items = [ + [ + 'id' => uuid(), + 'columns' => [ + [ + 'width' => '1/1', + 'blocks' => $items + ] + ] + ] + ]; + } + + return parent::factory($items, $params); + } + + /** + * Parse layouts data + * + * @param array|string $input + * @return array + */ + public static function parse($input): array + { + if (empty($input) === false && is_array($input) === false) { + try { + $input = Data::decode($input, 'json'); + } catch (Throwable $e) { + return []; + } + } + + if (empty($input) === true) { + return []; + } + + return $input; + } +} diff --git a/kirby/src/Cms/Media.php b/kirby/src/Cms/Media.php new file mode 100644 index 0000000..b3ff75c --- /dev/null +++ b/kirby/src/Cms/Media.php @@ -0,0 +1,172 @@ + + * @link https://getkirby.com + * @copyright Bastian Allgeier GmbH + * @license https://getkirby.com/license + */ +class Media +{ + /** + * Tries to find a file by model and filename + * and to copy it to the media folder. + * + * @param \Kirby\Cms\Model|null $model + * @param string $hash + * @param string $filename + * @return \Kirby\Cms\Response|false + */ + public static function link(Model $model = null, string $hash, string $filename) + { + if ($model === null) { + return false; + } + + // fix issues with spaces in filenames + $filename = urldecode($filename); + + // try to find a file by model and filename + // this should work for all original files + if ($file = $model->file($filename)) { + + // check if the request contained an outdated media hash + if ($file->mediaHash() !== $hash) { + // if at least the token was correct, redirect + if (Str::startsWith($hash, $file->mediaToken() . '-') === true) { + return Response::redirect($file->mediaUrl(), 307); + } else { + // don't leak the correct token + return new Response('Not Found', 'text/plain', 404); + } + } + + // send the file to the browser + return Response::file($file->publish()->mediaRoot()); + } + + // try to generate a thumb for the file + return static::thumb($model, $hash, $filename); + } + + /** + * Copy the file to the final media folder location + * + * @param \Kirby\Cms\File $file + * @param string $dest + * @return bool + */ + public static function publish(File $file, string $dest): bool + { + // never publish risky files (e.g. HTML, PHP or Apache config files) + FileRules::validFile($file, false); + + $src = $file->root(); + $version = dirname($dest); + $directory = dirname($version); + + // unpublish all files except stuff in the version folder + Media::unpublish($directory, $file, $version); + + // copy/overwrite the file to the dest folder + return F::copy($src, $dest, true); + } + + /** + * Tries to find a job file for the + * given filename and then calls the thumb + * component to create a thumbnail accordingly + * + * @param \Kirby\Cms\Model|string $model + * @param string $hash + * @param string $filename + * @return \Kirby\Cms\Response|false + */ + public static function thumb($model, string $hash, string $filename) + { + $kirby = App::instance(); + + // assets + if (is_string($model) === true) { + $root = $kirby->root('media') . '/assets/' . $model . '/' . $hash; + // parent files for file model that already included hash + } elseif (is_a($model, '\Kirby\Cms\File')) { + $root = dirname($model->mediaRoot()); + // model files + } else { + $root = $model->mediaRoot() . '/' . $hash; + } + + try { + $thumb = $root . '/' . $filename; + $job = $root . '/.jobs/' . $filename . '.json'; + $options = Data::read($job); + + if (empty($options) === true) { + return false; + } + + if (is_string($model) === true) { + $source = $kirby->root('index') . '/' . $model . '/' . $options['filename']; + } else { + $source = $model->file($options['filename'])->root(); + } + + try { + $kirby->thumb($source, $thumb, $options); + F::remove($job); + return Response::file($thumb); + } catch (Throwable $e) { + F::remove($thumb); + return Response::file($source); + } + } catch (Throwable $e) { + return false; + } + } + + /** + * Deletes all versions of the given file + * within the parent directory + * + * @param string $directory + * @param \Kirby\Cms\File $file + * @param string|null $ignore + * @return bool + */ + public static function unpublish(string $directory, File $file, string $ignore = null): bool + { + if (is_dir($directory) === false) { + return true; + } + + // get both old and new versions (pre and post Kirby 3.4.0) + $versions = array_merge( + glob($directory . '/' . crc32($file->filename()) . '-*', GLOB_ONLYDIR), + glob($directory . '/' . $file->mediaToken() . '-*', GLOB_ONLYDIR) + ); + + // delete all versions of the file + foreach ($versions as $version) { + if ($version === $ignore) { + continue; + } + + Dir::remove($version); + } + + return true; + } +} diff --git a/kirby/src/Cms/Model.php b/kirby/src/Cms/Model.php new file mode 100644 index 0000000..9cbda75 --- /dev/null +++ b/kirby/src/Cms/Model.php @@ -0,0 +1,109 @@ + + * @link https://getkirby.com + * @copyright Bastian Allgeier GmbH + * @license https://getkirby.com/license + */ +abstract class Model +{ + use Properties; + + /** + * The parent Kirby instance + * + * @var \Kirby\Cms\App + */ + public static $kirby; + + /** + * The parent site instance + * + * @var \Kirby\Cms\Site + */ + protected $site; + + /** + * Makes it possible to convert the entire model + * to a string. Mostly useful for debugging + * + * @return string + */ + public function __toString(): string + { + return $this->id(); + } + + /** + * Each model must return a unique id + * + * @return string|int + */ + public function id() + { + return null; + } + + /** + * Returns the parent Kirby instance + * + * @return \Kirby\Cms\App + */ + public function kirby() + { + return static::$kirby = static::$kirby ?? App::instance(); + } + + /** + * Returns the parent Site instance + * + * @return \Kirby\Cms\Site + */ + public function site() + { + return $this->site = $this->site ?? $this->kirby()->site(); + } + + /** + * Setter for the parent Kirby object + * + * @param \Kirby\Cms\App|null $kirby + * @return $this + */ + protected function setKirby(App $kirby = null) + { + static::$kirby = $kirby; + return $this; + } + + /** + * Setter for the parent site object + * + * @internal + * @param \Kirby\Cms\Site|null $site + * @return $this + */ + public function setSite(Site $site = null) + { + $this->site = $site; + return $this; + } + + /** + * Convert the model to a simple array + * + * @return array + */ + public function toArray(): array + { + return $this->propertiesToArray(); + } +} diff --git a/kirby/src/Cms/ModelPermissions.php b/kirby/src/Cms/ModelPermissions.php new file mode 100644 index 0000000..faba61e --- /dev/null +++ b/kirby/src/Cms/ModelPermissions.php @@ -0,0 +1,116 @@ + + * @link https://getkirby.com + * @copyright Bastian Allgeier GmbH + * @license https://getkirby.com/license + */ +abstract class ModelPermissions +{ + protected $category; + protected $model; + protected $options; + protected $permissions; + protected $user; + + /** + * @param string $method + * @param array $arguments + * @return bool + */ + public function __call(string $method, array $arguments = []): bool + { + return $this->can($method); + } + + /** + * ModelPermissions constructor + * + * @param \Kirby\Cms\Model $model + */ + public function __construct(Model $model) + { + $this->model = $model; + $this->options = $model->blueprint()->options(); + $this->user = $model->kirby()->user() ?? User::nobody(); + $this->permissions = $this->user->role()->permissions(); + } + + /** + * Improved `var_dump` output + * + * @return array + */ + public function __debugInfo(): array + { + return $this->toArray(); + } + + /** + * @param string $action + * @return bool + */ + public function can(string $action): bool + { + $role = $this->user->role()->id(); + + if ($role === 'nobody') { + return false; + } + + // check for a custom overall can method + if (method_exists($this, 'can' . $action) === true && $this->{'can' . $action}() === false) { + return false; + } + + // evaluate the blueprint options block + if (isset($this->options[$action]) === true) { + $options = $this->options[$action]; + + if ($options === false) { + return false; + } + + if ($options === true) { + return true; + } + + if (is_array($options) === true && A::isAssociative($options) === true) { + return $options[$role] ?? $options['*'] ?? false; + } + } + + return $this->permissions->for($this->category, $action); + } + + /** + * @param string $action + * @return bool + */ + public function cannot(string $action): bool + { + return $this->can($action) === false; + } + + /** + * @return array + */ + public function toArray(): array + { + $array = []; + + foreach ($this->options as $key => $value) { + $array[$key] = $this->can($key); + } + + return $array; + } +} diff --git a/kirby/src/Cms/ModelWithContent.php b/kirby/src/Cms/ModelWithContent.php new file mode 100644 index 0000000..4f3a8fd --- /dev/null +++ b/kirby/src/Cms/ModelWithContent.php @@ -0,0 +1,804 @@ + + * @link https://getkirby.com + * @copyright Bastian Allgeier GmbH + * @license https://getkirby.com/license + */ +abstract class ModelWithContent extends Model +{ + /** + * Each model must define a CLASS_ALIAS + * which will be used in template queries. + * The CLASS_ALIAS is a short human-readable + * version of the class name. I.e. page. + */ + const CLASS_ALIAS = null; + + /** + * The content + * + * @var \Kirby\Cms\Content + */ + public $content; + + /** + * @var \Kirby\Cms\Translations + */ + public $translations; + + /** + * Returns the blueprint of the model + * + * @return \Kirby\Cms\Blueprint + */ + abstract public function blueprint(); + + /** + * Returns an array with all blueprints that are available + * + * @param string|null $inSection + * @return array + */ + public function blueprints(string $inSection = null): array + { + $blueprints = []; + $blueprint = $this->blueprint(); + $sections = $inSection !== null ? [$blueprint->section($inSection)] : $blueprint->sections(); + + foreach ($sections as $section) { + if ($section === null) { + continue; + } + + foreach ((array)$section->blueprints() as $blueprint) { + $blueprints[$blueprint['name']] = $blueprint; + } + } + + return array_values($blueprints); + } + + /** + * Executes any given model action + * + * @param string $action + * @param array $arguments + * @param \Closure $callback + * @return mixed + */ + abstract protected function commit(string $action, array $arguments, Closure $callback); + + /** + * Returns the content + * + * @param string|null $languageCode + * @return \Kirby\Cms\Content + * @throws \Kirby\Exception\InvalidArgumentException If the language for the given code does not exist + */ + public function content(string $languageCode = null) + { + + // single language support + if ($this->kirby()->multilang() === false) { + if (is_a($this->content, 'Kirby\Cms\Content') === true) { + return $this->content; + } + + return $this->setContent($this->readContent())->content; + + // multi language support + } else { + + // only fetch from cache for the default language + if ($languageCode === null && is_a($this->content, 'Kirby\Cms\Content') === true) { + return $this->content; + } + + // get the translation by code + if ($translation = $this->translation($languageCode)) { + $content = new Content($translation->content(), $this); + } else { + throw new InvalidArgumentException('Invalid language: ' . $languageCode); + } + + // only store the content for the current language + if ($languageCode === null) { + $this->content = $content; + } + + return $content; + } + } + + /** + * Returns the absolute path to the content file + * + * @internal + * @param string|null $languageCode + * @param bool $force + * @return string + * @throws \Kirby\Exception\InvalidArgumentException If the language for the given code does not exist + */ + public function contentFile(string $languageCode = null, bool $force = false): string + { + $extension = $this->contentFileExtension(); + $directory = $this->contentFileDirectory(); + $filename = $this->contentFileName(); + + // overwrite the language code + if ($force === true) { + if (empty($languageCode) === false) { + return $directory . '/' . $filename . '.' . $languageCode . '.' . $extension; + } else { + return $directory . '/' . $filename . '.' . $extension; + } + } + + // add and validate the language code in multi language mode + if ($this->kirby()->multilang() === true) { + if ($language = $this->kirby()->languageCode($languageCode)) { + return $directory . '/' . $filename . '.' . $language . '.' . $extension; + } else { + throw new InvalidArgumentException('Invalid language: ' . $languageCode); + } + } else { + return $directory . '/' . $filename . '.' . $extension; + } + } + + /** + * Returns an array with all content files + * + * @return array + */ + public function contentFiles(): array + { + if ($this->kirby()->multilang() === true) { + $files = []; + foreach ($this->kirby()->languages()->codes() as $code) { + $files[] = $this->contentFile($code); + } + return $files; + } else { + return [ + $this->contentFile() + ]; + } + } + + /** + * Prepares the content that should be written + * to the text file + * + * @internal + * @param array $data + * @param string|null $languageCode + * @return array + */ + public function contentFileData(array $data, string $languageCode = null): array + { + return $data; + } + + /** + * Returns the absolute path to the + * folder in which the content file is + * located + * + * @internal + * @return string|null + */ + public function contentFileDirectory(): ?string + { + return $this->root(); + } + + /** + * Returns the extension of the content file + * + * @internal + * @return string + */ + public function contentFileExtension(): string + { + return $this->kirby()->contentExtension(); + } + + /** + * Needs to be declared by the final model + * + * @internal + * @return string + */ + abstract public function contentFileName(): string; + + /** + * Decrement a given field value + * + * @param string $field + * @param int $by + * @param int $min + * @return static + */ + public function decrement(string $field, int $by = 1, int $min = 0) + { + $value = (int)$this->content()->get($field)->value() - $by; + + if ($value < $min) { + $value = $min; + } + + return $this->update([$field => $value]); + } + + /** + * Returns the drag text from a custom callback + * if the callback is defined in the config + * + * @internal + * @param string $type markdown or kirbytext + * @param mixed ...$args + * @return string|null + */ + public function dragTextFromCallback(string $type, ...$args): ?string + { + $dragTextCallback = option('panel.' . $type . '.' . static::CLASS_ALIAS . 'DragText'); + + if (empty($dragTextCallback) === false && is_a($dragTextCallback, 'Closure') === true && ($dragText = $dragTextCallback($this, ...$args)) !== null) { + return $dragText; + } + + return null; + } + + /** + * Returns the correct drag text type + * depending on the given type or the + * configuration + * + * @internal + * @param string $type (null|auto|kirbytext|markdown) + * @return string + */ + public function dragTextType(string $type = null): string + { + $type = $type ?? 'auto'; + + if ($type === 'auto') { + $type = option('panel.kirbytext', true) ? 'kirbytext' : 'markdown'; + } + + return $type === 'markdown' ? 'markdown' : 'kirbytext'; + } + + /** + * Returns all content validation errors + * + * @return array + */ + public function errors(): array + { + $errors = []; + + foreach ($this->blueprint()->sections() as $section) { + $errors = array_merge($errors, $section->errors()); + } + + return $errors; + } + + /** + * Increment a given field value + * + * @param string $field + * @param int $by + * @param int|null $max + * @return static + */ + public function increment(string $field, int $by = 1, int $max = null) + { + $value = (int)$this->content()->get($field)->value() + $by; + + if ($max && $value > $max) { + $value = $max; + } + + return $this->update([$field => $value]); + } + + /** + * Checks if the model is locked for the current user + * + * @return bool + */ + public function isLocked(): bool + { + $lock = $this->lock(); + return $lock && $lock->isLocked() === true; + } + + /** + * Checks if the data has any errors + * + * @return bool + */ + public function isValid(): bool + { + return Form::for($this)->hasErrors() === false; + } + + /** + * Returns the lock object for this model + * + * Only if a content directory exists, + * virtual pages will need to overwrite this method + * + * @return \Kirby\Cms\ContentLock|null + */ + public function lock() + { + $dir = $this->contentFileDirectory(); + + if ( + $this->kirby()->option('content.locking', true) && + is_string($dir) === true && + file_exists($dir) === true + ) { + return new ContentLock($this); + } + } + + /** + * Returns the panel icon definition + * + * @internal + * @param array|null $params + * @return array + */ + public function panelIcon(array $params = null): array + { + $defaults = [ + 'type' => 'page', + 'ratio' => null, + 'back' => 'pattern', + 'color' => '#c5c9c6', + ]; + + return array_merge($defaults, $params ?? []); + } + + /** + * @internal + * @param string|array|false|null $settings + * @return array|null + */ + public function panelImage($settings = null): ?array + { + $defaults = [ + 'ratio' => '3/2', + 'back' => 'pattern', + 'cover' => false + ]; + + // switch the image off + if ($settings === false) { + return null; + } + + if (is_string($settings) === true) { + // use defined icon in blueprint + if ($settings === 'icon') { + return []; + } + + $settings = [ + 'query' => $settings + ]; + } + + if ($image = $this->panelImageSource($settings['query'] ?? null)) { + + // main url + $settings['url'] = $image->url(); + + // only create srcsets for actual File objects + if (is_a($image, 'Kirby\Cms\File') === true) { + + // for cards + $settings['cards'] = [ + 'url' => 'data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw', + 'srcset' => $image->srcset([ + 352, + 864, + 1408, + ]) + ]; + + // for lists + $settings['list'] = [ + 'url' => 'data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw', + 'srcset' => $image->srcset([ + '1x' => [ + 'width' => 38, + 'height' => 38, + 'crop' => 'center' + ], + '2x' => [ + 'width' => 76, + 'height' => 76, + 'crop' => 'center' + ], + ]) + ]; + } + + unset($settings['query']); + } + + return array_merge($defaults, (array)$settings); + } + + /** + * Returns the image file object based on provided query + * + * @internal + * @param string|null $query + * @return \Kirby\Cms\File|\Kirby\Cms\Asset|null + */ + protected function panelImageSource(string $query = null) + { + $image = $this->query($query ?? null); + + // validate the query result + if (is_a($image, 'Kirby\Cms\File') === false && is_a($image, 'Kirby\Cms\Asset') === false) { + $image = null; + } + + // fallback for files + if ($image === null && is_a($this, 'Kirby\Cms\File') === true && $this->isViewable() === true) { + $image = $this; + } + + return $image; + } + + /** + * Returns an array of all actions + * that can be performed in the Panel + * This also checks for the lock status + * @since 3.3.0 + * + * @param array $unlock An array of options that will be force-unlocked + * @return array + */ + public function panelOptions(array $unlock = []): array + { + $options = $this->permissions()->toArray(); + + if ($this->isLocked()) { + foreach ($options as $key => $value) { + if (in_array($key, $unlock)) { + continue; + } + + $options[$key] = false; + } + } + + return $options; + } + + /** + * Must return the permissions object for the model + * + * @return \Kirby\Cms\ModelPermissions + */ + abstract public function permissions(); + + /** + * Creates a string query, starting from the model + * + * @internal + * @param string|null $query + * @param string|null $expect + * @return mixed + */ + public function query(string $query = null, string $expect = null) + { + if ($query === null) { + return null; + } + + try { + $result = Str::query($query, [ + 'kirby' => $this->kirby(), + 'site' => is_a($this, 'Kirby\Cms\Site') ? $this : $this->site(), + static::CLASS_ALIAS => $this + ]); + } catch (Throwable $e) { + return null; + } + + if ($expect !== null && is_a($result, $expect) !== true) { + return null; + } + + return $result; + } + + /** + * Read the content from the content file + * + * @internal + * @param string|null $languageCode + * @return array + */ + public function readContent(string $languageCode = null): array + { + try { + return Data::read($this->contentFile($languageCode)); + } catch (Throwable $e) { + return []; + } + } + + /** + * Returns the absolute path to the model + * + * @return string|null + */ + abstract public function root(): ?string; + + /** + * Stores the content on disk + * + * @internal + * @param array|null $data + * @param string|null $languageCode + * @param bool $overwrite + * @return static + */ + public function save(array $data = null, string $languageCode = null, bool $overwrite = false) + { + if ($this->kirby()->multilang() === true) { + return $this->saveTranslation($data, $languageCode, $overwrite); + } else { + return $this->saveContent($data, $overwrite); + } + } + + /** + * Save the single language content + * + * @param array|null $data + * @param bool $overwrite + * @return static + */ + protected function saveContent(array $data = null, bool $overwrite = false) + { + // create a clone to avoid modifying the original + $clone = $this->clone(); + + // merge the new data with the existing content + $clone->content()->update($data, $overwrite); + + // send the full content array to the writer + $clone->writeContent($clone->content()->toArray()); + + return $clone; + } + + /** + * Save a translation + * + * @param array|null $data + * @param string|null $languageCode + * @param bool $overwrite + * @return static + * @throws \Kirby\Exception\InvalidArgumentException If the language for the given code does not exist + */ + protected function saveTranslation(array $data = null, string $languageCode = null, bool $overwrite = false) + { + // create a clone to not touch the original + $clone = $this->clone(); + + // fetch the matching translation and update all the strings + $translation = $clone->translation($languageCode); + + if ($translation === null) { + throw new InvalidArgumentException('Invalid language: ' . $languageCode); + } + + // get the content to store + $content = $translation->update($data, $overwrite)->content(); + $kirby = $this->kirby(); + $languageCode = $kirby->languageCode($languageCode); + + // remove all untranslatable fields + if ($languageCode !== $kirby->defaultLanguage()->code()) { + foreach ($this->blueprint()->fields() as $field) { + if (($field['translate'] ?? true) === false) { + $content[$field['name']] = null; + } + } + + // merge the translation with the new data + $translation->update($content, true); + } + + // send the full translation array to the writer + $clone->writeContent($translation->content(), $languageCode); + + // reset the content object + $clone->content = null; + + // return the updated model + return $clone; + } + + /** + * Sets the Content object + * + * @param array|null $content + * @return $this + */ + protected function setContent(array $content = null) + { + if ($content !== null) { + $content = new Content($content, $this); + } + + $this->content = $content; + return $this; + } + + /** + * Create the translations collection from an array + * + * @param array|null $translations + * @return $this + */ + protected function setTranslations(array $translations = null) + { + if ($translations !== null) { + $this->translations = new Collection(); + + foreach ($translations as $props) { + $props['parent'] = $this; + $translation = new ContentTranslation($props); + $this->translations->data[$translation->code()] = $translation; + } + } + + return $this; + } + + /** + * String template builder + * + * @param string|null $template + * @param array $data + * @param string $fallback Fallback for tokens in the template that cannot be replaced + * @return string + */ + public function toString(string $template = null, array $data = [], string $fallback = ''): string + { + if ($template === null) { + return $this->id(); + } + + $result = Str::template($template, array_replace([ + 'kirby' => $this->kirby(), + 'site' => is_a($this, 'Kirby\Cms\Site') ? $this : $this->site(), + static::CLASS_ALIAS => $this + ], $data), $fallback); + + return $result; + } + + /** + * Returns a single translation by language code + * If no code is specified the current translation is returned + * + * @param string|null $languageCode + * @return \Kirby\Cms\ContentTranslation|null + */ + public function translation(string $languageCode = null) + { + return $this->translations()->find($languageCode ?? $this->kirby()->language()->code()); + } + + /** + * Returns the translations collection + * + * @return \Kirby\Cms\Collection + */ + public function translations() + { + if ($this->translations !== null) { + return $this->translations; + } + + $this->translations = new Collection(); + + foreach ($this->kirby()->languages() as $language) { + $translation = new ContentTranslation([ + 'parent' => $this, + 'code' => $language->code(), + ]); + + $this->translations->data[$translation->code()] = $translation; + } + + return $this->translations; + } + + /** + * Updates the model data + * + * @param array|null $input + * @param string|null $languageCode + * @param bool $validate + * @return static + * @throws \Kirby\Exception\InvalidArgumentException If the input array contains invalid values + */ + public function update(array $input = null, string $languageCode = null, bool $validate = false) + { + $form = Form::for($this, [ + 'ignoreDisabled' => $validate === false, + 'input' => $input, + 'language' => $languageCode, + ]); + + // validate the input + if ($validate === true) { + if ($form->isInvalid() === true) { + throw new InvalidArgumentException([ + 'fallback' => 'Invalid form with errors', + 'details' => $form->errors() + ]); + } + } + + $arguments = [static::CLASS_ALIAS => $this, 'values' => $form->data(), 'strings' => $form->strings(), 'languageCode' => $languageCode]; + return $this->commit('update', $arguments, function ($model, $values, $strings, $languageCode) { + // save updated values + $model = $model->save($strings, $languageCode, true); + + // update model in siblings collection + $model->siblings()->add($model); + + return $model; + }); + } + + /** + * Low level data writer method + * to store the given data on disk or anywhere else + * + * @internal + * @param array $data + * @param string|null $languageCode + * @return bool + */ + public function writeContent(array $data, string $languageCode = null): bool + { + return Data::write( + $this->contentFile($languageCode), + $this->contentFileData($data, $languageCode) + ); + } +} diff --git a/kirby/src/Cms/Nest.php b/kirby/src/Cms/Nest.php new file mode 100644 index 0000000..0fa2318 --- /dev/null +++ b/kirby/src/Cms/Nest.php @@ -0,0 +1,48 @@ + + * @link https://getkirby.com + * @copyright Bastian Allgeier GmbH + * @license https://getkirby.com/license + */ +class Nest +{ + /** + * @param $data + * @param null $parent + * @return mixed + */ + public static function create($data, $parent = null) + { + if (is_scalar($data) === true) { + return new Field($parent, $data, $data); + } + + $result = []; + + foreach ($data as $key => $value) { + if (is_array($value) === true) { + $result[$key] = static::create($value, $parent); + } elseif (is_scalar($value) === true) { + $result[$key] = new Field($parent, $key, $value); + } + } + + if (is_int(key($data))) { + return new NestCollection($result); + } else { + return new NestObject($result); + } + } +} diff --git a/kirby/src/Cms/NestCollection.php b/kirby/src/Cms/NestCollection.php new file mode 100644 index 0000000..4dbe31b --- /dev/null +++ b/kirby/src/Cms/NestCollection.php @@ -0,0 +1,33 @@ + + * @link https://getkirby.com + * @copyright Bastian Allgeier GmbH + * @license https://getkirby.com/license + */ +class NestCollection extends BaseCollection +{ + /** + * Converts all objects in the collection + * to an array. This can also take a callback + * function to further modify the array result. + * + * @param \Closure|null $map + * @return array + */ + public function toArray(Closure $map = null): array + { + return parent::toArray($map ?? function ($object) { + return $object->toArray(); + }); + } +} diff --git a/kirby/src/Cms/NestObject.php b/kirby/src/Cms/NestObject.php new file mode 100644 index 0000000..f225376 --- /dev/null +++ b/kirby/src/Cms/NestObject.php @@ -0,0 +1,43 @@ + + * @link https://getkirby.com + * @copyright Bastian Allgeier GmbH + * @license https://getkirby.com/license + */ +class NestObject extends Obj +{ + /** + * Converts the object to an array + * + * @return array + */ + public function toArray(): array + { + $result = []; + + foreach ((array)$this as $key => $value) { + if (is_a($value, 'Kirby\Cms\Field') === true) { + $result[$key] = $value->value(); + continue; + } + + if (is_object($value) === true && method_exists($value, 'toArray')) { + $result[$key] = $value->toArray(); + continue; + } + + $result[$key] = $value; + } + + return $result; + } +} diff --git a/kirby/src/Cms/Page.php b/kirby/src/Cms/Page.php new file mode 100644 index 0000000..a817753 --- /dev/null +++ b/kirby/src/Cms/Page.php @@ -0,0 +1,1579 @@ + + * @link https://getkirby.com + * @copyright Bastian Allgeier GmbH + * @license https://getkirby.com/license + */ +class Page extends ModelWithContent +{ + const CLASS_ALIAS = 'page'; + + use PageActions; + use PageSiblings; + use HasChildren; + use HasFiles; + use HasMethods; + use HasSiblings; + + /** + * All registered page methods + * + * @var array + */ + public static $methods = []; + + /** + * Registry with all Page models + * + * @var array + */ + public static $models = []; + + /** + * The PageBlueprint object + * + * @var \Kirby\Cms\PageBlueprint + */ + protected $blueprint; + + /** + * Nesting level + * + * @var int + */ + protected $depth; + + /** + * Sorting number + slug + * + * @var string + */ + protected $dirname; + + /** + * Path of dirnames + * + * @var string + */ + protected $diruri; + + /** + * Draft status flag + * + * @var bool + */ + protected $isDraft; + + /** + * The Page id + * + * @var string + */ + protected $id; + + /** + * The template, that should be loaded + * if it exists + * + * @var \Kirby\Cms\Template + */ + protected $intendedTemplate; + + /** + * @var array + */ + protected $inventory; + + /** + * The sorting number + * + * @var int|null + */ + protected $num; + + /** + * The parent page + * + * @var \Kirby\Cms\Page|null + */ + protected $parent; + + /** + * Absolute path to the page directory + * + * @var string + */ + protected $root; + + /** + * The parent Site object + * + * @var \Kirby\Cms\Site|null + */ + protected $site; + + /** + * The URL-appendix aka slug + * + * @var string + */ + protected $slug; + + /** + * The intended page template + * + * @var \Kirby\Cms\Template + */ + protected $template; + + /** + * The page url + * + * @var string|null + */ + protected $url; + + /** + * Magic caller + * + * @param string $method + * @param array $arguments + * @return mixed + */ + public function __call(string $method, array $arguments = []) + { + // public property access + if (isset($this->$method) === true) { + return $this->$method; + } + + // page methods + if ($this->hasMethod($method)) { + return $this->callMethod($method, $arguments); + } + + // return page content otherwise + return $this->content()->get($method, $arguments); + } + + /** + * Creates a new page object + * + * @param array $props + */ + public function __construct(array $props) + { + // set the slug as the first property + $this->slug = $props['slug'] ?? null; + + // add all other properties + $this->setProperties($props); + } + + /** + * Improved `var_dump` output + * + * @return array + */ + public function __debugInfo(): array + { + return array_merge($this->toArray(), [ + 'content' => $this->content(), + 'children' => $this->children(), + 'siblings' => $this->siblings(), + 'translations' => $this->translations(), + 'files' => $this->files(), + ]); + } + + /** + * Returns the url to the api endpoint + * + * @internal + * @param bool $relative + * @return string + */ + public function apiUrl(bool $relative = false): string + { + if ($relative === true) { + return 'pages/' . $this->panelId(); + } else { + return $this->kirby()->url('api') . '/pages/' . $this->panelId(); + } + } + + /** + * Returns the blueprint object + * + * @return \Kirby\Cms\PageBlueprint + */ + public function blueprint() + { + if (is_a($this->blueprint, 'Kirby\Cms\PageBlueprint') === true) { + return $this->blueprint; + } + + return $this->blueprint = PageBlueprint::factory('pages/' . $this->intendedTemplate(), 'pages/default', $this); + } + + /** + * Returns an array with all blueprints that are available for the page + * + * @param string|null $inSection + * @return array + */ + public function blueprints(?string $inSection = null): array + { + if ($inSection !== null) { + return $this->blueprint()->section($inSection)->blueprints(); + } + + $blueprints = []; + $templates = $this->blueprint()->changeTemplate() ?? $this->blueprint()->options()['changeTemplate'] ?? []; + $currentTemplate = $this->intendedTemplate()->name(); + + if (is_array($templates) === false) { + $templates = []; + } + + // add the current template to the array if it's not already there + if (in_array($currentTemplate, $templates) === false) { + array_unshift($templates, $currentTemplate); + } + + // make sure every template is only included once + $templates = array_unique($templates); + + foreach ($templates as $template) { + try { + $props = Blueprint::load('pages/' . $template); + + $blueprints[] = [ + 'name' => basename($props['name']), + 'title' => $props['title'], + ]; + } catch (Exception $e) { + // skip invalid blueprints + } + } + + return array_values($blueprints); + } + + /** + * Builds the cache id for the page + * + * @param string $contentType + * @return string + */ + protected function cacheId(string $contentType): string + { + $cacheId = [$this->id()]; + + if ($this->kirby()->multilang() === true) { + $cacheId[] = $this->kirby()->language()->code(); + } + + $cacheId[] = $contentType; + + return implode('.', $cacheId); + } + + /** + * Prepares the content for the write method + * + * @internal + * @param array $data + * @param string|null $languageCode + * @return array + */ + public function contentFileData(array $data, ?string $languageCode = null): array + { + return A::prepend($data, [ + 'title' => $data['title'] ?? null, + 'slug' => $data['slug'] ?? null + ]); + } + + /** + * Returns the content text file + * which is found by the inventory method + * + * @internal + * @param string|null $languageCode + * @return string + */ + public function contentFileName(?string $languageCode = null): string + { + return $this->intendedTemplate()->name(); + } + + /** + * Call the page controller + * + * @internal + * @param array $data + * @param string $contentType + * @return array + * @throws \Kirby\Exception\InvalidArgumentException If the controller returns invalid objects for `kirby`, `site`, `pages` or `page` + */ + public function controller($data = [], $contentType = 'html'): array + { + // create the template data + $data = array_merge($data, [ + 'kirby' => $kirby = $this->kirby(), + 'site' => $site = $this->site(), + 'pages' => $site->children(), + 'page' => $site->visit($this) + ]); + + // call the template controller if there's one. + $controllerData = $kirby->controller($this->template()->name(), $data, $contentType); + + // merge controller data with original data safely + if (empty($controllerData) === false) { + $classes = [ + 'kirby' => 'Kirby\Cms\App', + 'site' => 'Kirby\Cms\Site', + 'pages' => 'Kirby\Cms\Pages', + 'page' => 'Kirby\Cms\Page' + ]; + + foreach ($controllerData as $key => $value) { + if (array_key_exists($key, $classes) === true) { + if (is_a($value, $classes[$key]) === true) { + $data[$key] = $value; + } else { + throw new InvalidArgumentException('The returned variable "' . $key . '" from the controller "' . $this->template()->name() . '" is not of the required type "' . $classes[$key] . '"'); + } + } else { + $data[$key] = $value; + } + } + } + + return $data; + } + + /** + * Returns a number indicating how deep the page + * is nested within the content folder + * + * @return int + */ + public function depth(): int + { + return $this->depth = $this->depth ?? (substr_count($this->id(), '/') + 1); + } + + /** + * Sorting number + Slug + * + * @return string + */ + public function dirname(): string + { + if ($this->dirname !== null) { + return $this->dirname; + } + + if ($this->num() !== null) { + return $this->dirname = $this->num() . Dir::$numSeparator . $this->uid(); + } else { + return $this->dirname = $this->uid(); + } + } + + /** + * Sorting number + Slug + * + * @return string + */ + public function diruri(): string + { + if (is_string($this->diruri) === true) { + return $this->diruri; + } + + if ($this->isDraft() === true) { + $dirname = '_drafts/' . $this->dirname(); + } else { + $dirname = $this->dirname(); + } + + if ($parent = $this->parent()) { + return $this->diruri = $parent->diruri() . '/' . $dirname; + } else { + return $this->diruri = $dirname; + } + } + + /** + * Provides a kirbytag or markdown + * tag for the page, which will be + * used in the panel, when the page + * gets dragged onto a textarea + * + * @internal + * @param string|null $type (null|auto|kirbytext|markdown) + * @return string + */ + public function dragText(string $type = null): string + { + $type = $this->dragTextType($type); + + if ($dragTextFromCallback = $this->dragTextFromCallback($type)) { + return $dragTextFromCallback; + } + + if ($type === 'markdown') { + return '[' . $this->title() . '](' . $this->url() . ')'; + } else { + return '(link: ' . $this->id() . ' text: ' . $this->title() . ')'; + } + } + + /** + * Checks if the page exists on disk + * + * @return bool + */ + public function exists(): bool + { + return is_dir($this->root()) === true; + } + + /** + * Constructs a Page object and also + * takes page models into account. + * + * @internal + * @param mixed $props + * @return static + */ + public static function factory($props) + { + if (empty($props['model']) === false) { + return static::model($props['model'], $props); + } + + return new static($props); + } + + /** + * Redirects to this page, + * wrapper for the `go()` helper + * + * @since 3.4.0 + * + * @param array $options Options for `Kirby\Http\Uri` to create URL parts + * @param int $code HTTP status code + */ + public function go(array $options = [], int $code = 302) + { + go($this->url($options), $code); + } + + /** + * Checks if the intended template + * for the page exists. + * + * @return bool + */ + public function hasTemplate(): bool + { + return $this->intendedTemplate() === $this->template(); + } + + /** + * Returns the Page Id + * + * @return string + */ + public function id(): string + { + if ($this->id !== null) { + return $this->id; + } + + // set the id, depending on the parent + if ($parent = $this->parent()) { + return $this->id = $parent->id() . '/' . $this->uid(); + } + + return $this->id = $this->uid(); + } + + /** + * Returns the template that should be + * loaded if it exists. + * + * @return \Kirby\Cms\Template + */ + public function intendedTemplate() + { + if ($this->intendedTemplate !== null) { + return $this->intendedTemplate; + } + + return $this->setTemplate($this->inventory()['template'])->intendedTemplate(); + } + + /** + * Returns the inventory of files + * children and content files + * + * @internal + * @return array + */ + public function inventory(): array + { + if ($this->inventory !== null) { + return $this->inventory; + } + + $kirby = $this->kirby(); + + return $this->inventory = Dir::inventory( + $this->root(), + $kirby->contentExtension(), + $kirby->contentIgnore(), + $kirby->multilang() + ); + } + + /** + * Compares the current object with the given page object + * + * @param \Kirby\Cms\Page|string $page + * @return bool + */ + public function is($page): bool + { + if (is_a($page, 'Kirby\Cms\Page') === false) { + if (is_string($page) === false) { + return false; + } + + $page = $this->kirby()->page($page); + } + + if (is_a($page, 'Kirby\Cms\Page') === false) { + return false; + } + + return $this->id() === $page->id(); + } + + /** + * Checks if the page is the current page + * + * @return bool + */ + public function isActive(): bool + { + if ($page = $this->site()->page()) { + if ($page->is($this) === true) { + return true; + } + } + + return false; + } + + /** + * Checks if the page is a direct or indirect ancestor of the given $page object + * + * @param Page $child + * @return bool + */ + public function isAncestorOf(Page $child): bool + { + return $child->parents()->has($this->id()) === true; + } + + /** + * Checks if the page can be cached in the + * pages cache. This will also check if one + * of the ignore rules from the config kick in. + * + * @return bool + */ + public function isCacheable(): bool + { + $kirby = $this->kirby(); + $cache = $kirby->cache('pages'); + $options = $cache->options(); + $ignore = $options['ignore'] ?? null; + + // the pages cache is switched off + if (($options['active'] ?? false) === false) { + return false; + } + + // inspect the current request + $request = $kirby->request(); + + // disable the pages cache for any request types but GET or HEAD + if (in_array($request->method(), ['GET', 'HEAD']) === false) { + return false; + } + + // disable the pages cache when there's request data + if (empty($request->data()) === false) { + return false; + } + + // disable the pages cache when there are any params + if ($request->params()->isNotEmpty()) { + return false; + } + + // check for a custom ignore rule + if (is_a($ignore, 'Closure') === true) { + if ($ignore($this) === true) { + return false; + } + } + + // ignore pages by id + if (is_array($ignore) === true) { + if (in_array($this->id(), $ignore) === true) { + return false; + } + } + + return true; + } + + /** + * Checks if the page is a child of the given page + * + * @param \Kirby\Cms\Page|string $parent + * @return bool + */ + public function isChildOf($parent): bool + { + if ($parentObj = $this->parent()) { + return $parentObj->is($parent); + } + + return false; + } + + /** + * Checks if the page is a descendant of the given page + * + * @param \Kirby\Cms\Page|string $parent + * @return bool + */ + public function isDescendantOf($parent): bool + { + if (is_string($parent) === true) { + $parent = $this->site()->find($parent); + } + + if (!$parent) { + return false; + } + + return $this->parents()->has($parent->id()) === true; + } + + /** + * Checks if the page is a descendant of the currently active page + * + * @return bool + */ + public function isDescendantOfActive(): bool + { + if ($active = $this->site()->page()) { + return $this->isDescendantOf($active); + } + + return false; + } + + /** + * Checks if the current page is a draft + * + * @return bool + */ + public function isDraft(): bool + { + return $this->isDraft; + } + + /** + * Checks if the page is the error page + * + * @return bool + */ + public function isErrorPage(): bool + { + return $this->id() === $this->site()->errorPageId(); + } + + /** + * Checks if the page is the home page + * + * @return bool + */ + public function isHomePage(): bool + { + return $this->id() === $this->site()->homePageId(); + } + + /** + * It's often required to check for the + * home and error page to stop certain + * actions. That's why there's a shortcut. + * + * @return bool + */ + public function isHomeOrErrorPage(): bool + { + return $this->isHomePage() === true || $this->isErrorPage() === true; + } + + /** + * Checks if the page has a sorting number + * + * @return bool + */ + public function isListed(): bool + { + return $this->num() !== null; + } + + /** + * Checks if the page is open. + * Open pages are either the current one + * or descendants of the current one. + * + * @return bool + */ + public function isOpen(): bool + { + if ($this->isActive() === true) { + return true; + } + + if ($page = $this->site()->page()) { + if ($page->parents()->has($this->id()) === true) { + return true; + } + } + + return false; + } + + /** + * Checks if the page is not a draft. + * + * @return bool + */ + public function isPublished(): bool + { + return $this->isDraft() === false; + } + + /** + * Check if the page can be read by the current user + * + * @return bool + */ + public function isReadable(): bool + { + static $readable = []; + + $template = $this->intendedTemplate()->name(); + + if (isset($readable[$template]) === true) { + return $readable[$template]; + } + + return $readable[$template] = $this->permissions()->can('read'); + } + + /** + * Checks if the page is sortable + * + * @return bool + */ + public function isSortable(): bool + { + return $this->permissions()->can('sort'); + } + + /** + * Checks if the page has no sorting number + * + * @return bool + */ + public function isUnlisted(): bool + { + return $this->isListed() === false; + } + + /** + * Checks if the page access is verified. + * This is only used for drafts so far. + * + * @internal + * @param string|null $token + * @return bool + */ + public function isVerified(string $token = null) + { + if ( + $this->isDraft() === false && + $this->parents()->findBy('status', 'draft') === null + ) { + return true; + } + + if ($token === null) { + return false; + } + + return $this->token() === $token; + } + + /** + * Returns the root to the media folder for the page + * + * @internal + * @return string + */ + public function mediaRoot(): string + { + return $this->kirby()->root('media') . '/pages/' . $this->id(); + } + + /** + * The page's base URL for any files + * + * @internal + * @return string + */ + public function mediaUrl(): string + { + return $this->kirby()->url('media') . '/pages/' . $this->id(); + } + + /** + * Creates a page model if it has been registered + * + * @internal + * @param string $name + * @param array $props + * @return static + */ + public static function model(string $name, array $props = []) + { + if ($class = (static::$models[$name] ?? null)) { + $object = new $class($props); + + if (is_a($object, 'Kirby\Cms\Page') === true) { + return $object; + } + } + + return new static($props); + } + + /** + * Returns the last modification date of the page + * + * @param string|null $format + * @param string|null $handler + * @param string|null $languageCode + * @return int|string + */ + public function modified(string $format = null, string $handler = null, string $languageCode = null) + { + return F::modified( + $this->contentFile($languageCode), + $format, + $handler ?? $this->kirby()->option('date.handler', 'date') + ); + } + + /** + * Returns the sorting number + * + * @return int|null + */ + public function num(): ?int + { + return $this->num; + } + + /** + * Returns the panel icon definition + * according to the blueprint settings + * + * @internal + * @param array|null $params + * @return array + */ + public function panelIcon(array $params = null): array + { + if ($icon = $this->blueprint()->icon()) { + $params['type'] = $icon; + } + + return parent::panelIcon($params); + } + + /** + * Returns the escaped Id, which is + * used in the panel to make routing work properly + * + * @internal + * @return string + */ + public function panelId(): string + { + return str_replace('/', '+', $this->id()); + } + + /** + * Returns the image file object based on provided query + * + * @internal + * @param string|null $query + * @return \Kirby\Cms\File|\Kirby\Cms\Asset|null + */ + protected function panelImageSource(string $query = null) + { + if ($query === null) { + $query = 'page.image'; + } + + return parent::panelImageSource($query); + } + + /** + * Returns the full path without leading slash + * + * @internal + * @return string + */ + public function panelPath(): string + { + return 'pages/' . $this->panelId(); + } + + /** + * Prepares the response data for page pickers + * and page fields + * + * @param array|null $params + * @return array + */ + public function panelPickerData(array $params = []): array + { + $image = $this->panelImage($params['image'] ?? []); + $icon = $this->panelIcon($image); + + // escape the default text + // TODO: no longer needed in 3.6 + $textQuery = $params['text'] ?? '{{ page.title }}'; + $text = $this->toString($textQuery); + if ($textQuery === '{{ page.title }}') { + $text = Escape::html($text); + } + + return [ + 'dragText' => $this->dragText(), + 'hasChildren' => $this->hasChildren(), + 'icon' => $icon, + 'id' => $this->id(), + 'image' => $image, + 'info' => $this->toString($params['info'] ?? false), + 'link' => $this->panelUrl(true), + 'text' => $text, + 'url' => $this->url(), + ]; + } + + /** + * Returns the url to the editing view + * in the panel + * + * @internal + * @param bool $relative + * @return string + */ + public function panelUrl(bool $relative = false): string + { + if ($relative === true) { + return '/' . $this->panelPath(); + } else { + return $this->kirby()->url('panel') . '/' . $this->panelPath(); + } + } + + /** + * Returns the parent Page object + * + * @return \Kirby\Cms\Page|null + */ + public function parent() + { + return $this->parent; + } + + /** + * Returns the parent id, if a parent exists + * + * @internal + * @return string|null + */ + public function parentId(): ?string + { + if ($parent = $this->parent()) { + return $parent->id(); + } + + return null; + } + + /** + * Returns the parent model, + * which can either be another Page + * or the Site + * + * @internal + * @return \Kirby\Cms\Page|\Kirby\Cms\Site + */ + public function parentModel() + { + return $this->parent() ?? $this->site(); + } + + /** + * Returns a list of all parents and their parents recursively + * + * @return \Kirby\Cms\Pages + */ + public function parents() + { + $parents = new Pages(); + $page = $this->parent(); + + while ($page !== null) { + $parents->append($page->id(), $page); + $page = $page->parent(); + } + + return $parents; + } + + /** + * Returns the permissions object for this page + * + * @return \Kirby\Cms\PagePermissions + */ + public function permissions() + { + return new PagePermissions($this); + } + + /** + * Draft preview Url + * + * @internal + * @return string|null + */ + public function previewUrl(): ?string + { + $preview = $this->blueprint()->preview(); + + if ($preview === false) { + return null; + } + + if ($preview === true) { + $url = $this->url(); + } else { + $url = $preview; + } + + if ($this->isDraft() === true) { + $uri = new Uri($url); + $uri->query->token = $this->token(); + + $url = $uri->toString(); + } + + return $url; + } + + /** + * Renders the page with the given data. + * + * An optional content type can be passed to + * render a content representation instead of + * the default template. + * + * @param array $data + * @param string $contentType + * @return string + * @throws \Kirby\Exception\NotFoundException If the default template cannot be found + */ + public function render(array $data = [], $contentType = 'html'): string + { + $kirby = $this->kirby(); + $cache = $cacheId = $html = null; + + // try to get the page from cache + if (empty($data) === true && $this->isCacheable() === true) { + $cache = $kirby->cache('pages'); + $cacheId = $this->cacheId($contentType); + $result = $cache->get($cacheId); + $html = $result['html'] ?? null; + $response = $result['response'] ?? []; + + // reconstruct the response configuration + if (empty($html) === false && empty($response) === false) { + $kirby->response()->fromArray($response); + } + } + + // fetch the page regularly + if ($html === null) { + if ($contentType === 'html') { + $template = $this->template(); + } else { + $template = $this->representation($contentType); + } + + if ($template->exists() === false) { + throw new NotFoundException([ + 'key' => 'template.default.notFound' + ]); + } + + $kirby->data = $this->controller($data, $contentType); + + // render the page + $html = $template->render($kirby->data); + + // convert the response configuration to an array + $response = $kirby->response()->toArray(); + + // cache the result + if ($cache !== null && $kirby->response()->cache() === true) { + $cache->set($cacheId, [ + 'html' => $html, + 'response' => $response + ], $kirby->response()->expires() ?? 0); + } + } + + return $html; + } + + /** + * @internal + * @param mixed $type + * @return \Kirby\Cms\Template + * @throws \Kirby\Exception\NotFoundException If the content representation cannot be found + */ + public function representation($type) + { + $kirby = $this->kirby(); + $template = $this->template(); + $representation = $kirby->template($template->name(), $type); + + if ($representation->exists() === true) { + return $representation; + } + + throw new NotFoundException('The content representation cannot be found'); + } + + /** + * Returns the absolute root to the page directory + * No matter if it exists or not. + * + * @return string + */ + public function root(): string + { + return $this->root = $this->root ?? $this->kirby()->root('content') . '/' . $this->diruri(); + } + + /** + * Returns the PageRules class instance + * which is being used in various methods + * to check for valid actions and input. + * + * @return \Kirby\Cms\PageRules + */ + protected function rules() + { + return new PageRules(); + } + + /** + * Search all pages within the current page + * + * @param string|null $query + * @param array $params + * @return \Kirby\Cms\Pages + */ + public function search(string $query = null, $params = []) + { + return $this->index()->search($query, $params); + } + + /** + * Sets the Blueprint object + * + * @param array|null $blueprint + * @return $this + */ + protected function setBlueprint(array $blueprint = null) + { + if ($blueprint !== null) { + $blueprint['model'] = $this; + $this->blueprint = new PageBlueprint($blueprint); + } + + return $this; + } + + /** + * Sets the dirname manually, which works + * more reliable in connection with the inventory + * than computing the dirname afterwards + * + * @param string|null $dirname + * @return $this + */ + protected function setDirname(string $dirname = null) + { + $this->dirname = $dirname; + return $this; + } + + /** + * Sets the draft flag + * + * @param bool $isDraft + * @return $this + */ + protected function setIsDraft(bool $isDraft = null) + { + $this->isDraft = $isDraft ?? false; + return $this; + } + + /** + * Sets the sorting number + * + * @param int|null $num + * @return $this + */ + protected function setNum(int $num = null) + { + $this->num = $num === null ? $num : (int)$num; + return $this; + } + + /** + * Sets the parent page object + * + * @param \Kirby\Cms\Page|null $parent + * @return $this + */ + protected function setParent(Page $parent = null) + { + $this->parent = $parent; + return $this; + } + + /** + * Sets the absolute path to the page + * + * @param string|null $root + * @return $this + */ + protected function setRoot(string $root = null) + { + $this->root = $root; + return $this; + } + + /** + * Sets the required Page slug + * + * @param string $slug + * @return $this + */ + protected function setSlug(string $slug) + { + $this->slug = $slug; + return $this; + } + + /** + * Sets the intended template + * + * @param string|null $template + * @return $this + */ + protected function setTemplate(string $template = null) + { + if ($template !== null) { + $this->intendedTemplate = $this->kirby()->template($template); + } + + return $this; + } + + /** + * Sets the Url + * + * @param string|null $url + * @return $this + */ + protected function setUrl(string $url = null) + { + if (is_string($url) === true) { + $url = rtrim($url, '/'); + } + + $this->url = $url; + return $this; + } + + /** + * Returns the slug of the page + * + * @param string|null $languageCode + * @return string + */ + public function slug(string $languageCode = null): string + { + if ($this->kirby()->multilang() === true) { + if ($languageCode === null) { + $languageCode = $this->kirby()->languageCode(); + } + + if ($translation = $this->translations()->find($languageCode)) { + return $translation->slug() ?? $this->slug; + } + } + + return $this->slug; + } + + /** + * Returns the page status, which + * can be `draft`, `listed` or `unlisted` + * + * @return string + */ + public function status(): string + { + if ($this->isDraft() === true) { + return 'draft'; + } + + if ($this->isUnlisted() === true) { + return 'unlisted'; + } + + return 'listed'; + } + + /** + * Returns the final template + * + * @return \Kirby\Cms\Template + */ + public function template() + { + if ($this->template !== null) { + return $this->template; + } + + $intended = $this->intendedTemplate(); + + if ($intended->exists() === true) { + return $this->template = $intended; + } + + return $this->template = $this->kirby()->template('default'); + } + + /** + * Returns the title field or the slug as fallback + * + * @return \Kirby\Cms\Field + */ + public function title() + { + return $this->content()->get('title')->or($this->slug()); + } + + /** + * Converts the most important + * properties to array + * + * @return array + */ + public function toArray(): array + { + return [ + 'children' => $this->children()->keys(), + 'content' => $this->content()->toArray(), + 'files' => $this->files()->keys(), + 'id' => $this->id(), + 'mediaUrl' => $this->mediaUrl(), + 'mediaRoot' => $this->mediaRoot(), + 'num' => $this->num(), + 'parent' => $this->parent() ? $this->parent()->id(): null, + 'slug' => $this->slug(), + 'template' => $this->template(), + 'translations' => $this->translations()->toArray(), + 'uid' => $this->uid(), + 'uri' => $this->uri(), + 'url' => $this->url() + ]; + } + + /** + * Returns a verification token, which + * is used for the draft authentication + * + * @return string + */ + protected function token(): string + { + return $this->kirby()->contentToken($this, $this->id() . $this->template()); + } + + /** + * Returns the UID of the page. + * The UID is basically the same as the + * slug, but stays the same on + * multi-language sites. Whereas the slug + * can be translated. + * + * @see self::slug() + * @return string + */ + public function uid(): string + { + return $this->slug; + } + + /** + * The uri is the same as the id, except + * that it will be translated in multi-language setups + * + * @param string|null $languageCode + * @return string + */ + public function uri(string $languageCode = null): string + { + // set the id, depending on the parent + if ($parent = $this->parent()) { + return $parent->uri($languageCode) . '/' . $this->slug($languageCode); + } + + return $this->slug($languageCode); + } + + /** + * Returns the Url + * + * @param array|string|null $options + * @return string + */ + public function url($options = null): string + { + if ($this->kirby()->multilang() === true) { + if (is_string($options) === true) { + return $this->urlForLanguage($options); + } else { + return $this->urlForLanguage(null, $options); + } + } + + if ($options !== null) { + return Url::to($this->url(), $options); + } + + if (is_string($this->url) === true) { + return $this->url; + } + + if ($this->isHomePage() === true) { + return $this->url = $this->site()->url(); + } + + if ($parent = $this->parent()) { + if ($parent->isHomePage() === true) { + return $this->url = $this->kirby()->url('base') . '/' . $parent->uid() . '/' . $this->uid(); + } else { + return $this->url = $this->parent()->url() . '/' . $this->uid(); + } + } + + return $this->url = $this->kirby()->url('base') . '/' . $this->uid(); + } + + /** + * Builds the Url for a specific language + * + * @internal + * @param string|null $language + * @param array|null $options + * @return string + */ + public function urlForLanguage($language = null, array $options = null): string + { + if ($options !== null) { + return Url::to($this->urlForLanguage($language), $options); + } + + if ($this->isHomePage() === true) { + return $this->url = $this->site()->urlForLanguage($language); + } + + if ($parent = $this->parent()) { + if ($parent->isHomePage() === true) { + return $this->url = $this->site()->urlForLanguage($language) . '/' . $parent->slug($language) . '/' . $this->slug($language); + } else { + return $this->url = $this->parent()->urlForLanguage($language) . '/' . $this->slug($language); + } + } + + return $this->url = $this->site()->urlForLanguage($language) . '/' . $this->slug($language); + } +} diff --git a/kirby/src/Cms/PageActions.php b/kirby/src/Cms/PageActions.php new file mode 100644 index 0000000..5a41bc0 --- /dev/null +++ b/kirby/src/Cms/PageActions.php @@ -0,0 +1,862 @@ + + * @link https://getkirby.com + * @copyright Bastian Allgeier GmbH + * @license https://getkirby.com/license + */ +trait PageActions +{ + /** + * Changes the sorting number. + * The sorting number must already be correct + * when the method is called. + * This only affects this page, + * siblings will not be resorted. + * + * @param int|null $num + * @return $this|static + * @throws \Kirby\Exception\LogicException If a draft is being sorted or the directory cannot be moved + */ + public function changeNum(int $num = null) + { + if ($this->isDraft() === true) { + throw new LogicException('Drafts cannot change their sorting number'); + } + + // don't run the action if everything stayed the same + if ($this->num() === $num) { + return $this; + } + + return $this->commit('changeNum', ['page' => $this, 'num' => $num], function ($oldPage, $num) { + $newPage = $oldPage->clone([ + 'num' => $num, + 'dirname' => null, + 'root' => null + ]); + + // actually move the page on disk + if ($oldPage->exists() === true) { + if (Dir::move($oldPage->root(), $newPage->root()) === true) { + // Updates the root path of the old page with the root path + // of the moved new page to use fly actions on old page in loop + $oldPage->setRoot($newPage->root()); + } else { + throw new LogicException('The page directory cannot be moved'); + } + } + + // overwrite the child in the parent page + $newPage + ->parentModel() + ->children() + ->set($newPage->id(), $newPage); + + return $newPage; + }); + } + + /** + * Changes the slug/uid of the page + * + * @param string $slug + * @param string|null $languageCode + * @return $this|static + * @throws \Kirby\Exception\LogicException If the directory cannot be moved + */ + public function changeSlug(string $slug, string $languageCode = null) + { + // always sanitize the slug + $slug = Str::slug($slug); + + // in multi-language installations the slug for the non-default + // languages is stored in the text file. The changeSlugForLanguage + // method takes care of that. + if ($language = $this->kirby()->language($languageCode)) { + if ($language->isDefault() === false) { + return $this->changeSlugForLanguage($slug, $languageCode); + } + } + + // if the slug stays exactly the same, + // nothing needs to be done. + if ($slug === $this->slug()) { + return $this; + } + + $arguments = ['page' => $this, 'slug' => $slug, 'languageCode' => null]; + return $this->commit('changeSlug', $arguments, function ($oldPage, $slug) { + $newPage = $oldPage->clone([ + 'slug' => $slug, + 'dirname' => null, + 'root' => null + ]); + + if ($oldPage->exists() === true) { + // remove the lock of the old page + if ($lock = $oldPage->lock()) { + $lock->remove(); + } + + // actually move stuff on disk + if (Dir::move($oldPage->root(), $newPage->root()) !== true) { + throw new LogicException('The page directory cannot be moved'); + } + + // remove from the siblings + $oldPage->parentModel()->children()->remove($oldPage); + + Dir::remove($oldPage->mediaRoot()); + } + + // overwrite the new page in the parent collection + if ($newPage->isDraft() === true) { + $newPage->parentModel()->drafts()->set($newPage->id(), $newPage); + } else { + $newPage->parentModel()->children()->set($newPage->id(), $newPage); + } + + return $newPage; + }); + } + + /** + * Change the slug for a specific language + * + * @param string $slug + * @param string|null $languageCode + * @return static + * @throws \Kirby\Exception\NotFoundException If the language for the given language code cannot be found + * @throws \Kirby\Exception\InvalidArgumentException If the slug for the default language is being changed + */ + protected function changeSlugForLanguage(string $slug, string $languageCode = null) + { + $language = $this->kirby()->language($languageCode); + + if (!$language) { + throw new NotFoundException('The language: "' . $languageCode . '" does not exist'); + } + + if ($language->isDefault() === true) { + throw new InvalidArgumentException('Use the changeSlug method to change the slug for the default language'); + } + + $arguments = ['page' => $this, 'slug' => $slug, 'languageCode' => $languageCode]; + return $this->commit('changeSlug', $arguments, function ($page, $slug, $languageCode) { + // remove the slug if it's the same as the folder name + if ($slug === $page->uid()) { + $slug = null; + } + + return $page->save(['slug' => $slug], $languageCode); + }); + } + + /** + * Change the status of the current page + * to either draft, listed or unlisted. + * If changing to `listed`, you can pass a position for the + * page in the siblings collection. Siblings will be resorted. + * + * @param string $status "draft", "listed" or "unlisted" + * @param int|null $position Optional sorting number + * @return static + * @throws \Kirby\Exception\InvalidArgumentException If an invalid status is being passed + */ + public function changeStatus(string $status, int $position = null) + { + switch ($status) { + case 'draft': + return $this->changeStatusToDraft(); + case 'listed': + return $this->changeStatusToListed($position); + case 'unlisted': + return $this->changeStatusToUnlisted(); + default: + throw new InvalidArgumentException('Invalid status: ' . $status); + } + } + + /** + * @return static + */ + protected function changeStatusToDraft() + { + $arguments = ['page' => $this, 'status' => 'draft', 'position' => null]; + $page = $this->commit('changeStatus', $arguments, function ($page) { + return $page->unpublish(); + }); + + return $page; + } + + /** + * @param int|null $position + * @return $this|static + */ + protected function changeStatusToListed(int $position = null) + { + // create a sorting number for the page + $num = $this->createNum($position); + + // don't sort if not necessary + if ($this->status() === 'listed' && $num === $this->num()) { + return $this; + } + + $arguments = ['page' => $this, 'status' => 'listed', 'position' => $num]; + $page = $this->commit('changeStatus', $arguments, function ($page, $status, $position) { + return $page->publish()->changeNum($position); + }); + + if ($this->blueprint()->num() === 'default') { + $page->resortSiblingsAfterListing($num); + } + + return $page; + } + + /** + * @return $this|static + */ + protected function changeStatusToUnlisted() + { + if ($this->status() === 'unlisted') { + return $this; + } + + $arguments = ['page' => $this, 'status' => 'unlisted', 'position' => null]; + $page = $this->commit('changeStatus', $arguments, function ($page) { + return $page->publish()->changeNum(null); + }); + + $this->resortSiblingsAfterUnlisting(); + + return $page; + } + + /** + * Change the position of the page in its siblings + * collection. Siblings will be resorted. If the page + * status isn't yet `listed`, it will be changed to it. + * + * @param int|null $position + * @return $this|static + */ + public function changeSort(int $position = null) + { + return $this->changeStatus('listed', $position); + } + + /** + * Changes the page template + * + * @param string $template + * @return $this|static + * @throws \Kirby\Exception\LogicException If the textfile cannot be renamed/moved + */ + public function changeTemplate(string $template) + { + if ($template === $this->intendedTemplate()->name()) { + return $this; + } + + return $this->commit('changeTemplate', ['page' => $this, 'template' => $template], function ($oldPage, $template) { + if ($this->kirby()->multilang() === true) { + $newPage = $this->clone([ + 'template' => $template + ]); + + foreach ($this->kirby()->languages()->codes() as $code) { + $content = $oldPage->content($code)->convertTo($template); + + if (F::remove($oldPage->contentFile($code)) !== true) { + throw new LogicException('The old text file could not be removed'); + } + + // save the language file + $newPage->save($content, $code); + } + + // return a fresh copy of the object + return $newPage->clone(); + } else { + $newPage = $this->clone([ + 'content' => $this->content()->convertTo($template), + 'template' => $template + ]); + + if (F::remove($oldPage->contentFile()) !== true) { + throw new LogicException('The old text file could not be removed'); + } + + return $newPage->save(); + } + }); + } + + /** + * Change the page title + * + * @param string $title + * @param string|null $languageCode + * @return static + */ + public function changeTitle(string $title, string $languageCode = null) + { + $arguments = ['page' => $this, 'title' => $title, 'languageCode' => $languageCode]; + return $this->commit('changeTitle', $arguments, function ($page, $title, $languageCode) { + return $page->save(['title' => $title], $languageCode); + }); + } + + /** + * Commits a page action, by following these steps + * + * 1. checks the action rules + * 2. sends the before hook + * 3. commits the store action + * 4. sends the after hook + * 5. returns the result + * + * @param string $action + * @param array $arguments + * @param \Closure $callback + * @return mixed + */ + protected function commit(string $action, array $arguments, Closure $callback) + { + $old = $this->hardcopy(); + $kirby = $this->kirby(); + $argumentValues = array_values($arguments); + + $this->rules()->$action(...$argumentValues); + $kirby->trigger('page.' . $action . ':before', $arguments); + + $result = $callback(...$argumentValues); + + if ($action === 'create') { + $argumentsAfter = ['page' => $result]; + } elseif ($action === 'duplicate') { + $argumentsAfter = ['duplicatePage' => $result, 'originalPage' => $old]; + } elseif ($action === 'delete') { + $argumentsAfter = ['status' => $result, 'page' => $old]; + } else { + $argumentsAfter = ['newPage' => $result, 'oldPage' => $old]; + } + $kirby->trigger('page.' . $action . ':after', $argumentsAfter); + + $kirby->cache('pages')->flush(); + return $result; + } + + /** + * Copies the page to a new parent + * + * @param array $options + * @return \Kirby\Cms\Page + * @throws \Kirby\Exception\DuplicateException If the page already exists + */ + public function copy(array $options = []) + { + $slug = $options['slug'] ?? $this->slug(); + $isDraft = $options['isDraft'] ?? $this->isDraft(); + $parent = $options['parent'] ?? null; + $parentModel = $options['parent'] ?? $this->site(); + $num = $options['num'] ?? null; + $children = $options['children'] ?? false; + $files = $options['files'] ?? false; + + // clean up the slug + $slug = Str::slug($slug); + + if ($parentModel->findPageOrDraft($slug)) { + throw new DuplicateException([ + 'key' => 'page.duplicate', + 'data' => [ + 'slug' => $slug + ] + ]); + } + + $tmp = new static([ + 'isDraft' => $isDraft, + 'num' => $num, + 'parent' => $parent, + 'slug' => $slug, + ]); + + $ignore = [ + $this->kirby()->locks()->file($this) + ]; + + // don't copy files + if ($files === false) { + foreach ($this->files() as $file) { + $ignore[] = $file->root(); + + // append all content files + array_push($ignore, ...$file->contentFiles()); + } + } + + Dir::copy($this->root(), $tmp->root(), $children, $ignore); + + $copy = $parentModel->clone()->findPageOrDraft($slug); + + // remove all translated slugs + if ($this->kirby()->multilang() === true) { + foreach ($this->kirby()->languages() as $language) { + if ($language->isDefault() === false && $copy->translation($language)->exists() === true) { + $copy = $copy->save(['slug' => null], $language->code()); + } + } + } + + // add copy to siblings + if ($isDraft === true) { + $parentModel->drafts()->append($copy->id(), $copy); + } else { + $parentModel->children()->append($copy->id(), $copy); + } + + return $copy; + } + + /** + * Creates and stores a new page + * + * @param array $props + * @return static + */ + public static function create(array $props) + { + // clean up the slug + $props['slug'] = Str::slug($props['slug'] ?? $props['content']['title'] ?? null); + $props['template'] = $props['model'] = strtolower($props['template'] ?? 'default'); + $props['isDraft'] = ($props['draft'] ?? true); + + // create a temporary page object + $page = Page::factory($props); + + // create a form for the page + $form = Form::for($page, [ + 'values' => $props['content'] ?? [] + ]); + + // inject the content + $page = $page->clone(['content' => $form->strings(true)]); + + // run the hooks and creation action + $page = $page->commit('create', ['page' => $page, 'input' => $props], function ($page, $props) { + + // always create pages in the default language + if ($page->kirby()->multilang() === true) { + $languageCode = $page->kirby()->defaultLanguage()->code(); + } else { + $languageCode = null; + } + + // write the content file + $page = $page->save($page->content()->toArray(), $languageCode); + + // flush the parent cache to get children and drafts right + if ($page->isDraft() === true) { + $page->parentModel()->drafts()->append($page->id(), $page); + } else { + $page->parentModel()->children()->append($page->id(), $page); + } + + return $page; + }); + + // publish the new page if a number is given + if (isset($props['num']) === true) { + $page = $page->changeStatus('listed', $props['num']); + } + + return $page; + } + + /** + * Creates a child of the current page + * + * @param array $props + * @return static + */ + public function createChild(array $props) + { + $props = array_merge($props, [ + 'url' => null, + 'num' => null, + 'parent' => $this, + 'site' => $this->site(), + ]); + + $modelClass = Page::$models[$props['template']] ?? Page::class; + return $modelClass::create($props); + } + + /** + * Create the sorting number for the page + * depending on the blueprint settings + * + * @param int|null $num + * @return int + */ + public function createNum(int $num = null): int + { + $mode = $this->blueprint()->num(); + + switch ($mode) { + case 'zero': + return 0; + case 'date': + case 'datetime': + $format = $mode === 'date' ? 'Ymd' : 'YmdHi'; + $lang = $this->kirby()->defaultLanguage() ?? null; + $field = $this->content($lang)->get('date'); + $date = $field->isEmpty() ? 'now' : $field; + // TODO: in 3.6.0 throw an error if date() doesn't + // return a number, see https://github.com/getkirby/kirby/pull/3061#discussion_r552783943 + return (int)date($format, strtotime($date)); + break; + case 'default': + + $max = $this + ->parentModel() + ->children() + ->listed() + ->merge($this) + ->count(); + + // default positioning at the end + if ($num === null) { + $num = $max; + } + + // avoid zeros or negative numbers + if ($num < 1) { + return 1; + } + + // avoid higher numbers than possible + if ($num > $max) { + return $max; + } + + return $num; + default: + // get instance with default language + $app = $this->kirby()->clone([], false); + $app->setCurrentLanguage(); + + $template = Str::template($mode, [ + 'kirby' => $app, + 'page' => $app->page($this->id()), + 'site' => $app->site(), + ], ''); + + return (int)$template; + } + } + + /** + * Deletes the page + * + * @param bool $force + * @return bool + */ + public function delete(bool $force = false): bool + { + return $this->commit('delete', ['page' => $this, 'force' => $force], function ($page, $force) { + + // delete all files individually + foreach ($page->files() as $file) { + $file->delete(); + } + + // delete all children individually + foreach ($page->children() as $child) { + $child->delete(true); + } + + // actually remove the page from disc + if ($page->exists() === true) { + + // delete all public media files + Dir::remove($page->mediaRoot()); + + // delete the content folder for this page + Dir::remove($page->root()); + + // if the page is a draft and the _drafts folder + // is now empty. clean it up. + if ($page->isDraft() === true) { + $draftsDir = dirname($page->root()); + + if (Dir::isEmpty($draftsDir) === true) { + Dir::remove($draftsDir); + } + } + } + + if ($page->isDraft() === true) { + $page->parentModel()->drafts()->remove($page); + } else { + $page->parentModel()->children()->remove($page); + $page->resortSiblingsAfterUnlisting(); + } + + return true; + }); + } + + /** + * Duplicates the page with the given + * slug and optionally copies all files + * + * @param string|null $slug + * @param array $options + * @return \Kirby\Cms\Page + */ + public function duplicate(string $slug = null, array $options = []) + { + + // create the slug for the duplicate + $slug = Str::slug($slug ?? $this->slug() . '-copy'); + + $arguments = ['originalPage' => $this, 'input' => $slug, 'options' => $options]; + return $this->commit('duplicate', $arguments, function ($page, $slug, $options) { + return $this->copy([ + 'parent' => $this->parent(), + 'slug' => $slug, + 'isDraft' => true, + 'files' => $options['files'] ?? false, + 'children' => $options['children'] ?? false, + ]); + }); + } + + /** + * @return $this|static + * @throws \Kirby\Exception\LogicException If the folder cannot be moved + */ + public function publish() + { + if ($this->isDraft() === false) { + return $this; + } + + $page = $this->clone([ + 'isDraft' => false, + 'root' => null + ]); + + // actually do it on disk + if ($this->exists() === true) { + if (Dir::move($this->root(), $page->root()) !== true) { + throw new LogicException('The draft folder cannot be moved'); + } + + // Get the draft folder and check if there are any other drafts + // left. Otherwise delete it. + $draftDir = dirname($this->root()); + + if (Dir::isEmpty($draftDir) === true) { + Dir::remove($draftDir); + } + } + + // remove the page from the parent drafts and add it to children + $page->parentModel()->drafts()->remove($page); + $page->parentModel()->children()->append($page->id(), $page); + + return $page; + } + + /** + * Clean internal caches + * @return $this + */ + public function purge() + { + $this->blueprint = null; + $this->children = null; + $this->content = null; + $this->drafts = null; + $this->files = null; + $this->inventory = null; + $this->translations = null; + + return $this; + } + + /** + * @param int|null $position + * @return bool + * @throws \Kirby\Exception\LogicException If the page is not included in the siblings collection + */ + protected function resortSiblingsAfterListing(int $position = null): bool + { + // get all siblings including the current page + $siblings = $this + ->parentModel() + ->children() + ->listed() + ->append($this) + ->filter(function ($page) { + return $page->blueprint()->num() === 'default'; + }); + + // get a non-associative array of ids + $keys = $siblings->keys(); + $index = array_search($this->id(), $keys); + + // if the page is not included in the siblings something went wrong + if ($index === false) { + throw new LogicException('The page is not included in the sorting index'); + } + + if ($position > count($keys)) { + $position = count($keys); + } + + // move the current page number in the array of keys + // subtract 1 from the num and the position, because of the + // zero-based array keys + $sorted = A::move($keys, $index, $position - 1); + + foreach ($sorted as $key => $id) { + if ($id === $this->id()) { + continue; + } else { + if ($sibling = $siblings->get($id)) { + $sibling->changeNum($key + 1); + } + } + } + + $parent = $this->parentModel(); + $parent->children = $parent->children()->sort('num', 'asc'); + + return true; + } + + /** + * @return bool + */ + public function resortSiblingsAfterUnlisting(): bool + { + $index = 0; + $parent = $this->parentModel(); + $siblings = $parent + ->children() + ->listed() + ->not($this) + ->filter(function ($page) { + return $page->blueprint()->num() === 'default'; + }); + + if ($siblings->count() > 0) { + foreach ($siblings as $sibling) { + $index++; + $sibling->changeNum($index); + } + + $parent->children = $siblings->sort('num', 'asc'); + } + + return true; + } + + /** + * @deprecated 3.5.0 Use `Page::changeSort()` instead + * @todo Remove in 3.6.0 + * + * @param null $position + * @return $this|static + */ + public function sort($position = null) + { + deprecated('$page->sort() is deprecated, use $page->changeSort() instead. $page->sort() will be removed in Kirby 3.6.0.'); + + return $this->changeStatus('listed', $position); + } + + /** + * Convert a page from listed or + * unlisted to draft. + * + * @return $this|static + * @throws \Kirby\Exception\LogicException If the folder cannot be moved + */ + public function unpublish() + { + if ($this->isDraft() === true) { + return $this; + } + + $page = $this->clone([ + 'isDraft' => true, + 'num' => null, + 'dirname' => null, + 'root' => null + ]); + + // actually do it on disk + if ($this->exists() === true) { + if (Dir::move($this->root(), $page->root()) !== true) { + throw new LogicException('The page folder cannot be moved to drafts'); + } + } + + // remove the page from the parent children and add it to drafts + $page->parentModel()->children()->remove($page); + $page->parentModel()->drafts()->append($page->id(), $page); + + $page->resortSiblingsAfterUnlisting(); + + return $page; + } + + /** + * Updates the page data + * + * @param array|null $input + * @param string|null $languageCode + * @param bool $validate + * @return static + */ + public function update(array $input = null, string $languageCode = null, bool $validate = false) + { + if ($this->isDraft() === true) { + $validate = false; + } + + $page = parent::update($input, $languageCode, $validate); + + // if num is created from page content, update num on content update + if ($page->isListed() === true && in_array($page->blueprint()->num(), ['zero', 'default']) === false) { + $page = $page->changeNum($page->createNum()); + } + + return $page; + } +} diff --git a/kirby/src/Cms/PageBlueprint.php b/kirby/src/Cms/PageBlueprint.php new file mode 100644 index 0000000..bef7ba1 --- /dev/null +++ b/kirby/src/Cms/PageBlueprint.php @@ -0,0 +1,209 @@ + + * @link https://getkirby.com + * @copyright Bastian Allgeier GmbH + * @license https://getkirby.com/license + */ +class PageBlueprint extends Blueprint +{ + /** + * Creates a new page blueprint object + * with the given props + * + * @param array $props + */ + public function __construct(array $props) + { + parent::__construct($props); + + // normalize all available page options + $this->props['options'] = $this->normalizeOptions( + $props['options'] ?? true, + // defaults + [ + 'changeSlug' => null, + 'changeStatus' => null, + 'changeTemplate' => null, + 'changeTitle' => null, + 'create' => null, + 'delete' => null, + 'duplicate' => null, + 'read' => null, + 'preview' => null, + 'sort' => null, + 'update' => null, + ], + // aliases (from v2) + [ + 'status' => 'changeStatus', + 'template' => 'changeTemplate', + 'title' => 'changeTitle', + 'url' => 'changeSlug', + ] + ); + + // normalize the ordering number + $this->props['num'] = $this->normalizeNum($props['num'] ?? 'default'); + + // normalize the available status array + $this->props['status'] = $this->normalizeStatus($props['status'] ?? null); + } + + /** + * Returns the page numbering mode + * + * @return string + */ + public function num(): string + { + return $this->props['num']; + } + + /** + * Normalizes the ordering number + * + * @param mixed $num + * @return string + */ + protected function normalizeNum($num): string + { + $aliases = [ + '0' => 'zero', + 'sort' => 'default', + ]; + + if (isset($aliases[$num]) === true) { + return $aliases[$num]; + } + + return $num; + } + + /** + * Normalizes the available status options for the page + * + * @param mixed $status + * @return array + */ + protected function normalizeStatus($status): array + { + $defaults = [ + 'draft' => [ + 'label' => $this->i18n('page.status.draft'), + 'text' => $this->i18n('page.status.draft.description'), + ], + 'unlisted' => [ + 'label' => $this->i18n('page.status.unlisted'), + 'text' => $this->i18n('page.status.unlisted.description'), + ], + 'listed' => [ + 'label' => $this->i18n('page.status.listed'), + 'text' => $this->i18n('page.status.listed.description'), + ] + ]; + + // use the defaults, when the status is not defined + if (empty($status) === true) { + $status = $defaults; + } + + // extend the status definition + $status = $this->extend($status); + + // clean up and translate each status + foreach ($status as $key => $options) { + + // skip invalid status definitions + if (in_array($key, ['draft', 'listed', 'unlisted']) === false || $options === false) { + unset($status[$key]); + continue; + } + + if ($options === true) { + $status[$key] = $defaults[$key]; + continue; + } + + // convert everything to a simple array + if (is_array($options) === false) { + $status[$key] = [ + 'label' => $options, + 'text' => null + ]; + } + + // always make sure to have a proper label + if (empty($status[$key]['label']) === true) { + $status[$key]['label'] = $defaults[$key]['label']; + } + + // also make sure to have the text field set + if (isset($status[$key]['text']) === false) { + $status[$key]['text'] = null; + } + + // translate text and label if necessary + $status[$key]['label'] = $this->i18n($status[$key]['label'], $status[$key]['label']); + $status[$key]['text'] = $this->i18n($status[$key]['text'], $status[$key]['text']); + } + + // the draft status is required + if (isset($status['draft']) === false) { + $status = ['draft' => $defaults['draft']] + $status; + } + + // remove the draft status for the home and error pages + if ($this->model->isHomeOrErrorPage() === true) { + unset($status['draft']); + } + + return $status; + } + + /** + * Returns the options object + * that handles page options and permissions + * + * @return array + */ + public function options(): array + { + return $this->props['options']; + } + + /** + * Returns the preview settings + * The preview setting controls the "Open" + * button in the panel and redirects it to a + * different URL if necessary. + * + * @return string|bool + */ + public function preview() + { + $preview = $this->props['options']['preview'] ?? true; + + if (is_string($preview) === true) { + return $this->model->toString($preview); + } + + return $preview; + } + + /** + * Returns the status array + * + * @return array + */ + public function status(): array + { + return $this->props['status']; + } +} diff --git a/kirby/src/Cms/PagePermissions.php b/kirby/src/Cms/PagePermissions.php new file mode 100644 index 0000000..acb0adf --- /dev/null +++ b/kirby/src/Cms/PagePermissions.php @@ -0,0 +1,80 @@ + + * @link https://getkirby.com + * @copyright Bastian Allgeier GmbH + * @license https://getkirby.com/license + */ +class PagePermissions extends ModelPermissions +{ + /** + * @var string + */ + protected $category = 'pages'; + + /** + * @return bool + */ + protected function canChangeSlug(): bool + { + return $this->model->isHomeOrErrorPage() !== true; + } + + /** + * @return bool + */ + protected function canChangeStatus(): bool + { + return $this->model->isErrorPage() !== true; + } + + /** + * @return bool + */ + protected function canChangeTemplate(): bool + { + if ($this->model->isHomeOrErrorPage() === true) { + return false; + } + + if (count($this->model->blueprints()) <= 1) { + return false; + } + + return true; + } + + /** + * @return bool + */ + protected function canDelete(): bool + { + return $this->model->isHomeOrErrorPage() !== true; + } + + /** + * @return bool + */ + protected function canSort(): bool + { + if ($this->model->isErrorPage() === true) { + return false; + } + + if ($this->model->isListed() !== true) { + return false; + } + + if ($this->model->blueprint()->num() !== 'default') { + return false; + } + + return true; + } +} diff --git a/kirby/src/Cms/PagePicker.php b/kirby/src/Cms/PagePicker.php new file mode 100644 index 0000000..375bff8 --- /dev/null +++ b/kirby/src/Cms/PagePicker.php @@ -0,0 +1,265 @@ + + * @link https://getkirby.com + * @copyright Bastian Allgeier GmbH + * @license https://getkirby.com/license + */ +class PagePicker extends Picker +{ + /** + * @var \Kirby\Cms\Pages + */ + protected $items; + + /** + * @var \Kirby\Cms\Pages + */ + protected $itemsForQuery; + + /** + * @var \Kirby\Cms\Page|\Kirby\Cms\Site|null + */ + protected $parent; + + /** + * Extends the basic defaults + * + * @return array + */ + public function defaults(): array + { + return array_merge(parent::defaults(), [ + // Page ID of the selected parent. Used to navigate + 'parent' => null, + // enable/disable subpage navigation + 'subpages' => true, + ]); + } + + /** + * Returns the parent model object that + * is currently selected in the page picker. + * It normally starts at the site, but can + * also be any subpage. When a query is given + * and subpage navigation is deactivated, + * there will be no model available at all. + * + * @return \Kirby\Cms\Page|\Kirby\Cms\Site|null + */ + public function model() + { + // no subpages navigation = no model + if ($this->options['subpages'] === false) { + return null; + } + + // the model for queries is a bit more tricky to find + if (empty($this->options['query']) === false) { + return $this->modelForQuery(); + } + + return $this->parent(); + } + + /** + * Returns a model object for the given + * query, depending on the parent and subpages + * options. + * + * @return \Kirby\Cms\Page|\Kirby\Cms\Site|null + */ + public function modelForQuery() + { + if ($this->options['subpages'] === true && empty($this->options['parent']) === false) { + return $this->parent(); + } + + if ($items = $this->items()) { + return $items->parent(); + } + + return null; + } + + /** + * Returns basic information about the + * parent model that is currently selected + * in the page picker. + * + * @param \Kirby\Cms\Site|\Kirby\Cms\Page|null + * @return array|null + */ + public function modelToArray($model = null): ?array + { + if ($model === null) { + return null; + } + + // the selected model is the site. there's nothing above + if (is_a($model, 'Kirby\Cms\Site') === true) { + return [ + 'id' => null, + 'parent' => null, + 'title' => $model->title()->value() + ]; + } + + // the top-most page has been reached + // the missing id indicates that there's nothing above + if ($model->id() === $this->start()->id()) { + return [ + 'id' => null, + 'parent' => null, + 'title' => $model->title()->value() + ]; + } + + // the model is a regular page + return [ + 'id' => $model->id(), + 'parent' => $model->parentModel()->id(), + 'title' => $model->title()->value() + ]; + } + + /** + * Search all pages for the picker + * + * @return \Kirby\Cms\Pages|null + */ + public function items() + { + // cache + if ($this->items !== null) { + return $this->items; + } + + // no query? simple parent-based search for pages + if (empty($this->options['query']) === true) { + $items = $this->itemsForParent(); + + // when subpage navigation is enabled, a parent + // might be passed in addition to the query. + // The parent then takes priority. + } elseif ($this->options['subpages'] === true && empty($this->options['parent']) === false) { + $items = $this->itemsForParent(); + + // search by query + } else { + $items = $this->itemsForQuery(); + } + + // filter protected pages + $items = $items->filter('isReadable', true); + + // search + $items = $this->search($items); + + // paginate the result + return $this->items = $this->paginate($items); + } + + /** + * Search for pages by parent + * + * @return \Kirby\Cms\Pages + */ + public function itemsForParent() + { + return $this->parent()->children(); + } + + /** + * Search for pages by query string + * + * @return \Kirby\Cms\Pages + * @throws \Kirby\Exception\InvalidArgumentException + */ + public function itemsForQuery() + { + // cache + if ($this->itemsForQuery !== null) { + return $this->itemsForQuery; + } + + $model = $this->options['model']; + $items = $model->query($this->options['query']); + + // help mitigate some typical query usage issues + // by converting site and page objects to proper + // pages by returning their children + if (is_a($items, 'Kirby\Cms\Site') === true) { + $items = $items->children(); + } elseif (is_a($items, 'Kirby\Cms\Page') === true) { + $items = $items->children(); + } elseif (is_a($items, 'Kirby\Cms\Pages') === false) { + throw new InvalidArgumentException('Your query must return a set of pages'); + } + + return $this->itemsForQuery = $items; + } + + /** + * Returns the parent model. + * The model will be used to fetch + * subpages unless there's a specific + * query to find pages instead. + * + * @return \Kirby\Cms\Page|\Kirby\Cms\Site + */ + public function parent() + { + if ($this->parent !== null) { + return $this->parent; + } + + return $this->parent = $this->kirby->page($this->options['parent']) ?? $this->site; + } + + /** + * Calculates the top-most model (page or site) + * that can be accessed when navigating + * through pages. + * + * @return \Kirby\Cms\Page|\Kirby\Cms\Site + */ + public function start() + { + if (empty($this->options['query']) === false) { + if ($items = $this->itemsForQuery()) { + return $items->parent(); + } + + return $this->site; + } + + return $this->site; + } + + /** + * Returns an associative array + * with all information for the picker. + * This will be passed directly to the API. + * + * @return array + */ + public function toArray(): array + { + $array = parent::toArray(); + $array['model'] = $this->modelToArray($this->model()); + + return $array; + } +} diff --git a/kirby/src/Cms/PageRules.php b/kirby/src/Cms/PageRules.php new file mode 100644 index 0000000..92d9f08 --- /dev/null +++ b/kirby/src/Cms/PageRules.php @@ -0,0 +1,432 @@ + + * @link https://getkirby.com + * @copyright Bastian Allgeier GmbH + * @license https://getkirby.com/license + */ +class PageRules +{ + /** + * Validates if the sorting number of the page can be changed + * + * @param \Kirby\Cms\Page $page + * @param int|null $num + * @return bool + * @throws \Kirby\Exception\InvalidArgumentException If the given number is invalid + */ + public static function changeNum(Page $page, int $num = null): bool + { + if ($num !== null && $num < 0) { + throw new InvalidArgumentException(['key' => 'page.num.invalid']); + } + + return true; + } + + /** + * Validates if the slug for the page can be changed + * + * @param \Kirby\Cms\Page $page + * @param string $slug + * @return bool + * @throws \Kirby\Exception\DuplicateException If a page with this slug already exists + * @throws \Kirby\Exception\PermissionException If the user is not allowed to change the slug + */ + public static function changeSlug(Page $page, string $slug): bool + { + if ($page->permissions()->changeSlug() !== true) { + throw new PermissionException([ + 'key' => 'page.changeSlug.permission', + 'data' => [ + 'slug' => $page->slug() + ] + ]); + } + + self::validateSlugLength($slug); + + $siblings = $page->parentModel()->children(); + $drafts = $page->parentModel()->drafts(); + + if ($duplicate = $siblings->find($slug)) { + if ($duplicate->is($page) === false) { + throw new DuplicateException([ + 'key' => 'page.duplicate', + 'data' => [ + 'slug' => $slug + ] + ]); + } + } + + if ($duplicate = $drafts->find($slug)) { + if ($duplicate->is($page) === false) { + throw new DuplicateException([ + 'key' => 'page.draft.duplicate', + 'data' => [ + 'slug' => $slug + ] + ]); + } + } + + return true; + } + + /** + * Validates if the status for the page can be changed + * + * @param \Kirby\Cms\Page $page + * @param string $status + * @param int|null $position + * @return bool + * @throws \Kirby\Exception\InvalidArgumentException If the given status is invalid + */ + public static function changeStatus(Page $page, string $status, int $position = null): bool + { + if (isset($page->blueprint()->status()[$status]) === false) { + throw new InvalidArgumentException(['key' => 'page.status.invalid']); + } + + switch ($status) { + case 'draft': + return static::changeStatusToDraft($page); + case 'listed': + return static::changeStatusToListed($page, $position); + case 'unlisted': + return static::changeStatusToUnlisted($page); + default: + throw new InvalidArgumentException(['key' => 'page.status.invalid']); + } + } + + /** + * Validates if a page can be converted to a draft + * + * @param \Kirby\Cms\Page $page + * @return bool + * @throws \Kirby\Exception\PermissionException If the user is not allowed to change the status or the page cannot be converted to a draft + */ + public static function changeStatusToDraft(Page $page) + { + if ($page->permissions()->changeStatus() !== true) { + throw new PermissionException([ + 'key' => 'page.changeStatus.permission', + 'data' => [ + 'slug' => $page->slug() + ] + ]); + } + + if ($page->isHomeOrErrorPage() === true) { + throw new PermissionException([ + 'key' => 'page.changeStatus.toDraft.invalid', + 'data' => [ + 'slug' => $page->slug() + ] + ]); + } + + return true; + } + + /** + * Validates if the status of a page can be changed to listed + * + * @param \Kirby\Cms\Page $page + * @param int $position + * @return bool + * @throws \Kirby\Exception\InvalidArgumentException If the given position is invalid + * @throws \Kirby\Exception\PermissionException If the user is not allowed to change the status or the status for the page cannot be changed by any user + */ + public static function changeStatusToListed(Page $page, int $position) + { + // no need to check for status changing permissions, + // instead we need to check for sorting permissions + if ($page->isListed() === true) { + if ($page->isSortable() !== true) { + throw new PermissionException([ + 'key' => 'page.sort.permission', + 'data' => [ + 'slug' => $page->slug() + ] + ]); + } + + return true; + } + + if ($page->permissions()->changeStatus() !== true) { + throw new PermissionException([ + 'key' => 'page.changeStatus.permission', + 'data' => [ + 'slug' => $page->slug() + ] + ]); + } + + if ($position !== null && $position < 0) { + throw new InvalidArgumentException(['key' => 'page.num.invalid']); + } + + if ($page->isDraft() === true && empty($page->errors()) === false) { + throw new PermissionException([ + 'key' => 'page.changeStatus.incomplete', + 'details' => $page->errors() + ]); + } + + return true; + } + + /** + * Validates if the status of a page can be changed to unlisted + * + * @param \Kirby\Cms\Page $page + * @return bool + * @throws \Kirby\Exception\PermissionException If the user is not allowed to change the status + */ + public static function changeStatusToUnlisted(Page $page) + { + if ($page->permissions()->changeStatus() !== true) { + throw new PermissionException([ + 'key' => 'page.changeStatus.permission', + 'data' => [ + 'slug' => $page->slug() + ] + ]); + } + + return true; + } + + /** + * Validates if the template of the page can be changed + * + * @param \Kirby\Cms\Page $page + * @param string $template + * @return bool + * @throws \Kirby\Exception\LogicException If the template of the page cannot be changed at all + * @throws \Kirby\Exception\PermissionException If the user is not allowed to change the template + */ + public static function changeTemplate(Page $page, string $template): bool + { + if ($page->permissions()->changeTemplate() !== true) { + throw new PermissionException([ + 'key' => 'page.changeTemplate.permission', + 'data' => [ + 'slug' => $page->slug() + ] + ]); + } + + if (count($page->blueprints()) <= 1) { + throw new LogicException([ + 'key' => 'page.changeTemplate.invalid', + 'data' => ['slug' => $page->slug()] + ]); + } + + return true; + } + + /** + * Validates if the title of the page can be changed + * + * @param \Kirby\Cms\Page $page + * @param string $title + * @return bool + * @throws \Kirby\Exception\InvalidArgumentException If the new title is empty + * @throws \Kirby\Exception\PermissionException If the user is not allowed to change the title + */ + public static function changeTitle(Page $page, string $title): bool + { + if ($page->permissions()->changeTitle() !== true) { + throw new PermissionException([ + 'key' => 'page.changeTitle.permission', + 'data' => [ + 'slug' => $page->slug() + ] + ]); + } + + if (Str::length($title) === 0) { + throw new InvalidArgumentException([ + 'key' => 'page.changeTitle.empty', + ]); + } + + return true; + } + + /** + * Validates if the page can be created + * + * @param \Kirby\Cms\Page $page + * @return bool + * @throws \Kirby\Exception\DuplicateException If the same page or a draft already exists + * @throws \Kirby\Exception\InvalidArgumentException If the slug is invalid + * @throws \Kirby\Exception\PermissionException If the user is not allowed to create this page + */ + public static function create(Page $page): bool + { + if ($page->permissions()->create() !== true) { + throw new PermissionException([ + 'key' => 'page.create.permission', + 'data' => [ + 'slug' => $page->slug() + ] + ]); + } + + self::validateSlugLength($page->slug()); + + if ($page->exists() === true) { + throw new DuplicateException([ + 'key' => 'page.draft.duplicate', + 'data' => [ + 'slug' => $page->slug() + ] + ]); + } + + $siblings = $page->parentModel()->children(); + $drafts = $page->parentModel()->drafts(); + $slug = $page->slug(); + + if ($siblings->find($slug)) { + throw new DuplicateException([ + 'key' => 'page.duplicate', + 'data' => ['slug' => $slug] + ]); + } + + if ($drafts->find($slug)) { + throw new DuplicateException([ + 'key' => 'page.draft.duplicate', + 'data' => ['slug' => $slug] + ]); + } + + return true; + } + + /** + * Validates if the page can be deleted + * + * @param \Kirby\Cms\Page $page + * @param bool $force + * @return bool + * @throws \Kirby\Exception\LogicException If the page has children and should not be force-deleted + * @throws \Kirby\Exception\PermissionException If the user is not allowed to delete the page + */ + public static function delete(Page $page, bool $force = false): bool + { + if ($page->permissions()->delete() !== true) { + throw new PermissionException([ + 'key' => 'page.delete.permission', + 'data' => [ + 'slug' => $page->slug() + ] + ]); + } + + if (($page->hasChildren() === true || $page->hasDrafts() === true) && $force === false) { + throw new LogicException(['key' => 'page.delete.hasChildren']); + } + + return true; + } + + /** + * Validates if the page can be duplicated + * + * @param \Kirby\Cms\Page $page + * @param string $slug + * @param array $options + * @return bool + * @throws \Kirby\Exception\PermissionException If the user is not allowed to duplicate the page + */ + public static function duplicate(Page $page, string $slug, array $options = []): bool + { + if ($page->permissions()->duplicate() !== true) { + throw new PermissionException([ + 'key' => 'page.duplicate.permission', + 'data' => [ + 'slug' => $page->slug() + ] + ]); + } + + self::validateSlugLength($slug); + + return true; + } + + /** + * Validates if the page can be updated + * + * @param \Kirby\Cms\Page $page + * @param array $content + * @return bool + * @throws \Kirby\Exception\PermissionException If the user is not allowed to update the page + */ + public static function update(Page $page, array $content = []): bool + { + if ($page->permissions()->update() !== true) { + throw new PermissionException([ + 'key' => 'page.update.permission', + 'data' => [ + 'slug' => $page->slug() + ] + ]); + } + + return true; + } + + /** + * Ensures that the slug is not empty and doesn't exceed the maximum length + * to make sure that the directory name will be accepted by the filesystem + * + * @param string $slug New slug to check + * @return void + * @throws \Kirby\Exception\InvalidArgumentException If the slug is empty or too long + */ + protected static function validateSlugLength(string $slug): void + { + $slugLength = Str::length($slug); + + if ($slugLength === 0) { + throw new InvalidArgumentException([ + 'key' => 'page.slug.invalid', + ]); + } + + if ($slugsMaxlength = App::instance()->option('slugs.maxlength', 255)) { + $maxlength = (int)$slugsMaxlength; + + if ($slugLength > $maxlength) { + throw new InvalidArgumentException([ + 'key' => 'page.slug.maxlength', + 'data' => [ + 'length' => $maxlength + ] + ]); + } + } + } +} diff --git a/kirby/src/Cms/PageSiblings.php b/kirby/src/Cms/PageSiblings.php new file mode 100644 index 0000000..3d24443 --- /dev/null +++ b/kirby/src/Cms/PageSiblings.php @@ -0,0 +1,140 @@ + + * @link https://getkirby.com + * @copyright Bastian Allgeier GmbH + * @license https://getkirby.com/license + */ +trait PageSiblings +{ + /** + * Checks if there's a next listed + * page in the siblings collection + * + * @param \Kirby\Cms\Collection|null $collection + * + * @return bool + */ + public function hasNextListed($collection = null): bool + { + return $this->nextListed($collection) !== null; + } + + /** + * Checks if there's a next unlisted + * page in the siblings collection + * + * @param \Kirby\Cms\Collection|null $collection + * + * @return bool + */ + public function hasNextUnlisted($collection = null): bool + { + return $this->nextUnlisted($collection) !== null; + } + + /** + * Checks if there's a previous listed + * page in the siblings collection + * + * @param \Kirby\Cms\Collection|null $collection + * + * @return bool + */ + public function hasPrevListed($collection = null): bool + { + return $this->prevListed($collection) !== null; + } + + /** + * Checks if there's a previous unlisted + * page in the siblings collection + * + * @param \Kirby\Cms\Collection|null $collection + * + * @return bool + */ + public function hasPrevUnlisted($collection = null): bool + { + return $this->prevUnlisted($collection) !== null; + } + + /** + * Returns the next listed page if it exists + * + * @param \Kirby\Cms\Collection|null $collection + * + * @return \Kirby\Cms\Page|null + */ + public function nextListed($collection = null) + { + return $this->nextAll($collection)->listed()->first(); + } + + /** + * Returns the next unlisted page if it exists + * + * @param \Kirby\Cms\Collection|null $collection + * + * @return \Kirby\Cms\Page|null + */ + public function nextUnlisted($collection = null) + { + return $this->nextAll($collection)->unlisted()->first(); + } + + /** + * Returns the previous listed page + * + * @param \Kirby\Cms\Collection|null $collection + * + * @return \Kirby\Cms\Page|null + */ + public function prevListed($collection = null) + { + return $this->prevAll($collection)->listed()->last(); + } + + /** + * Returns the previous unlisted page + * + * @param \Kirby\Cms\Collection|null $collection + * + * @return \Kirby\Cms\Page|null + */ + public function prevUnlisted($collection = null) + { + return $this->prevAll($collection)->unlisted()->first(); + } + + /** + * Private siblings collector + * + * @return \Kirby\Cms\Collection + */ + protected function siblingsCollection() + { + if ($this->isDraft() === true) { + return $this->parentModel()->drafts(); + } else { + return $this->parentModel()->children(); + } + } + + /** + * Returns siblings with the same template + * + * @param bool $self + * @return \Kirby\Cms\Pages + */ + public function templateSiblings(bool $self = true) + { + return $this->siblings($self)->filter('intendedTemplate', $this->intendedTemplate()->name()); + } +} diff --git a/kirby/src/Cms/Pages.php b/kirby/src/Cms/Pages.php new file mode 100644 index 0000000..1f97af5 --- /dev/null +++ b/kirby/src/Cms/Pages.php @@ -0,0 +1,515 @@ + + * @link https://getkirby.com + * @copyright Bastian Allgeier GmbH + * @license https://getkirby.com/license + */ +class Pages extends Collection +{ + /** + * Cache for the index only listed and unlisted pages + * + * @var \Kirby\Cms\Pages|null + */ + protected $index = null; + + /** + * Cache for the index all statuses also including drafts + * + * @var \Kirby\Cms\Pages|null + */ + protected $indexWithDrafts = null; + + /** + * All registered pages methods + * + * @var array + */ + public static $methods = []; + + /** + * Adds a single page or + * an entire second collection to the + * current collection + * + * @param mixed $object + * @return $this + * @throws \Kirby\Exception\InvalidArgumentException + */ + public function add($object) + { + // add a page collection + if (is_a($object, self::class) === true) { + $this->data = array_merge($this->data, $object->data); + + // add a page by id + } elseif (is_string($object) === true && $page = page($object)) { + $this->__set($page->id(), $page); + + // add a page object + } elseif (is_a($object, 'Kirby\Cms\Page') === true) { + $this->__set($object->id(), $object); + + // give a useful error message on invalid input + } elseif (in_array($object, [null, false, true], true) !== true) { + throw new InvalidArgumentException('You must pass a Page object to the Pages collection'); + } + + return $this; + } + + /** + * Returns all audio files of all children + * + * @return \Kirby\Cms\Files + */ + public function audio() + { + return $this->files()->filter('type', 'audio'); + } + + /** + * Returns all children for each page in the array + * + * @return \Kirby\Cms\Pages + */ + public function children() + { + $children = new Pages([], $this->parent); + + foreach ($this->data as $page) { + foreach ($page->children() as $childKey => $child) { + $children->data[$childKey] = $child; + } + } + + return $children; + } + + /** + * Returns all code files of all children + * + * @return \Kirby\Cms\Files + */ + public function code() + { + return $this->files()->filter('type', 'code'); + } + + /** + * Returns all documents of all children + * + * @return \Kirby\Cms\Files + */ + public function documents() + { + return $this->files()->filter('type', 'document'); + } + + /** + * Fetch all drafts for all pages in the collection + * + * @return \Kirby\Cms\Pages + */ + public function drafts() + { + $drafts = new Pages([], $this->parent); + + foreach ($this->data as $page) { + foreach ($page->drafts() as $draftKey => $draft) { + $drafts->data[$draftKey] = $draft; + } + } + + return $drafts; + } + + /** + * Creates a pages collection from an array of props + * + * @param array $pages + * @param \Kirby\Cms\Model|null $model + * @param bool $draft + * @return static + */ + public static function factory(array $pages, Model $model = null, bool $draft = false) + { + $model = $model ?? App::instance()->site(); + $children = new static([], $model); + $kirby = $model->kirby(); + + if (is_a($model, 'Kirby\Cms\Page') === true) { + $parent = $model; + $site = $model->site(); + } else { + $parent = null; + $site = $model; + } + + foreach ($pages as $props) { + $props['kirby'] = $kirby; + $props['parent'] = $parent; + $props['site'] = $site; + $props['isDraft'] = $draft; + + $page = Page::factory($props); + + $children->data[$page->id()] = $page; + } + + return $children; + } + + /** + * Returns all files of all children + * + * @return \Kirby\Cms\Files + */ + public function files() + { + $files = new Files([], $this->parent); + + foreach ($this->data as $page) { + foreach ($page->files() as $fileKey => $file) { + $files->data[$fileKey] = $file; + } + } + + return $files; + } + + /** + * Finds a page in the collection by id. + * This works recursively for children and + * children of children, etc. + * + * @param string|null $id + * @return mixed + */ + public function findById(string $id = null) + { + // remove trailing or leading slashes + $id = trim($id, '/'); + + // strip extensions from the id + if (strpos($id, '.') !== false) { + $info = pathinfo($id); + + if ($info['dirname'] !== '.') { + $id = $info['dirname'] . '/' . $info['filename']; + } else { + $id = $info['filename']; + } + } + + // try the obvious way + if ($page = $this->get($id)) { + return $page; + } + + $multiLang = App::instance()->multilang(); + + if ($multiLang === true && $page = $this->findBy('slug', $id)) { + return $page; + } + + $start = is_a($this->parent, 'Kirby\Cms\Page') === true ? $this->parent->id() : ''; + $page = $this->findByIdRecursive($id, $start, $multiLang); + + return $page; + } + + /** + * Finds a child or child of a child recursively. + * + * @param string $id + * @param string|null $startAt + * @param bool $multiLang + * @return mixed + */ + public function findByIdRecursive(string $id, string $startAt = null, bool $multiLang = false) + { + $path = explode('/', $id); + $item = null; + $query = $startAt; + + foreach ($path as $key) { + $collection = $item ? $item->children() : $this; + $query = ltrim($query . '/' . $key, '/'); + $item = $collection->get($query) ?? null; + + if ($item === null && $multiLang === true) { + $item = $collection->findBy('slug', $key); + } + + if ($item === null) { + return null; + } + } + + return $item; + } + + /** + * Uses the specialized find by id method + * + * @param string|null $key + * @return mixed + */ + public function findByKey(string $key = null) + { + return $this->findById($key); + } + + /** + * Alias for Pages::findById + * + * @param string $id + * @return \Kirby\Cms\Page|null + */ + public function findByUri(string $id) + { + return $this->findById($id); + } + + /** + * Finds the currently open page + * + * @return \Kirby\Cms\Page|null + */ + public function findOpen() + { + return $this->findBy('isOpen', true); + } + + /** + * Custom getter that is able to find + * extension pages + * + * @param string $key + * @param mixed $default + * @return \Kirby\Cms\Page|null + */ + public function get($key, $default = null) + { + if ($key === null) { + return null; + } + + if ($item = parent::get($key)) { + return $item; + } + + return App::instance()->extension('pages', $key); + } + + /** + * Returns all images of all children + * + * @return \Kirby\Cms\Files + */ + public function images() + { + return $this->files()->filter('type', 'image'); + } + + /** + * Create a recursive flat index of all + * pages and subpages, etc. + * + * @param bool $drafts + * @return \Kirby\Cms\Pages + */ + public function index(bool $drafts = false) + { + // get object property by cache mode + $index = $drafts === true ? $this->indexWithDrafts : $this->index; + + if (is_a($index, 'Kirby\Cms\Pages') === true) { + return $index; + } + + $index = new Pages([], $this->parent); + + foreach ($this->data as $pageKey => $page) { + $index->data[$pageKey] = $page; + $pageIndex = $page->index($drafts); + + if ($pageIndex) { + foreach ($pageIndex as $childKey => $child) { + $index->data[$childKey] = $child; + } + } + } + + if ($drafts === true) { + return $this->indexWithDrafts = $index; + } + + return $this->index = $index; + } + + /** + * Returns all listed pages in the collection + * + * @return \Kirby\Cms\Pages + */ + public function listed() + { + return $this->filter('isListed', '==', true); + } + + /** + * Returns all unlisted pages in the collection + * + * @return \Kirby\Cms\Pages + */ + public function unlisted() + { + return $this->filter('isUnlisted', '==', true); + } + + /** + * Include all given items in the collection + * + * @param mixed ...$args + * @return $this|static + */ + public function merge(...$args) + { + // merge multiple arguments at once + if (count($args) > 1) { + $collection = clone $this; + foreach ($args as $arg) { + $collection = $collection->merge($arg); + } + return $collection; + } + + // merge all parent drafts + if ($args[0] === 'drafts') { + if ($parent = $this->parent()) { + return $this->merge($parent->drafts()); + } + + return $this; + } + + // merge an entire collection + if (is_a($args[0], self::class) === true) { + $collection = clone $this; + $collection->data = array_merge($collection->data, $args[0]->data); + return $collection; + } + + // append a single page + if (is_a($args[0], 'Kirby\Cms\Page') === true) { + $collection = clone $this; + return $collection->set($args[0]->id(), $args[0]); + } + + // merge an array + if (is_array($args[0]) === true) { + $collection = clone $this; + foreach ($args[0] as $arg) { + $collection = $collection->merge($arg); + } + return $collection; + } + + if (is_string($args[0]) === true) { + return $this->merge(App::instance()->site()->find($args[0])); + } + + return $this; + } + + /** + * Filter all pages by excluding the given template + * @since 3.3.0 + * + * @param string|array $templates + * @return \Kirby\Cms\Pages + */ + public function notTemplate($templates) + { + if (empty($templates) === true) { + return $this; + } + + if (is_array($templates) === false) { + $templates = [$templates]; + } + + return $this->filter(function ($page) use ($templates) { + return !in_array($page->intendedTemplate()->name(), $templates); + }); + } + + /** + * Returns an array with all page numbers + * + * @return array + */ + public function nums(): array + { + return $this->pluck('num'); + } + + /* + * Returns all listed and unlisted pages in the collection + * + * @return \Kirby\Cms\Pages + */ + public function published() + { + return $this->filter('isDraft', '==', false); + } + + /** + * Filter all pages by the given template + * + * @param string|array $templates + * @return \Kirby\Cms\Pages + */ + public function template($templates) + { + if (empty($templates) === true) { + return $this; + } + + if (is_array($templates) === false) { + $templates = [$templates]; + } + + return $this->filter(function ($page) use ($templates) { + return in_array($page->intendedTemplate()->name(), $templates); + }); + } + + /** + * Returns all video files of all children + * + * @return \Kirby\Cms\Files + */ + public function videos() + { + return $this->files()->filter('type', 'video'); + } +} diff --git a/kirby/src/Cms/Pagination.php b/kirby/src/Cms/Pagination.php new file mode 100644 index 0000000..b064dc1 --- /dev/null +++ b/kirby/src/Cms/Pagination.php @@ -0,0 +1,179 @@ + + * @link https://getkirby.com + * @copyright Bastian Allgeier GmbH + * @license https://getkirby.com/license + */ +class Pagination extends BasePagination +{ + /** + * Pagination method (param, query, none) + * + * @var string + */ + protected $method; + + /** + * The base URL + * + * @var string + */ + protected $url; + + /** + * Variable name for query strings + * + * @var string + */ + protected $variable; + + /** + * Creates the pagination object. As a new + * property you can now pass the base Url. + * That Url must be the Url of the first + * page of the collection without additional + * pagination information/query parameters in it. + * + * ```php + * $pagination = new Pagination([ + * 'page' => 1, + * 'limit' => 10, + * 'total' => 120, + * 'method' => 'query', + * 'variable' => 'p', + * 'url' => new Uri('https://getkirby.com/blog') + * ]); + * ``` + * + * @param array $params + */ + public function __construct(array $params = []) + { + $kirby = App::instance(); + $config = $kirby->option('pagination', []); + $request = $kirby->request(); + + $params['limit'] = $params['limit'] ?? $config['limit'] ?? 20; + $params['method'] = $params['method'] ?? $config['method'] ?? 'param'; + $params['variable'] = $params['variable'] ?? $config['variable'] ?? 'page'; + + if (empty($params['url']) === true) { + $params['url'] = new Uri($kirby->url('current'), [ + 'params' => $request->params(), + 'query' => $request->query()->toArray(), + ]); + } + + if ($params['method'] === 'query') { + $params['page'] = $params['page'] ?? $params['url']->query()->get($params['variable']); + } elseif ($params['method'] === 'param') { + $params['page'] = $params['page'] ?? $params['url']->params()->get($params['variable']); + } + + parent::__construct($params); + + $this->method = $params['method']; + $this->url = $params['url']; + $this->variable = $params['variable']; + } + + /** + * Returns the Url for the first page + * + * @return string + */ + public function firstPageUrl(): string + { + return $this->pageUrl(1); + } + + /** + * Returns the Url for the last page + * + * @return string + */ + public function lastPageUrl(): string + { + return $this->pageUrl($this->lastPage()); + } + + /** + * Returns the Url for the next page. + * Returns null if there's no next page. + * + * @return string|null + */ + public function nextPageUrl(): ?string + { + if ($page = $this->nextPage()) { + return $this->pageUrl($page); + } + + return null; + } + + /** + * Returns the URL of the current page. + * If the `$page` variable is set, the URL + * for that page will be returned. + * + * @param int|null $page + * @return string|null + */ + public function pageUrl(int $page = null): ?string + { + if ($page === null) { + return $this->pageUrl($this->page()); + } + + $url = clone $this->url; + $variable = $this->variable; + + if ($this->hasPage($page) === false) { + return null; + } + + $pageValue = $page === 1 ? null : $page; + + if ($this->method === 'query') { + $url->query->$variable = $pageValue; + } elseif ($this->method === 'param') { + $url->params->$variable = $pageValue; + } else { + return null; + } + + return $url->toString(); + } + + /** + * Returns the Url for the previous page. + * Returns null if there's no previous page. + * + * @return string|null + */ + public function prevPageUrl(): ?string + { + if ($page = $this->prevPage()) { + return $this->pageUrl($page); + } + + return null; + } +} diff --git a/kirby/src/Cms/Panel.php b/kirby/src/Cms/Panel.php new file mode 100644 index 0000000..4941a76 --- /dev/null +++ b/kirby/src/Cms/Panel.php @@ -0,0 +1,139 @@ + + * @link https://getkirby.com + * @copyright Bastian Allgeier GmbH + * @license https://getkirby.com/license + */ +class Panel +{ + /** + * Returns custom css path for panel ui + * + * @param \Kirby\Cms\App $kirby + * @return string|false + */ + public static function customCss(App $kirby) + { + if ($css = $kirby->option('panel.css')) { + $asset = asset($css); + + if ($asset->exists() === true) { + return $asset->url() . '?' . $asset->modified(); + } + } + + return false; + } + + /** + * Returns predefined icons path as sprite svg file + * + * @param \Kirby\Cms\App $kirby + * @return string + */ + public static function icons(App $kirby): string + { + return F::read($kirby->root('kirby') . '/panel/dist/img/icons.svg'); + } + + /** + * Links all dist files in the media folder + * and returns the link to the requested asset + * + * @param \Kirby\Cms\App $kirby + * @return bool + * @throws \Exception If Panel assets could not be moved to the public directory + */ + public static function link(App $kirby): bool + { + $mediaRoot = $kirby->root('media') . '/panel'; + $panelRoot = $kirby->root('panel') . '/dist'; + $versionHash = $kirby->versionHash(); + $versionRoot = $mediaRoot . '/' . $versionHash; + + // check if the version already exists + if (is_dir($versionRoot) === true) { + return false; + } + + // delete the panel folder and all previous versions + Dir::remove($mediaRoot); + + // recreate the panel folder + Dir::make($mediaRoot, true); + + // create a symlink to the dist folder + if (Dir::copy($panelRoot, $versionRoot) !== true) { + throw new Exception('Panel assets could not be linked'); + } + + return true; + } + + /** + * Renders the main panel view + * + * @param \Kirby\Cms\App $kirby + * @return \Kirby\Http\Response + */ + public static function render(App $kirby) + { + try { + if (static::link($kirby) === true) { + usleep(1); + go($kirby->url('index') . '/' . $kirby->path()); + } + } catch (Throwable $e) { + die('The Panel assets cannot be installed properly. ' . $e->getMessage()); + } + + // get the uri object for the panel url + $uri = new Uri($url = $kirby->url('panel')); + + // fetch all plugins + $plugins = new PanelPlugins(); + + $view = new View($kirby->root('kirby') . '/views/panel.php', [ + 'kirby' => $kirby, + 'config' => $kirby->option('panel'), + 'assetUrl' => $kirby->url('media') . '/panel/' . $kirby->versionHash(), + 'customCss' => static::customCss($kirby), + 'icons' => static::icons($kirby), + 'pluginCss' => $plugins->url('css'), + 'pluginJs' => $plugins->url('js'), + 'panelUrl' => $uri->path()->toString(true) . '/', + 'nonce' => $kirby->nonce(), + 'options' => [ + 'url' => $url, + 'site' => $kirby->url('index'), + 'api' => $kirby->url('api'), + 'csrf' => $kirby->option('api.csrf') ?? csrf(), + 'translation' => 'en', + 'debug' => $kirby->option('debug', false), + 'search' => [ + 'limit' => $kirby->option('panel.search.limit') ?? 10 + ] + ] + ]); + + return new Response($view->render()); + } +} diff --git a/kirby/src/Cms/PanelPlugins.php b/kirby/src/Cms/PanelPlugins.php new file mode 100644 index 0000000..e05caea --- /dev/null +++ b/kirby/src/Cms/PanelPlugins.php @@ -0,0 +1,108 @@ + + * @link https://getkirby.com + * @copyright Bastian Allgeier GmbH + * @license https://getkirby.com/license + */ +class PanelPlugins +{ + /** + * Cache of all collected plugin files + * + * @var array + */ + public $files; + + /** + * Collects and returns the plugin files for all plugins + * + * @return array + */ + public function files(): array + { + if ($this->files !== null) { + return $this->files; + } + + $this->files = []; + + foreach (App::instance()->plugins() as $plugin) { + $this->files[] = $plugin->root() . '/index.css'; + $this->files[] = $plugin->root() . '/index.js'; + } + + return $this->files; + } + + /** + * Returns the last modification + * of the collected plugin files + * + * @return int + */ + public function modified(): int + { + $files = $this->files(); + $modified = [0]; + + foreach ($files as $file) { + $modified[] = F::modified($file); + } + + return max($modified); + } + + /** + * Read the files from all plugins and concatenate them + * + * @param string $type + * @return string + */ + public function read(string $type): string + { + $dist = []; + + foreach ($this->files() as $file) { + if (F::extension($file) === $type) { + if ($content = F::read($file)) { + if ($type === 'js') { + $content = trim($content); + + // make sure that each plugin is ended correctly + if (Str::endsWith($content, ';') === false) { + $content .= ';'; + } + } + + $dist[] = $content; + } + } + } + + return implode(PHP_EOL . PHP_EOL, $dist); + } + + /** + * Absolute url to the cache file + * This is used by the panel to link the plugins + * + * @param string $type + * @return string + */ + public function url(string $type): string + { + return App::instance()->url('media') . '/plugins/index.' . $type . '?' . $this->modified(); + } +} diff --git a/kirby/src/Cms/Permissions.php b/kirby/src/Cms/Permissions.php new file mode 100644 index 0000000..1daac9e --- /dev/null +++ b/kirby/src/Cms/Permissions.php @@ -0,0 +1,230 @@ + + * @link https://getkirby.com + * @copyright Bastian Allgeier GmbH + * @license https://getkirby.com/license + */ +class Permissions +{ + /** + * @var array + */ + public static $extendedActions = []; + + /** + * @var array + */ + protected $actions = [ + 'access' => [ + 'panel' => true, + 'settings' => true, + 'site' => true, + 'users' => true, + ], + 'files' => [ + 'changeName' => true, + 'create' => true, + 'delete' => true, + 'read' => true, + 'replace' => true, + 'update' => true + ], + 'languages' => [ + 'create' => true, + 'delete' => true + ], + 'pages' => [ + 'changeSlug' => true, + 'changeStatus' => true, + 'changeTemplate' => true, + 'changeTitle' => true, + 'create' => true, + 'delete' => true, + 'duplicate' => true, + 'preview' => true, + 'read' => true, + 'sort' => true, + 'update' => true + ], + 'site' => [ + 'changeTitle' => true, + 'update' => true + ], + 'users' => [ + 'changeEmail' => true, + 'changeLanguage' => true, + 'changeName' => true, + 'changePassword' => true, + 'changeRole' => true, + 'create' => true, + 'delete' => true, + 'update' => true + ], + 'user' => [ + 'changeEmail' => true, + 'changeLanguage' => true, + 'changeName' => true, + 'changePassword' => true, + 'changeRole' => true, + 'delete' => true, + 'update' => true + ] + ]; + + /** + * Permissions constructor + * + * @param array $settings + * @throws \Kirby\Exception\InvalidArgumentException + */ + public function __construct($settings = []) + { + // dynamically register the extended actions + foreach (static::$extendedActions as $key => $actions) { + if (isset($this->actions[$key]) === true) { + throw new InvalidArgumentException('The action ' . $key . ' is already a core action'); + } + + $this->actions[$key] = $actions; + } + + if (is_array($settings) === true) { + return $this->setCategories($settings); + } + + if (is_bool($settings) === true) { + return $this->setAll($settings); + } + } + + /** + * @param string|null $category + * @param string|null $action + * @return bool + */ + public function for(string $category = null, string $action = null): bool + { + if ($action === null) { + if ($this->hasCategory($category) === false) { + return false; + } + + return $this->actions[$category]; + } + + if ($this->hasAction($category, $action) === false) { + return false; + } + + return $this->actions[$category][$action]; + } + + /** + * @param string $category + * @param string $action + * @return bool + */ + protected function hasAction(string $category, string $action): bool + { + return $this->hasCategory($category) === true && array_key_exists($action, $this->actions[$category]) === true; + } + + /** + * @param string $category + * @return bool + */ + protected function hasCategory(string $category): bool + { + return array_key_exists($category, $this->actions) === true; + } + + /** + * @param string $category + * @param string $action + * @param $setting + * @return $this + */ + protected function setAction(string $category, string $action, $setting) + { + // wildcard to overwrite the entire category + if ($action === '*') { + return $this->setCategory($category, $setting); + } + + $this->actions[$category][$action] = $setting; + + return $this; + } + + /** + * @param bool $setting + * @return $this + */ + protected function setAll(bool $setting) + { + foreach ($this->actions as $categoryName => $actions) { + $this->setCategory($categoryName, $setting); + } + + return $this; + } + + /** + * @param array $settings + * @return $this + */ + protected function setCategories(array $settings) + { + foreach ($settings as $categoryName => $categoryActions) { + if (is_bool($categoryActions) === true) { + $this->setCategory($categoryName, $categoryActions); + } + + if (is_array($categoryActions) === true) { + foreach ($categoryActions as $actionName => $actionSetting) { + $this->setAction($categoryName, $actionName, $actionSetting); + } + } + } + + return $this; + } + + /** + * @param string $category + * @param bool $setting + * @return $this + * @throws \Kirby\Exception\InvalidArgumentException + */ + protected function setCategory(string $category, bool $setting) + { + if ($this->hasCategory($category) === false) { + throw new InvalidArgumentException('Invalid permissions category'); + } + + foreach ($this->actions[$category] as $actionName => $actionSetting) { + $this->actions[$category][$actionName] = $setting; + } + + return $this; + } + + /** + * @return array + */ + public function toArray(): array + { + return $this->actions; + } +} diff --git a/kirby/src/Cms/Picker.php b/kirby/src/Cms/Picker.php new file mode 100644 index 0000000..c729396 --- /dev/null +++ b/kirby/src/Cms/Picker.php @@ -0,0 +1,176 @@ + + * @link https://getkirby.com + * @copyright Bastian Allgeier GmbH + * @license https://getkirby.com/license + */ +abstract class Picker +{ + /** + * @var \Kirby\Cms\App + */ + protected $kirby; + + /** + * @var array + */ + protected $options; + + /** + * @var \Kirby\Cms\Site + */ + protected $site; + + /** + * Creates a new Picker instance + * + * @param array $params + */ + public function __construct(array $params = []) + { + $this->options = array_merge($this->defaults(), $params); + $this->kirby = $this->options['model']->kirby(); + $this->site = $this->kirby->site(); + } + + /** + * Return the array of default values + * + * @return array + */ + protected function defaults(): array + { + // default params + return [ + // image settings (ratio, cover, etc.) + 'image' => [], + // query template for the info field + 'info' => false, + // number of users displayed per pagination page + 'limit' => 20, + // optional mapping function for the result array + 'map' => null, + // the reference model + 'model' => site(), + // current page when paginating + 'page' => 1, + // a query string to fetch specific items + 'query' => null, + // search query + 'search' => null, + // query template for the text field + 'text' => null + ]; + } + + /** + * Fetches all items for the picker + * + * @return \Kirby\Cms\Collection|null + */ + abstract public function items(); + + /** + * Converts all given items to an associative + * array that is already optimized for the + * panel picker component. + * + * @param \Kirby\Cms\Collection|null $items + * @return array + */ + public function itemsToArray($items = null): array + { + if ($items === null) { + return []; + } + + $result = []; + + foreach ($items as $index => $item) { + if (empty($this->options['map']) === false) { + $result[] = $this->options['map']($item); + } else { + $result[] = $item->panelPickerData([ + 'image' => $this->options['image'], + 'info' => $this->options['info'], + 'model' => $this->options['model'], + 'text' => $this->options['text'], + ]); + } + } + + return $result; + } + + /** + * Apply pagination to the collection + * of items according to the options. + * + * @param \Kirby\Cms\Collection $items + * @return \Kirby\Cms\Collection + */ + public function paginate(Collection $items) + { + return $items->paginate([ + 'limit' => $this->options['limit'], + 'page' => $this->options['page'] + ]); + } + + /** + * Return the most relevant pagination + * info as array + * + * @param \Kirby\Cms\Pagination $pagination + * @return array + */ + public function paginationToArray(Pagination $pagination): array + { + return [ + 'limit' => $pagination->limit(), + 'page' => $pagination->page(), + 'total' => $pagination->total() + ]; + } + + /** + * Search through the collection of items + * if not deactivate in the options + * + * @param \Kirby\Cms\Collection $items + * @return \Kirby\Cms\Collection + */ + public function search(Collection $items) + { + if (empty($this->options['search']) === false) { + return $items->search($this->options['search']); + } + + return $items; + } + + /** + * Returns an associative array + * with all information for the picker. + * This will be passed directly to the API. + * + * @return array + */ + public function toArray(): array + { + $items = $this->items(); + + return [ + 'data' => $this->itemsToArray($items), + 'pagination' => $this->paginationToArray($items->pagination()), + ]; + } +} diff --git a/kirby/src/Cms/Plugin.php b/kirby/src/Cms/Plugin.php new file mode 100644 index 0000000..d74c7ff --- /dev/null +++ b/kirby/src/Cms/Plugin.php @@ -0,0 +1,158 @@ + + * @link https://getkirby.com + * @copyright Bastian Allgeier GmbH + * @license https://getkirby.com/license + */ +class Plugin extends Model +{ + protected $extends; + protected $info; + protected $name; + protected $root; + + /** + * @param string $key + * @param array|null $arguments + * @return mixed|null + */ + public function __call(string $key, array $arguments = null) + { + return $this->info()[$key] ?? null; + } + + /** + * Plugin constructor + * + * @param string $name + * @param array $extends + */ + public function __construct(string $name, array $extends = []) + { + $this->setName($name); + $this->extends = $extends; + $this->root = $extends['root'] ?? dirname(debug_backtrace()[0]['file']); + + unset($this->extends['root']); + } + + /** + * @return array + */ + public function extends(): array + { + return $this->extends; + } + + /** + * @return array + */ + public function info(): array + { + if (is_array($this->info) === true) { + return $this->info; + } + + try { + $info = Data::read($this->manifest()); + } catch (Exception $e) { + // there is no manifest file or it is invalid + $info = []; + } + + return $this->info = $info; + } + + /** + * @return string + */ + public function manifest(): string + { + return $this->root() . '/composer.json'; + } + + /** + * @return string + */ + public function mediaRoot(): string + { + return App::instance()->root('media') . '/plugins/' . $this->name(); + } + + /** + * @return string + */ + public function mediaUrl(): string + { + return App::instance()->url('media') . '/plugins/' . $this->name(); + } + + /** + * @return string + */ + public function name(): string + { + return $this->name; + } + + /** + * @param string $key + * @return mixed + */ + public function option(string $key) + { + return $this->kirby()->option($this->prefix() . '.' . $key); + } + + /** + * @return string + */ + public function prefix(): string + { + return str_replace('/', '.', $this->name()); + } + + /** + * @return string + */ + public function root(): string + { + return $this->root; + } + + /** + * @param string $name + * @return $this + * @throws \Kirby\Exception\InvalidArgumentException + */ + protected function setName(string $name) + { + if (preg_match('!^[a-z0-9-]+\/[a-z0-9-]+$!i', $name) !== 1) { + throw new InvalidArgumentException('The plugin name must follow the format "a-z0-9-/a-z0-9-"'); + } + + $this->name = $name; + return $this; + } + + /** + * @return array + */ + public function toArray(): array + { + return $this->propertiesToArray(); + } +} diff --git a/kirby/src/Cms/PluginAssets.php b/kirby/src/Cms/PluginAssets.php new file mode 100644 index 0000000..20f53c8 --- /dev/null +++ b/kirby/src/Cms/PluginAssets.php @@ -0,0 +1,84 @@ + + * @link https://getkirby.com + * @copyright Bastian Allgeier GmbH + * @license https://getkirby.com/license + */ +class PluginAssets +{ + /** + * Clean old/deprecated assets on every resolve + * + * @param string $pluginName + * @return void + */ + public static function clean(string $pluginName): void + { + if ($plugin = App::instance()->plugin($pluginName)) { + $root = $plugin->root() . '/assets'; + $media = $plugin->mediaRoot(); + $assets = Dir::index($media, true); + + foreach ($assets as $asset) { + $original = $root . '/' . $asset; + + if (file_exists($original) === false) { + $assetRoot = $media . '/' . $asset; + + if (is_file($assetRoot) === true) { + F::remove($assetRoot); + } else { + Dir::remove($assetRoot); + } + } + } + } + } + + /** + * Create a symlink for a plugin asset and + * return the public URL + * + * @param string $pluginName + * @param string $filename + * @return \Kirby\Cms\Response|null + */ + public static function resolve(string $pluginName, string $filename) + { + if ($plugin = App::instance()->plugin($pluginName)) { + $source = $plugin->root() . '/assets/' . $filename; + + if (F::exists($source, $plugin->root()) === true) { + // do some spring cleaning for older files + static::clean($pluginName); + + $target = $plugin->mediaRoot() . '/' . $filename; + $url = $plugin->mediaUrl() . '/' . $filename; + + // create the plugin directory first + Dir::make($plugin->mediaRoot(), true); + + if (F::link($source, $target, 'symlink') === true) { + return Response::redirect($url); + } + + return Response::file($source); + } + } + + return null; + } +} diff --git a/kirby/src/Cms/R.php b/kirby/src/Cms/R.php new file mode 100644 index 0000000..9c9c354 --- /dev/null +++ b/kirby/src/Cms/R.php @@ -0,0 +1,25 @@ + + * @link https://getkirby.com + * @copyright Bastian Allgeier GmbH + * @license https://getkirby.com/license + */ +class R extends Facade +{ + /** + * @return \Kirby\Http\Request + */ + public static function instance() + { + return App::instance()->request(); + } +} diff --git a/kirby/src/Cms/Responder.php b/kirby/src/Cms/Responder.php new file mode 100644 index 0000000..4ea8647 --- /dev/null +++ b/kirby/src/Cms/Responder.php @@ -0,0 +1,313 @@ + + * @link https://getkirby.com + * @copyright Bastian Allgeier GmbH + * @license https://getkirby.com/license + */ +class Responder +{ + /** + * Timestamp when the response expires + * in Kirby's cache + * + * @var int|null + */ + protected $expires = null; + + /** + * HTTP status code + * + * @var int + */ + protected $code = null; + + /** + * Response body + * + * @var string + */ + protected $body = null; + + /** + * Flag that defines whether the current + * response can be cached by Kirby's cache + * + * @var string + */ + protected $cache = true; + + /** + * HTTP headers + * + * @var array + */ + protected $headers = []; + + /** + * Content type + * + * @var string + */ + protected $type = null; + + /** + * Creates and sends the response + * + * @return string + */ + public function __toString(): string + { + return (string)$this->send(); + } + + /** + * Setter and getter for the response body + * + * @param string|null $body + * @return string|$this + */ + public function body(string $body = null) + { + if ($body === null) { + return $this->body; + } + + $this->body = $body; + return $this; + } + + /** + * Setter and getter for the flag that defines + * whether the current response can be cached + * by Kirby's cache + * @since 3.5.5 + * + * @param bool|null $cache + * @return bool|$this + */ + public function cache(?bool $cache = null) + { + if ($cache === null) { + return $this->cache; + } + + $this->cache = $cache; + return $this; + } + + /** + * Setter and getter for the cache expiry + * timestamp for Kirby's cache + * @since 3.5.5 + * + * @param int|string|null $expires Timestamp, number of minutes or time string to parse + * @param bool $override If `true`, the already defined timestamp will be overridden + * @return int|null|$this + */ + public function expires($expires = null, bool $override = false) + { + // getter + if ($expires === null && $override === false) { + return $this->expires; + } + + // explicit un-setter + if ($expires === null) { + $this->expires = null; + return $this; + } + + // normalize the value to an integer timestamp + if (is_int($expires) === true && $expires < 1000000000) { + // number of minutes + $expires = time() + ($expires * 60); + } elseif (is_int($expires) !== true) { + // time string + $parsedExpires = strtotime($expires); + + if (is_int($parsedExpires) !== true) { + throw new InvalidArgumentException('Invalid time string "' . $expires . '"'); + } + + $expires = $parsedExpires; + } + + // by default only ever *reduce* the cache expiry time + if ( + $override === true || + $this->expires === null || + $expires < $this->expires + ) { + $this->expires = $expires; + } + + return $this; + } + + /** + * Setter and getter for the status code + * + * @param int|null $code + * @return int|$this + */ + public function code(int $code = null) + { + if ($code === null) { + return $this->code; + } + + $this->code = $code; + return $this; + } + + /** + * Construct response from an array + * + * @param array $response + */ + public function fromArray(array $response): void + { + $this->body($response['body'] ?? null); + $this->expires($response['expires'] ?? null); + $this->code($response['code'] ?? null); + $this->headers($response['headers'] ?? null); + $this->type($response['type'] ?? null); + } + + /** + * Setter and getter for a single header + * + * @param string $key + * @param string|false|null $value + * @param bool $lazy If `true`, an existing header value is not overridden + * @return string|$this + */ + public function header(string $key, $value = null, bool $lazy = false) + { + if ($value === null) { + return $this->headers[$key] ?? null; + } + + if ($value === false) { + unset($this->headers[$key]); + return $this; + } + + if ($lazy === true && isset($this->headers[$key]) === true) { + return $this; + } + + $this->headers[$key] = $value; + return $this; + } + + /** + * Setter and getter for all headers + * + * @param array|null $headers + * @return array|$this + */ + public function headers(array $headers = null) + { + if ($headers === null) { + return $this->headers; + } + + $this->headers = $headers; + return $this; + } + + /** + * Shortcut to configure a json response + * + * @param array|null $json + * @return string|$this + */ + public function json(array $json = null) + { + if ($json !== null) { + $this->body(json_encode($json)); + } + + return $this->type('application/json'); + } + + /** + * Shortcut to create a redirect response + * + * @param string|null $location + * @param int|null $code + * @return $this + */ + public function redirect(?string $location = null, ?int $code = null) + { + $location = Url::to($location ?? '/'); + $location = Url::unIdn($location); + + return $this + ->header('Location', (string)$location) + ->code($code ?? 302); + } + + /** + * Creates and returns the response object from the config + * + * @param string|null $body + * @return \Kirby\Cms\Response + */ + public function send(string $body = null) + { + if ($body !== null) { + $this->body($body); + } + + return new Response($this->toArray()); + } + + /** + * Converts the response configuration + * to an array + * + * @return array + */ + public function toArray(): array + { + return [ + 'body' => $this->body, + 'code' => $this->code, + 'headers' => $this->headers, + 'type' => $this->type, + ]; + } + + /** + * Setter and getter for the content type + * + * @param string|null $type + * @return string|$this + */ + public function type(string $type = null) + { + if ($type === null) { + return $this->type; + } + + if (Str::contains($type, '/') === false) { + $type = Mime::fromExtension($type); + } + + $this->type = $type; + return $this; + } +} diff --git a/kirby/src/Cms/Response.php b/kirby/src/Cms/Response.php new file mode 100644 index 0000000..7882867 --- /dev/null +++ b/kirby/src/Cms/Response.php @@ -0,0 +1,30 @@ + + * @link https://getkirby.com + * @copyright Bastian Allgeier GmbH + * @license https://getkirby.com/license + */ +class Response extends \Kirby\Http\Response +{ + /** + * Adjusted redirect creation which + * parses locations with the Url::to method + * first. + * + * @param string $location + * @param int $code + * @return static + */ + public static function redirect(string $location = '/', int $code = 302) + { + return parent::redirect(Url::to($location), $code); + } +} diff --git a/kirby/src/Cms/Role.php b/kirby/src/Cms/Role.php new file mode 100644 index 0000000..217d8b5 --- /dev/null +++ b/kirby/src/Cms/Role.php @@ -0,0 +1,232 @@ + + * @link https://getkirby.com + * @copyright Bastian Allgeier GmbH + * @license https://getkirby.com/license + */ +class Role extends Model +{ + protected $description; + protected $name; + protected $permissions; + protected $title; + + public function __construct(array $props) + { + $this->setProperties($props); + } + + /** + * Improved `var_dump` output + * + * @return array + */ + public function __debugInfo(): array + { + return $this->toArray(); + } + + /** + * @return string + */ + public function __toString(): string + { + return $this->name(); + } + + /** + * @param array $inject + * @return static + */ + public static function admin(array $inject = []) + { + try { + return static::load('admin'); + } catch (Exception $e) { + return static::factory(static::defaults()['admin'], $inject); + } + } + + /** + * @return array + */ + protected static function defaults(): array + { + return [ + 'admin' => [ + 'name' => 'admin', + 'description' => I18n::translate('role.admin.description'), + 'title' => I18n::translate('role.admin.title'), + 'permissions' => true, + ], + 'nobody' => [ + 'name' => 'nobody', + 'description' => I18n::translate('role.nobody.description'), + 'title' => I18n::translate('role.nobody.title'), + 'permissions' => false, + ] + ]; + } + + /** + * @return mixed + */ + public function description() + { + return $this->description; + } + + /** + * @param array $props + * @param array $inject + * @return static + */ + public static function factory(array $props, array $inject = []) + { + return new static($props + $inject); + } + + /** + * @return string + */ + public function id(): string + { + return $this->name(); + } + + /** + * @return bool + */ + public function isAdmin(): bool + { + return $this->name() === 'admin'; + } + + /** + * @return bool + */ + public function isNobody(): bool + { + return $this->name() === 'nobody'; + } + + /** + * @param string $file + * @param array $inject + * @return static + */ + public static function load(string $file, array $inject = []) + { + $data = Data::read($file); + $data['name'] = F::name($file); + + return static::factory($data, $inject); + } + + /** + * @return string + */ + public function name(): string + { + return $this->name; + } + + /** + * @param array $inject + * @return static + */ + public static function nobody(array $inject = []) + { + try { + return static::load('nobody'); + } catch (Exception $e) { + return static::factory(static::defaults()['nobody'], $inject); + } + } + + /** + * @return \Kirby\Cms\Permissions + */ + public function permissions() + { + return $this->permissions; + } + + /** + * @param mixed $description + * @return $this + */ + protected function setDescription($description = null) + { + $this->description = I18n::translate($description, $description); + return $this; + } + + /** + * @param string $name + * @return $this + */ + protected function setName(string $name) + { + $this->name = $name; + return $this; + } + + /** + * @param mixed $permissions + * @return $this + */ + protected function setPermissions($permissions = null) + { + $this->permissions = new Permissions($permissions); + return $this; + } + + /** + * @param mixed $title + * @return $this + */ + protected function setTitle($title = null) + { + $this->title = I18n::translate($title, $title); + return $this; + } + + /** + * @return string + */ + public function title(): string + { + return $this->title = $this->title ?? ucfirst($this->name()); + } + + /** + * Converts the most important role + * properties to an array + * + * @return array + */ + public function toArray(): array + { + return [ + 'description' => $this->description(), + 'id' => $this->id(), + 'name' => $this->name(), + 'permissions' => $this->permissions()->toArray(), + 'title' => $this->title(), + ]; + } +} diff --git a/kirby/src/Cms/Roles.php b/kirby/src/Cms/Roles.php new file mode 100644 index 0000000..29a77e8 --- /dev/null +++ b/kirby/src/Cms/Roles.php @@ -0,0 +1,139 @@ + + * @link https://getkirby.com + * @copyright Bastian Allgeier GmbH + * @license https://getkirby.com/license + */ +class Roles extends Collection +{ + /** + * Returns a filtered list of all + * roles that can be created by the + * current user + * + * @return $this|static + * @throws \Exception + */ + public function canBeChanged() + { + if (App::instance()->user()) { + return $this->filter(function ($role) { + $newUser = new User([ + 'email' => 'test@getkirby.com', + 'role' => $role->id() + ]); + + return $newUser->permissions()->can('changeRole'); + }); + } + + return $this; + } + + /** + * Returns a filtered list of all + * roles that can be created by the + * current user + * + * @return $this|static + * @throws \Exception + */ + public function canBeCreated() + { + if (App::instance()->user()) { + return $this->filter(function ($role) { + $newUser = new User([ + 'email' => 'test@getkirby.com', + 'role' => $role->id() + ]); + + return $newUser->permissions()->can('create'); + }); + } + + return $this; + } + + /** + * @param array $roles + * @param array $inject + * @return static + */ + public static function factory(array $roles, array $inject = []) + { + $collection = new static(); + + // read all user blueprints + foreach ($roles as $props) { + $role = Role::factory($props, $inject); + $collection->set($role->id(), $role); + } + + // always include the admin role + if ($collection->find('admin') === null) { + $collection->set('admin', Role::admin()); + } + + // return the collection sorted by name + return $collection->sort('name', 'asc'); + } + + /** + * @param string|null $root + * @param array $inject + * @return static + */ + public static function load(string $root = null, array $inject = []) + { + $roles = new static(); + + // load roles from plugins + foreach (App::instance()->extensions('blueprints') as $blueprintName => $blueprint) { + if (substr($blueprintName, 0, 6) !== 'users/') { + continue; + } + + if (is_array($blueprint) === true) { + $role = Role::factory($blueprint, $inject); + } else { + $role = Role::load($blueprint, $inject); + } + + $roles->set($role->id(), $role); + } + + // load roles from directory + if ($root !== null) { + foreach (glob($root . '/*.yml') as $file) { + $filename = basename($file); + + if ($filename === 'default.yml') { + continue; + } + + $role = Role::load($file, $inject); + $roles->set($role->id(), $role); + } + } + + // always include the admin role + if ($roles->find('admin') === null) { + $roles->set('admin', Role::admin($inject)); + } + + // return the collection sorted by name + return $roles->sort('name', 'asc'); + } +} diff --git a/kirby/src/Cms/S.php b/kirby/src/Cms/S.php new file mode 100644 index 0000000..5ef8425 --- /dev/null +++ b/kirby/src/Cms/S.php @@ -0,0 +1,25 @@ + + * @link https://getkirby.com + * @copyright Bastian Allgeier GmbH + * @license https://getkirby.com/license + */ +class S extends Facade +{ + /** + * @return \Kirby\Session\Session + */ + public static function instance() + { + return App::instance()->session(); + } +} diff --git a/kirby/src/Cms/Search.php b/kirby/src/Cms/Search.php new file mode 100644 index 0000000..efa9af7 --- /dev/null +++ b/kirby/src/Cms/Search.php @@ -0,0 +1,62 @@ + + * @link https://getkirby.com + * @copyright Bastian Allgeier GmbH + * @license https://getkirby.com/license + */ +class Search +{ + /** + * @param string|null $query + * @param array $params + * @return \Kirby\Cms\Files + */ + public static function files(string $query = null, $params = []) + { + return App::instance()->site()->index()->files()->search($query, $params); + } + + /** + * Native search method to search for anything within the collection + * + * @param \Kirby\Cms\Collection $collection + * @param string|null $query + * @param mixed $params + * @return \Kirby\Cms\Collection|bool + */ + public static function collection(Collection $collection, string $query = null, $params = []) + { + $kirby = App::instance(); + return ($kirby->component('search'))($kirby, $collection, $query, $params); + } + + /** + * @param string|null $query + * @param array $params + * @return \Kirby\Cms\Pages + */ + public static function pages(string $query = null, $params = []) + { + return App::instance()->site()->index()->search($query, $params); + } + + /** + * @param string|null $query + * @param array $params + * @return \Kirby\Cms\Users + */ + public static function users(string $query = null, $params = []) + { + return App::instance()->users()->search($query, $params); + } +} diff --git a/kirby/src/Cms/Section.php b/kirby/src/Cms/Section.php new file mode 100644 index 0000000..da82871 --- /dev/null +++ b/kirby/src/Cms/Section.php @@ -0,0 +1,107 @@ + + * @link https://getkirby.com + * @copyright Bastian Allgeier GmbH + * @license https://getkirby.com/license + */ +class Section extends Component +{ + /** + * Registry for all component mixins + * + * @var array + */ + public static $mixins = []; + + /** + * Registry for all component types + * + * @var array + */ + public static $types = []; + + + /** + * Section constructor. + * + * @param string $type + * @param array $attrs + * @throws \Kirby\Exception\InvalidArgumentException + */ + public function __construct(string $type, array $attrs = []) + { + if (isset($attrs['model']) === false) { + throw new InvalidArgumentException('Undefined section model'); + } + + if (is_a($attrs['model'], 'Kirby\Cms\Model') === false) { + throw new InvalidArgumentException('Invalid section model'); + } + + // use the type as fallback for the name + $attrs['name'] = $attrs['name'] ?? $type; + $attrs['type'] = $type; + + parent::__construct($type, $attrs); + } + + public function errors(): array + { + if (array_key_exists('errors', $this->methods) === true) { + return $this->methods['errors']->call($this); + } + + return $this->errors ?? []; + } + + /** + * @return \Kirby\Cms\App + */ + public function kirby() + { + return $this->model()->kirby(); + } + + /** + * @return \Kirby\Cms\Model + */ + public function model() + { + return $this->model; + } + + /** + * @return array + */ + public function toArray(): array + { + $array = parent::toArray(); + + unset($array['model']); + + return $array; + } + + /** + * @return array + */ + public function toResponse(): array + { + return array_merge([ + 'status' => 'ok', + 'code' => 200, + 'name' => $this->name, + 'type' => $this->type + ], $this->toArray()); + } +} diff --git a/kirby/src/Cms/Site.php b/kirby/src/Cms/Site.php new file mode 100644 index 0000000..650c053 --- /dev/null +++ b/kirby/src/Cms/Site.php @@ -0,0 +1,678 @@ + + * @link https://getkirby.com + * @copyright Bastian Allgeier GmbH + * @license https://getkirby.com/license + */ +class Site extends ModelWithContent +{ + const CLASS_ALIAS = 'site'; + + use SiteActions; + use HasChildren; + use HasFiles; + use HasMethods; + + /** + * The SiteBlueprint object + * + * @var \Kirby\Cms\SiteBlueprint + */ + protected $blueprint; + + /** + * The error page object + * + * @var \Kirby\Cms\Page + */ + protected $errorPage; + + /** + * The id of the error page, which is + * fetched in the errorPage method + * + * @var string + */ + protected $errorPageId = 'error'; + + /** + * The home page object + * + * @var \Kirby\Cms\Page + */ + protected $homePage; + + /** + * The id of the home page, which is + * fetched in the errorPage method + * + * @var string + */ + protected $homePageId = 'home'; + + /** + * Cache for the inventory array + * + * @var array + */ + protected $inventory; + + /** + * The current page object + * + * @var \Kirby\Cms\Page + */ + protected $page; + + /** + * The absolute path to the site directory + * + * @var string + */ + protected $root; + + /** + * The page url + * + * @var string + */ + protected $url; + + /** + * Modified getter to also return fields + * from the content + * + * @param string $method + * @param array $arguments + * @return mixed + */ + public function __call(string $method, array $arguments = []) + { + // public property access + if (isset($this->$method) === true) { + return $this->$method; + } + + // site methods + if ($this->hasMethod($method)) { + return $this->callMethod($method, $arguments); + } + + // return site content otherwise + return $this->content()->get($method, $arguments); + } + + /** + * Creates a new Site object + * + * @param array $props + */ + public function __construct(array $props = []) + { + $this->setProperties($props); + } + + /** + * Improved `var_dump` output + * + * @return array + */ + public function __debugInfo(): array + { + return array_merge($this->toArray(), [ + 'content' => $this->content(), + 'children' => $this->children(), + 'files' => $this->files(), + ]); + } + + /** + * Makes it possible to convert the site model + * to a string. Mostly useful for debugging. + * + * @return string + */ + public function __toString(): string + { + return $this->url(); + } + + /** + * Returns the url to the api endpoint + * + * @internal + * @param bool $relative + * @return string + */ + public function apiUrl(bool $relative = false): string + { + if ($relative === true) { + return 'site'; + } else { + return $this->kirby()->url('api') . '/site'; + } + } + + /** + * Returns the blueprint object + * + * @return \Kirby\Cms\SiteBlueprint + */ + public function blueprint() + { + if (is_a($this->blueprint, 'Kirby\Cms\SiteBlueprint') === true) { + return $this->blueprint; + } + + return $this->blueprint = SiteBlueprint::factory('site', null, $this); + } + + /** + * Builds a breadcrumb collection + * + * @return \Kirby\Cms\Pages + */ + public function breadcrumb() + { + // get all parents and flip the order + $crumb = $this->page()->parents()->flip(); + + // add the home page + $crumb->prepend($this->homePage()->id(), $this->homePage()); + + // add the active page + $crumb->append($this->page()->id(), $this->page()); + + return $crumb; + } + + /** + * Prepares the content for the write method + * + * @internal + * @param array $data + * @param string|null $languageCode + * @return array + */ + public function contentFileData(array $data, ?string $languageCode = null): array + { + return A::prepend($data, [ + 'title' => $data['title'] ?? null, + ]); + } + + /** + * Filename for the content file + * + * @internal + * @return string + */ + public function contentFileName(): string + { + return 'site'; + } + + /** + * Returns the error page object + * + * @return \Kirby\Cms\Page|null + */ + public function errorPage() + { + if (is_a($this->errorPage, 'Kirby\Cms\Page') === true) { + return $this->errorPage; + } + + if ($error = $this->find($this->errorPageId())) { + return $this->errorPage = $error; + } + + return null; + } + + /** + * Returns the global error page id + * + * @internal + * @return string + */ + public function errorPageId(): string + { + return $this->errorPageId ?? 'error'; + } + + /** + * Checks if the site exists on disk + * + * @return bool + */ + public function exists(): bool + { + return is_dir($this->root()) === true; + } + + /** + * Returns the home page object + * + * @return \Kirby\Cms\Page|null + */ + public function homePage() + { + if (is_a($this->homePage, 'Kirby\Cms\Page') === true) { + return $this->homePage; + } + + if ($home = $this->find($this->homePageId())) { + return $this->homePage = $home; + } + + return null; + } + + /** + * Returns the global home page id + * + * @internal + * @return string + */ + public function homePageId(): string + { + return $this->homePageId ?? 'home'; + } + + /** + * Creates an inventory of all files + * and children in the site directory + * + * @internal + * @return array + */ + public function inventory(): array + { + if ($this->inventory !== null) { + return $this->inventory; + } + + $kirby = $this->kirby(); + + return $this->inventory = Dir::inventory( + $this->root(), + $kirby->contentExtension(), + $kirby->contentIgnore(), + $kirby->multilang() + ); + } + + /** + * Compares the current object with the given site object + * + * @param mixed $site + * @return bool + */ + public function is($site): bool + { + if (is_a($site, 'Kirby\Cms\Site') === false) { + return false; + } + + return $this === $site; + } + + /** + * Returns the root to the media folder for the site + * + * @internal + * @return string + */ + public function mediaRoot(): string + { + return $this->kirby()->root('media') . '/site'; + } + + /** + * The site's base url for any files + * + * @internal + * @return string + */ + public function mediaUrl(): string + { + return $this->kirby()->url('media') . '/site'; + } + + /** + * Gets the last modification date of all pages + * in the content folder. + * + * @param string|null $format + * @param string|null $handler + * @return int|string + */ + public function modified(?string $format = null, ?string $handler = null) + { + return Dir::modified( + $this->root(), + $format, + $handler ?? $this->kirby()->option('date.handler', 'date') + ); + } + + /** + * Returns the current page if `$path` + * is not specified. Otherwise it will try + * to find a page by the given path. + * + * If no current page is set with the page + * prop, the home page will be returned if + * it can be found. (see `Site::homePage()`) + * + * @param string|null $path omit for current page, + * otherwise e.g. `notes/across-the-ocean` + * @return \Kirby\Cms\Page|null + */ + public function page(?string $path = null) + { + if ($path !== null) { + return $this->find($path); + } + + if (is_a($this->page, 'Kirby\Cms\Page') === true) { + return $this->page; + } + + try { + return $this->page = $this->homePage(); + } catch (LogicException $e) { + return $this->page = null; + } + } + + /** + * Alias for `Site::children()` + * + * @return \Kirby\Cms\Pages + */ + public function pages() + { + return $this->children(); + } + + /** + * Returns the full path without leading slash + * + * @internal + * @return string + */ + public function panelPath(): string + { + return 'site'; + } + + /** + * Returns the url to the editing view + * in the panel + * + * @internal + * @param bool $relative + * @return string + */ + public function panelUrl(bool $relative = false): string + { + if ($relative === true) { + return '/' . $this->panelPath(); + } else { + return $this->kirby()->url('panel') . '/' . $this->panelPath(); + } + } + + /** + * Returns the permissions object for this site + * + * @return \Kirby\Cms\SitePermissions + */ + public function permissions() + { + return new SitePermissions($this); + } + + /** + * Preview Url + * + * @internal + * @return string|null + */ + public function previewUrl(): ?string + { + $preview = $this->blueprint()->preview(); + + if ($preview === false) { + return null; + } + + if ($preview === true) { + $url = $this->url(); + } else { + $url = $preview; + } + + return $url; + } + + /** + * Returns the absolute path to the content directory + * + * @return string + */ + public function root(): string + { + return $this->root = $this->root ?? $this->kirby()->root('content'); + } + + /** + * Returns the SiteRules class instance + * which is being used in various methods + * to check for valid actions and input. + * + * @return \Kirby\Cms\SiteRules + */ + protected function rules() + { + return new SiteRules(); + } + + /** + * Search all pages in the site + * + * @param string|null $query + * @param array $params + * @return \Kirby\Cms\Pages + */ + public function search(?string $query = null, $params = []) + { + return $this->index()->search($query, $params); + } + + /** + * Sets the Blueprint object + * + * @param array|null $blueprint + * @return $this + */ + protected function setBlueprint(?array $blueprint = null) + { + if ($blueprint !== null) { + $blueprint['model'] = $this; + $this->blueprint = new SiteBlueprint($blueprint); + } + + return $this; + } + + /** + * Sets the id of the error page, which + * is used in the errorPage method + * to get the default error page if nothing + * else is set. + * + * @param string $id + * @return $this + */ + protected function setErrorPageId(string $id = 'error') + { + $this->errorPageId = $id; + return $this; + } + + /** + * Sets the id of the home page, which + * is used in the homePage method + * to get the default home page if nothing + * else is set. + * + * @param string $id + * @return $this + */ + protected function setHomePageId(string $id = 'home') + { + $this->homePageId = $id; + return $this; + } + + /** + * Sets the current page object + * + * @internal + * @param \Kirby\Cms\Page|null $page + * @return $this + */ + public function setPage(?Page $page = null) + { + $this->page = $page; + return $this; + } + + /** + * Sets the Url + * + * @param string|null $url + * @return $this + */ + protected function setUrl($url = null) + { + $this->url = $url; + return $this; + } + + /** + * Converts the most important site + * properties to an array + * + * @return array + */ + public function toArray(): array + { + return [ + 'children' => $this->children()->keys(), + 'content' => $this->content()->toArray(), + 'errorPage' => $this->errorPage() ? $this->errorPage()->id(): false, + 'files' => $this->files()->keys(), + 'homePage' => $this->homePage() ? $this->homePage()->id(): false, + 'page' => $this->page() ? $this->page()->id(): false, + 'title' => $this->title()->value(), + 'url' => $this->url(), + ]; + } + + /** + * Returns the Url + * + * @param string|null $language + * @return string + */ + public function url($language = null): string + { + if ($language !== null || $this->kirby()->multilang() === true) { + return $this->urlForLanguage($language); + } + + return $this->url ?? $this->kirby()->url(); + } + + /** + * Returns the translated url + * + * @internal + * @param string|null $languageCode + * @param array|null $options + * @return string + */ + public function urlForLanguage(?string $languageCode = null, ?array $options = null): string + { + if ($language = $this->kirby()->language($languageCode)) { + return $language->url(); + } + + return $this->kirby()->url(); + } + + /** + * Sets the current page by + * id or page object and + * returns the current page + * + * @internal + * @param string|\Kirby\Cms\Page $page + * @param string|null $languageCode + * @return \Kirby\Cms\Page + */ + public function visit($page, ?string $languageCode = null) + { + if ($languageCode !== null) { + $this->kirby()->setCurrentTranslation($languageCode); + $this->kirby()->setCurrentLanguage($languageCode); + } + + // convert ids to a Page object + if (is_string($page)) { + $page = $this->find($page); + } + + // handle invalid pages + if (is_a($page, 'Kirby\Cms\Page') === false) { + throw new InvalidArgumentException('Invalid page object'); + } + + // set the current active page + $this->setPage($page); + + // return the page + return $page; + } + + /** + * Checks if any content of the site has been + * modified after the given unix timestamp + * This is mainly used to auto-update the cache + * + * @param mixed $time + * @return bool + */ + public function wasModifiedAfter($time): bool + { + return Dir::wasModifiedAfter($this->root(), $time); + } +} diff --git a/kirby/src/Cms/SiteActions.php b/kirby/src/Cms/SiteActions.php new file mode 100644 index 0000000..4297f39 --- /dev/null +++ b/kirby/src/Cms/SiteActions.php @@ -0,0 +1,98 @@ + + * @link https://getkirby.com + * @copyright Bastian Allgeier GmbH + * @license https://getkirby.com/license + */ +trait SiteActions +{ + /** + * Commits a site action, by following these steps + * + * 1. checks the action rules + * 2. sends the before hook + * 3. commits the store action + * 4. sends the after hook + * 5. returns the result + * + * @param string $action + * @param mixed ...$arguments + * @param Closure $callback + * @return mixed + */ + protected function commit(string $action, array $arguments, Closure $callback) + { + $old = $this->hardcopy(); + $kirby = $this->kirby(); + $argumentValues = array_values($arguments); + + $this->rules()->$action(...$argumentValues); + $kirby->trigger('site.' . $action . ':before', $arguments); + + $result = $callback(...$argumentValues); + + $kirby->trigger('site.' . $action . ':after', ['newSite' => $result, 'oldSite' => $old]); + + $kirby->cache('pages')->flush(); + return $result; + } + + /** + * Change the site title + * + * @param string $title + * @param string|null $languageCode + * @return static + */ + public function changeTitle(string $title, string $languageCode = null) + { + $arguments = ['site' => $this, 'title' => $title, 'languageCode' => $languageCode]; + return $this->commit('changeTitle', $arguments, function ($site, $title, $languageCode) { + return $site->save(['title' => $title], $languageCode); + }); + } + + /** + * Creates a main page + * + * @param array $props + * @return \Kirby\Cms\Page + */ + public function createChild(array $props) + { + $props = array_merge($props, [ + 'url' => null, + 'num' => null, + 'parent' => null, + 'site' => $this, + ]); + + return Page::create($props); + } + + /** + * Clean internal caches + * + * @return $this + */ + public function purge() + { + $this->blueprint = null; + $this->children = null; + $this->content = null; + $this->files = null; + $this->inventory = null; + $this->translations = null; + + return $this; + } +} diff --git a/kirby/src/Cms/SiteBlueprint.php b/kirby/src/Cms/SiteBlueprint.php new file mode 100644 index 0000000..9b58538 --- /dev/null +++ b/kirby/src/Cms/SiteBlueprint.php @@ -0,0 +1,60 @@ + + * @link https://getkirby.com + * @copyright Bastian Allgeier GmbH + * @license https://getkirby.com/license + */ +class SiteBlueprint extends Blueprint +{ + /** + * Creates a new page blueprint object + * with the given props + * + * @param array $props + */ + public function __construct(array $props) + { + parent::__construct($props); + + // normalize all available page options + $this->props['options'] = $this->normalizeOptions( + $props['options'] ?? true, + // defaults + [ + 'changeTitle' => null, + 'update' => null, + ], + // aliases + [ + 'title' => 'changeTitle', + ] + ); + } + + /** + * Returns the preview settings + * The preview setting controls the "Open" + * button in the panel and redirects it to a + * different URL if necessary. + * + * @return string|bool + */ + public function preview() + { + $preview = $this->props['options']['preview'] ?? true; + + if (is_string($preview) === true) { + return $this->model->toString($preview); + } + + return $preview; + } +} diff --git a/kirby/src/Cms/SitePermissions.php b/kirby/src/Cms/SitePermissions.php new file mode 100644 index 0000000..aa61ea8 --- /dev/null +++ b/kirby/src/Cms/SitePermissions.php @@ -0,0 +1,17 @@ + + * @link https://getkirby.com + * @copyright Bastian Allgeier GmbH + * @license https://getkirby.com/license + */ +class SitePermissions extends ModelPermissions +{ + protected $category = 'site'; +} diff --git a/kirby/src/Cms/SiteRules.php b/kirby/src/Cms/SiteRules.php new file mode 100644 index 0000000..f4797fd --- /dev/null +++ b/kirby/src/Cms/SiteRules.php @@ -0,0 +1,58 @@ + + * @link https://getkirby.com + * @copyright Bastian Allgeier GmbH + * @license https://getkirby.com/license + */ +class SiteRules +{ + /** + * Validates if the site title can be changed + * + * @param \Kirby\Cms\Site $site + * @param string $title + * @return bool + * @throws \Kirby\Exception\InvalidArgumentException If the title is empty + * @throws \Kirby\Exception\PermissionException If the user is not allowed to change the title + */ + public static function changeTitle(Site $site, string $title): bool + { + if ($site->permissions()->changeTitle() !== true) { + throw new PermissionException(['key' => 'site.changeTitle.permission']); + } + + if (Str::length($title) === 0) { + throw new InvalidArgumentException(['key' => 'site.changeTitle.empty']); + } + + return true; + } + + /** + * Validates if the site can be updated + * + * @param \Kirby\Cms\Site $site + * @param array $content + * @return bool + * @throws \Kirby\Exception\PermissionException If the user is not allowed to update the site + */ + public static function update(Site $site, array $content = []): bool + { + if ($site->permissions()->update() !== true) { + throw new PermissionException(['key' => 'site.update.permission']); + } + + return true; + } +} diff --git a/kirby/src/Cms/Structure.php b/kirby/src/Cms/Structure.php new file mode 100644 index 0000000..c947d17 --- /dev/null +++ b/kirby/src/Cms/Structure.php @@ -0,0 +1,64 @@ + + * @link https://getkirby.com + * @copyright Bastian Allgeier GmbH + * @license https://getkirby.com/license + */ +class Structure extends Collection +{ + /** + * Creates a new Collection with the given objects + * + * @param array $objects Kirby\Cms\StructureObject` objects or props arrays + * @param object|null $parent + */ + public function __construct($objects = [], $parent = null) + { + $this->parent = $parent; + $this->set($objects); + } + + /** + * The internal setter for collection items. + * This makes sure that nothing unexpected ends + * up in the collection. You can pass arrays or + * StructureObjects + * + * @param string $id + * @param array|StructureObject $props + * @throws \Kirby\Exception\InvalidArgumentException + */ + public function __set(string $id, $props) + { + if (is_a($props, 'Kirby\Cms\StructureObject') === true) { + $object = $props; + } else { + if (is_array($props) === false) { + throw new InvalidArgumentException('Invalid structure data'); + } + + $object = new StructureObject([ + 'content' => $props, + 'id' => $props['id'] ?? $id, + 'parent' => $this->parent, + 'structure' => $this + ]); + } + + return parent::__set($object->id(), $object); + } +} diff --git a/kirby/src/Cms/StructureObject.php b/kirby/src/Cms/StructureObject.php new file mode 100644 index 0000000..a2cbeb7 --- /dev/null +++ b/kirby/src/Cms/StructureObject.php @@ -0,0 +1,209 @@ + + * @link https://getkirby.com + * @copyright Bastian Allgeier GmbH + * @license https://getkirby.com/license + */ +class StructureObject extends Model +{ + use HasSiblings; + + /** + * The content + * + * @var Content + */ + protected $content; + + /** + * @var string + */ + protected $id; + + /** + * @var \Kirby\Cms\Site|\Kirby\Cms\Page|\Kirby\Cms\File|\Kirby\Cms\User|null + */ + protected $parent; + + /** + * The parent Structure collection + * + * @var Structure + */ + protected $structure; + + /** + * Modified getter to also return fields + * from the object's content + * + * @param string $method + * @param array $arguments + * @return mixed + */ + public function __call(string $method, array $arguments = []) + { + // public property access + if (isset($this->$method) === true) { + return $this->$method; + } + + return $this->content()->get($method, $arguments); + } + + /** + * Creates a new StructureObject with the given props + * + * @param array $props + */ + public function __construct(array $props) + { + $this->setProperties($props); + } + + /** + * Returns the content + * + * @return \Kirby\Cms\Content + */ + public function content() + { + if (is_a($this->content, 'Kirby\Cms\Content') === true) { + return $this->content; + } + + if (is_array($this->content) !== true) { + $this->content = []; + } + + return $this->content = new Content($this->content, $this->parent()); + } + + /** + * Returns the required id + * + * @return string + */ + public function id(): string + { + return $this->id; + } + + /** + * Compares the current object with the given structure object + * + * @param mixed $structure + * @return bool + */ + public function is($structure): bool + { + if (is_a($structure, 'Kirby\Cms\StructureObject') === false) { + return false; + } + + return $this === $structure; + } + + /** + * Returns the parent Model object + * + * @return \Kirby\Cms\Model + */ + public function parent() + { + return $this->parent; + } + + /** + * Sets the Content object with the given parent + * + * @param array|null $content + * @return $this + */ + protected function setContent(array $content = null) + { + $this->content = $content; + return $this; + } + + /** + * Sets the id of the object. + * The id is required. The structure + * class will use the index, if no id is + * specified. + * + * @param string $id + * @return $this + */ + protected function setId(string $id) + { + $this->id = $id; + return $this; + } + + /** + * Sets the parent Model + * + * @return $this + * @param \Kirby\Cms\Site|\Kirby\Cms\Page|\Kirby\Cms\File|\Kirby\Cms\User|null $parent + */ + protected function setParent(Model $parent = null) + { + $this->parent = $parent; + return $this; + } + + /** + * Sets the parent Structure collection + * + * @param \Kirby\Cms\Structure|null $structure + * @return $this + */ + protected function setStructure(Structure $structure = null) + { + $this->structure = $structure; + return $this; + } + + /** + * Returns the parent Structure collection as siblings + * + * @return \Kirby\Cms\Structure + */ + protected function siblingsCollection() + { + return $this->structure; + } + + /** + * Converts all fields in the object to a + * plain associative array. The id is + * injected into the array afterwards + * to make sure it's always present and + * not overloaded in the content. + * + * @return array + */ + public function toArray(): array + { + $array = $this->content()->toArray(); + $array['id'] = $this->id(); + + ksort($array); + + return $array; + } +} diff --git a/kirby/src/Cms/System.php b/kirby/src/Cms/System.php new file mode 100644 index 0000000..436a5b4 --- /dev/null +++ b/kirby/src/Cms/System.php @@ -0,0 +1,545 @@ + + * @link https://getkirby.com + * @copyright Bastian Allgeier GmbH + * @license https://getkirby.com/license + */ +class System +{ + /** + * @var \Kirby\Cms\App + */ + protected $app; + + /** + * @param \Kirby\Cms\App $app + */ + public function __construct(App $app) + { + $this->app = $app; + + // try to create all folders that could be missing + $this->init(); + } + + /** + * Improved `var_dump` output + * + * @return array + */ + public function __debugInfo(): array + { + return $this->toArray(); + } + + /** + * Get an status array of all checks + * + * @return array + */ + public function status(): array + { + return [ + 'accounts' => $this->accounts(), + 'content' => $this->content(), + 'curl' => $this->curl(), + 'sessions' => $this->sessions(), + 'mbstring' => $this->mbstring(), + 'media' => $this->media(), + 'php' => $this->php(), + 'server' => $this->server(), + ]; + } + + /** + * Check for a writable accounts folder + * + * @return bool + */ + public function accounts(): bool + { + return is_writable($this->app->root('accounts')); + } + + /** + * Check for a writable content folder + * + * @return bool + */ + public function content(): bool + { + return is_writable($this->app->root('content')); + } + + /** + * Check for an existing curl extension + * + * @return bool + */ + public function curl(): bool + { + return extension_loaded('curl'); + } + + /** + * Returns the app's human-readable + * index URL without scheme + * + * @return string + */ + public function indexUrl(): string + { + return $this->app->url('index', true)->setScheme(null)->setSlash(false)->toString(); + } + + /** + * Create the most important folders + * if they don't exist yet + * + * @return void + * @throws \Kirby\Exception\PermissionException + */ + public function init() + { + // init /site/accounts + try { + Dir::make($this->app->root('accounts')); + } catch (Throwable $e) { + throw new PermissionException('The accounts directory could not be created'); + } + + // init /content + try { + Dir::make($this->app->root('content')); + } catch (Throwable $e) { + throw new PermissionException('The content directory could not be created'); + } + + // init /media + try { + Dir::make($this->app->root('media')); + } catch (Throwable $e) { + throw new PermissionException('The media directory could not be created'); + } + } + + /** + * Check if the panel is installable. + * On a public server the panel.install + * option must be explicitly set to true + * to get the installer up and running. + * + * @return bool + */ + public function isInstallable(): bool + { + return $this->isLocal() === true || $this->app->option('panel.install', false) === true; + } + + /** + * Check if Kirby is already installed + * + * @return bool + */ + public function isInstalled(): bool + { + return $this->app->users()->count() > 0; + } + + /** + * Check if this is a local installation + * + * @return bool + */ + public function isLocal(): bool + { + $server = $this->app->server(); + $visitor = $this->app->visitor(); + $host = $server->host(); + + if ($host === 'localhost') { + return true; + } + + if (Str::endsWith($host, '.local') === true) { + return true; + } + + if (Str::endsWith($host, '.test') === true) { + return true; + } + + if (in_array($visitor->ip(), ['::1', '127.0.0.1']) === true) { + // ensure that there is no reverse proxy in between + + if ( + isset($_SERVER['HTTP_X_FORWARDED_FOR']) === true && + in_array($_SERVER['HTTP_X_FORWARDED_FOR'], ['::1', '127.0.0.1']) === false + ) { + return false; + } + + if ( + isset($_SERVER['HTTP_CLIENT_IP']) === true && + in_array($_SERVER['HTTP_CLIENT_IP'], ['::1', '127.0.0.1']) === false + ) { + return false; + } + + // no reverse proxy or the real client also comes from localhost + return true; + } + + return false; + } + + /** + * Check if all tests pass + * + * @return bool + */ + public function isOk(): bool + { + return in_array(false, array_values($this->status()), true) === false; + } + + /** + * Normalizes the app's index URL for + * licensing purposes + * + * @param string|null $url Input URL, by default the app's index URL + * @return string Normalized URL + */ + protected function licenseUrl(string $url = null): string + { + if ($url === null) { + $url = $this->indexUrl(); + } + + // remove common "testing" subdomains as well as www. + // to ensure that installations of the same site have + // the same license URL; only for installations at /, + // subdirectory installations are difficult to normalize + if (Str::contains($url, '/') === false) { + if (Str::startsWith($url, 'www.')) { + return substr($url, 4); + } + + if (Str::startsWith($url, 'dev.')) { + return substr($url, 4); + } + + if (Str::startsWith($url, 'test.')) { + return substr($url, 5); + } + + if (Str::startsWith($url, 'staging.')) { + return substr($url, 8); + } + } + + return $url; + } + + /** + * Loads the license file and returns + * the license information if available + * + * @return string|bool License key or `false` if the current user has + * permissions for access.settings, otherwise just a + * boolean that tells whether a valid license is active + */ + public function license() + { + try { + $license = Json::read($this->app->root('config') . '/.license'); + } catch (Throwable $e) { + return false; + } + + // check for all required fields for the validation + if (isset( + $license['license'], + $license['order'], + $license['date'], + $license['email'], + $license['domain'], + $license['signature'] + ) !== true) { + return false; + } + + // build the license verification data + $data = [ + 'license' => $license['license'], + 'order' => $license['order'], + 'email' => hash('sha256', $license['email'] . 'kwAHMLyLPBnHEskzH9pPbJsBxQhKXZnX'), + 'domain' => $license['domain'], + 'date' => $license['date'] + ]; + + + // get the public key + $pubKey = F::read($this->app->root('kirby') . '/kirby.pub'); + + // verify the license signature + if (openssl_verify(json_encode($data), hex2bin($license['signature']), $pubKey, 'RSA-SHA256') !== 1) { + return false; + } + + // verify the URL + if ($this->licenseUrl() !== $this->licenseUrl($license['domain'])) { + return false; + } + + // only return the actual license key if the + // current user has appropriate permissions + $user = $this->app->user(); + if ($user && $user->role()->permissions()->for('access', 'settings') === true) { + return $license['license']; + } else { + return true; + } + } + + /** + * Returns the configured UI modes for the login form + * with their respective options + * + * @return array + * + * @throws \Kirby\Exception\InvalidArgumentException If the configuration is invalid + * (only in debug mode) + */ + public function loginMethods(): array + { + $default = ['password' => []]; + $methods = A::wrap($this->app->option('auth.methods', $default)); + + // normalize the syntax variants + $normalized = []; + $uses2fa = false; + foreach ($methods as $key => $value) { + if (is_int($key) === true) { + // ['password'] + $normalized[$value] = []; + } elseif ($value === true) { + // ['password' => true] + $normalized[$key] = []; + } else { + // ['password' => [...]] + $normalized[$key] = $value; + + if (isset($value['2fa']) === true && $value['2fa'] === true) { + $uses2fa = true; + } + } + } + + // 2FA must not be circumvented by code-based modes + foreach (['code', 'password-reset'] as $method) { + if ($uses2fa === true && isset($normalized[$method]) === true) { + unset($normalized[$method]); + + if ($this->app->option('debug') === true) { + $message = 'The "' . $method . '" login method cannot be enabled when 2FA is required'; + throw new InvalidArgumentException($message); + } + } + } + + // only one code-based mode can be active at once + if ( + isset($normalized['code']) === true && + isset($normalized['password-reset']) === true + ) { + unset($normalized['code']); + + if ($this->app->option('debug') === true) { + $message = 'The "code" and "password-reset" login methods cannot be enabled together'; + throw new InvalidArgumentException($message); + } + } + + return $normalized; + } + + /** + * Check for an existing mbstring extension + * + * @return bool + */ + public function mbString(): bool + { + return extension_loaded('mbstring'); + } + + /** + * Check for a writable media folder + * + * @return bool + */ + public function media(): bool + { + return is_writable($this->app->root('media')); + } + + /** + * Check for a valid PHP version + * + * @return bool + */ + public function php(): bool + { + return + version_compare(PHP_VERSION, '7.3.0', '>=') === true && + version_compare(PHP_VERSION, '8.1.0', '<') === true; + } + + /** + * Validates the license key + * and adds it to the .license file in the config + * folder if possible. + * + * @param string|null $license + * @param string|null $email + * @return bool + * @throws \Kirby\Exception\Exception + * @throws \Kirby\Exception\InvalidArgumentException + */ + public function register(string $license = null, string $email = null): bool + { + if (Str::startsWith($license, 'K3-PRO-') === false) { + throw new InvalidArgumentException([ + 'key' => 'license.format' + ]); + } + + if (V::email($email) === false) { + throw new InvalidArgumentException([ + 'key' => 'license.email' + ]); + } + + $response = Remote::get('https://licenses.getkirby.com/register', [ + 'data' => [ + 'license' => $license, + 'email' => $email, + 'domain' => $this->indexUrl() + ] + ]); + + if ($response->code() !== 200) { + throw new Exception($response->content()); + } + + // decode the response + $json = Json::decode($response->content()); + + // replace the email with the plaintext version + $json['email'] = $email; + + // where to store the license file + $file = $this->app->root('config') . '/.license'; + + // save the license information + Json::write($file, $json); + + if ($this->license() === false) { + throw new InvalidArgumentException([ + 'key' => 'license.verification' + ]); + } + + return true; + } + + /** + * Check for a valid server environment + * + * @return bool + */ + public function server(): bool + { + if ($servers = $this->app->option('servers')) { + $servers = A::wrap($servers); + } else { + $servers = [ + 'apache', + 'caddy', + 'litespeed', + 'nginx', + 'php' + ]; + } + + $software = $_SERVER['SERVER_SOFTWARE'] ?? null; + + return preg_match('!(' . implode('|', $servers) . ')!i', $software) > 0; + } + + /** + * Check for a writable sessions folder + * + * @return bool + */ + public function sessions(): bool + { + return is_writable($this->app->root('sessions')); + } + + /** + * Return the status as array + * + * @return array + */ + public function toArray(): array + { + return $this->status(); + } + + /** + * Upgrade to the new folder separator + * + * @param string $root + * @return void + */ + public static function upgradeContent(string $root) + { + $index = Dir::read($root); + + foreach ($index as $dir) { + $oldRoot = $root . '/' . $dir; + $newRoot = preg_replace('!\/([0-9]+)\-!', '/$1_', $oldRoot); + + if (is_dir($oldRoot) === true) { + Dir::move($oldRoot, $newRoot); + static::upgradeContent($newRoot); + } + } + } +} diff --git a/kirby/src/Cms/Template.php b/kirby/src/Cms/Template.php new file mode 100644 index 0000000..b7df18e --- /dev/null +++ b/kirby/src/Cms/Template.php @@ -0,0 +1,201 @@ + + * @link https://getkirby.com + * @copyright Bastian Allgeier GmbH + * @license https://getkirby.com/license + */ +class Template +{ + /** + * Global template data + * + * @var array + */ + public static $data = []; + + /** + * The name of the template + * + * @var string + */ + protected $name; + + /** + * Template type (html, json, etc.) + * + * @var string + */ + protected $type; + + /** + * Default template type if no specific type is set + * + * @var string + */ + protected $defaultType; + + /** + * Creates a new template object + * + * @param string $name + * @param string $type + * @param string $defaultType + */ + public function __construct(string $name, string $type = 'html', string $defaultType = 'html') + { + $this->name = strtolower($name); + $this->type = $type; + $this->defaultType = $defaultType; + } + + /** + * Converts the object to a simple string + * This is used in template filters for example + * + * @return string + */ + public function __toString(): string + { + return $this->name; + } + + /** + * Checks if the template exists + * + * @return bool + */ + public function exists(): bool + { + return file_exists($this->file()); + } + + /** + * Returns the expected template file extension + * + * @return string + */ + public function extension(): string + { + return 'php'; + } + + /** + * Returns the default template type + * + * @return string + */ + public function defaultType(): string + { + return $this->defaultType; + } + + /** + * Returns the place where templates are located + * in the site folder and and can be found in extensions + * + * @return string + */ + public function store(): string + { + return 'templates'; + } + + /** + * Detects the location of the template file + * if it exists. + * + * @return string|null + */ + public function file(): ?string + { + if ($this->hasDefaultType() === true) { + try { + // Try the default template in the default template directory. + return F::realpath($this->root() . '/' . $this->name() . '.' . $this->extension(), $this->root()); + } catch (Exception $e) { + // ignore errors, continue searching + } + + // Look for the default template provided by an extension. + $path = App::instance()->extension($this->store(), $this->name()); + + if ($path !== null) { + return $path; + } + } + + $name = $this->name() . '.' . $this->type(); + + try { + // Try the template with type extension in the default template directory. + return F::realpath($this->root() . '/' . $name . '.' . $this->extension(), $this->root()); + } catch (Exception $e) { + // Look for the template with type extension provided by an extension. + // This might be null if the template does not exist. + return App::instance()->extension($this->store(), $name); + } + } + + /** + * Returns the template name + * + * @return string + */ + public function name(): string + { + return $this->name; + } + + /** + * @param array $data + * @return string + */ + public function render(array $data = []): string + { + return Tpl::load($this->file(), $data); + } + + /** + * Returns the root to the templates directory + * + * @return string + */ + public function root(): string + { + return App::instance()->root($this->store()); + } + + /** + * Returns the template type + * + * @return string + */ + public function type(): string + { + return $this->type; + } + + /** + * Checks if the template uses the default type + * + * @return bool + */ + public function hasDefaultType(): bool + { + $type = $this->type(); + + return $type === null || $type === $this->defaultType(); + } +} diff --git a/kirby/src/Cms/Translation.php b/kirby/src/Cms/Translation.php new file mode 100644 index 0000000..8efe6ff --- /dev/null +++ b/kirby/src/Cms/Translation.php @@ -0,0 +1,195 @@ + + * @link https://getkirby.com + * @copyright Bastian Allgeier GmbH + * @license https://getkirby.com/license + */ +class Translation +{ + /** + * @var string + */ + protected $code; + + /** + * @var array + */ + protected $data = []; + + /** + * @param string $code + * @param array $data + */ + public function __construct(string $code, array $data) + { + $this->code = $code; + $this->data = $data; + } + + /** + * Improved `var_dump` output + * + * @return array + */ + public function __debugInfo(): array + { + return $this->toArray(); + } + + /** + * Returns the translation author + * + * @return string + */ + public function author(): string + { + return $this->get('translation.author', 'Kirby'); + } + + /** + * Returns the official translation code + * + * @return string + */ + public function code(): string + { + return $this->code; + } + + /** + * Returns an array with all + * translation strings + * + * @return array + */ + public function data(): array + { + return $this->data; + } + + /** + * Returns the translation data and merges + * it with the data from the default translation + * + * @return array + */ + public function dataWithFallback(): array + { + if ($this->code === 'en') { + return $this->data; + } + + // get the fallback array + $fallback = App::instance()->translation('en')->data(); + + return array_merge($fallback, $this->data); + } + + /** + * Returns the writing direction + * (ltr or rtl) + * + * @return string + */ + public function direction(): string + { + return $this->get('translation.direction', 'ltr'); + } + + /** + * Returns a single translation + * string by key + * + * @param string $key + * @param string|null $default + * @return string|null + */ + public function get(string $key, string $default = null): ?string + { + return $this->data[$key] ?? $default; + } + + /** + * Returns the translation id, + * which is also the code + * + * @return string + */ + public function id(): string + { + return $this->code; + } + + /** + * Loads the translation from the + * json file in Kirby's translations folder + * + * @param string $code + * @param string $root + * @param array $inject + * @return static + */ + public static function load(string $code, string $root, array $inject = []) + { + try { + $data = array_merge(Data::read($root), $inject); + } catch (Exception $e) { + $data = []; + } + + return new static($code, $data); + } + + /** + * Returns the PHP locale of the translation + * + * @return string + */ + public function locale(): string + { + $default = $this->code; + if (Str::contains($default, '_') !== true) { + $default .= '_' . strtoupper($this->code); + } + + return $this->get('translation.locale', $default); + } + + /** + * Returns the human-readable translation name. + * + * @return string + */ + public function name(): string + { + return $this->get('translation.name', $this->code); + } + + /** + * Converts the most important + * properties to an array + * + * @return array + */ + public function toArray(): array + { + return [ + 'code' => $this->code(), + 'data' => $this->data(), + 'name' => $this->name(), + 'author' => $this->author(), + ]; + } +} diff --git a/kirby/src/Cms/Translations.php b/kirby/src/Cms/Translations.php new file mode 100644 index 0000000..9f018af --- /dev/null +++ b/kirby/src/Cms/Translations.php @@ -0,0 +1,78 @@ + + * @link https://getkirby.com + * @copyright Bastian Allgeier GmbH + * @license https://getkirby.com/license + */ +class Translations extends Collection +{ + /** + * @param string $code + * @return void + */ + public function start(string $code): void + { + F::move($this->parent->contentFile('', true), $this->parent->contentFile($code, true)); + } + + /** + * @param string $code + * @return void + */ + public function stop(string $code): void + { + F::move($this->parent->contentFile($code, true), $this->parent->contentFile('', true)); + } + + /** + * @param array $translations + * @return static + */ + public static function factory(array $translations) + { + $collection = new static(); + + foreach ($translations as $code => $props) { + $translation = new Translation($code, $props); + $collection->data[$translation->code()] = $translation; + } + + return $collection; + } + + /** + * @param string $root + * @param array $inject + * @return static + */ + public static function load(string $root, array $inject = []) + { + $collection = new static(); + + foreach (Dir::read($root) as $filename) { + if (F::extension($filename) !== 'json') { + continue; + } + + $locale = F::name($filename); + $translation = Translation::load($locale, $root . '/' . $filename, $inject[$locale] ?? []); + + $collection->data[$locale] = $translation; + } + + return $collection; + } +} diff --git a/kirby/src/Cms/Url.php b/kirby/src/Cms/Url.php new file mode 100644 index 0000000..635b0ef --- /dev/null +++ b/kirby/src/Cms/Url.php @@ -0,0 +1,69 @@ + + * @link https://getkirby.com + * @copyright Bastian Allgeier GmbH + * @license https://getkirby.com/license + */ +class Url extends BaseUrl +{ + public static $home = null; + + /** + * Returns the Url to the homepage + * + * @return string + */ + public static function home(): string + { + return App::instance()->url(); + } + + /** + * Creates an absolute Url to a template asset if it exists. This is used in the `css()` and `js()` helpers + * + * @param string $assetPath + * @param string $extension + * @return string|null + */ + public static function toTemplateAsset(string $assetPath, string $extension) + { + $kirby = App::instance(); + $page = $kirby->site()->page(); + $path = $assetPath . '/' . $page->template() . '.' . $extension; + $file = $kirby->root('assets') . '/' . $path; + $url = $kirby->url('assets') . '/' . $path; + + return file_exists($file) === true ? $url : null; + } + + /** + * Smart resolver for internal and external urls + * + * @param string|null $path + * @param array|string|null $options Either an array of options for the Uri class or a language string + * @return string + */ + public static function to(string $path = null, $options = null): string + { + $kirby = App::instance(); + + return ($kirby->component('url'))($kirby, $path, $options, function (string $path = null, $options = null) use ($kirby) { + return ($kirby->nativeComponent('url'))($kirby, $path, $options); + }); + } +} diff --git a/kirby/src/Cms/User.php b/kirby/src/Cms/User.php new file mode 100644 index 0000000..eb558e8 --- /dev/null +++ b/kirby/src/Cms/User.php @@ -0,0 +1,959 @@ + + * @link https://getkirby.com + * @copyright Bastian Allgeier GmbH + * @license https://getkirby.com/license + */ +class User extends ModelWithContent +{ + const CLASS_ALIAS = 'user'; + + use HasFiles; + use HasMethods; + use HasSiblings; + use UserActions; + + /** + * @var UserBlueprint + */ + protected $blueprint; + + /** + * @var array + */ + protected $credentials; + + /** + * @var string + */ + protected $email; + + /** + * @var string + */ + protected $hash; + + /** + * @var string + */ + protected $id; + + /** + * @var array|null + */ + protected $inventory; + + /** + * @var string + */ + protected $language; + + /** + * All registered user methods + * + * @var array + */ + public static $methods = []; + + /** + * Registry with all User models + * + * @var array + */ + public static $models = []; + + /** + * @var \Kirby\Cms\Field + */ + protected $name; + + /** + * @var string + */ + protected $password; + + /** + * The user role + * + * @var string + */ + protected $role; + + /** + * Modified getter to also return fields + * from the content + * + * @param string $method + * @param array $arguments + * @return mixed + */ + public function __call(string $method, array $arguments = []) + { + // public property access + if (isset($this->$method) === true) { + return $this->$method; + } + + // user methods + if ($this->hasMethod($method)) { + return $this->callMethod($method, $arguments); + } + + // return site content otherwise + return $this->content()->get($method, $arguments); + } + + /** + * Creates a new User object + * + * @param array $props + */ + public function __construct(array $props) + { + $props['id'] = $props['id'] ?? $this->createId(); + $this->setProperties($props); + } + + /** + * Improved `var_dump` output + * + * @return array + */ + public function __debugInfo(): array + { + return array_merge($this->toArray(), [ + 'avatar' => $this->avatar(), + 'content' => $this->content(), + 'role' => $this->role() + ]); + } + + /** + * Returns the url to the api endpoint + * + * @internal + * @param bool $relative + * @return string + */ + public function apiUrl(bool $relative = false): string + { + if ($relative === true) { + return 'users/' . $this->id(); + } else { + return $this->kirby()->url('api') . '/users/' . $this->id(); + } + } + + /** + * Returns the File object for the avatar or null + * + * @return \Kirby\Cms\File|null + */ + public function avatar() + { + return $this->files()->template('avatar')->first(); + } + + /** + * Returns the UserBlueprint object + * + * @return \Kirby\Cms\Blueprint + */ + public function blueprint() + { + if (is_a($this->blueprint, 'Kirby\Cms\Blueprint') === true) { + return $this->blueprint; + } + + try { + return $this->blueprint = UserBlueprint::factory('users/' . $this->role(), 'users/default', $this); + } catch (Exception $e) { + return $this->blueprint = new UserBlueprint([ + 'model' => $this, + 'name' => 'default', + 'title' => 'Default', + ]); + } + } + + /** + * Prepares the content for the write method + * + * @internal + * @param array $data + * @param string $languageCode|null Not used so far + * @return array + */ + public function contentFileData(array $data, string $languageCode = null): array + { + // remove stuff that has nothing to do in the text files + unset( + $data['email'], + $data['language'], + $data['name'], + $data['password'], + $data['role'] + ); + + return $data; + } + + /** + * Filename for the content file + * + * @internal + * @return string + */ + public function contentFileName(): string + { + return 'user'; + } + + protected function credentials(): array + { + return $this->credentials = $this->credentials ?? $this->readCredentials(); + } + + /** + * Returns the user email address + * + * @return string + */ + public function email(): ?string + { + return $this->email = $this->email ?? $this->credentials()['email'] ?? null; + } + + /** + * Checks if the user exists + * + * @return bool + */ + public function exists(): bool + { + return is_file($this->contentFile('default')) === true; + } + + /** + * Constructs a User object and also + * takes User models into account. + * + * @internal + * @param mixed $props + * @return static + */ + public static function factory($props) + { + if (empty($props['model']) === false) { + return static::model($props['model'], $props); + } + + return new static($props); + } + + /** + * Hashes the user's password unless it is `null`, + * which will leave it as `null` + * + * @internal + * @param string|null $password + * @return string|null + */ + public static function hashPassword($password): ?string + { + if ($password !== null) { + $password = password_hash($password, PASSWORD_DEFAULT); + } + + return $password; + } + + /** + * Returns the user id + * + * @return string + */ + public function id(): string + { + return $this->id; + } + + /** + * Returns the inventory of files + * children and content files + * + * @return array + */ + public function inventory(): array + { + if ($this->inventory !== null) { + return $this->inventory; + } + + $kirby = $this->kirby(); + + return $this->inventory = Dir::inventory( + $this->root(), + $kirby->contentExtension(), + $kirby->contentIgnore(), + $kirby->multilang() + ); + } + + /** + * Compares the current object with the given user object + * + * @param \Kirby\Cms\User|null $user + * @return bool + */ + public function is(User $user = null): bool + { + if ($user === null) { + return false; + } + + return $this->id() === $user->id(); + } + + /** + * Checks if this user has the admin role + * + * @return bool + */ + public function isAdmin(): bool + { + return $this->role()->id() === 'admin'; + } + + /** + * Checks if the current user is the virtual + * Kirby user + * + * @return bool + */ + public function isKirby(): bool + { + return $this->email() === 'kirby@getkirby.com'; + } + + /** + * Checks if the current user is this user + * + * @return bool + */ + public function isLoggedIn(): bool + { + return $this->is($this->kirby()->user()); + } + + /** + * Checks if the user is the last one + * with the admin role + * + * @return bool + */ + public function isLastAdmin(): bool + { + return $this->role()->isAdmin() === true && + $this->kirby()->users()->filter('role', 'admin')->count() <= 1; + } + + /** + * Checks if the user is the last user + * + * @return bool + */ + public function isLastUser(): bool + { + return $this->kirby()->users()->count() === 1; + } + + /** + * Checks if the current user is the virtual + * Nobody user + * + * @return bool + */ + public function isNobody(): bool + { + return $this->email() === 'nobody@getkirby.com'; + } + + /** + * Returns the user language + * + * @return string + */ + public function language(): string + { + return $this->language ?? $this->language = $this->credentials()['language'] ?? $this->kirby()->panelLanguage(); + } + + /** + * Logs the user in + * + * @param string $password + * @param \Kirby\Session\Session|array|null $session Session options or session object to set the user in + * @return bool + */ + public function login(string $password, $session = null): bool + { + $this->validatePassword($password); + $this->loginPasswordless($session); + + return true; + } + + /** + * Logs the user in without checking the password + * + * @param \Kirby\Session\Session|array|null $session Session options or session object to set the user in + * @return void + */ + public function loginPasswordless($session = null): void + { + $kirby = $this->kirby(); + + $session = $this->sessionFromOptions($session); + + $kirby->trigger('user.login:before', ['user' => $this, 'session' => $session]); + + $session->regenerateToken(); // privilege change + $session->data()->set('kirby.userId', $this->id()); + $this->kirby()->auth()->setUser($this); + + $kirby->trigger('user.login:after', ['user' => $this, 'session' => $session]); + } + + /** + * Logs the user out + * + * @param \Kirby\Session\Session|array|null $session Session options or session object to unset the user in + * @return void + */ + public function logout($session = null): void + { + $kirby = $this->kirby(); + $session = $this->sessionFromOptions($session); + + $kirby->trigger('user.logout:before', ['user' => $this, 'session' => $session]); + + // remove the user from the session for future requests + $session->data()->remove('kirby.userId'); + + // clear the cached user object from the app state of the current request + $this->kirby()->auth()->flush(); + + if ($session->data()->get() === []) { + // session is now empty, we might as well destroy it + $session->destroy(); + + $kirby->trigger('user.logout:after', ['user' => $this, 'session' => null]); + } else { + // privilege change + $session->regenerateToken(); + + $kirby->trigger('user.logout:after', ['user' => $this, 'session' => $session]); + } + } + + /** + * Returns the root to the media folder for the user + * + * @internal + * @return string + */ + public function mediaRoot(): string + { + return $this->kirby()->root('media') . '/users/' . $this->id(); + } + + /** + * Returns the media url for the user object + * + * @internal + * @return string + */ + public function mediaUrl(): string + { + return $this->kirby()->url('media') . '/users/' . $this->id(); + } + + /** + * Creates a user model if it has been registered + * + * @internal + * @param string $name + * @param array $props + * @return \Kirby\Cms\User + */ + public static function model(string $name, array $props = []) + { + if ($class = (static::$models[$name] ?? null)) { + $object = new $class($props); + + if (is_a($object, 'Kirby\Cms\User') === true) { + return $object; + } + } + + return new static($props); + } + + /** + * Returns the last modification date of the user + * + * @param string $format + * @param string|null $handler + * @param string|null $languageCode + * @return int|string + */ + public function modified(string $format = 'U', string $handler = null, string $languageCode = null) + { + $modifiedContent = F::modified($this->contentFile($languageCode)); + $modifiedIndex = F::modified($this->root() . '/index.php'); + $modifiedTotal = max([$modifiedContent, $modifiedIndex]); + $handler = $handler ?? $this->kirby()->option('date.handler', 'date'); + + return $handler($format, $modifiedTotal); + } + + /** + * Returns the user's name + * + * @return \Kirby\Cms\Field + */ + public function name() + { + if (is_string($this->name) === true) { + return new Field($this, 'name', $this->name); + } + + if ($this->name !== null) { + return $this->name; + } + + return $this->name = new Field($this, 'name', $this->credentials()['name'] ?? null); + } + + /** + * Returns the user's name or, + * if empty, the email address + * + * @return \Kirby\Cms\Field + */ + public function nameOrEmail() + { + $name = $this->name(); + return $name->isNotEmpty() ? $name : new Field($this, 'email', $this->email()); + } + + /** + * Create a dummy nobody + * + * @internal + * @return static + */ + public static function nobody() + { + return new static([ + 'email' => 'nobody@getkirby.com', + 'role' => 'nobody' + ]); + } + + /** + * Panel icon definition + * + * @internal + * @param array $params + * @return array + */ + public function panelIcon(array $params = null): array + { + $params['type'] = 'user'; + + return parent::panelIcon($params); + } + + /** + * Returns the image file object based on provided query + * + * @internal + * @param string|null $query + * @return \Kirby\Cms\File|\Kirby\Cms\Asset|null + */ + protected function panelImageSource(string $query = null) + { + if ($query === null) { + return $this->avatar(); + } + + return parent::panelImageSource($query); + } + + /** + * Returns the full path without leading slash + * + * @internal + * @return string + */ + public function panelPath(): string + { + return 'users/' . $this->id(); + } + + /** + * Returns prepared data for the panel user picker + * + * @param array|null $params + * @return array + */ + public function panelPickerData(array $params = null): array + { + $image = $this->panelImage($params['image'] ?? []); + $icon = $this->panelIcon($image); + + // escape the default text + // TODO: no longer needed in 3.6 + $textQuery = $params['text'] ?? '{{ user.username }}'; + $text = $this->toString($textQuery); + if ($textQuery === '{{ user.username }}') { + $text = Escape::html($text); + } + + return [ + 'icon' => $icon, + 'id' => $this->id(), + 'image' => $image, + 'email' => $this->email(), + 'info' => $this->toString($params['info'] ?? false), + 'link' => $this->panelUrl(true), + 'text' => $text, + 'username' => $this->username(), + ]; + } + + /** + * Returns the url to the editing view + * in the panel + * + * @internal + * @param bool $relative + * @return string + */ + public function panelUrl(bool $relative = false): string + { + if ($relative === true) { + return '/' . $this->panelPath(); + } else { + return $this->kirby()->url('panel') . '/' . $this->panelPath(); + } + } + + /** + * Returns the encrypted user password + * + * @return string|null + */ + public function password(): ?string + { + if ($this->password !== null) { + return $this->password; + } + + return $this->password = $this->readPassword(); + } + + /** + * @return \Kirby\Cms\UserPermissions + */ + public function permissions() + { + return new UserPermissions($this); + } + + /** + * Returns the user role + * + * @return \Kirby\Cms\Role + */ + public function role() + { + if (is_a($this->role, 'Kirby\Cms\Role') === true) { + return $this->role; + } + + $roleName = $this->role ?? $this->credentials()['role'] ?? 'visitor'; + + if ($role = $this->kirby()->roles()->find($roleName)) { + return $this->role = $role; + } + + return $this->role = Role::nobody(); + } + + /** + * Returns all available roles + * for this user, that can be selected + * by the authenticated user + * + * @return \Kirby\Cms\Roles + */ + public function roles() + { + $kirby = $this->kirby(); + $roles = $kirby->roles(); + + // a collection with just the one role of the user + $myRole = $roles->filter('id', $this->role()->id()); + + // if there's an authenticated user … + if ($user = $kirby->user()) { + + // admin users can select pretty much any role + if ($user->isAdmin() === true) { + // except if the user is the last admin + if ($this->isLastAdmin() === true) { + // in which case they have to stay admin + return $myRole; + } + + // return all roles for mighty admins + return $roles; + } + } + + // any other user can only keep their role + return $myRole; + } + + /** + * The absolute path to the user directory + * + * @return string + */ + public function root(): string + { + return $this->kirby()->root('accounts') . '/' . $this->id(); + } + + /** + * Returns the UserRules class to + * validate any important action. + * + * @return \Kirby\Cms\UserRules + */ + protected function rules() + { + return new UserRules(); + } + + /** + * Sets the Blueprint object + * + * @param array|null $blueprint + * @return $this + */ + protected function setBlueprint(array $blueprint = null) + { + if ($blueprint !== null) { + $blueprint['model'] = $this; + $this->blueprint = new UserBlueprint($blueprint); + } + + return $this; + } + + /** + * Sets the user email + * + * @param string $email|null + * @return $this + */ + protected function setEmail(string $email = null) + { + if ($email !== null) { + $this->email = Str::lower(trim($email)); + } + return $this; + } + + /** + * Sets the user id + * + * @param string $id|null + * @return $this + */ + protected function setId(string $id = null) + { + $this->id = $id; + return $this; + } + + /** + * Sets the user language + * + * @param string $language|null + * @return $this + */ + protected function setLanguage(string $language = null) + { + $this->language = $language !== null ? trim($language) : null; + return $this; + } + + /** + * Sets the user name + * + * @param string $name|null + * @return $this + */ + protected function setName(string $name = null) + { + $this->name = $name !== null ? trim(strip_tags($name)) : null; + return $this; + } + + /** + * Sets the user's password hash + * + * @param string $password|null + * @return $this + */ + protected function setPassword(string $password = null) + { + $this->password = $password; + return $this; + } + + /** + * Sets the user role + * + * @param string $role|null + * @return $this + */ + protected function setRole(string $role = null) + { + $this->role = $role !== null ? Str::lower(trim($role)) : null; + return $this; + } + + /** + * Converts session options into a session object + * + * @param \Kirby\Session\Session|array $session Session options or session object to unset the user in + * @return \Kirby\Session\Session + */ + protected function sessionFromOptions($session) + { + // use passed session options or session object if set + if (is_array($session) === true) { + $session = $this->kirby()->session($session); + } elseif (is_a($session, 'Kirby\Session\Session') === false) { + $session = $this->kirby()->session(['detect' => true]); + } + + return $session; + } + + /** + * Returns the parent Users collection + * + * @return \Kirby\Cms\Users + */ + protected function siblingsCollection() + { + return $this->kirby()->users(); + } + + /** + * Converts the most important user properties + * to an array + * + * @return array + */ + public function toArray(): array + { + return [ + 'avatar' => $this->avatar() ? $this->avatar()->toArray() : null, + 'content' => $this->content()->toArray(), + 'email' => $this->email(), + 'id' => $this->id(), + 'language' => $this->language(), + 'role' => $this->role()->name(), + 'username' => $this->username() + ]; + } + + /** + * String template builder + * + * @param string|null $template + * @param array|null $data + * @param string $fallback Fallback for tokens in the template that cannot be replaced + * @return string + */ + public function toString(string $template = null, array $data = [], string $fallback = ''): string + { + if ($template === null) { + $template = $this->email(); + } + + return parent::toString($template, $data); + } + + /** + * Returns the username + * which is the given name or the email + * as a fallback + * + * @return string|null + */ + public function username(): ?string + { + return $this->name()->or($this->email())->value(); + } + + /** + * Compares the given password with the stored one + * + * @param string $password|null + * @return bool + * + * @throws \Kirby\Exception\NotFoundException If the user has no password + * @throws \Kirby\Exception\InvalidArgumentException If the entered password is not valid + * or does not match the user password + */ + public function validatePassword(string $password = null): bool + { + if (empty($this->password()) === true) { + throw new NotFoundException(['key' => 'user.password.undefined']); + } + + if (Str::length($password) < 8) { + throw new InvalidArgumentException(['key' => 'user.password.invalid']); + } + + if (password_verify($password, $this->password()) !== true) { + throw new InvalidArgumentException(['key' => 'user.password.wrong']); + } + + return true; + } +} diff --git a/kirby/src/Cms/UserActions.php b/kirby/src/Cms/UserActions.php new file mode 100644 index 0000000..7343715 --- /dev/null +++ b/kirby/src/Cms/UserActions.php @@ -0,0 +1,354 @@ + + * @link https://getkirby.com + * @copyright Bastian Allgeier GmbH + * @license https://getkirby.com/license + */ +trait UserActions +{ + /** + * Changes the user email address + * + * @param string $email + * @return static + */ + public function changeEmail(string $email) + { + return $this->commit('changeEmail', ['user' => $this, 'email' => Idn::decodeEmail($email)], function ($user, $email) { + $user = $user->clone([ + 'email' => $email + ]); + + $user->updateCredentials([ + 'email' => $email + ]); + + return $user; + }); + } + + /** + * Changes the user language + * + * @param string $language + * @return static + */ + public function changeLanguage(string $language) + { + return $this->commit('changeLanguage', ['user' => $this, 'language' => $language], function ($user, $language) { + $user = $user->clone([ + 'language' => $language, + ]); + + $user->updateCredentials([ + 'language' => $language + ]); + + return $user; + }); + } + + /** + * Changes the screen name of the user + * + * @param string $name + * @return static + */ + public function changeName(string $name) + { + return $this->commit('changeName', ['user' => $this, 'name' => $name], function ($user, $name) { + $user = $user->clone([ + 'name' => $name + ]); + + $user->updateCredentials([ + 'name' => $name + ]); + + return $user; + }); + } + + /** + * Changes the user password + * + * @param string $password + * @return static + */ + public function changePassword(string $password) + { + return $this->commit('changePassword', ['user' => $this, 'password' => $password], function ($user, $password) { + $user = $user->clone([ + 'password' => $password = User::hashPassword($password) + ]); + + $user->writePassword($password); + + return $user; + }); + } + + /** + * Changes the user role + * + * @param string $role + * @return static + */ + public function changeRole(string $role) + { + return $this->commit('changeRole', ['user' => $this, 'role' => $role], function ($user, $role) { + $user = $user->clone([ + 'role' => $role, + ]); + + $user->updateCredentials([ + 'role' => $role + ]); + + return $user; + }); + } + + /** + * Commits a user action, by following these steps + * + * 1. checks the action rules + * 2. sends the before hook + * 3. commits the action + * 4. sends the after hook + * 5. returns the result + * + * @param string $action + * @param array $arguments + * @param \Closure $callback + * @return mixed + * @throws \Kirby\Exception\PermissionException + */ + protected function commit(string $action, array $arguments, Closure $callback) + { + if ($this->isKirby() === true) { + throw new PermissionException('The Kirby user cannot be changed'); + } + + $old = $this->hardcopy(); + $kirby = $this->kirby(); + $argumentValues = array_values($arguments); + + $this->rules()->$action(...$argumentValues); + $kirby->trigger('user.' . $action . ':before', $arguments); + + $result = $callback(...$argumentValues); + + if ($action === 'create') { + $argumentsAfter = ['user' => $result]; + } elseif ($action === 'delete') { + $argumentsAfter = ['status' => $result, 'user' => $old]; + } else { + $argumentsAfter = ['newUser' => $result, 'oldUser' => $old]; + } + $kirby->trigger('user.' . $action . ':after', $argumentsAfter); + + $kirby->cache('pages')->flush(); + return $result; + } + + /** + * Creates a new User from the given props and returns a new User object + * + * @param array|null $props + * @return static + */ + public static function create(array $props = null) + { + $data = $props; + + if (isset($props['email']) === true) { + $data['email'] = Idn::decodeEmail($props['email']); + } + + if (isset($props['password']) === true) { + $data['password'] = User::hashPassword($props['password']); + } + + $props['role'] = $props['model'] = strtolower($props['role'] ?? 'default'); + + $user = User::factory($data); + + // create a form for the user + $form = Form::for($user, [ + 'values' => $props['content'] ?? [] + ]); + + // inject the content + $user = $user->clone(['content' => $form->strings(true)]); + + // run the hook + return $user->commit('create', ['user' => $user, 'input' => $props], function ($user, $props) { + $user->writeCredentials([ + 'email' => $user->email(), + 'language' => $user->language(), + 'name' => $user->name()->value(), + 'role' => $user->role()->id(), + ]); + + $user->writePassword($user->password()); + + // always create users in the default language + if ($user->kirby()->multilang() === true) { + $languageCode = $user->kirby()->defaultLanguage()->code(); + } else { + $languageCode = null; + } + + // add the user to users collection + $user->kirby()->users()->add($user); + + // write the user data + return $user->save($user->content()->toArray(), $languageCode); + }); + } + + /** + * Returns a random user id + * + * @return string + */ + public function createId(): string + { + $length = 8; + $id = Str::random($length); + + while ($this->kirby()->users()->has($id)) { + $length++; + $id = Str::random($length); + } + + return $id; + } + + /** + * Deletes the user + * + * @return bool + * @throws \Kirby\Exception\LogicException + */ + public function delete(): bool + { + return $this->commit('delete', ['user' => $this], function ($user) { + if ($user->exists() === false) { + return true; + } + + // delete all public assets for this user + Dir::remove($user->mediaRoot()); + + // delete the user directory + if (Dir::remove($user->root()) !== true) { + throw new LogicException('The user directory for "' . $user->email() . '" could not be deleted'); + } + + // remove the user from users collection + $user->kirby()->users()->remove($user); + + return true; + }); + } + + /** + * Read the account information from disk + * + * @return array + */ + protected function readCredentials(): array + { + $path = $this->root() . '/index.php'; + + if (is_file($path) === true) { + $credentials = F::load($path); + + return is_array($credentials) === false ? [] : $credentials; + } else { + return []; + } + } + + /** + * Reads the user password from disk + * + * @return string|false + */ + protected function readPassword() + { + return F::read($this->root() . '/.htpasswd'); + } + + /** + * Updates the user data + * + * @param array|null $input + * @param string|null $languageCode + * @param bool $validate + * @return static + */ + public function update(array $input = null, string $languageCode = null, bool $validate = false) + { + $user = parent::update($input, $languageCode, $validate); + + // set auth user data only if the current user is this user + if ($user->isLoggedIn() === true) { + $this->kirby()->auth()->setUser($user); + } + + return $user; + } + + /** + * This always merges the existing credentials + * with the given input. + * + * @param array $credentials + * @return bool + */ + protected function updateCredentials(array $credentials): bool + { + return $this->writeCredentials(array_merge($this->credentials(), $credentials)); + } + + /** + * Writes the account information to disk + * + * @param array $credentials + * @return bool + */ + protected function writeCredentials(array $credentials): bool + { + return Data::write($this->root() . '/index.php', $credentials); + } + + /** + * Writes the password to disk + * + * @param string|null $password + * @return bool + */ + protected function writePassword(string $password = null): bool + { + return F::write($this->root() . '/.htpasswd', $password); + } +} diff --git a/kirby/src/Cms/UserBlueprint.php b/kirby/src/Cms/UserBlueprint.php new file mode 100644 index 0000000..ae55569 --- /dev/null +++ b/kirby/src/Cms/UserBlueprint.php @@ -0,0 +1,47 @@ + + * @link https://getkirby.com + * @copyright Bastian Allgeier GmbH + * @license https://getkirby.com/license + */ +class UserBlueprint extends Blueprint +{ + /** + * UserBlueprint constructor. + * + * @param array $props + * @throws \Kirby\Exception\InvalidArgumentException + */ + public function __construct(array $props) + { + // normalize and translate the description + $props['description'] = $this->i18n($props['description'] ?? null); + + // register the other props + parent::__construct($props); + + // normalize all available page options + $this->props['options'] = $this->normalizeOptions( + $props['options'] ?? true, + // defaults + [ + 'create' => null, + 'changeEmail' => null, + 'changeLanguage' => null, + 'changeName' => null, + 'changePassword' => null, + 'changeRole' => null, + 'delete' => null, + 'update' => null, + ] + ); + } +} diff --git a/kirby/src/Cms/UserPermissions.php b/kirby/src/Cms/UserPermissions.php new file mode 100644 index 0000000..35c706a --- /dev/null +++ b/kirby/src/Cms/UserPermissions.php @@ -0,0 +1,67 @@ + + * @link https://getkirby.com + * @copyright Bastian Allgeier GmbH + * @license https://getkirby.com/license + */ +class UserPermissions extends ModelPermissions +{ + /** + * @var string + */ + protected $category = 'users'; + + /** + * UserPermissions constructor + * + * @param \Kirby\Cms\Model $model + */ + public function __construct(Model $model) + { + parent::__construct($model); + + // change the scope of the permissions, when the current user is this user + $this->category = $this->user && $this->user->is($model) ? 'user' : 'users'; + } + + /** + * @return bool + */ + protected function canChangeRole(): bool + { + return $this->model->roles()->count() > 1; + } + + /** + * @return bool + */ + protected function canCreate(): bool + { + // the admin can always create new users + if ($this->user->isAdmin() === true) { + return true; + } + + // users who are not admins cannot create admins + if ($this->model->isAdmin() === true) { + return false; + } + + return true; + } + + /** + * @return bool + */ + protected function canDelete(): bool + { + return $this->model->isLastAdmin() !== true; + } +} diff --git a/kirby/src/Cms/UserPicker.php b/kirby/src/Cms/UserPicker.php new file mode 100644 index 0000000..fb796f0 --- /dev/null +++ b/kirby/src/Cms/UserPicker.php @@ -0,0 +1,69 @@ + + * @link https://getkirby.com + * @copyright Bastian Allgeier GmbH + * @license https://getkirby.com/license + */ +class UserPicker extends Picker +{ + /** + * Extends the basic defaults + * + * @return array + */ + public function defaults(): array + { + $defaults = parent::defaults(); + $defaults['text'] = '{{ user.username }}'; + + return $defaults; + } + + /** + * Search all users for the picker + * + * @return \Kirby\Cms\Users|null + * @throws \Kirby\Exception\InvalidArgumentException + */ + public function items() + { + $model = $this->options['model']; + + // find the right default query + if (empty($this->options['query']) === false) { + $query = $this->options['query']; + } elseif (is_a($model, 'Kirby\Cms\User') === true) { + $query = 'user.siblings'; + } else { + $query = 'kirby.users'; + } + + // fetch all users for the picker + $users = $model->query($query); + + // catch invalid data + if (is_a($users, 'Kirby\Cms\Users') === false) { + throw new InvalidArgumentException('Your query must return a set of users'); + } + + // search + $users = $this->search($users); + + // sort + $users = $users->sort('username', 'asc'); + + // paginate + return $this->paginate($users); + } +} diff --git a/kirby/src/Cms/UserRules.php b/kirby/src/Cms/UserRules.php new file mode 100644 index 0000000..f2f93b5 --- /dev/null +++ b/kirby/src/Cms/UserRules.php @@ -0,0 +1,365 @@ + + * @link https://getkirby.com + * @copyright Bastian Allgeier GmbH + * @license https://getkirby.com/license + */ +class UserRules +{ + /** + * Validates if the email address can be changed + * + * @param \Kirby\Cms\User $user + * @param string $email + * @return bool + * @throws \Kirby\Exception\PermissionException If the user is not allowed to change the address + */ + public static function changeEmail(User $user, string $email): bool + { + if ($user->permissions()->changeEmail() !== true) { + throw new PermissionException([ + 'key' => 'user.changeEmail.permission', + 'data' => ['name' => $user->username()] + ]); + } + + return static::validEmail($user, $email); + } + + /** + * Validates if the language can be changed + * + * @param \Kirby\Cms\User $user + * @param string $language + * @return bool + * @throws \Kirby\Exception\PermissionException If the user is not allowed to change the language + */ + public static function changeLanguage(User $user, string $language): bool + { + if ($user->permissions()->changeLanguage() !== true) { + throw new PermissionException([ + 'key' => 'user.changeLanguage.permission', + 'data' => ['name' => $user->username()] + ]); + } + + return static::validLanguage($user, $language); + } + + /** + * Validates if the name can be changed + * + * @param \Kirby\Cms\User $user + * @param string $name + * @return bool + * @throws \Kirby\Exception\PermissionException If the user is not allowed to change the name + */ + public static function changeName(User $user, string $name): bool + { + if ($user->permissions()->changeName() !== true) { + throw new PermissionException([ + 'key' => 'user.changeName.permission', + 'data' => ['name' => $user->username()] + ]); + } + + return true; + } + + /** + * Validates if the password can be changed + * + * @param \Kirby\Cms\User $user + * @param string $password + * @return bool + * @throws \Kirby\Exception\PermissionException If the user is not allowed to change the password + */ + public static function changePassword(User $user, string $password): bool + { + if ($user->permissions()->changePassword() !== true) { + throw new PermissionException([ + 'key' => 'user.changePassword.permission', + 'data' => ['name' => $user->username()] + ]); + } + + return static::validPassword($user, $password); + } + + /** + * Validates if the role can be changed + * + * @param \Kirby\Cms\User $user + * @param string $role + * @return bool + * @throws \Kirby\Exception\LogicException If the user is the last admin + * @throws \Kirby\Exception\PermissionException If the user is not allowed to change the role + */ + public static function changeRole(User $user, string $role): bool + { + // protect admin from role changes by non-admin + if ( + $user->kirby()->user()->isAdmin() === false && + $user->isAdmin() === true + ) { + throw new PermissionException([ + 'key' => 'user.changeRole.permission', + 'data' => ['name' => $user->username()] + ]); + } + + // prevent non-admins making a user to admin + if ( + $user->kirby()->user()->isAdmin() === false && + $role === 'admin' + ) { + throw new PermissionException([ + 'key' => 'user.changeRole.toAdmin' + ]); + } + + static::validRole($user, $role); + + if ($role !== 'admin' && $user->isLastAdmin() === true) { + throw new LogicException([ + 'key' => 'user.changeRole.lastAdmin', + 'data' => ['name' => $user->username()] + ]); + } + + if ($user->permissions()->changeRole() !== true) { + throw new PermissionException([ + 'key' => 'user.changeRole.permission', + 'data' => ['name' => $user->username()] + ]); + } + + return true; + } + + /** + * Validates if the user can be created + * + * @param \Kirby\Cms\User $user + * @param array $props + * @return bool + * @throws \Kirby\Exception\PermissionException If the user is not allowed to create a new user + */ + public static function create(User $user, array $props = []): bool + { + static::validId($user, $user->id()); + static::validEmail($user, $user->email(), true); + static::validLanguage($user, $user->language()); + + // the first user must have a password + if ($user->kirby()->users()->count() === 0 && empty($props['password'])) { + // trigger invalid password error + static::validPassword($user, ' '); + } + + if (empty($props['password']) === false) { + static::validPassword($user, $props['password']); + } + + // get the current user if it exists + $currentUser = $user->kirby()->user(); + + // admins are allowed everything + if ($currentUser && $currentUser->isAdmin() === true) { + return true; + } + + // only admins are allowed to add admins + $role = $props['role'] ?? null; + + if ($role === 'admin' && $currentUser && $currentUser->isAdmin() === false) { + throw new PermissionException([ + 'key' => 'user.create.permission' + ]); + } + + // check user permissions (if not on install) + if ($user->kirby()->users()->count() > 0) { + if ($user->permissions()->create() !== true) { + throw new PermissionException([ + 'key' => 'user.create.permission' + ]); + } + } + + return true; + } + + /** + * Validates if the user can be deleted + * + * @param \Kirby\Cms\User $user + * @return bool + * @throws \Kirby\Exception\LogicException If this is the last user or last admin, which cannot be deleted + * @throws \Kirby\Exception\PermissionException If the user is not allowed to delete this user + */ + public static function delete(User $user): bool + { + if ($user->isLastAdmin() === true) { + throw new LogicException(['key' => 'user.delete.lastAdmin']); + } + + if ($user->isLastUser() === true) { + throw new LogicException([ + 'key' => 'user.delete.lastUser' + ]); + } + + if ($user->permissions()->delete() !== true) { + throw new PermissionException([ + 'key' => 'user.delete.permission', + 'data' => ['name' => $user->username()] + ]); + } + + return true; + } + + /** + * Validates if the user can be updated + * + * @param \Kirby\Cms\User $user + * @param array $values + * @param array $strings + * @return bool + * @throws \Kirby\Exception\PermissionException If the user it not allowed to update this user + */ + public static function update(User $user, array $values = [], array $strings = []): bool + { + if ($user->permissions()->update() !== true) { + throw new PermissionException([ + 'key' => 'user.update.permission', + 'data' => ['name' => $user->username()] + ]); + } + + return true; + } + + /** + * Validates an email address + * + * @param \Kirby\Cms\User $user + * @param string $email + * @param bool $strict + * @return bool + * @throws \Kirby\Exception\DuplicateException If the email address already exists + * @throws \Kirby\Exception\InvalidArgumentException If the email address is invalid + */ + public static function validEmail(User $user, string $email, bool $strict = false): bool + { + if (V::email($email ?? null) === false) { + throw new InvalidArgumentException([ + 'key' => 'user.email.invalid', + ]); + } + + if ($strict === true) { + $duplicate = $user->kirby()->users()->find($email); + } else { + $duplicate = $user->kirby()->users()->not($user)->find($email); + } + + if ($duplicate) { + throw new DuplicateException([ + 'key' => 'user.duplicate', + 'data' => ['email' => $email] + ]); + } + + return true; + } + + /** + * Validates a user id + * + * @param \Kirby\Cms\User $user + * @param string $id + * @return bool + * @throws \Kirby\Exception\DuplicateException If the user already exists + */ + public static function validId(User $user, string $id): bool + { + if ($user->kirby()->users()->find($id)) { + throw new DuplicateException('A user with this id exists'); + } + + return true; + } + + /** + * Validates a user language code + * + * @param \Kirby\Cms\User $user + * @param string $language + * @return bool + * @throws \Kirby\Exception\InvalidArgumentException If the language does not exist + */ + public static function validLanguage(User $user, string $language): bool + { + if (in_array($language, $user->kirby()->translations()->keys(), true) === false) { + throw new InvalidArgumentException([ + 'key' => 'user.language.invalid', + ]); + } + + return true; + } + + /** + * Validates a password + * + * @param \Kirby\Cms\User $user + * @param string $password + * @return bool + * @throws \Kirby\Exception\InvalidArgumentException If the password is too short + */ + public static function validPassword(User $user, string $password): bool + { + if (Str::length($password ?? null) < 8) { + throw new InvalidArgumentException([ + 'key' => 'user.password.invalid', + ]); + } + + return true; + } + + /** + * Validates a user role + * + * @param \Kirby\Cms\User $user + * @param string $role + * @return bool + * @throws \Kirby\Exception\InvalidArgumentException If the user role does not exist + */ + public static function validRole(User $user, string $role): bool + { + if (is_a($user->kirby()->roles()->find($role), 'Kirby\Cms\Role') === true) { + return true; + } + + throw new InvalidArgumentException([ + 'key' => 'user.role.invalid', + ]); + } +} diff --git a/kirby/src/Cms/Users.php b/kirby/src/Cms/Users.php new file mode 100644 index 0000000..63b789b --- /dev/null +++ b/kirby/src/Cms/Users.php @@ -0,0 +1,140 @@ + + * @link https://getkirby.com + * @copyright Bastian Allgeier GmbH + * @license https://getkirby.com/license + */ +class Users extends Collection +{ + /** + * All registered users methods + * + * @var array + */ + public static $methods = []; + + public function create(array $data) + { + return User::create($data); + } + + /** + * Adds a single user or + * an entire second collection to the + * current collection + * + * @param mixed $object + * @return $this + */ + public function add($object) + { + // add a page collection + if (is_a($object, self::class) === true) { + $this->data = array_merge($this->data, $object->data); + + // add a user by id + } elseif (is_string($object) === true && $user = App::instance()->user($object)) { + $this->__set($user->id(), $user); + + // add a user object + } elseif (is_a($object, 'Kirby\Cms\User') === true) { + $this->__set($object->id(), $object); + } + + return $this; + } + + /** + * Takes an array of user props and creates a nice and clean user collection from it + * + * @param array $users + * @param array $inject + * @return static + */ + public static function factory(array $users, array $inject = []) + { + $collection = new static(); + + // read all user blueprints + foreach ($users as $props) { + $user = User::factory($props + $inject); + $collection->set($user->id(), $user); + } + + return $collection; + } + + /** + * Finds a user in the collection by id or email address + * + * @param string $key + * @return \Kirby\Cms\User|null + */ + public function findByKey(string $key) + { + if (Str::contains($key, '@') === true) { + return parent::findBy('email', Str::lower($key)); + } + + return parent::findByKey($key); + } + + /** + * Loads a user from disk by passing the absolute path (root) + * + * @param string $root + * @param array $inject + * @return static + */ + public static function load(string $root, array $inject = []) + { + $users = new static(); + + foreach (Dir::read($root) as $userDirectory) { + if (is_dir($root . '/' . $userDirectory) === false) { + continue; + } + + // get role information + $path = $root . '/' . $userDirectory . '/index.php'; + if (is_file($path) === true) { + $credentials = F::load($path); + } + + // create user model based on role + $user = User::factory([ + 'id' => $userDirectory, + 'model' => $credentials['role'] ?? null + ] + $inject); + + $users->set($user->id(), $user); + } + + return $users; + } + + /** + * Shortcut for `$users->filter('role', 'admin')` + * + * @param string $role + * @return static + */ + public function role(string $role) + { + return $this->filter('role', $role); + } +} diff --git a/kirby/src/Cms/Visitor.php b/kirby/src/Cms/Visitor.php new file mode 100644 index 0000000..19eeeb0 --- /dev/null +++ b/kirby/src/Cms/Visitor.php @@ -0,0 +1,25 @@ + + * @link https://getkirby.com + * @copyright Bastian Allgeier GmbH + * @license https://getkirby.com/license + */ +class Visitor extends Facade +{ + /** + * @return \Kirby\Http\Visitor + */ + public static function instance() + { + return App::instance()->visitor(); + } +} diff --git a/kirby/src/Data/Data.php b/kirby/src/Data/Data.php new file mode 100644 index 0000000..def29ac --- /dev/null +++ b/kirby/src/Data/Data.php @@ -0,0 +1,127 @@ + + * @link https://getkirby.com + * @copyright Bastian Allgeier GmbH + * @license https://opensource.org/licenses/MIT + */ +class Data +{ + /** + * Handler Type Aliases + * + * @var array + */ + public static $aliases = [ + 'md' => 'txt', + 'mdown' => 'txt', + 'rss' => 'xml', + 'yml' => 'yaml', + ]; + + /** + * All registered handlers + * + * @var array + */ + public static $handlers = [ + 'json' => 'Kirby\Data\Json', + 'php' => 'Kirby\Data\PHP', + 'txt' => 'Kirby\Data\Txt', + 'xml' => 'Kirby\Data\Xml', + 'yaml' => 'Kirby\Data\Yaml', + ]; + + /** + * Handler getter + * + * @param string $type + * @return \Kirby\Data\Handler + */ + public static function handler(string $type) + { + // normalize the type + $type = strtolower($type); + + // find a handler or alias + $handler = static::$handlers[$type] ?? + static::$handlers[static::$aliases[$type] ?? null] ?? + null; + + if (class_exists($handler)) { + return new $handler(); + } + + throw new Exception('Missing handler for type: "' . $type . '"'); + } + + /** + * Decodes data with the specified handler + * + * @param mixed $string + * @param string $type + * @return array + */ + public static function decode($string, string $type): array + { + return static::handler($type)->decode($string); + } + + /** + * Encodes data with the specified handler + * + * @param mixed $data + * @param string $type + * @return string + */ + public static function encode($data, string $type): string + { + return static::handler($type)->encode($data); + } + + /** + * Reads data from a file; + * the data handler is automatically chosen by + * the extension if not specified + * + * @param string $file + * @param string $type + * @return array + */ + public static function read(string $file, string $type = null): array + { + return static::handler($type ?? F::extension($file))->read($file); + } + + /** + * Writes data to a file; + * the data handler is automatically chosen by + * the extension if not specified + * + * @param string $file + * @param mixed $data + * @param string $type + * @return bool + */ + public static function write(string $file = null, $data = [], string $type = null): bool + { + return static::handler($type ?? F::extension($file))->write($file, $data); + } +} diff --git a/kirby/src/Data/Handler.php b/kirby/src/Data/Handler.php new file mode 100644 index 0000000..071f30c --- /dev/null +++ b/kirby/src/Data/Handler.php @@ -0,0 +1,66 @@ + + * @link https://getkirby.com + * @copyright Bastian Allgeier GmbH + * @license https://opensource.org/licenses/MIT + */ +abstract class Handler +{ + /** + * Parses an encoded string and returns a multi-dimensional array + * + * Needs to throw an Exception if the file can't be parsed. + * + * @param mixed $string + * @return array + */ + abstract public static function decode($string): array; + + /** + * Converts an array to an encoded string + * + * @param mixed $data + * @return string + */ + abstract public static function encode($data): string; + + /** + * Reads data from a file + * + * @param string $file + * @return array + */ + public static function read(string $file): array + { + $contents = F::read($file); + if ($contents === false) { + throw new Exception('The file "' . $file . '" does not exist'); + } + + return static::decode($contents); + } + + /** + * Writes data to a file + * + * @param string $file + * @param mixed $data + * @return bool + */ + public static function write(string $file = null, $data = []): bool + { + return F::write($file, static::encode($data)); + } +} diff --git a/kirby/src/Data/Json.php b/kirby/src/Data/Json.php new file mode 100644 index 0000000..542ce53 --- /dev/null +++ b/kirby/src/Data/Json.php @@ -0,0 +1,57 @@ + + * @link https://getkirby.com + * @copyright Bastian Allgeier GmbH + * @license https://opensource.org/licenses/MIT + */ +class Json extends Handler +{ + /** + * Converts an array to an encoded JSON string + * + * @param mixed $data + * @return string + */ + public static function encode($data): string + { + return json_encode($data, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE); + } + + /** + * Parses an encoded JSON string and returns a multi-dimensional array + * + * @param mixed $string + * @return array + */ + public static function decode($string): array + { + if ($string === null) { + return []; + } + + if (is_array($string) === true) { + return $string; + } + + if (is_string($string) === false) { + throw new InvalidArgumentException('Invalid JSON data; please pass a string'); + } + + $result = json_decode($string, true); + + if (is_array($result) === true) { + return $result; + } else { + throw new InvalidArgumentException('JSON string is invalid'); + } + } +} diff --git a/kirby/src/Data/PHP.php b/kirby/src/Data/PHP.php new file mode 100644 index 0000000..63efd1d --- /dev/null +++ b/kirby/src/Data/PHP.php @@ -0,0 +1,94 @@ + + * @link https://getkirby.com + * @copyright Bastian Allgeier GmbH + * @license https://opensource.org/licenses/MIT + */ +class PHP extends Handler +{ + /** + * Converts an array to PHP file content + * + * @param mixed $data + * @param string $indent For internal use only + * @return string + */ + public static function encode($data, string $indent = ''): string + { + switch (gettype($data)) { + case 'array': + $indexed = array_keys($data) === range(0, count($data) - 1); + $array = []; + + foreach ($data as $key => $value) { + $array[] = "$indent " . ($indexed ? '' : static::encode($key) . ' => ') . static::encode($value, "$indent "); + } + + return "[\n" . implode(",\n", $array) . "\n" . $indent . ']'; + case 'boolean': + return $data ? 'true' : 'false'; + case 'integer': + case 'double': + return $data; + default: + return var_export($data, true); + } + } + + /** + * PHP strings shouldn't be decoded manually + * + * @param mixed $array + * @return array + */ + public static function decode($array): array + { + throw new BadMethodCallException('The PHP::decode() method is not implemented'); + } + + /** + * Reads data from a file + * + * @param string $file + * @return array + */ + public static function read(string $file): array + { + if (is_file($file) !== true) { + throw new Exception('The file "' . $file . '" does not exist'); + } + + return (array)F::load($file, []); + } + + /** + * Creates a PHP file with the given data + * + * @param string $file + * @param mixed $data + * @return bool + */ + public static function write(string $file = null, $data = []): bool + { + $php = static::encode($data); + $php = " + * @link https://getkirby.com + * @copyright Bastian Allgeier GmbH + * @license https://opensource.org/licenses/MIT + */ +class Txt extends Handler +{ + /** + * Converts an array to an encoded Kirby txt string + * + * @param mixed $data + * @return string + */ + public static function encode($data): string + { + $result = []; + + foreach (A::wrap($data) as $key => $value) { + if (empty($key) === true || $value === null) { + continue; + } + + $key = Str::ucfirst(Str::slug($key)); + $value = static::encodeValue($value); + $result[$key] = static::encodeResult($key, $value); + } + + return implode("\n\n----\n\n", $result); + } + + /** + * Helper for converting the value + * + * @param array|string $value + * @return string + */ + protected static function encodeValue($value): string + { + // avoid problems with arrays + if (is_array($value) === true) { + $value = Data::encode($value, 'yaml'); + // avoid problems with localized floats + } elseif (is_float($value) === true) { + $value = Str::float($value); + } + + // escape accidental dividers within a field + $value = preg_replace('!(?<=\n|^)----!', '\\----', $value); + + return $value; + } + + /** + * Helper for converting the key and value to the result string + * + * @param string $key + * @param string $value + * @return string + */ + protected static function encodeResult(string $key, string $value): string + { + $result = $key . ':'; + + // multi-line content + if (preg_match('!\R!', $value) === 1) { + $result .= "\n\n"; + } else { + $result .= ' '; + } + + $result .= trim($value); + + return $result; + } + + /** + * Parses a Kirby txt string and returns a multi-dimensional array + * + * @param mixed $string + * @return array + */ + public static function decode($string): array + { + if ($string === null) { + return []; + } + + if (is_array($string) === true) { + return $string; + } + + if (is_string($string) === false) { + throw new InvalidArgumentException('Invalid TXT data; please pass a string'); + } + + // remove BOM + $string = str_replace("\xEF\xBB\xBF", '', $string); + // explode all fields by the line separator + $fields = preg_split('!\n----\s*\n*!', $string); + // start the data array + $data = []; + + // loop through all fields and add them to the content + foreach ($fields as $field) { + $pos = strpos($field, ':'); + $key = str_replace(['-', ' '], '_', strtolower(trim(substr($field, 0, $pos)))); + + // Don't add fields with empty keys + if (empty($key) === true) { + continue; + } + + $value = trim(substr($field, $pos + 1)); + + // unescape escaped dividers within a field + $data[$key] = preg_replace('!(?<=\n|^)\\\\----!', '----', $value); + } + + return $data; + } +} diff --git a/kirby/src/Data/Xml.php b/kirby/src/Data/Xml.php new file mode 100644 index 0000000..9c36be7 --- /dev/null +++ b/kirby/src/Data/Xml.php @@ -0,0 +1,64 @@ + + * @link https://getkirby.com + * @copyright Bastian Allgeier GmbH + * @license https://opensource.org/licenses/MIT + */ +class Xml extends Handler +{ + /** + * Converts an array to an encoded XML string + * + * @param mixed $data + * @return string + */ + public static function encode($data): string + { + return XmlConverter::create($data, 'data'); + } + + /** + * Parses an encoded XML string and returns a multi-dimensional array + * + * @param mixed $string + * @return array + */ + public static function decode($string): array + { + if ($string === null) { + return []; + } + + if (is_array($string) === true) { + return $string; + } + + if (is_string($string) === false) { + throw new InvalidArgumentException('Invalid XML data; please pass a string'); + } + + $result = XmlConverter::parse($string); + + if (is_array($result) === true) { + // remove the root's name if it is the default to ensure that + // the decoded data is the same as the input to the encode() method + if ($result['@name'] === 'data') { + unset($result['@name']); + } + + return $result; + } else { + throw new InvalidArgumentException('XML string is invalid'); + } + } +} diff --git a/kirby/src/Data/Yaml.php b/kirby/src/Data/Yaml.php new file mode 100644 index 0000000..df6956a --- /dev/null +++ b/kirby/src/Data/Yaml.php @@ -0,0 +1,77 @@ + + * @link https://getkirby.com + * @copyright Bastian Allgeier GmbH + * @license https://opensource.org/licenses/MIT + */ +class Yaml extends Handler +{ + /** + * Converts an array to an encoded YAML string + * + * @param mixed $data + * @return string + */ + public static function encode($data): string + { + // TODO: The locale magic should no longer be + // necessary when support for PHP 7.x is dropped + + // fetch the current locale setting for numbers + $locale = setlocale(LC_NUMERIC, 0); + + // change to english numerics to avoid issues with floats + setlocale(LC_NUMERIC, 'C'); + + // $data, $indent, $wordwrap, $no_opening_dashes + $yaml = Spyc::YAMLDump($data, false, false, true); + + // restore the previous locale settings + setlocale(LC_NUMERIC, $locale); + + return $yaml; + } + + /** + * Parses an encoded YAML string and returns a multi-dimensional array + * + * @param mixed $string + * @return array + */ + public static function decode($string): array + { + if ($string === null) { + return []; + } + + if (is_array($string) === true) { + return $string; + } + + if (is_string($string) === false) { + throw new InvalidArgumentException('Invalid YAML data; please pass a string'); + } + + // remove BOM + $string = str_replace("\xEF\xBB\xBF", '', $string); + $result = Spyc::YAMLLoadString($string); + + if (is_array($result)) { + return $result; + } else { + // apparently Spyc always returns an array, even for invalid YAML syntax + // so this Exception should currently never be thrown + throw new InvalidArgumentException('The YAML data cannot be parsed'); // @codeCoverageIgnore + } + } +} diff --git a/kirby/src/Database/Database.php b/kirby/src/Database/Database.php new file mode 100644 index 0000000..74eacb5 --- /dev/null +++ b/kirby/src/Database/Database.php @@ -0,0 +1,664 @@ + + * @link https://getkirby.com + * @copyright Bastian Allgeier GmbH + * @license https://opensource.org/licenses/MIT + */ +class Database +{ + /** + * The number of affected rows for the last query + * + * @var int|null + */ + protected $affected; + + /** + * Whitelist for column names + * + * @var array + */ + protected $columnWhitelist = []; + + /** + * The established connection + * + * @var \PDO|null + */ + protected $connection; + + /** + * A global array of started connections + * + * @var array + */ + public static $connections = []; + + /** + * Database name + * + * @var string + */ + protected $database; + + /** + * @var string + */ + protected $dsn; + + /** + * Set to true to throw exceptions on failed queries + * + * @var bool + */ + protected $fail = false; + + /** + * The connection id + * + * @var string + */ + protected $id; + + /** + * The last error + * + * @var \Exception|null + */ + protected $lastError; + + /** + * The last insert id + * + * @var int|null + */ + protected $lastId; + + /** + * The last query + * + * @var string + */ + protected $lastQuery; + + /** + * The last result set + * + * @var mixed + */ + protected $lastResult; + + /** + * Optional prefix for table names + * + * @var string + */ + protected $prefix; + + /** + * The PDO query statement + * + * @var \PDOStatement|null + */ + protected $statement; + + /** + * List of existing tables in the database + * + * @var array|null + */ + protected $tables; + + /** + * An array with all queries which are being made + * + * @var array + */ + protected $trace = []; + + /** + * The database type (mysql, sqlite) + * + * @var string + */ + protected $type; + + /** + * @var array + */ + public static $types = []; + + /** + * Creates a new Database instance + * + * @param array $params + * @return void + */ + public function __construct(array $params = []) + { + $this->connect($params); + } + + /** + * Returns one of the started instances + * + * @param string|null $id + * @return static|null + */ + public static function instance(string $id = null) + { + return $id === null ? A::last(static::$connections) : static::$connections[$id] ?? null; + } + + /** + * Returns all started instances + * + * @return array + */ + public static function instances(): array + { + return static::$connections; + } + + /** + * Connects to a database + * + * @param array|null $params This can either be a config key or an array of parameters for the connection + * @return \PDO|null + * @throws \Kirby\Exception\InvalidArgumentException + */ + public function connect(array $params = null) + { + $defaults = [ + 'database' => null, + 'type' => 'mysql', + 'prefix' => null, + 'user' => null, + 'password' => null, + 'id' => uniqid() + ]; + + $options = array_merge($defaults, $params); + + // store the database information + $this->database = $options['database']; + $this->type = $options['type']; + $this->prefix = $options['prefix']; + $this->id = $options['id']; + + if (isset(static::$types[$this->type]) === false) { + throw new InvalidArgumentException('Invalid database type: ' . $this->type); + } + + // fetch the dsn and store it + $this->dsn = static::$types[$this->type]['dsn']($options); + + // try to connect + $this->connection = new PDO($this->dsn, $options['user'], $options['password']); + $this->connection->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + $this->connection->setAttribute(PDO::ATTR_EMULATE_PREPARES, false); + + // store the connection + static::$connections[$this->id] = $this; + + // return the connection + return $this->connection; + } + + /** + * Returns the currently active connection + * + * @return \PDO|null + */ + public function connection(): ?PDO + { + return $this->connection; + } + + /** + * Sets the exception mode + * + * @param bool $fail + * @return \Kirby\Database\Database + */ + public function fail(bool $fail = true) + { + $this->fail = $fail; + return $this; + } + + /** + * Returns the used database type + * + * @return string + */ + public function type(): string + { + return $this->type; + } + + /** + * Returns the used table name prefix + * + * @return string|null + */ + public function prefix(): ?string + { + return $this->prefix; + } + + /** + * Escapes a value to be used for a safe query + * NOTE: Prepared statements using bound parameters are more secure and solid + * + * @param string $value + * @return string + */ + public function escape(string $value): string + { + return substr($this->connection()->quote($value), 1, -1); + } + + /** + * Adds a value to the db trace and also returns the entire trace if nothing is specified + * + * @param array|null $data + * @return array + */ + public function trace(array $data = null): array + { + // return the full trace + if ($data === null) { + return $this->trace; + } + + // add a new entry to the trace + $this->trace[] = $data; + + return $this->trace; + } + + /** + * Returns the number of affected rows for the last query + * + * @return int|null + */ + public function affected(): ?int + { + return $this->affected; + } + + /** + * Returns the last id if available + * + * @return int|null + */ + public function lastId(): ?int + { + return $this->lastId; + } + + /** + * Returns the last query + * + * @return string|null + */ + public function lastQuery(): ?string + { + return $this->lastQuery; + } + + /** + * Returns the last set of results + * + * @return mixed + */ + public function lastResult() + { + return $this->lastResult; + } + + /** + * Returns the last db error + * + * @return \Throwable + */ + public function lastError() + { + return $this->lastError; + } + + /** + * Returns the name of the database + * + * @return string|null + */ + public function name(): ?string + { + return $this->database; + } + + /** + * Private method to execute database queries. + * This is used by the query() and execute() methods + * + * @param string $query + * @param array $bindings + * @return bool + */ + protected function hit(string $query, array $bindings = []): bool + { + // try to prepare and execute the sql + try { + $this->statement = $this->connection->prepare($query); + $this->statement->execute($bindings); + + $this->affected = $this->statement->rowCount(); + $this->lastId = Str::startsWith($query, 'insert ', true) ? $this->connection->lastInsertId() : null; + $this->lastError = null; + + // store the final sql to add it to the trace later + $this->lastQuery = $this->statement->queryString; + } catch (Throwable $e) { + + // store the error + $this->affected = 0; + $this->lastError = $e; + $this->lastId = null; + $this->lastQuery = $query; + + // only throw the extension if failing is allowed + if ($this->fail === true) { + throw $e; + } + } + + // add a new entry to the singleton trace array + $this->trace([ + 'query' => $this->lastQuery, + 'bindings' => $bindings, + 'error' => $this->lastError + ]); + + // return true or false on success or failure + return $this->lastError === null; + } + + /** + * Executes a sql query, which is expected to return a set of results + * + * @param string $query + * @param array $bindings + * @param array $params + * @return mixed + */ + public function query(string $query, array $bindings = [], array $params = []) + { + $defaults = [ + 'flag' => null, + 'method' => 'fetchAll', + 'fetch' => 'Kirby\Toolkit\Obj', + 'iterator' => 'Kirby\Toolkit\Collection', + ]; + + $options = array_merge($defaults, $params); + + if ($this->hit($query, $bindings) === false) { + return false; + } + + // define the default flag for the fetch method + if ($options['fetch'] instanceof Closure || $options['fetch'] === 'array') { + $flags = PDO::FETCH_ASSOC; + } else { + $flags = PDO::FETCH_CLASS|PDO::FETCH_PROPS_LATE; + } + + // add optional flags + if (empty($options['flag']) === false) { + $flags |= $options['flag']; + } + + // set the fetch mode + if ($options['fetch'] instanceof Closure || $options['fetch'] === 'array') { + $this->statement->setFetchMode($flags); + } else { + $this->statement->setFetchMode($flags, $options['fetch']); + } + + // fetch that stuff + $results = $this->statement->{$options['method']}(); + + // apply the fetch closure to all results if given + if ($options['fetch'] instanceof Closure) { + foreach ($results as $key => $result) { + $results[$key] = $options['fetch']($result, $key); + } + } + + if ($options['iterator'] === 'array') { + return $this->lastResult = $results; + } + + return $this->lastResult = new $options['iterator']($results); + } + + /** + * Executes a sql query, which is expected to not return a set of results + * + * @param string $query + * @param array $bindings + * @return bool + */ + public function execute(string $query, array $bindings = []): bool + { + return $this->lastResult = $this->hit($query, $bindings); + } + + /** + * Returns the correct Sql generator instance + * for the type of database + * + * @return \Kirby\Database\Sql + */ + public function sql() + { + $className = static::$types[$this->type]['sql'] ?? 'Sql'; + return new $className($this); + } + + /** + * Sets the current table, which should be queried. Returns a + * Query object, which can be used to build a full query + * for that table + * + * @param string $table + * @return \Kirby\Database\Query + */ + public function table(string $table) + { + return new Query($this, $this->prefix() . $table); + } + + /** + * Checks if a table exists in the current database + * + * @param string $table + * @return bool + */ + public function validateTable(string $table): bool + { + if ($this->tables === null) { + // Get the list of tables from the database + $sql = $this->sql()->tables($this->database); + $results = $this->query($sql['query'], $sql['bindings']); + + if ($results) { + $this->tables = $results->pluck('name'); + } else { + return false; + } + } + + return in_array($table, $this->tables) === true; + } + + /** + * Checks if a column exists in a specified table + * + * @param string $table + * @param string $column + * @return bool + */ + public function validateColumn(string $table, string $column): bool + { + if (isset($this->columnWhitelist[$table]) === false) { + if ($this->validateTable($table) === false) { + $this->columnWhitelist[$table] = []; + return false; + } + + // Get the column whitelist from the database + $sql = $this->sql()->columns($table); + $results = $this->query($sql['query'], $sql['bindings']); + + if ($results) { + $this->columnWhitelist[$table] = $results->pluck('name'); + } else { + return false; + } + } + + return in_array($column, $this->columnWhitelist[$table]) === true; + } + + /** + * Creates a new table + * + * @param string $table + * @param array $columns + * @return bool + */ + public function createTable($table, $columns = []): bool + { + $sql = $this->sql()->createTable($table, $columns); + $queries = Str::split($sql['query'], ';'); + + foreach ($queries as $query) { + $query = trim($query); + + if ($this->execute($query, $sql['bindings']) === false) { + return false; + } + } + + // update cache + if (in_array($table, $this->tables ?? []) !== true) { + $this->tables[] = $table; + } + + return true; + } + + /** + * Drops a table + * + * @param string $table + * @return bool + */ + public function dropTable(string $table): bool + { + $sql = $this->sql()->dropTable($table); + if ($this->execute($sql['query'], $sql['bindings']) !== true) { + return false; + } + + // update cache + $key = array_search($table, $this->tables ?? []); + if ($key !== false) { + unset($this->tables[$key]); + } + + return true; + } + + /** + * Magic way to start queries for tables by + * using a method named like the table. + * I.e. $db->users()->all() + * + * @param mixed $method + * @param mixed $arguments + * @return \Kirby\Database\Query + */ + public function __call($method, $arguments = null) + { + return $this->table($method); + } +} + +/** + * MySQL database connector + */ +Database::$types['mysql'] = [ + 'sql' => 'Kirby\Database\Sql\Mysql', + 'dsn' => function (array $params) { + if (isset($params['host']) === false && isset($params['socket']) === false) { + throw new InvalidArgumentException('The mysql connection requires either a "host" or a "socket" parameter'); + } + + if (isset($params['database']) === false) { + throw new InvalidArgumentException('The mysql connection requires a "database" parameter'); + } + + $parts = []; + + if (empty($params['host']) === false) { + $parts[] = 'host=' . $params['host']; + } + + if (empty($params['port']) === false) { + $parts[] = 'port=' . $params['port']; + } + + if (empty($params['socket']) === false) { + $parts[] = 'unix_socket=' . $params['socket']; + } + + if (empty($params['database']) === false) { + $parts[] = 'dbname=' . $params['database']; + } + + $parts[] = 'charset=' . ($params['charset'] ?? 'utf8'); + + return 'mysql:' . implode(';', $parts); + } +]; + +/** + * SQLite database connector + */ +Database::$types['sqlite'] = [ + 'sql' => 'Kirby\Database\Sql\Sqlite', + 'dsn' => function (array $params) { + if (isset($params['database']) === false) { + throw new InvalidArgumentException('The sqlite connection requires a "database" parameter'); + } + + return 'sqlite:' . $params['database']; + } +]; diff --git a/kirby/src/Database/Db.php b/kirby/src/Database/Db.php new file mode 100644 index 0000000..08a1363 --- /dev/null +++ b/kirby/src/Database/Db.php @@ -0,0 +1,284 @@ + + * @link https://getkirby.com + * @copyright Bastian Allgeier GmbH + * @license https://opensource.org/licenses/MIT + */ +class Db +{ + /** + * Query shortcuts + * + * @var array + */ + public static $queries = []; + + /** + * The singleton Database object + * + * @var \Kirby\Database\Database + */ + public static $connection = null; + + /** + * (Re)connect the database + * + * @param array|null $params Pass `[]` to use the default params from the config, + * don't pass any argument to get the current connection + * @return \Kirby\Database\Database + */ + public static function connect(?array $params = null) + { + if ($params === null && static::$connection !== null) { + return static::$connection; + } + + // try to connect with the default + // connection settings if no params are set + $defaults = [ + 'type' => Config::get('db.type', 'mysql'), + 'host' => Config::get('db.host', 'localhost'), + 'user' => Config::get('db.user', 'root'), + 'password' => Config::get('db.password', ''), + 'database' => Config::get('db.database', ''), + 'prefix' => Config::get('db.prefix', ''), + 'port' => Config::get('db.port', '') + ]; + $params = $params ?? $defaults; + + return static::$connection = new Database($params); + } + + /** + * Returns the current database connection + * + * @return \Kirby\Database\Database|null + */ + public static function connection() + { + return static::$connection; + } + + /** + * Sets the current table which should be queried. Returns a + * Query object, which can be used to build a full query for + * that table. + * + * @param string $table + * @return \Kirby\Database\Query + */ + public static function table(string $table) + { + $db = static::connect(); + return $db->table($table); + } + + /** + * Executes a raw SQL query which expects a set of results + * + * @param string $query + * @param array $bindings + * @param array $params + * @return mixed + */ + public static function query(string $query, array $bindings = [], array $params = []) + { + $db = static::connect(); + return $db->query($query, $bindings, $params); + } + + /** + * Executes a raw SQL query which expects no set of results (i.e. update, insert, delete) + * + * @param string $query + * @param array $bindings + * @return bool + */ + public static function execute(string $query, array $bindings = []): bool + { + $db = static::connect(); + return $db->execute($query, $bindings); + } + + /** + * Magic calls for other static Db methods are + * redirected to either a predefined query or + * the respective method of the Database object + * + * @param string $method + * @param mixed $arguments + * @return mixed + * @throws \Kirby\Exception\InvalidArgumentException + */ + public static function __callStatic(string $method, $arguments) + { + if (isset(static::$queries[$method])) { + return static::$queries[$method](...$arguments); + } + + if (static::$connection !== null && method_exists(static::$connection, $method) === true) { + return call_user_func_array([static::$connection, $method], $arguments); + } + + throw new InvalidArgumentException('Invalid static Db method: ' . $method); + } +} + +/** + * Shortcut for SELECT clauses + * @codeCoverageIgnore + * + * @param string $table The name of the table which should be queried + * @param mixed $columns Either a string with columns or an array of column names + * @param mixed $where The WHERE clause; can be a string or an array + * @param string $order + * @param int $offset + * @param int $limit + * @return mixed + */ +Db::$queries['select'] = function (string $table, $columns = '*', $where = null, string $order = null, int $offset = 0, int $limit = null) { + return Db::table($table)->select($columns)->where($where)->order($order)->offset($offset)->limit($limit)->all(); +}; + +/** + * Shortcut for selecting a single row in a table + * @codeCoverageIgnore + * + * @param string $table The name of the table which should be queried + * @param mixed $columns Either a string with columns or an array of column names + * @param mixed $where The WHERE clause; can be a string or an array + * @param string $order + * @param int $offset + * @param int $limit + * @return mixed + */ +Db::$queries['first'] = Db::$queries['row'] = Db::$queries['one'] = function (string $table, $columns = '*', $where = null, string $order = null) { + return Db::table($table)->select($columns)->where($where)->order($order)->first(); +}; + +/** + * Returns only values from a single column + * @codeCoverageIgnore + * + * @param string $table The name of the table which should be queried + * @param string $column The name of the column to select from + * @param mixed $where The WHERE clause; can be a string or an array + * @param string $order + * @param int $offset + * @param int $limit + * @return mixed + */ +Db::$queries['column'] = function (string $table, string $column, $where = null, string $order = null, int $offset = 0, int $limit = null) { + return Db::table($table)->where($where)->order($order)->offset($offset)->limit($limit)->column($column); +}; + +/** + * Shortcut for inserting a new row into a table + * @codeCoverageIgnore + * + * @param string $table The name of the table which should be queried + * @param array $values An array of values which should be inserted + * @return int ID of the inserted row + */ +Db::$queries['insert'] = function (string $table, array $values): int { + return Db::table($table)->insert($values); +}; + +/** + * Shortcut for updating a row in a table + * @codeCoverageIgnore + * + * @param string $table The name of the table which should be queried + * @param array $values An array of values which should be inserted + * @param mixed $where An optional WHERE clause + * @return bool + */ +Db::$queries['update'] = function (string $table, array $values, $where = null): bool { + return Db::table($table)->where($where)->update($values); +}; + +/** + * Shortcut for deleting rows in a table + * @codeCoverageIgnore + * + * @param string $table The name of the table which should be queried + * @param mixed $where An optional WHERE clause + * @return bool + */ +Db::$queries['delete'] = function (string $table, $where = null): bool { + return Db::table($table)->where($where)->delete(); +}; + +/** + * Shortcut for counting rows in a table + * @codeCoverageIgnore + * + * @param string $table The name of the table which should be queried + * @param mixed $where An optional WHERE clause + * @return int + */ +Db::$queries['count'] = function (string $table, $where = null): int { + return Db::table($table)->where($where)->count(); +}; + +/** + * Shortcut for calculating the minimum value in a column + * @codeCoverageIgnore + * + * @param string $table The name of the table which should be queried + * @param string $column The name of the column of which the minimum should be calculated + * @param mixed $where An optional WHERE clause + * @return float + */ +Db::$queries['min'] = function (string $table, string $column, $where = null): float { + return Db::table($table)->where($where)->min($column); +}; + +/** + * Shortcut for calculating the maximum value in a column + * @codeCoverageIgnore + * + * @param string $table The name of the table which should be queried + * @param string $column The name of the column of which the maximum should be calculated + * @param mixed $where An optional WHERE clause + * @return float + */ +Db::$queries['max'] = function (string $table, string $column, $where = null): float { + return Db::table($table)->where($where)->max($column); +}; + +/** + * Shortcut for calculating the average value in a column + * @codeCoverageIgnore + * + * @param string $table The name of the table which should be queried + * @param string $column The name of the column of which the average should be calculated + * @param mixed $where An optional WHERE clause + * @return float + */ +Db::$queries['avg'] = function (string $table, string $column, $where = null): float { + return Db::table($table)->where($where)->avg($column); +}; + +/** + * Shortcut for calculating the sum of all values in a column + * @codeCoverageIgnore + * + * @param string $table The name of the table which should be queried + * @param string $column The name of the column of which the sum should be calculated + * @param mixed $where An optional WHERE clause + * @return float + */ +Db::$queries['sum'] = function (string $table, string $column, $where = null): float { + return Db::table($table)->where($where)->sum($column); +}; diff --git a/kirby/src/Database/Query.php b/kirby/src/Database/Query.php new file mode 100644 index 0000000..f56d951 --- /dev/null +++ b/kirby/src/Database/Query.php @@ -0,0 +1,1070 @@ + + * @link https://getkirby.com + * @copyright Bastian Allgeier GmbH + * @license https://opensource.org/licenses/MIT + */ +class Query +{ + const ERROR_INVALID_QUERY_METHOD = 0; + + /** + * Parent Database object + * + * @var \Kirby\Database\Database + */ + protected $database = null; + + /** + * The object which should be fetched for each row + * or function to call for each row + * + * @var string|\Closure + */ + protected $fetch = 'Kirby\Toolkit\Obj'; + + /** + * The iterator class, which should be used for result sets + * + * @var string + */ + protected $iterator = 'Kirby\Toolkit\Collection'; + + /** + * An array of bindings for the final query + * + * @var array + */ + protected $bindings = []; + + /** + * The table name + * + * @var string + */ + protected $table; + + /** + * The name of the primary key column + * + * @var string + */ + protected $primaryKeyName = 'id'; + + /** + * An array with additional join parameters + * + * @var array + */ + protected $join; + + /** + * A list of columns, which should be selected + * + * @var array|string + */ + protected $select; + + /** + * Boolean for distinct select clauses + * + * @var bool + */ + protected $distinct; + + /** + * Boolean for if exceptions should be thrown on failing queries + * + * @var bool + */ + protected $fail = false; + + /** + * A list of values for update and insert clauses + * + * @var array + */ + protected $values; + + /** + * WHERE clause + * + * @var mixed + */ + protected $where; + + /** + * GROUP BY clause + * + * @var mixed + */ + protected $group; + + /** + * HAVING clause + * + * @var mixed + */ + protected $having; + + /** + * ORDER BY clause + * + * @var mixed + */ + protected $order; + + /** + * The offset, which should be applied to the select query + * + * @var int + */ + protected $offset = 0; + + /** + * The limit, which should be applied to the select query + * + * @var int + */ + protected $limit; + + /** + * Boolean to enable query debugging + * + * @var bool + */ + protected $debug = false; + + /** + * Constructor + * + * @param \Kirby\Database\Database $database Database object + * @param string $table Optional name of the table, which should be queried + */ + public function __construct(Database $database, string $table) + { + $this->database = $database; + $this->table($table); + } + + /** + * Reset the query class after each db hit + */ + protected function reset() + { + $this->bindings = []; + $this->join = null; + $this->select = null; + $this->distinct = null; + $this->fail = false; + $this->values = null; + $this->where = null; + $this->group = null; + $this->having = null; + $this->order = null; + $this->offset = 0; + $this->limit = null; + $this->debug = false; + } + + /** + * Enables query debugging. + * If enabled, the query will return an array with all important info about + * the query instead of actually executing the query and returning results + * + * @param bool $debug + * @return \Kirby\Database\Query + */ + public function debug(bool $debug = true) + { + $this->debug = $debug; + return $this; + } + + /** + * Enables distinct select clauses. + * + * @param bool $distinct + * @return \Kirby\Database\Query + */ + public function distinct(bool $distinct = true) + { + $this->distinct = $distinct; + return $this; + } + + /** + * Enables failing queries. + * If enabled queries will no longer fail silently but throw an exception + * + * @param bool $fail + * @return \Kirby\Database\Query + */ + public function fail(bool $fail = true) + { + $this->fail = $fail; + return $this; + } + + /** + * Sets the object class, which should be fetched; + * set this to `'array'` to get a simple array instead of an object; + * pass a function that receives the `$data` and the `$key` to generate arbitrary data structures + * + * @param string|\Closure $fetch + * @return \Kirby\Database\Query + */ + public function fetch($fetch) + { + $this->fetch = $fetch; + return $this; + } + + /** + * Sets the iterator class, which should be used for multiple results + * Set this to array to get a simple array instead of an iterator object + * + * @param string $iterator + * @return \Kirby\Database\Query + */ + public function iterator(string $iterator) + { + $this->iterator = $iterator; + return $this; + } + + /** + * Sets the name of the table, which should be queried + * + * @param string $table + * @return \Kirby\Database\Query + * @throws \Kirby\Exception\InvalidArgumentException if the table does not exist + */ + public function table(string $table) + { + if ($this->database->validateTable($table) === false) { + throw new InvalidArgumentException('Invalid table: ' . $table); + } + + $this->table = $table; + return $this; + } + + /** + * Sets the name of the primary key column + * + * @param string $primaryKeyName + * @return \Kirby\Database\Query + */ + public function primaryKeyName(string $primaryKeyName) + { + $this->primaryKeyName = $primaryKeyName; + return $this; + } + + /** + * Sets the columns, which should be selected from the table + * By default all columns will be selected + * + * @param mixed $select Pass either a string of columns or an array + * @return \Kirby\Database\Query + */ + public function select($select) + { + $this->select = $select; + return $this; + } + + /** + * Adds a new join clause to the query + * + * @param string $table Name of the table, which should be joined + * @param string $on The on clause for this join + * @param string $type The join type. Uses an inner join by default + * @return $this + */ + public function join(string $table, string $on, string $type = 'JOIN') + { + $join = [ + 'table' => $table, + 'on' => $on, + 'type' => $type + ]; + + $this->join[] = $join; + return $this; + } + + /** + * Shortcut for creating a left join clause + * + * @param string $table Name of the table, which should be joined + * @param string $on The on clause for this join + * @return \Kirby\Database\Query + */ + public function leftJoin(string $table, string $on) + { + return $this->join($table, $on, 'left'); + } + + /** + * Shortcut for creating a right join clause + * + * @param string $table Name of the table, which should be joined + * @param string $on The on clause for this join + * @return \Kirby\Database\Query + */ + public function rightJoin(string $table, string $on) + { + return $this->join($table, $on, 'right'); + } + + /** + * Shortcut for creating an inner join clause + * + * @param string $table Name of the table, which should be joined + * @param string $on The on clause for this join + * @return \Kirby\Database\Query + */ + public function innerJoin($table, $on) + { + return $this->join($table, $on, 'inner'); + } + + /** + * Sets the values which should be used for the update or insert clause + * + * @param mixed $values Can either be a string or an array of values + * @return \Kirby\Database\Query + */ + public function values($values = []) + { + if ($values !== null) { + $this->values = $values; + } + return $this; + } + + /** + * Attaches additional bindings to the query. + * Also can be used as getter for all attached bindings by not passing an argument. + * + * @param mixed $bindings Array of bindings or null to use this method as getter + * @return array|\Kirby\Database\Query + */ + public function bindings(array $bindings = null) + { + if (is_array($bindings) === true) { + $this->bindings = array_merge($this->bindings, $bindings); + return $this; + } + + return $this->bindings; + } + + /** + * Attaches an additional where clause + * + * All available ways to add where clauses + * + * ->where('username like "myuser"'); (args: 1) + * ->where(['username' => 'myuser']); (args: 1) + * ->where(function($where) { $where->where('id', '=', 1) }) (args: 1) + * ->where('username like ?', 'myuser') (args: 2) + * ->where('username', 'like', 'myuser'); (args: 3) + * + * @param mixed ...$args + * @return \Kirby\Database\Query + */ + public function where(...$args) + { + $this->where = $this->filterQuery($args, $this->where); + return $this; + } + + /** + * Shortcut to attach a where clause with an OR operator. + * Check out the where() method docs for additional info. + * + * @param mixed ...$args + * @return \Kirby\Database\Query + */ + public function orWhere(...$args) + { + $mode = A::last($args); + + // if there's a where clause mode attribute attached… + if (in_array($mode, ['AND', 'OR']) === true) { + // remove that from the list of arguments + array_pop($args); + } + + // make sure to always attach the OR mode indicator + $args[] = 'OR'; + + $this->where(...$args); + return $this; + } + + /** + * Shortcut to attach a where clause with an AND operator. + * Check out the where() method docs for additional info. + * + * @param mixed ...$args + * @return \Kirby\Database\Query + */ + public function andWhere(...$args) + { + $mode = A::last($args); + + // if there's a where clause mode attribute attached… + if (in_array($mode, ['AND', 'OR']) === true) { + // remove that from the list of arguments + array_pop($args); + } + + // make sure to always attach the AND mode indicator + $args[] = 'AND'; + + $this->where(...$args); + return $this; + } + + /** + * Attaches a group by clause + * + * @param string|null $group + * @return \Kirby\Database\Query + */ + public function group(string $group = null) + { + $this->group = $group; + return $this; + } + + /** + * Attaches an additional having clause + * + * All available ways to add having clauses + * + * ->having('username like "myuser"'); (args: 1) + * ->having(['username' => 'myuser']); (args: 1) + * ->having(function($having) { $having->having('id', '=', 1) }) (args: 1) + * ->having('username like ?', 'myuser') (args: 2) + * ->having('username', 'like', 'myuser'); (args: 3) + * + * @param mixed ...$args + * @return \Kirby\Database\Query + */ + public function having(...$args) + { + $this->having = $this->filterQuery($args, $this->having); + return $this; + } + + /** + * Attaches an order clause + * + * @param string|null $order + * @return \Kirby\Database\Query + */ + public function order(string $order = null) + { + $this->order = $order; + return $this; + } + + /** + * Sets the offset for select clauses + * + * @param int|null $offset + * @return \Kirby\Database\Query + */ + public function offset(int $offset = null) + { + $this->offset = $offset; + return $this; + } + + /** + * Sets the limit for select clauses + * + * @param int|null $limit + * @return \Kirby\Database\Query + */ + public function limit(int $limit = null) + { + $this->limit = $limit; + return $this; + } + + /** + * Builds the different types of SQL queries + * This uses the SQL class to build stuff. + * + * @param string $type (select, update, insert) + * @return array The final query + */ + public function build(string $type) + { + $sql = $this->database->sql(); + + switch ($type) { + case 'select': + return $sql->select([ + 'table' => $this->table, + 'columns' => $this->select, + 'join' => $this->join, + 'distinct' => $this->distinct, + 'where' => $this->where, + 'group' => $this->group, + 'having' => $this->having, + 'order' => $this->order, + 'offset' => $this->offset, + 'limit' => $this->limit, + 'bindings' => $this->bindings + ]); + case 'update': + return $sql->update([ + 'table' => $this->table, + 'where' => $this->where, + 'values' => $this->values, + 'bindings' => $this->bindings + ]); + case 'insert': + return $sql->insert([ + 'table' => $this->table, + 'values' => $this->values, + 'bindings' => $this->bindings + ]); + case 'delete': + return $sql->delete([ + 'table' => $this->table, + 'where' => $this->where, + 'bindings' => $this->bindings + ]); + } + } + + /** + * Builds a count query + * + * @return int + */ + public function count(): int + { + return (int)$this->aggregate('COUNT'); + } + + /** + * Builds a max query + * + * @param string $column + * @return float + */ + public function max(string $column): float + { + return (float)$this->aggregate('MAX', $column); + } + + /** + * Builds a min query + * + * @param string $column + * @return float + */ + public function min(string $column): float + { + return (float)$this->aggregate('MIN', $column); + } + + /** + * Builds a sum query + * + * @param string $column + * @return float + */ + public function sum(string $column): float + { + return (float)$this->aggregate('SUM', $column); + } + + /** + * Builds an average query + * + * @param string $column + * @return float + */ + public function avg(string $column): float + { + return (float)$this->aggregate('AVG', $column); + } + + /** + * Builds an aggregation query. + * This is used by all the aggregation methods above + * + * @param string $method + * @param string $column + * @param int $default An optional default value, which should be returned if the query fails + * @return mixed + */ + public function aggregate(string $method, string $column = '*', $default = 0) + { + // reset the sorting to avoid counting issues + $this->order = null; + + // validate column + if ($column !== '*') { + $sql = $this->database->sql(); + $column = $sql->columnName($this->table, $column); + } + + $fetch = $this->fetch; + $row = $this->select($method . '(' . $column . ') as aggregation')->fetch('Obj')->first(); + + if ($this->debug === true) { + return $row; + } + + $result = $row ? $row->get('aggregation') : $default; + + $this->fetch($fetch); + + return $result; + } + + /** + * Used as an internal shortcut for firing a db query + * + * @param string|array $sql + * @param array $params + * @return mixed + */ + protected function query($sql, array $params = []) + { + if (is_string($sql) === true) { + $sql = [ + 'query' => $sql, + 'bindings' => $this->bindings() + ]; + } + + if ($this->debug) { + return [ + 'query' => $sql['query'], + 'bindings' => $this->bindings(), + 'options' => $params + ]; + } + + if ($this->fail) { + $this->database->fail(); + } + + $result = $this->database->query($sql['query'], $sql['bindings'], $params); + + $this->reset(); + + return $result; + } + + /** + * Used as an internal shortcut for executing a db query + * + * @param string|array $sql + * @param array $params + * @return mixed + */ + protected function execute($sql, array $params = []) + { + if (is_string($sql) === true) { + $sql = [ + 'query' => $sql, + 'bindings' => $this->bindings() + ]; + } + + if ($this->debug === true) { + return [ + 'query' => $sql['query'], + 'bindings' => $sql['bindings'], + 'options' => $params + ]; + } + + if ($this->fail) { + $this->database->fail(); + } + + $result = $this->database->execute($sql['query'], $sql['bindings'], $params); + + $this->reset(); + + return $result; + } + + /** + * Selects only one row from a table + * + * @return object + */ + public function first() + { + return $this->query($this->offset(0)->limit(1)->build('select'), [ + 'fetch' => $this->fetch, + 'iterator' => 'array', + 'method' => 'fetch', + ]); + } + + /** + * Selects only one row from a table + * + * @return object + */ + public function row() + { + return $this->first(); + } + + /** + * Selects only one row from a table + * + * @return object + */ + public function one() + { + return $this->first(); + } + + /** + * Automatically adds pagination to a query + * + * @param int $page + * @param int $limit The number of rows, which should be returned for each page + * @return object Collection iterator with attached pagination object + */ + public function page(int $page, int $limit) + { + // clone this to create a counter query + $counter = clone $this; + + // count the total number of rows for this query + $count = $counter->debug(false)->count(); + + // pagination + $pagination = new Pagination([ + 'limit' => $limit, + 'page' => $page, + 'total' => $count, + ]); + + // apply it to the dataset and retrieve all rows. make sure to use Collection as the iterator to be able to attach the pagination object + $iterator = $this->iterator; + $collection = $this->offset($pagination->offset())->limit($pagination->limit())->iterator('Collection')->all(); + + $this->iterator($iterator); + + // return debug information if debug mode is active + if ($this->debug) { + $collection['totalcount'] = $count; + return $collection; + } + + // store all pagination vars in a separate object + if ($collection) { + $collection->paginate($pagination); + } + + // return the limited collection + return $collection; + } + + /** + * Returns all matching rows from a table + * + * @return mixed + */ + public function all() + { + return $this->query($this->build('select'), [ + 'fetch' => $this->fetch, + 'iterator' => $this->iterator, + ]); + } + + /** + * Returns only values from a single column + * + * @param string $column + * @return mixed + */ + public function column(string $column) + { + // if there isn't already an explicit order, order by the primary key + // instead of the column that was requested (which would be implied otherwise) + if ($this->order === null) { + $sql = $this->database->sql(); + $primaryKey = $sql->combineIdentifier($this->table, $this->primaryKeyName); + + $this->order($primaryKey . ' ASC'); + } + + $results = $this->query($this->select([$column])->build('select'), [ + 'iterator' => 'array', + 'fetch' => 'array', + ]); + + if ($this->debug === true) { + return $results; + } + + $results = array_column($results, $column); + + if ($this->iterator === 'array') { + return $results; + } + + $iterator = $this->iterator; + + return new $iterator($results); + } + + /** + * Find a single row by column and value + * + * @param string $column + * @param mixed $value + * @return mixed + */ + public function findBy(string $column, $value) + { + return $this->where([$column => $value])->first(); + } + + /** + * Find a single row by its primary key + * + * @param mixed $id + * @return mixed + */ + public function find($id) + { + return $this->findBy($this->primaryKeyName, $id); + } + + /** + * Fires an insert query + * + * @param mixed $values You can pass values here or set them with ->values() before + * @return mixed Returns the last inserted id on success or false. + */ + public function insert($values = null) + { + $query = $this->execute($this->values($values)->build('insert')); + + if ($this->debug === true) { + return $query; + } + + return $query ? $this->database->lastId() : false; + } + + /** + * Fires an update query + * + * @param mixed $values You can pass values here or set them with ->values() before + * @param mixed $where You can pass a where clause here or set it with ->where() before + * @return bool + */ + public function update($values = null, $where = null) + { + return $this->execute($this->values($values)->where($where)->build('update')); + } + + /** + * Fires a delete query + * + * @param mixed $where You can pass a where clause here or set it with ->where() before + * @return bool + */ + public function delete($where = null) + { + return $this->execute($this->where($where)->build('delete')); + } + + /** + * Enables magic queries like findByUsername or findByEmail + * + * @param string $method + * @param array $arguments + * @return mixed + */ + public function __call(string $method, array $arguments = []) + { + if (preg_match('!^findBy([a-z]+)!i', $method, $match)) { + $column = Str::lower($match[1]); + return $this->findBy($column, $arguments[0]); + } else { + throw new InvalidArgumentException('Invalid query method: ' . $method, static::ERROR_INVALID_QUERY_METHOD); + } + } + + /** + * Builder for where and having clauses + * + * @param array $args Arguments, see where() description + * @param mixed $current Current value (like $this->where) + * @return string + */ + protected function filterQuery(array $args, $current) + { + $mode = A::last($args); + $result = ''; + + // if there's a where clause mode attribute attached… + if (in_array($mode, ['AND', 'OR'])) { + // remove that from the list of arguments + array_pop($args); + } else { + $mode = 'AND'; + } + + switch (count($args)) { + case 1: + + if ($args[0] === null) { + return $current; + + // ->where('username like "myuser"'); + } elseif (is_string($args[0]) === true) { + + // simply add the entire string to the where clause + // escaping or using bindings has to be done before calling this method + $result = $args[0]; + + // ->where(['username' => 'myuser']); + } elseif (is_array($args[0]) === true) { + + // simple array mode (AND operator) + $sql = $this->database->sql()->values($this->table, $args[0], ' AND ', true, true); + + $result = $sql['query']; + + $this->bindings($sql['bindings']); + } elseif (is_callable($args[0]) === true) { + $query = clone $this; + call_user_func($args[0], $query); + + // copy over the bindings from the nested query + $this->bindings = array_merge($this->bindings, $query->bindings); + + $result = '(' . $query->where . ')'; + } + + break; + case 2: + + // ->where('username like :username', ['username' => 'myuser']) + if (is_string($args[0]) === true && is_array($args[1]) === true) { + + // prepared where clause + $result = $args[0]; + + // store the bindings + $this->bindings($args[1]); + + // ->where('username like ?', 'myuser') + } elseif (is_string($args[0]) === true && is_string($args[1]) === true) { + + // prepared where clause + $result = $args[0]; + + // store the bindings + $this->bindings([$args[1]]); + } + + break; + case 3: + + // ->where('username', 'like', 'myuser'); + if (is_string($args[0]) === true && is_string($args[1]) === true) { + + // validate column + $sql = $this->database->sql(); + $key = $sql->columnName($this->table, $args[0]); + + // ->where('username', 'in', ['myuser', 'myotheruser']); + if (is_array($args[2]) === true) { + $predicate = trim(strtoupper($args[1])); + + if (in_array($predicate, ['IN', 'NOT IN']) === false) { + throw new InvalidArgumentException('Invalid predicate ' . $predicate); + } + + // build a list of bound values + $values = []; + $bindings = []; + + foreach ($args[2] as $value) { + $valueBinding = $sql->bindingName('value'); + $bindings[$valueBinding] = $value; + $values[] = $valueBinding; + } + + // add that to the where clause in parenthesis + $result = $key . ' ' . $predicate . ' (' . implode(', ', $values) . ')'; + + $this->bindings($bindings); + + // ->where('username', 'like', 'myuser'); + } else { + $predicate = trim(strtoupper($args[1])); + $predicates = [ + '=', '>=', '>', '<=', '<', '<>', '!=', '<=>', + 'IS', 'IS NOT', + 'BETWEEN', 'NOT BETWEEN', + 'LIKE', 'NOT LIKE', + 'SOUNDS LIKE', + 'REGEXP', 'NOT REGEXP' + ]; + + if (in_array($predicate, $predicates) === false) { + throw new InvalidArgumentException('Invalid predicate/operator ' . $predicate); + } + + $valueBinding = $sql->bindingName('value'); + $bindings[$valueBinding] = $args[2]; + + $result = $key . ' ' . $predicate . ' ' . $valueBinding; + + $this->bindings($bindings); + } + } + + break; + + } + + // attach the where clause + if (empty($current) === false) { + return $current . ' ' . $mode . ' ' . $result; + } else { + return $result; + } + } +} diff --git a/kirby/src/Database/Sql.php b/kirby/src/Database/Sql.php new file mode 100644 index 0000000..2738cfd --- /dev/null +++ b/kirby/src/Database/Sql.php @@ -0,0 +1,955 @@ + + * @link https://getkirby.com + * @copyright Bastian Allgeier GmbH + * @license https://opensource.org/licenses/MIT + */ +abstract class Sql +{ + /** + * List of literals which should not be escaped in queries + * + * @var array + */ + public static $literals = ['NOW()', null]; + + /** + * The parent database connection + * + * @var \Kirby\Database\Database + */ + protected $database; + + /** + * List of used bindings; used to avoid + * duplicate binding names + * + * @var array + */ + protected $bindings = []; + + /** + * Constructor + * @codeCoverageIgnore + * + * @param \Kirby\Database\Database $database + */ + public function __construct($database) + { + $this->database = $database; + } + + /** + * Returns a randomly generated binding name + * + * @param string $label String that only contains alphanumeric chars and + * underscores to use as a human-readable identifier + * @return string Binding name that is guaranteed to be unique for this connection + */ + public function bindingName(string $label): string + { + // make sure that the binding name is safe to prevent injections; + // otherwise use a generic label + if (!$label || preg_match('/^[a-zA-Z0-9_]+$/', $label) !== 1) { + $label = 'invalid'; + } + + // generate random bindings until the name is unique + do { + $binding = ':' . $label . '_' . Str::random(8, 'alphaNum'); + } while (in_array($binding, $this->bindings) === true); + + // cache the generated binding name for future invocations + $this->bindings[] = $binding; + return $binding; + } + + /** + * Returns a query to list the columns of a specified table; + * the query needs to return rows with a column `name` + * + * @param string $table Table name + * @return array + */ + abstract public function columns(string $table): array; + + /** + * Returns a query snippet for a column default value + * + * @param string $name Column name + * @param array $column Column definition array with an optional `default` key + * @return array Array with a `query` string and a `bindings` array + */ + public function columnDefault(string $name, array $column): array + { + if (isset($column['default']) === false) { + return [ + 'query' => null, + 'bindings' => [] + ]; + } + + $binding = $this->bindingName($name . '_default'); + + return [ + 'query' => 'DEFAULT ' . $binding, + 'bindings' => [ + $binding => $column['default'] + ] + ]; + } + + /** + * Returns the cleaned identifier based on the table and column name + * + * @param string $table Table name + * @param string $column Column name + * @param bool $enforceQualified If true, a qualified identifier is returned in all cases + * @return string|null Identifier or null if the table or column is invalid + */ + public function columnName(string $table, string $column, bool $enforceQualified = false): ?string + { + // ensure we have clean $table and $column values without qualified identifiers + list($table, $column) = $this->splitIdentifier($table, $column); + + // combine the identifiers again + if ($this->database->validateColumn($table, $column) === true) { + return $this->combineIdentifier($table, $column, $enforceQualified !== true); + } + + // the table or column does not exist + return null; + } + + /** + * Abstracted column types to simplify table + * creation for multiple database drivers + * @codeCoverageIgnore + * + * @return array + */ + public function columnTypes(): array + { + return [ + 'id' => '{{ name }} INT(11) UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY', + 'varchar' => '{{ name }} varchar(255) {{ null }} {{ default }} {{ unique }}', + 'text' => '{{ name }} TEXT {{ unique }}', + 'int' => '{{ name }} INT(11) UNSIGNED {{ null }} {{ default }} {{ unique }}', + 'timestamp' => '{{ name }} TIMESTAMP {{ null }} {{ default }} {{ unique }}' + ]; + } + + /** + * Combines an identifier (table and column) + * + * @param $table string + * @param $column string + * @param $values boolean Whether the identifier is going to be used for a VALUES clause; + * only relevant for SQLite + * @return string + */ + public function combineIdentifier(string $table, string $column, bool $values = false): string + { + return $this->quoteIdentifier($table) . '.' . $this->quoteIdentifier($column); + } + + /** + * Creates the CREATE TABLE syntax for a single column + * + * @param string $name Column name + * @param array $column Column definition array; valid keys: + * - `type` (required): Column template to use + * - `null`: Whether the column may be NULL (boolean) + * - `key`: Index this column is part of; special values `'primary'` for PRIMARY KEY and `true` for automatic naming + * - `unique`: Whether the index (or if not set the column itself) has a UNIQUE constraint + * - `default`: Default value of this column + * @return array Array with `query` and `key` strings, a `unique` boolean and a `bindings` array + * @throws \Kirby\Exception\InvalidArgumentException if no column type is given or the column type is not supported. + */ + public function createColumn(string $name, array $column): array + { + // column type + if (isset($column['type']) === false) { + throw new InvalidArgumentException('No column type given for column ' . $name); + } + $template = $this->columnTypes()[$column['type']] ?? null; + if (!$template) { + throw new InvalidArgumentException('Unsupported column type: ' . $column['type']); + } + + // null option + if (A::get($column, 'null') === false) { + $null = 'NOT NULL'; + } else { + $null = 'NULL'; + } + + // indexes/keys + if (isset($column['key']) === true) { + if (is_string($column['key']) === true) { + $column['key'] = strtolower($column['key']); + } elseif ($column['key'] === true) { + $column['key'] = $name . '_index'; + } + } + + // unique + $uniqueKey = false; + $uniqueColumn = null; + if (isset($column['unique']) === true && $column['unique'] === true) { + if (isset($column['key']) === true) { + // this column is part of an index, make that unique + $uniqueKey = true; + } else { + // make the column itself unique + $uniqueColumn = 'UNIQUE'; + } + } + + // default value + $columnDefault = $this->columnDefault($name, $column); + + $query = trim(Str::template($template, [ + 'name' => $this->quoteIdentifier($name), + 'null' => $null, + 'default' => $columnDefault['query'], + 'unique' => $uniqueColumn + ], '')); + + return [ + 'query' => $query, + 'bindings' => $columnDefault['bindings'], + 'key' => $column['key'] ?? null, + 'unique' => $uniqueKey + ]; + } + + /** + * Creates the inner query for the columns in a CREATE TABLE query + * + * @param array $columns Array of column definition arrays, see `Kirby\Database\Sql::createColumn()` + * @return array Array with a `query` string and `bindings`, `keys` and `unique` arrays + */ + public function createTableInner(array $columns): array + { + $query = []; + $bindings = []; + $keys = []; + $unique = []; + + foreach ($columns as $name => $column) { + $sql = $this->createColumn($name, $column); + + // collect query and bindings + $query[] = $sql['query']; + $bindings += $sql['bindings']; + + // make a list of keys per key name + if ($sql['key'] !== null) { + if (isset($keys[$sql['key']]) !== true) { + $keys[$sql['key']] = []; + } + + $keys[$sql['key']][] = $name; + if ($sql['unique'] === true) { + $unique[$sql['key']] = true; + } + } + } + + return [ + 'query' => implode(',' . PHP_EOL, $query), + 'bindings' => $bindings, + 'keys' => $keys, + 'unique' => $unique + ]; + } + + /** + * Creates a CREATE TABLE query + * + * @param string $table Table name + * @param array $columns Array of column definition arrays, see `Kirby\Database\Sql::createColumn()` + * @return array Array with a `query` string and a `bindings` array + */ + public function createTable(string $table, array $columns = []): array + { + $inner = $this->createTableInner($columns); + + // add keys + foreach ($inner['keys'] as $key => $columns) { + // quote each column name and make a list string out of the column names + $columns = implode(', ', array_map(function ($name) { + return $this->quoteIdentifier($name); + }, $columns)); + + if ($key === 'primary') { + $key = 'PRIMARY KEY'; + } else { + $unique = isset($inner['unique'][$key]) === true ? 'UNIQUE ' : ''; + $key = $unique . 'INDEX ' . $this->quoteIdentifier($key); + } + + $inner['query'] .= ',' . PHP_EOL . $key . ' (' . $columns . ')'; + } + + return [ + 'query' => 'CREATE TABLE ' . $this->quoteIdentifier($table) . ' (' . PHP_EOL . $inner['query'] . PHP_EOL . ')', + 'bindings' => $inner['bindings'] + ]; + } + + /** + * Builds a DELETE clause + * + * @param array $params List of parameters for the DELETE clause. See defaults for more info. + * @return array + */ + public function delete(array $params = []): array + { + $defaults = [ + 'table' => '', + 'where' => null, + 'bindings' => [] + ]; + + $options = array_merge($defaults, $params); + $bindings = $options['bindings']; + $query = ['DELETE']; + + // from + $this->extend($query, $bindings, $this->from($options['table'])); + + // where + $this->extend($query, $bindings, $this->where($options['where'])); + + return [ + 'query' => $this->query($query), + 'bindings' => $bindings + ]; + } + + /** + * Creates the sql for dropping a single table + * + * @param string $table + * @return array + */ + public function dropTable(string $table): array + { + return [ + 'query' => 'DROP TABLE ' . $this->tableName($table), + 'bindings' => [] + ]; + } + + /** + * Extends a given query and bindings + * by reference + * + * @param array $query + * @param array $bindings + * @param array $input + * @return void + */ + public function extend(&$query, array &$bindings, $input) + { + if (empty($input['query']) === false) { + $query[] = $input['query']; + $bindings = array_merge($bindings, $input['bindings']); + } + } + + /** + * Creates the from syntax + * + * @param string $table + * @return array + */ + public function from(string $table): array + { + return [ + 'query' => 'FROM ' . $this->tableName($table), + 'bindings' => [] + ]; + } + + /** + * Creates the group by syntax + * + * @param string $group + * @return array + */ + public function group(string $group = null): array + { + if (empty($group) === true) { + return [ + 'query' => null, + 'bindings' => [] + ]; + } + + return [ + 'query' => 'GROUP BY ' . $group, + 'bindings' => [] + ]; + } + + /** + * Creates the having syntax + * + * @param string|null $having + * @return array + */ + public function having(string $having = null): array + { + if (empty($having) === true) { + return [ + 'query' => null, + 'bindings' => [] + ]; + } + + return [ + 'query' => 'HAVING ' . $having, + 'bindings' => [] + ]; + } + + /** + * Creates an insert query + * + * @param array $params + * @return array + */ + public function insert(array $params = []): array + { + $table = $params['table'] ?? null; + $values = $params['values'] ?? null; + $bindings = $params['bindings']; + $query = ['INSERT INTO ' . $this->tableName($table)]; + + // add the values + $this->extend($query, $bindings, $this->values($table, $values, ', ', false)); + + return [ + 'query' => $this->query($query), + 'bindings' => $bindings + ]; + } + + /** + * Creates a join query + * + * @param string $table + * @param string $type + * @param string $on + * @return array + * @throws \Kirby\Exception\InvalidArgumentException if an invalid join type is given + */ + public function join(string $type, string $table, string $on): array + { + $types = [ + 'JOIN', + 'INNER JOIN', + 'OUTER JOIN', + 'LEFT OUTER JOIN', + 'LEFT JOIN', + 'RIGHT OUTER JOIN', + 'RIGHT JOIN', + 'FULL OUTER JOIN', + 'FULL JOIN', + 'NATURAL JOIN', + 'CROSS JOIN', + 'SELF JOIN' + ]; + + $type = strtoupper(trim($type)); + + // validate join type + if (in_array($type, $types) === false) { + throw new InvalidArgumentException('Invalid join type ' . $type); + } + + return [ + 'query' => $type . ' ' . $this->tableName($table) . ' ON ' . $on, + 'bindings' => [], + ]; + } + + /** + * Create the syntax for multiple joins + * + * @param array|null $joins + * @return array + */ + public function joins(array $joins = null): array + { + $query = []; + $bindings = []; + + foreach ((array)$joins as $join) { + $this->extend($query, $bindings, $this->join($join['type'] ?? 'JOIN', $join['table'] ?? null, $join['on'] ?? null)); + } + + return [ + 'query' => implode(' ', array_filter($query)), + 'bindings' => [], + ]; + } + + /** + * Creates a limit and offset query instruction + * + * @param int $offset + * @param int|null $limit + * @return array + */ + public function limit(int $offset = 0, int $limit = null): array + { + // no need to add it to the query + if ($offset === 0 && $limit === null) { + return [ + 'query' => null, + 'bindings' => [] + ]; + } + + $limit = $limit ?? '18446744073709551615'; + + $offsetBinding = $this->bindingName('offset'); + $limitBinding = $this->bindingName('limit'); + + return [ + 'query' => 'LIMIT ' . $offsetBinding . ', ' . $limitBinding, + 'bindings' => [ + $limitBinding => $limit, + $offsetBinding => $offset, + ] + ]; + } + + /** + * Creates the order by syntax + * + * @param string $order + * @return array + */ + public function order(string $order = null): array + { + if (empty($order) === true) { + return [ + 'query' => null, + 'bindings' => [] + ]; + } + + return [ + 'query' => 'ORDER BY ' . $order, + 'bindings' => [] + ]; + } + + /** + * Converts a query array into a final string + * + * @param array $query + * @param string $separator + * @return string + */ + public function query(array $query, string $separator = ' ') + { + return implode($separator, array_filter($query)); + } + + /** + * Quotes an identifier (table *or* column) + * + * @param $identifier string + * @return string + */ + public function quoteIdentifier(string $identifier): string + { + // * is special, don't quote that + if ($identifier === '*') { + return $identifier; + } + + // escape backticks inside the identifier name + $identifier = str_replace('`', '``', $identifier); + + // wrap in backticks + return '`' . $identifier . '`'; + } + + /** + * Builds a select clause + * + * @param array $params List of parameters for the select clause. Check out the defaults for more info. + * @return array An array with the query and the bindings + */ + public function select(array $params = []): array + { + $defaults = [ + 'table' => '', + 'columns' => '*', + 'join' => null, + 'distinct' => false, + 'where' => null, + 'group' => null, + 'having' => null, + 'order' => null, + 'offset' => 0, + 'limit' => null, + 'bindings' => [] + ]; + + $options = array_merge($defaults, $params); + $bindings = $options['bindings']; + $query = ['SELECT']; + + // select distinct values + if ($options['distinct'] === true) { + $query[] = 'DISTINCT'; + } + + // columns + $query[] = $this->selected($options['table'], $options['columns']); + + // from + $this->extend($query, $bindings, $this->from($options['table'])); + + // joins + $this->extend($query, $bindings, $this->joins($options['join'])); + + // where + $this->extend($query, $bindings, $this->where($options['where'])); + + // group + $this->extend($query, $bindings, $this->group($options['group'])); + + // having + $this->extend($query, $bindings, $this->having($options['having'])); + + // order + $this->extend($query, $bindings, $this->order($options['order'])); + + // offset and limit + $this->extend($query, $bindings, $this->limit($options['offset'], $options['limit'])); + + return [ + 'query' => $this->query($query), + 'bindings' => $bindings + ]; + } + + /** + * Creates a columns definition from string or array + * + * @param string $table + * @param array|string|null $columns + * @return string + */ + public function selected($table, $columns = null): string + { + // all columns + if (empty($columns) === true) { + return '*'; + } + + // array of columns + if (is_array($columns) === true) { + + // validate columns + $result = []; + + foreach ($columns as $column) { + list($table, $columnPart) = $this->splitIdentifier($table, $column); + + if ($this->validateColumn($table, $columnPart) === true) { + $result[] = $this->combineIdentifier($table, $columnPart); + } + } + + return implode(', ', $result); + } else { + return $columns; + } + } + + /** + * Splits a (qualified) identifier into table and column + * + * @param $table string Default table if the identifier is not qualified + * @param $identifier string + * @return array + * @throws \Kirby\Exception\InvalidArgumentException if an invalid identifier is given + */ + public function splitIdentifier($table, $identifier): array + { + // split by dot, but only outside of quotes + $parts = preg_split('/(?:`[^`]*`|"[^"]*")(*SKIP)(*F)|\./', $identifier); + + switch (count($parts)) { + // non-qualified identifier + case 1: + return [$table, $this->unquoteIdentifier($parts[0])]; + + // qualified identifier + case 2: + return [$this->unquoteIdentifier($parts[0]), $this->unquoteIdentifier($parts[1])]; + + // every other number is an error + default: + throw new InvalidArgumentException('Invalid identifier ' . $identifier); + } + } + + /** + * Returns a query to list the tables of the current database; + * the query needs to return rows with a column `name` + * + * @return array + */ + abstract public function tables(): array; + + /** + * Validates and quotes a table name + * + * @param string $table + * @return string + * @throws \Kirby\Exception\InvalidArgumentException if an invalid table name is given + */ + public function tableName(string $table): string + { + // validate table + if ($this->database->validateTable($table) === false) { + throw new InvalidArgumentException('Invalid table ' . $table); + } + + return $this->quoteIdentifier($table); + } + + /** + * Unquotes an identifier (table *or* column) + * + * @param $identifier string + * @return string + */ + public function unquoteIdentifier(string $identifier): string + { + // remove quotes around the identifier + if (in_array(Str::substr($identifier, 0, 1), ['"', '`']) === true) { + $identifier = Str::substr($identifier, 1); + } + + if (in_array(Str::substr($identifier, -1), ['"', '`']) === true) { + $identifier = Str::substr($identifier, 0, -1); + } + + // unescape duplicated quotes + return str_replace(['""', '``'], ['"', '`'], $identifier); + } + + /** + * Builds an update clause + * + * @param array $params List of parameters for the update clause. See defaults for more info. + * @return array + */ + public function update(array $params = []): array + { + $defaults = [ + 'table' => null, + 'values' => null, + 'where' => null, + 'bindings' => [] + ]; + + $options = array_merge($defaults, $params); + $bindings = $options['bindings']; + + // start the query + $query = ['UPDATE ' . $this->tableName($options['table']) . ' SET']; + + // add the values + $this->extend($query, $bindings, $this->values($options['table'], $options['values'])); + + // add the where clause + $this->extend($query, $bindings, $this->where($options['where'])); + + return [ + 'query' => $this->query($query), + 'bindings' => $bindings + ]; + } + + /** + * Validates a given column name in a table + * + * @param string $table + * @param string $column + * @return bool + * @throws \Kirby\Exception\InvalidArgumentException If the column is invalid + */ + public function validateColumn(string $table, string $column): bool + { + if ($this->database->validateColumn($table, $column) !== true) { + throw new InvalidArgumentException('Invalid column ' . $column); + } + + return true; + } + + /** + * Builds a safe list of values for insert, select or update queries + * + * @param string $table Table name + * @param mixed $values A value string or array of values + * @param string $separator A separator which should be used to join values + * @param bool $set If true builds a set list of values for update clauses + * @param bool $enforceQualified Always use fully qualified column names + */ + public function values(string $table, $values, string $separator = ', ', bool $set = true, bool $enforceQualified = false): array + { + if (is_array($values) === false) { + return [ + 'query' => $values, + 'bindings' => [] + ]; + } + + if ($set === true) { + return $this->valueSet($table, $values, $separator, $enforceQualified); + } else { + return $this->valueList($table, $values, $separator, $enforceQualified); + } + } + + /** + * Creates a list of fields and values + * + * @param string $table + * @param string|array $values + * @param string $separator + * @param bool $enforceQualified + * @param array + */ + public function valueList(string $table, $values, string $separator = ',', bool $enforceQualified = false): array + { + $fields = []; + $query = []; + $bindings = []; + + foreach ($values as $key => $value) { + $fields[] = $this->columnName($table, $key, $enforceQualified); + + if (in_array($value, static::$literals, true) === true) { + $query[] = $value ?: 'null'; + continue; + } + + if (is_array($value) === true) { + $value = json_encode($value); + } + + // add the binding + $bindings[$bindingName = $this->bindingName('value')] = $value; + + // create the query + $query[] = $bindingName; + } + + return [ + 'query' => '(' . implode($separator, $fields) . ') VALUES (' . implode($separator, $query) . ')', + 'bindings' => $bindings + ]; + } + + /** + * Creates a set of values + * + * @param string $table + * @param string|array $values + * @param string $separator + * @param bool $enforceQualified + * @param array + * @return array + */ + public function valueSet(string $table, $values, string $separator = ',', bool $enforceQualified = false): array + { + $query = []; + $bindings = []; + + foreach ($values as $column => $value) { + $key = $this->columnName($table, $column, $enforceQualified); + + if (in_array($value, static::$literals, true) === true) { + $query[] = $key . ' = ' . ($value ?: 'null'); + continue; + } + + if (is_array($value) === true) { + $value = json_encode($value); + } + + // add the binding + $bindings[$bindingName = $this->bindingName('value')] = $value; + + // create the query + $query[] = $key . ' = ' . $bindingName; + } + + return [ + 'query' => implode($separator, $query), + 'bindings' => $bindings + ]; + } + + /** + * @param string|array|null $where + * @param array $bindings + * @return array + */ + public function where($where, array $bindings = []): array + { + if (empty($where) === true) { + return [ + 'query' => null, + 'bindings' => [], + ]; + } + + if (is_string($where) === true) { + return [ + 'query' => 'WHERE ' . $where, + 'bindings' => $bindings + ]; + } + + $query = []; + + foreach ($where as $key => $value) { + $binding = $this->bindingName('where_' . $key); + $bindings[$binding] = $value; + + $query[] = $key . ' = ' . $binding; + } + + return [ + 'query' => 'WHERE ' . implode(' AND ', $query), + 'bindings' => $bindings + ]; + } +} diff --git a/kirby/src/Database/Sql/Mysql.php b/kirby/src/Database/Sql/Mysql.php new file mode 100644 index 0000000..853a39b --- /dev/null +++ b/kirby/src/Database/Sql/Mysql.php @@ -0,0 +1,59 @@ + + * @link https://getkirby.com + * @copyright Bastian Allgeier GmbH + * @license https://opensource.org/licenses/MIT + */ +class Mysql extends Sql +{ + /** + * Returns a query to list the columns of a specified table; + * the query needs to return rows with a column `name` + * + * @param string $table Table name + * @return array + */ + public function columns(string $table): array + { + $databaseBinding = $this->bindingName('database'); + $tableBinding = $this->bindingName('table'); + + $query = 'SELECT COLUMN_NAME AS name FROM INFORMATION_SCHEMA.COLUMNS '; + $query .= 'WHERE TABLE_SCHEMA = ' . $databaseBinding . ' AND TABLE_NAME = ' . $tableBinding; + + return [ + 'query' => $query, + 'bindings' => [ + $databaseBinding => $this->database->name(), + $tableBinding => $table, + ] + ]; + } + + /** + * Returns a query to list the tables of the current database; + * the query needs to return rows with a column `name` + * + * @return array + */ + public function tables(): array + { + $binding = $this->bindingName('database'); + + return [ + 'query' => 'SELECT TABLE_NAME AS name FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = ' . $binding, + 'bindings' => [ + $binding => $this->database->name() + ] + ]; + } +} diff --git a/kirby/src/Database/Sql/Sqlite.php b/kirby/src/Database/Sql/Sqlite.php new file mode 100644 index 0000000..1c691ed --- /dev/null +++ b/kirby/src/Database/Sql/Sqlite.php @@ -0,0 +1,143 @@ + + * @link https://getkirby.com + * @copyright Bastian Allgeier GmbH + * @license https://opensource.org/licenses/MIT + */ +class Sqlite extends Sql +{ + /** + * Returns a query to list the columns of a specified table; + * the query needs to return rows with a column `name` + * + * @param string $table Table name + * @return array + */ + public function columns(string $table): array + { + return [ + 'query' => 'PRAGMA table_info(' . $this->tableName($table) . ')', + 'bindings' => [], + ]; + } + + /** + * Abstracted column types to simplify table + * creation for multiple database drivers + * @codeCoverageIgnore + * + * @return array + */ + public function columnTypes(): array + { + return [ + 'id' => '{{ name }} INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL UNIQUE', + 'varchar' => '{{ name }} TEXT {{ null }} {{ default }} {{ unique }}', + 'text' => '{{ name }} TEXT {{ null }} {{ default }} {{ unique }}', + 'int' => '{{ name }} INTEGER {{ null }} {{ default }} {{ unique }}', + 'timestamp' => '{{ name }} INTEGER {{ null }} {{ default }} {{ unique }}' + ]; + } + + /** + * Combines an identifier (table and column) + * + * @param $table string + * @param $column string + * @param $values boolean Whether the identifier is going to be used for a VALUES clause; + * only relevant for SQLite + * @return string + */ + public function combineIdentifier(string $table, string $column, bool $values = false): string + { + // SQLite doesn't support qualified column names for VALUES clauses + if ($values === true) { + return $this->quoteIdentifier($column); + } + + return $this->quoteIdentifier($table) . '.' . $this->quoteIdentifier($column); + } + + /** + * Creates a CREATE TABLE query + * + * @param string $table Table name + * @param array $columns Array of column definition arrays, see `Kirby\Database\Sql::createColumn()` + * @return array Array with a `query` string and a `bindings` array + */ + public function createTable(string $table, array $columns = []): array + { + $inner = $this->createTableInner($columns); + + // add keys + $keys = []; + foreach ($inner['keys'] as $key => $columns) { + // quote each column name and make a list string out of the column names + $columns = implode(', ', array_map(function ($name) { + return $this->quoteIdentifier($name); + }, $columns)); + + if ($key === 'primary') { + $inner['query'] .= ',' . PHP_EOL . 'PRIMARY KEY (' . $columns . ')'; + } else { + // SQLite only supports index creation using a separate CREATE INDEX query + $unique = isset($inner['unique'][$key]) === true ? 'UNIQUE ' : ''; + $keys[] = 'CREATE ' . $unique . 'INDEX ' . $this->quoteIdentifier($table . '_index_' . $key) . + ' ON ' . $this->quoteIdentifier($table) . ' (' . $columns . ')'; + } + } + + $query = 'CREATE TABLE ' . $this->quoteIdentifier($table) . ' (' . PHP_EOL . $inner['query'] . PHP_EOL . ')'; + if (empty($keys) === false) { + $query .= ';' . PHP_EOL . implode(';' . PHP_EOL, $keys); + } + + return [ + 'query' => $query, + 'bindings' => $inner['bindings'] + ]; + } + + /** + * Quotes an identifier (table *or* column) + * + * @param $identifier string + * @return string + */ + public function quoteIdentifier(string $identifier): string + { + // * is special + if ($identifier === '*') { + return $identifier; + } + + // escape quotes inside the identifier name + $identifier = str_replace('"', '""', $identifier); + + // wrap in quotes + return '"' . $identifier . '"'; + } + + /** + * Returns a query to list the tables of the current database; + * the query needs to return rows with a column `name` + * + * @return string + */ + public function tables(): array + { + return [ + 'query' => 'SELECT name FROM sqlite_master WHERE type = "table"', + 'bindings' => [] + ]; + } +} diff --git a/kirby/src/Email/Body.php b/kirby/src/Email/Body.php new file mode 100644 index 0000000..f047176 --- /dev/null +++ b/kirby/src/Email/Body.php @@ -0,0 +1,85 @@ +, + * Nico Hoffmann + * @link https://getkirby.com + * @copyright Bastian Allgeier GmbH + * @license https://opensource.org/licenses/MIT + */ +class Body +{ + use Properties; + + /** + * @var string|null + */ + protected $html; + + /** + * @var string|null + */ + protected $text; + + /** + * Email body constructor + * + * @param array $props + */ + public function __construct(array $props = []) + { + $this->setProperties($props); + } + + /** + * Returns the HTML content of the email body + * + * @return string|null + */ + public function html() + { + return $this->html; + } + + /** + * Returns the plain text content of the email body + * + * @return string|null + */ + public function text() + { + return $this->text; + } + + /** + * Sets the HTML content for the email body + * + * @param string|null $html + * @return $this + */ + protected function setHtml(string $html = null) + { + $this->html = $html; + return $this; + } + + /** + * Sets the plain text content for the email body + * + * @param string|null $text + * @return $this + */ + protected function setText(string $text = null) + { + $this->text = $text; + return $this; + } +} diff --git a/kirby/src/Email/Email.php b/kirby/src/Email/Email.php new file mode 100644 index 0000000..fd20c9b --- /dev/null +++ b/kirby/src/Email/Email.php @@ -0,0 +1,472 @@ +, + * Nico Hoffmann + * @link https://getkirby.com + * @copyright Bastian Allgeier GmbH + * @license https://opensource.org/licenses/MIT + */ +class Email +{ + use Properties; + + /** + * If set to `true`, the debug mode is enabled + * for all emails + * + * @var bool + */ + public static $debug = false; + + /** + * Store for sent emails when `Email::$debug` + * is set to `true` + * + * @var array + */ + public static $emails = []; + + /** + * @var array|null + */ + protected $attachments; + + /** + * @var \Kirby\Email\Body|null + */ + protected $body; + + /** + * @var array|null + */ + protected $bcc; + + /** + * @var \Closure|null + */ + protected $beforeSend; + + /** + * @var array|null + */ + protected $cc; + + /** + * @var string|null + */ + protected $from; + + /** + * @var string|null + */ + protected $fromName; + + /** + * @var string|null + */ + protected $replyTo; + + /** + * @var string|null + */ + protected $replyToName; + + /** + * @var bool + */ + protected $isSent = false; + + /** + * @var string|null + */ + protected $subject; + + /** + * @var array|null + */ + protected $to; + + /** + * @var array|null + */ + protected $transport; + + /** + * Email constructor + * + * @param array $props + * @param bool $debug + */ + public function __construct(array $props = [], bool $debug = false) + { + $this->setProperties($props); + + // @codeCoverageIgnoreStart + if (static::$debug === false && $debug === false) { + $this->send(); + } elseif (static::$debug === true) { + static::$emails[] = $this; + } + // @codeCoverageIgnoreEnd + } + + /** + * Returns the email attachments + * + * @return array + */ + public function attachments(): array + { + return $this->attachments; + } + + /** + * Returns the email body + * + * @return \Kirby\Email\Body|null + */ + public function body() + { + return $this->body; + } + + /** + * Returns "bcc" recipients + * + * @return array + */ + public function bcc(): array + { + return $this->bcc; + } + + /** + * Returns the beforeSend callback closure, + * which has access to the PHPMailer instance + * + * @return \Closure|null + */ + public function beforeSend(): ?Closure + { + return $this->beforeSend; + } + + /** + * Returns "cc" recipients + * + * @return array + */ + public function cc(): array + { + return $this->cc; + } + + /** + * Returns default transport settings + * + * @return array + */ + protected function defaultTransport(): array + { + return [ + 'type' => 'mail' + ]; + } + + /** + * Returns the "from" email address + * + * @return string + */ + public function from(): string + { + return $this->from; + } + + /** + * Returns the "from" name + * + * @return string|null + */ + public function fromName(): ?string + { + return $this->fromName; + } + + /** + * Checks if the email has an HTML body + * + * @return bool + */ + public function isHtml() + { + return $this->body()->html() !== null; + } + + /** + * Checks if the email has been sent successfully + * + * @return bool + */ + public function isSent(): bool + { + return $this->isSent; + } + + /** + * Returns the "reply to" email address + * + * @return string + */ + public function replyTo(): string + { + return $this->replyTo; + } + + /** + * Returns the "reply to" name + * + * @return string|null + */ + public function replyToName(): ?string + { + return $this->replyToName; + } + + /** + * Converts single or multiple email addresses to a sanitized format + * + * @param string|array|null $email + * @param bool $multiple + * @return array|mixed|string + * @throws \Exception + */ + protected function resolveEmail($email = null, bool $multiple = true) + { + if ($email === null) { + return $multiple === true ? [] : ''; + } + + if (is_array($email) === false) { + $email = [$email => null]; + } + + $result = []; + foreach ($email as $address => $name) { + // convert simple email arrays to associative arrays + if (is_int($address) === true) { + // the value is the address, there is no name + $address = $name; + $result[$address] = null; + } else { + $result[$address] = $name; + } + + // ensure that the address is valid + if (V::email($address) === false) { + throw new Exception(sprintf('"%s" is not a valid email address', $address)); + } + } + + return $multiple === true ? $result : array_keys($result)[0]; + } + + /** + * Sends the email + * + * @return bool + */ + public function send(): bool + { + return $this->isSent = true; + } + + /** + * Sets the email attachments + * + * @param array|null $attachments + * @return $this + */ + protected function setAttachments($attachments = null) + { + $this->attachments = $attachments ?? []; + return $this; + } + + /** + * Sets the email body + * + * @param string|array $body + * @return $this + */ + protected function setBody($body) + { + if (is_string($body) === true) { + $body = ['text' => $body]; + } + + $this->body = new Body($body); + return $this; + } + + /** + * Sets "bcc" recipients + * + * @param string|array|null $bcc + * @return $this + */ + protected function setBcc($bcc = null) + { + $this->bcc = $this->resolveEmail($bcc); + return $this; + } + + /** + * Sets the "beforeSend" callback + * + * @param \Closure|null $beforeSend + * @return $this + */ + protected function setBeforeSend(?Closure $beforeSend = null) + { + $this->beforeSend = $beforeSend; + return $this; + } + + /** + * Sets "cc" recipients + * + * @param string|array|null $cc + * @return $this + */ + protected function setCc($cc = null) + { + $this->cc = $this->resolveEmail($cc); + return $this; + } + + /** + * Sets the "from" email address + * + * @param string $from + * @return $this + */ + protected function setFrom(string $from) + { + $this->from = $this->resolveEmail($from, false); + return $this; + } + + /** + * Sets the "from" name + * + * @param string|null $fromName + * @return $this + */ + protected function setFromName(string $fromName = null) + { + $this->fromName = $fromName; + return $this; + } + + /** + * Sets the "reply to" email address + * + * @param string|null $replyTo + * @return $this + */ + protected function setReplyTo(string $replyTo = null) + { + $this->replyTo = $this->resolveEmail($replyTo, false); + return $this; + } + + /** + * Sets the "reply to" name + * + * @param string|null $replyToName + * @return $this + */ + protected function setReplyToName(string $replyToName = null) + { + $this->replyToName = $replyToName; + return $this; + } + + /** + * Sets the email subject + * + * @param string $subject + * @return $this + */ + protected function setSubject(string $subject) + { + $this->subject = $subject; + return $this; + } + + /** + * Sets the recipients of the email + * + * @param string|array $to + * @return $this + */ + protected function setTo($to) + { + $this->to = $this->resolveEmail($to); + return $this; + } + + /** + * Sets the email transport settings + * + * @param array|null $transport + * @return $this + */ + protected function setTransport($transport = null) + { + $this->transport = $transport; + return $this; + } + + /** + * Returns the email subject + * + * @return string + */ + public function subject(): string + { + return $this->subject; + } + + /** + * Returns the email recipients + * + * @return array + */ + public function to(): array + { + return $this->to; + } + + /** + * Returns the email transports settings + * + * @return array + */ + public function transport(): array + { + return $this->transport ?? $this->defaultTransport(); + } +} diff --git a/kirby/src/Email/PHPMailer.php b/kirby/src/Email/PHPMailer.php new file mode 100644 index 0000000..93ddb45 --- /dev/null +++ b/kirby/src/Email/PHPMailer.php @@ -0,0 +1,113 @@ +, + * Nico Hoffmann + * @link https://getkirby.com + * @copyright Bastian Allgeier GmbH + * @license https://opensource.org/licenses/MIT + */ +class PHPMailer extends Email +{ + /** + * Sends email via PHPMailer library + * + * @param bool $debug + * @return bool + * @throws \Kirby\Exception\InvalidArgumentException + */ + public function send(bool $debug = false): bool + { + $mailer = new Mailer(true); + + // set sender's address + $mailer->setFrom($this->from(), $this->fromName() ?? ''); + + // optional reply-to address + if ($replyTo = $this->replyTo()) { + $mailer->addReplyTo($replyTo, $this->replyToName() ?? ''); + } + + // add (multiple) recepient, CC & BCC addresses + foreach ($this->to() as $email => $name) { + $mailer->addAddress($email, $name ?? ''); + } + foreach ($this->cc() as $email => $name) { + $mailer->addCC($email, $name ?? ''); + } + foreach ($this->bcc() as $email => $name) { + $mailer->addBCC($email, $name ?? ''); + } + + $mailer->Subject = $this->subject(); + $mailer->CharSet = 'UTF-8'; + + // set body according to html/text + if ($this->isHtml()) { + $mailer->isHTML(true); + $mailer->Body = $this->body()->html(); + $mailer->AltBody = $this->body()->text(); + } else { + $mailer->Body = $this->body()->text(); + } + + // add attachments + foreach ($this->attachments() as $attachment) { + $mailer->addAttachment($attachment); + } + + // smtp transport settings + if (($this->transport()['type'] ?? 'mail') === 'smtp') { + $mailer->isSMTP(); + $mailer->Host = $this->transport()['host'] ?? null; + $mailer->SMTPAuth = $this->transport()['auth'] ?? false; + $mailer->Username = $this->transport()['username'] ?? null; + $mailer->Password = $this->transport()['password'] ?? null; + $mailer->SMTPSecure = $this->transport()['security'] ?? 'ssl'; + $mailer->Port = $this->transport()['port'] ?? null; + + if ($mailer->SMTPSecure === true) { + switch ($mailer->Port) { + case null: + case 587: + $mailer->SMTPSecure = 'tls'; + $mailer->Port = 587; + break; + case 465: + $mailer->SMTPSecure = 'ssl'; + break; + default: + throw new InvalidArgumentException( + 'Could not automatically detect the "security" protocol from the ' . + '"port" option, please set it explicitly to "tls" or "ssl".' + ); + } + } + } + + // accessible phpMailer instance + $beforeSend = $this->beforeSend(); + + if (empty($beforeSend) === false && is_a($beforeSend, 'Closure') === true) { + $mailer = $beforeSend->call($this, $mailer) ?? $mailer; + + if (is_a($mailer, 'PHPMailer\PHPMailer\PHPMailer') === false) { + throw new InvalidArgumentException('"beforeSend" option return should be instance of PHPMailer\PHPMailer\PHPMailer class'); + } + } + + if ($debug === true) { + return $this->isSent = true; + } + + return $this->isSent = $mailer->send(); // @codeCoverageIgnore + } +} diff --git a/kirby/src/Exception/BadMethodCallException.php b/kirby/src/Exception/BadMethodCallException.php new file mode 100644 index 0000000..1be6fb7 --- /dev/null +++ b/kirby/src/Exception/BadMethodCallException.php @@ -0,0 +1,21 @@ + + * @link https://getkirby.com + * @copyright Bastian Allgeier GmbH + * @license https://opensource.org/licenses/MIT + */ +class BadMethodCallException extends Exception +{ + protected static $defaultKey = 'invalidMethod'; + protected static $defaultFallback = 'The method "{ method }" does not exist'; + protected static $defaultHttpCode = 400; + protected static $defaultData = ['method' => null]; +} diff --git a/kirby/src/Exception/DuplicateException.php b/kirby/src/Exception/DuplicateException.php new file mode 100644 index 0000000..7684c4c --- /dev/null +++ b/kirby/src/Exception/DuplicateException.php @@ -0,0 +1,21 @@ + + * @link https://getkirby.com + * @copyright Bastian Allgeier GmbH + * @license https://opensource.org/licenses/MIT + */ +class DuplicateException extends Exception +{ + protected static $defaultKey = 'duplicate'; + protected static $defaultFallback = 'The entry exists'; + protected static $defaultHttpCode = 400; +} diff --git a/kirby/src/Exception/ErrorPageException.php b/kirby/src/Exception/ErrorPageException.php new file mode 100644 index 0000000..1c1532d --- /dev/null +++ b/kirby/src/Exception/ErrorPageException.php @@ -0,0 +1,21 @@ + + * @link https://getkirby.com + * @copyright Bastian Allgeier GmbH + * @license https://opensource.org/licenses/MIT + */ +class ErrorPageException extends Exception +{ + protected static $defaultKey = 'errorPage'; + protected static $defaultFallback = 'Triggered error page'; + protected static $defaultHttpCode = 404; +} diff --git a/kirby/src/Exception/Exception.php b/kirby/src/Exception/Exception.php new file mode 100644 index 0000000..f6bc588 --- /dev/null +++ b/kirby/src/Exception/Exception.php @@ -0,0 +1,221 @@ + + * @link https://getkirby.com + * @copyright Bastian Allgeier GmbH + * @license https://opensource.org/licenses/MIT + */ +class Exception extends \Exception +{ + /** + * Data variables that can be used inside the exception message + * + * @var array + */ + protected $data; + + /** + * HTTP code that corresponds with the exception + * + * @var int + */ + protected $httpCode; + + /** + * Additional details that are not included in the exception message + * + * @var array + */ + protected $details; + + /** + * Whether the exception message could be translated into the user's language + * + * @var bool + */ + protected $isTranslated = true; + + /** + * Defaults that can be overridden by specific + * exception classes + */ + protected static $defaultKey = 'general'; + protected static $defaultFallback = 'An error occurred'; + protected static $defaultData = []; + protected static $defaultHttpCode = 500; + protected static $defaultDetails = []; + + /** + * Prefix for the exception key (e.g. 'error.general') + * + * @var string + */ + private static $prefix = 'error'; + + /** + * Class constructor + * + * @param array|string $args Full option array ('key', 'translate', 'fallback', + * 'data', 'httpCode', 'details' and 'previous') or + * just the message string + */ + public function __construct($args = []) + { + // set data and httpCode from provided arguments or defaults + $this->data = $args['data'] ?? static::$defaultData; + $this->httpCode = $args['httpCode'] ?? static::$defaultHttpCode; + $this->details = $args['details'] ?? static::$defaultDetails; + + // define the Exception key + $key = self::$prefix . '.' . ($args['key'] ?? static::$defaultKey); + + if (is_string($args) === true) { + $this->isTranslated = false; + parent::__construct($args); + } else { + // define whether message can/should be translated + $translate = ($args['translate'] ?? true) === true && class_exists('Kirby\Cms\App') === true; + + // fallback waterfall for message string + $message = null; + + if ($translate) { + // 1. translation for provided key in current language + // 2. translation for provided key in default language + if (isset($args['key']) === true) { + $message = I18n::translate(self::$prefix . '.' . $args['key']); + $this->isTranslated = true; + } + } + + // 3. provided fallback message + if ($message === null) { + $message = $args['fallback'] ?? null; + $this->isTranslated = false; + } + + if ($translate) { + // 4. translation for default key in current language + // 5. translation for default key in default language + if ($message === null) { + $message = I18n::translate(self::$prefix . '.' . static::$defaultKey); + $this->isTranslated = true; + } + } + + // 6. default fallback message + if ($message === null) { + $message = static::$defaultFallback; + $this->isTranslated = false; + } + + // format message with passed data + $message = Str::template($message, $this->data, '-', '{', '}'); + + // handover to Exception parent class constructor + parent::__construct($message, null, $args['previous'] ?? null); + } + + // set the Exception code to the key + $this->code = $key; + } + + /** + * Returns the file in which the Exception was created + * relative to the document root + * + * @return string + */ + final public function getFileRelative(): string + { + $file = $this->getFile(); + + if (empty($_SERVER['DOCUMENT_ROOT']) === false) { + $file = ltrim(Str::after($file, $_SERVER['DOCUMENT_ROOT']), '/'); + } + + return $file; + } + + /** + * Returns the data variables from the message + * + * @return array + */ + final public function getData(): array + { + return $this->data; + } + + /** + * Returns the additional details that are + * not included in the message + * + * @return array + */ + final public function getDetails(): array + { + return $this->details; + } + + /** + * Returns the exception key (error type) + * + * @return string + */ + final public function getKey(): string + { + return $this->getCode(); + } + + /** + * Returns the HTTP code that corresponds + * with the exception + * + * @return array + */ + final public function getHttpCode(): int + { + return $this->httpCode; + } + + /** + * Returns whether the exception message could + * be translated into the user's language + * + * @return bool + */ + final public function isTranslated(): bool + { + return $this->isTranslated; + } + + /** + * Converts the object to an array + * + * @return array + */ + public function toArray(): array + { + return [ + 'exception' => static::class, + 'message' => $this->getMessage(), + 'key' => $this->getKey(), + 'file' => $this->getFileRelative(), + 'line' => $this->getLine(), + 'details' => $this->getDetails(), + 'code' => $this->getHttpCode() + ]; + } +} diff --git a/kirby/src/Exception/InvalidArgumentException.php b/kirby/src/Exception/InvalidArgumentException.php new file mode 100644 index 0000000..e536f4f --- /dev/null +++ b/kirby/src/Exception/InvalidArgumentException.php @@ -0,0 +1,21 @@ + + * @link https://getkirby.com + * @copyright Bastian Allgeier GmbH + * @license https://opensource.org/licenses/MIT + */ +class InvalidArgumentException extends Exception +{ + protected static $defaultKey = 'invalidArgument'; + protected static $defaultFallback = 'Invalid argument "{ argument }" in method "{ method }"'; + protected static $defaultHttpCode = 400; + protected static $defaultData = ['argument' => null, 'method' => null]; +} diff --git a/kirby/src/Exception/LogicException.php b/kirby/src/Exception/LogicException.php new file mode 100644 index 0000000..7d9ac20 --- /dev/null +++ b/kirby/src/Exception/LogicException.php @@ -0,0 +1,20 @@ + + * @link https://getkirby.com + * @copyright Bastian Allgeier GmbH + * @license https://opensource.org/licenses/MIT + */ +class LogicException extends Exception +{ + protected static $defaultKey = 'logic'; + protected static $defaultFallback = 'This task cannot be finished'; + protected static $defaultHttpCode = 400; +} diff --git a/kirby/src/Exception/NotFoundException.php b/kirby/src/Exception/NotFoundException.php new file mode 100644 index 0000000..af08c04 --- /dev/null +++ b/kirby/src/Exception/NotFoundException.php @@ -0,0 +1,20 @@ + + * @link https://getkirby.com + * @copyright Bastian Allgeier GmbH + * @license https://opensource.org/licenses/MIT + */ +class NotFoundException extends Exception +{ + protected static $defaultKey = 'notFound'; + protected static $defaultFallback = 'Not found'; + protected static $defaultHttpCode = 404; +} diff --git a/kirby/src/Exception/PermissionException.php b/kirby/src/Exception/PermissionException.php new file mode 100644 index 0000000..b53a053 --- /dev/null +++ b/kirby/src/Exception/PermissionException.php @@ -0,0 +1,21 @@ + + * @link https://getkirby.com + * @copyright Bastian Allgeier GmbH + * @license https://opensource.org/licenses/MIT + */ +class PermissionException extends Exception +{ + protected static $defaultKey = 'permission'; + protected static $defaultFallback = 'You are not allowed to do this'; + protected static $defaultHttpCode = 403; +} diff --git a/kirby/src/Form/Field.php b/kirby/src/Form/Field.php new file mode 100644 index 0000000..cc5c843 --- /dev/null +++ b/kirby/src/Form/Field.php @@ -0,0 +1,506 @@ + + * @link https://getkirby.com + * @copyright Bastian Allgeier GmbH + * @license https://opensource.org/licenses/MIT + */ +class Field extends Component +{ + /** + * An array of all found errors + * + * @var array|null + */ + protected $errors; + + /** + * Parent collection with all fields of the current form + * + * @var \Kirby\Form\Fields|null + */ + protected $formFields; + + /** + * Registry for all component mixins + * + * @var array + */ + public static $mixins = []; + + /** + * Registry for all component types + * + * @var array + */ + public static $types = []; + + /** + * Field constructor + * + * @param string $type + * @param array $attrs + * @param \Kirby\Form\Fields|null $formFields + * @throws \Kirby\Exception\InvalidArgumentException + */ + public function __construct(string $type, array $attrs = [], ?Fields $formFields = null) + { + if (isset(static::$types[$type]) === false) { + throw new InvalidArgumentException('The field type "' . $type . '" does not exist'); + } + + if (isset($attrs['model']) === false) { + throw new InvalidArgumentException('Field requires a model'); + } + + $this->formFields = $formFields; + + // use the type as fallback for the name + $attrs['name'] = $attrs['name'] ?? $type; + $attrs['type'] = $type; + + parent::__construct($type, $attrs); + } + + /** + * Returns field api call + * + * @return mixed + */ + public function api() + { + if ( + isset($this->options['api']) === true && + is_a($this->options['api'], 'Closure') === true + ) { + return $this->options['api']->call($this); + } + } + + /** + * Returns field data + * + * @param bool $default + * @return mixed + */ + public function data(bool $default = false) + { + $save = $this->options['save'] ?? true; + + if ($default === true && $this->isEmpty($this->value)) { + $value = $this->default(); + } else { + $value = $this->value; + } + + if ($save === false) { + return null; + } + + if (is_a($save, 'Closure') === true) { + return $save->call($this, $value); + } + + return $value; + } + + /** + * Default props and computed of the field + * + * @return array + */ + public static function defaults(): array + { + return [ + 'props' => [ + /** + * Optional text that will be shown after the input + */ + 'after' => function ($after = null) { + return I18n::translate($after, $after); + }, + /** + * Sets the focus on this field when the form loads. Only the first field with this label gets + */ + 'autofocus' => function (bool $autofocus = null): bool { + return $autofocus ?? false; + }, + /** + * Optional text that will be shown before the input + */ + 'before' => function ($before = null) { + return I18n::translate($before, $before); + }, + /** + * Default value for the field, which will be used when a page/file/user is created + */ + 'default' => function ($default = null) { + return $default; + }, + /** + * If `true`, the field is no longer editable and will not be saved + */ + 'disabled' => function (bool $disabled = null): bool { + return $disabled ?? false; + }, + /** + * Optional help text below the field + */ + 'help' => function ($help = null) { + return I18n::translate($help, $help); + }, + /** + * Optional icon that will be shown at the end of the field + */ + 'icon' => function (string $icon = null) { + return $icon; + }, + /** + * The field label can be set as string or associative array with translations + */ + 'label' => function ($label = null) { + return I18n::translate($label, $label); + }, + /** + * Optional placeholder value that will be shown when the field is empty + */ + 'placeholder' => function ($placeholder = null) { + return I18n::translate($placeholder, $placeholder); + }, + /** + * If `true`, the field has to be filled in correctly to be saved. + */ + 'required' => function (bool $required = null): bool { + return $required ?? false; + }, + /** + * If `false`, the field will be disabled in non-default languages and cannot be translated. This is only relevant in multi-language setups. + */ + 'translate' => function (bool $translate = true): bool { + return $translate; + }, + /** + * Conditions when the field will be shown (since 3.1.0) + */ + 'when' => function ($when = null) { + return $when; + }, + /** + * The width of the field in the field grid. Available widths: `1/1`, `1/2`, `1/3`, `1/4`, `2/3`, `3/4` + */ + 'width' => function (string $width = '1/1') { + return $width; + }, + 'value' => function ($value = null) { + return $value; + } + ], + 'computed' => [ + 'after' => function () { + /** @var \Kirby\Form\Field $this */ + if ($this->after !== null) { + return $this->model()->toString($this->after); + } + }, + 'before' => function () { + /** @var \Kirby\Form\Field $this */ + if ($this->before !== null) { + return $this->model()->toString($this->before); + } + }, + 'default' => function () { + /** @var \Kirby\Form\Field $this */ + if ($this->default === null) { + return; + } + + if (is_string($this->default) === false) { + return $this->default; + } + + return $this->model()->toString($this->default); + }, + 'help' => function () { + /** @var \Kirby\Form\Field $this */ + if ($this->help) { + $help = $this->model()->toString($this->help); + $help = $this->kirby()->kirbytext($help); + return $help; + } + }, + 'label' => function () { + /** @var \Kirby\Form\Field $this */ + if ($this->label !== null) { + return $this->model()->toString($this->label); + } + }, + 'placeholder' => function () { + /** @var \Kirby\Form\Field $this */ + if ($this->placeholder !== null) { + return $this->model()->toString($this->placeholder); + } + } + ] + ]; + } + + /** + * Creates a new field instance + * + * @param string $type + * @param array $attrs + * @param Fields|null $formFields + * @return static + */ + public static function factory(string $type, array $attrs = [], ?Fields $formFields = null) + { + $field = static::$types[$type] ?? null; + + if (is_string($field) && class_exists($field) === true) { + $attrs['siblings'] = $formFields; + return new $field($attrs); + } + + return new static($type, $attrs, $formFields); + } + + /** + * Parent collection with all fields of the current form + * + * @return \Kirby\Form\Fields|null + */ + public function formFields(): ?Fields + { + return $this->formFields; + } + + /** + * Validates when run for the first time and returns any errors + * + * @return array + */ + public function errors(): array + { + if ($this->errors === null) { + $this->validate(); + } + + return $this->errors; + } + + /** + * Checks if the field is empty + * + * @param mixed ...$args + * @return bool + */ + public function isEmpty(...$args): bool + { + if (count($args) === 0) { + $value = $this->value(); + } else { + $value = $args[0]; + } + + if (isset($this->options['isEmpty']) === true) { + return $this->options['isEmpty']->call($this, $value); + } + + return in_array($value, [null, '', []], true); + } + + /** + * Checks if the field is invalid + * + * @return bool + */ + public function isInvalid(): bool + { + return empty($this->errors()) === false; + } + + /** + * Checks if the field is required + * + * @return bool + */ + public function isRequired(): bool + { + return $this->required ?? false; + } + + /** + * Checks if the field is valid + * + * @return bool + */ + public function isValid(): bool + { + return empty($this->errors()) === true; + } + + /** + * Returns the Kirby instance + * + * @return \Kirby\Cms\App + */ + public function kirby() + { + return $this->model()->kirby(); + } + + /** + * Returns the parent model + * + * @return mixed + */ + public function model() + { + return $this->model; + } + + /** + * Checks if the field needs a value before being saved; + * this is the case if all of the following requirements are met: + * - The field is saveable + * - The field is required + * - The field is currently empty + * - The field is not currently inactive because of a `when` rule + * + * @return bool + */ + protected function needsValue(): bool + { + // check simple conditions first + if ($this->save() === false || $this->isRequired() === false || $this->isEmpty() === false) { + return false; + } + + // check the data of the relevant fields if there is a `when` option + if (empty($this->when) === false && is_array($this->when) === true) { + $formFields = $this->formFields(); + + if ($formFields !== null) { + foreach ($this->when as $field => $value) { + $field = $formFields->get($field); + $inputValue = $field !== null ? $field->value() : ''; + + // if the input data doesn't match the requested `when` value, + // that means that this field is not required and can be saved + // (*all* `when` conditions must be met for this field to be required) + if ($inputValue !== $value) { + return false; + } + } + } + } + + // either there was no `when` condition or all conditions matched + return true; + } + + /** + * Checks if the field is saveable + * + * @return bool + */ + public function save(): bool + { + return ($this->options['save'] ?? true) !== false; + } + + /** + * Converts the field to a plain array + * + * @return array + */ + public function toArray(): array + { + $array = parent::toArray(); + + unset($array['model']); + + $array['saveable'] = $this->save(); + $array['signature'] = md5(json_encode($array)); + + ksort($array); + + return array_filter($array, function ($item) { + return $item !== null; + }); + } + + /** + * Runs the validations defined for the field + * + * @return void + */ + protected function validate(): void + { + $validations = $this->options['validations'] ?? []; + $this->errors = []; + + // validate required values + if ($this->needsValue() === true) { + $this->errors['required'] = I18n::translate('error.validation.required'); + } + + foreach ($validations as $key => $validation) { + if (is_int($key) === true) { + // predefined validation + try { + Validations::$validation($this, $this->value()); + } catch (Exception $e) { + $this->errors[$validation] = $e->getMessage(); + } + continue; + } + + if (is_a($validation, 'Closure') === true) { + try { + $validation->call($this, $this->value()); + } catch (Exception $e) { + $this->errors[$key] = $e->getMessage(); + } + } + } + + if ( + empty($this->validate) === false && + ($this->isEmpty() === false || $this->isRequired() === true) + ) { + $rules = A::wrap($this->validate); + $errors = V::errors($this->value(), $rules); + + if (empty($errors) === false) { + $this->errors = array_merge($this->errors, $errors); + } + } + } + + /** + * Returns the value of the field if saveable + * otherwise it returns null + * + * @return mixed + */ + public function value() + { + return $this->save() ? $this->value : null; + } +} diff --git a/kirby/src/Form/Field/BlocksField.php b/kirby/src/Form/Field/BlocksField.php new file mode 100644 index 0000000..bf88289 --- /dev/null +++ b/kirby/src/Form/Field/BlocksField.php @@ -0,0 +1,274 @@ +setFieldsets($params['fieldsets'] ?? null, $params['model'] ?? site()); + + parent::__construct($params); + + $this->setEmpty($params['empty'] ?? null); + $this->setGroup($params['group'] ?? 'blocks'); + $this->setMax($params['max'] ?? null); + $this->setMin($params['min'] ?? null); + $this->setPretty($params['pretty'] ?? false); + } + + public function blocksToValues($blocks, $to = 'values'): array + { + $result = []; + $fields = []; + + foreach ($blocks as $block) { + try { + $type = $block['type']; + + // get and cache fields at the same time + $fields[$type] = $fields[$type] ?? $this->fields($block['type']); + + // overwrite the block content with form values + $block['content'] = $this->form($fields[$type], $block['content'])->$to(); + + $result[] = $block; + } catch (Throwable $e) { + $result[] = $block; + + // skip invalid blocks + continue; + } + } + + return $result; + } + + public function fields(string $type) + { + return $this->fieldset($type)->fields(); + } + + public function fieldset(string $type) + { + if ($fieldset = $this->fieldsets->find($type)) { + return $fieldset; + } + + throw new NotFoundException('The fieldset ' . $type . ' could not be found'); + } + + public function fieldsets() + { + return $this->fieldsets; + } + + public function fieldsetGroups(): ?array + { + $fieldsetGroups = $this->fieldsets()->groups(); + return empty($fieldsetGroups) === true ? null : $fieldsetGroups; + } + + public function fill($value = null) + { + $value = BlocksCollection::parse($value); + $blocks = BlocksCollection::factory($value); + $this->value = $this->blocksToValues($blocks->toArray()); + } + + public function form(array $fields, array $input = []) + { + return new Form([ + 'fields' => $fields, + 'model' => $this->model, + 'strict' => true, + 'values' => $input, + ]); + } + + public function isEmpty(): bool + { + return count($this->value()) === 0; + } + + public function group(): string + { + return $this->group; + } + + public function pretty(): bool + { + return $this->pretty; + } + + public function props(): array + { + return [ + 'empty' => $this->empty(), + 'fieldsets' => $this->fieldsets()->toArray(), + 'fieldsetGroups' => $this->fieldsetGroups(), + 'group' => $this->group(), + 'max' => $this->max(), + 'min' => $this->min(), + ] + parent::props(); + } + + public function routes(): array + { + $field = $this; + + return [ + [ + 'pattern' => 'uuid', + 'action' => function () { + return ['uuid' => uuid()]; + } + ], + [ + 'pattern' => 'fieldsets/(:any)', + 'method' => 'GET', + 'action' => function ($fieldsetType) use ($field) { + $fields = $field->fields($fieldsetType); + $defaults = $field->form($fields, [])->data(true); + $content = $field->form($fields, $defaults)->values(); + + return Block::factory([ + 'content' => $content, + 'type' => $fieldsetType + ])->toArray(); + } + ], + [ + 'pattern' => 'fieldsets/(:any)/fields/(:any)/(:all?)', + 'method' => 'ALL', + 'action' => function (string $fieldsetType, string $fieldName, string $path = null) use ($field) { + $fields = $field->fields($fieldsetType); + $field = $field->form($fields)->field($fieldName); + + $fieldApi = $this->clone([ + 'routes' => $field->api(), + 'data' => array_merge($this->data(), ['field' => $field]) + ]); + + return $fieldApi->call($path, $this->requestMethod(), $this->requestData()); + } + ], + ]; + } + + public function store($value) + { + $blocks = $this->blocksToValues((array)$value, 'content'); + + // returns empty string to avoid storing empty array as string `[]` + // and to consistency work with `$field->isEmpty()` + if (empty($blocks) === true) { + return ''; + } + + return $this->valueToJson($blocks, $this->pretty()); + } + + protected function setFieldsets($fieldsets, $model) + { + if (is_string($fieldsets) === true) { + $fieldsets = []; + } + + $this->fieldsets = Fieldsets::factory($fieldsets, [ + 'parent' => $model + ]); + } + + protected function setGroup(string $group = null) + { + $this->group = $group; + } + + protected function setPretty(bool $pretty = false) + { + $this->pretty = $pretty; + } + + public function validations(): array + { + return [ + 'blocks' => function ($value) { + if ($this->min && count($value) < $this->min) { + throw new InvalidArgumentException([ + 'key' => 'blocks.min.' . ($this->min === 1 ? 'singular' : 'plural'), + 'data' => [ + 'min' => $this->min + ] + ]); + } + + if ($this->max && count($value) > $this->max) { + throw new InvalidArgumentException([ + 'key' => 'blocks.max.' . ($this->max === 1 ? 'singular' : 'plural'), + 'data' => [ + 'max' => $this->max + ] + ]); + } + + $fields = []; + $index = 0; + + foreach ($value as $block) { + $index++; + $blockType = $block['type']; + + try { + $blockFields = $fields[$blockType] ?? $this->fields($blockType) ?? []; + } catch (Throwable $e) { + // skip invalid blocks + continue; + } + + // store the fields for the next round + $fields[$blockType] = $blockFields; + + // overwrite the content with the serialized form + foreach ($this->form($blockFields, $block['content'])->fields() as $field) { + $errors = $field->errors(); + + // rough first validation + if (empty($errors) === false) { + throw new InvalidArgumentException([ + 'key' => 'blocks.validation', + 'data' => [ + 'index' => $index, + ] + ]); + } + } + } + + return true; + } + ]; + } +} diff --git a/kirby/src/Form/Field/LayoutField.php b/kirby/src/Form/Field/LayoutField.php new file mode 100644 index 0000000..ae4ced4 --- /dev/null +++ b/kirby/src/Form/Field/LayoutField.php @@ -0,0 +1,233 @@ +setModel($params['model'] ?? site()); + $this->setLayouts($params['layouts'] ?? ['1/1']); + $this->setSettings($params['settings'] ?? null); + + parent::__construct($params); + } + + public function fill($value = null) + { + $value = $this->valueFromJson($value); + $layouts = Layouts::factory($value, ['parent' => $this->model])->toArray(); + + foreach ($layouts as $layoutIndex => $layout) { + if ($this->settings !== null) { + $layouts[$layoutIndex]['attrs'] = $this->attrsForm($layout['attrs'])->values(); + } + + foreach ($layout['columns'] as $columnIndex => $column) { + $layouts[$layoutIndex]['columns'][$columnIndex]['blocks'] = $this->blocksToValues($column['blocks']); + } + } + + $this->value = $layouts; + } + + public function attrsForm(array $input = []) + { + $settings = $this->settings(); + + return new Form([ + 'fields' => $settings ? $settings->fields() : [], + 'model' => $this->model, + 'strict' => true, + 'values' => $input, + ]); + } + + public function layouts(): ?array + { + return $this->layouts; + } + + public function props(): array + { + $settings = $this->settings(); + + return array_merge(parent::props(), [ + 'settings' => $settings !== null ? $settings->toArray() : null, + 'layouts' => $this->layouts() + ]); + } + + public function routes(): array + { + $field = $this; + $routes = parent::routes(); + $routes[] = [ + 'pattern' => 'layout', + 'method' => 'POST', + 'action' => function () use ($field) { + $defaults = $field->attrsForm([])->data(true); + $attrs = $field->attrsForm($defaults)->values(); + $columns = get('columns') ?? ['1/1']; + + return Layout::factory([ + 'attrs' => $attrs, + 'columns' => array_map(function ($width) { + return [ + 'blocks' => [], + 'id' => uuid(), + 'width' => $width, + ]; + }, $columns) + ])->toArray(); + }, + ]; + + $routes[] = [ + 'pattern' => 'fields/(:any)/(:all?)', + 'method' => 'ALL', + 'action' => function (string $fieldName, string $path = null) use ($field) { + $form = $field->attrsForm(); + $field = $form->field($fieldName); + + $fieldApi = $this->clone([ + 'routes' => $field->api(), + 'data' => array_merge($this->data(), ['field' => $field]) + ]); + + return $fieldApi->call($path, $this->requestMethod(), $this->requestData()); + } + ]; + + return $routes; + } + + protected function setLayouts(array $layouts = []) + { + $this->layouts = array_map(function ($layout) { + return Str::split($layout); + }, $layouts); + } + + protected function setSettings($settings = null) + { + if (empty($settings) === true) { + $this->settings = null; + return; + } + + $settings = Blueprint::extend($settings); + + $settings['icon'] = 'dashboard'; + $settings['type'] = 'layout'; + $settings['parent'] = $this->model(); + + $this->settings = Fieldset::factory($settings); + } + + public function settings() + { + return $this->settings; + } + + public function store($value) + { + $value = Layouts::factory($value, ['parent' => $this->model])->toArray(); + + // returns empty string to avoid storing empty array as string `[]` + // and to consistency work with `$field->isEmpty()` + if (empty($value) === true) { + return ''; + } + + foreach ($value as $layoutIndex => $layout) { + if ($this->settings !== null) { + $value[$layoutIndex]['attrs'] = $this->attrsForm($layout['attrs'])->content(); + } + + foreach ($layout['columns'] as $columnIndex => $column) { + $value[$layoutIndex]['columns'][$columnIndex]['blocks'] = $this->blocksToValues($column['blocks'] ?? [], 'content'); + } + } + + return $this->valueToJson($value, $this->pretty()); + } + + public function validations(): array + { + return [ + 'layout' => function ($value) { + $fields = []; + $layoutIndex = 0; + + foreach ($value as $layout) { + $layoutIndex++; + + // validate settings form + foreach ($this->attrsForm($layout['attrs'] ?? [])->fields() as $field) { + $errors = $field->errors(); + + if (empty($errors) === false) { + throw new InvalidArgumentException([ + 'key' => 'layout.validation.settings', + 'data' => [ + 'index' => $layoutIndex + ] + ]); + } + } + + // validate blocks in the layout + $blockIndex = 0; + + foreach ($layout['columns'] ?? [] as $column) { + foreach ($column['blocks'] ?? [] as $block) { + $blockIndex++; + $blockType = $block['type']; + + try { + $blockFields = $fields[$blockType] ?? $this->fields($blockType) ?? []; + } catch (Throwable $e) { + // skip invalid blocks + continue; + } + + // store the fields for the next round + $fields[$blockType] = $blockFields; + + // overwrite the content with the serialized form + foreach ($this->form($blockFields, $block['content'])->fields() as $field) { + $errors = $field->errors(); + + // rough first validation + if (empty($errors) === false) { + throw new InvalidArgumentException([ + 'key' => 'layout.validation.block', + 'data' => [ + 'blockIndex' => $blockIndex, + 'layoutIndex' => $layoutIndex + ] + ]); + } + } + } + } + } + + return true; + } + ]; + } +} diff --git a/kirby/src/Form/FieldClass.php b/kirby/src/Form/FieldClass.php new file mode 100644 index 0000000..4b48fa5 --- /dev/null +++ b/kirby/src/Form/FieldClass.php @@ -0,0 +1,638 @@ +$param) === true) { + return $this->$param; + } + + return $this->params[$param] ?? null; + } + + public function __construct(array $params = []) + { + $this->params = $params; + + $this->setAfter($params['after'] ?? null); + $this->setAutofocus($params['autofocus'] ?? false); + $this->setBefore($params['before'] ?? null); + $this->setDefault($params['default'] ?? null); + $this->setDisabled($params['disabled'] ?? false); + $this->setHelp($params['help'] ?? null); + $this->setIcon($params['icon'] ?? null); + $this->setLabel($params['label'] ?? null); + $this->setModel($params['model'] ?? site()); + $this->setName($params['name'] ?? null); + $this->setPlaceholder($params['placeholder'] ?? null); + $this->setRequired($params['required'] ?? false); + $this->setSiblings($params['siblings'] ?? null); + $this->setTranslate($params['translate'] ?? true); + $this->setWhen($params['when'] ?? null); + $this->setWidth($params['width'] ?? null); + + if (array_key_exists('value', $params) === true) { + $this->fill($params['value']); + } + } + + public function after(): ?string + { + return $this->stringTemplate($this->after); + } + + public function api(): array + { + return $this->routes(); + } + + public function autofocus(): bool + { + return $this->autofocus; + } + + public function before(): ?string + { + return $this->stringTemplate($this->before); + } + + /** + * @deprecated + * + * Returns the field data + * in a format to be stored + * in Kirby's content fields + * + * @param bool $default + * @return mixed + */ + public function data(bool $default = false) + { + return $this->store($this->value($default)); + } + + /** + * Returns the default value for the field, + * which will be used when a page/file/user is created + */ + public function default() + { + if ($this->default === null) { + return; + } + + if (is_string($this->default) === false) { + return $this->default; + } + + return $this->stringTemplate($this->default); + } + + /** + * If `true`, the field is no longer editable and will not be saved + */ + public function disabled(): bool + { + return $this->disabled; + } + + /** + * Optional help text below the field + */ + public function help(): ?string + { + if (empty($this->help) === false) { + $help = $this->stringTemplate($this->help); + $help = $this->kirby()->kirbytext($help); + return $help; + } + + return null; + } + + protected function i18n($param) + { + return empty($param) === false ? I18n::translate($param, $param) : null; + } + + /** + * Optional icon that will be shown at the end of the field + */ + public function icon(): ?string + { + return $this->icon; + } + + public function id(): string + { + return $this->name(); + } + + public function isDisabled(): bool + { + return $this->disabled; + } + + public function isEmpty(): bool + { + return $this->isEmptyValue($this->value()); + } + + public function isEmptyValue($value): bool + { + return in_array($value, [null, '', []], true); + } + + /** + * Checks if the field is invalid + * + * @return bool + */ + public function isInvalid(): bool + { + return $this->isValid() === false; + } + + public function isRequired(): bool + { + return $this->required; + } + + public function isSaveable(): bool + { + return true; + } + + /** + * Checks if the field is valid + * + * @return bool + */ + public function isValid(): bool + { + return empty($this->errors()) === true; + } + + /** + * Runs all validations and returns an array of + * error messages + * + * @return array + */ + public function errors(): array + { + return $this->validate(); + } + + /** + * Setter for the value + * + * @param mixed $value + * @return void + */ + public function fill($value = null) + { + $this->value = $value; + } + + /** + * Returns the Kirby instance + * + * @return \Kirby\Cms\App + */ + public function kirby() + { + return $this->model->kirby(); + } + + /** + * The field label can be set as string or associative array with translations + */ + public function label(): string + { + return $this->stringTemplate($this->label ?? Str::ucfirst($this->name())); + } + + /** + * Returns the parent model + * + * @return mixed|null + */ + public function model() + { + return $this->model; + } + + /** + * Returns the field name + * + * @return string + */ + public function name(): string + { + return $this->name ?? $this->type(); + } + + /** + * Checks if the field needs a value before being saved; + * this is the case if all of the following requirements are met: + * - The field is saveable + * - The field is required + * - The field is currently empty + * - The field is not currently inactive because of a `when` rule + * + * @return bool + */ + protected function needsValue(): bool + { + // check simple conditions first + if ( + $this->isSaveable() === false || + $this->isRequired() === false || + $this->isEmpty() === false + ) { + return false; + } + + // check the data of the relevant fields if there is a `when` option + if (empty($this->when) === false && is_array($this->when) === true) { + $formFields = $this->siblings(); + + if ($formFields !== null) { + foreach ($this->when as $field => $value) { + $field = $formFields->get($field); + $inputValue = $field !== null ? $field->value() : ''; + + // if the input data doesn't match the requested `when` value, + // that means that this field is not required and can be saved + // (*all* `when` conditions must be met for this field to be required) + if ($inputValue !== $value) { + return false; + } + } + } + } + + // either there was no `when` condition or all conditions matched + return true; + } + + /** + * Returns all original params for the field + * + * @return array + */ + public function params(): array + { + return $this->params; + } + + /** + * Optional placeholder value that will be shown when the field is empty + */ + public function placeholder(): ?string + { + return $this->stringTemplate($this->placeholder); + } + + /** + * Define the props that will be sent to + * the Vue component + * + * @return array + */ + public function props(): array + { + return [ + 'after' => $this->after(), + 'autofocus' => $this->autofocus(), + 'before' => $this->before(), + 'default' => $this->default(), + 'disabled' => $this->isDisabled(), + 'help' => $this->help(), + 'icon' => $this->icon(), + 'label' => $this->label(), + 'name' => $this->name(), + 'placeholder' => $this->placeholder(), + 'required' => $this->isRequired(), + 'saveable' => $this->isSaveable(), + 'translate' => $this->translate(), + 'type' => $this->type(), + 'when' => $this->when(), + 'width' => $this->width(), + ]; + } + + /** + * If `true`, the field has to be filled in correctly to be saved. + * + * @return bool + */ + public function required(): bool + { + return $this->required; + } + + /** + * Routes for the field API + * + * @return array + */ + public function routes(): array + { + return []; + } + + /** + * @deprecated + * + * @return bool + */ + public function save() + { + return $this->isSaveable(); + } + + protected function setAfter($after = null) + { + $this->after = $this->i18n($after); + } + + protected function setAutofocus(bool $autofocus = false) + { + $this->autofocus = $autofocus; + } + + protected function setBefore($before = null) + { + $this->before = $this->i18n($before); + } + + protected function setDefault($default = null) + { + $this->default = $default; + } + + protected function setDisabled(bool $disabled = false) + { + $this->disabled = $disabled; + } + + protected function setHelp($help = null) + { + $this->help = $this->i18n($help); + } + + protected function setIcon(string $icon = null) + { + $this->icon = $icon; + } + + protected function setLabel($label = null) + { + $this->label = $this->i18n($label); + } + + protected function setModel(ModelWithContent $model) + { + $this->model = $model; + } + + protected function setName(string $name = null) + { + $this->name = $name; + } + + protected function setPlaceholder($placeholder = null) + { + $this->placeholder = $this->i18n($placeholder); + } + + protected function setRequired(bool $required = false) + { + $this->required = $required; + } + + protected function setSiblings(Fields $siblings = null) + { + $this->siblings = $siblings ?? new Fields([]); + } + + protected function setTranslate(bool $translate = true) + { + $this->translate = $translate; + } + + protected function setWhen($when = null) + { + $this->when = $when; + } + + protected function setWidth(string $width = null) + { + $this->width = $width; + } + + protected function siblingsCollection() + { + return $this->siblings; + } + + protected function stringTemplate(?string $string = null): ?string + { + if ($string !== null) { + return $this->model->toString($string); + } + + return null; + } + + public function store($value) + { + return $value; + } + + /** + * Should the field be translatable? + * + * @return bool + */ + public function translate(): bool + { + return $this->translate; + } + + /** + * Converts the field to a plain array + * + * @return array + */ + public function toArray(): array + { + $props = $this->props(); + $props['signature'] = md5(json_encode($props)); + + ksort($props); + + return array_filter($props, function ($item) { + return $item !== null; + }); + } + + public function type(): string + { + return lcfirst(basename(str_replace(['\\', 'Field'], ['/', ''], static::class))); + } + + /** + * Runs the validations defined for the field + * + * @return array + */ + protected function validate(): array + { + $validations = $this->validations(); + $value = $this->value(); + $errors = []; + + // validate required values + if ($this->needsValue() === true) { + $errors['required'] = I18n::translate('error.validation.required'); + } + + foreach ($validations as $key => $validation) { + if (is_int($key) === true) { + // predefined validation + try { + Validations::$validation($this, $value); + } catch (Exception $e) { + $errors[$validation] = $e->getMessage(); + } + continue; + } + + if (is_a($validation, 'Closure') === true) { + try { + $validation->call($this, $value); + } catch (Exception $e) { + $errors[$key] = $e->getMessage(); + } + } + } + + return $errors; + } + + /** + * Defines all validation rules + * + * @return array + */ + protected function validations(): array + { + return []; + } + + /** + * Returns the value of the field if saveable + * otherwise it returns null + * + * @return mixed + */ + public function value(bool $default = false) + { + if ($this->isSaveable() === false) { + return null; + } + + if ($default === true && $this->isEmpty() === true) { + return $this->default(); + } + + return $this->value; + } + + protected function valueFromJson($value): array + { + try { + return Data::decode($value, 'json'); + } catch (Throwable $e) { + return []; + } + } + + protected function valueFromYaml($value) + { + return Data::decode($value, 'yaml'); + } + + protected function valueToJson(array $value = null, bool $pretty = false): string + { + if ($pretty === true) { + return json_encode($value, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE); + } + + return json_encode($value); + } + + protected function valueToYaml(array $value = null): string + { + return Data::encode($value, 'yaml'); + } + + /** + * Conditions when the field will be shown + * + * @return array|null + */ + public function when(): ?array + { + return $this->when; + } + + /** + * Returns the width of the field in + * the Panel grid + * + * @return string + */ + public function width(): string + { + return $this->width ?? '1/1'; + } +} diff --git a/kirby/src/Form/Fields.php b/kirby/src/Form/Fields.php new file mode 100644 index 0000000..0ebb71b --- /dev/null +++ b/kirby/src/Form/Fields.php @@ -0,0 +1,57 @@ + + * @link https://getkirby.com + * @copyright Bastian Allgeier GmbH + * @license https://opensource.org/licenses/MIT + */ +class Fields extends Collection +{ + /** + * Internal setter for each object in the Collection. + * This takes care of validation and of setting + * the collection prop on each object correctly. + * + * @param string $name + * @param object|array $field + * @return $this + */ + public function __set(string $name, $field) + { + if (is_array($field) === true) { + // use the array key as name if the name is not set + $field['name'] = $field['name'] ?? $name; + $field = Field::factory($field['type'], $field, $this); + } + + return parent::__set($field->name(), $field); + } + + /** + * Converts the fields collection to an + * array and also does that for every + * included field. + * + * @param \Closure|null $map + * @return array + */ + public function toArray(Closure $map = null): array + { + $array = []; + + foreach ($this as $field) { + $array[$field->name()] = $field->toArray(); + } + + return $array; + } +} diff --git a/kirby/src/Form/Form.php b/kirby/src/Form/Form.php new file mode 100644 index 0000000..9a56a16 --- /dev/null +++ b/kirby/src/Form/Form.php @@ -0,0 +1,277 @@ + + * @link https://getkirby.com + * @copyright Bastian Allgeier GmbH + * @license https://opensource.org/licenses/MIT + */ +class Form +{ + /** + * An array of all found errors + * + * @var array|null + */ + protected $errors; + + /** + * Fields in the form + * + * @var \Kirby\Form\Fields|null + */ + protected $fields; + + /** + * All values of form + * + * @var array + */ + protected $values = []; + + /** + * Form constructor + * + * @param array $props + */ + public function __construct(array $props) + { + $fields = $props['fields'] ?? []; + $values = $props['values'] ?? []; + $input = $props['input'] ?? []; + $strict = $props['strict'] ?? false; + $inject = $props; + + // lowercase all value names + $values = array_change_key_case($values); + $input = array_change_key_case($input); + + unset($inject['fields'], $inject['values'], $inject['input']); + + $this->fields = new Fields(); + $this->values = []; + + foreach ($fields as $name => $props) { + + // inject stuff from the form constructor (model, etc.) + $props = array_merge($inject, $props); + + // inject the name + $props['name'] = $name = strtolower($name); + + // check if the field is disabled + $disabled = $props['disabled'] ?? false; + + // overwrite the field value if not set + if ($disabled === true) { + $props['value'] = $values[$name] ?? null; + } else { + $props['value'] = $input[$name] ?? $values[$name] ?? null; + } + + try { + $field = Field::factory($props['type'], $props, $this->fields); + } catch (Throwable $e) { + $field = static::exceptionField($e, $props); + } + + if ($field->save() !== false) { + $this->values[$name] = $field->value(); + } + + $this->fields->append($name, $field); + } + + if ($strict !== true) { + + // use all given values, no matter + // if there's a field or not. + $input = array_merge($values, $input); + + foreach ($input as $key => $value) { + if (isset($this->values[$key]) === false) { + $this->values[$key] = $value; + } + } + } + } + + /** + * Returns the data required to write to the content file + * Doesn't include default and null values + * + * @return array + */ + public function content(): array + { + return $this->data(false, false); + } + + /** + * Returns data for all fields in the form + * + * @param false $defaults + * @param bool $includeNulls + * @return array + */ + public function data($defaults = false, bool $includeNulls = true): array + { + $data = $this->values; + + foreach ($this->fields as $field) { + if ($field->save() === false || $field->unset() === true) { + if ($includeNulls === true) { + $data[$field->name()] = null; + } else { + unset($data[$field->name()]); + } + } else { + $data[$field->name()] = $field->data($defaults); + } + } + + return $data; + } + + /** + * An array of all found errors + * + * @return array + */ + public function errors(): array + { + if ($this->errors !== null) { + return $this->errors; + } + + $this->errors = []; + + foreach ($this->fields as $field) { + if (empty($field->errors()) === false) { + $this->errors[$field->name()] = [ + 'label' => $field->label(), + 'message' => $field->errors() + ]; + } + } + + return $this->errors; + } + + /** + * Shows the error with the field + * + * @param \Throwable $exception + * @param array $props + * @return \Kirby\Form\Field + */ + public static function exceptionField(Throwable $exception, array $props = []) + { + $message = $exception->getMessage(); + + if (App::instance()->option('debug') === true) { + $message .= ' in file: ' . $exception->getFile() . ' line: ' . $exception->getLine(); + } + + $props = array_merge($props, [ + 'label' => 'Error in "' . $props['name'] . '" field.', + 'theme' => 'negative', + 'text' => strip_tags($message), + ]); + + return Field::factory('info', $props); + } + + /** + * Returns form fields + * + * @return \Kirby\Form\Fields|null + */ + public function fields() + { + return $this->fields; + } + + /** + * Checks if the form is invalid + * + * @return bool + */ + public function isInvalid(): bool + { + return empty($this->errors()) === false; + } + + /** + * Checks if the form is valid + * + * @return bool + */ + public function isValid(): bool + { + return empty($this->errors()) === true; + } + + /** + * Converts the data of fields to strings + * + * @param false $defaults + * @return array + */ + public function strings($defaults = false): array + { + $strings = []; + + foreach ($this->data($defaults) as $key => $value) { + if ($value === null) { + $strings[$key] = null; + } elseif (is_array($value) === true) { + $strings[$key] = Data::encode($value, 'yaml'); + } else { + $strings[$key] = $value; + } + } + + return $strings; + } + + /** + * Converts the form to a plain array + * + * @return array + */ + public function toArray(): array + { + $array = [ + 'errors' => $this->errors(), + 'fields' => $this->fields->toArray(function ($item) { + return $item->toArray(); + }), + 'invalid' => $this->isInvalid() + ]; + + return $array; + } + + /** + * Returns form values + * + * @return array + */ + public function values(): array + { + return $this->values; + } +} diff --git a/kirby/src/Form/Mixin/EmptyState.php b/kirby/src/Form/Mixin/EmptyState.php new file mode 100644 index 0000000..14b2c36 --- /dev/null +++ b/kirby/src/Form/Mixin/EmptyState.php @@ -0,0 +1,18 @@ +empty = $this->i18n($empty); + } + + public function empty(): ?string + { + return $this->stringTemplate($this->empty); + } +} diff --git a/kirby/src/Form/Mixin/Max.php b/kirby/src/Form/Mixin/Max.php new file mode 100644 index 0000000..42b9ffb --- /dev/null +++ b/kirby/src/Form/Mixin/Max.php @@ -0,0 +1,18 @@ +max; + } + + protected function setMax(int $max = null) + { + $this->max = $max; + } +} diff --git a/kirby/src/Form/Mixin/Min.php b/kirby/src/Form/Mixin/Min.php new file mode 100644 index 0000000..7bf6585 --- /dev/null +++ b/kirby/src/Form/Mixin/Min.php @@ -0,0 +1,18 @@ +min; + } + + protected function setMin(int $min = null) + { + $this->min = $min; + } +} diff --git a/kirby/src/Form/Options.php b/kirby/src/Form/Options.php new file mode 100644 index 0000000..748f0db --- /dev/null +++ b/kirby/src/Form/Options.php @@ -0,0 +1,207 @@ + + * @link https://getkirby.com + * @copyright Bastian Allgeier GmbH + * @license https://opensource.org/licenses/MIT + */ +class Options +{ + /** + * Returns the classes of predefined Kirby objects + * + * @return array + */ + protected static function aliases(): array + { + return [ + 'Kirby\Cms\File' => 'file', + 'Kirby\Toolkit\Obj' => 'arrayItem', + 'Kirby\Cms\Block' => 'block', + 'Kirby\Cms\Page' => 'page', + 'Kirby\Cms\StructureObject' => 'structureItem', + 'Kirby\Cms\User' => 'user', + ]; + } + + /** + * Brings options through api + * + * @param $api + * @param \Kirby\Cms\Model|null $model + * @return array + */ + public static function api($api, $model = null): array + { + $model = $model ?? App::instance()->site(); + $fetch = null; + $text = null; + $value = null; + + if (is_array($api) === true) { + $fetch = $api['fetch'] ?? null; + $text = $api['text'] ?? null; + $value = $api['value'] ?? null; + $url = $api['url'] ?? null; + } else { + $url = $api; + } + + $optionsApi = new OptionsApi([ + 'data' => static::data($model), + 'fetch' => $fetch, + 'url' => $url, + 'text' => $text, + 'value' => $value + ]); + + return $optionsApi->options(); + } + + /** + * @param \Kirby\Cms\Model $model + * @return array + */ + protected static function data($model): array + { + $kirby = $model->kirby(); + + // default data setup + $data = [ + 'kirby' => $kirby, + 'site' => $kirby->site(), + 'users' => $kirby->users(), + ]; + + // add the model by the proper alias + foreach (static::aliases() as $className => $alias) { + if (is_a($model, $className) === true) { + $data[$alias] = $model; + } + } + + return $data; + } + + /** + * Brings options by supporting both api and query + * + * @param $options + * @param array $props + * @param \Kirby\Cms\Model|null $model + * @return array + */ + public static function factory($options, array $props = [], $model = null): array + { + switch ($options) { + case 'api': + $options = static::api($props['api'], $model); + break; + case 'query': + $options = static::query($props['query'], $model); + break; + case 'children': + case 'grandChildren': + case 'siblings': + case 'index': + case 'files': + case 'images': + case 'documents': + case 'videos': + case 'audio': + case 'code': + case 'archives': + $options = static::query('page.' . $options, $model); + break; + case 'pages': + $options = static::query('site.index', $model); + break; + } + + if (is_array($options) === false) { + return []; + } + + $result = []; + + foreach ($options as $key => $option) { + if (is_array($option) === false || isset($option['value']) === false) { + $option = [ + 'value' => is_int($key) ? $option : $key, + 'text' => $option + ]; + } + + // translate the option text + if (is_array($option['text']) === true) { + $option['text'] = I18n::translate($option['text'], $option['text']); + } + + // add the option to the list + $result[] = $option; + } + + return $result; + } + + /** + * Brings options with query + * + * @param $query + * @param \Kirby\Cms\Model|null $model + * @return array + */ + public static function query($query, $model = null): array + { + $model = $model ?? App::instance()->site(); + + // default text setup + $text = [ + 'arrayItem' => '{{ arrayItem.value }}', + 'block' => '{{ block.type }}: {{ block.id }}', + 'file' => '{{ file.filename }}', + 'page' => '{{ page.title }}', + 'structureItem' => '{{ structureItem.title }}', + 'user' => '{{ user.username }}', + ]; + + // default value setup + $value = [ + 'arrayItem' => '{{ arrayItem.value }}', + 'block' => '{{ block.id }}', + 'file' => '{{ file.id }}', + 'page' => '{{ page.id }}', + 'structureItem' => '{{ structureItem.id }}', + 'user' => '{{ user.email }}', + ]; + + // resolve array query setup + if (is_array($query) === true) { + $text = $query['text'] ?? $text; + $value = $query['value'] ?? $value; + $query = $query['fetch'] ?? null; + } + + $optionsQuery = new OptionsQuery([ + 'aliases' => static::aliases(), + 'data' => static::data($model), + 'query' => $query, + 'text' => $text, + 'value' => $value + ]); + + return $optionsQuery->options(); + } +} diff --git a/kirby/src/Form/OptionsApi.php b/kirby/src/Form/OptionsApi.php new file mode 100644 index 0000000..6cbf204 --- /dev/null +++ b/kirby/src/Form/OptionsApi.php @@ -0,0 +1,247 @@ + + * @link https://getkirby.com + * @copyright Bastian Allgeier GmbH + * @license https://opensource.org/licenses/MIT + */ +class OptionsApi +{ + use Properties; + + /** + * @var + */ + protected $data; + + /** + * @var + */ + protected $fetch; + + /** + * @var + */ + protected $options; + + /** + * @var string + */ + protected $text = '{{ item.value }}'; + + /** + * @var + */ + protected $url; + + /** + * @var string + */ + protected $value = '{{ item.key }}'; + + /** + * OptionsApi constructor + * + * @param array $props + */ + public function __construct(array $props) + { + $this->setProperties($props); + } + + /** + * @return array + */ + public function data(): array + { + return $this->data; + } + + /** + * @return mixed + */ + public function fetch() + { + return $this->fetch; + } + + /** + * @param string $field + * @param array $data + * @return string + */ + protected function field(string $field, array $data): string + { + $value = $this->$field(); + return Str::template($value, $data, [ + 'callback' => function ($result) { + return Escape::html($result); + } + ]); + } + + /** + * @return array + * @throws \Exception + * @throws \Kirby\Exception\InvalidArgumentException + */ + public function options(): array + { + if (is_array($this->options) === true) { + return $this->options; + } + + if (Url::isAbsolute($this->url()) === true) { + // URL, request via cURL + $data = Remote::get($this->url())->json(); + } else { + // local file, get contents locally + + // ensure the file exists before trying to load it as the + // file_get_contents() warnings need to be suppressed + if (is_file($this->url()) !== true) { + throw new Exception('Local file ' . $this->url() . ' was not found'); + } + + $content = @file_get_contents($this->url()); + + if (is_string($content) !== true) { + throw new Exception('Unexpected read error'); // @codeCoverageIgnore + } + + if (empty($content) === true) { + return []; + } + + $data = json_decode($content, true); + } + + if (is_array($data) === false) { + throw new InvalidArgumentException('Invalid options format'); + } + + $result = (new Query($this->fetch(), Nest::create($data)))->result(); + $options = []; + + foreach ($result as $item) { + $data = array_merge($this->data(), ['item' => $item]); + + $options[] = [ + 'text' => $this->field('text', $data), + 'value' => $this->field('value', $data), + ]; + } + + return $options; + } + + /** + * @param array $data + * @return $this + */ + protected function setData(array $data) + { + $this->data = $data; + return $this; + } + + /** + * @param string|null $fetch + * @return $this + */ + protected function setFetch(string $fetch = null) + { + $this->fetch = $fetch; + return $this; + } + + /** + * @param $options + * @return $this + */ + protected function setOptions($options = null) + { + $this->options = $options; + return $this; + } + + /** + * @param $text + * @return $this + */ + protected function setText($text = null) + { + $this->text = $text; + return $this; + } + + /** + * @param $url + * @return $this + */ + protected function setUrl($url) + { + $this->url = $url; + return $this; + } + + /** + * @param null $value + * @return $this + */ + protected function setValue($value = null) + { + $this->value = $value; + return $this; + } + + /** + * @return string + */ + public function text() + { + return $this->text; + } + + /** + * @return array + * @throws \Kirby\Exception\InvalidArgumentException + */ + public function toArray(): array + { + return $this->options(); + } + + /** + * @return string + */ + public function url(): string + { + return Str::template($this->url, $this->data()); + } + + /** + * @return string + */ + public function value() + { + return $this->value; + } +} diff --git a/kirby/src/Form/OptionsQuery.php b/kirby/src/Form/OptionsQuery.php new file mode 100644 index 0000000..74d196e --- /dev/null +++ b/kirby/src/Form/OptionsQuery.php @@ -0,0 +1,291 @@ + + * @link https://getkirby.com + * @copyright Bastian Allgeier GmbH + * @license https://opensource.org/licenses/MIT + */ +class OptionsQuery +{ + use Properties; + + /** + * @var array + */ + protected $aliases = []; + + /** + * @var array + */ + protected $data; + + /** + * @var array|string|null + */ + protected $options; + + /** + * @var string + */ + protected $query; + + /** + * @var mixed + */ + protected $text; + + /** + * @var mixed + */ + protected $value; + + /** + * OptionsQuery constructor + * + * @param array $props + */ + public function __construct(array $props) + { + $this->setProperties($props); + } + + /** + * @return array + */ + public function aliases(): array + { + return $this->aliases; + } + + /** + * @return array + */ + public function data(): array + { + return $this->data; + } + + /** + * @param string $object + * @param string $field + * @param array $data + * @return string + * @throws \Kirby\Exception\NotFoundException + */ + protected function template(string $object, string $field, array $data) + { + $value = $this->$field(); + + if (is_array($value) === true) { + if (isset($value[$object]) === false) { + throw new NotFoundException('Missing "' . $field . '" definition'); + } + + $value = $value[$object]; + } + + $result = Str::template($value, $data); + + // escape the default queries for the `text` field + // TODO: remove after default escape implemented for query templates in 3.6 + if ($field === 'text') { + $defaults = [ + 'arrayItem' => '{{ arrayItem.value }}', + 'block' => '{{ block.type }}: {{ block.id }}', + 'file' => '{{ file.filename }}', + 'page' => '{{ page.title }}', + 'structureItem' => '{{ structureItem.title }}', + 'user' => '{{ user.username }}', + ]; + + if (isset($defaults[$object]) && $value === $defaults[$object]) { + $result = Escape::html($result); + } + } + + return $result; + } + + /** + * @return array + */ + public function options(): array + { + if (is_array($this->options) === true) { + return $this->options; + } + + $data = $this->data(); + $query = new Query($this->query(), $data); + $result = $query->result(); + $result = $this->resultToCollection($result); + $options = []; + + foreach ($result as $item) { + $alias = $this->resolve($item); + $data = array_merge($data, [$alias => $item]); + + $options[] = [ + 'text' => $this->template($alias, 'text', $data), + 'value' => $this->template($alias, 'value', $data) + ]; + } + + return $this->options = $options; + } + + /** + * @return string + */ + public function query(): string + { + return $this->query; + } + + /** + * @param $object + * @return mixed|string|null + */ + public function resolve($object) + { + // fast access + if ($alias = ($this->aliases[get_class($object)] ?? null)) { + return $alias; + } + + // slow but precise resolving + foreach ($this->aliases as $className => $alias) { + if (is_a($object, $className) === true) { + return $alias; + } + } + + return 'item'; + } + + /** + * @param $result + * @throws \Kirby\Exception\InvalidArgumentException + */ + protected function resultToCollection($result) + { + if (is_array($result)) { + foreach ($result as $key => $item) { + if (is_scalar($item) === true) { + $result[$key] = new Obj([ + 'key' => new Field(null, 'key', $key), + 'value' => new Field(null, 'value', $item), + ]); + } + } + + $result = new Collection($result); + } + + if (is_a($result, 'Kirby\Toolkit\Collection') === false) { + throw new InvalidArgumentException('Invalid query result data'); + } + + return $result; + } + + /** + * @param array|null $aliases + * @return $this + */ + protected function setAliases(?array $aliases = null) + { + $this->aliases = $aliases; + return $this; + } + + /** + * @param array $data + * @return $this + */ + protected function setData(array $data) + { + $this->data = $data; + return $this; + } + + /** + * @param array|string|null $options + * @return $this + */ + protected function setOptions($options = null) + { + $this->options = $options; + return $this; + } + + /** + * @param string $query + * @return $this + */ + protected function setQuery(string $query) + { + $this->query = $query; + return $this; + } + + /** + * @param mixed $text + * @return $this + */ + protected function setText($text) + { + $this->text = $text; + return $this; + } + + /** + * @param mixed $value + * @return $this + */ + protected function setValue($value) + { + $this->value = $value; + return $this; + } + + /** + * @return mixed + */ + public function text() + { + return $this->text; + } + + public function toArray(): array + { + return $this->options(); + } + + /** + * @return mixed + */ + public function value() + { + return $this->value; + } +} diff --git a/kirby/src/Form/Validations.php b/kirby/src/Form/Validations.php new file mode 100644 index 0000000..d828298 --- /dev/null +++ b/kirby/src/Form/Validations.php @@ -0,0 +1,294 @@ + + * @link https://getkirby.com + * @copyright Bastian Allgeier GmbH + * @license https://opensource.org/licenses/MIT + */ +class Validations +{ + /** + * Validates if the field value is boolean + * + * @param \Kirby\Form\Field $field + * @param $value + * @return bool + * @throws \Kirby\Exception\InvalidArgumentException + */ + public static function boolean(Field $field, $value): bool + { + if ($field->isEmpty($value) === false) { + if (is_bool($value) === false) { + throw new InvalidArgumentException([ + 'key' => 'validation.boolean' + ]); + } + } + + return true; + } + + /** + * Validates if the field value is valid date + * + * @param \Kirby\Form\Field $field + * @param $value + * @return bool + * @throws \Kirby\Exception\InvalidArgumentException + */ + public static function date(Field $field, $value): bool + { + if ($field->isEmpty($value) === false) { + if (V::date($value) !== true) { + throw new InvalidArgumentException( + V::message('date', $value) + ); + } + } + + return true; + } + + /** + * Validates if the field value is valid email + * + * @param \Kirby\Form\Field $field + * @param $value + * @return bool + * @throws \Kirby\Exception\InvalidArgumentException + */ + public static function email(Field $field, $value): bool + { + if ($field->isEmpty($value) === false) { + if (V::email($value) === false) { + throw new InvalidArgumentException( + V::message('email', $value) + ); + } + } + + return true; + } + + /** + * Validates if the field value is maximum + * + * @param \Kirby\Form\Field $field + * @param $value + * @return bool + * @throws \Kirby\Exception\InvalidArgumentException + */ + public static function max(Field $field, $value): bool + { + if ($field->isEmpty($value) === false && $field->max() !== null) { + if (V::max($value, $field->max()) === false) { + throw new InvalidArgumentException( + V::message('max', $value, $field->max()) + ); + } + } + + return true; + } + + /** + * Validates if the field value is max length + * + * @param \Kirby\Form\Field $field + * @param $value + * @return bool + * @throws \Kirby\Exception\InvalidArgumentException + */ + public static function maxlength(Field $field, $value): bool + { + if ($field->isEmpty($value) === false && $field->maxlength() !== null) { + if (V::maxLength($value, $field->maxlength()) === false) { + throw new InvalidArgumentException( + V::message('maxlength', $value, $field->maxlength()) + ); + } + } + + return true; + } + + /** + * Validates if the field value is minimum + * + * @param \Kirby\Form\Field $field + * @param $value + * @return bool + * @throws \Kirby\Exception\InvalidArgumentException + */ + public static function min(Field $field, $value): bool + { + if ($field->isEmpty($value) === false && $field->min() !== null) { + if (V::min($value, $field->min()) === false) { + throw new InvalidArgumentException( + V::message('min', $value, $field->min()) + ); + } + } + + return true; + } + + /** + * Validates if the field value is min length + * + * @param \Kirby\Form\Field $field + * @param $value + * @return bool + * @throws \Kirby\Exception\InvalidArgumentException + */ + public static function minlength(Field $field, $value): bool + { + if ($field->isEmpty($value) === false && $field->minlength() !== null) { + if (V::minLength($value, $field->minlength()) === false) { + throw new InvalidArgumentException( + V::message('minlength', $value, $field->minlength()) + ); + } + } + + return true; + } + + /** + * Validates if the field value matches defined pattern + * + * @param \Kirby\Form\Field $field + * @param $value + * @return bool + * @throws \Kirby\Exception\InvalidArgumentException + */ + public static function pattern(Field $field, $value): bool + { + if ($field->isEmpty($value) === false && $field->pattern() !== null) { + if (V::match($value, '/' . $field->pattern() . '/i') === false) { + throw new InvalidArgumentException( + V::message('match') + ); + } + } + + return true; + } + + /** + * Validates if the field value is required + * + * @param \Kirby\Form\Field $field + * @param $value + * @return bool + * @throws \Kirby\Exception\InvalidArgumentException + */ + public static function required(Field $field, $value): bool + { + if ($field->isRequired() === true && $field->save() === true && $field->isEmpty($value) === true) { + throw new InvalidArgumentException([ + 'key' => 'validation.required' + ]); + } + + return true; + } + + /** + * Validates if the field value is in defined options + * + * @param \Kirby\Form\Field $field + * @param $value + * @return bool + * @throws \Kirby\Exception\InvalidArgumentException + */ + public static function option(Field $field, $value): bool + { + if ($field->isEmpty($value) === false) { + $values = array_column($field->options(), 'value'); + + if (in_array($value, $values, true) !== true) { + throw new InvalidArgumentException([ + 'key' => 'validation.option' + ]); + } + } + + return true; + } + + /** + * Validates if the field values is in defined options + * + * @param \Kirby\Form\Field $field + * @param $value + * @return bool + * @throws \Kirby\Exception\InvalidArgumentException + */ + public static function options(Field $field, $value): bool + { + if ($field->isEmpty($value) === false) { + $values = array_column($field->options(), 'value'); + foreach ($value as $val) { + if (in_array($val, $values, true) === false) { + throw new InvalidArgumentException([ + 'key' => 'validation.option' + ]); + } + } + } + + return true; + } + + /** + * Validates if the field value is valid time + * + * @param \Kirby\Form\Field $field + * @param $value + * @return bool + * @throws \Kirby\Exception\InvalidArgumentException + */ + public static function time(Field $field, $value): bool + { + if ($field->isEmpty($value) === false) { + if (V::time($value) !== true) { + throw new InvalidArgumentException( + V::message('time', $value) + ); + } + } + + return true; + } + + /** + * Validates if the field value is valid url + * + * @param \Kirby\Form\Field $field + * @param $value + * @return bool + * @throws \Kirby\Exception\InvalidArgumentException + */ + public static function url(Field $field, $value): bool + { + if ($field->isEmpty($value) === false) { + if (V::url($value) === false) { + throw new InvalidArgumentException( + V::message('url', $value) + ); + } + } + + return true; + } +} diff --git a/kirby/src/Http/Cookie.php b/kirby/src/Http/Cookie.php new file mode 100644 index 0000000..6111cd9 --- /dev/null +++ b/kirby/src/Http/Cookie.php @@ -0,0 +1,209 @@ + + * @link https://getkirby.com + * @copyright Bastian Allgeier GmbH + * @license https://opensource.org/licenses/MIT + */ +class Cookie +{ + /** + * Key to use for cookie signing + * @var string + */ + public static $key = 'KirbyHttpCookieKey'; + + /** + * Set a new cookie + * + * + * + * cookie::set('mycookie', 'hello', ['lifetime' => 60]); + * // expires in 1 hour + * + * + * + * @param string $key The name of the cookie + * @param string $value The cookie content + * @param array $options Array of options: + * lifetime, path, domain, secure, httpOnly, sameSite + * @return bool true: cookie was created, + * false: cookie creation failed + */ + public static function set(string $key, string $value, array $options = []): bool + { + // extract options + $expires = static::lifetime($options['lifetime'] ?? 0); + $path = $options['path'] ?? '/'; + $domain = $options['domain'] ?? null; + $secure = $options['secure'] ?? false; + $httponly = $options['httpOnly'] ?? true; + $samesite = $options['sameSite'] ?? 'Lax'; + + // add an HMAC signature of the value + $value = static::hmac($value) . '+' . $value; + + // store that thing in the cookie global + $_COOKIE[$key] = $value; + + // store the cookie + $options = compact('expires', 'path', 'domain', 'secure', 'httponly', 'samesite'); + return setcookie($key, $value, $options); + } + + /** + * Calculates the lifetime for a cookie + * + * @param int $minutes Number of minutes or timestamp + * @return int + */ + public static function lifetime(int $minutes): int + { + if ($minutes > 1000000000) { + // absolute timestamp + return $minutes; + } elseif ($minutes > 0) { + // minutes from now + return time() + ($minutes * 60); + } else { + return 0; + } + } + + /** + * Stores a cookie forever + * + * + * + * cookie::forever('mycookie', 'hello'); + * // never expires + * + * + * + * @param string $key The name of the cookie + * @param string $value The cookie content + * @param array $options Array of options: + * path, domain, secure, httpOnly + * @return bool true: cookie was created, + * false: cookie creation failed + */ + public static function forever(string $key, string $value, array $options = []): bool + { + // 9999-12-31 if supported (lower on 32-bit servers) + $options['lifetime'] = min(253402214400, PHP_INT_MAX); + return static::set($key, $value, $options); + } + + /** + * Get a cookie value + * + * + * + * cookie::get('mycookie', 'peter'); + * // sample output: 'hello' or if the cookie is not set 'peter' + * + * + * + * @param string|null $key The name of the cookie + * @param string|null $default The default value, which should be returned + * if the cookie has not been found + * @return mixed The found value + */ + public static function get(string $key = null, string $default = null) + { + if ($key === null) { + return $_COOKIE; + } + $value = $_COOKIE[$key] ?? null; + return empty($value) ? $default : static::parse($value); + } + + /** + * Checks if a cookie exists + * + * @param string $key + * @return bool + */ + public static function exists(string $key): bool + { + return static::get($key) !== null; + } + + /** + * Creates a HMAC for the cookie value + * Used as a cookie signature to prevent easy tampering with cookie data + * + * @param string $value + * @return string + */ + protected static function hmac(string $value): string + { + return hash_hmac('sha1', $value, static::$key); + } + + /** + * Parses the hashed value from a cookie + * and tries to extract the value + * + * @param string $string + * @return mixed + */ + protected static function parse(string $string) + { + // if no hash-value separator is present, we can't parse the value + if (strpos($string, '+') === false) { + return null; + } + + // extract hash and value + $hash = Str::before($string, '+'); + $value = Str::after($string, '+'); + + // if the hash or the value is missing at all return null + // $value can be an empty string, $hash can't be! + if (!is_string($hash) || $hash === '' || !is_string($value)) { + return null; + } + + // compare the extracted hash with the hashed value + // don't accept value if the hash is invalid + if (hash_equals(static::hmac($value), $hash) !== true) { + return null; + } + + return $value; + } + + /** + * Remove a cookie + * + * + * + * cookie::remove('mycookie'); + * // mycookie is now gone + * + * + * + * @param string $key The name of the cookie + * @return bool true: the cookie has been removed, + * false: the cookie could not be removed + */ + public static function remove(string $key): bool + { + if (isset($_COOKIE[$key])) { + unset($_COOKIE[$key]); + return setcookie($key, '', 1, '/') && setcookie($key, false); + } + + return false; + } +} diff --git a/kirby/src/Http/Exceptions/NextRouteException.php b/kirby/src/Http/Exceptions/NextRouteException.php new file mode 100644 index 0000000..de85efa --- /dev/null +++ b/kirby/src/Http/Exceptions/NextRouteException.php @@ -0,0 +1,16 @@ + + * @link https://getkirby.com + * @copyright Bastian Allgeier GmbH + * @license https://opensource.org/licenses/MIT + */ +class NextRouteException extends \Exception +{ +} diff --git a/kirby/src/Http/Header.php b/kirby/src/Http/Header.php new file mode 100644 index 0000000..6ee8680 --- /dev/null +++ b/kirby/src/Http/Header.php @@ -0,0 +1,316 @@ + + * @link https://getkirby.com + * @copyright Bastian Allgeier GmbH + * @license https://opensource.org/licenses/MIT + */ +class Header +{ + // configuration + public static $codes = [ + + // successful + '_200' => 'OK', + '_201' => 'Created', + '_202' => 'Accepted', + + // redirection + '_300' => 'Multiple Choices', + '_301' => 'Moved Permanently', + '_302' => 'Found', + '_303' => 'See Other', + '_304' => 'Not Modified', + '_307' => 'Temporary Redirect', + '_308' => 'Permanent Redirect', + + // client error + '_400' => 'Bad Request', + '_401' => 'Unauthorized', + '_402' => 'Payment Required', + '_403' => 'Forbidden', + '_404' => 'Not Found', + '_405' => 'Method Not Allowed', + '_406' => 'Not Acceptable', + '_410' => 'Gone', + '_418' => 'I\'m a teapot', + '_451' => 'Unavailable For Legal Reasons', + + // server error + '_500' => 'Internal Server Error', + '_501' => 'Not Implemented', + '_502' => 'Bad Gateway', + '_503' => 'Service Unavailable', + '_504' => 'Gateway Time-out' + ]; + + /** + * Sends a content type header + * + * @param string $mime + * @param string $charset + * @param bool $send + * @return string|void + */ + public static function contentType(string $mime, string $charset = 'UTF-8', bool $send = true) + { + if ($found = F::extensionToMime($mime)) { + $mime = $found; + } + + $header = 'Content-type: ' . $mime; + + if (empty($charset) === false) { + $header .= '; charset=' . $charset; + } + + if ($send === false) { + return $header; + } + + header($header); + } + + /** + * Creates headers by key and value + * + * @param string|array $key + * @param string|null $value + * @return string + */ + public static function create($key, string $value = null): string + { + if (is_array($key) === true) { + $headers = []; + + foreach ($key as $k => $v) { + $headers[] = static::create($k, $v); + } + + return implode("\r\n", $headers); + } + + // prevent header injection by stripping any newline characters from single headers + return str_replace(["\r", "\n"], '', $key . ': ' . $value); + } + + /** + * Shortcut for static::contentType() + * + * @param string $mime + * @param string $charset + * @param bool $send + * @return string|void + */ + public static function type(string $mime, string $charset = 'UTF-8', bool $send = true) + { + return static::contentType($mime, $charset, $send); + } + + /** + * Sends a status header + * + * Checks $code against a list of known status codes. To bypass this check + * and send a custom status code and message, use a $code string formatted + * as 3 digits followed by a space and a message, e.g. '999 Custom Status'. + * + * @param int|string $code The HTTP status code + * @param bool $send If set to false the header will be returned instead + * @return string|void + */ + public static function status($code = null, bool $send = true) + { + $codes = static::$codes; + $protocol = $_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.1'; + + // allow full control over code and message + if (is_string($code) === true && preg_match('/^\d{3} \w.+$/', $code) === 1) { + $message = substr(rtrim($code), 4); + $code = substr($code, 0, 3); + } else { + $code = array_key_exists('_' . $code, $codes) === false ? 500 : $code; + $message = $codes['_' . $code] ?? 'Something went wrong'; + } + + $header = $protocol . ' ' . $code . ' ' . $message; + + if ($send === false) { + return $header; + } + + // try to send the header + header($header); + } + + /** + * Sends a 200 header + * + * @param bool $send + * @return string|void + */ + public static function success(bool $send = true) + { + return static::status(200, $send); + } + + /** + * Sends a 201 header + * + * @param bool $send + * @return string|void + */ + public static function created(bool $send = true) + { + return static::status(201, $send); + } + + /** + * Sends a 202 header + * + * @param bool $send + * @return string|void + */ + public static function accepted(bool $send = true) + { + return static::status(202, $send); + } + + /** + * Sends a 400 header + * + * @param bool $send + * @return string|void + */ + public static function error(bool $send = true) + { + return static::status(400, $send); + } + + /** + * Sends a 403 header + * + * @param bool $send + * @return string|void + */ + public static function forbidden(bool $send = true) + { + return static::status(403, $send); + } + + /** + * Sends a 404 header + * + * @param bool $send + * @return string|void + */ + public static function notfound(bool $send = true) + { + return static::status(404, $send); + } + + /** + * Sends a 404 header + * + * @param bool $send + * @return string|void + */ + public static function missing(bool $send = true) + { + return static::status(404, $send); + } + + /** + * Sends a 410 header + * + * @param bool $send + * @return string|void + */ + public static function gone(bool $send = true) + { + return static::status(410, $send); + } + + /** + * Sends a 500 header + * + * @param bool $send + * @return string|void + */ + public static function panic(bool $send = true) + { + return static::status(500, $send); + } + + /** + * Sends a 503 header + * + * @param bool $send + * @return string|void + */ + public static function unavailable(bool $send = true) + { + return static::status(503, $send); + } + + /** + * Sends a redirect header + * + * @param string $url + * @param int $code + * @param bool $send + * @return string|void + */ + public static function redirect(string $url, int $code = 302, bool $send = true) + { + $status = static::status($code, false); + $location = 'Location:' . Url::unIdn($url); + + if ($send !== true) { + return $status . "\r\n" . $location; + } + + header($status); + header($location); + exit(); + } + + /** + * Sends download headers for anything that is downloadable + * + * @param array $params Check out the defaults array for available parameters + */ + public static function download(array $params = []) + { + $defaults = [ + 'name' => 'download', + 'size' => false, + 'mime' => 'application/force-download', + 'modified' => time() + ]; + + $options = array_merge($defaults, $params); + + header('Pragma: public'); + header('Cache-Control: no-cache, no-store, must-revalidate'); + header('Last-Modified: ' . gmdate('D, d M Y H:i:s', $options['modified']) . ' GMT'); + header('Content-Disposition: attachment; filename="' . $options['name'] . '"'); + header('Content-Transfer-Encoding: binary'); + + static::contentType($options['mime']); + + if ($options['size']) { + header('Content-Length: ' . $options['size']); + } + + header('Connection: close'); + } +} diff --git a/kirby/src/Http/Idn.php b/kirby/src/Http/Idn.php new file mode 100644 index 0000000..e4b58aa --- /dev/null +++ b/kirby/src/Http/Idn.php @@ -0,0 +1,64 @@ + + * @link https://getkirby.com + * @copyright Bastian Allgeier GmbH + * @license https://opensource.org/licenses/MIT + */ +class Idn +{ + public static function decode(string $domain) + { + return (new Punycode())->decode($domain); + } + + public static function encode(string $domain) + { + return (new Punycode())->encode($domain); + } + + /** + * Decodes a email address to the Unicode format + * + * @param string $email + * @return string + */ + public static function decodeEmail(string $email): string + { + if (Str::contains($email, 'xn--') === true) { + $parts = Str::split($email, '@'); + $address = $parts[0]; + $domain = Idn::decode($parts[1] ?? ''); + $email = $address . '@' . $domain; + } + + return $email; + } + + /** + * Encodes a email address to the Punycode format + * + * @param string $email + * @return string + */ + public static function encodeEmail(string $email): string + { + if (mb_detect_encoding($email, 'ASCII', true) === false) { + $parts = Str::split($email, '@'); + $address = $parts[0]; + $domain = Idn::encode($parts[1] ?? ''); + $email = $address . '@' . $domain; + } + + return $email; + } +} diff --git a/kirby/src/Http/Params.php b/kirby/src/Http/Params.php new file mode 100644 index 0000000..9139527 --- /dev/null +++ b/kirby/src/Http/Params.php @@ -0,0 +1,155 @@ + + * @link https://getkirby.com + * @copyright Bastian Allgeier GmbH + * @license https://opensource.org/licenses/MIT + */ +class Params extends Query +{ + /** + * @var null|string + */ + public static $separator; + + /** + * Creates a new params object + * + * @param array|string $params + */ + public function __construct($params) + { + if (is_string($params) === true) { + $params = static::extract($params)['params']; + } + + parent::__construct($params ?? []); + } + + /** + * Extract the params from a string or array + * + * @param string|array|null $path + * @return array + */ + public static function extract($path = null): array + { + if (empty($path) === true) { + return [ + 'path' => null, + 'params' => null, + 'slash' => false + ]; + } + + $slash = false; + + if (is_string($path) === true) { + $slash = substr($path, -1, 1) === '/'; + $path = Str::split($path, '/'); + } + + if (is_array($path) === true) { + $params = []; + $separator = static::separator(); + + foreach ($path as $index => $p) { + if (strpos($p, $separator) === false) { + continue; + } + + $paramParts = Str::split($p, $separator); + $paramKey = $paramParts[0]; + $paramValue = $paramParts[1] ?? null; + + $params[$paramKey] = $paramValue; + unset($path[$index]); + } + + return [ + 'path' => $path, + 'params' => $params, + 'slash' => $slash + ]; + } + + return [ + 'path' => null, + 'params' => null, + 'slash' => false + ]; + } + + /** + * Returns the param separator according + * to the operating system. + * + * Unix = ':' + * Windows = ';' + * + * @return string + */ + public static function separator(): string + { + if (static::$separator !== null) { + return static::$separator; + } + + if (DIRECTORY_SEPARATOR === '/') { + return static::$separator = ':'; + } else { + return static::$separator = ';'; + } + } + + /** + * Converts the params object to a params string + * which can then be used in the URL builder again + * + * @param bool $leadingSlash + * @param bool $trailingSlash + * @return string|null + * + * @todo The argument $leadingSlash is incompatible with + * Query::toString($questionMark = false); the Query class + * should be extracted into a common parent class for both + * Query and Params + * @psalm-suppress ParamNameMismatch + */ + public function toString($leadingSlash = false, $trailingSlash = false): string + { + if ($this->isEmpty() === true) { + return ''; + } + + $params = []; + $separator = static::separator(); + + foreach ($this as $key => $value) { + if ($value !== null && $value !== '') { + $params[] = $key . $separator . $value; + } + } + + if (empty($params) === true) { + return ''; + } + + $params = implode('/', $params); + + $leadingSlash = $leadingSlash === true ? '/' : null; + $trailingSlash = $trailingSlash === true ? '/' : null; + + return $leadingSlash . $params . $trailingSlash; + } +} diff --git a/kirby/src/Http/Path.php b/kirby/src/Http/Path.php new file mode 100644 index 0000000..eaa014a --- /dev/null +++ b/kirby/src/Http/Path.php @@ -0,0 +1,47 @@ + + * @link https://getkirby.com + * @copyright Bastian Allgeier GmbH + * @license https://opensource.org/licenses/MIT + */ +class Path extends Collection +{ + public function __construct($items) + { + if (is_string($items) === true) { + $items = Str::split($items, '/'); + } + + parent::__construct($items ?? []); + } + + public function __toString(): string + { + return $this->toString(); + } + + public function toString(bool $leadingSlash = false, bool $trailingSlash = false): string + { + if (empty($this->data) === true) { + return ''; + } + + $path = implode('/', $this->data); + + $leadingSlash = $leadingSlash === true ? '/' : null; + $trailingSlash = $trailingSlash === true ? '/' : null; + + return $leadingSlash . $path . $trailingSlash; + } +} diff --git a/kirby/src/Http/Query.php b/kirby/src/Http/Query.php new file mode 100644 index 0000000..0956802 --- /dev/null +++ b/kirby/src/Http/Query.php @@ -0,0 +1,58 @@ + + * @link https://getkirby.com + * @copyright Bastian Allgeier GmbH + * @license https://opensource.org/licenses/MIT + */ +class Query extends Obj +{ + public function __construct($query) + { + if (is_string($query) === true) { + parse_str(ltrim($query, '?'), $query); + } + + parent::__construct($query ?? []); + } + + public function isEmpty(): bool + { + return empty((array)$this) === true; + } + + public function isNotEmpty(): bool + { + return empty((array)$this) === false; + } + + public function __toString(): string + { + return $this->toString(); + } + + public function toString($questionMark = false): string + { + $query = http_build_query($this); + + if (empty($query) === true) { + return ''; + } + + if ($questionMark === true) { + $query = '?' . $query; + } + + return $query; + } +} diff --git a/kirby/src/Http/Remote.php b/kirby/src/Http/Remote.php new file mode 100644 index 0000000..31c1a99 --- /dev/null +++ b/kirby/src/Http/Remote.php @@ -0,0 +1,404 @@ + + * @link https://getkirby.com + * @copyright Bastian Allgeier GmbH + * @license https://opensource.org/licenses/MIT + */ +class Remote +{ + const CA_INTERNAL = 1; + const CA_SYSTEM = 2; + + /** + * @var array + */ + public static $defaults = [ + 'agent' => null, + 'basicAuth' => null, + 'body' => true, + 'ca' => self::CA_INTERNAL, + 'data' => [], + 'encoding' => 'utf-8', + 'file' => null, + 'headers' => [], + 'method' => 'GET', + 'progress' => null, + 'test' => false, + 'timeout' => 10, + ]; + + /** + * @var string + */ + public $content; + + /** + * @var resource + */ + public $curl; + + /** + * @var array + */ + public $curlopt = []; + + /** + * @var int + */ + public $errorCode; + + /** + * @var string + */ + public $errorMessage; + + /** + * @var array + */ + public $headers = []; + + /** + * @var array + */ + public $info = []; + + /** + * @var array + */ + public $options = []; + + /** + * Magic getter for request info data + * + * @param string $method + * @param array $arguments + * @return mixed + */ + public function __call(string $method, array $arguments = []) + { + $method = str_replace('-', '_', Str::kebab($method)); + return $this->info[$method] ?? null; + } + + /** + * Constructor + * + * @param string $url + * @param array $options + */ + public function __construct(string $url, array $options = []) + { + $defaults = static::$defaults; + + // update the defaults with App config if set; + // request the App instance lazily + $app = App::instance(null, true); + if ($app !== null) { + $defaults = array_merge($defaults, $app->option('remote', [])); + } + + // set all options + $this->options = array_merge($defaults, $options); + + // add the url + $this->options['url'] = $url; + + // send the request + $this->fetch(); + } + + public static function __callStatic(string $method, array $arguments = []) + { + return new static($arguments[0], array_merge(['method' => strtoupper($method)], $arguments[1] ?? [])); + } + + /** + * Returns the http status code + * + * @return int|null + */ + public function code(): ?int + { + return $this->info['http_code'] ?? null; + } + + /** + * Returns the response content + * + * @return mixed + */ + public function content() + { + return $this->content; + } + + /** + * Sets up all curl options and sends the request + * + * @return $this + */ + public function fetch() + { + // curl options + $this->curlopt = [ + CURLOPT_URL => $this->options['url'], + CURLOPT_ENCODING => $this->options['encoding'], + CURLOPT_CONNECTTIMEOUT => $this->options['timeout'], + CURLOPT_TIMEOUT => $this->options['timeout'], + CURLOPT_AUTOREFERER => true, + CURLOPT_RETURNTRANSFER => $this->options['body'], + CURLOPT_FOLLOWLOCATION => true, + CURLOPT_MAXREDIRS => 10, + CURLOPT_HEADER => false, + CURLOPT_HEADERFUNCTION => function ($curl, $header) { + $parts = Str::split($header, ':'); + + if (empty($parts[0]) === false && empty($parts[1]) === false) { + $key = array_shift($parts); + $this->headers[$key] = implode(':', $parts); + } + + return strlen($header); + } + ]; + + // determine the TLS CA to use + if ($this->options['ca'] === self::CA_INTERNAL) { + $this->curlopt[CURLOPT_SSL_VERIFYPEER] = true; + $this->curlopt[CURLOPT_CAINFO] = dirname(__DIR__, 2) . '/cacert.pem'; + } elseif ($this->options['ca'] === self::CA_SYSTEM) { + $this->curlopt[CURLOPT_SSL_VERIFYPEER] = true; + } elseif ($this->options['ca'] === false) { + $this->curlopt[CURLOPT_SSL_VERIFYPEER] = false; + } elseif ( + is_string($this->options['ca']) === true && + is_file($this->options['ca']) === true + ) { + $this->curlopt[CURLOPT_SSL_VERIFYPEER] = true; + $this->curlopt[CURLOPT_CAINFO] = $this->options['ca']; + } elseif ( + is_string($this->options['ca']) === true && + is_dir($this->options['ca']) === true + ) { + $this->curlopt[CURLOPT_SSL_VERIFYPEER] = true; + $this->curlopt[CURLOPT_CAPATH] = $this->options['ca']; + } else { + throw new InvalidArgumentException('Invalid "ca" option for the Remote class'); + } + + // add the progress + if (is_callable($this->options['progress']) === true) { + $this->curlopt[CURLOPT_NOPROGRESS] = false; + $this->curlopt[CURLOPT_PROGRESSFUNCTION] = $this->options['progress']; + } + + // add all headers + if (empty($this->options['headers']) === false) { + // convert associative arrays to strings + $headers = []; + foreach ($this->options['headers'] as $key => $value) { + if (is_string($key) === true) { + $headers[] = $key . ': ' . $value; + } else { + $headers[] = $value; + } + } + + $this->curlopt[CURLOPT_HTTPHEADER] = $headers; + } + + // add HTTP Basic authentication + if (empty($this->options['basicAuth']) === false) { + $this->curlopt[CURLOPT_USERPWD] = $this->options['basicAuth']; + } + + // add the user agent + if (empty($this->options['agent']) === false) { + $this->curlopt[CURLOPT_USERAGENT] = $this->options['agent']; + } + + // do some request specific stuff + switch (strtoupper($this->options['method'])) { + case 'POST': + $this->curlopt[CURLOPT_POST] = true; + $this->curlopt[CURLOPT_CUSTOMREQUEST] = 'POST'; + $this->curlopt[CURLOPT_POSTFIELDS] = $this->postfields($this->options['data']); + break; + case 'PUT': + $this->curlopt[CURLOPT_CUSTOMREQUEST] = 'PUT'; + $this->curlopt[CURLOPT_POSTFIELDS] = $this->postfields($this->options['data']); + + // put a file + if ($this->options['file']) { + $this->curlopt[CURLOPT_INFILE] = fopen($this->options['file'], 'r'); + $this->curlopt[CURLOPT_INFILESIZE] = F::size($this->options['file']); + } + break; + case 'PATCH': + $this->curlopt[CURLOPT_CUSTOMREQUEST] = 'PATCH'; + $this->curlopt[CURLOPT_POSTFIELDS] = $this->postfields($this->options['data']); + break; + case 'DELETE': + $this->curlopt[CURLOPT_CUSTOMREQUEST] = 'DELETE'; + $this->curlopt[CURLOPT_POSTFIELDS] = $this->postfields($this->options['data']); + break; + case 'HEAD': + $this->curlopt[CURLOPT_CUSTOMREQUEST] = 'HEAD'; + $this->curlopt[CURLOPT_POSTFIELDS] = $this->postfields($this->options['data']); + $this->curlopt[CURLOPT_NOBODY] = true; + break; + } + + if ($this->options['test'] === true) { + return $this; + } + + // start a curl request + $this->curl = curl_init(); + + curl_setopt_array($this->curl, $this->curlopt); + + $this->content = curl_exec($this->curl); + $this->info = curl_getinfo($this->curl); + $this->errorCode = curl_errno($this->curl); + $this->errorMessage = curl_error($this->curl); + + if ($this->errorCode) { + throw new Exception($this->errorMessage, $this->errorCode); + } + + curl_close($this->curl); + + return $this; + } + + /** + * Static method to send a GET request + * + * @param string $url + * @param array $params + * @return static + */ + public static function get(string $url, array $params = []) + { + $defaults = [ + 'method' => 'GET', + 'data' => [], + ]; + + $options = array_merge($defaults, $params); + $query = http_build_query($options['data']); + + if (empty($query) === false) { + $url = Url::hasQuery($url) === true ? $url . '&' . $query : $url . '?' . $query; + } + + // remove the data array from the options + unset($options['data']); + + return new static($url, $options); + } + + /** + * Returns all received headers + * + * @return array + */ + public function headers(): array + { + return $this->headers; + } + + /** + * Returns the request info + * + * @return array + */ + public function info(): array + { + return $this->info; + } + + /** + * Decode the response content + * + * @param bool $array decode as array or object + * @return array|\stdClass + */ + public function json(bool $array = true) + { + return json_decode($this->content(), $array); + } + + /** + * Returns the request method + * + * @return string + */ + public function method(): string + { + return $this->options['method']; + } + + /** + * Returns all options which have been + * set for the current request + * + * @return array + */ + public function options(): array + { + return $this->options; + } + + /** + * Internal method to handle post field data + * + * @param mixed $data + * @return mixed + */ + protected function postfields($data) + { + if (is_object($data) || is_array($data)) { + return http_build_query($data); + } else { + return $data; + } + } + + /** + * Static method to init this class and send a request + * + * @param string $url + * @param array $params + * @return static + */ + public static function request(string $url, array $params = []) + { + return new static($url, $params); + } + + /** + * Returns the request Url + * + * @return string + */ + public function url(): string + { + return $this->options['url']; + } +} diff --git a/kirby/src/Http/Request.php b/kirby/src/Http/Request.php new file mode 100644 index 0000000..dce994b --- /dev/null +++ b/kirby/src/Http/Request.php @@ -0,0 +1,413 @@ + + * @link https://getkirby.com + * @copyright Bastian Allgeier GmbH + * @license https://opensource.org/licenses/MIT + */ +class Request +{ + /** + * The auth object if available + * + * @var BearerAuth|BasicAuth|false|null + */ + protected $auth; + + /** + * The Body object is a wrapper around + * the request body, which parses the contents + * of the body and provides an API to fetch + * particular parts of the body + * + * Examples: + * + * `$request->body()->get('foo')` + * + * @var Body + */ + protected $body; + + /** + * The Files object is a wrapper around + * the $_FILES global. It sanitizes the + * $_FILES array and provides an API to fetch + * individual files by key + * + * Examples: + * + * `$request->files()->get('upload')['size']` + * `$request->file('upload')['size']` + * + * @var Files + */ + protected $files; + + /** + * The Method type + * + * @var string + */ + protected $method; + + /** + * All options that have been passed to + * the request in the constructor + * + * @var array + */ + protected $options; + + /** + * The Query object is a wrapper around + * the URL query string, which parses the + * string and provides a clean API to fetch + * particular parts of the query + * + * Examples: + * + * `$request->query()->get('foo')` + * + * @var Query + */ + protected $query; + + /** + * Request URL object + * + * @var Uri + */ + protected $url; + + /** + * Creates a new Request object + * You can either pass your own request + * data via the $options array or use + * the data from the incoming request. + * + * @param array $options + */ + public function __construct(array $options = []) + { + $this->options = $options; + $this->method = $this->detectRequestMethod($options['method'] ?? null); + + if (isset($options['body']) === true) { + $this->body = new Body($options['body']); + } + + if (isset($options['files']) === true) { + $this->files = new Files($options['files']); + } + + if (isset($options['query']) === true) { + $this->query = new Query($options['query']); + } + + if (isset($options['url']) === true) { + $this->url = new Uri($options['url']); + } + } + + /** + * Improved `var_dump` output + * + * @return array + */ + public function __debugInfo(): array + { + return [ + 'body' => $this->body(), + 'files' => $this->files(), + 'method' => $this->method(), + 'query' => $this->query(), + 'url' => $this->url()->toString() + ]; + } + + /** + * Returns the Auth object if authentication is set + * + * @return \Kirby\Http\Request\Auth\BasicAuth|\Kirby\Http\Request\Auth\BearerAuth|null + */ + public function auth() + { + if ($this->auth !== null) { + return $this->auth; + } + + if ($auth = $this->options['auth'] ?? $this->header('authorization')) { + $type = Str::before($auth, ' '); + $token = Str::after($auth, ' '); + $class = 'Kirby\\Http\\Request\\Auth\\' . ucfirst($type) . 'Auth'; + + if (class_exists($class) === false) { + return $this->auth = false; + } + + return $this->auth = new $class($token); + } + + return $this->auth = false; + } + + /** + * Returns the Body object + * + * @return \Kirby\Http\Request\Body + */ + public function body() + { + return $this->body = $this->body ?? new Body(); + } + + /** + * Checks if the request has been made from the command line + * + * @return bool + */ + public function cli(): bool + { + return Server::cli(); + } + + /** + * Returns a CSRF token if stored in a header or the query + * + * @return string|null + */ + public function csrf(): ?string + { + return $this->header('x-csrf') ?? $this->query()->get('csrf'); + } + + /** + * Returns the request input as array + * + * @return array + */ + public function data(): array + { + return array_merge($this->body()->toArray(), $this->query()->toArray()); + } + + /** + * Detect the request method from various + * options: given method, query string, server vars + * + * @param string $method + * @return string + */ + public function detectRequestMethod(string $method = null): string + { + // all possible methods + $methods = ['GET', 'HEAD', 'POST', 'PUT', 'DELETE', 'CONNECT', 'OPTIONS', 'TRACE', 'PATCH']; + + // the request method can be overwritten with a header + $methodOverride = strtoupper($_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE'] ?? null); + + if ($method === null && in_array($methodOverride, $methods) === true) { + $method = $methodOverride; + } + + // final chain of options to detect the method + $method = $method ?? $_SERVER['REQUEST_METHOD'] ?? 'GET'; + + // uppercase the shit out of it + $method = strtoupper($method); + + // sanitize the method + if (in_array($method, $methods) === false) { + $method = 'GET'; + } + + return $method; + } + + /** + * Returns the domain + * + * @return string + */ + public function domain(): string + { + return $this->url()->domain(); + } + + /** + * Fetches a single file array + * from the Files object by key + * + * @param string $key + * @return array|null + */ + public function file(string $key) + { + return $this->files()->get($key); + } + + /** + * Returns the Files object + * + * @return \Kirby\Cms\Files + */ + public function files() + { + return $this->files = $this->files ?? new Files(); + } + + /** + * Returns any data field from the request + * if it exists + * + * @param string|null|array $key + * @param mixed $fallback + * @return mixed + */ + public function get($key = null, $fallback = null) + { + return A::get($this->data(), $key, $fallback); + } + + /** + * Returns a header by key if it exists + * + * @param string $key + * @param mixed $fallback + * @return mixed + */ + public function header(string $key, $fallback = null) + { + $headers = array_change_key_case($this->headers()); + return $headers[strtolower($key)] ?? $fallback; + } + + /** + * Return all headers with polyfill for + * missing getallheaders function + * + * @return array + */ + public function headers(): array + { + $headers = []; + + foreach ($_SERVER as $key => $value) { + if (substr($key, 0, 5) !== 'HTTP_' && substr($key, 0, 14) !== 'REDIRECT_HTTP_') { + continue; + } + + // remove HTTP_ + $key = str_replace(['REDIRECT_HTTP_', 'HTTP_'], '', $key); + + // convert to lowercase + $key = strtolower($key); + + // replace _ with spaces + $key = str_replace('_', ' ', $key); + + // uppercase first char in each word + $key = ucwords($key); + + // convert spaces to dashes + $key = str_replace(' ', '-', $key); + + $headers[$key] = $value; + } + + return $headers; + } + + /** + * Checks if the given method name + * matches the name of the request method. + * + * @param string $method + * @return bool + */ + public function is(string $method): bool + { + return strtoupper($this->method) === strtoupper($method); + } + + /** + * Returns the request method + * + * @return string + */ + public function method(): string + { + return $this->method; + } + + /** + * Shortcut to the Params object + */ + public function params() + { + return $this->url()->params(); + } + + /** + * Shortcut to the Path object + */ + public function path() + { + return $this->url()->path(); + } + + /** + * Returns the Query object + * + * @return \Kirby\Http\Request\Query + */ + public function query() + { + return $this->query = $this->query ?? new Query(); + } + + /** + * Checks for a valid SSL connection + * + * @return bool + */ + public function ssl(): bool + { + return $this->url()->scheme() === 'https'; + } + + /** + * Returns the current Uri object. + * If you pass props you can safely modify + * the Url with new parameters without destroying + * the original object. + * + * @param array $props + * @return \Kirby\Http\Uri + */ + public function url(array $props = null) + { + if ($props !== null) { + return $this->url()->clone($props); + } + + return $this->url = $this->url ?? Uri::current(); + } +} diff --git a/kirby/src/Http/Request/Auth/BasicAuth.php b/kirby/src/Http/Request/Auth/BasicAuth.php new file mode 100644 index 0000000..4df6e8f --- /dev/null +++ b/kirby/src/Http/Request/Auth/BasicAuth.php @@ -0,0 +1,78 @@ +credentials = base64_decode($token); + $this->username = Str::before($this->credentials, ':'); + $this->password = Str::after($this->credentials, ':'); + } + + /** + * Returns the entire unencoded credentials string + * + * @return string + */ + public function credentials(): string + { + return $this->credentials; + } + + /** + * Returns the password + * + * @return string|null + */ + public function password(): ?string + { + return $this->password; + } + + /** + * Returns the authentication type + * + * @return string + */ + public function type(): string + { + return 'basic'; + } + + /** + * Returns the username + * + * @return string|null + */ + public function username(): ?string + { + return $this->username; + } +} diff --git a/kirby/src/Http/Request/Auth/BearerAuth.php b/kirby/src/Http/Request/Auth/BearerAuth.php new file mode 100644 index 0000000..2c5b1c2 --- /dev/null +++ b/kirby/src/Http/Request/Auth/BearerAuth.php @@ -0,0 +1,54 @@ +token = $token; + } + + /** + * Converts the object to a string + * + * @return string + */ + public function __toString(): string + { + return ucfirst($this->type()) . ' ' . $this->token(); + } + + /** + * Returns the authentication token + * + * @return string + */ + public function token(): string + { + return $this->token; + } + + /** + * Returns the auth type + * + * @return string + */ + public function type(): string + { + return 'bearer'; + } +} diff --git a/kirby/src/Http/Request/Body.php b/kirby/src/Http/Request/Body.php new file mode 100644 index 0000000..f9b6692 --- /dev/null +++ b/kirby/src/Http/Request/Body.php @@ -0,0 +1,129 @@ + + * @link https://getkirby.com + * @copyright Bastian Allgeier GmbH + * @license https://opensource.org/licenses/MIT + */ +class Body +{ + use Data; + + /** + * The raw body content + * + * @var string|array + */ + protected $contents; + + /** + * The parsed content as array + * + * @var array + */ + protected $data; + + /** + * Creates a new request body object. + * You can pass your own array or string. + * If null is being passed, the class will + * fetch the body either from the $_POST global + * or from php://input. + * + * @param array|string|null $contents + */ + public function __construct($contents = null) + { + $this->contents = $contents; + } + + /** + * Fetches the raw contents for the body + * or uses the passed contents. + * + * @return string|array + */ + public function contents() + { + if ($this->contents === null) { + if (empty($_POST) === false) { + $this->contents = $_POST; + } else { + $this->contents = file_get_contents('php://input'); + } + } + + return $this->contents; + } + + /** + * Parses the raw contents once and caches + * the result. The parser will try to convert + * the body with the json decoder first and + * then run parse_str to get some results + * if the json decoder failed. + * + * @return array + */ + public function data(): array + { + if (is_array($this->data) === true) { + return $this->data; + } + + $contents = $this->contents(); + + // return content which is already in array form + if (is_array($contents) === true) { + return $this->data = $contents; + } + + // try to convert the body from json + $json = json_decode($contents, true); + + if (is_array($json) === true) { + return $this->data = $json; + } + + if (strstr($contents, '=') !== false) { + // try to parse the body as query string + parse_str($contents, $parsed); + + if (is_array($parsed)) { + return $this->data = $parsed; + } + } + + return $this->data = []; + } + + /** + * Converts the data array back + * to a http query string + * + * @return string + */ + public function toString(): string + { + return http_build_query($this->data()); + } + + /** + * Magic string converter + * + * @return string + */ + public function __toString(): string + { + return $this->toString(); + } +} diff --git a/kirby/src/Http/Request/Data.php b/kirby/src/Http/Request/Data.php new file mode 100644 index 0000000..0a6bc7f --- /dev/null +++ b/kirby/src/Http/Request/Data.php @@ -0,0 +1,84 @@ + + * @link https://getkirby.com + * @copyright Bastian Allgeier GmbH + * @license https://opensource.org/licenses/MIT + */ +trait Data +{ + /** + * Improved `var_dump` output + * + * @return array + */ + public function __debugInfo(): array + { + return $this->toArray(); + } + + /** + * The data provider method has to be + * implemented by each class using this Trait + * and has to return an associative array + * for the get method + * + * @return array + */ + abstract public function data(): array; + + /** + * The get method is the heart and soul of this + * Trait. You can use it to fetch a single value + * of the data array by key or multiple values by + * passing an array of keys. + * + * @param string|array $key + * @param mixed|null $default + * @return mixed + */ + public function get($key, $default = null) + { + if (is_array($key) === true) { + $result = []; + foreach ($key as $k) { + $result[$k] = $this->get($k); + } + return $result; + } + + return $this->data()[$key] ?? $default; + } + + /** + * Returns the data array. + * This is basically an alias for Data::data() + * + * @return array + */ + public function toArray(): array + { + return $this->data(); + } + + /** + * Converts the data array to json + * + * @return string + */ + public function toJson(): string + { + return json_encode($this->data()); + } +} diff --git a/kirby/src/Http/Request/Files.php b/kirby/src/Http/Request/Files.php new file mode 100644 index 0000000..d5304dd --- /dev/null +++ b/kirby/src/Http/Request/Files.php @@ -0,0 +1,73 @@ + + * @link https://getkirby.com + * @copyright Bastian Allgeier GmbH + * @license https://opensource.org/licenses/MIT + */ +class Files +{ + use Data; + + /** + * Sanitized array of all received files + * + * @var array + */ + protected $files; + + /** + * Creates a new Files object + * Pass your own array to mock + * uploads. + * + * @param array|null $files + */ + public function __construct($files = null) + { + if ($files === null) { + $files = $_FILES; + } + + $this->files = []; + + foreach ($files as $key => $file) { + if (is_array($file['name'])) { + foreach ($file['name'] as $i => $name) { + $this->files[$key][] = [ + 'name' => $file['name'][$i] ?? null, + 'type' => $file['type'][$i] ?? null, + 'tmp_name' => $file['tmp_name'][$i] ?? null, + 'error' => $file['error'][$i] ?? null, + 'size' => $file['size'][$i] ?? null, + ]; + } + } else { + $this->files[$key] = $file; + } + } + } + + /** + * The data method returns the files + * array. This is only needed to make + * the Data trait work for the Files::get($key) + * method. + * + * @return array + */ + public function data(): array + { + return $this->files; + } +} diff --git a/kirby/src/Http/Request/Query.php b/kirby/src/Http/Request/Query.php new file mode 100644 index 0000000..a6101d0 --- /dev/null +++ b/kirby/src/Http/Request/Query.php @@ -0,0 +1,98 @@ + + * @link https://getkirby.com + * @copyright Bastian Allgeier GmbH + * @license https://opensource.org/licenses/MIT + */ +class Query +{ + use Data; + + /** + * The Query data array + * + * @var array|null + */ + protected $data; + + /** + * Creates a new Query object. + * The passed data can be an array + * or a parsable query string. If + * null is passed, the current Query + * will be taken from $_GET + * + * @param array|string|null $data + */ + public function __construct($data = null) + { + if ($data === null) { + $this->data = $_GET; + } elseif (is_array($data)) { + $this->data = $data; + } else { + parse_str($data, $parsed); + $this->data = $parsed; + } + } + + /** + * Returns the Query data as array + * + * @return array + */ + public function data(): array + { + return $this->data; + } + + /** + * Returns `true` if the request doesn't contain query variables + * + * @return bool + */ + public function isEmpty(): bool + { + return empty($this->data) === true; + } + + /** + * Returns `true` if the request contains query variables + * + * @return bool + */ + public function isNotEmpty(): bool + { + return empty($this->data) === false; + } + + /** + * Converts the query data array + * back to a query string + * + * @return string + */ + public function toString(): string + { + return http_build_query($this->data()); + } + + /** + * Magic string converter + * + * @return string + */ + public function __toString(): string + { + return $this->toString(); + } +} diff --git a/kirby/src/Http/Response.php b/kirby/src/Http/Response.php new file mode 100644 index 0000000..0172c6c --- /dev/null +++ b/kirby/src/Http/Response.php @@ -0,0 +1,317 @@ + + * @link https://getkirby.com + * @copyright Bastian Allgeier GmbH + * @license https://opensource.org/licenses/MIT + */ +class Response +{ + /** + * Store for all registered headers, + * which will be sent with the response + * + * @var array + */ + protected $headers = []; + + /** + * The response body + * + * @var string + */ + protected $body; + + /** + * The HTTP response code + * + * @var int + */ + protected $code; + + /** + * The content type for the response + * + * @var string + */ + protected $type; + + /** + * The content type charset + * + * @var string + */ + protected $charset = 'UTF-8'; + + /** + * Creates a new response object + * + * @param string $body + * @param string $type + * @param int $code + * @param array $headers + * @param string $charset + */ + public function __construct($body = '', ?string $type = null, ?int $code = null, ?array $headers = null, ?string $charset = null) + { + // array construction + if (is_array($body) === true) { + $params = $body; + $body = $params['body'] ?? ''; + $type = $params['type'] ?? $type; + $code = $params['code'] ?? $code; + $headers = $params['headers'] ?? $headers; + $charset = $params['charset'] ?? $charset; + } + + // regular construction + $this->body = $body; + $this->type = $type ?? 'text/html'; + $this->code = $code ?? 200; + $this->headers = $headers ?? []; + $this->charset = $charset ?? 'UTF-8'; + + // automatic mime type detection + if (strpos($this->type, '/') === false) { + $this->type = F::extensionToMime($this->type) ?? 'text/html'; + } + } + + /** + * Improved `var_dump` output + * + * @return array + */ + public function __debugInfo(): array + { + return $this->toArray(); + } + + /** + * Makes it possible to convert the + * entire response object to a string + * to send the headers and print the body + * + * @return string + */ + public function __toString(): string + { + try { + return $this->send(); + } catch (Throwable $e) { + return ''; + } + } + + /** + * Getter for the body + * + * @return string + */ + public function body(): string + { + return $this->body; + } + + /** + * Getter for the content type charset + * + * @return string + */ + public function charset(): string + { + return $this->charset; + } + + /** + * Getter for the HTTP status code + * + * @return int + */ + public function code(): int + { + return $this->code; + } + + /** + * Creates a response that triggers + * a file download for the given file + * + * @param string $file + * @param string $filename + * @param array $props Custom overrides for response props (e.g. headers) + * @return static + */ + public static function download(string $file, string $filename = null, array $props = []) + { + if (file_exists($file) === false) { + throw new Exception('The file could not be found'); + } + + $filename = $filename ?? basename($file); + $modified = filemtime($file); + $body = file_get_contents($file); + $size = strlen($body); + + $props = array_replace_recursive([ + 'body' => $body, + 'type' => 'application/force-download', + 'headers' => [ + 'Pragma' => 'public', + 'Cache-Control' => 'no-cache, no-store, must-revalidate', + 'Last-Modified' => gmdate('D, d M Y H:i:s', $modified) . ' GMT', + 'Content-Disposition' => 'attachment; filename="' . $filename . '"', + 'Content-Transfer-Encoding' => 'binary', + 'Content-Length' => $size, + 'Connection' => 'close' + ] + ], $props); + + return new static($props); + } + + /** + * Creates a response for a file and + * sends the file content to the browser + * + * @param string $file + * @param array $props Custom overrides for response props (e.g. headers) + * @return static + */ + public static function file(string $file, array $props = []) + { + $props = array_merge([ + 'body' => F::read($file), + 'type' => F::extensionToMime(F::extension($file)) + ], $props); + + return new static($props); + } + + /** + * Getter for single headers + * + * @param string $key Name of the header + * @return string|null + */ + public function header(string $key): ?string + { + return $this->headers[$key] ?? null; + } + + /** + * Getter for all headers + * + * @return array + */ + public function headers(): array + { + return $this->headers; + } + + /** + * Creates a json response with appropriate + * header and automatic conversion of arrays. + * + * @param string|array $body + * @param int $code + * @param bool $pretty + * @param array $headers + * @return static + */ + public static function json($body = '', ?int $code = null, ?bool $pretty = null, array $headers = []) + { + if (is_array($body) === true) { + $body = json_encode($body, $pretty === true ? JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES : null); + } + + return new static([ + 'body' => $body, + 'code' => $code, + 'type' => 'application/json', + 'headers' => $headers + ]); + } + + /** + * Creates a redirect response, + * which will send the visitor to the + * given location. + * + * @param string $location + * @param int $code + * @return static + */ + public static function redirect(string $location = '/', int $code = 302) + { + return new static([ + 'code' => $code, + 'headers' => [ + 'Location' => Url::unIdn($location) + ] + ]); + } + + /** + * Sends all registered headers and + * returns the response body + * + * @return string + */ + public function send(): string + { + // send the status response code + http_response_code($this->code()); + + // send all custom headers + foreach ($this->headers() as $key => $value) { + header($key . ': ' . $value); + } + + // send the content type header + header('Content-Type:' . $this->type() . '; charset=' . $this->charset()); + + // print the response body + return $this->body(); + } + + /** + * Converts all relevant response attributes + * to an associative array for debugging, + * testing or whatever. + * + * @return array + */ + public function toArray(): array + { + return [ + 'type' => $this->type(), + 'charset' => $this->charset(), + 'code' => $this->code(), + 'headers' => $this->headers(), + 'body' => $this->body() + ]; + } + + /** + * Getter for the content type + * + * @return string + */ + public function type(): string + { + return $this->type; + } +} diff --git a/kirby/src/Http/Route.php b/kirby/src/Http/Route.php new file mode 100644 index 0000000..9b56f36 --- /dev/null +++ b/kirby/src/Http/Route.php @@ -0,0 +1,230 @@ + + * @link https://getkirby.com + * @copyright Bastian Allgeier GmbH + * @license https://opensource.org/licenses/MIT + */ +class Route +{ + /** + * The callback action function + * + * @var Closure + */ + protected $action; + + /** + * Listed of parsed arguments + * + * @var array + */ + protected $arguments = []; + + /** + * An array of all passed attributes + * + * @var array + */ + protected $attributes = []; + + /** + * The registered request method + * + * @var string + */ + protected $method; + + /** + * The registered pattern + * + * @var string + */ + protected $pattern; + + /** + * Wildcards, which can be used in + * Route patterns to make regular expressions + * a little more human + * + * @var array + */ + protected $wildcards = [ + 'required' => [ + '(:num)' => '(-?[0-9]+)', + '(:alpha)' => '([a-zA-Z]+)', + '(:alphanum)' => '([a-zA-Z0-9]+)', + '(:any)' => '([a-zA-Z0-9\.\-_%= \+\@\(\)]+)', + '(:all)' => '(.*)', + ], + 'optional' => [ + '/(:num?)' => '(?:/(-?[0-9]+)', + '/(:alpha?)' => '(?:/([a-zA-Z]+)', + '/(:alphanum?)' => '(?:/([a-zA-Z0-9]+)', + '/(:any?)' => '(?:/([a-zA-Z0-9\.\-_%= \+\@\(\)]+)', + '/(:all?)' => '(?:/(.*)', + ], + ]; + + /** + * Magic getter for route attributes + * + * @param string $key + * @param array $arguments + * @return mixed + */ + public function __call(string $key, array $arguments = null) + { + return $this->attributes[$key] ?? null; + } + + /** + * Creates a new Route object for the given + * pattern(s), method(s) and the callback action + * + * @param string|array $pattern + * @param string|array $method + * @param Closure $action + * @param array $attributes + */ + public function __construct($pattern, $method, Closure $action, array $attributes = []) + { + $this->action = $action; + $this->attributes = $attributes; + $this->method = $method; + $this->pattern = $this->regex(ltrim($pattern, '/')); + } + + /** + * Getter for the action callback + * + * @return Closure + */ + public function action() + { + return $this->action; + } + + /** + * Returns all parsed arguments + * + * @return array + */ + public function arguments(): array + { + return $this->arguments; + } + + /** + * Getter for additional attributes + * + * @return array + */ + public function attributes(): array + { + return $this->attributes; + } + + /** + * Getter for the method + * + * @return string + */ + public function method(): string + { + return $this->method; + } + + /** + * Returns the route name if set + * + * @return string|null + */ + public function name(): ?string + { + return $this->attributes['name'] ?? null; + } + + /** + * Throws a specific exception to tell + * the router to jump to the next route + * @since 3.0.3 + * + * @return void + */ + public static function next(): void + { + throw new Exceptions\NextRouteException('next'); + } + + /** + * Getter for the pattern + * + * @return string + */ + public function pattern(): string + { + return $this->pattern; + } + + /** + * Converts the pattern into a full regular + * expression by replacing all the wildcards + * + * @param string $pattern + * @return string + */ + public function regex(string $pattern): string + { + $search = array_keys($this->wildcards['optional']); + $replace = array_values($this->wildcards['optional']); + + // For optional parameters, first translate the wildcards to their + // regex equivalent, sans the ")?" ending. We'll add the endings + // back on when we know the replacement count. + $pattern = str_replace($search, $replace, $pattern, $count); + + if ($count > 0) { + $pattern .= str_repeat(')?', $count); + } + + return strtr($pattern, $this->wildcards['required']); + } + + /** + * Tries to match the path with the regular expression and + * extracts all arguments for the Route action + * + * @param string $pattern + * @param string $path + * @return array|false + */ + public function parse(string $pattern, string $path) + { + // check for direct matches + if ($pattern === $path) { + return $this->arguments = []; + } + + // We only need to check routes with regular expression since all others + // would have been able to be matched by the search for literal matches + // we just did before we started searching. + if (strpos($pattern, '(') === false) { + return false; + } + + // If we have a match we'll return all results + // from the preg without the full first match. + if (preg_match('#^' . $this->regex($pattern) . '$#u', $path, $parameters)) { + return $this->arguments = array_slice($parameters, 1); + } + + return false; + } +} diff --git a/kirby/src/Http/Router.php b/kirby/src/Http/Router.php new file mode 100644 index 0000000..389d47a --- /dev/null +++ b/kirby/src/Http/Router.php @@ -0,0 +1,169 @@ + + * @link https://getkirby.com + * @copyright Bastian Allgeier GmbH + * @license https://opensource.org/licenses/MIT + */ +class Router +{ + public static $beforeEach; + public static $afterEach; + + /** + * Store for the current route, + * if one can be found + * + * @var Route|null + */ + protected $route; + + /** + * All registered routes, sorted by + * their request method. This makes + * it faster to find the right route + * later. + * + * @var array + */ + protected $routes = [ + 'GET' => [], + 'HEAD' => [], + 'POST' => [], + 'PUT' => [], + 'DELETE' => [], + 'CONNECT' => [], + 'OPTIONS' => [], + 'TRACE' => [], + 'PATCH' => [], + ]; + + /** + * Creates a new router object and + * registers all the given routes + * + * @param array $routes + */ + public function __construct(array $routes = []) + { + foreach ($routes as $props) { + if (isset($props['pattern'], $props['action']) === false) { + throw new InvalidArgumentException('Invalid route parameters'); + } + + $methods = array_map('trim', explode('|', strtoupper($props['method'] ?? 'GET'))); + $patterns = is_array($props['pattern']) === false ? [$props['pattern']] : $props['pattern']; + + if ($methods === ['ALL']) { + $methods = array_keys($this->routes); + } + + foreach ($methods as $method) { + foreach ($patterns as $pattern) { + $this->routes[$method][] = new Route($pattern, $method, $props['action'], $props); + } + } + } + } + + /** + * Calls the Router by path and method. + * This will try to find a Route object + * and then call the Route action with + * the appropriate arguments and a Result + * object. + * + * @param string $path + * @param string $method + * @param Closure|null $callback + * @return mixed + */ + public function call(string $path = null, string $method = 'GET', Closure $callback = null) + { + $path = $path ?? ''; + $ignore = []; + $result = null; + $loop = true; + + while ($loop === true) { + $route = $this->find($path, $method, $ignore); + + if (is_a(static::$beforeEach, 'Closure') === true) { + (static::$beforeEach)($route, $path, $method); + } + + try { + if ($callback) { + $result = $callback($route); + } else { + $result = $route->action()->call($route, ...$route->arguments()); + } + + $loop = false; + } catch (Exceptions\NextRouteException $e) { + $ignore[] = $route; + } + + if (is_a(static::$afterEach, 'Closure') === true) { + $final = $loop === false; + $result = (static::$afterEach)($route, $path, $method, $result, $final); + } + } + + return $result; + } + + /** + * Finds a Route object by path and method + * The Route's arguments method is used to + * find matches and return all the found + * arguments in the path. + * + * @param string $path + * @param string $method + * @param array $ignore + * @return \Kirby\Http\Route|null + */ + public function find(string $path, string $method, array $ignore = null) + { + if (isset($this->routes[$method]) === false) { + throw new InvalidArgumentException('Invalid routing method: ' . $method, 400); + } + + // remove leading and trailing slashes + $path = trim($path, '/'); + + foreach ($this->routes[$method] as $route) { + $arguments = $route->parse($route->pattern(), $path); + + if ($arguments !== false) { + if (empty($ignore) === true || in_array($route, $ignore) === false) { + return $this->route = $route; + } + } + } + + throw new Exception('No route found for path: "' . $path . '" and request method: "' . $method . '"', 404); + } + + /** + * Returns the current route. + * This will only return something, + * once Router::find() has been called + * and only if a route was found. + * + * @return \Kirby\Http\Route|null + */ + public function route() + { + return $this->route; + } +} diff --git a/kirby/src/Http/Server.php b/kirby/src/Http/Server.php new file mode 100644 index 0000000..1ccf919 --- /dev/null +++ b/kirby/src/Http/Server.php @@ -0,0 +1,169 @@ + + * @link https://getkirby.com + * @copyright Bastian Allgeier GmbH + * @license https://opensource.org/licenses/MIT + */ +class Server +{ + /** + * Cache for the cli status + * + * @var bool|null + */ + public static $cli; + + /** + * Returns the server's IP address + * + * @return string + */ + public static function address(): string + { + return static::get('SERVER_ADDR'); + } + + /** + * Checks if the request is being served by the CLI + * + * @return bool + */ + public static function cli(): bool + { + if (static::$cli !== null) { + return static::$cli; + } + + if (defined('STDIN') === true) { + return static::$cli = true; + } + + $term = getenv('TERM'); + + if (substr(PHP_SAPI, 0, 3) === 'cgi' && $term && $term !== 'unknown') { + return static::$cli = true; + } + + return static::$cli = false; + } + + /** + * Gets a value from the _SERVER array + * + * + * Server::get('document_root'); + * // sample output: /var/www/kirby + * + * Server::get(); + * // returns the whole server array + * + * + * @param mixed $key The key to look for. Pass false or null to + * return the entire server array. + * @param mixed $default Optional default value, which should be + * returned if no element has been found + * @return mixed + */ + public static function get($key = null, $default = null) + { + if ($key === null) { + return $_SERVER; + } + + $key = strtoupper($key); + $value = $_SERVER[$key] ?? $default; + return static::sanitize($key, $value); + } + + /** + * Help to sanitize some _SERVER keys + * + * @param string $key + * @param mixed $value + * @return mixed + */ + public static function sanitize(string $key, $value) + { + switch ($key) { + case 'SERVER_ADDR': + case 'SERVER_NAME': + case 'HTTP_HOST': + case 'HTTP_X_FORWARDED_HOST': + $value = strip_tags($value); + $value = preg_replace('![^\w.:-]+!iu', '', $value); + $value = trim($value, '-'); + $value = htmlspecialchars($value); + break; + case 'SERVER_PORT': + case 'HTTP_X_FORWARDED_PORT': + $value = (int)(preg_replace('![^0-9]+!', '', $value)); + break; + } + + return $value; + } + + /** + * Returns the correct port number + * + * @param bool $forwarded + * @return int + */ + public static function port(bool $forwarded = false): int + { + $port = $forwarded === true ? static::get('HTTP_X_FORWARDED_PORT') : null; + + if (empty($port) === true) { + $port = static::get('SERVER_PORT'); + } + + return $port; + } + + /** + * Checks for a https request + * + * @return bool + */ + public static function https(): bool + { + if (isset($_SERVER['HTTPS']) && !empty($_SERVER['HTTPS']) && strtolower($_SERVER['HTTPS']) !== 'off') { + return true; + } elseif (static::port() === 443) { + return true; + } elseif (in_array(static::get('HTTP_X_FORWARDED_PROTO'), ['https', 'https, http'])) { + return true; + } else { + return false; + } + } + + /** + * Returns the correct host + * + * @param bool $forwarded + * @return string + */ + public static function host(bool $forwarded = false): string + { + $host = $forwarded === true ? static::get('HTTP_X_FORWARDED_HOST') : null; + + if (empty($host) === true) { + $host = static::get('SERVER_NAME'); + } + + if (empty($host) === true) { + $host = static::get('SERVER_ADDR'); + } + + return explode(':', $host)[0]; + } +} diff --git a/kirby/src/Http/Uri.php b/kirby/src/Http/Uri.php new file mode 100644 index 0000000..5ca547f --- /dev/null +++ b/kirby/src/Http/Uri.php @@ -0,0 +1,563 @@ + + * @link https://getkirby.com + * @copyright Bastian Allgeier GmbH + * @license https://opensource.org/licenses/MIT + */ +class Uri +{ + use Properties; + + /** + * Cache for the current Uri object + * + * @var Uri|null + */ + public static $current; + + /** + * The fragment after the hash + * + * @var string|false + */ + protected $fragment; + + /** + * The host address + * + * @var string + */ + protected $host; + + /** + * The optional password for basic authentication + * + * @var string|false + */ + protected $password; + + /** + * The optional list of params + * + * @var Params + */ + protected $params; + + /** + * The optional path + * + * @var Path + */ + protected $path; + + /** + * The optional port number + * + * @var int|false + */ + protected $port; + + /** + * All original properties + * + * @var array + */ + protected $props; + + /** + * The optional query string without leading ? + * + * @var Query + */ + protected $query; + + /** + * https or http + * + * @var string + */ + protected $scheme = 'http'; + + /** + * @var bool + */ + protected $slash = false; + + /** + * The optional username for basic authentication + * + * @var string|false + */ + protected $username; + + /** + * Magic caller to access all properties + * + * @param string $property + * @param array $arguments + * @return mixed + */ + public function __call(string $property, array $arguments = []) + { + return $this->$property ?? null; + } + + /** + * Make sure that cloning also clones + * the path and query objects + * + * @return void + */ + public function __clone() + { + $this->path = clone $this->path; + $this->query = clone $this->query; + $this->params = clone $this->params; + } + + /** + * Creates a new URI object + * + * @param array|string $props + * @param array $inject + */ + public function __construct($props = [], array $inject = []) + { + if (is_string($props) === true) { + $props = parse_url($props); + $props['username'] = $props['user'] ?? null; + $props['password'] = $props['pass'] ?? null; + + $props = array_merge($props, $inject); + } + + // parse the path and extract params + if (empty($props['path']) === false) { + $extract = Params::extract($props['path']); + $props['params'] = $props['params'] ?? $extract['params']; + $props['path'] = $extract['path']; + $props['slash'] = $props['slash'] ?? $extract['slash']; + } + + $this->setProperties($this->props = $props); + } + + /** + * Magic getter + * + * @param string $property + * @return mixed + */ + public function __get(string $property) + { + return $this->$property ?? null; + } + + /** + * Magic setter + * + * @param string $property + * @param mixed $value + */ + public function __set(string $property, $value) + { + if (method_exists($this, 'set' . $property) === true) { + $this->{'set' . $property}($value); + } + } + + /** + * Converts the URL object to string + * + * @return string + */ + public function __toString(): string + { + try { + return $this->toString(); + } catch (Throwable $e) { + return ''; + } + } + + /** + * Returns the auth details (username:password) + * + * @return string|null + */ + public function auth(): ?string + { + $auth = trim($this->username . ':' . $this->password); + return $auth !== ':' ? $auth : null; + } + + /** + * Returns the base url (scheme + host) + * without trailing slash + * + * @return string|null + */ + public function base(): ?string + { + if ($domain = $this->domain()) { + return $this->scheme ? $this->scheme . '://' . $domain : $domain; + } + + return null; + } + + /** + * Clones the Uri object and applies optional + * new props. + * + * @param array $props + * @return static + */ + public function clone(array $props = []) + { + $clone = clone $this; + + foreach ($props as $key => $value) { + $clone->__set($key, $value); + } + + return $clone; + } + + /** + * @param array $props + * @param bool $forwarded + * @return static + */ + public static function current(array $props = [], bool $forwarded = false) + { + if (static::$current !== null) { + return static::$current; + } + + $uri = Server::get('REQUEST_URI'); + $uri = preg_replace('!^(http|https)\:\/\/' . Server::get('HTTP_HOST') . '!', '', $uri); + $uri = parse_url('http://getkirby.com' . $uri); + + $url = new static(array_merge([ + 'scheme' => Server::https() === true ? 'https' : 'http', + 'host' => Server::host($forwarded), + 'port' => Server::port($forwarded), + 'path' => $uri['path'] ?? null, + 'query' => $uri['query'] ?? null, + ], $props)); + + return $url; + } + + /** + * Returns the domain without scheme, path or query + * + * @return string|null + */ + public function domain(): ?string + { + if (empty($this->host) === true || $this->host === '/') { + return null; + } + + $auth = $this->auth(); + $domain = ''; + + if ($auth !== null) { + $domain .= $auth . '@'; + } + + $domain .= $this->host; + + if ($this->port !== null && in_array($this->port, [80, 443]) === false) { + $domain .= ':' . $this->port; + } + + return $domain; + } + + /** + * @return bool + */ + public function hasFragment(): bool + { + return empty($this->fragment) === false; + } + + /** + * @return bool + */ + public function hasPath(): bool + { + return $this->path()->isNotEmpty(); + } + + /** + * @return bool + */ + public function hasQuery(): bool + { + return $this->query()->isNotEmpty(); + } + + /** + * Tries to convert the internationalized host + * name to the human-readable UTF8 representation + * + * @return $this + */ + public function idn() + { + if (empty($this->host) === false) { + $this->setHost(Idn::decode($this->host)); + } + return $this; + } + + /** + * Creates an Uri object for the URL to the index.php + * or any other executed script. + * + * @param array $props + * @param bool $forwarded + * @return string + */ + public static function index(array $props = [], bool $forwarded = false) + { + if (Server::cli() === true) { + $path = null; + } else { + $path = Server::get('SCRIPT_NAME'); + // replace Windows backslashes + $path = str_replace('\\', '/', $path); + // remove the script + $path = dirname($path); + // replace those fucking backslashes again + $path = str_replace('\\', '/', $path); + // remove the leading and trailing slashes + $path = trim($path, '/'); + } + + if ($path === '.') { + $path = null; + } + + return static::current(array_merge($props, [ + 'path' => $path, + 'query' => null, + 'fragment' => null, + ]), $forwarded); + } + + + /** + * Checks if the host exists + * + * @return bool + */ + public function isAbsolute(): bool + { + return empty($this->host) === false; + } + + /** + * @param string|null $fragment + * @return $this + */ + public function setFragment(string $fragment = null) + { + $this->fragment = $fragment ? ltrim($fragment, '#') : null; + return $this; + } + + /** + * @param string $host + * @return $this + */ + public function setHost(string $host = null) + { + $this->host = $host; + return $this; + } + + /** + * @param \Kirby\Http\Params|string|array|null $params + * @return $this + */ + public function setParams($params = null) + { + $this->params = is_a($params, 'Kirby\Http\Params') === true ? $params : new Params($params); + return $this; + } + + /** + * @param string|null $password + * @return $this + */ + public function setPassword(string $password = null) + { + $this->password = $password; + return $this; + } + + /** + * @param \Kirby\Http\Path|string|array|null $path + * @return $this + */ + public function setPath($path = null) + { + $this->path = is_a($path, 'Kirby\Http\Path') === true ? $path : new Path($path); + return $this; + } + + /** + * @param int|null $port + * @return $this + */ + public function setPort(int $port = null) + { + if ($port === 0) { + $port = null; + } + + if ($port !== null) { + if ($port < 1 || $port > 65535) { + throw new InvalidArgumentException('Invalid port format: ' . $port); + } + } + + $this->port = $port; + return $this; + } + + /** + * @param \Kirby\Http\Query|string|array|null $query + * @return $this + */ + public function setQuery($query = null) + { + $this->query = is_a($query, 'Kirby\Http\Query') === true ? $query : new Query($query); + return $this; + } + + /** + * @param string $scheme + * @return $this + */ + public function setScheme(string $scheme = null) + { + if ($scheme !== null && in_array($scheme, ['http', 'https', 'ftp']) === false) { + throw new InvalidArgumentException('Invalid URL scheme: ' . $scheme); + } + + $this->scheme = $scheme; + return $this; + } + + /** + * Set if a trailing slash should be added to + * the path when the URI is being built + * + * @param bool $slash + * @return $this + */ + public function setSlash(bool $slash = false) + { + $this->slash = $slash; + return $this; + } + + /** + * @param string|null $username + * @return $this + */ + public function setUsername(string $username = null) + { + $this->username = $username; + return $this; + } + + /** + * Converts the Url object to an array + * + * @return array + */ + public function toArray(): array + { + $array = []; + + foreach ($this->propertyData as $key => $value) { + $value = $this->$key; + + if (is_object($value) === true) { + $value = $value->toArray(); + } + + $array[$key] = $value; + } + + return $array; + } + + public function toJson(...$arguments): string + { + return json_encode($this->toArray(), ...$arguments); + } + + /** + * Returns the full URL as string + * + * @return string + */ + public function toString(): string + { + $url = $this->base(); + $slash = true; + + if (empty($url) === true) { + $url = '/'; + $slash = false; + } + + $path = $this->path->toString($slash) . $this->params->toString(true); + + if ($this->slash && $slash === true) { + $path .= '/'; + } + + $url .= $path; + $url .= $this->query->toString(true); + + if (empty($this->fragment) === false) { + $url .= '#' . $this->fragment; + } + + return $url; + } + + /** + * Tries to convert a URL with an internationalized host + * name to the machine-readable Punycode representation + * + * @return $this + */ + public function unIdn() + { + if (empty($this->host) === false) { + $this->setHost(Idn::encode($this->host)); + } + return $this; + } +} diff --git a/kirby/src/Http/Url.php b/kirby/src/Http/Url.php new file mode 100644 index 0000000..6c182c8 --- /dev/null +++ b/kirby/src/Http/Url.php @@ -0,0 +1,287 @@ + + * @link https://getkirby.com + * @copyright Bastian Allgeier GmbH + * @license https://opensource.org/licenses/MIT + */ +class Url +{ + /** + * The base Url to build absolute Urls from + * + * @var string + */ + public static $home = '/'; + + /** + * The current Uri object + * + * @var Uri + */ + public static $current = null; + + /** + * Facade for all Uri object methods + * + * @param string $method + * @param array $arguments + * @return mixed + */ + public static function __callStatic(string $method, $arguments) + { + return (new Uri($arguments[0] ?? static::current()))->$method(...array_slice($arguments, 1)); + } + + /** + * Url Builder + * Actually just a factory for `new Uri($parts)` + * + * @param array $parts + * @param string|null $url + * @return string + */ + public static function build(array $parts = [], string $url = null): string + { + return (string)(new Uri($url ?? static::current()))->clone($parts); + } + + /** + * Returns the current url with all bells and whistles + * + * @return string + */ + public static function current(): string + { + return static::$current = static::$current ?? static::toObject()->toString(); + } + + /** + * Returns the url for the current directory + * + * @return string + */ + public static function currentDir(): string + { + return dirname(static::current()); + } + + /** + * Tries to fix a broken url without protocol + * + * @param string $url + * @return string + */ + public static function fix(string $url = null): string + { + // make sure to not touch absolute urls + return (!preg_match('!^(https|http|ftp)\:\/\/!i', $url)) ? 'http://' . $url : $url; + } + + /** + * Returns the home url if defined + * + * @return string + */ + public static function home(): string + { + return static::$home; + } + + /** + * Returns the url to the executed script + * + * @param array $props + * @param bool $forwarded + * @return string + */ + public static function index(array $props = [], bool $forwarded = false): string + { + return Uri::index($props, $forwarded)->toString(); + } + + /** + * Checks if an URL is absolute + * + * @param string $url + * @return bool + */ + public static function isAbsolute(string $url = null): bool + { + // matches the following groups of URLs: + // //example.com/uri + // http://example.com/uri, https://example.com/uri, ftp://example.com/uri + // mailto:example@example.com, geo:49.0158,8.3239?z=11 + return preg_match('!^(//|[a-z0-9+-.]+://|mailto:|tel:|geo:)!i', $url) === 1; + } + + /** + * Convert a relative path into an absolute URL + * + * @param string $path + * @param string $home + * @return string + */ + public static function makeAbsolute(string $path = null, string $home = null): string + { + if ($path === '' || $path === '/' || $path === null) { + return $home ?? static::home(); + } + + if (substr($path, 0, 1) === '#') { + return $path; + } + + if (static::isAbsolute($path)) { + return $path; + } + + // build the full url + $path = ltrim($path, '/'); + $home = $home ?? static::home(); + + if (empty($path) === true) { + return $home; + } + + return $home === '/' ? '/' . $path : $home . '/' . $path; + } + + /** + * Returns the path for the given url + * + * @param string|array|null $url + * @param bool $leadingSlash + * @param bool $trailingSlash + * @return string + */ + public static function path($url = null, bool $leadingSlash = false, bool $trailingSlash = false): string + { + return Url::toObject($url)->path()->toString($leadingSlash, $trailingSlash); + } + + /** + * Returns the query for the given url + * + * @param string|array|null $url + * @return string + */ + public static function query($url = null): string + { + return Url::toObject($url)->query()->toString(); + } + + /** + * Return the last url the user has been on if detectable + * + * @return string + */ + public static function last(): string + { + return $_SERVER['HTTP_REFERER'] ?? ''; + } + + /** + * Shortens the Url by removing all unnecessary parts + * + * @param string $url + * @param int $length + * @param bool $base + * @param string $rep + * @return string + */ + public static function short($url = null, int $length = 0, bool $base = false, string $rep = '…'): string + { + $uri = static::toObject($url); + + $uri->fragment = null; + $uri->query = null; + $uri->password = null; + $uri->port = null; + $uri->scheme = null; + $uri->username = null; + + // remove the trailing slash from the path + $uri->slash = false; + + $url = $base ? $uri->base() : $uri->toString(); + $url = str_replace('www.', '', $url); + + return Str::short($url, $length, $rep); + } + + /** + * Removes the path from the Url + * + * @param string $url + * @return string + */ + public static function stripPath($url = null): string + { + return static::toObject($url)->setPath(null)->toString(); + } + + /** + * Removes the query string from the Url + * + * @param string $url + * @return string + */ + public static function stripQuery($url = null): string + { + return static::toObject($url)->setQuery(null)->toString(); + } + + /** + * Removes the fragment (hash) from the Url + * + * @param string $url + * @return string + */ + public static function stripFragment($url = null): string + { + return static::toObject($url)->setFragment(null)->toString(); + } + + /** + * Smart resolver for internal and external urls + * + * @param string $path + * @param mixed $options + * @return string + */ + public static function to(string $path = null, $options = null): string + { + // keep relative urls + if (substr($path, 0, 2) === './' || substr($path, 0, 3) === '../') { + return $path; + } + + $url = static::makeAbsolute($path); + + if ($options === null) { + return $url; + } + + return (new Uri($url, $options))->toString(); + } + + /** + * Converts the Url to a Uri object + * + * @param string $url + * @return \Kirby\Http\Uri + */ + public static function toObject($url = null) + { + return $url === null ? Uri::current() : new Uri($url); + } +} diff --git a/kirby/src/Http/Visitor.php b/kirby/src/Http/Visitor.php new file mode 100644 index 0000000..0a7e8f3 --- /dev/null +++ b/kirby/src/Http/Visitor.php @@ -0,0 +1,252 @@ + + * @link https://getkirby.com + * @copyright Bastian Allgeier GmbH + * @license https://opensource.org/licenses/MIT + */ +class Visitor +{ + /** + * IP address + * @var string|null + */ + protected $ip; + + /** + * user agent + * @var string|null + */ + protected $userAgent; + + /** + * accepted language + * @var string|null + */ + protected $acceptedLanguage; + + /** + * accepted mime type + * @var string|null + */ + protected $acceptedMimeType; + + /** + * Creates a new visitor object. + * Optional arguments can be passed to + * modify the information about the visitor. + * + * By default everything is pulled from $_SERVER + * + * @param array $arguments + */ + public function __construct(array $arguments = []) + { + $this->ip($arguments['ip'] ?? $_SERVER['REMOTE_ADDR'] ?? ''); + $this->userAgent($arguments['userAgent'] ?? $_SERVER['HTTP_USER_AGENT'] ?? ''); + $this->acceptedLanguage($arguments['acceptedLanguage'] ?? $_SERVER['HTTP_ACCEPT_LANGUAGE'] ?? ''); + $this->acceptedMimeType($arguments['acceptedMimeType'] ?? $_SERVER['HTTP_ACCEPT'] ?? ''); + } + + /** + * Sets the accepted language if + * provided or returns the user's + * accepted language otherwise + * + * @param string|null $acceptedLanguage + * @return \Kirby\Toolkit\Obj|\Kirby\Http\Visitor|null + */ + public function acceptedLanguage(string $acceptedLanguage = null) + { + if ($acceptedLanguage === null) { + return $this->acceptedLanguages()->first(); + } + + $this->acceptedLanguage = $acceptedLanguage; + return $this; + } + + /** + * Returns an array of all accepted languages + * including their quality and locale + * + * @return \Kirby\Toolkit\Collection + */ + public function acceptedLanguages() + { + $accepted = Str::accepted($this->acceptedLanguage); + $languages = []; + + foreach ($accepted as $language) { + $value = $language['value']; + $parts = Str::split($value, '-'); + $code = isset($parts[0]) ? Str::lower($parts[0]) : null; + $region = isset($parts[1]) ? Str::upper($parts[1]) : null; + $locale = $region ? $code . '_' . $region : $code; + + $languages[$locale] = new Obj([ + 'code' => $code, + 'locale' => $locale, + 'original' => $value, + 'quality' => $language['quality'], + 'region' => $region, + ]); + } + + return new Collection($languages); + } + + /** + * Checks if the user accepts the given language + * + * @param string $code + * @return bool + */ + public function acceptsLanguage(string $code): bool + { + $mode = Str::contains($code, '_') === true ? 'locale' : 'code'; + + foreach ($this->acceptedLanguages() as $language) { + if ($language->$mode() === $code) { + return true; + } + } + + return false; + } + + /** + * Sets the accepted mime type if + * provided or returns the user's + * accepted mime type otherwise + * + * @param string|null $acceptedMimeType + * @return \Kirby\Toolkit\Obj|\Kirby\Http\Visitor + */ + public function acceptedMimeType(string $acceptedMimeType = null) + { + if ($acceptedMimeType === null) { + return $this->acceptedMimeTypes()->first(); + } + + $this->acceptedMimeType = $acceptedMimeType; + return $this; + } + + /** + * Returns a collection of all accepted mime types + * + * @return \Kirby\Toolkit\Collection + */ + public function acceptedMimeTypes() + { + $accepted = Str::accepted($this->acceptedMimeType); + $mimes = []; + + foreach ($accepted as $mime) { + $mimes[$mime['value']] = new Obj([ + 'type' => $mime['value'], + 'quality' => $mime['quality'], + ]); + } + + return new Collection($mimes); + } + + /** + * Checks if the user accepts the given mime type + * + * @param string $mimeType + * @return bool + */ + public function acceptsMimeType(string $mimeType): bool + { + return Mime::isAccepted($mimeType, $this->acceptedMimeType); + } + + /** + * Returns the MIME type from the provided list that + * is most accepted (= preferred) by the visitor + * @since 3.3.0 + * + * @param string ...$mimeTypes MIME types to query for + * @return string|null Preferred MIME type + */ + public function preferredMimeType(string ...$mimeTypes): ?string + { + foreach ($this->acceptedMimeTypes() as $acceptedMime) { + // look for direct matches + if (in_array($acceptedMime->type(), $mimeTypes)) { + return $acceptedMime->type(); + } + + // test each option against wildcard `Accept` values + foreach ($mimeTypes as $expectedMime) { + if (Mime::matches($expectedMime, $acceptedMime->type()) === true) { + return $expectedMime; + } + } + } + + return null; + } + + /** + * Returns true if the visitor prefers a JSON response over + * an HTML response based on the `Accept` request header + * @since 3.3.0 + * + * @return bool + */ + public function prefersJson(): bool + { + return $this->preferredMimeType('application/json', 'text/html') === 'application/json'; + } + + /** + * Sets the ip address if provided + * or returns the ip of the current + * visitor otherwise + * + * @param string|null $ip + * @return string|Visitor|null + */ + public function ip(string $ip = null) + { + if ($ip === null) { + return $this->ip; + } + $this->ip = $ip; + return $this; + } + + /** + * Sets the user agent if provided + * or returns the user agent string of + * the current visitor otherwise + * + * @param string|null $userAgent + * @return string|Visitor|null + */ + public function userAgent(string $userAgent = null) + { + if ($userAgent === null) { + return $this->userAgent; + } + $this->userAgent = $userAgent; + return $this; + } +} diff --git a/kirby/src/Image/Camera.php b/kirby/src/Image/Camera.php new file mode 100644 index 0000000..59d16d2 --- /dev/null +++ b/kirby/src/Image/Camera.php @@ -0,0 +1,93 @@ + + * @link https://getkirby.com + * @copyright Bastian Allgeier GmbH + * @license https://opensource.org/licenses/MIT + */ +class Camera +{ + /** + * Make exif data + * + * @var string|null + */ + protected $make; + + /** + * Model exif data + * + * @var string|null + */ + protected $model; + + /** + * Constructor + * + * @param array $exif + */ + public function __construct(array $exif) + { + $this->make = $exif['Make'] ?? null; + $this->model = $exif['Model'] ?? null; + } + + /** + * Returns the make of the camera + * + * @return string + */ + public function make(): ?string + { + return $this->make; + } + + /** + * Returns the camera model + * + * @return string + */ + public function model(): ?string + { + return $this->model; + } + + /** + * Converts the object into a nicely readable array + * + * @return array + */ + public function toArray(): array + { + return [ + 'make' => $this->make, + 'model' => $this->model + ]; + } + + /** + * Returns the full make + model name + * + * @return string + */ + public function __toString(): string + { + return trim($this->make . ' ' . $this->model); + } + + /** + * Improved `var_dump` output + * + * @return array + */ + public function __debugInfo(): array + { + return $this->toArray(); + } +} diff --git a/kirby/src/Image/Darkroom.php b/kirby/src/Image/Darkroom.php new file mode 100644 index 0000000..503eddc --- /dev/null +++ b/kirby/src/Image/Darkroom.php @@ -0,0 +1,145 @@ + + * @link https://getkirby.com + * @copyright Bastian Allgeier GmbH + * @license https://opensource.org/licenses/MIT + */ +class Darkroom +{ + public static $types = [ + 'gd' => 'Kirby\Image\Darkroom\GdLib', + 'im' => 'Kirby\Image\Darkroom\ImageMagick' + ]; + + protected $settings = []; + + /** + * Darkroom constructor + * + * @param array $settings + */ + public function __construct(array $settings = []) + { + $this->settings = array_merge($this->defaults(), $settings); + } + + /** + * Creates a new Darkroom instance for the given + * type/driver + * + * @param string $type + * @param array $settings + * @return mixed + * @throws \Exception + */ + public static function factory(string $type, array $settings = []) + { + if (isset(static::$types[$type]) === false) { + throw new Exception('Invalid Darkroom type'); + } + + $class = static::$types[$type]; + return new $class($settings); + } + + /** + * Returns the default thumb settings + * + * @return array + */ + protected function defaults(): array + { + return [ + 'autoOrient' => true, + 'crop' => false, + 'blur' => false, + 'grayscale' => false, + 'height' => null, + 'quality' => 90, + 'width' => null, + ]; + } + + /** + * Normalizes all thumb options + * + * @param array $options + * @return array + */ + protected function options(array $options = []): array + { + $options = array_merge($this->settings, $options); + + // normalize the crop option + if ($options['crop'] === true) { + $options['crop'] = 'center'; + } + + // normalize the blur option + if ($options['blur'] === true) { + $options['blur'] = 10; + } + + // normalize the greyscale option + if (isset($options['greyscale']) === true) { + $options['grayscale'] = $options['greyscale']; + unset($options['greyscale']); + } + + // normalize the bw option + if (isset($options['bw']) === true) { + $options['grayscale'] = $options['bw']; + unset($options['bw']); + } + + if ($options['quality'] === null) { + $options['quality'] = $this->settings['quality']; + } + + return $options; + } + + /** + * Calculates the dimensions of the final thumb based + * on the given options and returns a full array with + * all the final options to be used for the image generator + * + * @param string $file + * @param array $options + * @return array + */ + public function preprocess(string $file, array $options = []) + { + $options = $this->options($options); + $image = new Image($file); + $dimensions = $image->dimensions()->thumb($options); + + $options['width'] = $dimensions->width(); + $options['height'] = $dimensions->height(); + + return $options; + } + + /** + * This method must be replaced by the driver to run the + * actual image processing job. + * + * @param string $file + * @param array $options + * @return array + */ + public function process(string $file, array $options = []): array + { + return $this->preprocess($file, $options); + } +} diff --git a/kirby/src/Image/Darkroom/GdLib.php b/kirby/src/Image/Darkroom/GdLib.php new file mode 100644 index 0000000..eda6118 --- /dev/null +++ b/kirby/src/Image/Darkroom/GdLib.php @@ -0,0 +1,109 @@ + + * @link https://getkirby.com + * @copyright Bastian Allgeier GmbH + * @license https://opensource.org/licenses/MIT + */ +class GdLib extends Darkroom +{ + /** + * Processes the image with the SimpleImage library + * + * @param string $file + * @param array $options + * @return array + */ + public function process(string $file, array $options = []): array + { + $options = $this->preprocess($file, $options); + + $image = new SimpleImage(); + $image->fromFile($file); + + $image = $this->resize($image, $options); + $image = $this->autoOrient($image, $options); + $image = $this->blur($image, $options); + $image = $this->grayscale($image, $options); + + $image->toFile($file, null, $options['quality']); + + return $options; + } + + /** + * Activates the autoOrient option in SimpleImage + * unless this is deactivated + * + * @param \claviska\SimpleImage $image + * @param $options + * @return \claviska\SimpleImage + */ + protected function autoOrient(SimpleImage $image, $options) + { + if ($options['autoOrient'] === false) { + return $image; + } + + return $image->autoOrient(); + } + + /** + * Wrapper around SimpleImage's resize and crop methods + * + * @param \claviska\SimpleImage $image + * @param array $options + * @return \claviska\SimpleImage + */ + protected function resize(SimpleImage $image, array $options) + { + if ($options['crop'] === false) { + return $image->resize($options['width'], $options['height']); + } + + return $image->thumbnail($options['width'], $options['height'] ?? $options['width'], $options['crop']); + } + + /** + * Applies the correct blur settings for SimpleImage + * + * @param \claviska\SimpleImage $image + * @param array $options + * @return \claviska\SimpleImage + */ + protected function blur(SimpleImage $image, array $options) + { + if ($options['blur'] === false) { + return $image; + } + + return $image->blur('gaussian', (int)$options['blur']); + } + + /** + * Applies grayscale conversion if activated in the options. + * + * @param \claviska\SimpleImage $image + * @param array $options + * @return \claviska\SimpleImage + */ + protected function grayscale(SimpleImage $image, array $options) + { + if ($options['grayscale'] === false) { + return $image; + } + + return $image->desaturate(); + } +} diff --git a/kirby/src/Image/Darkroom/ImageMagick.php b/kirby/src/Image/Darkroom/ImageMagick.php new file mode 100644 index 0000000..f27dd4c --- /dev/null +++ b/kirby/src/Image/Darkroom/ImageMagick.php @@ -0,0 +1,228 @@ + + * @link https://getkirby.com + * @copyright Bastian Allgeier GmbH + * @license https://opensource.org/licenses/MIT + */ +class ImageMagick extends Darkroom +{ + /** + * Activates imagemagick's auto-orient feature unless + * it is deactivated via the options + * + * @param string $file + * @param array $options + * @return string + */ + protected function autoOrient(string $file, array $options) + { + if ($options['autoOrient'] === true) { + return '-auto-orient'; + } + } + + /** + * Applies the blur settings + * + * @param string $file + * @param array $options + * @return string + */ + protected function blur(string $file, array $options) + { + if ($options['blur'] !== false) { + return '-blur 0x' . $options['blur']; + } + } + + /** + * Keep animated gifs + * + * @param string $file + * @param array $options + * @return string + */ + protected function coalesce(string $file, array $options) + { + if (F::extension($file) === 'gif') { + return '-coalesce'; + } + } + + /** + * Creates the convert command with the right path to the binary file + * + * @param string $file + * @param array $options + * @return string + */ + protected function convert(string $file, array $options): string + { + return sprintf($options['bin'] . ' "%s"', $file); + } + + /** + * Returns additional default parameters for imagemagick + * + * @return array + */ + protected function defaults(): array + { + return parent::defaults() + [ + 'bin' => 'convert', + 'interlace' => false, + ]; + } + + /** + * Applies the correct settings for grayscale images + * + * @param string $file + * @param array $options + * @return string + */ + protected function grayscale(string $file, array $options) + { + if ($options['grayscale'] === true) { + return '-colorspace gray'; + } + } + + /** + * Applies the correct settings for interlaced JPEGs if + * activated via options + * + * @param string $file + * @param array $options + * @return string + */ + protected function interlace(string $file, array $options) + { + if ($options['interlace'] === true) { + return '-interlace line'; + } + } + + /** + * Creates and runs the full imagemagick command + * to process the image + * + * @param string $file + * @param array $options + * @return array + * @throws \Exception + */ + public function process(string $file, array $options = []): array + { + $options = $this->preprocess($file, $options); + $command = []; + + $command[] = $this->convert($file, $options); + $command[] = $this->strip($file, $options); + $command[] = $this->interlace($file, $options); + $command[] = $this->coalesce($file, $options); + $command[] = $this->grayscale($file, $options); + $command[] = $this->autoOrient($file, $options); + $command[] = $this->resize($file, $options); + $command[] = $this->quality($file, $options); + $command[] = $this->blur($file, $options); + $command[] = $this->save($file, $options); + + // remove all null values and join the parts + $command = implode(' ', array_filter($command)); + + // try to execute the command + exec($command, $output, $return); + + // log broken commands + if ($return !== 0) { + throw new Exception('The imagemagick convert command could not be executed: ' . $command); + } + + return $options; + } + + /** + * Applies the correct JPEG compression quality settings + * + * @param string $file + * @param array $options + * @return string + */ + protected function quality(string $file, array $options): string + { + return '-quality ' . $options['quality']; + } + + /** + * Creates the correct options to crop or resize the image + * and translates the crop positions for imagemagick + * + * @param string $file + * @param array $options + * @return string + */ + protected function resize(string $file, array $options): string + { + // simple resize + if ($options['crop'] === false) { + return sprintf('-resize %sx%s!', $options['width'], $options['height']); + } + + $gravities = [ + 'top left' => 'NorthWest', + 'top' => 'North', + 'top right' => 'NorthEast', + 'left' => 'West', + 'center' => 'Center', + 'right' => 'East', + 'bottom left' => 'SouthWest', + 'bottom' => 'South', + 'bottom right' => 'SouthEast' + ]; + + // translate the gravity option into something imagemagick understands + $gravity = $gravities[$options['crop']] ?? 'Center'; + + $command = sprintf('-resize %sx%s^', $options['width'], $options['height']); + $command .= sprintf(' -gravity %s -crop %sx%s+0+0', $gravity, $options['width'], $options['height']); + + return $command; + } + + /** + * Makes sure to not process too many images at once + * which could crash the server + * + * @param string $file + * @param array $options + * @return string + */ + protected function save(string $file, array $options): string + { + return sprintf('-limit thread 1 "%s"', $file); + } + + /** + * Removes all metadata from the image + * + * @param string $file + * @param array $options + * @return string + */ + protected function strip(string $file, array $options): string + { + return '-strip'; + } +} diff --git a/kirby/src/Image/Dimensions.php b/kirby/src/Image/Dimensions.php new file mode 100644 index 0000000..d4944db --- /dev/null +++ b/kirby/src/Image/Dimensions.php @@ -0,0 +1,430 @@ + + * @link https://getkirby.com + * @copyright Bastian Allgeier GmbH + * @license https://opensource.org/licenses/MIT + */ +class Dimensions +{ + /** + * the height of the parent object + * + * @var int + */ + public $height = 0; + + /** + * the width of the parent object + * + * @var int + */ + public $width = 0; + + /** + * Constructor + * + * @param int $width + * @param int $height + */ + public function __construct(int $width, int $height) + { + $this->width = $width; + $this->height = $height; + } + + /** + * Improved `var_dump` output + * + * @return array + */ + public function __debugInfo(): array + { + return $this->toArray(); + } + + /** + * Echos the dimensions as width × height + * + * @return string + */ + public function __toString(): string + { + return $this->width . ' × ' . $this->height; + } + + /** + * Crops the dimensions by width and height + * + * @param int $width + * @param int|null $height + * @return $this + */ + public function crop(int $width, int $height = null) + { + $this->width = $width; + $this->height = $width; + + if ($height !== 0 && $height !== null) { + $this->height = $height; + } + + return $this; + } + + /** + * Returns the height + * + * @return int + */ + public function height() + { + return $this->height; + } + + /** + * Recalculates the width and height to fit into the given box. + * + * + * + * $dimensions = new Dimensions(1200, 768); + * $dimensions->fit(500); + * + * echo $dimensions->width(); + * // output: 500 + * + * echo $dimensions->height(); + * // output: 320 + * + * + * + * @param int $box the max width and/or height + * @param bool $force If true, the dimensions will be + * upscaled to fit the box if smaller + * @return $this object with recalculated dimensions + */ + public function fit(int $box, bool $force = false) + { + if ($this->width === 0 || $this->height === 0) { + $this->width = $box; + $this->height = $box; + return $this; + } + + $ratio = $this->ratio(); + + if ($this->width > $this->height) { + // wider than tall + if ($this->width > $box || $force === true) { + $this->width = $box; + } + $this->height = (int)round($this->width / $ratio); + } elseif ($this->height > $this->width) { + // taller than wide + if ($this->height > $box || $force === true) { + $this->height = $box; + } + $this->width = (int)round($this->height * $ratio); + } elseif ($this->width > $box) { + // width = height but bigger than box + $this->width = $box; + $this->height = $box; + } + + return $this; + } + + /** + * Recalculates the width and height to fit the given height + * + * + * + * $dimensions = new Dimensions(1200, 768); + * $dimensions->fitHeight(500); + * + * echo $dimensions->width(); + * // output: 781 + * + * echo $dimensions->height(); + * // output: 500 + * + * + * + * @param int|null $fit the max height + * @param bool $force If true, the dimensions will be + * upscaled to fit the box if smaller + * @return $this object with recalculated dimensions + */ + public function fitHeight(int $fit = null, bool $force = false) + { + return $this->fitSize('height', $fit, $force); + } + + /** + * Helper for fitWidth and fitHeight methods + * + * @param string $ref reference (width or height) + * @param int|null $fit the max width + * @param bool $force If true, the dimensions will be + * upscaled to fit the box if smaller + * @return $this object with recalculated dimensions + */ + protected function fitSize(string $ref, int $fit = null, bool $force = false) + { + if ($fit === 0 || $fit === null) { + return $this; + } + + if ($this->$ref <= $fit && !$force) { + return $this; + } + + $ratio = $this->ratio(); + $mode = $ref === 'width'; + $this->width = $mode ? $fit : (int)round($fit * $ratio); + $this->height = !$mode ? $fit : (int)round($fit / $ratio); + + return $this; + } + + /** + * Recalculates the width and height to fit the given width + * + * + * + * $dimensions = new Dimensions(1200, 768); + * $dimensions->fitWidth(500); + * + * echo $dimensions->width(); + * // output: 500 + * + * echo $dimensions->height(); + * // output: 320 + * + * + * + * @param int|null $fit the max width + * @param bool $force If true, the dimensions will be + * upscaled to fit the box if smaller + * @return $this object with recalculated dimensions + */ + public function fitWidth(int $fit = null, bool $force = false) + { + return $this->fitSize('width', $fit, $force); + } + + /** + * Recalculates the dimensions by the width and height + * + * @param int|null $width the max height + * @param int|null $height the max width + * @param bool $force + * @return $this + */ + public function fitWidthAndHeight(int $width = null, int $height = null, bool $force = false) + { + if ($this->width > $this->height) { + $this->fitWidth($width, $force); + + // do another check for the max height + if ($this->height > $height) { + $this->fitHeight($height); + } + } else { + $this->fitHeight($height, $force); + + // do another check for the max width + if ($this->width > $width) { + $this->fitWidth($width); + } + } + + return $this; + } + + /** + * Detect the dimensions for an image file + * + * @param string $root + * @return static + */ + public static function forImage(string $root) + { + if (file_exists($root) === false) { + return new static(0, 0); + } + + $size = getimagesize($root); + return new static($size[0] ?? 0, $size[1] ?? 1); + } + + /** + * Detect the dimensions for a svg file + * + * @param string $root + * @return static + */ + public static function forSvg(string $root) + { + // avoid xml errors + libxml_use_internal_errors(true); + + $content = file_get_contents($root); + $height = 0; + $width = 0; + $xml = simplexml_load_string($content); + + if ($xml !== false) { + $attr = $xml->attributes(); + $width = (float)($attr->width); + $height = (float)($attr->height); + if (($width === 0.0 || $height === 0.0) && empty($attr->viewBox) === false) { + $box = explode(' ', $attr->viewBox); + $width = (float)($box[2] ?? 0); + $height = (float)($box[3] ?? 0); + } + } + + return new static($width, $height); + } + + /** + * Checks if the dimensions are landscape + * + * @return bool + */ + public function landscape(): bool + { + return $this->width > $this->height; + } + + /** + * Returns a string representation of the orientation + * + * @return string|false + */ + public function orientation() + { + if (!$this->ratio()) { + return false; + } + + if ($this->portrait()) { + return 'portrait'; + } + + if ($this->landscape()) { + return 'landscape'; + } + + return 'square'; + } + + /** + * Checks if the dimensions are portrait + * + * @return bool + */ + public function portrait(): bool + { + return $this->height > $this->width; + } + + /** + * Calculates and returns the ratio + * + * + * + * $dimensions = new Dimensions(1200, 768); + * echo $dimensions->ratio(); + * // output: 1.5625 + * + * + * + * @return float + */ + public function ratio(): float + { + if ($this->width !== 0 && $this->height !== 0) { + return $this->width / $this->height; + } + + return 0; + } + + /** + * @param int|null $width + * @param int|null $height + * @param bool $force + * @return $this + */ + public function resize(int $width = null, int $height = null, bool $force = false) + { + return $this->fitWidthAndHeight($width, $height, $force); + } + + /** + * Checks if the dimensions are square + * + * @return bool + */ + public function square(): bool + { + return $this->width === $this->height; + } + + /** + * Resize and crop + * + * @param array $options + * @return $this + */ + public function thumb(array $options = []) + { + $width = $options['width'] ?? null; + $height = $options['height'] ?? null; + $crop = $options['crop'] ?? false; + $method = $crop !== false ? 'crop' : 'resize'; + + if ($width === null && $height === null) { + return $this; + } + + return $this->$method($width, $height); + } + + /** + * Converts the dimensions object + * to a plain PHP array + * + * @return array + */ + public function toArray(): array + { + return [ + 'width' => $this->width(), + 'height' => $this->height(), + 'ratio' => $this->ratio(), + 'orientation' => $this->orientation(), + ]; + } + + /** + * Returns the width + * + * @return int + */ + public function width(): int + { + return $this->width; + } +} diff --git a/kirby/src/Image/Exif.php b/kirby/src/Image/Exif.php new file mode 100644 index 0000000..3452179 --- /dev/null +++ b/kirby/src/Image/Exif.php @@ -0,0 +1,296 @@ + + * @link https://getkirby.com + * @copyright Bastian Allgeier GmbH + * @license https://opensource.org/licenses/MIT + */ +class Exif +{ + /** + * the parent image object + * @var Image + */ + protected $image; + + /** + * the raw exif array + * @var array + */ + protected $data = []; + + /** + * the camera object with model and make + * @var Camera + */ + protected $camera; + + /** + * the location object + * @var Location + */ + protected $location; + + /** + * the timestamp + * + * @var string + */ + protected $timestamp; + + /** + * the exposure value + * + * @var string + */ + protected $exposure; + + /** + * the aperture value + * + * @var string + */ + protected $aperture; + + /** + * iso value + * + * @var string + */ + protected $iso; + + /** + * focal length + * + * @var string + */ + protected $focalLength; + + /** + * color or black/white + * @var bool + */ + protected $isColor; + + /** + * Constructor + * + * @param \Kirby\Image\Image $image + */ + public function __construct(Image $image) + { + $this->image = $image; + $this->data = $this->read(); + $this->parse(); + } + + /** + * Returns the raw data array from the parser + * + * @return array + */ + public function data(): array + { + return $this->data; + } + + /** + * Returns the Camera object + * + * @return \Kirby\Image\Camera|null + */ + public function camera() + { + if ($this->camera !== null) { + return $this->camera; + } + + return $this->camera = new Camera($this->data); + } + + /** + * Returns the location object + * + * @return \Kirby\Image\Location|null + */ + public function location() + { + if ($this->location !== null) { + return $this->location; + } + + return $this->location = new Location($this->data); + } + + /** + * Returns the timestamp + * + * @return string|null + */ + public function timestamp() + { + return $this->timestamp; + } + + /** + * Returns the exposure + * + * @return string|null + */ + public function exposure() + { + return $this->exposure; + } + + /** + * Returns the aperture + * + * @return string|null + */ + public function aperture() + { + return $this->aperture; + } + + /** + * Returns the iso value + * + * @return int|null + */ + public function iso() + { + return $this->iso; + } + + /** + * Checks if this is a color picture + * + * @return bool|null + */ + public function isColor() + { + return $this->isColor; + } + + /** + * Checks if this is a bw picture + * + * @return bool|null + */ + public function isBW(): ?bool + { + return ($this->isColor !== null) ? $this->isColor === false : null; + } + + /** + * Returns the focal length + * + * @return string|null + */ + public function focalLength() + { + return $this->focalLength; + } + + /** + * Read the exif data of the image object if possible + * + * @return mixed + */ + protected function read(): array + { + if (function_exists('exif_read_data') === false) { + return []; + } + + $data = @exif_read_data($this->image->root()); + return is_array($data) ? $data : []; + } + + /** + * Get all computed data + * + * @return array + */ + protected function computed(): array + { + return $this->data['COMPUTED'] ?? []; + } + + /** + * Pareses and stores all relevant exif data + */ + protected function parse() + { + $this->timestamp = $this->parseTimestamp(); + $this->exposure = $this->data['ExposureTime'] ?? null; + $this->iso = $this->data['ISOSpeedRatings'] ?? null; + $this->focalLength = $this->parseFocalLength(); + $this->aperture = $this->computed()['ApertureFNumber'] ?? null; + $this->isColor = V::accepted($this->computed()['IsColor'] ?? null); + } + + /** + * Return the timestamp when the picture has been taken + * + * @return string|int + */ + protected function parseTimestamp() + { + if (isset($this->data['DateTimeOriginal']) === true) { + return strtotime($this->data['DateTimeOriginal']); + } + + return $this->data['FileDateTime'] ?? $this->image->modified(); + } + + /** + * Teturn the focal length + * + * @return string|null + */ + protected function parseFocalLength() + { + return $this->data['FocalLength'] ?? $this->data['FocalLengthIn35mmFilm'] ?? null; + } + + /** + * Converts the object into a nicely readable array + * + * @return array + */ + public function toArray(): array + { + return [ + 'camera' => $this->camera() ? $this->camera()->toArray() : null, + 'location' => $this->location() ? $this->location()->toArray() : null, + 'timestamp' => $this->timestamp(), + 'exposure' => $this->exposure(), + 'aperture' => $this->aperture(), + 'iso' => $this->iso(), + 'focalLength' => $this->focalLength(), + 'isColor' => $this->isColor() + ]; + } + + /** + * Improved `var_dump` output + * + * @return array + */ + public function __debugInfo(): array + { + return array_merge($this->toArray(), [ + 'camera' => $this->camera(), + 'location' => $this->location() + ]); + } +} diff --git a/kirby/src/Image/Image.php b/kirby/src/Image/Image.php new file mode 100644 index 0000000..18d5edc --- /dev/null +++ b/kirby/src/Image/Image.php @@ -0,0 +1,339 @@ + + * @link https://getkirby.com + * @copyright Bastian Allgeier GmbH + * @license https://opensource.org/licenses/MIT + */ +class Image extends File +{ + /** + * optional url where the file is reachable + * @var string + */ + protected $url; + + /** + * @var \Kirby\Image\Exif|null + */ + protected $exif; + + /** + * @var \Kirby\Image\Dimensions|null + */ + protected $dimensions; + + /** + * Constructor + * + * @param string|null $root + * @param string|null $url + */ + public function __construct(string $root = null, string $url = null) + { + parent::__construct($root); + $this->url = $url; + } + + /** + * Improved `var_dump` output + * + * @return array + */ + public function __debugInfo(): array + { + return array_merge($this->toArray(), [ + 'dimensions' => $this->dimensions(), + 'exif' => $this->exif(), + ]); + } + + /** + * Returns a full link to this file + * Perfect for debugging in connection with echo + * + * @return string + */ + public function __toString(): string + { + return $this->root; + } + + /** + * Returns the dimensions of the file if possible + * + * @return \Kirby\Image\Dimensions + */ + public function dimensions() + { + if ($this->dimensions !== null) { + return $this->dimensions; + } + + if (in_array($this->mime(), ['image/jpeg', 'image/jp2', 'image/png', 'image/gif', 'image/webp'])) { + return $this->dimensions = Dimensions::forImage($this->root); + } + + if ($this->extension() === 'svg') { + return $this->dimensions = Dimensions::forSvg($this->root); + } + + return $this->dimensions = new Dimensions(0, 0); + } + + /* + * Automatically sends all needed headers for the file to be downloaded + * and echos the file's content + * + * @param string|null $filename Optional filename for the download + * @return string + */ + public function download($filename = null): string + { + return Response::download($this->root, $filename ?? $this->filename()); + } + + /** + * Returns the exif object for this file (if image) + * + * @return \Kirby\Image\Exif + */ + public function exif() + { + if ($this->exif !== null) { + return $this->exif; + } + $this->exif = new Exif($this); + return $this->exif; + } + + /** + * Sends an appropriate header for the asset + * + * @param bool $send + * @return \Kirby\Http\Response|string + */ + public function header(bool $send = true) + { + $response = new Response('', $this->mime()); + return $send === true ? $response->send() : $response; + } + + /** + * Returns the height of the asset + * + * @return int + */ + public function height(): int + { + return $this->dimensions()->height(); + } + + /** + * @param array $attr + * @return string + */ + public function html(array $attr = []): string + { + return Html::img($this->url(), $attr); + } + + /** + * Returns the PHP imagesize array + * + * @return array + */ + public function imagesize(): array + { + return getimagesize($this->root); + } + + /** + * Checks if the dimensions of the asset are portrait + * + * @return bool + */ + public function isPortrait(): bool + { + return $this->dimensions()->portrait(); + } + + /** + * Checks if the dimensions of the asset are landscape + * + * @return bool + */ + public function isLandscape(): bool + { + return $this->dimensions()->landscape(); + } + + /** + * Checks if the dimensions of the asset are square + * + * @return bool + */ + public function isSquare(): bool + { + return $this->dimensions()->square(); + } + + /** + * Runs a set of validations on the image object + * + * @param array $rules + * @return bool + * @throws \Exception + */ + public function match(array $rules): bool + { + $rules = array_change_key_case($rules); + + if (is_array($rules['mime'] ?? null) === true) { + $mime = $this->mime(); + + // determine if any pattern matches the MIME type; + // once any pattern matches, `$carry` is `true` and the rest is skipped + $matches = array_reduce($rules['mime'], function ($carry, $pattern) use ($mime) { + return $carry || Mime::matches($mime, $pattern); + }, false); + + if ($matches !== true) { + throw new Exception([ + 'key' => 'file.mime.invalid', + 'data' => compact('mime') + ]); + } + } + + if (is_array($rules['extension'] ?? null) === true) { + $extension = $this->extension(); + if (in_array($extension, $rules['extension']) !== true) { + throw new Exception([ + 'key' => 'file.extension.invalid', + 'data' => compact('extension') + ]); + } + } + + if (is_array($rules['type'] ?? null) === true) { + $type = $this->type(); + if (in_array($type, $rules['type']) !== true) { + throw new Exception([ + 'key' => 'file.type.invalid', + 'data' => compact('type') + ]); + } + } + + $validations = [ + 'maxsize' => ['size', 'max'], + 'minsize' => ['size', 'min'], + 'maxwidth' => ['width', 'max'], + 'minwidth' => ['width', 'min'], + 'maxheight' => ['height', 'max'], + 'minheight' => ['height', 'min'], + 'orientation' => ['orientation', 'same'] + ]; + + foreach ($validations as $key => $arguments) { + $rule = $rules[$key] ?? null; + + if ($rule !== null) { + $property = $arguments[0]; + $validator = $arguments[1]; + + if (V::$validator($this->$property(), $rule) === false) { + throw new Exception([ + 'key' => 'file.' . $key, + 'data' => [$property => $rule] + ]); + } + } + } + + return true; + } + + /** + * Returns the ratio of the asset + * + * @return float + */ + public function ratio(): float + { + return $this->dimensions()->ratio(); + } + + /** + * Returns the orientation as string + * landscape | portrait | square + * + * @return string + */ + public function orientation(): string + { + return $this->dimensions()->orientation(); + } + + /** + * Converts the media object to a + * plain PHP array + * + * @return array + */ + public function toArray(): array + { + return array_merge(parent::toArray(), [ + 'dimensions' => $this->dimensions()->toArray(), + 'exif' => $this->exif()->toArray(), + ]); + } + + /** + * Converts the entire file array into + * a json string + * + * @return string + */ + public function toJson(): string + { + return json_encode($this->toArray()); + } + + /** + * Returns the url + * + * @return string + */ + public function url() + { + return $this->url; + } + + /** + * Returns the width of the asset + * + * @return int + */ + public function width(): int + { + return $this->dimensions()->width(); + } +} diff --git a/kirby/src/Image/Location.php b/kirby/src/Image/Location.php new file mode 100644 index 0000000..8c864b6 --- /dev/null +++ b/kirby/src/Image/Location.php @@ -0,0 +1,136 @@ + + * @link https://getkirby.com + * @copyright Bastian Allgeier GmbH + * @license https://opensource.org/licenses/MIT + */ +class Location +{ + /** + * latitude + * + * @var float|null + */ + protected $lat; + + /** + * longitude + * + * @var float|null + */ + protected $lng; + + /** + * Constructor + * + * @param array $exif The entire exif array + */ + public function __construct(array $exif) + { + if (isset($exif['GPSLatitude']) === true && + isset($exif['GPSLatitudeRef']) === true && + isset($exif['GPSLongitude']) === true && + isset($exif['GPSLongitudeRef']) === true + ) { + $this->lat = $this->gps($exif['GPSLatitude'], $exif['GPSLatitudeRef']); + $this->lng = $this->gps($exif['GPSLongitude'], $exif['GPSLongitudeRef']); + } + } + + /** + * Returns the latitude + * + * @return float|null + */ + public function lat() + { + return $this->lat; + } + + /** + * Returns the longitude + * + * @return float|null + */ + public function lng() + { + return $this->lng; + } + + /** + * Converts the gps coordinates + * + * @param string|array $coord + * @param string $hemi + * @return float + */ + protected function gps($coord, string $hemi): float + { + $degrees = count($coord) > 0 ? $this->num($coord[0]) : 0; + $minutes = count($coord) > 1 ? $this->num($coord[1]) : 0; + $seconds = count($coord) > 2 ? $this->num($coord[2]) : 0; + + $hemi = strtoupper($hemi); + $flip = ($hemi === 'W' || $hemi === 'S') ? -1 : 1; + + return $flip * ($degrees + $minutes / 60 + $seconds / 3600); + } + + /** + * Converts coordinates to floats + * + * @param string $part + * @return float + */ + protected function num(string $part): float + { + $parts = explode('/', $part); + + if (count($parts) === 1) { + return (float)$parts[0]; + } + + return (float)($parts[0]) / (float)($parts[1]); + } + + /** + * Converts the object into a nicely readable array + * + * @return array + */ + public function toArray(): array + { + return [ + 'lat' => $this->lat(), + 'lng' => $this->lng() + ]; + } + + /** + * Echos the entire location as lat, lng + * + * @return string + */ + public function __toString(): string + { + return trim(trim($this->lat() . ', ' . $this->lng(), ',')); + } + + /** + * Improved `var_dump` output + * + * @return array + */ + public function __debugInfo(): array + { + return $this->toArray(); + } +} diff --git a/kirby/src/Parsley/Element.php b/kirby/src/Parsley/Element.php new file mode 100644 index 0000000..5265b6f --- /dev/null +++ b/kirby/src/Parsley/Element.php @@ -0,0 +1,100 @@ +marks = $marks; + $this->node = $node; + } + + public function attr(string $attr, $fallback = null) + { + if ($this->node->hasAttribute($attr)) { + return $this->node->getAttribute($attr) ?? $fallback; + } + + return $fallback; + } + + public function children() + { + return $this->node->childNodes; + } + + public function classList(): array + { + return Str::split($this->className(), ' '); + } + + public function className() + { + return $this->node->getAttribute('class'); + } + + public function element() + { + return $this->node; + } + + public function filter(string $query) + { + $result = []; + + if ($queryResult = $this->query($query)) { + foreach ($queryResult as $node) { + $result[] = new static($node); + } + } + + return $result; + } + + public function find(string $query) + { + if ($result = $this->query($query)[0]) { + return new static($result); + } + + return false; + } + + public function innerHtml(array $marks = null): string + { + return (new Inline($this->node, $marks ?? $this->marks))->innerHtml(); + } + + public function innerText() + { + return trim($this->node->textContent); + } + + public function outerHtml(array $marks = null): string + { + return $this->node->ownerDocument->saveHtml($this->node); + } + + public function query($query) + { + return (new DOMXPath($this->node->ownerDocument))->query($query, $this->node); + } + + public function remove() + { + $this->node->parentNode->removeChild($this->node); + } + + public function tagName(): string + { + return $this->node->tagName; + } +} diff --git a/kirby/src/Parsley/Inline.php b/kirby/src/Parsley/Inline.php new file mode 100644 index 0000000..96784d4 --- /dev/null +++ b/kirby/src/Parsley/Inline.php @@ -0,0 +1,72 @@ +createMarkRules($marks); + $this->html = trim($this->parseNode($node)); + } + + public function createMarkRules($marks) + { + foreach ($marks as $mark) { + $this->marks[$mark['tag']] = $mark; + } + } + + public function parseChildren($children): string + { + if (!$children) { + return ''; + } + + $html = ''; + foreach ($children as $child) { + $html .= $this->parseNode($child); + } + return $html; + } + + public function parseNode($node) + { + if (is_a($node, 'DOMText') === true) { + return $node->textContent; + } + + // ignore comments + if (is_a($node, 'DOMComment') === true) { + return ''; + } + + // known marks + if (array_key_exists($node->tagName, $this->marks) === true) { + $mark = $this->marks[$node->tagName]; + $attrs = []; + $defaults = $mark['defaults'] ?? []; + + foreach ($mark['attrs'] ?? [] as $attr) { + if ($node->hasAttribute($attr)) { + $attrs[$attr] = $node->getAttribute($attr); + } else { + $attrs[$attr] = $defaults[$attr] ?? null; + } + } + + return '<' . $node->tagName . attr($attrs, ' ') . '>' . $this->parseChildren($node->childNodes) . 'tagName . '>'; + } + + // unknown marks + return $this->parseChildren($node->childNodes); + } + + public function innerHtml() + { + return $this->html; + } +} diff --git a/kirby/src/Parsley/Parsley.php b/kirby/src/Parsley/Parsley.php new file mode 100644 index 0000000..9af2580 --- /dev/null +++ b/kirby/src/Parsley/Parsley.php @@ -0,0 +1,232 @@ +useXmlExtension() === false) { + $this->blocks[] = [ + 'type' => 'markdown', + 'content' => [ + 'text' => $html, + ] + ]; + return; + } + + libxml_use_internal_errors(true); + + $this->doc = new DOMDocument(); + $this->doc->preserveWhiteSpace = false; + $this->doc->loadHTML(mb_convert_encoding($html, 'HTML-ENTITIES', 'UTF-8')); + + libxml_clear_errors(); + + $this->schema = $schema ?? new Plain(); + $this->skip = $this->schema->skip(); + $this->marks = $this->schema->marks(); + $this->inline = []; + + $this->createNodeRules($this->schema->nodes()); + + $this->parseNode($this->body()); + $this->endInlineBlock(); + } + + public function blocks(): array + { + return $this->blocks; + } + + public function body() + { + return $this->body = $this->body ?? $this->query($this->doc, '/html/body')[0]; + } + + public function createNodeRules($nodes) + { + foreach ($nodes as $node) { + $this->nodes[$node['tag']] = $node; + } + } + + public function containsBlock($element): bool + { + if (!$element->childNodes) { + return false; + } + + foreach ($element->childNodes as $childNode) { + if ($this->isBlock($childNode) === true || $this->containsBlock($childNode)) { + return true; + } + } + + return false; + } + + public function endInlineBlock() + { + $html = []; + + foreach ($this->inline as $inline) { + $node = new Inline($inline, $this->marks); + $html[] = $node->innerHTML(); + } + + $innerHTML = implode(' ', $html); + + if ($fallback = $this->fallback($innerHTML)) { + $this->mergeOrAppend($fallback); + } + + $this->inline = []; + } + + public function fallback($node) + { + if (is_a($node, 'DOMText') === true) { + $html = $node->textContent; + } elseif (is_a($node, Element::class) === true) { + $html = $node->innerHtml(); + } elseif (is_string($node) === true) { + $html = $node; + } else { + $html = ''; + } + + if ($fallback = $this->schema->fallback($html)) { + return $fallback; + } + + return false; + } + + public function isBlock($element): bool + { + if (is_a($element, 'DOMElement') === false) { + return false; + } + + return array_key_exists($element->tagName, $this->nodes) === true; + } + + public function isInline($element) + { + if (is_a($element, 'DOMText') === true) { + return true; + } + + if (is_a($element, 'DOMElement') === true) { + if ($this->containsBlock($element) === true) { + return false; + } + + if ($element->tagName === 'p') { + return false; + } + + $marks = array_column($this->marks, 'tag'); + return in_array($element->tagName, $marks); + } + + return false; + } + + public function mergeOrAppend($block) + { + $lastIndex = count($this->blocks) - 1; + $lastItem = $this->blocks[$lastIndex] ?? null; + + // merge with previous block + if ($block['type'] === 'text' && $lastItem && $lastItem['type'] === 'text') { + $this->blocks[$lastIndex]['content']['text'] .= "\n\n" . $block['content']['text']; + + // append + } else { + $this->blocks[] = $block; + } + } + + public function parseNode($element) + { + // comments + if (is_a($element, 'DOMComment') === true) { + return true; + } + + + // inline context + if ($this->isInline($element)) { + $this->inline[] = $element; + return true; + } else { + $this->endInlineBlock(); + } + + // known block nodes + if ($this->isBlock($element) === true) { + if ($parser = ($this->nodes[$element->tagName]['parse'] ?? null)) { + if ($result = $parser(new Element($element, $this->marks))) { + $this->blocks[] = $result; + } + } + return true; + } + + // has only unkown children (div, etc.) + if ($this->containsBlock($element) === false) { + if (in_array($element->tagName, $this->skip) === true) { + return true; + } + + if ($element->tagName !== 'body') { + $node = new Element($element, $this->marks); + + if ($block = $this->fallback($node)) { + $this->mergeOrAppend($block); + } + + return true; + } + } + + // parse all children + foreach ($element->childNodes as $childNode) { + $this->parseNode($childNode); + } + } + + public function query($element, $query) + { + return (new DOMXPath($element))->query($query); + } + + public function useXmlExtension(): bool + { + if (static::$useXmlExtension !== true) { + return false; + } + + return class_exists('DOMDocument') === true; + } +} diff --git a/kirby/src/Parsley/Schema.php b/kirby/src/Parsley/Schema.php new file mode 100644 index 0000000..d6ff231 --- /dev/null +++ b/kirby/src/Parsley/Schema.php @@ -0,0 +1,11 @@ + [ + 'text' => '

    ' . $html . '

    ', + ], + 'type' => 'text', + ]; + } + + public function heading($node, $level) + { + $content = [ + 'level' => $level, + 'text' => $node->innerHTML() + ]; + + if ($id = $node->attr('id')) { + $content['id'] = $id; + } + + ksort($content); + + return [ + 'content' => $content, + 'type' => 'heading', + ]; + } + + public function list($node) + { + $html = []; + + foreach ($node->filter('li') as $li) { + $innerHtml = ''; + + foreach ($li->children() as $child) { + if (is_a($child, 'DOMText') === true) { + $innerHtml .= $child->textContent; + } elseif (is_a($child, 'DOMElement') === true) { + $child = new Element($child); + + if (in_array($child->tagName(), ['ul', 'ol']) === true) { + $innerHtml .= $this->list($child); + } else { + $innerHtml .= $child->innerHTML($this->marks()); + } + } + } + + $html[] = '
  • ' . trim($innerHtml) . '
  • '; + } + + return '<' . $node->tagName() . '>' . implode($html) . 'tagName() . '>'; + } + + public function marks(): array + { + return [ + [ + 'tag' => 'a', + 'attrs' => ['href', 'target', 'title'], + ], + [ + 'tag' => 'abbr', + ], + [ + 'tag' => 'b' + ], + [ + 'tag' => 'code' + ], + [ + 'tag' => 'del', + ], + [ + 'tag' => 'em', + ], + [ + 'tag' => 'i', + ], + [ + 'tag' => 'strike', + ], + [ + 'tag' => 'sub', + ], + [ + 'tag' => 'sup', + ], + [ + 'tag' => 'strong', + ], + [ + 'tag' => 'u', + ], + ]; + } + + public function nodes(): array + { + return [ + [ + 'tag' => 'blockquote', + 'parse' => function ($node) { + $citation = null; + $text = []; + + // get all the text for the quote + foreach ($node->element()->childNodes as $child) { + if (is_a($child, 'DOMText') === true) { + $text[] = trim($child->textContent); + } + if (is_a($child, 'DOMElement') === true && $child->tagName !== 'footer') { + $text[] = (new Element($child))->innerHTML($this->marks()); + } + } + + // filter empty blocks and separate text blocks with breaks + $text = implode('

    ', array_filter($text)); + + // get the citation from the footer + if ($footer = $node->find('footer')) { + $citation = $footer->innerHTML($this->marks()); + } + + return [ + 'content' => [ + 'citation' => $citation, + 'text' => $text + ], + 'type' => 'quote', + ]; + } + ], + [ + 'tag' => 'h1', + 'parse' => function ($node) { + return $this->heading($node, 'h1'); + } + ], + [ + 'tag' => 'h2', + 'parse' => function ($node) { + return $this->heading($node, 'h2'); + } + ], + [ + 'tag' => 'h3', + 'parse' => function ($node) { + return $this->heading($node, 'h3'); + } + ], + [ + 'tag' => 'h4', + 'parse' => function ($node) { + return $this->heading($node, 'h4'); + } + ], + [ + 'tag' => 'h5', + 'parse' => function ($node) { + return $this->heading($node, 'h5'); + } + ], + [ + 'tag' => 'h6', + 'parse' => function ($node) { + return $this->heading($node, 'h6'); + } + ], + [ + 'tag' => 'iframe', + 'parse' => function ($node) { + $caption = null; + $src = $node->attr('src'); + + if ($figcaption = $node->find('ancestor::figure[1]//figcaption')) { + $caption = $figcaption->innerHTML($this->marks()); + + // avoid parsing the caption twice + $figcaption->remove(); + } + + // reverse engineer video URLs + if (preg_match('!player.vimeo.com\/video\/([0-9]+)!i', $src, $array) === 1) { + $src = 'https://vimeo.com/' . $array[1]; + } elseif (preg_match('!youtube.com\/embed\/([a-zA-Z0-9_-]+)!', $src, $array) === 1) { + $src = 'https://youtube.com/watch?v=' . $array[1]; + } elseif (preg_match('!youtube-nocookie.com\/embed\/([a-zA-Z0-9_-]+)!', $src, $array) === 1) { + $src = 'https://youtube.com/watch?v=' . $array[1]; + } else { + $src = false; + } + + // correct video URL + if ($src) { + return [ + 'content' => [ + 'caption' => $caption, + 'url' => $src + ], + 'type' => 'video', + ]; + } + + return [ + 'content' => [ + 'text' => $node->outerHTML() + ], + 'type' => 'markdown', + ]; + } + ], + [ + 'tag' => 'img', + 'parse' => function ($node) { + $caption = null; + $link = null; + + if ($figcaption = $node->find('ancestor::figure[1]//figcaption')) { + $caption = $figcaption->innerHTML($this->marks()); + + // avoid parsing the caption twice + $figcaption->remove(); + } + + if ($a = $node->find('ancestor::a')) { + $link = $a->attr('href'); + } + + return [ + 'content' => [ + 'alt' => $node->attr('alt'), + 'caption' => $caption, + 'link' => $link, + 'location' => 'web', + 'src' => $node->attr('src'), + ], + 'type' => 'image', + ]; + } + ], + [ + 'tag' => 'ol', + 'parse' => function ($node) { + return [ + 'content' => [ + 'text' => $this->list($node) + ], + 'type' => 'list', + ]; + } + ], + [ + 'tag' => 'pre', + 'parse' => function ($node) { + $language = 'text'; + + if ($code = $node->find('//code')) { + foreach ($code->classList() as $className) { + if (preg_match('!language-(.*?)!', $className)) { + $language = str_replace('language-', '', $className); + break; + } + } + } + + return [ + 'content' => [ + 'code' => $node->innerText(), + 'language' => $language + ], + 'type' => 'code', + ]; + } + ], + [ + 'tag' => 'table', + 'parse' => function ($node) { + return [ + 'content' => [ + 'text' => $node->outerHTML(), + ], + 'type' => 'markdown', + ]; + } + ], + [ + 'tag' => 'ul', + 'parse' => function ($node) { + return [ + 'content' => [ + 'text' => $this->list($node) + ], + 'type' => 'list', + ]; + } + ], + ]; + } +} diff --git a/kirby/src/Parsley/Schema/Plain.php b/kirby/src/Parsley/Schema/Plain.php new file mode 100644 index 0000000..c2408e4 --- /dev/null +++ b/kirby/src/Parsley/Schema/Plain.php @@ -0,0 +1,40 @@ + 'text', + 'content' => [ + 'text' => $text + ] + ]; + } + + public function marks(): array + { + return []; + } + + public function nodes(): array + { + return []; + } + + public function skip(): array + { + return ['meta', 'script', 'style']; + } +} diff --git a/kirby/src/Sane/Handler.php b/kirby/src/Sane/Handler.php new file mode 100644 index 0000000..df06400 --- /dev/null +++ b/kirby/src/Sane/Handler.php @@ -0,0 +1,51 @@ + + * @link https://getkirby.com + * @copyright Bastian Allgeier GmbH + * @license https://opensource.org/licenses/MIT + */ +abstract class Handler +{ + /** + * Validates file contents + * + * @param string $string + * @return void + * + * @throws \Kirby\Exception\InvalidArgumentException If the file didn't pass validation + * @throws \Kirby\Exception\Exception On other errors + */ + abstract public static function validate(string $string): void; + + /** + * Validates the contents of a file + * + * @param string $file + * @return void + * + * @throws \Kirby\Exception\InvalidArgumentException If the file didn't pass validation + * @throws \Kirby\Exception\Exception On other errors + */ + public static function validateFile(string $file): void + { + $contents = F::read($file); + if ($contents === false) { + throw new Exception('The file "' . $file . '" does not exist'); + } + + static::validate($contents); + } +} diff --git a/kirby/src/Sane/Sane.php b/kirby/src/Sane/Sane.php new file mode 100644 index 0000000..a4d2116 --- /dev/null +++ b/kirby/src/Sane/Sane.php @@ -0,0 +1,129 @@ + + * @link https://getkirby.com + * @copyright Bastian Allgeier GmbH + * @license https://opensource.org/licenses/MIT + */ +class Sane +{ + /** + * Handler Type Aliases + * + * @var array + */ + public static $aliases = [ + 'image/svg+xml' => 'svg', + 'application/xml' => 'xml', + 'text/xml' => 'xml', + ]; + + /** + * All registered handlers + * + * @var array + */ + public static $handlers = [ + 'svg' => 'Kirby\Sane\Svg', + 'svgz' => 'Kirby\Sane\Svgz', + 'xml' => 'Kirby\Sane\Xml', + ]; + + /** + * Handler getter + * + * @param string $type + * @param bool $lazy If set to `true`, `null` is returned for undefined handlers + * @return \Kirby\Sane\Handler|null + * + * @throws \Kirby\Exception\NotFoundException If no handler was found and `$lazy` was set to `false` + */ + public static function handler(string $type, bool $lazy = false) + { + // normalize the type + $type = mb_strtolower($type); + + // find a handler or alias + $handler = static::$handlers[$type] ?? + static::$handlers[static::$aliases[$type] ?? null] ?? + null; + + if (empty($handler) === false && class_exists($handler) === true) { + return new $handler(); + } + + if ($lazy === true) { + return null; + } + + throw new NotFoundException('Missing handler for type: "' . $type . '"'); + } + + /** + * Validates file contents with the specified handler + * + * @param mixed $string + * @param string $type + * @return void + * + * @throws \Kirby\Exception\InvalidArgumentException If the file didn't pass validation + * @throws \Kirby\Exception\NotFoundException If the handler was not found + * @throws \Kirby\Exception\Exception On other errors + */ + public static function validate(string $string, string $type): void + { + static::handler($type)->validate($string); + } + + /** + * Validates the contents of a file; + * the sane handlers are automatically chosen by + * the extension and MIME type if not specified + * + * @param string $file + * @param string|bool $typeLazy Explicit handler type string, + * `true` for lazy autodetection or + * `false` for normal autodetection + * @return void + * + * @throws \Kirby\Exception\InvalidArgumentException If the file didn't pass validation + * @throws \Kirby\Exception\NotFoundException If the handler was not found + * @throws \Kirby\Exception\Exception On other errors + */ + public static function validateFile(string $file, $typeLazy = false): void + { + if (is_string($typeLazy) === true) { + static::handler($typeLazy)->validateFile($file); + return; + } + + $options = [F::extension($file), F::mime($file)]; + + // execute all handlers, but each class only once for performance; + // filter out all empty options + $usedHandlers = []; + foreach (array_filter($options) as $option) { + $handler = static::handler($option, $typeLazy === true); + $handlerClass = $handler ? get_class($handler) : null; + + if ($handler && in_array($handlerClass, $usedHandlers) === false) { + $handler->validateFile($file); + + $usedHandlers[] = $handlerClass; + } + } + } +} diff --git a/kirby/src/Sane/Svg.php b/kirby/src/Sane/Svg.php new file mode 100644 index 0000000..d3a30f7 --- /dev/null +++ b/kirby/src/Sane/Svg.php @@ -0,0 +1,486 @@ +, + * Lukas Bestle + * @link https://getkirby.com + * @copyright Bastian Allgeier GmbH + * @license https://opensource.org/licenses/MIT + */ +class Svg extends Xml +{ + /** + * Allow and block lists are inspired by DOMPurify + * + * @link https://github.com/cure53/DOMPurify + * @copyright 2015 Mario Heiderich + * @license https://www.apache.org/licenses/LICENSE-2.0 + */ + public static $allowedAttributes = [ + 'accent-height', + 'accumulate', + 'additive', + 'alignment-baseline', + 'ascent', + 'attributeName', + 'attributeType', + 'azimuth', + 'baseFrequency', + 'baseline-shift', + 'begin', + 'bias', + 'by', + 'class', + 'clip', + 'clipPathUnits', + 'clip-path', + 'clip-rule', + 'color', + 'color-interpolation', + 'color-interpolation-filters', + 'color-profile', + 'color-rendering', + 'cx', + 'cy', + 'd', + 'dx', + 'dy', + 'diffuseConstant', + 'direction', + 'display', + 'divisor', + 'dur', + 'edgeMode', + 'elevation', + 'end', + 'fill', + 'fill-opacity', + 'fill-rule', + 'filter', + 'filterUnits', + 'flood-color', + 'flood-opacity', + 'font-family', + 'font-size', + 'font-size-adjust', + 'font-stretch', + 'font-style', + 'font-variant', + 'font-weight', + 'fx', + 'fy', + 'g1', + 'g2', + 'glyph-name', + 'glyphRef', + 'gradientUnits', + 'gradientTransform', + 'height', + 'href', + 'id', + 'image-rendering', + 'in', + 'in2', + 'k', + 'k1', + 'k2', + 'k3', + 'k4', + 'kerning', + 'keyPoints', + 'keySplines', + 'keyTimes', + 'lang', + 'lengthAdjust', + 'letter-spacing', + 'kernelMatrix', + 'kernelUnitLength', + 'lighting-color', + 'local', + 'marker-end', + 'marker-mid', + 'marker-start', + 'markerHeight', + 'markerUnits', + 'markerWidth', + 'maskContentUnits', + 'maskUnits', + 'max', + 'mask', + 'media', + 'method', + 'mode', + 'min', + 'name', + 'numOctaves', + 'offset', + 'operator', + 'opacity', + 'order', + 'orient', + 'orientation', + 'origin', + 'overflow', + 'paint-order', + 'path', + 'pathLength', + 'patternContentUnits', + 'patternTransform', + 'patternUnits', + 'points', + 'preserveAlpha', + 'preserveAspectRatio', + 'primitiveUnits', + 'r', + 'rx', + 'ry', + 'radius', + 'refX', + 'refY', + 'repeatCount', + 'repeatDur', + 'restart', + 'result', + 'rotate', + 'scale', + 'seed', + 'shape-rendering', + 'specularConstant', + 'specularExponent', + 'spreadMethod', + 'startOffset', + 'stdDeviation', + 'stitchTiles', + 'stop-color', + 'stop-opacity', + 'stroke-dasharray', + 'stroke-dashoffset', + 'stroke-linecap', + 'stroke-linejoin', + 'stroke-miterlimit', + 'stroke-opacity', + 'stroke', + 'stroke-width', + 'style', + 'surfaceScale', + 'systemLanguage', + 'tabindex', + 'targetX', + 'targetY', + 'transform', + 'text-anchor', + 'text-decoration', + 'text-rendering', + 'textLength', + 'type', + 'u1', + 'u2', + 'unicode', + 'values', + 'viewBox', + 'visibility', + 'version', + 'vert-adv-y', + 'vert-origin-x', + 'vert-origin-y', + 'width', + 'word-spacing', + 'wrap', + 'writing-mode', + 'xChannelSelector', + 'yChannelSelector', + 'x', + 'x1', + 'x2', + 'xlink:href', + 'y', + 'y1', + 'y2', + 'z', + 'zoomAndPan', + ]; + + public static $allowedElements = [ + 'svg', + 'a', + 'altGlyph', + 'altGlyphDef', + 'altGlyphItem', + 'animateColor', + 'animateMotion', + 'animateTransform', + 'circle', + 'clipPath', + 'defs', + 'desc', + 'ellipse', + 'filter', + 'font', + 'g', + 'glyph', + 'glyphRef', + 'hkern', + 'image', + 'line', + 'linearGradient', + 'marker', + 'mask', + 'metadata', + 'mpath', + 'path', + 'pattern', + 'polygon', + 'polyline', + 'radialGradient', + 'rect', + 'stop', + 'style', + 'switch', + 'symbol', + 'text', + 'textPath', + 'title', + 'tref', + 'tspan', + 'use', + 'view', + 'vkern', + ]; + + public static $allowedFilters = [ + 'feBlend', + 'feColorMatrix', + 'feComponentTransfer', + 'feComposite', + 'feConvolveMatrix', + 'feDiffuseLighting', + 'feDisplacementMap', + 'feDistantLight', + 'feFlood', + 'feFuncA', + 'feFuncB', + 'feFuncG', + 'feFuncR', + 'feGaussianBlur', + 'feMerge', + 'feMergeNode', + 'feMorphology', + 'feOffset', + 'fePointLight', + 'feSpecularLighting', + 'feSpotLight', + 'feTile', + 'feTurbulence', + ]; + + public static $allowedNamespaces = [ + 'xmlns' => 'http://www.w3.org/2000/svg', + 'xmlns:svg' => 'http://www.w3.org/2000/svg', + 'xmlns:xlink' => 'http://www.w3.org/1999/xlink' + ]; + + /** + * IMPORTANT: Use lower-case names here because + * of the case-insensitive matching + */ + public static $disallowedElements = [ + 'animate', + 'color-profile', + 'cursor', + 'discard', + 'fedropshadow', + 'feimage', + 'font-face', + 'font-face-format', + 'font-face-name', + 'font-face-src', + 'font-face-uri', + 'foreignobject', + 'hatch', + 'hatchpath', + 'mesh', + 'meshgradient', + 'meshpatch', + 'meshrow', + 'missing-glyph', + 'script', + 'set', + 'solidcolor', + 'unknown', + ]; + + /** + * Validates file contents + * + * @param string $string + * @return void + * + * @throws \Kirby\Exception\InvalidArgumentException If the file didn't pass validation + */ + public static function validate(string $string): void + { + $svg = static::parse($string); + + $rootName = $svg->documentElement->nodeName; + if ($rootName !== 'svg') { + throw new InvalidArgumentException('The file is not a SVG (got <' . $rootName . '>)'); + } + + parent::validateDom($svg); + } + + /** + * Validates the attributes of an element + * + * @param \DOMXPath $xPath + * @param \DOMNode $element + * @return void + * + * @throws \Kirby\Exception\InvalidArgumentException If any of the attributes is not valid + */ + protected static function validateAttrs(DOMXPath $xPath, DOMNode $element): void + { + $elementName = $element->nodeName; + + foreach ($element->attributes ?? [] as $attr) { + $attrName = $attr->nodeName; + $attrValue = $attr->nodeValue; + + // allow all aria and data attributes + $beginning = mb_substr($attrName, 0, 5); + if ($beginning === 'aria-' || $beginning === 'data-') { + continue; + } + + if (in_array($attrName, static::$allowedAttributes) !== true) { + throw new InvalidArgumentException( + 'The "' . $attrName . '" attribute (line ' . + $attr->getLineNo() . ') is not allowed in SVGs' + ); + } + + // block nested elements ("Billion Laughs" DoS attack) + if ( + $elementName === 'use' && + Str::contains($attrName, 'href') !== false && + Str::startsWith($attrValue, '#') === true + ) { + // find the target (used element) + $id = str_replace('"', '', mb_substr($attrValue, 1)); + $target = $xPath->query('//*[@id="' . $id . '"]')->item(0); + + // the target must not contain any other elements + if ( + is_a($target, 'DOMElement') === true && + $target->getElementsByTagName('use')->count() > 0 + ) { + throw new InvalidArgumentException( + 'Nested "use" elements are not allowed in SVGs (used in line ' . + $element->getLineNo() . ')' + ); + } + } + } + + // validate `xmlns` attributes as well, which can only + // be properly extracted using SimpleXML + if (is_a($element, 'DOMElement') === true) { + $simpleXmlElement = simplexml_import_dom($element); + foreach ($simpleXmlElement->getDocNamespaces(false, false) as $namespace => $value) { + $namespace = 'xmlns' . ($namespace ? ':' . $namespace : ''); + + // check if the namespace is allowlisted + if ( + isset(static::$allowedNamespaces[$namespace]) !== true || + static::$allowedNamespaces[$namespace] !== $value + ) { + throw new InvalidArgumentException( + 'The namespace "' . $namespace . '" (around line ' . + $element->getLineNo() . ') is not allowed or has an invalid value' + ); + } + } + } + + parent::validateAttrs($xPath, $element); + } + + /** + * Validates the doctype if present + * + * @param \DOMDocumentType $doctype + * @return void + * + * @throws \Kirby\Exception\InvalidArgumentException If the doctype is not valid + */ + protected static function validateDoctype(DOMDocumentType $doctype): void + { + if (mb_strtolower($doctype->name) !== 'svg') { + throw new InvalidArgumentException('Invalid doctype'); + } + + parent::validateDoctype($doctype); + } + + /** + * Validates all given DOM elements and their attributes + * + * @param \DOMXPath $xPath + * @param \DOMNodeList $elements + * @return void + * + * @throws \Kirby\Exception\InvalidArgumentException If any of the elements is not valid + */ + protected static function validateElements(DOMXPath $xPath, DOMNodeList $elements): void + { + $allowedElements = array_merge(static::$allowedElements, static::$allowedFilters); + + foreach ($elements as $element) { + $elementName = $element->nodeName; + $elementNameLower = mb_strtolower($elementName); + + // check for block-listed elements + if (in_array($elementNameLower, static::$disallowedElements) === true) { + throw new InvalidArgumentException( + 'The "' . $elementName . '" element (line ' . + $element->getLineNo() . ') is not allowed in SVGs' + ); + } + + // check for allow-listed elements + if (in_array($elementName, $allowedElements) === false) { + throw new InvalidArgumentException( + 'The "' . $elementName . '" element (line ' . + $element->getLineNo() . ') is not allowed in SVGs' + ); + } + + // check for URLs inside + * + * text + * + * @param string $string + * @return string + */ + public static function css($string) + { + return static::escaper()->escapeCss($string); + } + + /** + * Get the escaper instance (and create if needed) + * + * @return \Laminas\Escaper\Escaper + */ + protected static function escaper() + { + return static::$escaper = static::$escaper ?? new Escaper('utf-8'); + } + + /** + * Escape HTML element content + * + * This can be used to put untrusted data directly into the HTML body somewhere. + * This includes inside normal tags like div, p, b, td, etc. + * + * Escapes &, <, >, ", and ' with HTML entity encoding to prevent switching + * into any execution context, such as script, style, or event handlers. + * + * ...ESCAPE UNTRUSTED DATA BEFORE PUTTING HERE... + *
    ...ESCAPE UNTRUSTED DATA BEFORE PUTTING HERE...
    + * + * @param string $string + * @return string + */ + public static function html($string) + { + return static::escaper()->escapeHtml($string); + } + + /** + * Escape JavaScript data values + * + * This can be used to put dynamically generated JavaScript code + * into both script blocks and event-handler attributes. + * + * + * + *
    + * + * @param string $string + * @return string + */ + public static function js($string) + { + return static::escaper()->escapeJs($string); + } + + /** + * Escape URL parameter values + * + * This can be used to put untrusted data into HTTP GET parameter values. + * This should not be used to escape an entire URI. + * + * link + * + * @param string $string + * @return string + */ + public static function url($string) + { + return rawurlencode($string); + } + + /** + * Escape XML element content + * + * Removes offending characters that could be wrongfully interpreted as XML markup. + * + * The following characters are reserved in XML and will be replaced with their + * corresponding XML entities: + * + * ' is replaced with ' + * " is replaced with " + * & is replaced with & + * < is replaced with < + * > is replaced with > + * + * @param string $string + * @return string + */ + public static function xml($string) + { + return htmlspecialchars($string, ENT_QUOTES | ENT_XML1, 'UTF-8'); + } +} diff --git a/kirby/src/Toolkit/F.php b/kirby/src/Toolkit/F.php new file mode 100644 index 0000000..9d6185d --- /dev/null +++ b/kirby/src/Toolkit/F.php @@ -0,0 +1,865 @@ + + * @link https://getkirby.com + * @copyright Bastian Allgeier GmbH + * @license https://opensource.org/licenses/MIT + */ +class F +{ + public static $types = [ + 'archive' => [ + 'gz', + 'gzip', + 'tar', + 'tgz', + 'zip', + ], + 'audio' => [ + 'aif', + 'aiff', + 'm4a', + 'midi', + 'mp3', + 'wav', + ], + 'code' => [ + 'css', + 'js', + 'json', + 'java', + 'htm', + 'html', + 'php', + 'rb', + 'py', + 'scss', + 'xml', + 'yaml', + 'yml', + ], + 'document' => [ + 'csv', + 'doc', + 'docx', + 'dotx', + 'indd', + 'md', + 'mdown', + 'pdf', + 'ppt', + 'pptx', + 'rtf', + 'txt', + 'xl', + 'xls', + 'xlsx', + 'xltx', + ], + 'image' => [ + 'ai', + 'avif', + 'bmp', + 'gif', + 'eps', + 'ico', + 'j2k', + 'jp2', + 'jpeg', + 'jpg', + 'jpe', + 'png', + 'ps', + 'psd', + 'svg', + 'tif', + 'tiff', + 'webp' + ], + 'video' => [ + 'avi', + 'flv', + 'm4v', + 'mov', + 'movie', + 'mpe', + 'mpg', + 'mp4', + 'ogg', + 'ogv', + 'swf', + 'webm', + ], + ]; + + public static $units = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']; + + /** + * Appends new content to an existing file + * + * @param string $file The path for the file + * @param mixed $content Either a string or an array. Arrays will be converted to JSON. + * @return bool + */ + public static function append(string $file, $content): bool + { + return static::write($file, $content, true); + } + + /** + * Returns the file content as base64 encoded string + * + * @param string $file The path for the file + * @return string + */ + public static function base64(string $file): string + { + return base64_encode(static::read($file)); + } + + /** + * Copy a file to a new location. + * + * @param string $source + * @param string $target + * @param bool $force + * @return bool + */ + public static function copy(string $source, string $target, bool $force = false): bool + { + if (file_exists($source) === false || (file_exists($target) === true && $force === false)) { + return false; + } + + $directory = dirname($target); + + // create the parent directory if it does not exist + if (is_dir($directory) === false) { + Dir::make($directory, true); + } + + return copy($source, $target); + } + + /** + * Just an alternative for dirname() to stay consistent + * + * + * + * $dirname = F::dirname('/var/www/test.txt'); + * // dirname is /var/www + * + * + * + * @param string $file The path + * @return string + */ + public static function dirname(string $file): string + { + return dirname($file); + } + + /** + * Checks if the file exists on disk + * + * @param string $file + * @param string $in + * @return bool + */ + public static function exists(string $file, string $in = null): bool + { + try { + static::realpath($file, $in); + return true; + } catch (Exception $e) { + return false; + } + } + + /** + * Gets the extension of a file + * + * @param string $file The filename or path + * @param string $extension Set an optional extension to overwrite the current one + * @return string + */ + public static function extension(string $file = null, string $extension = null): string + { + // overwrite the current extension + if ($extension !== null) { + return static::name($file) . '.' . $extension; + } + + // return the current extension + return Str::lower(pathinfo($file, PATHINFO_EXTENSION)); + } + + /** + * Converts a file extension to a mime type + * + * @param string $extension + * @return string|false + */ + public static function extensionToMime(string $extension) + { + return Mime::fromExtension($extension); + } + + /** + * Returns the file type for a passed extension + * + * @param string $extension + * @return string|false + */ + public static function extensionToType(string $extension) + { + foreach (static::$types as $type => $extensions) { + if (in_array($extension, $extensions) === true) { + return $type; + } + } + + return false; + } + + /** + * Returns all extensions for a certain file type + * + * @param string $type + * @return array + */ + public static function extensions(string $type = null) + { + if ($type === null) { + return array_keys(Mime::types()); + } + + return static::$types[$type] ?? []; + } + + /** + * Extracts the filename from a file path + * + * + * + * $filename = F::filename('/var/www/test.txt'); + * // filename is test.txt + * + * + * + * @param string $name The path + * @return string + */ + public static function filename(string $name): string + { + return pathinfo($name, PATHINFO_BASENAME); + } + + /** + * Invalidate opcode cache for file. + * + * @param string $file The path of the file + * @return bool + */ + public static function invalidateOpcodeCache(string $file): bool + { + if (function_exists('opcache_invalidate') && strlen(ini_get('opcache.restrict_api')) === 0) { + return opcache_invalidate($file, true); + } else { + return false; + } + } + + /** + * Checks if a file is of a certain type + * + * @param string $file Full path to the file + * @param string $value An extension or mime type + * @return bool + */ + public static function is(string $file, string $value): bool + { + // check for the extension + if (in_array($value, static::extensions()) === true) { + return static::extension($file) === $value; + } + + // check for the mime type + if (strpos($value, '/') !== false) { + return static::mime($file) === $value; + } + + return false; + } + + /** + * Checks if the file is readable + * + * @param string $file + * @return bool + */ + public static function isReadable(string $file): bool + { + return is_readable($file); + } + + /** + * Checks if the file is writable + * + * @param string $file + * @return bool + */ + public static function isWritable(string $file): bool + { + if (file_exists($file) === false) { + return is_writable(dirname($file)); + } + + return is_writable($file); + } + + /** + * Create a (symbolic) link to a file + * + * @param string $source + * @param string $link + * @param string $method + * @return bool + */ + public static function link(string $source, string $link, string $method = 'link'): bool + { + Dir::make(dirname($link), true); + + if (is_file($link) === true) { + return true; + } + + if (is_file($source) === false) { + throw new Exception(sprintf('The file "%s" does not exist and cannot be linked', $source)); + } + + try { + return $method($source, $link) === true; + } catch (Throwable $e) { + return false; + } + } + + /** + * Loads a file and returns the result or `false` if the + * file to load does not exist + * + * @param string $file + * @param mixed $fallback + * @param array $data Optional array of variables to extract in the variable scope + * @return mixed + */ + public static function load(string $file, $fallback = null, array $data = []) + { + if (is_file($file) === false) { + return $fallback; + } + + // we use the loadIsolated() method here to prevent the included + // file from overwriting our $fallback in this variable scope; see + // https://www.php.net/manual/en/function.include.php#example-124 + $result = static::loadIsolated($file, $data); + + if ($fallback !== null && gettype($result) !== gettype($fallback)) { + return $fallback; + } + + return $result; + } + + /** + * Loads a file with as little as possible in the variable scope + * + * @param string $file + * @param array $data Optional array of variables to extract in the variable scope + * @return mixed + */ + protected static function loadIsolated(string $file, array $data = []) + { + // extract the $data variables in this scope to be accessed by the included file; + // protect $file against being overwritten by a $data variable + $___file___ = $file; + extract($data); + + return include $___file___; + } + + /** + * Loads a file using `include_once()` and returns whether loading was successful + * + * @param string $file + * @return bool + */ + public static function loadOnce(string $file): bool + { + if (is_file($file) === false) { + return false; + } + + include_once $file; + return true; + } + + /** + * Returns the mime type of a file + * + * @param string $file + * @return string|false + */ + public static function mime(string $file) + { + return Mime::type($file); + } + + /** + * Converts a mime type to a file extension + * + * @param string $mime + * @return string|false + */ + public static function mimeToExtension(string $mime = null) + { + return Mime::toExtension($mime); + } + + /** + * Returns the type for a given mime + * + * @param string $mime + * @return string|false + */ + public static function mimeToType(string $mime) + { + return static::extensionToType(Mime::toExtension($mime)); + } + + /** + * Get the file's last modification time. + * + * @param string $file + * @param string $format + * @param string $handler date or strftime + * @return mixed + */ + public static function modified(string $file, string $format = null, string $handler = 'date') + { + if (file_exists($file) !== true) { + return false; + } + + $stat = stat($file); + $mtime = $stat['mtime'] ?? 0; + $ctime = $stat['ctime'] ?? 0; + $modified = max([$mtime, $ctime]); + + if (is_null($format) === true) { + return $modified; + } + + return $handler($format, $modified); + } + + /** + * Moves a file to a new location + * + * @param string $oldRoot The current path for the file + * @param string $newRoot The path to the new location + * @param bool $force Force move if the target file exists + * @return bool + */ + public static function move(string $oldRoot, string $newRoot, bool $force = false): bool + { + // check if the file exists + if (file_exists($oldRoot) === false) { + return false; + } + + if (file_exists($newRoot) === true) { + if ($force === false) { + return false; + } + + // delete the existing file + static::remove($newRoot); + } + + // actually move the file if it exists + if (rename($oldRoot, $newRoot) !== true) { + return false; + } + + return true; + } + + /** + * Extracts the name from a file path or filename without extension + * + * @param string $name The path or filename + * @return string + */ + public static function name(string $name): string + { + return pathinfo($name, PATHINFO_FILENAME); + } + + /** + * Converts an integer size into a human readable format + * + * @param mixed $size The file size or a file path + * @param string|null|false $locale Locale for number formatting, + * `null` for the current locale, + * `false` to disable number formatting + * @return string + */ + public static function niceSize($size, $locale = null): string + { + // file mode + if (is_string($size) === true && file_exists($size) === true) { + $size = static::size($size); + } + + // make sure it's an int + $size = (int)$size; + + // avoid errors for invalid sizes + if ($size <= 0) { + return '0 KB'; + } + + // the math magic + $size = round($size / pow(1024, ($unit = floor(log($size, 1024)))), 2); + + // format the number if requested + if ($locale !== false) { + $size = I18n::formatNumber($size, $locale); + } + + return $size . ' ' . static::$units[$unit]; + } + + /** + * Reads the content of a file or requests the + * contents of a remote HTTP or HTTPS URL + * + * @param string $file The path for the file or an absolute URL + * @return string|false + */ + public static function read(string $file) + { + if ( + is_file($file) !== true && + Str::startsWith($file, 'https://') !== true && + Str::startsWith($file, 'http://') !== true + ) { + return false; + } + + return @file_get_contents($file); + } + + /** + * Changes the name of the file without + * touching the extension + * + * @param string $file + * @param string $newName + * @param bool $overwrite Force overwrite existing files + * @return string|false + */ + public static function rename(string $file, string $newName, bool $overwrite = false) + { + // create the new name + $name = static::safeName(basename($newName)); + + // overwrite the root + $newRoot = rtrim(dirname($file) . '/' . $name . '.' . F::extension($file), '.'); + + // nothing has changed + if ($newRoot === $file) { + return $newRoot; + } + + if (F::move($file, $newRoot, $overwrite) !== true) { + return false; + } + + return $newRoot; + } + + /** + * Returns the absolute path to the file if the file can be found. + * + * @param string $file + * @param string $in + * @return string|null + */ + public static function realpath(string $file, string $in = null) + { + $realpath = realpath($file); + + if ($realpath === false || is_file($realpath) === false) { + throw new Exception(sprintf('The file does not exist at the given path: "%s"', $file)); + } + + if ($in !== null) { + $parent = realpath($in); + + if ($parent === false || is_dir($parent) === false) { + throw new Exception(sprintf('The parent directory does not exist: "%s"', $in)); + } + + if (substr($realpath, 0, strlen($parent)) !== $parent) { + throw new Exception('The file is not within the parent directory'); + } + } + + return $realpath; + } + + /** + * Returns the relative path of the file + * starting after $in + * + * @param string $file + * @param string $in + * @return string + */ + public static function relativepath(string $file, string $in = null): string + { + if (empty($in) === true) { + return basename($file); + } + + // windows + $file = str_replace('\\', '/', $file); + $in = str_replace('\\', '/', $in); + + if (Str::contains($file, $in) === false) { + return basename($file); + } + + return Str::after($file, $in); + } + + /** + * Deletes a file + * + * + * + * $remove = F::remove('test.txt'); + * if($remove) echo 'The file has been removed'; + * + * + * + * @param string $file The path for the file + * @return bool + */ + public static function remove(string $file): bool + { + if (strpos($file, '*') !== false) { + foreach (glob($file) as $f) { + static::remove($f); + } + + return true; + } + + $file = realpath($file); + + if (file_exists($file) === false) { + return true; + } + + return unlink($file); + } + + /** + * Sanitize a filename to strip unwanted special characters + * + * + * + * $safe = f::safeName('über genious.txt'); + * // safe will be ueber-genious.txt + * + * + * + * @param string $string The file name + * @return string + */ + public static function safeName(string $string): string + { + $name = static::name($string); + $extension = static::extension($string); + $safeName = Str::slug($name, '-', 'a-z0-9@._-'); + $safeExtension = empty($extension) === false ? '.' . Str::slug($extension) : ''; + + return $safeName . $safeExtension; + } + + /** + * Tries to find similar or the same file by + * building a glob based on the path + * + * @param string $path + * @param string $pattern + * @return array + */ + public static function similar(string $path, string $pattern = '*'): array + { + $dir = dirname($path); + $name = static::name($path); + $extension = static::extension($path); + $glob = $dir . '/' . $name . $pattern . '.' . $extension; + return glob($glob); + } + + /** + * Returns the size of a file. + * + * @param mixed $file The path + * @return int + */ + public static function size(string $file): int + { + try { + return filesize($file); + } catch (Throwable $e) { + return 0; + } + } + + /** + * Categorize the file + * + * @param string $file Either the file path or extension + * @return string|null + */ + public static function type(string $file) + { + $length = strlen($file); + + if ($length >= 2 && $length <= 4) { + // use the file name as extension + $extension = $file; + } else { + // get the extension from the filename + $extension = pathinfo($file, PATHINFO_EXTENSION); + } + + if (empty($extension) === true) { + // detect the mime type first to get the most reliable extension + $mime = static::mime($file); + $extension = static::mimeToExtension($mime); + } + + // sanitize extension + $extension = strtolower($extension); + + foreach (static::$types as $type => $extensions) { + if (in_array($extension, $extensions) === true) { + return $type; + } + } + + return null; + } + + /** + * Returns all extensions of a given file type + * or `null` if the file type is unknown + * + * @param string $type + * @return array|null + */ + public static function typeToExtensions(string $type): ?array + { + return static::$types[$type] ?? null; + } + + /** + * Unzips a zip file + * + * @param string $file + * @param string $to + * @return bool + */ + public static function unzip(string $file, string $to): bool + { + if (class_exists('ZipArchive') === false) { + throw new Exception('The ZipArchive class is not available'); + } + + $zip = new ZipArchive(); + + if ($zip->open($file) === true) { + $zip->extractTo($to); + $zip->close(); + return true; + } + + return false; + } + + /** + * Returns the file as data uri + * + * @param string $file The path for the file + * @return string|false + */ + public static function uri(string $file) + { + if ($mime = static::mime($file)) { + return 'data:' . $mime . ';base64,' . static::base64($file); + } + + return false; + } + + /** + * Creates a new file + * + * @param string $file The path for the new file + * @param mixed $content Either a string, an object or an array. Arrays and objects will be serialized. + * @param bool $append true: append the content to an exisiting file if available. false: overwrite. + * @return bool + */ + public static function write(string $file, $content, bool $append = false): bool + { + if (is_array($content) === true || is_object($content) === true) { + $content = serialize($content); + } + + $mode = $append === true ? FILE_APPEND | LOCK_EX : LOCK_EX; + + // if the parent directory does not exist, create it + if (is_dir(dirname($file)) === false) { + if (Dir::make(dirname($file)) === false) { + return false; + } + } + + if (static::isWritable($file) === false) { + throw new Exception('The file "' . $file . '" is not writable'); + } + + return file_put_contents($file, $content, $mode) !== false; + } +} diff --git a/kirby/src/Toolkit/Facade.php b/kirby/src/Toolkit/Facade.php new file mode 100644 index 0000000..3387a97 --- /dev/null +++ b/kirby/src/Toolkit/Facade.php @@ -0,0 +1,36 @@ + + * @link https://getkirby.com + * @copyright Bastian Allgeier GmbH + * @license https://opensource.org/licenses/MIT + */ +abstract class Facade +{ + /** + * Returns the instance that should be + * available statically + * + * @return mixed + */ + abstract public static function instance(); + + /** + * Proxy for all public instance calls + * + * @param string $method + * @param array $args + * @return mixed + */ + public static function __callStatic(string $method, array $args = null) + { + return static::instance()->$method(...$args); + } +} diff --git a/kirby/src/Toolkit/File.php b/kirby/src/Toolkit/File.php new file mode 100644 index 0000000..16ffbfb --- /dev/null +++ b/kirby/src/Toolkit/File.php @@ -0,0 +1,358 @@ + + * @link https://getkirby.com + * @copyright Bastian Allgeier GmbH + * @license https://opensource.org/licenses/MIT + */ +class File +{ + /** + * Absolute file path + * + * @var string + */ + protected $root; + + /** + * Constructs a new File object by absolute path + * + * @param string $root Absolute file path + */ + public function __construct(string $root = null) + { + $this->root = $root; + } + + /** + * Improved `var_dump` output + * + * @return array + */ + public function __debugInfo(): array + { + return $this->toArray(); + } + + /** + * Returns the file content as base64 encoded string + * + * @return string + */ + public function base64(): string + { + return base64_encode($this->read()); + } + + /** + * Copy a file to a new location. + * + * @param string $target + * @param bool $force + * @return static + */ + public function copy(string $target, bool $force = false) + { + if (F::copy($this->root, $target, $force) !== true) { + throw new Exception('The file "' . $this->root . '" could not be copied'); + } + + return new static($target); + } + + /** + * Returns the file as data uri + * + * @param bool $base64 Whether the data should be base64 encoded or not + * @return string + */ + public function dataUri(bool $base64 = true): string + { + if ($base64 === true) { + return 'data:' . $this->mime() . ';base64,' . $this->base64(); + } + + return 'data:' . $this->mime() . ',' . Escape::url($this->read()); + } + + /** + * Deletes the file + * + * @return bool + */ + public function delete(): bool + { + if (F::remove($this->root) !== true) { + throw new Exception('The file "' . $this->root . '" could not be deleted'); + } + + return true; + } + + /** + * Checks if the file actually exists + * + * @return bool + */ + public function exists(): bool + { + return file_exists($this->root) === true; + } + + /** + * Returns the current lowercase extension (without .) + * + * @return string + */ + public function extension(): string + { + return F::extension($this->root); + } + + /** + * Returns the filename + * + * @return string + */ + public function filename(): string + { + return basename($this->root); + } + + /** + * Returns a md5 hash of the root + * + * @return string + */ + public function hash(): string + { + return md5($this->root); + } + + /** + * Checks if a file is of a certain type + * + * @param string $value An extension or mime type + * @return bool + */ + public function is(string $value): bool + { + return F::is($this->root, $value); + } + + /** + * Checks if the file is readable + * + * @return bool + */ + public function isReadable(): bool + { + return is_readable($this->root) === true; + } + + /** + * Checks if the file is writable + * + * @return bool + */ + public function isWritable(): bool + { + return F::isWritable($this->root); + } + + /** + * Detects the mime type of the file + * + * @return string|null + */ + public function mime() + { + return Mime::type($this->root); + } + + /** + * Get the file's last modification time. + * + * @param string $format + * @param string $handler date or strftime + * @return mixed + */ + public function modified(string $format = null, string $handler = 'date') + { + return F::modified($this->root, $format, $handler); + } + + /** + * Move the file to a new location + * + * @param string $newRoot + * @param bool $overwrite Force overwriting any existing files + * @return static + */ + public function move(string $newRoot, bool $overwrite = false) + { + if (F::move($this->root, $newRoot, $overwrite) !== true) { + throw new Exception('The file: "' . $this->root . '" could not be moved to: "' . $newRoot . '"'); + } + + return new static($newRoot); + } + + /** + * Getter for the name of the file + * without the extension + * + * @return string + */ + public function name(): string + { + return pathinfo($this->root, PATHINFO_FILENAME); + } + + /** + * Returns the file size in a + * human-readable format + * + * @return string + */ + public function niceSize(): string + { + return F::niceSize($this->root); + } + + /** + * Reads the file content and returns it. + * + * @return string|false + */ + public function read() + { + return F::read($this->root); + } + + /** + * Returns the absolute path to the file + * + * @return string + */ + public function realpath(): string + { + return realpath($this->root); + } + + /** + * Changes the name of the file without + * touching the extension + * + * @param string $newName + * @param bool $overwrite Force overwrite existing files + * @return static + */ + public function rename(string $newName, bool $overwrite = false) + { + $newRoot = F::rename($this->root, $newName, $overwrite); + + if ($newRoot === false) { + throw new Exception('The file: "' . $this->root . '" could not be renamed to: "' . $newName . '"'); + } + + return new static($newRoot); + } + + /** + * Returns the given file path + * + * @return string|null + */ + public function root(): ?string + { + return $this->root; + } + + /** + * Returns the raw size of the file + * + * @return int + */ + public function size(): int + { + return F::size($this->root); + } + + /** + * Converts the media object to a + * plain PHP array + * + * @return array + */ + public function toArray(): array + { + return [ + 'root' => $this->root(), + 'hash' => $this->hash(), + 'filename' => $this->filename(), + 'name' => $this->name(), + 'safeName' => F::safeName($this->name()), + 'extension' => $this->extension(), + 'size' => $this->size(), + 'niceSize' => $this->niceSize(), + 'modified' => $this->modified('c'), + 'mime' => $this->mime(), + 'type' => $this->type(), + 'isWritable' => $this->isWritable(), + 'isReadable' => $this->isReadable(), + ]; + } + + /** + * Returns the file type. + * + * @return string|false + */ + public function type() + { + return F::type($this->root); + } + + /** + * Validates the file contents depending on the file type + * + * @param string|bool $typeLazy Explicit sane handler type string, + * `true` for lazy autodetection or + * `false` for normal autodetection + * @return void + * + * @throws \Kirby\Exception\InvalidArgumentException If the file didn't pass validation + * @throws \Kirby\Exception\NotFoundException If the handler was not found + * @throws \Kirby\Exception\Exception On other errors + */ + public function validateContents($typeLazy = false): void + { + Sane::validateFile($this->root(), $typeLazy); + } + + /** + * Writes content to the file + * + * @param string $content + * @return bool + */ + public function write($content): bool + { + if (F::write($this->root, $content) !== true) { + throw new Exception('The file "' . $this->root . '" could not be written'); + } + + return true; + } +} diff --git a/kirby/src/Toolkit/Html.php b/kirby/src/Toolkit/Html.php new file mode 100644 index 0000000..d51d0a0 --- /dev/null +++ b/kirby/src/Toolkit/Html.php @@ -0,0 +1,573 @@ + + * @link https://getkirby.com + * @copyright Bastian Allgeier GmbH + * @license https://opensource.org/licenses/MIT + */ +class Html extends Xml +{ + /** + * An internal store for an HTML entities translation table + * + * @var array + */ + public static $entities; + + /** + * Closing string for void tags; + * can be used to switch to trailing slashes if required + * + * ```php + * Html::$void = ' />' + * ``` + * + * @var string + */ + public static $void = '>'; + + /** + * List of HTML tags that are considered to be self-closing + * + * @var array + */ + public static $voidList = [ + 'area', + 'base', + 'br', + 'col', + 'command', + 'embed', + 'hr', + 'img', + 'input', + 'keygen', + 'link', + 'meta', + 'param', + 'source', + 'track', + 'wbr' + ]; + + /** + * Generic HTML tag generator + * Can be called like `Html::p('A paragraph', ['class' => 'text'])` + * + * @param string $tag Tag name + * @param array $arguments Further arguments for the Html::tag() method + * @return string + */ + public static function __callStatic(string $tag, array $arguments = []): string + { + if (static::isVoid($tag) === true) { + return static::tag($tag, null, ...$arguments); + } + + return static::tag($tag, ...$arguments); + } + + /** + * Generates an `` tag; automatically supports mailto: and tel: links + * + * @param string $href The URL for the `` tag + * @param string|array|null $text The optional text; if `null`, the URL will be used as text + * @param array $attr Additional attributes for the tag + * @return string The generated HTML + */ + public static function a(string $href, $text = null, array $attr = []): string + { + if (Str::startsWith($href, 'mailto:')) { + return static::email(substr($href, 7), $text, $attr); + } + + if (Str::startsWith($href, 'tel:')) { + return static::tel(substr($href, 4), $text, $attr); + } + + return static::link($href, $text, $attr); + } + + /** + * Generates a single attribute or a list of attributes + * + * @param string|array $name String: A single attribute with that name will be generated. + * Key-value array: A list of attributes will be generated. Don't pass a second argument in that case. + * @param mixed $value If used with a `$name` string, pass the value of the attribute here. + * If used with a `$name` array, this can be set to `false` to disable attribute sorting. + * @return string|null The generated HTML attributes string + */ + public static function attr($name, $value = null): ?string + { + // HTML supports boolean attributes without values + if (is_array($name) === false && is_bool($value) === true) { + return $value === true ? strtolower($name) : null; + } + + // all other cases can share the XML variant + $attr = parent::attr($name, $value); + + // HTML supports named entities + $entities = parent::entities(); + $html = array_keys($entities); + $xml = array_values($entities); + return str_replace($xml, $html, $attr); + } + + /** + * Converts lines in a string into HTML breaks + * + * @param string $string + * @return string + */ + public static function breaks(string $string): string + { + return nl2br($string); + } + + /** + * Generates an `` tag with `mailto:` + * + * @param string $email The email address + * @param string|array|null $text The optional text; if `null`, the email address will be used as text + * @param array $attr Additional attributes for the tag + * @return string The generated HTML + */ + public static function email(string $email, $text = null, array $attr = []): string + { + if (empty($email) === true) { + return ''; + } + + if (empty($text) === true) { + // show only the email address without additional parameters + $address = Str::contains($email, '?') ? Str::before($email, '?') : $email; + + $text = [Str::encode($address)]; + } + + $email = Str::encode($email); + $attr = array_merge([ + 'href' => [ + 'value' => 'mailto:' . $email, + 'escape' => false + ] + ], $attr); + + // add rel=noopener to target blank links to improve security + $attr['rel'] = static::rel($attr['rel'] ?? null, $attr['target'] ?? null); + + return static::tag('a', $text, $attr); + } + + /** + * Converts a string to an HTML-safe string + * + * @param string|null $string + * @param bool $keepTags If true, existing tags won't be escaped + * @return string The HTML string + * + * @psalm-suppress ParamNameMismatch + */ + public static function encode(?string $string, bool $keepTags = false): string + { + if ($string === null) { + return ''; + } + + if ($keepTags === true) { + $list = static::entities(); + unset($list['"'], $list['<'], $list['>'], $list['&']); + + $search = array_keys($list); + $values = array_values($list); + + return str_replace($search, $values, $string); + } + + return htmlentities($string, ENT_COMPAT, 'utf-8'); + } + + /** + * Returns the entity translation table + * + * @return array + */ + public static function entities(): array + { + return self::$entities = self::$entities ?? get_html_translation_table(HTML_ENTITIES); + } + + /** + * Creates a `
    ` tag with optional caption + * + * @param string|array $content Contents of the `
    ` tag + * @param string|array $caption Optional `
    ` text to use + * @param array $attr Additional attributes for the `
    ` tag + * @return string The generated HTML + */ + public static function figure($content, $caption = '', array $attr = []): string + { + if ($caption) { + $figcaption = static::tag('figcaption', $caption); + + if (is_string($content) === true) { + $content = [static::encode($content, false)]; + } + + $content[] = $figcaption; + } + + return static::tag('figure', $content, $attr); + } + + /** + * Embeds a GitHub Gist + * + * @param string $url Gist URL + * @param string|null $file Optional specific file to embed + * @param array $attr Additional attributes for the ` + + + + + diff --git a/kirby/vendor/filp/whoops/src/Whoops/Resources/views/panel_details.html.php b/kirby/vendor/filp/whoops/src/Whoops/Resources/views/panel_details.html.php new file mode 100644 index 0000000..a85e451 --- /dev/null +++ b/kirby/vendor/filp/whoops/src/Whoops/Resources/views/panel_details.html.php @@ -0,0 +1,2 @@ +render($frame_code) ?> +render($env_details) ?> \ No newline at end of file diff --git a/kirby/vendor/filp/whoops/src/Whoops/Resources/views/panel_details_outer.html.php b/kirby/vendor/filp/whoops/src/Whoops/Resources/views/panel_details_outer.html.php new file mode 100644 index 0000000..8162d8c --- /dev/null +++ b/kirby/vendor/filp/whoops/src/Whoops/Resources/views/panel_details_outer.html.php @@ -0,0 +1,3 @@ +
    + render($panel_details) ?> +
    \ No newline at end of file diff --git a/kirby/vendor/filp/whoops/src/Whoops/Resources/views/panel_left.html.php b/kirby/vendor/filp/whoops/src/Whoops/Resources/views/panel_left.html.php new file mode 100644 index 0000000..7e652e4 --- /dev/null +++ b/kirby/vendor/filp/whoops/src/Whoops/Resources/views/panel_left.html.php @@ -0,0 +1,4 @@ +render($header_outer); +$tpl->render($frames_description); +$tpl->render($frames_container); diff --git a/kirby/vendor/filp/whoops/src/Whoops/Resources/views/panel_left_outer.html.php b/kirby/vendor/filp/whoops/src/Whoops/Resources/views/panel_left_outer.html.php new file mode 100644 index 0000000..77b575c --- /dev/null +++ b/kirby/vendor/filp/whoops/src/Whoops/Resources/views/panel_left_outer.html.php @@ -0,0 +1,3 @@ +
    + render($panel_left) ?> +
    \ No newline at end of file diff --git a/kirby/vendor/filp/whoops/src/Whoops/Run.php b/kirby/vendor/filp/whoops/src/Whoops/Run.php new file mode 100644 index 0000000..639b3ae --- /dev/null +++ b/kirby/vendor/filp/whoops/src/Whoops/Run.php @@ -0,0 +1,545 @@ + + */ + +namespace Whoops; + +use InvalidArgumentException; +use Throwable; +use Whoops\Exception\ErrorException; +use Whoops\Exception\Inspector; +use Whoops\Handler\CallbackHandler; +use Whoops\Handler\Handler; +use Whoops\Handler\HandlerInterface; +use Whoops\Util\Misc; +use Whoops\Util\SystemFacade; + +final class Run implements RunInterface +{ + /** + * @var bool + */ + private $isRegistered; + + /** + * @var bool + */ + private $allowQuit = true; + + /** + * @var bool + */ + private $sendOutput = true; + + /** + * @var integer|false + */ + private $sendHttpCode = 500; + + /** + * @var integer|false + */ + private $sendExitCode = 1; + + /** + * @var HandlerInterface[] + */ + private $handlerStack = []; + + /** + * @var array + * @psalm-var list + */ + private $silencedPatterns = []; + + /** + * @var SystemFacade + */ + private $system; + + /** + * In certain scenarios, like in shutdown handler, we can not throw exceptions. + * + * @var bool + */ + private $canThrowExceptions = true; + + public function __construct(SystemFacade $system = null) + { + $this->system = $system ?: new SystemFacade; + } + + /** + * Explicitly request your handler runs as the last of all currently registered handlers. + * + * @param HandlerInterface $handler + * + * @return Run + */ + public function appendHandler($handler) + { + array_unshift($this->handlerStack, $this->resolveHandler($handler)); + return $this; + } + + /** + * Explicitly request your handler runs as the first of all currently registered handlers. + * + * @param HandlerInterface $handler + * + * @return Run + */ + public function prependHandler($handler) + { + return $this->pushHandler($handler); + } + + /** + * Register your handler as the last of all currently registered handlers (to be executed first). + * Prefer using appendHandler and prependHandler for clarity. + * + * @param Callable|HandlerInterface $handler + * + * @return Run + * + * @throws InvalidArgumentException If argument is not callable or instance of HandlerInterface. + */ + public function pushHandler($handler) + { + $this->handlerStack[] = $this->resolveHandler($handler); + return $this; + } + + /** + * Removes and returns the last handler pushed to the handler stack. + * + * @see Run::removeFirstHandler(), Run::removeLastHandler() + * + * @return HandlerInterface|null + */ + public function popHandler() + { + return array_pop($this->handlerStack); + } + + /** + * Removes the first handler. + * + * @return void + */ + public function removeFirstHandler() + { + array_pop($this->handlerStack); + } + + /** + * Removes the last handler. + * + * @return void + */ + public function removeLastHandler() + { + array_shift($this->handlerStack); + } + + /** + * Returns an array with all handlers, in the order they were added to the stack. + * + * @return array + */ + public function getHandlers() + { + return $this->handlerStack; + } + + /** + * Clears all handlers in the handlerStack, including the default PrettyPage handler. + * + * @return Run + */ + public function clearHandlers() + { + $this->handlerStack = []; + return $this; + } + + /** + * Registers this instance as an error handler. + * + * @return Run + */ + public function register() + { + if (!$this->isRegistered) { + // Workaround PHP bug 42098 + // https://bugs.php.net/bug.php?id=42098 + class_exists("\\Whoops\\Exception\\ErrorException"); + class_exists("\\Whoops\\Exception\\FrameCollection"); + class_exists("\\Whoops\\Exception\\Frame"); + class_exists("\\Whoops\\Exception\\Inspector"); + + $this->system->setErrorHandler([$this, self::ERROR_HANDLER]); + $this->system->setExceptionHandler([$this, self::EXCEPTION_HANDLER]); + $this->system->registerShutdownFunction([$this, self::SHUTDOWN_HANDLER]); + + $this->isRegistered = true; + } + + return $this; + } + + /** + * Unregisters all handlers registered by this Whoops\Run instance. + * + * @return Run + */ + public function unregister() + { + if ($this->isRegistered) { + $this->system->restoreExceptionHandler(); + $this->system->restoreErrorHandler(); + + $this->isRegistered = false; + } + + return $this; + } + + /** + * Should Whoops allow Handlers to force the script to quit? + * + * @param bool|int $exit + * + * @return bool + */ + public function allowQuit($exit = null) + { + if (func_num_args() == 0) { + return $this->allowQuit; + } + + return $this->allowQuit = (bool) $exit; + } + + /** + * Silence particular errors in particular files. + * + * @param array|string $patterns List or a single regex pattern to match. + * @param int $levels Defaults to E_STRICT | E_DEPRECATED. + * + * @return Run + */ + public function silenceErrorsInPaths($patterns, $levels = 10240) + { + $this->silencedPatterns = array_merge( + $this->silencedPatterns, + array_map( + function ($pattern) use ($levels) { + return [ + "pattern" => $pattern, + "levels" => $levels, + ]; + }, + (array) $patterns + ) + ); + + return $this; + } + + /** + * Returns an array with silent errors in path configuration. + * + * @return array + */ + public function getSilenceErrorsInPaths() + { + return $this->silencedPatterns; + } + + /** + * Should Whoops send HTTP error code to the browser if possible? + * Whoops will by default send HTTP code 500, but you may wish to + * use 502, 503, or another 5xx family code. + * + * @param bool|int $code + * + * @return int|false + * + * @throws InvalidArgumentException + */ + public function sendHttpCode($code = null) + { + if (func_num_args() == 0) { + return $this->sendHttpCode; + } + + if (!$code) { + return $this->sendHttpCode = false; + } + + if ($code === true) { + $code = 500; + } + + if ($code < 400 || 600 <= $code) { + throw new InvalidArgumentException( + "Invalid status code '$code', must be 4xx or 5xx" + ); + } + + return $this->sendHttpCode = $code; + } + + /** + * Should Whoops exit with a specific code on the CLI if possible? + * Whoops will exit with 1 by default, but you can specify something else. + * + * @param int $code + * + * @return int + * + * @throws InvalidArgumentException + */ + public function sendExitCode($code = null) + { + if (func_num_args() == 0) { + return $this->sendExitCode; + } + + if ($code < 0 || 255 <= $code) { + throw new InvalidArgumentException( + "Invalid status code '$code', must be between 0 and 254" + ); + } + + return $this->sendExitCode = (int) $code; + } + + /** + * Should Whoops push output directly to the client? + * If this is false, output will be returned by handleException. + * + * @param bool|int $send + * + * @return bool + */ + public function writeToOutput($send = null) + { + if (func_num_args() == 0) { + return $this->sendOutput; + } + + return $this->sendOutput = (bool) $send; + } + + /** + * Handles an exception, ultimately generating a Whoops error page. + * + * @param Throwable $exception + * + * @return string Output generated by handlers. + */ + public function handleException($exception) + { + // Walk the registered handlers in the reverse order + // they were registered, and pass off the exception + $inspector = $this->getInspector($exception); + + // Capture output produced while handling the exception, + // we might want to send it straight away to the client, + // or return it silently. + $this->system->startOutputBuffering(); + + // Just in case there are no handlers: + $handlerResponse = null; + $handlerContentType = null; + + try { + foreach (array_reverse($this->handlerStack) as $handler) { + $handler->setRun($this); + $handler->setInspector($inspector); + $handler->setException($exception); + + // The HandlerInterface does not require an Exception passed to handle() + // and neither of our bundled handlers use it. + // However, 3rd party handlers may have already relied on this parameter, + // and removing it would be possibly breaking for users. + $handlerResponse = $handler->handle($exception); + + // Collect the content type for possible sending in the headers. + $handlerContentType = method_exists($handler, 'contentType') ? $handler->contentType() : null; + + if (in_array($handlerResponse, [Handler::LAST_HANDLER, Handler::QUIT])) { + // The Handler has handled the exception in some way, and + // wishes to quit execution (Handler::QUIT), or skip any + // other handlers (Handler::LAST_HANDLER). If $this->allowQuit + // is false, Handler::QUIT behaves like Handler::LAST_HANDLER + break; + } + } + + $willQuit = $handlerResponse == Handler::QUIT && $this->allowQuit(); + } finally { + $output = $this->system->cleanOutputBuffer(); + } + + // If we're allowed to, send output generated by handlers directly + // to the output, otherwise, and if the script doesn't quit, return + // it so that it may be used by the caller + if ($this->writeToOutput()) { + // @todo Might be able to clean this up a bit better + if ($willQuit) { + // Cleanup all other output buffers before sending our output: + while ($this->system->getOutputBufferLevel() > 0) { + $this->system->endOutputBuffering(); + } + + // Send any headers if needed: + if (Misc::canSendHeaders() && $handlerContentType) { + header("Content-Type: {$handlerContentType}"); + } + } + + $this->writeToOutputNow($output); + } + + if ($willQuit) { + // HHVM fix for https://github.com/facebook/hhvm/issues/4055 + $this->system->flushOutputBuffer(); + + $this->system->stopExecution( + $this->sendExitCode() + ); + } + + return $output; + } + + /** + * Converts generic PHP errors to \ErrorException instances, before passing them off to be handled. + * + * This method MUST be compatible with set_error_handler. + * + * @param int $level + * @param string $message + * @param string|null $file + * @param int|null $line + * + * @return bool + * + * @throws ErrorException + */ + public function handleError($level, $message, $file = null, $line = null) + { + if ($level & $this->system->getErrorReportingLevel()) { + foreach ($this->silencedPatterns as $entry) { + $pathMatches = (bool) preg_match($entry["pattern"], $file); + $levelMatches = $level & $entry["levels"]; + if ($pathMatches && $levelMatches) { + // Ignore the error, abort handling + // See https://github.com/filp/whoops/issues/418 + return true; + } + } + + // XXX we pass $level for the "code" param only for BC reasons. + // see https://github.com/filp/whoops/issues/267 + $exception = new ErrorException($message, /*code*/ $level, /*severity*/ $level, $file, $line); + if ($this->canThrowExceptions) { + throw $exception; + } else { + $this->handleException($exception); + } + // Do not propagate errors which were already handled by Whoops. + return true; + } + + // Propagate error to the next handler, allows error_get_last() to + // work on silenced errors. + return false; + } + + /** + * Special case to deal with Fatal errors and the like. + * + * @return void + */ + public function handleShutdown() + { + // If we reached this step, we are in shutdown handler. + // An exception thrown in a shutdown handler will not be propagated + // to the exception handler. Pass that information along. + $this->canThrowExceptions = false; + + $error = $this->system->getLastError(); + if ($error && Misc::isLevelFatal($error['type'])) { + // If there was a fatal error, + // it was not handled in handleError yet. + $this->allowQuit = false; + $this->handleError( + $error['type'], + $error['message'], + $error['file'], + $error['line'] + ); + } + } + + /** + * @param Throwable $exception + * + * @return Inspector + */ + private function getInspector($exception) + { + return new Inspector($exception); + } + + /** + * Resolves the giving handler. + * + * @param HandlerInterface $handler + * + * @return HandlerInterface + * + * @throws InvalidArgumentException + */ + private function resolveHandler($handler) + { + if (is_callable($handler)) { + $handler = new CallbackHandler($handler); + } + + if (!$handler instanceof HandlerInterface) { + throw new InvalidArgumentException( + "Handler must be a callable, or instance of " + . "Whoops\\Handler\\HandlerInterface" + ); + } + + return $handler; + } + + /** + * Echo something to the browser. + * + * @param string $output + * + * @return Run + */ + private function writeToOutputNow($output) + { + if ($this->sendHttpCode() && Misc::canSendHeaders()) { + $this->system->setHttpResponseCode( + $this->sendHttpCode() + ); + } + + echo $output; + + return $this; + } +} diff --git a/kirby/vendor/filp/whoops/src/Whoops/RunInterface.php b/kirby/vendor/filp/whoops/src/Whoops/RunInterface.php new file mode 100644 index 0000000..8162fe4 --- /dev/null +++ b/kirby/vendor/filp/whoops/src/Whoops/RunInterface.php @@ -0,0 +1,140 @@ + + */ + +namespace Whoops; + +use InvalidArgumentException; +use Whoops\Exception\ErrorException; +use Whoops\Handler\HandlerInterface; + +interface RunInterface +{ + const EXCEPTION_HANDLER = "handleException"; + const ERROR_HANDLER = "handleError"; + const SHUTDOWN_HANDLER = "handleShutdown"; + + /** + * Pushes a handler to the end of the stack + * + * @throws InvalidArgumentException If argument is not callable or instance of HandlerInterface + * @param Callable|HandlerInterface $handler + * @return Run + */ + public function pushHandler($handler); + + /** + * Removes the last handler in the stack and returns it. + * Returns null if there"s nothing else to pop. + * + * @return null|HandlerInterface + */ + public function popHandler(); + + /** + * Returns an array with all handlers, in the + * order they were added to the stack. + * + * @return array + */ + public function getHandlers(); + + /** + * Clears all handlers in the handlerStack, including + * the default PrettyPage handler. + * + * @return Run + */ + public function clearHandlers(); + + /** + * Registers this instance as an error handler. + * + * @return Run + */ + public function register(); + + /** + * Unregisters all handlers registered by this Whoops\Run instance + * + * @return Run + */ + public function unregister(); + + /** + * Should Whoops allow Handlers to force the script to quit? + * + * @param bool|int $exit + * @return bool + */ + public function allowQuit($exit = null); + + /** + * Silence particular errors in particular files + * + * @param array|string $patterns List or a single regex pattern to match + * @param int $levels Defaults to E_STRICT | E_DEPRECATED + * @return \Whoops\Run + */ + public function silenceErrorsInPaths($patterns, $levels = 10240); + + /** + * Should Whoops send HTTP error code to the browser if possible? + * Whoops will by default send HTTP code 500, but you may wish to + * use 502, 503, or another 5xx family code. + * + * @param bool|int $code + * @return int|false + */ + public function sendHttpCode($code = null); + + /** + * Should Whoops exit with a specific code on the CLI if possible? + * Whoops will exit with 1 by default, but you can specify something else. + * + * @param int $code + * @return int + */ + public function sendExitCode($code = null); + + /** + * Should Whoops push output directly to the client? + * If this is false, output will be returned by handleException + * + * @param bool|int $send + * @return bool + */ + public function writeToOutput($send = null); + + /** + * Handles an exception, ultimately generating a Whoops error + * page. + * + * @param \Throwable $exception + * @return string Output generated by handlers + */ + public function handleException($exception); + + /** + * Converts generic PHP errors to \ErrorException + * instances, before passing them off to be handled. + * + * This method MUST be compatible with set_error_handler. + * + * @param int $level + * @param string $message + * @param string $file + * @param int $line + * + * @return bool + * @throws ErrorException + */ + public function handleError($level, $message, $file = null, $line = null); + + /** + * Special case to deal with Fatal errors and the like. + */ + public function handleShutdown(); +} diff --git a/kirby/vendor/filp/whoops/src/Whoops/Util/HtmlDumperOutput.php b/kirby/vendor/filp/whoops/src/Whoops/Util/HtmlDumperOutput.php new file mode 100644 index 0000000..8c828fd --- /dev/null +++ b/kirby/vendor/filp/whoops/src/Whoops/Util/HtmlDumperOutput.php @@ -0,0 +1,36 @@ + + */ + +namespace Whoops\Util; + +/** + * Used as output callable for Symfony\Component\VarDumper\Dumper\HtmlDumper::dump() + * + * @see TemplateHelper::dump() + */ +class HtmlDumperOutput +{ + private $output; + + public function __invoke($line, $depth) + { + // A negative depth means "end of dump" + if ($depth >= 0) { + // Adds a two spaces indentation to the line + $this->output .= str_repeat(' ', $depth) . $line . "\n"; + } + } + + public function getOutput() + { + return $this->output; + } + + public function clear() + { + $this->output = null; + } +} diff --git a/kirby/vendor/filp/whoops/src/Whoops/Util/Misc.php b/kirby/vendor/filp/whoops/src/Whoops/Util/Misc.php new file mode 100644 index 0000000..001a687 --- /dev/null +++ b/kirby/vendor/filp/whoops/src/Whoops/Util/Misc.php @@ -0,0 +1,77 @@ + + */ + +namespace Whoops\Util; + +class Misc +{ + /** + * Can we at this point in time send HTTP headers? + * + * Currently this checks if we are even serving an HTTP request, + * as opposed to running from a command line. + * + * If we are serving an HTTP request, we check if it's not too late. + * + * @return bool + */ + public static function canSendHeaders() + { + return isset($_SERVER["REQUEST_URI"]) && !headers_sent(); + } + + public static function isAjaxRequest() + { + return ( + !empty($_SERVER['HTTP_X_REQUESTED_WITH']) + && strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) == 'xmlhttprequest'); + } + + /** + * Check, if possible, that this execution was triggered by a command line. + * @return bool + */ + public static function isCommandLine() + { + return PHP_SAPI == 'cli'; + } + + /** + * Translate ErrorException code into the represented constant. + * + * @param int $error_code + * @return string + */ + public static function translateErrorCode($error_code) + { + $constants = get_defined_constants(true); + if (array_key_exists('Core', $constants)) { + foreach ($constants['Core'] as $constant => $value) { + if (substr($constant, 0, 2) == 'E_' && $value == $error_code) { + return $constant; + } + } + } + return "E_UNKNOWN"; + } + + /** + * Determine if an error level is fatal (halts execution) + * + * @param int $level + * @return bool + */ + public static function isLevelFatal($level) + { + $errors = E_ERROR; + $errors |= E_PARSE; + $errors |= E_CORE_ERROR; + $errors |= E_CORE_WARNING; + $errors |= E_COMPILE_ERROR; + $errors |= E_COMPILE_WARNING; + return ($level & $errors) > 0; + } +} diff --git a/kirby/vendor/filp/whoops/src/Whoops/Util/SystemFacade.php b/kirby/vendor/filp/whoops/src/Whoops/Util/SystemFacade.php new file mode 100644 index 0000000..9eb0acf --- /dev/null +++ b/kirby/vendor/filp/whoops/src/Whoops/Util/SystemFacade.php @@ -0,0 +1,144 @@ + + */ + +namespace Whoops\Util; + +class SystemFacade +{ + /** + * Turns on output buffering. + * + * @return bool + */ + public function startOutputBuffering() + { + return ob_start(); + } + + /** + * @param callable $handler + * @param int $types + * + * @return callable|null + */ + public function setErrorHandler(callable $handler, $types = 'use-php-defaults') + { + // Since PHP 5.4 the constant E_ALL contains all errors (even E_STRICT) + if ($types === 'use-php-defaults') { + $types = E_ALL; + } + return set_error_handler($handler, $types); + } + + /** + * @param callable $handler + * + * @return callable|null + */ + public function setExceptionHandler(callable $handler) + { + return set_exception_handler($handler); + } + + /** + * @return void + */ + public function restoreExceptionHandler() + { + restore_exception_handler(); + } + + /** + * @return void + */ + public function restoreErrorHandler() + { + restore_error_handler(); + } + + /** + * @param callable $function + * + * @return void + */ + public function registerShutdownFunction(callable $function) + { + register_shutdown_function($function); + } + + /** + * @return string|false + */ + public function cleanOutputBuffer() + { + return ob_get_clean(); + } + + /** + * @return int + */ + public function getOutputBufferLevel() + { + return ob_get_level(); + } + + /** + * @return bool + */ + public function endOutputBuffering() + { + return ob_end_clean(); + } + + /** + * @return void + */ + public function flushOutputBuffer() + { + flush(); + } + + /** + * @return int + */ + public function getErrorReportingLevel() + { + return error_reporting(); + } + + /** + * @return array|null + */ + public function getLastError() + { + return error_get_last(); + } + + /** + * @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'); + } + + return http_response_code($httpCode); + } + + /** + * @param int $exitStatus + */ + public function stopExecution($exitStatus) + { + exit($exitStatus); + } +} diff --git a/kirby/vendor/filp/whoops/src/Whoops/Util/TemplateHelper.php b/kirby/vendor/filp/whoops/src/Whoops/Util/TemplateHelper.php new file mode 100644 index 0000000..9c7cec2 --- /dev/null +++ b/kirby/vendor/filp/whoops/src/Whoops/Util/TemplateHelper.php @@ -0,0 +1,352 @@ + + */ + +namespace Whoops\Util; + +use Symfony\Component\VarDumper\Caster\Caster; +use Symfony\Component\VarDumper\Cloner\AbstractCloner; +use Symfony\Component\VarDumper\Cloner\VarCloner; +use Symfony\Component\VarDumper\Dumper\HtmlDumper; +use Whoops\Exception\Frame; + +/** + * Exposes useful tools for working with/in templates + */ +class TemplateHelper +{ + /** + * An array of variables to be passed to all templates + * @var array + */ + private $variables = []; + + /** + * @var HtmlDumper + */ + private $htmlDumper; + + /** + * @var HtmlDumperOutput + */ + private $htmlDumperOutput; + + /** + * @var AbstractCloner + */ + private $cloner; + + /** + * @var string + */ + private $applicationRootPath; + + public function __construct() + { + // root path for ordinary composer projects + $this->applicationRootPath = dirname(dirname(dirname(dirname(dirname(dirname(__DIR__)))))); + } + + /** + * Escapes a string for output in an HTML document + * + * @param string $raw + * @return string + */ + public function escape($raw) + { + $flags = ENT_QUOTES; + + // HHVM has all constants defined, but only ENT_IGNORE + // works at the moment + if (defined("ENT_SUBSTITUTE") && !defined("HHVM_VERSION")) { + $flags |= ENT_SUBSTITUTE; + } else { + // This is for 5.3. + // The documentation warns of a potential security issue, + // but it seems it does not apply in our case, because + // we do not blacklist anything anywhere. + $flags |= ENT_IGNORE; + } + + $raw = str_replace(chr(9), ' ', $raw); + + return htmlspecialchars($raw, $flags, "UTF-8"); + } + + /** + * Escapes a string for output in an HTML document, but preserves + * URIs within it, and converts them to clickable anchor elements. + * + * @param string $raw + * @return string + */ + public function escapeButPreserveUris($raw) + { + $escaped = $this->escape($raw); + return preg_replace( + "@([A-z]+?://([-\w\.]+[-\w])+(:\d+)?(/([\w/_\.#-]*(\?\S+)?[^\.\s])?)?)@", + "
    $1", + $escaped + ); + } + + /** + * Makes sure that the given string breaks on the delimiter. + * + * @param string $delimiter + * @param string $s + * @return string + */ + public function breakOnDelimiter($delimiter, $s) + { + $parts = explode($delimiter, $s); + foreach ($parts as &$part) { + $part = '' . $part . ''; + } + + return implode($delimiter, $parts); + } + + /** + * Replace the part of the path that all files have in common. + * + * @param string $path + * @return string + */ + public function shorten($path) + { + if ($this->applicationRootPath != "/") { + $path = str_replace($this->applicationRootPath, '…', $path); + } + + return $path; + } + + private function getDumper() + { + if (!$this->htmlDumper && class_exists('Symfony\Component\VarDumper\Cloner\VarCloner')) { + $this->htmlDumperOutput = new HtmlDumperOutput(); + // re-use the same var-dumper instance, so it won't re-render the global styles/scripts on each dump. + $this->htmlDumper = new HtmlDumper($this->htmlDumperOutput); + + $styles = [ + 'default' => 'color:#FFFFFF; line-height:normal; font:12px "Inconsolata", "Fira Mono", "Source Code Pro", Monaco, Consolas, "Lucida Console", monospace !important; word-wrap: break-word; white-space: pre-wrap; position:relative; z-index:99999; word-break: normal', + 'num' => 'color:#BCD42A', + 'const' => 'color: #4bb1b1;', + 'str' => 'color:#BCD42A', + 'note' => 'color:#ef7c61', + 'ref' => 'color:#A0A0A0', + 'public' => 'color:#FFFFFF', + 'protected' => 'color:#FFFFFF', + 'private' => 'color:#FFFFFF', + 'meta' => 'color:#FFFFFF', + 'key' => 'color:#BCD42A', + 'index' => 'color:#ef7c61', + ]; + $this->htmlDumper->setStyles($styles); + } + + return $this->htmlDumper; + } + + /** + * Format the given value into a human readable string. + * + * @param mixed $value + * @return string + */ + public function dump($value) + { + $dumper = $this->getDumper(); + + if ($dumper) { + // re-use the same DumpOutput instance, so it won't re-render the global styles/scripts on each dump. + // exclude verbose information (e.g. exception stack traces) + if (class_exists('Symfony\Component\VarDumper\Caster\Caster')) { + $cloneVar = $this->getCloner()->cloneVar($value, Caster::EXCLUDE_VERBOSE); + // Symfony VarDumper 2.6 Caster class dont exist. + } else { + $cloneVar = $this->getCloner()->cloneVar($value); + } + + $dumper->dump( + $cloneVar, + $this->htmlDumperOutput + ); + + $output = $this->htmlDumperOutput->getOutput(); + $this->htmlDumperOutput->clear(); + + return $output; + } + + return htmlspecialchars(print_r($value, true)); + } + + /** + * Format the args of the given Frame as a human readable html string + * + * @param Frame $frame + * @return string the rendered html + */ + public function dumpArgs(Frame $frame) + { + // we support frame args only when the optional dumper is available + if (!$this->getDumper()) { + return ''; + } + + $html = ''; + $numFrames = count($frame->getArgs()); + + if ($numFrames > 0) { + $html = '
      '; + foreach ($frame->getArgs() as $j => $frameArg) { + $html .= '
    1. '. $this->dump($frameArg) .'
    2. '; + } + $html .= '
    '; + } + + return $html; + } + + /** + * Convert a string to a slug version of itself + * + * @param string $original + * @return string + */ + public function slug($original) + { + $slug = str_replace(" ", "-", $original); + $slug = preg_replace('/[^\w\d\-\_]/i', '', $slug); + return strtolower($slug); + } + + /** + * Given a template path, render it within its own scope. This + * method also accepts an array of additional variables to be + * passed to the template. + * + * @param string $template + * @param array $additionalVariables + */ + public function render($template, array $additionalVariables = null) + { + $variables = $this->getVariables(); + + // Pass the helper to the template: + $variables["tpl"] = $this; + + if ($additionalVariables !== null) { + $variables = array_replace($variables, $additionalVariables); + } + + call_user_func(function () { + extract(func_get_arg(1)); + require func_get_arg(0); + }, $template, $variables); + } + + /** + * Sets the variables to be passed to all templates rendered + * by this template helper. + * + * @param array $variables + */ + public function setVariables(array $variables) + { + $this->variables = $variables; + } + + /** + * Sets a single template variable, by its name: + * + * @param string $variableName + * @param mixed $variableValue + */ + public function setVariable($variableName, $variableValue) + { + $this->variables[$variableName] = $variableValue; + } + + /** + * Gets a single template variable, by its name, or + * $defaultValue if the variable does not exist + * + * @param string $variableName + * @param mixed $defaultValue + * @return mixed + */ + public function getVariable($variableName, $defaultValue = null) + { + return isset($this->variables[$variableName]) ? + $this->variables[$variableName] : $defaultValue; + } + + /** + * Unsets a single template variable, by its name + * + * @param string $variableName + */ + public function delVariable($variableName) + { + unset($this->variables[$variableName]); + } + + /** + * Returns all variables for this helper + * + * @return array + */ + public function getVariables() + { + return $this->variables; + } + + /** + * Set the cloner used for dumping variables. + * + * @param AbstractCloner $cloner + */ + public function setCloner($cloner) + { + $this->cloner = $cloner; + } + + /** + * Get the cloner used for dumping variables. + * + * @return AbstractCloner + */ + public function getCloner() + { + if (!$this->cloner) { + $this->cloner = new VarCloner(); + } + return $this->cloner; + } + + /** + * Set the application root path. + * + * @param string $applicationRootPath + */ + public function setApplicationRootPath($applicationRootPath) + { + $this->applicationRootPath = $applicationRootPath; + } + + /** + * Return the application root path. + * + * @return string + */ + public function getApplicationRootPath() + { + return $this->applicationRootPath; + } +} diff --git a/kirby/vendor/laminas/laminas-escaper/COPYRIGHT.md b/kirby/vendor/laminas/laminas-escaper/COPYRIGHT.md new file mode 100644 index 0000000..0a8cccc --- /dev/null +++ b/kirby/vendor/laminas/laminas-escaper/COPYRIGHT.md @@ -0,0 +1 @@ +Copyright (c) 2020 Laminas Project a Series of LF Projects, LLC. (https://getlaminas.org/) diff --git a/kirby/vendor/laminas/laminas-escaper/LICENSE.md b/kirby/vendor/laminas/laminas-escaper/LICENSE.md new file mode 100644 index 0000000..10b40f1 --- /dev/null +++ b/kirby/vendor/laminas/laminas-escaper/LICENSE.md @@ -0,0 +1,26 @@ +Copyright (c) 2020 Laminas Project a Series of LF Projects, LLC. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +- Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +- Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +- Neither the name of Laminas Foundation nor the names of its contributors may + be used to endorse or promote products derived from this software without + specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/kirby/vendor/laminas/laminas-escaper/composer.json b/kirby/vendor/laminas/laminas-escaper/composer.json new file mode 100644 index 0000000..d01ae2d --- /dev/null +++ b/kirby/vendor/laminas/laminas-escaper/composer.json @@ -0,0 +1,61 @@ +{ + "name": "laminas/laminas-escaper", + "description": "Securely and safely escape HTML, HTML attributes, JavaScript, CSS, and URLs", + "license": "BSD-3-Clause", + "keywords": [ + "laminas", + "escaper" + ], + "homepage": "https://laminas.dev", + "support": { + "docs": "https://docs.laminas.dev/laminas-escaper/", + "issues": "https://github.com/laminas/laminas-escaper/issues", + "source": "https://github.com/laminas/laminas-escaper", + "rss": "https://github.com/laminas/laminas-escaper/releases.atom", + "chat": "https://laminas.dev/chat", + "forum": "https://discourse.laminas.dev" + }, + "config": { + "sort-packages": true + }, + "extra": { + }, + "require": { + "php": "^7.3 || ~8.0.0", + "laminas/laminas-zendframework-bridge": "^1.0" + }, + "suggest": { + "ext-iconv": "*", + "ext-mbstring": "*" + }, + "require-dev": { + "laminas/laminas-coding-standard": "~1.0.0", + "phpunit/phpunit": "^9.3", + "psalm/plugin-phpunit": "^0.12.2", + "vimeo/psalm": "^3.16" + }, + "autoload": { + "psr-4": { + "Laminas\\Escaper\\": "src/" + } + }, + "autoload-dev": { + "psr-4": { + "LaminasTest\\Escaper\\": "test/" + } + }, + "scripts": { + "check": [ + "@cs-check", + "@test" + ], + "cs-check": "phpcs", + "cs-fix": "phpcbf", + "static-analysis": "psalm --shepherd --stats", + "test": "phpunit --colors=always", + "test-coverage": "phpunit --colors=always --coverage-clover clover.xml" + }, + "replace": { + "zendframework/zend-escaper": "^2.6.1" + } +} diff --git a/kirby/vendor/laminas/laminas-escaper/src/Escaper.php b/kirby/vendor/laminas/laminas-escaper/src/Escaper.php new file mode 100644 index 0000000..9f903a5 --- /dev/null +++ b/kirby/vendor/laminas/laminas-escaper/src/Escaper.php @@ -0,0 +1,391 @@ + 'quot', // quotation mark + 38 => 'amp', // ampersand + 60 => 'lt', // less-than sign + 62 => 'gt', // greater-than sign + ]; + + /** + * Current encoding for escaping. If not UTF-8, we convert strings from this encoding + * pre-escaping and back to this encoding post-escaping. + * + * @var string + */ + protected $encoding = 'utf-8'; + + /** + * Holds the value of the special flags passed as second parameter to + * htmlspecialchars(). + * + * @var int + */ + protected $htmlSpecialCharsFlags; + + /** + * Static Matcher which escapes characters for HTML Attribute contexts + * + * @var callable + */ + protected $htmlAttrMatcher; + + /** + * Static Matcher which escapes characters for Javascript contexts + * + * @var callable + */ + protected $jsMatcher; + + /** + * Static Matcher which escapes characters for CSS Attribute contexts + * + * @var callable + */ + protected $cssMatcher; + + /** + * List of all encoding supported by this class + * + * @var array + */ + protected $supportedEncodings = [ + 'iso-8859-1', 'iso8859-1', 'iso-8859-5', 'iso8859-5', + 'iso-8859-15', 'iso8859-15', 'utf-8', 'cp866', + 'ibm866', '866', 'cp1251', 'windows-1251', + 'win-1251', '1251', 'cp1252', 'windows-1252', + '1252', 'koi8-r', 'koi8-ru', 'koi8r', + 'big5', '950', 'gb2312', '936', + 'big5-hkscs', 'shift_jis', 'sjis', 'sjis-win', + 'cp932', '932', 'euc-jp', 'eucjp', + 'eucjp-win', 'macroman' + ]; + + /** + * Constructor: Single parameter allows setting of global encoding for use by + * the current object. + * + * @param string $encoding + * @throws Exception\InvalidArgumentException + */ + public function __construct($encoding = null) + { + if ($encoding !== null) { + if (! is_string($encoding)) { + throw new Exception\InvalidArgumentException( + get_class($this) . ' constructor parameter must be a string, received ' . gettype($encoding) + ); + } + if ($encoding === '') { + throw new Exception\InvalidArgumentException( + get_class($this) . ' constructor parameter does not allow a blank value' + ); + } + + $encoding = strtolower($encoding); + if (! in_array($encoding, $this->supportedEncodings)) { + throw new Exception\InvalidArgumentException( + 'Value of \'' . $encoding . '\' passed to ' . get_class($this) + . ' constructor parameter is invalid. Provide an encoding supported by htmlspecialchars()' + ); + } + + $this->encoding = $encoding; + } + + // We take advantage of ENT_SUBSTITUTE flag to correctly deal with invalid UTF-8 sequences. + $this->htmlSpecialCharsFlags = ENT_QUOTES | ENT_SUBSTITUTE; + + // set matcher callbacks + $this->htmlAttrMatcher = [$this, 'htmlAttrMatcher']; + $this->jsMatcher = [$this, 'jsMatcher']; + $this->cssMatcher = [$this, 'cssMatcher']; + } + + /** + * Return the encoding that all output/input is expected to be encoded in. + * + * @return string + */ + public function getEncoding() + { + return $this->encoding; + } + + /** + * Escape a string for the HTML Body context where there are very few characters + * of special meaning. Internally this will use htmlspecialchars(). + * + * @param string $string + * @return string + */ + public function escapeHtml($string) + { + return htmlspecialchars($string, $this->htmlSpecialCharsFlags, $this->encoding); + } + + /** + * Escape a string for the HTML Attribute context. We use an extended set of characters + * to escape that are not covered by htmlspecialchars() to cover cases where an attribute + * might be unquoted or quoted illegally (e.g. backticks are valid quotes for IE). + * + * @param string $string + * @return string + */ + public function escapeHtmlAttr($string) + { + $string = $this->toUtf8($string); + if ($string === '' || ctype_digit($string)) { + return $string; + } + + $result = preg_replace_callback('/[^a-z0-9,\.\-_]/iSu', $this->htmlAttrMatcher, $string); + return $this->fromUtf8($result); + } + + /** + * Escape a string for the Javascript context. This does not use json_encode(). An extended + * set of characters are escaped beyond ECMAScript's rules for Javascript literal string + * escaping in order to prevent misinterpretation of Javascript as HTML leading to the + * injection of special characters and entities. The escaping used should be tolerant + * of cases where HTML escaping was not applied on top of Javascript escaping correctly. + * Backslash escaping is not used as it still leaves the escaped character as-is and so + * is not useful in a HTML context. + * + * @param string $string + * @return string + */ + public function escapeJs($string) + { + $string = $this->toUtf8($string); + if ($string === '' || ctype_digit($string)) { + return $string; + } + + $result = preg_replace_callback('/[^a-z0-9,\._]/iSu', $this->jsMatcher, $string); + return $this->fromUtf8($result); + } + + /** + * Escape a string for the URI or Parameter contexts. This should not be used to escape + * an entire URI - only a subcomponent being inserted. The function is a simple proxy + * to rawurlencode() which now implements RFC 3986 since PHP 5.3 completely. + * + * @param string $string + * @return string + */ + public function escapeUrl($string) + { + return rawurlencode($string); + } + + /** + * Escape a string for the CSS context. CSS escaping can be applied to any string being + * inserted into CSS and escapes everything except alphanumerics. + * + * @param string $string + * @return string + */ + public function escapeCss($string) + { + $string = $this->toUtf8($string); + if ($string === '' || ctype_digit($string)) { + return $string; + } + + $result = preg_replace_callback('/[^a-z0-9]/iSu', $this->cssMatcher, $string); + return $this->fromUtf8($result); + } + + /** + * Callback function for preg_replace_callback that applies HTML Attribute + * escaping to all matches. + * + * @param array $matches + * @return string + */ + protected function htmlAttrMatcher($matches) + { + $chr = $matches[0]; + $ord = ord($chr); + + /** + * The following replaces characters undefined in HTML with the + * hex entity for the Unicode replacement character. + */ + if (($ord <= 0x1f && $chr != "\t" && $chr != "\n" && $chr != "\r") + || ($ord >= 0x7f && $ord <= 0x9f) + ) { + return '�'; + } + + /** + * Check if the current character to escape has a name entity we should + * replace it with while grabbing the integer value of the character. + */ + if (strlen($chr) > 1) { + $chr = $this->convertEncoding($chr, 'UTF-32BE', 'UTF-8'); + } + + $hex = bin2hex($chr); + $ord = hexdec($hex); + if (isset(static::$htmlNamedEntityMap[$ord])) { + return '&' . static::$htmlNamedEntityMap[$ord] . ';'; + } + + /** + * Per OWASP recommendations, we'll use upper hex entities + * for any other characters where a named entity does not exist. + */ + if ($ord > 255) { + return sprintf('&#x%04X;', $ord); + } + return sprintf('&#x%02X;', $ord); + } + + /** + * Callback function for preg_replace_callback that applies Javascript + * escaping to all matches. + * + * @param array $matches + * @return string + */ + protected function jsMatcher($matches) + { + $chr = $matches[0]; + if (strlen($chr) == 1) { + return sprintf('\\x%02X', ord($chr)); + } + $chr = $this->convertEncoding($chr, 'UTF-16BE', 'UTF-8'); + $hex = strtoupper(bin2hex($chr)); + if (strlen($hex) <= 4) { + return sprintf('\\u%04s', $hex); + } + $highSurrogate = substr($hex, 0, 4); + $lowSurrogate = substr($hex, 4, 4); + return sprintf('\\u%04s\\u%04s', $highSurrogate, $lowSurrogate); + } + + /** + * Callback function for preg_replace_callback that applies CSS + * escaping to all matches. + * + * @param array $matches + * @return string + */ + protected function cssMatcher($matches) + { + $chr = $matches[0]; + if (strlen($chr) == 1) { + $ord = ord($chr); + } else { + $chr = $this->convertEncoding($chr, 'UTF-32BE', 'UTF-8'); + $ord = hexdec(bin2hex($chr)); + } + return sprintf('\\%X ', $ord); + } + + /** + * Converts a string to UTF-8 from the base encoding. The base encoding is set via this + * class' constructor. + * + * @param string $string + * @throws Exception\RuntimeException + * @return string + */ + protected function toUtf8($string) + { + if ($this->getEncoding() === 'utf-8') { + $result = $string; + } else { + $result = $this->convertEncoding($string, 'UTF-8', $this->getEncoding()); + } + + if (! $this->isUtf8($result)) { + throw new Exception\RuntimeException( + sprintf('String to be escaped was not valid UTF-8 or could not be converted: %s', $result) + ); + } + + return $result; + } + + /** + * Converts a string from UTF-8 to the base encoding. The base encoding is set via this + * class' constructor. + * @param string $string + * @return string + */ + protected function fromUtf8($string) + { + if ($this->getEncoding() === 'utf-8') { + return $string; + } + + return $this->convertEncoding($string, $this->getEncoding(), 'UTF-8'); + } + + /** + * Checks if a given string appears to be valid UTF-8 or not. + * + * @param string $string + * @return bool + */ + protected function isUtf8($string) + { + return ($string === '' || preg_match('/^./su', $string)); + } + + /** + * Encoding conversion helper which wraps iconv and mbstring where they exist or throws + * and exception where neither is available. + * + * @param string $string + * @param string $to + * @param array|string $from + * @throws Exception\RuntimeException + * @return string + */ + protected function convertEncoding($string, $to, $from) + { + if (function_exists('iconv')) { + $result = iconv($from, $to, $string); + } elseif (function_exists('mb_convert_encoding')) { + $result = mb_convert_encoding($string, $to, $from); + } else { + throw new Exception\RuntimeException( + get_class($this) + . ' requires either the iconv or mbstring extension to be installed' + . ' when escaping for non UTF-8 strings.' + ); + } + + if ($result === false) { + return ''; // return non-fatal blank string on encoding errors from users + } + return $result; + } +} diff --git a/kirby/vendor/laminas/laminas-escaper/src/Exception/ExceptionInterface.php b/kirby/vendor/laminas/laminas-escaper/src/Exception/ExceptionInterface.php new file mode 100644 index 0000000..7ebe04e --- /dev/null +++ b/kirby/vendor/laminas/laminas-escaper/src/Exception/ExceptionInterface.php @@ -0,0 +1,13 @@ + 'zendframework/zendframework', + 'zend-developer-tools/toolbar/bjy' => 'zend-developer-tools/toolbar/bjy', + 'zend-developer-tools/toolbar/doctrine' => 'zend-developer-tools/toolbar/doctrine', + + // NAMESPACES + // Zend Framework components + 'Zend\\AuraDi\\Config' => 'Laminas\\AuraDi\\Config', + 'Zend\\Authentication' => 'Laminas\\Authentication', + 'Zend\\Barcode' => 'Laminas\\Barcode', + 'Zend\\Cache' => 'Laminas\\Cache', + 'Zend\\Captcha' => 'Laminas\\Captcha', + 'Zend\\Code' => 'Laminas\\Code', + 'ZendCodingStandard\\Sniffs' => 'LaminasCodingStandard\\Sniffs', + 'ZendCodingStandard\\Utils' => 'LaminasCodingStandard\\Utils', + 'Zend\\ComponentInstaller' => 'Laminas\\ComponentInstaller', + 'Zend\\Config' => 'Laminas\\Config', + 'Zend\\ConfigAggregator' => 'Laminas\\ConfigAggregator', + 'Zend\\ConfigAggregatorModuleManager' => 'Laminas\\ConfigAggregatorModuleManager', + 'Zend\\ConfigAggregatorParameters' => 'Laminas\\ConfigAggregatorParameters', + 'Zend\\Console' => 'Laminas\\Console', + 'Zend\\ContainerConfigTest' => 'Laminas\\ContainerConfigTest', + 'Zend\\Crypt' => 'Laminas\\Crypt', + 'Zend\\Db' => 'Laminas\\Db', + 'ZendDeveloperTools' => 'Laminas\\DeveloperTools', + 'Zend\\Di' => 'Laminas\\Di', + 'Zend\\Diactoros' => 'Laminas\\Diactoros', + 'ZendDiagnostics\\Check' => 'Laminas\\Diagnostics\\Check', + 'ZendDiagnostics\\Result' => 'Laminas\\Diagnostics\\Result', + 'ZendDiagnostics\\Runner' => 'Laminas\\Diagnostics\\Runner', + 'Zend\\Dom' => 'Laminas\\Dom', + 'Zend\\Escaper' => 'Laminas\\Escaper', + 'Zend\\EventManager' => 'Laminas\\EventManager', + 'Zend\\Feed' => 'Laminas\\Feed', + 'Zend\\File' => 'Laminas\\File', + 'Zend\\Filter' => 'Laminas\\Filter', + 'Zend\\Form' => 'Laminas\\Form', + 'Zend\\Http' => 'Laminas\\Http', + 'Zend\\HttpHandlerRunner' => 'Laminas\\HttpHandlerRunner', + 'Zend\\Hydrator' => 'Laminas\\Hydrator', + 'Zend\\I18n' => 'Laminas\\I18n', + 'Zend\\InputFilter' => 'Laminas\\InputFilter', + 'Zend\\Json' => 'Laminas\\Json', + 'Zend\\Ldap' => 'Laminas\\Ldap', + 'Zend\\Loader' => 'Laminas\\Loader', + 'Zend\\Log' => 'Laminas\\Log', + 'Zend\\Mail' => 'Laminas\\Mail', + 'Zend\\Math' => 'Laminas\\Math', + 'Zend\\Memory' => 'Laminas\\Memory', + 'Zend\\Mime' => 'Laminas\\Mime', + 'Zend\\ModuleManager' => 'Laminas\\ModuleManager', + 'Zend\\Mvc' => 'Laminas\\Mvc', + 'Zend\\Navigation' => 'Laminas\\Navigation', + 'Zend\\Paginator' => 'Laminas\\Paginator', + 'Zend\\Permissions' => 'Laminas\\Permissions', + 'Zend\\Pimple\\Config' => 'Laminas\\Pimple\\Config', + 'Zend\\ProblemDetails' => 'Mezzio\\ProblemDetails', + 'Zend\\ProgressBar' => 'Laminas\\ProgressBar', + 'Zend\\Psr7Bridge' => 'Laminas\\Psr7Bridge', + 'Zend\\Router' => 'Laminas\\Router', + 'Zend\\Serializer' => 'Laminas\\Serializer', + 'Zend\\Server' => 'Laminas\\Server', + 'Zend\\ServiceManager' => 'Laminas\\ServiceManager', + 'ZendService\\ReCaptcha' => 'Laminas\\ReCaptcha', + 'ZendService\\Twitter' => 'Laminas\\Twitter', + 'Zend\\Session' => 'Laminas\\Session', + 'Zend\\SkeletonInstaller' => 'Laminas\\SkeletonInstaller', + 'Zend\\Soap' => 'Laminas\\Soap', + 'Zend\\Stdlib' => 'Laminas\\Stdlib', + 'Zend\\Stratigility' => 'Laminas\\Stratigility', + 'Zend\\Tag' => 'Laminas\\Tag', + 'Zend\\Test' => 'Laminas\\Test', + 'Zend\\Text' => 'Laminas\\Text', + 'Zend\\Uri' => 'Laminas\\Uri', + 'Zend\\Validator' => 'Laminas\\Validator', + 'Zend\\View' => 'Laminas\\View', + 'ZendXml' => 'Laminas\\Xml', + 'Zend\\Xml2Json' => 'Laminas\\Xml2Json', + 'Zend\\XmlRpc' => 'Laminas\\XmlRpc', + 'ZendOAuth' => 'Laminas\\OAuth', + + // class ZendAcl in zend-expressive-authorization-acl + 'ZendAcl' => 'LaminasAcl', + 'Zend\\Expressive\\Authorization\\Acl\\ZendAcl' => 'Mezzio\\Authorization\\Acl\\LaminasAcl', + // class ZendHttpClientDecorator in zend-feed + 'ZendHttp' => 'LaminasHttp', + // class ZendModuleProvider in zend-config-aggregator-modulemanager + 'ZendModule' => 'LaminasModule', + // class ZendRbac in zend-expressive-authorization-rbac + 'ZendRbac' => 'LaminasRbac', + 'Zend\\Expressive\\Authorization\\Rbac\\ZendRbac' => 'Mezzio\\Authorization\\Rbac\\LaminasRbac', + // class ZendRouter in zend-expressive-router-zendrouter + 'ZendRouter' => 'LaminasRouter', + 'Zend\\Expressive\\Router\\ZendRouter' => 'Mezzio\\Router\\LaminasRouter', + // class ZendViewRenderer in zend-expressive-zendviewrenderer + 'ZendViewRenderer' => 'LaminasViewRenderer', + 'Zend\\Expressive\\ZendView\\ZendViewRenderer' => 'Mezzio\\LaminasView\\LaminasViewRenderer', + 'a\\Zend' => 'a\\Zend', + 'b\\Zend' => 'b\\Zend', + 'c\\Zend' => 'c\\Zend', + 'd\\Zend' => 'd\\Zend', + 'e\\Zend' => 'e\\Zend', + 'f\\Zend' => 'f\\Zend', + 'g\\Zend' => 'g\\Zend', + 'h\\Zend' => 'h\\Zend', + 'i\\Zend' => 'i\\Zend', + 'j\\Zend' => 'j\\Zend', + 'k\\Zend' => 'k\\Zend', + 'l\\Zend' => 'l\\Zend', + 'm\\Zend' => 'm\\Zend', + 'n\\Zend' => 'n\\Zend', + 'o\\Zend' => 'o\\Zend', + 'p\\Zend' => 'p\\Zend', + 'q\\Zend' => 'q\\Zend', + 'r\\Zend' => 'r\\Zend', + 's\\Zend' => 's\\Zend', + 't\\Zend' => 't\\Zend', + 'u\\Zend' => 'u\\Zend', + 'v\\Zend' => 'v\\Zend', + 'w\\Zend' => 'w\\Zend', + 'x\\Zend' => 'x\\Zend', + 'y\\Zend' => 'y\\Zend', + 'z\\Zend' => 'z\\Zend', + + // Expressive + 'Zend\\Expressive' => 'Mezzio', + 'ZendAuthentication' => 'LaminasAuthentication', + 'ZendAcl' => 'LaminasAcl', + 'ZendRbac' => 'LaminasRbac', + 'ZendRouter' => 'LaminasRouter', + 'ExpressiveUrlGenerator' => 'MezzioUrlGenerator', + 'ExpressiveInstaller' => 'MezzioInstaller', + + // Apigility + 'ZF\\Apigility' => 'Laminas\\ApiTools', + 'ZF\\ApiProblem' => 'Laminas\\ApiTools\\ApiProblem', + 'ZF\\AssetManager' => 'Laminas\\ApiTools\\AssetManager', + 'ZF\\ComposerAutoloading' => 'Laminas\\ComposerAutoloading', + 'ZF\\Configuration' => 'Laminas\\ApiTools\\Configuration', + 'ZF\\ContentNegotiation' => 'Laminas\\ApiTools\\ContentNegotiation', + 'ZF\\ContentValidation' => 'Laminas\\ApiTools\\ContentValidation', + 'ZF\\DevelopmentMode' => 'Laminas\\DevelopmentMode', + 'ZF\\Doctrine\\QueryBuilder' => 'Laminas\\ApiTools\\Doctrine\\QueryBuilder', + 'ZF\\Hal' => 'Laminas\\ApiTools\\Hal', + 'ZF\\HttpCache' => 'Laminas\\ApiTools\\HttpCache', + 'ZF\\MvcAuth' => 'Laminas\\ApiTools\\MvcAuth', + 'ZF\\OAuth2' => 'Laminas\\ApiTools\\OAuth2', + 'ZF\\Rest' => 'Laminas\\ApiTools\\Rest', + 'ZF\\Rpc' => 'Laminas\\ApiTools\\Rpc', + 'ZF\\Versioning' => 'Laminas\\ApiTools\\Versioning', + 'a\\ZF' => 'a\\ZF', + 'b\\ZF' => 'b\\ZF', + 'c\\ZF' => 'c\\ZF', + 'd\\ZF' => 'd\\ZF', + 'e\\ZF' => 'e\\ZF', + 'f\\ZF' => 'f\\ZF', + 'g\\ZF' => 'g\\ZF', + 'h\\ZF' => 'h\\ZF', + 'i\\ZF' => 'i\\ZF', + 'j\\ZF' => 'j\\ZF', + 'k\\ZF' => 'k\\ZF', + 'l\\ZF' => 'l\\ZF', + 'm\\ZF' => 'm\\ZF', + 'n\\ZF' => 'n\\ZF', + 'o\\ZF' => 'o\\ZF', + 'p\\ZF' => 'p\\ZF', + 'q\\ZF' => 'q\\ZF', + 'r\\ZF' => 'r\\ZF', + 's\\ZF' => 's\\ZF', + 't\\ZF' => 't\\ZF', + 'u\\ZF' => 'u\\ZF', + 'v\\ZF' => 'v\\ZF', + 'w\\ZF' => 'w\\ZF', + 'x\\ZF' => 'x\\ZF', + 'y\\ZF' => 'y\\ZF', + 'z\\ZF' => 'z\\ZF', + + 'ApigilityModuleInterface' => 'ApiToolsModuleInterface', + 'ApigilityProviderInterface' => 'ApiToolsProviderInterface', + 'ApigilityVersionController' => 'ApiToolsVersionController', + + // PACKAGES + // ZF components, MVC + 'zendframework/skeleton-application' => 'laminas/skeleton-application', + 'zendframework/zend-auradi-config' => 'laminas/laminas-auradi-config', + 'zendframework/zend-authentication' => 'laminas/laminas-authentication', + 'zendframework/zend-barcode' => 'laminas/laminas-barcode', + 'zendframework/zend-cache' => 'laminas/laminas-cache', + 'zendframework/zend-captcha' => 'laminas/laminas-captcha', + 'zendframework/zend-code' => 'laminas/laminas-code', + 'zendframework/zend-coding-standard' => 'laminas/laminas-coding-standard', + 'zendframework/zend-component-installer' => 'laminas/laminas-component-installer', + 'zendframework/zend-composer-autoloading' => 'laminas/laminas-composer-autoloading', + 'zendframework/zend-config-aggregator' => 'laminas/laminas-config-aggregator', + 'zendframework/zend-config' => 'laminas/laminas-config', + 'zendframework/zend-console' => 'laminas/laminas-console', + 'zendframework/zend-container-config-test' => 'laminas/laminas-container-config-test', + 'zendframework/zend-crypt' => 'laminas/laminas-crypt', + 'zendframework/zend-db' => 'laminas/laminas-db', + 'zendframework/zend-developer-tools' => 'laminas/laminas-developer-tools', + 'zendframework/zend-diactoros' => 'laminas/laminas-diactoros', + 'zendframework/zenddiagnostics' => 'laminas/laminas-diagnostics', + 'zendframework/zend-di' => 'laminas/laminas-di', + 'zendframework/zend-dom' => 'laminas/laminas-dom', + 'zendframework/zend-escaper' => 'laminas/laminas-escaper', + 'zendframework/zend-eventmanager' => 'laminas/laminas-eventmanager', + 'zendframework/zend-feed' => 'laminas/laminas-feed', + 'zendframework/zend-file' => 'laminas/laminas-file', + 'zendframework/zend-filter' => 'laminas/laminas-filter', + 'zendframework/zend-form' => 'laminas/laminas-form', + 'zendframework/zend-httphandlerrunner' => 'laminas/laminas-httphandlerrunner', + 'zendframework/zend-http' => 'laminas/laminas-http', + 'zendframework/zend-hydrator' => 'laminas/laminas-hydrator', + 'zendframework/zend-i18n' => 'laminas/laminas-i18n', + 'zendframework/zend-i18n-resources' => 'laminas/laminas-i18n-resources', + 'zendframework/zend-inputfilter' => 'laminas/laminas-inputfilter', + 'zendframework/zend-json' => 'laminas/laminas-json', + 'zendframework/zend-json-server' => 'laminas/laminas-json-server', + 'zendframework/zend-ldap' => 'laminas/laminas-ldap', + 'zendframework/zend-loader' => 'laminas/laminas-loader', + 'zendframework/zend-log' => 'laminas/laminas-log', + 'zendframework/zend-mail' => 'laminas/laminas-mail', + 'zendframework/zend-math' => 'laminas/laminas-math', + 'zendframework/zend-memory' => 'laminas/laminas-memory', + 'zendframework/zend-mime' => 'laminas/laminas-mime', + 'zendframework/zend-modulemanager' => 'laminas/laminas-modulemanager', + 'zendframework/zend-mvc' => 'laminas/laminas-mvc', + 'zendframework/zend-navigation' => 'laminas/laminas-navigation', + 'zendframework/zend-oauth' => 'laminas/laminas-oauth', + 'zendframework/zend-paginator' => 'laminas/laminas-paginator', + 'zendframework/zend-permissions-acl' => 'laminas/laminas-permissions-acl', + 'zendframework/zend-permissions-rbac' => 'laminas/laminas-permissions-rbac', + 'zendframework/zend-pimple-config' => 'laminas/laminas-pimple-config', + 'zendframework/zend-progressbar' => 'laminas/laminas-progressbar', + 'zendframework/zend-psr7bridge' => 'laminas/laminas-psr7bridge', + 'zendframework/zend-recaptcha' => 'laminas/laminas-recaptcha', + 'zendframework/zend-router' => 'laminas/laminas-router', + 'zendframework/zend-serializer' => 'laminas/laminas-serializer', + 'zendframework/zend-server' => 'laminas/laminas-server', + 'zendframework/zend-servicemanager' => 'laminas/laminas-servicemanager', + 'zendframework/zendservice-recaptcha' => 'laminas/laminas-recaptcha', + 'zendframework/zendservice-twitter' => 'laminas/laminas-twitter', + 'zendframework/zend-session' => 'laminas/laminas-session', + 'zendframework/zend-skeleton-installer' => 'laminas/laminas-skeleton-installer', + 'zendframework/zend-soap' => 'laminas/laminas-soap', + 'zendframework/zend-stdlib' => 'laminas/laminas-stdlib', + 'zendframework/zend-stratigility' => 'laminas/laminas-stratigility', + 'zendframework/zend-tag' => 'laminas/laminas-tag', + 'zendframework/zend-test' => 'laminas/laminas-test', + 'zendframework/zend-text' => 'laminas/laminas-text', + 'zendframework/zend-uri' => 'laminas/laminas-uri', + 'zendframework/zend-validator' => 'laminas/laminas-validator', + 'zendframework/zend-view' => 'laminas/laminas-view', + 'zendframework/zend-xml2json' => 'laminas/laminas-xml2json', + 'zendframework/zend-xml' => 'laminas/laminas-xml', + 'zendframework/zend-xmlrpc' => 'laminas/laminas-xmlrpc', + + // Expressive packages + 'zendframework/zend-expressive' => 'mezzio/mezzio', + 'zendframework/zend-expressive-zendrouter' => 'mezzio/mezzio-laminasrouter', + 'zendframework/zend-problem-details' => 'mezzio/mezzio-problem-details', + 'zendframework/zend-expressive-zendviewrenderer' => 'mezzio/mezzio-laminasviewrenderer', + + // Apigility packages + 'zfcampus/apigility-documentation' => 'laminas-api-tools/documentation', + 'zfcampus/statuslib-example' => 'laminas-api-tools/statuslib-example', + 'zfcampus/zf-apigility' => 'laminas-api-tools/api-tools', + 'zfcampus/zf-api-problem' => 'laminas-api-tools/api-tools-api-problem', + 'zfcampus/zf-asset-manager' => 'laminas-api-tools/api-tools-asset-manager', + 'zfcampus/zf-configuration' => 'laminas-api-tools/api-tools-configuration', + 'zfcampus/zf-content-negotiation' => 'laminas-api-tools/api-tools-content-negotiation', + 'zfcampus/zf-content-validation' => 'laminas-api-tools/api-tools-content-validation', + 'zfcampus/zf-development-mode' => 'laminas/laminas-development-mode', + 'zfcampus/zf-doctrine-querybuilder' => 'laminas-api-tools/api-tools-doctrine-querybuilder', + 'zfcampus/zf-hal' => 'laminas-api-tools/api-tools-hal', + 'zfcampus/zf-http-cache' => 'laminas-api-tools/api-tools-http-cache', + 'zfcampus/zf-mvc-auth' => 'laminas-api-tools/api-tools-mvc-auth', + 'zfcampus/zf-oauth2' => 'laminas-api-tools/api-tools-oauth2', + 'zfcampus/zf-rest' => 'laminas-api-tools/api-tools-rest', + 'zfcampus/zf-rpc' => 'laminas-api-tools/api-tools-rpc', + 'zfcampus/zf-versioning' => 'laminas-api-tools/api-tools-versioning', + + // CONFIG KEYS, SCRIPT NAMES, ETC + // ZF components + '::fromZend' => '::fromLaminas', // psr7bridge + '::toZend' => '::toLaminas', // psr7bridge + 'use_zend_loader' => 'use_laminas_loader', // zend-modulemanager + 'zend-config' => 'laminas-config', + 'zend-developer-tools/' => 'laminas-developer-tools/', + 'zend-tag-cloud' => 'laminas-tag-cloud', + 'zenddevelopertools' => 'laminas-developer-tools', + 'zendbarcode' => 'laminasbarcode', + 'ZendBarcode' => 'LaminasBarcode', + 'zendcache' => 'laminascache', + 'ZendCache' => 'LaminasCache', + 'zendconfig' => 'laminasconfig', + 'ZendConfig' => 'LaminasConfig', + 'zendfeed' => 'laminasfeed', + 'ZendFeed' => 'LaminasFeed', + 'zendfilter' => 'laminasfilter', + 'ZendFilter' => 'LaminasFilter', + 'zendform' => 'laminasform', + 'ZendForm' => 'LaminasForm', + 'zendi18n' => 'laminasi18n', + 'ZendI18n' => 'LaminasI18n', + 'zendinputfilter' => 'laminasinputfilter', + 'ZendInputFilter' => 'LaminasInputFilter', + 'zendlog' => 'laminaslog', + 'ZendLog' => 'LaminasLog', + 'zendmail' => 'laminasmail', + 'ZendMail' => 'LaminasMail', + 'zendmvc' => 'laminasmvc', + 'ZendMvc' => 'LaminasMvc', + 'zendpaginator' => 'laminaspaginator', + 'ZendPaginator' => 'LaminasPaginator', + 'zendserializer' => 'laminasserializer', + 'ZendSerializer' => 'LaminasSerializer', + 'zendtag' => 'laminastag', + 'ZendTag' => 'LaminasTag', + 'zendtext' => 'laminastext', + 'ZendText' => 'LaminasText', + 'zendvalidator' => 'laminasvalidator', + 'ZendValidator' => 'LaminasValidator', + 'zendview' => 'laminasview', + 'ZendView' => 'LaminasView', + 'zend-framework.flf' => 'laminas-project.flf', + + // Expressive-related + "'zend-expressive'" => "'mezzio'", + '"zend-expressive"' => '"mezzio"', + 'zend-expressive.' => 'mezzio.', + 'zend-expressive-authorization' => 'mezzio-authorization', + 'zend-expressive-hal' => 'mezzio-hal', + 'zend-expressive-session' => 'mezzio-session', + 'zend-expressive-swoole' => 'mezzio-swoole', + 'zend-expressive-tooling' => 'mezzio-tooling', + + // Apigility-related + "'zf-apigility'" => "'api-tools'", + '"zf-apigility"' => '"api-tools"', + 'zf-apigility/' => 'api-tools/', + 'zf-apigility-admin' => 'api-tools-admin', + 'zf-content-negotiation' => 'api-tools-content-negotiation', + 'zf-hal' => 'api-tools-hal', + 'zf-rest' => 'api-tools-rest', + 'zf-rpc' => 'api-tools-rpc', + 'zf-content-validation' => 'api-tools-content-validation', + 'zf-apigility-ui' => 'api-tools-ui', + 'zf-apigility-documentation-blueprint' => 'api-tools-documentation-blueprint', + 'zf-apigility-documentation-swagger' => 'api-tools-documentation-swagger', + 'zf-apigility-welcome' => 'api-tools-welcome', + 'zf-api-problem' => 'api-tools-api-problem', + 'zf-configuration' => 'api-tools-configuration', + 'zf-http-cache' => 'api-tools-http-cache', + 'zf-mvc-auth' => 'api-tools-mvc-auth', + 'zf-oauth2' => 'api-tools-oauth2', + 'zf-versioning' => 'api-tools-versioning', + 'ZfApigilityDoctrineQueryProviderManager' => 'LaminasApiToolsDoctrineQueryProviderManager', + 'ZfApigilityDoctrineQueryCreateFilterManager' => 'LaminasApiToolsDoctrineQueryCreateFilterManager', + 'zf-apigility-doctrine' => 'api-tools-doctrine', + 'zf-development-mode' => 'laminas-development-mode', + 'zf-doctrine-querybuilder' => 'api-tools-doctrine-querybuilder', + + // 3rd party Apigility packages + 'api-skeletons/zf-' => 'api-skeletons/zf-', // api-skeletons packages + 'zf-oauth2-' => 'zf-oauth2-', // api-skeletons OAuth2-related packages + 'ZF\\OAuth2\\Client' => 'ZF\\OAuth2\\Client', // api-skeletons/zf-oauth2-client + 'ZF\\OAuth2\\Doctrine' => 'ZF\\OAuth2\\Doctrine', // api-skeletons/zf-oauth2-doctrine +]; diff --git a/kirby/vendor/laminas/laminas-zendframework-bridge/src/Autoloader.php b/kirby/vendor/laminas/laminas-zendframework-bridge/src/Autoloader.php new file mode 100644 index 0000000..261bb88 --- /dev/null +++ b/kirby/vendor/laminas/laminas-zendframework-bridge/src/Autoloader.php @@ -0,0 +1,166 @@ +loadClass($class)) { + $legacy = $namespaces[$check] + . strtr(substr($class, strlen($check)), [ + 'ApiTools' => 'Apigility', + 'Mezzio' => 'Expressive', + 'Laminas' => 'Zend', + ]); + class_alias($class, $legacy); + } + }; + } + + /** + * @return callable + */ + private static function createAppendAutoloader(array $namespaces, ArrayObject $loaded) + { + /** + * @param string $class Class name to autoload + * @return void + */ + return static function ($class) use ($namespaces, $loaded) { + $segments = explode('\\', $class); + + if ($segments[0] === 'ZendService' && isset($segments[1])) { + $segments[0] .= '\\' . $segments[1]; + unset($segments[1]); + $segments = array_values($segments); + } + + $i = 0; + $check = ''; + + // We are checking segments of the namespace to match quicker + while (isset($segments[$i + 1], $namespaces[$check . $segments[$i] . '\\'])) { + $check .= $segments[$i] . '\\'; + ++$i; + } + + if ($check === '') { + return; + } + + $alias = $namespaces[$check] + . strtr(substr($class, strlen($check)), [ + 'Apigility' => 'ApiTools', + 'Expressive' => 'Mezzio', + 'Zend' => 'Laminas', + 'AbstractZendServer' => 'AbstractZendServer', + 'ZendServerDisk' => 'ZendServerDisk', + 'ZendServerShm' => 'ZendServerShm', + 'ZendMonitor' => 'ZendMonitor', + ]); + + $loaded[$alias] = true; + if (class_exists($alias) || interface_exists($alias) || trait_exists($alias)) { + class_alias($alias, $class); + } + }; + } +} diff --git a/kirby/vendor/laminas/laminas-zendframework-bridge/src/ConfigPostProcessor.php b/kirby/vendor/laminas/laminas-zendframework-bridge/src/ConfigPostProcessor.php new file mode 100644 index 0000000..c3b601a --- /dev/null +++ b/kirby/vendor/laminas/laminas-zendframework-bridge/src/ConfigPostProcessor.php @@ -0,0 +1,426 @@ + true, + 'factories' => true, + 'invokables' => true, + 'services' => true, + ]; + + /** @var array String keys => string values */ + private $exactReplacements = [ + 'zend-expressive' => 'mezzio', + 'zf-apigility' => 'api-tools', + ]; + + /** @var Replacements */ + private $replacements; + + /** @var callable[] */ + private $rulesets; + + public function __construct() + { + $this->replacements = new Replacements(); + + /* Define the rulesets for replacements. + * + * Each ruleset has the following signature: + * + * @param mixed $value + * @param string[] $keys Full nested key hierarchy leading to the value + * @return null|callable + * + * If no match is made, a null is returned, allowing it to fallback to + * the next ruleset in the list. If a match is made, a callback is returned, + * and that will be used to perform the replacement on the value. + * + * The callback should have the following signature: + * + * @param mixed $value + * @param string[] $keys + * @return mixed The transformed value + */ + $this->rulesets = [ + // Exact values + function ($value) { + return is_string($value) && isset($this->exactReplacements[$value]) + ? [$this, 'replaceExactValue'] + : null; + }, + + // Router (MVC applications) + // We do not want to rewrite these. + function ($value, array $keys) { + $key = array_pop($keys); + // Only worried about a top-level "router" key. + return $key === 'router' && $keys === [] && is_array($value) + ? [$this, 'noopReplacement'] + : null; + }, + + // service- and pluginmanager handling + function ($value) { + return is_array($value) && array_intersect_key(self::SERVICE_MANAGER_KEYS_OF_INTEREST, $value) !== [] + ? [$this, 'replaceDependencyConfiguration'] + : null; + }, + + // Array values + function ($value, array $keys) { + return $keys !== [] && is_array($value) + ? [$this, '__invoke'] + : null; + }, + ]; + } + + /** + * @param string[] $keys Hierarchy of keys, for determining location in + * nested configuration. + * @return array + */ + public function __invoke(array $config, array $keys = []) + { + $rewritten = []; + + foreach ($config as $key => $value) { + // Determine new key from replacements + $newKey = is_string($key) ? $this->replace($key, $keys) : $key; + + // Keep original values with original key, if the key has changed, but only at the top-level. + if (empty($keys) && $newKey !== $key) { + $rewritten[$key] = $value; + } + + // Perform value replacements, if any + $newValue = $this->replace($value, $keys, $newKey); + + // Key does not already exist and/or is not an array value + if (! array_key_exists($newKey, $rewritten) || ! is_array($rewritten[$newKey])) { + // Do not overwrite existing values with null values + $rewritten[$newKey] = array_key_exists($newKey, $rewritten) && null === $newValue + ? $rewritten[$newKey] + : $newValue; + continue; + } + + // New value is null; nothing to do. + if (null === $newValue) { + continue; + } + + // Key already exists as an array value, but $value is not an array + if (! is_array($newValue)) { + $rewritten[$newKey][] = $newValue; + continue; + } + + // Key already exists as an array value, and $value is also an array + $rewritten[$newKey] = static::merge($rewritten[$newKey], $newValue); + } + + return $rewritten; + } + + /** + * Perform substitutions as needed on an individual value. + * + * The $key is provided to allow fine-grained selection of rewrite rules. + * + * @param mixed $value + * @param string[] $keys Key hierarchy + * @param null|int|string $key + * @return mixed + */ + private function replace($value, array $keys, $key = null) + { + // Add new key to the list of keys. + // We do not need to remove it later, as we are working on a copy of the array. + $keys[] = $key; + + // Identify rewrite strategy and perform replacements + $rewriteRule = $this->replacementRuleMatch($value, $keys); + return $rewriteRule($value, $keys); + } + + /** + * Merge two arrays together. + * + * If an integer key exists in both arrays, the value from the second array + * will be appended to the first array. If both values are arrays, they are + * merged together, else the value of the second array overwrites the one + * of the first array. + * + * Based on zend-stdlib Zend\Stdlib\ArrayUtils::merge + * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com) + * + * @return array + */ + public static function merge(array $a, array $b) + { + foreach ($b as $key => $value) { + if (! isset($a[$key]) && ! array_key_exists($key, $a)) { + $a[$key] = $value; + continue; + } + + if (null === $value && array_key_exists($key, $a)) { + // Leave as-is if value from $b is null + continue; + } + + if (is_int($key)) { + $a[] = $value; + continue; + } + + if (is_array($value) && is_array($a[$key])) { + $a[$key] = static::merge($a[$key], $value); + continue; + } + + $a[$key] = $value; + } + + return $a; + } + + /** + * @param mixed $value + * @param null|int|string $key + * @return callable Callable to invoke with value + */ + private function replacementRuleMatch($value, $key = null) + { + foreach ($this->rulesets as $ruleset) { + $result = $ruleset($value, $key); + if (is_callable($result)) { + return $result; + } + } + return [$this, 'fallbackReplacement']; + } + + /** + * Replace a value using the translation table, if the value is a string. + * + * @param mixed $value + * @return mixed + */ + private function fallbackReplacement($value) + { + return is_string($value) + ? $this->replacements->replace($value) + : $value; + } + + /** + * Replace a value matched exactly. + * + * @param mixed $value + * @return mixed + */ + private function replaceExactValue($value) + { + return $this->exactReplacements[$value]; + } + + private function replaceDependencyConfiguration(array $config) + { + $aliases = isset($config['aliases']) && is_array($config['aliases']) + ? $this->replaceDependencyAliases($config['aliases']) + : []; + + if ($aliases) { + $config['aliases'] = $aliases; + } + + $config = $this->replaceDependencyInvokables($config); + $config = $this->replaceDependencyFactories($config); + $config = $this->replaceDependencyServices($config); + + $keys = self::SERVICE_MANAGER_KEYS_OF_INTEREST; + foreach ($config as $key => $data) { + if (isset($keys[$key])) { + continue; + } + + $config[$key] = is_array($data) ? $this->__invoke($data, [$key]) : $data; + } + + return $config; + } + + /** + * Rewrite dependency aliases array + * + * In this case, we want to keep the alias as-is, but rewrite the target. + * + * We need also provide an additional alias if the alias key is a legacy class. + * + * @return array + */ + private function replaceDependencyAliases(array $aliases) + { + foreach ($aliases as $alias => $target) { + if (! is_string($alias) || ! is_string($target)) { + continue; + } + + $newTarget = $this->replacements->replace($target); + $newAlias = $this->replacements->replace($alias); + + $notIn = [$newTarget]; + $name = $newTarget; + while (isset($aliases[$name])) { + $notIn[] = $aliases[$name]; + $name = $aliases[$name]; + } + + if ($newAlias === $alias && ! in_array($alias, $notIn, true)) { + $aliases[$alias] = $newTarget; + continue; + } + + if (isset($aliases[$newAlias])) { + continue; + } + + if (! in_array($newAlias, $notIn, true)) { + $aliases[$alias] = $newAlias; + $aliases[$newAlias] = $newTarget; + } + } + + return $aliases; + } + + /** + * Rewrite dependency invokables array + * + * In this case, we want to keep the alias as-is, but rewrite the target. + * + * We need also provide an additional alias if invokable is defined with + * an alias which is a legacy class. + * + * @return array + */ + private function replaceDependencyInvokables(array $config) + { + if (empty($config['invokables']) || ! is_array($config['invokables'])) { + return $config; + } + + foreach ($config['invokables'] as $alias => $target) { + if (! is_string($alias)) { + continue; + } + + $newTarget = $this->replacements->replace($target); + $newAlias = $this->replacements->replace($alias); + + if ($alias === $target || isset($config['aliases'][$newAlias])) { + $config['invokables'][$alias] = $newTarget; + continue; + } + + $config['invokables'][$newAlias] = $newTarget; + + if ($newAlias === $alias) { + continue; + } + + $config['aliases'][$alias] = $newAlias; + + unset($config['invokables'][$alias]); + } + + return $config; + } + + /** + * @param mixed $value + * @return mixed Returns $value verbatim. + */ + private function noopReplacement($value) + { + return $value; + } + + private function replaceDependencyFactories(array $config) + { + if (empty($config['factories']) || ! is_array($config['factories'])) { + return $config; + } + + foreach ($config['factories'] as $service => $factory) { + if (! is_string($service)) { + continue; + } + + $replacedService = $this->replacements->replace($service); + $factory = is_string($factory) ? $this->replacements->replace($factory) : $factory; + $config['factories'][$replacedService] = $factory; + + if ($replacedService === $service) { + continue; + } + + unset($config['factories'][$service]); + if (isset($config['aliases'][$service])) { + continue; + } + + $config['aliases'][$service] = $replacedService; + } + + return $config; + } + + private function replaceDependencyServices(array $config) + { + if (empty($config['services']) || ! is_array($config['services'])) { + return $config; + } + + foreach ($config['services'] as $service => $serviceInstance) { + if (! is_string($service)) { + continue; + } + + $replacedService = $this->replacements->replace($service); + $serviceInstance = is_array($serviceInstance) ? $this->__invoke($serviceInstance) : $serviceInstance; + + $config['services'][$replacedService] = $serviceInstance; + + if ($service === $replacedService) { + continue; + } + + unset($config['services'][$service]); + + if (isset($config['aliases'][$service])) { + continue; + } + + $config['aliases'][$service] = $replacedService; + } + + return $config; + } +} diff --git a/kirby/vendor/laminas/laminas-zendframework-bridge/src/Module.php b/kirby/vendor/laminas/laminas-zendframework-bridge/src/Module.php new file mode 100644 index 0000000..9bb1298 --- /dev/null +++ b/kirby/vendor/laminas/laminas-zendframework-bridge/src/Module.php @@ -0,0 +1,48 @@ +getEventManager() + ->attach('mergeConfig', [$this, 'onMergeConfig']); + } + + /** + * Perform substitutions in the merged configuration. + * + * Rewrites keys and values matching known ZF classes, namespaces, and + * configuration keys to their Laminas equivalents. + * + * Type-hinting deliberately omitted to allow unit testing + * without dependencies on packages that do not exist yet. + * + * @param ModuleEvent $event + */ + public function onMergeConfig($event) + { + /** @var ConfigMergerInterface */ + $configMerger = $event->getConfigListener(); + $processor = new ConfigPostProcessor(); + $configMerger->setMergedConfig( + $processor( + $configMerger->getMergedConfig($returnAsObject = false) + ) + ); + } +} diff --git a/kirby/vendor/laminas/laminas-zendframework-bridge/src/Replacements.php b/kirby/vendor/laminas/laminas-zendframework-bridge/src/Replacements.php new file mode 100644 index 0000000..5a09ef1 --- /dev/null +++ b/kirby/vendor/laminas/laminas-zendframework-bridge/src/Replacements.php @@ -0,0 +1,40 @@ +replacements = array_merge( + require __DIR__ . '/../config/replacements.php', + $additionalReplacements + ); + + // Provide multiple variants of strings containing namespace separators + foreach ($this->replacements as $original => $replacement) { + if (false === strpos($original, '\\')) { + continue; + } + $this->replacements[str_replace('\\', '\\\\', $original)] = str_replace('\\', '\\\\', $replacement); + $this->replacements[str_replace('\\', '\\\\\\\\', $original)] = str_replace('\\', '\\\\\\\\', $replacement); + } + } + + /** + * @param string $value + * @return string + */ + public function replace($value) + { + return strtr($value, $this->replacements); + } +} diff --git a/kirby/vendor/laminas/laminas-zendframework-bridge/src/RewriteRules.php b/kirby/vendor/laminas/laminas-zendframework-bridge/src/RewriteRules.php new file mode 100644 index 0000000..61aa56d --- /dev/null +++ b/kirby/vendor/laminas/laminas-zendframework-bridge/src/RewriteRules.php @@ -0,0 +1,73 @@ + 'Mezzio\\ProblemDetails\\', + 'Zend\\Expressive\\' => 'Mezzio\\', + + // Laminas + 'Zend\\' => 'Laminas\\', + 'ZF\\ComposerAutoloading\\' => 'Laminas\\ComposerAutoloading\\', + 'ZF\\DevelopmentMode\\' => 'Laminas\\DevelopmentMode\\', + + // Apigility + 'ZF\\Apigility\\' => 'Laminas\\ApiTools\\', + 'ZF\\' => 'Laminas\\ApiTools\\', + + // ZendXml, API wrappers, zend-http OAuth support, zend-diagnostics, ZendDeveloperTools + 'ZendXml\\' => 'Laminas\\Xml\\', + 'ZendOAuth\\' => 'Laminas\\OAuth\\', + 'ZendDiagnostics\\' => 'Laminas\\Diagnostics\\', + 'ZendService\\ReCaptcha\\' => 'Laminas\\ReCaptcha\\', + 'ZendService\\Twitter\\' => 'Laminas\\Twitter\\', + 'ZendDeveloperTools\\' => 'Laminas\\DeveloperTools\\', + ]; + } + + /** + * @return array + */ + public static function namespaceReverse() + { + return [ + // ZendXml, ZendOAuth, ZendDiagnostics, ZendDeveloperTools + 'Laminas\\Xml\\' => 'ZendXml\\', + 'Laminas\\OAuth\\' => 'ZendOAuth\\', + 'Laminas\\Diagnostics\\' => 'ZendDiagnostics\\', + 'Laminas\\DeveloperTools\\' => 'ZendDeveloperTools\\', + + // Zend Service + 'Laminas\\ReCaptcha\\' => 'ZendService\\ReCaptcha\\', + 'Laminas\\Twitter\\' => 'ZendService\\Twitter\\', + + // Zend + 'Laminas\\' => 'Zend\\', + + // Expressive + 'Mezzio\\ProblemDetails\\' => 'Zend\\ProblemDetails\\', + 'Mezzio\\' => 'Zend\\Expressive\\', + + // Laminas to ZfCampus + 'Laminas\\ComposerAutoloading\\' => 'ZF\\ComposerAutoloading\\', + 'Laminas\\DevelopmentMode\\' => 'ZF\\DevelopmentMode\\', + + // Apigility + 'Laminas\\ApiTools\\Admin\\' => 'ZF\\Apigility\\Admin\\', + 'Laminas\\ApiTools\\Doctrine\\' => 'ZF\\Apigility\\Doctrine\\', + 'Laminas\\ApiTools\\Documentation\\' => 'ZF\\Apigility\\Documentation\\', + 'Laminas\\ApiTools\\Example\\' => 'ZF\\Apigility\\Example\\', + 'Laminas\\ApiTools\\Provider\\' => 'ZF\\Apigility\\Provider\\', + 'Laminas\\ApiTools\\Welcome\\' => 'ZF\\Apiglity\\Welcome\\', + 'Laminas\\ApiTools\\' => 'ZF\\', + ]; + } +} diff --git a/kirby/vendor/laminas/laminas-zendframework-bridge/src/autoload.php b/kirby/vendor/laminas/laminas-zendframework-bridge/src/autoload.php new file mode 100644 index 0000000..e92ff58 --- /dev/null +++ b/kirby/vendor/laminas/laminas-zendframework-bridge/src/autoload.php @@ -0,0 +1,3 @@ +=5.4.0", + "ext-gd": "*" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "~2", + "phpunit/phpunit": "~5" + }, + "autoload": { + "psr-4": { + "": "src" + } + } +} diff --git a/kirby/vendor/league/color-extractor/src/League/ColorExtractor/Color.php b/kirby/vendor/league/color-extractor/src/League/ColorExtractor/Color.php new file mode 100644 index 0000000..7b102c1 --- /dev/null +++ b/kirby/vendor/league/color-extractor/src/League/ColorExtractor/Color.php @@ -0,0 +1,51 @@ + $color >> 16 & 0xFF, + 'g' => $color >> 8 & 0xFF, + 'b' => $color & 0xFF, + ]; + } + + /** + * @param array $components + * + * @return int + */ + public static function fromRgbToInt(array $components) + { + return ($components['r'] * 65536) + ($components['g'] * 256) + ($components['b']); + } +} diff --git a/kirby/vendor/league/color-extractor/src/League/ColorExtractor/ColorExtractor.php b/kirby/vendor/league/color-extractor/src/League/ColorExtractor/ColorExtractor.php new file mode 100644 index 0000000..09e43c1 --- /dev/null +++ b/kirby/vendor/league/color-extractor/src/League/ColorExtractor/ColorExtractor.php @@ -0,0 +1,275 @@ +palette = $palette; + } + + /** + * @param int $colorCount + * + * @return array + */ + public function extract($colorCount = 1) + { + if (!$this->isInitialized()) { + $this->initialize(); + } + + return self::mergeColors($this->sortedColors, $colorCount, 100 / $colorCount); + } + + /** + * @return bool + */ + protected function isInitialized() + { + return $this->sortedColors !== null; + } + + protected function initialize() + { + $queue = new \SplPriorityQueue(); + $this->sortedColors = new \SplFixedArray(count($this->palette)); + + $i = 0; + foreach ($this->palette as $color => $count) { + $labColor = self::intColorToLab($color); + $queue->insert( + $color, + (sqrt($labColor['a'] * $labColor['a'] + $labColor['b'] * $labColor['b']) ?: 1) * + (1 - $labColor['L'] / 200) * + sqrt($count) + ); + ++$i; + } + + $i = 0; + while ($queue->valid()) { + $this->sortedColors[$i] = $queue->current(); + $queue->next(); + ++$i; + } + } + + /** + * @param \SplFixedArray $colors + * @param int $limit + * @param int $maxDelta + * + * @return array + */ + protected static function mergeColors(\SplFixedArray $colors, $limit, $maxDelta) + { + $limit = min(count($colors), $limit); + if ($limit === 1) { + return [$colors[0]]; + } + $labCache = new \SplFixedArray($limit - 1); + $mergedColors = []; + + foreach ($colors as $color) { + $hasColorBeenMerged = false; + + $colorLab = self::intColorToLab($color); + + foreach ($mergedColors as $i => $mergedColor) { + if (self::ciede2000DeltaE($colorLab, $labCache[$i]) < $maxDelta) { + $hasColorBeenMerged = true; + break; + } + } + + if ($hasColorBeenMerged) { + continue; + } + + $mergedColorCount = count($mergedColors); + $mergedColors[] = $color; + + if ($mergedColorCount + 1 == $limit) { + break; + } + + $labCache[$mergedColorCount] = $colorLab; + } + + return $mergedColors; + } + + /** + * @param array $firstLabColor + * @param array $secondLabColor + * + * @return float + */ + protected static function ciede2000DeltaE($firstLabColor, $secondLabColor) + { + $C1 = sqrt(pow($firstLabColor['a'], 2) + pow($firstLabColor['b'], 2)); + $C2 = sqrt(pow($secondLabColor['a'], 2) + pow($secondLabColor['b'], 2)); + $Cb = ($C1 + $C2) / 2; + + $G = .5 * (1 - sqrt(pow($Cb, 7) / (pow($Cb, 7) + pow(25, 7)))); + + $a1p = (1 + $G) * $firstLabColor['a']; + $a2p = (1 + $G) * $secondLabColor['a']; + + $C1p = sqrt(pow($a1p, 2) + pow($firstLabColor['b'], 2)); + $C2p = sqrt(pow($a2p, 2) + pow($secondLabColor['b'], 2)); + + $h1p = $a1p == 0 && $firstLabColor['b'] == 0 ? 0 : atan2($firstLabColor['b'], $a1p); + $h2p = $a2p == 0 && $secondLabColor['b'] == 0 ? 0 : atan2($secondLabColor['b'], $a2p); + + $LpDelta = $secondLabColor['L'] - $firstLabColor['L']; + $CpDelta = $C2p - $C1p; + + if ($C1p * $C2p == 0) { + $hpDelta = 0; + } elseif (abs($h2p - $h1p) <= 180) { + $hpDelta = $h2p - $h1p; + } elseif ($h2p - $h1p > 180) { + $hpDelta = $h2p - $h1p - 360; + } else { + $hpDelta = $h2p - $h1p + 360; + } + + $HpDelta = 2 * sqrt($C1p * $C2p) * sin($hpDelta / 2); + + $Lbp = ($firstLabColor['L'] + $secondLabColor['L']) / 2; + $Cbp = ($C1p + $C2p) / 2; + + if ($C1p * $C2p == 0) { + $hbp = $h1p + $h2p; + } elseif (abs($h1p - $h2p) <= 180) { + $hbp = ($h1p + $h2p) / 2; + } elseif ($h1p + $h2p < 360) { + $hbp = ($h1p + $h2p + 360) / 2; + } else { + $hbp = ($h1p + $h2p - 360) / 2; + } + + $T = 1 - .17 * cos($hbp - 30) + .24 * cos(2 * $hbp) + .32 * cos(3 * $hbp + 6) - .2 * cos(4 * $hbp - 63); + + $sigmaDelta = 30 * exp(-pow(($hbp - 275) / 25, 2)); + + $Rc = 2 * sqrt(pow($Cbp, 7) / (pow($Cbp, 7) + pow(25, 7))); + + $Sl = 1 + ((.015 * pow($Lbp - 50, 2)) / sqrt(20 + pow($Lbp - 50, 2))); + $Sc = 1 + .045 * $Cbp; + $Sh = 1 + .015 * $Cbp * $T; + + $Rt = -sin(2 * $sigmaDelta) * $Rc; + + return sqrt( + pow($LpDelta / $Sl, 2) + + pow($CpDelta / $Sc, 2) + + pow($HpDelta / $Sh, 2) + + $Rt * ($CpDelta / $Sc) * ($HpDelta / $Sh) + ); + } + + /** + * @param int $color + * + * @return array + */ + protected static function intColorToLab($color) + { + return self::xyzToLab( + self::srgbToXyz( + self::rgbToSrgb( + [ + 'R' => ($color >> 16) & 0xFF, + 'G' => ($color >> 8) & 0xFF, + 'B' => $color & 0xFF, + ] + ) + ) + ); + } + + /** + * @param int $value + * + * @return float + */ + protected static function rgbToSrgbStep($value) + { + $value /= 255; + + return $value <= .03928 ? + $value / 12.92 : + pow(($value + .055) / 1.055, 2.4); + } + + /** + * @param array $rgb + * + * @return array + */ + protected static function rgbToSrgb($rgb) + { + return [ + 'R' => self::rgbToSrgbStep($rgb['R']), + 'G' => self::rgbToSrgbStep($rgb['G']), + 'B' => self::rgbToSrgbStep($rgb['B']), + ]; + } + + /** + * @param array $rgb + * + * @return array + */ + protected static function srgbToXyz($rgb) + { + return [ + 'X' => (.4124564 * $rgb['R']) + (.3575761 * $rgb['G']) + (.1804375 * $rgb['B']), + 'Y' => (.2126729 * $rgb['R']) + (.7151522 * $rgb['G']) + (.0721750 * $rgb['B']), + 'Z' => (.0193339 * $rgb['R']) + (.1191920 * $rgb['G']) + (.9503041 * $rgb['B']), + ]; + } + + /** + * @param float $value + * + * @return float + */ + protected static function xyzToLabStep($value) + { + return $value > 216 / 24389 ? pow($value, 1 / 3) : 841 * $value / 108 + 4 / 29; + } + + /** + * @param array $xyz + * + * @return array + */ + protected static function xyzToLab($xyz) + { + //http://en.wikipedia.org/wiki/Illuminant_D65#Definition + $Xn = .95047; + $Yn = 1; + $Zn = 1.08883; + + // http://en.wikipedia.org/wiki/Lab_color_space#CIELAB-CIEXYZ_conversions + return [ + 'L' => 116 * self::xyzToLabStep($xyz['Y'] / $Yn) - 16, + 'a' => 500 * (self::xyzToLabStep($xyz['X'] / $Xn) - self::xyzToLabStep($xyz['Y'] / $Yn)), + 'b' => 200 * (self::xyzToLabStep($xyz['Y'] / $Yn) - self::xyzToLabStep($xyz['Z'] / $Zn)), + ]; + } +} diff --git a/kirby/vendor/league/color-extractor/src/League/ColorExtractor/Palette.php b/kirby/vendor/league/color-extractor/src/League/ColorExtractor/Palette.php new file mode 100644 index 0000000..d8fb4f9 --- /dev/null +++ b/kirby/vendor/league/color-extractor/src/League/ColorExtractor/Palette.php @@ -0,0 +1,126 @@ +colors); + } + + /** + * @return \ArrayIterator + */ + public function getIterator() + { + return new \ArrayIterator($this->colors); + } + + /** + * @param int $color + * + * @return int + */ + public function getColorCount($color) + { + return $this->colors[$color]; + } + + /** + * @param int $limit = null + * + * @return array + */ + public function getMostUsedColors($limit = null) + { + return array_slice($this->colors, 0, $limit, true); + } + + /** + * @param string $filename + * @param int|null $backgroundColor + * + * @return Palette + */ + public static function fromFilename($filename, $backgroundColor = null) + { + $image = imagecreatefromstring(file_get_contents($filename)); + $palette = self::fromGD($image, $backgroundColor); + imagedestroy($image); + + return $palette; + } + + /** + * @param resource $image + * @param int|null $backgroundColor + * + * @return Palette + * + * @throws \InvalidArgumentException + */ + public static function fromGD($image, $backgroundColor = null) + { + if (!is_resource($image) || get_resource_type($image) != 'gd') { + throw new \InvalidArgumentException('Image must be a gd resource'); + } + if ($backgroundColor !== null && (!is_numeric($backgroundColor) || $backgroundColor < 0 || $backgroundColor > 16777215)) { + throw new \InvalidArgumentException(sprintf('"%s" does not represent a valid color', $backgroundColor)); + } + + $palette = new self(); + + $areColorsIndexed = !imageistruecolor($image); + $imageWidth = imagesx($image); + $imageHeight = imagesy($image); + $palette->colors = []; + + $backgroundColorRed = ($backgroundColor >> 16) & 0xFF; + $backgroundColorGreen = ($backgroundColor >> 8) & 0xFF; + $backgroundColorBlue = $backgroundColor & 0xFF; + + for ($x = 0; $x < $imageWidth; ++$x) { + for ($y = 0; $y < $imageHeight; ++$y) { + $color = imagecolorat($image, $x, $y); + if ($areColorsIndexed) { + $colorComponents = imagecolorsforindex($image, $color); + $color = ($colorComponents['alpha'] * 16777216) + + ($colorComponents['red'] * 65536) + + ($colorComponents['green'] * 256) + + ($colorComponents['blue']); + } + + if ($alpha = $color >> 24) { + if ($backgroundColor === null) { + continue; + } + + $alpha /= 127; + $color = (int) (($color >> 16 & 0xFF) * (1 - $alpha) + $backgroundColorRed * $alpha) * 65536 + + (int) (($color >> 8 & 0xFF) * (1 - $alpha) + $backgroundColorGreen * $alpha) * 256 + + (int) (($color & 0xFF) * (1 - $alpha) + $backgroundColorBlue * $alpha); + } + + isset($palette->colors[$color]) ? + $palette->colors[$color] += 1 : + $palette->colors[$color] = 1; + } + } + + arsort($palette->colors); + + return $palette; + } + + protected function __construct() + { + $this->colors = []; + } +} diff --git a/kirby/vendor/michelf/php-smartypants/License.md b/kirby/vendor/michelf/php-smartypants/License.md new file mode 100644 index 0000000..20aad72 --- /dev/null +++ b/kirby/vendor/michelf/php-smartypants/License.md @@ -0,0 +1,36 @@ +PHP SmartyPants Lib +Copyright (c) 2005-2016 Michel Fortin + +All rights reserved. + +Original SmartyPants +Copyright (c) 2003-2004 John Gruber + +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + +* Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +* Neither the name "SmartyPants" nor the names of its contributors may + be used to endorse or promote products derived from this software + without specific prior written permission. + +This software is provided by the copyright holders and contributors "as +is" and any express or implied warranties, including, but not limited +to, the implied warranties of merchantability and fitness for a +particular purpose are disclaimed. In no event shall the copyright owner +or contributors be liable for any direct, indirect, incidental, special, +exemplary, or consequential damages (including, but not limited to, +procurement of substitute goods or services; loss of use, data, or +profits; or business interruption) however caused and on any theory of +liability, whether in contract, strict liability, or tort (including +negligence or otherwise) arising in any way out of the use of this +software, even if advised of the possibility of such damage. diff --git a/kirby/vendor/michelf/php-smartypants/Michelf/SmartyPants.inc.php b/kirby/vendor/michelf/php-smartypants/Michelf/SmartyPants.inc.php new file mode 100644 index 0000000..b4ee661 --- /dev/null +++ b/kirby/vendor/michelf/php-smartypants/Michelf/SmartyPants.inc.php @@ -0,0 +1,9 @@ + +# +# Original SmartyPants +# Copyright (c) 2003-2004 John Gruber +# +# +namespace Michelf; + + +# +# SmartyPants Parser Class +# + +class SmartyPants { + + ### Version ### + + const SMARTYPANTSLIB_VERSION = "1.8.1"; + + + ### Presets + + # SmartyPants does nothing at all + const ATTR_DO_NOTHING = 0; + # "--" for em-dashes; no en-dash support + const ATTR_EM_DASH = 1; + # "---" for em-dashes; "--" for en-dashes + const ATTR_LONG_EM_DASH_SHORT_EN = 2; + # "--" for em-dashes; "---" for en-dashes + const ATTR_SHORT_EM_DASH_LONG_EN = 3; + # "--" for em-dashes; "---" for en-dashes + const ATTR_STUPEFY = -1; + + # The default preset: ATTR_EM_DASH + const ATTR_DEFAULT = SmartyPants::ATTR_EM_DASH; + + + ### Standard Function Interface ### + + public static function defaultTransform($text, $attr = SmartyPants::ATTR_DEFAULT) { + # + # Initialize the parser and return the result of its transform method. + # This will work fine for derived classes too. + # + # Take parser class on which this function was called. + $parser_class = \get_called_class(); + + # try to take parser from the static parser list + static $parser_list; + $parser =& $parser_list[$parser_class][$attr]; + + # create the parser if not already set + if (!$parser) + $parser = new $parser_class($attr); + + # Transform text using parser. + return $parser->transform($text); + } + + + ### Configuration Variables ### + + # Partial regex for matching tags to skip + public $tags_to_skip = 'pre|code|kbd|script|style|math'; + + # Options to specify which transformations to make: + public $do_nothing = 0; # disable all transforms + public $do_quotes = 0; + public $do_backticks = 0; # 1 => double only, 2 => double & single + public $do_dashes = 0; # 1, 2, or 3 for the three modes described above + public $do_ellipses = 0; + public $do_stupefy = 0; + public $convert_quot = 0; # should we translate " entities into normal quotes? + + # Smart quote characters: + # Opening and closing smart double-quotes. + public $smart_doublequote_open = '“'; + public $smart_doublequote_close = '”'; + public $smart_singlequote_open = '‘'; + public $smart_singlequote_close = '’'; # Also apostrophe. + + # ``Backtick quotes'' + public $backtick_doublequote_open = '“'; // replacement for `` + public $backtick_doublequote_close = '”'; // replacement for '' + public $backtick_singlequote_open = '‘'; // replacement for ` + public $backtick_singlequote_close = '’'; // replacement for ' (also apostrophe) + + # Other punctuation + public $em_dash = '—'; + public $en_dash = '–'; + public $ellipsis = '…'; + + ### Parser Implementation ### + + public function __construct($attr = SmartyPants::ATTR_DEFAULT) { + # + # Initialize a parser with certain attributes. + # + # Parser attributes: + # 0 : do nothing + # 1 : set all + # 2 : set all, using old school en- and em- dash shortcuts + # 3 : set all, using inverted old school en and em- dash shortcuts + # + # q : quotes + # b : backtick quotes (``double'' only) + # B : backtick quotes (``double'' and `single') + # d : dashes + # D : old school dashes + # i : inverted old school dashes + # e : ellipses + # w : convert " entities to " for Dreamweaver users + # + if ($attr == "0") { + $this->do_nothing = 1; + } + else if ($attr == "1") { + # Do everything, turn all options on. + $this->do_quotes = 1; + $this->do_backticks = 1; + $this->do_dashes = 1; + $this->do_ellipses = 1; + } + else if ($attr == "2") { + # Do everything, turn all options on, use old school dash shorthand. + $this->do_quotes = 1; + $this->do_backticks = 1; + $this->do_dashes = 2; + $this->do_ellipses = 1; + } + else if ($attr == "3") { + # Do everything, turn all options on, use inverted old school dash shorthand. + $this->do_quotes = 1; + $this->do_backticks = 1; + $this->do_dashes = 3; + $this->do_ellipses = 1; + } + else if ($attr == "-1") { + # Special "stupefy" mode. + $this->do_stupefy = 1; + } + else { + $chars = preg_split('//', $attr); + foreach ($chars as $c){ + if ($c == "q") { $this->do_quotes = 1; } + else if ($c == "b") { $this->do_backticks = 1; } + else if ($c == "B") { $this->do_backticks = 2; } + else if ($c == "d") { $this->do_dashes = 1; } + else if ($c == "D") { $this->do_dashes = 2; } + else if ($c == "i") { $this->do_dashes = 3; } + else if ($c == "e") { $this->do_ellipses = 1; } + else if ($c == "w") { $this->convert_quot = 1; } + else { + # Unknown attribute option, ignore. + } + } + } + } + + public function transform($text) { + + if ($this->do_nothing) { + return $text; + } + + $tokens = $this->tokenizeHTML($text); + $result = ''; + $in_pre = 0; # Keep track of when we're inside
     or  tags.
    +
    +		$prev_token_last_char = ""; # This is a cheat, used to get some context
    +									# for one-character tokens that consist of 
    +									# just a quote char. What we do is remember
    +									# the last character of the previous text
    +									# token, to use as context to curl single-
    +									# character quote tokens correctly.
    +
    +		foreach ($tokens as $cur_token) {
    +			if ($cur_token[0] == "tag") {
    +				# Don't mess with quotes inside tags.
    +				$result .= $cur_token[1];
    +				if (preg_match('@<(/?)(?:'.$this->tags_to_skip.')[\s>]@', $cur_token[1], $matches)) {
    +					$in_pre = isset($matches[1]) && $matches[1] == '/' ? 0 : 1;
    +				}
    +			} else {
    +				$t = $cur_token[1];
    +				$last_char = substr($t, -1); # Remember last char of this token before processing.
    +				if (! $in_pre) {
    +					$t = $this->educate($t, $prev_token_last_char);
    +				}
    +				$prev_token_last_char = $last_char;
    +				$result .= $t;
    +			}
    +		}
    +
    +		return $result;
    +	}
    +
    +
    +	function decodeEntitiesInConfiguration() {
    +	#
    +	#   Utility function that converts entities in configuration variables to
    +	#   UTF-8 characters.
    +	#
    +		$output_config_vars = array(
    +			'smart_doublequote_open',
    +			'smart_doublequote_close',
    +			'smart_singlequote_open',
    +			'smart_singlequote_close',
    +			'backtick_doublequote_open',
    +			'backtick_doublequote_close',
    +			'backtick_singlequote_open',
    +			'backtick_singlequote_close',
    +			'em_dash',
    +			'en_dash',
    +			'ellipsis',
    +		);
    +		foreach ($output_config_vars as $var) {
    +			$this->$var = html_entity_decode($this->$var);
    +		}
    +	}
    +
    +
    +	protected function educate($t, $prev_token_last_char) {
    +		$t = $this->processEscapes($t);
    +
    +		if ($this->convert_quot) {
    +			$t = preg_replace('/"/', '"', $t);
    +		}
    +
    +		if ($this->do_dashes) {
    +			if ($this->do_dashes == 1) $t = $this->educateDashes($t);
    +			if ($this->do_dashes == 2) $t = $this->educateDashesOldSchool($t);
    +			if ($this->do_dashes == 3) $t = $this->educateDashesOldSchoolInverted($t);
    +		}
    +
    +		if ($this->do_ellipses) $t = $this->educateEllipses($t);
    +
    +		# Note: backticks need to be processed before quotes.
    +		if ($this->do_backticks) {
    +			$t = $this->educateBackticks($t);
    +			if ($this->do_backticks == 2) $t = $this->educateSingleBackticks($t);
    +		}
    +
    +		if ($this->do_quotes) {
    +			if ($t == "'") {
    +				# Special case: single-character ' token
    +				if (preg_match('/\S/', $prev_token_last_char)) {
    +					$t = $this->smart_singlequote_close;
    +				}
    +				else {
    +					$t = $this->smart_singlequote_open;
    +				}
    +			}
    +			else if ($t == '"') {
    +				# Special case: single-character " token
    +				if (preg_match('/\S/', $prev_token_last_char)) {
    +					$t = $this->smart_doublequote_close;
    +				}
    +				else {
    +					$t = $this->smart_doublequote_open;
    +				}
    +			}
    +			else {
    +				# Normal case:
    +				$t = $this->educateQuotes($t);
    +			}
    +		}
    +
    +		if ($this->do_stupefy) $t = $this->stupefyEntities($t);
    +		
    +		return $t;
    +	}
    +
    +
    +	protected function educateQuotes($_) {
    +	#
    +	#   Parameter:  String.
    +	#
    +	#   Returns:    The string, with "educated" curly quote HTML entities.
    +	#
    +	#   Example input:  "Isn't this fun?"
    +	#   Example output: “Isn’t this fun?”
    +	#
    +		$dq_open  = $this->smart_doublequote_open;
    +		$dq_close = $this->smart_doublequote_close;
    +		$sq_open  = $this->smart_singlequote_open;
    +		$sq_close = $this->smart_singlequote_close;
    +	
    +		# Make our own "punctuation" character class, because the POSIX-style
    +		# [:PUNCT:] is only available in Perl 5.6 or later:
    +		$punct_class = "[!\"#\\$\\%'()*+,-.\\/:;<=>?\\@\\[\\\\\]\\^_`{|}~]";
    +
    +		# Special case if the very first character is a quote
    +		# followed by punctuation at a non-word-break. Close the quotes by brute force:
    +		$_ = preg_replace(
    +			array("/^'(?=$punct_class\\B)/", "/^\"(?=$punct_class\\B)/"),
    +			array($sq_close,                 $dq_close), $_);
    +
    +		# Special case for double sets of quotes, e.g.:
    +		#   

    He said, "'Quoted' words in a larger quote."

    + $_ = preg_replace( + array("/\"'(?=\w)/", "/'\"(?=\w)/"), + array($dq_open.$sq_open, $sq_open.$dq_open), $_); + + # Special case for decade abbreviations (the '80s): + $_ = preg_replace("/'(?=\\d{2}s)/", $sq_close, $_); + + $close_class = '[^\ \t\r\n\[\{\(\-]'; + $dec_dashes = '&\#8211;|&\#8212;'; + + # Get most opening single quotes: + $_ = preg_replace("{ + ( + \\s | # a whitespace char, or +   | # a non-breaking space entity, or + -- | # dashes, or + &[mn]dash; | # named dash entities + $dec_dashes | # or decimal entities + &\\#x201[34]; # or hex + ) + ' # the quote + (?=\\w) # followed by a word character + }x", '\1'.$sq_open, $_); + # Single closing quotes: + $_ = preg_replace("{ + ($close_class)? + ' + (?(1)| # If $1 captured, then do nothing; + (?=\\s | s\\b) # otherwise, positive lookahead for a whitespace + ) # char or an 's' at a word ending position. This + # is a special case to handle something like: + # \"Custer's Last Stand.\" + }xi", '\1'.$sq_close, $_); + + # Any remaining single quotes should be opening ones: + $_ = str_replace("'", $sq_open, $_); + + + # Get most opening double quotes: + $_ = preg_replace("{ + ( + \\s | # a whitespace char, or +   | # a non-breaking space entity, or + -- | # dashes, or + &[mn]dash; | # named dash entities + $dec_dashes | # or decimal entities + &\\#x201[34]; # or hex + ) + \" # the quote + (?=\\w) # followed by a word character + }x", '\1'.$dq_open, $_); + + # Double closing quotes: + $_ = preg_replace("{ + ($close_class)? + \" + (?(1)|(?=\\s)) # If $1 captured, then do nothing; + # if not, then make sure the next char is whitespace. + }x", '\1'.$dq_close, $_); + + # Any remaining quotes should be opening ones. + $_ = str_replace('"', $dq_open, $_); + + return $_; + } + + + protected function educateBackticks($_) { + # + # Parameter: String. + # Returns: The string, with ``backticks'' -style double quotes + # translated into HTML curly quote entities. + # + # Example input: ``Isn't this fun?'' + # Example output: “Isn't this fun?” + # + + $_ = str_replace(array("``", "''",), + array($this->backtick_doublequote_open, + $this->backtick_doublequote_close), $_); + return $_; + } + + + protected function educateSingleBackticks($_) { + # + # Parameter: String. + # Returns: The string, with `backticks' -style single quotes + # translated into HTML curly quote entities. + # + # Example input: `Isn't this fun?' + # Example output: ‘Isn’t this fun?’ + # + + $_ = str_replace(array("`", "'",), + array($this->backtick_singlequote_open, + $this->backtick_singlequote_close), $_); + return $_; + } + + + protected function educateDashes($_) { + # + # Parameter: String. + # + # Returns: The string, with each instance of "--" translated to + # an em-dash HTML entity. + # + + $_ = str_replace('--', $this->em_dash, $_); + return $_; + } + + + protected function educateDashesOldSchool($_) { + # + # Parameter: String. + # + # Returns: The string, with each instance of "--" translated to + # an en-dash HTML entity, and each "---" translated to + # an em-dash HTML entity. + # + + # em en + $_ = str_replace(array("---", "--",), + array($this->em_dash, $this->en_dash), $_); + return $_; + } + + + protected function educateDashesOldSchoolInverted($_) { + # + # Parameter: String. + # + # Returns: The string, with each instance of "--" translated to + # an em-dash HTML entity, and each "---" translated to + # an en-dash HTML entity. Two reasons why: First, unlike the + # en- and em-dash syntax supported by + # EducateDashesOldSchool(), it's compatible with existing + # entries written before SmartyPants 1.1, back when "--" was + # only used for em-dashes. Second, em-dashes are more + # common than en-dashes, and so it sort of makes sense that + # the shortcut should be shorter to type. (Thanks to Aaron + # Swartz for the idea.) + # + + # en em + $_ = str_replace(array("---", "--",), + array($this->en_dash, $this->em_dash), $_); + return $_; + } + + + protected function educateEllipses($_) { + # + # Parameter: String. + # Returns: The string, with each instance of "..." translated to + # an ellipsis HTML entity. Also converts the case where + # there are spaces between the dots. + # + # Example input: Huh...? + # Example output: Huh…? + # + + $_ = str_replace(array("...", ". . .",), $this->ellipsis, $_); + return $_; + } + + + protected function stupefyEntities($_) { + # + # Parameter: String. + # Returns: The string, with each SmartyPants HTML entity translated to + # its ASCII counterpart. + # + # Example input: “Hello — world.” + # Example output: "Hello -- world." + # + + # en-dash em-dash + $_ = str_replace(array('–', '—'), + array('-', '--'), $_); + + # single quote open close + $_ = str_replace(array('‘', '’'), "'", $_); + + # double quote open close + $_ = str_replace(array('“', '”'), '"', $_); + + $_ = str_replace('…', '...', $_); # ellipsis + + return $_; + } + + + protected function processEscapes($_) { + # + # Parameter: String. + # Returns: The string, with after processing the following backslash + # escape sequences. This is useful if you want to force a "dumb" + # quote or other character to appear. + # + # Escape Value + # ------ ----- + # \\ \ + # \" " + # \' ' + # \. . + # \- - + # \` ` + # + $_ = str_replace( + array('\\\\', '\"', "\'", '\.', '\-', '\`'), + array('\', '"', ''', '.', '-', '`'), $_); + + return $_; + } + + + protected function tokenizeHTML($str) { + # + # Parameter: String containing HTML markup. + # Returns: An array of the tokens comprising the input + # string. Each token is either a tag (possibly with nested, + # tags contained therein, such as , or a + # run of text between tags. Each element of the array is a + # two-element array; the first is either 'tag' or 'text'; + # the second is the actual value. + # + # + # Regular expression derived from the _tokenize() subroutine in + # Brad Choate's MTRegex plugin. + # + # + $index = 0; + $tokens = array(); + + $match = '(?s:)|'. # comment + '(?s:<\?.*?\?>)|'. # processing instruction + # regular tags + '(?:<[/!$]?[-a-zA-Z0-9:]+\b(?>[^"\'>]+|"[^"]*"|\'[^\']*\')*>)'; + + $parts = preg_split("{($match)}", $str, -1, PREG_SPLIT_DELIM_CAPTURE); + + foreach ($parts as $part) { + if (++$index % 2 && $part != '') + $tokens[] = array('text', $part); + else + $tokens[] = array('tag', $part); + } + return $tokens; + } + +} diff --git a/kirby/vendor/michelf/php-smartypants/Michelf/SmartyPantsTypographer.inc.php b/kirby/vendor/michelf/php-smartypants/Michelf/SmartyPantsTypographer.inc.php new file mode 100644 index 0000000..9b3d274 --- /dev/null +++ b/kirby/vendor/michelf/php-smartypants/Michelf/SmartyPantsTypographer.inc.php @@ -0,0 +1,10 @@ + +# +# Original SmartyPants +# Copyright (c) 2003-2004 John Gruber +# +# +namespace Michelf; + + +# +# SmartyPants Typographer Parser Class +# +class SmartyPantsTypographer extends \Michelf\SmartyPants { + + ### Configuration Variables ### + + # Options to specify which transformations to make: + public $do_comma_quotes = 0; + public $do_guillemets = 0; + public $do_geresh_gershayim = 0; + public $do_space_emdash = 0; + public $do_space_endash = 0; + public $do_space_colon = 0; + public $do_space_semicolon = 0; + public $do_space_marks = 0; + public $do_space_frenchquote = 0; + public $do_space_thousand = 0; + public $do_space_unit = 0; + + # Quote characters for replacing ASCII approximations + public $doublequote_low = "„"; // replacement for ,, + public $guillemet_leftpointing = "«"; // replacement for << + public $guillemet_rightpointing = "»"; // replacement for >> + public $geresh = "׳"; + public $gershayim = "״"; + + # Space characters for different places: + # Space around em-dashes. "He_—_or she_—_should change that." + public $space_emdash = " "; + # Space around en-dashes. "He_–_or she_–_should change that." + public $space_endash = " "; + # Space before a colon. "He said_: here it is." + public $space_colon = " "; + # Space before a semicolon. "That's what I said_; that's what he said." + public $space_semicolon = " "; + # Space before a question mark and an exclamation mark: "¡_Holà_! What_?" + public $space_marks = " "; + # Space inside french quotes. "Voici la «_chose_» qui m'a attaqué." + public $space_frenchquote = " "; + # Space as thousand separator. "On compte 10_000 maisons sur cette liste." + public $space_thousand = " "; + # Space before a unit abreviation. "This 12_kg of matter costs 10_$." + public $space_unit = " "; + + + # Expression of a space (breakable or not): + public $space = '(?: | | |�*160;|�*[aA]0;)'; + + + ### Parser Implementation ### + + public function __construct($attr = SmartyPants::ATTR_DEFAULT) { + # + # Initialize a SmartyPantsTypographer_Parser with certain attributes. + # + # Parser attributes: + # 0 : do nothing + # 1 : set all, except dash spacing + # 2 : set all, except dash spacing, using old school en- and em- dash shortcuts + # 3 : set all, except dash spacing, using inverted old school en and em- dash shortcuts + # + # Punctuation: + # q -> quotes + # b -> backtick quotes (``double'' only) + # B -> backtick quotes (``double'' and `single') + # c -> comma quotes (,,double`` only) + # g -> guillemets (<> only) + # d -> dashes + # D -> old school dashes + # i -> inverted old school dashes + # e -> ellipses + # w -> convert " entities to " for Dreamweaver users + # + # Spacing: + # : -> colon spacing +- + # ; -> semicolon spacing +- + # m -> question and exclamation marks spacing +- + # h -> em-dash spacing +- + # H -> en-dash spacing +- + # f -> french quote spacing +- + # t -> thousand separator spacing - + # u -> unit spacing +- + # (you can add a plus sign after some of these options denoted by + to + # add the space when it is not already present, or you can add a minus + # sign to completly remove any space present) + # + # Initialize inherited SmartyPants parser. + parent::__construct($attr); + + if ($attr == "1" || $attr == "2" || $attr == "3") { + # Do everything, turn all options on. + $this->do_comma_quotes = 1; + $this->do_guillemets = 1; + $this->do_geresh_gershayim = 1; + $this->do_space_emdash = 1; + $this->do_space_endash = 1; + $this->do_space_colon = 1; + $this->do_space_semicolon = 1; + $this->do_space_marks = 1; + $this->do_space_frenchquote = 1; + $this->do_space_thousand = 1; + $this->do_space_unit = 1; + } + else if ($attr == "-1") { + # Special "stupefy" mode. + $this->do_stupefy = 1; + } + else { + $chars = preg_split('//', $attr); + foreach ($chars as $c){ + if ($c == "c") { $current =& $this->do_comma_quotes; } + else if ($c == "g") { $current =& $this->do_guillemets; } + else if ($c == "G") { $current =& $this->do_geresh_gershayim; } + else if ($c == ":") { $current =& $this->do_space_colon; } + else if ($c == ";") { $current =& $this->do_space_semicolon; } + else if ($c == "m") { $current =& $this->do_space_marks; } + else if ($c == "h") { $current =& $this->do_space_emdash; } + else if ($c == "H") { $current =& $this->do_space_endash; } + else if ($c == "f") { $current =& $this->do_space_frenchquote; } + else if ($c == "t") { $current =& $this->do_space_thousand; } + else if ($c == "u") { $current =& $this->do_space_unit; } + else if ($c == "+") { + $current = 2; + unset($current); + } + else if ($c == "-") { + $current = -1; + unset($current); + } + else { + # Unknown attribute option, ignore. + } + $current = 1; + } + } + } + + + function decodeEntitiesInConfiguration() { + parent::decodeEntitiesInConfiguration(); + $output_config_vars = array( + 'doublequote_low', + 'guillemet_leftpointing', + 'guillemet_rightpointing', + 'space_emdash', + 'space_endash', + 'space_colon', + 'space_semicolon', + 'space_marks', + 'space_frenchquote', + 'space_thousand', + 'space_unit', + ); + foreach ($output_config_vars as $var) { + $this->$var = html_entity_decode($this->$var); + } + } + + + function educate($t, $prev_token_last_char) { + # must happen before regular smart quotes + if ($this->do_geresh_gershayim) $t = $this->educateGereshGershayim($t); + + $t = parent::educate($t, $prev_token_last_char); + + if ($this->do_comma_quotes) $t = $this->educateCommaQuotes($t); + if ($this->do_guillemets) $t = $this->educateGuillemets($t); + + if ($this->do_space_emdash) $t = $this->spaceEmDash($t); + if ($this->do_space_endash) $t = $this->spaceEnDash($t); + if ($this->do_space_colon) $t = $this->spaceColon($t); + if ($this->do_space_semicolon) $t = $this->spaceSemicolon($t); + if ($this->do_space_marks) $t = $this->spaceMarks($t); + if ($this->do_space_frenchquote) $t = $this->spaceFrenchQuotes($t); + if ($this->do_space_thousand) $t = $this->spaceThousandSeparator($t); + if ($this->do_space_unit) $t = $this->spaceUnit($t); + + return $t; + } + + + protected function educateCommaQuotes($_) { + # + # Parameter: String. + # Returns: The string, with ,,comma,, -style double quotes + # translated into HTML curly quote entities. + # + # Example input: ,,Isn't this fun?,, + # Example output: „Isn't this fun?„ + # + # Note: this is meant to be used alongside with backtick quotes; there is + # no language that use only lower quotations alone mark like in the example. + # + $_ = str_replace(",,", $this->doublequote_low, $_); + return $_; + } + + + protected function educateGuillemets($_) { + # + # Parameter: String. + # Returns: The string, with << guillemets >> -style quotes + # translated into HTML guillemets entities. + # + # Example input: << Isn't this fun? >> + # Example output: „ Isn't this fun? „ + # + $_ = preg_replace("/(?:<|<){2}/", $this->guillemet_leftpointing, $_); + $_ = preg_replace("/(?:>|>){2}/", $this->guillemet_rightpointing, $_); + return $_; + } + + + protected function educateGereshGershayim($_) { + # + # Parameter: String, UTF-8 encoded. + # Returns: The string, where simple a or double quote surrounded by + # two hebrew characters is replaced into a typographic + # geresh or gershayim punctuation mark. + # + # Example input: צה"ל / צ'ארלס + # Example output: צה״ל / צ׳ארלס + # + // surrounding code points can be U+0590 to U+05BF and U+05D0 to U+05F2 + // encoded in UTF-8: D6.90 to D6.BF and D7.90 to D7.B2 + $_ = preg_replace('/(?<=\xD6[\x90-\xBF]|\xD7[\x90-\xB2])\'(?=\xD6[\x90-\xBF]|\xD7[\x90-\xB2])/', $this->geresh, $_); + $_ = preg_replace('/(?<=\xD6[\x90-\xBF]|\xD7[\x90-\xB2])"(?=\xD6[\x90-\xBF]|\xD7[\x90-\xB2])/', $this->gershayim, $_); + return $_; + } + + + protected function spaceFrenchQuotes($_) { + # + # Parameters: String, replacement character, and forcing flag. + # Returns: The string, with appropriates spaces replaced + # inside french-style quotes, only french quotes. + # + # Example input: Quotes in « French », »German« and »Finnish» style. + # Example output: Quotes in «_French_», »German« and »Finnish» style. + # + $opt = ( $this->do_space_frenchquote == 2 ? '?' : '' ); + $chr = ( $this->do_space_frenchquote != -1 ? $this->space_frenchquote : '' ); + + # Characters allowed immediatly outside quotes. + $outside_char = $this->space . '|\s|[.,:;!?\[\](){}|@*~=+-]|¡|¿'; + + $_ = preg_replace( + "/(^|$outside_char)(«|«|›|‹)$this->space$opt/", + "\\1\\2$chr", $_); + $_ = preg_replace( + "/$this->space$opt(»|»|‹|›)($outside_char|$)/", + "$chr\\1\\2", $_); + return $_; + } + + + protected function spaceColon($_) { + # + # Parameters: String, replacement character, and forcing flag. + # Returns: The string, with appropriates spaces replaced + # before colons. + # + # Example input: Ingredients : fun. + # Example output: Ingredients_: fun. + # + $opt = ( $this->do_space_colon == 2 ? '?' : '' ); + $chr = ( $this->do_space_colon != -1 ? $this->space_colon : '' ); + + $_ = preg_replace("/$this->space$opt(:)(\\s|$)/m", + "$chr\\1\\2", $_); + return $_; + } + + + protected function spaceSemicolon($_) { + # + # Parameters: String, replacement character, and forcing flag. + # Returns: The string, with appropriates spaces replaced + # before semicolons. + # + # Example input: There he goes ; there she goes. + # Example output: There he goes_; there she goes. + # + $opt = ( $this->do_space_semicolon == 2 ? '?' : '' ); + $chr = ( $this->do_space_semicolon != -1 ? $this->space_semicolon : '' ); + + $_ = preg_replace("/$this->space(;)(?=\\s|$)/m", + " \\1", $_); + $_ = preg_replace("/((?:^|\\s)(?>[^&;\\s]+|&#?[a-zA-Z0-9]+;)*)". + " $opt(;)(?=\\s|$)/m", + "\\1$chr\\2", $_); + return $_; + } + + + protected function spaceMarks($_) { + # + # Parameters: String, replacement character, and forcing flag. + # Returns: The string, with appropriates spaces replaced + # around question and exclamation marks. + # + # Example input: ¡ Holà ! What ? + # Example output: ¡_Holà_! What_? + # + $opt = ( $this->do_space_marks == 2 ? '?' : '' ); + $chr = ( $this->do_space_marks != -1 ? $this->space_marks : '' ); + + // Regular marks. + $_ = preg_replace("/$this->space$opt([?!]+)/", "$chr\\1", $_); + + // Inverted marks. + $imarks = "(?:¡|¡|¡|&#x[Aa]1;|¿|¿|¿|&#x[Bb][Ff];)"; + $_ = preg_replace("/($imarks+)$this->space$opt/", "\\1$chr", $_); + + return $_; + } + + + protected function spaceEmDash($_) { + # + # Parameters: String, two replacement characters separated by a hyphen (`-`), + # and forcing flag. + # + # Returns: The string, with appropriates spaces replaced + # around dashes. + # + # Example input: Then — without any plan — the fun happend. + # Example output: Then_—_without any plan_—_the fun happend. + # + $opt = ( $this->do_space_emdash == 2 ? '?' : '' ); + $chr = ( $this->do_space_emdash != -1 ? $this->space_emdash : '' ); + $_ = preg_replace("/$this->space$opt(—|—)$this->space$opt/", + "$chr\\1$chr", $_); + return $_; + } + + + protected function spaceEnDash($_) { + # + # Parameters: String, two replacement characters separated by a hyphen (`-`), + # and forcing flag. + # + # Returns: The string, with appropriates spaces replaced + # around dashes. + # + # Example input: Then — without any plan — the fun happend. + # Example output: Then_—_without any plan_—_the fun happend. + # + $opt = ( $this->do_space_endash == 2 ? '?' : '' ); + $chr = ( $this->do_space_endash != -1 ? $this->space_endash : '' ); + $_ = preg_replace("/$this->space$opt(–|–)$this->space$opt/", + "$chr\\1$chr", $_); + return $_; + } + + + protected function spaceThousandSeparator($_) { + # + # Parameters: String, replacement character, and forcing flag. + # Returns: The string, with appropriates spaces replaced + # inside numbers (thousand separator in french). + # + # Example input: Il y a 10 000 insectes amusants dans ton jardin. + # Example output: Il y a 10_000 insectes amusants dans ton jardin. + # + $chr = ( $this->do_space_thousand != -1 ? $this->space_thousand : '' ); + $_ = preg_replace('/([0-9]) ([0-9])/', "\\1$chr\\2", $_); + return $_; + } + + + protected $units = ' + ### Metric units (with prefixes) + (?: + p | + µ | µ | &\#0*181; | &\#[xX]0*[Bb]5; | + [mcdhkMGT] + )? + (?: + [mgstAKNJWCVFSTHBL]|mol|cd|rad|Hz|Pa|Wb|lm|lx|Bq|Gy|Sv|kat| + Ω | Ohm | Ω | &\#0*937; | &\#[xX]0*3[Aa]9; + )| + ### Computers units (KB, Kb, TB, Kbps) + [kKMGT]?(?:[oBb]|[oBb]ps|flops)| + ### Money + ¢ | ¢ | &\#0*162; | &\#[xX]0*[Aa]2; | + M?(?: + £ | £ | &\#0*163; | &\#[xX]0*[Aa]3; | + ¥ | ¥ | &\#0*165; | &\#[xX]0*[Aa]5; | + € | € | &\#0*8364; | &\#[xX]0*20[Aa][Cc]; | + $ + )| + ### Other units + (?: ° | ° | &\#0*176; | &\#[xX]0*[Bb]0; ) [CF]? | + %|pt|pi|M?px|em|en|gal|lb|[NSEOW]|[NS][EOW]|ha|mbar + '; //x + + protected function spaceUnit($_) { + # + # Parameters: String, replacement character, and forcing flag. + # Returns: The string, with appropriates spaces replaced + # before unit symbols. + # + # Example input: Get 3 mol of fun for 3 $. + # Example output: Get 3_mol of fun for 3_$. + # + $opt = ( $this->do_space_unit == 2 ? '?' : '' ); + $chr = ( $this->do_space_unit != -1 ? $this->space_unit : '' ); + + $_ = preg_replace('/ + (?:([0-9])[ ]'.$opt.') # Number followed by space. + ('.$this->units.') # Unit. + (?![a-zA-Z0-9]) # Negative lookahead for other unit characters. + /x', + "\\1$chr\\2", $_); + + return $_; + } + + + protected function spaceAbbr($_) { + # + # Parameters: String, replacement character, and forcing flag. + # Returns: The string, with appropriates spaces replaced + # around abbreviations. + # + # Example input: Fun i.e. something pleasant. + # Example output: Fun i.e._something pleasant. + # + $opt = ( $this->do_space_abbr == 2 ? '?' : '' ); + + $_ = preg_replace("/(^|\s)($this->abbr_after) $opt/m", + "\\1\\2$this->space_abbr", $_); + $_ = preg_replace("/( )$opt($this->abbr_sp_before)(?![a-zA-Z'])/m", + "\\1$this->space_abbr\\2", $_); + return $_; + } + + + protected function stupefyEntities($_) { + # + # Adding angle quotes and lower quotes to SmartyPants's stupefy mode. + # + $_ = parent::stupefyEntities($_); + + $_ = str_replace(array('„', '«', '»'), '"', $_); + + return $_; + } + + + protected function processEscapes($_) { + # + # Adding a few more escapes to SmartyPants's escapes: + # + # Escape Value + # ------ ----- + # \, , + # \< < + # \> > + # + $_ = parent::processEscapes($_); + + $_ = str_replace( + array('\,', '\<', '\>', '\<', '\>'), + array(',', '<', '>', '<', '>'), $_); + + return $_; + } +} diff --git a/kirby/vendor/michelf/php-smartypants/composer.json b/kirby/vendor/michelf/php-smartypants/composer.json new file mode 100644 index 0000000..2c2e6c1 --- /dev/null +++ b/kirby/vendor/michelf/php-smartypants/composer.json @@ -0,0 +1,26 @@ +{ + "name": "michelf/php-smartypants", + "type": "library", + "description": "PHP SmartyPants", + "homepage": "https://michelf.ca/projects/php-smartypants/", + "keywords": ["quotes", "dashes", "spaces", "typography", "typographer"], + "license": "BSD-3-Clause", + "authors": [ + { + "name": "Michel Fortin", + "email": "michel.fortin@michelf.ca", + "homepage": "https://michelf.ca/", + "role": "Developer" + }, + { + "name": "John Gruber", + "homepage": "https://daringfireball.net/" + } + ], + "require": { + "php": ">=5.3.0" + }, + "autoload": { + "psr-0": { "Michelf": "" } + } +} diff --git a/kirby/vendor/mustangostang/spyc/COPYING b/kirby/vendor/mustangostang/spyc/COPYING new file mode 100644 index 0000000..8e7ddbc --- /dev/null +++ b/kirby/vendor/mustangostang/spyc/COPYING @@ -0,0 +1,21 @@ +The MIT License + +Copyright (c) 2011 Vladimir Andersen + +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. \ No newline at end of file diff --git a/kirby/vendor/mustangostang/spyc/Spyc.php b/kirby/vendor/mustangostang/spyc/Spyc.php new file mode 100644 index 0000000..3fe0f62 --- /dev/null +++ b/kirby/vendor/mustangostang/spyc/Spyc.php @@ -0,0 +1,1186 @@ + + * @author Chris Wanstrath + * @link https://github.com/mustangostang/spyc/ + * @copyright Copyright 2005-2006 Chris Wanstrath, 2006-2011 Vlad Andersen + * @license http://www.opensource.org/licenses/mit-license.php MIT License + * @package Spyc + */ + +if (!function_exists('spyc_load')) { + /** + * Parses YAML to array. + * @param string $string YAML string. + * @return array + */ + function spyc_load ($string) { + return Spyc::YAMLLoadString($string); + } +} + +if (!function_exists('spyc_load_file')) { + /** + * Parses YAML to array. + * @param string $file Path to YAML file. + * @return array + */ + function spyc_load_file ($file) { + return Spyc::YAMLLoad($file); + } +} + +if (!function_exists('spyc_dump')) { + /** + * Dumps array to YAML. + * @param array $data Array. + * @return string + */ + function spyc_dump ($data) { + return Spyc::YAMLDump($data, false, false, true); + } +} + +if (!class_exists('Spyc')) { + +/** + * The Simple PHP YAML Class. + * + * This class can be used to read a YAML file and convert its contents + * into a PHP array. It currently supports a very limited subsection of + * the YAML spec. + * + * Usage: + * + * $Spyc = new Spyc; + * $array = $Spyc->load($file); + * + * or: + * + * $array = Spyc::YAMLLoad($file); + * + * or: + * + * $array = spyc_load_file($file); + * + * @package Spyc + */ +class Spyc { + + // SETTINGS + + const REMPTY = "\0\0\0\0\0"; + + /** + * Setting this to true will force YAMLDump to enclose any string value in + * quotes. False by default. + * + * @var bool + */ + public $setting_dump_force_quotes = false; + + /** + * Setting this to true will forse YAMLLoad to use syck_load function when + * possible. False by default. + * @var bool + */ + public $setting_use_syck_is_possible = false; + + /** + * Setting this to true will forse YAMLLoad to use syck_load function when + * possible. False by default. + * @var bool + */ + public $setting_empty_hash_as_object = false; + + + /**#@+ + * @access private + * @var mixed + */ + private $_dumpIndent; + private $_dumpWordWrap; + private $_containsGroupAnchor = false; + private $_containsGroupAlias = false; + private $path; + private $result; + private $LiteralPlaceHolder = '___YAML_Literal_Block___'; + private $SavedGroups = array(); + private $indent; + /** + * Path modifier that should be applied after adding current element. + * @var array + */ + private $delayedPath = array(); + + /**#@+ + * @access public + * @var mixed + */ + public $_nodeId; + +/** + * Load a valid YAML string to Spyc. + * @param string $input + * @return array + */ + public function load ($input) { + return $this->_loadString($input); + } + + /** + * Load a valid YAML file to Spyc. + * @param string $file + * @return array + */ + public function loadFile ($file) { + return $this->_load($file); + } + + /** + * Load YAML into a PHP array statically + * + * The load method, when supplied with a YAML stream (string or file), + * will do its best to convert YAML in a file into a PHP array. Pretty + * simple. + * Usage: + * + * $array = Spyc::YAMLLoad('lucky.yaml'); + * print_r($array); + * + * @access public + * @return array + * @param string $input Path of YAML file or string containing YAML + * @param array set options + */ + public static function YAMLLoad($input, $options = []) { + $Spyc = new Spyc; + foreach ($options as $key => $value) { + if (property_exists($Spyc, $key)) { + $Spyc->$key = $value; + } + } + return $Spyc->_load($input); + } + + /** + * Load a string of YAML into a PHP array statically + * + * The load method, when supplied with a YAML string, will do its best + * to convert YAML in a string into a PHP array. Pretty simple. + * + * Note: use this function if you don't want files from the file system + * loaded and processed as YAML. This is of interest to people concerned + * about security whose input is from a string. + * + * Usage: + * + * $array = Spyc::YAMLLoadString("---\n0: hello world\n"); + * print_r($array); + * + * @access public + * @return array + * @param string $input String containing YAML + * @param array set options + */ + public static function YAMLLoadString($input, $options = []) { + $Spyc = new Spyc; + foreach ($options as $key => $value) { + if (property_exists($Spyc, $key)) { + $Spyc->$key = $value; + } + } + return $Spyc->_loadString($input); + } + + /** + * Dump YAML from PHP array statically + * + * The dump method, when supplied with an array, will do its best + * to convert the array into friendly YAML. Pretty simple. Feel free to + * save the returned string as nothing.yaml and pass it around. + * + * Oh, and you can decide how big the indent is and what the wordwrap + * for folding is. Pretty cool -- just pass in 'false' for either if + * you want to use the default. + * + * Indent's default is 2 spaces, wordwrap's default is 40 characters. And + * you can turn off wordwrap by passing in 0. + * + * @access public + * @return string + * @param array|\stdClass $array PHP array + * @param int $indent Pass in false to use the default, which is 2 + * @param int $wordwrap Pass in 0 for no wordwrap, false for default (40) + * @param bool $no_opening_dashes Do not start YAML file with "---\n" + */ + public static function YAMLDump($array, $indent = false, $wordwrap = false, $no_opening_dashes = false) { + $spyc = new Spyc; + return $spyc->dump($array, $indent, $wordwrap, $no_opening_dashes); + } + + + /** + * Dump PHP array to YAML + * + * The dump method, when supplied with an array, will do its best + * to convert the array into friendly YAML. Pretty simple. Feel free to + * save the returned string as tasteful.yaml and pass it around. + * + * Oh, and you can decide how big the indent is and what the wordwrap + * for folding is. Pretty cool -- just pass in 'false' for either if + * you want to use the default. + * + * Indent's default is 2 spaces, wordwrap's default is 40 characters. And + * you can turn off wordwrap by passing in 0. + * + * @access public + * @return string + * @param array $array PHP array + * @param int $indent Pass in false to use the default, which is 2 + * @param int $wordwrap Pass in 0 for no wordwrap, false for default (40) + */ + public function dump($array,$indent = false,$wordwrap = false, $no_opening_dashes = false) { + // Dumps to some very clean YAML. We'll have to add some more features + // and options soon. And better support for folding. + + // New features and options. + if ($indent === false or !is_numeric($indent)) { + $this->_dumpIndent = 2; + } else { + $this->_dumpIndent = $indent; + } + + if ($wordwrap === false or !is_numeric($wordwrap)) { + $this->_dumpWordWrap = 40; + } else { + $this->_dumpWordWrap = $wordwrap; + } + + // New YAML document + $string = ""; + if (!$no_opening_dashes) $string = "---\n"; + + // Start at the base of the array and move through it. + if ($array) { + $array = (array)$array; + $previous_key = -1; + foreach ($array as $key => $value) { + if (!isset($first_key)) $first_key = $key; + $string .= $this->_yamlize($key,$value,0,$previous_key, $first_key, $array); + $previous_key = $key; + } + } + return $string; + } + + /** + * Attempts to convert a key / value array item to YAML + * @access private + * @return string + * @param $key The name of the key + * @param $value The value of the item + * @param $indent The indent of the current node + */ + private function _yamlize($key,$value,$indent, $previous_key = -1, $first_key = 0, $source_array = null) { + if(is_object($value)) $value = (array)$value; + if (is_array($value)) { + if (empty ($value)) + return $this->_dumpNode($key, array(), $indent, $previous_key, $first_key, $source_array); + // It has children. What to do? + // Make it the right kind of item + $string = $this->_dumpNode($key, self::REMPTY, $indent, $previous_key, $first_key, $source_array); + // Add the indent + $indent += $this->_dumpIndent; + // Yamlize the array + $string .= $this->_yamlizeArray($value,$indent); + } elseif (!is_array($value)) { + // It doesn't have children. Yip. + $string = $this->_dumpNode($key, $value, $indent, $previous_key, $first_key, $source_array); + } + return $string; + } + + /** + * Attempts to convert an array to YAML + * @access private + * @return string + * @param $array The array you want to convert + * @param $indent The indent of the current level + */ + private function _yamlizeArray($array,$indent) { + if (is_array($array)) { + $string = ''; + $previous_key = -1; + foreach ($array as $key => $value) { + if (!isset($first_key)) $first_key = $key; + $string .= $this->_yamlize($key, $value, $indent, $previous_key, $first_key, $array); + $previous_key = $key; + } + return $string; + } else { + return false; + } + } + + /** + * Returns YAML from a key and a value + * @access private + * @return string + * @param $key The name of the key + * @param $value The value of the item + * @param $indent The indent of the current node + */ + private function _dumpNode($key, $value, $indent, $previous_key = -1, $first_key = 0, $source_array = null) { + // do some folding here, for blocks + if (is_string ($value) && ((strpos($value,"\n") !== false || strpos($value,": ") !== false || strpos($value,"- ") !== false || + strpos($value,"*") !== false || strpos($value,"#") !== false || strpos($value,"<") !== false || strpos($value,">") !== false || strpos ($value, '%') !== false || strpos ($value, ' ') !== false || + strpos($value,"[") !== false || strpos($value,"]") !== false || strpos($value,"{") !== false || strpos($value,"}") !== false) || strpos($value,"&") !== false || strpos($value, "'") !== false || strpos($value, "!") === 0 || + substr ($value, -1, 1) == ':') + ) { + $value = $this->_doLiteralBlock($value,$indent); + } else { + $value = $this->_doFolding($value,$indent); + } + + if ($value === array()) $value = '[ ]'; + if ($value === "") $value = '""'; + if (self::isTranslationWord($value)) { + $value = $this->_doLiteralBlock($value, $indent); + } + if (trim ($value) != $value) + $value = $this->_doLiteralBlock($value,$indent); + + if (is_bool($value)) { + $value = $value ? "true" : "false"; + } + + if ($value === null) $value = 'null'; + if ($value === "'" . self::REMPTY . "'") $value = null; + + $spaces = str_repeat(' ',$indent); + + //if (is_int($key) && $key - 1 == $previous_key && $first_key===0) { + if (is_array ($source_array) && array_keys($source_array) === range(0, count($source_array) - 1)) { + // It's a sequence + $string = $spaces.'- '.$value."\n"; + } else { + // if ($first_key===0) throw new Exception('Keys are all screwy. The first one was zero, now it\'s "'. $key .'"'); + // It's mapped + if (strpos($key, ":") !== false || strpos($key, "#") !== false) { $key = '"' . $key . '"'; } + $string = rtrim ($spaces.$key.': '.$value)."\n"; + } + return $string; + } + + /** + * Creates a literal block for dumping + * @access private + * @return string + * @param $value + * @param $indent int The value of the indent + */ + private function _doLiteralBlock($value,$indent) { + if ($value === "\n") return '\n'; + if (strpos($value, "\n") === false && strpos($value, "'") === false) { + return sprintf ("'%s'", $value); + } + if (strpos($value, "\n") === false && strpos($value, '"') === false) { + return sprintf ('"%s"', $value); + } + $exploded = explode("\n",$value); + $newValue = '|'; + if (isset($exploded[0]) && ($exploded[0] == "|" || $exploded[0] == "|-" || $exploded[0] == ">")) { + $newValue = $exploded[0]; + unset($exploded[0]); + } + $indent += $this->_dumpIndent; + $spaces = str_repeat(' ',$indent); + foreach ($exploded as $line) { + $line = trim($line); + if (strpos($line, '"') === 0 && strrpos($line, '"') == (strlen($line)-1) || strpos($line, "'") === 0 && strrpos($line, "'") == (strlen($line)-1)) { + $line = substr($line, 1, -1); + } + $newValue .= "\n" . $spaces . ($line); + } + return $newValue; + } + + /** + * Folds a string of text, if necessary + * @access private + * @return string + * @param $value The string you wish to fold + */ + private function _doFolding($value,$indent) { + // Don't do anything if wordwrap is set to 0 + + if ($this->_dumpWordWrap !== 0 && is_string ($value) && strlen($value) > $this->_dumpWordWrap) { + $indent += $this->_dumpIndent; + $indent = str_repeat(' ',$indent); + $wrapped = wordwrap($value,$this->_dumpWordWrap,"\n$indent"); + $value = ">\n".$indent.$wrapped; + } else { + if ($this->setting_dump_force_quotes && is_string ($value) && $value !== self::REMPTY) + $value = '"' . $value . '"'; + if (is_numeric($value) && is_string($value)) + $value = '"' . $value . '"'; + } + + + return $value; + } + + private function isTrueWord($value) { + $words = self::getTranslations(array('true', 'on', 'yes', 'y')); + return in_array($value, $words, true); + } + + private function isFalseWord($value) { + $words = self::getTranslations(array('false', 'off', 'no', 'n')); + return in_array($value, $words, true); + } + + private function isNullWord($value) { + $words = self::getTranslations(array('null', '~')); + return in_array($value, $words, true); + } + + private function isTranslationWord($value) { + return ( + self::isTrueWord($value) || + self::isFalseWord($value) || + self::isNullWord($value) + ); + } + + /** + * Coerce a string into a native type + * Reference: http://yaml.org/type/bool.html + * TODO: Use only words from the YAML spec. + * @access private + * @param $value The value to coerce + */ + private function coerceValue(&$value) { + if (self::isTrueWord($value)) { + $value = true; + } else if (self::isFalseWord($value)) { + $value = false; + } else if (self::isNullWord($value)) { + $value = null; + } + } + + /** + * Given a set of words, perform the appropriate translations on them to + * match the YAML 1.1 specification for type coercing. + * @param $words The words to translate + * @access private + */ + private static function getTranslations(array $words) { + $result = array(); + foreach ($words as $i) { + $result = array_merge($result, array(ucfirst($i), strtoupper($i), strtolower($i))); + } + return $result; + } + +// LOADING FUNCTIONS + + private function _load($input) { + $Source = $this->loadFromSource($input); + return $this->loadWithSource($Source); + } + + private function _loadString($input) { + $Source = $this->loadFromString($input); + return $this->loadWithSource($Source); + } + + private function loadWithSource($Source) { + if (empty ($Source)) return array(); + if ($this->setting_use_syck_is_possible && function_exists ('syck_load')) { + $array = syck_load (implode ("\n", $Source)); + return is_array($array) ? $array : array(); + } + + $this->path = array(); + $this->result = array(); + + $cnt = count($Source); + for ($i = 0; $i < $cnt; $i++) { + $line = $Source[$i]; + + $this->indent = strlen($line) - strlen(ltrim($line)); + $tempPath = $this->getParentPathByIndent($this->indent); + $line = self::stripIndent($line, $this->indent); + if (self::isComment($line)) continue; + if (self::isEmpty($line)) continue; + $this->path = $tempPath; + + $literalBlockStyle = self::startsLiteralBlock($line); + if ($literalBlockStyle) { + $line = rtrim ($line, $literalBlockStyle . " \n"); + $literalBlock = ''; + $line .= ' '.$this->LiteralPlaceHolder; + $literal_block_indent = strlen($Source[$i+1]) - strlen(ltrim($Source[$i+1])); + while (++$i < $cnt && $this->literalBlockContinues($Source[$i], $this->indent)) { + $literalBlock = $this->addLiteralLine($literalBlock, $Source[$i], $literalBlockStyle, $literal_block_indent); + } + $i--; + } + + // Strip out comments + if (strpos ($line, '#')) { + $line = preg_replace('/\s*#([^"\']+)$/','',$line); + } + + while (++$i < $cnt && self::greedilyNeedNextLine($line)) { + $line = rtrim ($line, " \n\t\r") . ' ' . ltrim ($Source[$i], " \t"); + } + $i--; + + $lineArray = $this->_parseLine($line); + + if ($literalBlockStyle) + $lineArray = $this->revertLiteralPlaceHolder ($lineArray, $literalBlock); + + $this->addArray($lineArray, $this->indent); + + foreach ($this->delayedPath as $indent => $delayedPath) + $this->path[$indent] = $delayedPath; + + $this->delayedPath = array(); + + } + return $this->result; + } + + private function loadFromSource ($input) { + if (!empty($input) && strpos($input, "\n") === false && file_exists($input)) + $input = file_get_contents($input); + + return $this->loadFromString($input); + } + + private function loadFromString ($input) { + $lines = explode("\n",$input); + foreach ($lines as $k => $_) { + $lines[$k] = rtrim ($_, "\r"); + } + return $lines; + } + + /** + * Parses YAML code and returns an array for a node + * @access private + * @return array + * @param string $line A line from the YAML file + */ + private function _parseLine($line) { + if (!$line) return array(); + $line = trim($line); + if (!$line) return array(); + + $array = array(); + + $group = $this->nodeContainsGroup($line); + if ($group) { + $this->addGroup($line, $group); + $line = $this->stripGroup ($line, $group); + } + + if ($this->startsMappedSequence($line)) { + return $this->returnMappedSequence($line); + } + + if ($this->startsMappedValue($line)) { + return $this->returnMappedValue($line); + } + + if ($this->isArrayElement($line)) + return $this->returnArrayElement($line); + + if ($this->isPlainArray($line)) + return $this->returnPlainArray($line); + + return $this->returnKeyValuePair($line); + + } + + /** + * Finds the type of the passed value, returns the value as the new type. + * @access private + * @param string $value + * @return mixed + */ + private function _toType($value) { + if ($value === '') return ""; + + if ($this->setting_empty_hash_as_object && $value === '{}') { + return new stdClass(); + } + + $first_character = $value[0]; + $last_character = substr($value, -1, 1); + + $is_quoted = false; + do { + if (!$value) break; + if ($first_character != '"' && $first_character != "'") break; + if ($last_character != '"' && $last_character != "'") break; + $is_quoted = true; + } while (0); + + if ($is_quoted) { + $value = str_replace('\n', "\n", $value); + if ($first_character == "'") + return strtr(substr ($value, 1, -1), array ('\'\'' => '\'', '\\\''=> '\'')); + return strtr(substr ($value, 1, -1), array ('\\"' => '"', '\\\''=> '\'')); + } + + if (strpos($value, ' #') !== false && !$is_quoted) + $value = preg_replace('/\s+#(.+)$/','',$value); + + if ($first_character == '[' && $last_character == ']') { + // Take out strings sequences and mappings + $innerValue = trim(substr ($value, 1, -1)); + if ($innerValue === '') return array(); + $explode = $this->_inlineEscape($innerValue); + // Propagate value array + $value = array(); + foreach ($explode as $v) { + $value[] = $this->_toType($v); + } + return $value; + } + + if (strpos($value,': ')!==false && $first_character != '{') { + $array = explode(': ',$value); + $key = trim($array[0]); + array_shift($array); + $value = trim(implode(': ',$array)); + $value = $this->_toType($value); + return array($key => $value); + } + + if ($first_character == '{' && $last_character == '}') { + $innerValue = trim(substr ($value, 1, -1)); + if ($innerValue === '') return array(); + // Inline Mapping + // Take out strings sequences and mappings + $explode = $this->_inlineEscape($innerValue); + // Propagate value array + $array = array(); + foreach ($explode as $v) { + $SubArr = $this->_toType($v); + if (empty($SubArr)) continue; + if (is_array ($SubArr)) { + $array[key($SubArr)] = $SubArr[key($SubArr)]; continue; + } + $array[] = $SubArr; + } + return $array; + } + + if ($value == 'null' || $value == 'NULL' || $value == 'Null' || $value == '' || $value == '~') { + return null; + } + + if ( is_numeric($value) && preg_match ('/^(-|)[1-9]+[0-9]*$/', $value) ){ + $intvalue = (int)$value; + if ($intvalue != PHP_INT_MAX && $intvalue != ~PHP_INT_MAX) + $value = $intvalue; + return $value; + } + + if ( is_string($value) && preg_match('/^0[xX][0-9a-fA-F]+$/', $value)) { + // Hexadecimal value. + return hexdec($value); + } + + $this->coerceValue($value); + + if (is_numeric($value)) { + if ($value === '0') return 0; + if (rtrim ($value, 0) === $value) + $value = (float)$value; + return $value; + } + + return $value; + } + + /** + * Used in inlines to check for more inlines or quoted strings + * @access private + * @return array + */ + private function _inlineEscape($inline) { + // There's gotta be a cleaner way to do this... + // While pure sequences seem to be nesting just fine, + // pure mappings and mappings with sequences inside can't go very + // deep. This needs to be fixed. + + $seqs = array(); + $maps = array(); + $saved_strings = array(); + $saved_empties = array(); + + // Check for empty strings + $regex = '/("")|(\'\')/'; + if (preg_match_all($regex,$inline,$strings)) { + $saved_empties = $strings[0]; + $inline = preg_replace($regex,'YAMLEmpty',$inline); + } + unset($regex); + + // Check for strings + $regex = '/(?:(")|(?:\'))((?(1)[^"]+|[^\']+))(?(1)"|\')/'; + if (preg_match_all($regex,$inline,$strings)) { + $saved_strings = $strings[0]; + $inline = preg_replace($regex,'YAMLString',$inline); + } + unset($regex); + + // echo $inline; + + $i = 0; + do { + + // Check for sequences + while (preg_match('/\[([^{}\[\]]+)\]/U',$inline,$matchseqs)) { + $seqs[] = $matchseqs[0]; + $inline = preg_replace('/\[([^{}\[\]]+)\]/U', ('YAMLSeq' . (count($seqs) - 1) . 's'), $inline, 1); + } + + // Check for mappings + while (preg_match('/{([^\[\]{}]+)}/U',$inline,$matchmaps)) { + $maps[] = $matchmaps[0]; + $inline = preg_replace('/{([^\[\]{}]+)}/U', ('YAMLMap' . (count($maps) - 1) . 's'), $inline, 1); + } + + if ($i++ >= 10) break; + + } while (strpos ($inline, '[') !== false || strpos ($inline, '{') !== false); + + $explode = explode(',',$inline); + $explode = array_map('trim', $explode); + $stringi = 0; $i = 0; + + while (1) { + + // Re-add the sequences + if (!empty($seqs)) { + foreach ($explode as $key => $value) { + if (strpos($value,'YAMLSeq') !== false) { + foreach ($seqs as $seqk => $seq) { + $explode[$key] = str_replace(('YAMLSeq'.$seqk.'s'),$seq,$value); + $value = $explode[$key]; + } + } + } + } + + // Re-add the mappings + if (!empty($maps)) { + foreach ($explode as $key => $value) { + if (strpos($value,'YAMLMap') !== false) { + foreach ($maps as $mapk => $map) { + $explode[$key] = str_replace(('YAMLMap'.$mapk.'s'), $map, $value); + $value = $explode[$key]; + } + } + } + } + + + // Re-add the strings + if (!empty($saved_strings)) { + foreach ($explode as $key => $value) { + while (strpos($value,'YAMLString') !== false) { + $explode[$key] = preg_replace('/YAMLString/',$saved_strings[$stringi],$value, 1); + unset($saved_strings[$stringi]); + ++$stringi; + $value = $explode[$key]; + } + } + } + + + // Re-add the empties + if (!empty($saved_empties)) { + foreach ($explode as $key => $value) { + while (strpos($value,'YAMLEmpty') !== false) { + $explode[$key] = preg_replace('/YAMLEmpty/', '', $value, 1); + $value = $explode[$key]; + } + } + } + + $finished = true; + foreach ($explode as $key => $value) { + if (strpos($value,'YAMLSeq') !== false) { + $finished = false; break; + } + if (strpos($value,'YAMLMap') !== false) { + $finished = false; break; + } + if (strpos($value,'YAMLString') !== false) { + $finished = false; break; + } + if (strpos($value,'YAMLEmpty') !== false) { + $finished = false; break; + } + } + if ($finished) break; + + $i++; + if ($i > 10) + break; // Prevent infinite loops. + } + + + return $explode; + } + + private function literalBlockContinues ($line, $lineIndent) { + if (!trim($line)) return true; + if (strlen($line) - strlen(ltrim($line)) > $lineIndent) return true; + return false; + } + + private function referenceContentsByAlias ($alias) { + do { + if (!isset($this->SavedGroups[$alias])) { echo "Bad group name: $alias."; break; } + $groupPath = $this->SavedGroups[$alias]; + $value = $this->result; + foreach ($groupPath as $k) { + $value = $value[$k]; + } + } while (false); + return $value; + } + + private function addArrayInline ($array, $indent) { + $CommonGroupPath = $this->path; + if (empty ($array)) return false; + + foreach ($array as $k => $_) { + $this->addArray(array($k => $_), $indent); + $this->path = $CommonGroupPath; + } + return true; + } + + private function addArray ($incoming_data, $incoming_indent) { + + // print_r ($incoming_data); + + if (count ($incoming_data) > 1) + return $this->addArrayInline ($incoming_data, $incoming_indent); + + $key = key ($incoming_data); + $value = isset($incoming_data[$key]) ? $incoming_data[$key] : null; + if ($key === '__!YAMLZero') $key = '0'; + + if ($incoming_indent == 0 && !$this->_containsGroupAlias && !$this->_containsGroupAnchor) { // Shortcut for root-level values. + if ($key || $key === '' || $key === '0') { + $this->result[$key] = $value; + } else { + $this->result[] = $value; end ($this->result); $key = key ($this->result); + } + $this->path[$incoming_indent] = $key; + return; + } + + + + $history = array(); + // Unfolding inner array tree. + $history[] = $_arr = $this->result; + foreach ($this->path as $k) { + $history[] = $_arr = $_arr[$k]; + } + + if ($this->_containsGroupAlias) { + $value = $this->referenceContentsByAlias($this->_containsGroupAlias); + $this->_containsGroupAlias = false; + } + + + // Adding string or numeric key to the innermost level or $this->arr. + if (is_string($key) && $key == '<<') { + if (!is_array ($_arr)) { $_arr = array (); } + + $_arr = array_merge ($_arr, $value); + } else if ($key || $key === '' || $key === '0') { + if (!is_array ($_arr)) + $_arr = array ($key=>$value); + else + $_arr[$key] = $value; + } else { + if (!is_array ($_arr)) { $_arr = array ($value); $key = 0; } + else { $_arr[] = $value; end ($_arr); $key = key ($_arr); } + } + + $reverse_path = array_reverse($this->path); + $reverse_history = array_reverse ($history); + $reverse_history[0] = $_arr; + $cnt = count($reverse_history) - 1; + for ($i = 0; $i < $cnt; $i++) { + $reverse_history[$i+1][$reverse_path[$i]] = $reverse_history[$i]; + } + $this->result = $reverse_history[$cnt]; + + $this->path[$incoming_indent] = $key; + + if ($this->_containsGroupAnchor) { + $this->SavedGroups[$this->_containsGroupAnchor] = $this->path; + if (is_array ($value)) { + $k = key ($value); + if (!is_int ($k)) { + $this->SavedGroups[$this->_containsGroupAnchor][$incoming_indent + 2] = $k; + } + } + $this->_containsGroupAnchor = false; + } + + } + + private static function startsLiteralBlock ($line) { + $lastChar = substr (trim($line), -1); + if ($lastChar != '>' && $lastChar != '|') return false; + if ($lastChar == '|') return $lastChar; + // HTML tags should not be counted as literal blocks. + if (preg_match ('#<.*?>$#', $line)) return false; + return $lastChar; + } + + private static function greedilyNeedNextLine($line) { + $line = trim ($line); + if (!strlen($line)) return false; + if (substr ($line, -1, 1) == ']') return false; + if ($line[0] == '[') return true; + if (preg_match ('#^[^:]+?:\s*\[#', $line)) return true; + return false; + } + + private function addLiteralLine ($literalBlock, $line, $literalBlockStyle, $indent = -1) { + $line = self::stripIndent($line, $indent); + if ($literalBlockStyle !== '|') { + $line = self::stripIndent($line); + } + $line = rtrim ($line, "\r\n\t ") . "\n"; + if ($literalBlockStyle == '|') { + return $literalBlock . $line; + } + if (strlen($line) == 0) + return rtrim($literalBlock, ' ') . "\n"; + if ($line == "\n" && $literalBlockStyle == '>') { + return rtrim ($literalBlock, " \t") . "\n"; + } + if ($line != "\n") + $line = trim ($line, "\r\n ") . " "; + return $literalBlock . $line; + } + + function revertLiteralPlaceHolder ($lineArray, $literalBlock) { + foreach ($lineArray as $k => $_) { + if (is_array($_)) + $lineArray[$k] = $this->revertLiteralPlaceHolder ($_, $literalBlock); + else if (substr($_, -1 * strlen ($this->LiteralPlaceHolder)) == $this->LiteralPlaceHolder) + $lineArray[$k] = rtrim ($literalBlock, " \r\n"); + } + return $lineArray; + } + + private static function stripIndent ($line, $indent = -1) { + if ($indent == -1) $indent = strlen($line) - strlen(ltrim($line)); + return substr ($line, $indent); + } + + private function getParentPathByIndent ($indent) { + if ($indent == 0) return array(); + $linePath = $this->path; + do { + end($linePath); $lastIndentInParentPath = key($linePath); + if ($indent <= $lastIndentInParentPath) array_pop ($linePath); + } while ($indent <= $lastIndentInParentPath); + return $linePath; + } + + + private function clearBiggerPathValues ($indent) { + + + if ($indent == 0) $this->path = array(); + if (empty ($this->path)) return true; + + foreach ($this->path as $k => $_) { + if ($k > $indent) unset ($this->path[$k]); + } + + return true; + } + + + private static function isComment ($line) { + if (!$line) return false; + if ($line[0] == '#') return true; + if (trim($line, " \r\n\t") == '---') return true; + return false; + } + + private static function isEmpty ($line) { + return (trim ($line) === ''); + } + + + private function isArrayElement ($line) { + if (!$line || !is_scalar($line)) return false; + if (substr($line, 0, 2) != '- ') return false; + if (strlen ($line) > 3) + if (substr($line,0,3) == '---') return false; + + return true; + } + + private function isHashElement ($line) { + return strpos($line, ':'); + } + + private function isLiteral ($line) { + if ($this->isArrayElement($line)) return false; + if ($this->isHashElement($line)) return false; + return true; + } + + + private static function unquote ($value) { + if (!$value) return $value; + if (!is_string($value)) return $value; + if ($value[0] == '\'') return trim ($value, '\''); + if ($value[0] == '"') return trim ($value, '"'); + return $value; + } + + private function startsMappedSequence ($line) { + return (substr($line, 0, 2) == '- ' && substr ($line, -1, 1) == ':'); + } + + private function returnMappedSequence ($line) { + $array = array(); + $key = self::unquote(trim(substr($line,1,-1))); + $array[$key] = array(); + $this->delayedPath = array(strpos ($line, $key) + $this->indent => $key); + return array($array); + } + + private function checkKeysInValue($value) { + if (strchr('[{"\'', $value[0]) === false) { + if (strchr($value, ': ') !== false) { + throw new Exception('Too many keys: '.$value); + } + } + } + + private function returnMappedValue ($line) { + $this->checkKeysInValue($line); + $array = array(); + $key = self::unquote (trim(substr($line,0,-1))); + $array[$key] = ''; + return $array; + } + + private function startsMappedValue ($line) { + return (substr ($line, -1, 1) == ':'); + } + + private function isPlainArray ($line) { + return ($line[0] == '[' && substr ($line, -1, 1) == ']'); + } + + private function returnPlainArray ($line) { + return $this->_toType($line); + } + + private function returnKeyValuePair ($line) { + $array = array(); + $key = ''; + if (strpos ($line, ': ')) { + // It's a key/value pair most likely + // If the key is in double quotes pull it out + if (($line[0] == '"' || $line[0] == "'") && preg_match('/^(["\'](.*)["\'](\s)*:)/',$line,$matches)) { + $value = trim(str_replace($matches[1],'',$line)); + $key = $matches[2]; + } else { + // Do some guesswork as to the key and the value + $explode = explode(': ', $line); + $key = trim(array_shift($explode)); + $value = trim(implode(': ', $explode)); + $this->checkKeysInValue($value); + } + // Set the type of the value. Int, string, etc + $value = $this->_toType($value); + + if ($key === '0') $key = '__!YAMLZero'; + $array[$key] = $value; + } else { + $array = array ($line); + } + return $array; + + } + + + private function returnArrayElement ($line) { + if (strlen($line) <= 1) return array(array()); // Weird %) + $array = array(); + $value = trim(substr($line,1)); + $value = $this->_toType($value); + if ($this->isArrayElement($value)) { + $value = $this->returnArrayElement($value); + } + $array[] = $value; + return $array; + } + + + private function nodeContainsGroup ($line) { + $symbolsForReference = 'A-z0-9_\-'; + if (strpos($line, '&') === false && strpos($line, '*') === false) return false; // Please die fast ;-) + if ($line[0] == '&' && preg_match('/^(&['.$symbolsForReference.']+)/', $line, $matches)) return $matches[1]; + if ($line[0] == '*' && preg_match('/^(\*['.$symbolsForReference.']+)/', $line, $matches)) return $matches[1]; + if (preg_match('/(&['.$symbolsForReference.']+)$/', $line, $matches)) return $matches[1]; + if (preg_match('/(\*['.$symbolsForReference.']+$)/', $line, $matches)) return $matches[1]; + if (preg_match ('#^\s*<<\s*:\s*(\*[^\s]+).*$#', $line, $matches)) return $matches[1]; + return false; + + } + + private function addGroup ($line, $group) { + if ($group[0] == '&') $this->_containsGroupAnchor = substr ($group, 1); + if ($group[0] == '*') $this->_containsGroupAlias = substr ($group, 1); + //print_r ($this->path); + } + + private function stripGroup ($line, $group) { + $line = trim(str_replace($group, '', $line)); + return $line; + } +} +} + +// Enable use of Spyc from command line +// The syntax is the following: php Spyc.php spyc.yaml + +do { + if (PHP_SAPI != 'cli') break; + if (empty ($_SERVER['argc']) || $_SERVER['argc'] < 2) break; + if (empty ($_SERVER['PHP_SELF']) || FALSE === strpos ($_SERVER['PHP_SELF'], 'Spyc.php') ) break; + $file = $argv[1]; + echo json_encode (spyc_load_file ($file)); +} while (0); diff --git a/kirby/vendor/mustangostang/spyc/composer.json b/kirby/vendor/mustangostang/spyc/composer.json new file mode 100644 index 0000000..e5ab776 --- /dev/null +++ b/kirby/vendor/mustangostang/spyc/composer.json @@ -0,0 +1,30 @@ +{ + "name": "mustangostang/spyc", + "description": "A simple YAML loader/dumper class for PHP", + "type": "library", + "keywords": [ + "spyc", + "yaml", + "yml" + ], + "homepage": "https://github.com/mustangostang/spyc/", + "authors" : [{ + "name": "mustangostang", + "email": "vlad.andersen@gmail.com" + }], + "license": "MIT", + "require": { + "php": ">=5.3.1" + }, + "autoload": { + "files": [ "Spyc.php" ] + }, + "require-dev": { + "phpunit/phpunit": "4.3.*@dev" + }, + "extra": { + "branch-alias": { + "dev-master": "0.5.x-dev" + } + } +} diff --git a/kirby/vendor/phpmailer/phpmailer/LICENSE b/kirby/vendor/phpmailer/phpmailer/LICENSE new file mode 100644 index 0000000..f166cc5 --- /dev/null +++ b/kirby/vendor/phpmailer/phpmailer/LICENSE @@ -0,0 +1,502 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 2.1, February 1999 + + Copyright (C) 1991, 1999 Free Software Foundation, Inc. + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +[This is the first released version of the Lesser GPL. It also counts + as the successor of the GNU Library Public License, version 2, hence + the version number 2.1.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Lesser General Public License, applies to some +specially designated software packages--typically libraries--of the +Free Software Foundation and other authors who decide to use it. You +can use it too, but we suggest you first think carefully about whether +this license or the ordinary General Public License is the better +strategy to use in any particular case, based on the explanations below. + + When we speak of free software, we are referring to freedom of use, +not price. Our General Public Licenses are designed to make sure that +you have the freedom to distribute copies of free software (and charge +for this service if you wish); that you receive source code or can get +it if you want it; that you can change the software and use pieces of +it in new free programs; and that you are informed that you can do +these things. + + To protect your rights, we need to make restrictions that forbid +distributors to deny you these rights or to ask you to surrender these +rights. These restrictions translate to certain responsibilities for +you if you distribute copies of the library or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link other code with the library, you must provide +complete object files to the recipients, so that they can relink them +with the library after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + We protect your rights with a two-step method: (1) we copyright the +library, and (2) we offer you this license, which gives you legal +permission to copy, distribute and/or modify the library. + + To protect each distributor, we want to make it very clear that +there is no warranty for the free library. Also, if the library is +modified by someone else and passed on, the recipients should know +that what they have is not the original version, so that the original +author's reputation will not be affected by problems that might be +introduced by others. + + Finally, software patents pose a constant threat to the existence of +any free program. We wish to make sure that a company cannot +effectively restrict the users of a free program by obtaining a +restrictive license from a patent holder. Therefore, we insist that +any patent license obtained for a version of the library must be +consistent with the full freedom of use specified in this license. + + Most GNU software, including some libraries, is covered by the +ordinary GNU General Public License. This license, the GNU Lesser +General Public License, applies to certain designated libraries, and +is quite different from the ordinary General Public License. We use +this license for certain libraries in order to permit linking those +libraries into non-free programs. + + When a program is linked with a library, whether statically or using +a shared library, the combination of the two is legally speaking a +combined work, a derivative of the original library. The ordinary +General Public License therefore permits such linking only if the +entire combination fits its criteria of freedom. The Lesser General +Public License permits more lax criteria for linking other code with +the library. + + We call this license the "Lesser" General Public License because it +does Less to protect the user's freedom than the ordinary General +Public License. It also provides other free software developers Less +of an advantage over competing non-free programs. These disadvantages +are the reason we use the ordinary General Public License for many +libraries. However, the Lesser license provides advantages in certain +special circumstances. + + For example, on rare occasions, there may be a special need to +encourage the widest possible use of a certain library, so that it becomes +a de-facto standard. To achieve this, non-free programs must be +allowed to use the library. A more frequent case is that a free +library does the same job as widely used non-free libraries. In this +case, there is little to gain by limiting the free library to free +software only, so we use the Lesser General Public License. + + In other cases, permission to use a particular library in non-free +programs enables a greater number of people to use a large body of +free software. For example, permission to use the GNU C Library in +non-free programs enables many more people to use the whole GNU +operating system, as well as its variant, the GNU/Linux operating +system. + + Although the Lesser General Public License is Less protective of the +users' freedom, it does ensure that the user of a program that is +linked with the Library has the freedom and the wherewithal to run +that program using a modified version of the Library. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, whereas the latter must +be combined with the library in order to run. + + GNU LESSER GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library or other +program which contains a notice placed by the copyright holder or +other authorized party saying it may be distributed under the terms of +this Lesser General Public License (also called "this License"). +Each licensee is addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, complete source code means +all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control compilation +and installation of the library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete source code as you receive it, in any medium, provided that +you conspicuously and appropriately publish on each copy an +appropriate copyright notice and disclaimer of warranty; keep intact +all the notices that refer to this License and to the absence of any +warranty; and distribute a copy of this License along with the +Library. + + You may charge a fee for the physical act of transferring a copy, +and you may at your option offer warranty protection in exchange for a +fee. + + 2. You may modify your copy or copies of the Library or any portion +of it, thus forming a work based on the Library, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Library, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you accompany +it with the complete corresponding machine-readable source code, which +must be distributed under the terms of Sections 1 and 2 above on a +medium customarily used for software interchange. + + If distribution of object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the +source code from the same place satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also combine or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (1) uses at run time a + copy of the library already present on the user's computer system, + rather than copying library functions into the executable, and (2) + will operate properly with a modified version of the library, if + the user installs one, as long as the modified version is + interface-compatible with the version that the work was made with. + + c) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + + d) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + e) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the materials to be distributed need not include anything that is +normally distributed (in either source or binary form) with the major +components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies +the executable. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library is void, and will automatically terminate your +rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + + 9. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +subject to these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties with +this License. + + 11. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply, +and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library under this License may add +an explicit geographical distribution limitation excluding those countries, +so that distribution is permitted only in or among countries not thus +excluded. In such case, this License incorporates the limitation as if +written in the body of this License. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Lesser General Public License from time to time. +Such new versions will be similar in spirit to the present version, +but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +write to the author to ask for permission. For software which is +copyrighted by the Free Software Foundation, write to the Free +Software Foundation; we sometimes make exceptions for this. Our +decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY +KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU +FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Libraries + + If you develop a new library, and you want it to be of the greatest +possible use to the public, we recommend making it free software that +everyone can redistribute and change. You can do so by permitting +redistribution under these terms (or, alternatively, under the terms of the +ordinary General Public License). + + To apply these terms, attach the following notices to the library. It is +safest to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least the +"copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the library, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + library `Frob' (a library for tweaking knobs) written by James Random Hacker. + + , 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it! \ No newline at end of file diff --git a/kirby/vendor/phpmailer/phpmailer/composer.json b/kirby/vendor/phpmailer/phpmailer/composer.json new file mode 100644 index 0000000..58393b2 --- /dev/null +++ b/kirby/vendor/phpmailer/phpmailer/composer.json @@ -0,0 +1,65 @@ +{ + "name": "phpmailer/phpmailer", + "type": "library", + "description": "PHPMailer is a full-featured email creation and transfer class for PHP", + "authors": [ + { + "name": "Marcus Bointon", + "email": "phpmailer@synchromedia.co.uk" + }, + { + "name": "Jim Jagielski", + "email": "jimjag@gmail.com" + }, + { + "name": "Andy Prevost", + "email": "codeworxtech@users.sourceforge.net" + }, + { + "name": "Brent R. Matzelle" + } + ], + "funding": [ + { + "url": "https://github.com/Synchro", + "type": "github" + } + ], + "require": { + "php": ">=5.5.0", + "ext-ctype": "*", + "ext-filter": "*", + "ext-hash": "*" + }, + "require-dev": { + "dealerdirect/phpcodesniffer-composer-installer": "^0.7.0", + "doctrine/annotations": "^1.2", + "phpcompatibility/php-compatibility": "^9.3.5", + "roave/security-advisories": "dev-latest", + "squizlabs/php_codesniffer": "^3.5.6", + "yoast/phpunit-polyfills": "^0.2.0" + }, + "suggest": { + "ext-mbstring": "Needed to send email in multibyte encoding charset or decode encoded addresses", + "hayageek/oauth2-yahoo": "Needed for Yahoo XOAUTH2 authentication", + "league/oauth2-google": "Needed for Google XOAUTH2 authentication", + "psr/log": "For optional PSR-3 debug logging", + "stevenmaguire/oauth2-microsoft": "Needed for Microsoft XOAUTH2 authentication", + "symfony/polyfill-mbstring": "To support UTF-8 if the Mbstring PHP extension is not enabled (^1.2)" + }, + "autoload": { + "psr-4": { + "PHPMailer\\PHPMailer\\": "src/" + } + }, + "autoload-dev": { + "psr-4": { + "PHPMailer\\Test\\": "test/" + } + }, + "license": "LGPL-2.1-only", + "scripts": { + "check": "./vendor/bin/phpcs", + "test": "./vendor/bin/phpunit" + } +} diff --git a/kirby/vendor/phpmailer/phpmailer/get_oauth_token.php b/kirby/vendor/phpmailer/phpmailer/get_oauth_token.php new file mode 100644 index 0000000..befdc34 --- /dev/null +++ b/kirby/vendor/phpmailer/phpmailer/get_oauth_token.php @@ -0,0 +1,146 @@ + + * @author Jim Jagielski (jimjag) + * @author Andy Prevost (codeworxtech) + * @author Brent R. Matzelle (original founder) + * @copyright 2012 - 2020 Marcus Bointon + * @copyright 2010 - 2012 Jim Jagielski + * @copyright 2004 - 2009 Andy Prevost + * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License + * @note This program is distributed in the hope that it will be useful - WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. + */ + +/** + * Get an OAuth2 token from an OAuth2 provider. + * * Install this script on your server so that it's accessible + * as [https/http]:////get_oauth_token.php + * e.g.: http://localhost/phpmailer/get_oauth_token.php + * * Ensure dependencies are installed with 'composer install' + * * Set up an app in your Google/Yahoo/Microsoft account + * * Set the script address as the app's redirect URL + * If no refresh token is obtained when running this file, + * revoke access to your app and run the script again. + */ + +namespace PHPMailer\PHPMailer; + +/** + * Aliases for League Provider Classes + * Make sure you have added these to your composer.json and run `composer install` + * Plenty to choose from here: + * @see http://oauth2-client.thephpleague.com/providers/thirdparty/ + */ +//@see https://github.com/thephpleague/oauth2-google +use League\OAuth2\Client\Provider\Google; +//@see https://packagist.org/packages/hayageek/oauth2-yahoo +use Hayageek\OAuth2\Client\Provider\Yahoo; +//@see https://github.com/stevenmaguire/oauth2-microsoft +use Stevenmaguire\OAuth2\Client\Provider\Microsoft; + +if (!isset($_GET['code']) && !isset($_GET['provider'])) { + ?> + +Select Provider:
    +
    Google
    +Yahoo
    +Microsoft/Outlook/Hotmail/Live/Office365
    + + + $clientId, + 'clientSecret' => $clientSecret, + 'redirectUri' => $redirectUri, + 'accessType' => 'offline' +]; + +$options = []; +$provider = null; + +switch ($providerName) { + case 'Google': + $provider = new Google($params); + $options = [ + 'scope' => [ + 'https://mail.google.com/' + ] + ]; + break; + case 'Yahoo': + $provider = new Yahoo($params); + break; + case 'Microsoft': + $provider = new Microsoft($params); + $options = [ + 'scope' => [ + 'wl.imap', + 'wl.offline_access' + ] + ]; + break; +} + +if (null === $provider) { + exit('Provider missing'); +} + +if (!isset($_GET['code'])) { + //If we don't have an authorization code then get one + $authUrl = $provider->getAuthorizationUrl($options); + $_SESSION['oauth2state'] = $provider->getState(); + header('Location: ' . $authUrl); + exit; + //Check given state against previously stored one to mitigate CSRF attack +} elseif (empty($_GET['state']) || ($_GET['state'] !== $_SESSION['oauth2state'])) { + unset($_SESSION['oauth2state']); + unset($_SESSION['provider']); + exit('Invalid state'); +} else { + unset($_SESSION['provider']); + //Try to get an access token (using the authorization code grant) + $token = $provider->getAccessToken( + 'authorization_code', + [ + 'code' => $_GET['code'] + ] + ); + //Use this to interact with an API on the users behalf + //Use this to get a new access token if the old one expires + echo 'Refresh Token: ', $token->getRefreshToken(); +} diff --git a/kirby/vendor/phpmailer/phpmailer/language/phpmailer.lang-af.php b/kirby/vendor/phpmailer/phpmailer/language/phpmailer.lang-af.php new file mode 100644 index 0000000..0b2a72d --- /dev/null +++ b/kirby/vendor/phpmailer/phpmailer/language/phpmailer.lang-af.php @@ -0,0 +1,26 @@ + + */ + +$PHPMAILER_LANG['authenticate'] = 'خطأ SMTP : لا يمكن تأكيد الهوية.'; +$PHPMAILER_LANG['connect_host'] = 'خطأ SMTP: لا يمكن الاتصال بالخادم SMTP.'; +$PHPMAILER_LANG['data_not_accepted'] = 'خطأ SMTP: لم يتم قبول المعلومات .'; +$PHPMAILER_LANG['empty_message'] = 'نص الرسالة فارغ'; +$PHPMAILER_LANG['encoding'] = 'ترميز غير معروف: '; +$PHPMAILER_LANG['execute'] = 'لا يمكن تنفيذ : '; +$PHPMAILER_LANG['file_access'] = 'لا يمكن الوصول للملف: '; +$PHPMAILER_LANG['file_open'] = 'خطأ في الملف: لا يمكن فتحه: '; +$PHPMAILER_LANG['from_failed'] = 'خطأ على مستوى عنوان المرسل : '; +$PHPMAILER_LANG['instantiate'] = 'لا يمكن توفير خدمة البريد.'; +$PHPMAILER_LANG['invalid_address'] = 'الإرسال غير ممكن لأن عنوان البريد الإلكتروني غير صالح: '; +$PHPMAILER_LANG['mailer_not_supported'] = ' برنامج الإرسال غير مدعوم.'; +$PHPMAILER_LANG['provide_address'] = 'يجب توفير عنوان البريد الإلكتروني لمستلم واحد على الأقل.'; +$PHPMAILER_LANG['recipients_failed'] = 'خطأ SMTP: الأخطاء التالية فشل في الارسال لكل من : '; +$PHPMAILER_LANG['signing'] = 'خطأ في التوقيع: '; +$PHPMAILER_LANG['smtp_connect_failed'] = 'SMTP Connect() غير ممكن.'; +$PHPMAILER_LANG['smtp_error'] = 'خطأ على مستوى الخادم SMTP: '; +$PHPMAILER_LANG['variable_set'] = 'لا يمكن تعيين أو إعادة تعيين متغير: '; +$PHPMAILER_LANG['extension_missing'] = 'الإضافة غير موجودة: '; diff --git a/kirby/vendor/phpmailer/phpmailer/language/phpmailer.lang-az.php b/kirby/vendor/phpmailer/phpmailer/language/phpmailer.lang-az.php new file mode 100644 index 0000000..552167e --- /dev/null +++ b/kirby/vendor/phpmailer/phpmailer/language/phpmailer.lang-az.php @@ -0,0 +1,27 @@ + + */ + +$PHPMAILER_LANG['authenticate'] = 'SMTP Greška: Neuspjela prijava.'; +$PHPMAILER_LANG['connect_host'] = 'SMTP Greška: Nije moguće spojiti se sa SMTP serverom.'; +$PHPMAILER_LANG['data_not_accepted'] = 'SMTP Greška: Podatci nisu prihvaćeni.'; +$PHPMAILER_LANG['empty_message'] = 'Sadržaj poruke je prazan.'; +$PHPMAILER_LANG['encoding'] = 'Nepoznata kriptografija: '; +$PHPMAILER_LANG['execute'] = 'Nije moguće izvršiti naredbu: '; +$PHPMAILER_LANG['file_access'] = 'Nije moguće pristupiti datoteci: '; +$PHPMAILER_LANG['file_open'] = 'Nije moguće otvoriti datoteku: '; +$PHPMAILER_LANG['from_failed'] = 'SMTP Greška: Slanje sa navedenih e-mail adresa nije uspjelo: '; +$PHPMAILER_LANG['recipients_failed'] = 'SMTP Greška: Slanje na navedene e-mail adrese nije uspjelo: '; +$PHPMAILER_LANG['instantiate'] = 'Ne mogu pokrenuti mail funkcionalnost.'; +$PHPMAILER_LANG['invalid_address'] = 'E-mail nije poslan. Neispravna e-mail adresa: '; +$PHPMAILER_LANG['mailer_not_supported'] = ' mailer nije podržan.'; +$PHPMAILER_LANG['provide_address'] = 'Definišite barem jednu adresu primaoca.'; +$PHPMAILER_LANG['signing'] = 'Greška prilikom prijave: '; +$PHPMAILER_LANG['smtp_connect_failed'] = 'Spajanje na SMTP server nije uspjelo.'; +$PHPMAILER_LANG['smtp_error'] = 'SMTP greška: '; +$PHPMAILER_LANG['variable_set'] = 'Nije moguće postaviti varijablu ili je vratiti nazad: '; +$PHPMAILER_LANG['extension_missing'] = 'Nedostaje ekstenzija: '; diff --git a/kirby/vendor/phpmailer/phpmailer/language/phpmailer.lang-be.php b/kirby/vendor/phpmailer/phpmailer/language/phpmailer.lang-be.php new file mode 100644 index 0000000..9e92dda --- /dev/null +++ b/kirby/vendor/phpmailer/phpmailer/language/phpmailer.lang-be.php @@ -0,0 +1,27 @@ + + */ + +$PHPMAILER_LANG['authenticate'] = 'Памылка SMTP: памылка ідэнтыфікацыі.'; +$PHPMAILER_LANG['connect_host'] = 'Памылка SMTP: нельга ўстанавіць сувязь з SMTP-серверам.'; +$PHPMAILER_LANG['data_not_accepted'] = 'Памылка SMTP: звесткі непрынятыя.'; +$PHPMAILER_LANG['empty_message'] = 'Пустое паведамленне.'; +$PHPMAILER_LANG['encoding'] = 'Невядомая кадыроўка тэксту: '; +$PHPMAILER_LANG['execute'] = 'Нельга выканаць каманду: '; +$PHPMAILER_LANG['file_access'] = 'Няма доступу да файла: '; +$PHPMAILER_LANG['file_open'] = 'Нельга адкрыць файл: '; +$PHPMAILER_LANG['from_failed'] = 'Няправільны адрас адпраўніка: '; +$PHPMAILER_LANG['instantiate'] = 'Нельга прымяніць функцыю mail().'; +$PHPMAILER_LANG['invalid_address'] = 'Нельга даслаць паведамленне, няправільны email атрымальніка: '; +$PHPMAILER_LANG['provide_address'] = 'Запоўніце, калі ласка, правільны email атрымальніка.'; +$PHPMAILER_LANG['mailer_not_supported'] = ' - паштовы сервер не падтрымліваецца.'; +$PHPMAILER_LANG['recipients_failed'] = 'Памылка SMTP: няправільныя атрымальнікі: '; +$PHPMAILER_LANG['signing'] = 'Памылка подпісу паведамлення: '; +$PHPMAILER_LANG['smtp_connect_failed'] = 'Памылка сувязі з SMTP-серверам.'; +$PHPMAILER_LANG['smtp_error'] = 'Памылка SMTP: '; +$PHPMAILER_LANG['variable_set'] = 'Нельга ўстанавіць або перамяніць значэнне пераменнай: '; +//$PHPMAILER_LANG['extension_missing'] = 'Extension missing: '; diff --git a/kirby/vendor/phpmailer/phpmailer/language/phpmailer.lang-bg.php b/kirby/vendor/phpmailer/phpmailer/language/phpmailer.lang-bg.php new file mode 100644 index 0000000..c41f675 --- /dev/null +++ b/kirby/vendor/phpmailer/phpmailer/language/phpmailer.lang-bg.php @@ -0,0 +1,27 @@ + + */ + +$PHPMAILER_LANG['authenticate'] = 'SMTP грешка: Не може да се удостовери пред сървъра.'; +$PHPMAILER_LANG['connect_host'] = 'SMTP грешка: Не може да се свърже с SMTP хоста.'; +$PHPMAILER_LANG['data_not_accepted'] = 'SMTP грешка: данните не са приети.'; +$PHPMAILER_LANG['empty_message'] = 'Съдържанието на съобщението е празно'; +$PHPMAILER_LANG['encoding'] = 'Неизвестно кодиране: '; +$PHPMAILER_LANG['execute'] = 'Не може да се изпълни: '; +$PHPMAILER_LANG['file_access'] = 'Няма достъп до файл: '; +$PHPMAILER_LANG['file_open'] = 'Файлова грешка: Не може да се отвори файл: '; +$PHPMAILER_LANG['from_failed'] = 'Следните адреси за подател са невалидни: '; +$PHPMAILER_LANG['instantiate'] = 'Не може да се инстанцира функцията mail.'; +$PHPMAILER_LANG['invalid_address'] = 'Невалиден адрес: '; +$PHPMAILER_LANG['mailer_not_supported'] = ' - пощенски сървър не се поддържа.'; +$PHPMAILER_LANG['provide_address'] = 'Трябва да предоставите поне един email адрес за получател.'; +$PHPMAILER_LANG['recipients_failed'] = 'SMTP грешка: Следните адреси за Получател са невалидни: '; +$PHPMAILER_LANG['signing'] = 'Грешка при подписване: '; +$PHPMAILER_LANG['smtp_connect_failed'] = 'SMTP провален connect().'; +$PHPMAILER_LANG['smtp_error'] = 'SMTP сървърна грешка: '; +$PHPMAILER_LANG['variable_set'] = 'Не може да се установи или възстанови променлива: '; +$PHPMAILER_LANG['extension_missing'] = 'Липсва разширение: '; diff --git a/kirby/vendor/phpmailer/phpmailer/language/phpmailer.lang-ca.php b/kirby/vendor/phpmailer/phpmailer/language/phpmailer.lang-ca.php new file mode 100644 index 0000000..3468485 --- /dev/null +++ b/kirby/vendor/phpmailer/phpmailer/language/phpmailer.lang-ca.php @@ -0,0 +1,27 @@ + + */ + +$PHPMAILER_LANG['authenticate'] = 'Error SMTP: No s’ha pogut autenticar.'; +$PHPMAILER_LANG['connect_host'] = 'Error SMTP: No es pot connectar al servidor SMTP.'; +$PHPMAILER_LANG['data_not_accepted'] = 'Error SMTP: Dades no acceptades.'; +$PHPMAILER_LANG['empty_message'] = 'El cos del missatge està buit.'; +$PHPMAILER_LANG['encoding'] = 'Codificació desconeguda: '; +$PHPMAILER_LANG['execute'] = 'No es pot executar: '; +$PHPMAILER_LANG['file_access'] = 'No es pot accedir a l’arxiu: '; +$PHPMAILER_LANG['file_open'] = 'Error d’Arxiu: No es pot obrir l’arxiu: '; +$PHPMAILER_LANG['from_failed'] = 'La(s) següent(s) adreces de remitent han fallat: '; +$PHPMAILER_LANG['instantiate'] = 'No s’ha pogut crear una instància de la funció Mail.'; +$PHPMAILER_LANG['invalid_address'] = 'Adreça d’email invalida: '; +$PHPMAILER_LANG['mailer_not_supported'] = ' mailer no està suportat'; +$PHPMAILER_LANG['provide_address'] = 'S’ha de proveir almenys una adreça d’email com a destinatari.'; +$PHPMAILER_LANG['recipients_failed'] = 'Error SMTP: Els següents destinataris han fallat: '; +$PHPMAILER_LANG['signing'] = 'Error al signar: '; +$PHPMAILER_LANG['smtp_connect_failed'] = 'Ha fallat el SMTP Connect().'; +$PHPMAILER_LANG['smtp_error'] = 'Error del servidor SMTP: '; +$PHPMAILER_LANG['variable_set'] = 'No s’ha pogut establir o restablir la variable: '; +//$PHPMAILER_LANG['extension_missing'] = 'Extension missing: '; diff --git a/kirby/vendor/phpmailer/phpmailer/language/phpmailer.lang-ch.php b/kirby/vendor/phpmailer/phpmailer/language/phpmailer.lang-ch.php new file mode 100644 index 0000000..500c952 --- /dev/null +++ b/kirby/vendor/phpmailer/phpmailer/language/phpmailer.lang-ch.php @@ -0,0 +1,27 @@ + + */ + +$PHPMAILER_LANG['authenticate'] = 'SMTP 错误:身份验证失败。'; +$PHPMAILER_LANG['connect_host'] = 'SMTP 错误: 不能连接SMTP主机。'; +$PHPMAILER_LANG['data_not_accepted'] = 'SMTP 错误: 数据不可接受。'; +//$PHPMAILER_LANG['empty_message'] = 'Message body empty'; +$PHPMAILER_LANG['encoding'] = '未知编码:'; +$PHPMAILER_LANG['execute'] = '不能执行: '; +$PHPMAILER_LANG['file_access'] = '不能访问文件:'; +$PHPMAILER_LANG['file_open'] = '文件错误:不能打开文件:'; +$PHPMAILER_LANG['from_failed'] = '下面的发送地址邮件发送失败了: '; +$PHPMAILER_LANG['instantiate'] = '不能实现mail方法。'; +//$PHPMAILER_LANG['invalid_address'] = 'Invalid address: '; +$PHPMAILER_LANG['mailer_not_supported'] = ' 您所选择的发送邮件的方法并不支持。'; +$PHPMAILER_LANG['provide_address'] = '您必须提供至少一个 收信人的email地址。'; +$PHPMAILER_LANG['recipients_failed'] = 'SMTP 错误: 下面的 收件人失败了: '; +//$PHPMAILER_LANG['signing'] = 'Signing Error: '; +//$PHPMAILER_LANG['smtp_connect_failed'] = 'SMTP Connect() failed.'; +//$PHPMAILER_LANG['smtp_error'] = 'SMTP server error: '; +//$PHPMAILER_LANG['variable_set'] = 'Cannot set or reset variable: '; +//$PHPMAILER_LANG['extension_missing'] = 'Extension missing: '; diff --git a/kirby/vendor/phpmailer/phpmailer/language/phpmailer.lang-cs.php b/kirby/vendor/phpmailer/phpmailer/language/phpmailer.lang-cs.php new file mode 100644 index 0000000..e770a1a --- /dev/null +++ b/kirby/vendor/phpmailer/phpmailer/language/phpmailer.lang-cs.php @@ -0,0 +1,28 @@ + + * Rewrite and extension of the work by Mikael Stokkebro + * + */ + +$PHPMAILER_LANG['authenticate'] = 'SMTP fejl: Login mislykkedes.'; +$PHPMAILER_LANG['connect_host'] = 'SMTP fejl: Forbindelse til SMTP serveren kunne ikke oprettes.'; +$PHPMAILER_LANG['data_not_accepted'] = 'SMTP fejl: Data blev ikke accepteret.'; +$PHPMAILER_LANG['empty_message'] = 'Meddelelsen er uden indhold'; +$PHPMAILER_LANG['encoding'] = 'Ukendt encode-format: '; +$PHPMAILER_LANG['execute'] = 'Kunne ikke afvikle: '; +$PHPMAILER_LANG['file_access'] = 'Kunne ikke tilgå filen: '; +$PHPMAILER_LANG['file_open'] = 'Fil fejl: Kunne ikke åbne filen: '; +$PHPMAILER_LANG['from_failed'] = 'Følgende afsenderadresse er forkert: '; +$PHPMAILER_LANG['instantiate'] = 'Email funktionen kunne ikke initialiseres.'; +$PHPMAILER_LANG['invalid_address'] = 'Udgyldig adresse: '; +$PHPMAILER_LANG['mailer_not_supported'] = ' mailer understøttes ikke.'; +$PHPMAILER_LANG['provide_address'] = 'Indtast mindst en modtagers email adresse.'; +$PHPMAILER_LANG['recipients_failed'] = 'SMTP fejl: Følgende modtagere er forkerte: '; +$PHPMAILER_LANG['signing'] = 'Signeringsfejl: '; +$PHPMAILER_LANG['smtp_connect_failed'] = 'SMTP Connect() fejlede.'; +$PHPMAILER_LANG['smtp_error'] = 'SMTP server fejl: '; +$PHPMAILER_LANG['variable_set'] = 'Kunne ikke definere eller nulstille variablen: '; +$PHPMAILER_LANG['extension_missing'] = 'Udvidelse mangler: '; diff --git a/kirby/vendor/phpmailer/phpmailer/language/phpmailer.lang-de.php b/kirby/vendor/phpmailer/phpmailer/language/phpmailer.lang-de.php new file mode 100644 index 0000000..e7e59d2 --- /dev/null +++ b/kirby/vendor/phpmailer/phpmailer/language/phpmailer.lang-de.php @@ -0,0 +1,28 @@ + + */ + +$PHPMAILER_LANG['authenticate'] = 'Error SMTP: Imposible autentificar.'; +$PHPMAILER_LANG['connect_host'] = 'Error SMTP: Imposible conectar al servidor SMTP.'; +$PHPMAILER_LANG['data_not_accepted'] = 'Error SMTP: Datos no aceptados.'; +$PHPMAILER_LANG['empty_message'] = 'El cuerpo del mensaje está vacío.'; +$PHPMAILER_LANG['encoding'] = 'Codificación desconocida: '; +$PHPMAILER_LANG['execute'] = 'Imposible ejecutar: '; +$PHPMAILER_LANG['file_access'] = 'Imposible acceder al archivo: '; +$PHPMAILER_LANG['file_open'] = 'Error de Archivo: Imposible abrir el archivo: '; +$PHPMAILER_LANG['from_failed'] = 'La(s) siguiente(s) direcciones de remitente fallaron: '; +$PHPMAILER_LANG['instantiate'] = 'Imposible crear una instancia de la función Mail.'; +$PHPMAILER_LANG['invalid_address'] = 'Imposible enviar: dirección de email inválido: '; +$PHPMAILER_LANG['mailer_not_supported'] = ' mailer no está soportado.'; +$PHPMAILER_LANG['provide_address'] = 'Debe proporcionar al menos una dirección de email de destino.'; +$PHPMAILER_LANG['recipients_failed'] = 'Error SMTP: Los siguientes destinos fallaron: '; +$PHPMAILER_LANG['signing'] = 'Error al firmar: '; +$PHPMAILER_LANG['smtp_connect_failed'] = 'SMTP Connect() falló.'; +$PHPMAILER_LANG['smtp_error'] = 'Error del servidor SMTP: '; +$PHPMAILER_LANG['variable_set'] = 'No se pudo configurar la variable: '; +$PHPMAILER_LANG['extension_missing'] = 'Extensión faltante: '; diff --git a/kirby/vendor/phpmailer/phpmailer/language/phpmailer.lang-et.php b/kirby/vendor/phpmailer/phpmailer/language/phpmailer.lang-et.php new file mode 100644 index 0000000..93addc9 --- /dev/null +++ b/kirby/vendor/phpmailer/phpmailer/language/phpmailer.lang-et.php @@ -0,0 +1,28 @@ + + */ + +$PHPMAILER_LANG['authenticate'] = 'SMTP Viga: Autoriseerimise viga.'; +$PHPMAILER_LANG['connect_host'] = 'SMTP Viga: Ei õnnestunud luua ühendust SMTP serveriga.'; +$PHPMAILER_LANG['data_not_accepted'] = 'SMTP Viga: Vigased andmed.'; +$PHPMAILER_LANG['empty_message'] = 'Tühi kirja sisu'; +$PHPMAILER_LANG["encoding"] = 'Tundmatu kodeering: '; +$PHPMAILER_LANG['execute'] = 'Tegevus ebaõnnestus: '; +$PHPMAILER_LANG['file_access'] = 'Pole piisavalt õiguseid järgneva faili avamiseks: '; +$PHPMAILER_LANG['file_open'] = 'Faili Viga: Faili avamine ebaõnnestus: '; +$PHPMAILER_LANG['from_failed'] = 'Järgnev saatja e-posti aadress on vigane: '; +$PHPMAILER_LANG['instantiate'] = 'mail funktiooni käivitamine ebaõnnestus.'; +$PHPMAILER_LANG['invalid_address'] = 'Saatmine peatatud, e-posti address vigane: '; +$PHPMAILER_LANG['provide_address'] = 'Te peate määrama vähemalt ühe saaja e-posti aadressi.'; +$PHPMAILER_LANG['mailer_not_supported'] = ' maileri tugi puudub.'; +$PHPMAILER_LANG['recipients_failed'] = 'SMTP Viga: Järgnevate saajate e-posti aadressid on vigased: '; +$PHPMAILER_LANG["signing"] = 'Viga allkirjastamisel: '; +$PHPMAILER_LANG['smtp_connect_failed'] = 'SMTP Connect() ebaõnnestus.'; +$PHPMAILER_LANG['smtp_error'] = 'SMTP serveri viga: '; +$PHPMAILER_LANG['variable_set'] = 'Ei õnnestunud määrata või lähtestada muutujat: '; +$PHPMAILER_LANG['extension_missing'] = 'Nõutud laiendus on puudu: '; diff --git a/kirby/vendor/phpmailer/phpmailer/language/phpmailer.lang-fa.php b/kirby/vendor/phpmailer/phpmailer/language/phpmailer.lang-fa.php new file mode 100644 index 0000000..295a47f --- /dev/null +++ b/kirby/vendor/phpmailer/phpmailer/language/phpmailer.lang-fa.php @@ -0,0 +1,28 @@ + + * @author Mohammad Hossein Mojtahedi + */ + +$PHPMAILER_LANG['authenticate'] = 'خطای SMTP: احراز هویت با شکست مواجه شد.'; +$PHPMAILER_LANG['connect_host'] = 'خطای SMTP: اتصال به سرور SMTP برقرار نشد.'; +$PHPMAILER_LANG['data_not_accepted'] = 'خطای SMTP: داده‌ها نا‌درست هستند.'; +$PHPMAILER_LANG['empty_message'] = 'بخش متن پیام خالی است.'; +$PHPMAILER_LANG['encoding'] = 'کد‌گذاری نا‌شناخته: '; +$PHPMAILER_LANG['execute'] = 'امکان اجرا وجود ندارد: '; +$PHPMAILER_LANG['file_access'] = 'امکان دسترسی به فایل وجود ندارد: '; +$PHPMAILER_LANG['file_open'] = 'خطای File: امکان بازکردن فایل وجود ندارد: '; +$PHPMAILER_LANG['from_failed'] = 'آدرس فرستنده اشتباه است: '; +$PHPMAILER_LANG['instantiate'] = 'امکان معرفی تابع ایمیل وجود ندارد.'; +$PHPMAILER_LANG['invalid_address'] = 'آدرس ایمیل معتبر نیست: '; +$PHPMAILER_LANG['mailer_not_supported'] = ' mailer پشتیبانی نمی‌شود.'; +$PHPMAILER_LANG['provide_address'] = 'باید حداقل یک آدرس گیرنده وارد کنید.'; +$PHPMAILER_LANG['recipients_failed'] = 'خطای SMTP: ارسال به آدرس گیرنده با خطا مواجه شد: '; +$PHPMAILER_LANG['signing'] = 'خطا در امضا: '; +$PHPMAILER_LANG['smtp_connect_failed'] = 'خطا در اتصال به SMTP.'; +$PHPMAILER_LANG['smtp_error'] = 'خطا در SMTP Server: '; +$PHPMAILER_LANG['variable_set'] = 'امکان ارسال یا ارسال مجدد متغیر‌ها وجود ندارد: '; +$PHPMAILER_LANG['extension_missing'] = 'افزونه موجود نیست: '; diff --git a/kirby/vendor/phpmailer/phpmailer/language/phpmailer.lang-fi.php b/kirby/vendor/phpmailer/phpmailer/language/phpmailer.lang-fi.php new file mode 100644 index 0000000..243c054 --- /dev/null +++ b/kirby/vendor/phpmailer/phpmailer/language/phpmailer.lang-fi.php @@ -0,0 +1,28 @@ + + */ + +$PHPMAILER_LANG['authenticate'] = 'SMTP feilur: Kundi ikki góðkenna.'; +$PHPMAILER_LANG['connect_host'] = 'SMTP feilur: Kundi ikki knýta samband við SMTP vert.'; +$PHPMAILER_LANG['data_not_accepted'] = 'SMTP feilur: Data ikki góðkent.'; +//$PHPMAILER_LANG['empty_message'] = 'Message body empty'; +$PHPMAILER_LANG['encoding'] = 'Ókend encoding: '; +$PHPMAILER_LANG['execute'] = 'Kundi ikki útføra: '; +$PHPMAILER_LANG['file_access'] = 'Kundi ikki tilganga fílu: '; +$PHPMAILER_LANG['file_open'] = 'Fílu feilur: Kundi ikki opna fílu: '; +$PHPMAILER_LANG['from_failed'] = 'fylgjandi Frá/From adressa miseydnaðist: '; +$PHPMAILER_LANG['instantiate'] = 'Kuni ikki instantiera mail funktión.'; +//$PHPMAILER_LANG['invalid_address'] = 'Invalid address: '; +$PHPMAILER_LANG['mailer_not_supported'] = ' er ikki supporterað.'; +$PHPMAILER_LANG['provide_address'] = 'Tú skal uppgeva minst móttakara-emailadressu(r).'; +$PHPMAILER_LANG['recipients_failed'] = 'SMTP Feilur: Fylgjandi móttakarar miseydnaðust: '; +//$PHPMAILER_LANG['signing'] = 'Signing Error: '; +//$PHPMAILER_LANG['smtp_connect_failed'] = 'SMTP Connect() failed.'; +//$PHPMAILER_LANG['smtp_error'] = 'SMTP server error: '; +//$PHPMAILER_LANG['variable_set'] = 'Cannot set or reset variable: '; +//$PHPMAILER_LANG['extension_missing'] = 'Extension missing: '; diff --git a/kirby/vendor/phpmailer/phpmailer/language/phpmailer.lang-fr.php b/kirby/vendor/phpmailer/phpmailer/language/phpmailer.lang-fr.php new file mode 100644 index 0000000..b57f0ec --- /dev/null +++ b/kirby/vendor/phpmailer/phpmailer/language/phpmailer.lang-fr.php @@ -0,0 +1,32 @@ + + */ + +$PHPMAILER_LANG['authenticate'] = 'Erro SMTP: Non puido ser autentificado.'; +$PHPMAILER_LANG['connect_host'] = 'Erro SMTP: Non puido conectar co servidor SMTP.'; +$PHPMAILER_LANG['data_not_accepted'] = 'Erro SMTP: Datos non aceptados.'; +$PHPMAILER_LANG['empty_message'] = 'Corpo da mensaxe vacía'; +$PHPMAILER_LANG['encoding'] = 'Codificación descoñecida: '; +$PHPMAILER_LANG['execute'] = 'Non puido ser executado: '; +$PHPMAILER_LANG['file_access'] = 'Nob puido acceder ó arquivo: '; +$PHPMAILER_LANG['file_open'] = 'Erro de Arquivo: No puido abrir o arquivo: '; +$PHPMAILER_LANG['from_failed'] = 'A(s) seguinte(s) dirección(s) de remitente(s) deron erro: '; +$PHPMAILER_LANG['instantiate'] = 'Non puido crear unha instancia da función Mail.'; +$PHPMAILER_LANG['invalid_address'] = 'Non puido envia-lo correo: dirección de email inválida: '; +$PHPMAILER_LANG['mailer_not_supported'] = ' mailer non está soportado.'; +$PHPMAILER_LANG['provide_address'] = 'Debe engadir polo menos unha dirección de email coma destino.'; +$PHPMAILER_LANG['recipients_failed'] = 'Erro SMTP: Os seguintes destinos fallaron: '; +$PHPMAILER_LANG['signing'] = 'Erro ó firmar: '; +$PHPMAILER_LANG['smtp_connect_failed'] = 'SMTP Connect() fallou.'; +$PHPMAILER_LANG['smtp_error'] = 'Erro do servidor SMTP: '; +$PHPMAILER_LANG['variable_set'] = 'Non puidemos axustar ou reaxustar a variábel: '; +//$PHPMAILER_LANG['extension_missing'] = 'Extension missing: '; diff --git a/kirby/vendor/phpmailer/phpmailer/language/phpmailer.lang-he.php b/kirby/vendor/phpmailer/phpmailer/language/phpmailer.lang-he.php new file mode 100644 index 0000000..b123aa5 --- /dev/null +++ b/kirby/vendor/phpmailer/phpmailer/language/phpmailer.lang-he.php @@ -0,0 +1,27 @@ + + */ + +$PHPMAILER_LANG['authenticate'] = 'שגיאת SMTP: פעולת האימות נכשלה.'; +$PHPMAILER_LANG['connect_host'] = 'שגיאת SMTP: לא הצלחתי להתחבר לשרת SMTP.'; +$PHPMAILER_LANG['data_not_accepted'] = 'שגיאת SMTP: מידע לא התקבל.'; +$PHPMAILER_LANG['empty_message'] = 'גוף ההודעה ריק'; +$PHPMAILER_LANG['invalid_address'] = 'כתובת שגויה: '; +$PHPMAILER_LANG['encoding'] = 'קידוד לא מוכר: '; +$PHPMAILER_LANG['execute'] = 'לא הצלחתי להפעיל את: '; +$PHPMAILER_LANG['file_access'] = 'לא ניתן לגשת לקובץ: '; +$PHPMAILER_LANG['file_open'] = 'שגיאת קובץ: לא ניתן לגשת לקובץ: '; +$PHPMAILER_LANG['from_failed'] = 'כתובות הנמענים הבאות נכשלו: '; +$PHPMAILER_LANG['instantiate'] = 'לא הצלחתי להפעיל את פונקציית המייל.'; +$PHPMAILER_LANG['mailer_not_supported'] = ' אינה נתמכת.'; +$PHPMAILER_LANG['provide_address'] = 'חובה לספק לפחות כתובת אחת של מקבל המייל.'; +$PHPMAILER_LANG['recipients_failed'] = 'שגיאת SMTP: הנמענים הבאים נכשלו: '; +$PHPMAILER_LANG['signing'] = 'שגיאת חתימה: '; +$PHPMAILER_LANG['smtp_connect_failed'] = 'SMTP Connect() failed.'; +$PHPMAILER_LANG['smtp_error'] = 'שגיאת שרת SMTP: '; +$PHPMAILER_LANG['variable_set'] = 'לא ניתן לקבוע או לשנות את המשתנה: '; +//$PHPMAILER_LANG['extension_missing'] = 'Extension missing: '; diff --git a/kirby/vendor/phpmailer/phpmailer/language/phpmailer.lang-hi.php b/kirby/vendor/phpmailer/phpmailer/language/phpmailer.lang-hi.php new file mode 100644 index 0000000..d973a35 --- /dev/null +++ b/kirby/vendor/phpmailer/phpmailer/language/phpmailer.lang-hi.php @@ -0,0 +1,27 @@ + + */ + +$PHPMAILER_LANG['authenticate'] = 'SMTP त्रुटि: प्रामाणिकता की जांच नहीं हो सका। '; +$PHPMAILER_LANG['connect_host'] = 'SMTP त्रुटि: SMTP सर्वर से कनेक्ट नहीं हो सका। '; +$PHPMAILER_LANG['data_not_accepted'] = 'SMTP त्रुटि: डेटा स्वीकार नहीं किया जाता है। '; +$PHPMAILER_LANG['empty_message'] = 'संदेश खाली है। '; +$PHPMAILER_LANG['encoding'] = 'अज्ञात एन्कोडिंग प्रकार। '; +$PHPMAILER_LANG['execute'] = 'आदेश को निष्पादित करने में विफल। '; +$PHPMAILER_LANG['file_access'] = 'फ़ाइल उपलब्ध नहीं है। '; +$PHPMAILER_LANG['file_open'] = 'फ़ाइल त्रुटि: फाइल को खोला नहीं जा सका। '; +$PHPMAILER_LANG['from_failed'] = 'प्रेषक का पता गलत है। '; +$PHPMAILER_LANG['instantiate'] = 'मेल फ़ंक्शन कॉल नहीं कर सकता है।'; +$PHPMAILER_LANG['invalid_address'] = 'पता गलत है। '; +$PHPMAILER_LANG['mailer_not_supported'] = 'मेल सर्वर के साथ काम नहीं करता है। '; +$PHPMAILER_LANG['provide_address'] = 'आपको कम से कम एक प्राप्तकर्ता का ई-मेल पता प्रदान करना होगा।'; +$PHPMAILER_LANG['recipients_failed'] = 'SMTP त्रुटि: निम्न प्राप्तकर्ताओं को पते भेजने में विफल। '; +$PHPMAILER_LANG['signing'] = 'साइनअप त्रुटि:। '; +$PHPMAILER_LANG['smtp_connect_failed'] = 'SMTP का connect () फ़ंक्शन विफल हुआ। '; +$PHPMAILER_LANG['smtp_error'] = 'SMTP सर्वर त्रुटि। '; +$PHPMAILER_LANG['variable_set'] = 'चर को बना या संशोधित नहीं किया जा सकता। '; +$PHPMAILER_LANG['extension_missing'] = 'एक्सटेन्षन गायब है: '; diff --git a/kirby/vendor/phpmailer/phpmailer/language/phpmailer.lang-hr.php b/kirby/vendor/phpmailer/phpmailer/language/phpmailer.lang-hr.php new file mode 100644 index 0000000..cacb6c3 --- /dev/null +++ b/kirby/vendor/phpmailer/phpmailer/language/phpmailer.lang-hr.php @@ -0,0 +1,27 @@ + + */ + +$PHPMAILER_LANG['authenticate'] = 'SMTP Greška: Neuspjela autentikacija.'; +$PHPMAILER_LANG['connect_host'] = 'SMTP Greška: Ne mogu se spojiti na SMTP poslužitelj.'; +$PHPMAILER_LANG['data_not_accepted'] = 'SMTP Greška: Podatci nisu prihvaćeni.'; +$PHPMAILER_LANG['empty_message'] = 'Sadržaj poruke je prazan.'; +$PHPMAILER_LANG['encoding'] = 'Nepoznati encoding: '; +$PHPMAILER_LANG['execute'] = 'Nije moguće izvršiti naredbu: '; +$PHPMAILER_LANG['file_access'] = 'Nije moguće pristupiti datoteci: '; +$PHPMAILER_LANG['file_open'] = 'Nije moguće otvoriti datoteku: '; +$PHPMAILER_LANG['from_failed'] = 'SMTP Greška: Slanje s navedenih e-mail adresa nije uspjelo: '; +$PHPMAILER_LANG['recipients_failed'] = 'SMTP Greška: Slanje na navedenih e-mail adresa nije uspjelo: '; +$PHPMAILER_LANG['instantiate'] = 'Ne mogu pokrenuti mail funkcionalnost.'; +$PHPMAILER_LANG['invalid_address'] = 'E-mail nije poslan. Neispravna e-mail adresa: '; +$PHPMAILER_LANG['mailer_not_supported'] = ' mailer nije podržan.'; +$PHPMAILER_LANG['provide_address'] = 'Definirajte barem jednu adresu primatelja.'; +$PHPMAILER_LANG['signing'] = 'Greška prilikom prijave: '; +$PHPMAILER_LANG['smtp_connect_failed'] = 'Spajanje na SMTP poslužitelj nije uspjelo.'; +$PHPMAILER_LANG['smtp_error'] = 'Greška SMTP poslužitelja: '; +$PHPMAILER_LANG['variable_set'] = 'Ne mogu postaviti varijablu niti ju vratiti nazad: '; +$PHPMAILER_LANG['extension_missing'] = 'Nedostaje proširenje: '; diff --git a/kirby/vendor/phpmailer/phpmailer/language/phpmailer.lang-hu.php b/kirby/vendor/phpmailer/phpmailer/language/phpmailer.lang-hu.php new file mode 100644 index 0000000..e6b58b0 --- /dev/null +++ b/kirby/vendor/phpmailer/phpmailer/language/phpmailer.lang-hu.php @@ -0,0 +1,27 @@ + + */ + +$PHPMAILER_LANG['authenticate'] = 'SMTP -ի սխալ: չհաջողվեց ստուգել իսկությունը.'; +$PHPMAILER_LANG['connect_host'] = 'SMTP -ի սխալ: չհաջողվեց կապ հաստատել SMTP սերվերի հետ.'; +$PHPMAILER_LANG['data_not_accepted'] = 'SMTP -ի սխալ: տվյալները ընդունված չեն.'; +$PHPMAILER_LANG['empty_message'] = 'Հաղորդագրությունը դատարկ է'; +$PHPMAILER_LANG['encoding'] = 'Կոդավորման անհայտ տեսակ: '; +$PHPMAILER_LANG['execute'] = 'Չհաջողվեց իրականացնել հրամանը: '; +$PHPMAILER_LANG['file_access'] = 'Ֆայլը հասանելի չէ: '; +$PHPMAILER_LANG['file_open'] = 'Ֆայլի սխալ: ֆայլը չհաջողվեց բացել: '; +$PHPMAILER_LANG['from_failed'] = 'Ուղարկողի հետևյալ հասցեն սխալ է: '; +$PHPMAILER_LANG['instantiate'] = 'Հնարավոր չէ կանչել mail ֆունկցիան.'; +$PHPMAILER_LANG['invalid_address'] = 'Հասցեն սխալ է: '; +$PHPMAILER_LANG['mailer_not_supported'] = ' փոստային սերվերի հետ չի աշխատում.'; +$PHPMAILER_LANG['provide_address'] = 'Անհրաժեշտ է տրամադրել գոնե մեկ ստացողի e-mail հասցե.'; +$PHPMAILER_LANG['recipients_failed'] = 'SMTP -ի սխալ: չի հաջողվել ուղարկել հետևյալ ստացողների հասցեներին: '; +$PHPMAILER_LANG['signing'] = 'Ստորագրման սխալ: '; +$PHPMAILER_LANG['smtp_connect_failed'] = 'SMTP -ի connect() ֆունկցիան չի հաջողվել'; +$PHPMAILER_LANG['smtp_error'] = 'SMTP սերվերի սխալ: '; +$PHPMAILER_LANG['variable_set'] = 'Չի հաջողվում ստեղծել կամ վերափոխել փոփոխականը: '; +$PHPMAILER_LANG['extension_missing'] = 'Հավելվածը բացակայում է: '; diff --git a/kirby/vendor/phpmailer/phpmailer/language/phpmailer.lang-id.php b/kirby/vendor/phpmailer/phpmailer/language/phpmailer.lang-id.php new file mode 100644 index 0000000..212a11f --- /dev/null +++ b/kirby/vendor/phpmailer/phpmailer/language/phpmailer.lang-id.php @@ -0,0 +1,31 @@ + + * @author @januridp + * @author Ian Mustafa + */ + +$PHPMAILER_LANG['authenticate'] = 'Kesalahan SMTP: Tidak dapat mengotentikasi.'; +$PHPMAILER_LANG['connect_host'] = 'Kesalahan SMTP: Tidak dapat terhubung ke host SMTP.'; +$PHPMAILER_LANG['data_not_accepted'] = 'Kesalahan SMTP: Data tidak diterima.'; +$PHPMAILER_LANG['empty_message'] = 'Isi pesan kosong'; +$PHPMAILER_LANG['encoding'] = 'Pengkodean karakter tidak dikenali: '; +$PHPMAILER_LANG['execute'] = 'Tidak dapat menjalankan proses: '; +$PHPMAILER_LANG['file_access'] = 'Tidak dapat mengakses berkas: '; +$PHPMAILER_LANG['file_open'] = 'Kesalahan Berkas: Berkas tidak dapat dibuka: '; +$PHPMAILER_LANG['from_failed'] = 'Alamat pengirim berikut mengakibatkan kesalahan: '; +$PHPMAILER_LANG['instantiate'] = 'Tidak dapat menginisialisasi fungsi surel.'; +$PHPMAILER_LANG['invalid_address'] = 'Gagal terkirim, alamat surel tidak sesuai: '; +$PHPMAILER_LANG['invalid_hostentry'] = 'Gagal terkirim, entri host tidak sesuai: '; +$PHPMAILER_LANG['invalid_host'] = 'Gagal terkirim, host tidak sesuai: '; +$PHPMAILER_LANG['provide_address'] = 'Harus tersedia minimal satu alamat tujuan'; +$PHPMAILER_LANG['mailer_not_supported'] = ' mailer tidak didukung'; +$PHPMAILER_LANG['recipients_failed'] = 'Kesalahan SMTP: Alamat tujuan berikut menyebabkan kesalahan: '; +$PHPMAILER_LANG['signing'] = 'Kesalahan dalam penandatangan SSL: '; +$PHPMAILER_LANG['smtp_connect_failed'] = 'SMTP Connect() gagal.'; +$PHPMAILER_LANG['smtp_error'] = 'Kesalahan pada pelayan SMTP: '; +$PHPMAILER_LANG['variable_set'] = 'Tidak dapat mengatur atau mengatur ulang variabel: '; +$PHPMAILER_LANG['extension_missing'] = 'Ekstensi PHP tidak tersedia: '; diff --git a/kirby/vendor/phpmailer/phpmailer/language/phpmailer.lang-it.php b/kirby/vendor/phpmailer/phpmailer/language/phpmailer.lang-it.php new file mode 100644 index 0000000..08a6b73 --- /dev/null +++ b/kirby/vendor/phpmailer/phpmailer/language/phpmailer.lang-it.php @@ -0,0 +1,28 @@ + + * @author Stefano Sabatini + */ + +$PHPMAILER_LANG['authenticate'] = 'SMTP Error: Impossibile autenticarsi.'; +$PHPMAILER_LANG['connect_host'] = 'SMTP Error: Impossibile connettersi all\'host SMTP.'; +$PHPMAILER_LANG['data_not_accepted'] = 'SMTP Error: Dati non accettati dal server.'; +$PHPMAILER_LANG['empty_message'] = 'Il corpo del messaggio è vuoto'; +$PHPMAILER_LANG['encoding'] = 'Codifica dei caratteri sconosciuta: '; +$PHPMAILER_LANG['execute'] = 'Impossibile eseguire l\'operazione: '; +$PHPMAILER_LANG['file_access'] = 'Impossibile accedere al file: '; +$PHPMAILER_LANG['file_open'] = 'File Error: Impossibile aprire il file: '; +$PHPMAILER_LANG['from_failed'] = 'I seguenti indirizzi mittenti hanno generato errore: '; +$PHPMAILER_LANG['instantiate'] = 'Impossibile istanziare la funzione mail'; +$PHPMAILER_LANG['invalid_address'] = 'Impossibile inviare, l\'indirizzo email non è valido: '; +$PHPMAILER_LANG['provide_address'] = 'Deve essere fornito almeno un indirizzo ricevente'; +$PHPMAILER_LANG['mailer_not_supported'] = 'Mailer non supportato'; +$PHPMAILER_LANG['recipients_failed'] = 'SMTP Error: I seguenti indirizzi destinatari hanno generato un errore: '; +$PHPMAILER_LANG['signing'] = 'Errore nella firma: '; +$PHPMAILER_LANG['smtp_connect_failed'] = 'SMTP Connect() fallita.'; +$PHPMAILER_LANG['smtp_error'] = 'Errore del server SMTP: '; +$PHPMAILER_LANG['variable_set'] = 'Impossibile impostare o resettare la variabile: '; +$PHPMAILER_LANG['extension_missing'] = 'Estensione mancante: '; diff --git a/kirby/vendor/phpmailer/phpmailer/language/phpmailer.lang-ja.php b/kirby/vendor/phpmailer/phpmailer/language/phpmailer.lang-ja.php new file mode 100644 index 0000000..eee7989 --- /dev/null +++ b/kirby/vendor/phpmailer/phpmailer/language/phpmailer.lang-ja.php @@ -0,0 +1,28 @@ + + * @author Yoshi Sakai + */ + +$PHPMAILER_LANG['authenticate'] = 'SMTPエラー: 認証できませんでした。'; +$PHPMAILER_LANG['connect_host'] = 'SMTPエラー: SMTPホストに接続できませんでした。'; +$PHPMAILER_LANG['data_not_accepted'] = 'SMTPエラー: データが受け付けられませんでした。'; +//$PHPMAILER_LANG['empty_message'] = 'Message body empty'; +$PHPMAILER_LANG['encoding'] = '不明なエンコーディング: '; +$PHPMAILER_LANG['execute'] = '実行できませんでした: '; +$PHPMAILER_LANG['file_access'] = 'ファイルにアクセスできません: '; +$PHPMAILER_LANG['file_open'] = 'ファイルエラー: ファイルを開けません: '; +$PHPMAILER_LANG['from_failed'] = 'Fromアドレスを登録する際にエラーが発生しました: '; +$PHPMAILER_LANG['instantiate'] = 'メール関数が正常に動作しませんでした。'; +//$PHPMAILER_LANG['invalid_address'] = 'Invalid address: '; +$PHPMAILER_LANG['provide_address'] = '少なくとも1つメールアドレスを 指定する必要があります。'; +$PHPMAILER_LANG['mailer_not_supported'] = ' メーラーがサポートされていません。'; +$PHPMAILER_LANG['recipients_failed'] = 'SMTPエラー: 次の受信者アドレスに 間違いがあります: '; +//$PHPMAILER_LANG['signing'] = 'Signing Error: '; +//$PHPMAILER_LANG['smtp_connect_failed'] = 'SMTP Connect() failed.'; +//$PHPMAILER_LANG['smtp_error'] = 'SMTP server error: '; +//$PHPMAILER_LANG['variable_set'] = 'Cannot set or reset variable: '; +//$PHPMAILER_LANG['extension_missing'] = 'Extension missing: '; diff --git a/kirby/vendor/phpmailer/phpmailer/language/phpmailer.lang-ka.php b/kirby/vendor/phpmailer/phpmailer/language/phpmailer.lang-ka.php new file mode 100644 index 0000000..51fe403 --- /dev/null +++ b/kirby/vendor/phpmailer/phpmailer/language/phpmailer.lang-ka.php @@ -0,0 +1,27 @@ + + */ + +$PHPMAILER_LANG['authenticate'] = 'SMTP შეცდომა: ავტორიზაცია შეუძლებელია.'; +$PHPMAILER_LANG['connect_host'] = 'SMTP შეცდომა: SMTP სერვერთან დაკავშირება შეუძლებელია.'; +$PHPMAILER_LANG['data_not_accepted'] = 'SMTP შეცდომა: მონაცემები არ იქნა მიღებული.'; +$PHPMAILER_LANG['encoding'] = 'კოდირების უცნობი ტიპი: '; +$PHPMAILER_LANG['execute'] = 'შეუძლებელია შემდეგი ბრძანების შესრულება: '; +$PHPMAILER_LANG['file_access'] = 'შეუძლებელია წვდომა ფაილთან: '; +$PHPMAILER_LANG['file_open'] = 'ფაილური სისტემის შეცდომა: არ იხსნება ფაილი: '; +$PHPMAILER_LANG['from_failed'] = 'გამგზავნის არასწორი მისამართი: '; +$PHPMAILER_LANG['instantiate'] = 'mail ფუნქციის გაშვება ვერ ხერხდება.'; +$PHPMAILER_LANG['provide_address'] = 'გთხოვთ მიუთითოთ ერთი ადრესატის e-mail მისამართი მაინც.'; +$PHPMAILER_LANG['mailer_not_supported'] = ' - საფოსტო სერვერის მხარდაჭერა არ არის.'; +$PHPMAILER_LANG['recipients_failed'] = 'SMTP შეცდომა: შემდეგ მისამართებზე გაგზავნა ვერ მოხერხდა: '; +$PHPMAILER_LANG['empty_message'] = 'შეტყობინება ცარიელია'; +$PHPMAILER_LANG['invalid_address'] = 'არ გაიგზავნა, e-mail მისამართის არასწორი ფორმატი: '; +$PHPMAILER_LANG['signing'] = 'ხელმოწერის შეცდომა: '; +$PHPMAILER_LANG['smtp_connect_failed'] = 'შეცდომა SMTP სერვერთან დაკავშირებისას'; +$PHPMAILER_LANG['smtp_error'] = 'SMTP სერვერის შეცდომა: '; +$PHPMAILER_LANG['variable_set'] = 'შეუძლებელია შემდეგი ცვლადის შექმნა ან შეცვლა: '; +$PHPMAILER_LANG['extension_missing'] = 'ბიბლიოთეკა არ არსებობს: '; diff --git a/kirby/vendor/phpmailer/phpmailer/language/phpmailer.lang-ko.php b/kirby/vendor/phpmailer/phpmailer/language/phpmailer.lang-ko.php new file mode 100644 index 0000000..8c97dd9 --- /dev/null +++ b/kirby/vendor/phpmailer/phpmailer/language/phpmailer.lang-ko.php @@ -0,0 +1,27 @@ + + */ + +$PHPMAILER_LANG['authenticate'] = 'SMTP 오류: 인증할 수 없습니다.'; +$PHPMAILER_LANG['connect_host'] = 'SMTP 오류: SMTP 호스트에 접속할 수 없습니다.'; +$PHPMAILER_LANG['data_not_accepted'] = 'SMTP 오류: 데이터가 받아들여지지 않았습니다.'; +$PHPMAILER_LANG['empty_message'] = '메세지 내용이 없습니다'; +$PHPMAILER_LANG['encoding'] = '알 수 없는 인코딩: '; +$PHPMAILER_LANG['execute'] = '실행 불가: '; +$PHPMAILER_LANG['file_access'] = '파일 접근 불가: '; +$PHPMAILER_LANG['file_open'] = '파일 오류: 파일을 열 수 없습니다: '; +$PHPMAILER_LANG['from_failed'] = '다음 From 주소에서 오류가 발생했습니다: '; +$PHPMAILER_LANG['instantiate'] = 'mail 함수를 인스턴스화할 수 없습니다'; +$PHPMAILER_LANG['invalid_address'] = '잘못된 주소: '; +$PHPMAILER_LANG['mailer_not_supported'] = ' 메일러는 지원되지 않습니다.'; +$PHPMAILER_LANG['provide_address'] = '적어도 한 개 이상의 수신자 메일 주소를 제공해야 합니다.'; +$PHPMAILER_LANG['recipients_failed'] = 'SMTP 오류: 다음 수신자에서 오류가 발생했습니다: '; +$PHPMAILER_LANG['signing'] = '서명 오류: '; +$PHPMAILER_LANG['smtp_connect_failed'] = 'SMTP 연결을 실패하였습니다.'; +$PHPMAILER_LANG['smtp_error'] = 'SMTP 서버 오류: '; +$PHPMAILER_LANG['variable_set'] = '변수 설정 및 초기화 불가: '; +$PHPMAILER_LANG['extension_missing'] = '확장자 없음: '; diff --git a/kirby/vendor/phpmailer/phpmailer/language/phpmailer.lang-lt.php b/kirby/vendor/phpmailer/phpmailer/language/phpmailer.lang-lt.php new file mode 100644 index 0000000..4f115b1 --- /dev/null +++ b/kirby/vendor/phpmailer/phpmailer/language/phpmailer.lang-lt.php @@ -0,0 +1,27 @@ + + */ + +$PHPMAILER_LANG['authenticate'] = 'SMTP klaida: autentifikacija nepavyko.'; +$PHPMAILER_LANG['connect_host'] = 'SMTP klaida: nepavyksta prisijungti prie SMTP stoties.'; +$PHPMAILER_LANG['data_not_accepted'] = 'SMTP klaida: duomenys nepriimti.'; +$PHPMAILER_LANG['empty_message'] = 'Laiško turinys tuščias'; +$PHPMAILER_LANG['encoding'] = 'Neatpažinta koduotė: '; +$PHPMAILER_LANG['execute'] = 'Nepavyko įvykdyti komandos: '; +$PHPMAILER_LANG['file_access'] = 'Byla nepasiekiama: '; +$PHPMAILER_LANG['file_open'] = 'Bylos klaida: Nepavyksta atidaryti: '; +$PHPMAILER_LANG['from_failed'] = 'Neteisingas siuntėjo adresas: '; +$PHPMAILER_LANG['instantiate'] = 'Nepavyko paleisti mail funkcijos.'; +$PHPMAILER_LANG['invalid_address'] = 'Neteisingas adresas: '; +$PHPMAILER_LANG['mailer_not_supported'] = ' pašto stotis nepalaikoma.'; +$PHPMAILER_LANG['provide_address'] = 'Nurodykite bent vieną gavėjo adresą.'; +$PHPMAILER_LANG['recipients_failed'] = 'SMTP klaida: nepavyko išsiųsti šiems gavėjams: '; +$PHPMAILER_LANG['signing'] = 'Prisijungimo klaida: '; +$PHPMAILER_LANG['smtp_connect_failed'] = 'SMTP susijungimo klaida'; +$PHPMAILER_LANG['smtp_error'] = 'SMTP stoties klaida: '; +$PHPMAILER_LANG['variable_set'] = 'Nepavyko priskirti reikšmės kintamajam: '; +//$PHPMAILER_LANG['extension_missing'] = 'Extension missing: '; diff --git a/kirby/vendor/phpmailer/phpmailer/language/phpmailer.lang-lv.php b/kirby/vendor/phpmailer/phpmailer/language/phpmailer.lang-lv.php new file mode 100644 index 0000000..679b18c --- /dev/null +++ b/kirby/vendor/phpmailer/phpmailer/language/phpmailer.lang-lv.php @@ -0,0 +1,27 @@ + + */ + +$PHPMAILER_LANG['authenticate'] = 'SMTP kļūda: Autorizācija neizdevās.'; +$PHPMAILER_LANG['connect_host'] = 'SMTP Kļūda: Nevar izveidot savienojumu ar SMTP serveri.'; +$PHPMAILER_LANG['data_not_accepted'] = 'SMTP Kļūda: Nepieņem informāciju.'; +$PHPMAILER_LANG['empty_message'] = 'Ziņojuma teksts ir tukšs'; +$PHPMAILER_LANG['encoding'] = 'Neatpazīts kodējums: '; +$PHPMAILER_LANG['execute'] = 'Neizdevās izpildīt komandu: '; +$PHPMAILER_LANG['file_access'] = 'Fails nav pieejams: '; +$PHPMAILER_LANG['file_open'] = 'Faila kļūda: Nevar atvērt failu: '; +$PHPMAILER_LANG['from_failed'] = 'Nepareiza sūtītāja adrese: '; +$PHPMAILER_LANG['instantiate'] = 'Nevar palaist sūtīšanas funkciju.'; +$PHPMAILER_LANG['invalid_address'] = 'Nepareiza adrese: '; +$PHPMAILER_LANG['mailer_not_supported'] = ' sūtītājs netiek atbalstīts.'; +$PHPMAILER_LANG['provide_address'] = 'Lūdzu, norādiet vismaz vienu adresātu.'; +$PHPMAILER_LANG['recipients_failed'] = 'SMTP kļūda: neizdevās nosūtīt šādiem saņēmējiem: '; +$PHPMAILER_LANG['signing'] = 'Autorizācijas kļūda: '; +$PHPMAILER_LANG['smtp_connect_failed'] = 'SMTP savienojuma kļūda'; +$PHPMAILER_LANG['smtp_error'] = 'SMTP servera kļūda: '; +$PHPMAILER_LANG['variable_set'] = 'Nevar piešķirt mainīgā vērtību: '; +//$PHPMAILER_LANG['extension_missing'] = 'Extension missing: '; diff --git a/kirby/vendor/phpmailer/phpmailer/language/phpmailer.lang-mg.php b/kirby/vendor/phpmailer/phpmailer/language/phpmailer.lang-mg.php new file mode 100644 index 0000000..8a94f6a --- /dev/null +++ b/kirby/vendor/phpmailer/phpmailer/language/phpmailer.lang-mg.php @@ -0,0 +1,27 @@ + + */ + +$PHPMAILER_LANG['authenticate'] = 'Hadisoana SMTP: Tsy nahomby ny fanamarinana.'; +$PHPMAILER_LANG['connect_host'] = 'SMTP Error: Tsy afaka mampifandray amin\'ny mpampiantrano SMTP.'; +$PHPMAILER_LANG['data_not_accepted'] = 'SMTP diso: tsy voarakitra ny angona.'; +$PHPMAILER_LANG['empty_message'] = 'Tsy misy ny votoaty mailaka.'; +$PHPMAILER_LANG['encoding'] = 'Tsy fantatra encoding: '; +$PHPMAILER_LANG['execute'] = 'Tsy afaka manatanteraka ity baiko manaraka ity: '; +$PHPMAILER_LANG['file_access'] = 'Tsy nahomby ny fidirana amin\'ity rakitra ity: '; +$PHPMAILER_LANG['file_open'] = 'Hadisoana diso: Tsy afaka nanokatra ity file manaraka ity: '; +$PHPMAILER_LANG['from_failed'] = 'Ny adiresy iraka manaraka dia diso: '; +$PHPMAILER_LANG['instantiate'] = 'Tsy afaka nanomboka ny hetsika mail.'; +$PHPMAILER_LANG['invalid_address'] = 'Tsy mety ny adiresy: '; +$PHPMAILER_LANG['mailer_not_supported'] = ' mailer tsy manohana.'; +$PHPMAILER_LANG['provide_address'] = 'Alefaso azafady iray adiresy iray farafahakeliny.'; +$PHPMAILER_LANG['recipients_failed'] = 'SMTP Error: Tsy mety ireo mpanaraka ireto: '; +$PHPMAILER_LANG['signing'] = 'Error nandritra ny sonia:'; +$PHPMAILER_LANG['smtp_connect_failed'] = 'Tsy nahomby ny fifandraisana tamin\'ny server SMTP.'; +$PHPMAILER_LANG['smtp_error'] = 'Fahadisoana tamin\'ny server SMTP: '; +$PHPMAILER_LANG['variable_set'] = 'Tsy azo atao ny mametraka na mamerina ny variable: '; +$PHPMAILER_LANG['extension_missing'] = 'Tsy hita ny ampahany: '; diff --git a/kirby/vendor/phpmailer/phpmailer/language/phpmailer.lang-ms.php b/kirby/vendor/phpmailer/phpmailer/language/phpmailer.lang-ms.php new file mode 100644 index 0000000..71db338 --- /dev/null +++ b/kirby/vendor/phpmailer/phpmailer/language/phpmailer.lang-ms.php @@ -0,0 +1,27 @@ + + */ + +$PHPMAILER_LANG['authenticate'] = 'Ralat SMTP: Tidak dapat pengesahan.'; +$PHPMAILER_LANG['connect_host'] = 'Ralat SMTP: Tidak dapat menghubungi hos pelayan SMTP.'; +$PHPMAILER_LANG['data_not_accepted'] = 'Ralat SMTP: Data tidak diterima oleh pelayan.'; +$PHPMAILER_LANG['empty_message'] = 'Tiada isi untuk mesej'; +$PHPMAILER_LANG['encoding'] = 'Pengekodan tidak diketahui: '; +$PHPMAILER_LANG['execute'] = 'Tidak dapat melaksanakan: '; +$PHPMAILER_LANG['file_access'] = 'Tidak dapat mengakses fail: '; +$PHPMAILER_LANG['file_open'] = 'Ralat Fail: Tidak dapat membuka fail: '; +$PHPMAILER_LANG['from_failed'] = 'Berikut merupakan ralat dari alamat e-mel: '; +$PHPMAILER_LANG['instantiate'] = 'Tidak dapat memberi contoh fungsi e-mel.'; +$PHPMAILER_LANG['invalid_address'] = 'Alamat emel tidak sah: '; +$PHPMAILER_LANG['mailer_not_supported'] = ' jenis penghantar emel tidak disokong.'; +$PHPMAILER_LANG['provide_address'] = 'Anda perlu menyediakan sekurang-kurangnya satu alamat e-mel penerima.'; +$PHPMAILER_LANG['recipients_failed'] = 'Ralat SMTP: Penerima e-mel berikut telah gagal: '; +$PHPMAILER_LANG['signing'] = 'Ralat pada tanda tangan: '; +$PHPMAILER_LANG['smtp_connect_failed'] = 'SMTP Connect() telah gagal.'; +$PHPMAILER_LANG['smtp_error'] = 'Ralat pada pelayan SMTP: '; +$PHPMAILER_LANG['variable_set'] = 'Tidak boleh menetapkan atau menetapkan semula pembolehubah: '; +$PHPMAILER_LANG['extension_missing'] = 'Sambungan hilang: '; diff --git a/kirby/vendor/phpmailer/phpmailer/language/phpmailer.lang-nb.php b/kirby/vendor/phpmailer/phpmailer/language/phpmailer.lang-nb.php new file mode 100644 index 0000000..65793ce --- /dev/null +++ b/kirby/vendor/phpmailer/phpmailer/language/phpmailer.lang-nb.php @@ -0,0 +1,26 @@ + + */ + +$PHPMAILER_LANG['authenticate'] = 'SMTP-fout: authenticatie mislukt.'; +$PHPMAILER_LANG['connect_host'] = 'SMTP-fout: kon niet verbinden met SMTP-host.'; +$PHPMAILER_LANG['data_not_accepted'] = 'SMTP-fout: data niet geaccepteerd.'; +$PHPMAILER_LANG['empty_message'] = 'Berichttekst is leeg'; +$PHPMAILER_LANG['encoding'] = 'Onbekende codering: '; +$PHPMAILER_LANG['execute'] = 'Kon niet uitvoeren: '; +$PHPMAILER_LANG['file_access'] = 'Kreeg geen toegang tot bestand: '; +$PHPMAILER_LANG['file_open'] = 'Bestandsfout: kon bestand niet openen: '; +$PHPMAILER_LANG['from_failed'] = 'Het volgende afzendersadres is mislukt: '; +$PHPMAILER_LANG['instantiate'] = 'Kon mailfunctie niet initialiseren.'; +$PHPMAILER_LANG['invalid_address'] = 'Ongeldig adres: '; +$PHPMAILER_LANG['invalid_hostentry'] = 'Ongeldige hostentry: '; +$PHPMAILER_LANG['invalid_host'] = 'Ongeldige host: '; +$PHPMAILER_LANG['mailer_not_supported'] = ' mailer wordt niet ondersteund.'; +$PHPMAILER_LANG['provide_address'] = 'Er moet minstens één ontvanger worden opgegeven.'; +$PHPMAILER_LANG['recipients_failed'] = 'SMTP-fout: de volgende ontvangers zijn mislukt: '; +$PHPMAILER_LANG['signing'] = 'Signeerfout: '; +$PHPMAILER_LANG['smtp_connect_failed'] = 'SMTP Verbinding mislukt.'; +$PHPMAILER_LANG['smtp_error'] = 'SMTP-serverfout: '; +$PHPMAILER_LANG['variable_set'] = 'Kan de volgende variabele niet instellen of resetten: '; +$PHPMAILER_LANG['extension_missing'] = 'Extensie afwezig: '; diff --git a/kirby/vendor/phpmailer/phpmailer/language/phpmailer.lang-pl.php b/kirby/vendor/phpmailer/phpmailer/language/phpmailer.lang-pl.php new file mode 100644 index 0000000..23caa71 --- /dev/null +++ b/kirby/vendor/phpmailer/phpmailer/language/phpmailer.lang-pl.php @@ -0,0 +1,27 @@ + + */ + +$PHPMAILER_LANG['authenticate'] = 'Erro do SMTP: Não foi possível realizar a autenticação.'; +$PHPMAILER_LANG['connect_host'] = 'Erro do SMTP: Não foi possível realizar ligação com o servidor SMTP.'; +$PHPMAILER_LANG['data_not_accepted'] = 'Erro do SMTP: Os dados foram rejeitados.'; +$PHPMAILER_LANG['empty_message'] = 'A mensagem no e-mail está vazia.'; +$PHPMAILER_LANG['encoding'] = 'Codificação desconhecida: '; +$PHPMAILER_LANG['execute'] = 'Não foi possível executar: '; +$PHPMAILER_LANG['file_access'] = 'Não foi possível aceder o ficheiro: '; +$PHPMAILER_LANG['file_open'] = 'Abertura do ficheiro: Não foi possível abrir o ficheiro: '; +$PHPMAILER_LANG['from_failed'] = 'Ocorreram falhas nos endereços dos seguintes remententes: '; +$PHPMAILER_LANG['instantiate'] = 'Não foi possível iniciar uma instância da função mail.'; +$PHPMAILER_LANG['invalid_address'] = 'Não foi enviado nenhum e-mail para o endereço de e-mail inválido: '; +$PHPMAILER_LANG['mailer_not_supported'] = ' mailer não é suportado.'; +$PHPMAILER_LANG['provide_address'] = 'Tem de fornecer pelo menos um endereço como destinatário do e-mail.'; +$PHPMAILER_LANG['recipients_failed'] = 'Erro do SMTP: O endereço do seguinte destinatário falhou: '; +$PHPMAILER_LANG['signing'] = 'Erro ao assinar: '; +$PHPMAILER_LANG['smtp_connect_failed'] = 'SMTP Connect() falhou.'; +$PHPMAILER_LANG['smtp_error'] = 'Erro de servidor SMTP: '; +$PHPMAILER_LANG['variable_set'] = 'Não foi possível definir ou redefinir a variável: '; +$PHPMAILER_LANG['extension_missing'] = 'Extensão em falta: '; diff --git a/kirby/vendor/phpmailer/phpmailer/language/phpmailer.lang-pt_br.php b/kirby/vendor/phpmailer/phpmailer/language/phpmailer.lang-pt_br.php new file mode 100644 index 0000000..d863809 --- /dev/null +++ b/kirby/vendor/phpmailer/phpmailer/language/phpmailer.lang-pt_br.php @@ -0,0 +1,30 @@ + + * @author Lucas Guimarães + * @author Phelipe Alves + * @author Fabio Beneditto + */ + +$PHPMAILER_LANG['authenticate'] = 'Erro de SMTP: Não foi possível autenticar.'; +$PHPMAILER_LANG['connect_host'] = 'Erro de SMTP: Não foi possível conectar ao servidor SMTP.'; +$PHPMAILER_LANG['data_not_accepted'] = 'Erro de SMTP: Dados rejeitados.'; +$PHPMAILER_LANG['empty_message'] = 'Mensagem vazia'; +$PHPMAILER_LANG['encoding'] = 'Codificação desconhecida: '; +$PHPMAILER_LANG['execute'] = 'Não foi possível executar: '; +$PHPMAILER_LANG['file_access'] = 'Não foi possível acessar o arquivo: '; +$PHPMAILER_LANG['file_open'] = 'Erro de Arquivo: Não foi possível abrir o arquivo: '; +$PHPMAILER_LANG['from_failed'] = 'Os seguintes remetentes falharam: '; +$PHPMAILER_LANG['instantiate'] = 'Não foi possível instanciar a função mail.'; +$PHPMAILER_LANG['invalid_address'] = 'Endereço de e-mail inválido: '; +$PHPMAILER_LANG['mailer_not_supported'] = ' mailer não é suportado.'; +$PHPMAILER_LANG['provide_address'] = 'Você deve informar pelo menos um destinatário.'; +$PHPMAILER_LANG['recipients_failed'] = 'Erro de SMTP: Os seguintes destinatários falharam: '; +$PHPMAILER_LANG['signing'] = 'Erro de Assinatura: '; +$PHPMAILER_LANG['smtp_connect_failed'] = 'SMTP Connect() falhou.'; +$PHPMAILER_LANG['smtp_error'] = 'Erro de servidor SMTP: '; +$PHPMAILER_LANG['variable_set'] = 'Não foi possível definir ou redefinir a variável: '; +$PHPMAILER_LANG['extension_missing'] = 'Extensão não existe: '; diff --git a/kirby/vendor/phpmailer/phpmailer/language/phpmailer.lang-ro.php b/kirby/vendor/phpmailer/phpmailer/language/phpmailer.lang-ro.php new file mode 100644 index 0000000..292ec1e --- /dev/null +++ b/kirby/vendor/phpmailer/phpmailer/language/phpmailer.lang-ro.php @@ -0,0 +1,27 @@ + + */ + +$PHPMAILER_LANG['authenticate'] = 'Eroare SMTP: Autentificarea a eșuat.'; +$PHPMAILER_LANG['connect_host'] = 'Eroare SMTP: Conectarea la serverul SMTP a eșuat.'; +$PHPMAILER_LANG['data_not_accepted'] = 'Eroare SMTP: Datele nu au fost acceptate.'; +$PHPMAILER_LANG['empty_message'] = 'Mesajul este gol.'; +$PHPMAILER_LANG['encoding'] = 'Encodare necunoscută: '; +$PHPMAILER_LANG['execute'] = 'Nu se poate executa următoarea comandă: '; +$PHPMAILER_LANG['file_access'] = 'Nu se poate accesa următorul fișier: '; +$PHPMAILER_LANG['file_open'] = 'Eroare fișier: Nu se poate deschide următorul fișier: '; +$PHPMAILER_LANG['from_failed'] = 'Următoarele adrese From au dat eroare: '; +$PHPMAILER_LANG['instantiate'] = 'Funcția mail nu a putut fi inițializată.'; +$PHPMAILER_LANG['invalid_address'] = 'Adresa de email nu este validă: '; +$PHPMAILER_LANG['mailer_not_supported'] = ' mailer nu este suportat.'; +$PHPMAILER_LANG['provide_address'] = 'Trebuie să adăugați cel puțin o adresă de email.'; +$PHPMAILER_LANG['recipients_failed'] = 'Eroare SMTP: Următoarele adrese de email au eșuat: '; +$PHPMAILER_LANG['signing'] = 'A aparut o problemă la semnarea emailului. '; +$PHPMAILER_LANG['smtp_connect_failed'] = 'Conectarea la serverul SMTP a eșuat.'; +$PHPMAILER_LANG['smtp_error'] = 'Eroare server SMTP: '; +$PHPMAILER_LANG['variable_set'] = 'Nu se poate seta/reseta variabila. '; +$PHPMAILER_LANG['extension_missing'] = 'Lipsește extensia: '; diff --git a/kirby/vendor/phpmailer/phpmailer/language/phpmailer.lang-ru.php b/kirby/vendor/phpmailer/phpmailer/language/phpmailer.lang-ru.php new file mode 100644 index 0000000..8c8c5e8 --- /dev/null +++ b/kirby/vendor/phpmailer/phpmailer/language/phpmailer.lang-ru.php @@ -0,0 +1,28 @@ + + * @author Foster Snowhill + */ + +$PHPMAILER_LANG['authenticate'] = 'Ошибка SMTP: ошибка авторизации.'; +$PHPMAILER_LANG['connect_host'] = 'Ошибка SMTP: не удается подключиться к SMTP-серверу.'; +$PHPMAILER_LANG['data_not_accepted'] = 'Ошибка SMTP: данные не приняты.'; +$PHPMAILER_LANG['encoding'] = 'Неизвестная кодировка: '; +$PHPMAILER_LANG['execute'] = 'Невозможно выполнить команду: '; +$PHPMAILER_LANG['file_access'] = 'Нет доступа к файлу: '; +$PHPMAILER_LANG['file_open'] = 'Файловая ошибка: не удаётся открыть файл: '; +$PHPMAILER_LANG['from_failed'] = 'Неверный адрес отправителя: '; +$PHPMAILER_LANG['instantiate'] = 'Невозможно запустить функцию mail().'; +$PHPMAILER_LANG['provide_address'] = 'Пожалуйста, введите хотя бы один email-адрес получателя.'; +$PHPMAILER_LANG['mailer_not_supported'] = ' — почтовый сервер не поддерживается.'; +$PHPMAILER_LANG['recipients_failed'] = 'Ошибка SMTP: не удалась отправка таким адресатам: '; +$PHPMAILER_LANG['empty_message'] = 'Пустое сообщение'; +$PHPMAILER_LANG['invalid_address'] = 'Не отправлено из-за неправильного формата email-адреса: '; +$PHPMAILER_LANG['signing'] = 'Ошибка подписи: '; +$PHPMAILER_LANG['smtp_connect_failed'] = 'Ошибка соединения с SMTP-сервером'; +$PHPMAILER_LANG['smtp_error'] = 'Ошибка SMTP-сервера: '; +$PHPMAILER_LANG['variable_set'] = 'Невозможно установить или сбросить переменную: '; +$PHPMAILER_LANG['extension_missing'] = 'Расширение отсутствует: '; diff --git a/kirby/vendor/phpmailer/phpmailer/language/phpmailer.lang-sk.php b/kirby/vendor/phpmailer/phpmailer/language/phpmailer.lang-sk.php new file mode 100644 index 0000000..028f5bc --- /dev/null +++ b/kirby/vendor/phpmailer/phpmailer/language/phpmailer.lang-sk.php @@ -0,0 +1,30 @@ + + * @author Peter Orlický + */ + +$PHPMAILER_LANG['authenticate'] = 'SMTP Error: Chyba autentifikácie.'; +$PHPMAILER_LANG['connect_host'] = 'SMTP Error: Nebolo možné nadviazať spojenie so SMTP serverom.'; +$PHPMAILER_LANG['data_not_accepted'] = 'SMTP Error: Dáta neboli prijaté'; +$PHPMAILER_LANG['empty_message'] = 'Prázdne telo správy.'; +$PHPMAILER_LANG['encoding'] = 'Neznáme kódovanie: '; +$PHPMAILER_LANG['execute'] = 'Nedá sa vykonať: '; +$PHPMAILER_LANG['file_access'] = 'Súbor nebol nájdený: '; +$PHPMAILER_LANG['file_open'] = 'File Error: Súbor sa otvoriť pre čítanie: '; +$PHPMAILER_LANG['from_failed'] = 'Následujúca adresa From je nesprávna: '; +$PHPMAILER_LANG['instantiate'] = 'Nedá sa vytvoriť inštancia emailovej funkcie.'; +$PHPMAILER_LANG['invalid_address'] = 'Neodoslané, emailová adresa je nesprávna: '; +$PHPMAILER_LANG['invalid_hostentry'] = 'Záznam hostiteľa je nesprávny: '; +$PHPMAILER_LANG['invalid_host'] = 'Hostiteľ je nesprávny: '; +$PHPMAILER_LANG['mailer_not_supported'] = ' emailový klient nieje podporovaný.'; +$PHPMAILER_LANG['provide_address'] = 'Musíte zadať aspoň jednu emailovú adresu príjemcu.'; +$PHPMAILER_LANG['recipients_failed'] = 'SMTP Error: Adresy príjemcov niesu správne '; +$PHPMAILER_LANG['signing'] = 'Chyba prihlasovania: '; +$PHPMAILER_LANG['smtp_connect_failed'] = 'SMTP Connect() zlyhalo.'; +$PHPMAILER_LANG['smtp_error'] = 'SMTP chyba serveru: '; +$PHPMAILER_LANG['variable_set'] = 'Nemožno nastaviť alebo resetovať premennú: '; +$PHPMAILER_LANG['extension_missing'] = 'Chýba rozšírenie: '; diff --git a/kirby/vendor/phpmailer/phpmailer/language/phpmailer.lang-sl.php b/kirby/vendor/phpmailer/phpmailer/language/phpmailer.lang-sl.php new file mode 100644 index 0000000..c437a88 --- /dev/null +++ b/kirby/vendor/phpmailer/phpmailer/language/phpmailer.lang-sl.php @@ -0,0 +1,31 @@ + + * @author Filip Š + * @author Blaž Oražem + */ + +$PHPMAILER_LANG['authenticate'] = 'SMTP napaka: Avtentikacija ni uspela.'; +$PHPMAILER_LANG['connect_host'] = 'SMTP napaka: Vzpostavljanje povezave s SMTP gostiteljem ni uspelo.'; +$PHPMAILER_LANG['data_not_accepted'] = 'SMTP napaka: Strežnik zavrača podatke.'; +$PHPMAILER_LANG['empty_message'] = 'E-poštno sporočilo nima vsebine.'; +$PHPMAILER_LANG['encoding'] = 'Nepoznan tip kodiranja: '; +$PHPMAILER_LANG['execute'] = 'Operacija ni uspela: '; +$PHPMAILER_LANG['file_access'] = 'Nimam dostopa do datoteke: '; +$PHPMAILER_LANG['file_open'] = 'Ne morem odpreti datoteke: '; +$PHPMAILER_LANG['from_failed'] = 'Neveljaven e-naslov pošiljatelja: '; +$PHPMAILER_LANG['instantiate'] = 'Ne morem inicializirati mail funkcije.'; +$PHPMAILER_LANG['invalid_address'] = 'E-poštno sporočilo ni bilo poslano. E-naslov je neveljaven: '; +$PHPMAILER_LANG['invalid_hostentry'] = 'Neveljaven vnos gostitelja: '; +$PHPMAILER_LANG['invalid_host'] = 'Neveljaven gostitelj: '; +$PHPMAILER_LANG['mailer_not_supported'] = ' mailer ni podprt.'; +$PHPMAILER_LANG['provide_address'] = 'Prosimo, vnesite vsaj enega naslovnika.'; +$PHPMAILER_LANG['recipients_failed'] = 'SMTP napaka: Sledeči naslovniki so neveljavni: '; +$PHPMAILER_LANG['signing'] = 'Napaka pri podpisovanju: '; +$PHPMAILER_LANG['smtp_connect_failed'] = 'Ne morem vzpostaviti povezave s SMTP strežnikom.'; +$PHPMAILER_LANG['smtp_error'] = 'Napaka SMTP strežnika: '; +$PHPMAILER_LANG['variable_set'] = 'Ne morem nastaviti oz. ponastaviti spremenljivke: '; +$PHPMAILER_LANG['extension_missing'] = 'Manjkajoča razširitev: '; diff --git a/kirby/vendor/phpmailer/phpmailer/language/phpmailer.lang-sr.php b/kirby/vendor/phpmailer/phpmailer/language/phpmailer.lang-sr.php new file mode 100644 index 0000000..0b5280f --- /dev/null +++ b/kirby/vendor/phpmailer/phpmailer/language/phpmailer.lang-sr.php @@ -0,0 +1,28 @@ + + * @author Miloš Milanović + */ + +$PHPMAILER_LANG['authenticate'] = 'SMTP грешка: аутентификација није успела.'; +$PHPMAILER_LANG['connect_host'] = 'SMTP грешка: повезивање са SMTP сервером није успело.'; +$PHPMAILER_LANG['data_not_accepted'] = 'SMTP грешка: подаци нису прихваћени.'; +$PHPMAILER_LANG['empty_message'] = 'Садржај поруке је празан.'; +$PHPMAILER_LANG['encoding'] = 'Непознато кодирање: '; +$PHPMAILER_LANG['execute'] = 'Није могуће извршити наредбу: '; +$PHPMAILER_LANG['file_access'] = 'Није могуће приступити датотеци: '; +$PHPMAILER_LANG['file_open'] = 'Није могуће отворити датотеку: '; +$PHPMAILER_LANG['from_failed'] = 'SMTP грешка: слање са следећих адреса није успело: '; +$PHPMAILER_LANG['recipients_failed'] = 'SMTP грешка: слање на следеће адресе није успело: '; +$PHPMAILER_LANG['instantiate'] = 'Није могуће покренути mail функцију.'; +$PHPMAILER_LANG['invalid_address'] = 'Порука није послата. Неисправна адреса: '; +$PHPMAILER_LANG['mailer_not_supported'] = ' мејлер није подржан.'; +$PHPMAILER_LANG['provide_address'] = 'Дефинишите бар једну адресу примаоца.'; +$PHPMAILER_LANG['signing'] = 'Грешка приликом пријаве: '; +$PHPMAILER_LANG['smtp_connect_failed'] = 'Повезивање са SMTP сервером није успело.'; +$PHPMAILER_LANG['smtp_error'] = 'Грешка SMTP сервера: '; +$PHPMAILER_LANG['variable_set'] = 'Није могуће задати нити ресетовати променљиву: '; +$PHPMAILER_LANG['extension_missing'] = 'Недостаје проширење: '; diff --git a/kirby/vendor/phpmailer/phpmailer/language/phpmailer.lang-sr_latn.php b/kirby/vendor/phpmailer/phpmailer/language/phpmailer.lang-sr_latn.php new file mode 100644 index 0000000..6213832 --- /dev/null +++ b/kirby/vendor/phpmailer/phpmailer/language/phpmailer.lang-sr_latn.php @@ -0,0 +1,28 @@ + + * @author Miloš Milanović + */ + +$PHPMAILER_LANG['authenticate'] = 'SMTP greška: autentifikacija nije uspela.'; +$PHPMAILER_LANG['connect_host'] = 'SMTP greška: povezivanje sa SMTP serverom nije uspelo.'; +$PHPMAILER_LANG['data_not_accepted'] = 'SMTP greška: podaci nisu prihvaćeni.'; +$PHPMAILER_LANG['empty_message'] = 'Sadržaj poruke je prazan.'; +$PHPMAILER_LANG['encoding'] = 'Nepoznato kodiranje: '; +$PHPMAILER_LANG['execute'] = 'Nije moguće izvršiti naredbu: '; +$PHPMAILER_LANG['file_access'] = 'Nije moguće pristupiti datoteci: '; +$PHPMAILER_LANG['file_open'] = 'Nije moguće otvoriti datoteku: '; +$PHPMAILER_LANG['from_failed'] = 'SMTP greška: slanje sa sledećih adresa nije uspelo: '; +$PHPMAILER_LANG['recipients_failed'] = 'SMTP greška: slanje na sledeće adrese nije uspelo: '; +$PHPMAILER_LANG['instantiate'] = 'Nije moguće pokrenuti mail funkciju.'; +$PHPMAILER_LANG['invalid_address'] = 'Poruka nije poslata. Neispravna adresa: '; +$PHPMAILER_LANG['mailer_not_supported'] = ' majler nije podržan.'; +$PHPMAILER_LANG['provide_address'] = 'Definišite bar jednu adresu primaoca.'; +$PHPMAILER_LANG['signing'] = 'Greška prilikom prijave: '; +$PHPMAILER_LANG['smtp_connect_failed'] = 'Povezivanje sa SMTP serverom nije uspelo.'; +$PHPMAILER_LANG['smtp_error'] = 'Greška SMTP servera: '; +$PHPMAILER_LANG['variable_set'] = 'Nije moguće zadati niti resetovati promenljivu: '; +$PHPMAILER_LANG['extension_missing'] = 'Nedostaje proširenje: '; diff --git a/kirby/vendor/phpmailer/phpmailer/language/phpmailer.lang-sv.php b/kirby/vendor/phpmailer/phpmailer/language/phpmailer.lang-sv.php new file mode 100644 index 0000000..9872c19 --- /dev/null +++ b/kirby/vendor/phpmailer/phpmailer/language/phpmailer.lang-sv.php @@ -0,0 +1,27 @@ + + */ + +$PHPMAILER_LANG['authenticate'] = 'SMTP fel: Kunde inte autentisera.'; +$PHPMAILER_LANG['connect_host'] = 'SMTP fel: Kunde inte ansluta till SMTP-server.'; +$PHPMAILER_LANG['data_not_accepted'] = 'SMTP fel: Data accepterades inte.'; +//$PHPMAILER_LANG['empty_message'] = 'Message body empty'; +$PHPMAILER_LANG['encoding'] = 'Okänt encode-format: '; +$PHPMAILER_LANG['execute'] = 'Kunde inte köra: '; +$PHPMAILER_LANG['file_access'] = 'Ingen åtkomst till fil: '; +$PHPMAILER_LANG['file_open'] = 'Fil fel: Kunde inte öppna fil: '; +$PHPMAILER_LANG['from_failed'] = 'Följande avsändaradress är felaktig: '; +$PHPMAILER_LANG['instantiate'] = 'Kunde inte initiera e-postfunktion.'; +$PHPMAILER_LANG['invalid_address'] = 'Felaktig adress: '; +$PHPMAILER_LANG['provide_address'] = 'Du måste ange minst en mottagares e-postadress.'; +$PHPMAILER_LANG['mailer_not_supported'] = ' mailer stöds inte.'; +$PHPMAILER_LANG['recipients_failed'] = 'SMTP fel: Följande mottagare är felaktig: '; +$PHPMAILER_LANG['signing'] = 'Signeringsfel: '; +$PHPMAILER_LANG['smtp_connect_failed'] = 'SMTP Connect() misslyckades.'; +$PHPMAILER_LANG['smtp_error'] = 'SMTP serverfel: '; +$PHPMAILER_LANG['variable_set'] = 'Kunde inte definiera eller återställa variabel: '; +$PHPMAILER_LANG['extension_missing'] = 'Tillägg ej tillgängligt: '; diff --git a/kirby/vendor/phpmailer/phpmailer/language/phpmailer.lang-tl.php b/kirby/vendor/phpmailer/phpmailer/language/phpmailer.lang-tl.php new file mode 100644 index 0000000..d15bed1 --- /dev/null +++ b/kirby/vendor/phpmailer/phpmailer/language/phpmailer.lang-tl.php @@ -0,0 +1,28 @@ + + */ + +$PHPMAILER_LANG['authenticate'] = 'SMTP Error: Hindi mapatotohanan.'; +$PHPMAILER_LANG['connect_host'] = 'SMTP Error: Hindi makakonekta sa SMTP host.'; +$PHPMAILER_LANG['data_not_accepted'] = 'SMTP Error: Ang datos ay hindi naitanggap.'; +$PHPMAILER_LANG['empty_message'] = 'Walang laman ang mensahe'; +$PHPMAILER_LANG['encoding'] = 'Hindi alam ang encoding: '; +$PHPMAILER_LANG['execute'] = 'Hindi maisasagawa: '; +$PHPMAILER_LANG['file_access'] = 'Hindi ma-access ang file: '; +$PHPMAILER_LANG['file_open'] = 'File Error: Hindi mabuksan ang file: '; +$PHPMAILER_LANG['from_failed'] = 'Ang sumusunod na address ay nabigo: '; +$PHPMAILER_LANG['instantiate'] = 'Hindi maisimulan ang instance ng mail function.'; +$PHPMAILER_LANG['invalid_address'] = 'Hindi wasto ang address na naibigay: '; +$PHPMAILER_LANG['mailer_not_supported'] = 'Ang mailer ay hindi suportado.'; +$PHPMAILER_LANG['provide_address'] = 'Kailangan mong magbigay ng kahit isang email address na tatanggap.'; +$PHPMAILER_LANG['recipients_failed'] = 'SMTP Error: Ang mga sumusunod na tatanggap ay nabigo: '; +$PHPMAILER_LANG['signing'] = 'Hindi ma-sign: '; +$PHPMAILER_LANG['smtp_connect_failed'] = 'Ang SMTP connect() ay nabigo.'; +$PHPMAILER_LANG['smtp_error'] = 'Ang server ng SMTP ay nabigo: '; +$PHPMAILER_LANG['variable_set'] = 'Hindi matatakda o ma-reset ang mga variables: '; +$PHPMAILER_LANG['extension_missing'] = 'Nawawala ang extension: '; diff --git a/kirby/vendor/phpmailer/phpmailer/language/phpmailer.lang-tr.php b/kirby/vendor/phpmailer/phpmailer/language/phpmailer.lang-tr.php new file mode 100644 index 0000000..f938f80 --- /dev/null +++ b/kirby/vendor/phpmailer/phpmailer/language/phpmailer.lang-tr.php @@ -0,0 +1,31 @@ + + * @fixed by Boris Yurchenko + */ + +$PHPMAILER_LANG['authenticate'] = 'Помилка SMTP: помилка авторизації.'; +$PHPMAILER_LANG['connect_host'] = 'Помилка SMTP: не вдається під\'єднатися до SMTP-серверу.'; +$PHPMAILER_LANG['data_not_accepted'] = 'Помилка SMTP: дані не прийнято.'; +$PHPMAILER_LANG['encoding'] = 'Невідоме кодування: '; +$PHPMAILER_LANG['execute'] = 'Неможливо виконати команду: '; +$PHPMAILER_LANG['file_access'] = 'Немає доступу до файлу: '; +$PHPMAILER_LANG['file_open'] = 'Помилка файлової системи: не вдається відкрити файл: '; +$PHPMAILER_LANG['from_failed'] = 'Невірна адреса відправника: '; +$PHPMAILER_LANG['instantiate'] = 'Неможливо запустити функцію mail().'; +$PHPMAILER_LANG['provide_address'] = 'Будь ласка, введіть хоча б одну email-адресу отримувача.'; +$PHPMAILER_LANG['mailer_not_supported'] = ' - поштовий сервер не підтримується.'; +$PHPMAILER_LANG['recipients_failed'] = 'Помилка SMTP: не вдалося відправлення для таких отримувачів: '; +$PHPMAILER_LANG['empty_message'] = 'Пусте повідомлення'; +$PHPMAILER_LANG['invalid_address'] = 'Не відправлено через неправильний формат email-адреси: '; +$PHPMAILER_LANG['signing'] = 'Помилка підпису: '; +$PHPMAILER_LANG['smtp_connect_failed'] = 'Помилка з\'єднання з SMTP-сервером'; +$PHPMAILER_LANG['smtp_error'] = 'Помилка SMTP-сервера: '; +$PHPMAILER_LANG['variable_set'] = 'Неможливо встановити або скинути змінну: '; +$PHPMAILER_LANG['extension_missing'] = 'Розширення відсутнє: '; diff --git a/kirby/vendor/phpmailer/phpmailer/language/phpmailer.lang-vi.php b/kirby/vendor/phpmailer/phpmailer/language/phpmailer.lang-vi.php new file mode 100644 index 0000000..d65576e --- /dev/null +++ b/kirby/vendor/phpmailer/phpmailer/language/phpmailer.lang-vi.php @@ -0,0 +1,27 @@ + + */ + +$PHPMAILER_LANG['authenticate'] = 'Lỗi SMTP: Không thể xác thực.'; +$PHPMAILER_LANG['connect_host'] = 'Lỗi SMTP: Không thể kết nối máy chủ SMTP.'; +$PHPMAILER_LANG['data_not_accepted'] = 'Lỗi SMTP: Dữ liệu không được chấp nhận.'; +$PHPMAILER_LANG['empty_message'] = 'Không có nội dung'; +$PHPMAILER_LANG['encoding'] = 'Mã hóa không xác định: '; +$PHPMAILER_LANG['execute'] = 'Không thực hiện được: '; +$PHPMAILER_LANG['file_access'] = 'Không thể truy cập tệp tin '; +$PHPMAILER_LANG['file_open'] = 'Lỗi Tập tin: Không thể mở tệp tin: '; +$PHPMAILER_LANG['from_failed'] = 'Lỗi địa chỉ gửi đi: '; +$PHPMAILER_LANG['instantiate'] = 'Không dùng được các hàm gửi thư.'; +$PHPMAILER_LANG['invalid_address'] = 'Đại chỉ emai không đúng: '; +$PHPMAILER_LANG['mailer_not_supported'] = ' trình gửi thư không được hỗ trợ.'; +$PHPMAILER_LANG['provide_address'] = 'Bạn phải cung cấp ít nhất một địa chỉ người nhận.'; +$PHPMAILER_LANG['recipients_failed'] = 'Lỗi SMTP: lỗi địa chỉ người nhận: '; +$PHPMAILER_LANG['signing'] = 'Lỗi đăng nhập: '; +$PHPMAILER_LANG['smtp_connect_failed'] = 'Lỗi kết nối với SMTP'; +$PHPMAILER_LANG['smtp_error'] = 'Lỗi máy chủ smtp '; +$PHPMAILER_LANG['variable_set'] = 'Không thể thiết lập hoặc thiết lập lại biến: '; +//$PHPMAILER_LANG['extension_missing'] = 'Extension missing: '; diff --git a/kirby/vendor/phpmailer/phpmailer/language/phpmailer.lang-zh.php b/kirby/vendor/phpmailer/phpmailer/language/phpmailer.lang-zh.php new file mode 100644 index 0000000..35e4e70 --- /dev/null +++ b/kirby/vendor/phpmailer/phpmailer/language/phpmailer.lang-zh.php @@ -0,0 +1,29 @@ + + * @author Peter Dave Hello <@PeterDaveHello/> + * @author Jason Chiang + */ + +$PHPMAILER_LANG['authenticate'] = 'SMTP 錯誤:登入失敗。'; +$PHPMAILER_LANG['connect_host'] = 'SMTP 錯誤:無法連線到 SMTP 主機。'; +$PHPMAILER_LANG['data_not_accepted'] = 'SMTP 錯誤:無法接受的資料。'; +$PHPMAILER_LANG['empty_message'] = '郵件內容為空'; +$PHPMAILER_LANG['encoding'] = '未知編碼: '; +$PHPMAILER_LANG['execute'] = '無法執行:'; +$PHPMAILER_LANG['file_access'] = '無法存取檔案:'; +$PHPMAILER_LANG['file_open'] = '檔案錯誤:無法開啟檔案:'; +$PHPMAILER_LANG['from_failed'] = '發送地址錯誤:'; +$PHPMAILER_LANG['instantiate'] = '未知函數呼叫。'; +$PHPMAILER_LANG['invalid_address'] = '因為電子郵件地址無效,無法傳送: '; +$PHPMAILER_LANG['mailer_not_supported'] = '不支援的發信客戶端。'; +$PHPMAILER_LANG['provide_address'] = '必須提供至少一個收件人地址。'; +$PHPMAILER_LANG['recipients_failed'] = 'SMTP 錯誤:以下收件人地址錯誤:'; +$PHPMAILER_LANG['signing'] = '電子簽章錯誤: '; +$PHPMAILER_LANG['smtp_connect_failed'] = 'SMTP 連線失敗'; +$PHPMAILER_LANG['smtp_error'] = 'SMTP 伺服器錯誤: '; +$PHPMAILER_LANG['variable_set'] = '無法設定或重設變數: '; +$PHPMAILER_LANG['extension_missing'] = '遺失模組 Extension: '; diff --git a/kirby/vendor/phpmailer/phpmailer/language/phpmailer.lang-zh_cn.php b/kirby/vendor/phpmailer/phpmailer/language/phpmailer.lang-zh_cn.php new file mode 100644 index 0000000..728a499 --- /dev/null +++ b/kirby/vendor/phpmailer/phpmailer/language/phpmailer.lang-zh_cn.php @@ -0,0 +1,29 @@ + + * @author young + * @author Teddysun + */ + +$PHPMAILER_LANG['authenticate'] = 'SMTP 错误:登录失败。'; +$PHPMAILER_LANG['connect_host'] = 'SMTP 错误:无法连接到 SMTP 主机。'; +$PHPMAILER_LANG['data_not_accepted'] = 'SMTP 错误:数据不被接受。'; +$PHPMAILER_LANG['empty_message'] = '邮件正文为空。'; +$PHPMAILER_LANG['encoding'] = '未知编码:'; +$PHPMAILER_LANG['execute'] = '无法执行:'; +$PHPMAILER_LANG['file_access'] = '无法访问文件:'; +$PHPMAILER_LANG['file_open'] = '文件错误:无法打开文件:'; +$PHPMAILER_LANG['from_failed'] = '发送地址错误:'; +$PHPMAILER_LANG['instantiate'] = '未知函数调用。'; +$PHPMAILER_LANG['invalid_address'] = '发送失败,电子邮箱地址是无效的:'; +$PHPMAILER_LANG['mailer_not_supported'] = '发信客户端不被支持。'; +$PHPMAILER_LANG['provide_address'] = '必须提供至少一个收件人地址。'; +$PHPMAILER_LANG['recipients_failed'] = 'SMTP 错误:收件人地址错误:'; +$PHPMAILER_LANG['signing'] = '登录失败:'; +$PHPMAILER_LANG['smtp_connect_failed'] = 'SMTP服务器连接失败。'; +$PHPMAILER_LANG['smtp_error'] = 'SMTP服务器出错:'; +$PHPMAILER_LANG['variable_set'] = '无法设置或重置变量:'; +$PHPMAILER_LANG['extension_missing'] = '丢失模块 Extension:'; diff --git a/kirby/vendor/phpmailer/phpmailer/src/Exception.php b/kirby/vendor/phpmailer/phpmailer/src/Exception.php new file mode 100644 index 0000000..a50a899 --- /dev/null +++ b/kirby/vendor/phpmailer/phpmailer/src/Exception.php @@ -0,0 +1,40 @@ + + * @author Jim Jagielski (jimjag) + * @author Andy Prevost (codeworxtech) + * @author Brent R. Matzelle (original founder) + * @copyright 2012 - 2020 Marcus Bointon + * @copyright 2010 - 2012 Jim Jagielski + * @copyright 2004 - 2009 Andy Prevost + * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License + * @note This program is distributed in the hope that it will be useful - WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. + */ + +namespace PHPMailer\PHPMailer; + +/** + * PHPMailer exception handler. + * + * @author Marcus Bointon + */ +class Exception extends \Exception +{ + /** + * Prettify error message output. + * + * @return string + */ + public function errorMessage() + { + return '' . htmlspecialchars($this->getMessage()) . "
    \n"; + } +} diff --git a/kirby/vendor/phpmailer/phpmailer/src/OAuth.php b/kirby/vendor/phpmailer/phpmailer/src/OAuth.php new file mode 100644 index 0000000..c93d0be --- /dev/null +++ b/kirby/vendor/phpmailer/phpmailer/src/OAuth.php @@ -0,0 +1,139 @@ + + * @author Jim Jagielski (jimjag) + * @author Andy Prevost (codeworxtech) + * @author Brent R. Matzelle (original founder) + * @copyright 2012 - 2020 Marcus Bointon + * @copyright 2010 - 2012 Jim Jagielski + * @copyright 2004 - 2009 Andy Prevost + * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License + * @note This program is distributed in the hope that it will be useful - WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. + */ + +namespace PHPMailer\PHPMailer; + +use League\OAuth2\Client\Grant\RefreshToken; +use League\OAuth2\Client\Provider\AbstractProvider; +use League\OAuth2\Client\Token\AccessToken; + +/** + * OAuth - OAuth2 authentication wrapper class. + * Uses the oauth2-client package from the League of Extraordinary Packages. + * + * @see http://oauth2-client.thephpleague.com + * + * @author Marcus Bointon (Synchro/coolbru) + */ +class OAuth +{ + /** + * An instance of the League OAuth Client Provider. + * + * @var AbstractProvider + */ + protected $provider; + + /** + * The current OAuth access token. + * + * @var AccessToken + */ + protected $oauthToken; + + /** + * The user's email address, usually used as the login ID + * and also the from address when sending email. + * + * @var string + */ + protected $oauthUserEmail = ''; + + /** + * The client secret, generated in the app definition of the service you're connecting to. + * + * @var string + */ + protected $oauthClientSecret = ''; + + /** + * The client ID, generated in the app definition of the service you're connecting to. + * + * @var string + */ + protected $oauthClientId = ''; + + /** + * The refresh token, used to obtain new AccessTokens. + * + * @var string + */ + protected $oauthRefreshToken = ''; + + /** + * OAuth constructor. + * + * @param array $options Associative array containing + * `provider`, `userName`, `clientSecret`, `clientId` and `refreshToken` elements + */ + public function __construct($options) + { + $this->provider = $options['provider']; + $this->oauthUserEmail = $options['userName']; + $this->oauthClientSecret = $options['clientSecret']; + $this->oauthClientId = $options['clientId']; + $this->oauthRefreshToken = $options['refreshToken']; + } + + /** + * Get a new RefreshToken. + * + * @return RefreshToken + */ + protected function getGrant() + { + return new RefreshToken(); + } + + /** + * Get a new AccessToken. + * + * @return AccessToken + */ + protected function getToken() + { + return $this->provider->getAccessToken( + $this->getGrant(), + ['refresh_token' => $this->oauthRefreshToken] + ); + } + + /** + * Generate a base64-encoded OAuth token. + * + * @return string + */ + public function getOauth64() + { + //Get a new token if it's not available or has expired + if (null === $this->oauthToken || $this->oauthToken->hasExpired()) { + $this->oauthToken = $this->getToken(); + } + + return base64_encode( + 'user=' . + $this->oauthUserEmail . + "\001auth=Bearer " . + $this->oauthToken . + "\001\001" + ); + } +} diff --git a/kirby/vendor/phpmailer/phpmailer/src/PHPMailer.php b/kirby/vendor/phpmailer/phpmailer/src/PHPMailer.php new file mode 100644 index 0000000..eb4b742 --- /dev/null +++ b/kirby/vendor/phpmailer/phpmailer/src/PHPMailer.php @@ -0,0 +1,4970 @@ + + * @author Jim Jagielski (jimjag) + * @author Andy Prevost (codeworxtech) + * @author Brent R. Matzelle (original founder) + * @copyright 2012 - 2020 Marcus Bointon + * @copyright 2010 - 2012 Jim Jagielski + * @copyright 2004 - 2009 Andy Prevost + * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License + * @note This program is distributed in the hope that it will be useful - WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. + */ + +namespace PHPMailer\PHPMailer; + +/** + * PHPMailer - PHP email creation and transport class. + * + * @author Marcus Bointon (Synchro/coolbru) + * @author Jim Jagielski (jimjag) + * @author Andy Prevost (codeworxtech) + * @author Brent R. Matzelle (original founder) + */ +class PHPMailer +{ + const CHARSET_ASCII = 'us-ascii'; + const CHARSET_ISO88591 = 'iso-8859-1'; + const CHARSET_UTF8 = 'utf-8'; + + const CONTENT_TYPE_PLAINTEXT = 'text/plain'; + const CONTENT_TYPE_TEXT_CALENDAR = 'text/calendar'; + const CONTENT_TYPE_TEXT_HTML = 'text/html'; + const CONTENT_TYPE_MULTIPART_ALTERNATIVE = 'multipart/alternative'; + const CONTENT_TYPE_MULTIPART_MIXED = 'multipart/mixed'; + const CONTENT_TYPE_MULTIPART_RELATED = 'multipart/related'; + + const ENCODING_7BIT = '7bit'; + const ENCODING_8BIT = '8bit'; + const ENCODING_BASE64 = 'base64'; + const ENCODING_BINARY = 'binary'; + const ENCODING_QUOTED_PRINTABLE = 'quoted-printable'; + + const ENCRYPTION_STARTTLS = 'tls'; + const ENCRYPTION_SMTPS = 'ssl'; + + const ICAL_METHOD_REQUEST = 'REQUEST'; + const ICAL_METHOD_PUBLISH = 'PUBLISH'; + const ICAL_METHOD_REPLY = 'REPLY'; + const ICAL_METHOD_ADD = 'ADD'; + const ICAL_METHOD_CANCEL = 'CANCEL'; + const ICAL_METHOD_REFRESH = 'REFRESH'; + const ICAL_METHOD_COUNTER = 'COUNTER'; + const ICAL_METHOD_DECLINECOUNTER = 'DECLINECOUNTER'; + + /** + * Email priority. + * Options: null (default), 1 = High, 3 = Normal, 5 = low. + * When null, the header is not set at all. + * + * @var int|null + */ + public $Priority; + + /** + * The character set of the message. + * + * @var string + */ + public $CharSet = self::CHARSET_ISO88591; + + /** + * The MIME Content-type of the message. + * + * @var string + */ + public $ContentType = self::CONTENT_TYPE_PLAINTEXT; + + /** + * The message encoding. + * Options: "8bit", "7bit", "binary", "base64", and "quoted-printable". + * + * @var string + */ + public $Encoding = self::ENCODING_8BIT; + + /** + * Holds the most recent mailer error message. + * + * @var string + */ + public $ErrorInfo = ''; + + /** + * The From email address for the message. + * + * @var string + */ + public $From = 'root@localhost'; + + /** + * The From name of the message. + * + * @var string + */ + public $FromName = 'Root User'; + + /** + * The envelope sender of the message. + * This will usually be turned into a Return-Path header by the receiver, + * and is the address that bounces will be sent to. + * If not empty, will be passed via `-f` to sendmail or as the 'MAIL FROM' value over SMTP. + * + * @var string + */ + public $Sender = ''; + + /** + * The Subject of the message. + * + * @var string + */ + public $Subject = ''; + + /** + * An HTML or plain text message body. + * If HTML then call isHTML(true). + * + * @var string + */ + public $Body = ''; + + /** + * The plain-text message body. + * This body can be read by mail clients that do not have HTML email + * capability such as mutt & Eudora. + * Clients that can read HTML will view the normal Body. + * + * @var string + */ + public $AltBody = ''; + + /** + * An iCal message part body. + * Only supported in simple alt or alt_inline message types + * To generate iCal event structures, use classes like EasyPeasyICS or iCalcreator. + * + * @see http://sprain.ch/blog/downloads/php-class-easypeasyics-create-ical-files-with-php/ + * @see http://kigkonsult.se/iCalcreator/ + * + * @var string + */ + public $Ical = ''; + + /** + * Value-array of "method" in Contenttype header "text/calendar" + * + * @var string[] + */ + protected static $IcalMethods = [ + self::ICAL_METHOD_REQUEST, + self::ICAL_METHOD_PUBLISH, + self::ICAL_METHOD_REPLY, + self::ICAL_METHOD_ADD, + self::ICAL_METHOD_CANCEL, + self::ICAL_METHOD_REFRESH, + self::ICAL_METHOD_COUNTER, + self::ICAL_METHOD_DECLINECOUNTER, + ]; + + /** + * The complete compiled MIME message body. + * + * @var string + */ + protected $MIMEBody = ''; + + /** + * The complete compiled MIME message headers. + * + * @var string + */ + protected $MIMEHeader = ''; + + /** + * Extra headers that createHeader() doesn't fold in. + * + * @var string + */ + protected $mailHeader = ''; + + /** + * Word-wrap the message body to this number of chars. + * Set to 0 to not wrap. A useful value here is 78, for RFC2822 section 2.1.1 compliance. + * + * @see static::STD_LINE_LENGTH + * + * @var int + */ + public $WordWrap = 0; + + /** + * Which method to use to send mail. + * Options: "mail", "sendmail", or "smtp". + * + * @var string + */ + public $Mailer = 'mail'; + + /** + * The path to the sendmail program. + * + * @var string + */ + public $Sendmail = '/usr/sbin/sendmail'; + + /** + * Whether mail() uses a fully sendmail-compatible MTA. + * One which supports sendmail's "-oi -f" options. + * + * @var bool + */ + public $UseSendmailOptions = true; + + /** + * The email address that a reading confirmation should be sent to, also known as read receipt. + * + * @var string + */ + public $ConfirmReadingTo = ''; + + /** + * The hostname to use in the Message-ID header and as default HELO string. + * If empty, PHPMailer attempts to find one with, in order, + * $_SERVER['SERVER_NAME'], gethostname(), php_uname('n'), or the value + * 'localhost.localdomain'. + * + * @see PHPMailer::$Helo + * + * @var string + */ + public $Hostname = ''; + + /** + * An ID to be used in the Message-ID header. + * If empty, a unique id will be generated. + * You can set your own, but it must be in the format "", + * as defined in RFC5322 section 3.6.4 or it will be ignored. + * + * @see https://tools.ietf.org/html/rfc5322#section-3.6.4 + * + * @var string + */ + public $MessageID = ''; + + /** + * The message Date to be used in the Date header. + * If empty, the current date will be added. + * + * @var string + */ + public $MessageDate = ''; + + /** + * SMTP hosts. + * Either a single hostname or multiple semicolon-delimited hostnames. + * You can also specify a different port + * for each host by using this format: [hostname:port] + * (e.g. "smtp1.example.com:25;smtp2.example.com"). + * You can also specify encryption type, for example: + * (e.g. "tls://smtp1.example.com:587;ssl://smtp2.example.com:465"). + * Hosts will be tried in order. + * + * @var string + */ + public $Host = 'localhost'; + + /** + * The default SMTP server port. + * + * @var int + */ + public $Port = 25; + + /** + * The SMTP HELO/EHLO name used for the SMTP connection. + * Default is $Hostname. If $Hostname is empty, PHPMailer attempts to find + * one with the same method described above for $Hostname. + * + * @see PHPMailer::$Hostname + * + * @var string + */ + public $Helo = ''; + + /** + * What kind of encryption to use on the SMTP connection. + * Options: '', static::ENCRYPTION_STARTTLS, or static::ENCRYPTION_SMTPS. + * + * @var string + */ + public $SMTPSecure = ''; + + /** + * Whether to enable TLS encryption automatically if a server supports it, + * even if `SMTPSecure` is not set to 'tls'. + * Be aware that in PHP >= 5.6 this requires that the server's certificates are valid. + * + * @var bool + */ + public $SMTPAutoTLS = true; + + /** + * Whether to use SMTP authentication. + * Uses the Username and Password properties. + * + * @see PHPMailer::$Username + * @see PHPMailer::$Password + * + * @var bool + */ + public $SMTPAuth = false; + + /** + * Options array passed to stream_context_create when connecting via SMTP. + * + * @var array + */ + public $SMTPOptions = []; + + /** + * SMTP username. + * + * @var string + */ + public $Username = ''; + + /** + * SMTP password. + * + * @var string + */ + public $Password = ''; + + /** + * SMTP auth type. + * Options are CRAM-MD5, LOGIN, PLAIN, XOAUTH2, attempted in that order if not specified. + * + * @var string + */ + public $AuthType = ''; + + /** + * An instance of the PHPMailer OAuth class. + * + * @var OAuth + */ + protected $oauth; + + /** + * The SMTP server timeout in seconds. + * Default of 5 minutes (300sec) is from RFC2821 section 4.5.3.2. + * + * @var int + */ + public $Timeout = 300; + + /** + * Comma separated list of DSN notifications + * 'NEVER' under no circumstances a DSN must be returned to the sender. + * If you use NEVER all other notifications will be ignored. + * 'SUCCESS' will notify you when your mail has arrived at its destination. + * 'FAILURE' will arrive if an error occurred during delivery. + * 'DELAY' will notify you if there is an unusual delay in delivery, but the actual + * delivery's outcome (success or failure) is not yet decided. + * + * @see https://tools.ietf.org/html/rfc3461 See section 4.1 for more information about NOTIFY + */ + public $dsn = ''; + + /** + * SMTP class debug output mode. + * Debug output level. + * Options: + * @see SMTP::DEBUG_OFF: No output + * @see SMTP::DEBUG_CLIENT: Client messages + * @see SMTP::DEBUG_SERVER: Client and server messages + * @see SMTP::DEBUG_CONNECTION: As SERVER plus connection status + * @see SMTP::DEBUG_LOWLEVEL: Noisy, low-level data output, rarely needed + * + * @see SMTP::$do_debug + * + * @var int + */ + public $SMTPDebug = 0; + + /** + * How to handle debug output. + * Options: + * * `echo` Output plain-text as-is, appropriate for CLI + * * `html` Output escaped, line breaks converted to `
    `, appropriate for browser output + * * `error_log` Output to error log as configured in php.ini + * By default PHPMailer will use `echo` if run from a `cli` or `cli-server` SAPI, `html` otherwise. + * Alternatively, you can provide a callable expecting two params: a message string and the debug level: + * + * ```php + * $mail->Debugoutput = function($str, $level) {echo "debug level $level; message: $str";}; + * ``` + * + * Alternatively, you can pass in an instance of a PSR-3 compatible logger, though only `debug` + * level output is used: + * + * ```php + * $mail->Debugoutput = new myPsr3Logger; + * ``` + * + * @see SMTP::$Debugoutput + * + * @var string|callable|\Psr\Log\LoggerInterface + */ + public $Debugoutput = 'echo'; + + /** + * Whether to keep the SMTP connection open after each message. + * If this is set to true then the connection will remain open after a send, + * and closing the connection will require an explicit call to smtpClose(). + * It's a good idea to use this if you are sending multiple messages as it reduces overhead. + * See the mailing list example for how to use it. + * + * @var bool + */ + public $SMTPKeepAlive = false; + + /** + * Whether to split multiple to addresses into multiple messages + * or send them all in one message. + * Only supported in `mail` and `sendmail` transports, not in SMTP. + * + * @var bool + * + * @deprecated 6.0.0 PHPMailer isn't a mailing list manager! + */ + public $SingleTo = false; + + /** + * Storage for addresses when SingleTo is enabled. + * + * @var array + */ + protected $SingleToArray = []; + + /** + * Whether to generate VERP addresses on send. + * Only applicable when sending via SMTP. + * + * @see https://en.wikipedia.org/wiki/Variable_envelope_return_path + * @see http://www.postfix.org/VERP_README.html Postfix VERP info + * + * @var bool + */ + public $do_verp = false; + + /** + * Whether to allow sending messages with an empty body. + * + * @var bool + */ + public $AllowEmpty = false; + + /** + * DKIM selector. + * + * @var string + */ + public $DKIM_selector = ''; + + /** + * DKIM Identity. + * Usually the email address used as the source of the email. + * + * @var string + */ + public $DKIM_identity = ''; + + /** + * DKIM passphrase. + * Used if your key is encrypted. + * + * @var string + */ + public $DKIM_passphrase = ''; + + /** + * DKIM signing domain name. + * + * @example 'example.com' + * + * @var string + */ + public $DKIM_domain = ''; + + /** + * DKIM Copy header field values for diagnostic use. + * + * @var bool + */ + public $DKIM_copyHeaderFields = true; + + /** + * DKIM Extra signing headers. + * + * @example ['List-Unsubscribe', 'List-Help'] + * + * @var array + */ + public $DKIM_extraHeaders = []; + + /** + * DKIM private key file path. + * + * @var string + */ + public $DKIM_private = ''; + + /** + * DKIM private key string. + * + * If set, takes precedence over `$DKIM_private`. + * + * @var string + */ + public $DKIM_private_string = ''; + + /** + * Callback Action function name. + * + * The function that handles the result of the send email action. + * It is called out by send() for each email sent. + * + * Value can be any php callable: http://www.php.net/is_callable + * + * Parameters: + * bool $result result of the send action + * array $to email addresses of the recipients + * array $cc cc email addresses + * array $bcc bcc email addresses + * string $subject the subject + * string $body the email body + * string $from email address of sender + * string $extra extra information of possible use + * "smtp_transaction_id' => last smtp transaction id + * + * @var string + */ + public $action_function = ''; + + /** + * What to put in the X-Mailer header. + * Options: An empty string for PHPMailer default, whitespace/null for none, or a string to use. + * + * @var string|null + */ + public $XMailer = ''; + + /** + * Which validator to use by default when validating email addresses. + * May be a callable to inject your own validator, but there are several built-in validators. + * The default validator uses PHP's FILTER_VALIDATE_EMAIL filter_var option. + * + * @see PHPMailer::validateAddress() + * + * @var string|callable + */ + public static $validator = 'php'; + + /** + * An instance of the SMTP sender class. + * + * @var SMTP + */ + protected $smtp; + + /** + * The array of 'to' names and addresses. + * + * @var array + */ + protected $to = []; + + /** + * The array of 'cc' names and addresses. + * + * @var array + */ + protected $cc = []; + + /** + * The array of 'bcc' names and addresses. + * + * @var array + */ + protected $bcc = []; + + /** + * The array of reply-to names and addresses. + * + * @var array + */ + protected $ReplyTo = []; + + /** + * An array of all kinds of addresses. + * Includes all of $to, $cc, $bcc. + * + * @see PHPMailer::$to + * @see PHPMailer::$cc + * @see PHPMailer::$bcc + * + * @var array + */ + protected $all_recipients = []; + + /** + * An array of names and addresses queued for validation. + * In send(), valid and non duplicate entries are moved to $all_recipients + * and one of $to, $cc, or $bcc. + * This array is used only for addresses with IDN. + * + * @see PHPMailer::$to + * @see PHPMailer::$cc + * @see PHPMailer::$bcc + * @see PHPMailer::$all_recipients + * + * @var array + */ + protected $RecipientsQueue = []; + + /** + * An array of reply-to names and addresses queued for validation. + * In send(), valid and non duplicate entries are moved to $ReplyTo. + * This array is used only for addresses with IDN. + * + * @see PHPMailer::$ReplyTo + * + * @var array + */ + protected $ReplyToQueue = []; + + /** + * The array of attachments. + * + * @var array + */ + protected $attachment = []; + + /** + * The array of custom headers. + * + * @var array + */ + protected $CustomHeader = []; + + /** + * The most recent Message-ID (including angular brackets). + * + * @var string + */ + protected $lastMessageID = ''; + + /** + * The message's MIME type. + * + * @var string + */ + protected $message_type = ''; + + /** + * The array of MIME boundary strings. + * + * @var array + */ + protected $boundary = []; + + /** + * The array of available languages. + * + * @var array + */ + protected $language = []; + + /** + * The number of errors encountered. + * + * @var int + */ + protected $error_count = 0; + + /** + * The S/MIME certificate file path. + * + * @var string + */ + protected $sign_cert_file = ''; + + /** + * The S/MIME key file path. + * + * @var string + */ + protected $sign_key_file = ''; + + /** + * The optional S/MIME extra certificates ("CA Chain") file path. + * + * @var string + */ + protected $sign_extracerts_file = ''; + + /** + * The S/MIME password for the key. + * Used only if the key is encrypted. + * + * @var string + */ + protected $sign_key_pass = ''; + + /** + * Whether to throw exceptions for errors. + * + * @var bool + */ + protected $exceptions = false; + + /** + * Unique ID used for message ID and boundaries. + * + * @var string + */ + protected $uniqueid = ''; + + /** + * The PHPMailer Version number. + * + * @var string + */ + const VERSION = '6.5.0'; + + /** + * Error severity: message only, continue processing. + * + * @var int + */ + const STOP_MESSAGE = 0; + + /** + * Error severity: message, likely ok to continue processing. + * + * @var int + */ + const STOP_CONTINUE = 1; + + /** + * Error severity: message, plus full stop, critical error reached. + * + * @var int + */ + const STOP_CRITICAL = 2; + + /** + * The SMTP standard CRLF line break. + * If you want to change line break format, change static::$LE, not this. + */ + const CRLF = "\r\n"; + + /** + * "Folding White Space" a white space string used for line folding. + */ + const FWS = ' '; + + /** + * SMTP RFC standard line ending; Carriage Return, Line Feed. + * + * @var string + */ + protected static $LE = self::CRLF; + + /** + * The maximum line length supported by mail(). + * + * Background: mail() will sometimes corrupt messages + * with headers headers longer than 65 chars, see #818. + * + * @var int + */ + const MAIL_MAX_LINE_LENGTH = 63; + + /** + * The maximum line length allowed by RFC 2822 section 2.1.1. + * + * @var int + */ + const MAX_LINE_LENGTH = 998; + + /** + * The lower maximum line length allowed by RFC 2822 section 2.1.1. + * This length does NOT include the line break + * 76 means that lines will be 77 or 78 chars depending on whether + * the line break format is LF or CRLF; both are valid. + * + * @var int + */ + const STD_LINE_LENGTH = 76; + + /** + * Constructor. + * + * @param bool $exceptions Should we throw external exceptions? + */ + public function __construct($exceptions = null) + { + if (null !== $exceptions) { + $this->exceptions = (bool) $exceptions; + } + //Pick an appropriate debug output format automatically + $this->Debugoutput = (strpos(PHP_SAPI, 'cli') !== false ? 'echo' : 'html'); + } + + /** + * Destructor. + */ + public function __destruct() + { + //Close any open SMTP connection nicely + $this->smtpClose(); + } + + /** + * Call mail() in a safe_mode-aware fashion. + * Also, unless sendmail_path points to sendmail (or something that + * claims to be sendmail), don't pass params (not a perfect fix, + * but it will do). + * + * @param string $to To + * @param string $subject Subject + * @param string $body Message Body + * @param string $header Additional Header(s) + * @param string|null $params Params + * + * @return bool + */ + private function mailPassthru($to, $subject, $body, $header, $params) + { + //Check overloading of mail function to avoid double-encoding + if (ini_get('mbstring.func_overload') & 1) { + $subject = $this->secureHeader($subject); + } else { + $subject = $this->encodeHeader($this->secureHeader($subject)); + } + //Calling mail() with null params breaks + $this->edebug('Sending with mail()'); + $this->edebug('Sendmail path: ' . ini_get('sendmail_path')); + $this->edebug("Envelope sender: {$this->Sender}"); + $this->edebug("To: {$to}"); + $this->edebug("Subject: {$subject}"); + $this->edebug("Headers: {$header}"); + if (!$this->UseSendmailOptions || null === $params) { + $result = @mail($to, $subject, $body, $header); + } else { + $this->edebug("Additional params: {$params}"); + $result = @mail($to, $subject, $body, $header, $params); + } + $this->edebug('Result: ' . ($result ? 'true' : 'false')); + return $result; + } + + /** + * Output debugging info via a user-defined method. + * Only generates output if debug output is enabled. + * + * @see PHPMailer::$Debugoutput + * @see PHPMailer::$SMTPDebug + * + * @param string $str + */ + protected function edebug($str) + { + if ($this->SMTPDebug <= 0) { + return; + } + //Is this a PSR-3 logger? + if ($this->Debugoutput instanceof \Psr\Log\LoggerInterface) { + $this->Debugoutput->debug($str); + + return; + } + //Avoid clash with built-in function names + if (is_callable($this->Debugoutput) && !in_array($this->Debugoutput, ['error_log', 'html', 'echo'])) { + call_user_func($this->Debugoutput, $str, $this->SMTPDebug); + + return; + } + switch ($this->Debugoutput) { + case 'error_log': + //Don't output, just log + /** @noinspection ForgottenDebugOutputInspection */ + error_log($str); + break; + case 'html': + //Cleans up output a bit for a better looking, HTML-safe output + echo htmlentities( + preg_replace('/[\r\n]+/', '', $str), + ENT_QUOTES, + 'UTF-8' + ), "
    \n"; + break; + case 'echo': + default: + //Normalize line breaks + $str = preg_replace('/\r\n|\r/m', "\n", $str); + echo gmdate('Y-m-d H:i:s'), + "\t", + //Trim trailing space + trim( + //Indent for readability, except for trailing break + str_replace( + "\n", + "\n \t ", + trim($str) + ) + ), + "\n"; + } + } + + /** + * Sets message type to HTML or plain. + * + * @param bool $isHtml True for HTML mode + */ + public function isHTML($isHtml = true) + { + if ($isHtml) { + $this->ContentType = static::CONTENT_TYPE_TEXT_HTML; + } else { + $this->ContentType = static::CONTENT_TYPE_PLAINTEXT; + } + } + + /** + * Send messages using SMTP. + */ + public function isSMTP() + { + $this->Mailer = 'smtp'; + } + + /** + * Send messages using PHP's mail() function. + */ + public function isMail() + { + $this->Mailer = 'mail'; + } + + /** + * Send messages using $Sendmail. + */ + public function isSendmail() + { + $ini_sendmail_path = ini_get('sendmail_path'); + + if (false === stripos($ini_sendmail_path, 'sendmail')) { + $this->Sendmail = '/usr/sbin/sendmail'; + } else { + $this->Sendmail = $ini_sendmail_path; + } + $this->Mailer = 'sendmail'; + } + + /** + * Send messages using qmail. + */ + public function isQmail() + { + $ini_sendmail_path = ini_get('sendmail_path'); + + if (false === stripos($ini_sendmail_path, 'qmail')) { + $this->Sendmail = '/var/qmail/bin/qmail-inject'; + } else { + $this->Sendmail = $ini_sendmail_path; + } + $this->Mailer = 'qmail'; + } + + /** + * Add a "To" address. + * + * @param string $address The email address to send to + * @param string $name + * + * @throws Exception + * + * @return bool true on success, false if address already used or invalid in some way + */ + public function addAddress($address, $name = '') + { + return $this->addOrEnqueueAnAddress('to', $address, $name); + } + + /** + * Add a "CC" address. + * + * @param string $address The email address to send to + * @param string $name + * + * @throws Exception + * + * @return bool true on success, false if address already used or invalid in some way + */ + public function addCC($address, $name = '') + { + return $this->addOrEnqueueAnAddress('cc', $address, $name); + } + + /** + * Add a "BCC" address. + * + * @param string $address The email address to send to + * @param string $name + * + * @throws Exception + * + * @return bool true on success, false if address already used or invalid in some way + */ + public function addBCC($address, $name = '') + { + return $this->addOrEnqueueAnAddress('bcc', $address, $name); + } + + /** + * Add a "Reply-To" address. + * + * @param string $address The email address to reply to + * @param string $name + * + * @throws Exception + * + * @return bool true on success, false if address already used or invalid in some way + */ + public function addReplyTo($address, $name = '') + { + return $this->addOrEnqueueAnAddress('Reply-To', $address, $name); + } + + /** + * Add an address to one of the recipient arrays or to the ReplyTo array. Because PHPMailer + * can't validate addresses with an IDN without knowing the PHPMailer::$CharSet (that can still + * be modified after calling this function), addition of such addresses is delayed until send(). + * Addresses that have been added already return false, but do not throw exceptions. + * + * @param string $kind One of 'to', 'cc', 'bcc', or 'ReplyTo' + * @param string $address The email address to send, resp. to reply to + * @param string $name + * + * @throws Exception + * + * @return bool true on success, false if address already used or invalid in some way + */ + protected function addOrEnqueueAnAddress($kind, $address, $name) + { + $address = trim($address); + $name = trim(preg_replace('/[\r\n]+/', '', $name)); //Strip breaks and trim + $pos = strrpos($address, '@'); + if (false === $pos) { + //At-sign is missing. + $error_message = sprintf( + '%s (%s): %s', + $this->lang('invalid_address'), + $kind, + $address + ); + $this->setError($error_message); + $this->edebug($error_message); + if ($this->exceptions) { + throw new Exception($error_message); + } + + return false; + } + $params = [$kind, $address, $name]; + //Enqueue addresses with IDN until we know the PHPMailer::$CharSet. + if (static::idnSupported() && $this->has8bitChars(substr($address, ++$pos))) { + if ('Reply-To' !== $kind) { + if (!array_key_exists($address, $this->RecipientsQueue)) { + $this->RecipientsQueue[$address] = $params; + + return true; + } + } elseif (!array_key_exists($address, $this->ReplyToQueue)) { + $this->ReplyToQueue[$address] = $params; + + return true; + } + + return false; + } + + //Immediately add standard addresses without IDN. + return call_user_func_array([$this, 'addAnAddress'], $params); + } + + /** + * Add an address to one of the recipient arrays or to the ReplyTo array. + * Addresses that have been added already return false, but do not throw exceptions. + * + * @param string $kind One of 'to', 'cc', 'bcc', or 'ReplyTo' + * @param string $address The email address to send, resp. to reply to + * @param string $name + * + * @throws Exception + * + * @return bool true on success, false if address already used or invalid in some way + */ + protected function addAnAddress($kind, $address, $name = '') + { + if (!in_array($kind, ['to', 'cc', 'bcc', 'Reply-To'])) { + $error_message = sprintf( + '%s: %s', + $this->lang('Invalid recipient kind'), + $kind + ); + $this->setError($error_message); + $this->edebug($error_message); + if ($this->exceptions) { + throw new Exception($error_message); + } + + return false; + } + if (!static::validateAddress($address)) { + $error_message = sprintf( + '%s (%s): %s', + $this->lang('invalid_address'), + $kind, + $address + ); + $this->setError($error_message); + $this->edebug($error_message); + if ($this->exceptions) { + throw new Exception($error_message); + } + + return false; + } + if ('Reply-To' !== $kind) { + if (!array_key_exists(strtolower($address), $this->all_recipients)) { + $this->{$kind}[] = [$address, $name]; + $this->all_recipients[strtolower($address)] = true; + + return true; + } + } elseif (!array_key_exists(strtolower($address), $this->ReplyTo)) { + $this->ReplyTo[strtolower($address)] = [$address, $name]; + + return true; + } + + return false; + } + + /** + * Parse and validate a string containing one or more RFC822-style comma-separated email addresses + * of the form "display name
    " into an array of name/address pairs. + * Uses the imap_rfc822_parse_adrlist function if the IMAP extension is available. + * Note that quotes in the name part are removed. + * + * @see http://www.andrew.cmu.edu/user/agreen1/testing/mrbs/web/Mail/RFC822.php A more careful implementation + * + * @param string $addrstr The address list string + * @param bool $useimap Whether to use the IMAP extension to parse the list + * + * @return array + */ + public static function parseAddresses($addrstr, $useimap = true) + { + $addresses = []; + if ($useimap && function_exists('imap_rfc822_parse_adrlist')) { + //Use this built-in parser if it's available + $list = imap_rfc822_parse_adrlist($addrstr, ''); + foreach ($list as $address) { + if ( + ('.SYNTAX-ERROR.' !== $address->host) && static::validateAddress( + $address->mailbox . '@' . $address->host + ) + ) { + //Decode the name part if it's present and encoded + if ( + property_exists($address, 'personal') && + extension_loaded('mbstring') && + preg_match('/^=\?.*\?=$/', $address->personal) + ) { + $address->personal = mb_decode_mimeheader($address->personal); + } + + $addresses[] = [ + 'name' => (property_exists($address, 'personal') ? $address->personal : ''), + 'address' => $address->mailbox . '@' . $address->host, + ]; + } + } + } else { + //Use this simpler parser + $list = explode(',', $addrstr); + foreach ($list as $address) { + $address = trim($address); + //Is there a separate name part? + if (strpos($address, '<') === false) { + //No separate name, just use the whole thing + if (static::validateAddress($address)) { + $addresses[] = [ + 'name' => '', + 'address' => $address, + ]; + } + } else { + list($name, $email) = explode('<', $address); + $email = trim(str_replace('>', '', $email)); + $name = trim($name); + if (static::validateAddress($email)) { + //If this name is encoded, decode it + if (preg_match('/^=\?.*\?=$/', $name)) { + $name = mb_decode_mimeheader($name); + } + $addresses[] = [ + //Remove any surrounding quotes and spaces from the name + 'name' => trim($name, '\'" '), + 'address' => $email, + ]; + } + } + } + } + + return $addresses; + } + + /** + * Set the From and FromName properties. + * + * @param string $address + * @param string $name + * @param bool $auto Whether to also set the Sender address, defaults to true + * + * @throws Exception + * + * @return bool + */ + public function setFrom($address, $name = '', $auto = true) + { + $address = trim($address); + $name = trim(preg_replace('/[\r\n]+/', '', $name)); //Strip breaks and trim + //Don't validate now addresses with IDN. Will be done in send(). + $pos = strrpos($address, '@'); + if ( + (false === $pos) + || ((!$this->has8bitChars(substr($address, ++$pos)) || !static::idnSupported()) + && !static::validateAddress($address)) + ) { + $error_message = sprintf( + '%s (From): %s', + $this->lang('invalid_address'), + $address + ); + $this->setError($error_message); + $this->edebug($error_message); + if ($this->exceptions) { + throw new Exception($error_message); + } + + return false; + } + $this->From = $address; + $this->FromName = $name; + if ($auto && empty($this->Sender)) { + $this->Sender = $address; + } + + return true; + } + + /** + * Return the Message-ID header of the last email. + * Technically this is the value from the last time the headers were created, + * but it's also the message ID of the last sent message except in + * pathological cases. + * + * @return string + */ + public function getLastMessageID() + { + return $this->lastMessageID; + } + + /** + * Check that a string looks like an email address. + * Validation patterns supported: + * * `auto` Pick best pattern automatically; + * * `pcre8` Use the squiloople.com pattern, requires PCRE > 8.0; + * * `pcre` Use old PCRE implementation; + * * `php` Use PHP built-in FILTER_VALIDATE_EMAIL; + * * `html5` Use the pattern given by the HTML5 spec for 'email' type form input elements. + * * `noregex` Don't use a regex: super fast, really dumb. + * Alternatively you may pass in a callable to inject your own validator, for example: + * + * ```php + * PHPMailer::validateAddress('user@example.com', function($address) { + * return (strpos($address, '@') !== false); + * }); + * ``` + * + * You can also set the PHPMailer::$validator static to a callable, allowing built-in methods to use your validator. + * + * @param string $address The email address to check + * @param string|callable $patternselect Which pattern to use + * + * @return bool + */ + public static function validateAddress($address, $patternselect = null) + { + if (null === $patternselect) { + $patternselect = static::$validator; + } + //Don't allow strings as callables, see SECURITY.md and CVE-2021-3603 + if (is_callable($patternselect) && !is_string($patternselect)) { + return call_user_func($patternselect, $address); + } + //Reject line breaks in addresses; it's valid RFC5322, but not RFC5321 + if (strpos($address, "\n") !== false || strpos($address, "\r") !== false) { + return false; + } + switch ($patternselect) { + case 'pcre': //Kept for BC + case 'pcre8': + /* + * A more complex and more permissive version of the RFC5322 regex on which FILTER_VALIDATE_EMAIL + * is based. + * In addition to the addresses allowed by filter_var, also permits: + * * dotless domains: `a@b` + * * comments: `1234 @ local(blah) .machine .example` + * * quoted elements: `'"test blah"@example.org'` + * * numeric TLDs: `a@b.123` + * * unbracketed IPv4 literals: `a@192.168.0.1` + * * IPv6 literals: 'first.last@[IPv6:a1::]' + * Not all of these will necessarily work for sending! + * + * @see http://squiloople.com/2009/12/20/email-address-validation/ + * @copyright 2009-2010 Michael Rushton + * Feel free to use and redistribute this code. But please keep this copyright notice. + */ + return (bool) preg_match( + '/^(?!(?>(?1)"?(?>\\\[ -~]|[^"])"?(?1)){255,})(?!(?>(?1)"?(?>\\\[ -~]|[^"])"?(?1)){65,}@)' . + '((?>(?>(?>((?>(?>(?>\x0D\x0A)?[\t ])+|(?>[\t ]*\x0D\x0A)?[\t ]+)?)(\((?>(?2)' . + '(?>[\x01-\x08\x0B\x0C\x0E-\'*-\[\]-\x7F]|\\\[\x00-\x7F]|(?3)))*(?2)\)))+(?2))|(?2))?)' . + '([!#-\'*+\/-9=?^-~-]+|"(?>(?2)(?>[\x01-\x08\x0B\x0C\x0E-!#-\[\]-\x7F]|\\\[\x00-\x7F]))*' . + '(?2)")(?>(?1)\.(?1)(?4))*(?1)@(?!(?1)[a-z0-9-]{64,})(?1)(?>([a-z0-9](?>[a-z0-9-]*[a-z0-9])?)' . + '(?>(?1)\.(?!(?1)[a-z0-9-]{64,})(?1)(?5)){0,126}|\[(?:(?>IPv6:(?>([a-f0-9]{1,4})(?>:(?6)){7}' . + '|(?!(?:.*[a-f0-9][:\]]){8,})((?6)(?>:(?6)){0,6})?::(?7)?))|(?>(?>IPv6:(?>(?6)(?>:(?6)){5}:' . + '|(?!(?:.*[a-f0-9]:){6,})(?8)?::(?>((?6)(?>:(?6)){0,4}):)?))?(25[0-5]|2[0-4][0-9]|1[0-9]{2}' . + '|[1-9]?[0-9])(?>\.(?9)){3}))\])(?1)$/isD', + $address + ); + case 'html5': + /* + * This is the pattern used in the HTML5 spec for validation of 'email' type form input elements. + * + * @see https://html.spec.whatwg.org/#e-mail-state-(type=email) + */ + return (bool) preg_match( + '/^[a-zA-Z0-9.!#$%&\'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}' . + '[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/sD', + $address + ); + case 'php': + default: + return filter_var($address, FILTER_VALIDATE_EMAIL) !== false; + } + } + + /** + * Tells whether IDNs (Internationalized Domain Names) are supported or not. This requires the + * `intl` and `mbstring` PHP extensions. + * + * @return bool `true` if required functions for IDN support are present + */ + public static function idnSupported() + { + return function_exists('idn_to_ascii') && function_exists('mb_convert_encoding'); + } + + /** + * Converts IDN in given email address to its ASCII form, also known as punycode, if possible. + * Important: Address must be passed in same encoding as currently set in PHPMailer::$CharSet. + * This function silently returns unmodified address if: + * - No conversion is necessary (i.e. domain name is not an IDN, or is already in ASCII form) + * - Conversion to punycode is impossible (e.g. required PHP functions are not available) + * or fails for any reason (e.g. domain contains characters not allowed in an IDN). + * + * @see PHPMailer::$CharSet + * + * @param string $address The email address to convert + * + * @return string The encoded address in ASCII form + */ + public function punyencodeAddress($address) + { + //Verify we have required functions, CharSet, and at-sign. + $pos = strrpos($address, '@'); + if ( + !empty($this->CharSet) && + false !== $pos && + static::idnSupported() + ) { + $domain = substr($address, ++$pos); + //Verify CharSet string is a valid one, and domain properly encoded in this CharSet. + if ($this->has8bitChars($domain) && @mb_check_encoding($domain, $this->CharSet)) { + //Convert the domain from whatever charset it's in to UTF-8 + $domain = mb_convert_encoding($domain, self::CHARSET_UTF8, $this->CharSet); + //Ignore IDE complaints about this line - method signature changed in PHP 5.4 + $errorcode = 0; + if (defined('INTL_IDNA_VARIANT_UTS46')) { + //Use the current punycode standard (appeared in PHP 7.2) + $punycode = idn_to_ascii($domain, $errorcode, \INTL_IDNA_VARIANT_UTS46); + } elseif (defined('INTL_IDNA_VARIANT_2003')) { + //Fall back to this old, deprecated/removed encoding + $punycode = idn_to_ascii($domain, $errorcode, \INTL_IDNA_VARIANT_2003); + } else { + //Fall back to a default we don't know about + $punycode = idn_to_ascii($domain, $errorcode); + } + if (false !== $punycode) { + return substr($address, 0, $pos) . $punycode; + } + } + } + + return $address; + } + + /** + * Create a message and send it. + * Uses the sending method specified by $Mailer. + * + * @throws Exception + * + * @return bool false on error - See the ErrorInfo property for details of the error + */ + public function send() + { + try { + if (!$this->preSend()) { + return false; + } + + return $this->postSend(); + } catch (Exception $exc) { + $this->mailHeader = ''; + $this->setError($exc->getMessage()); + if ($this->exceptions) { + throw $exc; + } + + return false; + } + } + + /** + * Prepare a message for sending. + * + * @throws Exception + * + * @return bool + */ + public function preSend() + { + if ( + 'smtp' === $this->Mailer + || ('mail' === $this->Mailer && (\PHP_VERSION_ID >= 80000 || stripos(PHP_OS, 'WIN') === 0)) + ) { + //SMTP mandates RFC-compliant line endings + //and it's also used with mail() on Windows + static::setLE(self::CRLF); + } else { + //Maintain backward compatibility with legacy Linux command line mailers + static::setLE(PHP_EOL); + } + //Check for buggy PHP versions that add a header with an incorrect line break + if ( + 'mail' === $this->Mailer + && ((\PHP_VERSION_ID >= 70000 && \PHP_VERSION_ID < 70017) + || (\PHP_VERSION_ID >= 70100 && \PHP_VERSION_ID < 70103)) + && ini_get('mail.add_x_header') === '1' + && stripos(PHP_OS, 'WIN') === 0 + ) { + trigger_error( + 'Your version of PHP is affected by a bug that may result in corrupted messages.' . + ' To fix it, switch to sending using SMTP, disable the mail.add_x_header option in' . + ' your php.ini, switch to MacOS or Linux, or upgrade your PHP to version 7.0.17+ or 7.1.3+.', + E_USER_WARNING + ); + } + + try { + $this->error_count = 0; //Reset errors + $this->mailHeader = ''; + + //Dequeue recipient and Reply-To addresses with IDN + foreach (array_merge($this->RecipientsQueue, $this->ReplyToQueue) as $params) { + $params[1] = $this->punyencodeAddress($params[1]); + call_user_func_array([$this, 'addAnAddress'], $params); + } + if (count($this->to) + count($this->cc) + count($this->bcc) < 1) { + throw new Exception($this->lang('provide_address'), self::STOP_CRITICAL); + } + + //Validate From, Sender, and ConfirmReadingTo addresses + foreach (['From', 'Sender', 'ConfirmReadingTo'] as $address_kind) { + $this->$address_kind = trim($this->$address_kind); + if (empty($this->$address_kind)) { + continue; + } + $this->$address_kind = $this->punyencodeAddress($this->$address_kind); + if (!static::validateAddress($this->$address_kind)) { + $error_message = sprintf( + '%s (%s): %s', + $this->lang('invalid_address'), + $address_kind, + $this->$address_kind + ); + $this->setError($error_message); + $this->edebug($error_message); + if ($this->exceptions) { + throw new Exception($error_message); + } + + return false; + } + } + + //Set whether the message is multipart/alternative + if ($this->alternativeExists()) { + $this->ContentType = static::CONTENT_TYPE_MULTIPART_ALTERNATIVE; + } + + $this->setMessageType(); + //Refuse to send an empty message unless we are specifically allowing it + if (!$this->AllowEmpty && empty($this->Body)) { + throw new Exception($this->lang('empty_message'), self::STOP_CRITICAL); + } + + //Trim subject consistently + $this->Subject = trim($this->Subject); + //Create body before headers in case body makes changes to headers (e.g. altering transfer encoding) + $this->MIMEHeader = ''; + $this->MIMEBody = $this->createBody(); + //createBody may have added some headers, so retain them + $tempheaders = $this->MIMEHeader; + $this->MIMEHeader = $this->createHeader(); + $this->MIMEHeader .= $tempheaders; + + //To capture the complete message when using mail(), create + //an extra header list which createHeader() doesn't fold in + if ('mail' === $this->Mailer) { + if (count($this->to) > 0) { + $this->mailHeader .= $this->addrAppend('To', $this->to); + } else { + $this->mailHeader .= $this->headerLine('To', 'undisclosed-recipients:;'); + } + $this->mailHeader .= $this->headerLine( + 'Subject', + $this->encodeHeader($this->secureHeader($this->Subject)) + ); + } + + //Sign with DKIM if enabled + if ( + !empty($this->DKIM_domain) + && !empty($this->DKIM_selector) + && (!empty($this->DKIM_private_string) + || (!empty($this->DKIM_private) + && static::isPermittedPath($this->DKIM_private) + && file_exists($this->DKIM_private) + ) + ) + ) { + $header_dkim = $this->DKIM_Add( + $this->MIMEHeader . $this->mailHeader, + $this->encodeHeader($this->secureHeader($this->Subject)), + $this->MIMEBody + ); + $this->MIMEHeader = static::stripTrailingWSP($this->MIMEHeader) . static::$LE . + static::normalizeBreaks($header_dkim) . static::$LE; + } + + return true; + } catch (Exception $exc) { + $this->setError($exc->getMessage()); + if ($this->exceptions) { + throw $exc; + } + + return false; + } + } + + /** + * Actually send a message via the selected mechanism. + * + * @throws Exception + * + * @return bool + */ + public function postSend() + { + try { + //Choose the mailer and send through it + switch ($this->Mailer) { + case 'sendmail': + case 'qmail': + return $this->sendmailSend($this->MIMEHeader, $this->MIMEBody); + case 'smtp': + return $this->smtpSend($this->MIMEHeader, $this->MIMEBody); + case 'mail': + return $this->mailSend($this->MIMEHeader, $this->MIMEBody); + default: + $sendMethod = $this->Mailer . 'Send'; + if (method_exists($this, $sendMethod)) { + return $this->$sendMethod($this->MIMEHeader, $this->MIMEBody); + } + + return $this->mailSend($this->MIMEHeader, $this->MIMEBody); + } + } catch (Exception $exc) { + if ($this->Mailer === 'smtp' && $this->SMTPKeepAlive == true) { + $this->smtp->reset(); + } + $this->setError($exc->getMessage()); + $this->edebug($exc->getMessage()); + if ($this->exceptions) { + throw $exc; + } + } + + return false; + } + + /** + * Send mail using the $Sendmail program. + * + * @see PHPMailer::$Sendmail + * + * @param string $header The message headers + * @param string $body The message body + * + * @throws Exception + * + * @return bool + */ + protected function sendmailSend($header, $body) + { + if ($this->Mailer === 'qmail') { + $this->edebug('Sending with qmail'); + } else { + $this->edebug('Sending with sendmail'); + } + $header = static::stripTrailingWSP($header) . static::$LE . static::$LE; + //This sets the SMTP envelope sender which gets turned into a return-path header by the receiver + //A space after `-f` is optional, but there is a long history of its presence + //causing problems, so we don't use one + //Exim docs: http://www.exim.org/exim-html-current/doc/html/spec_html/ch-the_exim_command_line.html + //Sendmail docs: http://www.sendmail.org/~ca/email/man/sendmail.html + //Qmail docs: http://www.qmail.org/man/man8/qmail-inject.html + //Example problem: https://www.drupal.org/node/1057954 + if (empty($this->Sender) && !empty(ini_get('sendmail_from'))) { + //PHP config has a sender address we can use + $this->Sender = ini_get('sendmail_from'); + } + //CVE-2016-10033, CVE-2016-10045: Don't pass -f if characters will be escaped. + if (!empty($this->Sender) && static::validateAddress($this->Sender) && self::isShellSafe($this->Sender)) { + if ($this->Mailer === 'qmail') { + $sendmailFmt = '%s -f%s'; + } else { + $sendmailFmt = '%s -oi -f%s -t'; + } + } else { + //allow sendmail to choose a default envelope sender. It may + //seem preferable to force it to use the From header as with + //SMTP, but that introduces new problems (see + //), and + //it has historically worked this way. + $sendmailFmt = '%s -oi -t'; + } + + $sendmail = sprintf($sendmailFmt, escapeshellcmd($this->Sendmail), $this->Sender); + $this->edebug('Sendmail path: ' . $this->Sendmail); + $this->edebug('Sendmail command: ' . $sendmail); + $this->edebug('Envelope sender: ' . $this->Sender); + $this->edebug("Headers: {$header}"); + + if ($this->SingleTo) { + foreach ($this->SingleToArray as $toAddr) { + $mail = @popen($sendmail, 'w'); + if (!$mail) { + throw new Exception($this->lang('execute') . $this->Sendmail, self::STOP_CRITICAL); + } + $this->edebug("To: {$toAddr}"); + fwrite($mail, 'To: ' . $toAddr . "\n"); + fwrite($mail, $header); + fwrite($mail, $body); + $result = pclose($mail); + $addrinfo = static::parseAddresses($toAddr); + $this->doCallback( + ($result === 0), + [[$addrinfo['address'], $addrinfo['name']]], + $this->cc, + $this->bcc, + $this->Subject, + $body, + $this->From, + [] + ); + $this->edebug("Result: " . ($result === 0 ? 'true' : 'false')); + if (0 !== $result) { + throw new Exception($this->lang('execute') . $this->Sendmail, self::STOP_CRITICAL); + } + } + } else { + $mail = @popen($sendmail, 'w'); + if (!$mail) { + throw new Exception($this->lang('execute') . $this->Sendmail, self::STOP_CRITICAL); + } + fwrite($mail, $header); + fwrite($mail, $body); + $result = pclose($mail); + $this->doCallback( + ($result === 0), + $this->to, + $this->cc, + $this->bcc, + $this->Subject, + $body, + $this->From, + [] + ); + $this->edebug("Result: " . ($result === 0 ? 'true' : 'false')); + if (0 !== $result) { + throw new Exception($this->lang('execute') . $this->Sendmail, self::STOP_CRITICAL); + } + } + + return true; + } + + /** + * Fix CVE-2016-10033 and CVE-2016-10045 by disallowing potentially unsafe shell characters. + * Note that escapeshellarg and escapeshellcmd are inadequate for our purposes, especially on Windows. + * + * @see https://github.com/PHPMailer/PHPMailer/issues/924 CVE-2016-10045 bug report + * + * @param string $string The string to be validated + * + * @return bool + */ + protected static function isShellSafe($string) + { + //Future-proof + if ( + escapeshellcmd($string) !== $string + || !in_array(escapeshellarg($string), ["'$string'", "\"$string\""]) + ) { + return false; + } + + $length = strlen($string); + + for ($i = 0; $i < $length; ++$i) { + $c = $string[$i]; + + //All other characters have a special meaning in at least one common shell, including = and +. + //Full stop (.) has a special meaning in cmd.exe, but its impact should be negligible here. + //Note that this does permit non-Latin alphanumeric characters based on the current locale. + if (!ctype_alnum($c) && strpos('@_-.', $c) === false) { + return false; + } + } + + return true; + } + + /** + * Check whether a file path is of a permitted type. + * Used to reject URLs and phar files from functions that access local file paths, + * such as addAttachment. + * + * @param string $path A relative or absolute path to a file + * + * @return bool + */ + protected static function isPermittedPath($path) + { + //Matches scheme definition from https://tools.ietf.org/html/rfc3986#section-3.1 + return !preg_match('#^[a-z][a-z\d+.-]*://#i', $path); + } + + /** + * Check whether a file path is safe, accessible, and readable. + * + * @param string $path A relative or absolute path to a file + * + * @return bool + */ + protected static function fileIsAccessible($path) + { + if (!static::isPermittedPath($path)) { + return false; + } + $readable = file_exists($path); + //If not a UNC path (expected to start with \\), check read permission, see #2069 + if (strpos($path, '\\\\') !== 0) { + $readable = $readable && is_readable($path); + } + return $readable; + } + + /** + * Send mail using the PHP mail() function. + * + * @see http://www.php.net/manual/en/book.mail.php + * + * @param string $header The message headers + * @param string $body The message body + * + * @throws Exception + * + * @return bool + */ + protected function mailSend($header, $body) + { + $header = static::stripTrailingWSP($header) . static::$LE . static::$LE; + + $toArr = []; + foreach ($this->to as $toaddr) { + $toArr[] = $this->addrFormat($toaddr); + } + $to = implode(', ', $toArr); + + $params = null; + //This sets the SMTP envelope sender which gets turned into a return-path header by the receiver + //A space after `-f` is optional, but there is a long history of its presence + //causing problems, so we don't use one + //Exim docs: http://www.exim.org/exim-html-current/doc/html/spec_html/ch-the_exim_command_line.html + //Sendmail docs: http://www.sendmail.org/~ca/email/man/sendmail.html + //Qmail docs: http://www.qmail.org/man/man8/qmail-inject.html + //Example problem: https://www.drupal.org/node/1057954 + //CVE-2016-10033, CVE-2016-10045: Don't pass -f if characters will be escaped. + if (empty($this->Sender) && !empty(ini_get('sendmail_from'))) { + //PHP config has a sender address we can use + $this->Sender = ini_get('sendmail_from'); + } + if (!empty($this->Sender) && static::validateAddress($this->Sender)) { + if (self::isShellSafe($this->Sender)) { + $params = sprintf('-f%s', $this->Sender); + } + $old_from = ini_get('sendmail_from'); + ini_set('sendmail_from', $this->Sender); + } + $result = false; + if ($this->SingleTo && count($toArr) > 1) { + foreach ($toArr as $toAddr) { + $result = $this->mailPassthru($toAddr, $this->Subject, $body, $header, $params); + $addrinfo = static::parseAddresses($toAddr); + $this->doCallback( + $result, + [[$addrinfo['address'], $addrinfo['name']]], + $this->cc, + $this->bcc, + $this->Subject, + $body, + $this->From, + [] + ); + } + } else { + $result = $this->mailPassthru($to, $this->Subject, $body, $header, $params); + $this->doCallback($result, $this->to, $this->cc, $this->bcc, $this->Subject, $body, $this->From, []); + } + if (isset($old_from)) { + ini_set('sendmail_from', $old_from); + } + if (!$result) { + throw new Exception($this->lang('instantiate'), self::STOP_CRITICAL); + } + + return true; + } + + /** + * Get an instance to use for SMTP operations. + * Override this function to load your own SMTP implementation, + * or set one with setSMTPInstance. + * + * @return SMTP + */ + public function getSMTPInstance() + { + if (!is_object($this->smtp)) { + $this->smtp = new SMTP(); + } + + return $this->smtp; + } + + /** + * Provide an instance to use for SMTP operations. + * + * @return SMTP + */ + public function setSMTPInstance(SMTP $smtp) + { + $this->smtp = $smtp; + + return $this->smtp; + } + + /** + * Send mail via SMTP. + * Returns false if there is a bad MAIL FROM, RCPT, or DATA input. + * + * @see PHPMailer::setSMTPInstance() to use a different class. + * + * @uses \PHPMailer\PHPMailer\SMTP + * + * @param string $header The message headers + * @param string $body The message body + * + * @throws Exception + * + * @return bool + */ + protected function smtpSend($header, $body) + { + $header = static::stripTrailingWSP($header) . static::$LE . static::$LE; + $bad_rcpt = []; + if (!$this->smtpConnect($this->SMTPOptions)) { + throw new Exception($this->lang('smtp_connect_failed'), self::STOP_CRITICAL); + } + //Sender already validated in preSend() + if ('' === $this->Sender) { + $smtp_from = $this->From; + } else { + $smtp_from = $this->Sender; + } + if (!$this->smtp->mail($smtp_from)) { + $this->setError($this->lang('from_failed') . $smtp_from . ' : ' . implode(',', $this->smtp->getError())); + throw new Exception($this->ErrorInfo, self::STOP_CRITICAL); + } + + $callbacks = []; + //Attempt to send to all recipients + foreach ([$this->to, $this->cc, $this->bcc] as $togroup) { + foreach ($togroup as $to) { + if (!$this->smtp->recipient($to[0], $this->dsn)) { + $error = $this->smtp->getError(); + $bad_rcpt[] = ['to' => $to[0], 'error' => $error['detail']]; + $isSent = false; + } else { + $isSent = true; + } + + $callbacks[] = ['issent' => $isSent, 'to' => $to[0], 'name' => $to[1]]; + } + } + + //Only send the DATA command if we have viable recipients + if ((count($this->all_recipients) > count($bad_rcpt)) && !$this->smtp->data($header . $body)) { + throw new Exception($this->lang('data_not_accepted'), self::STOP_CRITICAL); + } + + $smtp_transaction_id = $this->smtp->getLastTransactionID(); + + if ($this->SMTPKeepAlive) { + $this->smtp->reset(); + } else { + $this->smtp->quit(); + $this->smtp->close(); + } + + foreach ($callbacks as $cb) { + $this->doCallback( + $cb['issent'], + [[$cb['to'], $cb['name']]], + [], + [], + $this->Subject, + $body, + $this->From, + ['smtp_transaction_id' => $smtp_transaction_id] + ); + } + + //Create error message for any bad addresses + if (count($bad_rcpt) > 0) { + $errstr = ''; + foreach ($bad_rcpt as $bad) { + $errstr .= $bad['to'] . ': ' . $bad['error']; + } + throw new Exception($this->lang('recipients_failed') . $errstr, self::STOP_CONTINUE); + } + + return true; + } + + /** + * Initiate a connection to an SMTP server. + * Returns false if the operation failed. + * + * @param array $options An array of options compatible with stream_context_create() + * + * @throws Exception + * + * @uses \PHPMailer\PHPMailer\SMTP + * + * @return bool + */ + public function smtpConnect($options = null) + { + if (null === $this->smtp) { + $this->smtp = $this->getSMTPInstance(); + } + + //If no options are provided, use whatever is set in the instance + if (null === $options) { + $options = $this->SMTPOptions; + } + + //Already connected? + if ($this->smtp->connected()) { + return true; + } + + $this->smtp->setTimeout($this->Timeout); + $this->smtp->setDebugLevel($this->SMTPDebug); + $this->smtp->setDebugOutput($this->Debugoutput); + $this->smtp->setVerp($this->do_verp); + $hosts = explode(';', $this->Host); + $lastexception = null; + + foreach ($hosts as $hostentry) { + $hostinfo = []; + if ( + !preg_match( + '/^(?:(ssl|tls):\/\/)?(.+?)(?::(\d+))?$/', + trim($hostentry), + $hostinfo + ) + ) { + $this->edebug($this->lang('invalid_hostentry') . ' ' . trim($hostentry)); + //Not a valid host entry + continue; + } + //$hostinfo[1]: optional ssl or tls prefix + //$hostinfo[2]: the hostname + //$hostinfo[3]: optional port number + //The host string prefix can temporarily override the current setting for SMTPSecure + //If it's not specified, the default value is used + + //Check the host name is a valid name or IP address before trying to use it + if (!static::isValidHost($hostinfo[2])) { + $this->edebug($this->lang('invalid_host') . ' ' . $hostinfo[2]); + continue; + } + $prefix = ''; + $secure = $this->SMTPSecure; + $tls = (static::ENCRYPTION_STARTTLS === $this->SMTPSecure); + if ('ssl' === $hostinfo[1] || ('' === $hostinfo[1] && static::ENCRYPTION_SMTPS === $this->SMTPSecure)) { + $prefix = 'ssl://'; + $tls = false; //Can't have SSL and TLS at the same time + $secure = static::ENCRYPTION_SMTPS; + } elseif ('tls' === $hostinfo[1]) { + $tls = true; + //TLS doesn't use a prefix + $secure = static::ENCRYPTION_STARTTLS; + } + //Do we need the OpenSSL extension? + $sslext = defined('OPENSSL_ALGO_SHA256'); + if (static::ENCRYPTION_STARTTLS === $secure || static::ENCRYPTION_SMTPS === $secure) { + //Check for an OpenSSL constant rather than using extension_loaded, which is sometimes disabled + if (!$sslext) { + throw new Exception($this->lang('extension_missing') . 'openssl', self::STOP_CRITICAL); + } + } + $host = $hostinfo[2]; + $port = $this->Port; + if ( + array_key_exists(3, $hostinfo) && + is_numeric($hostinfo[3]) && + $hostinfo[3] > 0 && + $hostinfo[3] < 65536 + ) { + $port = (int) $hostinfo[3]; + } + if ($this->smtp->connect($prefix . $host, $port, $this->Timeout, $options)) { + try { + if ($this->Helo) { + $hello = $this->Helo; + } else { + $hello = $this->serverHostname(); + } + $this->smtp->hello($hello); + //Automatically enable TLS encryption if: + //* it's not disabled + //* we have openssl extension + //* we are not already using SSL + //* the server offers STARTTLS + if ($this->SMTPAutoTLS && $sslext && 'ssl' !== $secure && $this->smtp->getServerExt('STARTTLS')) { + $tls = true; + } + if ($tls) { + if (!$this->smtp->startTLS()) { + throw new Exception($this->lang('connect_host')); + } + //We must resend EHLO after TLS negotiation + $this->smtp->hello($hello); + } + if ( + $this->SMTPAuth && !$this->smtp->authenticate( + $this->Username, + $this->Password, + $this->AuthType, + $this->oauth + ) + ) { + throw new Exception($this->lang('authenticate')); + } + + return true; + } catch (Exception $exc) { + $lastexception = $exc; + $this->edebug($exc->getMessage()); + //We must have connected, but then failed TLS or Auth, so close connection nicely + $this->smtp->quit(); + } + } + } + //If we get here, all connection attempts have failed, so close connection hard + $this->smtp->close(); + //As we've caught all exceptions, just report whatever the last one was + if ($this->exceptions && null !== $lastexception) { + throw $lastexception; + } + + return false; + } + + /** + * Close the active SMTP session if one exists. + */ + public function smtpClose() + { + if ((null !== $this->smtp) && $this->smtp->connected()) { + $this->smtp->quit(); + $this->smtp->close(); + } + } + + /** + * Set the language for error messages. + * Returns false if it cannot load the language file. + * The default language is English. + * + * @param string $langcode ISO 639-1 2-character language code (e.g. French is "fr") + * @param string $lang_path Path to the language file directory, with trailing separator (slash).D + * Do not set this from user input! + * + * @return bool + */ + public function setLanguage($langcode = 'en', $lang_path = '') + { + //Backwards compatibility for renamed language codes + $renamed_langcodes = [ + 'br' => 'pt_br', + 'cz' => 'cs', + 'dk' => 'da', + 'no' => 'nb', + 'se' => 'sv', + 'rs' => 'sr', + 'tg' => 'tl', + 'am' => 'hy', + ]; + + if (array_key_exists($langcode, $renamed_langcodes)) { + $langcode = $renamed_langcodes[$langcode]; + } + + //Define full set of translatable strings in English + $PHPMAILER_LANG = [ + 'authenticate' => 'SMTP Error: Could not authenticate.', + 'connect_host' => 'SMTP Error: Could not connect to SMTP host.', + 'data_not_accepted' => 'SMTP Error: data not accepted.', + 'empty_message' => 'Message body empty', + 'encoding' => 'Unknown encoding: ', + 'execute' => 'Could not execute: ', + 'file_access' => 'Could not access file: ', + 'file_open' => 'File Error: Could not open file: ', + 'from_failed' => 'The following From address failed: ', + 'instantiate' => 'Could not instantiate mail function.', + 'invalid_address' => 'Invalid address: ', + 'invalid_hostentry' => 'Invalid hostentry: ', + 'invalid_host' => 'Invalid host: ', + 'mailer_not_supported' => ' mailer is not supported.', + 'provide_address' => 'You must provide at least one recipient email address.', + 'recipients_failed' => 'SMTP Error: The following recipients failed: ', + 'signing' => 'Signing Error: ', + 'smtp_connect_failed' => 'SMTP connect() failed.', + 'smtp_error' => 'SMTP server error: ', + 'variable_set' => 'Cannot set or reset variable: ', + 'extension_missing' => 'Extension missing: ', + ]; + if (empty($lang_path)) { + //Calculate an absolute path so it can work if CWD is not here + $lang_path = dirname(__DIR__) . DIRECTORY_SEPARATOR . 'language' . DIRECTORY_SEPARATOR; + } + //Validate $langcode + if (!preg_match('/^[a-z]{2}(?:_[a-zA-Z]{2})?$/', $langcode)) { + $langcode = 'en'; + } + $foundlang = true; + $lang_file = $lang_path . 'phpmailer.lang-' . $langcode . '.php'; + //There is no English translation file + if ('en' !== $langcode) { + //Make sure language file path is readable + if (!static::fileIsAccessible($lang_file)) { + $foundlang = false; + } else { + //$foundlang = include $lang_file; + $lines = file($lang_file); + foreach ($lines as $line) { + //Translation file lines look like this: + //$PHPMAILER_LANG['authenticate'] = 'SMTP-Fehler: Authentifizierung fehlgeschlagen.'; + //These files are parsed as text and not PHP so as to avoid the possibility of code injection + //See https://blog.stevenlevithan.com/archives/match-quoted-string + $matches = []; + if ( + preg_match( + '/^\$PHPMAILER_LANG\[\'([a-z\d_]+)\'\]\s*=\s*(["\'])(.+)*?\2;/', + $line, + $matches + ) && + //Ignore unknown translation keys + array_key_exists($matches[1], $PHPMAILER_LANG) + ) { + //Overwrite language-specific strings so we'll never have missing translation keys. + $PHPMAILER_LANG[$matches[1]] = (string)$matches[3]; + } + } + } + } + $this->language = $PHPMAILER_LANG; + + return $foundlang; //Returns false if language not found + } + + /** + * Get the array of strings for the current language. + * + * @return array + */ + public function getTranslations() + { + return $this->language; + } + + /** + * Create recipient headers. + * + * @param string $type + * @param array $addr An array of recipients, + * where each recipient is a 2-element indexed array with element 0 containing an address + * and element 1 containing a name, like: + * [['joe@example.com', 'Joe User'], ['zoe@example.com', 'Zoe User']] + * + * @return string + */ + public function addrAppend($type, $addr) + { + $addresses = []; + foreach ($addr as $address) { + $addresses[] = $this->addrFormat($address); + } + + return $type . ': ' . implode(', ', $addresses) . static::$LE; + } + + /** + * Format an address for use in a message header. + * + * @param array $addr A 2-element indexed array, element 0 containing an address, element 1 containing a name like + * ['joe@example.com', 'Joe User'] + * + * @return string + */ + public function addrFormat($addr) + { + if (empty($addr[1])) { //No name provided + return $this->secureHeader($addr[0]); + } + + return $this->encodeHeader($this->secureHeader($addr[1]), 'phrase') . + ' <' . $this->secureHeader($addr[0]) . '>'; + } + + /** + * Word-wrap message. + * For use with mailers that do not automatically perform wrapping + * and for quoted-printable encoded messages. + * Original written by philippe. + * + * @param string $message The message to wrap + * @param int $length The line length to wrap to + * @param bool $qp_mode Whether to run in Quoted-Printable mode + * + * @return string + */ + public function wrapText($message, $length, $qp_mode = false) + { + if ($qp_mode) { + $soft_break = sprintf(' =%s', static::$LE); + } else { + $soft_break = static::$LE; + } + //If utf-8 encoding is used, we will need to make sure we don't + //split multibyte characters when we wrap + $is_utf8 = static::CHARSET_UTF8 === strtolower($this->CharSet); + $lelen = strlen(static::$LE); + $crlflen = strlen(static::$LE); + + $message = static::normalizeBreaks($message); + //Remove a trailing line break + if (substr($message, -$lelen) === static::$LE) { + $message = substr($message, 0, -$lelen); + } + + //Split message into lines + $lines = explode(static::$LE, $message); + //Message will be rebuilt in here + $message = ''; + foreach ($lines as $line) { + $words = explode(' ', $line); + $buf = ''; + $firstword = true; + foreach ($words as $word) { + if ($qp_mode && (strlen($word) > $length)) { + $space_left = $length - strlen($buf) - $crlflen; + if (!$firstword) { + if ($space_left > 20) { + $len = $space_left; + if ($is_utf8) { + $len = $this->utf8CharBoundary($word, $len); + } elseif ('=' === substr($word, $len - 1, 1)) { + --$len; + } elseif ('=' === substr($word, $len - 2, 1)) { + $len -= 2; + } + $part = substr($word, 0, $len); + $word = substr($word, $len); + $buf .= ' ' . $part; + $message .= $buf . sprintf('=%s', static::$LE); + } else { + $message .= $buf . $soft_break; + } + $buf = ''; + } + while ($word !== '') { + if ($length <= 0) { + break; + } + $len = $length; + if ($is_utf8) { + $len = $this->utf8CharBoundary($word, $len); + } elseif ('=' === substr($word, $len - 1, 1)) { + --$len; + } elseif ('=' === substr($word, $len - 2, 1)) { + $len -= 2; + } + $part = substr($word, 0, $len); + $word = (string) substr($word, $len); + + if ($word !== '') { + $message .= $part . sprintf('=%s', static::$LE); + } else { + $buf = $part; + } + } + } else { + $buf_o = $buf; + if (!$firstword) { + $buf .= ' '; + } + $buf .= $word; + + if ('' !== $buf_o && strlen($buf) > $length) { + $message .= $buf_o . $soft_break; + $buf = $word; + } + } + $firstword = false; + } + $message .= $buf . static::$LE; + } + + return $message; + } + + /** + * Find the last character boundary prior to $maxLength in a utf-8 + * quoted-printable encoded string. + * Original written by Colin Brown. + * + * @param string $encodedText utf-8 QP text + * @param int $maxLength Find the last character boundary prior to this length + * + * @return int + */ + public function utf8CharBoundary($encodedText, $maxLength) + { + $foundSplitPos = false; + $lookBack = 3; + while (!$foundSplitPos) { + $lastChunk = substr($encodedText, $maxLength - $lookBack, $lookBack); + $encodedCharPos = strpos($lastChunk, '='); + if (false !== $encodedCharPos) { + //Found start of encoded character byte within $lookBack block. + //Check the encoded byte value (the 2 chars after the '=') + $hex = substr($encodedText, $maxLength - $lookBack + $encodedCharPos + 1, 2); + $dec = hexdec($hex); + if ($dec < 128) { + //Single byte character. + //If the encoded char was found at pos 0, it will fit + //otherwise reduce maxLength to start of the encoded char + if ($encodedCharPos > 0) { + $maxLength -= $lookBack - $encodedCharPos; + } + $foundSplitPos = true; + } elseif ($dec >= 192) { + //First byte of a multi byte character + //Reduce maxLength to split at start of character + $maxLength -= $lookBack - $encodedCharPos; + $foundSplitPos = true; + } elseif ($dec < 192) { + //Middle byte of a multi byte character, look further back + $lookBack += 3; + } + } else { + //No encoded character found + $foundSplitPos = true; + } + } + + return $maxLength; + } + + /** + * Apply word wrapping to the message body. + * Wraps the message body to the number of chars set in the WordWrap property. + * You should only do this to plain-text bodies as wrapping HTML tags may break them. + * This is called automatically by createBody(), so you don't need to call it yourself. + */ + public function setWordWrap() + { + if ($this->WordWrap < 1) { + return; + } + + switch ($this->message_type) { + case 'alt': + case 'alt_inline': + case 'alt_attach': + case 'alt_inline_attach': + $this->AltBody = $this->wrapText($this->AltBody, $this->WordWrap); + break; + default: + $this->Body = $this->wrapText($this->Body, $this->WordWrap); + break; + } + } + + /** + * Assemble message headers. + * + * @return string The assembled headers + */ + public function createHeader() + { + $result = ''; + + $result .= $this->headerLine('Date', '' === $this->MessageDate ? self::rfcDate() : $this->MessageDate); + + //The To header is created automatically by mail(), so needs to be omitted here + if ('mail' !== $this->Mailer) { + if ($this->SingleTo) { + foreach ($this->to as $toaddr) { + $this->SingleToArray[] = $this->addrFormat($toaddr); + } + } elseif (count($this->to) > 0) { + $result .= $this->addrAppend('To', $this->to); + } elseif (count($this->cc) === 0) { + $result .= $this->headerLine('To', 'undisclosed-recipients:;'); + } + } + $result .= $this->addrAppend('From', [[trim($this->From), $this->FromName]]); + + //sendmail and mail() extract Cc from the header before sending + if (count($this->cc) > 0) { + $result .= $this->addrAppend('Cc', $this->cc); + } + + //sendmail and mail() extract Bcc from the header before sending + if ( + ( + 'sendmail' === $this->Mailer || 'qmail' === $this->Mailer || 'mail' === $this->Mailer + ) + && count($this->bcc) > 0 + ) { + $result .= $this->addrAppend('Bcc', $this->bcc); + } + + if (count($this->ReplyTo) > 0) { + $result .= $this->addrAppend('Reply-To', $this->ReplyTo); + } + + //mail() sets the subject itself + if ('mail' !== $this->Mailer) { + $result .= $this->headerLine('Subject', $this->encodeHeader($this->secureHeader($this->Subject))); + } + + //Only allow a custom message ID if it conforms to RFC 5322 section 3.6.4 + //https://tools.ietf.org/html/rfc5322#section-3.6.4 + if ('' !== $this->MessageID && preg_match('/^<.*@.*>$/', $this->MessageID)) { + $this->lastMessageID = $this->MessageID; + } else { + $this->lastMessageID = sprintf('<%s@%s>', $this->uniqueid, $this->serverHostname()); + } + $result .= $this->headerLine('Message-ID', $this->lastMessageID); + if (null !== $this->Priority) { + $result .= $this->headerLine('X-Priority', $this->Priority); + } + if ('' === $this->XMailer) { + $result .= $this->headerLine( + 'X-Mailer', + 'PHPMailer ' . self::VERSION . ' (https://github.com/PHPMailer/PHPMailer)' + ); + } else { + $myXmailer = trim($this->XMailer); + if ($myXmailer) { + $result .= $this->headerLine('X-Mailer', $myXmailer); + } + } + + if ('' !== $this->ConfirmReadingTo) { + $result .= $this->headerLine('Disposition-Notification-To', '<' . $this->ConfirmReadingTo . '>'); + } + + //Add custom headers + foreach ($this->CustomHeader as $header) { + $result .= $this->headerLine( + trim($header[0]), + $this->encodeHeader(trim($header[1])) + ); + } + if (!$this->sign_key_file) { + $result .= $this->headerLine('MIME-Version', '1.0'); + $result .= $this->getMailMIME(); + } + + return $result; + } + + /** + * Get the message MIME type headers. + * + * @return string + */ + public function getMailMIME() + { + $result = ''; + $ismultipart = true; + switch ($this->message_type) { + case 'inline': + $result .= $this->headerLine('Content-Type', static::CONTENT_TYPE_MULTIPART_RELATED . ';'); + $result .= $this->textLine(' boundary="' . $this->boundary[1] . '"'); + break; + case 'attach': + case 'inline_attach': + case 'alt_attach': + case 'alt_inline_attach': + $result .= $this->headerLine('Content-Type', static::CONTENT_TYPE_MULTIPART_MIXED . ';'); + $result .= $this->textLine(' boundary="' . $this->boundary[1] . '"'); + break; + case 'alt': + case 'alt_inline': + $result .= $this->headerLine('Content-Type', static::CONTENT_TYPE_MULTIPART_ALTERNATIVE . ';'); + $result .= $this->textLine(' boundary="' . $this->boundary[1] . '"'); + break; + default: + //Catches case 'plain': and case '': + $result .= $this->textLine('Content-Type: ' . $this->ContentType . '; charset=' . $this->CharSet); + $ismultipart = false; + break; + } + //RFC1341 part 5 says 7bit is assumed if not specified + if (static::ENCODING_7BIT !== $this->Encoding) { + //RFC 2045 section 6.4 says multipart MIME parts may only use 7bit, 8bit or binary CTE + if ($ismultipart) { + if (static::ENCODING_8BIT === $this->Encoding) { + $result .= $this->headerLine('Content-Transfer-Encoding', static::ENCODING_8BIT); + } + //The only remaining alternatives are quoted-printable and base64, which are both 7bit compatible + } else { + $result .= $this->headerLine('Content-Transfer-Encoding', $this->Encoding); + } + } + + return $result; + } + + /** + * Returns the whole MIME message. + * Includes complete headers and body. + * Only valid post preSend(). + * + * @see PHPMailer::preSend() + * + * @return string + */ + public function getSentMIMEMessage() + { + return static::stripTrailingWSP($this->MIMEHeader . $this->mailHeader) . + static::$LE . static::$LE . $this->MIMEBody; + } + + /** + * Create a unique ID to use for boundaries. + * + * @return string + */ + protected function generateId() + { + $len = 32; //32 bytes = 256 bits + $bytes = ''; + if (function_exists('random_bytes')) { + try { + $bytes = random_bytes($len); + } catch (\Exception $e) { + //Do nothing + } + } elseif (function_exists('openssl_random_pseudo_bytes')) { + /** @noinspection CryptographicallySecureRandomnessInspection */ + $bytes = openssl_random_pseudo_bytes($len); + } + if ($bytes === '') { + //We failed to produce a proper random string, so make do. + //Use a hash to force the length to the same as the other methods + $bytes = hash('sha256', uniqid((string) mt_rand(), true), true); + } + + //We don't care about messing up base64 format here, just want a random string + return str_replace(['=', '+', '/'], '', base64_encode(hash('sha256', $bytes, true))); + } + + /** + * Assemble the message body. + * Returns an empty string on failure. + * + * @throws Exception + * + * @return string The assembled message body + */ + public function createBody() + { + $body = ''; + //Create unique IDs and preset boundaries + $this->uniqueid = $this->generateId(); + $this->boundary[1] = 'b1_' . $this->uniqueid; + $this->boundary[2] = 'b2_' . $this->uniqueid; + $this->boundary[3] = 'b3_' . $this->uniqueid; + + if ($this->sign_key_file) { + $body .= $this->getMailMIME() . static::$LE; + } + + $this->setWordWrap(); + + $bodyEncoding = $this->Encoding; + $bodyCharSet = $this->CharSet; + //Can we do a 7-bit downgrade? + if (static::ENCODING_8BIT === $bodyEncoding && !$this->has8bitChars($this->Body)) { + $bodyEncoding = static::ENCODING_7BIT; + //All ISO 8859, Windows codepage and UTF-8 charsets are ascii compatible up to 7-bit + $bodyCharSet = static::CHARSET_ASCII; + } + //If lines are too long, and we're not already using an encoding that will shorten them, + //change to quoted-printable transfer encoding for the body part only + if (static::ENCODING_BASE64 !== $this->Encoding && static::hasLineLongerThanMax($this->Body)) { + $bodyEncoding = static::ENCODING_QUOTED_PRINTABLE; + } + + $altBodyEncoding = $this->Encoding; + $altBodyCharSet = $this->CharSet; + //Can we do a 7-bit downgrade? + if (static::ENCODING_8BIT === $altBodyEncoding && !$this->has8bitChars($this->AltBody)) { + $altBodyEncoding = static::ENCODING_7BIT; + //All ISO 8859, Windows codepage and UTF-8 charsets are ascii compatible up to 7-bit + $altBodyCharSet = static::CHARSET_ASCII; + } + //If lines are too long, and we're not already using an encoding that will shorten them, + //change to quoted-printable transfer encoding for the alt body part only + if (static::ENCODING_BASE64 !== $altBodyEncoding && static::hasLineLongerThanMax($this->AltBody)) { + $altBodyEncoding = static::ENCODING_QUOTED_PRINTABLE; + } + //Use this as a preamble in all multipart message types + $mimepre = 'This is a multi-part message in MIME format.' . static::$LE . static::$LE; + switch ($this->message_type) { + case 'inline': + $body .= $mimepre; + $body .= $this->getBoundary($this->boundary[1], $bodyCharSet, '', $bodyEncoding); + $body .= $this->encodeString($this->Body, $bodyEncoding); + $body .= static::$LE; + $body .= $this->attachAll('inline', $this->boundary[1]); + break; + case 'attach': + $body .= $mimepre; + $body .= $this->getBoundary($this->boundary[1], $bodyCharSet, '', $bodyEncoding); + $body .= $this->encodeString($this->Body, $bodyEncoding); + $body .= static::$LE; + $body .= $this->attachAll('attachment', $this->boundary[1]); + break; + case 'inline_attach': + $body .= $mimepre; + $body .= $this->textLine('--' . $this->boundary[1]); + $body .= $this->headerLine('Content-Type', static::CONTENT_TYPE_MULTIPART_RELATED . ';'); + $body .= $this->textLine(' boundary="' . $this->boundary[2] . '";'); + $body .= $this->textLine(' type="' . static::CONTENT_TYPE_TEXT_HTML . '"'); + $body .= static::$LE; + $body .= $this->getBoundary($this->boundary[2], $bodyCharSet, '', $bodyEncoding); + $body .= $this->encodeString($this->Body, $bodyEncoding); + $body .= static::$LE; + $body .= $this->attachAll('inline', $this->boundary[2]); + $body .= static::$LE; + $body .= $this->attachAll('attachment', $this->boundary[1]); + break; + case 'alt': + $body .= $mimepre; + $body .= $this->getBoundary( + $this->boundary[1], + $altBodyCharSet, + static::CONTENT_TYPE_PLAINTEXT, + $altBodyEncoding + ); + $body .= $this->encodeString($this->AltBody, $altBodyEncoding); + $body .= static::$LE; + $body .= $this->getBoundary( + $this->boundary[1], + $bodyCharSet, + static::CONTENT_TYPE_TEXT_HTML, + $bodyEncoding + ); + $body .= $this->encodeString($this->Body, $bodyEncoding); + $body .= static::$LE; + if (!empty($this->Ical)) { + $method = static::ICAL_METHOD_REQUEST; + foreach (static::$IcalMethods as $imethod) { + if (stripos($this->Ical, 'METHOD:' . $imethod) !== false) { + $method = $imethod; + break; + } + } + $body .= $this->getBoundary( + $this->boundary[1], + '', + static::CONTENT_TYPE_TEXT_CALENDAR . '; method=' . $method, + '' + ); + $body .= $this->encodeString($this->Ical, $this->Encoding); + $body .= static::$LE; + } + $body .= $this->endBoundary($this->boundary[1]); + break; + case 'alt_inline': + $body .= $mimepre; + $body .= $this->getBoundary( + $this->boundary[1], + $altBodyCharSet, + static::CONTENT_TYPE_PLAINTEXT, + $altBodyEncoding + ); + $body .= $this->encodeString($this->AltBody, $altBodyEncoding); + $body .= static::$LE; + $body .= $this->textLine('--' . $this->boundary[1]); + $body .= $this->headerLine('Content-Type', static::CONTENT_TYPE_MULTIPART_RELATED . ';'); + $body .= $this->textLine(' boundary="' . $this->boundary[2] . '";'); + $body .= $this->textLine(' type="' . static::CONTENT_TYPE_TEXT_HTML . '"'); + $body .= static::$LE; + $body .= $this->getBoundary( + $this->boundary[2], + $bodyCharSet, + static::CONTENT_TYPE_TEXT_HTML, + $bodyEncoding + ); + $body .= $this->encodeString($this->Body, $bodyEncoding); + $body .= static::$LE; + $body .= $this->attachAll('inline', $this->boundary[2]); + $body .= static::$LE; + $body .= $this->endBoundary($this->boundary[1]); + break; + case 'alt_attach': + $body .= $mimepre; + $body .= $this->textLine('--' . $this->boundary[1]); + $body .= $this->headerLine('Content-Type', static::CONTENT_TYPE_MULTIPART_ALTERNATIVE . ';'); + $body .= $this->textLine(' boundary="' . $this->boundary[2] . '"'); + $body .= static::$LE; + $body .= $this->getBoundary( + $this->boundary[2], + $altBodyCharSet, + static::CONTENT_TYPE_PLAINTEXT, + $altBodyEncoding + ); + $body .= $this->encodeString($this->AltBody, $altBodyEncoding); + $body .= static::$LE; + $body .= $this->getBoundary( + $this->boundary[2], + $bodyCharSet, + static::CONTENT_TYPE_TEXT_HTML, + $bodyEncoding + ); + $body .= $this->encodeString($this->Body, $bodyEncoding); + $body .= static::$LE; + if (!empty($this->Ical)) { + $method = static::ICAL_METHOD_REQUEST; + foreach (static::$IcalMethods as $imethod) { + if (stripos($this->Ical, 'METHOD:' . $imethod) !== false) { + $method = $imethod; + break; + } + } + $body .= $this->getBoundary( + $this->boundary[2], + '', + static::CONTENT_TYPE_TEXT_CALENDAR . '; method=' . $method, + '' + ); + $body .= $this->encodeString($this->Ical, $this->Encoding); + } + $body .= $this->endBoundary($this->boundary[2]); + $body .= static::$LE; + $body .= $this->attachAll('attachment', $this->boundary[1]); + break; + case 'alt_inline_attach': + $body .= $mimepre; + $body .= $this->textLine('--' . $this->boundary[1]); + $body .= $this->headerLine('Content-Type', static::CONTENT_TYPE_MULTIPART_ALTERNATIVE . ';'); + $body .= $this->textLine(' boundary="' . $this->boundary[2] . '"'); + $body .= static::$LE; + $body .= $this->getBoundary( + $this->boundary[2], + $altBodyCharSet, + static::CONTENT_TYPE_PLAINTEXT, + $altBodyEncoding + ); + $body .= $this->encodeString($this->AltBody, $altBodyEncoding); + $body .= static::$LE; + $body .= $this->textLine('--' . $this->boundary[2]); + $body .= $this->headerLine('Content-Type', static::CONTENT_TYPE_MULTIPART_RELATED . ';'); + $body .= $this->textLine(' boundary="' . $this->boundary[3] . '";'); + $body .= $this->textLine(' type="' . static::CONTENT_TYPE_TEXT_HTML . '"'); + $body .= static::$LE; + $body .= $this->getBoundary( + $this->boundary[3], + $bodyCharSet, + static::CONTENT_TYPE_TEXT_HTML, + $bodyEncoding + ); + $body .= $this->encodeString($this->Body, $bodyEncoding); + $body .= static::$LE; + $body .= $this->attachAll('inline', $this->boundary[3]); + $body .= static::$LE; + $body .= $this->endBoundary($this->boundary[2]); + $body .= static::$LE; + $body .= $this->attachAll('attachment', $this->boundary[1]); + break; + default: + //Catch case 'plain' and case '', applies to simple `text/plain` and `text/html` body content types + //Reset the `Encoding` property in case we changed it for line length reasons + $this->Encoding = $bodyEncoding; + $body .= $this->encodeString($this->Body, $this->Encoding); + break; + } + + if ($this->isError()) { + $body = ''; + if ($this->exceptions) { + throw new Exception($this->lang('empty_message'), self::STOP_CRITICAL); + } + } elseif ($this->sign_key_file) { + try { + if (!defined('PKCS7_TEXT')) { + throw new Exception($this->lang('extension_missing') . 'openssl'); + } + + $file = tempnam(sys_get_temp_dir(), 'srcsign'); + $signed = tempnam(sys_get_temp_dir(), 'mailsign'); + file_put_contents($file, $body); + + //Workaround for PHP bug https://bugs.php.net/bug.php?id=69197 + if (empty($this->sign_extracerts_file)) { + $sign = @openssl_pkcs7_sign( + $file, + $signed, + 'file://' . realpath($this->sign_cert_file), + ['file://' . realpath($this->sign_key_file), $this->sign_key_pass], + [] + ); + } else { + $sign = @openssl_pkcs7_sign( + $file, + $signed, + 'file://' . realpath($this->sign_cert_file), + ['file://' . realpath($this->sign_key_file), $this->sign_key_pass], + [], + PKCS7_DETACHED, + $this->sign_extracerts_file + ); + } + + @unlink($file); + if ($sign) { + $body = file_get_contents($signed); + @unlink($signed); + //The message returned by openssl contains both headers and body, so need to split them up + $parts = explode("\n\n", $body, 2); + $this->MIMEHeader .= $parts[0] . static::$LE . static::$LE; + $body = $parts[1]; + } else { + @unlink($signed); + throw new Exception($this->lang('signing') . openssl_error_string()); + } + } catch (Exception $exc) { + $body = ''; + if ($this->exceptions) { + throw $exc; + } + } + } + + return $body; + } + + /** + * Return the start of a message boundary. + * + * @param string $boundary + * @param string $charSet + * @param string $contentType + * @param string $encoding + * + * @return string + */ + protected function getBoundary($boundary, $charSet, $contentType, $encoding) + { + $result = ''; + if ('' === $charSet) { + $charSet = $this->CharSet; + } + if ('' === $contentType) { + $contentType = $this->ContentType; + } + if ('' === $encoding) { + $encoding = $this->Encoding; + } + $result .= $this->textLine('--' . $boundary); + $result .= sprintf('Content-Type: %s; charset=%s', $contentType, $charSet); + $result .= static::$LE; + //RFC1341 part 5 says 7bit is assumed if not specified + if (static::ENCODING_7BIT !== $encoding) { + $result .= $this->headerLine('Content-Transfer-Encoding', $encoding); + } + $result .= static::$LE; + + return $result; + } + + /** + * Return the end of a message boundary. + * + * @param string $boundary + * + * @return string + */ + protected function endBoundary($boundary) + { + return static::$LE . '--' . $boundary . '--' . static::$LE; + } + + /** + * Set the message type. + * PHPMailer only supports some preset message types, not arbitrary MIME structures. + */ + protected function setMessageType() + { + $type = []; + if ($this->alternativeExists()) { + $type[] = 'alt'; + } + if ($this->inlineImageExists()) { + $type[] = 'inline'; + } + if ($this->attachmentExists()) { + $type[] = 'attach'; + } + $this->message_type = implode('_', $type); + if ('' === $this->message_type) { + //The 'plain' message_type refers to the message having a single body element, not that it is plain-text + $this->message_type = 'plain'; + } + } + + /** + * Format a header line. + * + * @param string $name + * @param string|int $value + * + * @return string + */ + public function headerLine($name, $value) + { + return $name . ': ' . $value . static::$LE; + } + + /** + * Return a formatted mail line. + * + * @param string $value + * + * @return string + */ + public function textLine($value) + { + return $value . static::$LE; + } + + /** + * Add an attachment from a path on the filesystem. + * Never use a user-supplied path to a file! + * Returns false if the file could not be found or read. + * Explicitly *does not* support passing URLs; PHPMailer is not an HTTP client. + * If you need to do that, fetch the resource yourself and pass it in via a local file or string. + * + * @param string $path Path to the attachment + * @param string $name Overrides the attachment name + * @param string $encoding File encoding (see $Encoding) + * @param string $type MIME type, e.g. `image/jpeg`; determined automatically from $path if not specified + * @param string $disposition Disposition to use + * + * @throws Exception + * + * @return bool + */ + public function addAttachment( + $path, + $name = '', + $encoding = self::ENCODING_BASE64, + $type = '', + $disposition = 'attachment' + ) { + try { + if (!static::fileIsAccessible($path)) { + throw new Exception($this->lang('file_access') . $path, self::STOP_CONTINUE); + } + + //If a MIME type is not specified, try to work it out from the file name + if ('' === $type) { + $type = static::filenameToType($path); + } + + $filename = (string) static::mb_pathinfo($path, PATHINFO_BASENAME); + if ('' === $name) { + $name = $filename; + } + if (!$this->validateEncoding($encoding)) { + throw new Exception($this->lang('encoding') . $encoding); + } + + $this->attachment[] = [ + 0 => $path, + 1 => $filename, + 2 => $name, + 3 => $encoding, + 4 => $type, + 5 => false, //isStringAttachment + 6 => $disposition, + 7 => $name, + ]; + } catch (Exception $exc) { + $this->setError($exc->getMessage()); + $this->edebug($exc->getMessage()); + if ($this->exceptions) { + throw $exc; + } + + return false; + } + + return true; + } + + /** + * Return the array of attachments. + * + * @return array + */ + public function getAttachments() + { + return $this->attachment; + } + + /** + * Attach all file, string, and binary attachments to the message. + * Returns an empty string on failure. + * + * @param string $disposition_type + * @param string $boundary + * + * @throws Exception + * + * @return string + */ + protected function attachAll($disposition_type, $boundary) + { + //Return text of body + $mime = []; + $cidUniq = []; + $incl = []; + + //Add all attachments + foreach ($this->attachment as $attachment) { + //Check if it is a valid disposition_filter + if ($attachment[6] === $disposition_type) { + //Check for string attachment + $string = ''; + $path = ''; + $bString = $attachment[5]; + if ($bString) { + $string = $attachment[0]; + } else { + $path = $attachment[0]; + } + + $inclhash = hash('sha256', serialize($attachment)); + if (in_array($inclhash, $incl, true)) { + continue; + } + $incl[] = $inclhash; + $name = $attachment[2]; + $encoding = $attachment[3]; + $type = $attachment[4]; + $disposition = $attachment[6]; + $cid = $attachment[7]; + if ('inline' === $disposition && array_key_exists($cid, $cidUniq)) { + continue; + } + $cidUniq[$cid] = true; + + $mime[] = sprintf('--%s%s', $boundary, static::$LE); + //Only include a filename property if we have one + if (!empty($name)) { + $mime[] = sprintf( + 'Content-Type: %s; name=%s%s', + $type, + static::quotedString($this->encodeHeader($this->secureHeader($name))), + static::$LE + ); + } else { + $mime[] = sprintf( + 'Content-Type: %s%s', + $type, + static::$LE + ); + } + //RFC1341 part 5 says 7bit is assumed if not specified + if (static::ENCODING_7BIT !== $encoding) { + $mime[] = sprintf('Content-Transfer-Encoding: %s%s', $encoding, static::$LE); + } + + //Only set Content-IDs on inline attachments + if ((string) $cid !== '' && $disposition === 'inline') { + $mime[] = 'Content-ID: <' . $this->encodeHeader($this->secureHeader($cid)) . '>' . static::$LE; + } + + //Allow for bypassing the Content-Disposition header + if (!empty($disposition)) { + $encoded_name = $this->encodeHeader($this->secureHeader($name)); + if (!empty($encoded_name)) { + $mime[] = sprintf( + 'Content-Disposition: %s; filename=%s%s', + $disposition, + static::quotedString($encoded_name), + static::$LE . static::$LE + ); + } else { + $mime[] = sprintf( + 'Content-Disposition: %s%s', + $disposition, + static::$LE . static::$LE + ); + } + } else { + $mime[] = static::$LE; + } + + //Encode as string attachment + if ($bString) { + $mime[] = $this->encodeString($string, $encoding); + } else { + $mime[] = $this->encodeFile($path, $encoding); + } + if ($this->isError()) { + return ''; + } + $mime[] = static::$LE; + } + } + + $mime[] = sprintf('--%s--%s', $boundary, static::$LE); + + return implode('', $mime); + } + + /** + * Encode a file attachment in requested format. + * Returns an empty string on failure. + * + * @param string $path The full path to the file + * @param string $encoding The encoding to use; one of 'base64', '7bit', '8bit', 'binary', 'quoted-printable' + * + * @return string + */ + protected function encodeFile($path, $encoding = self::ENCODING_BASE64) + { + try { + if (!static::fileIsAccessible($path)) { + throw new Exception($this->lang('file_open') . $path, self::STOP_CONTINUE); + } + $file_buffer = file_get_contents($path); + if (false === $file_buffer) { + throw new Exception($this->lang('file_open') . $path, self::STOP_CONTINUE); + } + $file_buffer = $this->encodeString($file_buffer, $encoding); + + return $file_buffer; + } catch (Exception $exc) { + $this->setError($exc->getMessage()); + $this->edebug($exc->getMessage()); + if ($this->exceptions) { + throw $exc; + } + + return ''; + } + } + + /** + * Encode a string in requested format. + * Returns an empty string on failure. + * + * @param string $str The text to encode + * @param string $encoding The encoding to use; one of 'base64', '7bit', '8bit', 'binary', 'quoted-printable' + * + * @throws Exception + * + * @return string + */ + public function encodeString($str, $encoding = self::ENCODING_BASE64) + { + $encoded = ''; + switch (strtolower($encoding)) { + case static::ENCODING_BASE64: + $encoded = chunk_split( + base64_encode($str), + static::STD_LINE_LENGTH, + static::$LE + ); + break; + case static::ENCODING_7BIT: + case static::ENCODING_8BIT: + $encoded = static::normalizeBreaks($str); + //Make sure it ends with a line break + if (substr($encoded, -(strlen(static::$LE))) !== static::$LE) { + $encoded .= static::$LE; + } + break; + case static::ENCODING_BINARY: + $encoded = $str; + break; + case static::ENCODING_QUOTED_PRINTABLE: + $encoded = $this->encodeQP($str); + break; + default: + $this->setError($this->lang('encoding') . $encoding); + if ($this->exceptions) { + throw new Exception($this->lang('encoding') . $encoding); + } + break; + } + + return $encoded; + } + + /** + * Encode a header value (not including its label) optimally. + * Picks shortest of Q, B, or none. Result includes folding if needed. + * See RFC822 definitions for phrase, comment and text positions. + * + * @param string $str The header value to encode + * @param string $position What context the string will be used in + * + * @return string + */ + public function encodeHeader($str, $position = 'text') + { + $matchcount = 0; + switch (strtolower($position)) { + case 'phrase': + if (!preg_match('/[\200-\377]/', $str)) { + //Can't use addslashes as we don't know the value of magic_quotes_sybase + $encoded = addcslashes($str, "\0..\37\177\\\""); + if (($str === $encoded) && !preg_match('/[^A-Za-z0-9!#$%&\'*+\/=?^_`{|}~ -]/', $str)) { + return $encoded; + } + + return "\"$encoded\""; + } + $matchcount = preg_match_all('/[^\040\041\043-\133\135-\176]/', $str, $matches); + break; + /* @noinspection PhpMissingBreakStatementInspection */ + case 'comment': + $matchcount = preg_match_all('/[()"]/', $str, $matches); + //fallthrough + case 'text': + default: + $matchcount += preg_match_all('/[\000-\010\013\014\016-\037\177-\377]/', $str, $matches); + break; + } + + if ($this->has8bitChars($str)) { + $charset = $this->CharSet; + } else { + $charset = static::CHARSET_ASCII; + } + + //Q/B encoding adds 8 chars and the charset ("` =??[QB]??=`"). + $overhead = 8 + strlen($charset); + + if ('mail' === $this->Mailer) { + $maxlen = static::MAIL_MAX_LINE_LENGTH - $overhead; + } else { + $maxlen = static::MAX_LINE_LENGTH - $overhead; + } + + //Select the encoding that produces the shortest output and/or prevents corruption. + if ($matchcount > strlen($str) / 3) { + //More than 1/3 of the content needs encoding, use B-encode. + $encoding = 'B'; + } elseif ($matchcount > 0) { + //Less than 1/3 of the content needs encoding, use Q-encode. + $encoding = 'Q'; + } elseif (strlen($str) > $maxlen) { + //No encoding needed, but value exceeds max line length, use Q-encode to prevent corruption. + $encoding = 'Q'; + } else { + //No reformatting needed + $encoding = false; + } + + switch ($encoding) { + case 'B': + if ($this->hasMultiBytes($str)) { + //Use a custom function which correctly encodes and wraps long + //multibyte strings without breaking lines within a character + $encoded = $this->base64EncodeWrapMB($str, "\n"); + } else { + $encoded = base64_encode($str); + $maxlen -= $maxlen % 4; + $encoded = trim(chunk_split($encoded, $maxlen, "\n")); + } + $encoded = preg_replace('/^(.*)$/m', ' =?' . $charset . "?$encoding?\\1?=", $encoded); + break; + case 'Q': + $encoded = $this->encodeQ($str, $position); + $encoded = $this->wrapText($encoded, $maxlen, true); + $encoded = str_replace('=' . static::$LE, "\n", trim($encoded)); + $encoded = preg_replace('/^(.*)$/m', ' =?' . $charset . "?$encoding?\\1?=", $encoded); + break; + default: + return $str; + } + + return trim(static::normalizeBreaks($encoded)); + } + + /** + * Check if a string contains multi-byte characters. + * + * @param string $str multi-byte text to wrap encode + * + * @return bool + */ + public function hasMultiBytes($str) + { + if (function_exists('mb_strlen')) { + return strlen($str) > mb_strlen($str, $this->CharSet); + } + + //Assume no multibytes (we can't handle without mbstring functions anyway) + return false; + } + + /** + * Does a string contain any 8-bit chars (in any charset)? + * + * @param string $text + * + * @return bool + */ + public function has8bitChars($text) + { + return (bool) preg_match('/[\x80-\xFF]/', $text); + } + + /** + * Encode and wrap long multibyte strings for mail headers + * without breaking lines within a character. + * Adapted from a function by paravoid. + * + * @see http://www.php.net/manual/en/function.mb-encode-mimeheader.php#60283 + * + * @param string $str multi-byte text to wrap encode + * @param string $linebreak string to use as linefeed/end-of-line + * + * @return string + */ + public function base64EncodeWrapMB($str, $linebreak = null) + { + $start = '=?' . $this->CharSet . '?B?'; + $end = '?='; + $encoded = ''; + if (null === $linebreak) { + $linebreak = static::$LE; + } + + $mb_length = mb_strlen($str, $this->CharSet); + //Each line must have length <= 75, including $start and $end + $length = 75 - strlen($start) - strlen($end); + //Average multi-byte ratio + $ratio = $mb_length / strlen($str); + //Base64 has a 4:3 ratio + $avgLength = floor($length * $ratio * .75); + + $offset = 0; + for ($i = 0; $i < $mb_length; $i += $offset) { + $lookBack = 0; + do { + $offset = $avgLength - $lookBack; + $chunk = mb_substr($str, $i, $offset, $this->CharSet); + $chunk = base64_encode($chunk); + ++$lookBack; + } while (strlen($chunk) > $length); + $encoded .= $chunk . $linebreak; + } + + //Chomp the last linefeed + return substr($encoded, 0, -strlen($linebreak)); + } + + /** + * Encode a string in quoted-printable format. + * According to RFC2045 section 6.7. + * + * @param string $string The text to encode + * + * @return string + */ + public function encodeQP($string) + { + return static::normalizeBreaks(quoted_printable_encode($string)); + } + + /** + * Encode a string using Q encoding. + * + * @see http://tools.ietf.org/html/rfc2047#section-4.2 + * + * @param string $str the text to encode + * @param string $position Where the text is going to be used, see the RFC for what that means + * + * @return string + */ + public function encodeQ($str, $position = 'text') + { + //There should not be any EOL in the string + $pattern = ''; + $encoded = str_replace(["\r", "\n"], '', $str); + switch (strtolower($position)) { + case 'phrase': + //RFC 2047 section 5.3 + $pattern = '^A-Za-z0-9!*+\/ -'; + break; + /* + * RFC 2047 section 5.2. + * Build $pattern without including delimiters and [] + */ + /* @noinspection PhpMissingBreakStatementInspection */ + case 'comment': + $pattern = '\(\)"'; + /* Intentional fall through */ + case 'text': + default: + //RFC 2047 section 5.1 + //Replace every high ascii, control, =, ? and _ characters + $pattern = '\000-\011\013\014\016-\037\075\077\137\177-\377' . $pattern; + break; + } + $matches = []; + if (preg_match_all("/[{$pattern}]/", $encoded, $matches)) { + //If the string contains an '=', make sure it's the first thing we replace + //so as to avoid double-encoding + $eqkey = array_search('=', $matches[0], true); + if (false !== $eqkey) { + unset($matches[0][$eqkey]); + array_unshift($matches[0], '='); + } + foreach (array_unique($matches[0]) as $char) { + $encoded = str_replace($char, '=' . sprintf('%02X', ord($char)), $encoded); + } + } + //Replace spaces with _ (more readable than =20) + //RFC 2047 section 4.2(2) + return str_replace(' ', '_', $encoded); + } + + /** + * Add a string or binary attachment (non-filesystem). + * This method can be used to attach ascii or binary data, + * such as a BLOB record from a database. + * + * @param string $string String attachment data + * @param string $filename Name of the attachment + * @param string $encoding File encoding (see $Encoding) + * @param string $type File extension (MIME) type + * @param string $disposition Disposition to use + * + * @throws Exception + * + * @return bool True on successfully adding an attachment + */ + public function addStringAttachment( + $string, + $filename, + $encoding = self::ENCODING_BASE64, + $type = '', + $disposition = 'attachment' + ) { + try { + //If a MIME type is not specified, try to work it out from the file name + if ('' === $type) { + $type = static::filenameToType($filename); + } + + if (!$this->validateEncoding($encoding)) { + throw new Exception($this->lang('encoding') . $encoding); + } + + //Append to $attachment array + $this->attachment[] = [ + 0 => $string, + 1 => $filename, + 2 => static::mb_pathinfo($filename, PATHINFO_BASENAME), + 3 => $encoding, + 4 => $type, + 5 => true, //isStringAttachment + 6 => $disposition, + 7 => 0, + ]; + } catch (Exception $exc) { + $this->setError($exc->getMessage()); + $this->edebug($exc->getMessage()); + if ($this->exceptions) { + throw $exc; + } + + return false; + } + + return true; + } + + /** + * Add an embedded (inline) attachment from a file. + * This can include images, sounds, and just about any other document type. + * These differ from 'regular' attachments in that they are intended to be + * displayed inline with the message, not just attached for download. + * This is used in HTML messages that embed the images + * the HTML refers to using the $cid value. + * Never use a user-supplied path to a file! + * + * @param string $path Path to the attachment + * @param string $cid Content ID of the attachment; Use this to reference + * the content when using an embedded image in HTML + * @param string $name Overrides the attachment name + * @param string $encoding File encoding (see $Encoding) + * @param string $type File MIME type + * @param string $disposition Disposition to use + * + * @throws Exception + * + * @return bool True on successfully adding an attachment + */ + public function addEmbeddedImage( + $path, + $cid, + $name = '', + $encoding = self::ENCODING_BASE64, + $type = '', + $disposition = 'inline' + ) { + try { + if (!static::fileIsAccessible($path)) { + throw new Exception($this->lang('file_access') . $path, self::STOP_CONTINUE); + } + + //If a MIME type is not specified, try to work it out from the file name + if ('' === $type) { + $type = static::filenameToType($path); + } + + if (!$this->validateEncoding($encoding)) { + throw new Exception($this->lang('encoding') . $encoding); + } + + $filename = (string) static::mb_pathinfo($path, PATHINFO_BASENAME); + if ('' === $name) { + $name = $filename; + } + + //Append to $attachment array + $this->attachment[] = [ + 0 => $path, + 1 => $filename, + 2 => $name, + 3 => $encoding, + 4 => $type, + 5 => false, //isStringAttachment + 6 => $disposition, + 7 => $cid, + ]; + } catch (Exception $exc) { + $this->setError($exc->getMessage()); + $this->edebug($exc->getMessage()); + if ($this->exceptions) { + throw $exc; + } + + return false; + } + + return true; + } + + /** + * Add an embedded stringified attachment. + * This can include images, sounds, and just about any other document type. + * If your filename doesn't contain an extension, be sure to set the $type to an appropriate MIME type. + * + * @param string $string The attachment binary data + * @param string $cid Content ID of the attachment; Use this to reference + * the content when using an embedded image in HTML + * @param string $name A filename for the attachment. If this contains an extension, + * PHPMailer will attempt to set a MIME type for the attachment. + * For example 'file.jpg' would get an 'image/jpeg' MIME type. + * @param string $encoding File encoding (see $Encoding), defaults to 'base64' + * @param string $type MIME type - will be used in preference to any automatically derived type + * @param string $disposition Disposition to use + * + * @throws Exception + * + * @return bool True on successfully adding an attachment + */ + public function addStringEmbeddedImage( + $string, + $cid, + $name = '', + $encoding = self::ENCODING_BASE64, + $type = '', + $disposition = 'inline' + ) { + try { + //If a MIME type is not specified, try to work it out from the name + if ('' === $type && !empty($name)) { + $type = static::filenameToType($name); + } + + if (!$this->validateEncoding($encoding)) { + throw new Exception($this->lang('encoding') . $encoding); + } + + //Append to $attachment array + $this->attachment[] = [ + 0 => $string, + 1 => $name, + 2 => $name, + 3 => $encoding, + 4 => $type, + 5 => true, //isStringAttachment + 6 => $disposition, + 7 => $cid, + ]; + } catch (Exception $exc) { + $this->setError($exc->getMessage()); + $this->edebug($exc->getMessage()); + if ($this->exceptions) { + throw $exc; + } + + return false; + } + + return true; + } + + /** + * Validate encodings. + * + * @param string $encoding + * + * @return bool + */ + protected function validateEncoding($encoding) + { + return in_array( + $encoding, + [ + self::ENCODING_7BIT, + self::ENCODING_QUOTED_PRINTABLE, + self::ENCODING_BASE64, + self::ENCODING_8BIT, + self::ENCODING_BINARY, + ], + true + ); + } + + /** + * Check if an embedded attachment is present with this cid. + * + * @param string $cid + * + * @return bool + */ + protected function cidExists($cid) + { + foreach ($this->attachment as $attachment) { + if ('inline' === $attachment[6] && $cid === $attachment[7]) { + return true; + } + } + + return false; + } + + /** + * Check if an inline attachment is present. + * + * @return bool + */ + public function inlineImageExists() + { + foreach ($this->attachment as $attachment) { + if ('inline' === $attachment[6]) { + return true; + } + } + + return false; + } + + /** + * Check if an attachment (non-inline) is present. + * + * @return bool + */ + public function attachmentExists() + { + foreach ($this->attachment as $attachment) { + if ('attachment' === $attachment[6]) { + return true; + } + } + + return false; + } + + /** + * Check if this message has an alternative body set. + * + * @return bool + */ + public function alternativeExists() + { + return !empty($this->AltBody); + } + + /** + * Clear queued addresses of given kind. + * + * @param string $kind 'to', 'cc', or 'bcc' + */ + public function clearQueuedAddresses($kind) + { + $this->RecipientsQueue = array_filter( + $this->RecipientsQueue, + static function ($params) use ($kind) { + return $params[0] !== $kind; + } + ); + } + + /** + * Clear all To recipients. + */ + public function clearAddresses() + { + foreach ($this->to as $to) { + unset($this->all_recipients[strtolower($to[0])]); + } + $this->to = []; + $this->clearQueuedAddresses('to'); + } + + /** + * Clear all CC recipients. + */ + public function clearCCs() + { + foreach ($this->cc as $cc) { + unset($this->all_recipients[strtolower($cc[0])]); + } + $this->cc = []; + $this->clearQueuedAddresses('cc'); + } + + /** + * Clear all BCC recipients. + */ + public function clearBCCs() + { + foreach ($this->bcc as $bcc) { + unset($this->all_recipients[strtolower($bcc[0])]); + } + $this->bcc = []; + $this->clearQueuedAddresses('bcc'); + } + + /** + * Clear all ReplyTo recipients. + */ + public function clearReplyTos() + { + $this->ReplyTo = []; + $this->ReplyToQueue = []; + } + + /** + * Clear all recipient types. + */ + public function clearAllRecipients() + { + $this->to = []; + $this->cc = []; + $this->bcc = []; + $this->all_recipients = []; + $this->RecipientsQueue = []; + } + + /** + * Clear all filesystem, string, and binary attachments. + */ + public function clearAttachments() + { + $this->attachment = []; + } + + /** + * Clear all custom headers. + */ + public function clearCustomHeaders() + { + $this->CustomHeader = []; + } + + /** + * Add an error message to the error container. + * + * @param string $msg + */ + protected function setError($msg) + { + ++$this->error_count; + if ('smtp' === $this->Mailer && null !== $this->smtp) { + $lasterror = $this->smtp->getError(); + if (!empty($lasterror['error'])) { + $msg .= $this->lang('smtp_error') . $lasterror['error']; + if (!empty($lasterror['detail'])) { + $msg .= ' Detail: ' . $lasterror['detail']; + } + if (!empty($lasterror['smtp_code'])) { + $msg .= ' SMTP code: ' . $lasterror['smtp_code']; + } + if (!empty($lasterror['smtp_code_ex'])) { + $msg .= ' Additional SMTP info: ' . $lasterror['smtp_code_ex']; + } + } + } + $this->ErrorInfo = $msg; + } + + /** + * Return an RFC 822 formatted date. + * + * @return string + */ + public static function rfcDate() + { + //Set the time zone to whatever the default is to avoid 500 errors + //Will default to UTC if it's not set properly in php.ini + date_default_timezone_set(@date_default_timezone_get()); + + return date('D, j M Y H:i:s O'); + } + + /** + * Get the server hostname. + * Returns 'localhost.localdomain' if unknown. + * + * @return string + */ + protected function serverHostname() + { + $result = ''; + if (!empty($this->Hostname)) { + $result = $this->Hostname; + } elseif (isset($_SERVER) && array_key_exists('SERVER_NAME', $_SERVER)) { + $result = $_SERVER['SERVER_NAME']; + } elseif (function_exists('gethostname') && gethostname() !== false) { + $result = gethostname(); + } elseif (php_uname('n') !== false) { + $result = php_uname('n'); + } + if (!static::isValidHost($result)) { + return 'localhost.localdomain'; + } + + return $result; + } + + /** + * Validate whether a string contains a valid value to use as a hostname or IP address. + * IPv6 addresses must include [], e.g. `[::1]`, not just `::1`. + * + * @param string $host The host name or IP address to check + * + * @return bool + */ + public static function isValidHost($host) + { + //Simple syntax limits + if ( + empty($host) + || !is_string($host) + || strlen($host) > 256 + || !preg_match('/^([a-zA-Z\d.-]*|\[[a-fA-F\d:]+])$/', $host) + ) { + return false; + } + //Looks like a bracketed IPv6 address + if (strlen($host) > 2 && substr($host, 0, 1) === '[' && substr($host, -1, 1) === ']') { + return filter_var(substr($host, 1, -1), FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) !== false; + } + //If removing all the dots results in a numeric string, it must be an IPv4 address. + //Need to check this first because otherwise things like `999.0.0.0` are considered valid host names + if (is_numeric(str_replace('.', '', $host))) { + //Is it a valid IPv4 address? + return filter_var($host, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4) !== false; + } + if (filter_var('http://' . $host, FILTER_VALIDATE_URL) !== false) { + //Is it a syntactically valid hostname? + return true; + } + + return false; + } + + /** + * Get an error message in the current language. + * + * @param string $key + * + * @return string + */ + protected function lang($key) + { + if (count($this->language) < 1) { + $this->setLanguage(); //Set the default language + } + + if (array_key_exists($key, $this->language)) { + if ('smtp_connect_failed' === $key) { + //Include a link to troubleshooting docs on SMTP connection failure. + //This is by far the biggest cause of support questions + //but it's usually not PHPMailer's fault. + return $this->language[$key] . ' https://github.com/PHPMailer/PHPMailer/wiki/Troubleshooting'; + } + + return $this->language[$key]; + } + + //Return the key as a fallback + return $key; + } + + /** + * Check if an error occurred. + * + * @return bool True if an error did occur + */ + public function isError() + { + return $this->error_count > 0; + } + + /** + * Add a custom header. + * $name value can be overloaded to contain + * both header name and value (name:value). + * + * @param string $name Custom header name + * @param string|null $value Header value + * + * @throws Exception + */ + public function addCustomHeader($name, $value = null) + { + if (null === $value && strpos($name, ':') !== false) { + //Value passed in as name:value + list($name, $value) = explode(':', $name, 2); + } + $name = trim($name); + $value = trim($value); + //Ensure name is not empty, and that neither name nor value contain line breaks + if (empty($name) || strpbrk($name . $value, "\r\n") !== false) { + if ($this->exceptions) { + throw new Exception('Invalid header name or value'); + } + + return false; + } + $this->CustomHeader[] = [$name, $value]; + + return true; + } + + /** + * Returns all custom headers. + * + * @return array + */ + public function getCustomHeaders() + { + return $this->CustomHeader; + } + + /** + * Create a message body from an HTML string. + * Automatically inlines images and creates a plain-text version by converting the HTML, + * overwriting any existing values in Body and AltBody. + * Do not source $message content from user input! + * $basedir is prepended when handling relative URLs, e.g. and must not be empty + * will look for an image file in $basedir/images/a.png and convert it to inline. + * If you don't provide a $basedir, relative paths will be left untouched (and thus probably break in email) + * Converts data-uri images into embedded attachments. + * If you don't want to apply these transformations to your HTML, just set Body and AltBody directly. + * + * @param string $message HTML message string + * @param string $basedir Absolute path to a base directory to prepend to relative paths to images + * @param bool|callable $advanced Whether to use the internal HTML to text converter + * or your own custom converter + * @return string The transformed message body + * + * @throws Exception + * + * @see PHPMailer::html2text() + */ + public function msgHTML($message, $basedir = '', $advanced = false) + { + preg_match_all('/(? 1 && '/' !== substr($basedir, -1)) { + //Ensure $basedir has a trailing / + $basedir .= '/'; + } + foreach ($images[2] as $imgindex => $url) { + //Convert data URIs into embedded images + //e.g. "data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" + $match = []; + if (preg_match('#^data:(image/(?:jpe?g|gif|png));?(base64)?,(.+)#', $url, $match)) { + if (count($match) === 4 && static::ENCODING_BASE64 === $match[2]) { + $data = base64_decode($match[3]); + } elseif ('' === $match[2]) { + $data = rawurldecode($match[3]); + } else { + //Not recognised so leave it alone + continue; + } + //Hash the decoded data, not the URL, so that the same data-URI image used in multiple places + //will only be embedded once, even if it used a different encoding + $cid = substr(hash('sha256', $data), 0, 32) . '@phpmailer.0'; //RFC2392 S 2 + + if (!$this->cidExists($cid)) { + $this->addStringEmbeddedImage( + $data, + $cid, + 'embed' . $imgindex, + static::ENCODING_BASE64, + $match[1] + ); + } + $message = str_replace( + $images[0][$imgindex], + $images[1][$imgindex] . '="cid:' . $cid . '"', + $message + ); + continue; + } + if ( + //Only process relative URLs if a basedir is provided (i.e. no absolute local paths) + !empty($basedir) + //Ignore URLs containing parent dir traversal (..) + && (strpos($url, '..') === false) + //Do not change urls that are already inline images + && 0 !== strpos($url, 'cid:') + //Do not change absolute URLs, including anonymous protocol + && !preg_match('#^[a-z][a-z0-9+.-]*:?//#i', $url) + ) { + $filename = static::mb_pathinfo($url, PATHINFO_BASENAME); + $directory = dirname($url); + if ('.' === $directory) { + $directory = ''; + } + //RFC2392 S 2 + $cid = substr(hash('sha256', $url), 0, 32) . '@phpmailer.0'; + if (strlen($basedir) > 1 && '/' !== substr($basedir, -1)) { + $basedir .= '/'; + } + if (strlen($directory) > 1 && '/' !== substr($directory, -1)) { + $directory .= '/'; + } + if ( + $this->addEmbeddedImage( + $basedir . $directory . $filename, + $cid, + $filename, + static::ENCODING_BASE64, + static::_mime_types((string) static::mb_pathinfo($filename, PATHINFO_EXTENSION)) + ) + ) { + $message = preg_replace( + '/' . $images[1][$imgindex] . '=["\']' . preg_quote($url, '/') . '["\']/Ui', + $images[1][$imgindex] . '="cid:' . $cid . '"', + $message + ); + } + } + } + } + $this->isHTML(); + //Convert all message body line breaks to LE, makes quoted-printable encoding work much better + $this->Body = static::normalizeBreaks($message); + $this->AltBody = static::normalizeBreaks($this->html2text($message, $advanced)); + if (!$this->alternativeExists()) { + $this->AltBody = 'This is an HTML-only message. To view it, activate HTML in your email application.' + . static::$LE; + } + + return $this->Body; + } + + /** + * Convert an HTML string into plain text. + * This is used by msgHTML(). + * Note - older versions of this function used a bundled advanced converter + * which was removed for license reasons in #232. + * Example usage: + * + * ```php + * //Use default conversion + * $plain = $mail->html2text($html); + * //Use your own custom converter + * $plain = $mail->html2text($html, function($html) { + * $converter = new MyHtml2text($html); + * return $converter->get_text(); + * }); + * ``` + * + * @param string $html The HTML text to convert + * @param bool|callable $advanced Any boolean value to use the internal converter, + * or provide your own callable for custom conversion + * + * @return string + */ + public function html2text($html, $advanced = false) + { + if (is_callable($advanced)) { + return call_user_func($advanced, $html); + } + + return html_entity_decode( + trim(strip_tags(preg_replace('/<(head|title|style|script)[^>]*>.*?<\/\\1>/si', '', $html))), + ENT_QUOTES, + $this->CharSet + ); + } + + /** + * Get the MIME type for a file extension. + * + * @param string $ext File extension + * + * @return string MIME type of file + */ + public static function _mime_types($ext = '') + { + $mimes = [ + 'xl' => 'application/excel', + 'js' => 'application/javascript', + 'hqx' => 'application/mac-binhex40', + 'cpt' => 'application/mac-compactpro', + 'bin' => 'application/macbinary', + 'doc' => 'application/msword', + 'word' => 'application/msword', + 'xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', + 'xltx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.template', + 'potx' => 'application/vnd.openxmlformats-officedocument.presentationml.template', + 'ppsx' => 'application/vnd.openxmlformats-officedocument.presentationml.slideshow', + 'pptx' => 'application/vnd.openxmlformats-officedocument.presentationml.presentation', + 'sldx' => 'application/vnd.openxmlformats-officedocument.presentationml.slide', + 'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', + 'dotx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.template', + 'xlam' => 'application/vnd.ms-excel.addin.macroEnabled.12', + 'xlsb' => 'application/vnd.ms-excel.sheet.binary.macroEnabled.12', + 'class' => 'application/octet-stream', + 'dll' => 'application/octet-stream', + 'dms' => 'application/octet-stream', + 'exe' => 'application/octet-stream', + 'lha' => 'application/octet-stream', + 'lzh' => 'application/octet-stream', + 'psd' => 'application/octet-stream', + 'sea' => 'application/octet-stream', + 'so' => 'application/octet-stream', + 'oda' => 'application/oda', + 'pdf' => 'application/pdf', + 'ai' => 'application/postscript', + 'eps' => 'application/postscript', + 'ps' => 'application/postscript', + 'smi' => 'application/smil', + 'smil' => 'application/smil', + 'mif' => 'application/vnd.mif', + 'xls' => 'application/vnd.ms-excel', + 'ppt' => 'application/vnd.ms-powerpoint', + 'wbxml' => 'application/vnd.wap.wbxml', + 'wmlc' => 'application/vnd.wap.wmlc', + 'dcr' => 'application/x-director', + 'dir' => 'application/x-director', + 'dxr' => 'application/x-director', + 'dvi' => 'application/x-dvi', + 'gtar' => 'application/x-gtar', + 'php3' => 'application/x-httpd-php', + 'php4' => 'application/x-httpd-php', + 'php' => 'application/x-httpd-php', + 'phtml' => 'application/x-httpd-php', + 'phps' => 'application/x-httpd-php-source', + 'swf' => 'application/x-shockwave-flash', + 'sit' => 'application/x-stuffit', + 'tar' => 'application/x-tar', + 'tgz' => 'application/x-tar', + 'xht' => 'application/xhtml+xml', + 'xhtml' => 'application/xhtml+xml', + 'zip' => 'application/zip', + 'mid' => 'audio/midi', + 'midi' => 'audio/midi', + 'mp2' => 'audio/mpeg', + 'mp3' => 'audio/mpeg', + 'm4a' => 'audio/mp4', + 'mpga' => 'audio/mpeg', + 'aif' => 'audio/x-aiff', + 'aifc' => 'audio/x-aiff', + 'aiff' => 'audio/x-aiff', + 'ram' => 'audio/x-pn-realaudio', + 'rm' => 'audio/x-pn-realaudio', + 'rpm' => 'audio/x-pn-realaudio-plugin', + 'ra' => 'audio/x-realaudio', + 'wav' => 'audio/x-wav', + 'mka' => 'audio/x-matroska', + 'bmp' => 'image/bmp', + 'gif' => 'image/gif', + 'jpeg' => 'image/jpeg', + 'jpe' => 'image/jpeg', + 'jpg' => 'image/jpeg', + 'png' => 'image/png', + 'tiff' => 'image/tiff', + 'tif' => 'image/tiff', + 'webp' => 'image/webp', + 'avif' => 'image/avif', + 'heif' => 'image/heif', + 'heifs' => 'image/heif-sequence', + 'heic' => 'image/heic', + 'heics' => 'image/heic-sequence', + 'eml' => 'message/rfc822', + 'css' => 'text/css', + 'html' => 'text/html', + 'htm' => 'text/html', + 'shtml' => 'text/html', + 'log' => 'text/plain', + 'text' => 'text/plain', + 'txt' => 'text/plain', + 'rtx' => 'text/richtext', + 'rtf' => 'text/rtf', + 'vcf' => 'text/vcard', + 'vcard' => 'text/vcard', + 'ics' => 'text/calendar', + 'xml' => 'text/xml', + 'xsl' => 'text/xml', + 'wmv' => 'video/x-ms-wmv', + 'mpeg' => 'video/mpeg', + 'mpe' => 'video/mpeg', + 'mpg' => 'video/mpeg', + 'mp4' => 'video/mp4', + 'm4v' => 'video/mp4', + 'mov' => 'video/quicktime', + 'qt' => 'video/quicktime', + 'rv' => 'video/vnd.rn-realvideo', + 'avi' => 'video/x-msvideo', + 'movie' => 'video/x-sgi-movie', + 'webm' => 'video/webm', + 'mkv' => 'video/x-matroska', + ]; + $ext = strtolower($ext); + if (array_key_exists($ext, $mimes)) { + return $mimes[$ext]; + } + + return 'application/octet-stream'; + } + + /** + * Map a file name to a MIME type. + * Defaults to 'application/octet-stream', i.e.. arbitrary binary data. + * + * @param string $filename A file name or full path, does not need to exist as a file + * + * @return string + */ + public static function filenameToType($filename) + { + //In case the path is a URL, strip any query string before getting extension + $qpos = strpos($filename, '?'); + if (false !== $qpos) { + $filename = substr($filename, 0, $qpos); + } + $ext = static::mb_pathinfo($filename, PATHINFO_EXTENSION); + + return static::_mime_types($ext); + } + + /** + * Multi-byte-safe pathinfo replacement. + * Drop-in replacement for pathinfo(), but multibyte- and cross-platform-safe. + * + * @see http://www.php.net/manual/en/function.pathinfo.php#107461 + * + * @param string $path A filename or path, does not need to exist as a file + * @param int|string $options Either a PATHINFO_* constant, + * or a string name to return only the specified piece + * + * @return string|array + */ + public static function mb_pathinfo($path, $options = null) + { + $ret = ['dirname' => '', 'basename' => '', 'extension' => '', 'filename' => '']; + $pathinfo = []; + if (preg_match('#^(.*?)[\\\\/]*(([^/\\\\]*?)(\.([^.\\\\/]+?)|))[\\\\/.]*$#m', $path, $pathinfo)) { + if (array_key_exists(1, $pathinfo)) { + $ret['dirname'] = $pathinfo[1]; + } + if (array_key_exists(2, $pathinfo)) { + $ret['basename'] = $pathinfo[2]; + } + if (array_key_exists(5, $pathinfo)) { + $ret['extension'] = $pathinfo[5]; + } + if (array_key_exists(3, $pathinfo)) { + $ret['filename'] = $pathinfo[3]; + } + } + switch ($options) { + case PATHINFO_DIRNAME: + case 'dirname': + return $ret['dirname']; + case PATHINFO_BASENAME: + case 'basename': + return $ret['basename']; + case PATHINFO_EXTENSION: + case 'extension': + return $ret['extension']; + case PATHINFO_FILENAME: + case 'filename': + return $ret['filename']; + default: + return $ret; + } + } + + /** + * Set or reset instance properties. + * You should avoid this function - it's more verbose, less efficient, more error-prone and + * harder to debug than setting properties directly. + * Usage Example: + * `$mail->set('SMTPSecure', static::ENCRYPTION_STARTTLS);` + * is the same as: + * `$mail->SMTPSecure = static::ENCRYPTION_STARTTLS;`. + * + * @param string $name The property name to set + * @param mixed $value The value to set the property to + * + * @return bool + */ + public function set($name, $value = '') + { + if (property_exists($this, $name)) { + $this->$name = $value; + + return true; + } + $this->setError($this->lang('variable_set') . $name); + + return false; + } + + /** + * Strip newlines to prevent header injection. + * + * @param string $str + * + * @return string + */ + public function secureHeader($str) + { + return trim(str_replace(["\r", "\n"], '', $str)); + } + + /** + * Normalize line breaks in a string. + * Converts UNIX LF, Mac CR and Windows CRLF line breaks into a single line break format. + * Defaults to CRLF (for message bodies) and preserves consecutive breaks. + * + * @param string $text + * @param string $breaktype What kind of line break to use; defaults to static::$LE + * + * @return string + */ + public static function normalizeBreaks($text, $breaktype = null) + { + if (null === $breaktype) { + $breaktype = static::$LE; + } + //Normalise to \n + $text = str_replace([self::CRLF, "\r"], "\n", $text); + //Now convert LE as needed + if ("\n" !== $breaktype) { + $text = str_replace("\n", $breaktype, $text); + } + + return $text; + } + + /** + * Remove trailing breaks from a string. + * + * @param string $text + * + * @return string The text to remove breaks from + */ + public static function stripTrailingWSP($text) + { + return rtrim($text, " \r\n\t"); + } + + /** + * Return the current line break format string. + * + * @return string + */ + public static function getLE() + { + return static::$LE; + } + + /** + * Set the line break format string, e.g. "\r\n". + * + * @param string $le + */ + protected static function setLE($le) + { + static::$LE = $le; + } + + /** + * Set the public and private key files and password for S/MIME signing. + * + * @param string $cert_filename + * @param string $key_filename + * @param string $key_pass Password for private key + * @param string $extracerts_filename Optional path to chain certificate + */ + public function sign($cert_filename, $key_filename, $key_pass, $extracerts_filename = '') + { + $this->sign_cert_file = $cert_filename; + $this->sign_key_file = $key_filename; + $this->sign_key_pass = $key_pass; + $this->sign_extracerts_file = $extracerts_filename; + } + + /** + * Quoted-Printable-encode a DKIM header. + * + * @param string $txt + * + * @return string + */ + public function DKIM_QP($txt) + { + $line = ''; + $len = strlen($txt); + for ($i = 0; $i < $len; ++$i) { + $ord = ord($txt[$i]); + if (((0x21 <= $ord) && ($ord <= 0x3A)) || $ord === 0x3C || ((0x3E <= $ord) && ($ord <= 0x7E))) { + $line .= $txt[$i]; + } else { + $line .= '=' . sprintf('%02X', $ord); + } + } + + return $line; + } + + /** + * Generate a DKIM signature. + * + * @param string $signHeader + * + * @throws Exception + * + * @return string The DKIM signature value + */ + public function DKIM_Sign($signHeader) + { + if (!defined('PKCS7_TEXT')) { + if ($this->exceptions) { + throw new Exception($this->lang('extension_missing') . 'openssl'); + } + + return ''; + } + $privKeyStr = !empty($this->DKIM_private_string) ? + $this->DKIM_private_string : + file_get_contents($this->DKIM_private); + if ('' !== $this->DKIM_passphrase) { + $privKey = openssl_pkey_get_private($privKeyStr, $this->DKIM_passphrase); + } else { + $privKey = openssl_pkey_get_private($privKeyStr); + } + if (openssl_sign($signHeader, $signature, $privKey, 'sha256WithRSAEncryption')) { + if (\PHP_MAJOR_VERSION < 8) { + openssl_pkey_free($privKey); + } + + return base64_encode($signature); + } + if (\PHP_MAJOR_VERSION < 8) { + openssl_pkey_free($privKey); + } + + return ''; + } + + /** + * Generate a DKIM canonicalization header. + * Uses the 'relaxed' algorithm from RFC6376 section 3.4.2. + * Canonicalized headers should *always* use CRLF, regardless of mailer setting. + * + * @see https://tools.ietf.org/html/rfc6376#section-3.4.2 + * + * @param string $signHeader Header + * + * @return string + */ + public function DKIM_HeaderC($signHeader) + { + //Normalize breaks to CRLF (regardless of the mailer) + $signHeader = static::normalizeBreaks($signHeader, self::CRLF); + //Unfold header lines + //Note PCRE \s is too broad a definition of whitespace; RFC5322 defines it as `[ \t]` + //@see https://tools.ietf.org/html/rfc5322#section-2.2 + //That means this may break if you do something daft like put vertical tabs in your headers. + $signHeader = preg_replace('/\r\n[ \t]+/', ' ', $signHeader); + //Break headers out into an array + $lines = explode(self::CRLF, $signHeader); + foreach ($lines as $key => $line) { + //If the header is missing a :, skip it as it's invalid + //This is likely to happen because the explode() above will also split + //on the trailing LE, leaving an empty line + if (strpos($line, ':') === false) { + continue; + } + list($heading, $value) = explode(':', $line, 2); + //Lower-case header name + $heading = strtolower($heading); + //Collapse white space within the value, also convert WSP to space + $value = preg_replace('/[ \t]+/', ' ', $value); + //RFC6376 is slightly unclear here - it says to delete space at the *end* of each value + //But then says to delete space before and after the colon. + //Net result is the same as trimming both ends of the value. + //By elimination, the same applies to the field name + $lines[$key] = trim($heading, " \t") . ':' . trim($value, " \t"); + } + + return implode(self::CRLF, $lines); + } + + /** + * Generate a DKIM canonicalization body. + * Uses the 'simple' algorithm from RFC6376 section 3.4.3. + * Canonicalized bodies should *always* use CRLF, regardless of mailer setting. + * + * @see https://tools.ietf.org/html/rfc6376#section-3.4.3 + * + * @param string $body Message Body + * + * @return string + */ + public function DKIM_BodyC($body) + { + if (empty($body)) { + return self::CRLF; + } + //Normalize line endings to CRLF + $body = static::normalizeBreaks($body, self::CRLF); + + //Reduce multiple trailing line breaks to a single one + return static::stripTrailingWSP($body) . self::CRLF; + } + + /** + * Create the DKIM header and body in a new message header. + * + * @param string $headers_line Header lines + * @param string $subject Subject + * @param string $body Body + * + * @throws Exception + * + * @return string + */ + public function DKIM_Add($headers_line, $subject, $body) + { + $DKIMsignatureType = 'rsa-sha256'; //Signature & hash algorithms + $DKIMcanonicalization = 'relaxed/simple'; //Canonicalization methods of header & body + $DKIMquery = 'dns/txt'; //Query method + $DKIMtime = time(); + //Always sign these headers without being asked + //Recommended list from https://tools.ietf.org/html/rfc6376#section-5.4.1 + $autoSignHeaders = [ + 'from', + 'to', + 'cc', + 'date', + 'subject', + 'reply-to', + 'message-id', + 'content-type', + 'mime-version', + 'x-mailer', + ]; + if (stripos($headers_line, 'Subject') === false) { + $headers_line .= 'Subject: ' . $subject . static::$LE; + } + $headerLines = explode(static::$LE, $headers_line); + $currentHeaderLabel = ''; + $currentHeaderValue = ''; + $parsedHeaders = []; + $headerLineIndex = 0; + $headerLineCount = count($headerLines); + foreach ($headerLines as $headerLine) { + $matches = []; + if (preg_match('/^([^ \t]*?)(?::[ \t]*)(.*)$/', $headerLine, $matches)) { + if ($currentHeaderLabel !== '') { + //We were previously in another header; This is the start of a new header, so save the previous one + $parsedHeaders[] = ['label' => $currentHeaderLabel, 'value' => $currentHeaderValue]; + } + $currentHeaderLabel = $matches[1]; + $currentHeaderValue = $matches[2]; + } elseif (preg_match('/^[ \t]+(.*)$/', $headerLine, $matches)) { + //This is a folded continuation of the current header, so unfold it + $currentHeaderValue .= ' ' . $matches[1]; + } + ++$headerLineIndex; + if ($headerLineIndex >= $headerLineCount) { + //This was the last line, so finish off this header + $parsedHeaders[] = ['label' => $currentHeaderLabel, 'value' => $currentHeaderValue]; + } + } + $copiedHeaders = []; + $headersToSignKeys = []; + $headersToSign = []; + foreach ($parsedHeaders as $header) { + //Is this header one that must be included in the DKIM signature? + if (in_array(strtolower($header['label']), $autoSignHeaders, true)) { + $headersToSignKeys[] = $header['label']; + $headersToSign[] = $header['label'] . ': ' . $header['value']; + if ($this->DKIM_copyHeaderFields) { + $copiedHeaders[] = $header['label'] . ':' . //Note no space after this, as per RFC + str_replace('|', '=7C', $this->DKIM_QP($header['value'])); + } + continue; + } + //Is this an extra custom header we've been asked to sign? + if (in_array($header['label'], $this->DKIM_extraHeaders, true)) { + //Find its value in custom headers + foreach ($this->CustomHeader as $customHeader) { + if ($customHeader[0] === $header['label']) { + $headersToSignKeys[] = $header['label']; + $headersToSign[] = $header['label'] . ': ' . $header['value']; + if ($this->DKIM_copyHeaderFields) { + $copiedHeaders[] = $header['label'] . ':' . //Note no space after this, as per RFC + str_replace('|', '=7C', $this->DKIM_QP($header['value'])); + } + //Skip straight to the next header + continue 2; + } + } + } + } + $copiedHeaderFields = ''; + if ($this->DKIM_copyHeaderFields && count($copiedHeaders) > 0) { + //Assemble a DKIM 'z' tag + $copiedHeaderFields = ' z='; + $first = true; + foreach ($copiedHeaders as $copiedHeader) { + if (!$first) { + $copiedHeaderFields .= static::$LE . ' |'; + } + //Fold long values + if (strlen($copiedHeader) > self::STD_LINE_LENGTH - 3) { + $copiedHeaderFields .= substr( + chunk_split($copiedHeader, self::STD_LINE_LENGTH - 3, static::$LE . self::FWS), + 0, + -strlen(static::$LE . self::FWS) + ); + } else { + $copiedHeaderFields .= $copiedHeader; + } + $first = false; + } + $copiedHeaderFields .= ';' . static::$LE; + } + $headerKeys = ' h=' . implode(':', $headersToSignKeys) . ';' . static::$LE; + $headerValues = implode(static::$LE, $headersToSign); + $body = $this->DKIM_BodyC($body); + //Base64 of packed binary SHA-256 hash of body + $DKIMb64 = base64_encode(pack('H*', hash('sha256', $body))); + $ident = ''; + if ('' !== $this->DKIM_identity) { + $ident = ' i=' . $this->DKIM_identity . ';' . static::$LE; + } + //The DKIM-Signature header is included in the signature *except for* the value of the `b` tag + //which is appended after calculating the signature + //https://tools.ietf.org/html/rfc6376#section-3.5 + $dkimSignatureHeader = 'DKIM-Signature: v=1;' . + ' d=' . $this->DKIM_domain . ';' . + ' s=' . $this->DKIM_selector . ';' . static::$LE . + ' a=' . $DKIMsignatureType . ';' . + ' q=' . $DKIMquery . ';' . + ' t=' . $DKIMtime . ';' . + ' c=' . $DKIMcanonicalization . ';' . static::$LE . + $headerKeys . + $ident . + $copiedHeaderFields . + ' bh=' . $DKIMb64 . ';' . static::$LE . + ' b='; + //Canonicalize the set of headers + $canonicalizedHeaders = $this->DKIM_HeaderC( + $headerValues . static::$LE . $dkimSignatureHeader + ); + $signature = $this->DKIM_Sign($canonicalizedHeaders); + $signature = trim(chunk_split($signature, self::STD_LINE_LENGTH - 3, static::$LE . self::FWS)); + + return static::normalizeBreaks($dkimSignatureHeader . $signature); + } + + /** + * Detect if a string contains a line longer than the maximum line length + * allowed by RFC 2822 section 2.1.1. + * + * @param string $str + * + * @return bool + */ + public static function hasLineLongerThanMax($str) + { + return (bool) preg_match('/^(.{' . (self::MAX_LINE_LENGTH + strlen(static::$LE)) . ',})/m', $str); + } + + /** + * If a string contains any "special" characters, double-quote the name, + * and escape any double quotes with a backslash. + * + * @param string $str + * + * @return string + * + * @see RFC822 3.4.1 + */ + public static function quotedString($str) + { + if (preg_match('/[ ()<>@,;:"\/\[\]?=]/', $str)) { + //If the string contains any of these chars, it must be double-quoted + //and any double quotes must be escaped with a backslash + return '"' . str_replace('"', '\\"', $str) . '"'; + } + + //Return the string untouched, it doesn't need quoting + return $str; + } + + /** + * Allows for public read access to 'to' property. + * Before the send() call, queued addresses (i.e. with IDN) are not yet included. + * + * @return array + */ + public function getToAddresses() + { + return $this->to; + } + + /** + * Allows for public read access to 'cc' property. + * Before the send() call, queued addresses (i.e. with IDN) are not yet included. + * + * @return array + */ + public function getCcAddresses() + { + return $this->cc; + } + + /** + * Allows for public read access to 'bcc' property. + * Before the send() call, queued addresses (i.e. with IDN) are not yet included. + * + * @return array + */ + public function getBccAddresses() + { + return $this->bcc; + } + + /** + * Allows for public read access to 'ReplyTo' property. + * Before the send() call, queued addresses (i.e. with IDN) are not yet included. + * + * @return array + */ + public function getReplyToAddresses() + { + return $this->ReplyTo; + } + + /** + * Allows for public read access to 'all_recipients' property. + * Before the send() call, queued addresses (i.e. with IDN) are not yet included. + * + * @return array + */ + public function getAllRecipientAddresses() + { + return $this->all_recipients; + } + + /** + * Perform a callback. + * + * @param bool $isSent + * @param array $to + * @param array $cc + * @param array $bcc + * @param string $subject + * @param string $body + * @param string $from + * @param array $extra + */ + protected function doCallback($isSent, $to, $cc, $bcc, $subject, $body, $from, $extra) + { + if (!empty($this->action_function) && is_callable($this->action_function)) { + call_user_func($this->action_function, $isSent, $to, $cc, $bcc, $subject, $body, $from, $extra); + } + } + + /** + * Get the OAuth instance. + * + * @return OAuth + */ + public function getOAuth() + { + return $this->oauth; + } + + /** + * Set an OAuth instance. + */ + public function setOAuth(OAuth $oauth) + { + $this->oauth = $oauth; + } +} diff --git a/kirby/vendor/phpmailer/phpmailer/src/POP3.php b/kirby/vendor/phpmailer/phpmailer/src/POP3.php new file mode 100644 index 0000000..b38964b --- /dev/null +++ b/kirby/vendor/phpmailer/phpmailer/src/POP3.php @@ -0,0 +1,448 @@ + + * @author Jim Jagielski (jimjag) + * @author Andy Prevost (codeworxtech) + * @author Brent R. Matzelle (original founder) + * @copyright 2012 - 2020 Marcus Bointon + * @copyright 2010 - 2012 Jim Jagielski + * @copyright 2004 - 2009 Andy Prevost + * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License + * @note This program is distributed in the hope that it will be useful - WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. + */ + +namespace PHPMailer\PHPMailer; + +/** + * PHPMailer POP-Before-SMTP Authentication Class. + * Specifically for PHPMailer to use for RFC1939 POP-before-SMTP authentication. + * 1) This class does not support APOP authentication. + * 2) Opening and closing lots of POP3 connections can be quite slow. If you need + * to send a batch of emails then just perform the authentication once at the start, + * and then loop through your mail sending script. Providing this process doesn't + * take longer than the verification period lasts on your POP3 server, you should be fine. + * 3) This is really ancient technology; you should only need to use it to talk to very old systems. + * 4) This POP3 class is deliberately lightweight and incomplete, implementing just + * enough to do authentication. + * If you want a more complete class there are other POP3 classes for PHP available. + * + * @author Richard Davey (original author) + * @author Marcus Bointon (Synchro/coolbru) + * @author Jim Jagielski (jimjag) + * @author Andy Prevost (codeworxtech) + */ +class POP3 +{ + /** + * The POP3 PHPMailer Version number. + * + * @var string + */ + const VERSION = '6.5.0'; + + /** + * Default POP3 port number. + * + * @var int + */ + const DEFAULT_PORT = 110; + + /** + * Default timeout in seconds. + * + * @var int + */ + const DEFAULT_TIMEOUT = 30; + + /** + * POP3 class debug output mode. + * Debug output level. + * Options: + * @see POP3::DEBUG_OFF: No output + * @see POP3::DEBUG_SERVER: Server messages, connection/server errors + * @see POP3::DEBUG_CLIENT: Client and Server messages, connection/server errors + * + * @var int + */ + public $do_debug = self::DEBUG_OFF; + + /** + * POP3 mail server hostname. + * + * @var string + */ + public $host; + + /** + * POP3 port number. + * + * @var int + */ + public $port; + + /** + * POP3 Timeout Value in seconds. + * + * @var int + */ + public $tval; + + /** + * POP3 username. + * + * @var string + */ + public $username; + + /** + * POP3 password. + * + * @var string + */ + public $password; + + /** + * Resource handle for the POP3 connection socket. + * + * @var resource + */ + protected $pop_conn; + + /** + * Are we connected? + * + * @var bool + */ + protected $connected = false; + + /** + * Error container. + * + * @var array + */ + protected $errors = []; + + /** + * Line break constant. + */ + const LE = "\r\n"; + + /** + * Debug level for no output. + * + * @var int + */ + const DEBUG_OFF = 0; + + /** + * Debug level to show server -> client messages + * also shows clients connection errors or errors from server + * + * @var int + */ + const DEBUG_SERVER = 1; + + /** + * Debug level to show client -> server and server -> client messages. + * + * @var int + */ + const DEBUG_CLIENT = 2; + + /** + * Simple static wrapper for all-in-one POP before SMTP. + * + * @param string $host The hostname to connect to + * @param int|bool $port The port number to connect to + * @param int|bool $timeout The timeout value + * @param string $username + * @param string $password + * @param int $debug_level + * + * @return bool + */ + public static function popBeforeSmtp( + $host, + $port = false, + $timeout = false, + $username = '', + $password = '', + $debug_level = 0 + ) { + $pop = new self(); + + return $pop->authorise($host, $port, $timeout, $username, $password, $debug_level); + } + + /** + * Authenticate with a POP3 server. + * A connect, login, disconnect sequence + * appropriate for POP-before SMTP authorisation. + * + * @param string $host The hostname to connect to + * @param int|bool $port The port number to connect to + * @param int|bool $timeout The timeout value + * @param string $username + * @param string $password + * @param int $debug_level + * + * @return bool + */ + public function authorise($host, $port = false, $timeout = false, $username = '', $password = '', $debug_level = 0) + { + $this->host = $host; + //If no port value provided, use default + if (false === $port) { + $this->port = static::DEFAULT_PORT; + } else { + $this->port = (int) $port; + } + //If no timeout value provided, use default + if (false === $timeout) { + $this->tval = static::DEFAULT_TIMEOUT; + } else { + $this->tval = (int) $timeout; + } + $this->do_debug = $debug_level; + $this->username = $username; + $this->password = $password; + //Reset the error log + $this->errors = []; + //Connect + $result = $this->connect($this->host, $this->port, $this->tval); + if ($result) { + $login_result = $this->login($this->username, $this->password); + if ($login_result) { + $this->disconnect(); + + return true; + } + } + //We need to disconnect regardless of whether the login succeeded + $this->disconnect(); + + return false; + } + + /** + * Connect to a POP3 server. + * + * @param string $host + * @param int|bool $port + * @param int $tval + * + * @return bool + */ + public function connect($host, $port = false, $tval = 30) + { + //Are we already connected? + if ($this->connected) { + return true; + } + + //On Windows this will raise a PHP Warning error if the hostname doesn't exist. + //Rather than suppress it with @fsockopen, capture it cleanly instead + set_error_handler([$this, 'catchWarning']); + + if (false === $port) { + $port = static::DEFAULT_PORT; + } + + //Connect to the POP3 server + $errno = 0; + $errstr = ''; + $this->pop_conn = fsockopen( + $host, //POP3 Host + $port, //Port # + $errno, //Error Number + $errstr, //Error Message + $tval + ); //Timeout (seconds) + //Restore the error handler + restore_error_handler(); + + //Did we connect? + if (false === $this->pop_conn) { + //It would appear not... + $this->setError( + "Failed to connect to server $host on port $port. errno: $errno; errstr: $errstr" + ); + + return false; + } + + //Increase the stream time-out + stream_set_timeout($this->pop_conn, $tval, 0); + + //Get the POP3 server response + $pop3_response = $this->getResponse(); + //Check for the +OK + if ($this->checkResponse($pop3_response)) { + //The connection is established and the POP3 server is talking + $this->connected = true; + + return true; + } + + return false; + } + + /** + * Log in to the POP3 server. + * Does not support APOP (RFC 2828, 4949). + * + * @param string $username + * @param string $password + * + * @return bool + */ + public function login($username = '', $password = '') + { + if (!$this->connected) { + $this->setError('Not connected to POP3 server'); + } + if (empty($username)) { + $username = $this->username; + } + if (empty($password)) { + $password = $this->password; + } + + //Send the Username + $this->sendString("USER $username" . static::LE); + $pop3_response = $this->getResponse(); + if ($this->checkResponse($pop3_response)) { + //Send the Password + $this->sendString("PASS $password" . static::LE); + $pop3_response = $this->getResponse(); + if ($this->checkResponse($pop3_response)) { + return true; + } + } + + return false; + } + + /** + * Disconnect from the POP3 server. + */ + public function disconnect() + { + $this->sendString('QUIT'); + //The QUIT command may cause the daemon to exit, which will kill our connection + //So ignore errors here + try { + @fclose($this->pop_conn); + } catch (Exception $e) { + //Do nothing + } + } + + /** + * Get a response from the POP3 server. + * + * @param int $size The maximum number of bytes to retrieve + * + * @return string + */ + protected function getResponse($size = 128) + { + $response = fgets($this->pop_conn, $size); + if ($this->do_debug >= self::DEBUG_SERVER) { + echo 'Server -> Client: ', $response; + } + + return $response; + } + + /** + * Send raw data to the POP3 server. + * + * @param string $string + * + * @return int + */ + protected function sendString($string) + { + if ($this->pop_conn) { + if ($this->do_debug >= self::DEBUG_CLIENT) { //Show client messages when debug >= 2 + echo 'Client -> Server: ', $string; + } + + return fwrite($this->pop_conn, $string, strlen($string)); + } + + return 0; + } + + /** + * Checks the POP3 server response. + * Looks for for +OK or -ERR. + * + * @param string $string + * + * @return bool + */ + protected function checkResponse($string) + { + if (strpos($string, '+OK') !== 0) { + $this->setError("Server reported an error: $string"); + + return false; + } + + return true; + } + + /** + * Add an error to the internal error store. + * Also display debug output if it's enabled. + * + * @param string $error + */ + protected function setError($error) + { + $this->errors[] = $error; + if ($this->do_debug >= self::DEBUG_SERVER) { + echo '
    ';
    +            foreach ($this->errors as $e) {
    +                print_r($e);
    +            }
    +            echo '
    '; + } + } + + /** + * Get an array of error messages, if any. + * + * @return array + */ + public function getErrors() + { + return $this->errors; + } + + /** + * POP3 connection error handler. + * + * @param int $errno + * @param string $errstr + * @param string $errfile + * @param int $errline + */ + protected function catchWarning($errno, $errstr, $errfile, $errline) + { + $this->setError( + 'Connecting to the POP3 server raised a PHP warning:' . + "errno: $errno errstr: $errstr; errfile: $errfile; errline: $errline" + ); + } +} diff --git a/kirby/vendor/phpmailer/phpmailer/src/SMTP.php b/kirby/vendor/phpmailer/phpmailer/src/SMTP.php new file mode 100644 index 0000000..a4a91ed --- /dev/null +++ b/kirby/vendor/phpmailer/phpmailer/src/SMTP.php @@ -0,0 +1,1456 @@ + + * @author Jim Jagielski (jimjag) + * @author Andy Prevost (codeworxtech) + * @author Brent R. Matzelle (original founder) + * @copyright 2012 - 2020 Marcus Bointon + * @copyright 2010 - 2012 Jim Jagielski + * @copyright 2004 - 2009 Andy Prevost + * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License + * @note This program is distributed in the hope that it will be useful - WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. + */ + +namespace PHPMailer\PHPMailer; + +/** + * PHPMailer RFC821 SMTP email transport class. + * Implements RFC 821 SMTP commands and provides some utility methods for sending mail to an SMTP server. + * + * @author Chris Ryan + * @author Marcus Bointon + */ +class SMTP +{ + /** + * The PHPMailer SMTP version number. + * + * @var string + */ + const VERSION = '6.5.0'; + + /** + * SMTP line break constant. + * + * @var string + */ + const LE = "\r\n"; + + /** + * The SMTP port to use if one is not specified. + * + * @var int + */ + const DEFAULT_PORT = 25; + + /** + * The maximum line length allowed by RFC 5321 section 4.5.3.1.6, + * *excluding* a trailing CRLF break. + * + * @see https://tools.ietf.org/html/rfc5321#section-4.5.3.1.6 + * + * @var int + */ + const MAX_LINE_LENGTH = 998; + + /** + * The maximum line length allowed for replies in RFC 5321 section 4.5.3.1.5, + * *including* a trailing CRLF line break. + * + * @see https://tools.ietf.org/html/rfc5321#section-4.5.3.1.5 + * + * @var int + */ + const MAX_REPLY_LENGTH = 512; + + /** + * Debug level for no output. + * + * @var int + */ + const DEBUG_OFF = 0; + + /** + * Debug level to show client -> server messages. + * + * @var int + */ + const DEBUG_CLIENT = 1; + + /** + * Debug level to show client -> server and server -> client messages. + * + * @var int + */ + const DEBUG_SERVER = 2; + + /** + * Debug level to show connection status, client -> server and server -> client messages. + * + * @var int + */ + const DEBUG_CONNECTION = 3; + + /** + * Debug level to show all messages. + * + * @var int + */ + const DEBUG_LOWLEVEL = 4; + + /** + * Debug output level. + * Options: + * * self::DEBUG_OFF (`0`) No debug output, default + * * self::DEBUG_CLIENT (`1`) Client commands + * * self::DEBUG_SERVER (`2`) Client commands and server responses + * * self::DEBUG_CONNECTION (`3`) As DEBUG_SERVER plus connection status + * * self::DEBUG_LOWLEVEL (`4`) Low-level data output, all messages. + * + * @var int + */ + public $do_debug = self::DEBUG_OFF; + + /** + * How to handle debug output. + * Options: + * * `echo` Output plain-text as-is, appropriate for CLI + * * `html` Output escaped, line breaks converted to `
    `, appropriate for browser output + * * `error_log` Output to error log as configured in php.ini + * Alternatively, you can provide a callable expecting two params: a message string and the debug level: + * + * ```php + * $smtp->Debugoutput = function($str, $level) {echo "debug level $level; message: $str";}; + * ``` + * + * Alternatively, you can pass in an instance of a PSR-3 compatible logger, though only `debug` + * level output is used: + * + * ```php + * $mail->Debugoutput = new myPsr3Logger; + * ``` + * + * @var string|callable|\Psr\Log\LoggerInterface + */ + public $Debugoutput = 'echo'; + + /** + * Whether to use VERP. + * + * @see http://en.wikipedia.org/wiki/Variable_envelope_return_path + * @see http://www.postfix.org/VERP_README.html Info on VERP + * + * @var bool + */ + public $do_verp = false; + + /** + * The timeout value for connection, in seconds. + * Default of 5 minutes (300sec) is from RFC2821 section 4.5.3.2. + * This needs to be quite high to function correctly with hosts using greetdelay as an anti-spam measure. + * + * @see http://tools.ietf.org/html/rfc2821#section-4.5.3.2 + * + * @var int + */ + public $Timeout = 300; + + /** + * How long to wait for commands to complete, in seconds. + * Default of 5 minutes (300sec) is from RFC2821 section 4.5.3.2. + * + * @var int + */ + public $Timelimit = 300; + + /** + * Patterns to extract an SMTP transaction id from reply to a DATA command. + * The first capture group in each regex will be used as the ID. + * MS ESMTP returns the message ID, which may not be correct for internal tracking. + * + * @var string[] + */ + protected $smtp_transaction_id_patterns = [ + 'exim' => '/[\d]{3} OK id=(.*)/', + 'sendmail' => '/[\d]{3} 2.0.0 (.*) Message/', + 'postfix' => '/[\d]{3} 2.0.0 Ok: queued as (.*)/', + 'Microsoft_ESMTP' => '/[0-9]{3} 2.[\d].0 (.*)@(?:.*) Queued mail for delivery/', + 'Amazon_SES' => '/[\d]{3} Ok (.*)/', + 'SendGrid' => '/[\d]{3} Ok: queued as (.*)/', + 'CampaignMonitor' => '/[\d]{3} 2.0.0 OK:([a-zA-Z\d]{48})/', + 'Haraka' => '/[\d]{3} Message Queued \((.*)\)/', + ]; + + /** + * The last transaction ID issued in response to a DATA command, + * if one was detected. + * + * @var string|bool|null + */ + protected $last_smtp_transaction_id; + + /** + * The socket for the server connection. + * + * @var ?resource + */ + protected $smtp_conn; + + /** + * Error information, if any, for the last SMTP command. + * + * @var array + */ + protected $error = [ + 'error' => '', + 'detail' => '', + 'smtp_code' => '', + 'smtp_code_ex' => '', + ]; + + /** + * The reply the server sent to us for HELO. + * If null, no HELO string has yet been received. + * + * @var string|null + */ + protected $helo_rply; + + /** + * The set of SMTP extensions sent in reply to EHLO command. + * Indexes of the array are extension names. + * Value at index 'HELO' or 'EHLO' (according to command that was sent) + * represents the server name. In case of HELO it is the only element of the array. + * Other values can be boolean TRUE or an array containing extension options. + * If null, no HELO/EHLO string has yet been received. + * + * @var array|null + */ + protected $server_caps; + + /** + * The most recent reply received from the server. + * + * @var string + */ + protected $last_reply = ''; + + /** + * Output debugging info via a user-selected method. + * + * @param string $str Debug string to output + * @param int $level The debug level of this message; see DEBUG_* constants + * + * @see SMTP::$Debugoutput + * @see SMTP::$do_debug + */ + protected function edebug($str, $level = 0) + { + if ($level > $this->do_debug) { + return; + } + //Is this a PSR-3 logger? + if ($this->Debugoutput instanceof \Psr\Log\LoggerInterface) { + $this->Debugoutput->debug($str); + + return; + } + //Avoid clash with built-in function names + if (is_callable($this->Debugoutput) && !in_array($this->Debugoutput, ['error_log', 'html', 'echo'])) { + call_user_func($this->Debugoutput, $str, $level); + + return; + } + switch ($this->Debugoutput) { + case 'error_log': + //Don't output, just log + error_log($str); + break; + case 'html': + //Cleans up output a bit for a better looking, HTML-safe output + echo gmdate('Y-m-d H:i:s'), ' ', htmlentities( + preg_replace('/[\r\n]+/', '', $str), + ENT_QUOTES, + 'UTF-8' + ), "
    \n"; + break; + case 'echo': + default: + //Normalize line breaks + $str = preg_replace('/\r\n|\r/m', "\n", $str); + echo gmdate('Y-m-d H:i:s'), + "\t", + //Trim trailing space + trim( + //Indent for readability, except for trailing break + str_replace( + "\n", + "\n \t ", + trim($str) + ) + ), + "\n"; + } + } + + /** + * Connect to an SMTP server. + * + * @param string $host SMTP server IP or host name + * @param int $port The port number to connect to + * @param int $timeout How long to wait for the connection to open + * @param array $options An array of options for stream_context_create() + * + * @return bool + */ + public function connect($host, $port = null, $timeout = 30, $options = []) + { + //Clear errors to avoid confusion + $this->setError(''); + //Make sure we are __not__ connected + if ($this->connected()) { + //Already connected, generate error + $this->setError('Already connected to a server'); + + return false; + } + if (empty($port)) { + $port = self::DEFAULT_PORT; + } + //Connect to the SMTP server + $this->edebug( + "Connection: opening to $host:$port, timeout=$timeout, options=" . + (count($options) > 0 ? var_export($options, true) : 'array()'), + self::DEBUG_CONNECTION + ); + + $this->smtp_conn = $this->getSMTPConnection($host, $port, $timeout, $options); + + if ($this->smtp_conn === false) { + //Error info already set inside `getSMTPConnection()` + return false; + } + + $this->edebug('Connection: opened', self::DEBUG_CONNECTION); + + //Get any announcement + $this->last_reply = $this->get_lines(); + $this->edebug('SERVER -> CLIENT: ' . $this->last_reply, self::DEBUG_SERVER); + $responseCode = (int)substr($this->last_reply, 0, 3); + if ($responseCode === 220) { + return true; + } + //Anything other than a 220 response means something went wrong + //RFC 5321 says the server will wait for us to send a QUIT in response to a 554 error + //https://tools.ietf.org/html/rfc5321#section-3.1 + if ($responseCode === 554) { + $this->quit(); + } + //This will handle 421 responses which may not wait for a QUIT (e.g. if the server is being shut down) + $this->edebug('Connection: closing due to error', self::DEBUG_CONNECTION); + $this->close(); + return false; + } + + /** + * Create connection to the SMTP server. + * + * @param string $host SMTP server IP or host name + * @param int $port The port number to connect to + * @param int $timeout How long to wait for the connection to open + * @param array $options An array of options for stream_context_create() + * + * @return false|resource + */ + protected function getSMTPConnection($host, $port = null, $timeout = 30, $options = []) + { + static $streamok; + //This is enabled by default since 5.0.0 but some providers disable it + //Check this once and cache the result + if (null === $streamok) { + $streamok = function_exists('stream_socket_client'); + } + + $errno = 0; + $errstr = ''; + if ($streamok) { + $socket_context = stream_context_create($options); + set_error_handler([$this, 'errorHandler']); + $connection = stream_socket_client( + $host . ':' . $port, + $errno, + $errstr, + $timeout, + STREAM_CLIENT_CONNECT, + $socket_context + ); + restore_error_handler(); + } else { + //Fall back to fsockopen which should work in more places, but is missing some features + $this->edebug( + 'Connection: stream_socket_client not available, falling back to fsockopen', + self::DEBUG_CONNECTION + ); + set_error_handler([$this, 'errorHandler']); + $connection = fsockopen( + $host, + $port, + $errno, + $errstr, + $timeout + ); + restore_error_handler(); + } + + //Verify we connected properly + if (!is_resource($connection)) { + $this->setError( + 'Failed to connect to server', + '', + (string) $errno, + $errstr + ); + $this->edebug( + 'SMTP ERROR: ' . $this->error['error'] + . ": $errstr ($errno)", + self::DEBUG_CLIENT + ); + + return false; + } + + //SMTP server can take longer to respond, give longer timeout for first read + //Windows does not have support for this timeout function + if (strpos(PHP_OS, 'WIN') !== 0) { + $max = (int)ini_get('max_execution_time'); + //Don't bother if unlimited, or if set_time_limit is disabled + if (0 !== $max && $timeout > $max && strpos(ini_get('disable_functions'), 'set_time_limit') === false) { + @set_time_limit($timeout); + } + stream_set_timeout($connection, $timeout, 0); + } + + return $connection; + } + + /** + * Initiate a TLS (encrypted) session. + * + * @return bool + */ + public function startTLS() + { + if (!$this->sendCommand('STARTTLS', 'STARTTLS', 220)) { + return false; + } + + //Allow the best TLS version(s) we can + $crypto_method = STREAM_CRYPTO_METHOD_TLS_CLIENT; + + //PHP 5.6.7 dropped inclusion of TLS 1.1 and 1.2 in STREAM_CRYPTO_METHOD_TLS_CLIENT + //so add them back in manually if we can + if (defined('STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT')) { + $crypto_method |= STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT; + $crypto_method |= STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT; + } + + //Begin encrypted connection + set_error_handler([$this, 'errorHandler']); + $crypto_ok = stream_socket_enable_crypto( + $this->smtp_conn, + true, + $crypto_method + ); + restore_error_handler(); + + return (bool) $crypto_ok; + } + + /** + * Perform SMTP authentication. + * Must be run after hello(). + * + * @see hello() + * + * @param string $username The user name + * @param string $password The password + * @param string $authtype The auth type (CRAM-MD5, PLAIN, LOGIN, XOAUTH2) + * @param OAuth $OAuth An optional OAuth instance for XOAUTH2 authentication + * + * @return bool True if successfully authenticated + */ + public function authenticate( + $username, + $password, + $authtype = null, + $OAuth = null + ) { + if (!$this->server_caps) { + $this->setError('Authentication is not allowed before HELO/EHLO'); + + return false; + } + + if (array_key_exists('EHLO', $this->server_caps)) { + //SMTP extensions are available; try to find a proper authentication method + if (!array_key_exists('AUTH', $this->server_caps)) { + $this->setError('Authentication is not allowed at this stage'); + //'at this stage' means that auth may be allowed after the stage changes + //e.g. after STARTTLS + + return false; + } + + $this->edebug('Auth method requested: ' . ($authtype ?: 'UNSPECIFIED'), self::DEBUG_LOWLEVEL); + $this->edebug( + 'Auth methods available on the server: ' . implode(',', $this->server_caps['AUTH']), + self::DEBUG_LOWLEVEL + ); + + //If we have requested a specific auth type, check the server supports it before trying others + if (null !== $authtype && !in_array($authtype, $this->server_caps['AUTH'], true)) { + $this->edebug('Requested auth method not available: ' . $authtype, self::DEBUG_LOWLEVEL); + $authtype = null; + } + + if (empty($authtype)) { + //If no auth mechanism is specified, attempt to use these, in this order + //Try CRAM-MD5 first as it's more secure than the others + foreach (['CRAM-MD5', 'LOGIN', 'PLAIN', 'XOAUTH2'] as $method) { + if (in_array($method, $this->server_caps['AUTH'], true)) { + $authtype = $method; + break; + } + } + if (empty($authtype)) { + $this->setError('No supported authentication methods found'); + + return false; + } + $this->edebug('Auth method selected: ' . $authtype, self::DEBUG_LOWLEVEL); + } + + if (!in_array($authtype, $this->server_caps['AUTH'], true)) { + $this->setError("The requested authentication method \"$authtype\" is not supported by the server"); + + return false; + } + } elseif (empty($authtype)) { + $authtype = 'LOGIN'; + } + switch ($authtype) { + case 'PLAIN': + //Start authentication + if (!$this->sendCommand('AUTH', 'AUTH PLAIN', 334)) { + return false; + } + //Send encoded username and password + if ( + //Format from https://tools.ietf.org/html/rfc4616#section-2 + //We skip the first field (it's forgery), so the string starts with a null byte + !$this->sendCommand( + 'User & Password', + base64_encode("\0" . $username . "\0" . $password), + 235 + ) + ) { + return false; + } + break; + case 'LOGIN': + //Start authentication + if (!$this->sendCommand('AUTH', 'AUTH LOGIN', 334)) { + return false; + } + if (!$this->sendCommand('Username', base64_encode($username), 334)) { + return false; + } + if (!$this->sendCommand('Password', base64_encode($password), 235)) { + return false; + } + break; + case 'CRAM-MD5': + //Start authentication + if (!$this->sendCommand('AUTH CRAM-MD5', 'AUTH CRAM-MD5', 334)) { + return false; + } + //Get the challenge + $challenge = base64_decode(substr($this->last_reply, 4)); + + //Build the response + $response = $username . ' ' . $this->hmac($challenge, $password); + + //send encoded credentials + return $this->sendCommand('Username', base64_encode($response), 235); + case 'XOAUTH2': + //The OAuth instance must be set up prior to requesting auth. + if (null === $OAuth) { + return false; + } + $oauth = $OAuth->getOauth64(); + + //Start authentication + if (!$this->sendCommand('AUTH', 'AUTH XOAUTH2 ' . $oauth, 235)) { + return false; + } + break; + default: + $this->setError("Authentication method \"$authtype\" is not supported"); + + return false; + } + + return true; + } + + /** + * Calculate an MD5 HMAC hash. + * Works like hash_hmac('md5', $data, $key) + * in case that function is not available. + * + * @param string $data The data to hash + * @param string $key The key to hash with + * + * @return string + */ + protected function hmac($data, $key) + { + if (function_exists('hash_hmac')) { + return hash_hmac('md5', $data, $key); + } + + //The following borrowed from + //http://php.net/manual/en/function.mhash.php#27225 + + //RFC 2104 HMAC implementation for php. + //Creates an md5 HMAC. + //Eliminates the need to install mhash to compute a HMAC + //by Lance Rushing + + $bytelen = 64; //byte length for md5 + if (strlen($key) > $bytelen) { + $key = pack('H*', md5($key)); + } + $key = str_pad($key, $bytelen, chr(0x00)); + $ipad = str_pad('', $bytelen, chr(0x36)); + $opad = str_pad('', $bytelen, chr(0x5c)); + $k_ipad = $key ^ $ipad; + $k_opad = $key ^ $opad; + + return md5($k_opad . pack('H*', md5($k_ipad . $data))); + } + + /** + * Check connection state. + * + * @return bool True if connected + */ + public function connected() + { + if (is_resource($this->smtp_conn)) { + $sock_status = stream_get_meta_data($this->smtp_conn); + if ($sock_status['eof']) { + //The socket is valid but we are not connected + $this->edebug( + 'SMTP NOTICE: EOF caught while checking if connected', + self::DEBUG_CLIENT + ); + $this->close(); + + return false; + } + + return true; //everything looks good + } + + return false; + } + + /** + * Close the socket and clean up the state of the class. + * Don't use this function without first trying to use QUIT. + * + * @see quit() + */ + public function close() + { + $this->setError(''); + $this->server_caps = null; + $this->helo_rply = null; + if (is_resource($this->smtp_conn)) { + //Close the connection and cleanup + fclose($this->smtp_conn); + $this->smtp_conn = null; //Makes for cleaner serialization + $this->edebug('Connection: closed', self::DEBUG_CONNECTION); + } + } + + /** + * Send an SMTP DATA command. + * Issues a data command and sends the msg_data to the server, + * finializing the mail transaction. $msg_data is the message + * that is to be send with the headers. Each header needs to be + * on a single line followed by a with the message headers + * and the message body being separated by an additional . + * Implements RFC 821: DATA . + * + * @param string $msg_data Message data to send + * + * @return bool + */ + public function data($msg_data) + { + //This will use the standard timelimit + if (!$this->sendCommand('DATA', 'DATA', 354)) { + return false; + } + + /* The server is ready to accept data! + * According to rfc821 we should not send more than 1000 characters on a single line (including the LE) + * so we will break the data up into lines by \r and/or \n then if needed we will break each of those into + * smaller lines to fit within the limit. + * We will also look for lines that start with a '.' and prepend an additional '.'. + * NOTE: this does not count towards line-length limit. + */ + + //Normalize line breaks before exploding + $lines = explode("\n", str_replace(["\r\n", "\r"], "\n", $msg_data)); + + /* To distinguish between a complete RFC822 message and a plain message body, we check if the first field + * of the first line (':' separated) does not contain a space then it _should_ be a header and we will + * process all lines before a blank line as headers. + */ + + $field = substr($lines[0], 0, strpos($lines[0], ':')); + $in_headers = false; + if (!empty($field) && strpos($field, ' ') === false) { + $in_headers = true; + } + + foreach ($lines as $line) { + $lines_out = []; + if ($in_headers && $line === '') { + $in_headers = false; + } + //Break this line up into several smaller lines if it's too long + //Micro-optimisation: isset($str[$len]) is faster than (strlen($str) > $len), + while (isset($line[self::MAX_LINE_LENGTH])) { + //Working backwards, try to find a space within the last MAX_LINE_LENGTH chars of the line to break on + //so as to avoid breaking in the middle of a word + $pos = strrpos(substr($line, 0, self::MAX_LINE_LENGTH), ' '); + //Deliberately matches both false and 0 + if (!$pos) { + //No nice break found, add a hard break + $pos = self::MAX_LINE_LENGTH - 1; + $lines_out[] = substr($line, 0, $pos); + $line = substr($line, $pos); + } else { + //Break at the found point + $lines_out[] = substr($line, 0, $pos); + //Move along by the amount we dealt with + $line = substr($line, $pos + 1); + } + //If processing headers add a LWSP-char to the front of new line RFC822 section 3.1.1 + if ($in_headers) { + $line = "\t" . $line; + } + } + $lines_out[] = $line; + + //Send the lines to the server + foreach ($lines_out as $line_out) { + //Dot-stuffing as per RFC5321 section 4.5.2 + //https://tools.ietf.org/html/rfc5321#section-4.5.2 + if (!empty($line_out) && $line_out[0] === '.') { + $line_out = '.' . $line_out; + } + $this->client_send($line_out . static::LE, 'DATA'); + } + } + + //Message data has been sent, complete the command + //Increase timelimit for end of DATA command + $savetimelimit = $this->Timelimit; + $this->Timelimit *= 2; + $result = $this->sendCommand('DATA END', '.', 250); + $this->recordLastTransactionID(); + //Restore timelimit + $this->Timelimit = $savetimelimit; + + return $result; + } + + /** + * Send an SMTP HELO or EHLO command. + * Used to identify the sending server to the receiving server. + * This makes sure that client and server are in a known state. + * Implements RFC 821: HELO + * and RFC 2821 EHLO. + * + * @param string $host The host name or IP to connect to + * + * @return bool + */ + public function hello($host = '') + { + //Try extended hello first (RFC 2821) + if ($this->sendHello('EHLO', $host)) { + return true; + } + + //Some servers shut down the SMTP service here (RFC 5321) + if (substr($this->helo_rply, 0, 3) == '421') { + return false; + } + + return $this->sendHello('HELO', $host); + } + + /** + * Send an SMTP HELO or EHLO command. + * Low-level implementation used by hello(). + * + * @param string $hello The HELO string + * @param string $host The hostname to say we are + * + * @return bool + * + * @see hello() + */ + protected function sendHello($hello, $host) + { + $noerror = $this->sendCommand($hello, $hello . ' ' . $host, 250); + $this->helo_rply = $this->last_reply; + if ($noerror) { + $this->parseHelloFields($hello); + } else { + $this->server_caps = null; + } + + return $noerror; + } + + /** + * Parse a reply to HELO/EHLO command to discover server extensions. + * In case of HELO, the only parameter that can be discovered is a server name. + * + * @param string $type `HELO` or `EHLO` + */ + protected function parseHelloFields($type) + { + $this->server_caps = []; + $lines = explode("\n", $this->helo_rply); + + foreach ($lines as $n => $s) { + //First 4 chars contain response code followed by - or space + $s = trim(substr($s, 4)); + if (empty($s)) { + continue; + } + $fields = explode(' ', $s); + if (!empty($fields)) { + if (!$n) { + $name = $type; + $fields = $fields[0]; + } else { + $name = array_shift($fields); + switch ($name) { + case 'SIZE': + $fields = ($fields ? $fields[0] : 0); + break; + case 'AUTH': + if (!is_array($fields)) { + $fields = []; + } + break; + default: + $fields = true; + } + } + $this->server_caps[$name] = $fields; + } + } + } + + /** + * Send an SMTP MAIL command. + * Starts a mail transaction from the email address specified in + * $from. Returns true if successful or false otherwise. If True + * the mail transaction is started and then one or more recipient + * commands may be called followed by a data command. + * Implements RFC 821: MAIL FROM: . + * + * @param string $from Source address of this message + * + * @return bool + */ + public function mail($from) + { + $useVerp = ($this->do_verp ? ' XVERP' : ''); + + return $this->sendCommand( + 'MAIL FROM', + 'MAIL FROM:<' . $from . '>' . $useVerp, + 250 + ); + } + + /** + * Send an SMTP QUIT command. + * Closes the socket if there is no error or the $close_on_error argument is true. + * Implements from RFC 821: QUIT . + * + * @param bool $close_on_error Should the connection close if an error occurs? + * + * @return bool + */ + public function quit($close_on_error = true) + { + $noerror = $this->sendCommand('QUIT', 'QUIT', 221); + $err = $this->error; //Save any error + if ($noerror || $close_on_error) { + $this->close(); + $this->error = $err; //Restore any error from the quit command + } + + return $noerror; + } + + /** + * Send an SMTP RCPT command. + * Sets the TO argument to $toaddr. + * Returns true if the recipient was accepted false if it was rejected. + * Implements from RFC 821: RCPT TO: . + * + * @param string $address The address the message is being sent to + * @param string $dsn Comma separated list of DSN notifications. NEVER, SUCCESS, FAILURE + * or DELAY. If you specify NEVER all other notifications are ignored. + * + * @return bool + */ + public function recipient($address, $dsn = '') + { + if (empty($dsn)) { + $rcpt = 'RCPT TO:<' . $address . '>'; + } else { + $dsn = strtoupper($dsn); + $notify = []; + + if (strpos($dsn, 'NEVER') !== false) { + $notify[] = 'NEVER'; + } else { + foreach (['SUCCESS', 'FAILURE', 'DELAY'] as $value) { + if (strpos($dsn, $value) !== false) { + $notify[] = $value; + } + } + } + + $rcpt = 'RCPT TO:<' . $address . '> NOTIFY=' . implode(',', $notify); + } + + return $this->sendCommand( + 'RCPT TO', + $rcpt, + [250, 251] + ); + } + + /** + * Send an SMTP RSET command. + * Abort any transaction that is currently in progress. + * Implements RFC 821: RSET . + * + * @return bool True on success + */ + public function reset() + { + return $this->sendCommand('RSET', 'RSET', 250); + } + + /** + * Send a command to an SMTP server and check its return code. + * + * @param string $command The command name - not sent to the server + * @param string $commandstring The actual command to send + * @param int|array $expect One or more expected integer success codes + * + * @return bool True on success + */ + protected function sendCommand($command, $commandstring, $expect) + { + if (!$this->connected()) { + $this->setError("Called $command without being connected"); + + return false; + } + //Reject line breaks in all commands + if ((strpos($commandstring, "\n") !== false) || (strpos($commandstring, "\r") !== false)) { + $this->setError("Command '$command' contained line breaks"); + + return false; + } + $this->client_send($commandstring . static::LE, $command); + + $this->last_reply = $this->get_lines(); + //Fetch SMTP code and possible error code explanation + $matches = []; + if (preg_match('/^([\d]{3})[ -](?:([\d]\\.[\d]\\.[\d]{1,2}) )?/', $this->last_reply, $matches)) { + $code = (int) $matches[1]; + $code_ex = (count($matches) > 2 ? $matches[2] : null); + //Cut off error code from each response line + $detail = preg_replace( + "/{$code}[ -]" . + ($code_ex ? str_replace('.', '\\.', $code_ex) . ' ' : '') . '/m', + '', + $this->last_reply + ); + } else { + //Fall back to simple parsing if regex fails + $code = (int) substr($this->last_reply, 0, 3); + $code_ex = null; + $detail = substr($this->last_reply, 4); + } + + $this->edebug('SERVER -> CLIENT: ' . $this->last_reply, self::DEBUG_SERVER); + + if (!in_array($code, (array) $expect, true)) { + $this->setError( + "$command command failed", + $detail, + $code, + $code_ex + ); + $this->edebug( + 'SMTP ERROR: ' . $this->error['error'] . ': ' . $this->last_reply, + self::DEBUG_CLIENT + ); + + return false; + } + + $this->setError(''); + + return true; + } + + /** + * Send an SMTP SAML command. + * Starts a mail transaction from the email address specified in $from. + * Returns true if successful or false otherwise. If True + * the mail transaction is started and then one or more recipient + * commands may be called followed by a data command. This command + * will send the message to the users terminal if they are logged + * in and send them an email. + * Implements RFC 821: SAML FROM: . + * + * @param string $from The address the message is from + * + * @return bool + */ + public function sendAndMail($from) + { + return $this->sendCommand('SAML', "SAML FROM:$from", 250); + } + + /** + * Send an SMTP VRFY command. + * + * @param string $name The name to verify + * + * @return bool + */ + public function verify($name) + { + return $this->sendCommand('VRFY', "VRFY $name", [250, 251]); + } + + /** + * Send an SMTP NOOP command. + * Used to keep keep-alives alive, doesn't actually do anything. + * + * @return bool + */ + public function noop() + { + return $this->sendCommand('NOOP', 'NOOP', 250); + } + + /** + * Send an SMTP TURN command. + * This is an optional command for SMTP that this class does not support. + * This method is here to make the RFC821 Definition complete for this class + * and _may_ be implemented in future. + * Implements from RFC 821: TURN . + * + * @return bool + */ + public function turn() + { + $this->setError('The SMTP TURN command is not implemented'); + $this->edebug('SMTP NOTICE: ' . $this->error['error'], self::DEBUG_CLIENT); + + return false; + } + + /** + * Send raw data to the server. + * + * @param string $data The data to send + * @param string $command Optionally, the command this is part of, used only for controlling debug output + * + * @return int|bool The number of bytes sent to the server or false on error + */ + public function client_send($data, $command = '') + { + //If SMTP transcripts are left enabled, or debug output is posted online + //it can leak credentials, so hide credentials in all but lowest level + if ( + self::DEBUG_LOWLEVEL > $this->do_debug && + in_array($command, ['User & Password', 'Username', 'Password'], true) + ) { + $this->edebug('CLIENT -> SERVER: [credentials hidden]', self::DEBUG_CLIENT); + } else { + $this->edebug('CLIENT -> SERVER: ' . $data, self::DEBUG_CLIENT); + } + set_error_handler([$this, 'errorHandler']); + $result = fwrite($this->smtp_conn, $data); + restore_error_handler(); + + return $result; + } + + /** + * Get the latest error. + * + * @return array + */ + public function getError() + { + return $this->error; + } + + /** + * Get SMTP extensions available on the server. + * + * @return array|null + */ + public function getServerExtList() + { + return $this->server_caps; + } + + /** + * Get metadata about the SMTP server from its HELO/EHLO response. + * The method works in three ways, dependent on argument value and current state: + * 1. HELO/EHLO has not been sent - returns null and populates $this->error. + * 2. HELO has been sent - + * $name == 'HELO': returns server name + * $name == 'EHLO': returns boolean false + * $name == any other string: returns null and populates $this->error + * 3. EHLO has been sent - + * $name == 'HELO'|'EHLO': returns the server name + * $name == any other string: if extension $name exists, returns True + * or its options (e.g. AUTH mechanisms supported). Otherwise returns False. + * + * @param string $name Name of SMTP extension or 'HELO'|'EHLO' + * + * @return string|bool|null + */ + public function getServerExt($name) + { + if (!$this->server_caps) { + $this->setError('No HELO/EHLO was sent'); + + return; + } + + if (!array_key_exists($name, $this->server_caps)) { + if ('HELO' === $name) { + return $this->server_caps['EHLO']; + } + if ('EHLO' === $name || array_key_exists('EHLO', $this->server_caps)) { + return false; + } + $this->setError('HELO handshake was used; No information about server extensions available'); + + return; + } + + return $this->server_caps[$name]; + } + + /** + * Get the last reply from the server. + * + * @return string + */ + public function getLastReply() + { + return $this->last_reply; + } + + /** + * Read the SMTP server's response. + * Either before eof or socket timeout occurs on the operation. + * With SMTP we can tell if we have more lines to read if the + * 4th character is '-' symbol. If it is a space then we don't + * need to read anything else. + * + * @return string + */ + protected function get_lines() + { + //If the connection is bad, give up straight away + if (!is_resource($this->smtp_conn)) { + return ''; + } + $data = ''; + $endtime = 0; + stream_set_timeout($this->smtp_conn, $this->Timeout); + if ($this->Timelimit > 0) { + $endtime = time() + $this->Timelimit; + } + $selR = [$this->smtp_conn]; + $selW = null; + while (is_resource($this->smtp_conn) && !feof($this->smtp_conn)) { + //Must pass vars in here as params are by reference + //solution for signals inspired by https://github.com/symfony/symfony/pull/6540 + set_error_handler([$this, 'errorHandler']); + $n = stream_select($selR, $selW, $selW, $this->Timelimit); + restore_error_handler(); + + if ($n === false) { + $message = $this->getError()['detail']; + + $this->edebug( + 'SMTP -> get_lines(): select failed (' . $message . ')', + self::DEBUG_LOWLEVEL + ); + + //stream_select returns false when the `select` system call is interrupted + //by an incoming signal, try the select again + if (stripos($message, 'interrupted system call') !== false) { + $this->edebug( + 'SMTP -> get_lines(): retrying stream_select', + self::DEBUG_LOWLEVEL + ); + $this->setError(''); + continue; + } + + break; + } + + if (!$n) { + $this->edebug( + 'SMTP -> get_lines(): select timed-out in (' . $this->Timelimit . ' sec)', + self::DEBUG_LOWLEVEL + ); + break; + } + + //Deliberate noise suppression - errors are handled afterwards + $str = @fgets($this->smtp_conn, self::MAX_REPLY_LENGTH); + $this->edebug('SMTP INBOUND: "' . trim($str) . '"', self::DEBUG_LOWLEVEL); + $data .= $str; + //If response is only 3 chars (not valid, but RFC5321 S4.2 says it must be handled), + //or 4th character is a space or a line break char, we are done reading, break the loop. + //String array access is a significant micro-optimisation over strlen + if (!isset($str[3]) || $str[3] === ' ' || $str[3] === "\r" || $str[3] === "\n") { + break; + } + //Timed-out? Log and break + $info = stream_get_meta_data($this->smtp_conn); + if ($info['timed_out']) { + $this->edebug( + 'SMTP -> get_lines(): stream timed-out (' . $this->Timeout . ' sec)', + self::DEBUG_LOWLEVEL + ); + break; + } + //Now check if reads took too long + if ($endtime && time() > $endtime) { + $this->edebug( + 'SMTP -> get_lines(): timelimit reached (' . + $this->Timelimit . ' sec)', + self::DEBUG_LOWLEVEL + ); + break; + } + } + + return $data; + } + + /** + * Enable or disable VERP address generation. + * + * @param bool $enabled + */ + public function setVerp($enabled = false) + { + $this->do_verp = $enabled; + } + + /** + * Get VERP address generation mode. + * + * @return bool + */ + public function getVerp() + { + return $this->do_verp; + } + + /** + * Set error messages and codes. + * + * @param string $message The error message + * @param string $detail Further detail on the error + * @param string $smtp_code An associated SMTP error code + * @param string $smtp_code_ex Extended SMTP code + */ + protected function setError($message, $detail = '', $smtp_code = '', $smtp_code_ex = '') + { + $this->error = [ + 'error' => $message, + 'detail' => $detail, + 'smtp_code' => $smtp_code, + 'smtp_code_ex' => $smtp_code_ex, + ]; + } + + /** + * Set debug output method. + * + * @param string|callable $method The name of the mechanism to use for debugging output, or a callable to handle it + */ + public function setDebugOutput($method = 'echo') + { + $this->Debugoutput = $method; + } + + /** + * Get debug output method. + * + * @return string + */ + public function getDebugOutput() + { + return $this->Debugoutput; + } + + /** + * Set debug output level. + * + * @param int $level + */ + public function setDebugLevel($level = 0) + { + $this->do_debug = $level; + } + + /** + * Get debug output level. + * + * @return int + */ + public function getDebugLevel() + { + return $this->do_debug; + } + + /** + * Set SMTP timeout. + * + * @param int $timeout The timeout duration in seconds + */ + public function setTimeout($timeout = 0) + { + $this->Timeout = $timeout; + } + + /** + * Get SMTP timeout. + * + * @return int + */ + public function getTimeout() + { + return $this->Timeout; + } + + /** + * Reports an error number and string. + * + * @param int $errno The error number returned by PHP + * @param string $errmsg The error message returned by PHP + * @param string $errfile The file the error occurred in + * @param int $errline The line number the error occurred on + */ + protected function errorHandler($errno, $errmsg, $errfile = '', $errline = 0) + { + $notice = 'Connection failed.'; + $this->setError( + $notice, + $errmsg, + (string) $errno + ); + $this->edebug( + "$notice Error #$errno: $errmsg [$errfile line $errline]", + self::DEBUG_CONNECTION + ); + } + + /** + * Extract and return the ID of the last SMTP transaction based on + * a list of patterns provided in SMTP::$smtp_transaction_id_patterns. + * Relies on the host providing the ID in response to a DATA command. + * If no reply has been received yet, it will return null. + * If no pattern was matched, it will return false. + * + * @return bool|string|null + */ + protected function recordLastTransactionID() + { + $reply = $this->getLastReply(); + + if (empty($reply)) { + $this->last_smtp_transaction_id = null; + } else { + $this->last_smtp_transaction_id = false; + foreach ($this->smtp_transaction_id_patterns as $smtp_transaction_id_pattern) { + $matches = []; + if (preg_match($smtp_transaction_id_pattern, $reply, $matches)) { + $this->last_smtp_transaction_id = trim($matches[1]); + break; + } + } + } + + return $this->last_smtp_transaction_id; + } + + /** + * Get the queue/transaction ID of the last SMTP transaction + * If no reply has been received yet, it will return null. + * If no pattern was matched, it will return false. + * + * @return bool|string|null + * + * @see recordLastTransactionID() + */ + public function getLastTransactionID() + { + return $this->last_smtp_transaction_id; + } +} diff --git a/kirby/vendor/psr/log/LICENSE b/kirby/vendor/psr/log/LICENSE new file mode 100644 index 0000000..474c952 --- /dev/null +++ b/kirby/vendor/psr/log/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2012 PHP Framework Interoperability Group + +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/kirby/vendor/psr/log/Psr/Log/AbstractLogger.php b/kirby/vendor/psr/log/Psr/Log/AbstractLogger.php new file mode 100644 index 0000000..e02f9da --- /dev/null +++ b/kirby/vendor/psr/log/Psr/Log/AbstractLogger.php @@ -0,0 +1,128 @@ +log(LogLevel::EMERGENCY, $message, $context); + } + + /** + * Action must be taken immediately. + * + * Example: Entire website down, database unavailable, etc. This should + * trigger the SMS alerts and wake you up. + * + * @param string $message + * @param mixed[] $context + * + * @return void + */ + public function alert($message, array $context = array()) + { + $this->log(LogLevel::ALERT, $message, $context); + } + + /** + * Critical conditions. + * + * Example: Application component unavailable, unexpected exception. + * + * @param string $message + * @param mixed[] $context + * + * @return void + */ + public function critical($message, array $context = array()) + { + $this->log(LogLevel::CRITICAL, $message, $context); + } + + /** + * Runtime errors that do not require immediate action but should typically + * be logged and monitored. + * + * @param string $message + * @param mixed[] $context + * + * @return void + */ + public function error($message, array $context = array()) + { + $this->log(LogLevel::ERROR, $message, $context); + } + + /** + * Exceptional occurrences that are not errors. + * + * Example: Use of deprecated APIs, poor use of an API, undesirable things + * that are not necessarily wrong. + * + * @param string $message + * @param mixed[] $context + * + * @return void + */ + public function warning($message, array $context = array()) + { + $this->log(LogLevel::WARNING, $message, $context); + } + + /** + * Normal but significant events. + * + * @param string $message + * @param mixed[] $context + * + * @return void + */ + public function notice($message, array $context = array()) + { + $this->log(LogLevel::NOTICE, $message, $context); + } + + /** + * Interesting events. + * + * Example: User logs in, SQL logs. + * + * @param string $message + * @param mixed[] $context + * + * @return void + */ + public function info($message, array $context = array()) + { + $this->log(LogLevel::INFO, $message, $context); + } + + /** + * Detailed debug information. + * + * @param string $message + * @param mixed[] $context + * + * @return void + */ + public function debug($message, array $context = array()) + { + $this->log(LogLevel::DEBUG, $message, $context); + } +} diff --git a/kirby/vendor/psr/log/Psr/Log/InvalidArgumentException.php b/kirby/vendor/psr/log/Psr/Log/InvalidArgumentException.php new file mode 100644 index 0000000..67f852d --- /dev/null +++ b/kirby/vendor/psr/log/Psr/Log/InvalidArgumentException.php @@ -0,0 +1,7 @@ +logger = $logger; + } +} diff --git a/kirby/vendor/psr/log/Psr/Log/LoggerInterface.php b/kirby/vendor/psr/log/Psr/Log/LoggerInterface.php new file mode 100644 index 0000000..2206cfd --- /dev/null +++ b/kirby/vendor/psr/log/Psr/Log/LoggerInterface.php @@ -0,0 +1,125 @@ +log(LogLevel::EMERGENCY, $message, $context); + } + + /** + * Action must be taken immediately. + * + * Example: Entire website down, database unavailable, etc. This should + * trigger the SMS alerts and wake you up. + * + * @param string $message + * @param array $context + * + * @return void + */ + public function alert($message, array $context = array()) + { + $this->log(LogLevel::ALERT, $message, $context); + } + + /** + * Critical conditions. + * + * Example: Application component unavailable, unexpected exception. + * + * @param string $message + * @param array $context + * + * @return void + */ + public function critical($message, array $context = array()) + { + $this->log(LogLevel::CRITICAL, $message, $context); + } + + /** + * Runtime errors that do not require immediate action but should typically + * be logged and monitored. + * + * @param string $message + * @param array $context + * + * @return void + */ + public function error($message, array $context = array()) + { + $this->log(LogLevel::ERROR, $message, $context); + } + + /** + * Exceptional occurrences that are not errors. + * + * Example: Use of deprecated APIs, poor use of an API, undesirable things + * that are not necessarily wrong. + * + * @param string $message + * @param array $context + * + * @return void + */ + public function warning($message, array $context = array()) + { + $this->log(LogLevel::WARNING, $message, $context); + } + + /** + * Normal but significant events. + * + * @param string $message + * @param array $context + * + * @return void + */ + public function notice($message, array $context = array()) + { + $this->log(LogLevel::NOTICE, $message, $context); + } + + /** + * Interesting events. + * + * Example: User logs in, SQL logs. + * + * @param string $message + * @param array $context + * + * @return void + */ + public function info($message, array $context = array()) + { + $this->log(LogLevel::INFO, $message, $context); + } + + /** + * Detailed debug information. + * + * @param string $message + * @param array $context + * + * @return void + */ + public function debug($message, array $context = array()) + { + $this->log(LogLevel::DEBUG, $message, $context); + } + + /** + * Logs with an arbitrary level. + * + * @param mixed $level + * @param string $message + * @param array $context + * + * @return void + * + * @throws \Psr\Log\InvalidArgumentException + */ + abstract public function log($level, $message, array $context = array()); +} diff --git a/kirby/vendor/psr/log/Psr/Log/NullLogger.php b/kirby/vendor/psr/log/Psr/Log/NullLogger.php new file mode 100644 index 0000000..c8f7293 --- /dev/null +++ b/kirby/vendor/psr/log/Psr/Log/NullLogger.php @@ -0,0 +1,30 @@ +logger) { }` + * blocks. + */ +class NullLogger extends AbstractLogger +{ + /** + * Logs with an arbitrary level. + * + * @param mixed $level + * @param string $message + * @param array $context + * + * @return void + * + * @throws \Psr\Log\InvalidArgumentException + */ + public function log($level, $message, array $context = array()) + { + // noop + } +} diff --git a/kirby/vendor/psr/log/composer.json b/kirby/vendor/psr/log/composer.json new file mode 100644 index 0000000..ca05695 --- /dev/null +++ b/kirby/vendor/psr/log/composer.json @@ -0,0 +1,26 @@ +{ + "name": "psr/log", + "description": "Common interface for logging libraries", + "keywords": ["psr", "psr-3", "log"], + "homepage": "https://github.com/php-fig/log", + "license": "MIT", + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "require": { + "php": ">=5.3.0" + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "Psr/Log/" + } + }, + "extra": { + "branch-alias": { + "dev-master": "1.1.x-dev" + } + } +} diff --git a/kirby/vendor/symfony/polyfill-mbstring/LICENSE b/kirby/vendor/symfony/polyfill-mbstring/LICENSE new file mode 100644 index 0000000..4cd8bdd --- /dev/null +++ b/kirby/vendor/symfony/polyfill-mbstring/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2015-2019 Fabien Potencier + +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/kirby/vendor/symfony/polyfill-mbstring/Mbstring.php b/kirby/vendor/symfony/polyfill-mbstring/Mbstring.php new file mode 100644 index 0000000..c31611f --- /dev/null +++ b/kirby/vendor/symfony/polyfill-mbstring/Mbstring.php @@ -0,0 +1,869 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Polyfill\Mbstring; + +/** + * Partial mbstring implementation in PHP, iconv based, UTF-8 centric. + * + * Implemented: + * - mb_chr - Returns a specific character from its Unicode code point + * - mb_convert_encoding - Convert character encoding + * - mb_convert_variables - Convert character code in variable(s) + * - mb_decode_mimeheader - Decode string in MIME header field + * - mb_encode_mimeheader - Encode string for MIME header XXX NATIVE IMPLEMENTATION IS REALLY BUGGED + * - mb_decode_numericentity - Decode HTML numeric string reference to character + * - mb_encode_numericentity - Encode character to HTML numeric string reference + * - mb_convert_case - Perform case folding on a string + * - mb_detect_encoding - Detect character encoding + * - mb_get_info - Get internal settings of mbstring + * - mb_http_input - Detect HTTP input character encoding + * - mb_http_output - Set/Get HTTP output character encoding + * - mb_internal_encoding - Set/Get internal character encoding + * - mb_list_encodings - Returns an array of all supported encodings + * - mb_ord - Returns the Unicode code point of a character + * - mb_output_handler - Callback function converts character encoding in output buffer + * - mb_scrub - Replaces ill-formed byte sequences with substitute characters + * - mb_strlen - Get string length + * - mb_strpos - Find position of first occurrence of string in a string + * - mb_strrpos - Find position of last occurrence of a string in a string + * - mb_str_split - Convert a string to an array + * - mb_strtolower - Make a string lowercase + * - mb_strtoupper - Make a string uppercase + * - mb_substitute_character - Set/Get substitution character + * - mb_substr - Get part of string + * - mb_stripos - Finds position of first occurrence of a string within another, case insensitive + * - mb_stristr - Finds first occurrence of a string within another, case insensitive + * - mb_strrchr - Finds the last occurrence of a character in a string within another + * - mb_strrichr - Finds the last occurrence of a character in a string within another, case insensitive + * - mb_strripos - Finds position of last occurrence of a string within another, case insensitive + * - mb_strstr - Finds first occurrence of a string within another + * - mb_strwidth - Return width of string + * - mb_substr_count - Count the number of substring occurrences + * + * Not implemented: + * - mb_convert_kana - Convert "kana" one from another ("zen-kaku", "han-kaku" and more) + * - mb_ereg_* - Regular expression with multibyte support + * - mb_parse_str - Parse GET/POST/COOKIE data and set global variable + * - mb_preferred_mime_name - Get MIME charset string + * - mb_regex_encoding - Returns current encoding for multibyte regex as string + * - mb_regex_set_options - Set/Get the default options for mbregex functions + * - mb_send_mail - Send encoded mail + * - mb_split - Split multibyte string using regular expression + * - mb_strcut - Get part of string + * - mb_strimwidth - Get truncated string with specified width + * + * @author Nicolas Grekas + * + * @internal + */ +final class Mbstring +{ + public const MB_CASE_FOLD = \PHP_INT_MAX; + + private static $encodingList = ['ASCII', 'UTF-8']; + private static $language = 'neutral'; + private static $internalEncoding = 'UTF-8'; + private static $caseFold = [ + ['µ', 'ſ', "\xCD\x85", 'ς', "\xCF\x90", "\xCF\x91", "\xCF\x95", "\xCF\x96", "\xCF\xB0", "\xCF\xB1", "\xCF\xB5", "\xE1\xBA\x9B", "\xE1\xBE\xBE"], + ['μ', 's', 'ι', 'σ', 'β', 'θ', 'φ', 'π', 'κ', 'ρ', 'ε', "\xE1\xB9\xA1", 'ι'], + ]; + + public static function mb_convert_encoding($s, $toEncoding, $fromEncoding = null) + { + if (\is_array($fromEncoding) || false !== strpos($fromEncoding, ',')) { + $fromEncoding = self::mb_detect_encoding($s, $fromEncoding); + } else { + $fromEncoding = self::getEncoding($fromEncoding); + } + + $toEncoding = self::getEncoding($toEncoding); + + if ('BASE64' === $fromEncoding) { + $s = base64_decode($s); + $fromEncoding = $toEncoding; + } + + if ('BASE64' === $toEncoding) { + return base64_encode($s); + } + + if ('HTML-ENTITIES' === $toEncoding || 'HTML' === $toEncoding) { + if ('HTML-ENTITIES' === $fromEncoding || 'HTML' === $fromEncoding) { + $fromEncoding = 'Windows-1252'; + } + if ('UTF-8' !== $fromEncoding) { + $s = \iconv($fromEncoding, 'UTF-8//IGNORE', $s); + } + + return preg_replace_callback('/[\x80-\xFF]+/', [__CLASS__, 'html_encoding_callback'], $s); + } + + if ('HTML-ENTITIES' === $fromEncoding) { + $s = html_entity_decode($s, \ENT_COMPAT, 'UTF-8'); + $fromEncoding = 'UTF-8'; + } + + return \iconv($fromEncoding, $toEncoding.'//IGNORE', $s); + } + + public static function mb_convert_variables($toEncoding, $fromEncoding, &...$vars) + { + $ok = true; + array_walk_recursive($vars, function (&$v) use (&$ok, $toEncoding, $fromEncoding) { + if (false === $v = self::mb_convert_encoding($v, $toEncoding, $fromEncoding)) { + $ok = false; + } + }); + + return $ok ? $fromEncoding : false; + } + + public static function mb_decode_mimeheader($s) + { + return \iconv_mime_decode($s, 2, self::$internalEncoding); + } + + public static function mb_encode_mimeheader($s, $charset = null, $transferEncoding = null, $linefeed = null, $indent = null) + { + trigger_error('mb_encode_mimeheader() is bugged. Please use iconv_mime_encode() instead', \E_USER_WARNING); + } + + public static function mb_decode_numericentity($s, $convmap, $encoding = null) + { + if (null !== $s && !is_scalar($s) && !(\is_object($s) && method_exists($s, '__toString'))) { + trigger_error('mb_decode_numericentity() expects parameter 1 to be string, '.\gettype($s).' given', \E_USER_WARNING); + + return null; + } + + if (!\is_array($convmap) || (80000 > \PHP_VERSION_ID && !$convmap)) { + return false; + } + + if (null !== $encoding && !is_scalar($encoding)) { + trigger_error('mb_decode_numericentity() expects parameter 3 to be string, '.\gettype($s).' given', \E_USER_WARNING); + + return ''; // Instead of null (cf. mb_encode_numericentity). + } + + $s = (string) $s; + if ('' === $s) { + return ''; + } + + $encoding = self::getEncoding($encoding); + + if ('UTF-8' === $encoding) { + $encoding = null; + if (!preg_match('//u', $s)) { + $s = @\iconv('UTF-8', 'UTF-8//IGNORE', $s); + } + } else { + $s = \iconv($encoding, 'UTF-8//IGNORE', $s); + } + + $cnt = floor(\count($convmap) / 4) * 4; + + for ($i = 0; $i < $cnt; $i += 4) { + // collector_decode_htmlnumericentity ignores $convmap[$i + 3] + $convmap[$i] += $convmap[$i + 2]; + $convmap[$i + 1] += $convmap[$i + 2]; + } + + $s = preg_replace_callback('/&#(?:0*([0-9]+)|x0*([0-9a-fA-F]+))(?!&);?/', function (array $m) use ($cnt, $convmap) { + $c = isset($m[2]) ? (int) hexdec($m[2]) : $m[1]; + for ($i = 0; $i < $cnt; $i += 4) { + if ($c >= $convmap[$i] && $c <= $convmap[$i + 1]) { + return self::mb_chr($c - $convmap[$i + 2]); + } + } + + return $m[0]; + }, $s); + + if (null === $encoding) { + return $s; + } + + return \iconv('UTF-8', $encoding.'//IGNORE', $s); + } + + public static function mb_encode_numericentity($s, $convmap, $encoding = null, $is_hex = false) + { + if (null !== $s && !is_scalar($s) && !(\is_object($s) && method_exists($s, '__toString'))) { + trigger_error('mb_encode_numericentity() expects parameter 1 to be string, '.\gettype($s).' given', \E_USER_WARNING); + + return null; + } + + if (!\is_array($convmap) || (80000 > \PHP_VERSION_ID && !$convmap)) { + return false; + } + + if (null !== $encoding && !is_scalar($encoding)) { + trigger_error('mb_encode_numericentity() expects parameter 3 to be string, '.\gettype($s).' given', \E_USER_WARNING); + + return null; // Instead of '' (cf. mb_decode_numericentity). + } + + if (null !== $is_hex && !is_scalar($is_hex)) { + trigger_error('mb_encode_numericentity() expects parameter 4 to be boolean, '.\gettype($s).' given', \E_USER_WARNING); + + return null; + } + + $s = (string) $s; + if ('' === $s) { + return ''; + } + + $encoding = self::getEncoding($encoding); + + if ('UTF-8' === $encoding) { + $encoding = null; + if (!preg_match('//u', $s)) { + $s = @\iconv('UTF-8', 'UTF-8//IGNORE', $s); + } + } else { + $s = \iconv($encoding, 'UTF-8//IGNORE', $s); + } + + static $ulenMask = ["\xC0" => 2, "\xD0" => 2, "\xE0" => 3, "\xF0" => 4]; + + $cnt = floor(\count($convmap) / 4) * 4; + $i = 0; + $len = \strlen($s); + $result = ''; + + while ($i < $len) { + $ulen = $s[$i] < "\x80" ? 1 : $ulenMask[$s[$i] & "\xF0"]; + $uchr = substr($s, $i, $ulen); + $i += $ulen; + $c = self::mb_ord($uchr); + + for ($j = 0; $j < $cnt; $j += 4) { + if ($c >= $convmap[$j] && $c <= $convmap[$j + 1]) { + $cOffset = ($c + $convmap[$j + 2]) & $convmap[$j + 3]; + $result .= $is_hex ? sprintf('&#x%X;', $cOffset) : '&#'.$cOffset.';'; + continue 2; + } + } + $result .= $uchr; + } + + if (null === $encoding) { + return $result; + } + + return \iconv('UTF-8', $encoding.'//IGNORE', $result); + } + + public static function mb_convert_case($s, $mode, $encoding = null) + { + $s = (string) $s; + if ('' === $s) { + return ''; + } + + $encoding = self::getEncoding($encoding); + + if ('UTF-8' === $encoding) { + $encoding = null; + if (!preg_match('//u', $s)) { + $s = @\iconv('UTF-8', 'UTF-8//IGNORE', $s); + } + } else { + $s = \iconv($encoding, 'UTF-8//IGNORE', $s); + } + + if (\MB_CASE_TITLE == $mode) { + static $titleRegexp = null; + if (null === $titleRegexp) { + $titleRegexp = self::getData('titleCaseRegexp'); + } + $s = preg_replace_callback($titleRegexp, [__CLASS__, 'title_case'], $s); + } else { + if (\MB_CASE_UPPER == $mode) { + static $upper = null; + if (null === $upper) { + $upper = self::getData('upperCase'); + } + $map = $upper; + } else { + if (self::MB_CASE_FOLD === $mode) { + $s = str_replace(self::$caseFold[0], self::$caseFold[1], $s); + } + + static $lower = null; + if (null === $lower) { + $lower = self::getData('lowerCase'); + } + $map = $lower; + } + + static $ulenMask = ["\xC0" => 2, "\xD0" => 2, "\xE0" => 3, "\xF0" => 4]; + + $i = 0; + $len = \strlen($s); + + while ($i < $len) { + $ulen = $s[$i] < "\x80" ? 1 : $ulenMask[$s[$i] & "\xF0"]; + $uchr = substr($s, $i, $ulen); + $i += $ulen; + + if (isset($map[$uchr])) { + $uchr = $map[$uchr]; + $nlen = \strlen($uchr); + + if ($nlen == $ulen) { + $nlen = $i; + do { + $s[--$nlen] = $uchr[--$ulen]; + } while ($ulen); + } else { + $s = substr_replace($s, $uchr, $i - $ulen, $ulen); + $len += $nlen - $ulen; + $i += $nlen - $ulen; + } + } + } + } + + if (null === $encoding) { + return $s; + } + + return \iconv('UTF-8', $encoding.'//IGNORE', $s); + } + + public static function mb_internal_encoding($encoding = null) + { + if (null === $encoding) { + return self::$internalEncoding; + } + + $normalizedEncoding = self::getEncoding($encoding); + + if ('UTF-8' === $normalizedEncoding || false !== @\iconv($normalizedEncoding, $normalizedEncoding, ' ')) { + self::$internalEncoding = $normalizedEncoding; + + return true; + } + + if (80000 > \PHP_VERSION_ID) { + return false; + } + + throw new \ValueError(sprintf('Argument #1 ($encoding) must be a valid encoding, "%s" given', $encoding)); + } + + public static function mb_language($lang = null) + { + if (null === $lang) { + return self::$language; + } + + switch ($normalizedLang = strtolower($lang)) { + case 'uni': + case 'neutral': + self::$language = $normalizedLang; + + return true; + } + + if (80000 > \PHP_VERSION_ID) { + return false; + } + + throw new \ValueError(sprintf('Argument #1 ($language) must be a valid language, "%s" given', $lang)); + } + + public static function mb_list_encodings() + { + return ['UTF-8']; + } + + public static function mb_encoding_aliases($encoding) + { + switch (strtoupper($encoding)) { + case 'UTF8': + case 'UTF-8': + return ['utf8']; + } + + return false; + } + + public static function mb_check_encoding($var = null, $encoding = null) + { + if (null === $encoding) { + if (null === $var) { + return false; + } + $encoding = self::$internalEncoding; + } + + return self::mb_detect_encoding($var, [$encoding]) || false !== @\iconv($encoding, $encoding, $var); + } + + public static function mb_detect_encoding($str, $encodingList = null, $strict = false) + { + if (null === $encodingList) { + $encodingList = self::$encodingList; + } else { + if (!\is_array($encodingList)) { + $encodingList = array_map('trim', explode(',', $encodingList)); + } + $encodingList = array_map('strtoupper', $encodingList); + } + + foreach ($encodingList as $enc) { + switch ($enc) { + case 'ASCII': + if (!preg_match('/[\x80-\xFF]/', $str)) { + return $enc; + } + break; + + case 'UTF8': + case 'UTF-8': + if (preg_match('//u', $str)) { + return 'UTF-8'; + } + break; + + default: + if (0 === strncmp($enc, 'ISO-8859-', 9)) { + return $enc; + } + } + } + + return false; + } + + public static function mb_detect_order($encodingList = null) + { + if (null === $encodingList) { + return self::$encodingList; + } + + if (!\is_array($encodingList)) { + $encodingList = array_map('trim', explode(',', $encodingList)); + } + $encodingList = array_map('strtoupper', $encodingList); + + foreach ($encodingList as $enc) { + switch ($enc) { + default: + if (strncmp($enc, 'ISO-8859-', 9)) { + return false; + } + // no break + case 'ASCII': + case 'UTF8': + case 'UTF-8': + } + } + + self::$encodingList = $encodingList; + + return true; + } + + public static function mb_strlen($s, $encoding = null) + { + $encoding = self::getEncoding($encoding); + if ('CP850' === $encoding || 'ASCII' === $encoding) { + return \strlen($s); + } + + return @\iconv_strlen($s, $encoding); + } + + public static function mb_strpos($haystack, $needle, $offset = 0, $encoding = null) + { + $encoding = self::getEncoding($encoding); + if ('CP850' === $encoding || 'ASCII' === $encoding) { + return strpos($haystack, $needle, $offset); + } + + $needle = (string) $needle; + if ('' === $needle) { + if (80000 > \PHP_VERSION_ID) { + trigger_error(__METHOD__.': Empty delimiter', \E_USER_WARNING); + + return false; + } + + return 0; + } + + return \iconv_strpos($haystack, $needle, $offset, $encoding); + } + + public static function mb_strrpos($haystack, $needle, $offset = 0, $encoding = null) + { + $encoding = self::getEncoding($encoding); + if ('CP850' === $encoding || 'ASCII' === $encoding) { + return strrpos($haystack, $needle, $offset); + } + + if ($offset != (int) $offset) { + $offset = 0; + } elseif ($offset = (int) $offset) { + if ($offset < 0) { + if (0 > $offset += self::mb_strlen($needle)) { + $haystack = self::mb_substr($haystack, 0, $offset, $encoding); + } + $offset = 0; + } else { + $haystack = self::mb_substr($haystack, $offset, 2147483647, $encoding); + } + } + + $pos = '' !== $needle || 80000 > \PHP_VERSION_ID + ? \iconv_strrpos($haystack, $needle, $encoding) + : self::mb_strlen($haystack, $encoding); + + return false !== $pos ? $offset + $pos : false; + } + + public static function mb_str_split($string, $split_length = 1, $encoding = null) + { + if (null !== $string && !is_scalar($string) && !(\is_object($string) && method_exists($string, '__toString'))) { + trigger_error('mb_str_split() expects parameter 1 to be string, '.\gettype($string).' given', \E_USER_WARNING); + + return null; + } + + if (1 > $split_length = (int) $split_length) { + if (80000 > \PHP_VERSION_ID) { + trigger_error('The length of each segment must be greater than zero', \E_USER_WARNING); + return false; + } + + throw new \ValueError('Argument #2 ($length) must be greater than 0'); + } + + if (null === $encoding) { + $encoding = mb_internal_encoding(); + } + + if ('UTF-8' === $encoding = self::getEncoding($encoding)) { + $rx = '/('; + while (65535 < $split_length) { + $rx .= '.{65535}'; + $split_length -= 65535; + } + $rx .= '.{'.$split_length.'})/us'; + + return preg_split($rx, $string, null, \PREG_SPLIT_DELIM_CAPTURE | \PREG_SPLIT_NO_EMPTY); + } + + $result = []; + $length = mb_strlen($string, $encoding); + + for ($i = 0; $i < $length; $i += $split_length) { + $result[] = mb_substr($string, $i, $split_length, $encoding); + } + + return $result; + } + + public static function mb_strtolower($s, $encoding = null) + { + return self::mb_convert_case($s, \MB_CASE_LOWER, $encoding); + } + + public static function mb_strtoupper($s, $encoding = null) + { + return self::mb_convert_case($s, \MB_CASE_UPPER, $encoding); + } + + public static function mb_substitute_character($c = null) + { + if (null === $c) { + return 'none'; + } + if (0 === strcasecmp($c, 'none')) { + return true; + } + if (80000 > \PHP_VERSION_ID) { + return false; + } + + throw new \ValueError('Argument #1 ($substitute_character) must be "none", "long", "entity" or a valid codepoint'); + } + + public static function mb_substr($s, $start, $length = null, $encoding = null) + { + $encoding = self::getEncoding($encoding); + if ('CP850' === $encoding || 'ASCII' === $encoding) { + return (string) substr($s, $start, null === $length ? 2147483647 : $length); + } + + if ($start < 0) { + $start = \iconv_strlen($s, $encoding) + $start; + if ($start < 0) { + $start = 0; + } + } + + if (null === $length) { + $length = 2147483647; + } elseif ($length < 0) { + $length = \iconv_strlen($s, $encoding) + $length - $start; + if ($length < 0) { + return ''; + } + } + + return (string) \iconv_substr($s, $start, $length, $encoding); + } + + public static function mb_stripos($haystack, $needle, $offset = 0, $encoding = null) + { + $haystack = self::mb_convert_case($haystack, self::MB_CASE_FOLD, $encoding); + $needle = self::mb_convert_case($needle, self::MB_CASE_FOLD, $encoding); + + return self::mb_strpos($haystack, $needle, $offset, $encoding); + } + + public static function mb_stristr($haystack, $needle, $part = false, $encoding = null) + { + $pos = self::mb_stripos($haystack, $needle, 0, $encoding); + + return self::getSubpart($pos, $part, $haystack, $encoding); + } + + public static function mb_strrchr($haystack, $needle, $part = false, $encoding = null) + { + $encoding = self::getEncoding($encoding); + if ('CP850' === $encoding || 'ASCII' === $encoding) { + $pos = strrpos($haystack, $needle); + } else { + $needle = self::mb_substr($needle, 0, 1, $encoding); + $pos = \iconv_strrpos($haystack, $needle, $encoding); + } + + return self::getSubpart($pos, $part, $haystack, $encoding); + } + + public static function mb_strrichr($haystack, $needle, $part = false, $encoding = null) + { + $needle = self::mb_substr($needle, 0, 1, $encoding); + $pos = self::mb_strripos($haystack, $needle, $encoding); + + return self::getSubpart($pos, $part, $haystack, $encoding); + } + + public static function mb_strripos($haystack, $needle, $offset = 0, $encoding = null) + { + $haystack = self::mb_convert_case($haystack, self::MB_CASE_FOLD, $encoding); + $needle = self::mb_convert_case($needle, self::MB_CASE_FOLD, $encoding); + + return self::mb_strrpos($haystack, $needle, $offset, $encoding); + } + + public static function mb_strstr($haystack, $needle, $part = false, $encoding = null) + { + $pos = strpos($haystack, $needle); + if (false === $pos) { + return false; + } + if ($part) { + return substr($haystack, 0, $pos); + } + + return substr($haystack, $pos); + } + + public static function mb_get_info($type = 'all') + { + $info = [ + 'internal_encoding' => self::$internalEncoding, + 'http_output' => 'pass', + 'http_output_conv_mimetypes' => '^(text/|application/xhtml\+xml)', + 'func_overload' => 0, + 'func_overload_list' => 'no overload', + 'mail_charset' => 'UTF-8', + 'mail_header_encoding' => 'BASE64', + 'mail_body_encoding' => 'BASE64', + 'illegal_chars' => 0, + 'encoding_translation' => 'Off', + 'language' => self::$language, + 'detect_order' => self::$encodingList, + 'substitute_character' => 'none', + 'strict_detection' => 'Off', + ]; + + if ('all' === $type) { + return $info; + } + if (isset($info[$type])) { + return $info[$type]; + } + + return false; + } + + public static function mb_http_input($type = '') + { + return false; + } + + public static function mb_http_output($encoding = null) + { + return null !== $encoding ? 'pass' === $encoding : 'pass'; + } + + public static function mb_strwidth($s, $encoding = null) + { + $encoding = self::getEncoding($encoding); + + if ('UTF-8' !== $encoding) { + $s = \iconv($encoding, 'UTF-8//IGNORE', $s); + } + + $s = preg_replace('/[\x{1100}-\x{115F}\x{2329}\x{232A}\x{2E80}-\x{303E}\x{3040}-\x{A4CF}\x{AC00}-\x{D7A3}\x{F900}-\x{FAFF}\x{FE10}-\x{FE19}\x{FE30}-\x{FE6F}\x{FF00}-\x{FF60}\x{FFE0}-\x{FFE6}\x{20000}-\x{2FFFD}\x{30000}-\x{3FFFD}]/u', '', $s, -1, $wide); + + return ($wide << 1) + \iconv_strlen($s, 'UTF-8'); + } + + public static function mb_substr_count($haystack, $needle, $encoding = null) + { + return substr_count($haystack, $needle); + } + + public static function mb_output_handler($contents, $status) + { + return $contents; + } + + public static function mb_chr($code, $encoding = null) + { + if (0x80 > $code %= 0x200000) { + $s = \chr($code); + } elseif (0x800 > $code) { + $s = \chr(0xC0 | $code >> 6).\chr(0x80 | $code & 0x3F); + } elseif (0x10000 > $code) { + $s = \chr(0xE0 | $code >> 12).\chr(0x80 | $code >> 6 & 0x3F).\chr(0x80 | $code & 0x3F); + } else { + $s = \chr(0xF0 | $code >> 18).\chr(0x80 | $code >> 12 & 0x3F).\chr(0x80 | $code >> 6 & 0x3F).\chr(0x80 | $code & 0x3F); + } + + if ('UTF-8' !== $encoding = self::getEncoding($encoding)) { + $s = mb_convert_encoding($s, $encoding, 'UTF-8'); + } + + return $s; + } + + public static function mb_ord($s, $encoding = null) + { + if ('UTF-8' !== $encoding = self::getEncoding($encoding)) { + $s = mb_convert_encoding($s, 'UTF-8', $encoding); + } + + if (1 === \strlen($s)) { + return \ord($s); + } + + $code = ($s = unpack('C*', substr($s, 0, 4))) ? $s[1] : 0; + if (0xF0 <= $code) { + return (($code - 0xF0) << 18) + (($s[2] - 0x80) << 12) + (($s[3] - 0x80) << 6) + $s[4] - 0x80; + } + if (0xE0 <= $code) { + return (($code - 0xE0) << 12) + (($s[2] - 0x80) << 6) + $s[3] - 0x80; + } + if (0xC0 <= $code) { + return (($code - 0xC0) << 6) + $s[2] - 0x80; + } + + return $code; + } + + private static function getSubpart($pos, $part, $haystack, $encoding) + { + if (false === $pos) { + return false; + } + if ($part) { + return self::mb_substr($haystack, 0, $pos, $encoding); + } + + return self::mb_substr($haystack, $pos, null, $encoding); + } + + private static function html_encoding_callback(array $m) + { + $i = 1; + $entities = ''; + $m = unpack('C*', htmlentities($m[0], \ENT_COMPAT, 'UTF-8')); + + while (isset($m[$i])) { + if (0x80 > $m[$i]) { + $entities .= \chr($m[$i++]); + continue; + } + if (0xF0 <= $m[$i]) { + $c = (($m[$i++] - 0xF0) << 18) + (($m[$i++] - 0x80) << 12) + (($m[$i++] - 0x80) << 6) + $m[$i++] - 0x80; + } elseif (0xE0 <= $m[$i]) { + $c = (($m[$i++] - 0xE0) << 12) + (($m[$i++] - 0x80) << 6) + $m[$i++] - 0x80; + } else { + $c = (($m[$i++] - 0xC0) << 6) + $m[$i++] - 0x80; + } + + $entities .= '&#'.$c.';'; + } + + return $entities; + } + + private static function title_case(array $s) + { + return self::mb_convert_case($s[1], \MB_CASE_UPPER, 'UTF-8').self::mb_convert_case($s[2], \MB_CASE_LOWER, 'UTF-8'); + } + + private static function getData($file) + { + if (file_exists($file = __DIR__.'/Resources/unidata/'.$file.'.php')) { + return require $file; + } + + return false; + } + + private static function getEncoding($encoding) + { + if (null === $encoding) { + return self::$internalEncoding; + } + + if ('UTF-8' === $encoding) { + return 'UTF-8'; + } + + $encoding = strtoupper($encoding); + + if ('8BIT' === $encoding || 'BINARY' === $encoding) { + return 'CP850'; + } + + if ('UTF8' === $encoding) { + return 'UTF-8'; + } + + return $encoding; + } +} diff --git a/kirby/vendor/symfony/polyfill-mbstring/Resources/unidata/lowerCase.php b/kirby/vendor/symfony/polyfill-mbstring/Resources/unidata/lowerCase.php new file mode 100644 index 0000000..fac60b0 --- /dev/null +++ b/kirby/vendor/symfony/polyfill-mbstring/Resources/unidata/lowerCase.php @@ -0,0 +1,1397 @@ + 'a', + 'B' => 'b', + 'C' => 'c', + 'D' => 'd', + 'E' => 'e', + 'F' => 'f', + 'G' => 'g', + 'H' => 'h', + 'I' => 'i', + 'J' => 'j', + 'K' => 'k', + 'L' => 'l', + 'M' => 'm', + 'N' => 'n', + 'O' => 'o', + 'P' => 'p', + 'Q' => 'q', + 'R' => 'r', + 'S' => 's', + 'T' => 't', + 'U' => 'u', + 'V' => 'v', + 'W' => 'w', + 'X' => 'x', + 'Y' => 'y', + 'Z' => 'z', + 'À' => 'à', + 'Á' => 'á', + 'Â' => 'â', + 'Ã' => 'ã', + 'Ä' => 'ä', + 'Å' => 'å', + 'Æ' => 'æ', + 'Ç' => 'ç', + 'È' => 'è', + 'É' => 'é', + 'Ê' => 'ê', + 'Ë' => 'ë', + 'Ì' => 'ì', + 'Í' => 'í', + 'Î' => 'î', + 'Ï' => 'ï', + 'Ð' => 'ð', + 'Ñ' => 'ñ', + 'Ò' => 'ò', + 'Ó' => 'ó', + 'Ô' => 'ô', + 'Õ' => 'õ', + 'Ö' => 'ö', + 'Ø' => 'ø', + 'Ù' => 'ù', + 'Ú' => 'ú', + 'Û' => 'û', + 'Ü' => 'ü', + 'Ý' => 'ý', + 'Þ' => 'þ', + 'Ā' => 'ā', + 'Ă' => 'ă', + 'Ą' => 'ą', + 'Ć' => 'ć', + 'Ĉ' => 'ĉ', + 'Ċ' => 'ċ', + 'Č' => 'č', + 'Ď' => 'ď', + 'Đ' => 'đ', + 'Ē' => 'ē', + 'Ĕ' => 'ĕ', + 'Ė' => 'ė', + 'Ę' => 'ę', + 'Ě' => 'ě', + 'Ĝ' => 'ĝ', + 'Ğ' => 'ğ', + 'Ġ' => 'ġ', + 'Ģ' => 'ģ', + 'Ĥ' => 'ĥ', + 'Ħ' => 'ħ', + 'Ĩ' => 'ĩ', + 'Ī' => 'ī', + 'Ĭ' => 'ĭ', + 'Į' => 'į', + 'İ' => 'i̇', + 'IJ' => 'ij', + 'Ĵ' => 'ĵ', + 'Ķ' => 'ķ', + 'Ĺ' => 'ĺ', + 'Ļ' => 'ļ', + 'Ľ' => 'ľ', + 'Ŀ' => 'ŀ', + 'Ł' => 'ł', + 'Ń' => 'ń', + 'Ņ' => 'ņ', + 'Ň' => 'ň', + 'Ŋ' => 'ŋ', + 'Ō' => 'ō', + 'Ŏ' => 'ŏ', + 'Ő' => 'ő', + 'Œ' => 'œ', + 'Ŕ' => 'ŕ', + 'Ŗ' => 'ŗ', + 'Ř' => 'ř', + 'Ś' => 'ś', + 'Ŝ' => 'ŝ', + 'Ş' => 'ş', + 'Š' => 'š', + 'Ţ' => 'ţ', + 'Ť' => 'ť', + 'Ŧ' => 'ŧ', + 'Ũ' => 'ũ', + 'Ū' => 'ū', + 'Ŭ' => 'ŭ', + 'Ů' => 'ů', + 'Ű' => 'ű', + 'Ų' => 'ų', + 'Ŵ' => 'ŵ', + 'Ŷ' => 'ŷ', + 'Ÿ' => 'ÿ', + 'Ź' => 'ź', + 'Ż' => 'ż', + 'Ž' => 'ž', + 'Ɓ' => 'ɓ', + 'Ƃ' => 'ƃ', + 'Ƅ' => 'ƅ', + 'Ɔ' => 'ɔ', + 'Ƈ' => 'ƈ', + 'Ɖ' => 'ɖ', + 'Ɗ' => 'ɗ', + 'Ƌ' => 'ƌ', + 'Ǝ' => 'ǝ', + 'Ə' => 'ə', + 'Ɛ' => 'ɛ', + 'Ƒ' => 'ƒ', + 'Ɠ' => 'ɠ', + 'Ɣ' => 'ɣ', + 'Ɩ' => 'ɩ', + 'Ɨ' => 'ɨ', + 'Ƙ' => 'ƙ', + 'Ɯ' => 'ɯ', + 'Ɲ' => 'ɲ', + 'Ɵ' => 'ɵ', + 'Ơ' => 'ơ', + 'Ƣ' => 'ƣ', + 'Ƥ' => 'ƥ', + 'Ʀ' => 'ʀ', + 'Ƨ' => 'ƨ', + 'Ʃ' => 'ʃ', + 'Ƭ' => 'ƭ', + 'Ʈ' => 'ʈ', + 'Ư' => 'ư', + 'Ʊ' => 'ʊ', + 'Ʋ' => 'ʋ', + 'Ƴ' => 'ƴ', + 'Ƶ' => 'ƶ', + 'Ʒ' => 'ʒ', + 'Ƹ' => 'ƹ', + 'Ƽ' => 'ƽ', + 'DŽ' => 'dž', + 'Dž' => 'dž', + 'LJ' => 'lj', + 'Lj' => 'lj', + 'NJ' => 'nj', + 'Nj' => 'nj', + 'Ǎ' => 'ǎ', + 'Ǐ' => 'ǐ', + 'Ǒ' => 'ǒ', + 'Ǔ' => 'ǔ', + 'Ǖ' => 'ǖ', + 'Ǘ' => 'ǘ', + 'Ǚ' => 'ǚ', + 'Ǜ' => 'ǜ', + 'Ǟ' => 'ǟ', + 'Ǡ' => 'ǡ', + 'Ǣ' => 'ǣ', + 'Ǥ' => 'ǥ', + 'Ǧ' => 'ǧ', + 'Ǩ' => 'ǩ', + 'Ǫ' => 'ǫ', + 'Ǭ' => 'ǭ', + 'Ǯ' => 'ǯ', + 'DZ' => 'dz', + 'Dz' => 'dz', + 'Ǵ' => 'ǵ', + 'Ƕ' => 'ƕ', + 'Ƿ' => 'ƿ', + 'Ǹ' => 'ǹ', + 'Ǻ' => 'ǻ', + 'Ǽ' => 'ǽ', + 'Ǿ' => 'ǿ', + 'Ȁ' => 'ȁ', + 'Ȃ' => 'ȃ', + 'Ȅ' => 'ȅ', + 'Ȇ' => 'ȇ', + 'Ȉ' => 'ȉ', + 'Ȋ' => 'ȋ', + 'Ȍ' => 'ȍ', + 'Ȏ' => 'ȏ', + 'Ȑ' => 'ȑ', + 'Ȓ' => 'ȓ', + 'Ȕ' => 'ȕ', + 'Ȗ' => 'ȗ', + 'Ș' => 'ș', + 'Ț' => 'ț', + 'Ȝ' => 'ȝ', + 'Ȟ' => 'ȟ', + 'Ƞ' => 'ƞ', + 'Ȣ' => 'ȣ', + 'Ȥ' => 'ȥ', + 'Ȧ' => 'ȧ', + 'Ȩ' => 'ȩ', + 'Ȫ' => 'ȫ', + 'Ȭ' => 'ȭ', + 'Ȯ' => 'ȯ', + 'Ȱ' => 'ȱ', + 'Ȳ' => 'ȳ', + 'Ⱥ' => 'ⱥ', + 'Ȼ' => 'ȼ', + 'Ƚ' => 'ƚ', + 'Ⱦ' => 'ⱦ', + 'Ɂ' => 'ɂ', + 'Ƀ' => 'ƀ', + 'Ʉ' => 'ʉ', + 'Ʌ' => 'ʌ', + 'Ɇ' => 'ɇ', + 'Ɉ' => 'ɉ', + 'Ɋ' => 'ɋ', + 'Ɍ' => 'ɍ', + 'Ɏ' => 'ɏ', + 'Ͱ' => 'ͱ', + 'Ͳ' => 'ͳ', + 'Ͷ' => 'ͷ', + 'Ϳ' => 'ϳ', + 'Ά' => 'ά', + 'Έ' => 'έ', + 'Ή' => 'ή', + 'Ί' => 'ί', + 'Ό' => 'ό', + 'Ύ' => 'ύ', + 'Ώ' => 'ώ', + 'Α' => 'α', + 'Β' => 'β', + 'Γ' => 'γ', + 'Δ' => 'δ', + 'Ε' => 'ε', + 'Ζ' => 'ζ', + 'Η' => 'η', + 'Θ' => 'θ', + 'Ι' => 'ι', + 'Κ' => 'κ', + 'Λ' => 'λ', + 'Μ' => 'μ', + 'Ν' => 'ν', + 'Ξ' => 'ξ', + 'Ο' => 'ο', + 'Π' => 'π', + 'Ρ' => 'ρ', + 'Σ' => 'σ', + 'Τ' => 'τ', + 'Υ' => 'υ', + 'Φ' => 'φ', + 'Χ' => 'χ', + 'Ψ' => 'ψ', + 'Ω' => 'ω', + 'Ϊ' => 'ϊ', + 'Ϋ' => 'ϋ', + 'Ϗ' => 'ϗ', + 'Ϙ' => 'ϙ', + 'Ϛ' => 'ϛ', + 'Ϝ' => 'ϝ', + 'Ϟ' => 'ϟ', + 'Ϡ' => 'ϡ', + 'Ϣ' => 'ϣ', + 'Ϥ' => 'ϥ', + 'Ϧ' => 'ϧ', + 'Ϩ' => 'ϩ', + 'Ϫ' => 'ϫ', + 'Ϭ' => 'ϭ', + 'Ϯ' => 'ϯ', + 'ϴ' => 'θ', + 'Ϸ' => 'ϸ', + 'Ϲ' => 'ϲ', + 'Ϻ' => 'ϻ', + 'Ͻ' => 'ͻ', + 'Ͼ' => 'ͼ', + 'Ͽ' => 'ͽ', + 'Ѐ' => 'ѐ', + 'Ё' => 'ё', + 'Ђ' => 'ђ', + 'Ѓ' => 'ѓ', + 'Є' => 'є', + 'Ѕ' => 'ѕ', + 'І' => 'і', + 'Ї' => 'ї', + 'Ј' => 'ј', + 'Љ' => 'љ', + 'Њ' => 'њ', + 'Ћ' => 'ћ', + 'Ќ' => 'ќ', + 'Ѝ' => 'ѝ', + 'Ў' => 'ў', + 'Џ' => 'џ', + 'А' => 'а', + 'Б' => 'б', + 'В' => 'в', + 'Г' => 'г', + 'Д' => 'д', + 'Е' => 'е', + 'Ж' => 'ж', + 'З' => 'з', + 'И' => 'и', + 'Й' => 'й', + 'К' => 'к', + 'Л' => 'л', + 'М' => 'м', + 'Н' => 'н', + 'О' => 'о', + 'П' => 'п', + 'Р' => 'р', + 'С' => 'с', + 'Т' => 'т', + 'У' => 'у', + 'Ф' => 'ф', + 'Х' => 'х', + 'Ц' => 'ц', + 'Ч' => 'ч', + 'Ш' => 'ш', + 'Щ' => 'щ', + 'Ъ' => 'ъ', + 'Ы' => 'ы', + 'Ь' => 'ь', + 'Э' => 'э', + 'Ю' => 'ю', + 'Я' => 'я', + 'Ѡ' => 'ѡ', + 'Ѣ' => 'ѣ', + 'Ѥ' => 'ѥ', + 'Ѧ' => 'ѧ', + 'Ѩ' => 'ѩ', + 'Ѫ' => 'ѫ', + 'Ѭ' => 'ѭ', + 'Ѯ' => 'ѯ', + 'Ѱ' => 'ѱ', + 'Ѳ' => 'ѳ', + 'Ѵ' => 'ѵ', + 'Ѷ' => 'ѷ', + 'Ѹ' => 'ѹ', + 'Ѻ' => 'ѻ', + 'Ѽ' => 'ѽ', + 'Ѿ' => 'ѿ', + 'Ҁ' => 'ҁ', + 'Ҋ' => 'ҋ', + 'Ҍ' => 'ҍ', + 'Ҏ' => 'ҏ', + 'Ґ' => 'ґ', + 'Ғ' => 'ғ', + 'Ҕ' => 'ҕ', + 'Җ' => 'җ', + 'Ҙ' => 'ҙ', + 'Қ' => 'қ', + 'Ҝ' => 'ҝ', + 'Ҟ' => 'ҟ', + 'Ҡ' => 'ҡ', + 'Ң' => 'ң', + 'Ҥ' => 'ҥ', + 'Ҧ' => 'ҧ', + 'Ҩ' => 'ҩ', + 'Ҫ' => 'ҫ', + 'Ҭ' => 'ҭ', + 'Ү' => 'ү', + 'Ұ' => 'ұ', + 'Ҳ' => 'ҳ', + 'Ҵ' => 'ҵ', + 'Ҷ' => 'ҷ', + 'Ҹ' => 'ҹ', + 'Һ' => 'һ', + 'Ҽ' => 'ҽ', + 'Ҿ' => 'ҿ', + 'Ӏ' => 'ӏ', + 'Ӂ' => 'ӂ', + 'Ӄ' => 'ӄ', + 'Ӆ' => 'ӆ', + 'Ӈ' => 'ӈ', + 'Ӊ' => 'ӊ', + 'Ӌ' => 'ӌ', + 'Ӎ' => 'ӎ', + 'Ӑ' => 'ӑ', + 'Ӓ' => 'ӓ', + 'Ӕ' => 'ӕ', + 'Ӗ' => 'ӗ', + 'Ә' => 'ә', + 'Ӛ' => 'ӛ', + 'Ӝ' => 'ӝ', + 'Ӟ' => 'ӟ', + 'Ӡ' => 'ӡ', + 'Ӣ' => 'ӣ', + 'Ӥ' => 'ӥ', + 'Ӧ' => 'ӧ', + 'Ө' => 'ө', + 'Ӫ' => 'ӫ', + 'Ӭ' => 'ӭ', + 'Ӯ' => 'ӯ', + 'Ӱ' => 'ӱ', + 'Ӳ' => 'ӳ', + 'Ӵ' => 'ӵ', + 'Ӷ' => 'ӷ', + 'Ӹ' => 'ӹ', + 'Ӻ' => 'ӻ', + 'Ӽ' => 'ӽ', + 'Ӿ' => 'ӿ', + 'Ԁ' => 'ԁ', + 'Ԃ' => 'ԃ', + 'Ԅ' => 'ԅ', + 'Ԇ' => 'ԇ', + 'Ԉ' => 'ԉ', + 'Ԋ' => 'ԋ', + 'Ԍ' => 'ԍ', + 'Ԏ' => 'ԏ', + 'Ԑ' => 'ԑ', + 'Ԓ' => 'ԓ', + 'Ԕ' => 'ԕ', + 'Ԗ' => 'ԗ', + 'Ԙ' => 'ԙ', + 'Ԛ' => 'ԛ', + 'Ԝ' => 'ԝ', + 'Ԟ' => 'ԟ', + 'Ԡ' => 'ԡ', + 'Ԣ' => 'ԣ', + 'Ԥ' => 'ԥ', + 'Ԧ' => 'ԧ', + 'Ԩ' => 'ԩ', + 'Ԫ' => 'ԫ', + 'Ԭ' => 'ԭ', + 'Ԯ' => 'ԯ', + 'Ա' => 'ա', + 'Բ' => 'բ', + 'Գ' => 'գ', + 'Դ' => 'դ', + 'Ե' => 'ե', + 'Զ' => 'զ', + 'Է' => 'է', + 'Ը' => 'ը', + 'Թ' => 'թ', + 'Ժ' => 'ժ', + 'Ի' => 'ի', + 'Լ' => 'լ', + 'Խ' => 'խ', + 'Ծ' => 'ծ', + 'Կ' => 'կ', + 'Հ' => 'հ', + 'Ձ' => 'ձ', + 'Ղ' => 'ղ', + 'Ճ' => 'ճ', + 'Մ' => 'մ', + 'Յ' => 'յ', + 'Ն' => 'ն', + 'Շ' => 'շ', + 'Ո' => 'ո', + 'Չ' => 'չ', + 'Պ' => 'պ', + 'Ջ' => 'ջ', + 'Ռ' => 'ռ', + 'Ս' => 'ս', + 'Վ' => 'վ', + 'Տ' => 'տ', + 'Ր' => 'ր', + 'Ց' => 'ց', + 'Ւ' => 'ւ', + 'Փ' => 'փ', + 'Ք' => 'ք', + 'Օ' => 'օ', + 'Ֆ' => 'ֆ', + 'Ⴀ' => 'ⴀ', + 'Ⴁ' => 'ⴁ', + 'Ⴂ' => 'ⴂ', + 'Ⴃ' => 'ⴃ', + 'Ⴄ' => 'ⴄ', + 'Ⴅ' => 'ⴅ', + 'Ⴆ' => 'ⴆ', + 'Ⴇ' => 'ⴇ', + 'Ⴈ' => 'ⴈ', + 'Ⴉ' => 'ⴉ', + 'Ⴊ' => 'ⴊ', + 'Ⴋ' => 'ⴋ', + 'Ⴌ' => 'ⴌ', + 'Ⴍ' => 'ⴍ', + 'Ⴎ' => 'ⴎ', + 'Ⴏ' => 'ⴏ', + 'Ⴐ' => 'ⴐ', + 'Ⴑ' => 'ⴑ', + 'Ⴒ' => 'ⴒ', + 'Ⴓ' => 'ⴓ', + 'Ⴔ' => 'ⴔ', + 'Ⴕ' => 'ⴕ', + 'Ⴖ' => 'ⴖ', + 'Ⴗ' => 'ⴗ', + 'Ⴘ' => 'ⴘ', + 'Ⴙ' => 'ⴙ', + 'Ⴚ' => 'ⴚ', + 'Ⴛ' => 'ⴛ', + 'Ⴜ' => 'ⴜ', + 'Ⴝ' => 'ⴝ', + 'Ⴞ' => 'ⴞ', + 'Ⴟ' => 'ⴟ', + 'Ⴠ' => 'ⴠ', + 'Ⴡ' => 'ⴡ', + 'Ⴢ' => 'ⴢ', + 'Ⴣ' => 'ⴣ', + 'Ⴤ' => 'ⴤ', + 'Ⴥ' => 'ⴥ', + 'Ⴧ' => 'ⴧ', + 'Ⴭ' => 'ⴭ', + 'Ꭰ' => 'ꭰ', + 'Ꭱ' => 'ꭱ', + 'Ꭲ' => 'ꭲ', + 'Ꭳ' => 'ꭳ', + 'Ꭴ' => 'ꭴ', + 'Ꭵ' => 'ꭵ', + 'Ꭶ' => 'ꭶ', + 'Ꭷ' => 'ꭷ', + 'Ꭸ' => 'ꭸ', + 'Ꭹ' => 'ꭹ', + 'Ꭺ' => 'ꭺ', + 'Ꭻ' => 'ꭻ', + 'Ꭼ' => 'ꭼ', + 'Ꭽ' => 'ꭽ', + 'Ꭾ' => 'ꭾ', + 'Ꭿ' => 'ꭿ', + 'Ꮀ' => 'ꮀ', + 'Ꮁ' => 'ꮁ', + 'Ꮂ' => 'ꮂ', + 'Ꮃ' => 'ꮃ', + 'Ꮄ' => 'ꮄ', + 'Ꮅ' => 'ꮅ', + 'Ꮆ' => 'ꮆ', + 'Ꮇ' => 'ꮇ', + 'Ꮈ' => 'ꮈ', + 'Ꮉ' => 'ꮉ', + 'Ꮊ' => 'ꮊ', + 'Ꮋ' => 'ꮋ', + 'Ꮌ' => 'ꮌ', + 'Ꮍ' => 'ꮍ', + 'Ꮎ' => 'ꮎ', + 'Ꮏ' => 'ꮏ', + 'Ꮐ' => 'ꮐ', + 'Ꮑ' => 'ꮑ', + 'Ꮒ' => 'ꮒ', + 'Ꮓ' => 'ꮓ', + 'Ꮔ' => 'ꮔ', + 'Ꮕ' => 'ꮕ', + 'Ꮖ' => 'ꮖ', + 'Ꮗ' => 'ꮗ', + 'Ꮘ' => 'ꮘ', + 'Ꮙ' => 'ꮙ', + 'Ꮚ' => 'ꮚ', + 'Ꮛ' => 'ꮛ', + 'Ꮜ' => 'ꮜ', + 'Ꮝ' => 'ꮝ', + 'Ꮞ' => 'ꮞ', + 'Ꮟ' => 'ꮟ', + 'Ꮠ' => 'ꮠ', + 'Ꮡ' => 'ꮡ', + 'Ꮢ' => 'ꮢ', + 'Ꮣ' => 'ꮣ', + 'Ꮤ' => 'ꮤ', + 'Ꮥ' => 'ꮥ', + 'Ꮦ' => 'ꮦ', + 'Ꮧ' => 'ꮧ', + 'Ꮨ' => 'ꮨ', + 'Ꮩ' => 'ꮩ', + 'Ꮪ' => 'ꮪ', + 'Ꮫ' => 'ꮫ', + 'Ꮬ' => 'ꮬ', + 'Ꮭ' => 'ꮭ', + 'Ꮮ' => 'ꮮ', + 'Ꮯ' => 'ꮯ', + 'Ꮰ' => 'ꮰ', + 'Ꮱ' => 'ꮱ', + 'Ꮲ' => 'ꮲ', + 'Ꮳ' => 'ꮳ', + 'Ꮴ' => 'ꮴ', + 'Ꮵ' => 'ꮵ', + 'Ꮶ' => 'ꮶ', + 'Ꮷ' => 'ꮷ', + 'Ꮸ' => 'ꮸ', + 'Ꮹ' => 'ꮹ', + 'Ꮺ' => 'ꮺ', + 'Ꮻ' => 'ꮻ', + 'Ꮼ' => 'ꮼ', + 'Ꮽ' => 'ꮽ', + 'Ꮾ' => 'ꮾ', + 'Ꮿ' => 'ꮿ', + 'Ᏸ' => 'ᏸ', + 'Ᏹ' => 'ᏹ', + 'Ᏺ' => 'ᏺ', + 'Ᏻ' => 'ᏻ', + 'Ᏼ' => 'ᏼ', + 'Ᏽ' => 'ᏽ', + 'Ა' => 'ა', + 'Ბ' => 'ბ', + 'Გ' => 'გ', + 'Დ' => 'დ', + 'Ე' => 'ე', + 'Ვ' => 'ვ', + 'Ზ' => 'ზ', + 'Თ' => 'თ', + 'Ი' => 'ი', + 'Კ' => 'კ', + 'Ლ' => 'ლ', + 'Მ' => 'მ', + 'Ნ' => 'ნ', + 'Ო' => 'ო', + 'Პ' => 'პ', + 'Ჟ' => 'ჟ', + 'Რ' => 'რ', + 'Ს' => 'ს', + 'Ტ' => 'ტ', + 'Უ' => 'უ', + 'Ფ' => 'ფ', + 'Ქ' => 'ქ', + 'Ღ' => 'ღ', + 'Ყ' => 'ყ', + 'Შ' => 'შ', + 'Ჩ' => 'ჩ', + 'Ც' => 'ც', + 'Ძ' => 'ძ', + 'Წ' => 'წ', + 'Ჭ' => 'ჭ', + 'Ხ' => 'ხ', + 'Ჯ' => 'ჯ', + 'Ჰ' => 'ჰ', + 'Ჱ' => 'ჱ', + 'Ჲ' => 'ჲ', + 'Ჳ' => 'ჳ', + 'Ჴ' => 'ჴ', + 'Ჵ' => 'ჵ', + 'Ჶ' => 'ჶ', + 'Ჷ' => 'ჷ', + 'Ჸ' => 'ჸ', + 'Ჹ' => 'ჹ', + 'Ჺ' => 'ჺ', + 'Ჽ' => 'ჽ', + 'Ჾ' => 'ჾ', + 'Ჿ' => 'ჿ', + 'Ḁ' => 'ḁ', + 'Ḃ' => 'ḃ', + 'Ḅ' => 'ḅ', + 'Ḇ' => 'ḇ', + 'Ḉ' => 'ḉ', + 'Ḋ' => 'ḋ', + 'Ḍ' => 'ḍ', + 'Ḏ' => 'ḏ', + 'Ḑ' => 'ḑ', + 'Ḓ' => 'ḓ', + 'Ḕ' => 'ḕ', + 'Ḗ' => 'ḗ', + 'Ḙ' => 'ḙ', + 'Ḛ' => 'ḛ', + 'Ḝ' => 'ḝ', + 'Ḟ' => 'ḟ', + 'Ḡ' => 'ḡ', + 'Ḣ' => 'ḣ', + 'Ḥ' => 'ḥ', + 'Ḧ' => 'ḧ', + 'Ḩ' => 'ḩ', + 'Ḫ' => 'ḫ', + 'Ḭ' => 'ḭ', + 'Ḯ' => 'ḯ', + 'Ḱ' => 'ḱ', + 'Ḳ' => 'ḳ', + 'Ḵ' => 'ḵ', + 'Ḷ' => 'ḷ', + 'Ḹ' => 'ḹ', + 'Ḻ' => 'ḻ', + 'Ḽ' => 'ḽ', + 'Ḿ' => 'ḿ', + 'Ṁ' => 'ṁ', + 'Ṃ' => 'ṃ', + 'Ṅ' => 'ṅ', + 'Ṇ' => 'ṇ', + 'Ṉ' => 'ṉ', + 'Ṋ' => 'ṋ', + 'Ṍ' => 'ṍ', + 'Ṏ' => 'ṏ', + 'Ṑ' => 'ṑ', + 'Ṓ' => 'ṓ', + 'Ṕ' => 'ṕ', + 'Ṗ' => 'ṗ', + 'Ṙ' => 'ṙ', + 'Ṛ' => 'ṛ', + 'Ṝ' => 'ṝ', + 'Ṟ' => 'ṟ', + 'Ṡ' => 'ṡ', + 'Ṣ' => 'ṣ', + 'Ṥ' => 'ṥ', + 'Ṧ' => 'ṧ', + 'Ṩ' => 'ṩ', + 'Ṫ' => 'ṫ', + 'Ṭ' => 'ṭ', + 'Ṯ' => 'ṯ', + 'Ṱ' => 'ṱ', + 'Ṳ' => 'ṳ', + 'Ṵ' => 'ṵ', + 'Ṷ' => 'ṷ', + 'Ṹ' => 'ṹ', + 'Ṻ' => 'ṻ', + 'Ṽ' => 'ṽ', + 'Ṿ' => 'ṿ', + 'Ẁ' => 'ẁ', + 'Ẃ' => 'ẃ', + 'Ẅ' => 'ẅ', + 'Ẇ' => 'ẇ', + 'Ẉ' => 'ẉ', + 'Ẋ' => 'ẋ', + 'Ẍ' => 'ẍ', + 'Ẏ' => 'ẏ', + 'Ẑ' => 'ẑ', + 'Ẓ' => 'ẓ', + 'Ẕ' => 'ẕ', + 'ẞ' => 'ß', + 'Ạ' => 'ạ', + 'Ả' => 'ả', + 'Ấ' => 'ấ', + 'Ầ' => 'ầ', + 'Ẩ' => 'ẩ', + 'Ẫ' => 'ẫ', + 'Ậ' => 'ậ', + 'Ắ' => 'ắ', + 'Ằ' => 'ằ', + 'Ẳ' => 'ẳ', + 'Ẵ' => 'ẵ', + 'Ặ' => 'ặ', + 'Ẹ' => 'ẹ', + 'Ẻ' => 'ẻ', + 'Ẽ' => 'ẽ', + 'Ế' => 'ế', + 'Ề' => 'ề', + 'Ể' => 'ể', + 'Ễ' => 'ễ', + 'Ệ' => 'ệ', + 'Ỉ' => 'ỉ', + 'Ị' => 'ị', + 'Ọ' => 'ọ', + 'Ỏ' => 'ỏ', + 'Ố' => 'ố', + 'Ồ' => 'ồ', + 'Ổ' => 'ổ', + 'Ỗ' => 'ỗ', + 'Ộ' => 'ộ', + 'Ớ' => 'ớ', + 'Ờ' => 'ờ', + 'Ở' => 'ở', + 'Ỡ' => 'ỡ', + 'Ợ' => 'ợ', + 'Ụ' => 'ụ', + 'Ủ' => 'ủ', + 'Ứ' => 'ứ', + 'Ừ' => 'ừ', + 'Ử' => 'ử', + 'Ữ' => 'ữ', + 'Ự' => 'ự', + 'Ỳ' => 'ỳ', + 'Ỵ' => 'ỵ', + 'Ỷ' => 'ỷ', + 'Ỹ' => 'ỹ', + 'Ỻ' => 'ỻ', + 'Ỽ' => 'ỽ', + 'Ỿ' => 'ỿ', + 'Ἀ' => 'ἀ', + 'Ἁ' => 'ἁ', + 'Ἂ' => 'ἂ', + 'Ἃ' => 'ἃ', + 'Ἄ' => 'ἄ', + 'Ἅ' => 'ἅ', + 'Ἆ' => 'ἆ', + 'Ἇ' => 'ἇ', + 'Ἐ' => 'ἐ', + 'Ἑ' => 'ἑ', + 'Ἒ' => 'ἒ', + 'Ἓ' => 'ἓ', + 'Ἔ' => 'ἔ', + 'Ἕ' => 'ἕ', + 'Ἠ' => 'ἠ', + 'Ἡ' => 'ἡ', + 'Ἢ' => 'ἢ', + 'Ἣ' => 'ἣ', + 'Ἤ' => 'ἤ', + 'Ἥ' => 'ἥ', + 'Ἦ' => 'ἦ', + 'Ἧ' => 'ἧ', + 'Ἰ' => 'ἰ', + 'Ἱ' => 'ἱ', + 'Ἲ' => 'ἲ', + 'Ἳ' => 'ἳ', + 'Ἴ' => 'ἴ', + 'Ἵ' => 'ἵ', + 'Ἶ' => 'ἶ', + 'Ἷ' => 'ἷ', + 'Ὀ' => 'ὀ', + 'Ὁ' => 'ὁ', + 'Ὂ' => 'ὂ', + 'Ὃ' => 'ὃ', + 'Ὄ' => 'ὄ', + 'Ὅ' => 'ὅ', + 'Ὑ' => 'ὑ', + 'Ὓ' => 'ὓ', + 'Ὕ' => 'ὕ', + 'Ὗ' => 'ὗ', + 'Ὠ' => 'ὠ', + 'Ὡ' => 'ὡ', + 'Ὢ' => 'ὢ', + 'Ὣ' => 'ὣ', + 'Ὤ' => 'ὤ', + 'Ὥ' => 'ὥ', + 'Ὦ' => 'ὦ', + 'Ὧ' => 'ὧ', + 'ᾈ' => 'ᾀ', + 'ᾉ' => 'ᾁ', + 'ᾊ' => 'ᾂ', + 'ᾋ' => 'ᾃ', + 'ᾌ' => 'ᾄ', + 'ᾍ' => 'ᾅ', + 'ᾎ' => 'ᾆ', + 'ᾏ' => 'ᾇ', + 'ᾘ' => 'ᾐ', + 'ᾙ' => 'ᾑ', + 'ᾚ' => 'ᾒ', + 'ᾛ' => 'ᾓ', + 'ᾜ' => 'ᾔ', + 'ᾝ' => 'ᾕ', + 'ᾞ' => 'ᾖ', + 'ᾟ' => 'ᾗ', + 'ᾨ' => 'ᾠ', + 'ᾩ' => 'ᾡ', + 'ᾪ' => 'ᾢ', + 'ᾫ' => 'ᾣ', + 'ᾬ' => 'ᾤ', + 'ᾭ' => 'ᾥ', + 'ᾮ' => 'ᾦ', + 'ᾯ' => 'ᾧ', + 'Ᾰ' => 'ᾰ', + 'Ᾱ' => 'ᾱ', + 'Ὰ' => 'ὰ', + 'Ά' => 'ά', + 'ᾼ' => 'ᾳ', + 'Ὲ' => 'ὲ', + 'Έ' => 'έ', + 'Ὴ' => 'ὴ', + 'Ή' => 'ή', + 'ῌ' => 'ῃ', + 'Ῐ' => 'ῐ', + 'Ῑ' => 'ῑ', + 'Ὶ' => 'ὶ', + 'Ί' => 'ί', + 'Ῠ' => 'ῠ', + 'Ῡ' => 'ῡ', + 'Ὺ' => 'ὺ', + 'Ύ' => 'ύ', + 'Ῥ' => 'ῥ', + 'Ὸ' => 'ὸ', + 'Ό' => 'ό', + 'Ὼ' => 'ὼ', + 'Ώ' => 'ώ', + 'ῼ' => 'ῳ', + 'Ω' => 'ω', + 'K' => 'k', + 'Å' => 'å', + 'Ⅎ' => 'ⅎ', + 'Ⅰ' => 'ⅰ', + 'Ⅱ' => 'ⅱ', + 'Ⅲ' => 'ⅲ', + 'Ⅳ' => 'ⅳ', + 'Ⅴ' => 'ⅴ', + 'Ⅵ' => 'ⅵ', + 'Ⅶ' => 'ⅶ', + 'Ⅷ' => 'ⅷ', + 'Ⅸ' => 'ⅸ', + 'Ⅹ' => 'ⅹ', + 'Ⅺ' => 'ⅺ', + 'Ⅻ' => 'ⅻ', + 'Ⅼ' => 'ⅼ', + 'Ⅽ' => 'ⅽ', + 'Ⅾ' => 'ⅾ', + 'Ⅿ' => 'ⅿ', + 'Ↄ' => 'ↄ', + 'Ⓐ' => 'ⓐ', + 'Ⓑ' => 'ⓑ', + 'Ⓒ' => 'ⓒ', + 'Ⓓ' => 'ⓓ', + 'Ⓔ' => 'ⓔ', + 'Ⓕ' => 'ⓕ', + 'Ⓖ' => 'ⓖ', + 'Ⓗ' => 'ⓗ', + 'Ⓘ' => 'ⓘ', + 'Ⓙ' => 'ⓙ', + 'Ⓚ' => 'ⓚ', + 'Ⓛ' => 'ⓛ', + 'Ⓜ' => 'ⓜ', + 'Ⓝ' => 'ⓝ', + 'Ⓞ' => 'ⓞ', + 'Ⓟ' => 'ⓟ', + 'Ⓠ' => 'ⓠ', + 'Ⓡ' => 'ⓡ', + 'Ⓢ' => 'ⓢ', + 'Ⓣ' => 'ⓣ', + 'Ⓤ' => 'ⓤ', + 'Ⓥ' => 'ⓥ', + 'Ⓦ' => 'ⓦ', + 'Ⓧ' => 'ⓧ', + 'Ⓨ' => 'ⓨ', + 'Ⓩ' => 'ⓩ', + 'Ⰰ' => 'ⰰ', + 'Ⰱ' => 'ⰱ', + 'Ⰲ' => 'ⰲ', + 'Ⰳ' => 'ⰳ', + 'Ⰴ' => 'ⰴ', + 'Ⰵ' => 'ⰵ', + 'Ⰶ' => 'ⰶ', + 'Ⰷ' => 'ⰷ', + 'Ⰸ' => 'ⰸ', + 'Ⰹ' => 'ⰹ', + 'Ⰺ' => 'ⰺ', + 'Ⰻ' => 'ⰻ', + 'Ⰼ' => 'ⰼ', + 'Ⰽ' => 'ⰽ', + 'Ⰾ' => 'ⰾ', + 'Ⰿ' => 'ⰿ', + 'Ⱀ' => 'ⱀ', + 'Ⱁ' => 'ⱁ', + 'Ⱂ' => 'ⱂ', + 'Ⱃ' => 'ⱃ', + 'Ⱄ' => 'ⱄ', + 'Ⱅ' => 'ⱅ', + 'Ⱆ' => 'ⱆ', + 'Ⱇ' => 'ⱇ', + 'Ⱈ' => 'ⱈ', + 'Ⱉ' => 'ⱉ', + 'Ⱊ' => 'ⱊ', + 'Ⱋ' => 'ⱋ', + 'Ⱌ' => 'ⱌ', + 'Ⱍ' => 'ⱍ', + 'Ⱎ' => 'ⱎ', + 'Ⱏ' => 'ⱏ', + 'Ⱐ' => 'ⱐ', + 'Ⱑ' => 'ⱑ', + 'Ⱒ' => 'ⱒ', + 'Ⱓ' => 'ⱓ', + 'Ⱔ' => 'ⱔ', + 'Ⱕ' => 'ⱕ', + 'Ⱖ' => 'ⱖ', + 'Ⱗ' => 'ⱗ', + 'Ⱘ' => 'ⱘ', + 'Ⱙ' => 'ⱙ', + 'Ⱚ' => 'ⱚ', + 'Ⱛ' => 'ⱛ', + 'Ⱜ' => 'ⱜ', + 'Ⱝ' => 'ⱝ', + 'Ⱞ' => 'ⱞ', + 'Ⱡ' => 'ⱡ', + 'Ɫ' => 'ɫ', + 'Ᵽ' => 'ᵽ', + 'Ɽ' => 'ɽ', + 'Ⱨ' => 'ⱨ', + 'Ⱪ' => 'ⱪ', + 'Ⱬ' => 'ⱬ', + 'Ɑ' => 'ɑ', + 'Ɱ' => 'ɱ', + 'Ɐ' => 'ɐ', + 'Ɒ' => 'ɒ', + 'Ⱳ' => 'ⱳ', + 'Ⱶ' => 'ⱶ', + 'Ȿ' => 'ȿ', + 'Ɀ' => 'ɀ', + 'Ⲁ' => 'ⲁ', + 'Ⲃ' => 'ⲃ', + 'Ⲅ' => 'ⲅ', + 'Ⲇ' => 'ⲇ', + 'Ⲉ' => 'ⲉ', + 'Ⲋ' => 'ⲋ', + 'Ⲍ' => 'ⲍ', + 'Ⲏ' => 'ⲏ', + 'Ⲑ' => 'ⲑ', + 'Ⲓ' => 'ⲓ', + 'Ⲕ' => 'ⲕ', + 'Ⲗ' => 'ⲗ', + 'Ⲙ' => 'ⲙ', + 'Ⲛ' => 'ⲛ', + 'Ⲝ' => 'ⲝ', + 'Ⲟ' => 'ⲟ', + 'Ⲡ' => 'ⲡ', + 'Ⲣ' => 'ⲣ', + 'Ⲥ' => 'ⲥ', + 'Ⲧ' => 'ⲧ', + 'Ⲩ' => 'ⲩ', + 'Ⲫ' => 'ⲫ', + 'Ⲭ' => 'ⲭ', + 'Ⲯ' => 'ⲯ', + 'Ⲱ' => 'ⲱ', + 'Ⲳ' => 'ⲳ', + 'Ⲵ' => 'ⲵ', + 'Ⲷ' => 'ⲷ', + 'Ⲹ' => 'ⲹ', + 'Ⲻ' => 'ⲻ', + 'Ⲽ' => 'ⲽ', + 'Ⲿ' => 'ⲿ', + 'Ⳁ' => 'ⳁ', + 'Ⳃ' => 'ⳃ', + 'Ⳅ' => 'ⳅ', + 'Ⳇ' => 'ⳇ', + 'Ⳉ' => 'ⳉ', + 'Ⳋ' => 'ⳋ', + 'Ⳍ' => 'ⳍ', + 'Ⳏ' => 'ⳏ', + 'Ⳑ' => 'ⳑ', + 'Ⳓ' => 'ⳓ', + 'Ⳕ' => 'ⳕ', + 'Ⳗ' => 'ⳗ', + 'Ⳙ' => 'ⳙ', + 'Ⳛ' => 'ⳛ', + 'Ⳝ' => 'ⳝ', + 'Ⳟ' => 'ⳟ', + 'Ⳡ' => 'ⳡ', + 'Ⳣ' => 'ⳣ', + 'Ⳬ' => 'ⳬ', + 'Ⳮ' => 'ⳮ', + 'Ⳳ' => 'ⳳ', + 'Ꙁ' => 'ꙁ', + 'Ꙃ' => 'ꙃ', + 'Ꙅ' => 'ꙅ', + 'Ꙇ' => 'ꙇ', + 'Ꙉ' => 'ꙉ', + 'Ꙋ' => 'ꙋ', + 'Ꙍ' => 'ꙍ', + 'Ꙏ' => 'ꙏ', + 'Ꙑ' => 'ꙑ', + 'Ꙓ' => 'ꙓ', + 'Ꙕ' => 'ꙕ', + 'Ꙗ' => 'ꙗ', + 'Ꙙ' => 'ꙙ', + 'Ꙛ' => 'ꙛ', + 'Ꙝ' => 'ꙝ', + 'Ꙟ' => 'ꙟ', + 'Ꙡ' => 'ꙡ', + 'Ꙣ' => 'ꙣ', + 'Ꙥ' => 'ꙥ', + 'Ꙧ' => 'ꙧ', + 'Ꙩ' => 'ꙩ', + 'Ꙫ' => 'ꙫ', + 'Ꙭ' => 'ꙭ', + 'Ꚁ' => 'ꚁ', + 'Ꚃ' => 'ꚃ', + 'Ꚅ' => 'ꚅ', + 'Ꚇ' => 'ꚇ', + 'Ꚉ' => 'ꚉ', + 'Ꚋ' => 'ꚋ', + 'Ꚍ' => 'ꚍ', + 'Ꚏ' => 'ꚏ', + 'Ꚑ' => 'ꚑ', + 'Ꚓ' => 'ꚓ', + 'Ꚕ' => 'ꚕ', + 'Ꚗ' => 'ꚗ', + 'Ꚙ' => 'ꚙ', + 'Ꚛ' => 'ꚛ', + 'Ꜣ' => 'ꜣ', + 'Ꜥ' => 'ꜥ', + 'Ꜧ' => 'ꜧ', + 'Ꜩ' => 'ꜩ', + 'Ꜫ' => 'ꜫ', + 'Ꜭ' => 'ꜭ', + 'Ꜯ' => 'ꜯ', + 'Ꜳ' => 'ꜳ', + 'Ꜵ' => 'ꜵ', + 'Ꜷ' => 'ꜷ', + 'Ꜹ' => 'ꜹ', + 'Ꜻ' => 'ꜻ', + 'Ꜽ' => 'ꜽ', + 'Ꜿ' => 'ꜿ', + 'Ꝁ' => 'ꝁ', + 'Ꝃ' => 'ꝃ', + 'Ꝅ' => 'ꝅ', + 'Ꝇ' => 'ꝇ', + 'Ꝉ' => 'ꝉ', + 'Ꝋ' => 'ꝋ', + 'Ꝍ' => 'ꝍ', + 'Ꝏ' => 'ꝏ', + 'Ꝑ' => 'ꝑ', + 'Ꝓ' => 'ꝓ', + 'Ꝕ' => 'ꝕ', + 'Ꝗ' => 'ꝗ', + 'Ꝙ' => 'ꝙ', + 'Ꝛ' => 'ꝛ', + 'Ꝝ' => 'ꝝ', + 'Ꝟ' => 'ꝟ', + 'Ꝡ' => 'ꝡ', + 'Ꝣ' => 'ꝣ', + 'Ꝥ' => 'ꝥ', + 'Ꝧ' => 'ꝧ', + 'Ꝩ' => 'ꝩ', + 'Ꝫ' => 'ꝫ', + 'Ꝭ' => 'ꝭ', + 'Ꝯ' => 'ꝯ', + 'Ꝺ' => 'ꝺ', + 'Ꝼ' => 'ꝼ', + 'Ᵹ' => 'ᵹ', + 'Ꝿ' => 'ꝿ', + 'Ꞁ' => 'ꞁ', + 'Ꞃ' => 'ꞃ', + 'Ꞅ' => 'ꞅ', + 'Ꞇ' => 'ꞇ', + 'Ꞌ' => 'ꞌ', + 'Ɥ' => 'ɥ', + 'Ꞑ' => 'ꞑ', + 'Ꞓ' => 'ꞓ', + 'Ꞗ' => 'ꞗ', + 'Ꞙ' => 'ꞙ', + 'Ꞛ' => 'ꞛ', + 'Ꞝ' => 'ꞝ', + 'Ꞟ' => 'ꞟ', + 'Ꞡ' => 'ꞡ', + 'Ꞣ' => 'ꞣ', + 'Ꞥ' => 'ꞥ', + 'Ꞧ' => 'ꞧ', + 'Ꞩ' => 'ꞩ', + 'Ɦ' => 'ɦ', + 'Ɜ' => 'ɜ', + 'Ɡ' => 'ɡ', + 'Ɬ' => 'ɬ', + 'Ɪ' => 'ɪ', + 'Ʞ' => 'ʞ', + 'Ʇ' => 'ʇ', + 'Ʝ' => 'ʝ', + 'Ꭓ' => 'ꭓ', + 'Ꞵ' => 'ꞵ', + 'Ꞷ' => 'ꞷ', + 'Ꞹ' => 'ꞹ', + 'Ꞻ' => 'ꞻ', + 'Ꞽ' => 'ꞽ', + 'Ꞿ' => 'ꞿ', + 'Ꟃ' => 'ꟃ', + 'Ꞔ' => 'ꞔ', + 'Ʂ' => 'ʂ', + 'Ᶎ' => 'ᶎ', + 'Ꟈ' => 'ꟈ', + 'Ꟊ' => 'ꟊ', + 'Ꟶ' => 'ꟶ', + 'A' => 'a', + 'B' => 'b', + 'C' => 'c', + 'D' => 'd', + 'E' => 'e', + 'F' => 'f', + 'G' => 'g', + 'H' => 'h', + 'I' => 'i', + 'J' => 'j', + 'K' => 'k', + 'L' => 'l', + 'M' => 'm', + 'N' => 'n', + 'O' => 'o', + 'P' => 'p', + 'Q' => 'q', + 'R' => 'r', + 'S' => 's', + 'T' => 't', + 'U' => 'u', + 'V' => 'v', + 'W' => 'w', + 'X' => 'x', + 'Y' => 'y', + 'Z' => 'z', + '𐐀' => '𐐨', + '𐐁' => '𐐩', + '𐐂' => '𐐪', + '𐐃' => '𐐫', + '𐐄' => '𐐬', + '𐐅' => '𐐭', + '𐐆' => '𐐮', + '𐐇' => '𐐯', + '𐐈' => '𐐰', + '𐐉' => '𐐱', + '𐐊' => '𐐲', + '𐐋' => '𐐳', + '𐐌' => '𐐴', + '𐐍' => '𐐵', + '𐐎' => '𐐶', + '𐐏' => '𐐷', + '𐐐' => '𐐸', + '𐐑' => '𐐹', + '𐐒' => '𐐺', + '𐐓' => '𐐻', + '𐐔' => '𐐼', + '𐐕' => '𐐽', + '𐐖' => '𐐾', + '𐐗' => '𐐿', + '𐐘' => '𐑀', + '𐐙' => '𐑁', + '𐐚' => '𐑂', + '𐐛' => '𐑃', + '𐐜' => '𐑄', + '𐐝' => '𐑅', + '𐐞' => '𐑆', + '𐐟' => '𐑇', + '𐐠' => '𐑈', + '𐐡' => '𐑉', + '𐐢' => '𐑊', + '𐐣' => '𐑋', + '𐐤' => '𐑌', + '𐐥' => '𐑍', + '𐐦' => '𐑎', + '𐐧' => '𐑏', + '𐒰' => '𐓘', + '𐒱' => '𐓙', + '𐒲' => '𐓚', + '𐒳' => '𐓛', + '𐒴' => '𐓜', + '𐒵' => '𐓝', + '𐒶' => '𐓞', + '𐒷' => '𐓟', + '𐒸' => '𐓠', + '𐒹' => '𐓡', + '𐒺' => '𐓢', + '𐒻' => '𐓣', + '𐒼' => '𐓤', + '𐒽' => '𐓥', + '𐒾' => '𐓦', + '𐒿' => '𐓧', + '𐓀' => '𐓨', + '𐓁' => '𐓩', + '𐓂' => '𐓪', + '𐓃' => '𐓫', + '𐓄' => '𐓬', + '𐓅' => '𐓭', + '𐓆' => '𐓮', + '𐓇' => '𐓯', + '𐓈' => '𐓰', + '𐓉' => '𐓱', + '𐓊' => '𐓲', + '𐓋' => '𐓳', + '𐓌' => '𐓴', + '𐓍' => '𐓵', + '𐓎' => '𐓶', + '𐓏' => '𐓷', + '𐓐' => '𐓸', + '𐓑' => '𐓹', + '𐓒' => '𐓺', + '𐓓' => '𐓻', + '𐲀' => '𐳀', + '𐲁' => '𐳁', + '𐲂' => '𐳂', + '𐲃' => '𐳃', + '𐲄' => '𐳄', + '𐲅' => '𐳅', + '𐲆' => '𐳆', + '𐲇' => '𐳇', + '𐲈' => '𐳈', + '𐲉' => '𐳉', + '𐲊' => '𐳊', + '𐲋' => '𐳋', + '𐲌' => '𐳌', + '𐲍' => '𐳍', + '𐲎' => '𐳎', + '𐲏' => '𐳏', + '𐲐' => '𐳐', + '𐲑' => '𐳑', + '𐲒' => '𐳒', + '𐲓' => '𐳓', + '𐲔' => '𐳔', + '𐲕' => '𐳕', + '𐲖' => '𐳖', + '𐲗' => '𐳗', + '𐲘' => '𐳘', + '𐲙' => '𐳙', + '𐲚' => '𐳚', + '𐲛' => '𐳛', + '𐲜' => '𐳜', + '𐲝' => '𐳝', + '𐲞' => '𐳞', + '𐲟' => '𐳟', + '𐲠' => '𐳠', + '𐲡' => '𐳡', + '𐲢' => '𐳢', + '𐲣' => '𐳣', + '𐲤' => '𐳤', + '𐲥' => '𐳥', + '𐲦' => '𐳦', + '𐲧' => '𐳧', + '𐲨' => '𐳨', + '𐲩' => '𐳩', + '𐲪' => '𐳪', + '𐲫' => '𐳫', + '𐲬' => '𐳬', + '𐲭' => '𐳭', + '𐲮' => '𐳮', + '𐲯' => '𐳯', + '𐲰' => '𐳰', + '𐲱' => '𐳱', + '𐲲' => '𐳲', + '𑢠' => '𑣀', + '𑢡' => '𑣁', + '𑢢' => '𑣂', + '𑢣' => '𑣃', + '𑢤' => '𑣄', + '𑢥' => '𑣅', + '𑢦' => '𑣆', + '𑢧' => '𑣇', + '𑢨' => '𑣈', + '𑢩' => '𑣉', + '𑢪' => '𑣊', + '𑢫' => '𑣋', + '𑢬' => '𑣌', + '𑢭' => '𑣍', + '𑢮' => '𑣎', + '𑢯' => '𑣏', + '𑢰' => '𑣐', + '𑢱' => '𑣑', + '𑢲' => '𑣒', + '𑢳' => '𑣓', + '𑢴' => '𑣔', + '𑢵' => '𑣕', + '𑢶' => '𑣖', + '𑢷' => '𑣗', + '𑢸' => '𑣘', + '𑢹' => '𑣙', + '𑢺' => '𑣚', + '𑢻' => '𑣛', + '𑢼' => '𑣜', + '𑢽' => '𑣝', + '𑢾' => '𑣞', + '𑢿' => '𑣟', + '𖹀' => '𖹠', + '𖹁' => '𖹡', + '𖹂' => '𖹢', + '𖹃' => '𖹣', + '𖹄' => '𖹤', + '𖹅' => '𖹥', + '𖹆' => '𖹦', + '𖹇' => '𖹧', + '𖹈' => '𖹨', + '𖹉' => '𖹩', + '𖹊' => '𖹪', + '𖹋' => '𖹫', + '𖹌' => '𖹬', + '𖹍' => '𖹭', + '𖹎' => '𖹮', + '𖹏' => '𖹯', + '𖹐' => '𖹰', + '𖹑' => '𖹱', + '𖹒' => '𖹲', + '𖹓' => '𖹳', + '𖹔' => '𖹴', + '𖹕' => '𖹵', + '𖹖' => '𖹶', + '𖹗' => '𖹷', + '𖹘' => '𖹸', + '𖹙' => '𖹹', + '𖹚' => '𖹺', + '𖹛' => '𖹻', + '𖹜' => '𖹼', + '𖹝' => '𖹽', + '𖹞' => '𖹾', + '𖹟' => '𖹿', + '𞤀' => '𞤢', + '𞤁' => '𞤣', + '𞤂' => '𞤤', + '𞤃' => '𞤥', + '𞤄' => '𞤦', + '𞤅' => '𞤧', + '𞤆' => '𞤨', + '𞤇' => '𞤩', + '𞤈' => '𞤪', + '𞤉' => '𞤫', + '𞤊' => '𞤬', + '𞤋' => '𞤭', + '𞤌' => '𞤮', + '𞤍' => '𞤯', + '𞤎' => '𞤰', + '𞤏' => '𞤱', + '𞤐' => '𞤲', + '𞤑' => '𞤳', + '𞤒' => '𞤴', + '𞤓' => '𞤵', + '𞤔' => '𞤶', + '𞤕' => '𞤷', + '𞤖' => '𞤸', + '𞤗' => '𞤹', + '𞤘' => '𞤺', + '𞤙' => '𞤻', + '𞤚' => '𞤼', + '𞤛' => '𞤽', + '𞤜' => '𞤾', + '𞤝' => '𞤿', + '𞤞' => '𞥀', + '𞤟' => '𞥁', + '𞤠' => '𞥂', + '𞤡' => '𞥃', +); diff --git a/kirby/vendor/symfony/polyfill-mbstring/Resources/unidata/titleCaseRegexp.php b/kirby/vendor/symfony/polyfill-mbstring/Resources/unidata/titleCaseRegexp.php new file mode 100644 index 0000000..2a8f6e7 --- /dev/null +++ b/kirby/vendor/symfony/polyfill-mbstring/Resources/unidata/titleCaseRegexp.php @@ -0,0 +1,5 @@ + 'A', + 'b' => 'B', + 'c' => 'C', + 'd' => 'D', + 'e' => 'E', + 'f' => 'F', + 'g' => 'G', + 'h' => 'H', + 'i' => 'I', + 'j' => 'J', + 'k' => 'K', + 'l' => 'L', + 'm' => 'M', + 'n' => 'N', + 'o' => 'O', + 'p' => 'P', + 'q' => 'Q', + 'r' => 'R', + 's' => 'S', + 't' => 'T', + 'u' => 'U', + 'v' => 'V', + 'w' => 'W', + 'x' => 'X', + 'y' => 'Y', + 'z' => 'Z', + 'µ' => 'Μ', + 'à' => 'À', + 'á' => 'Á', + 'â' => 'Â', + 'ã' => 'Ã', + 'ä' => 'Ä', + 'å' => 'Å', + 'æ' => 'Æ', + 'ç' => 'Ç', + 'è' => 'È', + 'é' => 'É', + 'ê' => 'Ê', + 'ë' => 'Ë', + 'ì' => 'Ì', + 'í' => 'Í', + 'î' => 'Î', + 'ï' => 'Ï', + 'ð' => 'Ð', + 'ñ' => 'Ñ', + 'ò' => 'Ò', + 'ó' => 'Ó', + 'ô' => 'Ô', + 'õ' => 'Õ', + 'ö' => 'Ö', + 'ø' => 'Ø', + 'ù' => 'Ù', + 'ú' => 'Ú', + 'û' => 'Û', + 'ü' => 'Ü', + 'ý' => 'Ý', + 'þ' => 'Þ', + 'ÿ' => 'Ÿ', + 'ā' => 'Ā', + 'ă' => 'Ă', + 'ą' => 'Ą', + 'ć' => 'Ć', + 'ĉ' => 'Ĉ', + 'ċ' => 'Ċ', + 'č' => 'Č', + 'ď' => 'Ď', + 'đ' => 'Đ', + 'ē' => 'Ē', + 'ĕ' => 'Ĕ', + 'ė' => 'Ė', + 'ę' => 'Ę', + 'ě' => 'Ě', + 'ĝ' => 'Ĝ', + 'ğ' => 'Ğ', + 'ġ' => 'Ġ', + 'ģ' => 'Ģ', + 'ĥ' => 'Ĥ', + 'ħ' => 'Ħ', + 'ĩ' => 'Ĩ', + 'ī' => 'Ī', + 'ĭ' => 'Ĭ', + 'į' => 'Į', + 'ı' => 'I', + 'ij' => 'IJ', + 'ĵ' => 'Ĵ', + 'ķ' => 'Ķ', + 'ĺ' => 'Ĺ', + 'ļ' => 'Ļ', + 'ľ' => 'Ľ', + 'ŀ' => 'Ŀ', + 'ł' => 'Ł', + 'ń' => 'Ń', + 'ņ' => 'Ņ', + 'ň' => 'Ň', + 'ŋ' => 'Ŋ', + 'ō' => 'Ō', + 'ŏ' => 'Ŏ', + 'ő' => 'Ő', + 'œ' => 'Œ', + 'ŕ' => 'Ŕ', + 'ŗ' => 'Ŗ', + 'ř' => 'Ř', + 'ś' => 'Ś', + 'ŝ' => 'Ŝ', + 'ş' => 'Ş', + 'š' => 'Š', + 'ţ' => 'Ţ', + 'ť' => 'Ť', + 'ŧ' => 'Ŧ', + 'ũ' => 'Ũ', + 'ū' => 'Ū', + 'ŭ' => 'Ŭ', + 'ů' => 'Ů', + 'ű' => 'Ű', + 'ų' => 'Ų', + 'ŵ' => 'Ŵ', + 'ŷ' => 'Ŷ', + 'ź' => 'Ź', + 'ż' => 'Ż', + 'ž' => 'Ž', + 'ſ' => 'S', + 'ƀ' => 'Ƀ', + 'ƃ' => 'Ƃ', + 'ƅ' => 'Ƅ', + 'ƈ' => 'Ƈ', + 'ƌ' => 'Ƌ', + 'ƒ' => 'Ƒ', + 'ƕ' => 'Ƕ', + 'ƙ' => 'Ƙ', + 'ƚ' => 'Ƚ', + 'ƞ' => 'Ƞ', + 'ơ' => 'Ơ', + 'ƣ' => 'Ƣ', + 'ƥ' => 'Ƥ', + 'ƨ' => 'Ƨ', + 'ƭ' => 'Ƭ', + 'ư' => 'Ư', + 'ƴ' => 'Ƴ', + 'ƶ' => 'Ƶ', + 'ƹ' => 'Ƹ', + 'ƽ' => 'Ƽ', + 'ƿ' => 'Ƿ', + 'Dž' => 'DŽ', + 'dž' => 'DŽ', + 'Lj' => 'LJ', + 'lj' => 'LJ', + 'Nj' => 'NJ', + 'nj' => 'NJ', + 'ǎ' => 'Ǎ', + 'ǐ' => 'Ǐ', + 'ǒ' => 'Ǒ', + 'ǔ' => 'Ǔ', + 'ǖ' => 'Ǖ', + 'ǘ' => 'Ǘ', + 'ǚ' => 'Ǚ', + 'ǜ' => 'Ǜ', + 'ǝ' => 'Ǝ', + 'ǟ' => 'Ǟ', + 'ǡ' => 'Ǡ', + 'ǣ' => 'Ǣ', + 'ǥ' => 'Ǥ', + 'ǧ' => 'Ǧ', + 'ǩ' => 'Ǩ', + 'ǫ' => 'Ǫ', + 'ǭ' => 'Ǭ', + 'ǯ' => 'Ǯ', + 'Dz' => 'DZ', + 'dz' => 'DZ', + 'ǵ' => 'Ǵ', + 'ǹ' => 'Ǹ', + 'ǻ' => 'Ǻ', + 'ǽ' => 'Ǽ', + 'ǿ' => 'Ǿ', + 'ȁ' => 'Ȁ', + 'ȃ' => 'Ȃ', + 'ȅ' => 'Ȅ', + 'ȇ' => 'Ȇ', + 'ȉ' => 'Ȉ', + 'ȋ' => 'Ȋ', + 'ȍ' => 'Ȍ', + 'ȏ' => 'Ȏ', + 'ȑ' => 'Ȑ', + 'ȓ' => 'Ȓ', + 'ȕ' => 'Ȕ', + 'ȗ' => 'Ȗ', + 'ș' => 'Ș', + 'ț' => 'Ț', + 'ȝ' => 'Ȝ', + 'ȟ' => 'Ȟ', + 'ȣ' => 'Ȣ', + 'ȥ' => 'Ȥ', + 'ȧ' => 'Ȧ', + 'ȩ' => 'Ȩ', + 'ȫ' => 'Ȫ', + 'ȭ' => 'Ȭ', + 'ȯ' => 'Ȯ', + 'ȱ' => 'Ȱ', + 'ȳ' => 'Ȳ', + 'ȼ' => 'Ȼ', + 'ȿ' => 'Ȿ', + 'ɀ' => 'Ɀ', + 'ɂ' => 'Ɂ', + 'ɇ' => 'Ɇ', + 'ɉ' => 'Ɉ', + 'ɋ' => 'Ɋ', + 'ɍ' => 'Ɍ', + 'ɏ' => 'Ɏ', + 'ɐ' => 'Ɐ', + 'ɑ' => 'Ɑ', + 'ɒ' => 'Ɒ', + 'ɓ' => 'Ɓ', + 'ɔ' => 'Ɔ', + 'ɖ' => 'Ɖ', + 'ɗ' => 'Ɗ', + 'ə' => 'Ə', + 'ɛ' => 'Ɛ', + 'ɜ' => 'Ɜ', + 'ɠ' => 'Ɠ', + 'ɡ' => 'Ɡ', + 'ɣ' => 'Ɣ', + 'ɥ' => 'Ɥ', + 'ɦ' => 'Ɦ', + 'ɨ' => 'Ɨ', + 'ɩ' => 'Ɩ', + 'ɪ' => 'Ɪ', + 'ɫ' => 'Ɫ', + 'ɬ' => 'Ɬ', + 'ɯ' => 'Ɯ', + 'ɱ' => 'Ɱ', + 'ɲ' => 'Ɲ', + 'ɵ' => 'Ɵ', + 'ɽ' => 'Ɽ', + 'ʀ' => 'Ʀ', + 'ʂ' => 'Ʂ', + 'ʃ' => 'Ʃ', + 'ʇ' => 'Ʇ', + 'ʈ' => 'Ʈ', + 'ʉ' => 'Ʉ', + 'ʊ' => 'Ʊ', + 'ʋ' => 'Ʋ', + 'ʌ' => 'Ʌ', + 'ʒ' => 'Ʒ', + 'ʝ' => 'Ʝ', + 'ʞ' => 'Ʞ', + 'ͅ' => 'Ι', + 'ͱ' => 'Ͱ', + 'ͳ' => 'Ͳ', + 'ͷ' => 'Ͷ', + 'ͻ' => 'Ͻ', + 'ͼ' => 'Ͼ', + 'ͽ' => 'Ͽ', + 'ά' => 'Ά', + 'έ' => 'Έ', + 'ή' => 'Ή', + 'ί' => 'Ί', + 'α' => 'Α', + 'β' => 'Β', + 'γ' => 'Γ', + 'δ' => 'Δ', + 'ε' => 'Ε', + 'ζ' => 'Ζ', + 'η' => 'Η', + 'θ' => 'Θ', + 'ι' => 'Ι', + 'κ' => 'Κ', + 'λ' => 'Λ', + 'μ' => 'Μ', + 'ν' => 'Ν', + 'ξ' => 'Ξ', + 'ο' => 'Ο', + 'π' => 'Π', + 'ρ' => 'Ρ', + 'ς' => 'Σ', + 'σ' => 'Σ', + 'τ' => 'Τ', + 'υ' => 'Υ', + 'φ' => 'Φ', + 'χ' => 'Χ', + 'ψ' => 'Ψ', + 'ω' => 'Ω', + 'ϊ' => 'Ϊ', + 'ϋ' => 'Ϋ', + 'ό' => 'Ό', + 'ύ' => 'Ύ', + 'ώ' => 'Ώ', + 'ϐ' => 'Β', + 'ϑ' => 'Θ', + 'ϕ' => 'Φ', + 'ϖ' => 'Π', + 'ϗ' => 'Ϗ', + 'ϙ' => 'Ϙ', + 'ϛ' => 'Ϛ', + 'ϝ' => 'Ϝ', + 'ϟ' => 'Ϟ', + 'ϡ' => 'Ϡ', + 'ϣ' => 'Ϣ', + 'ϥ' => 'Ϥ', + 'ϧ' => 'Ϧ', + 'ϩ' => 'Ϩ', + 'ϫ' => 'Ϫ', + 'ϭ' => 'Ϭ', + 'ϯ' => 'Ϯ', + 'ϰ' => 'Κ', + 'ϱ' => 'Ρ', + 'ϲ' => 'Ϲ', + 'ϳ' => 'Ϳ', + 'ϵ' => 'Ε', + 'ϸ' => 'Ϸ', + 'ϻ' => 'Ϻ', + 'а' => 'А', + 'б' => 'Б', + 'в' => 'В', + 'г' => 'Г', + 'д' => 'Д', + 'е' => 'Е', + 'ж' => 'Ж', + 'з' => 'З', + 'и' => 'И', + 'й' => 'Й', + 'к' => 'К', + 'л' => 'Л', + 'м' => 'М', + 'н' => 'Н', + 'о' => 'О', + 'п' => 'П', + 'р' => 'Р', + 'с' => 'С', + 'т' => 'Т', + 'у' => 'У', + 'ф' => 'Ф', + 'х' => 'Х', + 'ц' => 'Ц', + 'ч' => 'Ч', + 'ш' => 'Ш', + 'щ' => 'Щ', + 'ъ' => 'Ъ', + 'ы' => 'Ы', + 'ь' => 'Ь', + 'э' => 'Э', + 'ю' => 'Ю', + 'я' => 'Я', + 'ѐ' => 'Ѐ', + 'ё' => 'Ё', + 'ђ' => 'Ђ', + 'ѓ' => 'Ѓ', + 'є' => 'Є', + 'ѕ' => 'Ѕ', + 'і' => 'І', + 'ї' => 'Ї', + 'ј' => 'Ј', + 'љ' => 'Љ', + 'њ' => 'Њ', + 'ћ' => 'Ћ', + 'ќ' => 'Ќ', + 'ѝ' => 'Ѝ', + 'ў' => 'Ў', + 'џ' => 'Џ', + 'ѡ' => 'Ѡ', + 'ѣ' => 'Ѣ', + 'ѥ' => 'Ѥ', + 'ѧ' => 'Ѧ', + 'ѩ' => 'Ѩ', + 'ѫ' => 'Ѫ', + 'ѭ' => 'Ѭ', + 'ѯ' => 'Ѯ', + 'ѱ' => 'Ѱ', + 'ѳ' => 'Ѳ', + 'ѵ' => 'Ѵ', + 'ѷ' => 'Ѷ', + 'ѹ' => 'Ѹ', + 'ѻ' => 'Ѻ', + 'ѽ' => 'Ѽ', + 'ѿ' => 'Ѿ', + 'ҁ' => 'Ҁ', + 'ҋ' => 'Ҋ', + 'ҍ' => 'Ҍ', + 'ҏ' => 'Ҏ', + 'ґ' => 'Ґ', + 'ғ' => 'Ғ', + 'ҕ' => 'Ҕ', + 'җ' => 'Җ', + 'ҙ' => 'Ҙ', + 'қ' => 'Қ', + 'ҝ' => 'Ҝ', + 'ҟ' => 'Ҟ', + 'ҡ' => 'Ҡ', + 'ң' => 'Ң', + 'ҥ' => 'Ҥ', + 'ҧ' => 'Ҧ', + 'ҩ' => 'Ҩ', + 'ҫ' => 'Ҫ', + 'ҭ' => 'Ҭ', + 'ү' => 'Ү', + 'ұ' => 'Ұ', + 'ҳ' => 'Ҳ', + 'ҵ' => 'Ҵ', + 'ҷ' => 'Ҷ', + 'ҹ' => 'Ҹ', + 'һ' => 'Һ', + 'ҽ' => 'Ҽ', + 'ҿ' => 'Ҿ', + 'ӂ' => 'Ӂ', + 'ӄ' => 'Ӄ', + 'ӆ' => 'Ӆ', + 'ӈ' => 'Ӈ', + 'ӊ' => 'Ӊ', + 'ӌ' => 'Ӌ', + 'ӎ' => 'Ӎ', + 'ӏ' => 'Ӏ', + 'ӑ' => 'Ӑ', + 'ӓ' => 'Ӓ', + 'ӕ' => 'Ӕ', + 'ӗ' => 'Ӗ', + 'ә' => 'Ә', + 'ӛ' => 'Ӛ', + 'ӝ' => 'Ӝ', + 'ӟ' => 'Ӟ', + 'ӡ' => 'Ӡ', + 'ӣ' => 'Ӣ', + 'ӥ' => 'Ӥ', + 'ӧ' => 'Ӧ', + 'ө' => 'Ө', + 'ӫ' => 'Ӫ', + 'ӭ' => 'Ӭ', + 'ӯ' => 'Ӯ', + 'ӱ' => 'Ӱ', + 'ӳ' => 'Ӳ', + 'ӵ' => 'Ӵ', + 'ӷ' => 'Ӷ', + 'ӹ' => 'Ӹ', + 'ӻ' => 'Ӻ', + 'ӽ' => 'Ӽ', + 'ӿ' => 'Ӿ', + 'ԁ' => 'Ԁ', + 'ԃ' => 'Ԃ', + 'ԅ' => 'Ԅ', + 'ԇ' => 'Ԇ', + 'ԉ' => 'Ԉ', + 'ԋ' => 'Ԋ', + 'ԍ' => 'Ԍ', + 'ԏ' => 'Ԏ', + 'ԑ' => 'Ԑ', + 'ԓ' => 'Ԓ', + 'ԕ' => 'Ԕ', + 'ԗ' => 'Ԗ', + 'ԙ' => 'Ԙ', + 'ԛ' => 'Ԛ', + 'ԝ' => 'Ԝ', + 'ԟ' => 'Ԟ', + 'ԡ' => 'Ԡ', + 'ԣ' => 'Ԣ', + 'ԥ' => 'Ԥ', + 'ԧ' => 'Ԧ', + 'ԩ' => 'Ԩ', + 'ԫ' => 'Ԫ', + 'ԭ' => 'Ԭ', + 'ԯ' => 'Ԯ', + 'ա' => 'Ա', + 'բ' => 'Բ', + 'գ' => 'Գ', + 'դ' => 'Դ', + 'ե' => 'Ե', + 'զ' => 'Զ', + 'է' => 'Է', + 'ը' => 'Ը', + 'թ' => 'Թ', + 'ժ' => 'Ժ', + 'ի' => 'Ի', + 'լ' => 'Լ', + 'խ' => 'Խ', + 'ծ' => 'Ծ', + 'կ' => 'Կ', + 'հ' => 'Հ', + 'ձ' => 'Ձ', + 'ղ' => 'Ղ', + 'ճ' => 'Ճ', + 'մ' => 'Մ', + 'յ' => 'Յ', + 'ն' => 'Ն', + 'շ' => 'Շ', + 'ո' => 'Ո', + 'չ' => 'Չ', + 'պ' => 'Պ', + 'ջ' => 'Ջ', + 'ռ' => 'Ռ', + 'ս' => 'Ս', + 'վ' => 'Վ', + 'տ' => 'Տ', + 'ր' => 'Ր', + 'ց' => 'Ց', + 'ւ' => 'Ւ', + 'փ' => 'Փ', + 'ք' => 'Ք', + 'օ' => 'Օ', + 'ֆ' => 'Ֆ', + 'ა' => 'Ა', + 'ბ' => 'Ბ', + 'გ' => 'Გ', + 'დ' => 'Დ', + 'ე' => 'Ე', + 'ვ' => 'Ვ', + 'ზ' => 'Ზ', + 'თ' => 'Თ', + 'ი' => 'Ი', + 'კ' => 'Კ', + 'ლ' => 'Ლ', + 'მ' => 'Მ', + 'ნ' => 'Ნ', + 'ო' => 'Ო', + 'პ' => 'Პ', + 'ჟ' => 'Ჟ', + 'რ' => 'Რ', + 'ს' => 'Ს', + 'ტ' => 'Ტ', + 'უ' => 'Უ', + 'ფ' => 'Ფ', + 'ქ' => 'Ქ', + 'ღ' => 'Ღ', + 'ყ' => 'Ყ', + 'შ' => 'Შ', + 'ჩ' => 'Ჩ', + 'ც' => 'Ც', + 'ძ' => 'Ძ', + 'წ' => 'Წ', + 'ჭ' => 'Ჭ', + 'ხ' => 'Ხ', + 'ჯ' => 'Ჯ', + 'ჰ' => 'Ჰ', + 'ჱ' => 'Ჱ', + 'ჲ' => 'Ჲ', + 'ჳ' => 'Ჳ', + 'ჴ' => 'Ჴ', + 'ჵ' => 'Ჵ', + 'ჶ' => 'Ჶ', + 'ჷ' => 'Ჷ', + 'ჸ' => 'Ჸ', + 'ჹ' => 'Ჹ', + 'ჺ' => 'Ჺ', + 'ჽ' => 'Ჽ', + 'ჾ' => 'Ჾ', + 'ჿ' => 'Ჿ', + 'ᏸ' => 'Ᏸ', + 'ᏹ' => 'Ᏹ', + 'ᏺ' => 'Ᏺ', + 'ᏻ' => 'Ᏻ', + 'ᏼ' => 'Ᏼ', + 'ᏽ' => 'Ᏽ', + 'ᲀ' => 'В', + 'ᲁ' => 'Д', + 'ᲂ' => 'О', + 'ᲃ' => 'С', + 'ᲄ' => 'Т', + 'ᲅ' => 'Т', + 'ᲆ' => 'Ъ', + 'ᲇ' => 'Ѣ', + 'ᲈ' => 'Ꙋ', + 'ᵹ' => 'Ᵹ', + 'ᵽ' => 'Ᵽ', + 'ᶎ' => 'Ᶎ', + 'ḁ' => 'Ḁ', + 'ḃ' => 'Ḃ', + 'ḅ' => 'Ḅ', + 'ḇ' => 'Ḇ', + 'ḉ' => 'Ḉ', + 'ḋ' => 'Ḋ', + 'ḍ' => 'Ḍ', + 'ḏ' => 'Ḏ', + 'ḑ' => 'Ḑ', + 'ḓ' => 'Ḓ', + 'ḕ' => 'Ḕ', + 'ḗ' => 'Ḗ', + 'ḙ' => 'Ḙ', + 'ḛ' => 'Ḛ', + 'ḝ' => 'Ḝ', + 'ḟ' => 'Ḟ', + 'ḡ' => 'Ḡ', + 'ḣ' => 'Ḣ', + 'ḥ' => 'Ḥ', + 'ḧ' => 'Ḧ', + 'ḩ' => 'Ḩ', + 'ḫ' => 'Ḫ', + 'ḭ' => 'Ḭ', + 'ḯ' => 'Ḯ', + 'ḱ' => 'Ḱ', + 'ḳ' => 'Ḳ', + 'ḵ' => 'Ḵ', + 'ḷ' => 'Ḷ', + 'ḹ' => 'Ḹ', + 'ḻ' => 'Ḻ', + 'ḽ' => 'Ḽ', + 'ḿ' => 'Ḿ', + 'ṁ' => 'Ṁ', + 'ṃ' => 'Ṃ', + 'ṅ' => 'Ṅ', + 'ṇ' => 'Ṇ', + 'ṉ' => 'Ṉ', + 'ṋ' => 'Ṋ', + 'ṍ' => 'Ṍ', + 'ṏ' => 'Ṏ', + 'ṑ' => 'Ṑ', + 'ṓ' => 'Ṓ', + 'ṕ' => 'Ṕ', + 'ṗ' => 'Ṗ', + 'ṙ' => 'Ṙ', + 'ṛ' => 'Ṛ', + 'ṝ' => 'Ṝ', + 'ṟ' => 'Ṟ', + 'ṡ' => 'Ṡ', + 'ṣ' => 'Ṣ', + 'ṥ' => 'Ṥ', + 'ṧ' => 'Ṧ', + 'ṩ' => 'Ṩ', + 'ṫ' => 'Ṫ', + 'ṭ' => 'Ṭ', + 'ṯ' => 'Ṯ', + 'ṱ' => 'Ṱ', + 'ṳ' => 'Ṳ', + 'ṵ' => 'Ṵ', + 'ṷ' => 'Ṷ', + 'ṹ' => 'Ṹ', + 'ṻ' => 'Ṻ', + 'ṽ' => 'Ṽ', + 'ṿ' => 'Ṿ', + 'ẁ' => 'Ẁ', + 'ẃ' => 'Ẃ', + 'ẅ' => 'Ẅ', + 'ẇ' => 'Ẇ', + 'ẉ' => 'Ẉ', + 'ẋ' => 'Ẋ', + 'ẍ' => 'Ẍ', + 'ẏ' => 'Ẏ', + 'ẑ' => 'Ẑ', + 'ẓ' => 'Ẓ', + 'ẕ' => 'Ẕ', + 'ẛ' => 'Ṡ', + 'ạ' => 'Ạ', + 'ả' => 'Ả', + 'ấ' => 'Ấ', + 'ầ' => 'Ầ', + 'ẩ' => 'Ẩ', + 'ẫ' => 'Ẫ', + 'ậ' => 'Ậ', + 'ắ' => 'Ắ', + 'ằ' => 'Ằ', + 'ẳ' => 'Ẳ', + 'ẵ' => 'Ẵ', + 'ặ' => 'Ặ', + 'ẹ' => 'Ẹ', + 'ẻ' => 'Ẻ', + 'ẽ' => 'Ẽ', + 'ế' => 'Ế', + 'ề' => 'Ề', + 'ể' => 'Ể', + 'ễ' => 'Ễ', + 'ệ' => 'Ệ', + 'ỉ' => 'Ỉ', + 'ị' => 'Ị', + 'ọ' => 'Ọ', + 'ỏ' => 'Ỏ', + 'ố' => 'Ố', + 'ồ' => 'Ồ', + 'ổ' => 'Ổ', + 'ỗ' => 'Ỗ', + 'ộ' => 'Ộ', + 'ớ' => 'Ớ', + 'ờ' => 'Ờ', + 'ở' => 'Ở', + 'ỡ' => 'Ỡ', + 'ợ' => 'Ợ', + 'ụ' => 'Ụ', + 'ủ' => 'Ủ', + 'ứ' => 'Ứ', + 'ừ' => 'Ừ', + 'ử' => 'Ử', + 'ữ' => 'Ữ', + 'ự' => 'Ự', + 'ỳ' => 'Ỳ', + 'ỵ' => 'Ỵ', + 'ỷ' => 'Ỷ', + 'ỹ' => 'Ỹ', + 'ỻ' => 'Ỻ', + 'ỽ' => 'Ỽ', + 'ỿ' => 'Ỿ', + 'ἀ' => 'Ἀ', + 'ἁ' => 'Ἁ', + 'ἂ' => 'Ἂ', + 'ἃ' => 'Ἃ', + 'ἄ' => 'Ἄ', + 'ἅ' => 'Ἅ', + 'ἆ' => 'Ἆ', + 'ἇ' => 'Ἇ', + 'ἐ' => 'Ἐ', + 'ἑ' => 'Ἑ', + 'ἒ' => 'Ἒ', + 'ἓ' => 'Ἓ', + 'ἔ' => 'Ἔ', + 'ἕ' => 'Ἕ', + 'ἠ' => 'Ἠ', + 'ἡ' => 'Ἡ', + 'ἢ' => 'Ἢ', + 'ἣ' => 'Ἣ', + 'ἤ' => 'Ἤ', + 'ἥ' => 'Ἥ', + 'ἦ' => 'Ἦ', + 'ἧ' => 'Ἧ', + 'ἰ' => 'Ἰ', + 'ἱ' => 'Ἱ', + 'ἲ' => 'Ἲ', + 'ἳ' => 'Ἳ', + 'ἴ' => 'Ἴ', + 'ἵ' => 'Ἵ', + 'ἶ' => 'Ἶ', + 'ἷ' => 'Ἷ', + 'ὀ' => 'Ὀ', + 'ὁ' => 'Ὁ', + 'ὂ' => 'Ὂ', + 'ὃ' => 'Ὃ', + 'ὄ' => 'Ὄ', + 'ὅ' => 'Ὅ', + 'ὑ' => 'Ὑ', + 'ὓ' => 'Ὓ', + 'ὕ' => 'Ὕ', + 'ὗ' => 'Ὗ', + 'ὠ' => 'Ὠ', + 'ὡ' => 'Ὡ', + 'ὢ' => 'Ὢ', + 'ὣ' => 'Ὣ', + 'ὤ' => 'Ὤ', + 'ὥ' => 'Ὥ', + 'ὦ' => 'Ὦ', + 'ὧ' => 'Ὧ', + 'ὰ' => 'Ὰ', + 'ά' => 'Ά', + 'ὲ' => 'Ὲ', + 'έ' => 'Έ', + 'ὴ' => 'Ὴ', + 'ή' => 'Ή', + 'ὶ' => 'Ὶ', + 'ί' => 'Ί', + 'ὸ' => 'Ὸ', + 'ό' => 'Ό', + 'ὺ' => 'Ὺ', + 'ύ' => 'Ύ', + 'ὼ' => 'Ὼ', + 'ώ' => 'Ώ', + 'ᾀ' => 'ἈΙ', + 'ᾁ' => 'ἉΙ', + 'ᾂ' => 'ἊΙ', + 'ᾃ' => 'ἋΙ', + 'ᾄ' => 'ἌΙ', + 'ᾅ' => 'ἍΙ', + 'ᾆ' => 'ἎΙ', + 'ᾇ' => 'ἏΙ', + 'ᾐ' => 'ἨΙ', + 'ᾑ' => 'ἩΙ', + 'ᾒ' => 'ἪΙ', + 'ᾓ' => 'ἫΙ', + 'ᾔ' => 'ἬΙ', + 'ᾕ' => 'ἭΙ', + 'ᾖ' => 'ἮΙ', + 'ᾗ' => 'ἯΙ', + 'ᾠ' => 'ὨΙ', + 'ᾡ' => 'ὩΙ', + 'ᾢ' => 'ὪΙ', + 'ᾣ' => 'ὫΙ', + 'ᾤ' => 'ὬΙ', + 'ᾥ' => 'ὭΙ', + 'ᾦ' => 'ὮΙ', + 'ᾧ' => 'ὯΙ', + 'ᾰ' => 'Ᾰ', + 'ᾱ' => 'Ᾱ', + 'ᾳ' => 'ΑΙ', + 'ι' => 'Ι', + 'ῃ' => 'ΗΙ', + 'ῐ' => 'Ῐ', + 'ῑ' => 'Ῑ', + 'ῠ' => 'Ῠ', + 'ῡ' => 'Ῡ', + 'ῥ' => 'Ῥ', + 'ῳ' => 'ΩΙ', + 'ⅎ' => 'Ⅎ', + 'ⅰ' => 'Ⅰ', + 'ⅱ' => 'Ⅱ', + 'ⅲ' => 'Ⅲ', + 'ⅳ' => 'Ⅳ', + 'ⅴ' => 'Ⅴ', + 'ⅵ' => 'Ⅵ', + 'ⅶ' => 'Ⅶ', + 'ⅷ' => 'Ⅷ', + 'ⅸ' => 'Ⅸ', + 'ⅹ' => 'Ⅹ', + 'ⅺ' => 'Ⅺ', + 'ⅻ' => 'Ⅻ', + 'ⅼ' => 'Ⅼ', + 'ⅽ' => 'Ⅽ', + 'ⅾ' => 'Ⅾ', + 'ⅿ' => 'Ⅿ', + 'ↄ' => 'Ↄ', + 'ⓐ' => 'Ⓐ', + 'ⓑ' => 'Ⓑ', + 'ⓒ' => 'Ⓒ', + 'ⓓ' => 'Ⓓ', + 'ⓔ' => 'Ⓔ', + 'ⓕ' => 'Ⓕ', + 'ⓖ' => 'Ⓖ', + 'ⓗ' => 'Ⓗ', + 'ⓘ' => 'Ⓘ', + 'ⓙ' => 'Ⓙ', + 'ⓚ' => 'Ⓚ', + 'ⓛ' => 'Ⓛ', + 'ⓜ' => 'Ⓜ', + 'ⓝ' => 'Ⓝ', + 'ⓞ' => 'Ⓞ', + 'ⓟ' => 'Ⓟ', + 'ⓠ' => 'Ⓠ', + 'ⓡ' => 'Ⓡ', + 'ⓢ' => 'Ⓢ', + 'ⓣ' => 'Ⓣ', + 'ⓤ' => 'Ⓤ', + 'ⓥ' => 'Ⓥ', + 'ⓦ' => 'Ⓦ', + 'ⓧ' => 'Ⓧ', + 'ⓨ' => 'Ⓨ', + 'ⓩ' => 'Ⓩ', + 'ⰰ' => 'Ⰰ', + 'ⰱ' => 'Ⰱ', + 'ⰲ' => 'Ⰲ', + 'ⰳ' => 'Ⰳ', + 'ⰴ' => 'Ⰴ', + 'ⰵ' => 'Ⰵ', + 'ⰶ' => 'Ⰶ', + 'ⰷ' => 'Ⰷ', + 'ⰸ' => 'Ⰸ', + 'ⰹ' => 'Ⰹ', + 'ⰺ' => 'Ⰺ', + 'ⰻ' => 'Ⰻ', + 'ⰼ' => 'Ⰼ', + 'ⰽ' => 'Ⰽ', + 'ⰾ' => 'Ⰾ', + 'ⰿ' => 'Ⰿ', + 'ⱀ' => 'Ⱀ', + 'ⱁ' => 'Ⱁ', + 'ⱂ' => 'Ⱂ', + 'ⱃ' => 'Ⱃ', + 'ⱄ' => 'Ⱄ', + 'ⱅ' => 'Ⱅ', + 'ⱆ' => 'Ⱆ', + 'ⱇ' => 'Ⱇ', + 'ⱈ' => 'Ⱈ', + 'ⱉ' => 'Ⱉ', + 'ⱊ' => 'Ⱊ', + 'ⱋ' => 'Ⱋ', + 'ⱌ' => 'Ⱌ', + 'ⱍ' => 'Ⱍ', + 'ⱎ' => 'Ⱎ', + 'ⱏ' => 'Ⱏ', + 'ⱐ' => 'Ⱐ', + 'ⱑ' => 'Ⱑ', + 'ⱒ' => 'Ⱒ', + 'ⱓ' => 'Ⱓ', + 'ⱔ' => 'Ⱔ', + 'ⱕ' => 'Ⱕ', + 'ⱖ' => 'Ⱖ', + 'ⱗ' => 'Ⱗ', + 'ⱘ' => 'Ⱘ', + 'ⱙ' => 'Ⱙ', + 'ⱚ' => 'Ⱚ', + 'ⱛ' => 'Ⱛ', + 'ⱜ' => 'Ⱜ', + 'ⱝ' => 'Ⱝ', + 'ⱞ' => 'Ⱞ', + 'ⱡ' => 'Ⱡ', + 'ⱥ' => 'Ⱥ', + 'ⱦ' => 'Ⱦ', + 'ⱨ' => 'Ⱨ', + 'ⱪ' => 'Ⱪ', + 'ⱬ' => 'Ⱬ', + 'ⱳ' => 'Ⱳ', + 'ⱶ' => 'Ⱶ', + 'ⲁ' => 'Ⲁ', + 'ⲃ' => 'Ⲃ', + 'ⲅ' => 'Ⲅ', + 'ⲇ' => 'Ⲇ', + 'ⲉ' => 'Ⲉ', + 'ⲋ' => 'Ⲋ', + 'ⲍ' => 'Ⲍ', + 'ⲏ' => 'Ⲏ', + 'ⲑ' => 'Ⲑ', + 'ⲓ' => 'Ⲓ', + 'ⲕ' => 'Ⲕ', + 'ⲗ' => 'Ⲗ', + 'ⲙ' => 'Ⲙ', + 'ⲛ' => 'Ⲛ', + 'ⲝ' => 'Ⲝ', + 'ⲟ' => 'Ⲟ', + 'ⲡ' => 'Ⲡ', + 'ⲣ' => 'Ⲣ', + 'ⲥ' => 'Ⲥ', + 'ⲧ' => 'Ⲧ', + 'ⲩ' => 'Ⲩ', + 'ⲫ' => 'Ⲫ', + 'ⲭ' => 'Ⲭ', + 'ⲯ' => 'Ⲯ', + 'ⲱ' => 'Ⲱ', + 'ⲳ' => 'Ⲳ', + 'ⲵ' => 'Ⲵ', + 'ⲷ' => 'Ⲷ', + 'ⲹ' => 'Ⲹ', + 'ⲻ' => 'Ⲻ', + 'ⲽ' => 'Ⲽ', + 'ⲿ' => 'Ⲿ', + 'ⳁ' => 'Ⳁ', + 'ⳃ' => 'Ⳃ', + 'ⳅ' => 'Ⳅ', + 'ⳇ' => 'Ⳇ', + 'ⳉ' => 'Ⳉ', + 'ⳋ' => 'Ⳋ', + 'ⳍ' => 'Ⳍ', + 'ⳏ' => 'Ⳏ', + 'ⳑ' => 'Ⳑ', + 'ⳓ' => 'Ⳓ', + 'ⳕ' => 'Ⳕ', + 'ⳗ' => 'Ⳗ', + 'ⳙ' => 'Ⳙ', + 'ⳛ' => 'Ⳛ', + 'ⳝ' => 'Ⳝ', + 'ⳟ' => 'Ⳟ', + 'ⳡ' => 'Ⳡ', + 'ⳣ' => 'Ⳣ', + 'ⳬ' => 'Ⳬ', + 'ⳮ' => 'Ⳮ', + 'ⳳ' => 'Ⳳ', + 'ⴀ' => 'Ⴀ', + 'ⴁ' => 'Ⴁ', + 'ⴂ' => 'Ⴂ', + 'ⴃ' => 'Ⴃ', + 'ⴄ' => 'Ⴄ', + 'ⴅ' => 'Ⴅ', + 'ⴆ' => 'Ⴆ', + 'ⴇ' => 'Ⴇ', + 'ⴈ' => 'Ⴈ', + 'ⴉ' => 'Ⴉ', + 'ⴊ' => 'Ⴊ', + 'ⴋ' => 'Ⴋ', + 'ⴌ' => 'Ⴌ', + 'ⴍ' => 'Ⴍ', + 'ⴎ' => 'Ⴎ', + 'ⴏ' => 'Ⴏ', + 'ⴐ' => 'Ⴐ', + 'ⴑ' => 'Ⴑ', + 'ⴒ' => 'Ⴒ', + 'ⴓ' => 'Ⴓ', + 'ⴔ' => 'Ⴔ', + 'ⴕ' => 'Ⴕ', + 'ⴖ' => 'Ⴖ', + 'ⴗ' => 'Ⴗ', + 'ⴘ' => 'Ⴘ', + 'ⴙ' => 'Ⴙ', + 'ⴚ' => 'Ⴚ', + 'ⴛ' => 'Ⴛ', + 'ⴜ' => 'Ⴜ', + 'ⴝ' => 'Ⴝ', + 'ⴞ' => 'Ⴞ', + 'ⴟ' => 'Ⴟ', + 'ⴠ' => 'Ⴠ', + 'ⴡ' => 'Ⴡ', + 'ⴢ' => 'Ⴢ', + 'ⴣ' => 'Ⴣ', + 'ⴤ' => 'Ⴤ', + 'ⴥ' => 'Ⴥ', + 'ⴧ' => 'Ⴧ', + 'ⴭ' => 'Ⴭ', + 'ꙁ' => 'Ꙁ', + 'ꙃ' => 'Ꙃ', + 'ꙅ' => 'Ꙅ', + 'ꙇ' => 'Ꙇ', + 'ꙉ' => 'Ꙉ', + 'ꙋ' => 'Ꙋ', + 'ꙍ' => 'Ꙍ', + 'ꙏ' => 'Ꙏ', + 'ꙑ' => 'Ꙑ', + 'ꙓ' => 'Ꙓ', + 'ꙕ' => 'Ꙕ', + 'ꙗ' => 'Ꙗ', + 'ꙙ' => 'Ꙙ', + 'ꙛ' => 'Ꙛ', + 'ꙝ' => 'Ꙝ', + 'ꙟ' => 'Ꙟ', + 'ꙡ' => 'Ꙡ', + 'ꙣ' => 'Ꙣ', + 'ꙥ' => 'Ꙥ', + 'ꙧ' => 'Ꙧ', + 'ꙩ' => 'Ꙩ', + 'ꙫ' => 'Ꙫ', + 'ꙭ' => 'Ꙭ', + 'ꚁ' => 'Ꚁ', + 'ꚃ' => 'Ꚃ', + 'ꚅ' => 'Ꚅ', + 'ꚇ' => 'Ꚇ', + 'ꚉ' => 'Ꚉ', + 'ꚋ' => 'Ꚋ', + 'ꚍ' => 'Ꚍ', + 'ꚏ' => 'Ꚏ', + 'ꚑ' => 'Ꚑ', + 'ꚓ' => 'Ꚓ', + 'ꚕ' => 'Ꚕ', + 'ꚗ' => 'Ꚗ', + 'ꚙ' => 'Ꚙ', + 'ꚛ' => 'Ꚛ', + 'ꜣ' => 'Ꜣ', + 'ꜥ' => 'Ꜥ', + 'ꜧ' => 'Ꜧ', + 'ꜩ' => 'Ꜩ', + 'ꜫ' => 'Ꜫ', + 'ꜭ' => 'Ꜭ', + 'ꜯ' => 'Ꜯ', + 'ꜳ' => 'Ꜳ', + 'ꜵ' => 'Ꜵ', + 'ꜷ' => 'Ꜷ', + 'ꜹ' => 'Ꜹ', + 'ꜻ' => 'Ꜻ', + 'ꜽ' => 'Ꜽ', + 'ꜿ' => 'Ꜿ', + 'ꝁ' => 'Ꝁ', + 'ꝃ' => 'Ꝃ', + 'ꝅ' => 'Ꝅ', + 'ꝇ' => 'Ꝇ', + 'ꝉ' => 'Ꝉ', + 'ꝋ' => 'Ꝋ', + 'ꝍ' => 'Ꝍ', + 'ꝏ' => 'Ꝏ', + 'ꝑ' => 'Ꝑ', + 'ꝓ' => 'Ꝓ', + 'ꝕ' => 'Ꝕ', + 'ꝗ' => 'Ꝗ', + 'ꝙ' => 'Ꝙ', + 'ꝛ' => 'Ꝛ', + 'ꝝ' => 'Ꝝ', + 'ꝟ' => 'Ꝟ', + 'ꝡ' => 'Ꝡ', + 'ꝣ' => 'Ꝣ', + 'ꝥ' => 'Ꝥ', + 'ꝧ' => 'Ꝧ', + 'ꝩ' => 'Ꝩ', + 'ꝫ' => 'Ꝫ', + 'ꝭ' => 'Ꝭ', + 'ꝯ' => 'Ꝯ', + 'ꝺ' => 'Ꝺ', + 'ꝼ' => 'Ꝼ', + 'ꝿ' => 'Ꝿ', + 'ꞁ' => 'Ꞁ', + 'ꞃ' => 'Ꞃ', + 'ꞅ' => 'Ꞅ', + 'ꞇ' => 'Ꞇ', + 'ꞌ' => 'Ꞌ', + 'ꞑ' => 'Ꞑ', + 'ꞓ' => 'Ꞓ', + 'ꞔ' => 'Ꞔ', + 'ꞗ' => 'Ꞗ', + 'ꞙ' => 'Ꞙ', + 'ꞛ' => 'Ꞛ', + 'ꞝ' => 'Ꞝ', + 'ꞟ' => 'Ꞟ', + 'ꞡ' => 'Ꞡ', + 'ꞣ' => 'Ꞣ', + 'ꞥ' => 'Ꞥ', + 'ꞧ' => 'Ꞧ', + 'ꞩ' => 'Ꞩ', + 'ꞵ' => 'Ꞵ', + 'ꞷ' => 'Ꞷ', + 'ꞹ' => 'Ꞹ', + 'ꞻ' => 'Ꞻ', + 'ꞽ' => 'Ꞽ', + 'ꞿ' => 'Ꞿ', + 'ꟃ' => 'Ꟃ', + 'ꟈ' => 'Ꟈ', + 'ꟊ' => 'Ꟊ', + 'ꟶ' => 'Ꟶ', + 'ꭓ' => 'Ꭓ', + 'ꭰ' => 'Ꭰ', + 'ꭱ' => 'Ꭱ', + 'ꭲ' => 'Ꭲ', + 'ꭳ' => 'Ꭳ', + 'ꭴ' => 'Ꭴ', + 'ꭵ' => 'Ꭵ', + 'ꭶ' => 'Ꭶ', + 'ꭷ' => 'Ꭷ', + 'ꭸ' => 'Ꭸ', + 'ꭹ' => 'Ꭹ', + 'ꭺ' => 'Ꭺ', + 'ꭻ' => 'Ꭻ', + 'ꭼ' => 'Ꭼ', + 'ꭽ' => 'Ꭽ', + 'ꭾ' => 'Ꭾ', + 'ꭿ' => 'Ꭿ', + 'ꮀ' => 'Ꮀ', + 'ꮁ' => 'Ꮁ', + 'ꮂ' => 'Ꮂ', + 'ꮃ' => 'Ꮃ', + 'ꮄ' => 'Ꮄ', + 'ꮅ' => 'Ꮅ', + 'ꮆ' => 'Ꮆ', + 'ꮇ' => 'Ꮇ', + 'ꮈ' => 'Ꮈ', + 'ꮉ' => 'Ꮉ', + 'ꮊ' => 'Ꮊ', + 'ꮋ' => 'Ꮋ', + 'ꮌ' => 'Ꮌ', + 'ꮍ' => 'Ꮍ', + 'ꮎ' => 'Ꮎ', + 'ꮏ' => 'Ꮏ', + 'ꮐ' => 'Ꮐ', + 'ꮑ' => 'Ꮑ', + 'ꮒ' => 'Ꮒ', + 'ꮓ' => 'Ꮓ', + 'ꮔ' => 'Ꮔ', + 'ꮕ' => 'Ꮕ', + 'ꮖ' => 'Ꮖ', + 'ꮗ' => 'Ꮗ', + 'ꮘ' => 'Ꮘ', + 'ꮙ' => 'Ꮙ', + 'ꮚ' => 'Ꮚ', + 'ꮛ' => 'Ꮛ', + 'ꮜ' => 'Ꮜ', + 'ꮝ' => 'Ꮝ', + 'ꮞ' => 'Ꮞ', + 'ꮟ' => 'Ꮟ', + 'ꮠ' => 'Ꮠ', + 'ꮡ' => 'Ꮡ', + 'ꮢ' => 'Ꮢ', + 'ꮣ' => 'Ꮣ', + 'ꮤ' => 'Ꮤ', + 'ꮥ' => 'Ꮥ', + 'ꮦ' => 'Ꮦ', + 'ꮧ' => 'Ꮧ', + 'ꮨ' => 'Ꮨ', + 'ꮩ' => 'Ꮩ', + 'ꮪ' => 'Ꮪ', + 'ꮫ' => 'Ꮫ', + 'ꮬ' => 'Ꮬ', + 'ꮭ' => 'Ꮭ', + 'ꮮ' => 'Ꮮ', + 'ꮯ' => 'Ꮯ', + 'ꮰ' => 'Ꮰ', + 'ꮱ' => 'Ꮱ', + 'ꮲ' => 'Ꮲ', + 'ꮳ' => 'Ꮳ', + 'ꮴ' => 'Ꮴ', + 'ꮵ' => 'Ꮵ', + 'ꮶ' => 'Ꮶ', + 'ꮷ' => 'Ꮷ', + 'ꮸ' => 'Ꮸ', + 'ꮹ' => 'Ꮹ', + 'ꮺ' => 'Ꮺ', + 'ꮻ' => 'Ꮻ', + 'ꮼ' => 'Ꮼ', + 'ꮽ' => 'Ꮽ', + 'ꮾ' => 'Ꮾ', + 'ꮿ' => 'Ꮿ', + 'a' => 'A', + 'b' => 'B', + 'c' => 'C', + 'd' => 'D', + 'e' => 'E', + 'f' => 'F', + 'g' => 'G', + 'h' => 'H', + 'i' => 'I', + 'j' => 'J', + 'k' => 'K', + 'l' => 'L', + 'm' => 'M', + 'n' => 'N', + 'o' => 'O', + 'p' => 'P', + 'q' => 'Q', + 'r' => 'R', + 's' => 'S', + 't' => 'T', + 'u' => 'U', + 'v' => 'V', + 'w' => 'W', + 'x' => 'X', + 'y' => 'Y', + 'z' => 'Z', + '𐐨' => '𐐀', + '𐐩' => '𐐁', + '𐐪' => '𐐂', + '𐐫' => '𐐃', + '𐐬' => '𐐄', + '𐐭' => '𐐅', + '𐐮' => '𐐆', + '𐐯' => '𐐇', + '𐐰' => '𐐈', + '𐐱' => '𐐉', + '𐐲' => '𐐊', + '𐐳' => '𐐋', + '𐐴' => '𐐌', + '𐐵' => '𐐍', + '𐐶' => '𐐎', + '𐐷' => '𐐏', + '𐐸' => '𐐐', + '𐐹' => '𐐑', + '𐐺' => '𐐒', + '𐐻' => '𐐓', + '𐐼' => '𐐔', + '𐐽' => '𐐕', + '𐐾' => '𐐖', + '𐐿' => '𐐗', + '𐑀' => '𐐘', + '𐑁' => '𐐙', + '𐑂' => '𐐚', + '𐑃' => '𐐛', + '𐑄' => '𐐜', + '𐑅' => '𐐝', + '𐑆' => '𐐞', + '𐑇' => '𐐟', + '𐑈' => '𐐠', + '𐑉' => '𐐡', + '𐑊' => '𐐢', + '𐑋' => '𐐣', + '𐑌' => '𐐤', + '𐑍' => '𐐥', + '𐑎' => '𐐦', + '𐑏' => '𐐧', + '𐓘' => '𐒰', + '𐓙' => '𐒱', + '𐓚' => '𐒲', + '𐓛' => '𐒳', + '𐓜' => '𐒴', + '𐓝' => '𐒵', + '𐓞' => '𐒶', + '𐓟' => '𐒷', + '𐓠' => '𐒸', + '𐓡' => '𐒹', + '𐓢' => '𐒺', + '𐓣' => '𐒻', + '𐓤' => '𐒼', + '𐓥' => '𐒽', + '𐓦' => '𐒾', + '𐓧' => '𐒿', + '𐓨' => '𐓀', + '𐓩' => '𐓁', + '𐓪' => '𐓂', + '𐓫' => '𐓃', + '𐓬' => '𐓄', + '𐓭' => '𐓅', + '𐓮' => '𐓆', + '𐓯' => '𐓇', + '𐓰' => '𐓈', + '𐓱' => '𐓉', + '𐓲' => '𐓊', + '𐓳' => '𐓋', + '𐓴' => '𐓌', + '𐓵' => '𐓍', + '𐓶' => '𐓎', + '𐓷' => '𐓏', + '𐓸' => '𐓐', + '𐓹' => '𐓑', + '𐓺' => '𐓒', + '𐓻' => '𐓓', + '𐳀' => '𐲀', + '𐳁' => '𐲁', + '𐳂' => '𐲂', + '𐳃' => '𐲃', + '𐳄' => '𐲄', + '𐳅' => '𐲅', + '𐳆' => '𐲆', + '𐳇' => '𐲇', + '𐳈' => '𐲈', + '𐳉' => '𐲉', + '𐳊' => '𐲊', + '𐳋' => '𐲋', + '𐳌' => '𐲌', + '𐳍' => '𐲍', + '𐳎' => '𐲎', + '𐳏' => '𐲏', + '𐳐' => '𐲐', + '𐳑' => '𐲑', + '𐳒' => '𐲒', + '𐳓' => '𐲓', + '𐳔' => '𐲔', + '𐳕' => '𐲕', + '𐳖' => '𐲖', + '𐳗' => '𐲗', + '𐳘' => '𐲘', + '𐳙' => '𐲙', + '𐳚' => '𐲚', + '𐳛' => '𐲛', + '𐳜' => '𐲜', + '𐳝' => '𐲝', + '𐳞' => '𐲞', + '𐳟' => '𐲟', + '𐳠' => '𐲠', + '𐳡' => '𐲡', + '𐳢' => '𐲢', + '𐳣' => '𐲣', + '𐳤' => '𐲤', + '𐳥' => '𐲥', + '𐳦' => '𐲦', + '𐳧' => '𐲧', + '𐳨' => '𐲨', + '𐳩' => '𐲩', + '𐳪' => '𐲪', + '𐳫' => '𐲫', + '𐳬' => '𐲬', + '𐳭' => '𐲭', + '𐳮' => '𐲮', + '𐳯' => '𐲯', + '𐳰' => '𐲰', + '𐳱' => '𐲱', + '𐳲' => '𐲲', + '𑣀' => '𑢠', + '𑣁' => '𑢡', + '𑣂' => '𑢢', + '𑣃' => '𑢣', + '𑣄' => '𑢤', + '𑣅' => '𑢥', + '𑣆' => '𑢦', + '𑣇' => '𑢧', + '𑣈' => '𑢨', + '𑣉' => '𑢩', + '𑣊' => '𑢪', + '𑣋' => '𑢫', + '𑣌' => '𑢬', + '𑣍' => '𑢭', + '𑣎' => '𑢮', + '𑣏' => '𑢯', + '𑣐' => '𑢰', + '𑣑' => '𑢱', + '𑣒' => '𑢲', + '𑣓' => '𑢳', + '𑣔' => '𑢴', + '𑣕' => '𑢵', + '𑣖' => '𑢶', + '𑣗' => '𑢷', + '𑣘' => '𑢸', + '𑣙' => '𑢹', + '𑣚' => '𑢺', + '𑣛' => '𑢻', + '𑣜' => '𑢼', + '𑣝' => '𑢽', + '𑣞' => '𑢾', + '𑣟' => '𑢿', + '𖹠' => '𖹀', + '𖹡' => '𖹁', + '𖹢' => '𖹂', + '𖹣' => '𖹃', + '𖹤' => '𖹄', + '𖹥' => '𖹅', + '𖹦' => '𖹆', + '𖹧' => '𖹇', + '𖹨' => '𖹈', + '𖹩' => '𖹉', + '𖹪' => '𖹊', + '𖹫' => '𖹋', + '𖹬' => '𖹌', + '𖹭' => '𖹍', + '𖹮' => '𖹎', + '𖹯' => '𖹏', + '𖹰' => '𖹐', + '𖹱' => '𖹑', + '𖹲' => '𖹒', + '𖹳' => '𖹓', + '𖹴' => '𖹔', + '𖹵' => '𖹕', + '𖹶' => '𖹖', + '𖹷' => '𖹗', + '𖹸' => '𖹘', + '𖹹' => '𖹙', + '𖹺' => '𖹚', + '𖹻' => '𖹛', + '𖹼' => '𖹜', + '𖹽' => '𖹝', + '𖹾' => '𖹞', + '𖹿' => '𖹟', + '𞤢' => '𞤀', + '𞤣' => '𞤁', + '𞤤' => '𞤂', + '𞤥' => '𞤃', + '𞤦' => '𞤄', + '𞤧' => '𞤅', + '𞤨' => '𞤆', + '𞤩' => '𞤇', + '𞤪' => '𞤈', + '𞤫' => '𞤉', + '𞤬' => '𞤊', + '𞤭' => '𞤋', + '𞤮' => '𞤌', + '𞤯' => '𞤍', + '𞤰' => '𞤎', + '𞤱' => '𞤏', + '𞤲' => '𞤐', + '𞤳' => '𞤑', + '𞤴' => '𞤒', + '𞤵' => '𞤓', + '𞤶' => '𞤔', + '𞤷' => '𞤕', + '𞤸' => '𞤖', + '𞤹' => '𞤗', + '𞤺' => '𞤘', + '𞤻' => '𞤙', + '𞤼' => '𞤚', + '𞤽' => '𞤛', + '𞤾' => '𞤜', + '𞤿' => '𞤝', + '𞥀' => '𞤞', + '𞥁' => '𞤟', + '𞥂' => '𞤠', + '𞥃' => '𞤡', + 'ß' => 'SS', + 'ff' => 'FF', + 'fi' => 'FI', + 'fl' => 'FL', + 'ffi' => 'FFI', + 'ffl' => 'FFL', + 'ſt' => 'ST', + 'st' => 'ST', + 'և' => 'ԵՒ', + 'ﬓ' => 'ՄՆ', + 'ﬔ' => 'ՄԵ', + 'ﬕ' => 'ՄԻ', + 'ﬖ' => 'ՎՆ', + 'ﬗ' => 'ՄԽ', + 'ʼn' => 'ʼN', + 'ΐ' => 'Ϊ́', + 'ΰ' => 'Ϋ́', + 'ǰ' => 'J̌', + 'ẖ' => 'H̱', + 'ẗ' => 'T̈', + 'ẘ' => 'W̊', + 'ẙ' => 'Y̊', + 'ẚ' => 'Aʾ', + 'ὐ' => 'Υ̓', + 'ὒ' => 'Υ̓̀', + 'ὔ' => 'Υ̓́', + 'ὖ' => 'Υ̓͂', + 'ᾶ' => 'Α͂', + 'ῆ' => 'Η͂', + 'ῒ' => 'Ϊ̀', + 'ΐ' => 'Ϊ́', + 'ῖ' => 'Ι͂', + 'ῗ' => 'Ϊ͂', + 'ῢ' => 'Ϋ̀', + 'ΰ' => 'Ϋ́', + 'ῤ' => 'Ρ̓', + 'ῦ' => 'Υ͂', + 'ῧ' => 'Ϋ͂', + 'ῶ' => 'Ω͂', + 'ᾈ' => 'ἈΙ', + 'ᾉ' => 'ἉΙ', + 'ᾊ' => 'ἊΙ', + 'ᾋ' => 'ἋΙ', + 'ᾌ' => 'ἌΙ', + 'ᾍ' => 'ἍΙ', + 'ᾎ' => 'ἎΙ', + 'ᾏ' => 'ἏΙ', + 'ᾘ' => 'ἨΙ', + 'ᾙ' => 'ἩΙ', + 'ᾚ' => 'ἪΙ', + 'ᾛ' => 'ἫΙ', + 'ᾜ' => 'ἬΙ', + 'ᾝ' => 'ἭΙ', + 'ᾞ' => 'ἮΙ', + 'ᾟ' => 'ἯΙ', + 'ᾨ' => 'ὨΙ', + 'ᾩ' => 'ὩΙ', + 'ᾪ' => 'ὪΙ', + 'ᾫ' => 'ὫΙ', + 'ᾬ' => 'ὬΙ', + 'ᾭ' => 'ὭΙ', + 'ᾮ' => 'ὮΙ', + 'ᾯ' => 'ὯΙ', + 'ᾼ' => 'ΑΙ', + 'ῌ' => 'ΗΙ', + 'ῼ' => 'ΩΙ', + 'ᾲ' => 'ᾺΙ', + 'ᾴ' => 'ΆΙ', + 'ῂ' => 'ῊΙ', + 'ῄ' => 'ΉΙ', + 'ῲ' => 'ῺΙ', + 'ῴ' => 'ΏΙ', + 'ᾷ' => 'Α͂Ι', + 'ῇ' => 'Η͂Ι', + 'ῷ' => 'Ω͂Ι', +); diff --git a/kirby/vendor/symfony/polyfill-mbstring/bootstrap.php b/kirby/vendor/symfony/polyfill-mbstring/bootstrap.php new file mode 100644 index 0000000..1fedd1f --- /dev/null +++ b/kirby/vendor/symfony/polyfill-mbstring/bootstrap.php @@ -0,0 +1,147 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Symfony\Polyfill\Mbstring as p; + +if (\PHP_VERSION_ID >= 80000) { + return require __DIR__.'/bootstrap80.php'; +} + +if (!function_exists('mb_convert_encoding')) { + function mb_convert_encoding($string, $to_encoding, $from_encoding = null) { return p\Mbstring::mb_convert_encoding($string, $to_encoding, $from_encoding); } +} +if (!function_exists('mb_decode_mimeheader')) { + function mb_decode_mimeheader($string) { return p\Mbstring::mb_decode_mimeheader($string); } +} +if (!function_exists('mb_encode_mimeheader')) { + function mb_encode_mimeheader($string, $charset = null, $transfer_encoding = null, $newline = "\r\n", $indent = 0) { return p\Mbstring::mb_encode_mimeheader($string, $charset, $transfer_encoding, $newline, $indent); } +} +if (!function_exists('mb_decode_numericentity')) { + function mb_decode_numericentity($string, $map, $encoding = null) { return p\Mbstring::mb_decode_numericentity($string, $map, $encoding); } +} +if (!function_exists('mb_encode_numericentity')) { + function mb_encode_numericentity($string, $map, $encoding = null, $hex = false) { return p\Mbstring::mb_encode_numericentity($string, $map, $encoding, $hex); } +} +if (!function_exists('mb_convert_case')) { + function mb_convert_case($string, $mode, $encoding = null) { return p\Mbstring::mb_convert_case($string, $mode, $encoding); } +} +if (!function_exists('mb_internal_encoding')) { + function mb_internal_encoding($encoding = null) { return p\Mbstring::mb_internal_encoding($encoding); } +} +if (!function_exists('mb_language')) { + function mb_language($language = null) { return p\Mbstring::mb_language($language); } +} +if (!function_exists('mb_list_encodings')) { + function mb_list_encodings() { return p\Mbstring::mb_list_encodings(); } +} +if (!function_exists('mb_encoding_aliases')) { + function mb_encoding_aliases($encoding) { return p\Mbstring::mb_encoding_aliases($encoding); } +} +if (!function_exists('mb_check_encoding')) { + function mb_check_encoding($value = null, $encoding = null) { return p\Mbstring::mb_check_encoding($value, $encoding); } +} +if (!function_exists('mb_detect_encoding')) { + function mb_detect_encoding($string, $encodings = null, $strict = false) { return p\Mbstring::mb_detect_encoding($string, $encodings, $strict); } +} +if (!function_exists('mb_detect_order')) { + function mb_detect_order($encoding = null) { return p\Mbstring::mb_detect_order($encoding); } +} +if (!function_exists('mb_parse_str')) { + function mb_parse_str($string, &$result = []) { parse_str($string, $result); return (bool) $result; } +} +if (!function_exists('mb_strlen')) { + function mb_strlen($string, $encoding = null) { return p\Mbstring::mb_strlen($string, $encoding); } +} +if (!function_exists('mb_strpos')) { + function mb_strpos($haystack, $needle, $offset = 0, $encoding = null) { return p\Mbstring::mb_strpos($haystack, $needle, $offset, $encoding); } +} +if (!function_exists('mb_strtolower')) { + function mb_strtolower($string, $encoding = null) { return p\Mbstring::mb_strtolower($string, $encoding); } +} +if (!function_exists('mb_strtoupper')) { + function mb_strtoupper($string, $encoding = null) { return p\Mbstring::mb_strtoupper($string, $encoding); } +} +if (!function_exists('mb_substitute_character')) { + function mb_substitute_character($substitute_character = null) { return p\Mbstring::mb_substitute_character($substitute_character); } +} +if (!function_exists('mb_substr')) { + function mb_substr($string, $start, $length = 2147483647, $encoding = null) { return p\Mbstring::mb_substr($string, $start, $length, $encoding); } +} +if (!function_exists('mb_stripos')) { + function mb_stripos($haystack, $needle, $offset = 0, $encoding = null) { return p\Mbstring::mb_stripos($haystack, $needle, $offset, $encoding); } +} +if (!function_exists('mb_stristr')) { + function mb_stristr($haystack, $needle, $before_needle = false, $encoding = null) { return p\Mbstring::mb_stristr($haystack, $needle, $before_needle, $encoding); } +} +if (!function_exists('mb_strrchr')) { + function mb_strrchr($haystack, $needle, $before_needle = false, $encoding = null) { return p\Mbstring::mb_strrchr($haystack, $needle, $before_needle, $encoding); } +} +if (!function_exists('mb_strrichr')) { + function mb_strrichr($haystack, $needle, $before_needle = false, $encoding = null) { return p\Mbstring::mb_strrichr($haystack, $needle, $before_needle, $encoding); } +} +if (!function_exists('mb_strripos')) { + function mb_strripos($haystack, $needle, $offset = 0, $encoding = null) { return p\Mbstring::mb_strripos($haystack, $needle, $offset, $encoding); } +} +if (!function_exists('mb_strrpos')) { + function mb_strrpos($haystack, $needle, $offset = 0, $encoding = null) { return p\Mbstring::mb_strrpos($haystack, $needle, $offset, $encoding); } +} +if (!function_exists('mb_strstr')) { + function mb_strstr($haystack, $needle, $before_needle = false, $encoding = null) { return p\Mbstring::mb_strstr($haystack, $needle, $before_needle, $encoding); } +} +if (!function_exists('mb_get_info')) { + function mb_get_info($type = 'all') { return p\Mbstring::mb_get_info($type); } +} +if (!function_exists('mb_http_output')) { + function mb_http_output($encoding = null) { return p\Mbstring::mb_http_output($encoding); } +} +if (!function_exists('mb_strwidth')) { + function mb_strwidth($string, $encoding = null) { return p\Mbstring::mb_strwidth($string, $encoding); } +} +if (!function_exists('mb_substr_count')) { + function mb_substr_count($haystack, $needle, $encoding = null) { return p\Mbstring::mb_substr_count($haystack, $needle, $encoding); } +} +if (!function_exists('mb_output_handler')) { + function mb_output_handler($string, $status) { return p\Mbstring::mb_output_handler($string, $status); } +} +if (!function_exists('mb_http_input')) { + function mb_http_input($type = null) { return p\Mbstring::mb_http_input($type); } +} + +if (!function_exists('mb_convert_variables')) { + function mb_convert_variables($to_encoding, $from_encoding, &...$vars) { return p\Mbstring::mb_convert_variables($to_encoding, $from_encoding, ...$vars); } +} + +if (!function_exists('mb_ord')) { + function mb_ord($string, $encoding = null) { return p\Mbstring::mb_ord($string, $encoding); } +} +if (!function_exists('mb_chr')) { + function mb_chr($codepoint, $encoding = null) { return p\Mbstring::mb_chr($codepoint, $encoding); } +} +if (!function_exists('mb_scrub')) { + function mb_scrub($string, $encoding = null) { $encoding = null === $encoding ? mb_internal_encoding() : $encoding; return mb_convert_encoding($string, $encoding, $encoding); } +} +if (!function_exists('mb_str_split')) { + function mb_str_split($string, $length = 1, $encoding = null) { return p\Mbstring::mb_str_split($string, $length, $encoding); } +} + +if (extension_loaded('mbstring')) { + return; +} + +if (!defined('MB_CASE_UPPER')) { + define('MB_CASE_UPPER', 0); +} +if (!defined('MB_CASE_LOWER')) { + define('MB_CASE_LOWER', 1); +} +if (!defined('MB_CASE_TITLE')) { + define('MB_CASE_TITLE', 2); +} diff --git a/kirby/vendor/symfony/polyfill-mbstring/bootstrap80.php b/kirby/vendor/symfony/polyfill-mbstring/bootstrap80.php new file mode 100644 index 0000000..82f5ac4 --- /dev/null +++ b/kirby/vendor/symfony/polyfill-mbstring/bootstrap80.php @@ -0,0 +1,143 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Symfony\Polyfill\Mbstring as p; + +if (!function_exists('mb_convert_encoding')) { + function mb_convert_encoding(array|string|null $string, ?string $to_encoding, array|string|null $from_encoding = null): array|string|false { return p\Mbstring::mb_convert_encoding($string ?? '', (string) $to_encoding, $from_encoding); } +} +if (!function_exists('mb_decode_mimeheader')) { + function mb_decode_mimeheader(?string $string): string { return p\Mbstring::mb_decode_mimeheader((string) $string); } +} +if (!function_exists('mb_encode_mimeheader')) { + function mb_encode_mimeheader(?string $string, ?string $charset = null, ?string $transfer_encoding = null, ?string $newline = "\r\n", ?int $indent = 0): string { return p\Mbstring::mb_encode_mimeheader((string) $string, $charset, $transfer_encoding, (string) $newline, (int) $indent); } +} +if (!function_exists('mb_decode_numericentity')) { + function mb_decode_numericentity(?string $string, array $map, ?string $encoding = null): string { return p\Mbstring::mb_decode_numericentity((string) $string, $map, $encoding); } +} +if (!function_exists('mb_encode_numericentity')) { + function mb_encode_numericentity(?string $string, array $map, ?string $encoding = null, ?bool $hex = false): string { return p\Mbstring::mb_encode_numericentity((string) $string, $map, $encoding, (bool) $hex); } +} +if (!function_exists('mb_convert_case')) { + function mb_convert_case(?string $string, ?int $mode, ?string $encoding = null): string { return p\Mbstring::mb_convert_case((string) $string, (int) $mode, $encoding); } +} +if (!function_exists('mb_internal_encoding')) { + function mb_internal_encoding(?string $encoding = null): string|bool { return p\Mbstring::mb_internal_encoding($encoding); } +} +if (!function_exists('mb_language')) { + function mb_language(?string $language = null): string|bool { return p\Mbstring::mb_language($language); } +} +if (!function_exists('mb_list_encodings')) { + function mb_list_encodings(): array { return p\Mbstring::mb_list_encodings(); } +} +if (!function_exists('mb_encoding_aliases')) { + function mb_encoding_aliases(?string $encoding): array { return p\Mbstring::mb_encoding_aliases((string) $encoding); } +} +if (!function_exists('mb_check_encoding')) { + function mb_check_encoding(array|string|null $value = null, ?string $encoding = null): bool { return p\Mbstring::mb_check_encoding($value, $encoding); } +} +if (!function_exists('mb_detect_encoding')) { + function mb_detect_encoding(?string $string, array|string|null $encodings = null, ?bool $strict = false): string|false { return p\Mbstring::mb_detect_encoding((string) $string, $encodings, (bool) $strict); } +} +if (!function_exists('mb_detect_order')) { + function mb_detect_order(array|string|null $encoding = null): array|bool { return p\Mbstring::mb_detect_order($encoding); } +} +if (!function_exists('mb_parse_str')) { + function mb_parse_str(?string $string, &$result = []): bool { parse_str((string) $string, $result); return (bool) $result; } +} +if (!function_exists('mb_strlen')) { + function mb_strlen(?string $string, ?string $encoding = null): int { return p\Mbstring::mb_strlen((string) $string, $encoding); } +} +if (!function_exists('mb_strpos')) { + function mb_strpos(?string $haystack, ?string $needle, ?int $offset = 0, ?string $encoding = null): int|false { return p\Mbstring::mb_strpos((string) $haystack, (string) $needle, (int) $offset, $encoding); } +} +if (!function_exists('mb_strtolower')) { + function mb_strtolower(?string $string, ?string $encoding = null): string { return p\Mbstring::mb_strtolower((string) $string, $encoding); } +} +if (!function_exists('mb_strtoupper')) { + function mb_strtoupper(?string $string, ?string $encoding = null): string { return p\Mbstring::mb_strtoupper((string) $string, $encoding); } +} +if (!function_exists('mb_substitute_character')) { + function mb_substitute_character(string|int|null $substitute_character = null): string|int|bool { return p\Mbstring::mb_substitute_character($substitute_character); } +} +if (!function_exists('mb_substr')) { + function mb_substr(?string $string, ?int $start, ?int $length = null, ?string $encoding = null): string { return p\Mbstring::mb_substr((string) $string, (int) $start, $length, $encoding); } +} +if (!function_exists('mb_stripos')) { + function mb_stripos(?string $haystack, ?string $needle, ?int $offset = 0, ?string $encoding = null): int|false { return p\Mbstring::mb_stripos((string) $haystack, (string) $needle, (int) $offset, $encoding); } +} +if (!function_exists('mb_stristr')) { + function mb_stristr(?string $haystack, ?string $needle, ?bool $before_needle = false, ?string $encoding = null): string|false { return p\Mbstring::mb_stristr((string) $haystack, (string) $needle, (bool) $before_needle, $encoding); } +} +if (!function_exists('mb_strrchr')) { + function mb_strrchr(?string $haystack, ?string $needle, ?bool $before_needle = false, ?string $encoding = null): string|false { return p\Mbstring::mb_strrchr((string) $haystack, (string) $needle, (bool) $before_needle, $encoding); } +} +if (!function_exists('mb_strrichr')) { + function mb_strrichr(?string $haystack, ?string $needle, ?bool $before_needle = false, ?string $encoding = null): string|false { return p\Mbstring::mb_strrichr((string) $haystack, (string) $needle, (bool) $before_needle, $encoding); } +} +if (!function_exists('mb_strripos')) { + function mb_strripos(?string $haystack, ?string $needle, ?int $offset = 0, ?string $encoding = null): int|false { return p\Mbstring::mb_strripos((string) $haystack, (string) $needle, (int) $offset, $encoding); } +} +if (!function_exists('mb_strrpos')) { + function mb_strrpos(?string $haystack, ?string $needle, ?int $offset = 0, ?string $encoding = null): int|false { return p\Mbstring::mb_strrpos((string) $haystack, (string) $needle, (int) $offset, $encoding); } +} +if (!function_exists('mb_strstr')) { + function mb_strstr(?string $haystack, ?string $needle, ?bool $before_needle = false, ?string $encoding = null): string|false { return p\Mbstring::mb_strstr((string) $haystack, (string) $needle, (bool) $before_needle, $encoding); } +} +if (!function_exists('mb_get_info')) { + function mb_get_info(?string $type = 'all'): array|string|int|false { return p\Mbstring::mb_get_info((string) $type); } +} +if (!function_exists('mb_http_output')) { + function mb_http_output(?string $encoding = null): string|bool { return p\Mbstring::mb_http_output($encoding); } +} +if (!function_exists('mb_strwidth')) { + function mb_strwidth(?string $string, ?string $encoding = null): int { return p\Mbstring::mb_strwidth((string) $string, $encoding); } +} +if (!function_exists('mb_substr_count')) { + function mb_substr_count(?string $haystack, ?string $needle, ?string $encoding = null): int { return p\Mbstring::mb_substr_count((string) $haystack, (string) $needle, $encoding); } +} +if (!function_exists('mb_output_handler')) { + function mb_output_handler(?string $string, ?int $status): string { return p\Mbstring::mb_output_handler((string) $string, (int) $status); } +} +if (!function_exists('mb_http_input')) { + function mb_http_input(?string $type = null): array|string|false { return p\Mbstring::mb_http_input($type); } +} + +if (!function_exists('mb_convert_variables')) { + function mb_convert_variables(?string $to_encoding, array|string|null $from_encoding, mixed &$var, mixed &...$vars): string|false { return p\Mbstring::mb_convert_variables((string) $to_encoding, $from_encoding ?? '', $var, ...$vars); } +} + +if (!function_exists('mb_ord')) { + function mb_ord(?string $string, ?string $encoding = null): int|false { return p\Mbstring::mb_ord((string) $string, $encoding); } +} +if (!function_exists('mb_chr')) { + function mb_chr(?int $codepoint, ?string $encoding = null): string|false { return p\Mbstring::mb_chr((int) $codepoint, $encoding); } +} +if (!function_exists('mb_scrub')) { + function mb_scrub(?string $string, ?string $encoding = null): string { $encoding ??= mb_internal_encoding(); return mb_convert_encoding((string) $string, $encoding, $encoding); } +} +if (!function_exists('mb_str_split')) { + function mb_str_split(?string $string, ?int $length = 1, ?string $encoding = null): array { return p\Mbstring::mb_str_split((string) $string, (int) $length, $encoding); } +} + +if (extension_loaded('mbstring')) { + return; +} + +if (!defined('MB_CASE_UPPER')) { + define('MB_CASE_UPPER', 0); +} +if (!defined('MB_CASE_LOWER')) { + define('MB_CASE_LOWER', 1); +} +if (!defined('MB_CASE_TITLE')) { + define('MB_CASE_TITLE', 2); +} diff --git a/kirby/vendor/symfony/polyfill-mbstring/composer.json b/kirby/vendor/symfony/polyfill-mbstring/composer.json new file mode 100644 index 0000000..2ed7a74 --- /dev/null +++ b/kirby/vendor/symfony/polyfill-mbstring/composer.json @@ -0,0 +1,38 @@ +{ + "name": "symfony/polyfill-mbstring", + "type": "library", + "description": "Symfony polyfill for the Mbstring extension", + "keywords": ["polyfill", "shim", "compatibility", "portable", "mbstring"], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=7.1" + }, + "autoload": { + "psr-4": { "Symfony\\Polyfill\\Mbstring\\": "" }, + "files": [ "bootstrap.php" ] + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "minimum-stability": "dev", + "extra": { + "branch-alias": { + "dev-main": "1.23-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + } +} diff --git a/kirby/vendor/true/punycode/LICENSE b/kirby/vendor/true/punycode/LICENSE new file mode 100644 index 0000000..963c72a --- /dev/null +++ b/kirby/vendor/true/punycode/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2014 TrueServer B.V. + +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. \ No newline at end of file diff --git a/kirby/vendor/true/punycode/composer.json b/kirby/vendor/true/punycode/composer.json new file mode 100644 index 0000000..5b2815a --- /dev/null +++ b/kirby/vendor/true/punycode/composer.json @@ -0,0 +1,26 @@ +{ + "name": "true/punycode", + "description": "A Bootstring encoding of Unicode for Internationalized Domain Names in Applications (IDNA)", + "keywords": ["IDNA", "punycode"], + "homepage": "https://github.com/true/php-punycode", + "license": "MIT", + "authors": [ + { + "name": "Renan Gonçalves", + "email": "renan.saddam@gmail.com" + } + ], + "autoload": { + "psr-4": { + "TrueBV\\": "src/" + } + }, + "require": { + "symfony/polyfill-mbstring": "^1.3", + "php": ">=5.3.0" + }, + "require-dev": { + "phpunit/phpunit": "~4.7", + "squizlabs/php_codesniffer": "~2.0" + } +} diff --git a/kirby/vendor/true/punycode/src/Exception/DomainOutOfBoundsException.php b/kirby/vendor/true/punycode/src/Exception/DomainOutOfBoundsException.php new file mode 100644 index 0000000..f50fdc9 --- /dev/null +++ b/kirby/vendor/true/punycode/src/Exception/DomainOutOfBoundsException.php @@ -0,0 +1,13 @@ + + */ +class DomainOutOfBoundsException extends OutOfBoundsException +{ + +} diff --git a/kirby/vendor/true/punycode/src/Exception/LabelOutOfBoundsException.php b/kirby/vendor/true/punycode/src/Exception/LabelOutOfBoundsException.php new file mode 100644 index 0000000..e23b141 --- /dev/null +++ b/kirby/vendor/true/punycode/src/Exception/LabelOutOfBoundsException.php @@ -0,0 +1,13 @@ + + */ +class LabelOutOfBoundsException extends OutOfBoundsException +{ + +} diff --git a/kirby/vendor/true/punycode/src/Exception/OutOfBoundsException.php b/kirby/vendor/true/punycode/src/Exception/OutOfBoundsException.php new file mode 100644 index 0000000..73b7229 --- /dev/null +++ b/kirby/vendor/true/punycode/src/Exception/OutOfBoundsException.php @@ -0,0 +1,13 @@ + + */ +class OutOfBoundsException extends \RuntimeException +{ + +} diff --git a/kirby/vendor/true/punycode/src/Punycode.php b/kirby/vendor/true/punycode/src/Punycode.php new file mode 100644 index 0000000..fbc54dd --- /dev/null +++ b/kirby/vendor/true/punycode/src/Punycode.php @@ -0,0 +1,360 @@ + 0, 'b' => 1, 'c' => 2, 'd' => 3, 'e' => 4, 'f' => 5, + 'g' => 6, 'h' => 7, 'i' => 8, 'j' => 9, 'k' => 10, 'l' => 11, + 'm' => 12, 'n' => 13, 'o' => 14, 'p' => 15, 'q' => 16, 'r' => 17, + 's' => 18, 't' => 19, 'u' => 20, 'v' => 21, 'w' => 22, 'x' => 23, + 'y' => 24, 'z' => 25, '0' => 26, '1' => 27, '2' => 28, '3' => 29, + '4' => 30, '5' => 31, '6' => 32, '7' => 33, '8' => 34, '9' => 35 + ); + + /** + * Character encoding + * + * @param string + */ + protected $encoding; + + /** + * Constructor + * + * @param string $encoding Character encoding + */ + public function __construct($encoding = 'UTF-8') + { + $this->encoding = $encoding; + } + + /** + * Encode a domain to its Punycode version + * + * @param string $input Domain name in Unicode to be encoded + * @return string Punycode representation in ASCII + */ + public function encode($input) + { + $input = mb_strtolower($input, $this->encoding); + $parts = explode('.', $input); + foreach ($parts as &$part) { + $length = strlen($part); + if ($length < 1) { + throw new LabelOutOfBoundsException(sprintf('The length of any one label is limited to between 1 and 63 octets, but %s given.', $length)); + } + $part = $this->encodePart($part); + } + $output = implode('.', $parts); + $length = strlen($output); + if ($length > 255) { + throw new DomainOutOfBoundsException(sprintf('A full domain name is limited to 255 octets (including the separators), %s given.', $length)); + } + + return $output; + } + + /** + * Encode a part of a domain name, such as tld, to its Punycode version + * + * @param string $input Part of a domain name + * @return string Punycode representation of a domain part + */ + protected function encodePart($input) + { + $codePoints = $this->listCodePoints($input); + + $n = static::INITIAL_N; + $bias = static::INITIAL_BIAS; + $delta = 0; + $h = $b = count($codePoints['basic']); + + $output = ''; + foreach ($codePoints['basic'] as $code) { + $output .= $this->codePointToChar($code); + } + if ($input === $output) { + return $output; + } + if ($b > 0) { + $output .= static::DELIMITER; + } + + $codePoints['nonBasic'] = array_unique($codePoints['nonBasic']); + sort($codePoints['nonBasic']); + + $i = 0; + $length = mb_strlen($input, $this->encoding); + while ($h < $length) { + $m = $codePoints['nonBasic'][$i++]; + $delta = $delta + ($m - $n) * ($h + 1); + $n = $m; + + foreach ($codePoints['all'] as $c) { + if ($c < $n || $c < static::INITIAL_N) { + $delta++; + } + if ($c === $n) { + $q = $delta; + for ($k = static::BASE;; $k += static::BASE) { + $t = $this->calculateThreshold($k, $bias); + if ($q < $t) { + break; + } + + $code = $t + (($q - $t) % (static::BASE - $t)); + $output .= static::$encodeTable[$code]; + + $q = ($q - $t) / (static::BASE - $t); + } + + $output .= static::$encodeTable[$q]; + $bias = $this->adapt($delta, $h + 1, ($h === $b)); + $delta = 0; + $h++; + } + } + + $delta++; + $n++; + } + $out = static::PREFIX . $output; + $length = strlen($out); + if ($length > 63 || $length < 1) { + throw new LabelOutOfBoundsException(sprintf('The length of any one label is limited to between 1 and 63 octets, but %s given.', $length)); + } + + return $out; + } + + /** + * Decode a Punycode domain name to its Unicode counterpart + * + * @param string $input Domain name in Punycode + * @return string Unicode domain name + */ + public function decode($input) + { + $input = strtolower($input); + $parts = explode('.', $input); + foreach ($parts as &$part) { + $length = strlen($part); + if ($length > 63 || $length < 1) { + throw new LabelOutOfBoundsException(sprintf('The length of any one label is limited to between 1 and 63 octets, but %s given.', $length)); + } + if (strpos($part, static::PREFIX) !== 0) { + continue; + } + + $part = substr($part, strlen(static::PREFIX)); + $part = $this->decodePart($part); + } + $output = implode('.', $parts); + $length = strlen($output); + if ($length > 255) { + throw new DomainOutOfBoundsException(sprintf('A full domain name is limited to 255 octets (including the separators), %s given.', $length)); + } + + return $output; + } + + /** + * Decode a part of domain name, such as tld + * + * @param string $input Part of a domain name + * @return string Unicode domain part + */ + protected function decodePart($input) + { + $n = static::INITIAL_N; + $i = 0; + $bias = static::INITIAL_BIAS; + $output = ''; + + $pos = strrpos($input, static::DELIMITER); + if ($pos !== false) { + $output = substr($input, 0, $pos++); + } else { + $pos = 0; + } + + $outputLength = strlen($output); + $inputLength = strlen($input); + while ($pos < $inputLength) { + $oldi = $i; + $w = 1; + + for ($k = static::BASE;; $k += static::BASE) { + $digit = static::$decodeTable[$input[$pos++]]; + $i = $i + ($digit * $w); + $t = $this->calculateThreshold($k, $bias); + + if ($digit < $t) { + break; + } + + $w = $w * (static::BASE - $t); + } + + $bias = $this->adapt($i - $oldi, ++$outputLength, ($oldi === 0)); + $n = $n + (int) ($i / $outputLength); + $i = $i % ($outputLength); + $output = mb_substr($output, 0, $i, $this->encoding) . $this->codePointToChar($n) . mb_substr($output, $i, $outputLength - 1, $this->encoding); + + $i++; + } + + return $output; + } + + /** + * Calculate the bias threshold to fall between TMIN and TMAX + * + * @param integer $k + * @param integer $bias + * @return integer + */ + protected function calculateThreshold($k, $bias) + { + if ($k <= $bias + static::TMIN) { + return static::TMIN; + } elseif ($k >= $bias + static::TMAX) { + return static::TMAX; + } + return $k - $bias; + } + + /** + * Bias adaptation + * + * @param integer $delta + * @param integer $numPoints + * @param boolean $firstTime + * @return integer + */ + protected function adapt($delta, $numPoints, $firstTime) + { + $delta = (int) ( + ($firstTime) + ? $delta / static::DAMP + : $delta / 2 + ); + $delta += (int) ($delta / $numPoints); + + $k = 0; + while ($delta > ((static::BASE - static::TMIN) * static::TMAX) / 2) { + $delta = (int) ($delta / (static::BASE - static::TMIN)); + $k = $k + static::BASE; + } + $k = $k + (int) (((static::BASE - static::TMIN + 1) * $delta) / ($delta + static::SKEW)); + + return $k; + } + + /** + * List code points for a given input + * + * @param string $input + * @return array Multi-dimension array with basic, non-basic and aggregated code points + */ + protected function listCodePoints($input) + { + $codePoints = array( + 'all' => array(), + 'basic' => array(), + 'nonBasic' => array(), + ); + + $length = mb_strlen($input, $this->encoding); + for ($i = 0; $i < $length; $i++) { + $char = mb_substr($input, $i, 1, $this->encoding); + $code = $this->charToCodePoint($char); + if ($code < 128) { + $codePoints['all'][] = $codePoints['basic'][] = $code; + } else { + $codePoints['all'][] = $codePoints['nonBasic'][] = $code; + } + } + + return $codePoints; + } + + /** + * Convert a single or multi-byte character to its code point + * + * @param string $char + * @return integer + */ + protected function charToCodePoint($char) + { + $code = ord($char[0]); + if ($code < 128) { + return $code; + } elseif ($code < 224) { + return (($code - 192) * 64) + (ord($char[1]) - 128); + } elseif ($code < 240) { + return (($code - 224) * 4096) + ((ord($char[1]) - 128) * 64) + (ord($char[2]) - 128); + } else { + return (($code - 240) * 262144) + ((ord($char[1]) - 128) * 4096) + ((ord($char[2]) - 128) * 64) + (ord($char[3]) - 128); + } + } + + /** + * Convert a code point to its single or multi-byte character + * + * @param integer $code + * @return string + */ + protected function codePointToChar($code) + { + if ($code <= 0x7F) { + return chr($code); + } elseif ($code <= 0x7FF) { + return chr(($code >> 6) + 192) . chr(($code & 63) + 128); + } elseif ($code <= 0xFFFF) { + return chr(($code >> 12) + 224) . chr((($code >> 6) & 63) + 128) . chr(($code & 63) + 128); + } else { + return chr(($code >> 18) + 240) . chr((($code >> 12) & 63) + 128) . chr((($code >> 6) & 63) + 128) . chr(($code & 63) + 128); + } + } +} diff --git a/kirby/views/fatal.php b/kirby/views/fatal.php new file mode 100644 index 0000000..17bf703 --- /dev/null +++ b/kirby/views/fatal.php @@ -0,0 +1,11 @@ + + +

    + This page is currently offline due to an unexpected error. We are very sorry for the inconvenience and will fix it as soon as possible. +

    +

    + Advice for developers and administrators:
    + Enable debug mode to get further information about the error. +

    + + diff --git a/kirby/views/panel.php b/kirby/views/panel.php new file mode 100644 index 0000000..c28bb1e --- /dev/null +++ b/kirby/views/panel.php @@ -0,0 +1,60 @@ + $config + * @var string $assetUrl + * @var string|false $customCss + * @var string $icons + * @var string $pluginCss + * @var string $pluginJs + * @var string $panelUrl + * @var string $nonce + * @var array $options + */ ?> + + + + + + + + + Kirby Panel + + + + + + + + + + + + + + + + + +
    + + + + + + + + + + + + + + + + diff --git a/kirby/views/php.php b/kirby/views/php.php new file mode 100644 index 0000000..1822568 --- /dev/null +++ b/kirby/views/php.php @@ -0,0 +1,11 @@ + + +

    + This page is currently offline. We are very sorry for the inconvenience and will fix it as soon as possible. +

    +

    + Advice for developers and administrators:
    + Change the PHP version to 7.3, 7.4 or 8.0 (PHP 7.4 is recommended) +

    + + diff --git a/kirby/views/snippets/footer.php b/kirby/views/snippets/footer.php new file mode 100644 index 0000000..308b1d0 --- /dev/null +++ b/kirby/views/snippets/footer.php @@ -0,0 +1,2 @@ + + diff --git a/kirby/views/snippets/header.php b/kirby/views/snippets/header.php new file mode 100644 index 0000000..558624e --- /dev/null +++ b/kirby/views/snippets/header.php @@ -0,0 +1,39 @@ + + + + + + + Error + + + + + diff --git a/media/index.html b/media/index.html new file mode 100644 index 0000000..e69de29 diff --git a/site/accounts/index.html b/site/accounts/index.html new file mode 100644 index 0000000..e69de29 diff --git a/site/blueprints/pages/default.yml b/site/blueprints/pages/default.yml new file mode 100644 index 0000000..0021d73 --- /dev/null +++ b/site/blueprints/pages/default.yml @@ -0,0 +1,8 @@ +title: Default Page +preset: page +fields: + text: + label: Text + type: textarea + size: large + diff --git a/site/blueprints/site.yml b/site/blueprints/site.yml new file mode 100644 index 0000000..3dd50f1 --- /dev/null +++ b/site/blueprints/site.yml @@ -0,0 +1,3 @@ +title: Site +preset: pages +unlisted: true diff --git a/site/cache/index.html b/site/cache/index.html new file mode 100644 index 0000000..e69de29 diff --git a/site/config/config.php b/site/config/config.php new file mode 100644 index 0000000..79fd5d4 --- /dev/null +++ b/site/config/config.php @@ -0,0 +1,7 @@ + [ + 'language' => 'fr' + ] +]; diff --git a/site/plugins/kirby-twig/LICENSE.md b/site/plugins/kirby-twig/LICENSE.md new file mode 100644 index 0000000..4378196 --- /dev/null +++ b/site/plugins/kirby-twig/LICENSE.md @@ -0,0 +1,9 @@ +MIT License + +Copyright (c) 2019 + +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/site/plugins/kirby-twig/README.md b/site/plugins/kirby-twig/README.md new file mode 100644 index 0000000..5672c98 --- /dev/null +++ b/site/plugins/kirby-twig/README.md @@ -0,0 +1,102 @@ +# Twig Plugin for Kirby CMS + + + +- Adds support for [Twig templates](http://twig.sensiolabs.org/) to [Kirby CMS](https://getkirby.com/) (3.0+). +- PHP templates still work, you don’t have to rewrite them if you don’t want to. + + +## What it looks like + +Before: + +```php + +

    title() ?>

    + +``` + +After: + +```twig +{# site/templates/hello.twig #} +

    {{ page.title }}

    +
    +``` + + +## Installation + +### Download + +Download and copy this repository to `/site/plugins/kirby-twig`. + +### Git submodule + +``` +git submodule add https://github.com/amteich/kirby-twig.git site/plugins/kirby-twig +``` + +### Composer + +``` +composer require amteich/kirby-twig +``` + +**** + +## Usage + +### Page templates + +Now that the plugin is installed and active, you can write Twig templates in the `site/templates` directory. For example, if the text file for your articles is named `post.txt`, you could have a `post.twig` template like this: + +```twig +{% extends 'layout.twig' %} +{% block content %} +
    +

    {{ page.title }}

    + {{ page.text.kirbytext | raw }} +
    +{% endblock %} +``` + +See the `{% extends '@templates/layout.twig' %}` and `{% block content %}` parts? They’re a powerful way to manage having a common page layout for many templates, and only changing the core content (and/or other specific parts). Read [our Twig templating guide](doc/guide.md) for more information. + +### Hint: Accessing pagemethods instead of public variables + +Twig calls to specific methods, like for instance `page.children` sometimes return `NULL`. This can occur, if there is also a public variable which is only initialized after calling the corresponding method. + +`{{ page.children }}` returns `NULL`, because the public variable is returned. Please call the method instead like this: `{{ page.children() }}`. + +## Options + +You can find a full list of options in the [options documentation](doc/options.md). + +**** + +## More documentation + +- [Twig templating guide for Kirby](doc/guide.md) +- [Available options](doc/options.md) +- [Using your own functions in templates](doc/functions.md) +- [Rendering a template in PHP: the `twig` helper](doc/twighelper.md) +- [Displaying Twig errors](doc/errors.md) + +## License + +[MIT](LICENSE.md) + +## Credits + +- Maintainer: [Christian Zehetner](https://github.com/seehat) +- Twig library: Fabien Potencier and contributors / [License](https://github.com/twigphp/Twig/blob/3.x/LICENSE) +- Twig plugin for Kirby 2: Florens Verschelde diff --git a/site/plugins/kirby-twig/composer.json b/site/plugins/kirby-twig/composer.json new file mode 100644 index 0000000..d07a8a5 --- /dev/null +++ b/site/plugins/kirby-twig/composer.json @@ -0,0 +1,29 @@ +{ + "name": "amteich/kirby-twig", + "description": "Twig templating support for Kirby CMS", + "type": "kirby-plugin", + "version": "4.1.4", + "license": "MIT", + "authors": [ + { + "name": "Christian Zehetner", + "email": "christian@am-teich.com" + }, + { + "name": "Florens Verschelde", + "email": "florens@fvsch.com" + } + ], + "require": { + "getkirby/composer-installer": "^1.1", + "twig/twig": "^3.0" + }, + "autoload": { + "psr-4": { + "amteich\\Twig\\": "src/classes/" + } + }, + "config": { + "optimize-autoloader": true + } +} diff --git a/site/plugins/kirby-twig/composer.lock b/site/plugins/kirby-twig/composer.lock new file mode 100644 index 0000000..2947c5d --- /dev/null +++ b/site/plugins/kirby-twig/composer.lock @@ -0,0 +1,287 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "a6f6c274441eefbc8a209a46f685b858", + "packages": [ + { + "name": "getkirby/composer-installer", + "version": "1.2.1", + "source": { + "type": "git", + "url": "https://github.com/getkirby/composer-installer.git", + "reference": "c98ece30bfba45be7ce457e1102d1b169d922f3d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/getkirby/composer-installer/zipball/c98ece30bfba45be7ce457e1102d1b169d922f3d", + "reference": "c98ece30bfba45be7ce457e1102d1b169d922f3d", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.0 || ^2.0" + }, + "require-dev": { + "composer/composer": "^1.8 || ^2.0" + }, + "type": "composer-plugin", + "extra": { + "class": "Kirby\\ComposerInstaller\\Plugin" + }, + "autoload": { + "psr-4": { + "Kirby\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Kirby's custom Composer installer for the Kirby CMS and for Kirby plugins", + "homepage": "https://getkirby.com", + "funding": [ + { + "url": "https://getkirby.com/buy", + "type": "custom" + } + ], + "time": "2020-12-28T12:54:39+00:00" + }, + { + "name": "symfony/polyfill-ctype", + "version": "v1.22.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "c6c942b1ac76c82448322025e084cadc56048b4e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/c6c942b1ac76c82448322025e084cadc56048b4e", + "reference": "c6c942b1ac76c82448322025e084cadc56048b4e", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "suggest": { + "ext-ctype": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.22-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Ctype\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Gert de Pagter", + "email": "BackEndTea@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for ctype functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "ctype", + "polyfill", + "portable" + ], + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-01-07T16:49:33+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.22.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "5232de97ee3b75b0360528dae24e73db49566ab1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/5232de97ee3b75b0360528dae24e73db49566ab1", + "reference": "5232de97ee3b75b0360528dae24e73db49566ab1", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.22-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-01-22T09:19:47+00:00" + }, + { + "name": "twig/twig", + "version": "v3.3.0", + "source": { + "type": "git", + "url": "https://github.com/twigphp/Twig.git", + "reference": "1f3b7e2c06cc05d42936a8ad508ff1db7975cdc5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/twigphp/Twig/zipball/1f3b7e2c06cc05d42936a8ad508ff1db7975cdc5", + "reference": "1f3b7e2c06cc05d42936a8ad508ff1db7975cdc5", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/polyfill-ctype": "^1.8", + "symfony/polyfill-mbstring": "^1.3" + }, + "require-dev": { + "psr/container": "^1.0", + "symfony/phpunit-bridge": "^4.4.9|^5.0.9" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.3-dev" + } + }, + "autoload": { + "psr-4": { + "Twig\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com", + "homepage": "http://fabien.potencier.org", + "role": "Lead Developer" + }, + { + "name": "Twig Team", + "role": "Contributors" + }, + { + "name": "Armin Ronacher", + "email": "armin.ronacher@active-4.com", + "role": "Project Founder" + } + ], + "description": "Twig, the flexible, fast, and secure template language for PHP", + "homepage": "https://twig.symfony.com", + "keywords": [ + "templating" + ], + "funding": [ + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/twig/twig", + "type": "tidelift" + } + ], + "time": "2021-02-08T09:54:36+00:00" + } + ], + "packages-dev": [], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": [], + "platform-dev": [], + "plugin-api-version": "1.1.0" +} diff --git a/site/plugins/kirby-twig/index.php b/site/plugins/kirby-twig/index.php new file mode 100644 index 0000000..49cd19c --- /dev/null +++ b/site/plugins/kirby-twig/index.php @@ -0,0 +1,46 @@ + [ + 'usephp' => true + ], + 'components' => [ + 'template' => function (App $kirby, string $name, string $contentType = 'html', string $defaultType = 'html') { + return new amteich\Twig\Template($name, $contentType, $defaultType); + }, + 'snippet' => function (Kirby $kirby, string $name, array $data = []) { + $snippets = A::wrap($name); + + foreach ($snippets as $name) { + $name = (string)$name; + $file = $kirby->root('snippets') . '/' . $name . '.php'; + + if (file_exists($file) === false) { + $file = $kirby->root('snippets') . '/' . $name . '.twig'; + if (file_exists($file)) { + return twig('@snippets/' . $name . '.twig', $data); + } + else { + $file = $kirby->extensions('snippets')[$name] ?? null; + + if (Str::endsWith(strtolower($file), '.twig')) { + return twig($name . '.twig', $data); + } + } + } + + if ($file) { + break; + } + } + + return Snippet::load($file, $data); + } + ] +]); diff --git a/site/plugins/kirby-twig/src/classes/Environment.php b/site/plugins/kirby-twig/src/classes/Environment.php new file mode 100755 index 0000000..4c56c9b --- /dev/null +++ b/site/plugins/kirby-twig/src/classes/Environment.php @@ -0,0 +1,425 @@ + + */ +class Environment +{ + /** @var Twig_Environment */ + public $twig = null; + + /** @var boolean */ + public $debug = false; + + /** @var TwigEnv */ + private static $instance = null; + + /** + * Kirby helper functions to expose as simple Twig functions + * + * We're exposing all helper functions documented in + * https://getkirby.com/docs/cheatsheet#helpers + * with just a few exceptions (sending email, saving files…) + * + * Prefix the function name with '*' to mark the + * function's output as safe (avoiding HTML escaping). + * + * @var array + */ + private $defaultFunctions = [ + '*attr' => 'attr', + 'asset' => 'asset', + 'collection' => 'collection', + '*csrf' => 'csrf', + '*csrf_field' => 'csrf_field', + '*honeypot_field' => 'honeypot_field', + '*css' => 'css', + // Skipping: e - Twig syntax is simple: {{ condition ? 'a' : 'b' }} + '*esc' => 'esc', + 'error' => 'amteich\Twig\Functions::error', + 'get' => 'get', + '*gist' => 'gist', + 'go' => 'go', + 'gravatar' => 'gravatar', + '*h' => 'h', + '*html' => 'html', + '*image' => 'image', + 'invalid' => 'invalid', + '*js' => 'js', + 'kirby' => 'kirby', + '*kirbytag' => 'kirbytag', + '*kirbytags' => 'kirbytags', + '*kirbytext' => 'kirbytext', + '*markdown' => 'markdown', + 'option' => 'option', // Get config value => 'option' + 'memory' => 'memory', + '*multiline' => 'multiline', + 'page' => 'page', + 'pages' => 'pages', + 'param' => 'param', + 'params' => 'params', + '*pattern' => 'pattern', + // Skipping: r - Same reason as for ecco/e + 'timestamp' => 'timestamp', + 'site' => 'site', + 'size' => 'size', + 'slug' => 'Str::slug', + '*smartypants' => 'smartypants', + '*snippet' => 'snippet', + '*strftime' => 'strftime', + '*svg' => 'svg', + 't' => 't', + 'tc' => 'tc', + 'tt' => 'tt', + '*twitter' => 'twitter', + 'u' => 'u', + 'url' => 'url', + 'url_build' => 'Url::build', + '*video' => 'video', + '*vimeo' => 'vimeo', + '*widont' => 'widont', + '*youtube' => 'youtube', + ]; + + /** + * Default twig tests + * + * @var array + */ + private $defaultTests = [ + 'of_type' => 'amteich\Twig\Tests::of_type', + ]; + + private $templateDir = null; + + /** + * Prepare the Twig environment + * @throws Twig_Error_Loader + */ + public function __construct(string $viewPath = '') + { + $kirby = Kirby::instance(); + $this->debug = option('debug'); + $this->templateDir = $kirby->root('templates'); + + // Get environment & user config + $options = [ + 'core' => [ + 'debug' => $this->debug, + 'strict_variables' => option('amteich.twig.strict', $this->debug), + 'autoescape' => option('amteich.twig.autoescape', 'html'), + 'cache' => false + ], + 'namespace' => [ + 'templates' => $this->templateDir, + 'snippets' => kirby()->roots()->snippets(), + 'plugins' => kirby()->roots()->plugins(), + ], + 'paths' => [], + 'function' => array_merge( + $this->defaultFunctions, + option('amteich.twig.env.functions', []) + ), + 'extension' => option('amteich.twig.env.extensions', []), + 'filter' => option('amteich.twig.env.filters', []), + 'test' => array_merge( + $this->defaultTests, + option('amteich.twig.env.tests', []) + ), + ]; + + // Set cache directory + if (option('amteich.twig.cache')) { + $options['core']['cache'] = kirby()->roots()->cache() . '/twig'; + } + + // Set up the template loader + $loader = new Twig_Loader_Filesystem($this->templateDir); + + // add site templates dir if environment got initialized by a plugintemplate + if ($this->templateDir != $kirby->root('templates')) { + $loader->addPath($kirby->root('templates')); + } + + // add plugin snippet paths + foreach ($kirby->extensions('snippets') as $snippetpath => $root) { + if (Str::endsWith(strtolower($root), '.twig')) { + $loader->addPath(str_replace($snippetpath . '.twig', '', $root)); + } + } + + // is viewpath in a plugin, add the pluginpath + if ($viewPath != $this->templateDir) { + $loader->addPath($viewPath); + } + + $options['namespace'] = array_merge( + $options['namespace'], + option('amteich.twig.namespaces', []) + ); + + $canSkip = ['snippets', 'plugins', 'assets']; + foreach ($options['namespace'] as $key=>$path) { + if (!is_string($path)) continue; + if (in_array($key, $canSkip) && !file_exists($path)) continue; + $loader->addPath($path, $key); + } + + $options['paths'] = option('amteich.twig.paths', []); + foreach ($options['paths'] as $path) { + $loader->addPath($path); + } + + // load plugin component paths + foreach ($kirby->plugins() as $id => $plugin) { + if (isset($plugin->extends()['twigcomponents'])) + { + $twigcomponents = $plugin->extends()['twigcomponents']; + if (is_callable($twigcomponents)) { + $twigcomponents = $twigcomponents(); + } + $loader->addPath($twigcomponents); + } + } + + // Start up Twig + $this->twig = new Twig_Environment($loader, $options['core']); + + // Enable Twig’s dump function + $this->twig->addExtension(new Twig_Extension_Debug()); + + // Plug in functions and filters + foreach ($options['extension'] as $className) { + $this->twig->addExtension(new $className()); + } + foreach ($options['function'] as $name => $func) { + $this->addCallable('function', $name, $func); + } + foreach ($options['filter'] as $name => $func) { + $this->addCallable('filter', $name, $func); + } + foreach ($options['test'] as $name => $func) { + $this->addCallable('test', $name, $func); + } + + // Make sure the instance is stored / overwritten + static::$instance = $this; + } + + /** + * Return a new instance or the cached instance if it exists + * @return TwigEnv + */ + public static function instance() + { + if (is_null(static::$instance)) { + static::$instance = new static; + } + return static::$instance; + } + + /** + * Render a Twig template from a file path, + * similarly to how Tpl::load renders a PHP template + * @param string $filePath + * @param array $tplData + * @param bool $return + * @param bool $isPage + * @return string + * @throws Twig_Error + */ + public function renderPath($filePath='', $tplData=[], $isPage=false) + { + // Remove the start of the templates path, since Twig asks + // for a path starting from one of the registered directories. + $path = ltrim(str_replace($this->templateDir, '', + preg_replace('#[\\\/]+#', '/', $filePath)), '/'); + + try { + $content = $this->twig->render($path, $tplData); + } + catch (Twig_Error $err) { + $content = $this->error($err, $isPage); + } + + return $content; + } + + /** + * Render a Twig template from a string + * @param string $tplString + * @param array $tplData + * @return string + * @throws Twig_Error + * @throws \Exception + * @throws \Throwable + */ + public function renderString($tplString='', $tplData=[]) + { + try { + return $this->twig->createTemplate($tplString)->render($tplData); + } + catch (Twig_Error $err) { + return $this->error($err, false, $tplString); + } + } + + /** + * Handle Twig errors, with different scenarios depending on if we're + * rendering a full page or a fragment (e.g. when using the `twig` helper), + * and if we're in debug mode or not. + * + * | Page mode | Fragment mode + * -------|----------------------| -------------- + * Debug: | Custom error page | Error message + * -------|----------------------| -------------- + * Prod: | Standard error page, | Empty string + * | or let error through | + * + * @param Twig_Error $err + * @param boolean $isPage + * @param string $templateString + * @return string|Response + * @throws Twig_Error + */ + private function error(Twig_Error $err, $isPage=false, $templateString=null) + { + if (!$this->debug) { + if (!$isPage) return ''; + // Debug mode off: show the site's error page + try { + $kirby = Kirby::instance(); + $page = $kirby->site()->page($kirby->get('option', 'error')); + if ($page) return $kirby->render($page); + } + // avoid loops + catch (Twig_Error $err2) { + } + // Error page didn't exist or was buggy: rethrow the initial error + // Can result in the 'fatal.php' white error page (in Kirby 2.4+ + // with Whoops active), or an empty response (white page). + // That’s consistent with errors for e.g. missing base templates. + throw $err; + } + + // Gather information + $sourceContext = $err->getSourceContext(); + $name = $sourceContext != null ? $sourceContext->getName() : ''; + $line = $err->getTemplateLine(); + $msg = $err->getRawMessage(); + $path = null; + $code = $templateString ? $templateString : ''; + if (!$templateString) { + try { + $source = $this->twig->getLoader()->getSourceContext($name); + $path = $source->getPath(); + $code = $source->getCode(); + + } + catch (Twig_Error $err2) {} + } + + // When returning a HTML fragment + if (!$isPage && $this->debug) { + $info = get_class($err) . ', line ' . $line . ' of ' . + ($templateString ? 'template string:' : $name); + $src = $this->getSourceExcerpt($code, $line, 1, false); + return 'Error: ' . $info . "\n" . + '
    '.$src.'
    ' . "\n" . + '➡ ' . $msg . "
    \n"; + } + + // When rendering a full page with Twig: make a custom error page + // Note for Kirby 2.4+: we don't use the Whoops error page because + // it's not possible to surface Twig source code in it's stack trace + // and code viewer. Whoops will only show the PHP method calls going + // in in the Twig library. That's a know — but unresolved — issue. + // https://github.com/filp/whoops/issues/167 + // https://github.com/twigphp/Twig/issues/1347 + // So we roll our own. + $html = Tpl::load(dirname(__DIR__) . '/errorpage.php', [ + 'title' => get_class($err), + 'subtitle' => 'Line ' . $line . ' of ' . ($path ? $path : $name), + 'message' => $msg, + 'code' => $this->getSourceExcerpt($code, $line, 6, true) + ]); + return new Response($html, 'html', 500); + } + + /** + * Extract a few lines of source code from a source string + * @param string $source + * @param int $line + * @param int $plus + * @param bool $format + * @return string + */ + private function getSourceExcerpt($source='', $line=1, $plus=1, $format=false) + { + $excerpt = []; + $twig = Html::encode($source, false); + $lines = preg_split("/(\r\n|\n|\r)/", $twig); + $start = max(1, $line - $plus); + $limit = min(count($lines), $line + $plus); + for ($i = $start - 1; $i < $limit; $i++) { + if ($format) { + $attr = 'data-line="'.($i+1).'"'; + if ($i === $line - 1) $excerpt[] = "$lines[$i]"; + else $excerpt[] = "$lines[$i]"; + } + else { + $excerpt[] = $lines[$i]; + } + } + return implode("\n", $excerpt); + } + + /** + * Expose a function to the Twig environment as a function or filter + * @param string $type + * @param string $name + * @param string|Closure $func + */ + private function addCallable($type, $name, $func) + { + if (!is_string($name) || !is_callable($func)) { + return; + } + $twname = trim($name, '*'); + $params = []; + if (strpos($name, '*') === 0) { + $params['is_safe'] = ['html']; + } + if ($type === 'function') { + $this->twig->addFunction(new Twig_Function($twname, $func, $params)); + } + if ($type === 'filter') { + $this->twig->addFilter(new Twig_Filter($twname, $func, $params)); + } + if ($type === 'test') { + $this->twig->addTest(new Twig_Test($twname, $func)); + } + } +} diff --git a/site/plugins/kirby-twig/src/classes/Functions.php b/site/plugins/kirby-twig/src/classes/Functions.php new file mode 100644 index 0000000..eb3e84e --- /dev/null +++ b/site/plugins/kirby-twig/src/classes/Functions.php @@ -0,0 +1,15 @@ +' . $context, -1, $sourceObj); + } + +} diff --git a/site/plugins/kirby-twig/src/classes/Plugin.php b/site/plugins/kirby-twig/src/classes/Plugin.php new file mode 100644 index 0000000..f7ae0aa --- /dev/null +++ b/site/plugins/kirby-twig/src/classes/Plugin.php @@ -0,0 +1,47 @@ + + */ +class Plugin +{ + /** + * Renders a Twig template string or template file + * Can be used in Kirby controllers and PHP templates + * + * * Example usage: + * + * 'World']) ?> + * 'Home page']) ?> + * + * Note: in Twig templates, you should use the `include` tag or function instead. + * + * @param string $template - path or template string to render + * @param array $userData - data to pass as variables to the template + * @return string + */ + static public function render($template, $userData) + { + if (!is_string($template)) return ''; + $path = strlen($template) <= 256 ? trim($template) : ''; + $data = array_merge(is_array($userData) ? $userData : [], ['kirby' => kirby()]); + $data = array_merge($data, Template::$data); + + $twig = Environment::instance(); + + // treat template as a path only if it *looks like* a Twig template path + if (Str::startsWith($path, '@') || Str::endsWith(strtolower($path), '.twig')) { + return $twig->renderPath($path, $data); + } + return $twig->renderString($template, $data); + } +} diff --git a/site/plugins/kirby-twig/src/classes/Template.php b/site/plugins/kirby-twig/src/classes/Template.php new file mode 100644 index 0000000..667f56a --- /dev/null +++ b/site/plugins/kirby-twig/src/classes/Template.php @@ -0,0 +1,117 @@ + + */ +class Template extends \Kirby\Cms\Template +{ + private static $twig; + + /** + * Creates a new template object + * + * @param string $name + * @param string $type + * @param string $defaultType + */ + public function __construct(string $name, string $contentType = 'html', string $defaultType = 'html') + { + parent::__construct($name, $contentType, $defaultType); + $viewPath = dirname($this->file()); + static::$twig = new Environment($viewPath); + } + + /** + * Returns the expected template file extension + * + * @return string + */ + public function extension(): string + { + return 'twig'; + } + + public function isTwig(): bool + { + $length = strlen($this->extension()); + return substr($this->file(), -$length) === $this->extension(); + } + + /** + * Detects the location of the template file + * if it exists. + * + * @return string|null + */ + public function file(): ?string + { + $usephp = option('amteich.twig.usephp', true); + $type = $this->type(); + + if ($this->hasDefaultType() === true) { + + $base = $this->root() . '/' . $this->name(); + $twig = $base . '.twig'; + $php = $base . '.php'; + + if ($usephp and !is_file($twig) and is_file($php)) { + return F::realpath($php, $this->root()); + } else { + try { + return F::realpath($twig, $this->root()); + } catch (Exception $e) { + $path = App::instance()->extension($this->store(), $this->name()); + + if ($path !== null) { + return $path; + } + } + } + + } + + $name = $this->name() . '.' . $type; + $base = $this->root() . '/' . $name; + $twig = $base . '.twig'; + $php = $base . '.php'; + + // only check existing files if PHP template support is active + if ($usephp and !is_file($twig) and is_file($php)) { + return F::realpath($php, $this->root()); + } else { + try { + return F::realpath($twig, $this->root()); + } catch (Exception $e) { + // Look for the template with type extension provided by an extension. + // This might be null if the template does not exist. + return App::instance()->extension($this->store(), $name); + } + } + } + + /** + * @param array $data + * @return string + */ + public function render(array $data = []): string + { + if ($this->isTwig()) { + return static::$twig->renderPath($this->name() . '.' . $this->extension(), $data, true); + } + return parent::render($data); + } +} diff --git a/site/plugins/kirby-twig/src/classes/Tests.php b/site/plugins/kirby-twig/src/classes/Tests.php new file mode 100644 index 0000000..c47bf4f --- /dev/null +++ b/site/plugins/kirby-twig/src/classes/Tests.php @@ -0,0 +1,50 @@ + + + + + + <?php echo $title ?> + + + +
    +

    + $subtitle\n"; } ?> + $message

    \n"; } ?> +
    + $code
    "; } ?> + + diff --git a/site/plugins/kirby-twig/src/helpers.php b/site/plugins/kirby-twig/src/helpers.php new file mode 100644 index 0000000..a3a2c56 --- /dev/null +++ b/site/plugins/kirby-twig/src/helpers.php @@ -0,0 +1,15 @@ + + * @param string $template + * @param array $userData + * @return string + */ +function twig($template='', $userData=[]) +{ + return amteich\Twig\Plugin::render($template, $userData); +} diff --git a/site/plugins/kirby-twig/vendor/autoload.php b/site/plugins/kirby-twig/vendor/autoload.php new file mode 100644 index 0000000..a7f858f --- /dev/null +++ b/site/plugins/kirby-twig/vendor/autoload.php @@ -0,0 +1,7 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Autoload; + +/** + * ClassLoader implements a PSR-0, PSR-4 and classmap class loader. + * + * $loader = new \Composer\Autoload\ClassLoader(); + * + * // register classes with namespaces + * $loader->add('Symfony\Component', __DIR__.'/component'); + * $loader->add('Symfony', __DIR__.'/framework'); + * + * // activate the autoloader + * $loader->register(); + * + * // to enable searching the include path (eg. for PEAR packages) + * $loader->setUseIncludePath(true); + * + * In this example, if you try to use a class in the Symfony\Component + * namespace or one of its children (Symfony\Component\Console for instance), + * the autoloader will first look for the class under the component/ + * directory, and it will then fallback to the framework/ directory if not + * found before giving up. + * + * This class is loosely based on the Symfony UniversalClassLoader. + * + * @author Fabien Potencier + * @author Jordi Boggiano + * @see http://www.php-fig.org/psr/psr-0/ + * @see http://www.php-fig.org/psr/psr-4/ + */ +class ClassLoader +{ + // PSR-4 + private $prefixLengthsPsr4 = array(); + private $prefixDirsPsr4 = array(); + private $fallbackDirsPsr4 = array(); + + // PSR-0 + private $prefixesPsr0 = array(); + private $fallbackDirsPsr0 = array(); + + private $useIncludePath = false; + private $classMap = array(); + private $classMapAuthoritative = false; + private $missingClasses = array(); + private $apcuPrefix; + + public function getPrefixes() + { + if (!empty($this->prefixesPsr0)) { + return call_user_func_array('array_merge', array_values($this->prefixesPsr0)); + } + + return array(); + } + + public function getPrefixesPsr4() + { + return $this->prefixDirsPsr4; + } + + public function getFallbackDirs() + { + return $this->fallbackDirsPsr0; + } + + public function getFallbackDirsPsr4() + { + return $this->fallbackDirsPsr4; + } + + public function getClassMap() + { + return $this->classMap; + } + + /** + * @param array $classMap Class to filename map + */ + public function addClassMap(array $classMap) + { + if ($this->classMap) { + $this->classMap = array_merge($this->classMap, $classMap); + } else { + $this->classMap = $classMap; + } + } + + /** + * Registers a set of PSR-0 directories for a given prefix, either + * appending or prepending to the ones previously set for this prefix. + * + * @param string $prefix The prefix + * @param array|string $paths The PSR-0 root directories + * @param bool $prepend Whether to prepend the directories + */ + public function add($prefix, $paths, $prepend = false) + { + if (!$prefix) { + if ($prepend) { + $this->fallbackDirsPsr0 = array_merge( + (array) $paths, + $this->fallbackDirsPsr0 + ); + } else { + $this->fallbackDirsPsr0 = array_merge( + $this->fallbackDirsPsr0, + (array) $paths + ); + } + + return; + } + + $first = $prefix[0]; + if (!isset($this->prefixesPsr0[$first][$prefix])) { + $this->prefixesPsr0[$first][$prefix] = (array) $paths; + + return; + } + if ($prepend) { + $this->prefixesPsr0[$first][$prefix] = array_merge( + (array) $paths, + $this->prefixesPsr0[$first][$prefix] + ); + } else { + $this->prefixesPsr0[$first][$prefix] = array_merge( + $this->prefixesPsr0[$first][$prefix], + (array) $paths + ); + } + } + + /** + * Registers a set of PSR-4 directories for a given namespace, either + * appending or prepending to the ones previously set for this namespace. + * + * @param string $prefix The prefix/namespace, with trailing '\\' + * @param array|string $paths The PSR-4 base directories + * @param bool $prepend Whether to prepend the directories + * + * @throws \InvalidArgumentException + */ + public function addPsr4($prefix, $paths, $prepend = false) + { + if (!$prefix) { + // Register directories for the root namespace. + if ($prepend) { + $this->fallbackDirsPsr4 = array_merge( + (array) $paths, + $this->fallbackDirsPsr4 + ); + } else { + $this->fallbackDirsPsr4 = array_merge( + $this->fallbackDirsPsr4, + (array) $paths + ); + } + } elseif (!isset($this->prefixDirsPsr4[$prefix])) { + // Register directories for a new namespace. + $length = strlen($prefix); + if ('\\' !== $prefix[$length - 1]) { + throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); + } + $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; + $this->prefixDirsPsr4[$prefix] = (array) $paths; + } elseif ($prepend) { + // Prepend directories for an already registered namespace. + $this->prefixDirsPsr4[$prefix] = array_merge( + (array) $paths, + $this->prefixDirsPsr4[$prefix] + ); + } else { + // Append directories for an already registered namespace. + $this->prefixDirsPsr4[$prefix] = array_merge( + $this->prefixDirsPsr4[$prefix], + (array) $paths + ); + } + } + + /** + * Registers a set of PSR-0 directories for a given prefix, + * replacing any others previously set for this prefix. + * + * @param string $prefix The prefix + * @param array|string $paths The PSR-0 base directories + */ + public function set($prefix, $paths) + { + if (!$prefix) { + $this->fallbackDirsPsr0 = (array) $paths; + } else { + $this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths; + } + } + + /** + * Registers a set of PSR-4 directories for a given namespace, + * replacing any others previously set for this namespace. + * + * @param string $prefix The prefix/namespace, with trailing '\\' + * @param array|string $paths The PSR-4 base directories + * + * @throws \InvalidArgumentException + */ + public function setPsr4($prefix, $paths) + { + if (!$prefix) { + $this->fallbackDirsPsr4 = (array) $paths; + } else { + $length = strlen($prefix); + if ('\\' !== $prefix[$length - 1]) { + throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); + } + $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; + $this->prefixDirsPsr4[$prefix] = (array) $paths; + } + } + + /** + * Turns on searching the include path for class files. + * + * @param bool $useIncludePath + */ + public function setUseIncludePath($useIncludePath) + { + $this->useIncludePath = $useIncludePath; + } + + /** + * Can be used to check if the autoloader uses the include path to check + * for classes. + * + * @return bool + */ + public function getUseIncludePath() + { + return $this->useIncludePath; + } + + /** + * Turns off searching the prefix and fallback directories for classes + * that have not been registered with the class map. + * + * @param bool $classMapAuthoritative + */ + public function setClassMapAuthoritative($classMapAuthoritative) + { + $this->classMapAuthoritative = $classMapAuthoritative; + } + + /** + * Should class lookup fail if not found in the current class map? + * + * @return bool + */ + public function isClassMapAuthoritative() + { + return $this->classMapAuthoritative; + } + + /** + * APCu prefix to use to cache found/not-found classes, if the extension is enabled. + * + * @param string|null $apcuPrefix + */ + public function setApcuPrefix($apcuPrefix) + { + $this->apcuPrefix = function_exists('apcu_fetch') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN) ? $apcuPrefix : null; + } + + /** + * The APCu prefix in use, or null if APCu caching is not enabled. + * + * @return string|null + */ + public function getApcuPrefix() + { + return $this->apcuPrefix; + } + + /** + * Registers this instance as an autoloader. + * + * @param bool $prepend Whether to prepend the autoloader or not + */ + public function register($prepend = false) + { + spl_autoload_register(array($this, 'loadClass'), true, $prepend); + } + + /** + * Unregisters this instance as an autoloader. + */ + public function unregister() + { + spl_autoload_unregister(array($this, 'loadClass')); + } + + /** + * Loads the given class or interface. + * + * @param string $class The name of the class + * @return bool|null True if loaded, null otherwise + */ + public function loadClass($class) + { + if ($file = $this->findFile($class)) { + includeFile($file); + + return true; + } + } + + /** + * Finds the path to the file where the class is defined. + * + * @param string $class The name of the class + * + * @return string|false The path if found, false otherwise + */ + public function findFile($class) + { + // class map lookup + if (isset($this->classMap[$class])) { + return $this->classMap[$class]; + } + if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) { + return false; + } + if (null !== $this->apcuPrefix) { + $file = apcu_fetch($this->apcuPrefix.$class, $hit); + if ($hit) { + return $file; + } + } + + $file = $this->findFileWithExtension($class, '.php'); + + // Search for Hack files if we are running on HHVM + if (false === $file && defined('HHVM_VERSION')) { + $file = $this->findFileWithExtension($class, '.hh'); + } + + if (null !== $this->apcuPrefix) { + apcu_add($this->apcuPrefix.$class, $file); + } + + if (false === $file) { + // Remember that this class does not exist. + $this->missingClasses[$class] = true; + } + + return $file; + } + + private function findFileWithExtension($class, $ext) + { + // PSR-4 lookup + $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext; + + $first = $class[0]; + if (isset($this->prefixLengthsPsr4[$first])) { + $subPath = $class; + while (false !== $lastPos = strrpos($subPath, '\\')) { + $subPath = substr($subPath, 0, $lastPos); + $search = $subPath . '\\'; + if (isset($this->prefixDirsPsr4[$search])) { + $pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1); + foreach ($this->prefixDirsPsr4[$search] as $dir) { + if (file_exists($file = $dir . $pathEnd)) { + return $file; + } + } + } + } + } + + // PSR-4 fallback dirs + foreach ($this->fallbackDirsPsr4 as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) { + return $file; + } + } + + // PSR-0 lookup + if (false !== $pos = strrpos($class, '\\')) { + // namespaced class name + $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1) + . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR); + } else { + // PEAR-like class name + $logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext; + } + + if (isset($this->prefixesPsr0[$first])) { + foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) { + if (0 === strpos($class, $prefix)) { + foreach ($dirs as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { + return $file; + } + } + } + } + } + + // PSR-0 fallback dirs + foreach ($this->fallbackDirsPsr0 as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { + return $file; + } + } + + // PSR-0 include paths. + if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) { + return $file; + } + + return false; + } +} + +/** + * Scope isolated include. + * + * Prevents access to $this/self from included files. + */ +function includeFile($file) +{ + include $file; +} diff --git a/site/plugins/kirby-twig/vendor/composer/autoload_classmap.php b/site/plugins/kirby-twig/vendor/composer/autoload_classmap.php new file mode 100644 index 0000000..abd8c27 --- /dev/null +++ b/site/plugins/kirby-twig/vendor/composer/autoload_classmap.php @@ -0,0 +1,194 @@ + $vendorDir . '/getkirby/composer-installer/src/ComposerInstaller/CmsInstaller.php', + 'Kirby\\ComposerInstaller\\Installer' => $vendorDir . '/getkirby/composer-installer/src/ComposerInstaller/Installer.php', + 'Kirby\\ComposerInstaller\\Plugin' => $vendorDir . '/getkirby/composer-installer/src/ComposerInstaller/Plugin.php', + 'Kirby\\ComposerInstaller\\PluginInstaller' => $vendorDir . '/getkirby/composer-installer/src/ComposerInstaller/PluginInstaller.php', + 'Symfony\\Polyfill\\Ctype\\Ctype' => $vendorDir . '/symfony/polyfill-ctype/Ctype.php', + 'Symfony\\Polyfill\\Mbstring\\Mbstring' => $vendorDir . '/symfony/polyfill-mbstring/Mbstring.php', + 'Twig\\Cache\\CacheInterface' => $vendorDir . '/twig/twig/src/Cache/CacheInterface.php', + 'Twig\\Cache\\FilesystemCache' => $vendorDir . '/twig/twig/src/Cache/FilesystemCache.php', + 'Twig\\Cache\\NullCache' => $vendorDir . '/twig/twig/src/Cache/NullCache.php', + 'Twig\\Compiler' => $vendorDir . '/twig/twig/src/Compiler.php', + 'Twig\\Environment' => $vendorDir . '/twig/twig/src/Environment.php', + 'Twig\\Error\\Error' => $vendorDir . '/twig/twig/src/Error/Error.php', + 'Twig\\Error\\LoaderError' => $vendorDir . '/twig/twig/src/Error/LoaderError.php', + 'Twig\\Error\\RuntimeError' => $vendorDir . '/twig/twig/src/Error/RuntimeError.php', + 'Twig\\Error\\SyntaxError' => $vendorDir . '/twig/twig/src/Error/SyntaxError.php', + 'Twig\\ExpressionParser' => $vendorDir . '/twig/twig/src/ExpressionParser.php', + 'Twig\\ExtensionSet' => $vendorDir . '/twig/twig/src/ExtensionSet.php', + 'Twig\\Extension\\AbstractExtension' => $vendorDir . '/twig/twig/src/Extension/AbstractExtension.php', + 'Twig\\Extension\\CoreExtension' => $vendorDir . '/twig/twig/src/Extension/CoreExtension.php', + 'Twig\\Extension\\DebugExtension' => $vendorDir . '/twig/twig/src/Extension/DebugExtension.php', + 'Twig\\Extension\\EscaperExtension' => $vendorDir . '/twig/twig/src/Extension/EscaperExtension.php', + 'Twig\\Extension\\ExtensionInterface' => $vendorDir . '/twig/twig/src/Extension/ExtensionInterface.php', + 'Twig\\Extension\\GlobalsInterface' => $vendorDir . '/twig/twig/src/Extension/GlobalsInterface.php', + 'Twig\\Extension\\OptimizerExtension' => $vendorDir . '/twig/twig/src/Extension/OptimizerExtension.php', + 'Twig\\Extension\\ProfilerExtension' => $vendorDir . '/twig/twig/src/Extension/ProfilerExtension.php', + 'Twig\\Extension\\RuntimeExtensionInterface' => $vendorDir . '/twig/twig/src/Extension/RuntimeExtensionInterface.php', + 'Twig\\Extension\\SandboxExtension' => $vendorDir . '/twig/twig/src/Extension/SandboxExtension.php', + 'Twig\\Extension\\StagingExtension' => $vendorDir . '/twig/twig/src/Extension/StagingExtension.php', + 'Twig\\Extension\\StringLoaderExtension' => $vendorDir . '/twig/twig/src/Extension/StringLoaderExtension.php', + 'Twig\\FileExtensionEscapingStrategy' => $vendorDir . '/twig/twig/src/FileExtensionEscapingStrategy.php', + 'Twig\\Lexer' => $vendorDir . '/twig/twig/src/Lexer.php', + 'Twig\\Loader\\ArrayLoader' => $vendorDir . '/twig/twig/src/Loader/ArrayLoader.php', + 'Twig\\Loader\\ChainLoader' => $vendorDir . '/twig/twig/src/Loader/ChainLoader.php', + 'Twig\\Loader\\FilesystemLoader' => $vendorDir . '/twig/twig/src/Loader/FilesystemLoader.php', + 'Twig\\Loader\\LoaderInterface' => $vendorDir . '/twig/twig/src/Loader/LoaderInterface.php', + 'Twig\\Markup' => $vendorDir . '/twig/twig/src/Markup.php', + 'Twig\\NodeTraverser' => $vendorDir . '/twig/twig/src/NodeTraverser.php', + 'Twig\\NodeVisitor\\AbstractNodeVisitor' => $vendorDir . '/twig/twig/src/NodeVisitor/AbstractNodeVisitor.php', + 'Twig\\NodeVisitor\\EscaperNodeVisitor' => $vendorDir . '/twig/twig/src/NodeVisitor/EscaperNodeVisitor.php', + 'Twig\\NodeVisitor\\MacroAutoImportNodeVisitor' => $vendorDir . '/twig/twig/src/NodeVisitor/MacroAutoImportNodeVisitor.php', + 'Twig\\NodeVisitor\\NodeVisitorInterface' => $vendorDir . '/twig/twig/src/NodeVisitor/NodeVisitorInterface.php', + 'Twig\\NodeVisitor\\OptimizerNodeVisitor' => $vendorDir . '/twig/twig/src/NodeVisitor/OptimizerNodeVisitor.php', + 'Twig\\NodeVisitor\\SafeAnalysisNodeVisitor' => $vendorDir . '/twig/twig/src/NodeVisitor/SafeAnalysisNodeVisitor.php', + 'Twig\\NodeVisitor\\SandboxNodeVisitor' => $vendorDir . '/twig/twig/src/NodeVisitor/SandboxNodeVisitor.php', + 'Twig\\Node\\AutoEscapeNode' => $vendorDir . '/twig/twig/src/Node/AutoEscapeNode.php', + 'Twig\\Node\\BlockNode' => $vendorDir . '/twig/twig/src/Node/BlockNode.php', + 'Twig\\Node\\BlockReferenceNode' => $vendorDir . '/twig/twig/src/Node/BlockReferenceNode.php', + 'Twig\\Node\\BodyNode' => $vendorDir . '/twig/twig/src/Node/BodyNode.php', + 'Twig\\Node\\CheckSecurityCallNode' => $vendorDir . '/twig/twig/src/Node/CheckSecurityCallNode.php', + 'Twig\\Node\\CheckSecurityNode' => $vendorDir . '/twig/twig/src/Node/CheckSecurityNode.php', + 'Twig\\Node\\CheckToStringNode' => $vendorDir . '/twig/twig/src/Node/CheckToStringNode.php', + 'Twig\\Node\\DeprecatedNode' => $vendorDir . '/twig/twig/src/Node/DeprecatedNode.php', + 'Twig\\Node\\DoNode' => $vendorDir . '/twig/twig/src/Node/DoNode.php', + 'Twig\\Node\\EmbedNode' => $vendorDir . '/twig/twig/src/Node/EmbedNode.php', + 'Twig\\Node\\Expression\\AbstractExpression' => $vendorDir . '/twig/twig/src/Node/Expression/AbstractExpression.php', + 'Twig\\Node\\Expression\\ArrayExpression' => $vendorDir . '/twig/twig/src/Node/Expression/ArrayExpression.php', + 'Twig\\Node\\Expression\\ArrowFunctionExpression' => $vendorDir . '/twig/twig/src/Node/Expression/ArrowFunctionExpression.php', + 'Twig\\Node\\Expression\\AssignNameExpression' => $vendorDir . '/twig/twig/src/Node/Expression/AssignNameExpression.php', + 'Twig\\Node\\Expression\\Binary\\AbstractBinary' => $vendorDir . '/twig/twig/src/Node/Expression/Binary/AbstractBinary.php', + 'Twig\\Node\\Expression\\Binary\\AddBinary' => $vendorDir . '/twig/twig/src/Node/Expression/Binary/AddBinary.php', + 'Twig\\Node\\Expression\\Binary\\AndBinary' => $vendorDir . '/twig/twig/src/Node/Expression/Binary/AndBinary.php', + 'Twig\\Node\\Expression\\Binary\\BitwiseAndBinary' => $vendorDir . '/twig/twig/src/Node/Expression/Binary/BitwiseAndBinary.php', + 'Twig\\Node\\Expression\\Binary\\BitwiseOrBinary' => $vendorDir . '/twig/twig/src/Node/Expression/Binary/BitwiseOrBinary.php', + 'Twig\\Node\\Expression\\Binary\\BitwiseXorBinary' => $vendorDir . '/twig/twig/src/Node/Expression/Binary/BitwiseXorBinary.php', + 'Twig\\Node\\Expression\\Binary\\ConcatBinary' => $vendorDir . '/twig/twig/src/Node/Expression/Binary/ConcatBinary.php', + 'Twig\\Node\\Expression\\Binary\\DivBinary' => $vendorDir . '/twig/twig/src/Node/Expression/Binary/DivBinary.php', + 'Twig\\Node\\Expression\\Binary\\EndsWithBinary' => $vendorDir . '/twig/twig/src/Node/Expression/Binary/EndsWithBinary.php', + 'Twig\\Node\\Expression\\Binary\\EqualBinary' => $vendorDir . '/twig/twig/src/Node/Expression/Binary/EqualBinary.php', + 'Twig\\Node\\Expression\\Binary\\FloorDivBinary' => $vendorDir . '/twig/twig/src/Node/Expression/Binary/FloorDivBinary.php', + 'Twig\\Node\\Expression\\Binary\\GreaterBinary' => $vendorDir . '/twig/twig/src/Node/Expression/Binary/GreaterBinary.php', + 'Twig\\Node\\Expression\\Binary\\GreaterEqualBinary' => $vendorDir . '/twig/twig/src/Node/Expression/Binary/GreaterEqualBinary.php', + 'Twig\\Node\\Expression\\Binary\\InBinary' => $vendorDir . '/twig/twig/src/Node/Expression/Binary/InBinary.php', + 'Twig\\Node\\Expression\\Binary\\LessBinary' => $vendorDir . '/twig/twig/src/Node/Expression/Binary/LessBinary.php', + 'Twig\\Node\\Expression\\Binary\\LessEqualBinary' => $vendorDir . '/twig/twig/src/Node/Expression/Binary/LessEqualBinary.php', + 'Twig\\Node\\Expression\\Binary\\MatchesBinary' => $vendorDir . '/twig/twig/src/Node/Expression/Binary/MatchesBinary.php', + 'Twig\\Node\\Expression\\Binary\\ModBinary' => $vendorDir . '/twig/twig/src/Node/Expression/Binary/ModBinary.php', + 'Twig\\Node\\Expression\\Binary\\MulBinary' => $vendorDir . '/twig/twig/src/Node/Expression/Binary/MulBinary.php', + 'Twig\\Node\\Expression\\Binary\\NotEqualBinary' => $vendorDir . '/twig/twig/src/Node/Expression/Binary/NotEqualBinary.php', + 'Twig\\Node\\Expression\\Binary\\NotInBinary' => $vendorDir . '/twig/twig/src/Node/Expression/Binary/NotInBinary.php', + 'Twig\\Node\\Expression\\Binary\\OrBinary' => $vendorDir . '/twig/twig/src/Node/Expression/Binary/OrBinary.php', + 'Twig\\Node\\Expression\\Binary\\PowerBinary' => $vendorDir . '/twig/twig/src/Node/Expression/Binary/PowerBinary.php', + 'Twig\\Node\\Expression\\Binary\\RangeBinary' => $vendorDir . '/twig/twig/src/Node/Expression/Binary/RangeBinary.php', + 'Twig\\Node\\Expression\\Binary\\SpaceshipBinary' => $vendorDir . '/twig/twig/src/Node/Expression/Binary/SpaceshipBinary.php', + 'Twig\\Node\\Expression\\Binary\\StartsWithBinary' => $vendorDir . '/twig/twig/src/Node/Expression/Binary/StartsWithBinary.php', + 'Twig\\Node\\Expression\\Binary\\SubBinary' => $vendorDir . '/twig/twig/src/Node/Expression/Binary/SubBinary.php', + 'Twig\\Node\\Expression\\BlockReferenceExpression' => $vendorDir . '/twig/twig/src/Node/Expression/BlockReferenceExpression.php', + 'Twig\\Node\\Expression\\CallExpression' => $vendorDir . '/twig/twig/src/Node/Expression/CallExpression.php', + 'Twig\\Node\\Expression\\ConditionalExpression' => $vendorDir . '/twig/twig/src/Node/Expression/ConditionalExpression.php', + 'Twig\\Node\\Expression\\ConstantExpression' => $vendorDir . '/twig/twig/src/Node/Expression/ConstantExpression.php', + 'Twig\\Node\\Expression\\FilterExpression' => $vendorDir . '/twig/twig/src/Node/Expression/FilterExpression.php', + 'Twig\\Node\\Expression\\Filter\\DefaultFilter' => $vendorDir . '/twig/twig/src/Node/Expression/Filter/DefaultFilter.php', + 'Twig\\Node\\Expression\\FunctionExpression' => $vendorDir . '/twig/twig/src/Node/Expression/FunctionExpression.php', + 'Twig\\Node\\Expression\\GetAttrExpression' => $vendorDir . '/twig/twig/src/Node/Expression/GetAttrExpression.php', + 'Twig\\Node\\Expression\\InlinePrint' => $vendorDir . '/twig/twig/src/Node/Expression/InlinePrint.php', + 'Twig\\Node\\Expression\\MethodCallExpression' => $vendorDir . '/twig/twig/src/Node/Expression/MethodCallExpression.php', + 'Twig\\Node\\Expression\\NameExpression' => $vendorDir . '/twig/twig/src/Node/Expression/NameExpression.php', + 'Twig\\Node\\Expression\\NullCoalesceExpression' => $vendorDir . '/twig/twig/src/Node/Expression/NullCoalesceExpression.php', + 'Twig\\Node\\Expression\\ParentExpression' => $vendorDir . '/twig/twig/src/Node/Expression/ParentExpression.php', + 'Twig\\Node\\Expression\\TempNameExpression' => $vendorDir . '/twig/twig/src/Node/Expression/TempNameExpression.php', + 'Twig\\Node\\Expression\\TestExpression' => $vendorDir . '/twig/twig/src/Node/Expression/TestExpression.php', + 'Twig\\Node\\Expression\\Test\\ConstantTest' => $vendorDir . '/twig/twig/src/Node/Expression/Test/ConstantTest.php', + 'Twig\\Node\\Expression\\Test\\DefinedTest' => $vendorDir . '/twig/twig/src/Node/Expression/Test/DefinedTest.php', + 'Twig\\Node\\Expression\\Test\\DivisiblebyTest' => $vendorDir . '/twig/twig/src/Node/Expression/Test/DivisiblebyTest.php', + 'Twig\\Node\\Expression\\Test\\EvenTest' => $vendorDir . '/twig/twig/src/Node/Expression/Test/EvenTest.php', + 'Twig\\Node\\Expression\\Test\\NullTest' => $vendorDir . '/twig/twig/src/Node/Expression/Test/NullTest.php', + 'Twig\\Node\\Expression\\Test\\OddTest' => $vendorDir . '/twig/twig/src/Node/Expression/Test/OddTest.php', + 'Twig\\Node\\Expression\\Test\\SameasTest' => $vendorDir . '/twig/twig/src/Node/Expression/Test/SameasTest.php', + 'Twig\\Node\\Expression\\Unary\\AbstractUnary' => $vendorDir . '/twig/twig/src/Node/Expression/Unary/AbstractUnary.php', + 'Twig\\Node\\Expression\\Unary\\NegUnary' => $vendorDir . '/twig/twig/src/Node/Expression/Unary/NegUnary.php', + 'Twig\\Node\\Expression\\Unary\\NotUnary' => $vendorDir . '/twig/twig/src/Node/Expression/Unary/NotUnary.php', + 'Twig\\Node\\Expression\\Unary\\PosUnary' => $vendorDir . '/twig/twig/src/Node/Expression/Unary/PosUnary.php', + 'Twig\\Node\\Expression\\VariadicExpression' => $vendorDir . '/twig/twig/src/Node/Expression/VariadicExpression.php', + 'Twig\\Node\\FlushNode' => $vendorDir . '/twig/twig/src/Node/FlushNode.php', + 'Twig\\Node\\ForLoopNode' => $vendorDir . '/twig/twig/src/Node/ForLoopNode.php', + 'Twig\\Node\\ForNode' => $vendorDir . '/twig/twig/src/Node/ForNode.php', + 'Twig\\Node\\IfNode' => $vendorDir . '/twig/twig/src/Node/IfNode.php', + 'Twig\\Node\\ImportNode' => $vendorDir . '/twig/twig/src/Node/ImportNode.php', + 'Twig\\Node\\IncludeNode' => $vendorDir . '/twig/twig/src/Node/IncludeNode.php', + 'Twig\\Node\\MacroNode' => $vendorDir . '/twig/twig/src/Node/MacroNode.php', + 'Twig\\Node\\ModuleNode' => $vendorDir . '/twig/twig/src/Node/ModuleNode.php', + 'Twig\\Node\\Node' => $vendorDir . '/twig/twig/src/Node/Node.php', + 'Twig\\Node\\NodeCaptureInterface' => $vendorDir . '/twig/twig/src/Node/NodeCaptureInterface.php', + 'Twig\\Node\\NodeOutputInterface' => $vendorDir . '/twig/twig/src/Node/NodeOutputInterface.php', + 'Twig\\Node\\PrintNode' => $vendorDir . '/twig/twig/src/Node/PrintNode.php', + 'Twig\\Node\\SandboxNode' => $vendorDir . '/twig/twig/src/Node/SandboxNode.php', + 'Twig\\Node\\SetNode' => $vendorDir . '/twig/twig/src/Node/SetNode.php', + 'Twig\\Node\\TextNode' => $vendorDir . '/twig/twig/src/Node/TextNode.php', + 'Twig\\Node\\WithNode' => $vendorDir . '/twig/twig/src/Node/WithNode.php', + 'Twig\\Parser' => $vendorDir . '/twig/twig/src/Parser.php', + 'Twig\\Profiler\\Dumper\\BaseDumper' => $vendorDir . '/twig/twig/src/Profiler/Dumper/BaseDumper.php', + 'Twig\\Profiler\\Dumper\\BlackfireDumper' => $vendorDir . '/twig/twig/src/Profiler/Dumper/BlackfireDumper.php', + 'Twig\\Profiler\\Dumper\\HtmlDumper' => $vendorDir . '/twig/twig/src/Profiler/Dumper/HtmlDumper.php', + 'Twig\\Profiler\\Dumper\\TextDumper' => $vendorDir . '/twig/twig/src/Profiler/Dumper/TextDumper.php', + 'Twig\\Profiler\\NodeVisitor\\ProfilerNodeVisitor' => $vendorDir . '/twig/twig/src/Profiler/NodeVisitor/ProfilerNodeVisitor.php', + 'Twig\\Profiler\\Node\\EnterProfileNode' => $vendorDir . '/twig/twig/src/Profiler/Node/EnterProfileNode.php', + 'Twig\\Profiler\\Node\\LeaveProfileNode' => $vendorDir . '/twig/twig/src/Profiler/Node/LeaveProfileNode.php', + 'Twig\\Profiler\\Profile' => $vendorDir . '/twig/twig/src/Profiler/Profile.php', + 'Twig\\RuntimeLoader\\ContainerRuntimeLoader' => $vendorDir . '/twig/twig/src/RuntimeLoader/ContainerRuntimeLoader.php', + 'Twig\\RuntimeLoader\\FactoryRuntimeLoader' => $vendorDir . '/twig/twig/src/RuntimeLoader/FactoryRuntimeLoader.php', + 'Twig\\RuntimeLoader\\RuntimeLoaderInterface' => $vendorDir . '/twig/twig/src/RuntimeLoader/RuntimeLoaderInterface.php', + 'Twig\\Sandbox\\SecurityError' => $vendorDir . '/twig/twig/src/Sandbox/SecurityError.php', + 'Twig\\Sandbox\\SecurityNotAllowedFilterError' => $vendorDir . '/twig/twig/src/Sandbox/SecurityNotAllowedFilterError.php', + 'Twig\\Sandbox\\SecurityNotAllowedFunctionError' => $vendorDir . '/twig/twig/src/Sandbox/SecurityNotAllowedFunctionError.php', + 'Twig\\Sandbox\\SecurityNotAllowedMethodError' => $vendorDir . '/twig/twig/src/Sandbox/SecurityNotAllowedMethodError.php', + 'Twig\\Sandbox\\SecurityNotAllowedPropertyError' => $vendorDir . '/twig/twig/src/Sandbox/SecurityNotAllowedPropertyError.php', + 'Twig\\Sandbox\\SecurityNotAllowedTagError' => $vendorDir . '/twig/twig/src/Sandbox/SecurityNotAllowedTagError.php', + 'Twig\\Sandbox\\SecurityPolicy' => $vendorDir . '/twig/twig/src/Sandbox/SecurityPolicy.php', + 'Twig\\Sandbox\\SecurityPolicyInterface' => $vendorDir . '/twig/twig/src/Sandbox/SecurityPolicyInterface.php', + 'Twig\\Source' => $vendorDir . '/twig/twig/src/Source.php', + 'Twig\\Template' => $vendorDir . '/twig/twig/src/Template.php', + 'Twig\\TemplateWrapper' => $vendorDir . '/twig/twig/src/TemplateWrapper.php', + 'Twig\\Test\\IntegrationTestCase' => $vendorDir . '/twig/twig/src/Test/IntegrationTestCase.php', + 'Twig\\Test\\NodeTestCase' => $vendorDir . '/twig/twig/src/Test/NodeTestCase.php', + 'Twig\\Token' => $vendorDir . '/twig/twig/src/Token.php', + 'Twig\\TokenParser\\AbstractTokenParser' => $vendorDir . '/twig/twig/src/TokenParser/AbstractTokenParser.php', + 'Twig\\TokenParser\\ApplyTokenParser' => $vendorDir . '/twig/twig/src/TokenParser/ApplyTokenParser.php', + 'Twig\\TokenParser\\AutoEscapeTokenParser' => $vendorDir . '/twig/twig/src/TokenParser/AutoEscapeTokenParser.php', + 'Twig\\TokenParser\\BlockTokenParser' => $vendorDir . '/twig/twig/src/TokenParser/BlockTokenParser.php', + 'Twig\\TokenParser\\DeprecatedTokenParser' => $vendorDir . '/twig/twig/src/TokenParser/DeprecatedTokenParser.php', + 'Twig\\TokenParser\\DoTokenParser' => $vendorDir . '/twig/twig/src/TokenParser/DoTokenParser.php', + 'Twig\\TokenParser\\EmbedTokenParser' => $vendorDir . '/twig/twig/src/TokenParser/EmbedTokenParser.php', + 'Twig\\TokenParser\\ExtendsTokenParser' => $vendorDir . '/twig/twig/src/TokenParser/ExtendsTokenParser.php', + 'Twig\\TokenParser\\FlushTokenParser' => $vendorDir . '/twig/twig/src/TokenParser/FlushTokenParser.php', + 'Twig\\TokenParser\\ForTokenParser' => $vendorDir . '/twig/twig/src/TokenParser/ForTokenParser.php', + 'Twig\\TokenParser\\FromTokenParser' => $vendorDir . '/twig/twig/src/TokenParser/FromTokenParser.php', + 'Twig\\TokenParser\\IfTokenParser' => $vendorDir . '/twig/twig/src/TokenParser/IfTokenParser.php', + 'Twig\\TokenParser\\ImportTokenParser' => $vendorDir . '/twig/twig/src/TokenParser/ImportTokenParser.php', + 'Twig\\TokenParser\\IncludeTokenParser' => $vendorDir . '/twig/twig/src/TokenParser/IncludeTokenParser.php', + 'Twig\\TokenParser\\MacroTokenParser' => $vendorDir . '/twig/twig/src/TokenParser/MacroTokenParser.php', + 'Twig\\TokenParser\\SandboxTokenParser' => $vendorDir . '/twig/twig/src/TokenParser/SandboxTokenParser.php', + 'Twig\\TokenParser\\SetTokenParser' => $vendorDir . '/twig/twig/src/TokenParser/SetTokenParser.php', + 'Twig\\TokenParser\\TokenParserInterface' => $vendorDir . '/twig/twig/src/TokenParser/TokenParserInterface.php', + 'Twig\\TokenParser\\UseTokenParser' => $vendorDir . '/twig/twig/src/TokenParser/UseTokenParser.php', + 'Twig\\TokenParser\\WithTokenParser' => $vendorDir . '/twig/twig/src/TokenParser/WithTokenParser.php', + 'Twig\\TokenStream' => $vendorDir . '/twig/twig/src/TokenStream.php', + 'Twig\\TwigFilter' => $vendorDir . '/twig/twig/src/TwigFilter.php', + 'Twig\\TwigFunction' => $vendorDir . '/twig/twig/src/TwigFunction.php', + 'Twig\\TwigTest' => $vendorDir . '/twig/twig/src/TwigTest.php', + 'Twig\\Util\\DeprecationCollector' => $vendorDir . '/twig/twig/src/Util/DeprecationCollector.php', + 'Twig\\Util\\TemplateDirIterator' => $vendorDir . '/twig/twig/src/Util/TemplateDirIterator.php', + 'amteich\\Twig\\Environment' => $baseDir . '/src/classes/Environment.php', + 'amteich\\Twig\\Functions' => $baseDir . '/src/classes/Functions.php', + 'amteich\\Twig\\Plugin' => $baseDir . '/src/classes/Plugin.php', + 'amteich\\Twig\\Template' => $baseDir . '/src/classes/Template.php', + 'amteich\\Twig\\Tests' => $baseDir . '/src/classes/Tests.php', +); diff --git a/site/plugins/kirby-twig/vendor/composer/autoload_files.php b/site/plugins/kirby-twig/vendor/composer/autoload_files.php new file mode 100644 index 0000000..a3e38ec --- /dev/null +++ b/site/plugins/kirby-twig/vendor/composer/autoload_files.php @@ -0,0 +1,11 @@ + $vendorDir . '/symfony/polyfill-ctype/bootstrap.php', + '0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => $vendorDir . '/symfony/polyfill-mbstring/bootstrap.php', +); diff --git a/site/plugins/kirby-twig/vendor/composer/autoload_namespaces.php b/site/plugins/kirby-twig/vendor/composer/autoload_namespaces.php new file mode 100644 index 0000000..b7fc012 --- /dev/null +++ b/site/plugins/kirby-twig/vendor/composer/autoload_namespaces.php @@ -0,0 +1,9 @@ + array($baseDir . '/src/classes'), + 'Twig\\' => array($vendorDir . '/twig/twig/src'), + 'Symfony\\Polyfill\\Mbstring\\' => array($vendorDir . '/symfony/polyfill-mbstring'), + 'Symfony\\Polyfill\\Ctype\\' => array($vendorDir . '/symfony/polyfill-ctype'), + 'Kirby\\' => array($vendorDir . '/getkirby/composer-installer/src'), +); diff --git a/site/plugins/kirby-twig/vendor/composer/autoload_real.php b/site/plugins/kirby-twig/vendor/composer/autoload_real.php new file mode 100644 index 0000000..ba3e03f --- /dev/null +++ b/site/plugins/kirby-twig/vendor/composer/autoload_real.php @@ -0,0 +1,73 @@ += 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded()); + if ($useStaticLoader) { + require_once __DIR__ . '/autoload_static.php'; + + call_user_func(\Composer\Autoload\ComposerStaticInit0fcad4e01a7109b33a336ffeaadc7293::getInitializer($loader)); + } else { + $map = require __DIR__ . '/autoload_namespaces.php'; + foreach ($map as $namespace => $path) { + $loader->set($namespace, $path); + } + + $map = require __DIR__ . '/autoload_psr4.php'; + foreach ($map as $namespace => $path) { + $loader->setPsr4($namespace, $path); + } + + $classMap = require __DIR__ . '/autoload_classmap.php'; + if ($classMap) { + $loader->addClassMap($classMap); + } + } + + $loader->register(true); + + if ($useStaticLoader) { + $includeFiles = Composer\Autoload\ComposerStaticInit0fcad4e01a7109b33a336ffeaadc7293::$files; + } else { + $includeFiles = require __DIR__ . '/autoload_files.php'; + } + foreach ($includeFiles as $fileIdentifier => $file) { + composerRequire0fcad4e01a7109b33a336ffeaadc7293($fileIdentifier, $file); + } + + return $loader; + } +} + +function composerRequire0fcad4e01a7109b33a336ffeaadc7293($fileIdentifier, $file) +{ + if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) { + require $file; + + $GLOBALS['__composer_autoload_files'][$fileIdentifier] = true; + } +} diff --git a/site/plugins/kirby-twig/vendor/composer/autoload_static.php b/site/plugins/kirby-twig/vendor/composer/autoload_static.php new file mode 100644 index 0000000..2f5b454 --- /dev/null +++ b/site/plugins/kirby-twig/vendor/composer/autoload_static.php @@ -0,0 +1,254 @@ + __DIR__ . '/..' . '/symfony/polyfill-ctype/bootstrap.php', + '0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => __DIR__ . '/..' . '/symfony/polyfill-mbstring/bootstrap.php', + ); + + public static $prefixLengthsPsr4 = array ( + 'a' => + array ( + 'amteich\\Twig\\' => 13, + ), + 'T' => + array ( + 'Twig\\' => 5, + ), + 'S' => + array ( + 'Symfony\\Polyfill\\Mbstring\\' => 26, + 'Symfony\\Polyfill\\Ctype\\' => 23, + ), + 'K' => + array ( + 'Kirby\\' => 6, + ), + ); + + public static $prefixDirsPsr4 = array ( + 'amteich\\Twig\\' => + array ( + 0 => __DIR__ . '/../..' . '/src/classes', + ), + 'Twig\\' => + array ( + 0 => __DIR__ . '/..' . '/twig/twig/src', + ), + 'Symfony\\Polyfill\\Mbstring\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/polyfill-mbstring', + ), + 'Symfony\\Polyfill\\Ctype\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/polyfill-ctype', + ), + 'Kirby\\' => + array ( + 0 => __DIR__ . '/..' . '/getkirby/composer-installer/src', + ), + ); + + public static $classMap = array ( + 'Kirby\\ComposerInstaller\\CmsInstaller' => __DIR__ . '/..' . '/getkirby/composer-installer/src/ComposerInstaller/CmsInstaller.php', + 'Kirby\\ComposerInstaller\\Installer' => __DIR__ . '/..' . '/getkirby/composer-installer/src/ComposerInstaller/Installer.php', + 'Kirby\\ComposerInstaller\\Plugin' => __DIR__ . '/..' . '/getkirby/composer-installer/src/ComposerInstaller/Plugin.php', + 'Kirby\\ComposerInstaller\\PluginInstaller' => __DIR__ . '/..' . '/getkirby/composer-installer/src/ComposerInstaller/PluginInstaller.php', + 'Symfony\\Polyfill\\Ctype\\Ctype' => __DIR__ . '/..' . '/symfony/polyfill-ctype/Ctype.php', + 'Symfony\\Polyfill\\Mbstring\\Mbstring' => __DIR__ . '/..' . '/symfony/polyfill-mbstring/Mbstring.php', + 'Twig\\Cache\\CacheInterface' => __DIR__ . '/..' . '/twig/twig/src/Cache/CacheInterface.php', + 'Twig\\Cache\\FilesystemCache' => __DIR__ . '/..' . '/twig/twig/src/Cache/FilesystemCache.php', + 'Twig\\Cache\\NullCache' => __DIR__ . '/..' . '/twig/twig/src/Cache/NullCache.php', + 'Twig\\Compiler' => __DIR__ . '/..' . '/twig/twig/src/Compiler.php', + 'Twig\\Environment' => __DIR__ . '/..' . '/twig/twig/src/Environment.php', + 'Twig\\Error\\Error' => __DIR__ . '/..' . '/twig/twig/src/Error/Error.php', + 'Twig\\Error\\LoaderError' => __DIR__ . '/..' . '/twig/twig/src/Error/LoaderError.php', + 'Twig\\Error\\RuntimeError' => __DIR__ . '/..' . '/twig/twig/src/Error/RuntimeError.php', + 'Twig\\Error\\SyntaxError' => __DIR__ . '/..' . '/twig/twig/src/Error/SyntaxError.php', + 'Twig\\ExpressionParser' => __DIR__ . '/..' . '/twig/twig/src/ExpressionParser.php', + 'Twig\\ExtensionSet' => __DIR__ . '/..' . '/twig/twig/src/ExtensionSet.php', + 'Twig\\Extension\\AbstractExtension' => __DIR__ . '/..' . '/twig/twig/src/Extension/AbstractExtension.php', + 'Twig\\Extension\\CoreExtension' => __DIR__ . '/..' . '/twig/twig/src/Extension/CoreExtension.php', + 'Twig\\Extension\\DebugExtension' => __DIR__ . '/..' . '/twig/twig/src/Extension/DebugExtension.php', + 'Twig\\Extension\\EscaperExtension' => __DIR__ . '/..' . '/twig/twig/src/Extension/EscaperExtension.php', + 'Twig\\Extension\\ExtensionInterface' => __DIR__ . '/..' . '/twig/twig/src/Extension/ExtensionInterface.php', + 'Twig\\Extension\\GlobalsInterface' => __DIR__ . '/..' . '/twig/twig/src/Extension/GlobalsInterface.php', + 'Twig\\Extension\\OptimizerExtension' => __DIR__ . '/..' . '/twig/twig/src/Extension/OptimizerExtension.php', + 'Twig\\Extension\\ProfilerExtension' => __DIR__ . '/..' . '/twig/twig/src/Extension/ProfilerExtension.php', + 'Twig\\Extension\\RuntimeExtensionInterface' => __DIR__ . '/..' . '/twig/twig/src/Extension/RuntimeExtensionInterface.php', + 'Twig\\Extension\\SandboxExtension' => __DIR__ . '/..' . '/twig/twig/src/Extension/SandboxExtension.php', + 'Twig\\Extension\\StagingExtension' => __DIR__ . '/..' . '/twig/twig/src/Extension/StagingExtension.php', + 'Twig\\Extension\\StringLoaderExtension' => __DIR__ . '/..' . '/twig/twig/src/Extension/StringLoaderExtension.php', + 'Twig\\FileExtensionEscapingStrategy' => __DIR__ . '/..' . '/twig/twig/src/FileExtensionEscapingStrategy.php', + 'Twig\\Lexer' => __DIR__ . '/..' . '/twig/twig/src/Lexer.php', + 'Twig\\Loader\\ArrayLoader' => __DIR__ . '/..' . '/twig/twig/src/Loader/ArrayLoader.php', + 'Twig\\Loader\\ChainLoader' => __DIR__ . '/..' . '/twig/twig/src/Loader/ChainLoader.php', + 'Twig\\Loader\\FilesystemLoader' => __DIR__ . '/..' . '/twig/twig/src/Loader/FilesystemLoader.php', + 'Twig\\Loader\\LoaderInterface' => __DIR__ . '/..' . '/twig/twig/src/Loader/LoaderInterface.php', + 'Twig\\Markup' => __DIR__ . '/..' . '/twig/twig/src/Markup.php', + 'Twig\\NodeTraverser' => __DIR__ . '/..' . '/twig/twig/src/NodeTraverser.php', + 'Twig\\NodeVisitor\\AbstractNodeVisitor' => __DIR__ . '/..' . '/twig/twig/src/NodeVisitor/AbstractNodeVisitor.php', + 'Twig\\NodeVisitor\\EscaperNodeVisitor' => __DIR__ . '/..' . '/twig/twig/src/NodeVisitor/EscaperNodeVisitor.php', + 'Twig\\NodeVisitor\\MacroAutoImportNodeVisitor' => __DIR__ . '/..' . '/twig/twig/src/NodeVisitor/MacroAutoImportNodeVisitor.php', + 'Twig\\NodeVisitor\\NodeVisitorInterface' => __DIR__ . '/..' . '/twig/twig/src/NodeVisitor/NodeVisitorInterface.php', + 'Twig\\NodeVisitor\\OptimizerNodeVisitor' => __DIR__ . '/..' . '/twig/twig/src/NodeVisitor/OptimizerNodeVisitor.php', + 'Twig\\NodeVisitor\\SafeAnalysisNodeVisitor' => __DIR__ . '/..' . '/twig/twig/src/NodeVisitor/SafeAnalysisNodeVisitor.php', + 'Twig\\NodeVisitor\\SandboxNodeVisitor' => __DIR__ . '/..' . '/twig/twig/src/NodeVisitor/SandboxNodeVisitor.php', + 'Twig\\Node\\AutoEscapeNode' => __DIR__ . '/..' . '/twig/twig/src/Node/AutoEscapeNode.php', + 'Twig\\Node\\BlockNode' => __DIR__ . '/..' . '/twig/twig/src/Node/BlockNode.php', + 'Twig\\Node\\BlockReferenceNode' => __DIR__ . '/..' . '/twig/twig/src/Node/BlockReferenceNode.php', + 'Twig\\Node\\BodyNode' => __DIR__ . '/..' . '/twig/twig/src/Node/BodyNode.php', + 'Twig\\Node\\CheckSecurityCallNode' => __DIR__ . '/..' . '/twig/twig/src/Node/CheckSecurityCallNode.php', + 'Twig\\Node\\CheckSecurityNode' => __DIR__ . '/..' . '/twig/twig/src/Node/CheckSecurityNode.php', + 'Twig\\Node\\CheckToStringNode' => __DIR__ . '/..' . '/twig/twig/src/Node/CheckToStringNode.php', + 'Twig\\Node\\DeprecatedNode' => __DIR__ . '/..' . '/twig/twig/src/Node/DeprecatedNode.php', + 'Twig\\Node\\DoNode' => __DIR__ . '/..' . '/twig/twig/src/Node/DoNode.php', + 'Twig\\Node\\EmbedNode' => __DIR__ . '/..' . '/twig/twig/src/Node/EmbedNode.php', + 'Twig\\Node\\Expression\\AbstractExpression' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/AbstractExpression.php', + 'Twig\\Node\\Expression\\ArrayExpression' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/ArrayExpression.php', + 'Twig\\Node\\Expression\\ArrowFunctionExpression' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/ArrowFunctionExpression.php', + 'Twig\\Node\\Expression\\AssignNameExpression' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/AssignNameExpression.php', + 'Twig\\Node\\Expression\\Binary\\AbstractBinary' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/Binary/AbstractBinary.php', + 'Twig\\Node\\Expression\\Binary\\AddBinary' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/Binary/AddBinary.php', + 'Twig\\Node\\Expression\\Binary\\AndBinary' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/Binary/AndBinary.php', + 'Twig\\Node\\Expression\\Binary\\BitwiseAndBinary' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/Binary/BitwiseAndBinary.php', + 'Twig\\Node\\Expression\\Binary\\BitwiseOrBinary' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/Binary/BitwiseOrBinary.php', + 'Twig\\Node\\Expression\\Binary\\BitwiseXorBinary' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/Binary/BitwiseXorBinary.php', + 'Twig\\Node\\Expression\\Binary\\ConcatBinary' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/Binary/ConcatBinary.php', + 'Twig\\Node\\Expression\\Binary\\DivBinary' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/Binary/DivBinary.php', + 'Twig\\Node\\Expression\\Binary\\EndsWithBinary' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/Binary/EndsWithBinary.php', + 'Twig\\Node\\Expression\\Binary\\EqualBinary' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/Binary/EqualBinary.php', + 'Twig\\Node\\Expression\\Binary\\FloorDivBinary' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/Binary/FloorDivBinary.php', + 'Twig\\Node\\Expression\\Binary\\GreaterBinary' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/Binary/GreaterBinary.php', + 'Twig\\Node\\Expression\\Binary\\GreaterEqualBinary' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/Binary/GreaterEqualBinary.php', + 'Twig\\Node\\Expression\\Binary\\InBinary' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/Binary/InBinary.php', + 'Twig\\Node\\Expression\\Binary\\LessBinary' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/Binary/LessBinary.php', + 'Twig\\Node\\Expression\\Binary\\LessEqualBinary' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/Binary/LessEqualBinary.php', + 'Twig\\Node\\Expression\\Binary\\MatchesBinary' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/Binary/MatchesBinary.php', + 'Twig\\Node\\Expression\\Binary\\ModBinary' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/Binary/ModBinary.php', + 'Twig\\Node\\Expression\\Binary\\MulBinary' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/Binary/MulBinary.php', + 'Twig\\Node\\Expression\\Binary\\NotEqualBinary' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/Binary/NotEqualBinary.php', + 'Twig\\Node\\Expression\\Binary\\NotInBinary' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/Binary/NotInBinary.php', + 'Twig\\Node\\Expression\\Binary\\OrBinary' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/Binary/OrBinary.php', + 'Twig\\Node\\Expression\\Binary\\PowerBinary' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/Binary/PowerBinary.php', + 'Twig\\Node\\Expression\\Binary\\RangeBinary' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/Binary/RangeBinary.php', + 'Twig\\Node\\Expression\\Binary\\SpaceshipBinary' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/Binary/SpaceshipBinary.php', + 'Twig\\Node\\Expression\\Binary\\StartsWithBinary' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/Binary/StartsWithBinary.php', + 'Twig\\Node\\Expression\\Binary\\SubBinary' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/Binary/SubBinary.php', + 'Twig\\Node\\Expression\\BlockReferenceExpression' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/BlockReferenceExpression.php', + 'Twig\\Node\\Expression\\CallExpression' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/CallExpression.php', + 'Twig\\Node\\Expression\\ConditionalExpression' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/ConditionalExpression.php', + 'Twig\\Node\\Expression\\ConstantExpression' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/ConstantExpression.php', + 'Twig\\Node\\Expression\\FilterExpression' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/FilterExpression.php', + 'Twig\\Node\\Expression\\Filter\\DefaultFilter' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/Filter/DefaultFilter.php', + 'Twig\\Node\\Expression\\FunctionExpression' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/FunctionExpression.php', + 'Twig\\Node\\Expression\\GetAttrExpression' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/GetAttrExpression.php', + 'Twig\\Node\\Expression\\InlinePrint' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/InlinePrint.php', + 'Twig\\Node\\Expression\\MethodCallExpression' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/MethodCallExpression.php', + 'Twig\\Node\\Expression\\NameExpression' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/NameExpression.php', + 'Twig\\Node\\Expression\\NullCoalesceExpression' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/NullCoalesceExpression.php', + 'Twig\\Node\\Expression\\ParentExpression' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/ParentExpression.php', + 'Twig\\Node\\Expression\\TempNameExpression' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/TempNameExpression.php', + 'Twig\\Node\\Expression\\TestExpression' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/TestExpression.php', + 'Twig\\Node\\Expression\\Test\\ConstantTest' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/Test/ConstantTest.php', + 'Twig\\Node\\Expression\\Test\\DefinedTest' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/Test/DefinedTest.php', + 'Twig\\Node\\Expression\\Test\\DivisiblebyTest' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/Test/DivisiblebyTest.php', + 'Twig\\Node\\Expression\\Test\\EvenTest' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/Test/EvenTest.php', + 'Twig\\Node\\Expression\\Test\\NullTest' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/Test/NullTest.php', + 'Twig\\Node\\Expression\\Test\\OddTest' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/Test/OddTest.php', + 'Twig\\Node\\Expression\\Test\\SameasTest' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/Test/SameasTest.php', + 'Twig\\Node\\Expression\\Unary\\AbstractUnary' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/Unary/AbstractUnary.php', + 'Twig\\Node\\Expression\\Unary\\NegUnary' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/Unary/NegUnary.php', + 'Twig\\Node\\Expression\\Unary\\NotUnary' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/Unary/NotUnary.php', + 'Twig\\Node\\Expression\\Unary\\PosUnary' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/Unary/PosUnary.php', + 'Twig\\Node\\Expression\\VariadicExpression' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/VariadicExpression.php', + 'Twig\\Node\\FlushNode' => __DIR__ . '/..' . '/twig/twig/src/Node/FlushNode.php', + 'Twig\\Node\\ForLoopNode' => __DIR__ . '/..' . '/twig/twig/src/Node/ForLoopNode.php', + 'Twig\\Node\\ForNode' => __DIR__ . '/..' . '/twig/twig/src/Node/ForNode.php', + 'Twig\\Node\\IfNode' => __DIR__ . '/..' . '/twig/twig/src/Node/IfNode.php', + 'Twig\\Node\\ImportNode' => __DIR__ . '/..' . '/twig/twig/src/Node/ImportNode.php', + 'Twig\\Node\\IncludeNode' => __DIR__ . '/..' . '/twig/twig/src/Node/IncludeNode.php', + 'Twig\\Node\\MacroNode' => __DIR__ . '/..' . '/twig/twig/src/Node/MacroNode.php', + 'Twig\\Node\\ModuleNode' => __DIR__ . '/..' . '/twig/twig/src/Node/ModuleNode.php', + 'Twig\\Node\\Node' => __DIR__ . '/..' . '/twig/twig/src/Node/Node.php', + 'Twig\\Node\\NodeCaptureInterface' => __DIR__ . '/..' . '/twig/twig/src/Node/NodeCaptureInterface.php', + 'Twig\\Node\\NodeOutputInterface' => __DIR__ . '/..' . '/twig/twig/src/Node/NodeOutputInterface.php', + 'Twig\\Node\\PrintNode' => __DIR__ . '/..' . '/twig/twig/src/Node/PrintNode.php', + 'Twig\\Node\\SandboxNode' => __DIR__ . '/..' . '/twig/twig/src/Node/SandboxNode.php', + 'Twig\\Node\\SetNode' => __DIR__ . '/..' . '/twig/twig/src/Node/SetNode.php', + 'Twig\\Node\\TextNode' => __DIR__ . '/..' . '/twig/twig/src/Node/TextNode.php', + 'Twig\\Node\\WithNode' => __DIR__ . '/..' . '/twig/twig/src/Node/WithNode.php', + 'Twig\\Parser' => __DIR__ . '/..' . '/twig/twig/src/Parser.php', + 'Twig\\Profiler\\Dumper\\BaseDumper' => __DIR__ . '/..' . '/twig/twig/src/Profiler/Dumper/BaseDumper.php', + 'Twig\\Profiler\\Dumper\\BlackfireDumper' => __DIR__ . '/..' . '/twig/twig/src/Profiler/Dumper/BlackfireDumper.php', + 'Twig\\Profiler\\Dumper\\HtmlDumper' => __DIR__ . '/..' . '/twig/twig/src/Profiler/Dumper/HtmlDumper.php', + 'Twig\\Profiler\\Dumper\\TextDumper' => __DIR__ . '/..' . '/twig/twig/src/Profiler/Dumper/TextDumper.php', + 'Twig\\Profiler\\NodeVisitor\\ProfilerNodeVisitor' => __DIR__ . '/..' . '/twig/twig/src/Profiler/NodeVisitor/ProfilerNodeVisitor.php', + 'Twig\\Profiler\\Node\\EnterProfileNode' => __DIR__ . '/..' . '/twig/twig/src/Profiler/Node/EnterProfileNode.php', + 'Twig\\Profiler\\Node\\LeaveProfileNode' => __DIR__ . '/..' . '/twig/twig/src/Profiler/Node/LeaveProfileNode.php', + 'Twig\\Profiler\\Profile' => __DIR__ . '/..' . '/twig/twig/src/Profiler/Profile.php', + 'Twig\\RuntimeLoader\\ContainerRuntimeLoader' => __DIR__ . '/..' . '/twig/twig/src/RuntimeLoader/ContainerRuntimeLoader.php', + 'Twig\\RuntimeLoader\\FactoryRuntimeLoader' => __DIR__ . '/..' . '/twig/twig/src/RuntimeLoader/FactoryRuntimeLoader.php', + 'Twig\\RuntimeLoader\\RuntimeLoaderInterface' => __DIR__ . '/..' . '/twig/twig/src/RuntimeLoader/RuntimeLoaderInterface.php', + 'Twig\\Sandbox\\SecurityError' => __DIR__ . '/..' . '/twig/twig/src/Sandbox/SecurityError.php', + 'Twig\\Sandbox\\SecurityNotAllowedFilterError' => __DIR__ . '/..' . '/twig/twig/src/Sandbox/SecurityNotAllowedFilterError.php', + 'Twig\\Sandbox\\SecurityNotAllowedFunctionError' => __DIR__ . '/..' . '/twig/twig/src/Sandbox/SecurityNotAllowedFunctionError.php', + 'Twig\\Sandbox\\SecurityNotAllowedMethodError' => __DIR__ . '/..' . '/twig/twig/src/Sandbox/SecurityNotAllowedMethodError.php', + 'Twig\\Sandbox\\SecurityNotAllowedPropertyError' => __DIR__ . '/..' . '/twig/twig/src/Sandbox/SecurityNotAllowedPropertyError.php', + 'Twig\\Sandbox\\SecurityNotAllowedTagError' => __DIR__ . '/..' . '/twig/twig/src/Sandbox/SecurityNotAllowedTagError.php', + 'Twig\\Sandbox\\SecurityPolicy' => __DIR__ . '/..' . '/twig/twig/src/Sandbox/SecurityPolicy.php', + 'Twig\\Sandbox\\SecurityPolicyInterface' => __DIR__ . '/..' . '/twig/twig/src/Sandbox/SecurityPolicyInterface.php', + 'Twig\\Source' => __DIR__ . '/..' . '/twig/twig/src/Source.php', + 'Twig\\Template' => __DIR__ . '/..' . '/twig/twig/src/Template.php', + 'Twig\\TemplateWrapper' => __DIR__ . '/..' . '/twig/twig/src/TemplateWrapper.php', + 'Twig\\Test\\IntegrationTestCase' => __DIR__ . '/..' . '/twig/twig/src/Test/IntegrationTestCase.php', + 'Twig\\Test\\NodeTestCase' => __DIR__ . '/..' . '/twig/twig/src/Test/NodeTestCase.php', + 'Twig\\Token' => __DIR__ . '/..' . '/twig/twig/src/Token.php', + 'Twig\\TokenParser\\AbstractTokenParser' => __DIR__ . '/..' . '/twig/twig/src/TokenParser/AbstractTokenParser.php', + 'Twig\\TokenParser\\ApplyTokenParser' => __DIR__ . '/..' . '/twig/twig/src/TokenParser/ApplyTokenParser.php', + 'Twig\\TokenParser\\AutoEscapeTokenParser' => __DIR__ . '/..' . '/twig/twig/src/TokenParser/AutoEscapeTokenParser.php', + 'Twig\\TokenParser\\BlockTokenParser' => __DIR__ . '/..' . '/twig/twig/src/TokenParser/BlockTokenParser.php', + 'Twig\\TokenParser\\DeprecatedTokenParser' => __DIR__ . '/..' . '/twig/twig/src/TokenParser/DeprecatedTokenParser.php', + 'Twig\\TokenParser\\DoTokenParser' => __DIR__ . '/..' . '/twig/twig/src/TokenParser/DoTokenParser.php', + 'Twig\\TokenParser\\EmbedTokenParser' => __DIR__ . '/..' . '/twig/twig/src/TokenParser/EmbedTokenParser.php', + 'Twig\\TokenParser\\ExtendsTokenParser' => __DIR__ . '/..' . '/twig/twig/src/TokenParser/ExtendsTokenParser.php', + 'Twig\\TokenParser\\FlushTokenParser' => __DIR__ . '/..' . '/twig/twig/src/TokenParser/FlushTokenParser.php', + 'Twig\\TokenParser\\ForTokenParser' => __DIR__ . '/..' . '/twig/twig/src/TokenParser/ForTokenParser.php', + 'Twig\\TokenParser\\FromTokenParser' => __DIR__ . '/..' . '/twig/twig/src/TokenParser/FromTokenParser.php', + 'Twig\\TokenParser\\IfTokenParser' => __DIR__ . '/..' . '/twig/twig/src/TokenParser/IfTokenParser.php', + 'Twig\\TokenParser\\ImportTokenParser' => __DIR__ . '/..' . '/twig/twig/src/TokenParser/ImportTokenParser.php', + 'Twig\\TokenParser\\IncludeTokenParser' => __DIR__ . '/..' . '/twig/twig/src/TokenParser/IncludeTokenParser.php', + 'Twig\\TokenParser\\MacroTokenParser' => __DIR__ . '/..' . '/twig/twig/src/TokenParser/MacroTokenParser.php', + 'Twig\\TokenParser\\SandboxTokenParser' => __DIR__ . '/..' . '/twig/twig/src/TokenParser/SandboxTokenParser.php', + 'Twig\\TokenParser\\SetTokenParser' => __DIR__ . '/..' . '/twig/twig/src/TokenParser/SetTokenParser.php', + 'Twig\\TokenParser\\TokenParserInterface' => __DIR__ . '/..' . '/twig/twig/src/TokenParser/TokenParserInterface.php', + 'Twig\\TokenParser\\UseTokenParser' => __DIR__ . '/..' . '/twig/twig/src/TokenParser/UseTokenParser.php', + 'Twig\\TokenParser\\WithTokenParser' => __DIR__ . '/..' . '/twig/twig/src/TokenParser/WithTokenParser.php', + 'Twig\\TokenStream' => __DIR__ . '/..' . '/twig/twig/src/TokenStream.php', + 'Twig\\TwigFilter' => __DIR__ . '/..' . '/twig/twig/src/TwigFilter.php', + 'Twig\\TwigFunction' => __DIR__ . '/..' . '/twig/twig/src/TwigFunction.php', + 'Twig\\TwigTest' => __DIR__ . '/..' . '/twig/twig/src/TwigTest.php', + 'Twig\\Util\\DeprecationCollector' => __DIR__ . '/..' . '/twig/twig/src/Util/DeprecationCollector.php', + 'Twig\\Util\\TemplateDirIterator' => __DIR__ . '/..' . '/twig/twig/src/Util/TemplateDirIterator.php', + 'amteich\\Twig\\Environment' => __DIR__ . '/../..' . '/src/classes/Environment.php', + 'amteich\\Twig\\Functions' => __DIR__ . '/../..' . '/src/classes/Functions.php', + 'amteich\\Twig\\Plugin' => __DIR__ . '/../..' . '/src/classes/Plugin.php', + 'amteich\\Twig\\Template' => __DIR__ . '/../..' . '/src/classes/Template.php', + 'amteich\\Twig\\Tests' => __DIR__ . '/../..' . '/src/classes/Tests.php', + ); + + public static function getInitializer(ClassLoader $loader) + { + return \Closure::bind(function () use ($loader) { + $loader->prefixLengthsPsr4 = ComposerStaticInit0fcad4e01a7109b33a336ffeaadc7293::$prefixLengthsPsr4; + $loader->prefixDirsPsr4 = ComposerStaticInit0fcad4e01a7109b33a336ffeaadc7293::$prefixDirsPsr4; + $loader->classMap = ComposerStaticInit0fcad4e01a7109b33a336ffeaadc7293::$classMap; + + }, null, ClassLoader::class); + } +} diff --git a/site/plugins/kirby-twig/vendor/symfony/polyfill-ctype/Ctype.php b/site/plugins/kirby-twig/vendor/symfony/polyfill-ctype/Ctype.php new file mode 100644 index 0000000..58414dc --- /dev/null +++ b/site/plugins/kirby-twig/vendor/symfony/polyfill-ctype/Ctype.php @@ -0,0 +1,227 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Polyfill\Ctype; + +/** + * Ctype implementation through regex. + * + * @internal + * + * @author Gert de Pagter + */ +final class Ctype +{ + /** + * Returns TRUE if every character in text is either a letter or a digit, FALSE otherwise. + * + * @see https://php.net/ctype-alnum + * + * @param string|int $text + * + * @return bool + */ + public static function ctype_alnum($text) + { + $text = self::convert_int_to_char_for_ctype($text); + + return \is_string($text) && '' !== $text && !preg_match('/[^A-Za-z0-9]/', $text); + } + + /** + * Returns TRUE if every character in text is a letter, FALSE otherwise. + * + * @see https://php.net/ctype-alpha + * + * @param string|int $text + * + * @return bool + */ + public static function ctype_alpha($text) + { + $text = self::convert_int_to_char_for_ctype($text); + + return \is_string($text) && '' !== $text && !preg_match('/[^A-Za-z]/', $text); + } + + /** + * Returns TRUE if every character in text is a control character from the current locale, FALSE otherwise. + * + * @see https://php.net/ctype-cntrl + * + * @param string|int $text + * + * @return bool + */ + public static function ctype_cntrl($text) + { + $text = self::convert_int_to_char_for_ctype($text); + + return \is_string($text) && '' !== $text && !preg_match('/[^\x00-\x1f\x7f]/', $text); + } + + /** + * Returns TRUE if every character in the string text is a decimal digit, FALSE otherwise. + * + * @see https://php.net/ctype-digit + * + * @param string|int $text + * + * @return bool + */ + public static function ctype_digit($text) + { + $text = self::convert_int_to_char_for_ctype($text); + + return \is_string($text) && '' !== $text && !preg_match('/[^0-9]/', $text); + } + + /** + * Returns TRUE if every character in text is printable and actually creates visible output (no white space), FALSE otherwise. + * + * @see https://php.net/ctype-graph + * + * @param string|int $text + * + * @return bool + */ + public static function ctype_graph($text) + { + $text = self::convert_int_to_char_for_ctype($text); + + return \is_string($text) && '' !== $text && !preg_match('/[^!-~]/', $text); + } + + /** + * Returns TRUE if every character in text is a lowercase letter. + * + * @see https://php.net/ctype-lower + * + * @param string|int $text + * + * @return bool + */ + public static function ctype_lower($text) + { + $text = self::convert_int_to_char_for_ctype($text); + + return \is_string($text) && '' !== $text && !preg_match('/[^a-z]/', $text); + } + + /** + * Returns TRUE if every character in text will actually create output (including blanks). Returns FALSE if text contains control characters or characters that do not have any output or control function at all. + * + * @see https://php.net/ctype-print + * + * @param string|int $text + * + * @return bool + */ + public static function ctype_print($text) + { + $text = self::convert_int_to_char_for_ctype($text); + + return \is_string($text) && '' !== $text && !preg_match('/[^ -~]/', $text); + } + + /** + * Returns TRUE if every character in text is printable, but neither letter, digit or blank, FALSE otherwise. + * + * @see https://php.net/ctype-punct + * + * @param string|int $text + * + * @return bool + */ + public static function ctype_punct($text) + { + $text = self::convert_int_to_char_for_ctype($text); + + return \is_string($text) && '' !== $text && !preg_match('/[^!-\/\:-@\[-`\{-~]/', $text); + } + + /** + * Returns TRUE if every character in text creates some sort of white space, FALSE otherwise. Besides the blank character this also includes tab, vertical tab, line feed, carriage return and form feed characters. + * + * @see https://php.net/ctype-space + * + * @param string|int $text + * + * @return bool + */ + public static function ctype_space($text) + { + $text = self::convert_int_to_char_for_ctype($text); + + return \is_string($text) && '' !== $text && !preg_match('/[^\s]/', $text); + } + + /** + * Returns TRUE if every character in text is an uppercase letter. + * + * @see https://php.net/ctype-upper + * + * @param string|int $text + * + * @return bool + */ + public static function ctype_upper($text) + { + $text = self::convert_int_to_char_for_ctype($text); + + return \is_string($text) && '' !== $text && !preg_match('/[^A-Z]/', $text); + } + + /** + * Returns TRUE if every character in text is a hexadecimal 'digit', that is a decimal digit or a character from [A-Fa-f] , FALSE otherwise. + * + * @see https://php.net/ctype-xdigit + * + * @param string|int $text + * + * @return bool + */ + public static function ctype_xdigit($text) + { + $text = self::convert_int_to_char_for_ctype($text); + + return \is_string($text) && '' !== $text && !preg_match('/[^A-Fa-f0-9]/', $text); + } + + /** + * Converts integers to their char versions according to normal ctype behaviour, if needed. + * + * If an integer between -128 and 255 inclusive is provided, + * it is interpreted as the ASCII value of a single character + * (negative values have 256 added in order to allow characters in the Extended ASCII range). + * Any other integer is interpreted as a string containing the decimal digits of the integer. + * + * @param string|int $int + * + * @return mixed + */ + private static function convert_int_to_char_for_ctype($int) + { + if (!\is_int($int)) { + return $int; + } + + if ($int < -128 || $int > 255) { + return (string) $int; + } + + if ($int < 0) { + $int += 256; + } + + return \chr($int); + } +} diff --git a/site/plugins/kirby-twig/vendor/symfony/polyfill-ctype/bootstrap.php b/site/plugins/kirby-twig/vendor/symfony/polyfill-ctype/bootstrap.php new file mode 100644 index 0000000..d54524b --- /dev/null +++ b/site/plugins/kirby-twig/vendor/symfony/polyfill-ctype/bootstrap.php @@ -0,0 +1,50 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Symfony\Polyfill\Ctype as p; + +if (\PHP_VERSION_ID >= 80000) { + return require __DIR__.'/bootstrap80.php'; +} + +if (!function_exists('ctype_alnum')) { + function ctype_alnum($text) { return p\Ctype::ctype_alnum($text); } +} +if (!function_exists('ctype_alpha')) { + function ctype_alpha($text) { return p\Ctype::ctype_alpha($text); } +} +if (!function_exists('ctype_cntrl')) { + function ctype_cntrl($text) { return p\Ctype::ctype_cntrl($text); } +} +if (!function_exists('ctype_digit')) { + function ctype_digit($text) { return p\Ctype::ctype_digit($text); } +} +if (!function_exists('ctype_graph')) { + function ctype_graph($text) { return p\Ctype::ctype_graph($text); } +} +if (!function_exists('ctype_lower')) { + function ctype_lower($text) { return p\Ctype::ctype_lower($text); } +} +if (!function_exists('ctype_print')) { + function ctype_print($text) { return p\Ctype::ctype_print($text); } +} +if (!function_exists('ctype_punct')) { + function ctype_punct($text) { return p\Ctype::ctype_punct($text); } +} +if (!function_exists('ctype_space')) { + function ctype_space($text) { return p\Ctype::ctype_space($text); } +} +if (!function_exists('ctype_upper')) { + function ctype_upper($text) { return p\Ctype::ctype_upper($text); } +} +if (!function_exists('ctype_xdigit')) { + function ctype_xdigit($text) { return p\Ctype::ctype_xdigit($text); } +} diff --git a/site/plugins/kirby-twig/vendor/symfony/polyfill-ctype/bootstrap80.php b/site/plugins/kirby-twig/vendor/symfony/polyfill-ctype/bootstrap80.php new file mode 100644 index 0000000..ab2f861 --- /dev/null +++ b/site/plugins/kirby-twig/vendor/symfony/polyfill-ctype/bootstrap80.php @@ -0,0 +1,46 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Symfony\Polyfill\Ctype as p; + +if (!function_exists('ctype_alnum')) { + function ctype_alnum(mixed $text): bool { return p\Ctype::ctype_alnum($text); } +} +if (!function_exists('ctype_alpha')) { + function ctype_alpha(mixed $text): bool { return p\Ctype::ctype_alpha($text); } +} +if (!function_exists('ctype_cntrl')) { + function ctype_cntrl(mixed $text): bool { return p\Ctype::ctype_cntrl($text); } +} +if (!function_exists('ctype_digit')) { + function ctype_digit(mixed $text): bool { return p\Ctype::ctype_digit($text); } +} +if (!function_exists('ctype_graph')) { + function ctype_graph(mixed $text): bool { return p\Ctype::ctype_graph($text); } +} +if (!function_exists('ctype_lower')) { + function ctype_lower(mixed $text): bool { return p\Ctype::ctype_lower($text); } +} +if (!function_exists('ctype_print')) { + function ctype_print(mixed $text): bool { return p\Ctype::ctype_print($text); } +} +if (!function_exists('ctype_punct')) { + function ctype_punct(mixed $text): bool { return p\Ctype::ctype_punct($text); } +} +if (!function_exists('ctype_space')) { + function ctype_space(mixed $text): bool { return p\Ctype::ctype_space($text); } +} +if (!function_exists('ctype_upper')) { + function ctype_upper(mixed $text): bool { return p\Ctype::ctype_upper($text); } +} +if (!function_exists('ctype_xdigit')) { + function ctype_xdigit(mixed $text): bool { return p\Ctype::ctype_xdigit($text); } +} diff --git a/site/plugins/kirby-twig/vendor/symfony/polyfill-mbstring/Mbstring.php b/site/plugins/kirby-twig/vendor/symfony/polyfill-mbstring/Mbstring.php new file mode 100644 index 0000000..8b3b758 --- /dev/null +++ b/site/plugins/kirby-twig/vendor/symfony/polyfill-mbstring/Mbstring.php @@ -0,0 +1,869 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Polyfill\Mbstring; + +/** + * Partial mbstring implementation in PHP, iconv based, UTF-8 centric. + * + * Implemented: + * - mb_chr - Returns a specific character from its Unicode code point + * - mb_convert_encoding - Convert character encoding + * - mb_convert_variables - Convert character code in variable(s) + * - mb_decode_mimeheader - Decode string in MIME header field + * - mb_encode_mimeheader - Encode string for MIME header XXX NATIVE IMPLEMENTATION IS REALLY BUGGED + * - mb_decode_numericentity - Decode HTML numeric string reference to character + * - mb_encode_numericentity - Encode character to HTML numeric string reference + * - mb_convert_case - Perform case folding on a string + * - mb_detect_encoding - Detect character encoding + * - mb_get_info - Get internal settings of mbstring + * - mb_http_input - Detect HTTP input character encoding + * - mb_http_output - Set/Get HTTP output character encoding + * - mb_internal_encoding - Set/Get internal character encoding + * - mb_list_encodings - Returns an array of all supported encodings + * - mb_ord - Returns the Unicode code point of a character + * - mb_output_handler - Callback function converts character encoding in output buffer + * - mb_scrub - Replaces ill-formed byte sequences with substitute characters + * - mb_strlen - Get string length + * - mb_strpos - Find position of first occurrence of string in a string + * - mb_strrpos - Find position of last occurrence of a string in a string + * - mb_str_split - Convert a string to an array + * - mb_strtolower - Make a string lowercase + * - mb_strtoupper - Make a string uppercase + * - mb_substitute_character - Set/Get substitution character + * - mb_substr - Get part of string + * - mb_stripos - Finds position of first occurrence of a string within another, case insensitive + * - mb_stristr - Finds first occurrence of a string within another, case insensitive + * - mb_strrchr - Finds the last occurrence of a character in a string within another + * - mb_strrichr - Finds the last occurrence of a character in a string within another, case insensitive + * - mb_strripos - Finds position of last occurrence of a string within another, case insensitive + * - mb_strstr - Finds first occurrence of a string within another + * - mb_strwidth - Return width of string + * - mb_substr_count - Count the number of substring occurrences + * + * Not implemented: + * - mb_convert_kana - Convert "kana" one from another ("zen-kaku", "han-kaku" and more) + * - mb_ereg_* - Regular expression with multibyte support + * - mb_parse_str - Parse GET/POST/COOKIE data and set global variable + * - mb_preferred_mime_name - Get MIME charset string + * - mb_regex_encoding - Returns current encoding for multibyte regex as string + * - mb_regex_set_options - Set/Get the default options for mbregex functions + * - mb_send_mail - Send encoded mail + * - mb_split - Split multibyte string using regular expression + * - mb_strcut - Get part of string + * - mb_strimwidth - Get truncated string with specified width + * + * @author Nicolas Grekas + * + * @internal + */ +final class Mbstring +{ + public const MB_CASE_FOLD = \PHP_INT_MAX; + + private static $encodingList = ['ASCII', 'UTF-8']; + private static $language = 'neutral'; + private static $internalEncoding = 'UTF-8'; + private static $caseFold = [ + ['µ', 'ſ', "\xCD\x85", 'ς', "\xCF\x90", "\xCF\x91", "\xCF\x95", "\xCF\x96", "\xCF\xB0", "\xCF\xB1", "\xCF\xB5", "\xE1\xBA\x9B", "\xE1\xBE\xBE"], + ['μ', 's', 'ι', 'σ', 'β', 'θ', 'φ', 'π', 'κ', 'ρ', 'ε', "\xE1\xB9\xA1", 'ι'], + ]; + + public static function mb_convert_encoding($s, $toEncoding, $fromEncoding = null) + { + if (\is_array($fromEncoding) || false !== strpos($fromEncoding, ',')) { + $fromEncoding = self::mb_detect_encoding($s, $fromEncoding); + } else { + $fromEncoding = self::getEncoding($fromEncoding); + } + + $toEncoding = self::getEncoding($toEncoding); + + if ('BASE64' === $fromEncoding) { + $s = base64_decode($s); + $fromEncoding = $toEncoding; + } + + if ('BASE64' === $toEncoding) { + return base64_encode($s); + } + + if ('HTML-ENTITIES' === $toEncoding || 'HTML' === $toEncoding) { + if ('HTML-ENTITIES' === $fromEncoding || 'HTML' === $fromEncoding) { + $fromEncoding = 'Windows-1252'; + } + if ('UTF-8' !== $fromEncoding) { + $s = iconv($fromEncoding, 'UTF-8//IGNORE', $s); + } + + return preg_replace_callback('/[\x80-\xFF]+/', [__CLASS__, 'html_encoding_callback'], $s); + } + + if ('HTML-ENTITIES' === $fromEncoding) { + $s = html_entity_decode($s, \ENT_COMPAT, 'UTF-8'); + $fromEncoding = 'UTF-8'; + } + + return iconv($fromEncoding, $toEncoding.'//IGNORE', $s); + } + + public static function mb_convert_variables($toEncoding, $fromEncoding, &...$vars) + { + $ok = true; + array_walk_recursive($vars, function (&$v) use (&$ok, $toEncoding, $fromEncoding) { + if (false === $v = self::mb_convert_encoding($v, $toEncoding, $fromEncoding)) { + $ok = false; + } + }); + + return $ok ? $fromEncoding : false; + } + + public static function mb_decode_mimeheader($s) + { + return iconv_mime_decode($s, 2, self::$internalEncoding); + } + + public static function mb_encode_mimeheader($s, $charset = null, $transferEncoding = null, $linefeed = null, $indent = null) + { + trigger_error('mb_encode_mimeheader() is bugged. Please use iconv_mime_encode() instead', \E_USER_WARNING); + } + + public static function mb_decode_numericentity($s, $convmap, $encoding = null) + { + if (null !== $s && !is_scalar($s) && !(\is_object($s) && method_exists($s, '__toString'))) { + trigger_error('mb_decode_numericentity() expects parameter 1 to be string, '.\gettype($s).' given', \E_USER_WARNING); + + return null; + } + + if (!\is_array($convmap) || (80000 > \PHP_VERSION_ID && !$convmap)) { + return false; + } + + if (null !== $encoding && !is_scalar($encoding)) { + trigger_error('mb_decode_numericentity() expects parameter 3 to be string, '.\gettype($s).' given', \E_USER_WARNING); + + return ''; // Instead of null (cf. mb_encode_numericentity). + } + + $s = (string) $s; + if ('' === $s) { + return ''; + } + + $encoding = self::getEncoding($encoding); + + if ('UTF-8' === $encoding) { + $encoding = null; + if (!preg_match('//u', $s)) { + $s = @iconv('UTF-8', 'UTF-8//IGNORE', $s); + } + } else { + $s = iconv($encoding, 'UTF-8//IGNORE', $s); + } + + $cnt = floor(\count($convmap) / 4) * 4; + + for ($i = 0; $i < $cnt; $i += 4) { + // collector_decode_htmlnumericentity ignores $convmap[$i + 3] + $convmap[$i] += $convmap[$i + 2]; + $convmap[$i + 1] += $convmap[$i + 2]; + } + + $s = preg_replace_callback('/&#(?:0*([0-9]+)|x0*([0-9a-fA-F]+))(?!&);?/', function (array $m) use ($cnt, $convmap) { + $c = isset($m[2]) ? (int) hexdec($m[2]) : $m[1]; + for ($i = 0; $i < $cnt; $i += 4) { + if ($c >= $convmap[$i] && $c <= $convmap[$i + 1]) { + return self::mb_chr($c - $convmap[$i + 2]); + } + } + + return $m[0]; + }, $s); + + if (null === $encoding) { + return $s; + } + + return iconv('UTF-8', $encoding.'//IGNORE', $s); + } + + public static function mb_encode_numericentity($s, $convmap, $encoding = null, $is_hex = false) + { + if (null !== $s && !is_scalar($s) && !(\is_object($s) && method_exists($s, '__toString'))) { + trigger_error('mb_encode_numericentity() expects parameter 1 to be string, '.\gettype($s).' given', \E_USER_WARNING); + + return null; + } + + if (!\is_array($convmap) || (80000 > \PHP_VERSION_ID && !$convmap)) { + return false; + } + + if (null !== $encoding && !is_scalar($encoding)) { + trigger_error('mb_encode_numericentity() expects parameter 3 to be string, '.\gettype($s).' given', \E_USER_WARNING); + + return null; // Instead of '' (cf. mb_decode_numericentity). + } + + if (null !== $is_hex && !is_scalar($is_hex)) { + trigger_error('mb_encode_numericentity() expects parameter 4 to be boolean, '.\gettype($s).' given', \E_USER_WARNING); + + return null; + } + + $s = (string) $s; + if ('' === $s) { + return ''; + } + + $encoding = self::getEncoding($encoding); + + if ('UTF-8' === $encoding) { + $encoding = null; + if (!preg_match('//u', $s)) { + $s = @iconv('UTF-8', 'UTF-8//IGNORE', $s); + } + } else { + $s = iconv($encoding, 'UTF-8//IGNORE', $s); + } + + static $ulenMask = ["\xC0" => 2, "\xD0" => 2, "\xE0" => 3, "\xF0" => 4]; + + $cnt = floor(\count($convmap) / 4) * 4; + $i = 0; + $len = \strlen($s); + $result = ''; + + while ($i < $len) { + $ulen = $s[$i] < "\x80" ? 1 : $ulenMask[$s[$i] & "\xF0"]; + $uchr = substr($s, $i, $ulen); + $i += $ulen; + $c = self::mb_ord($uchr); + + for ($j = 0; $j < $cnt; $j += 4) { + if ($c >= $convmap[$j] && $c <= $convmap[$j + 1]) { + $cOffset = ($c + $convmap[$j + 2]) & $convmap[$j + 3]; + $result .= $is_hex ? sprintf('&#x%X;', $cOffset) : '&#'.$cOffset.';'; + continue 2; + } + } + $result .= $uchr; + } + + if (null === $encoding) { + return $result; + } + + return iconv('UTF-8', $encoding.'//IGNORE', $result); + } + + public static function mb_convert_case($s, $mode, $encoding = null) + { + $s = (string) $s; + if ('' === $s) { + return ''; + } + + $encoding = self::getEncoding($encoding); + + if ('UTF-8' === $encoding) { + $encoding = null; + if (!preg_match('//u', $s)) { + $s = @iconv('UTF-8', 'UTF-8//IGNORE', $s); + } + } else { + $s = iconv($encoding, 'UTF-8//IGNORE', $s); + } + + if (\MB_CASE_TITLE == $mode) { + static $titleRegexp = null; + if (null === $titleRegexp) { + $titleRegexp = self::getData('titleCaseRegexp'); + } + $s = preg_replace_callback($titleRegexp, [__CLASS__, 'title_case'], $s); + } else { + if (\MB_CASE_UPPER == $mode) { + static $upper = null; + if (null === $upper) { + $upper = self::getData('upperCase'); + } + $map = $upper; + } else { + if (self::MB_CASE_FOLD === $mode) { + $s = str_replace(self::$caseFold[0], self::$caseFold[1], $s); + } + + static $lower = null; + if (null === $lower) { + $lower = self::getData('lowerCase'); + } + $map = $lower; + } + + static $ulenMask = ["\xC0" => 2, "\xD0" => 2, "\xE0" => 3, "\xF0" => 4]; + + $i = 0; + $len = \strlen($s); + + while ($i < $len) { + $ulen = $s[$i] < "\x80" ? 1 : $ulenMask[$s[$i] & "\xF0"]; + $uchr = substr($s, $i, $ulen); + $i += $ulen; + + if (isset($map[$uchr])) { + $uchr = $map[$uchr]; + $nlen = \strlen($uchr); + + if ($nlen == $ulen) { + $nlen = $i; + do { + $s[--$nlen] = $uchr[--$ulen]; + } while ($ulen); + } else { + $s = substr_replace($s, $uchr, $i - $ulen, $ulen); + $len += $nlen - $ulen; + $i += $nlen - $ulen; + } + } + } + } + + if (null === $encoding) { + return $s; + } + + return iconv('UTF-8', $encoding.'//IGNORE', $s); + } + + public static function mb_internal_encoding($encoding = null) + { + if (null === $encoding) { + return self::$internalEncoding; + } + + $normalizedEncoding = self::getEncoding($encoding); + + if ('UTF-8' === $normalizedEncoding || false !== @iconv($normalizedEncoding, $normalizedEncoding, ' ')) { + self::$internalEncoding = $normalizedEncoding; + + return true; + } + + if (80000 > \PHP_VERSION_ID) { + return false; + } + + throw new \ValueError(sprintf('Argument #1 ($encoding) must be a valid encoding, "%s" given', $encoding)); + } + + public static function mb_language($lang = null) + { + if (null === $lang) { + return self::$language; + } + + switch ($normalizedLang = strtolower($lang)) { + case 'uni': + case 'neutral': + self::$language = $normalizedLang; + + return true; + } + + if (80000 > \PHP_VERSION_ID) { + return false; + } + + throw new \ValueError(sprintf('Argument #1 ($language) must be a valid language, "%s" given', $lang)); + } + + public static function mb_list_encodings() + { + return ['UTF-8']; + } + + public static function mb_encoding_aliases($encoding) + { + switch (strtoupper($encoding)) { + case 'UTF8': + case 'UTF-8': + return ['utf8']; + } + + return false; + } + + public static function mb_check_encoding($var = null, $encoding = null) + { + if (null === $encoding) { + if (null === $var) { + return false; + } + $encoding = self::$internalEncoding; + } + + return self::mb_detect_encoding($var, [$encoding]) || false !== @iconv($encoding, $encoding, $var); + } + + public static function mb_detect_encoding($str, $encodingList = null, $strict = false) + { + if (null === $encodingList) { + $encodingList = self::$encodingList; + } else { + if (!\is_array($encodingList)) { + $encodingList = array_map('trim', explode(',', $encodingList)); + } + $encodingList = array_map('strtoupper', $encodingList); + } + + foreach ($encodingList as $enc) { + switch ($enc) { + case 'ASCII': + if (!preg_match('/[\x80-\xFF]/', $str)) { + return $enc; + } + break; + + case 'UTF8': + case 'UTF-8': + if (preg_match('//u', $str)) { + return 'UTF-8'; + } + break; + + default: + if (0 === strncmp($enc, 'ISO-8859-', 9)) { + return $enc; + } + } + } + + return false; + } + + public static function mb_detect_order($encodingList = null) + { + if (null === $encodingList) { + return self::$encodingList; + } + + if (!\is_array($encodingList)) { + $encodingList = array_map('trim', explode(',', $encodingList)); + } + $encodingList = array_map('strtoupper', $encodingList); + + foreach ($encodingList as $enc) { + switch ($enc) { + default: + if (strncmp($enc, 'ISO-8859-', 9)) { + return false; + } + // no break + case 'ASCII': + case 'UTF8': + case 'UTF-8': + } + } + + self::$encodingList = $encodingList; + + return true; + } + + public static function mb_strlen($s, $encoding = null) + { + $encoding = self::getEncoding($encoding); + if ('CP850' === $encoding || 'ASCII' === $encoding) { + return \strlen($s); + } + + return @iconv_strlen($s, $encoding); + } + + public static function mb_strpos($haystack, $needle, $offset = 0, $encoding = null) + { + $encoding = self::getEncoding($encoding); + if ('CP850' === $encoding || 'ASCII' === $encoding) { + return strpos($haystack, $needle, $offset); + } + + $needle = (string) $needle; + if ('' === $needle) { + if (80000 > \PHP_VERSION_ID) { + trigger_error(__METHOD__.': Empty delimiter', \E_USER_WARNING); + + return false; + } + + return 0; + } + + return iconv_strpos($haystack, $needle, $offset, $encoding); + } + + public static function mb_strrpos($haystack, $needle, $offset = 0, $encoding = null) + { + $encoding = self::getEncoding($encoding); + if ('CP850' === $encoding || 'ASCII' === $encoding) { + return strrpos($haystack, $needle, $offset); + } + + if ($offset != (int) $offset) { + $offset = 0; + } elseif ($offset = (int) $offset) { + if ($offset < 0) { + if (0 > $offset += self::mb_strlen($needle)) { + $haystack = self::mb_substr($haystack, 0, $offset, $encoding); + } + $offset = 0; + } else { + $haystack = self::mb_substr($haystack, $offset, 2147483647, $encoding); + } + } + + $pos = '' !== $needle || 80000 > \PHP_VERSION_ID + ? iconv_strrpos($haystack, $needle, $encoding) + : self::mb_strlen($haystack, $encoding); + + return false !== $pos ? $offset + $pos : false; + } + + public static function mb_str_split($string, $split_length = 1, $encoding = null) + { + if (null !== $string && !is_scalar($string) && !(\is_object($string) && method_exists($string, '__toString'))) { + trigger_error('mb_str_split() expects parameter 1 to be string, '.\gettype($string).' given', \E_USER_WARNING); + + return null; + } + + if (1 > $split_length = (int) $split_length) { + if (80000 > \PHP_VERSION_ID) { + trigger_error('The length of each segment must be greater than zero', \E_USER_WARNING); + return false; + } + + throw new \ValueError('Argument #2 ($length) must be greater than 0'); + } + + if (null === $encoding) { + $encoding = mb_internal_encoding(); + } + + if ('UTF-8' === $encoding = self::getEncoding($encoding)) { + $rx = '/('; + while (65535 < $split_length) { + $rx .= '.{65535}'; + $split_length -= 65535; + } + $rx .= '.{'.$split_length.'})/us'; + + return preg_split($rx, $string, null, \PREG_SPLIT_DELIM_CAPTURE | \PREG_SPLIT_NO_EMPTY); + } + + $result = []; + $length = mb_strlen($string, $encoding); + + for ($i = 0; $i < $length; $i += $split_length) { + $result[] = mb_substr($string, $i, $split_length, $encoding); + } + + return $result; + } + + public static function mb_strtolower($s, $encoding = null) + { + return self::mb_convert_case($s, \MB_CASE_LOWER, $encoding); + } + + public static function mb_strtoupper($s, $encoding = null) + { + return self::mb_convert_case($s, \MB_CASE_UPPER, $encoding); + } + + public static function mb_substitute_character($c = null) + { + if (null === $c) { + return 'none'; + } + if (0 === strcasecmp($c, 'none')) { + return true; + } + if (80000 > \PHP_VERSION_ID) { + return false; + } + + throw new \ValueError('Argument #1 ($substitute_character) must be "none", "long", "entity" or a valid codepoint'); + } + + public static function mb_substr($s, $start, $length = null, $encoding = null) + { + $encoding = self::getEncoding($encoding); + if ('CP850' === $encoding || 'ASCII' === $encoding) { + return (string) substr($s, $start, null === $length ? 2147483647 : $length); + } + + if ($start < 0) { + $start = iconv_strlen($s, $encoding) + $start; + if ($start < 0) { + $start = 0; + } + } + + if (null === $length) { + $length = 2147483647; + } elseif ($length < 0) { + $length = iconv_strlen($s, $encoding) + $length - $start; + if ($length < 0) { + return ''; + } + } + + return (string) iconv_substr($s, $start, $length, $encoding); + } + + public static function mb_stripos($haystack, $needle, $offset = 0, $encoding = null) + { + $haystack = self::mb_convert_case($haystack, self::MB_CASE_FOLD, $encoding); + $needle = self::mb_convert_case($needle, self::MB_CASE_FOLD, $encoding); + + return self::mb_strpos($haystack, $needle, $offset, $encoding); + } + + public static function mb_stristr($haystack, $needle, $part = false, $encoding = null) + { + $pos = self::mb_stripos($haystack, $needle, 0, $encoding); + + return self::getSubpart($pos, $part, $haystack, $encoding); + } + + public static function mb_strrchr($haystack, $needle, $part = false, $encoding = null) + { + $encoding = self::getEncoding($encoding); + if ('CP850' === $encoding || 'ASCII' === $encoding) { + $pos = strrpos($haystack, $needle); + } else { + $needle = self::mb_substr($needle, 0, 1, $encoding); + $pos = iconv_strrpos($haystack, $needle, $encoding); + } + + return self::getSubpart($pos, $part, $haystack, $encoding); + } + + public static function mb_strrichr($haystack, $needle, $part = false, $encoding = null) + { + $needle = self::mb_substr($needle, 0, 1, $encoding); + $pos = self::mb_strripos($haystack, $needle, $encoding); + + return self::getSubpart($pos, $part, $haystack, $encoding); + } + + public static function mb_strripos($haystack, $needle, $offset = 0, $encoding = null) + { + $haystack = self::mb_convert_case($haystack, self::MB_CASE_FOLD, $encoding); + $needle = self::mb_convert_case($needle, self::MB_CASE_FOLD, $encoding); + + return self::mb_strrpos($haystack, $needle, $offset, $encoding); + } + + public static function mb_strstr($haystack, $needle, $part = false, $encoding = null) + { + $pos = strpos($haystack, $needle); + if (false === $pos) { + return false; + } + if ($part) { + return substr($haystack, 0, $pos); + } + + return substr($haystack, $pos); + } + + public static function mb_get_info($type = 'all') + { + $info = [ + 'internal_encoding' => self::$internalEncoding, + 'http_output' => 'pass', + 'http_output_conv_mimetypes' => '^(text/|application/xhtml\+xml)', + 'func_overload' => 0, + 'func_overload_list' => 'no overload', + 'mail_charset' => 'UTF-8', + 'mail_header_encoding' => 'BASE64', + 'mail_body_encoding' => 'BASE64', + 'illegal_chars' => 0, + 'encoding_translation' => 'Off', + 'language' => self::$language, + 'detect_order' => self::$encodingList, + 'substitute_character' => 'none', + 'strict_detection' => 'Off', + ]; + + if ('all' === $type) { + return $info; + } + if (isset($info[$type])) { + return $info[$type]; + } + + return false; + } + + public static function mb_http_input($type = '') + { + return false; + } + + public static function mb_http_output($encoding = null) + { + return null !== $encoding ? 'pass' === $encoding : 'pass'; + } + + public static function mb_strwidth($s, $encoding = null) + { + $encoding = self::getEncoding($encoding); + + if ('UTF-8' !== $encoding) { + $s = iconv($encoding, 'UTF-8//IGNORE', $s); + } + + $s = preg_replace('/[\x{1100}-\x{115F}\x{2329}\x{232A}\x{2E80}-\x{303E}\x{3040}-\x{A4CF}\x{AC00}-\x{D7A3}\x{F900}-\x{FAFF}\x{FE10}-\x{FE19}\x{FE30}-\x{FE6F}\x{FF00}-\x{FF60}\x{FFE0}-\x{FFE6}\x{20000}-\x{2FFFD}\x{30000}-\x{3FFFD}]/u', '', $s, -1, $wide); + + return ($wide << 1) + iconv_strlen($s, 'UTF-8'); + } + + public static function mb_substr_count($haystack, $needle, $encoding = null) + { + return substr_count($haystack, $needle); + } + + public static function mb_output_handler($contents, $status) + { + return $contents; + } + + public static function mb_chr($code, $encoding = null) + { + if (0x80 > $code %= 0x200000) { + $s = \chr($code); + } elseif (0x800 > $code) { + $s = \chr(0xC0 | $code >> 6).\chr(0x80 | $code & 0x3F); + } elseif (0x10000 > $code) { + $s = \chr(0xE0 | $code >> 12).\chr(0x80 | $code >> 6 & 0x3F).\chr(0x80 | $code & 0x3F); + } else { + $s = \chr(0xF0 | $code >> 18).\chr(0x80 | $code >> 12 & 0x3F).\chr(0x80 | $code >> 6 & 0x3F).\chr(0x80 | $code & 0x3F); + } + + if ('UTF-8' !== $encoding = self::getEncoding($encoding)) { + $s = mb_convert_encoding($s, $encoding, 'UTF-8'); + } + + return $s; + } + + public static function mb_ord($s, $encoding = null) + { + if ('UTF-8' !== $encoding = self::getEncoding($encoding)) { + $s = mb_convert_encoding($s, 'UTF-8', $encoding); + } + + if (1 === \strlen($s)) { + return \ord($s); + } + + $code = ($s = unpack('C*', substr($s, 0, 4))) ? $s[1] : 0; + if (0xF0 <= $code) { + return (($code - 0xF0) << 18) + (($s[2] - 0x80) << 12) + (($s[3] - 0x80) << 6) + $s[4] - 0x80; + } + if (0xE0 <= $code) { + return (($code - 0xE0) << 12) + (($s[2] - 0x80) << 6) + $s[3] - 0x80; + } + if (0xC0 <= $code) { + return (($code - 0xC0) << 6) + $s[2] - 0x80; + } + + return $code; + } + + private static function getSubpart($pos, $part, $haystack, $encoding) + { + if (false === $pos) { + return false; + } + if ($part) { + return self::mb_substr($haystack, 0, $pos, $encoding); + } + + return self::mb_substr($haystack, $pos, null, $encoding); + } + + private static function html_encoding_callback(array $m) + { + $i = 1; + $entities = ''; + $m = unpack('C*', htmlentities($m[0], \ENT_COMPAT, 'UTF-8')); + + while (isset($m[$i])) { + if (0x80 > $m[$i]) { + $entities .= \chr($m[$i++]); + continue; + } + if (0xF0 <= $m[$i]) { + $c = (($m[$i++] - 0xF0) << 18) + (($m[$i++] - 0x80) << 12) + (($m[$i++] - 0x80) << 6) + $m[$i++] - 0x80; + } elseif (0xE0 <= $m[$i]) { + $c = (($m[$i++] - 0xE0) << 12) + (($m[$i++] - 0x80) << 6) + $m[$i++] - 0x80; + } else { + $c = (($m[$i++] - 0xC0) << 6) + $m[$i++] - 0x80; + } + + $entities .= '&#'.$c.';'; + } + + return $entities; + } + + private static function title_case(array $s) + { + return self::mb_convert_case($s[1], \MB_CASE_UPPER, 'UTF-8').self::mb_convert_case($s[2], \MB_CASE_LOWER, 'UTF-8'); + } + + private static function getData($file) + { + if (file_exists($file = __DIR__.'/Resources/unidata/'.$file.'.php')) { + return require $file; + } + + return false; + } + + private static function getEncoding($encoding) + { + if (null === $encoding) { + return self::$internalEncoding; + } + + if ('UTF-8' === $encoding) { + return 'UTF-8'; + } + + $encoding = strtoupper($encoding); + + if ('8BIT' === $encoding || 'BINARY' === $encoding) { + return 'CP850'; + } + + if ('UTF8' === $encoding) { + return 'UTF-8'; + } + + return $encoding; + } +} diff --git a/site/plugins/kirby-twig/vendor/symfony/polyfill-mbstring/Resources/unidata/lowerCase.php b/site/plugins/kirby-twig/vendor/symfony/polyfill-mbstring/Resources/unidata/lowerCase.php new file mode 100644 index 0000000..a22eca5 --- /dev/null +++ b/site/plugins/kirby-twig/vendor/symfony/polyfill-mbstring/Resources/unidata/lowerCase.php @@ -0,0 +1,1397 @@ + 'a', + 'B' => 'b', + 'C' => 'c', + 'D' => 'd', + 'E' => 'e', + 'F' => 'f', + 'G' => 'g', + 'H' => 'h', + 'I' => 'i', + 'J' => 'j', + 'K' => 'k', + 'L' => 'l', + 'M' => 'm', + 'N' => 'n', + 'O' => 'o', + 'P' => 'p', + 'Q' => 'q', + 'R' => 'r', + 'S' => 's', + 'T' => 't', + 'U' => 'u', + 'V' => 'v', + 'W' => 'w', + 'X' => 'x', + 'Y' => 'y', + 'Z' => 'z', + 'À' => 'à', + 'Á' => 'á', + 'Â' => 'â', + 'Ã' => 'ã', + 'Ä' => 'ä', + 'Å' => 'å', + 'Æ' => 'æ', + 'Ç' => 'ç', + 'È' => 'è', + 'É' => 'é', + 'Ê' => 'ê', + 'Ë' => 'ë', + 'Ì' => 'ì', + 'Í' => 'í', + 'Î' => 'î', + 'Ï' => 'ï', + 'Ð' => 'ð', + 'Ñ' => 'ñ', + 'Ò' => 'ò', + 'Ó' => 'ó', + 'Ô' => 'ô', + 'Õ' => 'õ', + 'Ö' => 'ö', + 'Ø' => 'ø', + 'Ù' => 'ù', + 'Ú' => 'ú', + 'Û' => 'û', + 'Ü' => 'ü', + 'Ý' => 'ý', + 'Þ' => 'þ', + 'Ā' => 'ā', + 'Ă' => 'ă', + 'Ą' => 'ą', + 'Ć' => 'ć', + 'Ĉ' => 'ĉ', + 'Ċ' => 'ċ', + 'Č' => 'č', + 'Ď' => 'ď', + 'Đ' => 'đ', + 'Ē' => 'ē', + 'Ĕ' => 'ĕ', + 'Ė' => 'ė', + 'Ę' => 'ę', + 'Ě' => 'ě', + 'Ĝ' => 'ĝ', + 'Ğ' => 'ğ', + 'Ġ' => 'ġ', + 'Ģ' => 'ģ', + 'Ĥ' => 'ĥ', + 'Ħ' => 'ħ', + 'Ĩ' => 'ĩ', + 'Ī' => 'ī', + 'Ĭ' => 'ĭ', + 'Į' => 'į', + 'İ' => 'i', + 'IJ' => 'ij', + 'Ĵ' => 'ĵ', + 'Ķ' => 'ķ', + 'Ĺ' => 'ĺ', + 'Ļ' => 'ļ', + 'Ľ' => 'ľ', + 'Ŀ' => 'ŀ', + 'Ł' => 'ł', + 'Ń' => 'ń', + 'Ņ' => 'ņ', + 'Ň' => 'ň', + 'Ŋ' => 'ŋ', + 'Ō' => 'ō', + 'Ŏ' => 'ŏ', + 'Ő' => 'ő', + 'Œ' => 'œ', + 'Ŕ' => 'ŕ', + 'Ŗ' => 'ŗ', + 'Ř' => 'ř', + 'Ś' => 'ś', + 'Ŝ' => 'ŝ', + 'Ş' => 'ş', + 'Š' => 'š', + 'Ţ' => 'ţ', + 'Ť' => 'ť', + 'Ŧ' => 'ŧ', + 'Ũ' => 'ũ', + 'Ū' => 'ū', + 'Ŭ' => 'ŭ', + 'Ů' => 'ů', + 'Ű' => 'ű', + 'Ų' => 'ų', + 'Ŵ' => 'ŵ', + 'Ŷ' => 'ŷ', + 'Ÿ' => 'ÿ', + 'Ź' => 'ź', + 'Ż' => 'ż', + 'Ž' => 'ž', + 'Ɓ' => 'ɓ', + 'Ƃ' => 'ƃ', + 'Ƅ' => 'ƅ', + 'Ɔ' => 'ɔ', + 'Ƈ' => 'ƈ', + 'Ɖ' => 'ɖ', + 'Ɗ' => 'ɗ', + 'Ƌ' => 'ƌ', + 'Ǝ' => 'ǝ', + 'Ə' => 'ə', + 'Ɛ' => 'ɛ', + 'Ƒ' => 'ƒ', + 'Ɠ' => 'ɠ', + 'Ɣ' => 'ɣ', + 'Ɩ' => 'ɩ', + 'Ɨ' => 'ɨ', + 'Ƙ' => 'ƙ', + 'Ɯ' => 'ɯ', + 'Ɲ' => 'ɲ', + 'Ɵ' => 'ɵ', + 'Ơ' => 'ơ', + 'Ƣ' => 'ƣ', + 'Ƥ' => 'ƥ', + 'Ʀ' => 'ʀ', + 'Ƨ' => 'ƨ', + 'Ʃ' => 'ʃ', + 'Ƭ' => 'ƭ', + 'Ʈ' => 'ʈ', + 'Ư' => 'ư', + 'Ʊ' => 'ʊ', + 'Ʋ' => 'ʋ', + 'Ƴ' => 'ƴ', + 'Ƶ' => 'ƶ', + 'Ʒ' => 'ʒ', + 'Ƹ' => 'ƹ', + 'Ƽ' => 'ƽ', + 'DŽ' => 'dž', + 'Dž' => 'dž', + 'LJ' => 'lj', + 'Lj' => 'lj', + 'NJ' => 'nj', + 'Nj' => 'nj', + 'Ǎ' => 'ǎ', + 'Ǐ' => 'ǐ', + 'Ǒ' => 'ǒ', + 'Ǔ' => 'ǔ', + 'Ǖ' => 'ǖ', + 'Ǘ' => 'ǘ', + 'Ǚ' => 'ǚ', + 'Ǜ' => 'ǜ', + 'Ǟ' => 'ǟ', + 'Ǡ' => 'ǡ', + 'Ǣ' => 'ǣ', + 'Ǥ' => 'ǥ', + 'Ǧ' => 'ǧ', + 'Ǩ' => 'ǩ', + 'Ǫ' => 'ǫ', + 'Ǭ' => 'ǭ', + 'Ǯ' => 'ǯ', + 'DZ' => 'dz', + 'Dz' => 'dz', + 'Ǵ' => 'ǵ', + 'Ƕ' => 'ƕ', + 'Ƿ' => 'ƿ', + 'Ǹ' => 'ǹ', + 'Ǻ' => 'ǻ', + 'Ǽ' => 'ǽ', + 'Ǿ' => 'ǿ', + 'Ȁ' => 'ȁ', + 'Ȃ' => 'ȃ', + 'Ȅ' => 'ȅ', + 'Ȇ' => 'ȇ', + 'Ȉ' => 'ȉ', + 'Ȋ' => 'ȋ', + 'Ȍ' => 'ȍ', + 'Ȏ' => 'ȏ', + 'Ȑ' => 'ȑ', + 'Ȓ' => 'ȓ', + 'Ȕ' => 'ȕ', + 'Ȗ' => 'ȗ', + 'Ș' => 'ș', + 'Ț' => 'ț', + 'Ȝ' => 'ȝ', + 'Ȟ' => 'ȟ', + 'Ƞ' => 'ƞ', + 'Ȣ' => 'ȣ', + 'Ȥ' => 'ȥ', + 'Ȧ' => 'ȧ', + 'Ȩ' => 'ȩ', + 'Ȫ' => 'ȫ', + 'Ȭ' => 'ȭ', + 'Ȯ' => 'ȯ', + 'Ȱ' => 'ȱ', + 'Ȳ' => 'ȳ', + 'Ⱥ' => 'ⱥ', + 'Ȼ' => 'ȼ', + 'Ƚ' => 'ƚ', + 'Ⱦ' => 'ⱦ', + 'Ɂ' => 'ɂ', + 'Ƀ' => 'ƀ', + 'Ʉ' => 'ʉ', + 'Ʌ' => 'ʌ', + 'Ɇ' => 'ɇ', + 'Ɉ' => 'ɉ', + 'Ɋ' => 'ɋ', + 'Ɍ' => 'ɍ', + 'Ɏ' => 'ɏ', + 'Ͱ' => 'ͱ', + 'Ͳ' => 'ͳ', + 'Ͷ' => 'ͷ', + 'Ϳ' => 'ϳ', + 'Ά' => 'ά', + 'Έ' => 'έ', + 'Ή' => 'ή', + 'Ί' => 'ί', + 'Ό' => 'ό', + 'Ύ' => 'ύ', + 'Ώ' => 'ώ', + 'Α' => 'α', + 'Β' => 'β', + 'Γ' => 'γ', + 'Δ' => 'δ', + 'Ε' => 'ε', + 'Ζ' => 'ζ', + 'Η' => 'η', + 'Θ' => 'θ', + 'Ι' => 'ι', + 'Κ' => 'κ', + 'Λ' => 'λ', + 'Μ' => 'μ', + 'Ν' => 'ν', + 'Ξ' => 'ξ', + 'Ο' => 'ο', + 'Π' => 'π', + 'Ρ' => 'ρ', + 'Σ' => 'σ', + 'Τ' => 'τ', + 'Υ' => 'υ', + 'Φ' => 'φ', + 'Χ' => 'χ', + 'Ψ' => 'ψ', + 'Ω' => 'ω', + 'Ϊ' => 'ϊ', + 'Ϋ' => 'ϋ', + 'Ϗ' => 'ϗ', + 'Ϙ' => 'ϙ', + 'Ϛ' => 'ϛ', + 'Ϝ' => 'ϝ', + 'Ϟ' => 'ϟ', + 'Ϡ' => 'ϡ', + 'Ϣ' => 'ϣ', + 'Ϥ' => 'ϥ', + 'Ϧ' => 'ϧ', + 'Ϩ' => 'ϩ', + 'Ϫ' => 'ϫ', + 'Ϭ' => 'ϭ', + 'Ϯ' => 'ϯ', + 'ϴ' => 'θ', + 'Ϸ' => 'ϸ', + 'Ϲ' => 'ϲ', + 'Ϻ' => 'ϻ', + 'Ͻ' => 'ͻ', + 'Ͼ' => 'ͼ', + 'Ͽ' => 'ͽ', + 'Ѐ' => 'ѐ', + 'Ё' => 'ё', + 'Ђ' => 'ђ', + 'Ѓ' => 'ѓ', + 'Є' => 'є', + 'Ѕ' => 'ѕ', + 'І' => 'і', + 'Ї' => 'ї', + 'Ј' => 'ј', + 'Љ' => 'љ', + 'Њ' => 'њ', + 'Ћ' => 'ћ', + 'Ќ' => 'ќ', + 'Ѝ' => 'ѝ', + 'Ў' => 'ў', + 'Џ' => 'џ', + 'А' => 'а', + 'Б' => 'б', + 'В' => 'в', + 'Г' => 'г', + 'Д' => 'д', + 'Е' => 'е', + 'Ж' => 'ж', + 'З' => 'з', + 'И' => 'и', + 'Й' => 'й', + 'К' => 'к', + 'Л' => 'л', + 'М' => 'м', + 'Н' => 'н', + 'О' => 'о', + 'П' => 'п', + 'Р' => 'р', + 'С' => 'с', + 'Т' => 'т', + 'У' => 'у', + 'Ф' => 'ф', + 'Х' => 'х', + 'Ц' => 'ц', + 'Ч' => 'ч', + 'Ш' => 'ш', + 'Щ' => 'щ', + 'Ъ' => 'ъ', + 'Ы' => 'ы', + 'Ь' => 'ь', + 'Э' => 'э', + 'Ю' => 'ю', + 'Я' => 'я', + 'Ѡ' => 'ѡ', + 'Ѣ' => 'ѣ', + 'Ѥ' => 'ѥ', + 'Ѧ' => 'ѧ', + 'Ѩ' => 'ѩ', + 'Ѫ' => 'ѫ', + 'Ѭ' => 'ѭ', + 'Ѯ' => 'ѯ', + 'Ѱ' => 'ѱ', + 'Ѳ' => 'ѳ', + 'Ѵ' => 'ѵ', + 'Ѷ' => 'ѷ', + 'Ѹ' => 'ѹ', + 'Ѻ' => 'ѻ', + 'Ѽ' => 'ѽ', + 'Ѿ' => 'ѿ', + 'Ҁ' => 'ҁ', + 'Ҋ' => 'ҋ', + 'Ҍ' => 'ҍ', + 'Ҏ' => 'ҏ', + 'Ґ' => 'ґ', + 'Ғ' => 'ғ', + 'Ҕ' => 'ҕ', + 'Җ' => 'җ', + 'Ҙ' => 'ҙ', + 'Қ' => 'қ', + 'Ҝ' => 'ҝ', + 'Ҟ' => 'ҟ', + 'Ҡ' => 'ҡ', + 'Ң' => 'ң', + 'Ҥ' => 'ҥ', + 'Ҧ' => 'ҧ', + 'Ҩ' => 'ҩ', + 'Ҫ' => 'ҫ', + 'Ҭ' => 'ҭ', + 'Ү' => 'ү', + 'Ұ' => 'ұ', + 'Ҳ' => 'ҳ', + 'Ҵ' => 'ҵ', + 'Ҷ' => 'ҷ', + 'Ҹ' => 'ҹ', + 'Һ' => 'һ', + 'Ҽ' => 'ҽ', + 'Ҿ' => 'ҿ', + 'Ӏ' => 'ӏ', + 'Ӂ' => 'ӂ', + 'Ӄ' => 'ӄ', + 'Ӆ' => 'ӆ', + 'Ӈ' => 'ӈ', + 'Ӊ' => 'ӊ', + 'Ӌ' => 'ӌ', + 'Ӎ' => 'ӎ', + 'Ӑ' => 'ӑ', + 'Ӓ' => 'ӓ', + 'Ӕ' => 'ӕ', + 'Ӗ' => 'ӗ', + 'Ә' => 'ә', + 'Ӛ' => 'ӛ', + 'Ӝ' => 'ӝ', + 'Ӟ' => 'ӟ', + 'Ӡ' => 'ӡ', + 'Ӣ' => 'ӣ', + 'Ӥ' => 'ӥ', + 'Ӧ' => 'ӧ', + 'Ө' => 'ө', + 'Ӫ' => 'ӫ', + 'Ӭ' => 'ӭ', + 'Ӯ' => 'ӯ', + 'Ӱ' => 'ӱ', + 'Ӳ' => 'ӳ', + 'Ӵ' => 'ӵ', + 'Ӷ' => 'ӷ', + 'Ӹ' => 'ӹ', + 'Ӻ' => 'ӻ', + 'Ӽ' => 'ӽ', + 'Ӿ' => 'ӿ', + 'Ԁ' => 'ԁ', + 'Ԃ' => 'ԃ', + 'Ԅ' => 'ԅ', + 'Ԇ' => 'ԇ', + 'Ԉ' => 'ԉ', + 'Ԋ' => 'ԋ', + 'Ԍ' => 'ԍ', + 'Ԏ' => 'ԏ', + 'Ԑ' => 'ԑ', + 'Ԓ' => 'ԓ', + 'Ԕ' => 'ԕ', + 'Ԗ' => 'ԗ', + 'Ԙ' => 'ԙ', + 'Ԛ' => 'ԛ', + 'Ԝ' => 'ԝ', + 'Ԟ' => 'ԟ', + 'Ԡ' => 'ԡ', + 'Ԣ' => 'ԣ', + 'Ԥ' => 'ԥ', + 'Ԧ' => 'ԧ', + 'Ԩ' => 'ԩ', + 'Ԫ' => 'ԫ', + 'Ԭ' => 'ԭ', + 'Ԯ' => 'ԯ', + 'Ա' => 'ա', + 'Բ' => 'բ', + 'Գ' => 'գ', + 'Դ' => 'դ', + 'Ե' => 'ե', + 'Զ' => 'զ', + 'Է' => 'է', + 'Ը' => 'ը', + 'Թ' => 'թ', + 'Ժ' => 'ժ', + 'Ի' => 'ի', + 'Լ' => 'լ', + 'Խ' => 'խ', + 'Ծ' => 'ծ', + 'Կ' => 'կ', + 'Հ' => 'հ', + 'Ձ' => 'ձ', + 'Ղ' => 'ղ', + 'Ճ' => 'ճ', + 'Մ' => 'մ', + 'Յ' => 'յ', + 'Ն' => 'ն', + 'Շ' => 'շ', + 'Ո' => 'ո', + 'Չ' => 'չ', + 'Պ' => 'պ', + 'Ջ' => 'ջ', + 'Ռ' => 'ռ', + 'Ս' => 'ս', + 'Վ' => 'վ', + 'Տ' => 'տ', + 'Ր' => 'ր', + 'Ց' => 'ց', + 'Ւ' => 'ւ', + 'Փ' => 'փ', + 'Ք' => 'ք', + 'Օ' => 'օ', + 'Ֆ' => 'ֆ', + 'Ⴀ' => 'ⴀ', + 'Ⴁ' => 'ⴁ', + 'Ⴂ' => 'ⴂ', + 'Ⴃ' => 'ⴃ', + 'Ⴄ' => 'ⴄ', + 'Ⴅ' => 'ⴅ', + 'Ⴆ' => 'ⴆ', + 'Ⴇ' => 'ⴇ', + 'Ⴈ' => 'ⴈ', + 'Ⴉ' => 'ⴉ', + 'Ⴊ' => 'ⴊ', + 'Ⴋ' => 'ⴋ', + 'Ⴌ' => 'ⴌ', + 'Ⴍ' => 'ⴍ', + 'Ⴎ' => 'ⴎ', + 'Ⴏ' => 'ⴏ', + 'Ⴐ' => 'ⴐ', + 'Ⴑ' => 'ⴑ', + 'Ⴒ' => 'ⴒ', + 'Ⴓ' => 'ⴓ', + 'Ⴔ' => 'ⴔ', + 'Ⴕ' => 'ⴕ', + 'Ⴖ' => 'ⴖ', + 'Ⴗ' => 'ⴗ', + 'Ⴘ' => 'ⴘ', + 'Ⴙ' => 'ⴙ', + 'Ⴚ' => 'ⴚ', + 'Ⴛ' => 'ⴛ', + 'Ⴜ' => 'ⴜ', + 'Ⴝ' => 'ⴝ', + 'Ⴞ' => 'ⴞ', + 'Ⴟ' => 'ⴟ', + 'Ⴠ' => 'ⴠ', + 'Ⴡ' => 'ⴡ', + 'Ⴢ' => 'ⴢ', + 'Ⴣ' => 'ⴣ', + 'Ⴤ' => 'ⴤ', + 'Ⴥ' => 'ⴥ', + 'Ⴧ' => 'ⴧ', + 'Ⴭ' => 'ⴭ', + 'Ꭰ' => 'ꭰ', + 'Ꭱ' => 'ꭱ', + 'Ꭲ' => 'ꭲ', + 'Ꭳ' => 'ꭳ', + 'Ꭴ' => 'ꭴ', + 'Ꭵ' => 'ꭵ', + 'Ꭶ' => 'ꭶ', + 'Ꭷ' => 'ꭷ', + 'Ꭸ' => 'ꭸ', + 'Ꭹ' => 'ꭹ', + 'Ꭺ' => 'ꭺ', + 'Ꭻ' => 'ꭻ', + 'Ꭼ' => 'ꭼ', + 'Ꭽ' => 'ꭽ', + 'Ꭾ' => 'ꭾ', + 'Ꭿ' => 'ꭿ', + 'Ꮀ' => 'ꮀ', + 'Ꮁ' => 'ꮁ', + 'Ꮂ' => 'ꮂ', + 'Ꮃ' => 'ꮃ', + 'Ꮄ' => 'ꮄ', + 'Ꮅ' => 'ꮅ', + 'Ꮆ' => 'ꮆ', + 'Ꮇ' => 'ꮇ', + 'Ꮈ' => 'ꮈ', + 'Ꮉ' => 'ꮉ', + 'Ꮊ' => 'ꮊ', + 'Ꮋ' => 'ꮋ', + 'Ꮌ' => 'ꮌ', + 'Ꮍ' => 'ꮍ', + 'Ꮎ' => 'ꮎ', + 'Ꮏ' => 'ꮏ', + 'Ꮐ' => 'ꮐ', + 'Ꮑ' => 'ꮑ', + 'Ꮒ' => 'ꮒ', + 'Ꮓ' => 'ꮓ', + 'Ꮔ' => 'ꮔ', + 'Ꮕ' => 'ꮕ', + 'Ꮖ' => 'ꮖ', + 'Ꮗ' => 'ꮗ', + 'Ꮘ' => 'ꮘ', + 'Ꮙ' => 'ꮙ', + 'Ꮚ' => 'ꮚ', + 'Ꮛ' => 'ꮛ', + 'Ꮜ' => 'ꮜ', + 'Ꮝ' => 'ꮝ', + 'Ꮞ' => 'ꮞ', + 'Ꮟ' => 'ꮟ', + 'Ꮠ' => 'ꮠ', + 'Ꮡ' => 'ꮡ', + 'Ꮢ' => 'ꮢ', + 'Ꮣ' => 'ꮣ', + 'Ꮤ' => 'ꮤ', + 'Ꮥ' => 'ꮥ', + 'Ꮦ' => 'ꮦ', + 'Ꮧ' => 'ꮧ', + 'Ꮨ' => 'ꮨ', + 'Ꮩ' => 'ꮩ', + 'Ꮪ' => 'ꮪ', + 'Ꮫ' => 'ꮫ', + 'Ꮬ' => 'ꮬ', + 'Ꮭ' => 'ꮭ', + 'Ꮮ' => 'ꮮ', + 'Ꮯ' => 'ꮯ', + 'Ꮰ' => 'ꮰ', + 'Ꮱ' => 'ꮱ', + 'Ꮲ' => 'ꮲ', + 'Ꮳ' => 'ꮳ', + 'Ꮴ' => 'ꮴ', + 'Ꮵ' => 'ꮵ', + 'Ꮶ' => 'ꮶ', + 'Ꮷ' => 'ꮷ', + 'Ꮸ' => 'ꮸ', + 'Ꮹ' => 'ꮹ', + 'Ꮺ' => 'ꮺ', + 'Ꮻ' => 'ꮻ', + 'Ꮼ' => 'ꮼ', + 'Ꮽ' => 'ꮽ', + 'Ꮾ' => 'ꮾ', + 'Ꮿ' => 'ꮿ', + 'Ᏸ' => 'ᏸ', + 'Ᏹ' => 'ᏹ', + 'Ᏺ' => 'ᏺ', + 'Ᏻ' => 'ᏻ', + 'Ᏼ' => 'ᏼ', + 'Ᏽ' => 'ᏽ', + 'Ა' => 'ა', + 'Ბ' => 'ბ', + 'Გ' => 'გ', + 'Დ' => 'დ', + 'Ე' => 'ე', + 'Ვ' => 'ვ', + 'Ზ' => 'ზ', + 'Თ' => 'თ', + 'Ი' => 'ი', + 'Კ' => 'კ', + 'Ლ' => 'ლ', + 'Მ' => 'მ', + 'Ნ' => 'ნ', + 'Ო' => 'ო', + 'Პ' => 'პ', + 'Ჟ' => 'ჟ', + 'Რ' => 'რ', + 'Ს' => 'ს', + 'Ტ' => 'ტ', + 'Უ' => 'უ', + 'Ფ' => 'ფ', + 'Ქ' => 'ქ', + 'Ღ' => 'ღ', + 'Ყ' => 'ყ', + 'Შ' => 'შ', + 'Ჩ' => 'ჩ', + 'Ც' => 'ც', + 'Ძ' => 'ძ', + 'Წ' => 'წ', + 'Ჭ' => 'ჭ', + 'Ხ' => 'ხ', + 'Ჯ' => 'ჯ', + 'Ჰ' => 'ჰ', + 'Ჱ' => 'ჱ', + 'Ჲ' => 'ჲ', + 'Ჳ' => 'ჳ', + 'Ჴ' => 'ჴ', + 'Ჵ' => 'ჵ', + 'Ჶ' => 'ჶ', + 'Ჷ' => 'ჷ', + 'Ჸ' => 'ჸ', + 'Ჹ' => 'ჹ', + 'Ჺ' => 'ჺ', + 'Ჽ' => 'ჽ', + 'Ჾ' => 'ჾ', + 'Ჿ' => 'ჿ', + 'Ḁ' => 'ḁ', + 'Ḃ' => 'ḃ', + 'Ḅ' => 'ḅ', + 'Ḇ' => 'ḇ', + 'Ḉ' => 'ḉ', + 'Ḋ' => 'ḋ', + 'Ḍ' => 'ḍ', + 'Ḏ' => 'ḏ', + 'Ḑ' => 'ḑ', + 'Ḓ' => 'ḓ', + 'Ḕ' => 'ḕ', + 'Ḗ' => 'ḗ', + 'Ḙ' => 'ḙ', + 'Ḛ' => 'ḛ', + 'Ḝ' => 'ḝ', + 'Ḟ' => 'ḟ', + 'Ḡ' => 'ḡ', + 'Ḣ' => 'ḣ', + 'Ḥ' => 'ḥ', + 'Ḧ' => 'ḧ', + 'Ḩ' => 'ḩ', + 'Ḫ' => 'ḫ', + 'Ḭ' => 'ḭ', + 'Ḯ' => 'ḯ', + 'Ḱ' => 'ḱ', + 'Ḳ' => 'ḳ', + 'Ḵ' => 'ḵ', + 'Ḷ' => 'ḷ', + 'Ḹ' => 'ḹ', + 'Ḻ' => 'ḻ', + 'Ḽ' => 'ḽ', + 'Ḿ' => 'ḿ', + 'Ṁ' => 'ṁ', + 'Ṃ' => 'ṃ', + 'Ṅ' => 'ṅ', + 'Ṇ' => 'ṇ', + 'Ṉ' => 'ṉ', + 'Ṋ' => 'ṋ', + 'Ṍ' => 'ṍ', + 'Ṏ' => 'ṏ', + 'Ṑ' => 'ṑ', + 'Ṓ' => 'ṓ', + 'Ṕ' => 'ṕ', + 'Ṗ' => 'ṗ', + 'Ṙ' => 'ṙ', + 'Ṛ' => 'ṛ', + 'Ṝ' => 'ṝ', + 'Ṟ' => 'ṟ', + 'Ṡ' => 'ṡ', + 'Ṣ' => 'ṣ', + 'Ṥ' => 'ṥ', + 'Ṧ' => 'ṧ', + 'Ṩ' => 'ṩ', + 'Ṫ' => 'ṫ', + 'Ṭ' => 'ṭ', + 'Ṯ' => 'ṯ', + 'Ṱ' => 'ṱ', + 'Ṳ' => 'ṳ', + 'Ṵ' => 'ṵ', + 'Ṷ' => 'ṷ', + 'Ṹ' => 'ṹ', + 'Ṻ' => 'ṻ', + 'Ṽ' => 'ṽ', + 'Ṿ' => 'ṿ', + 'Ẁ' => 'ẁ', + 'Ẃ' => 'ẃ', + 'Ẅ' => 'ẅ', + 'Ẇ' => 'ẇ', + 'Ẉ' => 'ẉ', + 'Ẋ' => 'ẋ', + 'Ẍ' => 'ẍ', + 'Ẏ' => 'ẏ', + 'Ẑ' => 'ẑ', + 'Ẓ' => 'ẓ', + 'Ẕ' => 'ẕ', + 'ẞ' => 'ß', + 'Ạ' => 'ạ', + 'Ả' => 'ả', + 'Ấ' => 'ấ', + 'Ầ' => 'ầ', + 'Ẩ' => 'ẩ', + 'Ẫ' => 'ẫ', + 'Ậ' => 'ậ', + 'Ắ' => 'ắ', + 'Ằ' => 'ằ', + 'Ẳ' => 'ẳ', + 'Ẵ' => 'ẵ', + 'Ặ' => 'ặ', + 'Ẹ' => 'ẹ', + 'Ẻ' => 'ẻ', + 'Ẽ' => 'ẽ', + 'Ế' => 'ế', + 'Ề' => 'ề', + 'Ể' => 'ể', + 'Ễ' => 'ễ', + 'Ệ' => 'ệ', + 'Ỉ' => 'ỉ', + 'Ị' => 'ị', + 'Ọ' => 'ọ', + 'Ỏ' => 'ỏ', + 'Ố' => 'ố', + 'Ồ' => 'ồ', + 'Ổ' => 'ổ', + 'Ỗ' => 'ỗ', + 'Ộ' => 'ộ', + 'Ớ' => 'ớ', + 'Ờ' => 'ờ', + 'Ở' => 'ở', + 'Ỡ' => 'ỡ', + 'Ợ' => 'ợ', + 'Ụ' => 'ụ', + 'Ủ' => 'ủ', + 'Ứ' => 'ứ', + 'Ừ' => 'ừ', + 'Ử' => 'ử', + 'Ữ' => 'ữ', + 'Ự' => 'ự', + 'Ỳ' => 'ỳ', + 'Ỵ' => 'ỵ', + 'Ỷ' => 'ỷ', + 'Ỹ' => 'ỹ', + 'Ỻ' => 'ỻ', + 'Ỽ' => 'ỽ', + 'Ỿ' => 'ỿ', + 'Ἀ' => 'ἀ', + 'Ἁ' => 'ἁ', + 'Ἂ' => 'ἂ', + 'Ἃ' => 'ἃ', + 'Ἄ' => 'ἄ', + 'Ἅ' => 'ἅ', + 'Ἆ' => 'ἆ', + 'Ἇ' => 'ἇ', + 'Ἐ' => 'ἐ', + 'Ἑ' => 'ἑ', + 'Ἒ' => 'ἒ', + 'Ἓ' => 'ἓ', + 'Ἔ' => 'ἔ', + 'Ἕ' => 'ἕ', + 'Ἠ' => 'ἠ', + 'Ἡ' => 'ἡ', + 'Ἢ' => 'ἢ', + 'Ἣ' => 'ἣ', + 'Ἤ' => 'ἤ', + 'Ἥ' => 'ἥ', + 'Ἦ' => 'ἦ', + 'Ἧ' => 'ἧ', + 'Ἰ' => 'ἰ', + 'Ἱ' => 'ἱ', + 'Ἲ' => 'ἲ', + 'Ἳ' => 'ἳ', + 'Ἴ' => 'ἴ', + 'Ἵ' => 'ἵ', + 'Ἶ' => 'ἶ', + 'Ἷ' => 'ἷ', + 'Ὀ' => 'ὀ', + 'Ὁ' => 'ὁ', + 'Ὂ' => 'ὂ', + 'Ὃ' => 'ὃ', + 'Ὄ' => 'ὄ', + 'Ὅ' => 'ὅ', + 'Ὑ' => 'ὑ', + 'Ὓ' => 'ὓ', + 'Ὕ' => 'ὕ', + 'Ὗ' => 'ὗ', + 'Ὠ' => 'ὠ', + 'Ὡ' => 'ὡ', + 'Ὢ' => 'ὢ', + 'Ὣ' => 'ὣ', + 'Ὤ' => 'ὤ', + 'Ὥ' => 'ὥ', + 'Ὦ' => 'ὦ', + 'Ὧ' => 'ὧ', + 'ᾈ' => 'ᾀ', + 'ᾉ' => 'ᾁ', + 'ᾊ' => 'ᾂ', + 'ᾋ' => 'ᾃ', + 'ᾌ' => 'ᾄ', + 'ᾍ' => 'ᾅ', + 'ᾎ' => 'ᾆ', + 'ᾏ' => 'ᾇ', + 'ᾘ' => 'ᾐ', + 'ᾙ' => 'ᾑ', + 'ᾚ' => 'ᾒ', + 'ᾛ' => 'ᾓ', + 'ᾜ' => 'ᾔ', + 'ᾝ' => 'ᾕ', + 'ᾞ' => 'ᾖ', + 'ᾟ' => 'ᾗ', + 'ᾨ' => 'ᾠ', + 'ᾩ' => 'ᾡ', + 'ᾪ' => 'ᾢ', + 'ᾫ' => 'ᾣ', + 'ᾬ' => 'ᾤ', + 'ᾭ' => 'ᾥ', + 'ᾮ' => 'ᾦ', + 'ᾯ' => 'ᾧ', + 'Ᾰ' => 'ᾰ', + 'Ᾱ' => 'ᾱ', + 'Ὰ' => 'ὰ', + 'Ά' => 'ά', + 'ᾼ' => 'ᾳ', + 'Ὲ' => 'ὲ', + 'Έ' => 'έ', + 'Ὴ' => 'ὴ', + 'Ή' => 'ή', + 'ῌ' => 'ῃ', + 'Ῐ' => 'ῐ', + 'Ῑ' => 'ῑ', + 'Ὶ' => 'ὶ', + 'Ί' => 'ί', + 'Ῠ' => 'ῠ', + 'Ῡ' => 'ῡ', + 'Ὺ' => 'ὺ', + 'Ύ' => 'ύ', + 'Ῥ' => 'ῥ', + 'Ὸ' => 'ὸ', + 'Ό' => 'ό', + 'Ὼ' => 'ὼ', + 'Ώ' => 'ώ', + 'ῼ' => 'ῳ', + 'Ω' => 'ω', + 'K' => 'k', + 'Å' => 'å', + 'Ⅎ' => 'ⅎ', + 'Ⅰ' => 'ⅰ', + 'Ⅱ' => 'ⅱ', + 'Ⅲ' => 'ⅲ', + 'Ⅳ' => 'ⅳ', + 'Ⅴ' => 'ⅴ', + 'Ⅵ' => 'ⅵ', + 'Ⅶ' => 'ⅶ', + 'Ⅷ' => 'ⅷ', + 'Ⅸ' => 'ⅸ', + 'Ⅹ' => 'ⅹ', + 'Ⅺ' => 'ⅺ', + 'Ⅻ' => 'ⅻ', + 'Ⅼ' => 'ⅼ', + 'Ⅽ' => 'ⅽ', + 'Ⅾ' => 'ⅾ', + 'Ⅿ' => 'ⅿ', + 'Ↄ' => 'ↄ', + 'Ⓐ' => 'ⓐ', + 'Ⓑ' => 'ⓑ', + 'Ⓒ' => 'ⓒ', + 'Ⓓ' => 'ⓓ', + 'Ⓔ' => 'ⓔ', + 'Ⓕ' => 'ⓕ', + 'Ⓖ' => 'ⓖ', + 'Ⓗ' => 'ⓗ', + 'Ⓘ' => 'ⓘ', + 'Ⓙ' => 'ⓙ', + 'Ⓚ' => 'ⓚ', + 'Ⓛ' => 'ⓛ', + 'Ⓜ' => 'ⓜ', + 'Ⓝ' => 'ⓝ', + 'Ⓞ' => 'ⓞ', + 'Ⓟ' => 'ⓟ', + 'Ⓠ' => 'ⓠ', + 'Ⓡ' => 'ⓡ', + 'Ⓢ' => 'ⓢ', + 'Ⓣ' => 'ⓣ', + 'Ⓤ' => 'ⓤ', + 'Ⓥ' => 'ⓥ', + 'Ⓦ' => 'ⓦ', + 'Ⓧ' => 'ⓧ', + 'Ⓨ' => 'ⓨ', + 'Ⓩ' => 'ⓩ', + 'Ⰰ' => 'ⰰ', + 'Ⰱ' => 'ⰱ', + 'Ⰲ' => 'ⰲ', + 'Ⰳ' => 'ⰳ', + 'Ⰴ' => 'ⰴ', + 'Ⰵ' => 'ⰵ', + 'Ⰶ' => 'ⰶ', + 'Ⰷ' => 'ⰷ', + 'Ⰸ' => 'ⰸ', + 'Ⰹ' => 'ⰹ', + 'Ⰺ' => 'ⰺ', + 'Ⰻ' => 'ⰻ', + 'Ⰼ' => 'ⰼ', + 'Ⰽ' => 'ⰽ', + 'Ⰾ' => 'ⰾ', + 'Ⰿ' => 'ⰿ', + 'Ⱀ' => 'ⱀ', + 'Ⱁ' => 'ⱁ', + 'Ⱂ' => 'ⱂ', + 'Ⱃ' => 'ⱃ', + 'Ⱄ' => 'ⱄ', + 'Ⱅ' => 'ⱅ', + 'Ⱆ' => 'ⱆ', + 'Ⱇ' => 'ⱇ', + 'Ⱈ' => 'ⱈ', + 'Ⱉ' => 'ⱉ', + 'Ⱊ' => 'ⱊ', + 'Ⱋ' => 'ⱋ', + 'Ⱌ' => 'ⱌ', + 'Ⱍ' => 'ⱍ', + 'Ⱎ' => 'ⱎ', + 'Ⱏ' => 'ⱏ', + 'Ⱐ' => 'ⱐ', + 'Ⱑ' => 'ⱑ', + 'Ⱒ' => 'ⱒ', + 'Ⱓ' => 'ⱓ', + 'Ⱔ' => 'ⱔ', + 'Ⱕ' => 'ⱕ', + 'Ⱖ' => 'ⱖ', + 'Ⱗ' => 'ⱗ', + 'Ⱘ' => 'ⱘ', + 'Ⱙ' => 'ⱙ', + 'Ⱚ' => 'ⱚ', + 'Ⱛ' => 'ⱛ', + 'Ⱜ' => 'ⱜ', + 'Ⱝ' => 'ⱝ', + 'Ⱞ' => 'ⱞ', + 'Ⱡ' => 'ⱡ', + 'Ɫ' => 'ɫ', + 'Ᵽ' => 'ᵽ', + 'Ɽ' => 'ɽ', + 'Ⱨ' => 'ⱨ', + 'Ⱪ' => 'ⱪ', + 'Ⱬ' => 'ⱬ', + 'Ɑ' => 'ɑ', + 'Ɱ' => 'ɱ', + 'Ɐ' => 'ɐ', + 'Ɒ' => 'ɒ', + 'Ⱳ' => 'ⱳ', + 'Ⱶ' => 'ⱶ', + 'Ȿ' => 'ȿ', + 'Ɀ' => 'ɀ', + 'Ⲁ' => 'ⲁ', + 'Ⲃ' => 'ⲃ', + 'Ⲅ' => 'ⲅ', + 'Ⲇ' => 'ⲇ', + 'Ⲉ' => 'ⲉ', + 'Ⲋ' => 'ⲋ', + 'Ⲍ' => 'ⲍ', + 'Ⲏ' => 'ⲏ', + 'Ⲑ' => 'ⲑ', + 'Ⲓ' => 'ⲓ', + 'Ⲕ' => 'ⲕ', + 'Ⲗ' => 'ⲗ', + 'Ⲙ' => 'ⲙ', + 'Ⲛ' => 'ⲛ', + 'Ⲝ' => 'ⲝ', + 'Ⲟ' => 'ⲟ', + 'Ⲡ' => 'ⲡ', + 'Ⲣ' => 'ⲣ', + 'Ⲥ' => 'ⲥ', + 'Ⲧ' => 'ⲧ', + 'Ⲩ' => 'ⲩ', + 'Ⲫ' => 'ⲫ', + 'Ⲭ' => 'ⲭ', + 'Ⲯ' => 'ⲯ', + 'Ⲱ' => 'ⲱ', + 'Ⲳ' => 'ⲳ', + 'Ⲵ' => 'ⲵ', + 'Ⲷ' => 'ⲷ', + 'Ⲹ' => 'ⲹ', + 'Ⲻ' => 'ⲻ', + 'Ⲽ' => 'ⲽ', + 'Ⲿ' => 'ⲿ', + 'Ⳁ' => 'ⳁ', + 'Ⳃ' => 'ⳃ', + 'Ⳅ' => 'ⳅ', + 'Ⳇ' => 'ⳇ', + 'Ⳉ' => 'ⳉ', + 'Ⳋ' => 'ⳋ', + 'Ⳍ' => 'ⳍ', + 'Ⳏ' => 'ⳏ', + 'Ⳑ' => 'ⳑ', + 'Ⳓ' => 'ⳓ', + 'Ⳕ' => 'ⳕ', + 'Ⳗ' => 'ⳗ', + 'Ⳙ' => 'ⳙ', + 'Ⳛ' => 'ⳛ', + 'Ⳝ' => 'ⳝ', + 'Ⳟ' => 'ⳟ', + 'Ⳡ' => 'ⳡ', + 'Ⳣ' => 'ⳣ', + 'Ⳬ' => 'ⳬ', + 'Ⳮ' => 'ⳮ', + 'Ⳳ' => 'ⳳ', + 'Ꙁ' => 'ꙁ', + 'Ꙃ' => 'ꙃ', + 'Ꙅ' => 'ꙅ', + 'Ꙇ' => 'ꙇ', + 'Ꙉ' => 'ꙉ', + 'Ꙋ' => 'ꙋ', + 'Ꙍ' => 'ꙍ', + 'Ꙏ' => 'ꙏ', + 'Ꙑ' => 'ꙑ', + 'Ꙓ' => 'ꙓ', + 'Ꙕ' => 'ꙕ', + 'Ꙗ' => 'ꙗ', + 'Ꙙ' => 'ꙙ', + 'Ꙛ' => 'ꙛ', + 'Ꙝ' => 'ꙝ', + 'Ꙟ' => 'ꙟ', + 'Ꙡ' => 'ꙡ', + 'Ꙣ' => 'ꙣ', + 'Ꙥ' => 'ꙥ', + 'Ꙧ' => 'ꙧ', + 'Ꙩ' => 'ꙩ', + 'Ꙫ' => 'ꙫ', + 'Ꙭ' => 'ꙭ', + 'Ꚁ' => 'ꚁ', + 'Ꚃ' => 'ꚃ', + 'Ꚅ' => 'ꚅ', + 'Ꚇ' => 'ꚇ', + 'Ꚉ' => 'ꚉ', + 'Ꚋ' => 'ꚋ', + 'Ꚍ' => 'ꚍ', + 'Ꚏ' => 'ꚏ', + 'Ꚑ' => 'ꚑ', + 'Ꚓ' => 'ꚓ', + 'Ꚕ' => 'ꚕ', + 'Ꚗ' => 'ꚗ', + 'Ꚙ' => 'ꚙ', + 'Ꚛ' => 'ꚛ', + 'Ꜣ' => 'ꜣ', + 'Ꜥ' => 'ꜥ', + 'Ꜧ' => 'ꜧ', + 'Ꜩ' => 'ꜩ', + 'Ꜫ' => 'ꜫ', + 'Ꜭ' => 'ꜭ', + 'Ꜯ' => 'ꜯ', + 'Ꜳ' => 'ꜳ', + 'Ꜵ' => 'ꜵ', + 'Ꜷ' => 'ꜷ', + 'Ꜹ' => 'ꜹ', + 'Ꜻ' => 'ꜻ', + 'Ꜽ' => 'ꜽ', + 'Ꜿ' => 'ꜿ', + 'Ꝁ' => 'ꝁ', + 'Ꝃ' => 'ꝃ', + 'Ꝅ' => 'ꝅ', + 'Ꝇ' => 'ꝇ', + 'Ꝉ' => 'ꝉ', + 'Ꝋ' => 'ꝋ', + 'Ꝍ' => 'ꝍ', + 'Ꝏ' => 'ꝏ', + 'Ꝑ' => 'ꝑ', + 'Ꝓ' => 'ꝓ', + 'Ꝕ' => 'ꝕ', + 'Ꝗ' => 'ꝗ', + 'Ꝙ' => 'ꝙ', + 'Ꝛ' => 'ꝛ', + 'Ꝝ' => 'ꝝ', + 'Ꝟ' => 'ꝟ', + 'Ꝡ' => 'ꝡ', + 'Ꝣ' => 'ꝣ', + 'Ꝥ' => 'ꝥ', + 'Ꝧ' => 'ꝧ', + 'Ꝩ' => 'ꝩ', + 'Ꝫ' => 'ꝫ', + 'Ꝭ' => 'ꝭ', + 'Ꝯ' => 'ꝯ', + 'Ꝺ' => 'ꝺ', + 'Ꝼ' => 'ꝼ', + 'Ᵹ' => 'ᵹ', + 'Ꝿ' => 'ꝿ', + 'Ꞁ' => 'ꞁ', + 'Ꞃ' => 'ꞃ', + 'Ꞅ' => 'ꞅ', + 'Ꞇ' => 'ꞇ', + 'Ꞌ' => 'ꞌ', + 'Ɥ' => 'ɥ', + 'Ꞑ' => 'ꞑ', + 'Ꞓ' => 'ꞓ', + 'Ꞗ' => 'ꞗ', + 'Ꞙ' => 'ꞙ', + 'Ꞛ' => 'ꞛ', + 'Ꞝ' => 'ꞝ', + 'Ꞟ' => 'ꞟ', + 'Ꞡ' => 'ꞡ', + 'Ꞣ' => 'ꞣ', + 'Ꞥ' => 'ꞥ', + 'Ꞧ' => 'ꞧ', + 'Ꞩ' => 'ꞩ', + 'Ɦ' => 'ɦ', + 'Ɜ' => 'ɜ', + 'Ɡ' => 'ɡ', + 'Ɬ' => 'ɬ', + 'Ɪ' => 'ɪ', + 'Ʞ' => 'ʞ', + 'Ʇ' => 'ʇ', + 'Ʝ' => 'ʝ', + 'Ꭓ' => 'ꭓ', + 'Ꞵ' => 'ꞵ', + 'Ꞷ' => 'ꞷ', + 'Ꞹ' => 'ꞹ', + 'Ꞻ' => 'ꞻ', + 'Ꞽ' => 'ꞽ', + 'Ꞿ' => 'ꞿ', + 'Ꟃ' => 'ꟃ', + 'Ꞔ' => 'ꞔ', + 'Ʂ' => 'ʂ', + 'Ᶎ' => 'ᶎ', + 'Ꟈ' => 'ꟈ', + 'Ꟊ' => 'ꟊ', + 'Ꟶ' => 'ꟶ', + 'A' => 'a', + 'B' => 'b', + 'C' => 'c', + 'D' => 'd', + 'E' => 'e', + 'F' => 'f', + 'G' => 'g', + 'H' => 'h', + 'I' => 'i', + 'J' => 'j', + 'K' => 'k', + 'L' => 'l', + 'M' => 'm', + 'N' => 'n', + 'O' => 'o', + 'P' => 'p', + 'Q' => 'q', + 'R' => 'r', + 'S' => 's', + 'T' => 't', + 'U' => 'u', + 'V' => 'v', + 'W' => 'w', + 'X' => 'x', + 'Y' => 'y', + 'Z' => 'z', + '𐐀' => '𐐨', + '𐐁' => '𐐩', + '𐐂' => '𐐪', + '𐐃' => '𐐫', + '𐐄' => '𐐬', + '𐐅' => '𐐭', + '𐐆' => '𐐮', + '𐐇' => '𐐯', + '𐐈' => '𐐰', + '𐐉' => '𐐱', + '𐐊' => '𐐲', + '𐐋' => '𐐳', + '𐐌' => '𐐴', + '𐐍' => '𐐵', + '𐐎' => '𐐶', + '𐐏' => '𐐷', + '𐐐' => '𐐸', + '𐐑' => '𐐹', + '𐐒' => '𐐺', + '𐐓' => '𐐻', + '𐐔' => '𐐼', + '𐐕' => '𐐽', + '𐐖' => '𐐾', + '𐐗' => '𐐿', + '𐐘' => '𐑀', + '𐐙' => '𐑁', + '𐐚' => '𐑂', + '𐐛' => '𐑃', + '𐐜' => '𐑄', + '𐐝' => '𐑅', + '𐐞' => '𐑆', + '𐐟' => '𐑇', + '𐐠' => '𐑈', + '𐐡' => '𐑉', + '𐐢' => '𐑊', + '𐐣' => '𐑋', + '𐐤' => '𐑌', + '𐐥' => '𐑍', + '𐐦' => '𐑎', + '𐐧' => '𐑏', + '𐒰' => '𐓘', + '𐒱' => '𐓙', + '𐒲' => '𐓚', + '𐒳' => '𐓛', + '𐒴' => '𐓜', + '𐒵' => '𐓝', + '𐒶' => '𐓞', + '𐒷' => '𐓟', + '𐒸' => '𐓠', + '𐒹' => '𐓡', + '𐒺' => '𐓢', + '𐒻' => '𐓣', + '𐒼' => '𐓤', + '𐒽' => '𐓥', + '𐒾' => '𐓦', + '𐒿' => '𐓧', + '𐓀' => '𐓨', + '𐓁' => '𐓩', + '𐓂' => '𐓪', + '𐓃' => '𐓫', + '𐓄' => '𐓬', + '𐓅' => '𐓭', + '𐓆' => '𐓮', + '𐓇' => '𐓯', + '𐓈' => '𐓰', + '𐓉' => '𐓱', + '𐓊' => '𐓲', + '𐓋' => '𐓳', + '𐓌' => '𐓴', + '𐓍' => '𐓵', + '𐓎' => '𐓶', + '𐓏' => '𐓷', + '𐓐' => '𐓸', + '𐓑' => '𐓹', + '𐓒' => '𐓺', + '𐓓' => '𐓻', + '𐲀' => '𐳀', + '𐲁' => '𐳁', + '𐲂' => '𐳂', + '𐲃' => '𐳃', + '𐲄' => '𐳄', + '𐲅' => '𐳅', + '𐲆' => '𐳆', + '𐲇' => '𐳇', + '𐲈' => '𐳈', + '𐲉' => '𐳉', + '𐲊' => '𐳊', + '𐲋' => '𐳋', + '𐲌' => '𐳌', + '𐲍' => '𐳍', + '𐲎' => '𐳎', + '𐲏' => '𐳏', + '𐲐' => '𐳐', + '𐲑' => '𐳑', + '𐲒' => '𐳒', + '𐲓' => '𐳓', + '𐲔' => '𐳔', + '𐲕' => '𐳕', + '𐲖' => '𐳖', + '𐲗' => '𐳗', + '𐲘' => '𐳘', + '𐲙' => '𐳙', + '𐲚' => '𐳚', + '𐲛' => '𐳛', + '𐲜' => '𐳜', + '𐲝' => '𐳝', + '𐲞' => '𐳞', + '𐲟' => '𐳟', + '𐲠' => '𐳠', + '𐲡' => '𐳡', + '𐲢' => '𐳢', + '𐲣' => '𐳣', + '𐲤' => '𐳤', + '𐲥' => '𐳥', + '𐲦' => '𐳦', + '𐲧' => '𐳧', + '𐲨' => '𐳨', + '𐲩' => '𐳩', + '𐲪' => '𐳪', + '𐲫' => '𐳫', + '𐲬' => '𐳬', + '𐲭' => '𐳭', + '𐲮' => '𐳮', + '𐲯' => '𐳯', + '𐲰' => '𐳰', + '𐲱' => '𐳱', + '𐲲' => '𐳲', + '𑢠' => '𑣀', + '𑢡' => '𑣁', + '𑢢' => '𑣂', + '𑢣' => '𑣃', + '𑢤' => '𑣄', + '𑢥' => '𑣅', + '𑢦' => '𑣆', + '𑢧' => '𑣇', + '𑢨' => '𑣈', + '𑢩' => '𑣉', + '𑢪' => '𑣊', + '𑢫' => '𑣋', + '𑢬' => '𑣌', + '𑢭' => '𑣍', + '𑢮' => '𑣎', + '𑢯' => '𑣏', + '𑢰' => '𑣐', + '𑢱' => '𑣑', + '𑢲' => '𑣒', + '𑢳' => '𑣓', + '𑢴' => '𑣔', + '𑢵' => '𑣕', + '𑢶' => '𑣖', + '𑢷' => '𑣗', + '𑢸' => '𑣘', + '𑢹' => '𑣙', + '𑢺' => '𑣚', + '𑢻' => '𑣛', + '𑢼' => '𑣜', + '𑢽' => '𑣝', + '𑢾' => '𑣞', + '𑢿' => '𑣟', + '𖹀' => '𖹠', + '𖹁' => '𖹡', + '𖹂' => '𖹢', + '𖹃' => '𖹣', + '𖹄' => '𖹤', + '𖹅' => '𖹥', + '𖹆' => '𖹦', + '𖹇' => '𖹧', + '𖹈' => '𖹨', + '𖹉' => '𖹩', + '𖹊' => '𖹪', + '𖹋' => '𖹫', + '𖹌' => '𖹬', + '𖹍' => '𖹭', + '𖹎' => '𖹮', + '𖹏' => '𖹯', + '𖹐' => '𖹰', + '𖹑' => '𖹱', + '𖹒' => '𖹲', + '𖹓' => '𖹳', + '𖹔' => '𖹴', + '𖹕' => '𖹵', + '𖹖' => '𖹶', + '𖹗' => '𖹷', + '𖹘' => '𖹸', + '𖹙' => '𖹹', + '𖹚' => '𖹺', + '𖹛' => '𖹻', + '𖹜' => '𖹼', + '𖹝' => '𖹽', + '𖹞' => '𖹾', + '𖹟' => '𖹿', + '𞤀' => '𞤢', + '𞤁' => '𞤣', + '𞤂' => '𞤤', + '𞤃' => '𞤥', + '𞤄' => '𞤦', + '𞤅' => '𞤧', + '𞤆' => '𞤨', + '𞤇' => '𞤩', + '𞤈' => '𞤪', + '𞤉' => '𞤫', + '𞤊' => '𞤬', + '𞤋' => '𞤭', + '𞤌' => '𞤮', + '𞤍' => '𞤯', + '𞤎' => '𞤰', + '𞤏' => '𞤱', + '𞤐' => '𞤲', + '𞤑' => '𞤳', + '𞤒' => '𞤴', + '𞤓' => '𞤵', + '𞤔' => '𞤶', + '𞤕' => '𞤷', + '𞤖' => '𞤸', + '𞤗' => '𞤹', + '𞤘' => '𞤺', + '𞤙' => '𞤻', + '𞤚' => '𞤼', + '𞤛' => '𞤽', + '𞤜' => '𞤾', + '𞤝' => '𞤿', + '𞤞' => '𞥀', + '𞤟' => '𞥁', + '𞤠' => '𞥂', + '𞤡' => '𞥃', +); diff --git a/site/plugins/kirby-twig/vendor/symfony/polyfill-mbstring/Resources/unidata/titleCaseRegexp.php b/site/plugins/kirby-twig/vendor/symfony/polyfill-mbstring/Resources/unidata/titleCaseRegexp.php new file mode 100644 index 0000000..2a8f6e7 --- /dev/null +++ b/site/plugins/kirby-twig/vendor/symfony/polyfill-mbstring/Resources/unidata/titleCaseRegexp.php @@ -0,0 +1,5 @@ + 'A', + 'b' => 'B', + 'c' => 'C', + 'd' => 'D', + 'e' => 'E', + 'f' => 'F', + 'g' => 'G', + 'h' => 'H', + 'i' => 'I', + 'j' => 'J', + 'k' => 'K', + 'l' => 'L', + 'm' => 'M', + 'n' => 'N', + 'o' => 'O', + 'p' => 'P', + 'q' => 'Q', + 'r' => 'R', + 's' => 'S', + 't' => 'T', + 'u' => 'U', + 'v' => 'V', + 'w' => 'W', + 'x' => 'X', + 'y' => 'Y', + 'z' => 'Z', + 'µ' => 'Μ', + 'à' => 'À', + 'á' => 'Á', + 'â' => 'Â', + 'ã' => 'Ã', + 'ä' => 'Ä', + 'å' => 'Å', + 'æ' => 'Æ', + 'ç' => 'Ç', + 'è' => 'È', + 'é' => 'É', + 'ê' => 'Ê', + 'ë' => 'Ë', + 'ì' => 'Ì', + 'í' => 'Í', + 'î' => 'Î', + 'ï' => 'Ï', + 'ð' => 'Ð', + 'ñ' => 'Ñ', + 'ò' => 'Ò', + 'ó' => 'Ó', + 'ô' => 'Ô', + 'õ' => 'Õ', + 'ö' => 'Ö', + 'ø' => 'Ø', + 'ù' => 'Ù', + 'ú' => 'Ú', + 'û' => 'Û', + 'ü' => 'Ü', + 'ý' => 'Ý', + 'þ' => 'Þ', + 'ÿ' => 'Ÿ', + 'ā' => 'Ā', + 'ă' => 'Ă', + 'ą' => 'Ą', + 'ć' => 'Ć', + 'ĉ' => 'Ĉ', + 'ċ' => 'Ċ', + 'č' => 'Č', + 'ď' => 'Ď', + 'đ' => 'Đ', + 'ē' => 'Ē', + 'ĕ' => 'Ĕ', + 'ė' => 'Ė', + 'ę' => 'Ę', + 'ě' => 'Ě', + 'ĝ' => 'Ĝ', + 'ğ' => 'Ğ', + 'ġ' => 'Ġ', + 'ģ' => 'Ģ', + 'ĥ' => 'Ĥ', + 'ħ' => 'Ħ', + 'ĩ' => 'Ĩ', + 'ī' => 'Ī', + 'ĭ' => 'Ĭ', + 'į' => 'Į', + 'ı' => 'I', + 'ij' => 'IJ', + 'ĵ' => 'Ĵ', + 'ķ' => 'Ķ', + 'ĺ' => 'Ĺ', + 'ļ' => 'Ļ', + 'ľ' => 'Ľ', + 'ŀ' => 'Ŀ', + 'ł' => 'Ł', + 'ń' => 'Ń', + 'ņ' => 'Ņ', + 'ň' => 'Ň', + 'ŋ' => 'Ŋ', + 'ō' => 'Ō', + 'ŏ' => 'Ŏ', + 'ő' => 'Ő', + 'œ' => 'Œ', + 'ŕ' => 'Ŕ', + 'ŗ' => 'Ŗ', + 'ř' => 'Ř', + 'ś' => 'Ś', + 'ŝ' => 'Ŝ', + 'ş' => 'Ş', + 'š' => 'Š', + 'ţ' => 'Ţ', + 'ť' => 'Ť', + 'ŧ' => 'Ŧ', + 'ũ' => 'Ũ', + 'ū' => 'Ū', + 'ŭ' => 'Ŭ', + 'ů' => 'Ů', + 'ű' => 'Ű', + 'ų' => 'Ų', + 'ŵ' => 'Ŵ', + 'ŷ' => 'Ŷ', + 'ź' => 'Ź', + 'ż' => 'Ż', + 'ž' => 'Ž', + 'ſ' => 'S', + 'ƀ' => 'Ƀ', + 'ƃ' => 'Ƃ', + 'ƅ' => 'Ƅ', + 'ƈ' => 'Ƈ', + 'ƌ' => 'Ƌ', + 'ƒ' => 'Ƒ', + 'ƕ' => 'Ƕ', + 'ƙ' => 'Ƙ', + 'ƚ' => 'Ƚ', + 'ƞ' => 'Ƞ', + 'ơ' => 'Ơ', + 'ƣ' => 'Ƣ', + 'ƥ' => 'Ƥ', + 'ƨ' => 'Ƨ', + 'ƭ' => 'Ƭ', + 'ư' => 'Ư', + 'ƴ' => 'Ƴ', + 'ƶ' => 'Ƶ', + 'ƹ' => 'Ƹ', + 'ƽ' => 'Ƽ', + 'ƿ' => 'Ƿ', + 'Dž' => 'DŽ', + 'dž' => 'DŽ', + 'Lj' => 'LJ', + 'lj' => 'LJ', + 'Nj' => 'NJ', + 'nj' => 'NJ', + 'ǎ' => 'Ǎ', + 'ǐ' => 'Ǐ', + 'ǒ' => 'Ǒ', + 'ǔ' => 'Ǔ', + 'ǖ' => 'Ǖ', + 'ǘ' => 'Ǘ', + 'ǚ' => 'Ǚ', + 'ǜ' => 'Ǜ', + 'ǝ' => 'Ǝ', + 'ǟ' => 'Ǟ', + 'ǡ' => 'Ǡ', + 'ǣ' => 'Ǣ', + 'ǥ' => 'Ǥ', + 'ǧ' => 'Ǧ', + 'ǩ' => 'Ǩ', + 'ǫ' => 'Ǫ', + 'ǭ' => 'Ǭ', + 'ǯ' => 'Ǯ', + 'Dz' => 'DZ', + 'dz' => 'DZ', + 'ǵ' => 'Ǵ', + 'ǹ' => 'Ǹ', + 'ǻ' => 'Ǻ', + 'ǽ' => 'Ǽ', + 'ǿ' => 'Ǿ', + 'ȁ' => 'Ȁ', + 'ȃ' => 'Ȃ', + 'ȅ' => 'Ȅ', + 'ȇ' => 'Ȇ', + 'ȉ' => 'Ȉ', + 'ȋ' => 'Ȋ', + 'ȍ' => 'Ȍ', + 'ȏ' => 'Ȏ', + 'ȑ' => 'Ȑ', + 'ȓ' => 'Ȓ', + 'ȕ' => 'Ȕ', + 'ȗ' => 'Ȗ', + 'ș' => 'Ș', + 'ț' => 'Ț', + 'ȝ' => 'Ȝ', + 'ȟ' => 'Ȟ', + 'ȣ' => 'Ȣ', + 'ȥ' => 'Ȥ', + 'ȧ' => 'Ȧ', + 'ȩ' => 'Ȩ', + 'ȫ' => 'Ȫ', + 'ȭ' => 'Ȭ', + 'ȯ' => 'Ȯ', + 'ȱ' => 'Ȱ', + 'ȳ' => 'Ȳ', + 'ȼ' => 'Ȼ', + 'ȿ' => 'Ȿ', + 'ɀ' => 'Ɀ', + 'ɂ' => 'Ɂ', + 'ɇ' => 'Ɇ', + 'ɉ' => 'Ɉ', + 'ɋ' => 'Ɋ', + 'ɍ' => 'Ɍ', + 'ɏ' => 'Ɏ', + 'ɐ' => 'Ɐ', + 'ɑ' => 'Ɑ', + 'ɒ' => 'Ɒ', + 'ɓ' => 'Ɓ', + 'ɔ' => 'Ɔ', + 'ɖ' => 'Ɖ', + 'ɗ' => 'Ɗ', + 'ə' => 'Ə', + 'ɛ' => 'Ɛ', + 'ɜ' => 'Ɜ', + 'ɠ' => 'Ɠ', + 'ɡ' => 'Ɡ', + 'ɣ' => 'Ɣ', + 'ɥ' => 'Ɥ', + 'ɦ' => 'Ɦ', + 'ɨ' => 'Ɨ', + 'ɩ' => 'Ɩ', + 'ɪ' => 'Ɪ', + 'ɫ' => 'Ɫ', + 'ɬ' => 'Ɬ', + 'ɯ' => 'Ɯ', + 'ɱ' => 'Ɱ', + 'ɲ' => 'Ɲ', + 'ɵ' => 'Ɵ', + 'ɽ' => 'Ɽ', + 'ʀ' => 'Ʀ', + 'ʂ' => 'Ʂ', + 'ʃ' => 'Ʃ', + 'ʇ' => 'Ʇ', + 'ʈ' => 'Ʈ', + 'ʉ' => 'Ʉ', + 'ʊ' => 'Ʊ', + 'ʋ' => 'Ʋ', + 'ʌ' => 'Ʌ', + 'ʒ' => 'Ʒ', + 'ʝ' => 'Ʝ', + 'ʞ' => 'Ʞ', + 'ͅ' => 'Ι', + 'ͱ' => 'Ͱ', + 'ͳ' => 'Ͳ', + 'ͷ' => 'Ͷ', + 'ͻ' => 'Ͻ', + 'ͼ' => 'Ͼ', + 'ͽ' => 'Ͽ', + 'ά' => 'Ά', + 'έ' => 'Έ', + 'ή' => 'Ή', + 'ί' => 'Ί', + 'α' => 'Α', + 'β' => 'Β', + 'γ' => 'Γ', + 'δ' => 'Δ', + 'ε' => 'Ε', + 'ζ' => 'Ζ', + 'η' => 'Η', + 'θ' => 'Θ', + 'ι' => 'Ι', + 'κ' => 'Κ', + 'λ' => 'Λ', + 'μ' => 'Μ', + 'ν' => 'Ν', + 'ξ' => 'Ξ', + 'ο' => 'Ο', + 'π' => 'Π', + 'ρ' => 'Ρ', + 'ς' => 'Σ', + 'σ' => 'Σ', + 'τ' => 'Τ', + 'υ' => 'Υ', + 'φ' => 'Φ', + 'χ' => 'Χ', + 'ψ' => 'Ψ', + 'ω' => 'Ω', + 'ϊ' => 'Ϊ', + 'ϋ' => 'Ϋ', + 'ό' => 'Ό', + 'ύ' => 'Ύ', + 'ώ' => 'Ώ', + 'ϐ' => 'Β', + 'ϑ' => 'Θ', + 'ϕ' => 'Φ', + 'ϖ' => 'Π', + 'ϗ' => 'Ϗ', + 'ϙ' => 'Ϙ', + 'ϛ' => 'Ϛ', + 'ϝ' => 'Ϝ', + 'ϟ' => 'Ϟ', + 'ϡ' => 'Ϡ', + 'ϣ' => 'Ϣ', + 'ϥ' => 'Ϥ', + 'ϧ' => 'Ϧ', + 'ϩ' => 'Ϩ', + 'ϫ' => 'Ϫ', + 'ϭ' => 'Ϭ', + 'ϯ' => 'Ϯ', + 'ϰ' => 'Κ', + 'ϱ' => 'Ρ', + 'ϲ' => 'Ϲ', + 'ϳ' => 'Ϳ', + 'ϵ' => 'Ε', + 'ϸ' => 'Ϸ', + 'ϻ' => 'Ϻ', + 'а' => 'А', + 'б' => 'Б', + 'в' => 'В', + 'г' => 'Г', + 'д' => 'Д', + 'е' => 'Е', + 'ж' => 'Ж', + 'з' => 'З', + 'и' => 'И', + 'й' => 'Й', + 'к' => 'К', + 'л' => 'Л', + 'м' => 'М', + 'н' => 'Н', + 'о' => 'О', + 'п' => 'П', + 'р' => 'Р', + 'с' => 'С', + 'т' => 'Т', + 'у' => 'У', + 'ф' => 'Ф', + 'х' => 'Х', + 'ц' => 'Ц', + 'ч' => 'Ч', + 'ш' => 'Ш', + 'щ' => 'Щ', + 'ъ' => 'Ъ', + 'ы' => 'Ы', + 'ь' => 'Ь', + 'э' => 'Э', + 'ю' => 'Ю', + 'я' => 'Я', + 'ѐ' => 'Ѐ', + 'ё' => 'Ё', + 'ђ' => 'Ђ', + 'ѓ' => 'Ѓ', + 'є' => 'Є', + 'ѕ' => 'Ѕ', + 'і' => 'І', + 'ї' => 'Ї', + 'ј' => 'Ј', + 'љ' => 'Љ', + 'њ' => 'Њ', + 'ћ' => 'Ћ', + 'ќ' => 'Ќ', + 'ѝ' => 'Ѝ', + 'ў' => 'Ў', + 'џ' => 'Џ', + 'ѡ' => 'Ѡ', + 'ѣ' => 'Ѣ', + 'ѥ' => 'Ѥ', + 'ѧ' => 'Ѧ', + 'ѩ' => 'Ѩ', + 'ѫ' => 'Ѫ', + 'ѭ' => 'Ѭ', + 'ѯ' => 'Ѯ', + 'ѱ' => 'Ѱ', + 'ѳ' => 'Ѳ', + 'ѵ' => 'Ѵ', + 'ѷ' => 'Ѷ', + 'ѹ' => 'Ѹ', + 'ѻ' => 'Ѻ', + 'ѽ' => 'Ѽ', + 'ѿ' => 'Ѿ', + 'ҁ' => 'Ҁ', + 'ҋ' => 'Ҋ', + 'ҍ' => 'Ҍ', + 'ҏ' => 'Ҏ', + 'ґ' => 'Ґ', + 'ғ' => 'Ғ', + 'ҕ' => 'Ҕ', + 'җ' => 'Җ', + 'ҙ' => 'Ҙ', + 'қ' => 'Қ', + 'ҝ' => 'Ҝ', + 'ҟ' => 'Ҟ', + 'ҡ' => 'Ҡ', + 'ң' => 'Ң', + 'ҥ' => 'Ҥ', + 'ҧ' => 'Ҧ', + 'ҩ' => 'Ҩ', + 'ҫ' => 'Ҫ', + 'ҭ' => 'Ҭ', + 'ү' => 'Ү', + 'ұ' => 'Ұ', + 'ҳ' => 'Ҳ', + 'ҵ' => 'Ҵ', + 'ҷ' => 'Ҷ', + 'ҹ' => 'Ҹ', + 'һ' => 'Һ', + 'ҽ' => 'Ҽ', + 'ҿ' => 'Ҿ', + 'ӂ' => 'Ӂ', + 'ӄ' => 'Ӄ', + 'ӆ' => 'Ӆ', + 'ӈ' => 'Ӈ', + 'ӊ' => 'Ӊ', + 'ӌ' => 'Ӌ', + 'ӎ' => 'Ӎ', + 'ӏ' => 'Ӏ', + 'ӑ' => 'Ӑ', + 'ӓ' => 'Ӓ', + 'ӕ' => 'Ӕ', + 'ӗ' => 'Ӗ', + 'ә' => 'Ә', + 'ӛ' => 'Ӛ', + 'ӝ' => 'Ӝ', + 'ӟ' => 'Ӟ', + 'ӡ' => 'Ӡ', + 'ӣ' => 'Ӣ', + 'ӥ' => 'Ӥ', + 'ӧ' => 'Ӧ', + 'ө' => 'Ө', + 'ӫ' => 'Ӫ', + 'ӭ' => 'Ӭ', + 'ӯ' => 'Ӯ', + 'ӱ' => 'Ӱ', + 'ӳ' => 'Ӳ', + 'ӵ' => 'Ӵ', + 'ӷ' => 'Ӷ', + 'ӹ' => 'Ӹ', + 'ӻ' => 'Ӻ', + 'ӽ' => 'Ӽ', + 'ӿ' => 'Ӿ', + 'ԁ' => 'Ԁ', + 'ԃ' => 'Ԃ', + 'ԅ' => 'Ԅ', + 'ԇ' => 'Ԇ', + 'ԉ' => 'Ԉ', + 'ԋ' => 'Ԋ', + 'ԍ' => 'Ԍ', + 'ԏ' => 'Ԏ', + 'ԑ' => 'Ԑ', + 'ԓ' => 'Ԓ', + 'ԕ' => 'Ԕ', + 'ԗ' => 'Ԗ', + 'ԙ' => 'Ԙ', + 'ԛ' => 'Ԛ', + 'ԝ' => 'Ԝ', + 'ԟ' => 'Ԟ', + 'ԡ' => 'Ԡ', + 'ԣ' => 'Ԣ', + 'ԥ' => 'Ԥ', + 'ԧ' => 'Ԧ', + 'ԩ' => 'Ԩ', + 'ԫ' => 'Ԫ', + 'ԭ' => 'Ԭ', + 'ԯ' => 'Ԯ', + 'ա' => 'Ա', + 'բ' => 'Բ', + 'գ' => 'Գ', + 'դ' => 'Դ', + 'ե' => 'Ե', + 'զ' => 'Զ', + 'է' => 'Է', + 'ը' => 'Ը', + 'թ' => 'Թ', + 'ժ' => 'Ժ', + 'ի' => 'Ի', + 'լ' => 'Լ', + 'խ' => 'Խ', + 'ծ' => 'Ծ', + 'կ' => 'Կ', + 'հ' => 'Հ', + 'ձ' => 'Ձ', + 'ղ' => 'Ղ', + 'ճ' => 'Ճ', + 'մ' => 'Մ', + 'յ' => 'Յ', + 'ն' => 'Ն', + 'շ' => 'Շ', + 'ո' => 'Ո', + 'չ' => 'Չ', + 'պ' => 'Պ', + 'ջ' => 'Ջ', + 'ռ' => 'Ռ', + 'ս' => 'Ս', + 'վ' => 'Վ', + 'տ' => 'Տ', + 'ր' => 'Ր', + 'ց' => 'Ց', + 'ւ' => 'Ւ', + 'փ' => 'Փ', + 'ք' => 'Ք', + 'օ' => 'Օ', + 'ֆ' => 'Ֆ', + 'ა' => 'Ა', + 'ბ' => 'Ბ', + 'გ' => 'Გ', + 'დ' => 'Დ', + 'ე' => 'Ე', + 'ვ' => 'Ვ', + 'ზ' => 'Ზ', + 'თ' => 'Თ', + 'ი' => 'Ი', + 'კ' => 'Კ', + 'ლ' => 'Ლ', + 'მ' => 'Მ', + 'ნ' => 'Ნ', + 'ო' => 'Ო', + 'პ' => 'Პ', + 'ჟ' => 'Ჟ', + 'რ' => 'Რ', + 'ს' => 'Ს', + 'ტ' => 'Ტ', + 'უ' => 'Უ', + 'ფ' => 'Ფ', + 'ქ' => 'Ქ', + 'ღ' => 'Ღ', + 'ყ' => 'Ყ', + 'შ' => 'Შ', + 'ჩ' => 'Ჩ', + 'ც' => 'Ც', + 'ძ' => 'Ძ', + 'წ' => 'Წ', + 'ჭ' => 'Ჭ', + 'ხ' => 'Ხ', + 'ჯ' => 'Ჯ', + 'ჰ' => 'Ჰ', + 'ჱ' => 'Ჱ', + 'ჲ' => 'Ჲ', + 'ჳ' => 'Ჳ', + 'ჴ' => 'Ჴ', + 'ჵ' => 'Ჵ', + 'ჶ' => 'Ჶ', + 'ჷ' => 'Ჷ', + 'ჸ' => 'Ჸ', + 'ჹ' => 'Ჹ', + 'ჺ' => 'Ჺ', + 'ჽ' => 'Ჽ', + 'ჾ' => 'Ჾ', + 'ჿ' => 'Ჿ', + 'ᏸ' => 'Ᏸ', + 'ᏹ' => 'Ᏹ', + 'ᏺ' => 'Ᏺ', + 'ᏻ' => 'Ᏻ', + 'ᏼ' => 'Ᏼ', + 'ᏽ' => 'Ᏽ', + 'ᲀ' => 'В', + 'ᲁ' => 'Д', + 'ᲂ' => 'О', + 'ᲃ' => 'С', + 'ᲄ' => 'Т', + 'ᲅ' => 'Т', + 'ᲆ' => 'Ъ', + 'ᲇ' => 'Ѣ', + 'ᲈ' => 'Ꙋ', + 'ᵹ' => 'Ᵹ', + 'ᵽ' => 'Ᵽ', + 'ᶎ' => 'Ᶎ', + 'ḁ' => 'Ḁ', + 'ḃ' => 'Ḃ', + 'ḅ' => 'Ḅ', + 'ḇ' => 'Ḇ', + 'ḉ' => 'Ḉ', + 'ḋ' => 'Ḋ', + 'ḍ' => 'Ḍ', + 'ḏ' => 'Ḏ', + 'ḑ' => 'Ḑ', + 'ḓ' => 'Ḓ', + 'ḕ' => 'Ḕ', + 'ḗ' => 'Ḗ', + 'ḙ' => 'Ḙ', + 'ḛ' => 'Ḛ', + 'ḝ' => 'Ḝ', + 'ḟ' => 'Ḟ', + 'ḡ' => 'Ḡ', + 'ḣ' => 'Ḣ', + 'ḥ' => 'Ḥ', + 'ḧ' => 'Ḧ', + 'ḩ' => 'Ḩ', + 'ḫ' => 'Ḫ', + 'ḭ' => 'Ḭ', + 'ḯ' => 'Ḯ', + 'ḱ' => 'Ḱ', + 'ḳ' => 'Ḳ', + 'ḵ' => 'Ḵ', + 'ḷ' => 'Ḷ', + 'ḹ' => 'Ḹ', + 'ḻ' => 'Ḻ', + 'ḽ' => 'Ḽ', + 'ḿ' => 'Ḿ', + 'ṁ' => 'Ṁ', + 'ṃ' => 'Ṃ', + 'ṅ' => 'Ṅ', + 'ṇ' => 'Ṇ', + 'ṉ' => 'Ṉ', + 'ṋ' => 'Ṋ', + 'ṍ' => 'Ṍ', + 'ṏ' => 'Ṏ', + 'ṑ' => 'Ṑ', + 'ṓ' => 'Ṓ', + 'ṕ' => 'Ṕ', + 'ṗ' => 'Ṗ', + 'ṙ' => 'Ṙ', + 'ṛ' => 'Ṛ', + 'ṝ' => 'Ṝ', + 'ṟ' => 'Ṟ', + 'ṡ' => 'Ṡ', + 'ṣ' => 'Ṣ', + 'ṥ' => 'Ṥ', + 'ṧ' => 'Ṧ', + 'ṩ' => 'Ṩ', + 'ṫ' => 'Ṫ', + 'ṭ' => 'Ṭ', + 'ṯ' => 'Ṯ', + 'ṱ' => 'Ṱ', + 'ṳ' => 'Ṳ', + 'ṵ' => 'Ṵ', + 'ṷ' => 'Ṷ', + 'ṹ' => 'Ṹ', + 'ṻ' => 'Ṻ', + 'ṽ' => 'Ṽ', + 'ṿ' => 'Ṿ', + 'ẁ' => 'Ẁ', + 'ẃ' => 'Ẃ', + 'ẅ' => 'Ẅ', + 'ẇ' => 'Ẇ', + 'ẉ' => 'Ẉ', + 'ẋ' => 'Ẋ', + 'ẍ' => 'Ẍ', + 'ẏ' => 'Ẏ', + 'ẑ' => 'Ẑ', + 'ẓ' => 'Ẓ', + 'ẕ' => 'Ẕ', + 'ẛ' => 'Ṡ', + 'ạ' => 'Ạ', + 'ả' => 'Ả', + 'ấ' => 'Ấ', + 'ầ' => 'Ầ', + 'ẩ' => 'Ẩ', + 'ẫ' => 'Ẫ', + 'ậ' => 'Ậ', + 'ắ' => 'Ắ', + 'ằ' => 'Ằ', + 'ẳ' => 'Ẳ', + 'ẵ' => 'Ẵ', + 'ặ' => 'Ặ', + 'ẹ' => 'Ẹ', + 'ẻ' => 'Ẻ', + 'ẽ' => 'Ẽ', + 'ế' => 'Ế', + 'ề' => 'Ề', + 'ể' => 'Ể', + 'ễ' => 'Ễ', + 'ệ' => 'Ệ', + 'ỉ' => 'Ỉ', + 'ị' => 'Ị', + 'ọ' => 'Ọ', + 'ỏ' => 'Ỏ', + 'ố' => 'Ố', + 'ồ' => 'Ồ', + 'ổ' => 'Ổ', + 'ỗ' => 'Ỗ', + 'ộ' => 'Ộ', + 'ớ' => 'Ớ', + 'ờ' => 'Ờ', + 'ở' => 'Ở', + 'ỡ' => 'Ỡ', + 'ợ' => 'Ợ', + 'ụ' => 'Ụ', + 'ủ' => 'Ủ', + 'ứ' => 'Ứ', + 'ừ' => 'Ừ', + 'ử' => 'Ử', + 'ữ' => 'Ữ', + 'ự' => 'Ự', + 'ỳ' => 'Ỳ', + 'ỵ' => 'Ỵ', + 'ỷ' => 'Ỷ', + 'ỹ' => 'Ỹ', + 'ỻ' => 'Ỻ', + 'ỽ' => 'Ỽ', + 'ỿ' => 'Ỿ', + 'ἀ' => 'Ἀ', + 'ἁ' => 'Ἁ', + 'ἂ' => 'Ἂ', + 'ἃ' => 'Ἃ', + 'ἄ' => 'Ἄ', + 'ἅ' => 'Ἅ', + 'ἆ' => 'Ἆ', + 'ἇ' => 'Ἇ', + 'ἐ' => 'Ἐ', + 'ἑ' => 'Ἑ', + 'ἒ' => 'Ἒ', + 'ἓ' => 'Ἓ', + 'ἔ' => 'Ἔ', + 'ἕ' => 'Ἕ', + 'ἠ' => 'Ἠ', + 'ἡ' => 'Ἡ', + 'ἢ' => 'Ἢ', + 'ἣ' => 'Ἣ', + 'ἤ' => 'Ἤ', + 'ἥ' => 'Ἥ', + 'ἦ' => 'Ἦ', + 'ἧ' => 'Ἧ', + 'ἰ' => 'Ἰ', + 'ἱ' => 'Ἱ', + 'ἲ' => 'Ἲ', + 'ἳ' => 'Ἳ', + 'ἴ' => 'Ἴ', + 'ἵ' => 'Ἵ', + 'ἶ' => 'Ἶ', + 'ἷ' => 'Ἷ', + 'ὀ' => 'Ὀ', + 'ὁ' => 'Ὁ', + 'ὂ' => 'Ὂ', + 'ὃ' => 'Ὃ', + 'ὄ' => 'Ὄ', + 'ὅ' => 'Ὅ', + 'ὑ' => 'Ὑ', + 'ὓ' => 'Ὓ', + 'ὕ' => 'Ὕ', + 'ὗ' => 'Ὗ', + 'ὠ' => 'Ὠ', + 'ὡ' => 'Ὡ', + 'ὢ' => 'Ὢ', + 'ὣ' => 'Ὣ', + 'ὤ' => 'Ὤ', + 'ὥ' => 'Ὥ', + 'ὦ' => 'Ὦ', + 'ὧ' => 'Ὧ', + 'ὰ' => 'Ὰ', + 'ά' => 'Ά', + 'ὲ' => 'Ὲ', + 'έ' => 'Έ', + 'ὴ' => 'Ὴ', + 'ή' => 'Ή', + 'ὶ' => 'Ὶ', + 'ί' => 'Ί', + 'ὸ' => 'Ὸ', + 'ό' => 'Ό', + 'ὺ' => 'Ὺ', + 'ύ' => 'Ύ', + 'ὼ' => 'Ὼ', + 'ώ' => 'Ώ', + 'ᾀ' => 'ᾈ', + 'ᾁ' => 'ᾉ', + 'ᾂ' => 'ᾊ', + 'ᾃ' => 'ᾋ', + 'ᾄ' => 'ᾌ', + 'ᾅ' => 'ᾍ', + 'ᾆ' => 'ᾎ', + 'ᾇ' => 'ᾏ', + 'ᾐ' => 'ᾘ', + 'ᾑ' => 'ᾙ', + 'ᾒ' => 'ᾚ', + 'ᾓ' => 'ᾛ', + 'ᾔ' => 'ᾜ', + 'ᾕ' => 'ᾝ', + 'ᾖ' => 'ᾞ', + 'ᾗ' => 'ᾟ', + 'ᾠ' => 'ᾨ', + 'ᾡ' => 'ᾩ', + 'ᾢ' => 'ᾪ', + 'ᾣ' => 'ᾫ', + 'ᾤ' => 'ᾬ', + 'ᾥ' => 'ᾭ', + 'ᾦ' => 'ᾮ', + 'ᾧ' => 'ᾯ', + 'ᾰ' => 'Ᾰ', + 'ᾱ' => 'Ᾱ', + 'ᾳ' => 'ᾼ', + 'ι' => 'Ι', + 'ῃ' => 'ῌ', + 'ῐ' => 'Ῐ', + 'ῑ' => 'Ῑ', + 'ῠ' => 'Ῠ', + 'ῡ' => 'Ῡ', + 'ῥ' => 'Ῥ', + 'ῳ' => 'ῼ', + 'ⅎ' => 'Ⅎ', + 'ⅰ' => 'Ⅰ', + 'ⅱ' => 'Ⅱ', + 'ⅲ' => 'Ⅲ', + 'ⅳ' => 'Ⅳ', + 'ⅴ' => 'Ⅴ', + 'ⅵ' => 'Ⅵ', + 'ⅶ' => 'Ⅶ', + 'ⅷ' => 'Ⅷ', + 'ⅸ' => 'Ⅸ', + 'ⅹ' => 'Ⅹ', + 'ⅺ' => 'Ⅺ', + 'ⅻ' => 'Ⅻ', + 'ⅼ' => 'Ⅼ', + 'ⅽ' => 'Ⅽ', + 'ⅾ' => 'Ⅾ', + 'ⅿ' => 'Ⅿ', + 'ↄ' => 'Ↄ', + 'ⓐ' => 'Ⓐ', + 'ⓑ' => 'Ⓑ', + 'ⓒ' => 'Ⓒ', + 'ⓓ' => 'Ⓓ', + 'ⓔ' => 'Ⓔ', + 'ⓕ' => 'Ⓕ', + 'ⓖ' => 'Ⓖ', + 'ⓗ' => 'Ⓗ', + 'ⓘ' => 'Ⓘ', + 'ⓙ' => 'Ⓙ', + 'ⓚ' => 'Ⓚ', + 'ⓛ' => 'Ⓛ', + 'ⓜ' => 'Ⓜ', + 'ⓝ' => 'Ⓝ', + 'ⓞ' => 'Ⓞ', + 'ⓟ' => 'Ⓟ', + 'ⓠ' => 'Ⓠ', + 'ⓡ' => 'Ⓡ', + 'ⓢ' => 'Ⓢ', + 'ⓣ' => 'Ⓣ', + 'ⓤ' => 'Ⓤ', + 'ⓥ' => 'Ⓥ', + 'ⓦ' => 'Ⓦ', + 'ⓧ' => 'Ⓧ', + 'ⓨ' => 'Ⓨ', + 'ⓩ' => 'Ⓩ', + 'ⰰ' => 'Ⰰ', + 'ⰱ' => 'Ⰱ', + 'ⰲ' => 'Ⰲ', + 'ⰳ' => 'Ⰳ', + 'ⰴ' => 'Ⰴ', + 'ⰵ' => 'Ⰵ', + 'ⰶ' => 'Ⰶ', + 'ⰷ' => 'Ⰷ', + 'ⰸ' => 'Ⰸ', + 'ⰹ' => 'Ⰹ', + 'ⰺ' => 'Ⰺ', + 'ⰻ' => 'Ⰻ', + 'ⰼ' => 'Ⰼ', + 'ⰽ' => 'Ⰽ', + 'ⰾ' => 'Ⰾ', + 'ⰿ' => 'Ⰿ', + 'ⱀ' => 'Ⱀ', + 'ⱁ' => 'Ⱁ', + 'ⱂ' => 'Ⱂ', + 'ⱃ' => 'Ⱃ', + 'ⱄ' => 'Ⱄ', + 'ⱅ' => 'Ⱅ', + 'ⱆ' => 'Ⱆ', + 'ⱇ' => 'Ⱇ', + 'ⱈ' => 'Ⱈ', + 'ⱉ' => 'Ⱉ', + 'ⱊ' => 'Ⱊ', + 'ⱋ' => 'Ⱋ', + 'ⱌ' => 'Ⱌ', + 'ⱍ' => 'Ⱍ', + 'ⱎ' => 'Ⱎ', + 'ⱏ' => 'Ⱏ', + 'ⱐ' => 'Ⱐ', + 'ⱑ' => 'Ⱑ', + 'ⱒ' => 'Ⱒ', + 'ⱓ' => 'Ⱓ', + 'ⱔ' => 'Ⱔ', + 'ⱕ' => 'Ⱕ', + 'ⱖ' => 'Ⱖ', + 'ⱗ' => 'Ⱗ', + 'ⱘ' => 'Ⱘ', + 'ⱙ' => 'Ⱙ', + 'ⱚ' => 'Ⱚ', + 'ⱛ' => 'Ⱛ', + 'ⱜ' => 'Ⱜ', + 'ⱝ' => 'Ⱝ', + 'ⱞ' => 'Ⱞ', + 'ⱡ' => 'Ⱡ', + 'ⱥ' => 'Ⱥ', + 'ⱦ' => 'Ⱦ', + 'ⱨ' => 'Ⱨ', + 'ⱪ' => 'Ⱪ', + 'ⱬ' => 'Ⱬ', + 'ⱳ' => 'Ⱳ', + 'ⱶ' => 'Ⱶ', + 'ⲁ' => 'Ⲁ', + 'ⲃ' => 'Ⲃ', + 'ⲅ' => 'Ⲅ', + 'ⲇ' => 'Ⲇ', + 'ⲉ' => 'Ⲉ', + 'ⲋ' => 'Ⲋ', + 'ⲍ' => 'Ⲍ', + 'ⲏ' => 'Ⲏ', + 'ⲑ' => 'Ⲑ', + 'ⲓ' => 'Ⲓ', + 'ⲕ' => 'Ⲕ', + 'ⲗ' => 'Ⲗ', + 'ⲙ' => 'Ⲙ', + 'ⲛ' => 'Ⲛ', + 'ⲝ' => 'Ⲝ', + 'ⲟ' => 'Ⲟ', + 'ⲡ' => 'Ⲡ', + 'ⲣ' => 'Ⲣ', + 'ⲥ' => 'Ⲥ', + 'ⲧ' => 'Ⲧ', + 'ⲩ' => 'Ⲩ', + 'ⲫ' => 'Ⲫ', + 'ⲭ' => 'Ⲭ', + 'ⲯ' => 'Ⲯ', + 'ⲱ' => 'Ⲱ', + 'ⲳ' => 'Ⲳ', + 'ⲵ' => 'Ⲵ', + 'ⲷ' => 'Ⲷ', + 'ⲹ' => 'Ⲹ', + 'ⲻ' => 'Ⲻ', + 'ⲽ' => 'Ⲽ', + 'ⲿ' => 'Ⲿ', + 'ⳁ' => 'Ⳁ', + 'ⳃ' => 'Ⳃ', + 'ⳅ' => 'Ⳅ', + 'ⳇ' => 'Ⳇ', + 'ⳉ' => 'Ⳉ', + 'ⳋ' => 'Ⳋ', + 'ⳍ' => 'Ⳍ', + 'ⳏ' => 'Ⳏ', + 'ⳑ' => 'Ⳑ', + 'ⳓ' => 'Ⳓ', + 'ⳕ' => 'Ⳕ', + 'ⳗ' => 'Ⳗ', + 'ⳙ' => 'Ⳙ', + 'ⳛ' => 'Ⳛ', + 'ⳝ' => 'Ⳝ', + 'ⳟ' => 'Ⳟ', + 'ⳡ' => 'Ⳡ', + 'ⳣ' => 'Ⳣ', + 'ⳬ' => 'Ⳬ', + 'ⳮ' => 'Ⳮ', + 'ⳳ' => 'Ⳳ', + 'ⴀ' => 'Ⴀ', + 'ⴁ' => 'Ⴁ', + 'ⴂ' => 'Ⴂ', + 'ⴃ' => 'Ⴃ', + 'ⴄ' => 'Ⴄ', + 'ⴅ' => 'Ⴅ', + 'ⴆ' => 'Ⴆ', + 'ⴇ' => 'Ⴇ', + 'ⴈ' => 'Ⴈ', + 'ⴉ' => 'Ⴉ', + 'ⴊ' => 'Ⴊ', + 'ⴋ' => 'Ⴋ', + 'ⴌ' => 'Ⴌ', + 'ⴍ' => 'Ⴍ', + 'ⴎ' => 'Ⴎ', + 'ⴏ' => 'Ⴏ', + 'ⴐ' => 'Ⴐ', + 'ⴑ' => 'Ⴑ', + 'ⴒ' => 'Ⴒ', + 'ⴓ' => 'Ⴓ', + 'ⴔ' => 'Ⴔ', + 'ⴕ' => 'Ⴕ', + 'ⴖ' => 'Ⴖ', + 'ⴗ' => 'Ⴗ', + 'ⴘ' => 'Ⴘ', + 'ⴙ' => 'Ⴙ', + 'ⴚ' => 'Ⴚ', + 'ⴛ' => 'Ⴛ', + 'ⴜ' => 'Ⴜ', + 'ⴝ' => 'Ⴝ', + 'ⴞ' => 'Ⴞ', + 'ⴟ' => 'Ⴟ', + 'ⴠ' => 'Ⴠ', + 'ⴡ' => 'Ⴡ', + 'ⴢ' => 'Ⴢ', + 'ⴣ' => 'Ⴣ', + 'ⴤ' => 'Ⴤ', + 'ⴥ' => 'Ⴥ', + 'ⴧ' => 'Ⴧ', + 'ⴭ' => 'Ⴭ', + 'ꙁ' => 'Ꙁ', + 'ꙃ' => 'Ꙃ', + 'ꙅ' => 'Ꙅ', + 'ꙇ' => 'Ꙇ', + 'ꙉ' => 'Ꙉ', + 'ꙋ' => 'Ꙋ', + 'ꙍ' => 'Ꙍ', + 'ꙏ' => 'Ꙏ', + 'ꙑ' => 'Ꙑ', + 'ꙓ' => 'Ꙓ', + 'ꙕ' => 'Ꙕ', + 'ꙗ' => 'Ꙗ', + 'ꙙ' => 'Ꙙ', + 'ꙛ' => 'Ꙛ', + 'ꙝ' => 'Ꙝ', + 'ꙟ' => 'Ꙟ', + 'ꙡ' => 'Ꙡ', + 'ꙣ' => 'Ꙣ', + 'ꙥ' => 'Ꙥ', + 'ꙧ' => 'Ꙧ', + 'ꙩ' => 'Ꙩ', + 'ꙫ' => 'Ꙫ', + 'ꙭ' => 'Ꙭ', + 'ꚁ' => 'Ꚁ', + 'ꚃ' => 'Ꚃ', + 'ꚅ' => 'Ꚅ', + 'ꚇ' => 'Ꚇ', + 'ꚉ' => 'Ꚉ', + 'ꚋ' => 'Ꚋ', + 'ꚍ' => 'Ꚍ', + 'ꚏ' => 'Ꚏ', + 'ꚑ' => 'Ꚑ', + 'ꚓ' => 'Ꚓ', + 'ꚕ' => 'Ꚕ', + 'ꚗ' => 'Ꚗ', + 'ꚙ' => 'Ꚙ', + 'ꚛ' => 'Ꚛ', + 'ꜣ' => 'Ꜣ', + 'ꜥ' => 'Ꜥ', + 'ꜧ' => 'Ꜧ', + 'ꜩ' => 'Ꜩ', + 'ꜫ' => 'Ꜫ', + 'ꜭ' => 'Ꜭ', + 'ꜯ' => 'Ꜯ', + 'ꜳ' => 'Ꜳ', + 'ꜵ' => 'Ꜵ', + 'ꜷ' => 'Ꜷ', + 'ꜹ' => 'Ꜹ', + 'ꜻ' => 'Ꜻ', + 'ꜽ' => 'Ꜽ', + 'ꜿ' => 'Ꜿ', + 'ꝁ' => 'Ꝁ', + 'ꝃ' => 'Ꝃ', + 'ꝅ' => 'Ꝅ', + 'ꝇ' => 'Ꝇ', + 'ꝉ' => 'Ꝉ', + 'ꝋ' => 'Ꝋ', + 'ꝍ' => 'Ꝍ', + 'ꝏ' => 'Ꝏ', + 'ꝑ' => 'Ꝑ', + 'ꝓ' => 'Ꝓ', + 'ꝕ' => 'Ꝕ', + 'ꝗ' => 'Ꝗ', + 'ꝙ' => 'Ꝙ', + 'ꝛ' => 'Ꝛ', + 'ꝝ' => 'Ꝝ', + 'ꝟ' => 'Ꝟ', + 'ꝡ' => 'Ꝡ', + 'ꝣ' => 'Ꝣ', + 'ꝥ' => 'Ꝥ', + 'ꝧ' => 'Ꝧ', + 'ꝩ' => 'Ꝩ', + 'ꝫ' => 'Ꝫ', + 'ꝭ' => 'Ꝭ', + 'ꝯ' => 'Ꝯ', + 'ꝺ' => 'Ꝺ', + 'ꝼ' => 'Ꝼ', + 'ꝿ' => 'Ꝿ', + 'ꞁ' => 'Ꞁ', + 'ꞃ' => 'Ꞃ', + 'ꞅ' => 'Ꞅ', + 'ꞇ' => 'Ꞇ', + 'ꞌ' => 'Ꞌ', + 'ꞑ' => 'Ꞑ', + 'ꞓ' => 'Ꞓ', + 'ꞔ' => 'Ꞔ', + 'ꞗ' => 'Ꞗ', + 'ꞙ' => 'Ꞙ', + 'ꞛ' => 'Ꞛ', + 'ꞝ' => 'Ꞝ', + 'ꞟ' => 'Ꞟ', + 'ꞡ' => 'Ꞡ', + 'ꞣ' => 'Ꞣ', + 'ꞥ' => 'Ꞥ', + 'ꞧ' => 'Ꞧ', + 'ꞩ' => 'Ꞩ', + 'ꞵ' => 'Ꞵ', + 'ꞷ' => 'Ꞷ', + 'ꞹ' => 'Ꞹ', + 'ꞻ' => 'Ꞻ', + 'ꞽ' => 'Ꞽ', + 'ꞿ' => 'Ꞿ', + 'ꟃ' => 'Ꟃ', + 'ꟈ' => 'Ꟈ', + 'ꟊ' => 'Ꟊ', + 'ꟶ' => 'Ꟶ', + 'ꭓ' => 'Ꭓ', + 'ꭰ' => 'Ꭰ', + 'ꭱ' => 'Ꭱ', + 'ꭲ' => 'Ꭲ', + 'ꭳ' => 'Ꭳ', + 'ꭴ' => 'Ꭴ', + 'ꭵ' => 'Ꭵ', + 'ꭶ' => 'Ꭶ', + 'ꭷ' => 'Ꭷ', + 'ꭸ' => 'Ꭸ', + 'ꭹ' => 'Ꭹ', + 'ꭺ' => 'Ꭺ', + 'ꭻ' => 'Ꭻ', + 'ꭼ' => 'Ꭼ', + 'ꭽ' => 'Ꭽ', + 'ꭾ' => 'Ꭾ', + 'ꭿ' => 'Ꭿ', + 'ꮀ' => 'Ꮀ', + 'ꮁ' => 'Ꮁ', + 'ꮂ' => 'Ꮂ', + 'ꮃ' => 'Ꮃ', + 'ꮄ' => 'Ꮄ', + 'ꮅ' => 'Ꮅ', + 'ꮆ' => 'Ꮆ', + 'ꮇ' => 'Ꮇ', + 'ꮈ' => 'Ꮈ', + 'ꮉ' => 'Ꮉ', + 'ꮊ' => 'Ꮊ', + 'ꮋ' => 'Ꮋ', + 'ꮌ' => 'Ꮌ', + 'ꮍ' => 'Ꮍ', + 'ꮎ' => 'Ꮎ', + 'ꮏ' => 'Ꮏ', + 'ꮐ' => 'Ꮐ', + 'ꮑ' => 'Ꮑ', + 'ꮒ' => 'Ꮒ', + 'ꮓ' => 'Ꮓ', + 'ꮔ' => 'Ꮔ', + 'ꮕ' => 'Ꮕ', + 'ꮖ' => 'Ꮖ', + 'ꮗ' => 'Ꮗ', + 'ꮘ' => 'Ꮘ', + 'ꮙ' => 'Ꮙ', + 'ꮚ' => 'Ꮚ', + 'ꮛ' => 'Ꮛ', + 'ꮜ' => 'Ꮜ', + 'ꮝ' => 'Ꮝ', + 'ꮞ' => 'Ꮞ', + 'ꮟ' => 'Ꮟ', + 'ꮠ' => 'Ꮠ', + 'ꮡ' => 'Ꮡ', + 'ꮢ' => 'Ꮢ', + 'ꮣ' => 'Ꮣ', + 'ꮤ' => 'Ꮤ', + 'ꮥ' => 'Ꮥ', + 'ꮦ' => 'Ꮦ', + 'ꮧ' => 'Ꮧ', + 'ꮨ' => 'Ꮨ', + 'ꮩ' => 'Ꮩ', + 'ꮪ' => 'Ꮪ', + 'ꮫ' => 'Ꮫ', + 'ꮬ' => 'Ꮬ', + 'ꮭ' => 'Ꮭ', + 'ꮮ' => 'Ꮮ', + 'ꮯ' => 'Ꮯ', + 'ꮰ' => 'Ꮰ', + 'ꮱ' => 'Ꮱ', + 'ꮲ' => 'Ꮲ', + 'ꮳ' => 'Ꮳ', + 'ꮴ' => 'Ꮴ', + 'ꮵ' => 'Ꮵ', + 'ꮶ' => 'Ꮶ', + 'ꮷ' => 'Ꮷ', + 'ꮸ' => 'Ꮸ', + 'ꮹ' => 'Ꮹ', + 'ꮺ' => 'Ꮺ', + 'ꮻ' => 'Ꮻ', + 'ꮼ' => 'Ꮼ', + 'ꮽ' => 'Ꮽ', + 'ꮾ' => 'Ꮾ', + 'ꮿ' => 'Ꮿ', + 'a' => 'A', + 'b' => 'B', + 'c' => 'C', + 'd' => 'D', + 'e' => 'E', + 'f' => 'F', + 'g' => 'G', + 'h' => 'H', + 'i' => 'I', + 'j' => 'J', + 'k' => 'K', + 'l' => 'L', + 'm' => 'M', + 'n' => 'N', + 'o' => 'O', + 'p' => 'P', + 'q' => 'Q', + 'r' => 'R', + 's' => 'S', + 't' => 'T', + 'u' => 'U', + 'v' => 'V', + 'w' => 'W', + 'x' => 'X', + 'y' => 'Y', + 'z' => 'Z', + '𐐨' => '𐐀', + '𐐩' => '𐐁', + '𐐪' => '𐐂', + '𐐫' => '𐐃', + '𐐬' => '𐐄', + '𐐭' => '𐐅', + '𐐮' => '𐐆', + '𐐯' => '𐐇', + '𐐰' => '𐐈', + '𐐱' => '𐐉', + '𐐲' => '𐐊', + '𐐳' => '𐐋', + '𐐴' => '𐐌', + '𐐵' => '𐐍', + '𐐶' => '𐐎', + '𐐷' => '𐐏', + '𐐸' => '𐐐', + '𐐹' => '𐐑', + '𐐺' => '𐐒', + '𐐻' => '𐐓', + '𐐼' => '𐐔', + '𐐽' => '𐐕', + '𐐾' => '𐐖', + '𐐿' => '𐐗', + '𐑀' => '𐐘', + '𐑁' => '𐐙', + '𐑂' => '𐐚', + '𐑃' => '𐐛', + '𐑄' => '𐐜', + '𐑅' => '𐐝', + '𐑆' => '𐐞', + '𐑇' => '𐐟', + '𐑈' => '𐐠', + '𐑉' => '𐐡', + '𐑊' => '𐐢', + '𐑋' => '𐐣', + '𐑌' => '𐐤', + '𐑍' => '𐐥', + '𐑎' => '𐐦', + '𐑏' => '𐐧', + '𐓘' => '𐒰', + '𐓙' => '𐒱', + '𐓚' => '𐒲', + '𐓛' => '𐒳', + '𐓜' => '𐒴', + '𐓝' => '𐒵', + '𐓞' => '𐒶', + '𐓟' => '𐒷', + '𐓠' => '𐒸', + '𐓡' => '𐒹', + '𐓢' => '𐒺', + '𐓣' => '𐒻', + '𐓤' => '𐒼', + '𐓥' => '𐒽', + '𐓦' => '𐒾', + '𐓧' => '𐒿', + '𐓨' => '𐓀', + '𐓩' => '𐓁', + '𐓪' => '𐓂', + '𐓫' => '𐓃', + '𐓬' => '𐓄', + '𐓭' => '𐓅', + '𐓮' => '𐓆', + '𐓯' => '𐓇', + '𐓰' => '𐓈', + '𐓱' => '𐓉', + '𐓲' => '𐓊', + '𐓳' => '𐓋', + '𐓴' => '𐓌', + '𐓵' => '𐓍', + '𐓶' => '𐓎', + '𐓷' => '𐓏', + '𐓸' => '𐓐', + '𐓹' => '𐓑', + '𐓺' => '𐓒', + '𐓻' => '𐓓', + '𐳀' => '𐲀', + '𐳁' => '𐲁', + '𐳂' => '𐲂', + '𐳃' => '𐲃', + '𐳄' => '𐲄', + '𐳅' => '𐲅', + '𐳆' => '𐲆', + '𐳇' => '𐲇', + '𐳈' => '𐲈', + '𐳉' => '𐲉', + '𐳊' => '𐲊', + '𐳋' => '𐲋', + '𐳌' => '𐲌', + '𐳍' => '𐲍', + '𐳎' => '𐲎', + '𐳏' => '𐲏', + '𐳐' => '𐲐', + '𐳑' => '𐲑', + '𐳒' => '𐲒', + '𐳓' => '𐲓', + '𐳔' => '𐲔', + '𐳕' => '𐲕', + '𐳖' => '𐲖', + '𐳗' => '𐲗', + '𐳘' => '𐲘', + '𐳙' => '𐲙', + '𐳚' => '𐲚', + '𐳛' => '𐲛', + '𐳜' => '𐲜', + '𐳝' => '𐲝', + '𐳞' => '𐲞', + '𐳟' => '𐲟', + '𐳠' => '𐲠', + '𐳡' => '𐲡', + '𐳢' => '𐲢', + '𐳣' => '𐲣', + '𐳤' => '𐲤', + '𐳥' => '𐲥', + '𐳦' => '𐲦', + '𐳧' => '𐲧', + '𐳨' => '𐲨', + '𐳩' => '𐲩', + '𐳪' => '𐲪', + '𐳫' => '𐲫', + '𐳬' => '𐲬', + '𐳭' => '𐲭', + '𐳮' => '𐲮', + '𐳯' => '𐲯', + '𐳰' => '𐲰', + '𐳱' => '𐲱', + '𐳲' => '𐲲', + '𑣀' => '𑢠', + '𑣁' => '𑢡', + '𑣂' => '𑢢', + '𑣃' => '𑢣', + '𑣄' => '𑢤', + '𑣅' => '𑢥', + '𑣆' => '𑢦', + '𑣇' => '𑢧', + '𑣈' => '𑢨', + '𑣉' => '𑢩', + '𑣊' => '𑢪', + '𑣋' => '𑢫', + '𑣌' => '𑢬', + '𑣍' => '𑢭', + '𑣎' => '𑢮', + '𑣏' => '𑢯', + '𑣐' => '𑢰', + '𑣑' => '𑢱', + '𑣒' => '𑢲', + '𑣓' => '𑢳', + '𑣔' => '𑢴', + '𑣕' => '𑢵', + '𑣖' => '𑢶', + '𑣗' => '𑢷', + '𑣘' => '𑢸', + '𑣙' => '𑢹', + '𑣚' => '𑢺', + '𑣛' => '𑢻', + '𑣜' => '𑢼', + '𑣝' => '𑢽', + '𑣞' => '𑢾', + '𑣟' => '𑢿', + '𖹠' => '𖹀', + '𖹡' => '𖹁', + '𖹢' => '𖹂', + '𖹣' => '𖹃', + '𖹤' => '𖹄', + '𖹥' => '𖹅', + '𖹦' => '𖹆', + '𖹧' => '𖹇', + '𖹨' => '𖹈', + '𖹩' => '𖹉', + '𖹪' => '𖹊', + '𖹫' => '𖹋', + '𖹬' => '𖹌', + '𖹭' => '𖹍', + '𖹮' => '𖹎', + '𖹯' => '𖹏', + '𖹰' => '𖹐', + '𖹱' => '𖹑', + '𖹲' => '𖹒', + '𖹳' => '𖹓', + '𖹴' => '𖹔', + '𖹵' => '𖹕', + '𖹶' => '𖹖', + '𖹷' => '𖹗', + '𖹸' => '𖹘', + '𖹹' => '𖹙', + '𖹺' => '𖹚', + '𖹻' => '𖹛', + '𖹼' => '𖹜', + '𖹽' => '𖹝', + '𖹾' => '𖹞', + '𖹿' => '𖹟', + '𞤢' => '𞤀', + '𞤣' => '𞤁', + '𞤤' => '𞤂', + '𞤥' => '𞤃', + '𞤦' => '𞤄', + '𞤧' => '𞤅', + '𞤨' => '𞤆', + '𞤩' => '𞤇', + '𞤪' => '𞤈', + '𞤫' => '𞤉', + '𞤬' => '𞤊', + '𞤭' => '𞤋', + '𞤮' => '𞤌', + '𞤯' => '𞤍', + '𞤰' => '𞤎', + '𞤱' => '𞤏', + '𞤲' => '𞤐', + '𞤳' => '𞤑', + '𞤴' => '𞤒', + '𞤵' => '𞤓', + '𞤶' => '𞤔', + '𞤷' => '𞤕', + '𞤸' => '𞤖', + '𞤹' => '𞤗', + '𞤺' => '𞤘', + '𞤻' => '𞤙', + '𞤼' => '𞤚', + '𞤽' => '𞤛', + '𞤾' => '𞤜', + '𞤿' => '𞤝', + '𞥀' => '𞤞', + '𞥁' => '𞤟', + '𞥂' => '𞤠', + '𞥃' => '𞤡', +); diff --git a/site/plugins/kirby-twig/vendor/symfony/polyfill-mbstring/bootstrap.php b/site/plugins/kirby-twig/vendor/symfony/polyfill-mbstring/bootstrap.php new file mode 100644 index 0000000..c45624c --- /dev/null +++ b/site/plugins/kirby-twig/vendor/symfony/polyfill-mbstring/bootstrap.php @@ -0,0 +1,147 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Symfony\Polyfill\Mbstring as p; + +if (\PHP_VERSION_ID >= 80000) { + return require __DIR__.'/bootstrap80.php'; +} + +if (!function_exists('mb_convert_encoding')) { + function mb_convert_encoding($string, $to_encoding, $from_encoding = null) { return p\Mbstring::mb_convert_encoding($string, $to_encoding, $from_encoding); } +} +if (!function_exists('mb_decode_mimeheader')) { + function mb_decode_mimeheader($string) { return p\Mbstring::mb_decode_mimeheader($string); } +} +if (!function_exists('mb_encode_mimeheader')) { + function mb_encode_mimeheader($string, $charset = null, $transfer_encoding = null, $newline = "\r\n", $indent = 0) { return p\Mbstring::mb_encode_mimeheader($string, $charset, $transfer_encoding, $newline, $indent); } +} +if (!function_exists('mb_decode_numericentity')) { + function mb_decode_numericentity($string, $map, $encoding = null) { return p\Mbstring::mb_decode_numericentity($string, $map, $encoding); } +} +if (!function_exists('mb_encode_numericentity')) { + function mb_encode_numericentity($string, $map, $encoding = null, $hex = false) { return p\Mbstring::mb_encode_numericentity($string, $map, $encoding, $hex); } +} +if (!function_exists('mb_convert_case')) { + function mb_convert_case($string, $mode, $encoding = null) { return p\Mbstring::mb_convert_case($string, $mode, $encoding); } +} +if (!function_exists('mb_internal_encoding')) { + function mb_internal_encoding($encoding = null) { return p\Mbstring::mb_internal_encoding($encoding); } +} +if (!function_exists('mb_language')) { + function mb_language($language = null) { return p\Mbstring::mb_language($language); } +} +if (!function_exists('mb_list_encodings')) { + function mb_list_encodings() { return p\Mbstring::mb_list_encodings(); } +} +if (!function_exists('mb_encoding_aliases')) { + function mb_encoding_aliases($encoding) { return p\Mbstring::mb_encoding_aliases($encoding); } +} +if (!function_exists('mb_check_encoding')) { + function mb_check_encoding($value = null, $encoding = null) { return p\Mbstring::mb_check_encoding($value, $encoding); } +} +if (!function_exists('mb_detect_encoding')) { + function mb_detect_encoding($string, $encodings = null, $strict = false) { return p\Mbstring::mb_detect_encoding($string, $encodings, $strict); } +} +if (!function_exists('mb_detect_order')) { + function mb_detect_order($encoding = null) { return p\Mbstring::mb_detect_order($encoding); } +} +if (!function_exists('mb_parse_str')) { + function mb_parse_str($string, &$result = []) { parse_str($string, $result); } +} +if (!function_exists('mb_strlen')) { + function mb_strlen($string, $encoding = null) { return p\Mbstring::mb_strlen($string, $encoding); } +} +if (!function_exists('mb_strpos')) { + function mb_strpos($haystack, $needle, $offset = 0, $encoding = null) { return p\Mbstring::mb_strpos($haystack, $needle, $offset, $encoding); } +} +if (!function_exists('mb_strtolower')) { + function mb_strtolower($string, $encoding = null) { return p\Mbstring::mb_strtolower($string, $encoding); } +} +if (!function_exists('mb_strtoupper')) { + function mb_strtoupper($string, $encoding = null) { return p\Mbstring::mb_strtoupper($string, $encoding); } +} +if (!function_exists('mb_substitute_character')) { + function mb_substitute_character($substitute_character = null) { return p\Mbstring::mb_substitute_character($substitute_character); } +} +if (!function_exists('mb_substr')) { + function mb_substr($string, $start, $length = 2147483647, $encoding = null) { return p\Mbstring::mb_substr($string, $start, $length, $encoding); } +} +if (!function_exists('mb_stripos')) { + function mb_stripos($haystack, $needle, $offset = 0, $encoding = null) { return p\Mbstring::mb_stripos($haystack, $needle, $offset, $encoding); } +} +if (!function_exists('mb_stristr')) { + function mb_stristr($haystack, $needle, $before_needle = false, $encoding = null) { return p\Mbstring::mb_stristr($haystack, $needle, $before_needle, $encoding); } +} +if (!function_exists('mb_strrchr')) { + function mb_strrchr($haystack, $needle, $before_needle = false, $encoding = null) { return p\Mbstring::mb_strrchr($haystack, $needle, $before_needle, $encoding); } +} +if (!function_exists('mb_strrichr')) { + function mb_strrichr($haystack, $needle, $before_needle = false, $encoding = null) { return p\Mbstring::mb_strrichr($haystack, $needle, $before_needle, $encoding); } +} +if (!function_exists('mb_strripos')) { + function mb_strripos($haystack, $needle, $offset = 0, $encoding = null) { return p\Mbstring::mb_strripos($haystack, $needle, $offset, $encoding); } +} +if (!function_exists('mb_strrpos')) { + function mb_strrpos($haystack, $needle, $offset = 0, $encoding = null) { return p\Mbstring::mb_strrpos($haystack, $needle, $offset, $encoding); } +} +if (!function_exists('mb_strstr')) { + function mb_strstr($haystack, $needle, $before_needle = false, $encoding = null) { return p\Mbstring::mb_strstr($haystack, $needle, $before_needle, $encoding); } +} +if (!function_exists('mb_get_info')) { + function mb_get_info($type = 'all') { return p\Mbstring::mb_get_info($type); } +} +if (!function_exists('mb_http_output')) { + function mb_http_output($encoding = null) { return p\Mbstring::mb_http_output($encoding); } +} +if (!function_exists('mb_strwidth')) { + function mb_strwidth($string, $encoding = null) { return p\Mbstring::mb_strwidth($string, $encoding); } +} +if (!function_exists('mb_substr_count')) { + function mb_substr_count($haystack, $needle, $encoding = null) { return p\Mbstring::mb_substr_count($haystack, $needle, $encoding); } +} +if (!function_exists('mb_output_handler')) { + function mb_output_handler($string, $status) { return p\Mbstring::mb_output_handler($string, $status); } +} +if (!function_exists('mb_http_input')) { + function mb_http_input($type = null) { return p\Mbstring::mb_http_input($type); } +} + +if (!function_exists('mb_convert_variables')) { + function mb_convert_variables($to_encoding, $from_encoding, &...$vars) { return p\Mbstring::mb_convert_variables($to_encoding, $from_encoding, ...$vars); } +} + +if (!function_exists('mb_ord')) { + function mb_ord($string, $encoding = null) { return p\Mbstring::mb_ord($string, $encoding); } +} +if (!function_exists('mb_chr')) { + function mb_chr($codepoint, $encoding = null) { return p\Mbstring::mb_chr($codepoint, $encoding); } +} +if (!function_exists('mb_scrub')) { + function mb_scrub($string, $encoding = null) { $encoding = null === $encoding ? mb_internal_encoding() : $encoding; return mb_convert_encoding($string, $encoding, $encoding); } +} +if (!function_exists('mb_str_split')) { + function mb_str_split($string, $length = 1, $encoding = null) { return p\Mbstring::mb_str_split($string, $length, $encoding); } +} + +if (extension_loaded('mbstring')) { + return; +} + +if (!defined('MB_CASE_UPPER')) { + define('MB_CASE_UPPER', 0); +} +if (!defined('MB_CASE_LOWER')) { + define('MB_CASE_LOWER', 1); +} +if (!defined('MB_CASE_TITLE')) { + define('MB_CASE_TITLE', 2); +} diff --git a/site/plugins/kirby-twig/vendor/symfony/polyfill-mbstring/bootstrap80.php b/site/plugins/kirby-twig/vendor/symfony/polyfill-mbstring/bootstrap80.php new file mode 100644 index 0000000..f404f5f --- /dev/null +++ b/site/plugins/kirby-twig/vendor/symfony/polyfill-mbstring/bootstrap80.php @@ -0,0 +1,143 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Symfony\Polyfill\Mbstring as p; + +if (!function_exists('mb_convert_encoding')) { + function mb_convert_encoding(array|string|null $string, ?string $to_encoding, array|string|null $from_encoding = null): array|string|false { return p\Mbstring::mb_convert_encoding($string ?? '', (string) $to_encoding, $from_encoding); } +} +if (!function_exists('mb_decode_mimeheader')) { + function mb_decode_mimeheader(?string $string): string { return p\Mbstring::mb_decode_mimeheader((string) $string); } +} +if (!function_exists('mb_encode_mimeheader')) { + function mb_encode_mimeheader(?string $string, ?string $charset = null, ?string $transfer_encoding = null, ?string $newline = "\r\n", ?int $indent = 0): string { return p\Mbstring::mb_encode_mimeheader((string) $string, $charset, $transfer_encoding, (string) $newline, (int) $indent); } +} +if (!function_exists('mb_decode_numericentity')) { + function mb_decode_numericentity(?string $string, array $map, ?string $encoding = null): string { return p\Mbstring::mb_decode_numericentity((string) $string, $map, $encoding); } +} +if (!function_exists('mb_encode_numericentity')) { + function mb_encode_numericentity(?string $string, array $map, ?string $encoding = null, ?bool $hex = false): string { return p\Mbstring::mb_encode_numericentity((string) $string, $map, $encoding, (bool) $hex); } +} +if (!function_exists('mb_convert_case')) { + function mb_convert_case(?string $string, ?int $mode, ?string $encoding = null): string { return p\Mbstring::mb_convert_case((string) $string, (int) $mode, $encoding); } +} +if (!function_exists('mb_internal_encoding')) { + function mb_internal_encoding(?string $encoding = null): string|bool { return p\Mbstring::mb_internal_encoding($encoding); } +} +if (!function_exists('mb_language')) { + function mb_language(?string $language = null): string|bool { return p\Mbstring::mb_language($language); } +} +if (!function_exists('mb_list_encodings')) { + function mb_list_encodings(): array { return p\Mbstring::mb_list_encodings(); } +} +if (!function_exists('mb_encoding_aliases')) { + function mb_encoding_aliases(?string $encoding): array { return p\Mbstring::mb_encoding_aliases((string) $encoding); } +} +if (!function_exists('mb_check_encoding')) { + function mb_check_encoding(array|string|null $value = null, ?string $encoding = null): bool { return p\Mbstring::mb_check_encoding($value, $encoding); } +} +if (!function_exists('mb_detect_encoding')) { + function mb_detect_encoding(?string $string, array|string|null $encodings = null, ?bool $strict = false): string|false { return p\Mbstring::mb_detect_encoding((string) $string, $encodings, (bool) $strict); } +} +if (!function_exists('mb_detect_order')) { + function mb_detect_order(array|string|null $encoding = null): array|bool { return p\Mbstring::mb_detect_order((string) $encoding); } +} +if (!function_exists('mb_parse_str')) { + function mb_parse_str(?string $string, &$result = []): bool { parse_str((string) $string, $result); } +} +if (!function_exists('mb_strlen')) { + function mb_strlen(?string $string, ?string $encoding = null): int { return p\Mbstring::mb_strlen((string) $string, $encoding); } +} +if (!function_exists('mb_strpos')) { + function mb_strpos(?string $haystack, ?string $needle, ?int $offset = 0, ?string $encoding = null): int|false { return p\Mbstring::mb_strpos((string) $haystack, (string) $needle, (int) $offset, $encoding); } +} +if (!function_exists('mb_strtolower')) { + function mb_strtolower(?string $string, ?string $encoding = null): string { return p\Mbstring::mb_strtolower((string) $string, $encoding); } +} +if (!function_exists('mb_strtoupper')) { + function mb_strtoupper(?string $string, ?string $encoding = null): string { return p\Mbstring::mb_strtoupper((string) $string, $encoding); } +} +if (!function_exists('mb_substitute_character')) { + function mb_substitute_character(string|int|null $substitute_character = null): string|int|bool { return p\Mbstring::mb_substitute_character($substitute_character); } +} +if (!function_exists('mb_substr')) { + function mb_substr(?string $string, ?int $start, ?int $length = null, ?string $encoding = null): string { return p\Mbstring::mb_substr((string) $string, (int) $start, $length, $encoding); } +} +if (!function_exists('mb_stripos')) { + function mb_stripos(?string $haystack, ?string $needle, ?int $offset = 0, ?string $encoding = null): int|false { return p\Mbstring::mb_stripos((string) $haystack, (string) $needle, (int) $offset, $encoding); } +} +if (!function_exists('mb_stristr')) { + function mb_stristr(?string $haystack, ?string $needle, ?bool $before_needle = false, ?string $encoding = null): string|false { return p\Mbstring::mb_stristr((string) $haystack, (string) $needle, (bool) $before_needle, $encoding); } +} +if (!function_exists('mb_strrchr')) { + function mb_strrchr(?string $haystack, ?string $needle, ?bool $before_needle = false, ?string $encoding = null): string|false { return p\Mbstring::mb_strrchr((string) $haystack, (string) $needle, $before_needle, (bool) $encoding); } +} +if (!function_exists('mb_strrichr')) { + function mb_strrichr(?string $haystack, ?string $needle, ?bool $before_needle = false, ?string $encoding = null): string|false { return p\Mbstring::mb_strrichr((string) $haystack, (string) $needle, (bool) $before_needle, $encoding); } +} +if (!function_exists('mb_strripos')) { + function mb_strripos(?string $haystack, ?string $needle, ?int $offset = 0, ?string $encoding = null): int|false { return p\Mbstring::mb_strripos((string) $haystack, (string) $needle, (int) $offset, $encoding); } +} +if (!function_exists('mb_strrpos')) { + function mb_strrpos(?string $haystack, ?string $needle, ?int $offset = 0, ?string $encoding = null): int|false { return p\Mbstring::mb_strrpos((string) $haystack, (string) $needle, (int) $offset, $encoding); } +} +if (!function_exists('mb_strstr')) { + function mb_strstr(?string $haystack, ?string $needle, ?bool $before_needle = false, ?string $encoding = null): string|false { return p\Mbstring::mb_strstr((string) $haystack, (string) $needle, (bool) $before_needle, $encoding); } +} +if (!function_exists('mb_get_info')) { + function mb_get_info(?string $type = 'all'): array|string|int|false { return p\Mbstring::mb_get_info((string) $type); } +} +if (!function_exists('mb_http_output')) { + function mb_http_output(?string $encoding = null): string|bool { return p\Mbstring::mb_http_output($encoding); } +} +if (!function_exists('mb_strwidth')) { + function mb_strwidth(?string $string, ?string $encoding = null): int { return p\Mbstring::mb_strwidth((string) $string, $encoding); } +} +if (!function_exists('mb_substr_count')) { + function mb_substr_count(?string $haystack, ?string $needle, ?string $encoding = null): int { return p\Mbstring::mb_substr_count((string) $haystack, (string) $needle, $encoding); } +} +if (!function_exists('mb_output_handler')) { + function mb_output_handler(?string $string, ?int $status): string { return p\Mbstring::mb_output_handler((string) $string, (int) $status); } +} +if (!function_exists('mb_http_input')) { + function mb_http_input(?string $type = null): array|string|false { return p\Mbstring::mb_http_input($type); } +} + +if (!function_exists('mb_convert_variables')) { + function mb_convert_variables(?string $to_encoding, array|string|null $from_encoding, mixed &$var, mixed &...$vars): string|false { return p\Mbstring::mb_convert_variables((string) $to_encoding, $from_encoding ?? '', $var, ...$vars); } +} + +if (!function_exists('mb_ord')) { + function mb_ord(?string $string, ?string $encoding = null): int|false { return p\Mbstring::mb_ord((string) $string, $encoding); } +} +if (!function_exists('mb_chr')) { + function mb_chr(?int $codepoint, ?string $encoding = null): string|false { return p\Mbstring::mb_chr((int) $codepoint, $encoding); } +} +if (!function_exists('mb_scrub')) { + function mb_scrub(?string $string, ?string $encoding = null): string { $encoding ??= mb_internal_encoding(); return mb_convert_encoding((string) $string, $encoding, $encoding); } +} +if (!function_exists('mb_str_split')) { + function mb_str_split(?string $string, ?int $length = 1, ?string $encoding = null): array { return p\Mbstring::mb_str_split((string) $string, (int) $length, $encoding); } +} + +if (extension_loaded('mbstring')) { + return; +} + +if (!defined('MB_CASE_UPPER')) { + define('MB_CASE_UPPER', 0); +} +if (!defined('MB_CASE_LOWER')) { + define('MB_CASE_LOWER', 1); +} +if (!defined('MB_CASE_TITLE')) { + define('MB_CASE_TITLE', 2); +} diff --git a/site/plugins/kirby-twig/vendor/twig/twig/CHANGELOG b/site/plugins/kirby-twig/vendor/twig/twig/CHANGELOG new file mode 100644 index 0000000..6215efb --- /dev/null +++ b/site/plugins/kirby-twig/vendor/twig/twig/CHANGELOG @@ -0,0 +1,1598 @@ +# 3.3.0 (2021-02-08) + + * Fix macro calls in a "cache" tag + * Add the slug filter + * Allow extra bundle to be compatible with Twig 2 + +# 3.2.1 (2021-01-05) + + * Fix extra bundle compat with older versions of Symfony + +# 3.2.0 (2021-01-05) + + * Add the Cache extension in the "extra" repositories: "cache" tag + * Add "registerUndefinedTokenParserCallback" + * Mark built-in node visitors as @internal + * Fix "odd" not working for negative numbers + +# 3.1.1 (2020-10-27) + + * Fix "include(template_from_string())" + +# 3.1.0 (2020-10-21) + + * Fix sandbox support when using "include(template_from_string())" + * Make round brackets optional for one argument tests like "same as" or "divisible by" + * Add support for ES2015 style object initialisation shortcut { a } is the same as { 'a': a } + +# 3.0.5 (2020-08-05) + + * Fix twig_compare w.r.t. whitespace trimming + * Fix sandbox not disabled if syntax error occurs within {% sandbox %} tag + * Fix a regression when not using a space before an operator + * Restrict callables to closures in filters + * Allow trailing commas in argument lists (in calls as well as definitions) + +# 3.0.4 (2020-07-05) + + * Fix comparison operators + * Fix options not taken into account when using "Michelf\MarkdownExtra" + * Fix "Twig\Extra\Intl\IntlExtension::getCountryName()" to accept "null" as a first argument + * Throw exception in case non-Traversable data is passed to "filter" + * Fix context optimization on PHP 7.4 + * Fix PHP 8 compatibility + * Fix ambiguous syntax parsing + +# 3.0.3 (2020-02-11) + + * Add a check to ensure that iconv() is defined + +# 3.0.2 (2020-02-11) + + * Avoid exceptions when an intl resource is not found + * Fix implementation of case-insensitivity for method names + +# 3.0.1 (2019-12-28) + + * fixed Symfony 5.0 support for the HTML extra extension + +# 3.0.0 (2019-11-15) + + * fixed number formatter in Intl extra extension when using a formatter prototype + +# 3.0.0-BETA1 (2019-11-11) + + * removed the "if" condition support on the "for" tag + * made the in, <, >, <=, >=, ==, and != operators more strict when comparing strings and integers/floats + * removed the "filter" tag + * added type hints everywhere + * changed Environment::resolveTemplate() to always return a TemplateWrapper instance + * removed Template::__toString() + * removed Parser::isReservedMacroName() + * removed SanboxedPrintNode + * removed Node::setTemplateName() + * made classes maked as "@final" final + * removed InitRuntimeInterface, ExistsLoaderInterface, and SourceContextLoaderInterface + * removed the "spaceless" tag + * removed Twig\Environment::getBaseTemplateClass() and Twig\Environment::setBaseTemplateClass() + * removed the "base_template_class" option on Twig\Environment + * bumped minimum PHP version to 7.2 + * removed PSR-0 classes + +# 2.14.4 (2021-XX-XX) + + * Add the slug filter + +# 2.14.3 (2021-01-05) + + * Fix extra bundle compat with older versions of Symfony + +# 2.14.2 (2021-01-05) + + * Fix "odd" not working for negative numbers + +# 2.14.1 (2020-10-27) + +* Fix "include(template_from_string())" + +# 2.14.0 (2020-10-21) + + * Fix sandbox support when using "include(template_from_string())" + * Make round brackets optional for one argument tests like "same as" or "divisible by" + * Add support for ES2015 style object initialisation shortcut { a } is the same as { 'a': a } + * Drop PHP 7.1 support + +# 2.13.1 (2020-08-05) + + * Fix sandbox not disabled if syntax error occurs within {% sandbox %} tag + * Fix a regression when not using a space before an operator + * Restrict callables to closures in filters + * Allow trailing commas in argument lists (in calls as well as definitions) + +# 2.13.0 (2020-07-05) + + * Fix options not taken into account when using "Michelf\MarkdownExtra" + * Fix "Twig\Extra\Intl\IntlExtension::getCountryName()" to accept "null" as a first argument + * Drop support for PHP 7.0 + * Throw exception in case non-Traversable data is passed to "filter" + * Fix context optimization on PHP 7.4 + * Fix PHP 8 compatibility + * Fix ambiguous syntax parsing + +# 2.12.5 (2020-02-11) + + * Add a check to ensure that iconv() is defined + +# 2.12.4 (2020-02-11) + + * Avoid exceptions when an intl resource is not found + * Fix implementation of case-insensitivity for method names + +# 2.12.3 (2019-12-28) + + * fixed Symfony 5.0 support for the HTML extra extension + * fixed number formatter in Intl extra extension when using a formatter prototype + +# 2.12.2 (2019-11-11) + + * added supported for exponential numbers + +# 2.12.1 (2019-10-17) + + * added the String extension in the "extra" repositories: "u" filter + +# 2.12.0 (2019-10-05) + + * added the spaceship operator ("<=>"), useful when using an arrow function in the "sort" filter + * added support for an "arrow" function on the "sort" filter + * added the CssInliner extension in the "extra" repositories: "inline_css" + filter + * added the Inky extension in the "extra" repositories: "inky_to_html" filter + * added Intl extension in the "extra" repositories: "country_name", + "currency_name", "currency_symbol", "language_name", "locale_name", + "timezone_name", "format_currency", "format_number", + "format_*_number", "format_datetime", "format_date", and "format_time" + filters, and the "country_timezones" function + * added the Markdown extension in the "extra" repositories: "markdown_to_html", + and "html_to_markdown" filters + * added the HtmlExtension extension in the "extra" repositories: "date_uri" + filter, and "html_classes" function + * optimized "block('foo') ?? 'bar'" + * fixed the empty test on Traversable instances + * fixed array_key_exists() on objects + * fixed cache when opcache is installed but disabled + * fixed using macros in arrow functions + * fixed split filter on edge case + +# 2.11.3 (2019-06-18) + + * display partial output (PHP buffer) when an error occurs in debug mode + * fixed the filter filter (allow the result to be used several times) + * fixed macro auto-import when a template contains only macros + +# 2.11.2 (2019-06-05) + + * fixed macro auto-import + +# 2.11.1 (2019-06-04) + + * added support for "Twig\Markup" instances in the "in" test (again) + * allowed string operators as variables names in assignments + * fixed support for macros defined in parent templates + +# 2.11.0 (2019-05-31) + + * added the possibility to register classes/interfaces as being safe for the escaper ("EscaperExtension::addSafeClass()") + * deprecated CoreExtension::setEscaper() and CoreExtension::getEscapers() in favor of the same methods on EscaperExtension + * macros are now auto-imported in the template they are defined (under the ``_self`` variable) + * added support for macros on "is defined" tests + * fixed macros "import" when using the same name in the parent and child templates + * fixed recursive macros + * macros imported "globally" in a template are now available in macros without re-importing them + * fixed the "filter" filter when the argument is \Traversable but does not implement \Iterator (\SimpleXmlElement for instance) + * fixed a PHP fatal error when calling a macro imported in a block in a nested block + * fixed a PHP fatal error when calling a macro imported in the template in another macro + * fixed wrong error message on "import" and "from" + +# 2.10.0 (2019-05-14) + + * deprecated "if" conditions on "for" tags + * added "filter", "map", and "reduce" filters (and support for arrow functions) + * fixed partial output leak when a PHP fatal error occurs + * optimized context access on PHP 7.4 + +# 2.9.0 (2019-04-28) + + * deprecated returning "false" to remove a Node from NodeVisitorInterface::leaveNode() + * allowed Twig\NodeVisitor\NodeVisitorInterface::leaveNode() to return "null" instead of "false" (same meaning) + * deprecated the "filter" tag (use the "apply" tag instead) + * added the "apply" tag as a replacement for the "filter" tag + * allowed Twig\Loader\FilesystemLoader::findTemplate() to return "null" instead of "false" (same meaning) + * added support for "Twig\Markup" instances in the "in" test + * fixed "import" when macros are stored in a template string + * fixed Lexer when using custom options containing the # char + * added template line number to twig_get_attribute() + +# 2.8.1 (2019-04-16) + + * fixed EscaperNodeVisitor + * deprecated passing a 3rd, 4th, and 5th arguments to the Sandbox exception classes + * deprecated Node::setTemplateName() in favor of Node::setSourceContext() + +# 2.8.0 (2019-04-16) + + * added Traversable support for the length filter + * fixed some wrong location in error messages + * made exception creation faster + * made escaping on ternary expressions (?: and ??) more fine-grained + * added the possibility to give a nice name to string templates (template_from_string function) + * fixed the "with" behavior to always include the globals (for consistency with the "include" and "embed" tags) + * fixed "include" with "ignore missing" when an error loading occurs in the included template + * added support for a new whitespace trimming option ({%~ ~%}, {{~ ~}}, {#~ ~#}) + * added the "column" filter + +# 2.7.4 (2019-03-23) + + * fixed variadic support + * fixed CheckToStringNode implementation (broken when a function/filter is variadic) + +# 2.7.3 (2019-03-21) + + * fixed the spaceless filter so that it behaves like the spaceless tag + * fixed BC break on Environment::resolveTemplate() + * allowed Traversable objects to be used in the "with" tag + * allowed Traversable objects to be used in the "with" tag + * allowed Traversable objects to be used in the "with" argument of the "include" and "embed" tags + +# 2.7.2 (2019-03-12) + + * added TemplateWrapper::getTemplateName() + +# 2.7.1 (2019-03-12) + + * fixed class aliases + +# 2.7.0 (2019-03-12) + + * fixed sandbox security issue (under some circumstances, calling the + __toString() method on an object was possible even if not allowed by the + security policy) + * fixed batch filter clobbers array keys when fill parameter is used + * added preserveKeys support for the batch filter + * fixed "embed" support when used from "template_from_string" + * deprecated passing a Twig\Template to Twig\Environment::load()/Twig\Environment::resolveTemplate() + * added the possibility to pass a TemplateWrapper to Twig\Environment::load() + * marked Twig\Environment::getTemplateClass() as internal (implementation detail) + * improved the performance of the sandbox + * deprecated the spaceless tag + * added a spaceless filter + * added max value to the "random" function + * deprecated Twig\Extension\InitRuntimeInterface + * deprecated Twig\Loader\ExistsLoaderInterface + * deprecated PSR-0 classes in favor of namespaced ones + * made namespace classes the default classes (PSR-0 ones are aliases now) + * added Twig\Loader\ChainLoader::getLoaders() + * removed duplicated directory separator in FilesystemLoader + * deprecated the "base_template_class" option on Twig\Environment + * deprecated the Twig\Environment::getBaseTemplateClass() and + Twig\Environment::setBaseTemplateClass() methods + * changed internal code to use the namespaced classes as much as possible + * deprecated Twig_Parser::isReservedMacroName() + +# 2.6.2 (2019-01-14) + + * fixed regression (key exists check for non ArrayObject objects) + +# 2.6.1 (2019-01-14) + + * fixed ArrayObject access with a null value + * fixed embedded templates starting with a BOM + * fixed using a Twig_TemplateWrapper instance as an argument to extends + * fixed error location when calling an undefined block + * deprecated passing a string as a source on Twig_Error + * switched generated code to use the PHP short array notation + * fixed float representation in compiled templates + * added a second argument to the join filter (last separator configuration) + +# 2.6.0 (2018-12-16) + + * made sure twig_include returns a string + * fixed multi-byte UFT-8 in escape('html_attr') + * added the "deprecated" tag + * added support for dynamically named tests + * fixed GlobalsInterface extended class + * fixed filesystem loader throwing an exception instead of returning false + +# 2.5.0 (2018-07-13) + + * deprecated using the spaceless tag at the root level of a child template (noop anyway) + * deprecated the possibility to define a block in a non-capturing block in a child template + * added the Symfony ctype polyfill as a dependency + * fixed reporting the proper location for errors compiled in templates + * fixed the error handling for the optimized extension-based function calls + * ensured that syntax errors are triggered with the right line + * "js" filter now produces valid JSON + +# 2.4.8 (2018-04-02) + + * fixed a regression when using the "default" filter or the "defined" test on non-existing arrays + +# 2.4.7 (2018-03-20) + + * optimized runtime performance + * optimized parser performance by inlining the constant values + * fixed block names unicity + * fixed counting children of SimpleXMLElement objects + * added missing else clause to avoid infinite loops + * fixed .. (range operator) in sandbox policy + +# 2.4.6 (2018-03-03) + + * fixed a regression in the way the profiler is registered in templates + +# 2.4.5 (2018-03-02) + + * optimized the performance of calling an extension method at runtime + * optimized the performance of the dot operator for array and method calls + * added an exception when using "===" instead of "same as" + * fixed possible array to string conversion concealing actual error + * made variable names deterministic in compiled templates + * fixed length filter when passing an instance of IteratorAggregate + * fixed Environment::resolveTemplate to accept instances of TemplateWrapper + +# 2.4.4 (2017-09-27) + + * added Twig_Profiler_Profile::reset() + * fixed use TokenParser to return an empty Node + * added RuntimeExtensionInterface + * added circular reference detection when loading templates + * added support for runtime loaders in IntegrationTestCase + * fixed deprecation when using Twig_Profiler_Dumper_Html + * removed @final from Twig_Profiler_Dumper_Text + +# 2.4.3 (2017-06-07) + + * fixed namespaces introduction + +# 2.4.2 (2017-06-05) + + * fixed namespaces introduction + +# 2.4.1 (2017-06-05) + + * fixed namespaces introduction + +# 2.4.0 (2017-06-05) + + * added support for PHPUnit 6 when testing extensions + * fixed PHP 7.2 compatibility + * fixed template name generation in Twig_Environment::createTemplate() + * removed final tag on Twig_TokenParser_Include + * dropped HHVM support + * added namespaced aliases for all (non-deprecated) classes and interfaces + * marked Twig_Filter, Twig_Function, Twig_Test, Twig_Node_Module and Twig_Profiler_Profile as final via the @final annotation + +# 2.3.2 (2017-04-20) + + * fixed edge case in the method cache for Twig attributes + +# 2.3.1 (2017-04-18) + + * fixed the empty() test + +# 2.3.0 (2017-03-22) + + * fixed a race condition handling when writing cache files + * "length" filter now returns string length when applied to an object that does + not implement \Countable but provides __toString() + * "empty" test will now consider the return value of the __toString() method for + objects implement __toString() but not \Countable + * fixed JS escaping for unicode characters with higher code points + * added error message when calling `parent()` in a block that doesn't exist in the parent template + +# 2.2.0 (2017-02-26) + + * added a PSR-11 compatible runtime loader + * added `side` argument to `trim` to allow left or right trimming only. + +# 2.1.0 (2017-01-11) + + * fixed twig_get_attribute() + * added Twig_NodeCaptureInterface for nodes that capture all output + +# 2.0.0 (2017-01-05) + + * removed the C extension + * moved Twig_Environment::getAttribute() to twig_get_attribute() + * removed Twig_Environment::getLexer(), Twig_Environment::getParser(), Twig_Environment::getCompiler() + * removed Twig_Compiler::getFilename() + * added hasser support in Twig_Template::getAttribute() + * sped up the json_encode filter + * removed reserved macro names; all names can be used as macro + * removed Twig_Template::getEnvironment() + * changed _self variable to return the current template name + * made the loader a required argument of Twig_Environment constructor + * removed Twig_Environment::clearTemplateCache() + * removed Twig_Autoloader (use Composer instead) + * removed `true` as an equivalent to `html` for the auto-escaping strategy + * removed pre-1.8 autoescape tag syntax + * dropped support for PHP 5.x + * removed the ability to register a global variable after the runtime or the extensions have been initialized + * improved the performance of the filesystem loader + * removed features that were deprecated in 1.x + +# 1.44.3 (2021-XX-XX) + + * n/a + +# 1.44.2 (2021-01-05) + + * Fix "odd" not working for negative numbers + +# 1.44.1 (2020-10-27) + + * Fix "include(template_from_string())" + +# 1.44.0 (2020-10-21) + + * Remove implicit dependency on ext/iconv in JS escaper + * Fix sandbox support when using "include(template_from_string())" + * Make round brackets optional for one argument tests like "same as" or "divisible by" + * Add support for ES2015 style object initialisation shortcut { a } is the same as { 'a': a } + * Fix filter(), map(), and reduce() to throw a RuntimeError instead of a PHP TypeError + * Drop PHP 7.1 support + +# 1.43.1 (2020-08-05) + + * Fix sandbox not disabled if syntax error occurs within {% sandbox %} tag + * Fix a regression when not using a space before an operator + * Restrict callables to closures in filters + * Allow trailing commas in argument lists (in calls as well as definitions) + +# 1.43.0 (2020-07-05) + + * Throw exception in case non-Traversable data is passed to "filter" + * Fix context optimization on PHP 7.4 + * Fix PHP 8 compatibility + * Drop PHP 5.5 5.6, and 7.0 support + * Fix ambiguous syntax parsing + * In sandbox, the `filter`, `map` and `reduce` filters require Closures in `arrow` parameter + +# 1.42.5 (2020-02-11) + + * Fix implementation of case-insensitivity for method names + +# 1.42.4 (2019-11-11) + + * optimized "block('foo') ?? 'bar" + * added supported for exponential numbers + +# 1.42.3 (2019-08-24) + + * fixed the "split" filter when the delimiter is "0" + * fixed the "empty" test on Traversable instances + * fixed cache when opcache is installed but disabled + * fixed PHP 7.4 compatibility + * bumped the minimal PHP version to 5.5 + +# 1.42.2 (2019-06-18) + + * Display partial output (PHP buffer) when an error occurs in debug mode + +# 1.42.1 (2019-06-04) + + * added support for "Twig\Markup" instances in the "in" test (again) + * allowed string operators as variables names in assignments + +# 1.42.0 (2019-05-31) + + * fixed the "filter" filter when the argument is \Traversable but does not implement \Iterator (\SimpleXmlElement for instance) + * fixed a PHP fatal error when calling a macro imported in a block in a nested block + * fixed a PHP fatal error when calling a macro imported in the template in another macro + * fixed wrong error message on "import" and "from" + +# 1.41.0 (2019-05-14) + + * fixed support for PHP 7.4 + * added "filter", "map", and "reduce" filters (and support for arrow functions) + * fixed partial output leak when a PHP fatal error occurs + * optimized context access on PHP 7.4 + +# 1.40.1 (2019-04-29) + +# fixed regression in NodeTraverser + +# 1.40.0 (2019-04-28) + + * allowed Twig\NodeVisitor\NodeVisitorInterface::leaveNode() to return "null" instead of "false" (same meaning) + * added the "apply" tag as a replacement for the "filter" tag + * allowed Twig\Loader\FilesystemLoader::findTemplate() to return "null" instead of "false" (same meaning) + * added support for "Twig\Markup" instances in the "in" test + * fixed Lexer when using custom options containing the # char + * fixed "import" when macros are stored in a template string + +# 1.39.1 (2019-04-16) + + * fixed EscaperNodeVisitor + +# 1.39.0 (2019-04-16) + + * added Traversable support for the length filter + * fixed some wrong location in error messages + * made exception creation faster + * made escaping on ternary expressions (?: and ??) more fine-grained + * added the possibility to give a nice name to string templates (template_from_string function) + * fixed the "with" behavior to always include the globals (for consistency with the "include" and "embed" tags) + * fixed "include" with "ignore missing" when an error loading occurs in the included template + * added support for a new whitespace trimming option ({%~ ~%}, {{~ ~}}, {#~ ~#}) + +# 1.38.4 (2019-03-23) + + * fixed CheckToStringNode implementation (broken when a function/filter is variadic) + +# 1.38.3 (2019-03-21) + + * fixed the spaceless filter so that it behaves like the spaceless tag + * fixed BC break on Environment::resolveTemplate() + * fixed the bundled Autoloader to also load namespaced classes + * allowed Traversable objects to be used in the "with" tag + * allowed Traversable objects to be used in the "with" argument of the "include" and "embed" tags + +# 1.38.2 (2019-03-12) + + * added TemplateWrapper::getTemplateName() + +# 1.38.1 (2019-03-12) + + * fixed class aliases + +# 1.38.0 (2019-03-12) + + * fixed sandbox security issue (under some circumstances, calling the + __toString() method on an object was possible even if not allowed by the + security policy) + * fixed batch filter clobbers array keys when fill parameter is used + * added preserveKeys support for the batch filter + * fixed "embed" support when used from "template_from_string" + * added the possibility to pass a TemplateWrapper to Twig\Environment::load() + * improved the performance of the sandbox + * added a spaceless filter + * added max value to the "random" function + * made namespace classes the default classes (PSR-0 ones are aliases now) + * removed duplicated directory separator in FilesystemLoader + * added Twig\Loader\ChainLoader::getLoaders() + * changed internal code to use the namespaced classes as much as possible + +# 1.37.1 (2019-01-14) + + * fixed regression (key exists check for non ArrayObject objects) + * fixed logic in TemplateWrapper + +# 1.37.0 (2019-01-14) + + * fixed ArrayObject access with a null value + * fixed embedded templates starting with a BOM + * fixed using a Twig_TemplateWrapper instance as an argument to extends + * switched generated code to use the PHP short array notation + * dropped PHP 5.3 support + * fixed float representation in compiled templates + * added a second argument to the join filter (last separator configuration) + +# 1.36.0 (2018-12-16) + + * made sure twig_include returns a string + * fixed multi-byte UFT-8 in escape('html_attr') + * added the "deprecated" tag + * added support for dynamically named tests + * fixed GlobalsInterface extended class + * fixed filesystem loader throwing an exception instead of returning false + +# 1.35.4 (2018-07-13) + + * ensured that syntax errors are triggered with the right line + * added the Symfony ctype polyfill as a dependency + * "js" filter now produces valid JSON + +# 1.35.3 (2018-03-20) + + * fixed block names unicity + * fixed counting children of SimpleXMLElement objects + * added missing else clause to avoid infinite loops + * fixed .. (range operator) in sandbox policy + +# 1.35.2 (2018-03-03) + + * fixed a regression in the way the profiler is registered in templates + +# 1.35.1 (2018-03-02) + + * added an exception when using "===" instead of "same as" + * fixed possible array to string conversion concealing actual error + * made variable names deterministic in compiled templates + * fixed length filter when passing an instance of IteratorAggregate + * fixed Environment::resolveTemplate to accept instances of TemplateWrapper + +# 1.35.0 (2017-09-27) + + * added Twig_Profiler_Profile::reset() + * fixed use TokenParser to return an empty Node + * added RuntimeExtensionInterface + * added circular reference detection when loading templates + +# 1.34.4 (2017-07-04) + + * added support for runtime loaders in IntegrationTestCase + * fixed deprecation when using Twig_Profiler_Dumper_Html + +# 1.34.3 (2017-06-07) + + * fixed namespaces introduction + +# 1.34.2 (2017-06-05) + + * fixed namespaces introduction + +# 1.34.1 (2017-06-05) + + * fixed namespaces introduction + +# 1.34.0 (2017-06-05) + + * added support for PHPUnit 6 when testing extensions + * fixed PHP 7.2 compatibility + * fixed template name generation in Twig_Environment::createTemplate() + * removed final tag on Twig_TokenParser_Include + * added namespaced aliases for all (non-deprecated) classes and interfaces + * dropped HHVM support + * dropped PHP 5.2 support + +# 1.33.2 (2017-04-20) + + * fixed edge case in the method cache for Twig attributes + +# 1.33.1 (2017-04-18) + + * fixed the empty() test + +# 1.33.0 (2017-03-22) + + * fixed a race condition handling when writing cache files + * "length" filter now returns string length when applied to an object that does + not implement \Countable but provides __toString() + * "empty" test will now consider the return value of the __toString() method for + objects implement __toString() but not \Countable + * fixed JS escaping for unicode characters with higher code points + +# 1.32.0 (2017-02-26) + + * fixed deprecation notice in Twig_Util_DeprecationCollector + * added a PSR-11 compatible runtime loader + * added `side` argument to `trim` to allow left or right trimming only. + +# 1.31.0 (2017-01-11) + + * added Twig_NodeCaptureInterface for nodes that capture all output + * fixed marking the environment as initialized too early + * fixed C89 compat for the C extension + * turned fatal error into exception when a previously generated cache is corrupted + * fixed offline cache warm-ups for embedded templates + +# 1.30.0 (2016-12-23) + + * added Twig_FactoryRuntimeLoader + * deprecated function/test/filter/tag overriding + * deprecated the "disable_c_ext" attribute on Twig_Node_Expression_GetAttr + +# 1.29.0 (2016-12-13) + + * fixed sandbox being left enabled if an exception is thrown while rendering + * marked some classes as being final (via @final) + * made Twig_Error report real source path when possible + * added support for {{ _self }} to provide an upgrade path from 1.x to 2.0 (replaces {{ _self.templateName }}) + * deprecated silent display of undefined blocks + * deprecated support for mbstring.func_overload != 0 + +# 1.28.2 (2016-11-23) + + * fixed precedence between getFoo() and isFoo() in Twig_Template::getAttribute() + * improved a deprecation message + +# 1.28.1 (2016-11-18) + + * fixed block() function when used with a template argument + +# 1.28.0 (2016-11-17) + + * added support for the PHP 7 null coalescing operator for the ?? Twig implementation + * exposed a way to access template data and methods in a portable way + * changed context access to use the PHP 7 null coalescing operator when available + * added the "with" tag + * added support for a custom template on the block() function + * added "is defined" support for block() and constant() + * optimized the way attributes are fetched + +# 1.27.0 (2016-10-25) + + * deprecated Twig_Parser::getEnvironment() + * deprecated Twig_Parser::addHandler() and Twig_Parser::addNodeVisitor() + * deprecated Twig_Compiler::addIndentation() + * fixed regression when registering two extensions having the same class name + * deprecated Twig_LoaderInterface::getSource() (implement Twig_SourceContextLoaderInterface instead) + * fixed the filesystem loader with relative paths + * deprecated Twig_Node::getLine() in favor of Twig_Node::getTemplateLine() + * deprecated Twig_Template::getSource() in favor of Twig_Template::getSourceContext() + * deprecated Twig_Node::getFilename() in favor of Twig_Node::getTemplateName() + * deprecated the "filename" escaping strategy (use "name" instead) + * added Twig_Source to hold information about the original template + * deprecated Twig_Error::getTemplateFile() and Twig_Error::setTemplateFile() in favor of Twig_Error::getTemplateName() and Twig_Error::setTemplateName() + * deprecated Parser::getFilename() + * fixed template paths when a template name contains a protocol like vfs:// + * improved debugging with Twig_Sandbox_SecurityError exceptions for disallowed methods and properties + +# 1.26.1 (2016-10-05) + + * removed template source code from generated template classes when debug is disabled + * fixed default implementation of Twig_Template::getDebugInfo() for better BC + * fixed regression on static calls for functions/filters/tests + +# 1.26.0 (2016-10-02) + + * added template cache invalidation based on more environment options + * added a missing deprecation notice + * fixed template paths when a template is stored in a PHAR file + * allowed filters/functions/tests implementation to use a different class than the extension they belong to + * deprecated Twig_ExtensionInterface::getName() + +# 1.25.0 (2016-09-21) + + * changed the way we store template source in template classes + * removed usage of realpath in cache keys + * fixed Twig cache sharing when used with different versions of PHP + * removed embed parent workaround for simple use cases + * deprecated the ability to store non Node instances in Node::$nodes + * deprecated Twig_Environment::getLexer(), Twig_Environment::getParser(), Twig_Environment::getCompiler() + * deprecated Twig_Compiler::getFilename() + +# 1.24.2 (2016-09-01) + + * fixed static callables + * fixed a potential PHP warning when loading the cache + * fixed a case where the autoescaping does not work as expected + +# 1.24.1 (2016-05-30) + + * fixed reserved keywords (forbids true, false, null and none keywords for variables names) + * fixed support for PHP7 (Throwable support) + * marked the following methods as being internals on Twig_Environment: + getFunctions(), getFilters(), getTests(), getFunction(), getFilter(), getTest(), + getTokenParsers(), getTags(), getNodeVisitors(), getUnaryOperators(), getBinaryOperators(), + getFunctions(), getFilters(), getGlobals(), initGlobals(), initExtensions(), and initExtension() + +# 1.24.0 (2016-01-25) + + * adding support for the ?? operator + * fixed the defined test when used on a constant, a map, or a sequence + * undeprecated _self (should only be used to get the template name, not the template instance) + * fixed parsing on PHP7 + +# 1.23.3 (2016-01-11) + + * fixed typo + +# 1.23.2 (2015-01-11) + + * added versions in deprecated messages + * made file cache tolerant for trailing (back)slashes on directory configuration + * deprecated unused Twig_Node_Expression_ExtensionReference class + +# 1.23.1 (2015-11-05) + + * fixed some exception messages which triggered PHP warnings + * fixed BC on Twig_Test_NodeTestCase + +# 1.23.0 (2015-10-29) + + * deprecated the possibility to override an extension by registering another one with the same name + * deprecated Twig_ExtensionInterface::getGlobals() (added Twig_Extension_GlobalsInterface for BC) + * deprecated Twig_ExtensionInterface::initRuntime() (added Twig_Extension_InitRuntimeInterface for BC) + * deprecated Twig_Environment::computeAlternatives() + +# 1.22.3 (2015-10-13) + + * fixed regression when using null as a cache strategy + * improved performance when checking template freshness + * fixed warnings when loaded templates do not exist + * fixed template class name generation to prevent possible collisions + * fixed logic for custom escapers to call them even on integers and null values + * changed template cache names to take into account the Twig C extension + +# 1.22.2 (2015-09-22) + + * fixed a race condition in template loading + +# 1.22.1 (2015-09-15) + + * fixed regression in template_from_string + +# 1.22.0 (2015-09-13) + + * made Twig_Test_IntegrationTestCase more flexible + * added an option to force PHP bytecode invalidation when writing a compiled template into the cache + * fixed the profiler duration for the root node + * changed template cache names to take into account enabled extensions + * deprecated Twig_Environment::clearCacheFiles(), Twig_Environment::getCacheFilename(), + Twig_Environment::writeCacheFile(), and Twig_Environment::getTemplateClassPrefix() + * added a way to override the filesystem template cache system + * added a way to get the original template source from Twig_Template + +# 1.21.2 (2015-09-09) + + * fixed variable names for the deprecation triggering code + * fixed escaping strategy detection based on filename + * added Traversable support for replace, merge, and sort + * deprecated support for character by character replacement for the "replace" filter + +# 1.21.1 (2015-08-26) + + * fixed regression when using the deprecated Twig_Test_* classes + +# 1.21.0 (2015-08-24) + + * added deprecation notices for deprecated features + * added a deprecation "framework" for filters/functions/tests and test fixtures + +# 1.20.0 (2015-08-12) + + * forbid access to the Twig environment from templates and internal parts of Twig_Template + * fixed limited RCEs when in sandbox mode + * deprecated Twig_Template::getEnvironment() + * deprecated the _self variable for usage outside of the from and import tags + * added Twig_BaseNodeVisitor to ease the compatibility of node visitors + between 1.x and 2.x + +# 1.19.0 (2015-07-31) + + * fixed wrong error message when including an undefined template in a child template + * added support for variadic filters, functions, and tests + * added support for extra positional arguments in macros + * added ignore_missing flag to the source function + * fixed batch filter with zero items + * deprecated Twig_Environment::clearTemplateCache() + * fixed sandbox disabling when using the include function + +# 1.18.2 (2015-06-06) + + * fixed template/line guessing in exceptions for nested templates + * optimized the number of inodes and the size of realpath cache when using the cache + +# 1.18.1 (2015-04-19) + + * fixed memory leaks in the C extension + * deprecated Twig_Loader_String + * fixed the slice filter when used with a SimpleXMLElement object + * fixed filesystem loader when trying to load non-files (like directories) + +# 1.18.0 (2015-01-25) + + * fixed some error messages where the line was wrong (unknown variables or argument names) + * added a new way to customize the main Module node (via empty nodes) + * added Twig_Environment::createTemplate() to create a template from a string + * added a profiler + * fixed filesystem loader cache when different file paths are used for the same template + +# 1.17.0 (2015-01-14) + + * added a 'filename' autoescaping strategy, which dynamically chooses the + autoescaping strategy for a template based on template file extension. + +# 1.16.3 (2014-12-25) + + * fixed regression for dynamic parent templates + * fixed cache management with statcache + * fixed a regression in the slice filter + +# 1.16.2 (2014-10-17) + + * fixed timezone on dates as strings + * fixed 2-words test names when a custom node class is not used + * fixed macros when using an argument named like a PHP super global (like GET or POST) + * fixed date_modify when working with DateTimeImmutable + * optimized for loops + * fixed multi-byte characters handling in the split filter + * fixed a regression in the in operator + * fixed a regression in the slice filter + +# 1.16.1 (2014-10-10) + + * improved error reporting in a sandboxed template + * fixed missing error file/line information under certain circumstances + * fixed wrong error line number in some error messages + * fixed the in operator to use strict comparisons + * sped up the slice filter + * fixed for mb function overload mb_substr acting different + * fixed the attribute() function when passing a variable for the arguments + +# 1.16.0 (2014-07-05) + + * changed url_encode to always encode according to RFC 3986 + * fixed inheritance in a 'use'-hierarchy + * removed the __toString policy check when the sandbox is disabled + * fixed recursively calling blocks in templates with inheritance + +# 1.15.1 (2014-02-13) + + * fixed the conversion of the special '0000-00-00 00:00' date + * added an error message when trying to import an undefined block from a trait + * fixed a C extension crash when accessing defined but uninitialized property. + +# 1.15.0 (2013-12-06) + + * made ignoreStrictCheck in Template::getAttribute() works with __call() methods throwing BadMethodCallException + * added min and max functions + * added the round filter + * fixed a bug that prevented the optimizers to be enabled/disabled selectively + * fixed first and last filters for UTF-8 strings + * added a source function to include the content of a template without rendering it + * fixed the C extension sandbox behavior when get or set is prepend to method name + +# 1.14.2 (2013-10-30) + + * fixed error filename/line when an error occurs in an included file + * allowed operators that contain whitespaces to have more than one whitespace + * allowed tests to be made of 1 or 2 words (like "same as" or "divisible by") + +# 1.14.1 (2013-10-15) + + * made it possible to use named operators as variables + * fixed the possibility to have a variable named 'matches' + * added support for PHP 5.5 DateTimeInterface + +# 1.14.0 (2013-10-03) + + * fixed usage of the html_attr escaping strategy to avoid double-escaping with the html strategy + * added new operators: ends with, starts with, and matches + * fixed some compatibility issues with HHVM + * added a way to add custom escaping strategies + * fixed the C extension compilation on Windows + * fixed the batch filter when using a fill argument with an exact match of elements to batch + * fixed the filesystem loader cache when a template name exists in several namespaces + * fixed template_from_string when the template includes or extends other ones + * fixed a crash of the C extension on an edge case + +# 1.13.2 (2013-08-03) + + * fixed the error line number for an error occurs in and embedded template + * fixed crashes of the C extension on some edge cases + +# 1.13.1 (2013-06-06) + + * added the possibility to ignore the filesystem constructor argument in Twig_Loader_Filesystem + * fixed Twig_Loader_Chain::exists() for a loader which implements Twig_ExistsLoaderInterface + * adjusted backtrace call to reduce memory usage when an error occurs + * added support for object instances as the second argument of the constant test + * fixed the include function when used in an assignment + +# 1.13.0 (2013-05-10) + + * fixed getting a numeric-like item on a variable ('09' for instance) + * fixed getting a boolean or float key on an array, so it is consistent with PHP's array access: + `{{ array[false] }}` behaves the same as `echo $array[false];` (equals `$array[0]`) + * made the escape filter 20% faster for happy path (escaping string for html with UTF-8) + * changed ☃ to § in tests + * enforced usage of named arguments after positional ones + +# 1.12.3 (2013-04-08) + + * fixed a security issue in the filesystem loader where it was possible to include a template one + level above the configured path + * fixed fatal error that should be an exception when adding a filter/function/test too late + * added a batch filter + * added support for encoding an array as query string in the url_encode filter + +# 1.12.2 (2013-02-09) + + * fixed the timezone used by the date filter and function when the given date contains a timezone (like 2010-01-28T15:00:00+02:00) + * fixed globals when getGlobals is called early on + * added the first and last filter + +# 1.12.1 (2013-01-15) + + * added support for object instances as the second argument of the constant function + * relaxed globals management to avoid a BC break + * added support for {{ some_string[:2] }} + +# 1.12.0 (2013-01-08) + + * added verbatim as an alias for the raw tag to avoid confusion with the raw filter + * fixed registration of tests and functions as anonymous functions + * fixed globals management + +# 1.12.0-RC1 (2012-12-29) + + * added an include function (does the same as the include tag but in a more flexible way) + * added the ability to use any PHP callable to define filters, functions, and tests + * added a syntax error when using a loop variable that is not defined + * added the ability to set default values for macro arguments + * added support for named arguments for filters, tests, and functions + * moved filters/functions/tests syntax errors to the parser + * added support for extended ternary operator syntaxes + +# 1.11.1 (2012-11-11) + + * fixed debug info line numbering (was off by 2) + * fixed escaping when calling a macro inside another one (regression introduced in 1.9.1) + * optimized variable access on PHP 5.4 + * fixed a crash of the C extension when an exception was thrown from a macro called without being imported (using _self.XXX) + +# 1.11.0 (2012-11-07) + + * fixed macro compilation when a variable name is a PHP reserved keyword + * changed the date filter behavior to always apply the default timezone, except if false is passed as the timezone + * fixed bitwise operator precedences + * added the template_from_string function + * fixed default timezone usage for the date function + * optimized the way Twig exceptions are managed (to make them faster) + * added Twig_ExistsLoaderInterface (implementing this interface in your loader make the chain loader much faster) + +# 1.10.3 (2012-10-19) + + * fixed wrong template location in some error messages + * reverted a BC break introduced in 1.10.2 + * added a split filter + +# 1.10.2 (2012-10-15) + + * fixed macro calls on PHP 5.4 + +# 1.10.1 (2012-10-15) + + * made a speed optimization to macro calls when imported via the "import" tag + * fixed C extension compilation on Windows + * fixed a segfault in the C extension when using DateTime objects + +# 1.10.0 (2012-09-28) + + * extracted functional tests framework to make it reusable for third-party extensions + * added namespaced templates support in Twig_Loader_Filesystem + * added Twig_Loader_Filesystem::prependPath() + * fixed an error when a token parser pass a closure as a test to the subparse() method + +# 1.9.2 (2012-08-25) + + * fixed the in operator for objects that contain circular references + * fixed the C extension when accessing a public property of an object implementing the \ArrayAccess interface + +# 1.9.1 (2012-07-22) + + * optimized macro calls when auto-escaping is on + * fixed wrong parent class for Twig_Function_Node + * made Twig_Loader_Chain more explicit about problems + +# 1.9.0 (2012-07-13) + + * made the parsing independent of the template loaders + * fixed exception trace when an error occurs when rendering a child template + * added escaping strategies for CSS, URL, and HTML attributes + * fixed nested embed tag calls + * added the date_modify filter + +# 1.8.3 (2012-06-17) + + * fixed paths in the filesystem loader when passing a path that ends with a slash or a backslash + * fixed escaping when a project defines a function named html or js + * fixed chmod mode to apply the umask correctly + +# 1.8.2 (2012-05-30) + + * added the abs filter + * fixed a regression when using a number in template attributes + * fixed compiler when mbstring.func_overload is set to 2 + * fixed DateTimeZone support in date filter + +# 1.8.1 (2012-05-17) + + * fixed a regression when dealing with SimpleXMLElement instances in templates + * fixed "is_safe" value for the "dump" function when "html_errors" is not defined in php.ini + * switched to use mbstring whenever possible instead of iconv (you might need to update your encoding as mbstring and iconv encoding names sometimes differ) + +# 1.8.0 (2012-05-08) + + * enforced interface when adding tests, filters, functions, and node visitors from extensions + * fixed a side-effect of the date filter where the timezone might be changed + * simplified usage of the autoescape tag; the only (optional) argument is now the escaping strategy or false (with a BC layer) + * added a way to dynamically change the auto-escaping strategy according to the template "filename" + * changed the autoescape option to also accept a supported escaping strategy (for BC, true is equivalent to html) + * added an embed tag + +# 1.7.0 (2012-04-24) + + * fixed a PHP warning when using CIFS + * fixed template line number in some exceptions + * added an iterable test + * added an error when defining two blocks with the same name in a template + * added the preserves_safety option for filters + * fixed a PHP notice when trying to access a key on a non-object/array variable + * enhanced error reporting when the template file is an instance of SplFileInfo + * added Twig_Environment::mergeGlobals() + * added compilation checks to avoid misuses of the sandbox tag + * fixed filesystem loader freshness logic for high traffic websites + * fixed random function when charset is null + +# 1.6.5 (2012-04-11) + + * fixed a regression when a template only extends another one without defining any blocks + +# 1.6.4 (2012-04-02) + + * fixed PHP notice in Twig_Error::guessTemplateLine() introduced in 1.6.3 + * fixed performance when compiling large files + * optimized parent template creation when the template does not use dynamic inheritance + +# 1.6.3 (2012-03-22) + + * fixed usage of Z_ADDREF_P for PHP 5.2 in the C extension + * fixed compilation of numeric values used in templates when using a locale where the decimal separator is not a dot + * made the strategy used to guess the real template file name and line number in exception messages much faster and more accurate + +# 1.6.2 (2012-03-18) + + * fixed sandbox mode when used with inheritance + * added preserveKeys support for the slice filter + * fixed the date filter when a DateTime instance is passed with a specific timezone + * added a trim filter + +# 1.6.1 (2012-02-29) + + * fixed Twig C extension + * removed the creation of Twig_Markup instances when not needed + * added a way to set the default global timezone for dates + * fixed the slice filter on strings when the length is not specified + * fixed the creation of the cache directory in case of a race condition + +# 1.6.0 (2012-02-04) + + * fixed raw blocks when used with the whitespace trim option + * made a speed optimization to macro calls when imported via the "from" tag + * fixed globals, parsers, visitors, filters, tests, and functions management in Twig_Environment when a new one or new extension is added + * fixed the attribute function when passing arguments + * added slice notation support for the [] operator (syntactic sugar for the slice operator) + * added a slice filter + * added string support for the reverse filter + * fixed the empty test and the length filter for Twig_Markup instances + * added a date function to ease date comparison + * fixed unary operators precedence + * added recursive parsing support in the parser + * added string and integer handling for the random function + +# 1.5.1 (2012-01-05) + + * fixed a regression when parsing strings + +# 1.5.0 (2012-01-04) + + * added Traversable objects support for the join filter + +# 1.5.0-RC2 (2011-12-30) + + * added a way to set the default global date interval format + * fixed the date filter for DateInterval instances (setTimezone() does not exist for them) + * refactored Twig_Template::display() to ease its extension + * added a number_format filter + +# 1.5.0-RC1 (2011-12-26) + + * removed the need to quote hash keys + * allowed hash keys to be any expression + * added a do tag + * added a flush tag + * added support for dynamically named filters and functions + * added a dump function to help debugging templates + * added a nl2br filter + * added a random function + * added a way to change the default format for the date filter + * fixed the lexer when an operator ending with a letter ends a line + * added string interpolation support + * enhanced exceptions for unknown filters, functions, tests, and tags + +# 1.4.0 (2011-12-07) + + * fixed lexer when using big numbers (> PHP_INT_MAX) + * added missing preserveKeys argument to the reverse filter + * fixed macros containing filter tag calls + +# 1.4.0-RC2 (2011-11-27) + + * removed usage of Reflection in Twig_Template::getAttribute() + * added a C extension that can optionally replace Twig_Template::getAttribute() + * added negative timestamp support to the date filter + +# 1.4.0-RC1 (2011-11-20) + + * optimized variable access when using PHP 5.4 + * changed the precedence of the .. operator to be more consistent with languages that implements such a feature like Ruby + * added an Exception to Twig_Loader_Array::isFresh() method when the template does not exist to be consistent with other loaders + * added Twig_Function_Node to allow more complex functions to have their own Node class + * added Twig_Filter_Node to allow more complex filters to have their own Node class + * added Twig_Test_Node to allow more complex tests to have their own Node class + * added a better error message when a template is empty but contain a BOM + * fixed "in" operator for empty strings + * fixed the "defined" test and the "default" filter (now works with more than one call (foo.bar.foo) and for both values of the strict_variables option) + * changed the way extensions are loaded (addFilter/addFunction/addGlobal/addTest/addNodeVisitor/addTokenParser/addExtension can now be called in any order) + * added Twig_Environment::display() + * made the escape filter smarter when the encoding is not supported by PHP + * added a convert_encoding filter + * moved all node manipulations outside the compile() Node method + * made several speed optimizations + +# 1.3.0 (2011-10-08) + +no changes + +# 1.3.0-RC1 (2011-10-04) + + * added an optimization for the parent() function + * added cache reloading when auto_reload is true and an extension has been modified + * added the possibility to force the escaping of a string already marked as safe (instance of Twig_Markup) + * allowed empty templates to be used as traits + * added traits support for the "parent" function + +# 1.2.0 (2011-09-13) + +no changes + +# 1.2.0-RC1 (2011-09-10) + + * enhanced the exception when a tag remains unclosed + * added support for empty Countable objects for the "empty" test + * fixed algorithm that determines if a template using inheritance is valid (no output between block definitions) + * added better support for encoding problems when escaping a string (available as of PHP 5.4) + * added a way to ignore a missing template when using the "include" tag ({% include "foo" ignore missing %}) + * added support for an array of templates to the "include" and "extends" tags ({% include ['foo', 'bar'] %}) + * added support for bitwise operators in expressions + * added the "attribute" function to allow getting dynamic attributes on variables + * added Twig_Loader_Chain + * added Twig_Loader_Array::setTemplate() + * added an optimization for the set tag when used to capture a large chunk of static text + * changed name regex to match PHP one "[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*" (works for blocks, tags, functions, filters, and macros) + * removed the possibility to use the "extends" tag from a block + * added "if" modifier support to "for" loops + +# 1.1.2 (2011-07-30) + + * fixed json_encode filter on PHP 5.2 + * fixed regression introduced in 1.1.1 ({{ block(foo|lower) }}) + * fixed inheritance when using conditional parents + * fixed compilation of templates when the body of a child template is not empty + * fixed output when a macro throws an exception + * fixed a parsing problem when a large chunk of text is enclosed in a comment tag + * added PHPDoc for all Token parsers and Core extension functions + +# 1.1.1 (2011-07-17) + + * added a performance optimization in the Optimizer (also helps to lower the number of nested level calls) + * made some performance improvement for some edge cases + +# 1.1.0 (2011-06-28) + + * fixed json_encode filter + +# 1.1.0-RC3 (2011-06-24) + + * fixed method case-sensitivity when using the sandbox mode + * added timezone support for the date filter + * fixed possible security problems with NUL bytes + +# 1.1.0-RC2 (2011-06-16) + + * added an exception when the template passed to "use" is not a string + * made 'a.b is defined' not throw an exception if a is not defined (in strict mode) + * added {% line \d+ %} directive + +# 1.1.0-RC1 (2011-05-28) + +Flush your cache after upgrading. + + * fixed date filter when using a timestamp + * fixed the defined test for some cases + * fixed a parsing problem when a large chunk of text is enclosed in a raw tag + * added support for horizontal reuse of template blocks (see docs for more information) + * added whitespace control modifier to all tags (see docs for more information) + * added null as an alias for none (the null test is also an alias for the none test now) + * made TRUE, FALSE, NONE equivalent to their lowercase counterparts + * wrapped all compilation and runtime exceptions with Twig_Error_Runtime and added logic to guess the template name and line + * moved display() method to Twig_Template (generated templates should now use doDisplay() instead) + +# 1.0.0 (2011-03-27) + + * fixed output when using mbstring + * fixed duplicate call of methods when using the sandbox + * made the charset configurable for the escape filter + +# 1.0.0-RC2 (2011-02-21) + + * changed the way {% set %} works when capturing (the content is now marked as safe) + * added support for macro name in the endmacro tag + * make Twig_Error compatible with PHP 5.3.0 > + * fixed an infinite loop on some Windows configurations + * fixed the "length" filter for numbers + * fixed Template::getAttribute() as properties in PHP are case sensitive + * removed coupling between Twig_Node and Twig_Template + * fixed the ternary operator precedence rule + +# 1.0.0-RC1 (2011-01-09) + +Backward incompatibilities: + + * the "items" filter, which has been deprecated for quite a long time now, has been removed + * the "range" filter has been converted to a function: 0|range(10) -> range(0, 10) + * the "constant" filter has been converted to a function: {{ some_date|date('DATE_W3C'|constant) }} -> {{ some_date|date(constant('DATE_W3C')) }} + * the "cycle" filter has been converted to a function: {{ ['odd', 'even']|cycle(i) }} -> {{ cycle(['odd', 'even'], i) }} + * the "for" tag does not support "joined by" anymore + * the "autoescape" first argument is now "true"/"false" (instead of "on"/"off") + * the "parent" tag has been replaced by a "parent" function ({{ parent() }} instead of {% parent %}) + * the "display" tag has been replaced by a "block" function ({{ block('title') }} instead of {% display title %}) + * removed the grammar and simple token parser (moved to the Twig Extensions repository) + +Changes: + + * added "needs_context" option for filters and functions (the context is then passed as a first argument) + * added global variables support + * made macros return their value instead of echoing directly (fixes calling a macro in sandbox mode) + * added the "from" tag to import macros as functions + * added support for functions (a function is just syntactic sugar for a getAttribute() call) + * made macros callable when sandbox mode is enabled + * added an exception when a macro uses a reserved name + * the "default" filter now uses the "empty" test instead of just checking for null + * added the "empty" test + +# 0.9.10 (2010-12-16) + +Backward incompatibilities: + + * The Escaper extension is enabled by default, which means that all displayed + variables are now automatically escaped. You can revert to the previous + behavior by removing the extension via $env->removeExtension('escaper') + or just set the 'autoescape' option to 'false'. + * removed the "without loop" attribute for the "for" tag (not needed anymore + as the Optimizer take care of that for most cases) + * arrays and hashes have now a different syntax + * arrays keep the same syntax with square brackets: [1, 2] + * hashes now use curly braces (["a": "b"] should now be written as {"a": "b"}) + * support for "arrays with keys" and "hashes without keys" is not supported anymore ([1, "foo": "bar"] or {"foo": "bar", 1}) + * the i18n extension is now part of the Twig Extensions repository + +Changes: + + * added the merge filter + * removed 'is_escaper' option for filters (a left over from the previous version) -- you must use 'is_safe' now instead + * fixed usage of operators as method names (like is, in, and not) + * changed the order of execution for node visitors + * fixed default() filter behavior when used with strict_variables set to on + * fixed filesystem loader compatibility with PHAR files + * enhanced error messages when an unexpected token is parsed in an expression + * fixed filename not being added to syntax error messages + * added the autoescape option to enable/disable autoescaping + * removed the newline after a comment (mimics PHP behavior) + * added a syntax error exception when parent block is used on a template that does not extend another one + * made the Escaper extension enabled by default + * fixed sandbox extension when used with auto output escaping + * fixed escaper when wrapping a Twig_Node_Print (the original class must be preserved) + * added an Optimizer extension (enabled by default; optimizes "for" loops and "raw" filters) + * added priority to node visitors + +# 0.9.9 (2010-11-28) + +Backward incompatibilities: + * the self special variable has been renamed to _self + * the odd and even filters are now tests: + {{ foo|odd }} must now be written {{ foo is odd }} + * the "safe" filter has been renamed to "raw" + * in Node classes, + sub-nodes are now accessed via getNode() (instead of property access) + attributes via getAttribute() (instead of array access) + * the urlencode filter had been renamed to url_encode + * the include tag now merges the passed variables with the current context by default + (the old behavior is still possible by adding the "only" keyword) + * moved Exceptions to Twig_Error_* (Twig_SyntaxError/Twig_RuntimeError are now Twig_Error_Syntax/Twig_Error_Runtime) + * removed support for {{ 1 < i < 3 }} (use {{ i > 1 and i < 3 }} instead) + * the "in" filter has been removed ({{ a|in(b) }} should now be written {{ a in b }}) + +Changes: + * added file and line to Twig_Error_Runtime exceptions thrown from Twig_Template + * changed trans tag to accept any variable for the plural count + * fixed sandbox mode (__toString() method check was not enforced if called implicitly from complex statements) + * added the ** (power) operator + * changed the algorithm used for parsing expressions + * added the spaceless tag + * removed trim_blocks option + * added support for is*() methods for attributes (foo.bar now looks for foo->getBar() or foo->isBar()) + * changed all exceptions to extend Twig_Error + * fixed unary expressions ({{ not(1 or 0) }}) + * fixed child templates (with an extend tag) that uses one or more imports + * added support for {{ 1 not in [2, 3] }} (more readable than the current {{ not (1 in [2, 3]) }}) + * escaping has been rewritten + * the implementation of template inheritance has been rewritten + (blocks can now be called individually and still work with inheritance) + * fixed error handling for if tag when a syntax error occurs within a subparse process + * added a way to implement custom logic for resolving token parsers given a tag name + * fixed js escaper to be stricter (now uses a whilelist-based js escaper) + * added the following filers: "constant", "trans", "replace", "json_encode" + * added a "constant" test + * fixed objects with __toString() not being autoescaped + * fixed subscript expressions when calling __call() (methods now keep the case) + * added "test" feature (accessible via the "is" operator) + * removed the debug tag (should be done in an extension) + * fixed trans tag when no vars are used in plural form + * fixed race condition when writing template cache + * added the special _charset variable to reference the current charset + * added the special _context variable to reference the current context + * renamed self to _self (to avoid conflict) + * fixed Twig_Template::getAttribute() for protected properties + +# 0.9.8 (2010-06-28) + +Backward incompatibilities: + * the trans tag plural count is now attached to the plural tag: + old: `{% trans count %}...{% plural %}...{% endtrans %}` + new: `{% trans %}...{% plural count %}...{% endtrans %}` + + * added a way to translate strings coming from a variable ({% trans var %}) + * fixed trans tag when used with the Escaper extension + * fixed default cache umask + * removed Twig_Template instances from the debug tag output + * fixed objects with __isset() defined + * fixed set tag when used with a capture + * fixed type hinting for Twig_Environment::addFilter() method + +# 0.9.7 (2010-06-12) + +Backward incompatibilities: + * changed 'as' to '=' for the set tag ({% set title as "Title" %} must now be {% set title = "Title" %}) + * removed the sandboxed attribute of the include tag (use the new sandbox tag instead) + * refactored the Node system (if you have custom nodes, you will have to update them to use the new API) + + * added self as a special variable that refers to the current template (useful for importing macros from the current template) + * added Twig_Template instance support to the include tag + * added support for dynamic and conditional inheritance ({% extends some_var %} and {% extends standalone ? "minimum" : "base" %}) + * added a grammar sub-framework to ease the creation of custom tags + * fixed the for tag for large arrays (some loop variables are now only available for arrays and objects that implement the Countable interface) + * removed the Twig_Resource::resolveMissingFilter() method + * fixed the filter tag which did not apply filtering to included files + * added a bunch of unit tests + * added a bunch of phpdoc + * added a sandbox tag in the sandbox extension + * changed the date filter to support any date format supported by DateTime + * added strict_variable setting to throw an exception when an invalid variable is used in a template (disabled by default) + * added the lexer, parser, and compiler as arguments to the Twig_Environment constructor + * changed the cache option to only accepts an explicit path to a cache directory or false + * added a way to add token parsers, filters, and visitors without creating an extension + * added three interfaces: Twig_NodeInterface, Twig_TokenParserInterface, and Twig_FilterInterface + * changed the generated code to match the new coding standards + * fixed sandbox mode (__toString() method check was not enforced if called implicitly from a simple statement like {{ article }}) + * added an exception when a child template has a non-empty body (as it is always ignored when rendering) + +# 0.9.6 (2010-05-12) + + * fixed variables defined outside a loop and for which the value changes in a for loop + * fixed the test suite for PHP 5.2 and older versions of PHPUnit + * added support for __call() in expression resolution + * fixed node visiting for macros (macros are now visited by visitors as any other node) + * fixed nested block definitions with a parent call (rarely useful but nonetheless supported now) + * added the cycle filter + * fixed the Lexer when mbstring.func_overload is used with an mbstring.internal_encoding different from ASCII + * added a long-syntax for the set tag ({% set foo %}...{% endset %}) + * unit tests are now powered by PHPUnit + * added support for gettext via the `i18n` extension + * fixed twig_capitalize_string_filter() and fixed twig_length_filter() when used with UTF-8 values + * added a more useful exception if an if tag is not closed properly + * added support for escaping strategy in the autoescape tag + * fixed lexer when a template has a big chunk of text between/in a block + +# 0.9.5 (2010-01-20) + +As for any new release, don't forget to remove all cached templates after +upgrading. + +If you have defined custom filters, you MUST upgrade them for this release. To +upgrade, replace "array" with "new Twig_Filter_Function", and replace the +environment constant by the "needs_environment" option: + + // before + 'even' => array('twig_is_even_filter', false), + 'escape' => array('twig_escape_filter', true), + + // after + 'even' => new Twig_Filter_Function('twig_is_even_filter'), + 'escape' => new Twig_Filter_Function('twig_escape_filter', array('needs_environment' => true)), + +If you have created NodeTransformer classes, you will need to upgrade them to +the new interface (please note that the interface is not yet considered +stable). + + * fixed list nodes that did not extend the Twig_NodeListInterface + * added the "without loop" option to the for tag (it disables the generation of the loop variable) + * refactored node transformers to node visitors + * fixed automatic-escaping for blocks + * added a way to specify variables to pass to an included template + * changed the automatic-escaping rules to be more sensible and more configurable in custom filters (the documentation lists all the rules) + * improved the filter system to allow object methods to be used as filters + * changed the Array and String loaders to actually make use of the cache mechanism + * included the default filter function definitions in the extension class files directly (Core, Escaper) + * added the // operator (like the floor() PHP function) + * added the .. operator (as a syntactic sugar for the range filter when the step is 1) + * added the in operator (as a syntactic sugar for the in filter) + * added the following filters in the Core extension: in, range + * added support for arrays (same behavior as in PHP, a mix between lists and dictionaries, arrays and hashes) + * enhanced some error messages to provide better feedback in case of parsing errors + +# 0.9.4 (2009-12-02) + +If you have custom loaders, you MUST upgrade them for this release: The +Twig_Loader base class has been removed, and the Twig_LoaderInterface has also +been changed (see the source code for more information or the documentation). + + * added support for DateTime instances for the date filter + * fixed loop.last when the array only has one item + * made it possible to insert newlines in tag and variable blocks + * fixed a bug when a literal '\n' were present in a template text + * fixed bug when the filename of a template contains */ + * refactored loaders + +# 0.9.3 (2009-11-11) + +This release is NOT backward compatible with the previous releases. + + The loaders do not take the cache and autoReload arguments anymore. Instead, + the Twig_Environment class has two new options: cache and auto_reload. + Upgrading your code means changing this kind of code: + + $loader = new Twig_Loader_Filesystem('/path/to/templates', '/path/to/compilation_cache', true); + $twig = new Twig_Environment($loader); + + to something like this: + + $loader = new Twig_Loader_Filesystem('/path/to/templates'); + $twig = new Twig_Environment($loader, array( + 'cache' => '/path/to/compilation_cache', + 'auto_reload' => true, + )); + + * deprecated the "items" filter as it is not needed anymore + * made cache and auto_reload options of Twig_Environment instead of arguments of Twig_Loader + * optimized template loading speed + * removed output when an error occurs in a template and render() is used + * made major speed improvements for loops (up to 300% on even the smallest loops) + * added properties as part of the sandbox mode + * added public properties support (obj.item can now be the item property on the obj object) + * extended set tag to support expression as value ({% set foo as 'foo' ~ 'bar' %} ) + * fixed bug when \ was used in HTML + +# 0.9.2 (2009-10-29) + + * made some speed optimizations + * changed the cache extension to .php + * added a js escaping strategy + * added support for short block tag + * changed the filter tag to allow chained filters + * made lexer more flexible as you can now change the default delimiters + * added set tag + * changed default directory permission when cache dir does not exist (more secure) + * added macro support + * changed filters first optional argument to be a Twig_Environment instance instead of a Twig_Template instance + * made Twig_Autoloader::autoload() a static method + * avoid writing template file if an error occurs + * added $ escaping when outputting raw strings + * enhanced some error messages to ease debugging + * fixed empty cache files when the template contains an error + +# 0.9.1 (2009-10-14) + + * fixed a bug in PHP 5.2.6 + * fixed numbers with one than one decimal + * added support for method calls with arguments ({{ foo.bar('a', 43) }}) + * made small speed optimizations + * made minor tweaks to allow better extensibility and flexibility + +# 0.9.0 (2009-10-12) + + * Initial release diff --git a/site/plugins/kirby-twig/vendor/twig/twig/README.rst b/site/plugins/kirby-twig/vendor/twig/twig/README.rst new file mode 100644 index 0000000..d896ff5 --- /dev/null +++ b/site/plugins/kirby-twig/vendor/twig/twig/README.rst @@ -0,0 +1,24 @@ +Twig, the flexible, fast, and secure template language for PHP +============================================================== + +Twig is a template language for PHP, released under the new BSD license (code +and documentation). + +Twig uses a syntax similar to the Django and Jinja template languages which +inspired the Twig runtime environment. + +Sponsors +-------- + +.. raw:: html + + + Blackfire.io + + +More Information +---------------- + +Read the `documentation`_ for more information. + +.. _documentation: https://twig.symfony.com/documentation diff --git a/site/plugins/kirby-twig/vendor/twig/twig/drupal_test.sh b/site/plugins/kirby-twig/vendor/twig/twig/drupal_test.sh new file mode 100755 index 0000000..a25d886 --- /dev/null +++ b/site/plugins/kirby-twig/vendor/twig/twig/drupal_test.sh @@ -0,0 +1,50 @@ +#!/bin/bash + +set -x +set -e + +REPO=`pwd` +cd /tmp +rm -rf drupal-twig-test +composer create-project --no-interaction drupal/recommended-project:9.1.x-dev drupal-twig-test +cd drupal-twig-test +(cd vendor/twig && rm -rf twig && ln -sf $REPO twig) +php ./web/core/scripts/drupal install --no-interaction demo_umami > output +perl -p -i -e 's/^([A-Za-z]+)\: (.+)$/export DRUPAL_\1=\2/' output +source output +#echo '$config["system.logging"]["error_level"] = "verbose";' >> web/sites/default/settings.php + +wget https://get.symfony.com/cli/installer -O - | bash +export PATH="$HOME/.symfony/bin:$PATH" +symfony server:start -d --no-tls + +curl -OLsS https://get.blackfire.io/blackfire-player.phar +chmod +x blackfire-player.phar +cat > drupal-tests.bkf < + */ +interface CacheInterface +{ + /** + * Generates a cache key for the given template class name. + */ + public function generateKey(string $name, string $className): string; + + /** + * Writes the compiled template to cache. + * + * @param string $content The template representation as a PHP class + */ + public function write(string $key, string $content): void; + + /** + * Loads a template from the cache. + */ + public function load(string $key): void; + + /** + * Returns the modification timestamp of a key. + */ + public function getTimestamp(string $key): int; +} diff --git a/site/plugins/kirby-twig/vendor/twig/twig/src/Cache/FilesystemCache.php b/site/plugins/kirby-twig/vendor/twig/twig/src/Cache/FilesystemCache.php new file mode 100644 index 0000000..abb2358 --- /dev/null +++ b/site/plugins/kirby-twig/vendor/twig/twig/src/Cache/FilesystemCache.php @@ -0,0 +1,87 @@ + + */ +class FilesystemCache implements CacheInterface +{ + const FORCE_BYTECODE_INVALIDATION = 1; + + private $directory; + private $options; + + public function __construct(string $directory, int $options = 0) + { + $this->directory = rtrim($directory, '\/').'/'; + $this->options = $options; + } + + public function generateKey(string $name, string $className): string + { + $hash = hash('sha256', $className); + + return $this->directory.$hash[0].$hash[1].'/'.$hash.'.php'; + } + + public function load(string $key): void + { + if (is_file($key)) { + @include_once $key; + } + } + + public function write(string $key, string $content): void + { + $dir = \dirname($key); + if (!is_dir($dir)) { + if (false === @mkdir($dir, 0777, true)) { + clearstatcache(true, $dir); + if (!is_dir($dir)) { + throw new \RuntimeException(sprintf('Unable to create the cache directory (%s).', $dir)); + } + } + } elseif (!is_writable($dir)) { + throw new \RuntimeException(sprintf('Unable to write in the cache directory (%s).', $dir)); + } + + $tmpFile = tempnam($dir, basename($key)); + if (false !== @file_put_contents($tmpFile, $content) && @rename($tmpFile, $key)) { + @chmod($key, 0666 & ~umask()); + + if (self::FORCE_BYTECODE_INVALIDATION == ($this->options & self::FORCE_BYTECODE_INVALIDATION)) { + // Compile cached file into bytecode cache + if (\function_exists('opcache_invalidate') && filter_var(ini_get('opcache.enable'), FILTER_VALIDATE_BOOLEAN)) { + @opcache_invalidate($key, true); + } elseif (\function_exists('apc_compile_file')) { + apc_compile_file($key); + } + } + + return; + } + + throw new \RuntimeException(sprintf('Failed to write cache file "%s".', $key)); + } + + public function getTimestamp(string $key): int + { + if (!is_file($key)) { + return 0; + } + + return (int) @filemtime($key); + } +} diff --git a/site/plugins/kirby-twig/vendor/twig/twig/src/Cache/NullCache.php b/site/plugins/kirby-twig/vendor/twig/twig/src/Cache/NullCache.php new file mode 100644 index 0000000..8d20d59 --- /dev/null +++ b/site/plugins/kirby-twig/vendor/twig/twig/src/Cache/NullCache.php @@ -0,0 +1,38 @@ + + */ +final class NullCache implements CacheInterface +{ + public function generateKey(string $name, string $className): string + { + return ''; + } + + public function write(string $key, string $content): void + { + } + + public function load(string $key): void + { + } + + public function getTimestamp(string $key): int + { + return 0; + } +} diff --git a/site/plugins/kirby-twig/vendor/twig/twig/src/Compiler.php b/site/plugins/kirby-twig/vendor/twig/twig/src/Compiler.php new file mode 100644 index 0000000..7754c08 --- /dev/null +++ b/site/plugins/kirby-twig/vendor/twig/twig/src/Compiler.php @@ -0,0 +1,214 @@ + + */ +class Compiler +{ + private $lastLine; + private $source; + private $indentation; + private $env; + private $debugInfo = []; + private $sourceOffset; + private $sourceLine; + private $varNameSalt = 0; + + public function __construct(Environment $env) + { + $this->env = $env; + } + + public function getEnvironment(): Environment + { + return $this->env; + } + + public function getSource(): string + { + return $this->source; + } + + /** + * @return $this + */ + public function compile(Node $node, int $indentation = 0) + { + $this->lastLine = null; + $this->source = ''; + $this->debugInfo = []; + $this->sourceOffset = 0; + // source code starts at 1 (as we then increment it when we encounter new lines) + $this->sourceLine = 1; + $this->indentation = $indentation; + $this->varNameSalt = 0; + + $node->compile($this); + + return $this; + } + + /** + * @return $this + */ + public function subcompile(Node $node, bool $raw = true) + { + if (false === $raw) { + $this->source .= str_repeat(' ', $this->indentation * 4); + } + + $node->compile($this); + + return $this; + } + + /** + * Adds a raw string to the compiled code. + * + * @return $this + */ + public function raw(string $string) + { + $this->source .= $string; + + return $this; + } + + /** + * Writes a string to the compiled code by adding indentation. + * + * @return $this + */ + public function write(...$strings) + { + foreach ($strings as $string) { + $this->source .= str_repeat(' ', $this->indentation * 4).$string; + } + + return $this; + } + + /** + * Adds a quoted string to the compiled code. + * + * @return $this + */ + public function string(string $value) + { + $this->source .= sprintf('"%s"', addcslashes($value, "\0\t\"\$\\")); + + return $this; + } + + /** + * Returns a PHP representation of a given value. + * + * @return $this + */ + public function repr($value) + { + if (\is_int($value) || \is_float($value)) { + if (false !== $locale = setlocale(LC_NUMERIC, '0')) { + setlocale(LC_NUMERIC, 'C'); + } + + $this->raw(var_export($value, true)); + + if (false !== $locale) { + setlocale(LC_NUMERIC, $locale); + } + } elseif (null === $value) { + $this->raw('null'); + } elseif (\is_bool($value)) { + $this->raw($value ? 'true' : 'false'); + } elseif (\is_array($value)) { + $this->raw('array('); + $first = true; + foreach ($value as $key => $v) { + if (!$first) { + $this->raw(', '); + } + $first = false; + $this->repr($key); + $this->raw(' => '); + $this->repr($v); + } + $this->raw(')'); + } else { + $this->string($value); + } + + return $this; + } + + /** + * @return $this + */ + public function addDebugInfo(Node $node) + { + if ($node->getTemplateLine() != $this->lastLine) { + $this->write(sprintf("// line %d\n", $node->getTemplateLine())); + + $this->sourceLine += substr_count($this->source, "\n", $this->sourceOffset); + $this->sourceOffset = \strlen($this->source); + $this->debugInfo[$this->sourceLine] = $node->getTemplateLine(); + + $this->lastLine = $node->getTemplateLine(); + } + + return $this; + } + + public function getDebugInfo(): array + { + ksort($this->debugInfo); + + return $this->debugInfo; + } + + /** + * @return $this + */ + public function indent(int $step = 1) + { + $this->indentation += $step; + + return $this; + } + + /** + * @return $this + * + * @throws \LogicException When trying to outdent too much so the indentation would become negative + */ + public function outdent(int $step = 1) + { + // can't outdent by more steps than the current indentation level + if ($this->indentation < $step) { + throw new \LogicException('Unable to call outdent() as the indentation would become negative.'); + } + + $this->indentation -= $step; + + return $this; + } + + public function getVarName(): string + { + return sprintf('__internal_%s', hash('sha256', __METHOD__.$this->varNameSalt++)); + } +} diff --git a/site/plugins/kirby-twig/vendor/twig/twig/src/Environment.php b/site/plugins/kirby-twig/vendor/twig/twig/src/Environment.php new file mode 100644 index 0000000..fae5bc9 --- /dev/null +++ b/site/plugins/kirby-twig/vendor/twig/twig/src/Environment.php @@ -0,0 +1,814 @@ + + */ +class Environment +{ + const VERSION = '3.3.0'; + const VERSION_ID = 30300; + const MAJOR_VERSION = 3; + const MINOR_VERSION = 3; + const RELEASE_VERSION = 0; + const EXTRA_VERSION = ''; + + private $charset; + private $loader; + private $debug; + private $autoReload; + private $cache; + private $lexer; + private $parser; + private $compiler; + private $globals = []; + private $resolvedGlobals; + private $loadedTemplates; + private $strictVariables; + private $templateClassPrefix = '__TwigTemplate_'; + private $originalCache; + private $extensionSet; + private $runtimeLoaders = []; + private $runtimes = []; + private $optionsHash; + + /** + * Constructor. + * + * Available options: + * + * * debug: When set to true, it automatically set "auto_reload" to true as + * well (default to false). + * + * * charset: The charset used by the templates (default to UTF-8). + * + * * cache: An absolute path where to store the compiled templates, + * a \Twig\Cache\CacheInterface implementation, + * or false to disable compilation cache (default). + * + * * auto_reload: Whether to reload the template if the original source changed. + * If you don't provide the auto_reload option, it will be + * determined automatically based on the debug value. + * + * * strict_variables: Whether to ignore invalid variables in templates + * (default to false). + * + * * autoescape: Whether to enable auto-escaping (default to html): + * * false: disable auto-escaping + * * html, js: set the autoescaping to one of the supported strategies + * * name: set the autoescaping strategy based on the template name extension + * * PHP callback: a PHP callback that returns an escaping strategy based on the template "name" + * + * * optimizations: A flag that indicates which optimizations to apply + * (default to -1 which means that all optimizations are enabled; + * set it to 0 to disable). + */ + public function __construct(LoaderInterface $loader, $options = []) + { + $this->setLoader($loader); + + $options = array_merge([ + 'debug' => false, + 'charset' => 'UTF-8', + 'strict_variables' => false, + 'autoescape' => 'html', + 'cache' => false, + 'auto_reload' => null, + 'optimizations' => -1, + ], $options); + + $this->debug = (bool) $options['debug']; + $this->setCharset($options['charset'] ?? 'UTF-8'); + $this->autoReload = null === $options['auto_reload'] ? $this->debug : (bool) $options['auto_reload']; + $this->strictVariables = (bool) $options['strict_variables']; + $this->setCache($options['cache']); + $this->extensionSet = new ExtensionSet(); + + $this->addExtension(new CoreExtension()); + $this->addExtension(new EscaperExtension($options['autoescape'])); + $this->addExtension(new OptimizerExtension($options['optimizations'])); + } + + /** + * Enables debugging mode. + */ + public function enableDebug() + { + $this->debug = true; + $this->updateOptionsHash(); + } + + /** + * Disables debugging mode. + */ + public function disableDebug() + { + $this->debug = false; + $this->updateOptionsHash(); + } + + /** + * Checks if debug mode is enabled. + * + * @return bool true if debug mode is enabled, false otherwise + */ + public function isDebug() + { + return $this->debug; + } + + /** + * Enables the auto_reload option. + */ + public function enableAutoReload() + { + $this->autoReload = true; + } + + /** + * Disables the auto_reload option. + */ + public function disableAutoReload() + { + $this->autoReload = false; + } + + /** + * Checks if the auto_reload option is enabled. + * + * @return bool true if auto_reload is enabled, false otherwise + */ + public function isAutoReload() + { + return $this->autoReload; + } + + /** + * Enables the strict_variables option. + */ + public function enableStrictVariables() + { + $this->strictVariables = true; + $this->updateOptionsHash(); + } + + /** + * Disables the strict_variables option. + */ + public function disableStrictVariables() + { + $this->strictVariables = false; + $this->updateOptionsHash(); + } + + /** + * Checks if the strict_variables option is enabled. + * + * @return bool true if strict_variables is enabled, false otherwise + */ + public function isStrictVariables() + { + return $this->strictVariables; + } + + /** + * Gets the current cache implementation. + * + * @param bool $original Whether to return the original cache option or the real cache instance + * + * @return CacheInterface|string|false A Twig\Cache\CacheInterface implementation, + * an absolute path to the compiled templates, + * or false to disable cache + */ + public function getCache($original = true) + { + return $original ? $this->originalCache : $this->cache; + } + + /** + * Sets the current cache implementation. + * + * @param CacheInterface|string|false $cache A Twig\Cache\CacheInterface implementation, + * an absolute path to the compiled templates, + * or false to disable cache + */ + public function setCache($cache) + { + if (\is_string($cache)) { + $this->originalCache = $cache; + $this->cache = new FilesystemCache($cache); + } elseif (false === $cache) { + $this->originalCache = $cache; + $this->cache = new NullCache(); + } elseif ($cache instanceof CacheInterface) { + $this->originalCache = $this->cache = $cache; + } else { + throw new \LogicException(sprintf('Cache can only be a string, false, or a \Twig\Cache\CacheInterface implementation.')); + } + } + + /** + * Gets the template class associated with the given string. + * + * The generated template class is based on the following parameters: + * + * * The cache key for the given template; + * * The currently enabled extensions; + * * Whether the Twig C extension is available or not; + * * PHP version; + * * Twig version; + * * Options with what environment was created. + * + * @param string $name The name for which to calculate the template class name + * @param int|null $index The index if it is an embedded template + * + * @internal + */ + public function getTemplateClass(string $name, int $index = null): string + { + $key = $this->getLoader()->getCacheKey($name).$this->optionsHash; + + return $this->templateClassPrefix.hash('sha256', $key).(null === $index ? '' : '___'.$index); + } + + /** + * Renders a template. + * + * @param string|TemplateWrapper $name The template name + * + * @throws LoaderError When the template cannot be found + * @throws SyntaxError When an error occurred during compilation + * @throws RuntimeError When an error occurred during rendering + */ + public function render($name, array $context = []): string + { + return $this->load($name)->render($context); + } + + /** + * Displays a template. + * + * @param string|TemplateWrapper $name The template name + * + * @throws LoaderError When the template cannot be found + * @throws SyntaxError When an error occurred during compilation + * @throws RuntimeError When an error occurred during rendering + */ + public function display($name, array $context = []): void + { + $this->load($name)->display($context); + } + + /** + * Loads a template. + * + * @param string|TemplateWrapper $name The template name + * + * @throws LoaderError When the template cannot be found + * @throws RuntimeError When a previously generated cache is corrupted + * @throws SyntaxError When an error occurred during compilation + */ + public function load($name): TemplateWrapper + { + if ($name instanceof TemplateWrapper) { + return $name; + } + + return new TemplateWrapper($this, $this->loadTemplate($this->getTemplateClass($name), $name)); + } + + /** + * Loads a template internal representation. + * + * This method is for internal use only and should never be called + * directly. + * + * @param string $name The template name + * @param int $index The index if it is an embedded template + * + * @throws LoaderError When the template cannot be found + * @throws RuntimeError When a previously generated cache is corrupted + * @throws SyntaxError When an error occurred during compilation + * + * @internal + */ + public function loadTemplate(string $cls, string $name, int $index = null): Template + { + $mainCls = $cls; + if (null !== $index) { + $cls .= '___'.$index; + } + + if (isset($this->loadedTemplates[$cls])) { + return $this->loadedTemplates[$cls]; + } + + if (!class_exists($cls, false)) { + $key = $this->cache->generateKey($name, $mainCls); + + if (!$this->isAutoReload() || $this->isTemplateFresh($name, $this->cache->getTimestamp($key))) { + $this->cache->load($key); + } + + $source = null; + if (!class_exists($cls, false)) { + $source = $this->getLoader()->getSourceContext($name); + $content = $this->compileSource($source); + $this->cache->write($key, $content); + $this->cache->load($key); + + if (!class_exists($mainCls, false)) { + /* Last line of defense if either $this->bcWriteCacheFile was used, + * $this->cache is implemented as a no-op or we have a race condition + * where the cache was cleared between the above calls to write to and load from + * the cache. + */ + eval('?>'.$content); + } + + if (!class_exists($cls, false)) { + throw new RuntimeError(sprintf('Failed to load Twig template "%s", index "%s": cache might be corrupted.', $name, $index), -1, $source); + } + } + } + + $this->extensionSet->initRuntime(); + + return $this->loadedTemplates[$cls] = new $cls($this); + } + + /** + * Creates a template from source. + * + * This method should not be used as a generic way to load templates. + * + * @param string $template The template source + * @param string $name An optional name of the template to be used in error messages + * + * @throws LoaderError When the template cannot be found + * @throws SyntaxError When an error occurred during compilation + */ + public function createTemplate(string $template, string $name = null): TemplateWrapper + { + $hash = hash('sha256', $template, false); + if (null !== $name) { + $name = sprintf('%s (string template %s)', $name, $hash); + } else { + $name = sprintf('__string_template__%s', $hash); + } + + $loader = new ChainLoader([ + new ArrayLoader([$name => $template]), + $current = $this->getLoader(), + ]); + + $this->setLoader($loader); + try { + return new TemplateWrapper($this, $this->loadTemplate($this->getTemplateClass($name), $name)); + } finally { + $this->setLoader($current); + } + } + + /** + * Returns true if the template is still fresh. + * + * Besides checking the loader for freshness information, + * this method also checks if the enabled extensions have + * not changed. + * + * @param int $time The last modification time of the cached template + */ + public function isTemplateFresh(string $name, int $time): bool + { + return $this->extensionSet->getLastModified() <= $time && $this->getLoader()->isFresh($name, $time); + } + + /** + * Tries to load a template consecutively from an array. + * + * Similar to load() but it also accepts instances of \Twig\Template and + * \Twig\TemplateWrapper, and an array of templates where each is tried to be loaded. + * + * @param string|TemplateWrapper|array $names A template or an array of templates to try consecutively + * + * @throws LoaderError When none of the templates can be found + * @throws SyntaxError When an error occurred during compilation + */ + public function resolveTemplate($names): TemplateWrapper + { + if (!\is_array($names)) { + return $this->load($names); + } + + foreach ($names as $name) { + try { + return $this->load($name); + } catch (LoaderError $e) { + } + } + + throw new LoaderError(sprintf('Unable to find one of the following templates: "%s".', implode('", "', $names))); + } + + public function setLexer(Lexer $lexer) + { + $this->lexer = $lexer; + } + + /** + * @throws SyntaxError When the code is syntactically wrong + */ + public function tokenize(Source $source): TokenStream + { + if (null === $this->lexer) { + $this->lexer = new Lexer($this); + } + + return $this->lexer->tokenize($source); + } + + public function setParser(Parser $parser) + { + $this->parser = $parser; + } + + /** + * Converts a token stream to a node tree. + * + * @throws SyntaxError When the token stream is syntactically or semantically wrong + */ + public function parse(TokenStream $stream): ModuleNode + { + if (null === $this->parser) { + $this->parser = new Parser($this); + } + + return $this->parser->parse($stream); + } + + public function setCompiler(Compiler $compiler) + { + $this->compiler = $compiler; + } + + /** + * Compiles a node and returns the PHP code. + */ + public function compile(Node $node): string + { + if (null === $this->compiler) { + $this->compiler = new Compiler($this); + } + + return $this->compiler->compile($node)->getSource(); + } + + /** + * Compiles a template source code. + * + * @throws SyntaxError When there was an error during tokenizing, parsing or compiling + */ + public function compileSource(Source $source): string + { + try { + return $this->compile($this->parse($this->tokenize($source))); + } catch (Error $e) { + $e->setSourceContext($source); + throw $e; + } catch (\Exception $e) { + throw new SyntaxError(sprintf('An exception has been thrown during the compilation of a template ("%s").', $e->getMessage()), -1, $source, $e); + } + } + + public function setLoader(LoaderInterface $loader) + { + $this->loader = $loader; + } + + public function getLoader(): LoaderInterface + { + return $this->loader; + } + + public function setCharset(string $charset) + { + if ('UTF8' === $charset = strtoupper($charset)) { + // iconv on Windows requires "UTF-8" instead of "UTF8" + $charset = 'UTF-8'; + } + + $this->charset = $charset; + } + + public function getCharset(): string + { + return $this->charset; + } + + public function hasExtension(string $class): bool + { + return $this->extensionSet->hasExtension($class); + } + + public function addRuntimeLoader(RuntimeLoaderInterface $loader) + { + $this->runtimeLoaders[] = $loader; + } + + public function getExtension(string $class): ExtensionInterface + { + return $this->extensionSet->getExtension($class); + } + + /** + * Returns the runtime implementation of a Twig element (filter/function/tag/test). + * + * @param string $class A runtime class name + * + * @return object The runtime implementation + * + * @throws RuntimeError When the template cannot be found + */ + public function getRuntime(string $class) + { + if (isset($this->runtimes[$class])) { + return $this->runtimes[$class]; + } + + foreach ($this->runtimeLoaders as $loader) { + if (null !== $runtime = $loader->load($class)) { + return $this->runtimes[$class] = $runtime; + } + } + + throw new RuntimeError(sprintf('Unable to load the "%s" runtime.', $class)); + } + + public function addExtension(ExtensionInterface $extension) + { + $this->extensionSet->addExtension($extension); + $this->updateOptionsHash(); + } + + /** + * @param ExtensionInterface[] $extensions An array of extensions + */ + public function setExtensions(array $extensions) + { + $this->extensionSet->setExtensions($extensions); + $this->updateOptionsHash(); + } + + /** + * @return ExtensionInterface[] An array of extensions (keys are for internal usage only and should not be relied on) + */ + public function getExtensions(): array + { + return $this->extensionSet->getExtensions(); + } + + public function addTokenParser(TokenParserInterface $parser) + { + $this->extensionSet->addTokenParser($parser); + } + + /** + * @return TokenParserInterface[] + * + * @internal + */ + public function getTokenParsers(): array + { + return $this->extensionSet->getTokenParsers(); + } + + /** + * @internal + */ + public function getTokenParser(string $name): ?TokenParserInterface + { + return $this->extensionSet->getTokenParser($name); + } + + public function registerUndefinedTokenParserCallback(callable $callable): void + { + $this->extensionSet->registerUndefinedTokenParserCallback($callable); + } + + public function addNodeVisitor(NodeVisitorInterface $visitor) + { + $this->extensionSet->addNodeVisitor($visitor); + } + + /** + * @return NodeVisitorInterface[] + * + * @internal + */ + public function getNodeVisitors(): array + { + return $this->extensionSet->getNodeVisitors(); + } + + public function addFilter(TwigFilter $filter) + { + $this->extensionSet->addFilter($filter); + } + + /** + * @internal + */ + public function getFilter(string $name): ?TwigFilter + { + return $this->extensionSet->getFilter($name); + } + + public function registerUndefinedFilterCallback(callable $callable): void + { + $this->extensionSet->registerUndefinedFilterCallback($callable); + } + + /** + * Gets the registered Filters. + * + * Be warned that this method cannot return filters defined with registerUndefinedFilterCallback. + * + * @return TwigFilter[] + * + * @see registerUndefinedFilterCallback + * + * @internal + */ + public function getFilters(): array + { + return $this->extensionSet->getFilters(); + } + + public function addTest(TwigTest $test) + { + $this->extensionSet->addTest($test); + } + + /** + * @return TwigTest[] + * + * @internal + */ + public function getTests(): array + { + return $this->extensionSet->getTests(); + } + + /** + * @internal + */ + public function getTest(string $name): ?TwigTest + { + return $this->extensionSet->getTest($name); + } + + public function addFunction(TwigFunction $function) + { + $this->extensionSet->addFunction($function); + } + + /** + * @internal + */ + public function getFunction(string $name): ?TwigFunction + { + return $this->extensionSet->getFunction($name); + } + + public function registerUndefinedFunctionCallback(callable $callable): void + { + $this->extensionSet->registerUndefinedFunctionCallback($callable); + } + + /** + * Gets registered functions. + * + * Be warned that this method cannot return functions defined with registerUndefinedFunctionCallback. + * + * @return TwigFunction[] + * + * @see registerUndefinedFunctionCallback + * + * @internal + */ + public function getFunctions(): array + { + return $this->extensionSet->getFunctions(); + } + + /** + * Registers a Global. + * + * New globals can be added before compiling or rendering a template; + * but after, you can only update existing globals. + * + * @param mixed $value The global value + */ + public function addGlobal(string $name, $value) + { + if ($this->extensionSet->isInitialized() && !\array_key_exists($name, $this->getGlobals())) { + throw new \LogicException(sprintf('Unable to add global "%s" as the runtime or the extensions have already been initialized.', $name)); + } + + if (null !== $this->resolvedGlobals) { + $this->resolvedGlobals[$name] = $value; + } else { + $this->globals[$name] = $value; + } + } + + /** + * @internal + */ + public function getGlobals(): array + { + if ($this->extensionSet->isInitialized()) { + if (null === $this->resolvedGlobals) { + $this->resolvedGlobals = array_merge($this->extensionSet->getGlobals(), $this->globals); + } + + return $this->resolvedGlobals; + } + + return array_merge($this->extensionSet->getGlobals(), $this->globals); + } + + public function mergeGlobals(array $context): array + { + // we don't use array_merge as the context being generally + // bigger than globals, this code is faster. + foreach ($this->getGlobals() as $key => $value) { + if (!\array_key_exists($key, $context)) { + $context[$key] = $value; + } + } + + return $context; + } + + /** + * @internal + */ + public function getUnaryOperators(): array + { + return $this->extensionSet->getUnaryOperators(); + } + + /** + * @internal + */ + public function getBinaryOperators(): array + { + return $this->extensionSet->getBinaryOperators(); + } + + private function updateOptionsHash(): void + { + $this->optionsHash = implode(':', [ + $this->extensionSet->getSignature(), + PHP_MAJOR_VERSION, + PHP_MINOR_VERSION, + self::VERSION, + (int) $this->debug, + (int) $this->strictVariables, + ]); + } +} diff --git a/site/plugins/kirby-twig/vendor/twig/twig/src/Error/Error.php b/site/plugins/kirby-twig/vendor/twig/twig/src/Error/Error.php new file mode 100644 index 0000000..d8a30c5 --- /dev/null +++ b/site/plugins/kirby-twig/vendor/twig/twig/src/Error/Error.php @@ -0,0 +1,227 @@ + + */ +class Error extends \Exception +{ + private $lineno; + private $name; + private $rawMessage; + private $sourcePath; + private $sourceCode; + + /** + * Constructor. + * + * By default, automatic guessing is enabled. + * + * @param string $message The error message + * @param int $lineno The template line where the error occurred + * @param Source|null $source The source context where the error occurred + */ + public function __construct(string $message, int $lineno = -1, Source $source = null, \Exception $previous = null) + { + parent::__construct('', 0, $previous); + + if (null === $source) { + $name = null; + } else { + $name = $source->getName(); + $this->sourceCode = $source->getCode(); + $this->sourcePath = $source->getPath(); + } + + $this->lineno = $lineno; + $this->name = $name; + $this->rawMessage = $message; + $this->updateRepr(); + } + + public function getRawMessage(): string + { + return $this->rawMessage; + } + + public function getTemplateLine(): int + { + return $this->lineno; + } + + public function setTemplateLine(int $lineno): void + { + $this->lineno = $lineno; + + $this->updateRepr(); + } + + public function getSourceContext(): ?Source + { + return $this->name ? new Source($this->sourceCode, $this->name, $this->sourcePath) : null; + } + + public function setSourceContext(Source $source = null): void + { + if (null === $source) { + $this->sourceCode = $this->name = $this->sourcePath = null; + } else { + $this->sourceCode = $source->getCode(); + $this->name = $source->getName(); + $this->sourcePath = $source->getPath(); + } + + $this->updateRepr(); + } + + public function guess(): void + { + $this->guessTemplateInfo(); + $this->updateRepr(); + } + + public function appendMessage($rawMessage): void + { + $this->rawMessage .= $rawMessage; + $this->updateRepr(); + } + + private function updateRepr(): void + { + $this->message = $this->rawMessage; + + if ($this->sourcePath && $this->lineno > 0) { + $this->file = $this->sourcePath; + $this->line = $this->lineno; + + return; + } + + $dot = false; + if ('.' === substr($this->message, -1)) { + $this->message = substr($this->message, 0, -1); + $dot = true; + } + + $questionMark = false; + if ('?' === substr($this->message, -1)) { + $this->message = substr($this->message, 0, -1); + $questionMark = true; + } + + if ($this->name) { + if (\is_string($this->name) || (\is_object($this->name) && method_exists($this->name, '__toString'))) { + $name = sprintf('"%s"', $this->name); + } else { + $name = json_encode($this->name); + } + $this->message .= sprintf(' in %s', $name); + } + + if ($this->lineno && $this->lineno >= 0) { + $this->message .= sprintf(' at line %d', $this->lineno); + } + + if ($dot) { + $this->message .= '.'; + } + + if ($questionMark) { + $this->message .= '?'; + } + } + + private function guessTemplateInfo(): void + { + $template = null; + $templateClass = null; + + $backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS | DEBUG_BACKTRACE_PROVIDE_OBJECT); + foreach ($backtrace as $trace) { + if (isset($trace['object']) && $trace['object'] instanceof Template) { + $currentClass = \get_class($trace['object']); + $isEmbedContainer = 0 === strpos($templateClass, $currentClass); + if (null === $this->name || ($this->name == $trace['object']->getTemplateName() && !$isEmbedContainer)) { + $template = $trace['object']; + $templateClass = \get_class($trace['object']); + } + } + } + + // update template name + if (null !== $template && null === $this->name) { + $this->name = $template->getTemplateName(); + } + + // update template path if any + if (null !== $template && null === $this->sourcePath) { + $src = $template->getSourceContext(); + $this->sourceCode = $src->getCode(); + $this->sourcePath = $src->getPath(); + } + + if (null === $template || $this->lineno > -1) { + return; + } + + $r = new \ReflectionObject($template); + $file = $r->getFileName(); + + $exceptions = [$e = $this]; + while ($e = $e->getPrevious()) { + $exceptions[] = $e; + } + + while ($e = array_pop($exceptions)) { + $traces = $e->getTrace(); + array_unshift($traces, ['file' => $e->getFile(), 'line' => $e->getLine()]); + + while ($trace = array_shift($traces)) { + if (!isset($trace['file']) || !isset($trace['line']) || $file != $trace['file']) { + continue; + } + + foreach ($template->getDebugInfo() as $codeLine => $templateLine) { + if ($codeLine <= $trace['line']) { + // update template line + $this->lineno = $templateLine; + + return; + } + } + } + } + } +} diff --git a/site/plugins/kirby-twig/vendor/twig/twig/src/Error/LoaderError.php b/site/plugins/kirby-twig/vendor/twig/twig/src/Error/LoaderError.php new file mode 100644 index 0000000..7c8c23c --- /dev/null +++ b/site/plugins/kirby-twig/vendor/twig/twig/src/Error/LoaderError.php @@ -0,0 +1,21 @@ + + */ +class LoaderError extends Error +{ +} diff --git a/site/plugins/kirby-twig/vendor/twig/twig/src/Error/RuntimeError.php b/site/plugins/kirby-twig/vendor/twig/twig/src/Error/RuntimeError.php new file mode 100644 index 0000000..f6b8476 --- /dev/null +++ b/site/plugins/kirby-twig/vendor/twig/twig/src/Error/RuntimeError.php @@ -0,0 +1,22 @@ + + */ +class RuntimeError extends Error +{ +} diff --git a/site/plugins/kirby-twig/vendor/twig/twig/src/Error/SyntaxError.php b/site/plugins/kirby-twig/vendor/twig/twig/src/Error/SyntaxError.php new file mode 100644 index 0000000..726b330 --- /dev/null +++ b/site/plugins/kirby-twig/vendor/twig/twig/src/Error/SyntaxError.php @@ -0,0 +1,46 @@ + + */ +class SyntaxError extends Error +{ + /** + * Tweaks the error message to include suggestions. + * + * @param string $name The original name of the item that does not exist + * @param array $items An array of possible items + */ + public function addSuggestions(string $name, array $items): void + { + $alternatives = []; + foreach ($items as $item) { + $lev = levenshtein($name, $item); + if ($lev <= \strlen($name) / 3 || false !== strpos($item, $name)) { + $alternatives[$item] = $lev; + } + } + + if (!$alternatives) { + return; + } + + asort($alternatives); + + $this->appendMessage(sprintf(' Did you mean "%s"?', implode('", "', array_keys($alternatives)))); + } +} diff --git a/site/plugins/kirby-twig/vendor/twig/twig/src/ExpressionParser.php b/site/plugins/kirby-twig/vendor/twig/twig/src/ExpressionParser.php new file mode 100644 index 0000000..243c7f6 --- /dev/null +++ b/site/plugins/kirby-twig/vendor/twig/twig/src/ExpressionParser.php @@ -0,0 +1,823 @@ + + * + * @internal + */ +class ExpressionParser +{ + const OPERATOR_LEFT = 1; + const OPERATOR_RIGHT = 2; + + private $parser; + private $env; + private $unaryOperators; + private $binaryOperators; + + public function __construct(Parser $parser, Environment $env) + { + $this->parser = $parser; + $this->env = $env; + $this->unaryOperators = $env->getUnaryOperators(); + $this->binaryOperators = $env->getBinaryOperators(); + } + + public function parseExpression($precedence = 0, $allowArrow = false) + { + if ($allowArrow && $arrow = $this->parseArrow()) { + return $arrow; + } + + $expr = $this->getPrimary(); + $token = $this->parser->getCurrentToken(); + while ($this->isBinary($token) && $this->binaryOperators[$token->getValue()]['precedence'] >= $precedence) { + $op = $this->binaryOperators[$token->getValue()]; + $this->parser->getStream()->next(); + + if ('is not' === $token->getValue()) { + $expr = $this->parseNotTestExpression($expr); + } elseif ('is' === $token->getValue()) { + $expr = $this->parseTestExpression($expr); + } elseif (isset($op['callable'])) { + $expr = $op['callable']($this->parser, $expr); + } else { + $expr1 = $this->parseExpression(self::OPERATOR_LEFT === $op['associativity'] ? $op['precedence'] + 1 : $op['precedence']); + $class = $op['class']; + $expr = new $class($expr, $expr1, $token->getLine()); + } + + $token = $this->parser->getCurrentToken(); + } + + if (0 === $precedence) { + return $this->parseConditionalExpression($expr); + } + + return $expr; + } + + /** + * @return ArrowFunctionExpression|null + */ + private function parseArrow() + { + $stream = $this->parser->getStream(); + + // short array syntax (one argument, no parentheses)? + if ($stream->look(1)->test(/* Token::ARROW_TYPE */ 12)) { + $line = $stream->getCurrent()->getLine(); + $token = $stream->expect(/* Token::NAME_TYPE */ 5); + $names = [new AssignNameExpression($token->getValue(), $token->getLine())]; + $stream->expect(/* Token::ARROW_TYPE */ 12); + + return new ArrowFunctionExpression($this->parseExpression(0), new Node($names), $line); + } + + // first, determine if we are parsing an arrow function by finding => (long form) + $i = 0; + if (!$stream->look($i)->test(/* Token::PUNCTUATION_TYPE */ 9, '(')) { + return null; + } + ++$i; + while (true) { + // variable name + ++$i; + if (!$stream->look($i)->test(/* Token::PUNCTUATION_TYPE */ 9, ',')) { + break; + } + ++$i; + } + if (!$stream->look($i)->test(/* Token::PUNCTUATION_TYPE */ 9, ')')) { + return null; + } + ++$i; + if (!$stream->look($i)->test(/* Token::ARROW_TYPE */ 12)) { + return null; + } + + // yes, let's parse it properly + $token = $stream->expect(/* Token::PUNCTUATION_TYPE */ 9, '('); + $line = $token->getLine(); + + $names = []; + while (true) { + $token = $stream->expect(/* Token::NAME_TYPE */ 5); + $names[] = new AssignNameExpression($token->getValue(), $token->getLine()); + + if (!$stream->nextIf(/* Token::PUNCTUATION_TYPE */ 9, ',')) { + break; + } + } + $stream->expect(/* Token::PUNCTUATION_TYPE */ 9, ')'); + $stream->expect(/* Token::ARROW_TYPE */ 12); + + return new ArrowFunctionExpression($this->parseExpression(0), new Node($names), $line); + } + + private function getPrimary(): AbstractExpression + { + $token = $this->parser->getCurrentToken(); + + if ($this->isUnary($token)) { + $operator = $this->unaryOperators[$token->getValue()]; + $this->parser->getStream()->next(); + $expr = $this->parseExpression($operator['precedence']); + $class = $operator['class']; + + return $this->parsePostfixExpression(new $class($expr, $token->getLine())); + } elseif ($token->test(/* Token::PUNCTUATION_TYPE */ 9, '(')) { + $this->parser->getStream()->next(); + $expr = $this->parseExpression(); + $this->parser->getStream()->expect(/* Token::PUNCTUATION_TYPE */ 9, ')', 'An opened parenthesis is not properly closed'); + + return $this->parsePostfixExpression($expr); + } + + return $this->parsePrimaryExpression(); + } + + private function parseConditionalExpression($expr): AbstractExpression + { + while ($this->parser->getStream()->nextIf(/* Token::PUNCTUATION_TYPE */ 9, '?')) { + if (!$this->parser->getStream()->nextIf(/* Token::PUNCTUATION_TYPE */ 9, ':')) { + $expr2 = $this->parseExpression(); + if ($this->parser->getStream()->nextIf(/* Token::PUNCTUATION_TYPE */ 9, ':')) { + $expr3 = $this->parseExpression(); + } else { + $expr3 = new ConstantExpression('', $this->parser->getCurrentToken()->getLine()); + } + } else { + $expr2 = $expr; + $expr3 = $this->parseExpression(); + } + + $expr = new ConditionalExpression($expr, $expr2, $expr3, $this->parser->getCurrentToken()->getLine()); + } + + return $expr; + } + + private function isUnary(Token $token): bool + { + return $token->test(/* Token::OPERATOR_TYPE */ 8) && isset($this->unaryOperators[$token->getValue()]); + } + + private function isBinary(Token $token): bool + { + return $token->test(/* Token::OPERATOR_TYPE */ 8) && isset($this->binaryOperators[$token->getValue()]); + } + + public function parsePrimaryExpression() + { + $token = $this->parser->getCurrentToken(); + switch ($token->getType()) { + case /* Token::NAME_TYPE */ 5: + $this->parser->getStream()->next(); + switch ($token->getValue()) { + case 'true': + case 'TRUE': + $node = new ConstantExpression(true, $token->getLine()); + break; + + case 'false': + case 'FALSE': + $node = new ConstantExpression(false, $token->getLine()); + break; + + case 'none': + case 'NONE': + case 'null': + case 'NULL': + $node = new ConstantExpression(null, $token->getLine()); + break; + + default: + if ('(' === $this->parser->getCurrentToken()->getValue()) { + $node = $this->getFunctionNode($token->getValue(), $token->getLine()); + } else { + $node = new NameExpression($token->getValue(), $token->getLine()); + } + } + break; + + case /* Token::NUMBER_TYPE */ 6: + $this->parser->getStream()->next(); + $node = new ConstantExpression($token->getValue(), $token->getLine()); + break; + + case /* Token::STRING_TYPE */ 7: + case /* Token::INTERPOLATION_START_TYPE */ 10: + $node = $this->parseStringExpression(); + break; + + case /* Token::OPERATOR_TYPE */ 8: + if (preg_match(Lexer::REGEX_NAME, $token->getValue(), $matches) && $matches[0] == $token->getValue()) { + // in this context, string operators are variable names + $this->parser->getStream()->next(); + $node = new NameExpression($token->getValue(), $token->getLine()); + break; + } elseif (isset($this->unaryOperators[$token->getValue()])) { + $class = $this->unaryOperators[$token->getValue()]['class']; + if (!\in_array($class, [NegUnary::class, PosUnary::class])) { + throw new SyntaxError(sprintf('Unexpected unary operator "%s".', $token->getValue()), $token->getLine(), $this->parser->getStream()->getSourceContext()); + } + + $this->parser->getStream()->next(); + $expr = $this->parsePrimaryExpression(); + + $node = new $class($expr, $token->getLine()); + break; + } + + // no break + default: + if ($token->test(/* Token::PUNCTUATION_TYPE */ 9, '[')) { + $node = $this->parseArrayExpression(); + } elseif ($token->test(/* Token::PUNCTUATION_TYPE */ 9, '{')) { + $node = $this->parseHashExpression(); + } elseif ($token->test(/* Token::OPERATOR_TYPE */ 8, '=') && ('==' === $this->parser->getStream()->look(-1)->getValue() || '!=' === $this->parser->getStream()->look(-1)->getValue())) { + throw new SyntaxError(sprintf('Unexpected operator of value "%s". Did you try to use "===" or "!==" for strict comparison? Use "is same as(value)" instead.', $token->getValue()), $token->getLine(), $this->parser->getStream()->getSourceContext()); + } else { + throw new SyntaxError(sprintf('Unexpected token "%s" of value "%s".', Token::typeToEnglish($token->getType()), $token->getValue()), $token->getLine(), $this->parser->getStream()->getSourceContext()); + } + } + + return $this->parsePostfixExpression($node); + } + + public function parseStringExpression() + { + $stream = $this->parser->getStream(); + + $nodes = []; + // a string cannot be followed by another string in a single expression + $nextCanBeString = true; + while (true) { + if ($nextCanBeString && $token = $stream->nextIf(/* Token::STRING_TYPE */ 7)) { + $nodes[] = new ConstantExpression($token->getValue(), $token->getLine()); + $nextCanBeString = false; + } elseif ($stream->nextIf(/* Token::INTERPOLATION_START_TYPE */ 10)) { + $nodes[] = $this->parseExpression(); + $stream->expect(/* Token::INTERPOLATION_END_TYPE */ 11); + $nextCanBeString = true; + } else { + break; + } + } + + $expr = array_shift($nodes); + foreach ($nodes as $node) { + $expr = new ConcatBinary($expr, $node, $node->getTemplateLine()); + } + + return $expr; + } + + public function parseArrayExpression() + { + $stream = $this->parser->getStream(); + $stream->expect(/* Token::PUNCTUATION_TYPE */ 9, '[', 'An array element was expected'); + + $node = new ArrayExpression([], $stream->getCurrent()->getLine()); + $first = true; + while (!$stream->test(/* Token::PUNCTUATION_TYPE */ 9, ']')) { + if (!$first) { + $stream->expect(/* Token::PUNCTUATION_TYPE */ 9, ',', 'An array element must be followed by a comma'); + + // trailing ,? + if ($stream->test(/* Token::PUNCTUATION_TYPE */ 9, ']')) { + break; + } + } + $first = false; + + $node->addElement($this->parseExpression()); + } + $stream->expect(/* Token::PUNCTUATION_TYPE */ 9, ']', 'An opened array is not properly closed'); + + return $node; + } + + public function parseHashExpression() + { + $stream = $this->parser->getStream(); + $stream->expect(/* Token::PUNCTUATION_TYPE */ 9, '{', 'A hash element was expected'); + + $node = new ArrayExpression([], $stream->getCurrent()->getLine()); + $first = true; + while (!$stream->test(/* Token::PUNCTUATION_TYPE */ 9, '}')) { + if (!$first) { + $stream->expect(/* Token::PUNCTUATION_TYPE */ 9, ',', 'A hash value must be followed by a comma'); + + // trailing ,? + if ($stream->test(/* Token::PUNCTUATION_TYPE */ 9, '}')) { + break; + } + } + $first = false; + + // a hash key can be: + // + // * a number -- 12 + // * a string -- 'a' + // * a name, which is equivalent to a string -- a + // * an expression, which must be enclosed in parentheses -- (1 + 2) + if ($token = $stream->nextIf(/* Token::NAME_TYPE */ 5)) { + $key = new ConstantExpression($token->getValue(), $token->getLine()); + + // {a} is a shortcut for {a:a} + if ($stream->test(Token::PUNCTUATION_TYPE, [',', '}'])) { + $value = new NameExpression($key->getAttribute('value'), $key->getTemplateLine()); + $node->addElement($value, $key); + continue; + } + } elseif (($token = $stream->nextIf(/* Token::STRING_TYPE */ 7)) || $token = $stream->nextIf(/* Token::NUMBER_TYPE */ 6)) { + $key = new ConstantExpression($token->getValue(), $token->getLine()); + } elseif ($stream->test(/* Token::PUNCTUATION_TYPE */ 9, '(')) { + $key = $this->parseExpression(); + } else { + $current = $stream->getCurrent(); + + throw new SyntaxError(sprintf('A hash key must be a quoted string, a number, a name, or an expression enclosed in parentheses (unexpected token "%s" of value "%s".', Token::typeToEnglish($current->getType()), $current->getValue()), $current->getLine(), $stream->getSourceContext()); + } + + $stream->expect(/* Token::PUNCTUATION_TYPE */ 9, ':', 'A hash key must be followed by a colon (:)'); + $value = $this->parseExpression(); + + $node->addElement($value, $key); + } + $stream->expect(/* Token::PUNCTUATION_TYPE */ 9, '}', 'An opened hash is not properly closed'); + + return $node; + } + + public function parsePostfixExpression($node) + { + while (true) { + $token = $this->parser->getCurrentToken(); + if (/* Token::PUNCTUATION_TYPE */ 9 == $token->getType()) { + if ('.' == $token->getValue() || '[' == $token->getValue()) { + $node = $this->parseSubscriptExpression($node); + } elseif ('|' == $token->getValue()) { + $node = $this->parseFilterExpression($node); + } else { + break; + } + } else { + break; + } + } + + return $node; + } + + public function getFunctionNode($name, $line) + { + switch ($name) { + case 'parent': + $this->parseArguments(); + if (!\count($this->parser->getBlockStack())) { + throw new SyntaxError('Calling "parent" outside a block is forbidden.', $line, $this->parser->getStream()->getSourceContext()); + } + + if (!$this->parser->getParent() && !$this->parser->hasTraits()) { + throw new SyntaxError('Calling "parent" on a template that does not extend nor "use" another template is forbidden.', $line, $this->parser->getStream()->getSourceContext()); + } + + return new ParentExpression($this->parser->peekBlockStack(), $line); + case 'block': + $args = $this->parseArguments(); + if (\count($args) < 1) { + throw new SyntaxError('The "block" function takes one argument (the block name).', $line, $this->parser->getStream()->getSourceContext()); + } + + return new BlockReferenceExpression($args->getNode(0), \count($args) > 1 ? $args->getNode(1) : null, $line); + case 'attribute': + $args = $this->parseArguments(); + if (\count($args) < 2) { + throw new SyntaxError('The "attribute" function takes at least two arguments (the variable and the attributes).', $line, $this->parser->getStream()->getSourceContext()); + } + + return new GetAttrExpression($args->getNode(0), $args->getNode(1), \count($args) > 2 ? $args->getNode(2) : null, Template::ANY_CALL, $line); + default: + if (null !== $alias = $this->parser->getImportedSymbol('function', $name)) { + $arguments = new ArrayExpression([], $line); + foreach ($this->parseArguments() as $n) { + $arguments->addElement($n); + } + + $node = new MethodCallExpression($alias['node'], $alias['name'], $arguments, $line); + $node->setAttribute('safe', true); + + return $node; + } + + $args = $this->parseArguments(true); + $class = $this->getFunctionNodeClass($name, $line); + + return new $class($name, $args, $line); + } + } + + public function parseSubscriptExpression($node) + { + $stream = $this->parser->getStream(); + $token = $stream->next(); + $lineno = $token->getLine(); + $arguments = new ArrayExpression([], $lineno); + $type = Template::ANY_CALL; + if ('.' == $token->getValue()) { + $token = $stream->next(); + if ( + /* Token::NAME_TYPE */ 5 == $token->getType() + || + /* Token::NUMBER_TYPE */ 6 == $token->getType() + || + (/* Token::OPERATOR_TYPE */ 8 == $token->getType() && preg_match(Lexer::REGEX_NAME, $token->getValue())) + ) { + $arg = new ConstantExpression($token->getValue(), $lineno); + + if ($stream->test(/* Token::PUNCTUATION_TYPE */ 9, '(')) { + $type = Template::METHOD_CALL; + foreach ($this->parseArguments() as $n) { + $arguments->addElement($n); + } + } + } else { + throw new SyntaxError('Expected name or number.', $lineno, $stream->getSourceContext()); + } + + if ($node instanceof NameExpression && null !== $this->parser->getImportedSymbol('template', $node->getAttribute('name'))) { + if (!$arg instanceof ConstantExpression) { + throw new SyntaxError(sprintf('Dynamic macro names are not supported (called on "%s").', $node->getAttribute('name')), $token->getLine(), $stream->getSourceContext()); + } + + $name = $arg->getAttribute('value'); + + $node = new MethodCallExpression($node, 'macro_'.$name, $arguments, $lineno); + $node->setAttribute('safe', true); + + return $node; + } + } else { + $type = Template::ARRAY_CALL; + + // slice? + $slice = false; + if ($stream->test(/* Token::PUNCTUATION_TYPE */ 9, ':')) { + $slice = true; + $arg = new ConstantExpression(0, $token->getLine()); + } else { + $arg = $this->parseExpression(); + } + + if ($stream->nextIf(/* Token::PUNCTUATION_TYPE */ 9, ':')) { + $slice = true; + } + + if ($slice) { + if ($stream->test(/* Token::PUNCTUATION_TYPE */ 9, ']')) { + $length = new ConstantExpression(null, $token->getLine()); + } else { + $length = $this->parseExpression(); + } + + $class = $this->getFilterNodeClass('slice', $token->getLine()); + $arguments = new Node([$arg, $length]); + $filter = new $class($node, new ConstantExpression('slice', $token->getLine()), $arguments, $token->getLine()); + + $stream->expect(/* Token::PUNCTUATION_TYPE */ 9, ']'); + + return $filter; + } + + $stream->expect(/* Token::PUNCTUATION_TYPE */ 9, ']'); + } + + return new GetAttrExpression($node, $arg, $arguments, $type, $lineno); + } + + public function parseFilterExpression($node) + { + $this->parser->getStream()->next(); + + return $this->parseFilterExpressionRaw($node); + } + + public function parseFilterExpressionRaw($node, $tag = null) + { + while (true) { + $token = $this->parser->getStream()->expect(/* Token::NAME_TYPE */ 5); + + $name = new ConstantExpression($token->getValue(), $token->getLine()); + if (!$this->parser->getStream()->test(/* Token::PUNCTUATION_TYPE */ 9, '(')) { + $arguments = new Node(); + } else { + $arguments = $this->parseArguments(true, false, true); + } + + $class = $this->getFilterNodeClass($name->getAttribute('value'), $token->getLine()); + + $node = new $class($node, $name, $arguments, $token->getLine(), $tag); + + if (!$this->parser->getStream()->test(/* Token::PUNCTUATION_TYPE */ 9, '|')) { + break; + } + + $this->parser->getStream()->next(); + } + + return $node; + } + + /** + * Parses arguments. + * + * @param bool $namedArguments Whether to allow named arguments or not + * @param bool $definition Whether we are parsing arguments for a function definition + * + * @return Node + * + * @throws SyntaxError + */ + public function parseArguments($namedArguments = false, $definition = false, $allowArrow = false) + { + $args = []; + $stream = $this->parser->getStream(); + + $stream->expect(/* Token::PUNCTUATION_TYPE */ 9, '(', 'A list of arguments must begin with an opening parenthesis'); + while (!$stream->test(/* Token::PUNCTUATION_TYPE */ 9, ')')) { + if (!empty($args)) { + $stream->expect(/* Token::PUNCTUATION_TYPE */ 9, ',', 'Arguments must be separated by a comma'); + + // if the comma above was a trailing comma, early exit the argument parse loop + if ($stream->test(/* Token::PUNCTUATION_TYPE */ 9, ')')) { + break; + } + } + + if ($definition) { + $token = $stream->expect(/* Token::NAME_TYPE */ 5, null, 'An argument must be a name'); + $value = new NameExpression($token->getValue(), $this->parser->getCurrentToken()->getLine()); + } else { + $value = $this->parseExpression(0, $allowArrow); + } + + $name = null; + if ($namedArguments && $token = $stream->nextIf(/* Token::OPERATOR_TYPE */ 8, '=')) { + if (!$value instanceof NameExpression) { + throw new SyntaxError(sprintf('A parameter name must be a string, "%s" given.', \get_class($value)), $token->getLine(), $stream->getSourceContext()); + } + $name = $value->getAttribute('name'); + + if ($definition) { + $value = $this->parsePrimaryExpression(); + + if (!$this->checkConstantExpression($value)) { + throw new SyntaxError(sprintf('A default value for an argument must be a constant (a boolean, a string, a number, or an array).'), $token->getLine(), $stream->getSourceContext()); + } + } else { + $value = $this->parseExpression(0, $allowArrow); + } + } + + if ($definition) { + if (null === $name) { + $name = $value->getAttribute('name'); + $value = new ConstantExpression(null, $this->parser->getCurrentToken()->getLine()); + } + $args[$name] = $value; + } else { + if (null === $name) { + $args[] = $value; + } else { + $args[$name] = $value; + } + } + } + $stream->expect(/* Token::PUNCTUATION_TYPE */ 9, ')', 'A list of arguments must be closed by a parenthesis'); + + return new Node($args); + } + + public function parseAssignmentExpression() + { + $stream = $this->parser->getStream(); + $targets = []; + while (true) { + $token = $this->parser->getCurrentToken(); + if ($stream->test(/* Token::OPERATOR_TYPE */ 8) && preg_match(Lexer::REGEX_NAME, $token->getValue())) { + // in this context, string operators are variable names + $this->parser->getStream()->next(); + } else { + $stream->expect(/* Token::NAME_TYPE */ 5, null, 'Only variables can be assigned to'); + } + $value = $token->getValue(); + if (\in_array(strtr($value, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz'), ['true', 'false', 'none', 'null'])) { + throw new SyntaxError(sprintf('You cannot assign a value to "%s".', $value), $token->getLine(), $stream->getSourceContext()); + } + $targets[] = new AssignNameExpression($value, $token->getLine()); + + if (!$stream->nextIf(/* Token::PUNCTUATION_TYPE */ 9, ',')) { + break; + } + } + + return new Node($targets); + } + + public function parseMultitargetExpression() + { + $targets = []; + while (true) { + $targets[] = $this->parseExpression(); + if (!$this->parser->getStream()->nextIf(/* Token::PUNCTUATION_TYPE */ 9, ',')) { + break; + } + } + + return new Node($targets); + } + + private function parseNotTestExpression(Node $node): NotUnary + { + return new NotUnary($this->parseTestExpression($node), $this->parser->getCurrentToken()->getLine()); + } + + private function parseTestExpression(Node $node): TestExpression + { + $stream = $this->parser->getStream(); + list($name, $test) = $this->getTest($node->getTemplateLine()); + + $class = $this->getTestNodeClass($test); + $arguments = null; + if ($stream->test(/* Token::PUNCTUATION_TYPE */ 9, '(')) { + $arguments = $this->parseArguments(true); + } elseif ($test->hasOneMandatoryArgument()) { + $arguments = new Node([0 => $this->parsePrimaryExpression()]); + } + + if ('defined' === $name && $node instanceof NameExpression && null !== $alias = $this->parser->getImportedSymbol('function', $node->getAttribute('name'))) { + $node = new MethodCallExpression($alias['node'], $alias['name'], new ArrayExpression([], $node->getTemplateLine()), $node->getTemplateLine()); + $node->setAttribute('safe', true); + } + + return new $class($node, $name, $arguments, $this->parser->getCurrentToken()->getLine()); + } + + private function getTest(int $line): array + { + $stream = $this->parser->getStream(); + $name = $stream->expect(/* Token::NAME_TYPE */ 5)->getValue(); + + if ($test = $this->env->getTest($name)) { + return [$name, $test]; + } + + if ($stream->test(/* Token::NAME_TYPE */ 5)) { + // try 2-words tests + $name = $name.' '.$this->parser->getCurrentToken()->getValue(); + + if ($test = $this->env->getTest($name)) { + $stream->next(); + + return [$name, $test]; + } + } + + $e = new SyntaxError(sprintf('Unknown "%s" test.', $name), $line, $stream->getSourceContext()); + $e->addSuggestions($name, array_keys($this->env->getTests())); + + throw $e; + } + + private function getTestNodeClass(TwigTest $test): string + { + if ($test->isDeprecated()) { + $stream = $this->parser->getStream(); + $message = sprintf('Twig Test "%s" is deprecated', $test->getName()); + + if ($test->getDeprecatedVersion()) { + $message .= sprintf(' since version %s', $test->getDeprecatedVersion()); + } + if ($test->getAlternative()) { + $message .= sprintf('. Use "%s" instead', $test->getAlternative()); + } + $src = $stream->getSourceContext(); + $message .= sprintf(' in %s at line %d.', $src->getPath() ?: $src->getName(), $stream->getCurrent()->getLine()); + + @trigger_error($message, E_USER_DEPRECATED); + } + + return $test->getNodeClass(); + } + + private function getFunctionNodeClass(string $name, int $line): string + { + if (!$function = $this->env->getFunction($name)) { + $e = new SyntaxError(sprintf('Unknown "%s" function.', $name), $line, $this->parser->getStream()->getSourceContext()); + $e->addSuggestions($name, array_keys($this->env->getFunctions())); + + throw $e; + } + + if ($function->isDeprecated()) { + $message = sprintf('Twig Function "%s" is deprecated', $function->getName()); + if ($function->getDeprecatedVersion()) { + $message .= sprintf(' since version %s', $function->getDeprecatedVersion()); + } + if ($function->getAlternative()) { + $message .= sprintf('. Use "%s" instead', $function->getAlternative()); + } + $src = $this->parser->getStream()->getSourceContext(); + $message .= sprintf(' in %s at line %d.', $src->getPath() ?: $src->getName(), $line); + + @trigger_error($message, E_USER_DEPRECATED); + } + + return $function->getNodeClass(); + } + + private function getFilterNodeClass(string $name, int $line): string + { + if (!$filter = $this->env->getFilter($name)) { + $e = new SyntaxError(sprintf('Unknown "%s" filter.', $name), $line, $this->parser->getStream()->getSourceContext()); + $e->addSuggestions($name, array_keys($this->env->getFilters())); + + throw $e; + } + + if ($filter->isDeprecated()) { + $message = sprintf('Twig Filter "%s" is deprecated', $filter->getName()); + if ($filter->getDeprecatedVersion()) { + $message .= sprintf(' since version %s', $filter->getDeprecatedVersion()); + } + if ($filter->getAlternative()) { + $message .= sprintf('. Use "%s" instead', $filter->getAlternative()); + } + $src = $this->parser->getStream()->getSourceContext(); + $message .= sprintf(' in %s at line %d.', $src->getPath() ?: $src->getName(), $line); + + @trigger_error($message, E_USER_DEPRECATED); + } + + return $filter->getNodeClass(); + } + + // checks that the node only contains "constant" elements + private function checkConstantExpression(Node $node): bool + { + if (!($node instanceof ConstantExpression || $node instanceof ArrayExpression + || $node instanceof NegUnary || $node instanceof PosUnary + )) { + return false; + } + + foreach ($node as $n) { + if (!$this->checkConstantExpression($n)) { + return false; + } + } + + return true; + } +} diff --git a/site/plugins/kirby-twig/vendor/twig/twig/src/Extension/AbstractExtension.php b/site/plugins/kirby-twig/vendor/twig/twig/src/Extension/AbstractExtension.php new file mode 100644 index 0000000..422925f --- /dev/null +++ b/site/plugins/kirby-twig/vendor/twig/twig/src/Extension/AbstractExtension.php @@ -0,0 +1,45 @@ +dateFormats[0] = $format; + } + + if (null !== $dateIntervalFormat) { + $this->dateFormats[1] = $dateIntervalFormat; + } + } + + /** + * Gets the default format to be used by the date filter. + * + * @return array The default date format string and the default date interval format string + */ + public function getDateFormat() + { + return $this->dateFormats; + } + + /** + * Sets the default timezone to be used by the date filter. + * + * @param \DateTimeZone|string $timezone The default timezone string or a \DateTimeZone object + */ + public function setTimezone($timezone) + { + $this->timezone = $timezone instanceof \DateTimeZone ? $timezone : new \DateTimeZone($timezone); + } + + /** + * Gets the default timezone to be used by the date filter. + * + * @return \DateTimeZone The default timezone currently in use + */ + public function getTimezone() + { + if (null === $this->timezone) { + $this->timezone = new \DateTimeZone(date_default_timezone_get()); + } + + return $this->timezone; + } + + /** + * Sets the default format to be used by the number_format filter. + * + * @param int $decimal the number of decimal places to use + * @param string $decimalPoint the character(s) to use for the decimal point + * @param string $thousandSep the character(s) to use for the thousands separator + */ + public function setNumberFormat($decimal, $decimalPoint, $thousandSep) + { + $this->numberFormat = [$decimal, $decimalPoint, $thousandSep]; + } + + /** + * Get the default format used by the number_format filter. + * + * @return array The arguments for number_format() + */ + public function getNumberFormat() + { + return $this->numberFormat; + } + + public function getTokenParsers(): array + { + return [ + new ApplyTokenParser(), + new ForTokenParser(), + new IfTokenParser(), + new ExtendsTokenParser(), + new IncludeTokenParser(), + new BlockTokenParser(), + new UseTokenParser(), + new MacroTokenParser(), + new ImportTokenParser(), + new FromTokenParser(), + new SetTokenParser(), + new FlushTokenParser(), + new DoTokenParser(), + new EmbedTokenParser(), + new WithTokenParser(), + new DeprecatedTokenParser(), + ]; + } + + public function getFilters(): array + { + return [ + // formatting filters + new TwigFilter('date', 'twig_date_format_filter', ['needs_environment' => true]), + new TwigFilter('date_modify', 'twig_date_modify_filter', ['needs_environment' => true]), + new TwigFilter('format', 'sprintf'), + new TwigFilter('replace', 'twig_replace_filter'), + new TwigFilter('number_format', 'twig_number_format_filter', ['needs_environment' => true]), + new TwigFilter('abs', 'abs'), + new TwigFilter('round', 'twig_round'), + + // encoding + new TwigFilter('url_encode', 'twig_urlencode_filter'), + new TwigFilter('json_encode', 'json_encode'), + new TwigFilter('convert_encoding', 'twig_convert_encoding'), + + // string filters + new TwigFilter('title', 'twig_title_string_filter', ['needs_environment' => true]), + new TwigFilter('capitalize', 'twig_capitalize_string_filter', ['needs_environment' => true]), + new TwigFilter('upper', 'twig_upper_filter', ['needs_environment' => true]), + new TwigFilter('lower', 'twig_lower_filter', ['needs_environment' => true]), + new TwigFilter('striptags', 'strip_tags'), + new TwigFilter('trim', 'twig_trim_filter'), + new TwigFilter('nl2br', 'nl2br', ['pre_escape' => 'html', 'is_safe' => ['html']]), + new TwigFilter('spaceless', 'twig_spaceless', ['is_safe' => ['html']]), + + // array helpers + new TwigFilter('join', 'twig_join_filter'), + new TwigFilter('split', 'twig_split_filter', ['needs_environment' => true]), + new TwigFilter('sort', 'twig_sort_filter'), + new TwigFilter('merge', 'twig_array_merge'), + new TwigFilter('batch', 'twig_array_batch'), + new TwigFilter('column', 'twig_array_column'), + new TwigFilter('filter', 'twig_array_filter', ['needs_environment' => true]), + new TwigFilter('map', 'twig_array_map', ['needs_environment' => true]), + new TwigFilter('reduce', 'twig_array_reduce', ['needs_environment' => true]), + + // string/array filters + new TwigFilter('reverse', 'twig_reverse_filter', ['needs_environment' => true]), + new TwigFilter('length', 'twig_length_filter', ['needs_environment' => true]), + new TwigFilter('slice', 'twig_slice', ['needs_environment' => true]), + new TwigFilter('first', 'twig_first', ['needs_environment' => true]), + new TwigFilter('last', 'twig_last', ['needs_environment' => true]), + + // iteration and runtime + new TwigFilter('default', '_twig_default_filter', ['node_class' => DefaultFilter::class]), + new TwigFilter('keys', 'twig_get_array_keys_filter'), + ]; + } + + public function getFunctions(): array + { + return [ + new TwigFunction('max', 'max'), + new TwigFunction('min', 'min'), + new TwigFunction('range', 'range'), + new TwigFunction('constant', 'twig_constant'), + new TwigFunction('cycle', 'twig_cycle'), + new TwigFunction('random', 'twig_random', ['needs_environment' => true]), + new TwigFunction('date', 'twig_date_converter', ['needs_environment' => true]), + new TwigFunction('include', 'twig_include', ['needs_environment' => true, 'needs_context' => true, 'is_safe' => ['all']]), + new TwigFunction('source', 'twig_source', ['needs_environment' => true, 'is_safe' => ['all']]), + ]; + } + + public function getTests(): array + { + return [ + new TwigTest('even', null, ['node_class' => EvenTest::class]), + new TwigTest('odd', null, ['node_class' => OddTest::class]), + new TwigTest('defined', null, ['node_class' => DefinedTest::class]), + new TwigTest('same as', null, ['node_class' => SameasTest::class, 'one_mandatory_argument' => true]), + new TwigTest('none', null, ['node_class' => NullTest::class]), + new TwigTest('null', null, ['node_class' => NullTest::class]), + new TwigTest('divisible by', null, ['node_class' => DivisiblebyTest::class, 'one_mandatory_argument' => true]), + new TwigTest('constant', null, ['node_class' => ConstantTest::class]), + new TwigTest('empty', 'twig_test_empty'), + new TwigTest('iterable', 'twig_test_iterable'), + ]; + } + + public function getNodeVisitors(): array + { + return [new MacroAutoImportNodeVisitor()]; + } + + public function getOperators(): array + { + return [ + [ + 'not' => ['precedence' => 50, 'class' => NotUnary::class], + '-' => ['precedence' => 500, 'class' => NegUnary::class], + '+' => ['precedence' => 500, 'class' => PosUnary::class], + ], + [ + 'or' => ['precedence' => 10, 'class' => OrBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT], + 'and' => ['precedence' => 15, 'class' => AndBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT], + 'b-or' => ['precedence' => 16, 'class' => BitwiseOrBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT], + 'b-xor' => ['precedence' => 17, 'class' => BitwiseXorBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT], + 'b-and' => ['precedence' => 18, 'class' => BitwiseAndBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT], + '==' => ['precedence' => 20, 'class' => EqualBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT], + '!=' => ['precedence' => 20, 'class' => NotEqualBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT], + '<=>' => ['precedence' => 20, 'class' => SpaceshipBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT], + '<' => ['precedence' => 20, 'class' => LessBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT], + '>' => ['precedence' => 20, 'class' => GreaterBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT], + '>=' => ['precedence' => 20, 'class' => GreaterEqualBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT], + '<=' => ['precedence' => 20, 'class' => LessEqualBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT], + 'not in' => ['precedence' => 20, 'class' => NotInBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT], + 'in' => ['precedence' => 20, 'class' => InBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT], + 'matches' => ['precedence' => 20, 'class' => MatchesBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT], + 'starts with' => ['precedence' => 20, 'class' => StartsWithBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT], + 'ends with' => ['precedence' => 20, 'class' => EndsWithBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT], + '..' => ['precedence' => 25, 'class' => RangeBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT], + '+' => ['precedence' => 30, 'class' => AddBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT], + '-' => ['precedence' => 30, 'class' => SubBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT], + '~' => ['precedence' => 40, 'class' => ConcatBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT], + '*' => ['precedence' => 60, 'class' => MulBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT], + '/' => ['precedence' => 60, 'class' => DivBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT], + '//' => ['precedence' => 60, 'class' => FloorDivBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT], + '%' => ['precedence' => 60, 'class' => ModBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT], + 'is' => ['precedence' => 100, 'associativity' => ExpressionParser::OPERATOR_LEFT], + 'is not' => ['precedence' => 100, 'associativity' => ExpressionParser::OPERATOR_LEFT], + '**' => ['precedence' => 200, 'class' => PowerBinary::class, 'associativity' => ExpressionParser::OPERATOR_RIGHT], + '??' => ['precedence' => 300, 'class' => NullCoalesceExpression::class, 'associativity' => ExpressionParser::OPERATOR_RIGHT], + ], + ]; + } +} +} + +namespace { + use Twig\Environment; + use Twig\Error\LoaderError; + use Twig\Error\RuntimeError; + use Twig\Extension\CoreExtension; + use Twig\Extension\SandboxExtension; + use Twig\Markup; + use Twig\Source; + use Twig\Template; + use Twig\TemplateWrapper; + +/** + * Cycles over a value. + * + * @param \ArrayAccess|array $values + * @param int $position The cycle position + * + * @return string The next value in the cycle + */ +function twig_cycle($values, $position) +{ + if (!\is_array($values) && !$values instanceof \ArrayAccess) { + return $values; + } + + return $values[$position % \count($values)]; +} + +/** + * Returns a random value depending on the supplied parameter type: + * - a random item from a \Traversable or array + * - a random character from a string + * - a random integer between 0 and the integer parameter. + * + * @param \Traversable|array|int|float|string $values The values to pick a random item from + * @param int|null $max Maximum value used when $values is an int + * + * @throws RuntimeError when $values is an empty array (does not apply to an empty string which is returned as is) + * + * @return mixed A random value from the given sequence + */ +function twig_random(Environment $env, $values = null, $max = null) +{ + if (null === $values) { + return null === $max ? mt_rand() : mt_rand(0, $max); + } + + if (\is_int($values) || \is_float($values)) { + if (null === $max) { + if ($values < 0) { + $max = 0; + $min = $values; + } else { + $max = $values; + $min = 0; + } + } else { + $min = $values; + $max = $max; + } + + return mt_rand($min, $max); + } + + if (\is_string($values)) { + if ('' === $values) { + return ''; + } + + $charset = $env->getCharset(); + + if ('UTF-8' !== $charset) { + $values = twig_convert_encoding($values, 'UTF-8', $charset); + } + + // unicode version of str_split() + // split at all positions, but not after the start and not before the end + $values = preg_split('/(? $value) { + $values[$i] = twig_convert_encoding($value, $charset, 'UTF-8'); + } + } + } + + if (!twig_test_iterable($values)) { + return $values; + } + + $values = twig_to_array($values); + + if (0 === \count($values)) { + throw new RuntimeError('The random function cannot pick from an empty array.'); + } + + return $values[array_rand($values, 1)]; +} + +/** + * Converts a date to the given format. + * + * {{ post.published_at|date("m/d/Y") }} + * + * @param \DateTimeInterface|\DateInterval|string $date A date + * @param string|null $format The target format, null to use the default + * @param \DateTimeZone|string|false|null $timezone The target timezone, null to use the default, false to leave unchanged + * + * @return string The formatted date + */ +function twig_date_format_filter(Environment $env, $date, $format = null, $timezone = null) +{ + if (null === $format) { + $formats = $env->getExtension(CoreExtension::class)->getDateFormat(); + $format = $date instanceof \DateInterval ? $formats[1] : $formats[0]; + } + + if ($date instanceof \DateInterval) { + return $date->format($format); + } + + return twig_date_converter($env, $date, $timezone)->format($format); +} + +/** + * Returns a new date object modified. + * + * {{ post.published_at|date_modify("-1day")|date("m/d/Y") }} + * + * @param \DateTimeInterface|string $date A date + * @param string $modifier A modifier string + * + * @return \DateTimeInterface + */ +function twig_date_modify_filter(Environment $env, $date, $modifier) +{ + $date = twig_date_converter($env, $date, false); + + return $date->modify($modifier); +} + +/** + * Converts an input to a \DateTime instance. + * + * {% if date(user.created_at) < date('+2days') %} + * {# do something #} + * {% endif %} + * + * @param \DateTimeInterface|string|null $date A date or null to use the current time + * @param \DateTimeZone|string|false|null $timezone The target timezone, null to use the default, false to leave unchanged + * + * @return \DateTimeInterface + */ +function twig_date_converter(Environment $env, $date = null, $timezone = null) +{ + // determine the timezone + if (false !== $timezone) { + if (null === $timezone) { + $timezone = $env->getExtension(CoreExtension::class)->getTimezone(); + } elseif (!$timezone instanceof \DateTimeZone) { + $timezone = new \DateTimeZone($timezone); + } + } + + // immutable dates + if ($date instanceof \DateTimeImmutable) { + return false !== $timezone ? $date->setTimezone($timezone) : $date; + } + + if ($date instanceof \DateTimeInterface) { + $date = clone $date; + if (false !== $timezone) { + $date->setTimezone($timezone); + } + + return $date; + } + + if (null === $date || 'now' === $date) { + return new \DateTime($date, false !== $timezone ? $timezone : $env->getExtension(CoreExtension::class)->getTimezone()); + } + + $asString = (string) $date; + if (ctype_digit($asString) || (!empty($asString) && '-' === $asString[0] && ctype_digit(substr($asString, 1)))) { + $date = new \DateTime('@'.$date); + } else { + $date = new \DateTime($date, $env->getExtension(CoreExtension::class)->getTimezone()); + } + + if (false !== $timezone) { + $date->setTimezone($timezone); + } + + return $date; +} + +/** + * Replaces strings within a string. + * + * @param string $str String to replace in + * @param array|\Traversable $from Replace values + * + * @return string + */ +function twig_replace_filter($str, $from) +{ + if (!twig_test_iterable($from)) { + throw new RuntimeError(sprintf('The "replace" filter expects an array or "Traversable" as replace values, got "%s".', \is_object($from) ? \get_class($from) : \gettype($from))); + } + + return strtr($str, twig_to_array($from)); +} + +/** + * Rounds a number. + * + * @param int|float $value The value to round + * @param int|float $precision The rounding precision + * @param string $method The method to use for rounding + * + * @return int|float The rounded number + */ +function twig_round($value, $precision = 0, $method = 'common') +{ + if ('common' === $method) { + return round($value, $precision); + } + + if ('ceil' !== $method && 'floor' !== $method) { + throw new RuntimeError('The round filter only supports the "common", "ceil", and "floor" methods.'); + } + + return $method($value * 10 ** $precision) / 10 ** $precision; +} + +/** + * Number format filter. + * + * All of the formatting options can be left null, in that case the defaults will + * be used. Supplying any of the parameters will override the defaults set in the + * environment object. + * + * @param mixed $number A float/int/string of the number to format + * @param int $decimal the number of decimal points to display + * @param string $decimalPoint the character(s) to use for the decimal point + * @param string $thousandSep the character(s) to use for the thousands separator + * + * @return string The formatted number + */ +function twig_number_format_filter(Environment $env, $number, $decimal = null, $decimalPoint = null, $thousandSep = null) +{ + $defaults = $env->getExtension(CoreExtension::class)->getNumberFormat(); + if (null === $decimal) { + $decimal = $defaults[0]; + } + + if (null === $decimalPoint) { + $decimalPoint = $defaults[1]; + } + + if (null === $thousandSep) { + $thousandSep = $defaults[2]; + } + + return number_format((float) $number, $decimal, $decimalPoint, $thousandSep); +} + +/** + * URL encodes (RFC 3986) a string as a path segment or an array as a query string. + * + * @param string|array $url A URL or an array of query parameters + * + * @return string The URL encoded value + */ +function twig_urlencode_filter($url) +{ + if (\is_array($url)) { + return http_build_query($url, '', '&', PHP_QUERY_RFC3986); + } + + return rawurlencode($url); +} + +/** + * Merges an array with another one. + * + * {% set items = { 'apple': 'fruit', 'orange': 'fruit' } %} + * + * {% set items = items|merge({ 'peugeot': 'car' }) %} + * + * {# items now contains { 'apple': 'fruit', 'orange': 'fruit', 'peugeot': 'car' } #} + * + * @param array|\Traversable $arr1 An array + * @param array|\Traversable $arr2 An array + * + * @return array The merged array + */ +function twig_array_merge($arr1, $arr2) +{ + if (!twig_test_iterable($arr1)) { + throw new RuntimeError(sprintf('The merge filter only works with arrays or "Traversable", got "%s" as first argument.', \gettype($arr1))); + } + + if (!twig_test_iterable($arr2)) { + throw new RuntimeError(sprintf('The merge filter only works with arrays or "Traversable", got "%s" as second argument.', \gettype($arr2))); + } + + return array_merge(twig_to_array($arr1), twig_to_array($arr2)); +} + +/** + * Slices a variable. + * + * @param mixed $item A variable + * @param int $start Start of the slice + * @param int $length Size of the slice + * @param bool $preserveKeys Whether to preserve key or not (when the input is an array) + * + * @return mixed The sliced variable + */ +function twig_slice(Environment $env, $item, $start, $length = null, $preserveKeys = false) +{ + if ($item instanceof \Traversable) { + while ($item instanceof \IteratorAggregate) { + $item = $item->getIterator(); + } + + if ($start >= 0 && $length >= 0 && $item instanceof \Iterator) { + try { + return iterator_to_array(new \LimitIterator($item, $start, null === $length ? -1 : $length), $preserveKeys); + } catch (\OutOfBoundsException $e) { + return []; + } + } + + $item = iterator_to_array($item, $preserveKeys); + } + + if (\is_array($item)) { + return \array_slice($item, $start, $length, $preserveKeys); + } + + $item = (string) $item; + + return (string) mb_substr($item, $start, $length, $env->getCharset()); +} + +/** + * Returns the first element of the item. + * + * @param mixed $item A variable + * + * @return mixed The first element of the item + */ +function twig_first(Environment $env, $item) +{ + $elements = twig_slice($env, $item, 0, 1, false); + + return \is_string($elements) ? $elements : current($elements); +} + +/** + * Returns the last element of the item. + * + * @param mixed $item A variable + * + * @return mixed The last element of the item + */ +function twig_last(Environment $env, $item) +{ + $elements = twig_slice($env, $item, -1, 1, false); + + return \is_string($elements) ? $elements : current($elements); +} + +/** + * Joins the values to a string. + * + * The separators between elements are empty strings per default, you can define them with the optional parameters. + * + * {{ [1, 2, 3]|join(', ', ' and ') }} + * {# returns 1, 2 and 3 #} + * + * {{ [1, 2, 3]|join('|') }} + * {# returns 1|2|3 #} + * + * {{ [1, 2, 3]|join }} + * {# returns 123 #} + * + * @param array $value An array + * @param string $glue The separator + * @param string|null $and The separator for the last pair + * + * @return string The concatenated string + */ +function twig_join_filter($value, $glue = '', $and = null) +{ + if (!twig_test_iterable($value)) { + $value = (array) $value; + } + + $value = twig_to_array($value, false); + + if (0 === \count($value)) { + return ''; + } + + if (null === $and || $and === $glue) { + return implode($glue, $value); + } + + if (1 === \count($value)) { + return $value[0]; + } + + return implode($glue, \array_slice($value, 0, -1)).$and.$value[\count($value) - 1]; +} + +/** + * Splits the string into an array. + * + * {{ "one,two,three"|split(',') }} + * {# returns [one, two, three] #} + * + * {{ "one,two,three,four,five"|split(',', 3) }} + * {# returns [one, two, "three,four,five"] #} + * + * {{ "123"|split('') }} + * {# returns [1, 2, 3] #} + * + * {{ "aabbcc"|split('', 2) }} + * {# returns [aa, bb, cc] #} + * + * @param string $value A string + * @param string $delimiter The delimiter + * @param int $limit The limit + * + * @return array The split string as an array + */ +function twig_split_filter(Environment $env, $value, $delimiter, $limit = null) +{ + if (\strlen($delimiter) > 0) { + return null === $limit ? explode($delimiter, $value) : explode($delimiter, $value, $limit); + } + + if ($limit <= 1) { + return preg_split('/(?getCharset()); + if ($length < $limit) { + return [$value]; + } + + $r = []; + for ($i = 0; $i < $length; $i += $limit) { + $r[] = mb_substr($value, $i, $limit, $env->getCharset()); + } + + return $r; +} + +// The '_default' filter is used internally to avoid using the ternary operator +// which costs a lot for big contexts (before PHP 5.4). So, on average, +// a function call is cheaper. +/** + * @internal + */ +function _twig_default_filter($value, $default = '') +{ + if (twig_test_empty($value)) { + return $default; + } + + return $value; +} + +/** + * Returns the keys for the given array. + * + * It is useful when you want to iterate over the keys of an array: + * + * {% for key in array|keys %} + * {# ... #} + * {% endfor %} + * + * @param array $array An array + * + * @return array The keys + */ +function twig_get_array_keys_filter($array) +{ + if ($array instanceof \Traversable) { + while ($array instanceof \IteratorAggregate) { + $array = $array->getIterator(); + } + + if ($array instanceof \Iterator) { + $keys = []; + $array->rewind(); + while ($array->valid()) { + $keys[] = $array->key(); + $array->next(); + } + + return $keys; + } + + $keys = []; + foreach ($array as $key => $item) { + $keys[] = $key; + } + + return $keys; + } + + if (!\is_array($array)) { + return []; + } + + return array_keys($array); +} + +/** + * Reverses a variable. + * + * @param array|\Traversable|string $item An array, a \Traversable instance, or a string + * @param bool $preserveKeys Whether to preserve key or not + * + * @return mixed The reversed input + */ +function twig_reverse_filter(Environment $env, $item, $preserveKeys = false) +{ + if ($item instanceof \Traversable) { + return array_reverse(iterator_to_array($item), $preserveKeys); + } + + if (\is_array($item)) { + return array_reverse($item, $preserveKeys); + } + + $string = (string) $item; + + $charset = $env->getCharset(); + + if ('UTF-8' !== $charset) { + $item = twig_convert_encoding($string, 'UTF-8', $charset); + } + + preg_match_all('/./us', $item, $matches); + + $string = implode('', array_reverse($matches[0])); + + if ('UTF-8' !== $charset) { + $string = twig_convert_encoding($string, $charset, 'UTF-8'); + } + + return $string; +} + +/** + * Sorts an array. + * + * @param array|\Traversable $array + * + * @return array + */ +function twig_sort_filter($array, $arrow = null) +{ + if ($array instanceof \Traversable) { + $array = iterator_to_array($array); + } elseif (!\is_array($array)) { + throw new RuntimeError(sprintf('The sort filter only works with arrays or "Traversable", got "%s".', \gettype($array))); + } + + if (null !== $arrow) { + uasort($array, $arrow); + } else { + asort($array); + } + + return $array; +} + +/** + * @internal + */ +function twig_in_filter($value, $compare) +{ + if ($value instanceof Markup) { + $value = (string) $value; + } + if ($compare instanceof Markup) { + $compare = (string) $compare; + } + + if (\is_string($compare)) { + if (\is_string($value) || \is_int($value) || \is_float($value)) { + return '' === $value || false !== strpos($compare, (string) $value); + } + + return false; + } + + if (!is_iterable($compare)) { + return false; + } + + if (\is_object($value) || \is_resource($value)) { + if (!\is_array($compare)) { + foreach ($compare as $item) { + if ($item === $value) { + return true; + } + } + + return false; + } + + return \in_array($value, $compare, true); + } + + foreach ($compare as $item) { + if (0 === twig_compare($value, $item)) { + return true; + } + } + + return false; +} + +/** + * Compares two values using a more strict version of the PHP non-strict comparison operator. + * + * @see https://wiki.php.net/rfc/string_to_number_comparison + * @see https://wiki.php.net/rfc/trailing_whitespace_numerics + * + * @internal + */ +function twig_compare($a, $b) +{ + // int <=> string + if (\is_int($a) && \is_string($b)) { + $bTrim = trim($b, " \t\n\r\v\f"); + if (!is_numeric($bTrim)) { + return (string) $a <=> $b; + } + if ((int) $bTrim == $bTrim) { + return $a <=> (int) $bTrim; + } else { + return (float) $a <=> (float) $bTrim; + } + } + if (\is_string($a) && \is_int($b)) { + $aTrim = trim($a, " \t\n\r\v\f"); + if (!is_numeric($aTrim)) { + return $a <=> (string) $b; + } + if ((int) $aTrim == $aTrim) { + return (int) $aTrim <=> $b; + } else { + return (float) $aTrim <=> (float) $b; + } + } + + // float <=> string + if (\is_float($a) && \is_string($b)) { + if (is_nan($a)) { + return 1; + } + $bTrim = trim($b, " \t\n\r\v\f"); + if (!is_numeric($bTrim)) { + return (string) $a <=> $b; + } + + return $a <=> (float) $bTrim; + } + if (\is_string($a) && \is_float($b)) { + if (is_nan($b)) { + return 1; + } + $aTrim = trim($a, " \t\n\r\v\f"); + if (!is_numeric($aTrim)) { + return $a <=> (string) $b; + } + + return (float) $aTrim <=> $b; + } + + // fallback to <=> + return $a <=> $b; +} + +/** + * Returns a trimmed string. + * + * @return string + * + * @throws RuntimeError When an invalid trimming side is used (not a string or not 'left', 'right', or 'both') + */ +function twig_trim_filter($string, $characterMask = null, $side = 'both') +{ + if (null === $characterMask) { + $characterMask = " \t\n\r\0\x0B"; + } + + switch ($side) { + case 'both': + return trim($string, $characterMask); + case 'left': + return ltrim($string, $characterMask); + case 'right': + return rtrim($string, $characterMask); + default: + throw new RuntimeError('Trimming side must be "left", "right" or "both".'); + } +} + +/** + * Removes whitespaces between HTML tags. + * + * @return string + */ +function twig_spaceless($content) +{ + return trim(preg_replace('/>\s+<', $content)); +} + +function twig_convert_encoding($string, $to, $from) +{ + if (!\function_exists('iconv')) { + throw new RuntimeError('Unable to convert encoding: required function iconv() does not exist. You should install ext-iconv or symfony/polyfill-iconv.'); + } + + return iconv($from, $to, $string); +} + +/** + * Returns the length of a variable. + * + * @param mixed $thing A variable + * + * @return int The length of the value + */ +function twig_length_filter(Environment $env, $thing) +{ + if (null === $thing) { + return 0; + } + + if (is_scalar($thing)) { + return mb_strlen($thing, $env->getCharset()); + } + + if ($thing instanceof \Countable || \is_array($thing) || $thing instanceof \SimpleXMLElement) { + return \count($thing); + } + + if ($thing instanceof \Traversable) { + return iterator_count($thing); + } + + if (method_exists($thing, '__toString') && !$thing instanceof \Countable) { + return mb_strlen((string) $thing, $env->getCharset()); + } + + return 1; +} + +/** + * Converts a string to uppercase. + * + * @param string $string A string + * + * @return string The uppercased string + */ +function twig_upper_filter(Environment $env, $string) +{ + return mb_strtoupper($string, $env->getCharset()); +} + +/** + * Converts a string to lowercase. + * + * @param string $string A string + * + * @return string The lowercased string + */ +function twig_lower_filter(Environment $env, $string) +{ + return mb_strtolower($string, $env->getCharset()); +} + +/** + * Returns a titlecased string. + * + * @param string $string A string + * + * @return string The titlecased string + */ +function twig_title_string_filter(Environment $env, $string) +{ + if (null !== $charset = $env->getCharset()) { + return mb_convert_case($string, MB_CASE_TITLE, $charset); + } + + return ucwords(strtolower($string)); +} + +/** + * Returns a capitalized string. + * + * @param string $string A string + * + * @return string The capitalized string + */ +function twig_capitalize_string_filter(Environment $env, $string) +{ + $charset = $env->getCharset(); + + return mb_strtoupper(mb_substr($string, 0, 1, $charset), $charset).mb_strtolower(mb_substr($string, 1, null, $charset), $charset); +} + +/** + * @internal + */ +function twig_call_macro(Template $template, string $method, array $args, int $lineno, array $context, Source $source) +{ + if (!method_exists($template, $method)) { + $parent = $template; + while ($parent = $parent->getParent($context)) { + if (method_exists($parent, $method)) { + return $parent->$method(...$args); + } + } + + throw new RuntimeError(sprintf('Macro "%s" is not defined in template "%s".', substr($method, \strlen('macro_')), $template->getTemplateName()), $lineno, $source); + } + + return $template->$method(...$args); +} + +/** + * @internal + */ +function twig_ensure_traversable($seq) +{ + if ($seq instanceof \Traversable || \is_array($seq)) { + return $seq; + } + + return []; +} + +/** + * @internal + */ +function twig_to_array($seq, $preserveKeys = true) +{ + if ($seq instanceof \Traversable) { + return iterator_to_array($seq, $preserveKeys); + } + + if (!\is_array($seq)) { + return $seq; + } + + return $preserveKeys ? $seq : array_values($seq); +} + +/** + * Checks if a variable is empty. + * + * {# evaluates to true if the foo variable is null, false, or the empty string #} + * {% if foo is empty %} + * {# ... #} + * {% endif %} + * + * @param mixed $value A variable + * + * @return bool true if the value is empty, false otherwise + */ +function twig_test_empty($value) +{ + if ($value instanceof \Countable) { + return 0 === \count($value); + } + + if ($value instanceof \Traversable) { + return !iterator_count($value); + } + + if (\is_object($value) && method_exists($value, '__toString')) { + return '' === (string) $value; + } + + return '' === $value || false === $value || null === $value || [] === $value; +} + +/** + * Checks if a variable is traversable. + * + * {# evaluates to true if the foo variable is an array or a traversable object #} + * {% if foo is iterable %} + * {# ... #} + * {% endif %} + * + * @param mixed $value A variable + * + * @return bool true if the value is traversable + */ +function twig_test_iterable($value) +{ + return $value instanceof \Traversable || \is_array($value); +} + +/** + * Renders a template. + * + * @param array $context + * @param string|array $template The template to render or an array of templates to try consecutively + * @param array $variables The variables to pass to the template + * @param bool $withContext + * @param bool $ignoreMissing Whether to ignore missing templates or not + * @param bool $sandboxed Whether to sandbox the template or not + * + * @return string The rendered template + */ +function twig_include(Environment $env, $context, $template, $variables = [], $withContext = true, $ignoreMissing = false, $sandboxed = false) +{ + $alreadySandboxed = false; + $sandbox = null; + if ($withContext) { + $variables = array_merge($context, $variables); + } + + if ($isSandboxed = $sandboxed && $env->hasExtension(SandboxExtension::class)) { + $sandbox = $env->getExtension(SandboxExtension::class); + if (!$alreadySandboxed = $sandbox->isSandboxed()) { + $sandbox->enableSandbox(); + } + + foreach ((\is_array($template) ? $template : [$template]) as $name) { + // if a Template instance is passed, it might have been instantiated outside of a sandbox, check security + if ($name instanceof TemplateWrapper || $name instanceof Template) { + $name->unwrap()->checkSecurity(); + } + } + } + + try { + $loaded = null; + try { + $loaded = $env->resolveTemplate($template); + } catch (LoaderError $e) { + if (!$ignoreMissing) { + throw $e; + } + } + + return $loaded ? $loaded->render($variables) : ''; + } finally { + if ($isSandboxed && !$alreadySandboxed) { + $sandbox->disableSandbox(); + } + } +} + +/** + * Returns a template content without rendering it. + * + * @param string $name The template name + * @param bool $ignoreMissing Whether to ignore missing templates or not + * + * @return string The template source + */ +function twig_source(Environment $env, $name, $ignoreMissing = false) +{ + $loader = $env->getLoader(); + try { + return $loader->getSourceContext($name)->getCode(); + } catch (LoaderError $e) { + if (!$ignoreMissing) { + throw $e; + } + } +} + +/** + * Provides the ability to get constants from instances as well as class/global constants. + * + * @param string $constant The name of the constant + * @param object|null $object The object to get the constant from + * + * @return string + */ +function twig_constant($constant, $object = null) +{ + if (null !== $object) { + $constant = \get_class($object).'::'.$constant; + } + + return \constant($constant); +} + +/** + * Checks if a constant exists. + * + * @param string $constant The name of the constant + * @param object|null $object The object to get the constant from + * + * @return bool + */ +function twig_constant_is_defined($constant, $object = null) +{ + if (null !== $object) { + $constant = \get_class($object).'::'.$constant; + } + + return \defined($constant); +} + +/** + * Batches item. + * + * @param array $items An array of items + * @param int $size The size of the batch + * @param mixed $fill A value used to fill missing items + * + * @return array + */ +function twig_array_batch($items, $size, $fill = null, $preserveKeys = true) +{ + if (!twig_test_iterable($items)) { + throw new RuntimeError(sprintf('The "batch" filter expects an array or "Traversable", got "%s".', \is_object($items) ? \get_class($items) : \gettype($items))); + } + + $size = ceil($size); + + $result = array_chunk(twig_to_array($items, $preserveKeys), $size, $preserveKeys); + + if (null !== $fill && $result) { + $last = \count($result) - 1; + if ($fillCount = $size - \count($result[$last])) { + for ($i = 0; $i < $fillCount; ++$i) { + $result[$last][] = $fill; + } + } + } + + return $result; +} + +/** + * Returns the attribute value for a given array/object. + * + * @param mixed $object The object or array from where to get the item + * @param mixed $item The item to get from the array or object + * @param array $arguments An array of arguments to pass if the item is an object method + * @param string $type The type of attribute (@see \Twig\Template constants) + * @param bool $isDefinedTest Whether this is only a defined check + * @param bool $ignoreStrictCheck Whether to ignore the strict attribute check or not + * @param int $lineno The template line where the attribute was called + * + * @return mixed The attribute value, or a Boolean when $isDefinedTest is true, or null when the attribute is not set and $ignoreStrictCheck is true + * + * @throws RuntimeError if the attribute does not exist and Twig is running in strict mode and $isDefinedTest is false + * + * @internal + */ +function twig_get_attribute(Environment $env, Source $source, $object, $item, array $arguments = [], $type = /* Template::ANY_CALL */ 'any', $isDefinedTest = false, $ignoreStrictCheck = false, $sandboxed = false, int $lineno = -1) +{ + // array + if (/* Template::METHOD_CALL */ 'method' !== $type) { + $arrayItem = \is_bool($item) || \is_float($item) ? (int) $item : $item; + + if (((\is_array($object) || $object instanceof \ArrayObject) && (isset($object[$arrayItem]) || \array_key_exists($arrayItem, (array) $object))) + || ($object instanceof ArrayAccess && isset($object[$arrayItem])) + ) { + if ($isDefinedTest) { + return true; + } + + return $object[$arrayItem]; + } + + if (/* Template::ARRAY_CALL */ 'array' === $type || !\is_object($object)) { + if ($isDefinedTest) { + return false; + } + + if ($ignoreStrictCheck || !$env->isStrictVariables()) { + return; + } + + if ($object instanceof ArrayAccess) { + $message = sprintf('Key "%s" in object with ArrayAccess of class "%s" does not exist.', $arrayItem, \get_class($object)); + } elseif (\is_object($object)) { + $message = sprintf('Impossible to access a key "%s" on an object of class "%s" that does not implement ArrayAccess interface.', $item, \get_class($object)); + } elseif (\is_array($object)) { + if (empty($object)) { + $message = sprintf('Key "%s" does not exist as the array is empty.', $arrayItem); + } else { + $message = sprintf('Key "%s" for array with keys "%s" does not exist.', $arrayItem, implode(', ', array_keys($object))); + } + } elseif (/* Template::ARRAY_CALL */ 'array' === $type) { + if (null === $object) { + $message = sprintf('Impossible to access a key ("%s") on a null variable.', $item); + } else { + $message = sprintf('Impossible to access a key ("%s") on a %s variable ("%s").', $item, \gettype($object), $object); + } + } elseif (null === $object) { + $message = sprintf('Impossible to access an attribute ("%s") on a null variable.', $item); + } else { + $message = sprintf('Impossible to access an attribute ("%s") on a %s variable ("%s").', $item, \gettype($object), $object); + } + + throw new RuntimeError($message, $lineno, $source); + } + } + + if (!\is_object($object)) { + if ($isDefinedTest) { + return false; + } + + if ($ignoreStrictCheck || !$env->isStrictVariables()) { + return; + } + + if (null === $object) { + $message = sprintf('Impossible to invoke a method ("%s") on a null variable.', $item); + } elseif (\is_array($object)) { + $message = sprintf('Impossible to invoke a method ("%s") on an array.', $item); + } else { + $message = sprintf('Impossible to invoke a method ("%s") on a %s variable ("%s").', $item, \gettype($object), $object); + } + + throw new RuntimeError($message, $lineno, $source); + } + + if ($object instanceof Template) { + throw new RuntimeError('Accessing \Twig\Template attributes is forbidden.', $lineno, $source); + } + + // object property + if (/* Template::METHOD_CALL */ 'method' !== $type) { + if (isset($object->$item) || \array_key_exists((string) $item, (array) $object)) { + if ($isDefinedTest) { + return true; + } + + if ($sandboxed) { + $env->getExtension(SandboxExtension::class)->checkPropertyAllowed($object, $item, $lineno, $source); + } + + return $object->$item; + } + } + + static $cache = []; + + $class = \get_class($object); + + // object method + // precedence: getXxx() > isXxx() > hasXxx() + if (!isset($cache[$class])) { + $methods = get_class_methods($object); + sort($methods); + $lcMethods = array_map(function ($value) { return strtr($value, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz'); }, $methods); + $classCache = []; + foreach ($methods as $i => $method) { + $classCache[$method] = $method; + $classCache[$lcName = $lcMethods[$i]] = $method; + + if ('g' === $lcName[0] && 0 === strpos($lcName, 'get')) { + $name = substr($method, 3); + $lcName = substr($lcName, 3); + } elseif ('i' === $lcName[0] && 0 === strpos($lcName, 'is')) { + $name = substr($method, 2); + $lcName = substr($lcName, 2); + } elseif ('h' === $lcName[0] && 0 === strpos($lcName, 'has')) { + $name = substr($method, 3); + $lcName = substr($lcName, 3); + if (\in_array('is'.$lcName, $lcMethods)) { + continue; + } + } else { + continue; + } + + // skip get() and is() methods (in which case, $name is empty) + if ($name) { + if (!isset($classCache[$name])) { + $classCache[$name] = $method; + } + + if (!isset($classCache[$lcName])) { + $classCache[$lcName] = $method; + } + } + } + $cache[$class] = $classCache; + } + + $call = false; + if (isset($cache[$class][$item])) { + $method = $cache[$class][$item]; + } elseif (isset($cache[$class][$lcItem = strtr($item, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz')])) { + $method = $cache[$class][$lcItem]; + } elseif (isset($cache[$class]['__call'])) { + $method = $item; + $call = true; + } else { + if ($isDefinedTest) { + return false; + } + + if ($ignoreStrictCheck || !$env->isStrictVariables()) { + return; + } + + throw new RuntimeError(sprintf('Neither the property "%1$s" nor one of the methods "%1$s()", "get%1$s()"/"is%1$s()"/"has%1$s()" or "__call()" exist and have public access in class "%2$s".', $item, $class), $lineno, $source); + } + + if ($isDefinedTest) { + return true; + } + + if ($sandboxed) { + $env->getExtension(SandboxExtension::class)->checkMethodAllowed($object, $method, $lineno, $source); + } + + // Some objects throw exceptions when they have __call, and the method we try + // to call is not supported. If ignoreStrictCheck is true, we should return null. + try { + $ret = $object->$method(...$arguments); + } catch (\BadMethodCallException $e) { + if ($call && ($ignoreStrictCheck || !$env->isStrictVariables())) { + return; + } + throw $e; + } + + return $ret; +} + +/** + * Returns the values from a single column in the input array. + * + *
    + *  {% set items = [{ 'fruit' : 'apple'}, {'fruit' : 'orange' }] %}
    + *
    + *  {% set fruits = items|column('fruit') %}
    + *
    + *  {# fruits now contains ['apple', 'orange'] #}
    + * 
    + * + * @param array|Traversable $array An array + * @param mixed $name The column name + * @param mixed $index The column to use as the index/keys for the returned array + * + * @return array The array of values + */ +function twig_array_column($array, $name, $index = null): array +{ + if ($array instanceof Traversable) { + $array = iterator_to_array($array); + } elseif (!\is_array($array)) { + throw new RuntimeError(sprintf('The column filter only works with arrays or "Traversable", got "%s" as first argument.', \gettype($array))); + } + + return array_column($array, $name, $index); +} + +function twig_array_filter(Environment $env, $array, $arrow) +{ + if (!twig_test_iterable($array)) { + throw new RuntimeError(sprintf('The "filter" filter expects an array or "Traversable", got "%s".', \is_object($array) ? \get_class($array) : \gettype($array))); + } + + if (!$arrow instanceof Closure && $env->hasExtension('\Twig\Extension\SandboxExtension') && $env->getExtension('\Twig\Extension\SandboxExtension')->isSandboxed()) { + throw new RuntimeError('The callable passed to "filter" filter must be a Closure in sandbox mode.'); + } + + if (\is_array($array)) { + return array_filter($array, $arrow, \ARRAY_FILTER_USE_BOTH); + } + + // the IteratorIterator wrapping is needed as some internal PHP classes are \Traversable but do not implement \Iterator + return new \CallbackFilterIterator(new \IteratorIterator($array), $arrow); +} + +function twig_array_map(Environment $env, $array, $arrow) +{ + if (!$arrow instanceof Closure && $env->hasExtension('\Twig\Extension\SandboxExtension') && $env->getExtension('\Twig\Extension\SandboxExtension')->isSandboxed()) { + throw new RuntimeError('The callable passed to the "map" filter must be a Closure in sandbox mode.'); + } + + $r = []; + foreach ($array as $k => $v) { + $r[$k] = $arrow($v, $k); + } + + return $r; +} + +function twig_array_reduce(Environment $env, $array, $arrow, $initial = null) +{ + if (!$arrow instanceof Closure && $env->hasExtension('\Twig\Extension\SandboxExtension') && $env->getExtension('\Twig\Extension\SandboxExtension')->isSandboxed()) { + throw new RuntimeError('The callable passed to the "reduce" filter must be a Closure in sandbox mode.'); + } + + if (!\is_array($array)) { + if (!$array instanceof \Traversable) { + throw new RuntimeError(sprintf('The "reduce" filter only works with arrays or "Traversable", got "%s" as first argument.', \gettype($array))); + } + + $array = iterator_to_array($array); + } + + return array_reduce($array, $arrow, $initial); +} +} diff --git a/site/plugins/kirby-twig/vendor/twig/twig/src/Extension/DebugExtension.php b/site/plugins/kirby-twig/vendor/twig/twig/src/Extension/DebugExtension.php new file mode 100644 index 0000000..bfb23d7 --- /dev/null +++ b/site/plugins/kirby-twig/vendor/twig/twig/src/Extension/DebugExtension.php @@ -0,0 +1,64 @@ + $isDumpOutputHtmlSafe ? ['html'] : [], 'needs_context' => true, 'needs_environment' => true, 'is_variadic' => true]), + ]; + } +} +} + +namespace { +use Twig\Environment; +use Twig\Template; +use Twig\TemplateWrapper; + +function twig_var_dump(Environment $env, $context, ...$vars) +{ + if (!$env->isDebug()) { + return; + } + + ob_start(); + + if (!$vars) { + $vars = []; + foreach ($context as $key => $value) { + if (!$value instanceof Template && !$value instanceof TemplateWrapper) { + $vars[$key] = $value; + } + } + + var_dump($vars); + } else { + var_dump(...$vars); + } + + return ob_get_clean(); +} +} diff --git a/site/plugins/kirby-twig/vendor/twig/twig/src/Extension/EscaperExtension.php b/site/plugins/kirby-twig/vendor/twig/twig/src/Extension/EscaperExtension.php new file mode 100644 index 0000000..642c64c --- /dev/null +++ b/site/plugins/kirby-twig/vendor/twig/twig/src/Extension/EscaperExtension.php @@ -0,0 +1,421 @@ +setDefaultStrategy($defaultStrategy); + } + + public function getTokenParsers(): array + { + return [new AutoEscapeTokenParser()]; + } + + public function getNodeVisitors(): array + { + return [new EscaperNodeVisitor()]; + } + + public function getFilters(): array + { + return [ + new TwigFilter('escape', 'twig_escape_filter', ['needs_environment' => true, 'is_safe_callback' => 'twig_escape_filter_is_safe']), + new TwigFilter('e', 'twig_escape_filter', ['needs_environment' => true, 'is_safe_callback' => 'twig_escape_filter_is_safe']), + new TwigFilter('raw', 'twig_raw_filter', ['is_safe' => ['all']]), + ]; + } + + /** + * Sets the default strategy to use when not defined by the user. + * + * The strategy can be a valid PHP callback that takes the template + * name as an argument and returns the strategy to use. + * + * @param string|false|callable $defaultStrategy An escaping strategy + */ + public function setDefaultStrategy($defaultStrategy): void + { + if ('name' === $defaultStrategy) { + $defaultStrategy = [FileExtensionEscapingStrategy::class, 'guess']; + } + + $this->defaultStrategy = $defaultStrategy; + } + + /** + * Gets the default strategy to use when not defined by the user. + * + * @param string $name The template name + * + * @return string|false The default strategy to use for the template + */ + public function getDefaultStrategy(string $name) + { + // disable string callables to avoid calling a function named html or js, + // or any other upcoming escaping strategy + if (!\is_string($this->defaultStrategy) && false !== $this->defaultStrategy) { + return \call_user_func($this->defaultStrategy, $name); + } + + return $this->defaultStrategy; + } + + /** + * Defines a new escaper to be used via the escape filter. + * + * @param string $strategy The strategy name that should be used as a strategy in the escape call + * @param callable $callable A valid PHP callable + */ + public function setEscaper($strategy, callable $callable) + { + $this->escapers[$strategy] = $callable; + } + + /** + * Gets all defined escapers. + * + * @return callable[] An array of escapers + */ + public function getEscapers() + { + return $this->escapers; + } + + public function setSafeClasses(array $safeClasses = []) + { + $this->safeClasses = []; + $this->safeLookup = []; + foreach ($safeClasses as $class => $strategies) { + $this->addSafeClass($class, $strategies); + } + } + + public function addSafeClass(string $class, array $strategies) + { + $class = ltrim($class, '\\'); + if (!isset($this->safeClasses[$class])) { + $this->safeClasses[$class] = []; + } + $this->safeClasses[$class] = array_merge($this->safeClasses[$class], $strategies); + + foreach ($strategies as $strategy) { + $this->safeLookup[$strategy][$class] = true; + } + } +} +} + +namespace { +use Twig\Environment; +use Twig\Error\RuntimeError; +use Twig\Extension\EscaperExtension; +use Twig\Markup; +use Twig\Node\Expression\ConstantExpression; +use Twig\Node\Node; + +/** + * Marks a variable as being safe. + * + * @param string $string A PHP variable + */ +function twig_raw_filter($string) +{ + return $string; +} + +/** + * Escapes a string. + * + * @param mixed $string The value to be escaped + * @param string $strategy The escaping strategy + * @param string $charset The charset + * @param bool $autoescape Whether the function is called by the auto-escaping feature (true) or by the developer (false) + * + * @return string + */ +function twig_escape_filter(Environment $env, $string, $strategy = 'html', $charset = null, $autoescape = false) +{ + if ($autoescape && $string instanceof Markup) { + return $string; + } + + if (!\is_string($string)) { + if (\is_object($string) && method_exists($string, '__toString')) { + if ($autoescape) { + $c = \get_class($string); + $ext = $env->getExtension(EscaperExtension::class); + if (!isset($ext->safeClasses[$c])) { + $ext->safeClasses[$c] = []; + foreach (class_parents($string) + class_implements($string) as $class) { + if (isset($ext->safeClasses[$class])) { + $ext->safeClasses[$c] = array_unique(array_merge($ext->safeClasses[$c], $ext->safeClasses[$class])); + foreach ($ext->safeClasses[$class] as $s) { + $ext->safeLookup[$s][$c] = true; + } + } + } + } + if (isset($ext->safeLookup[$strategy][$c]) || isset($ext->safeLookup['all'][$c])) { + return (string) $string; + } + } + + $string = (string) $string; + } elseif (\in_array($strategy, ['html', 'js', 'css', 'html_attr', 'url'])) { + return $string; + } + } + + if ('' === $string) { + return ''; + } + + if (null === $charset) { + $charset = $env->getCharset(); + } + + switch ($strategy) { + case 'html': + // see https://secure.php.net/htmlspecialchars + + // Using a static variable to avoid initializing the array + // each time the function is called. Moving the declaration on the + // top of the function slow downs other escaping strategies. + static $htmlspecialcharsCharsets = [ + 'ISO-8859-1' => true, 'ISO8859-1' => true, + 'ISO-8859-15' => true, 'ISO8859-15' => true, + 'utf-8' => true, 'UTF-8' => true, + 'CP866' => true, 'IBM866' => true, '866' => true, + 'CP1251' => true, 'WINDOWS-1251' => true, 'WIN-1251' => true, + '1251' => true, + 'CP1252' => true, 'WINDOWS-1252' => true, '1252' => true, + 'KOI8-R' => true, 'KOI8-RU' => true, 'KOI8R' => true, + 'BIG5' => true, '950' => true, + 'GB2312' => true, '936' => true, + 'BIG5-HKSCS' => true, + 'SHIFT_JIS' => true, 'SJIS' => true, '932' => true, + 'EUC-JP' => true, 'EUCJP' => true, + 'ISO8859-5' => true, 'ISO-8859-5' => true, 'MACROMAN' => true, + ]; + + if (isset($htmlspecialcharsCharsets[$charset])) { + return htmlspecialchars($string, ENT_QUOTES | ENT_SUBSTITUTE, $charset); + } + + if (isset($htmlspecialcharsCharsets[strtoupper($charset)])) { + // cache the lowercase variant for future iterations + $htmlspecialcharsCharsets[$charset] = true; + + return htmlspecialchars($string, ENT_QUOTES | ENT_SUBSTITUTE, $charset); + } + + $string = twig_convert_encoding($string, 'UTF-8', $charset); + $string = htmlspecialchars($string, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8'); + + return iconv('UTF-8', $charset, $string); + + case 'js': + // escape all non-alphanumeric characters + // into their \x or \uHHHH representations + if ('UTF-8' !== $charset) { + $string = twig_convert_encoding($string, 'UTF-8', $charset); + } + + if (!preg_match('//u', $string)) { + throw new RuntimeError('The string to escape is not a valid UTF-8 string.'); + } + + $string = preg_replace_callback('#[^a-zA-Z0-9,\._]#Su', function ($matches) { + $char = $matches[0]; + + /* + * A few characters have short escape sequences in JSON and JavaScript. + * Escape sequences supported only by JavaScript, not JSON, are omitted. + * \" is also supported but omitted, because the resulting string is not HTML safe. + */ + static $shortMap = [ + '\\' => '\\\\', + '/' => '\\/', + "\x08" => '\b', + "\x0C" => '\f', + "\x0A" => '\n', + "\x0D" => '\r', + "\x09" => '\t', + ]; + + if (isset($shortMap[$char])) { + return $shortMap[$char]; + } + + $codepoint = mb_ord($char); + if (0x10000 > $codepoint) { + return sprintf('\u%04X', $codepoint); + } + + // Split characters outside the BMP into surrogate pairs + // https://tools.ietf.org/html/rfc2781.html#section-2.1 + $u = $codepoint - 0x10000; + $high = 0xD800 | ($u >> 10); + $low = 0xDC00 | ($u & 0x3FF); + + return sprintf('\u%04X\u%04X', $high, $low); + }, $string); + + if ('UTF-8' !== $charset) { + $string = iconv('UTF-8', $charset, $string); + } + + return $string; + + case 'css': + if ('UTF-8' !== $charset) { + $string = twig_convert_encoding($string, 'UTF-8', $charset); + } + + if (!preg_match('//u', $string)) { + throw new RuntimeError('The string to escape is not a valid UTF-8 string.'); + } + + $string = preg_replace_callback('#[^a-zA-Z0-9]#Su', function ($matches) { + $char = $matches[0]; + + return sprintf('\\%X ', 1 === \strlen($char) ? \ord($char) : mb_ord($char, 'UTF-8')); + }, $string); + + if ('UTF-8' !== $charset) { + $string = iconv('UTF-8', $charset, $string); + } + + return $string; + + case 'html_attr': + if ('UTF-8' !== $charset) { + $string = twig_convert_encoding($string, 'UTF-8', $charset); + } + + if (!preg_match('//u', $string)) { + throw new RuntimeError('The string to escape is not a valid UTF-8 string.'); + } + + $string = preg_replace_callback('#[^a-zA-Z0-9,\.\-_]#Su', function ($matches) { + /** + * This function is adapted from code coming from Zend Framework. + * + * @copyright Copyright (c) 2005-2012 Zend Technologies USA Inc. (https://www.zend.com) + * @license https://framework.zend.com/license/new-bsd New BSD License + */ + $chr = $matches[0]; + $ord = \ord($chr); + + /* + * The following replaces characters undefined in HTML with the + * hex entity for the Unicode replacement character. + */ + if (($ord <= 0x1f && "\t" != $chr && "\n" != $chr && "\r" != $chr) || ($ord >= 0x7f && $ord <= 0x9f)) { + return '�'; + } + + /* + * Check if the current character to escape has a name entity we should + * replace it with while grabbing the hex value of the character. + */ + if (1 === \strlen($chr)) { + /* + * While HTML supports far more named entities, the lowest common denominator + * has become HTML5's XML Serialisation which is restricted to the those named + * entities that XML supports. Using HTML entities would result in this error: + * XML Parsing Error: undefined entity + */ + static $entityMap = [ + 34 => '"', /* quotation mark */ + 38 => '&', /* ampersand */ + 60 => '<', /* less-than sign */ + 62 => '>', /* greater-than sign */ + ]; + + if (isset($entityMap[$ord])) { + return $entityMap[$ord]; + } + + return sprintf('&#x%02X;', $ord); + } + + /* + * Per OWASP recommendations, we'll use hex entities for any other + * characters where a named entity does not exist. + */ + return sprintf('&#x%04X;', mb_ord($chr, 'UTF-8')); + }, $string); + + if ('UTF-8' !== $charset) { + $string = iconv('UTF-8', $charset, $string); + } + + return $string; + + case 'url': + return rawurlencode($string); + + default: + static $escapers; + + if (null === $escapers) { + $escapers = $env->getExtension(EscaperExtension::class)->getEscapers(); + } + + if (isset($escapers[$strategy])) { + return $escapers[$strategy]($env, $string, $charset); + } + + $validStrategies = implode(', ', array_merge(['html', 'js', 'url', 'css', 'html_attr'], array_keys($escapers))); + + throw new RuntimeError(sprintf('Invalid escaping strategy "%s" (valid ones: %s).', $strategy, $validStrategies)); + } +} + +/** + * @internal + */ +function twig_escape_filter_is_safe(Node $filterArgs) +{ + foreach ($filterArgs as $arg) { + if ($arg instanceof ConstantExpression) { + return [$arg->getAttribute('value')]; + } + + return []; + } + + return ['html']; +} +} diff --git a/site/plugins/kirby-twig/vendor/twig/twig/src/Extension/ExtensionInterface.php b/site/plugins/kirby-twig/vendor/twig/twig/src/Extension/ExtensionInterface.php new file mode 100644 index 0000000..75fa237 --- /dev/null +++ b/site/plugins/kirby-twig/vendor/twig/twig/src/Extension/ExtensionInterface.php @@ -0,0 +1,68 @@ + + */ +interface ExtensionInterface +{ + /** + * Returns the token parser instances to add to the existing list. + * + * @return TokenParserInterface[] + */ + public function getTokenParsers(); + + /** + * Returns the node visitor instances to add to the existing list. + * + * @return NodeVisitorInterface[] + */ + public function getNodeVisitors(); + + /** + * Returns a list of filters to add to the existing list. + * + * @return TwigFilter[] + */ + public function getFilters(); + + /** + * Returns a list of tests to add to the existing list. + * + * @return TwigTest[] + */ + public function getTests(); + + /** + * Returns a list of functions to add to the existing list. + * + * @return TwigFunction[] + */ + public function getFunctions(); + + /** + * Returns a list of operators to add to the existing list. + * + * @return array First array of unary operators, second array of binary operators + */ + public function getOperators(); +} diff --git a/site/plugins/kirby-twig/vendor/twig/twig/src/Extension/GlobalsInterface.php b/site/plugins/kirby-twig/vendor/twig/twig/src/Extension/GlobalsInterface.php new file mode 100644 index 0000000..ec0c682 --- /dev/null +++ b/site/plugins/kirby-twig/vendor/twig/twig/src/Extension/GlobalsInterface.php @@ -0,0 +1,25 @@ + + */ +interface GlobalsInterface +{ + public function getGlobals(): array; +} diff --git a/site/plugins/kirby-twig/vendor/twig/twig/src/Extension/OptimizerExtension.php b/site/plugins/kirby-twig/vendor/twig/twig/src/Extension/OptimizerExtension.php new file mode 100644 index 0000000..965bfdb --- /dev/null +++ b/site/plugins/kirby-twig/vendor/twig/twig/src/Extension/OptimizerExtension.php @@ -0,0 +1,29 @@ +optimizers = $optimizers; + } + + public function getNodeVisitors(): array + { + return [new OptimizerNodeVisitor($this->optimizers)]; + } +} diff --git a/site/plugins/kirby-twig/vendor/twig/twig/src/Extension/ProfilerExtension.php b/site/plugins/kirby-twig/vendor/twig/twig/src/Extension/ProfilerExtension.php new file mode 100644 index 0000000..43e4a44 --- /dev/null +++ b/site/plugins/kirby-twig/vendor/twig/twig/src/Extension/ProfilerExtension.php @@ -0,0 +1,52 @@ +actives[] = $profile; + } + + /** + * @return void + */ + public function enter(Profile $profile) + { + $this->actives[0]->addProfile($profile); + array_unshift($this->actives, $profile); + } + + /** + * @return void + */ + public function leave(Profile $profile) + { + $profile->leave(); + array_shift($this->actives); + + if (1 === \count($this->actives)) { + $this->actives[0]->leave(); + } + } + + public function getNodeVisitors(): array + { + return [new ProfilerNodeVisitor(static::class)]; + } +} diff --git a/site/plugins/kirby-twig/vendor/twig/twig/src/Extension/RuntimeExtensionInterface.php b/site/plugins/kirby-twig/vendor/twig/twig/src/Extension/RuntimeExtensionInterface.php new file mode 100644 index 0000000..63bc3b1 --- /dev/null +++ b/site/plugins/kirby-twig/vendor/twig/twig/src/Extension/RuntimeExtensionInterface.php @@ -0,0 +1,19 @@ + + */ +interface RuntimeExtensionInterface +{ +} diff --git a/site/plugins/kirby-twig/vendor/twig/twig/src/Extension/SandboxExtension.php b/site/plugins/kirby-twig/vendor/twig/twig/src/Extension/SandboxExtension.php new file mode 100644 index 0000000..0a28cab --- /dev/null +++ b/site/plugins/kirby-twig/vendor/twig/twig/src/Extension/SandboxExtension.php @@ -0,0 +1,123 @@ +policy = $policy; + $this->sandboxedGlobally = $sandboxed; + } + + public function getTokenParsers(): array + { + return [new SandboxTokenParser()]; + } + + public function getNodeVisitors(): array + { + return [new SandboxNodeVisitor()]; + } + + public function enableSandbox(): void + { + $this->sandboxed = true; + } + + public function disableSandbox(): void + { + $this->sandboxed = false; + } + + public function isSandboxed(): bool + { + return $this->sandboxedGlobally || $this->sandboxed; + } + + public function isSandboxedGlobally(): bool + { + return $this->sandboxedGlobally; + } + + public function setSecurityPolicy(SecurityPolicyInterface $policy) + { + $this->policy = $policy; + } + + public function getSecurityPolicy(): SecurityPolicyInterface + { + return $this->policy; + } + + public function checkSecurity($tags, $filters, $functions): void + { + if ($this->isSandboxed()) { + $this->policy->checkSecurity($tags, $filters, $functions); + } + } + + public function checkMethodAllowed($obj, $method, int $lineno = -1, Source $source = null): void + { + if ($this->isSandboxed()) { + try { + $this->policy->checkMethodAllowed($obj, $method); + } catch (SecurityNotAllowedMethodError $e) { + $e->setSourceContext($source); + $e->setTemplateLine($lineno); + + throw $e; + } + } + } + + public function checkPropertyAllowed($obj, $method, int $lineno = -1, Source $source = null): void + { + if ($this->isSandboxed()) { + try { + $this->policy->checkPropertyAllowed($obj, $method); + } catch (SecurityNotAllowedPropertyError $e) { + $e->setSourceContext($source); + $e->setTemplateLine($lineno); + + throw $e; + } + } + } + + public function ensureToStringAllowed($obj, int $lineno = -1, Source $source = null) + { + if ($this->isSandboxed() && \is_object($obj) && method_exists($obj, '__toString')) { + try { + $this->policy->checkMethodAllowed($obj, '__toString'); + } catch (SecurityNotAllowedMethodError $e) { + $e->setSourceContext($source); + $e->setTemplateLine($lineno); + + throw $e; + } + } + + return $obj; + } +} diff --git a/site/plugins/kirby-twig/vendor/twig/twig/src/Extension/StagingExtension.php b/site/plugins/kirby-twig/vendor/twig/twig/src/Extension/StagingExtension.php new file mode 100644 index 0000000..0ea47f9 --- /dev/null +++ b/site/plugins/kirby-twig/vendor/twig/twig/src/Extension/StagingExtension.php @@ -0,0 +1,100 @@ + + * + * @internal + */ +final class StagingExtension extends AbstractExtension +{ + private $functions = []; + private $filters = []; + private $visitors = []; + private $tokenParsers = []; + private $tests = []; + + public function addFunction(TwigFunction $function): void + { + if (isset($this->functions[$function->getName()])) { + throw new \LogicException(sprintf('Function "%s" is already registered.', $function->getName())); + } + + $this->functions[$function->getName()] = $function; + } + + public function getFunctions(): array + { + return $this->functions; + } + + public function addFilter(TwigFilter $filter): void + { + if (isset($this->filters[$filter->getName()])) { + throw new \LogicException(sprintf('Filter "%s" is already registered.', $filter->getName())); + } + + $this->filters[$filter->getName()] = $filter; + } + + public function getFilters(): array + { + return $this->filters; + } + + public function addNodeVisitor(NodeVisitorInterface $visitor): void + { + $this->visitors[] = $visitor; + } + + public function getNodeVisitors(): array + { + return $this->visitors; + } + + public function addTokenParser(TokenParserInterface $parser): void + { + if (isset($this->tokenParsers[$parser->getTag()])) { + throw new \LogicException(sprintf('Tag "%s" is already registered.', $parser->getTag())); + } + + $this->tokenParsers[$parser->getTag()] = $parser; + } + + public function getTokenParsers(): array + { + return $this->tokenParsers; + } + + public function addTest(TwigTest $test): void + { + if (isset($this->tests[$test->getName()])) { + throw new \LogicException(sprintf('Test "%s" is already registered.', $test->getName())); + } + + $this->tests[$test->getName()] = $test; + } + + public function getTests(): array + { + return $this->tests; + } +} diff --git a/site/plugins/kirby-twig/vendor/twig/twig/src/Extension/StringLoaderExtension.php b/site/plugins/kirby-twig/vendor/twig/twig/src/Extension/StringLoaderExtension.php new file mode 100644 index 0000000..7b45147 --- /dev/null +++ b/site/plugins/kirby-twig/vendor/twig/twig/src/Extension/StringLoaderExtension.php @@ -0,0 +1,42 @@ + true]), + ]; + } +} +} + +namespace { +use Twig\Environment; +use Twig\TemplateWrapper; + +/** + * Loads a template from a string. + * + * {{ include(template_from_string("Hello {{ name }}")) }} + * + * @param string $template A template as a string or object implementing __toString() + * @param string $name An optional name of the template to be used in error messages + */ +function twig_template_from_string(Environment $env, $template, string $name = null): TemplateWrapper +{ + return $env->createTemplate((string) $template, $name); +} +} diff --git a/site/plugins/kirby-twig/vendor/twig/twig/src/ExtensionSet.php b/site/plugins/kirby-twig/vendor/twig/twig/src/ExtensionSet.php new file mode 100644 index 0000000..36e5bbc --- /dev/null +++ b/site/plugins/kirby-twig/vendor/twig/twig/src/ExtensionSet.php @@ -0,0 +1,463 @@ + + * + * @internal + */ +final class ExtensionSet +{ + private $extensions; + private $initialized = false; + private $runtimeInitialized = false; + private $staging; + private $parsers; + private $visitors; + private $filters; + private $tests; + private $functions; + private $unaryOperators; + private $binaryOperators; + private $globals; + private $functionCallbacks = []; + private $filterCallbacks = []; + private $parserCallbacks = []; + private $lastModified = 0; + + public function __construct() + { + $this->staging = new StagingExtension(); + } + + public function initRuntime() + { + $this->runtimeInitialized = true; + } + + public function hasExtension(string $class): bool + { + return isset($this->extensions[ltrim($class, '\\')]); + } + + public function getExtension(string $class): ExtensionInterface + { + $class = ltrim($class, '\\'); + + if (!isset($this->extensions[$class])) { + throw new RuntimeError(sprintf('The "%s" extension is not enabled.', $class)); + } + + return $this->extensions[$class]; + } + + /** + * @param ExtensionInterface[] $extensions + */ + public function setExtensions(array $extensions): void + { + foreach ($extensions as $extension) { + $this->addExtension($extension); + } + } + + /** + * @return ExtensionInterface[] + */ + public function getExtensions(): array + { + return $this->extensions; + } + + public function getSignature(): string + { + return json_encode(array_keys($this->extensions)); + } + + public function isInitialized(): bool + { + return $this->initialized || $this->runtimeInitialized; + } + + public function getLastModified(): int + { + if (0 !== $this->lastModified) { + return $this->lastModified; + } + + foreach ($this->extensions as $extension) { + $r = new \ReflectionObject($extension); + if (is_file($r->getFileName()) && ($extensionTime = filemtime($r->getFileName())) > $this->lastModified) { + $this->lastModified = $extensionTime; + } + } + + return $this->lastModified; + } + + public function addExtension(ExtensionInterface $extension): void + { + $class = \get_class($extension); + + if ($this->initialized) { + throw new \LogicException(sprintf('Unable to register extension "%s" as extensions have already been initialized.', $class)); + } + + if (isset($this->extensions[$class])) { + throw new \LogicException(sprintf('Unable to register extension "%s" as it is already registered.', $class)); + } + + $this->extensions[$class] = $extension; + } + + public function addFunction(TwigFunction $function): void + { + if ($this->initialized) { + throw new \LogicException(sprintf('Unable to add function "%s" as extensions have already been initialized.', $function->getName())); + } + + $this->staging->addFunction($function); + } + + /** + * @return TwigFunction[] + */ + public function getFunctions(): array + { + if (!$this->initialized) { + $this->initExtensions(); + } + + return $this->functions; + } + + public function getFunction(string $name): ?TwigFunction + { + if (!$this->initialized) { + $this->initExtensions(); + } + + if (isset($this->functions[$name])) { + return $this->functions[$name]; + } + + foreach ($this->functions as $pattern => $function) { + $pattern = str_replace('\\*', '(.*?)', preg_quote($pattern, '#'), $count); + + if ($count && preg_match('#^'.$pattern.'$#', $name, $matches)) { + array_shift($matches); + $function->setArguments($matches); + + return $function; + } + } + + foreach ($this->functionCallbacks as $callback) { + if (false !== $function = $callback($name)) { + return $function; + } + } + + return null; + } + + public function registerUndefinedFunctionCallback(callable $callable): void + { + $this->functionCallbacks[] = $callable; + } + + public function addFilter(TwigFilter $filter): void + { + if ($this->initialized) { + throw new \LogicException(sprintf('Unable to add filter "%s" as extensions have already been initialized.', $filter->getName())); + } + + $this->staging->addFilter($filter); + } + + /** + * @return TwigFilter[] + */ + public function getFilters(): array + { + if (!$this->initialized) { + $this->initExtensions(); + } + + return $this->filters; + } + + public function getFilter(string $name): ?TwigFilter + { + if (!$this->initialized) { + $this->initExtensions(); + } + + if (isset($this->filters[$name])) { + return $this->filters[$name]; + } + + foreach ($this->filters as $pattern => $filter) { + $pattern = str_replace('\\*', '(.*?)', preg_quote($pattern, '#'), $count); + + if ($count && preg_match('#^'.$pattern.'$#', $name, $matches)) { + array_shift($matches); + $filter->setArguments($matches); + + return $filter; + } + } + + foreach ($this->filterCallbacks as $callback) { + if (false !== $filter = $callback($name)) { + return $filter; + } + } + + return null; + } + + public function registerUndefinedFilterCallback(callable $callable): void + { + $this->filterCallbacks[] = $callable; + } + + public function addNodeVisitor(NodeVisitorInterface $visitor): void + { + if ($this->initialized) { + throw new \LogicException('Unable to add a node visitor as extensions have already been initialized.'); + } + + $this->staging->addNodeVisitor($visitor); + } + + /** + * @return NodeVisitorInterface[] + */ + public function getNodeVisitors(): array + { + if (!$this->initialized) { + $this->initExtensions(); + } + + return $this->visitors; + } + + public function addTokenParser(TokenParserInterface $parser): void + { + if ($this->initialized) { + throw new \LogicException('Unable to add a token parser as extensions have already been initialized.'); + } + + $this->staging->addTokenParser($parser); + } + + /** + * @return TokenParserInterface[] + */ + public function getTokenParsers(): array + { + if (!$this->initialized) { + $this->initExtensions(); + } + + return $this->parsers; + } + + public function getTokenParser(string $name): ?TokenParserInterface + { + if (!$this->initialized) { + $this->initExtensions(); + } + + if (isset($this->parsers[$name])) { + return $this->parsers[$name]; + } + + foreach ($this->parserCallbacks as $callback) { + if (false !== $parser = $callback($name)) { + return $parser; + } + } + + return null; + } + + public function registerUndefinedTokenParserCallback(callable $callable): void + { + $this->parserCallbacks[] = $callable; + } + + public function getGlobals(): array + { + if (null !== $this->globals) { + return $this->globals; + } + + $globals = []; + foreach ($this->extensions as $extension) { + if (!$extension instanceof GlobalsInterface) { + continue; + } + + $extGlobals = $extension->getGlobals(); + if (!\is_array($extGlobals)) { + throw new \UnexpectedValueException(sprintf('"%s::getGlobals()" must return an array of globals.', \get_class($extension))); + } + + $globals = array_merge($globals, $extGlobals); + } + + if ($this->initialized) { + $this->globals = $globals; + } + + return $globals; + } + + public function addTest(TwigTest $test): void + { + if ($this->initialized) { + throw new \LogicException(sprintf('Unable to add test "%s" as extensions have already been initialized.', $test->getName())); + } + + $this->staging->addTest($test); + } + + /** + * @return TwigTest[] + */ + public function getTests(): array + { + if (!$this->initialized) { + $this->initExtensions(); + } + + return $this->tests; + } + + public function getTest(string $name): ?TwigTest + { + if (!$this->initialized) { + $this->initExtensions(); + } + + if (isset($this->tests[$name])) { + return $this->tests[$name]; + } + + foreach ($this->tests as $pattern => $test) { + $pattern = str_replace('\\*', '(.*?)', preg_quote($pattern, '#'), $count); + + if ($count) { + if (preg_match('#^'.$pattern.'$#', $name, $matches)) { + array_shift($matches); + $test->setArguments($matches); + + return $test; + } + } + } + + return null; + } + + public function getUnaryOperators(): array + { + if (!$this->initialized) { + $this->initExtensions(); + } + + return $this->unaryOperators; + } + + public function getBinaryOperators(): array + { + if (!$this->initialized) { + $this->initExtensions(); + } + + return $this->binaryOperators; + } + + private function initExtensions(): void + { + $this->parsers = []; + $this->filters = []; + $this->functions = []; + $this->tests = []; + $this->visitors = []; + $this->unaryOperators = []; + $this->binaryOperators = []; + + foreach ($this->extensions as $extension) { + $this->initExtension($extension); + } + $this->initExtension($this->staging); + // Done at the end only, so that an exception during initialization does not mark the environment as initialized when catching the exception + $this->initialized = true; + } + + private function initExtension(ExtensionInterface $extension): void + { + // filters + foreach ($extension->getFilters() as $filter) { + $this->filters[$filter->getName()] = $filter; + } + + // functions + foreach ($extension->getFunctions() as $function) { + $this->functions[$function->getName()] = $function; + } + + // tests + foreach ($extension->getTests() as $test) { + $this->tests[$test->getName()] = $test; + } + + // token parsers + foreach ($extension->getTokenParsers() as $parser) { + if (!$parser instanceof TokenParserInterface) { + throw new \LogicException('getTokenParsers() must return an array of \Twig\TokenParser\TokenParserInterface.'); + } + + $this->parsers[$parser->getTag()] = $parser; + } + + // node visitors + foreach ($extension->getNodeVisitors() as $visitor) { + $this->visitors[] = $visitor; + } + + // operators + if ($operators = $extension->getOperators()) { + if (!\is_array($operators)) { + throw new \InvalidArgumentException(sprintf('"%s::getOperators()" must return an array with operators, got "%s".', \get_class($extension), \is_object($operators) ? \get_class($operators) : \gettype($operators).(\is_resource($operators) ? '' : '#'.$operators))); + } + + if (2 !== \count($operators)) { + throw new \InvalidArgumentException(sprintf('"%s::getOperators()" must return an array of 2 elements, got %d.', \get_class($extension), \count($operators))); + } + + $this->unaryOperators = array_merge($this->unaryOperators, $operators[0]); + $this->binaryOperators = array_merge($this->binaryOperators, $operators[1]); + } + } +} diff --git a/site/plugins/kirby-twig/vendor/twig/twig/src/FileExtensionEscapingStrategy.php b/site/plugins/kirby-twig/vendor/twig/twig/src/FileExtensionEscapingStrategy.php new file mode 100644 index 0000000..28579c3 --- /dev/null +++ b/site/plugins/kirby-twig/vendor/twig/twig/src/FileExtensionEscapingStrategy.php @@ -0,0 +1,60 @@ + + */ +class FileExtensionEscapingStrategy +{ + /** + * Guesses the best autoescaping strategy based on the file name. + * + * @param string $name The template name + * + * @return string|false The escaping strategy name to use or false to disable + */ + public static function guess(string $name) + { + if (\in_array(substr($name, -1), ['/', '\\'])) { + return 'html'; // return html for directories + } + + if ('.twig' === substr($name, -5)) { + $name = substr($name, 0, -5); + } + + $extension = pathinfo($name, PATHINFO_EXTENSION); + + switch ($extension) { + case 'js': + return 'js'; + + case 'css': + return 'css'; + + case 'txt': + return false; + + default: + return 'html'; + } + } +} diff --git a/site/plugins/kirby-twig/vendor/twig/twig/src/Lexer.php b/site/plugins/kirby-twig/vendor/twig/twig/src/Lexer.php new file mode 100644 index 0000000..4fcb223 --- /dev/null +++ b/site/plugins/kirby-twig/vendor/twig/twig/src/Lexer.php @@ -0,0 +1,501 @@ + + */ +class Lexer +{ + private $tokens; + private $code; + private $cursor; + private $lineno; + private $end; + private $state; + private $states; + private $brackets; + private $env; + private $source; + private $options; + private $regexes; + private $position; + private $positions; + private $currentVarBlockLine; + + const STATE_DATA = 0; + const STATE_BLOCK = 1; + const STATE_VAR = 2; + const STATE_STRING = 3; + const STATE_INTERPOLATION = 4; + + const REGEX_NAME = '/[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*/A'; + const REGEX_NUMBER = '/[0-9]+(?:\.[0-9]+)?([Ee][\+\-][0-9]+)?/A'; + const REGEX_STRING = '/"([^#"\\\\]*(?:\\\\.[^#"\\\\]*)*)"|\'([^\'\\\\]*(?:\\\\.[^\'\\\\]*)*)\'/As'; + const REGEX_DQ_STRING_DELIM = '/"/A'; + const REGEX_DQ_STRING_PART = '/[^#"\\\\]*(?:(?:\\\\.|#(?!\{))[^#"\\\\]*)*/As'; + const PUNCTUATION = '()[]{}?:.,|'; + + public function __construct(Environment $env, array $options = []) + { + $this->env = $env; + + $this->options = array_merge([ + 'tag_comment' => ['{#', '#}'], + 'tag_block' => ['{%', '%}'], + 'tag_variable' => ['{{', '}}'], + 'whitespace_trim' => '-', + 'whitespace_line_trim' => '~', + 'whitespace_line_chars' => ' \t\0\x0B', + 'interpolation' => ['#{', '}'], + ], $options); + + // when PHP 7.3 is the min version, we will be able to remove the '#' part in preg_quote as it's part of the default + $this->regexes = [ + // }} + 'lex_var' => '{ + \s* + (?:'. + preg_quote($this->options['whitespace_trim'].$this->options['tag_variable'][1], '#').'\s*'. // -}}\s* + '|'. + preg_quote($this->options['whitespace_line_trim'].$this->options['tag_variable'][1], '#').'['.$this->options['whitespace_line_chars'].']*'. // ~}}[ \t\0\x0B]* + '|'. + preg_quote($this->options['tag_variable'][1], '#'). // }} + ') + }Ax', + + // %} + 'lex_block' => '{ + \s* + (?:'. + preg_quote($this->options['whitespace_trim'].$this->options['tag_block'][1], '#').'\s*\n?'. // -%}\s*\n? + '|'. + preg_quote($this->options['whitespace_line_trim'].$this->options['tag_block'][1], '#').'['.$this->options['whitespace_line_chars'].']*'. // ~%}[ \t\0\x0B]* + '|'. + preg_quote($this->options['tag_block'][1], '#').'\n?'. // %}\n? + ') + }Ax', + + // {% endverbatim %} + 'lex_raw_data' => '{'. + preg_quote($this->options['tag_block'][0], '#'). // {% + '('. + $this->options['whitespace_trim']. // - + '|'. + $this->options['whitespace_line_trim']. // ~ + ')?\s*endverbatim\s*'. + '(?:'. + preg_quote($this->options['whitespace_trim'].$this->options['tag_block'][1], '#').'\s*'. // -%} + '|'. + preg_quote($this->options['whitespace_line_trim'].$this->options['tag_block'][1], '#').'['.$this->options['whitespace_line_chars'].']*'. // ~%}[ \t\0\x0B]* + '|'. + preg_quote($this->options['tag_block'][1], '#'). // %} + ') + }sx', + + 'operator' => $this->getOperatorRegex(), + + // #} + 'lex_comment' => '{ + (?:'. + preg_quote($this->options['whitespace_trim'].$this->options['tag_comment'][1], '#').'\s*\n?'. // -#}\s*\n? + '|'. + preg_quote($this->options['whitespace_line_trim'].$this->options['tag_comment'][1], '#').'['.$this->options['whitespace_line_chars'].']*'. // ~#}[ \t\0\x0B]* + '|'. + preg_quote($this->options['tag_comment'][1], '#').'\n?'. // #}\n? + ') + }sx', + + // verbatim %} + 'lex_block_raw' => '{ + \s*verbatim\s* + (?:'. + preg_quote($this->options['whitespace_trim'].$this->options['tag_block'][1], '#').'\s*'. // -%}\s* + '|'. + preg_quote($this->options['whitespace_line_trim'].$this->options['tag_block'][1], '#').'['.$this->options['whitespace_line_chars'].']*'. // ~%}[ \t\0\x0B]* + '|'. + preg_quote($this->options['tag_block'][1], '#'). // %} + ') + }Asx', + + 'lex_block_line' => '{\s*line\s+(\d+)\s*'.preg_quote($this->options['tag_block'][1], '#').'}As', + + // {{ or {% or {# + 'lex_tokens_start' => '{ + ('. + preg_quote($this->options['tag_variable'][0], '#'). // {{ + '|'. + preg_quote($this->options['tag_block'][0], '#'). // {% + '|'. + preg_quote($this->options['tag_comment'][0], '#'). // {# + ')('. + preg_quote($this->options['whitespace_trim'], '#'). // - + '|'. + preg_quote($this->options['whitespace_line_trim'], '#'). // ~ + ')? + }sx', + 'interpolation_start' => '{'.preg_quote($this->options['interpolation'][0], '#').'\s*}A', + 'interpolation_end' => '{\s*'.preg_quote($this->options['interpolation'][1], '#').'}A', + ]; + } + + public function tokenize(Source $source): TokenStream + { + $this->source = $source; + $this->code = str_replace(["\r\n", "\r"], "\n", $source->getCode()); + $this->cursor = 0; + $this->lineno = 1; + $this->end = \strlen($this->code); + $this->tokens = []; + $this->state = self::STATE_DATA; + $this->states = []; + $this->brackets = []; + $this->position = -1; + + // find all token starts in one go + preg_match_all($this->regexes['lex_tokens_start'], $this->code, $matches, PREG_OFFSET_CAPTURE); + $this->positions = $matches; + + while ($this->cursor < $this->end) { + // dispatch to the lexing functions depending + // on the current state + switch ($this->state) { + case self::STATE_DATA: + $this->lexData(); + break; + + case self::STATE_BLOCK: + $this->lexBlock(); + break; + + case self::STATE_VAR: + $this->lexVar(); + break; + + case self::STATE_STRING: + $this->lexString(); + break; + + case self::STATE_INTERPOLATION: + $this->lexInterpolation(); + break; + } + } + + $this->pushToken(/* Token::EOF_TYPE */ -1); + + if (!empty($this->brackets)) { + list($expect, $lineno) = array_pop($this->brackets); + throw new SyntaxError(sprintf('Unclosed "%s".', $expect), $lineno, $this->source); + } + + return new TokenStream($this->tokens, $this->source); + } + + private function lexData(): void + { + // if no matches are left we return the rest of the template as simple text token + if ($this->position == \count($this->positions[0]) - 1) { + $this->pushToken(/* Token::TEXT_TYPE */ 0, substr($this->code, $this->cursor)); + $this->cursor = $this->end; + + return; + } + + // Find the first token after the current cursor + $position = $this->positions[0][++$this->position]; + while ($position[1] < $this->cursor) { + if ($this->position == \count($this->positions[0]) - 1) { + return; + } + $position = $this->positions[0][++$this->position]; + } + + // push the template text first + $text = $textContent = substr($this->code, $this->cursor, $position[1] - $this->cursor); + + // trim? + if (isset($this->positions[2][$this->position][0])) { + if ($this->options['whitespace_trim'] === $this->positions[2][$this->position][0]) { + // whitespace_trim detected ({%-, {{- or {#-) + $text = rtrim($text); + } elseif ($this->options['whitespace_line_trim'] === $this->positions[2][$this->position][0]) { + // whitespace_line_trim detected ({%~, {{~ or {#~) + // don't trim \r and \n + $text = rtrim($text, " \t\0\x0B"); + } + } + $this->pushToken(/* Token::TEXT_TYPE */ 0, $text); + $this->moveCursor($textContent.$position[0]); + + switch ($this->positions[1][$this->position][0]) { + case $this->options['tag_comment'][0]: + $this->lexComment(); + break; + + case $this->options['tag_block'][0]: + // raw data? + if (preg_match($this->regexes['lex_block_raw'], $this->code, $match, 0, $this->cursor)) { + $this->moveCursor($match[0]); + $this->lexRawData(); + // {% line \d+ %} + } elseif (preg_match($this->regexes['lex_block_line'], $this->code, $match, 0, $this->cursor)) { + $this->moveCursor($match[0]); + $this->lineno = (int) $match[1]; + } else { + $this->pushToken(/* Token::BLOCK_START_TYPE */ 1); + $this->pushState(self::STATE_BLOCK); + $this->currentVarBlockLine = $this->lineno; + } + break; + + case $this->options['tag_variable'][0]: + $this->pushToken(/* Token::VAR_START_TYPE */ 2); + $this->pushState(self::STATE_VAR); + $this->currentVarBlockLine = $this->lineno; + break; + } + } + + private function lexBlock(): void + { + if (empty($this->brackets) && preg_match($this->regexes['lex_block'], $this->code, $match, 0, $this->cursor)) { + $this->pushToken(/* Token::BLOCK_END_TYPE */ 3); + $this->moveCursor($match[0]); + $this->popState(); + } else { + $this->lexExpression(); + } + } + + private function lexVar(): void + { + if (empty($this->brackets) && preg_match($this->regexes['lex_var'], $this->code, $match, 0, $this->cursor)) { + $this->pushToken(/* Token::VAR_END_TYPE */ 4); + $this->moveCursor($match[0]); + $this->popState(); + } else { + $this->lexExpression(); + } + } + + private function lexExpression(): void + { + // whitespace + if (preg_match('/\s+/A', $this->code, $match, 0, $this->cursor)) { + $this->moveCursor($match[0]); + + if ($this->cursor >= $this->end) { + throw new SyntaxError(sprintf('Unclosed "%s".', self::STATE_BLOCK === $this->state ? 'block' : 'variable'), $this->currentVarBlockLine, $this->source); + } + } + + // arrow function + if ('=' === $this->code[$this->cursor] && '>' === $this->code[$this->cursor + 1]) { + $this->pushToken(Token::ARROW_TYPE, '=>'); + $this->moveCursor('=>'); + } + // operators + elseif (preg_match($this->regexes['operator'], $this->code, $match, 0, $this->cursor)) { + $this->pushToken(/* Token::OPERATOR_TYPE */ 8, preg_replace('/\s+/', ' ', $match[0])); + $this->moveCursor($match[0]); + } + // names + elseif (preg_match(self::REGEX_NAME, $this->code, $match, 0, $this->cursor)) { + $this->pushToken(/* Token::NAME_TYPE */ 5, $match[0]); + $this->moveCursor($match[0]); + } + // numbers + elseif (preg_match(self::REGEX_NUMBER, $this->code, $match, 0, $this->cursor)) { + $number = (float) $match[0]; // floats + if (ctype_digit($match[0]) && $number <= PHP_INT_MAX) { + $number = (int) $match[0]; // integers lower than the maximum + } + $this->pushToken(/* Token::NUMBER_TYPE */ 6, $number); + $this->moveCursor($match[0]); + } + // punctuation + elseif (false !== strpos(self::PUNCTUATION, $this->code[$this->cursor])) { + // opening bracket + if (false !== strpos('([{', $this->code[$this->cursor])) { + $this->brackets[] = [$this->code[$this->cursor], $this->lineno]; + } + // closing bracket + elseif (false !== strpos(')]}', $this->code[$this->cursor])) { + if (empty($this->brackets)) { + throw new SyntaxError(sprintf('Unexpected "%s".', $this->code[$this->cursor]), $this->lineno, $this->source); + } + + list($expect, $lineno) = array_pop($this->brackets); + if ($this->code[$this->cursor] != strtr($expect, '([{', ')]}')) { + throw new SyntaxError(sprintf('Unclosed "%s".', $expect), $lineno, $this->source); + } + } + + $this->pushToken(/* Token::PUNCTUATION_TYPE */ 9, $this->code[$this->cursor]); + ++$this->cursor; + } + // strings + elseif (preg_match(self::REGEX_STRING, $this->code, $match, 0, $this->cursor)) { + $this->pushToken(/* Token::STRING_TYPE */ 7, stripcslashes(substr($match[0], 1, -1))); + $this->moveCursor($match[0]); + } + // opening double quoted string + elseif (preg_match(self::REGEX_DQ_STRING_DELIM, $this->code, $match, 0, $this->cursor)) { + $this->brackets[] = ['"', $this->lineno]; + $this->pushState(self::STATE_STRING); + $this->moveCursor($match[0]); + } + // unlexable + else { + throw new SyntaxError(sprintf('Unexpected character "%s".', $this->code[$this->cursor]), $this->lineno, $this->source); + } + } + + private function lexRawData(): void + { + if (!preg_match($this->regexes['lex_raw_data'], $this->code, $match, PREG_OFFSET_CAPTURE, $this->cursor)) { + throw new SyntaxError('Unexpected end of file: Unclosed "verbatim" block.', $this->lineno, $this->source); + } + + $text = substr($this->code, $this->cursor, $match[0][1] - $this->cursor); + $this->moveCursor($text.$match[0][0]); + + // trim? + if (isset($match[1][0])) { + if ($this->options['whitespace_trim'] === $match[1][0]) { + // whitespace_trim detected ({%-, {{- or {#-) + $text = rtrim($text); + } else { + // whitespace_line_trim detected ({%~, {{~ or {#~) + // don't trim \r and \n + $text = rtrim($text, " \t\0\x0B"); + } + } + + $this->pushToken(/* Token::TEXT_TYPE */ 0, $text); + } + + private function lexComment(): void + { + if (!preg_match($this->regexes['lex_comment'], $this->code, $match, PREG_OFFSET_CAPTURE, $this->cursor)) { + throw new SyntaxError('Unclosed comment.', $this->lineno, $this->source); + } + + $this->moveCursor(substr($this->code, $this->cursor, $match[0][1] - $this->cursor).$match[0][0]); + } + + private function lexString(): void + { + if (preg_match($this->regexes['interpolation_start'], $this->code, $match, 0, $this->cursor)) { + $this->brackets[] = [$this->options['interpolation'][0], $this->lineno]; + $this->pushToken(/* Token::INTERPOLATION_START_TYPE */ 10); + $this->moveCursor($match[0]); + $this->pushState(self::STATE_INTERPOLATION); + } elseif (preg_match(self::REGEX_DQ_STRING_PART, $this->code, $match, 0, $this->cursor) && \strlen($match[0]) > 0) { + $this->pushToken(/* Token::STRING_TYPE */ 7, stripcslashes($match[0])); + $this->moveCursor($match[0]); + } elseif (preg_match(self::REGEX_DQ_STRING_DELIM, $this->code, $match, 0, $this->cursor)) { + list($expect, $lineno) = array_pop($this->brackets); + if ('"' != $this->code[$this->cursor]) { + throw new SyntaxError(sprintf('Unclosed "%s".', $expect), $lineno, $this->source); + } + + $this->popState(); + ++$this->cursor; + } else { + // unlexable + throw new SyntaxError(sprintf('Unexpected character "%s".', $this->code[$this->cursor]), $this->lineno, $this->source); + } + } + + private function lexInterpolation(): void + { + $bracket = end($this->brackets); + if ($this->options['interpolation'][0] === $bracket[0] && preg_match($this->regexes['interpolation_end'], $this->code, $match, 0, $this->cursor)) { + array_pop($this->brackets); + $this->pushToken(/* Token::INTERPOLATION_END_TYPE */ 11); + $this->moveCursor($match[0]); + $this->popState(); + } else { + $this->lexExpression(); + } + } + + private function pushToken($type, $value = ''): void + { + // do not push empty text tokens + if (/* Token::TEXT_TYPE */ 0 === $type && '' === $value) { + return; + } + + $this->tokens[] = new Token($type, $value, $this->lineno); + } + + private function moveCursor($text): void + { + $this->cursor += \strlen($text); + $this->lineno += substr_count($text, "\n"); + } + + private function getOperatorRegex(): string + { + $operators = array_merge( + ['='], + array_keys($this->env->getUnaryOperators()), + array_keys($this->env->getBinaryOperators()) + ); + + $operators = array_combine($operators, array_map('strlen', $operators)); + arsort($operators); + + $regex = []; + foreach ($operators as $operator => $length) { + // an operator that ends with a character must be followed by + // a whitespace, a parenthesis, an opening map [ or sequence { + $r = preg_quote($operator, '/'); + if (ctype_alpha($operator[$length - 1])) { + $r .= '(?=[\s()\[{])'; + } + + // an operator that begins with a character must not have a dot or pipe before + if (ctype_alpha($operator[0])) { + $r = '(?states[] = $this->state; + $this->state = $state; + } + + private function popState(): void + { + if (0 === \count($this->states)) { + throw new \LogicException('Cannot pop state without a previous state.'); + } + + $this->state = array_pop($this->states); + } +} diff --git a/site/plugins/kirby-twig/vendor/twig/twig/src/Loader/ArrayLoader.php b/site/plugins/kirby-twig/vendor/twig/twig/src/Loader/ArrayLoader.php new file mode 100644 index 0000000..a6164bb --- /dev/null +++ b/site/plugins/kirby-twig/vendor/twig/twig/src/Loader/ArrayLoader.php @@ -0,0 +1,78 @@ + + */ +final class ArrayLoader implements LoaderInterface +{ + private $templates = []; + + /** + * @param array $templates An array of templates (keys are the names, and values are the source code) + */ + public function __construct(array $templates = []) + { + $this->templates = $templates; + } + + public function setTemplate(string $name, string $template): void + { + $this->templates[$name] = $template; + } + + public function getSourceContext(string $name): Source + { + $name = (string) $name; + if (!isset($this->templates[$name])) { + throw new LoaderError(sprintf('Template "%s" is not defined.', $name)); + } + + return new Source($this->templates[$name], $name); + } + + public function exists(string $name): bool + { + return isset($this->templates[$name]); + } + + public function getCacheKey(string $name): string + { + if (!isset($this->templates[$name])) { + throw new LoaderError(sprintf('Template "%s" is not defined.', $name)); + } + + return $name.':'.$this->templates[$name]; + } + + public function isFresh(string $name, int $time): bool + { + if (!isset($this->templates[$name])) { + throw new LoaderError(sprintf('Template "%s" is not defined.', $name)); + } + + return true; + } +} diff --git a/site/plugins/kirby-twig/vendor/twig/twig/src/Loader/ChainLoader.php b/site/plugins/kirby-twig/vendor/twig/twig/src/Loader/ChainLoader.php new file mode 100644 index 0000000..fbf4f3a --- /dev/null +++ b/site/plugins/kirby-twig/vendor/twig/twig/src/Loader/ChainLoader.php @@ -0,0 +1,119 @@ + + */ +final class ChainLoader implements LoaderInterface +{ + private $hasSourceCache = []; + private $loaders = []; + + /** + * @param LoaderInterface[] $loaders + */ + public function __construct(array $loaders = []) + { + foreach ($loaders as $loader) { + $this->addLoader($loader); + } + } + + public function addLoader(LoaderInterface $loader): void + { + $this->loaders[] = $loader; + $this->hasSourceCache = []; + } + + /** + * @return LoaderInterface[] + */ + public function getLoaders(): array + { + return $this->loaders; + } + + public function getSourceContext(string $name): Source + { + $exceptions = []; + foreach ($this->loaders as $loader) { + if (!$loader->exists($name)) { + continue; + } + + try { + return $loader->getSourceContext($name); + } catch (LoaderError $e) { + $exceptions[] = $e->getMessage(); + } + } + + throw new LoaderError(sprintf('Template "%s" is not defined%s.', $name, $exceptions ? ' ('.implode(', ', $exceptions).')' : '')); + } + + public function exists(string $name): bool + { + if (isset($this->hasSourceCache[$name])) { + return $this->hasSourceCache[$name]; + } + + foreach ($this->loaders as $loader) { + if ($loader->exists($name)) { + return $this->hasSourceCache[$name] = true; + } + } + + return $this->hasSourceCache[$name] = false; + } + + public function getCacheKey(string $name): string + { + $exceptions = []; + foreach ($this->loaders as $loader) { + if (!$loader->exists($name)) { + continue; + } + + try { + return $loader->getCacheKey($name); + } catch (LoaderError $e) { + $exceptions[] = \get_class($loader).': '.$e->getMessage(); + } + } + + throw new LoaderError(sprintf('Template "%s" is not defined%s.', $name, $exceptions ? ' ('.implode(', ', $exceptions).')' : '')); + } + + public function isFresh(string $name, int $time): bool + { + $exceptions = []; + foreach ($this->loaders as $loader) { + if (!$loader->exists($name)) { + continue; + } + + try { + return $loader->isFresh($name, $time); + } catch (LoaderError $e) { + $exceptions[] = \get_class($loader).': '.$e->getMessage(); + } + } + + throw new LoaderError(sprintf('Template "%s" is not defined%s.', $name, $exceptions ? ' ('.implode(', ', $exceptions).')' : '')); + } +} diff --git a/site/plugins/kirby-twig/vendor/twig/twig/src/Loader/FilesystemLoader.php b/site/plugins/kirby-twig/vendor/twig/twig/src/Loader/FilesystemLoader.php new file mode 100644 index 0000000..e55943d --- /dev/null +++ b/site/plugins/kirby-twig/vendor/twig/twig/src/Loader/FilesystemLoader.php @@ -0,0 +1,283 @@ + + */ +class FilesystemLoader implements LoaderInterface +{ + /** Identifier of the main namespace. */ + const MAIN_NAMESPACE = '__main__'; + + protected $paths = []; + protected $cache = []; + protected $errorCache = []; + + private $rootPath; + + /** + * @param string|array $paths A path or an array of paths where to look for templates + * @param string|null $rootPath The root path common to all relative paths (null for getcwd()) + */ + public function __construct($paths = [], string $rootPath = null) + { + $this->rootPath = (null === $rootPath ? getcwd() : $rootPath).\DIRECTORY_SEPARATOR; + if (false !== $realPath = realpath($rootPath)) { + $this->rootPath = $realPath.\DIRECTORY_SEPARATOR; + } + + if ($paths) { + $this->setPaths($paths); + } + } + + /** + * Returns the paths to the templates. + */ + public function getPaths(string $namespace = self::MAIN_NAMESPACE): array + { + return isset($this->paths[$namespace]) ? $this->paths[$namespace] : []; + } + + /** + * Returns the path namespaces. + * + * The main namespace is always defined. + */ + public function getNamespaces(): array + { + return array_keys($this->paths); + } + + /** + * @param string|array $paths A path or an array of paths where to look for templates + */ + public function setPaths($paths, string $namespace = self::MAIN_NAMESPACE): void + { + if (!\is_array($paths)) { + $paths = [$paths]; + } + + $this->paths[$namespace] = []; + foreach ($paths as $path) { + $this->addPath($path, $namespace); + } + } + + /** + * @throws LoaderError + */ + public function addPath(string $path, string $namespace = self::MAIN_NAMESPACE): void + { + // invalidate the cache + $this->cache = $this->errorCache = []; + + $checkPath = $this->isAbsolutePath($path) ? $path : $this->rootPath.$path; + if (!is_dir($checkPath)) { + throw new LoaderError(sprintf('The "%s" directory does not exist ("%s").', $path, $checkPath)); + } + + $this->paths[$namespace][] = rtrim($path, '/\\'); + } + + /** + * @throws LoaderError + */ + public function prependPath(string $path, string $namespace = self::MAIN_NAMESPACE): void + { + // invalidate the cache + $this->cache = $this->errorCache = []; + + $checkPath = $this->isAbsolutePath($path) ? $path : $this->rootPath.$path; + if (!is_dir($checkPath)) { + throw new LoaderError(sprintf('The "%s" directory does not exist ("%s").', $path, $checkPath)); + } + + $path = rtrim($path, '/\\'); + + if (!isset($this->paths[$namespace])) { + $this->paths[$namespace][] = $path; + } else { + array_unshift($this->paths[$namespace], $path); + } + } + + public function getSourceContext(string $name): Source + { + if (null === $path = $this->findTemplate($name)) { + return new Source('', $name, ''); + } + + return new Source(file_get_contents($path), $name, $path); + } + + public function getCacheKey(string $name): string + { + if (null === $path = $this->findTemplate($name)) { + return ''; + } + $len = \strlen($this->rootPath); + if (0 === strncmp($this->rootPath, $path, $len)) { + return substr($path, $len); + } + + return $path; + } + + /** + * @return bool + */ + public function exists(string $name) + { + $name = $this->normalizeName($name); + + if (isset($this->cache[$name])) { + return true; + } + + return null !== $this->findTemplate($name, false); + } + + public function isFresh(string $name, int $time): bool + { + // false support to be removed in 3.0 + if (null === $path = $this->findTemplate($name)) { + return false; + } + + return filemtime($path) < $time; + } + + /** + * @return string|null + */ + protected function findTemplate(string $name, bool $throw = true) + { + $name = $this->normalizeName($name); + + if (isset($this->cache[$name])) { + return $this->cache[$name]; + } + + if (isset($this->errorCache[$name])) { + if (!$throw) { + return null; + } + + throw new LoaderError($this->errorCache[$name]); + } + + try { + $this->validateName($name); + + list($namespace, $shortname) = $this->parseName($name); + } catch (LoaderError $e) { + if (!$throw) { + return null; + } + + throw $e; + } + + if (!isset($this->paths[$namespace])) { + $this->errorCache[$name] = sprintf('There are no registered paths for namespace "%s".', $namespace); + + if (!$throw) { + return null; + } + + throw new LoaderError($this->errorCache[$name]); + } + + foreach ($this->paths[$namespace] as $path) { + if (!$this->isAbsolutePath($path)) { + $path = $this->rootPath.$path; + } + + if (is_file($path.'/'.$shortname)) { + if (false !== $realpath = realpath($path.'/'.$shortname)) { + return $this->cache[$name] = $realpath; + } + + return $this->cache[$name] = $path.'/'.$shortname; + } + } + + $this->errorCache[$name] = sprintf('Unable to find template "%s" (looked into: %s).', $name, implode(', ', $this->paths[$namespace])); + + if (!$throw) { + return null; + } + + throw new LoaderError($this->errorCache[$name]); + } + + private function normalizeName(string $name): string + { + return preg_replace('#/{2,}#', '/', str_replace('\\', '/', $name)); + } + + private function parseName(string $name, string $default = self::MAIN_NAMESPACE): array + { + if (isset($name[0]) && '@' == $name[0]) { + if (false === $pos = strpos($name, '/')) { + throw new LoaderError(sprintf('Malformed namespaced template name "%s" (expecting "@namespace/template_name").', $name)); + } + + $namespace = substr($name, 1, $pos - 1); + $shortname = substr($name, $pos + 1); + + return [$namespace, $shortname]; + } + + return [$default, $name]; + } + + private function validateName(string $name): void + { + if (false !== strpos($name, "\0")) { + throw new LoaderError('A template name cannot contain NUL bytes.'); + } + + $name = ltrim($name, '/'); + $parts = explode('/', $name); + $level = 0; + foreach ($parts as $part) { + if ('..' === $part) { + --$level; + } elseif ('.' !== $part) { + ++$level; + } + + if ($level < 0) { + throw new LoaderError(sprintf('Looks like you try to load a template outside configured directories (%s).', $name)); + } + } + } + + private function isAbsolutePath(string $file): bool + { + return strspn($file, '/\\', 0, 1) + || (\strlen($file) > 3 && ctype_alpha($file[0]) + && ':' === $file[1] + && strspn($file, '/\\', 2, 1) + ) + || null !== parse_url($file, PHP_URL_SCHEME) + ; + } +} diff --git a/site/plugins/kirby-twig/vendor/twig/twig/src/Loader/LoaderInterface.php b/site/plugins/kirby-twig/vendor/twig/twig/src/Loader/LoaderInterface.php new file mode 100644 index 0000000..fec7e85 --- /dev/null +++ b/site/plugins/kirby-twig/vendor/twig/twig/src/Loader/LoaderInterface.php @@ -0,0 +1,49 @@ + + */ +interface LoaderInterface +{ + /** + * Returns the source context for a given template logical name. + * + * @throws LoaderError When $name is not found + */ + public function getSourceContext(string $name): Source; + + /** + * Gets the cache key to use for the cache for a given template name. + * + * @throws LoaderError When $name is not found + */ + public function getCacheKey(string $name): string; + + /** + * @param int $time Timestamp of the last modification time of the cached template + * + * @throws LoaderError When $name is not found + */ + public function isFresh(string $name, int $time): bool; + + /** + * @return bool + */ + public function exists(string $name); +} diff --git a/site/plugins/kirby-twig/vendor/twig/twig/src/Markup.php b/site/plugins/kirby-twig/vendor/twig/twig/src/Markup.php new file mode 100644 index 0000000..c8c5e1a --- /dev/null +++ b/site/plugins/kirby-twig/vendor/twig/twig/src/Markup.php @@ -0,0 +1,47 @@ + + */ +class Markup implements \Countable, \JsonSerializable +{ + private $content; + private $charset; + + public function __construct($content, $charset) + { + $this->content = (string) $content; + $this->charset = $charset; + } + + public function __toString() + { + return $this->content; + } + + /** + * @return int + */ + public function count() + { + return mb_strlen($this->content, $this->charset); + } + + public function jsonSerialize() + { + return $this->content; + } +} diff --git a/site/plugins/kirby-twig/vendor/twig/twig/src/Node/AutoEscapeNode.php b/site/plugins/kirby-twig/vendor/twig/twig/src/Node/AutoEscapeNode.php new file mode 100644 index 0000000..cd97041 --- /dev/null +++ b/site/plugins/kirby-twig/vendor/twig/twig/src/Node/AutoEscapeNode.php @@ -0,0 +1,38 @@ + + */ +class AutoEscapeNode extends Node +{ + public function __construct($value, Node $body, int $lineno, string $tag = 'autoescape') + { + parent::__construct(['body' => $body], ['value' => $value], $lineno, $tag); + } + + public function compile(Compiler $compiler): void + { + $compiler->subcompile($this->getNode('body')); + } +} diff --git a/site/plugins/kirby-twig/vendor/twig/twig/src/Node/BlockNode.php b/site/plugins/kirby-twig/vendor/twig/twig/src/Node/BlockNode.php new file mode 100644 index 0000000..0632ba7 --- /dev/null +++ b/site/plugins/kirby-twig/vendor/twig/twig/src/Node/BlockNode.php @@ -0,0 +1,44 @@ + + */ +class BlockNode extends Node +{ + public function __construct(string $name, Node $body, int $lineno, string $tag = null) + { + parent::__construct(['body' => $body], ['name' => $name], $lineno, $tag); + } + + public function compile(Compiler $compiler): void + { + $compiler + ->addDebugInfo($this) + ->write(sprintf("public function block_%s(\$context, array \$blocks = [])\n", $this->getAttribute('name')), "{\n") + ->indent() + ->write("\$macros = \$this->macros;\n") + ; + + $compiler + ->subcompile($this->getNode('body')) + ->outdent() + ->write("}\n\n") + ; + } +} diff --git a/site/plugins/kirby-twig/vendor/twig/twig/src/Node/BlockReferenceNode.php b/site/plugins/kirby-twig/vendor/twig/twig/src/Node/BlockReferenceNode.php new file mode 100644 index 0000000..cc8af5b --- /dev/null +++ b/site/plugins/kirby-twig/vendor/twig/twig/src/Node/BlockReferenceNode.php @@ -0,0 +1,36 @@ + + */ +class BlockReferenceNode extends Node implements NodeOutputInterface +{ + public function __construct(string $name, int $lineno, string $tag = null) + { + parent::__construct([], ['name' => $name], $lineno, $tag); + } + + public function compile(Compiler $compiler): void + { + $compiler + ->addDebugInfo($this) + ->write(sprintf("\$this->displayBlock('%s', \$context, \$blocks);\n", $this->getAttribute('name'))) + ; + } +} diff --git a/site/plugins/kirby-twig/vendor/twig/twig/src/Node/BodyNode.php b/site/plugins/kirby-twig/vendor/twig/twig/src/Node/BodyNode.php new file mode 100644 index 0000000..041cbf6 --- /dev/null +++ b/site/plugins/kirby-twig/vendor/twig/twig/src/Node/BodyNode.php @@ -0,0 +1,21 @@ + + */ +class BodyNode extends Node +{ +} diff --git a/site/plugins/kirby-twig/vendor/twig/twig/src/Node/CheckSecurityCallNode.php b/site/plugins/kirby-twig/vendor/twig/twig/src/Node/CheckSecurityCallNode.php new file mode 100644 index 0000000..a78a38d --- /dev/null +++ b/site/plugins/kirby-twig/vendor/twig/twig/src/Node/CheckSecurityCallNode.php @@ -0,0 +1,28 @@ + + */ +class CheckSecurityCallNode extends Node +{ + public function compile(Compiler $compiler) + { + $compiler + ->write("\$this->sandbox = \$this->env->getExtension('\Twig\Extension\SandboxExtension');\n") + ->write("\$this->checkSecurity();\n") + ; + } +} diff --git a/site/plugins/kirby-twig/vendor/twig/twig/src/Node/CheckSecurityNode.php b/site/plugins/kirby-twig/vendor/twig/twig/src/Node/CheckSecurityNode.php new file mode 100644 index 0000000..4727327 --- /dev/null +++ b/site/plugins/kirby-twig/vendor/twig/twig/src/Node/CheckSecurityNode.php @@ -0,0 +1,88 @@ + + */ +class CheckSecurityNode extends Node +{ + private $usedFilters; + private $usedTags; + private $usedFunctions; + + public function __construct(array $usedFilters, array $usedTags, array $usedFunctions) + { + $this->usedFilters = $usedFilters; + $this->usedTags = $usedTags; + $this->usedFunctions = $usedFunctions; + + parent::__construct(); + } + + public function compile(Compiler $compiler): void + { + $tags = $filters = $functions = []; + foreach (['tags', 'filters', 'functions'] as $type) { + foreach ($this->{'used'.ucfirst($type)} as $name => $node) { + if ($node instanceof Node) { + ${$type}[$name] = $node->getTemplateLine(); + } else { + ${$type}[$node] = null; + } + } + } + + $compiler + ->write("\n") + ->write("public function checkSecurity()\n") + ->write("{\n") + ->indent() + ->write('static $tags = ')->repr(array_filter($tags))->raw(";\n") + ->write('static $filters = ')->repr(array_filter($filters))->raw(";\n") + ->write('static $functions = ')->repr(array_filter($functions))->raw(";\n\n") + ->write("try {\n") + ->indent() + ->write("\$this->sandbox->checkSecurity(\n") + ->indent() + ->write(!$tags ? "[],\n" : "['".implode("', '", array_keys($tags))."'],\n") + ->write(!$filters ? "[],\n" : "['".implode("', '", array_keys($filters))."'],\n") + ->write(!$functions ? "[]\n" : "['".implode("', '", array_keys($functions))."']\n") + ->outdent() + ->write(");\n") + ->outdent() + ->write("} catch (SecurityError \$e) {\n") + ->indent() + ->write("\$e->setSourceContext(\$this->source);\n\n") + ->write("if (\$e instanceof SecurityNotAllowedTagError && isset(\$tags[\$e->getTagName()])) {\n") + ->indent() + ->write("\$e->setTemplateLine(\$tags[\$e->getTagName()]);\n") + ->outdent() + ->write("} elseif (\$e instanceof SecurityNotAllowedFilterError && isset(\$filters[\$e->getFilterName()])) {\n") + ->indent() + ->write("\$e->setTemplateLine(\$filters[\$e->getFilterName()]);\n") + ->outdent() + ->write("} elseif (\$e instanceof SecurityNotAllowedFunctionError && isset(\$functions[\$e->getFunctionName()])) {\n") + ->indent() + ->write("\$e->setTemplateLine(\$functions[\$e->getFunctionName()]);\n") + ->outdent() + ->write("}\n\n") + ->write("throw \$e;\n") + ->outdent() + ->write("}\n\n") + ->outdent() + ->write("}\n") + ; + } +} diff --git a/site/plugins/kirby-twig/vendor/twig/twig/src/Node/CheckToStringNode.php b/site/plugins/kirby-twig/vendor/twig/twig/src/Node/CheckToStringNode.php new file mode 100644 index 0000000..c7a9d69 --- /dev/null +++ b/site/plugins/kirby-twig/vendor/twig/twig/src/Node/CheckToStringNode.php @@ -0,0 +1,45 @@ + + */ +class CheckToStringNode extends AbstractExpression +{ + public function __construct(AbstractExpression $expr) + { + parent::__construct(['expr' => $expr], [], $expr->getTemplateLine(), $expr->getNodeTag()); + } + + public function compile(Compiler $compiler): void + { + $expr = $this->getNode('expr'); + $compiler + ->raw('$this->sandbox->ensureToStringAllowed(') + ->subcompile($expr) + ->raw(', ') + ->repr($expr->getTemplateLine()) + ->raw(', $this->source)') + ; + } +} diff --git a/site/plugins/kirby-twig/vendor/twig/twig/src/Node/DeprecatedNode.php b/site/plugins/kirby-twig/vendor/twig/twig/src/Node/DeprecatedNode.php new file mode 100644 index 0000000..5ff4430 --- /dev/null +++ b/site/plugins/kirby-twig/vendor/twig/twig/src/Node/DeprecatedNode.php @@ -0,0 +1,53 @@ + + */ +class DeprecatedNode extends Node +{ + public function __construct(AbstractExpression $expr, int $lineno, string $tag = null) + { + parent::__construct(['expr' => $expr], [], $lineno, $tag); + } + + public function compile(Compiler $compiler): void + { + $compiler->addDebugInfo($this); + + $expr = $this->getNode('expr'); + + if ($expr instanceof ConstantExpression) { + $compiler->write('@trigger_error(') + ->subcompile($expr); + } else { + $varName = $compiler->getVarName(); + $compiler->write(sprintf('$%s = ', $varName)) + ->subcompile($expr) + ->raw(";\n") + ->write(sprintf('@trigger_error($%s', $varName)); + } + + $compiler + ->raw('.') + ->string(sprintf(' ("%s" at line %d).', $this->getTemplateName(), $this->getTemplateLine())) + ->raw(", E_USER_DEPRECATED);\n") + ; + } +} diff --git a/site/plugins/kirby-twig/vendor/twig/twig/src/Node/DoNode.php b/site/plugins/kirby-twig/vendor/twig/twig/src/Node/DoNode.php new file mode 100644 index 0000000..f7783d1 --- /dev/null +++ b/site/plugins/kirby-twig/vendor/twig/twig/src/Node/DoNode.php @@ -0,0 +1,38 @@ + + */ +class DoNode extends Node +{ + public function __construct(AbstractExpression $expr, int $lineno, string $tag = null) + { + parent::__construct(['expr' => $expr], [], $lineno, $tag); + } + + public function compile(Compiler $compiler): void + { + $compiler + ->addDebugInfo($this) + ->write('') + ->subcompile($this->getNode('expr')) + ->raw(";\n") + ; + } +} diff --git a/site/plugins/kirby-twig/vendor/twig/twig/src/Node/EmbedNode.php b/site/plugins/kirby-twig/vendor/twig/twig/src/Node/EmbedNode.php new file mode 100644 index 0000000..903c3f6 --- /dev/null +++ b/site/plugins/kirby-twig/vendor/twig/twig/src/Node/EmbedNode.php @@ -0,0 +1,48 @@ + + */ +class EmbedNode extends IncludeNode +{ + // we don't inject the module to avoid node visitors to traverse it twice (as it will be already visited in the main module) + public function __construct(string $name, int $index, ?AbstractExpression $variables, bool $only, bool $ignoreMissing, int $lineno, string $tag = null) + { + parent::__construct(new ConstantExpression('not_used', $lineno), $variables, $only, $ignoreMissing, $lineno, $tag); + + $this->setAttribute('name', $name); + $this->setAttribute('index', $index); + } + + protected function addGetTemplate(Compiler $compiler): void + { + $compiler + ->write('$this->loadTemplate(') + ->string($this->getAttribute('name')) + ->raw(', ') + ->repr($this->getTemplateName()) + ->raw(', ') + ->repr($this->getTemplateLine()) + ->raw(', ') + ->string($this->getAttribute('index')) + ->raw(')') + ; + } +} diff --git a/site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/AbstractExpression.php b/site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/AbstractExpression.php new file mode 100644 index 0000000..42da055 --- /dev/null +++ b/site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/AbstractExpression.php @@ -0,0 +1,24 @@ + + */ +abstract class AbstractExpression extends Node +{ +} diff --git a/site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/ArrayExpression.php b/site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/ArrayExpression.php new file mode 100644 index 0000000..0e25fe4 --- /dev/null +++ b/site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/ArrayExpression.php @@ -0,0 +1,85 @@ +index = -1; + foreach ($this->getKeyValuePairs() as $pair) { + if ($pair['key'] instanceof ConstantExpression && ctype_digit((string) $pair['key']->getAttribute('value')) && $pair['key']->getAttribute('value') > $this->index) { + $this->index = $pair['key']->getAttribute('value'); + } + } + } + + public function getKeyValuePairs(): array + { + $pairs = []; + foreach (array_chunk($this->nodes, 2) as $pair) { + $pairs[] = [ + 'key' => $pair[0], + 'value' => $pair[1], + ]; + } + + return $pairs; + } + + public function hasElement(AbstractExpression $key): bool + { + foreach ($this->getKeyValuePairs() as $pair) { + // we compare the string representation of the keys + // to avoid comparing the line numbers which are not relevant here. + if ((string) $key === (string) $pair['key']) { + return true; + } + } + + return false; + } + + public function addElement(AbstractExpression $value, AbstractExpression $key = null): void + { + if (null === $key) { + $key = new ConstantExpression(++$this->index, $value->getTemplateLine()); + } + + array_push($this->nodes, $key, $value); + } + + public function compile(Compiler $compiler): void + { + $compiler->raw('['); + $first = true; + foreach ($this->getKeyValuePairs() as $pair) { + if (!$first) { + $compiler->raw(', '); + } + $first = false; + + $compiler + ->subcompile($pair['key']) + ->raw(' => ') + ->subcompile($pair['value']) + ; + } + $compiler->raw(']'); + } +} diff --git a/site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/ArrowFunctionExpression.php b/site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/ArrowFunctionExpression.php new file mode 100644 index 0000000..eaad03c --- /dev/null +++ b/site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/ArrowFunctionExpression.php @@ -0,0 +1,64 @@ + + */ +class ArrowFunctionExpression extends AbstractExpression +{ + public function __construct(AbstractExpression $expr, Node $names, $lineno, $tag = null) + { + parent::__construct(['expr' => $expr, 'names' => $names], [], $lineno, $tag); + } + + public function compile(Compiler $compiler): void + { + $compiler + ->addDebugInfo($this) + ->raw('function (') + ; + foreach ($this->getNode('names') as $i => $name) { + if ($i) { + $compiler->raw(', '); + } + + $compiler + ->raw('$__') + ->raw($name->getAttribute('name')) + ->raw('__') + ; + } + $compiler + ->raw(') use ($context, $macros) { ') + ; + foreach ($this->getNode('names') as $name) { + $compiler + ->raw('$context["') + ->raw($name->getAttribute('name')) + ->raw('"] = $__') + ->raw($name->getAttribute('name')) + ->raw('__; ') + ; + } + $compiler + ->raw('return ') + ->subcompile($this->getNode('expr')) + ->raw('; }') + ; + } +} diff --git a/site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/AssignNameExpression.php b/site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/AssignNameExpression.php new file mode 100644 index 0000000..7dd1bc4 --- /dev/null +++ b/site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/AssignNameExpression.php @@ -0,0 +1,27 @@ +raw('$context[') + ->string($this->getAttribute('name')) + ->raw(']') + ; + } +} diff --git a/site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/Binary/AbstractBinary.php b/site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/Binary/AbstractBinary.php new file mode 100644 index 0000000..c424e5c --- /dev/null +++ b/site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/Binary/AbstractBinary.php @@ -0,0 +1,42 @@ + $left, 'right' => $right], [], $lineno); + } + + public function compile(Compiler $compiler): void + { + $compiler + ->raw('(') + ->subcompile($this->getNode('left')) + ->raw(' ') + ; + $this->operator($compiler); + $compiler + ->raw(' ') + ->subcompile($this->getNode('right')) + ->raw(')') + ; + } + + abstract public function operator(Compiler $compiler): Compiler; +} diff --git a/site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/Binary/AddBinary.php b/site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/Binary/AddBinary.php new file mode 100644 index 0000000..ee4307e --- /dev/null +++ b/site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/Binary/AddBinary.php @@ -0,0 +1,23 @@ +raw('+'); + } +} diff --git a/site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/Binary/AndBinary.php b/site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/Binary/AndBinary.php new file mode 100644 index 0000000..5f2380d --- /dev/null +++ b/site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/Binary/AndBinary.php @@ -0,0 +1,23 @@ +raw('&&'); + } +} diff --git a/site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/Binary/BitwiseAndBinary.php b/site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/Binary/BitwiseAndBinary.php new file mode 100644 index 0000000..db7d6d6 --- /dev/null +++ b/site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/Binary/BitwiseAndBinary.php @@ -0,0 +1,23 @@ +raw('&'); + } +} diff --git a/site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/Binary/BitwiseOrBinary.php b/site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/Binary/BitwiseOrBinary.php new file mode 100644 index 0000000..ce803dd --- /dev/null +++ b/site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/Binary/BitwiseOrBinary.php @@ -0,0 +1,23 @@ +raw('|'); + } +} diff --git a/site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/Binary/BitwiseXorBinary.php b/site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/Binary/BitwiseXorBinary.php new file mode 100644 index 0000000..5c29785 --- /dev/null +++ b/site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/Binary/BitwiseXorBinary.php @@ -0,0 +1,23 @@ +raw('^'); + } +} diff --git a/site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/Binary/ConcatBinary.php b/site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/Binary/ConcatBinary.php new file mode 100644 index 0000000..f825ab8 --- /dev/null +++ b/site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/Binary/ConcatBinary.php @@ -0,0 +1,23 @@ +raw('.'); + } +} diff --git a/site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/Binary/DivBinary.php b/site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/Binary/DivBinary.php new file mode 100644 index 0000000..e3817d1 --- /dev/null +++ b/site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/Binary/DivBinary.php @@ -0,0 +1,23 @@ +raw('/'); + } +} diff --git a/site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/Binary/EndsWithBinary.php b/site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/Binary/EndsWithBinary.php new file mode 100644 index 0000000..c3516b8 --- /dev/null +++ b/site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/Binary/EndsWithBinary.php @@ -0,0 +1,35 @@ +getVarName(); + $right = $compiler->getVarName(); + $compiler + ->raw(sprintf('(is_string($%s = ', $left)) + ->subcompile($this->getNode('left')) + ->raw(sprintf(') && is_string($%s = ', $right)) + ->subcompile($this->getNode('right')) + ->raw(sprintf(') && (\'\' === $%2$s || $%2$s === substr($%1$s, -strlen($%2$s))))', $left, $right)) + ; + } + + public function operator(Compiler $compiler): Compiler + { + return $compiler->raw(''); + } +} diff --git a/site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/Binary/EqualBinary.php b/site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/Binary/EqualBinary.php new file mode 100644 index 0000000..6b48549 --- /dev/null +++ b/site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/Binary/EqualBinary.php @@ -0,0 +1,39 @@ += 80000) { + parent::compile($compiler); + + return; + } + + $compiler + ->raw('(0 === twig_compare(') + ->subcompile($this->getNode('left')) + ->raw(', ') + ->subcompile($this->getNode('right')) + ->raw('))') + ; + } + + public function operator(Compiler $compiler): Compiler + { + return $compiler->raw('=='); + } +} diff --git a/site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/Binary/FloorDivBinary.php b/site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/Binary/FloorDivBinary.php new file mode 100644 index 0000000..d7e7980 --- /dev/null +++ b/site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/Binary/FloorDivBinary.php @@ -0,0 +1,29 @@ +raw('(int) floor('); + parent::compile($compiler); + $compiler->raw(')'); + } + + public function operator(Compiler $compiler): Compiler + { + return $compiler->raw('/'); + } +} diff --git a/site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/Binary/GreaterBinary.php b/site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/Binary/GreaterBinary.php new file mode 100644 index 0000000..e1dd067 --- /dev/null +++ b/site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/Binary/GreaterBinary.php @@ -0,0 +1,39 @@ += 80000) { + parent::compile($compiler); + + return; + } + + $compiler + ->raw('(1 === twig_compare(') + ->subcompile($this->getNode('left')) + ->raw(', ') + ->subcompile($this->getNode('right')) + ->raw('))') + ; + } + + public function operator(Compiler $compiler): Compiler + { + return $compiler->raw('>'); + } +} diff --git a/site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/Binary/GreaterEqualBinary.php b/site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/Binary/GreaterEqualBinary.php new file mode 100644 index 0000000..df9bfcf --- /dev/null +++ b/site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/Binary/GreaterEqualBinary.php @@ -0,0 +1,39 @@ += 80000) { + parent::compile($compiler); + + return; + } + + $compiler + ->raw('(0 <= twig_compare(') + ->subcompile($this->getNode('left')) + ->raw(', ') + ->subcompile($this->getNode('right')) + ->raw('))') + ; + } + + public function operator(Compiler $compiler): Compiler + { + return $compiler->raw('>='); + } +} diff --git a/site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/Binary/InBinary.php b/site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/Binary/InBinary.php new file mode 100644 index 0000000..6dbfa97 --- /dev/null +++ b/site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/Binary/InBinary.php @@ -0,0 +1,33 @@ +raw('twig_in_filter(') + ->subcompile($this->getNode('left')) + ->raw(', ') + ->subcompile($this->getNode('right')) + ->raw(')') + ; + } + + public function operator(Compiler $compiler): Compiler + { + return $compiler->raw('in'); + } +} diff --git a/site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/Binary/LessBinary.php b/site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/Binary/LessBinary.php new file mode 100644 index 0000000..598e629 --- /dev/null +++ b/site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/Binary/LessBinary.php @@ -0,0 +1,39 @@ += 80000) { + parent::compile($compiler); + + return; + } + + $compiler + ->raw('(-1 === twig_compare(') + ->subcompile($this->getNode('left')) + ->raw(', ') + ->subcompile($this->getNode('right')) + ->raw('))') + ; + } + + public function operator(Compiler $compiler): Compiler + { + return $compiler->raw('<'); + } +} diff --git a/site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/Binary/LessEqualBinary.php b/site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/Binary/LessEqualBinary.php new file mode 100644 index 0000000..e3c4af5 --- /dev/null +++ b/site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/Binary/LessEqualBinary.php @@ -0,0 +1,39 @@ += 80000) { + parent::compile($compiler); + + return; + } + + $compiler + ->raw('(0 >= twig_compare(') + ->subcompile($this->getNode('left')) + ->raw(', ') + ->subcompile($this->getNode('right')) + ->raw('))') + ; + } + + public function operator(Compiler $compiler): Compiler + { + return $compiler->raw('<='); + } +} diff --git a/site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/Binary/MatchesBinary.php b/site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/Binary/MatchesBinary.php new file mode 100644 index 0000000..bc97292 --- /dev/null +++ b/site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/Binary/MatchesBinary.php @@ -0,0 +1,33 @@ +raw('preg_match(') + ->subcompile($this->getNode('right')) + ->raw(', ') + ->subcompile($this->getNode('left')) + ->raw(')') + ; + } + + public function operator(Compiler $compiler): Compiler + { + return $compiler->raw(''); + } +} diff --git a/site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/Binary/ModBinary.php b/site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/Binary/ModBinary.php new file mode 100644 index 0000000..271b45c --- /dev/null +++ b/site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/Binary/ModBinary.php @@ -0,0 +1,23 @@ +raw('%'); + } +} diff --git a/site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/Binary/MulBinary.php b/site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/Binary/MulBinary.php new file mode 100644 index 0000000..6d4c1e0 --- /dev/null +++ b/site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/Binary/MulBinary.php @@ -0,0 +1,23 @@ +raw('*'); + } +} diff --git a/site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/Binary/NotEqualBinary.php b/site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/Binary/NotEqualBinary.php new file mode 100644 index 0000000..db47a28 --- /dev/null +++ b/site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/Binary/NotEqualBinary.php @@ -0,0 +1,39 @@ += 80000) { + parent::compile($compiler); + + return; + } + + $compiler + ->raw('(0 !== twig_compare(') + ->subcompile($this->getNode('left')) + ->raw(', ') + ->subcompile($this->getNode('right')) + ->raw('))') + ; + } + + public function operator(Compiler $compiler): Compiler + { + return $compiler->raw('!='); + } +} diff --git a/site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/Binary/NotInBinary.php b/site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/Binary/NotInBinary.php new file mode 100644 index 0000000..fcba6cc --- /dev/null +++ b/site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/Binary/NotInBinary.php @@ -0,0 +1,33 @@ +raw('!twig_in_filter(') + ->subcompile($this->getNode('left')) + ->raw(', ') + ->subcompile($this->getNode('right')) + ->raw(')') + ; + } + + public function operator(Compiler $compiler): Compiler + { + return $compiler->raw('not in'); + } +} diff --git a/site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/Binary/OrBinary.php b/site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/Binary/OrBinary.php new file mode 100644 index 0000000..21f87c9 --- /dev/null +++ b/site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/Binary/OrBinary.php @@ -0,0 +1,23 @@ +raw('||'); + } +} diff --git a/site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/Binary/PowerBinary.php b/site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/Binary/PowerBinary.php new file mode 100644 index 0000000..c9f4c66 --- /dev/null +++ b/site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/Binary/PowerBinary.php @@ -0,0 +1,22 @@ +raw('**'); + } +} diff --git a/site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/Binary/RangeBinary.php b/site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/Binary/RangeBinary.php new file mode 100644 index 0000000..55982c8 --- /dev/null +++ b/site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/Binary/RangeBinary.php @@ -0,0 +1,33 @@ +raw('range(') + ->subcompile($this->getNode('left')) + ->raw(', ') + ->subcompile($this->getNode('right')) + ->raw(')') + ; + } + + public function operator(Compiler $compiler): Compiler + { + return $compiler->raw('..'); + } +} diff --git a/site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/Binary/SpaceshipBinary.php b/site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/Binary/SpaceshipBinary.php new file mode 100644 index 0000000..ae5a4a4 --- /dev/null +++ b/site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/Binary/SpaceshipBinary.php @@ -0,0 +1,22 @@ +raw('<=>'); + } +} diff --git a/site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/Binary/StartsWithBinary.php b/site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/Binary/StartsWithBinary.php new file mode 100644 index 0000000..d0df1c4 --- /dev/null +++ b/site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/Binary/StartsWithBinary.php @@ -0,0 +1,35 @@ +getVarName(); + $right = $compiler->getVarName(); + $compiler + ->raw(sprintf('(is_string($%s = ', $left)) + ->subcompile($this->getNode('left')) + ->raw(sprintf(') && is_string($%s = ', $right)) + ->subcompile($this->getNode('right')) + ->raw(sprintf(') && (\'\' === $%2$s || 0 === strpos($%1$s, $%2$s)))', $left, $right)) + ; + } + + public function operator(Compiler $compiler): Compiler + { + return $compiler->raw(''); + } +} diff --git a/site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/Binary/SubBinary.php b/site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/Binary/SubBinary.php new file mode 100644 index 0000000..eeb87fa --- /dev/null +++ b/site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/Binary/SubBinary.php @@ -0,0 +1,23 @@ +raw('-'); + } +} diff --git a/site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/BlockReferenceExpression.php b/site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/BlockReferenceExpression.php new file mode 100644 index 0000000..b1e2a8f --- /dev/null +++ b/site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/BlockReferenceExpression.php @@ -0,0 +1,86 @@ + + */ +class BlockReferenceExpression extends AbstractExpression +{ + public function __construct(Node $name, ?Node $template, int $lineno, string $tag = null) + { + $nodes = ['name' => $name]; + if (null !== $template) { + $nodes['template'] = $template; + } + + parent::__construct($nodes, ['is_defined_test' => false, 'output' => false], $lineno, $tag); + } + + public function compile(Compiler $compiler): void + { + if ($this->getAttribute('is_defined_test')) { + $this->compileTemplateCall($compiler, 'hasBlock'); + } else { + if ($this->getAttribute('output')) { + $compiler->addDebugInfo($this); + + $this + ->compileTemplateCall($compiler, 'displayBlock') + ->raw(";\n"); + } else { + $this->compileTemplateCall($compiler, 'renderBlock'); + } + } + } + + private function compileTemplateCall(Compiler $compiler, string $method): Compiler + { + if (!$this->hasNode('template')) { + $compiler->write('$this'); + } else { + $compiler + ->write('$this->loadTemplate(') + ->subcompile($this->getNode('template')) + ->raw(', ') + ->repr($this->getTemplateName()) + ->raw(', ') + ->repr($this->getTemplateLine()) + ->raw(')') + ; + } + + $compiler->raw(sprintf('->%s', $method)); + + return $this->compileBlockArguments($compiler); + } + + private function compileBlockArguments(Compiler $compiler): Compiler + { + $compiler + ->raw('(') + ->subcompile($this->getNode('name')) + ->raw(', $context'); + + if (!$this->hasNode('template')) { + $compiler->raw(', $blocks'); + } + + return $compiler->raw(')'); + } +} diff --git a/site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/CallExpression.php b/site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/CallExpression.php new file mode 100644 index 0000000..fdf92a8 --- /dev/null +++ b/site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/CallExpression.php @@ -0,0 +1,320 @@ +getAttribute('callable'); + + $closingParenthesis = false; + $isArray = false; + if (\is_string($callable) && false === strpos($callable, '::')) { + $compiler->raw($callable); + } else { + list($r, $callable) = $this->reflectCallable($callable); + if ($r instanceof \ReflectionMethod && \is_string($callable[0])) { + if ($r->isStatic()) { + $compiler->raw(sprintf('%s::%s', $callable[0], $callable[1])); + } else { + $compiler->raw(sprintf('$this->env->getRuntime(\'%s\')->%s', $callable[0], $callable[1])); + } + } elseif ($r instanceof \ReflectionMethod && $callable[0] instanceof ExtensionInterface) { + $class = \get_class($callable[0]); + if (!$compiler->getEnvironment()->hasExtension($class)) { + // Compile a non-optimized call to trigger a \Twig\Error\RuntimeError, which cannot be a compile-time error + $compiler->raw(sprintf('$this->env->getExtension(\'%s\')', $class)); + } else { + $compiler->raw(sprintf('$this->extensions[\'%s\']', ltrim($class, '\\'))); + } + + $compiler->raw(sprintf('->%s', $callable[1])); + } else { + $closingParenthesis = true; + $isArray = true; + $compiler->raw(sprintf('call_user_func_array($this->env->get%s(\'%s\')->getCallable(), ', ucfirst($this->getAttribute('type')), $this->getAttribute('name'))); + } + } + + $this->compileArguments($compiler, $isArray); + + if ($closingParenthesis) { + $compiler->raw(')'); + } + } + + protected function compileArguments(Compiler $compiler, $isArray = false): void + { + $compiler->raw($isArray ? '[' : '('); + + $first = true; + + if ($this->hasAttribute('needs_environment') && $this->getAttribute('needs_environment')) { + $compiler->raw('$this->env'); + $first = false; + } + + if ($this->hasAttribute('needs_context') && $this->getAttribute('needs_context')) { + if (!$first) { + $compiler->raw(', '); + } + $compiler->raw('$context'); + $first = false; + } + + if ($this->hasAttribute('arguments')) { + foreach ($this->getAttribute('arguments') as $argument) { + if (!$first) { + $compiler->raw(', '); + } + $compiler->string($argument); + $first = false; + } + } + + if ($this->hasNode('node')) { + if (!$first) { + $compiler->raw(', '); + } + $compiler->subcompile($this->getNode('node')); + $first = false; + } + + if ($this->hasNode('arguments')) { + $callable = $this->getAttribute('callable'); + $arguments = $this->getArguments($callable, $this->getNode('arguments')); + foreach ($arguments as $node) { + if (!$first) { + $compiler->raw(', '); + } + $compiler->subcompile($node); + $first = false; + } + } + + $compiler->raw($isArray ? ']' : ')'); + } + + protected function getArguments($callable, $arguments) + { + $callType = $this->getAttribute('type'); + $callName = $this->getAttribute('name'); + + $parameters = []; + $named = false; + foreach ($arguments as $name => $node) { + if (!\is_int($name)) { + $named = true; + $name = $this->normalizeName($name); + } elseif ($named) { + throw new SyntaxError(sprintf('Positional arguments cannot be used after named arguments for %s "%s".', $callType, $callName), $this->getTemplateLine(), $this->getSourceContext()); + } + + $parameters[$name] = $node; + } + + $isVariadic = $this->hasAttribute('is_variadic') && $this->getAttribute('is_variadic'); + if (!$named && !$isVariadic) { + return $parameters; + } + + if (!$callable) { + if ($named) { + $message = sprintf('Named arguments are not supported for %s "%s".', $callType, $callName); + } else { + $message = sprintf('Arbitrary positional arguments are not supported for %s "%s".', $callType, $callName); + } + + throw new \LogicException($message); + } + + list($callableParameters, $isPhpVariadic) = $this->getCallableParameters($callable, $isVariadic); + $arguments = []; + $names = []; + $missingArguments = []; + $optionalArguments = []; + $pos = 0; + foreach ($callableParameters as $callableParameter) { + $name = $this->normalizeName($callableParameter->name); + if (\PHP_VERSION_ID >= 80000 && 'range' === $callable) { + if ('start' === $name) { + $name = 'low'; + } elseif ('end' === $name) { + $name = 'high'; + } + } + + $names[] = $name; + + if (\array_key_exists($name, $parameters)) { + if (\array_key_exists($pos, $parameters)) { + throw new SyntaxError(sprintf('Argument "%s" is defined twice for %s "%s".', $name, $callType, $callName), $this->getTemplateLine(), $this->getSourceContext()); + } + + if (\count($missingArguments)) { + throw new SyntaxError(sprintf( + 'Argument "%s" could not be assigned for %s "%s(%s)" because it is mapped to an internal PHP function which cannot determine default value for optional argument%s "%s".', + $name, $callType, $callName, implode(', ', $names), \count($missingArguments) > 1 ? 's' : '', implode('", "', $missingArguments) + ), $this->getTemplateLine(), $this->getSourceContext()); + } + + $arguments = array_merge($arguments, $optionalArguments); + $arguments[] = $parameters[$name]; + unset($parameters[$name]); + $optionalArguments = []; + } elseif (\array_key_exists($pos, $parameters)) { + $arguments = array_merge($arguments, $optionalArguments); + $arguments[] = $parameters[$pos]; + unset($parameters[$pos]); + $optionalArguments = []; + ++$pos; + } elseif ($callableParameter->isDefaultValueAvailable()) { + $optionalArguments[] = new ConstantExpression($callableParameter->getDefaultValue(), -1); + } elseif ($callableParameter->isOptional()) { + if (empty($parameters)) { + break; + } else { + $missingArguments[] = $name; + } + } else { + throw new SyntaxError(sprintf('Value for argument "%s" is required for %s "%s".', $name, $callType, $callName), $this->getTemplateLine(), $this->getSourceContext()); + } + } + + if ($isVariadic) { + $arbitraryArguments = $isPhpVariadic ? new VariadicExpression([], -1) : new ArrayExpression([], -1); + foreach ($parameters as $key => $value) { + if (\is_int($key)) { + $arbitraryArguments->addElement($value); + } else { + $arbitraryArguments->addElement($value, new ConstantExpression($key, -1)); + } + unset($parameters[$key]); + } + + if ($arbitraryArguments->count()) { + $arguments = array_merge($arguments, $optionalArguments); + $arguments[] = $arbitraryArguments; + } + } + + if (!empty($parameters)) { + $unknownParameter = null; + foreach ($parameters as $parameter) { + if ($parameter instanceof Node) { + $unknownParameter = $parameter; + break; + } + } + + throw new SyntaxError( + sprintf( + 'Unknown argument%s "%s" for %s "%s(%s)".', + \count($parameters) > 1 ? 's' : '', implode('", "', array_keys($parameters)), $callType, $callName, implode(', ', $names) + ), + $unknownParameter ? $unknownParameter->getTemplateLine() : $this->getTemplateLine(), + $unknownParameter ? $unknownParameter->getSourceContext() : $this->getSourceContext() + ); + } + + return $arguments; + } + + protected function normalizeName(string $name): string + { + return strtolower(preg_replace(['/([A-Z]+)([A-Z][a-z])/', '/([a-z\d])([A-Z])/'], ['\\1_\\2', '\\1_\\2'], $name)); + } + + private function getCallableParameters($callable, bool $isVariadic): array + { + list($r) = $this->reflectCallable($callable); + if (null === $r) { + return [[], false]; + } + + $parameters = $r->getParameters(); + if ($this->hasNode('node')) { + array_shift($parameters); + } + if ($this->hasAttribute('needs_environment') && $this->getAttribute('needs_environment')) { + array_shift($parameters); + } + if ($this->hasAttribute('needs_context') && $this->getAttribute('needs_context')) { + array_shift($parameters); + } + if ($this->hasAttribute('arguments') && null !== $this->getAttribute('arguments')) { + foreach ($this->getAttribute('arguments') as $argument) { + array_shift($parameters); + } + } + $isPhpVariadic = false; + if ($isVariadic) { + $argument = end($parameters); + $isArray = $argument && $argument->hasType() && 'array' === $argument->getType()->getName(); + if ($isArray && $argument->isDefaultValueAvailable() && [] === $argument->getDefaultValue()) { + array_pop($parameters); + } elseif ($argument && $argument->isVariadic()) { + array_pop($parameters); + $isPhpVariadic = true; + } else { + $callableName = $r->name; + if ($r instanceof \ReflectionMethod) { + $callableName = $r->getDeclaringClass()->name.'::'.$callableName; + } + + throw new \LogicException(sprintf('The last parameter of "%s" for %s "%s" must be an array with default value, eg. "array $arg = []".', $callableName, $this->getAttribute('type'), $this->getAttribute('name'))); + } + } + + return [$parameters, $isPhpVariadic]; + } + + private function reflectCallable($callable) + { + if (null !== $this->reflector) { + return $this->reflector; + } + + if (\is_array($callable)) { + if (!method_exists($callable[0], $callable[1])) { + // __call() + return [null, []]; + } + $r = new \ReflectionMethod($callable[0], $callable[1]); + } elseif (\is_object($callable) && !$callable instanceof \Closure) { + $r = new \ReflectionObject($callable); + $r = $r->getMethod('__invoke'); + $callable = [$callable, '__invoke']; + } elseif (\is_string($callable) && false !== $pos = strpos($callable, '::')) { + $class = substr($callable, 0, $pos); + $method = substr($callable, $pos + 2); + if (!method_exists($class, $method)) { + // __staticCall() + return [null, []]; + } + $r = new \ReflectionMethod($callable); + $callable = [$class, $method]; + } else { + $r = new \ReflectionFunction($callable); + } + + return $this->reflector = [$r, $callable]; + } +} diff --git a/site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/ConditionalExpression.php b/site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/ConditionalExpression.php new file mode 100644 index 0000000..2c7bd0a --- /dev/null +++ b/site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/ConditionalExpression.php @@ -0,0 +1,36 @@ + $expr1, 'expr2' => $expr2, 'expr3' => $expr3], [], $lineno); + } + + public function compile(Compiler $compiler): void + { + $compiler + ->raw('((') + ->subcompile($this->getNode('expr1')) + ->raw(') ? (') + ->subcompile($this->getNode('expr2')) + ->raw(') : (') + ->subcompile($this->getNode('expr3')) + ->raw('))') + ; + } +} diff --git a/site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/ConstantExpression.php b/site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/ConstantExpression.php new file mode 100644 index 0000000..7ddbcc6 --- /dev/null +++ b/site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/ConstantExpression.php @@ -0,0 +1,28 @@ + $value], $lineno); + } + + public function compile(Compiler $compiler): void + { + $compiler->repr($this->getAttribute('value')); + } +} diff --git a/site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/Filter/DefaultFilter.php b/site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/Filter/DefaultFilter.php new file mode 100644 index 0000000..6a572d4 --- /dev/null +++ b/site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/Filter/DefaultFilter.php @@ -0,0 +1,52 @@ + + */ +class DefaultFilter extends FilterExpression +{ + public function __construct(Node $node, ConstantExpression $filterName, Node $arguments, int $lineno, string $tag = null) + { + $default = new FilterExpression($node, new ConstantExpression('default', $node->getTemplateLine()), $arguments, $node->getTemplateLine()); + + if ('default' === $filterName->getAttribute('value') && ($node instanceof NameExpression || $node instanceof GetAttrExpression)) { + $test = new DefinedTest(clone $node, 'defined', new Node(), $node->getTemplateLine()); + $false = \count($arguments) ? $arguments->getNode(0) : new ConstantExpression('', $node->getTemplateLine()); + + $node = new ConditionalExpression($test, $default, $false, $node->getTemplateLine()); + } else { + $node = $default; + } + + parent::__construct($node, $filterName, $arguments, $lineno, $tag); + } + + public function compile(Compiler $compiler): void + { + $compiler->subcompile($this->getNode('node')); + } +} diff --git a/site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/FilterExpression.php b/site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/FilterExpression.php new file mode 100644 index 0000000..0fc1588 --- /dev/null +++ b/site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/FilterExpression.php @@ -0,0 +1,40 @@ + $node, 'filter' => $filterName, 'arguments' => $arguments], [], $lineno, $tag); + } + + public function compile(Compiler $compiler): void + { + $name = $this->getNode('filter')->getAttribute('value'); + $filter = $compiler->getEnvironment()->getFilter($name); + + $this->setAttribute('name', $name); + $this->setAttribute('type', 'filter'); + $this->setAttribute('needs_environment', $filter->needsEnvironment()); + $this->setAttribute('needs_context', $filter->needsContext()); + $this->setAttribute('arguments', $filter->getArguments()); + $this->setAttribute('callable', $filter->getCallable()); + $this->setAttribute('is_variadic', $filter->isVariadic()); + + $this->compileCallable($compiler); + } +} diff --git a/site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/FunctionExpression.php b/site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/FunctionExpression.php new file mode 100644 index 0000000..7126977 --- /dev/null +++ b/site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/FunctionExpression.php @@ -0,0 +1,43 @@ + $arguments], ['name' => $name, 'is_defined_test' => false], $lineno); + } + + public function compile(Compiler $compiler) + { + $name = $this->getAttribute('name'); + $function = $compiler->getEnvironment()->getFunction($name); + + $this->setAttribute('name', $name); + $this->setAttribute('type', 'function'); + $this->setAttribute('needs_environment', $function->needsEnvironment()); + $this->setAttribute('needs_context', $function->needsContext()); + $this->setAttribute('arguments', $function->getArguments()); + $callable = $function->getCallable(); + if ('constant' === $name && $this->getAttribute('is_defined_test')) { + $callable = 'twig_constant_is_defined'; + } + $this->setAttribute('callable', $callable); + $this->setAttribute('is_variadic', $function->isVariadic()); + + $this->compileCallable($compiler); + } +} diff --git a/site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/GetAttrExpression.php b/site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/GetAttrExpression.php new file mode 100644 index 0000000..e6a75ce --- /dev/null +++ b/site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/GetAttrExpression.php @@ -0,0 +1,87 @@ + $node, 'attribute' => $attribute]; + if (null !== $arguments) { + $nodes['arguments'] = $arguments; + } + + parent::__construct($nodes, ['type' => $type, 'is_defined_test' => false, 'ignore_strict_check' => false, 'optimizable' => true], $lineno); + } + + public function compile(Compiler $compiler): void + { + $env = $compiler->getEnvironment(); + + // optimize array calls + if ( + $this->getAttribute('optimizable') + && (!$env->isStrictVariables() || $this->getAttribute('ignore_strict_check')) + && !$this->getAttribute('is_defined_test') + && Template::ARRAY_CALL === $this->getAttribute('type') + ) { + $var = '$'.$compiler->getVarName(); + $compiler + ->raw('(('.$var.' = ') + ->subcompile($this->getNode('node')) + ->raw(') && is_array(') + ->raw($var) + ->raw(') || ') + ->raw($var) + ->raw(' instanceof ArrayAccess ? (') + ->raw($var) + ->raw('[') + ->subcompile($this->getNode('attribute')) + ->raw('] ?? null) : null)') + ; + + return; + } + + $compiler->raw('twig_get_attribute($this->env, $this->source, '); + + if ($this->getAttribute('ignore_strict_check')) { + $this->getNode('node')->setAttribute('ignore_strict_check', true); + } + + $compiler + ->subcompile($this->getNode('node')) + ->raw(', ') + ->subcompile($this->getNode('attribute')) + ; + + if ($this->hasNode('arguments')) { + $compiler->raw(', ')->subcompile($this->getNode('arguments')); + } else { + $compiler->raw(', []'); + } + + $compiler->raw(', ') + ->repr($this->getAttribute('type')) + ->raw(', ')->repr($this->getAttribute('is_defined_test')) + ->raw(', ')->repr($this->getAttribute('ignore_strict_check')) + ->raw(', ')->repr($env->hasExtension(SandboxExtension::class)) + ->raw(', ')->repr($this->getNode('node')->getTemplateLine()) + ->raw(')') + ; + } +} diff --git a/site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/InlinePrint.php b/site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/InlinePrint.php new file mode 100644 index 0000000..1ad4751 --- /dev/null +++ b/site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/InlinePrint.php @@ -0,0 +1,35 @@ + $node], [], $lineno); + } + + public function compile(Compiler $compiler): void + { + $compiler + ->raw('print (') + ->subcompile($this->getNode('node')) + ->raw(')') + ; + } +} diff --git a/site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/MethodCallExpression.php b/site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/MethodCallExpression.php new file mode 100644 index 0000000..d5ec0b6 --- /dev/null +++ b/site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/MethodCallExpression.php @@ -0,0 +1,62 @@ + $node, 'arguments' => $arguments], ['method' => $method, 'safe' => false, 'is_defined_test' => false], $lineno); + + if ($node instanceof NameExpression) { + $node->setAttribute('always_defined', true); + } + } + + public function compile(Compiler $compiler): void + { + if ($this->getAttribute('is_defined_test')) { + $compiler + ->raw('method_exists($macros[') + ->repr($this->getNode('node')->getAttribute('name')) + ->raw('], ') + ->repr($this->getAttribute('method')) + ->raw(')') + ; + + return; + } + + $compiler + ->raw('twig_call_macro($macros[') + ->repr($this->getNode('node')->getAttribute('name')) + ->raw('], ') + ->repr($this->getAttribute('method')) + ->raw(', [') + ; + $first = true; + foreach ($this->getNode('arguments')->getKeyValuePairs() as $pair) { + if (!$first) { + $compiler->raw(', '); + } + $first = false; + + $compiler->subcompile($pair['value']); + } + $compiler + ->raw('], ') + ->repr($this->getTemplateLine()) + ->raw(', $context, $this->getSourceContext())'); + } +} diff --git a/site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/NameExpression.php b/site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/NameExpression.php new file mode 100644 index 0000000..c3563f0 --- /dev/null +++ b/site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/NameExpression.php @@ -0,0 +1,97 @@ + '$this->getTemplateName()', + '_context' => '$context', + '_charset' => '$this->env->getCharset()', + ]; + + public function __construct(string $name, int $lineno) + { + parent::__construct([], ['name' => $name, 'is_defined_test' => false, 'ignore_strict_check' => false, 'always_defined' => false], $lineno); + } + + public function compile(Compiler $compiler): void + { + $name = $this->getAttribute('name'); + + $compiler->addDebugInfo($this); + + if ($this->getAttribute('is_defined_test')) { + if ($this->isSpecial()) { + $compiler->repr(true); + } elseif (\PHP_VERSION_ID >= 70400) { + $compiler + ->raw('array_key_exists(') + ->string($name) + ->raw(', $context)') + ; + } else { + $compiler + ->raw('(isset($context[') + ->string($name) + ->raw(']) || array_key_exists(') + ->string($name) + ->raw(', $context))') + ; + } + } elseif ($this->isSpecial()) { + $compiler->raw($this->specialVars[$name]); + } elseif ($this->getAttribute('always_defined')) { + $compiler + ->raw('$context[') + ->string($name) + ->raw(']') + ; + } else { + if ($this->getAttribute('ignore_strict_check') || !$compiler->getEnvironment()->isStrictVariables()) { + $compiler + ->raw('($context[') + ->string($name) + ->raw('] ?? null)') + ; + } else { + $compiler + ->raw('(isset($context[') + ->string($name) + ->raw(']) || array_key_exists(') + ->string($name) + ->raw(', $context) ? $context[') + ->string($name) + ->raw('] : (function () { throw new RuntimeError(\'Variable ') + ->string($name) + ->raw(' does not exist.\', ') + ->repr($this->lineno) + ->raw(', $this->source); })()') + ->raw(')') + ; + } + } + } + + public function isSpecial() + { + return isset($this->specialVars[$this->getAttribute('name')]); + } + + public function isSimple() + { + return !$this->isSpecial() && !$this->getAttribute('is_defined_test'); + } +} diff --git a/site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/NullCoalesceExpression.php b/site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/NullCoalesceExpression.php new file mode 100644 index 0000000..a72bc4f --- /dev/null +++ b/site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/NullCoalesceExpression.php @@ -0,0 +1,60 @@ +getTemplateLine()); + // for "block()", we don't need the null test as the return value is always a string + if (!$left instanceof BlockReferenceExpression) { + $test = new AndBinary( + $test, + new NotUnary(new NullTest($left, 'null', new Node(), $left->getTemplateLine()), $left->getTemplateLine()), + $left->getTemplateLine() + ); + } + + parent::__construct($test, $left, $right, $lineno); + } + + public function compile(Compiler $compiler): void + { + /* + * This optimizes only one case. PHP 7 also supports more complex expressions + * that can return null. So, for instance, if log is defined, log("foo") ?? "..." works, + * but log($a["foo"]) ?? "..." does not if $a["foo"] is not defined. More advanced + * cases might be implemented as an optimizer node visitor, but has not been done + * as benefits are probably not worth the added complexity. + */ + if ($this->getNode('expr2') instanceof NameExpression) { + $this->getNode('expr2')->setAttribute('always_defined', true); + $compiler + ->raw('((') + ->subcompile($this->getNode('expr2')) + ->raw(') ?? (') + ->subcompile($this->getNode('expr3')) + ->raw('))') + ; + } else { + parent::compile($compiler); + } + } +} diff --git a/site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/ParentExpression.php b/site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/ParentExpression.php new file mode 100644 index 0000000..2549197 --- /dev/null +++ b/site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/ParentExpression.php @@ -0,0 +1,46 @@ + + */ +class ParentExpression extends AbstractExpression +{ + public function __construct(string $name, int $lineno, string $tag = null) + { + parent::__construct([], ['output' => false, 'name' => $name], $lineno, $tag); + } + + public function compile(Compiler $compiler): void + { + if ($this->getAttribute('output')) { + $compiler + ->addDebugInfo($this) + ->write('$this->displayParentBlock(') + ->string($this->getAttribute('name')) + ->raw(", \$context, \$blocks);\n") + ; + } else { + $compiler + ->raw('$this->renderParentBlock(') + ->string($this->getAttribute('name')) + ->raw(', $context, $blocks)') + ; + } + } +} diff --git a/site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/TempNameExpression.php b/site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/TempNameExpression.php new file mode 100644 index 0000000..004c704 --- /dev/null +++ b/site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/TempNameExpression.php @@ -0,0 +1,31 @@ + $name], $lineno); + } + + public function compile(Compiler $compiler): void + { + $compiler + ->raw('$_') + ->raw($this->getAttribute('name')) + ->raw('_') + ; + } +} diff --git a/site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/Test/ConstantTest.php b/site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/Test/ConstantTest.php new file mode 100644 index 0000000..57e9319 --- /dev/null +++ b/site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/Test/ConstantTest.php @@ -0,0 +1,49 @@ + + */ +class ConstantTest extends TestExpression +{ + public function compile(Compiler $compiler): void + { + $compiler + ->raw('(') + ->subcompile($this->getNode('node')) + ->raw(' === constant(') + ; + + if ($this->getNode('arguments')->hasNode(1)) { + $compiler + ->raw('get_class(') + ->subcompile($this->getNode('arguments')->getNode(1)) + ->raw(')."::".') + ; + } + + $compiler + ->subcompile($this->getNode('arguments')->getNode(0)) + ->raw('))') + ; + } +} diff --git a/site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/Test/DefinedTest.php b/site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/Test/DefinedTest.php new file mode 100644 index 0000000..3953bbb --- /dev/null +++ b/site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/Test/DefinedTest.php @@ -0,0 +1,74 @@ + + */ +class DefinedTest extends TestExpression +{ + public function __construct(Node $node, string $name, ?Node $arguments, int $lineno) + { + if ($node instanceof NameExpression) { + $node->setAttribute('is_defined_test', true); + } elseif ($node instanceof GetAttrExpression) { + $node->setAttribute('is_defined_test', true); + $this->changeIgnoreStrictCheck($node); + } elseif ($node instanceof BlockReferenceExpression) { + $node->setAttribute('is_defined_test', true); + } elseif ($node instanceof FunctionExpression && 'constant' === $node->getAttribute('name')) { + $node->setAttribute('is_defined_test', true); + } elseif ($node instanceof ConstantExpression || $node instanceof ArrayExpression) { + $node = new ConstantExpression(true, $node->getTemplateLine()); + } elseif ($node instanceof MethodCallExpression) { + $node->setAttribute('is_defined_test', true); + } else { + throw new SyntaxError('The "defined" test only works with simple variables.', $lineno); + } + + parent::__construct($node, $name, $arguments, $lineno); + } + + private function changeIgnoreStrictCheck(GetAttrExpression $node) + { + $node->setAttribute('optimizable', false); + $node->setAttribute('ignore_strict_check', true); + + if ($node->getNode('node') instanceof GetAttrExpression) { + $this->changeIgnoreStrictCheck($node->getNode('node')); + } + } + + public function compile(Compiler $compiler): void + { + $compiler->subcompile($this->getNode('node')); + } +} diff --git a/site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/Test/DivisiblebyTest.php b/site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/Test/DivisiblebyTest.php new file mode 100644 index 0000000..4cb3ee0 --- /dev/null +++ b/site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/Test/DivisiblebyTest.php @@ -0,0 +1,36 @@ + + */ +class DivisiblebyTest extends TestExpression +{ + public function compile(Compiler $compiler): void + { + $compiler + ->raw('(0 == ') + ->subcompile($this->getNode('node')) + ->raw(' % ') + ->subcompile($this->getNode('arguments')->getNode(0)) + ->raw(')') + ; + } +} diff --git a/site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/Test/EvenTest.php b/site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/Test/EvenTest.php new file mode 100644 index 0000000..a0e3ed6 --- /dev/null +++ b/site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/Test/EvenTest.php @@ -0,0 +1,35 @@ + + */ +class EvenTest extends TestExpression +{ + public function compile(Compiler $compiler): void + { + $compiler + ->raw('(') + ->subcompile($this->getNode('node')) + ->raw(' % 2 == 0') + ->raw(')') + ; + } +} diff --git a/site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/Test/NullTest.php b/site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/Test/NullTest.php new file mode 100644 index 0000000..45b54ae --- /dev/null +++ b/site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/Test/NullTest.php @@ -0,0 +1,34 @@ + + */ +class NullTest extends TestExpression +{ + public function compile(Compiler $compiler): void + { + $compiler + ->raw('(null === ') + ->subcompile($this->getNode('node')) + ->raw(')') + ; + } +} diff --git a/site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/Test/OddTest.php b/site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/Test/OddTest.php new file mode 100644 index 0000000..d56c711 --- /dev/null +++ b/site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/Test/OddTest.php @@ -0,0 +1,35 @@ + + */ +class OddTest extends TestExpression +{ + public function compile(Compiler $compiler): void + { + $compiler + ->raw('(') + ->subcompile($this->getNode('node')) + ->raw(' % 2 != 0') + ->raw(')') + ; + } +} diff --git a/site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/Test/SameasTest.php b/site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/Test/SameasTest.php new file mode 100644 index 0000000..c96d2bc --- /dev/null +++ b/site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/Test/SameasTest.php @@ -0,0 +1,34 @@ + + */ +class SameasTest extends TestExpression +{ + public function compile(Compiler $compiler): void + { + $compiler + ->raw('(') + ->subcompile($this->getNode('node')) + ->raw(' === ') + ->subcompile($this->getNode('arguments')->getNode(0)) + ->raw(')') + ; + } +} diff --git a/site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/TestExpression.php b/site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/TestExpression.php new file mode 100644 index 0000000..e518bd8 --- /dev/null +++ b/site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/TestExpression.php @@ -0,0 +1,42 @@ + $node]; + if (null !== $arguments) { + $nodes['arguments'] = $arguments; + } + + parent::__construct($nodes, ['name' => $name], $lineno); + } + + public function compile(Compiler $compiler): void + { + $name = $this->getAttribute('name'); + $test = $compiler->getEnvironment()->getTest($name); + + $this->setAttribute('name', $name); + $this->setAttribute('type', 'test'); + $this->setAttribute('arguments', $test->getArguments()); + $this->setAttribute('callable', $test->getCallable()); + $this->setAttribute('is_variadic', $test->isVariadic()); + + $this->compileCallable($compiler); + } +} diff --git a/site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/Unary/AbstractUnary.php b/site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/Unary/AbstractUnary.php new file mode 100644 index 0000000..e31e3f8 --- /dev/null +++ b/site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/Unary/AbstractUnary.php @@ -0,0 +1,34 @@ + $node], [], $lineno); + } + + public function compile(Compiler $compiler): void + { + $compiler->raw(' '); + $this->operator($compiler); + $compiler->subcompile($this->getNode('node')); + } + + abstract public function operator(Compiler $compiler): Compiler; +} diff --git a/site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/Unary/NegUnary.php b/site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/Unary/NegUnary.php new file mode 100644 index 0000000..dc2f235 --- /dev/null +++ b/site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/Unary/NegUnary.php @@ -0,0 +1,23 @@ +raw('-'); + } +} diff --git a/site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/Unary/NotUnary.php b/site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/Unary/NotUnary.php new file mode 100644 index 0000000..55c11ba --- /dev/null +++ b/site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/Unary/NotUnary.php @@ -0,0 +1,23 @@ +raw('!'); + } +} diff --git a/site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/Unary/PosUnary.php b/site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/Unary/PosUnary.php new file mode 100644 index 0000000..4b0a062 --- /dev/null +++ b/site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/Unary/PosUnary.php @@ -0,0 +1,23 @@ +raw('+'); + } +} diff --git a/site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/VariadicExpression.php b/site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/VariadicExpression.php new file mode 100644 index 0000000..a1bdb48 --- /dev/null +++ b/site/plugins/kirby-twig/vendor/twig/twig/src/Node/Expression/VariadicExpression.php @@ -0,0 +1,24 @@ +raw('...'); + + parent::compile($compiler); + } +} diff --git a/site/plugins/kirby-twig/vendor/twig/twig/src/Node/FlushNode.php b/site/plugins/kirby-twig/vendor/twig/twig/src/Node/FlushNode.php new file mode 100644 index 0000000..fa50a88 --- /dev/null +++ b/site/plugins/kirby-twig/vendor/twig/twig/src/Node/FlushNode.php @@ -0,0 +1,35 @@ + + */ +class FlushNode extends Node +{ + public function __construct(int $lineno, string $tag) + { + parent::__construct([], [], $lineno, $tag); + } + + public function compile(Compiler $compiler): void + { + $compiler + ->addDebugInfo($this) + ->write("flush();\n") + ; + } +} diff --git a/site/plugins/kirby-twig/vendor/twig/twig/src/Node/ForLoopNode.php b/site/plugins/kirby-twig/vendor/twig/twig/src/Node/ForLoopNode.php new file mode 100644 index 0000000..d5ce845 --- /dev/null +++ b/site/plugins/kirby-twig/vendor/twig/twig/src/Node/ForLoopNode.php @@ -0,0 +1,49 @@ + + */ +class ForLoopNode extends Node +{ + public function __construct(int $lineno, string $tag = null) + { + parent::__construct([], ['with_loop' => false, 'ifexpr' => false, 'else' => false], $lineno, $tag); + } + + public function compile(Compiler $compiler): void + { + if ($this->getAttribute('else')) { + $compiler->write("\$context['_iterated'] = true;\n"); + } + + if ($this->getAttribute('with_loop')) { + $compiler + ->write("++\$context['loop']['index0'];\n") + ->write("++\$context['loop']['index'];\n") + ->write("\$context['loop']['first'] = false;\n") + ->write("if (isset(\$context['loop']['length'])) {\n") + ->indent() + ->write("--\$context['loop']['revindex0'];\n") + ->write("--\$context['loop']['revindex'];\n") + ->write("\$context['loop']['last'] = 0 === \$context['loop']['revindex0'];\n") + ->outdent() + ->write("}\n") + ; + } + } +} diff --git a/site/plugins/kirby-twig/vendor/twig/twig/src/Node/ForNode.php b/site/plugins/kirby-twig/vendor/twig/twig/src/Node/ForNode.php new file mode 100644 index 0000000..04addfb --- /dev/null +++ b/site/plugins/kirby-twig/vendor/twig/twig/src/Node/ForNode.php @@ -0,0 +1,107 @@ + + */ +class ForNode extends Node +{ + private $loop; + + public function __construct(AssignNameExpression $keyTarget, AssignNameExpression $valueTarget, AbstractExpression $seq, ?Node $ifexpr, Node $body, ?Node $else, int $lineno, string $tag = null) + { + $body = new Node([$body, $this->loop = new ForLoopNode($lineno, $tag)]); + + $nodes = ['key_target' => $keyTarget, 'value_target' => $valueTarget, 'seq' => $seq, 'body' => $body]; + if (null !== $else) { + $nodes['else'] = $else; + } + + parent::__construct($nodes, ['with_loop' => true], $lineno, $tag); + } + + public function compile(Compiler $compiler): void + { + $compiler + ->addDebugInfo($this) + ->write("\$context['_parent'] = \$context;\n") + ->write("\$context['_seq'] = twig_ensure_traversable(") + ->subcompile($this->getNode('seq')) + ->raw(");\n") + ; + + if ($this->hasNode('else')) { + $compiler->write("\$context['_iterated'] = false;\n"); + } + + if ($this->getAttribute('with_loop')) { + $compiler + ->write("\$context['loop'] = [\n") + ->write(" 'parent' => \$context['_parent'],\n") + ->write(" 'index0' => 0,\n") + ->write(" 'index' => 1,\n") + ->write(" 'first' => true,\n") + ->write("];\n") + ->write("if (is_array(\$context['_seq']) || (is_object(\$context['_seq']) && \$context['_seq'] instanceof \Countable)) {\n") + ->indent() + ->write("\$length = count(\$context['_seq']);\n") + ->write("\$context['loop']['revindex0'] = \$length - 1;\n") + ->write("\$context['loop']['revindex'] = \$length;\n") + ->write("\$context['loop']['length'] = \$length;\n") + ->write("\$context['loop']['last'] = 1 === \$length;\n") + ->outdent() + ->write("}\n") + ; + } + + $this->loop->setAttribute('else', $this->hasNode('else')); + $this->loop->setAttribute('with_loop', $this->getAttribute('with_loop')); + + $compiler + ->write("foreach (\$context['_seq'] as ") + ->subcompile($this->getNode('key_target')) + ->raw(' => ') + ->subcompile($this->getNode('value_target')) + ->raw(") {\n") + ->indent() + ->subcompile($this->getNode('body')) + ->outdent() + ->write("}\n") + ; + + if ($this->hasNode('else')) { + $compiler + ->write("if (!\$context['_iterated']) {\n") + ->indent() + ->subcompile($this->getNode('else')) + ->outdent() + ->write("}\n") + ; + } + + $compiler->write("\$_parent = \$context['_parent'];\n"); + + // remove some "private" loop variables (needed for nested loops) + $compiler->write('unset($context[\'_seq\'], $context[\'_iterated\'], $context[\''.$this->getNode('key_target')->getAttribute('name').'\'], $context[\''.$this->getNode('value_target')->getAttribute('name').'\'], $context[\'_parent\'], $context[\'loop\']);'."\n"); + + // keep the values set in the inner context for variables defined in the outer context + $compiler->write("\$context = array_intersect_key(\$context, \$_parent) + \$_parent;\n"); + } +} diff --git a/site/plugins/kirby-twig/vendor/twig/twig/src/Node/IfNode.php b/site/plugins/kirby-twig/vendor/twig/twig/src/Node/IfNode.php new file mode 100644 index 0000000..5fa2008 --- /dev/null +++ b/site/plugins/kirby-twig/vendor/twig/twig/src/Node/IfNode.php @@ -0,0 +1,70 @@ + + */ +class IfNode extends Node +{ + public function __construct(Node $tests, ?Node $else, int $lineno, string $tag = null) + { + $nodes = ['tests' => $tests]; + if (null !== $else) { + $nodes['else'] = $else; + } + + parent::__construct($nodes, [], $lineno, $tag); + } + + public function compile(Compiler $compiler): void + { + $compiler->addDebugInfo($this); + for ($i = 0, $count = \count($this->getNode('tests')); $i < $count; $i += 2) { + if ($i > 0) { + $compiler + ->outdent() + ->write('} elseif (') + ; + } else { + $compiler + ->write('if (') + ; + } + + $compiler + ->subcompile($this->getNode('tests')->getNode($i)) + ->raw(") {\n") + ->indent() + ->subcompile($this->getNode('tests')->getNode($i + 1)) + ; + } + + if ($this->hasNode('else')) { + $compiler + ->outdent() + ->write("} else {\n") + ->indent() + ->subcompile($this->getNode('else')) + ; + } + + $compiler + ->outdent() + ->write("}\n"); + } +} diff --git a/site/plugins/kirby-twig/vendor/twig/twig/src/Node/ImportNode.php b/site/plugins/kirby-twig/vendor/twig/twig/src/Node/ImportNode.php new file mode 100644 index 0000000..5378d79 --- /dev/null +++ b/site/plugins/kirby-twig/vendor/twig/twig/src/Node/ImportNode.php @@ -0,0 +1,63 @@ + + */ +class ImportNode extends Node +{ + public function __construct(AbstractExpression $expr, AbstractExpression $var, int $lineno, string $tag = null, bool $global = true) + { + parent::__construct(['expr' => $expr, 'var' => $var], ['global' => $global], $lineno, $tag); + } + + public function compile(Compiler $compiler): void + { + $compiler + ->addDebugInfo($this) + ->write('$macros[') + ->repr($this->getNode('var')->getAttribute('name')) + ->raw('] = ') + ; + + if ($this->getAttribute('global')) { + $compiler + ->raw('$this->macros[') + ->repr($this->getNode('var')->getAttribute('name')) + ->raw('] = ') + ; + } + + if ($this->getNode('expr') instanceof NameExpression && '_self' === $this->getNode('expr')->getAttribute('name')) { + $compiler->raw('$this'); + } else { + $compiler + ->raw('$this->loadTemplate(') + ->subcompile($this->getNode('expr')) + ->raw(', ') + ->repr($this->getTemplateName()) + ->raw(', ') + ->repr($this->getTemplateLine()) + ->raw(')->unwrap()') + ; + } + + $compiler->raw(";\n"); + } +} diff --git a/site/plugins/kirby-twig/vendor/twig/twig/src/Node/IncludeNode.php b/site/plugins/kirby-twig/vendor/twig/twig/src/Node/IncludeNode.php new file mode 100644 index 0000000..d01f1fc --- /dev/null +++ b/site/plugins/kirby-twig/vendor/twig/twig/src/Node/IncludeNode.php @@ -0,0 +1,106 @@ + + */ +class IncludeNode extends Node implements NodeOutputInterface +{ + public function __construct(AbstractExpression $expr, ?AbstractExpression $variables, bool $only, bool $ignoreMissing, int $lineno, string $tag = null) + { + $nodes = ['expr' => $expr]; + if (null !== $variables) { + $nodes['variables'] = $variables; + } + + parent::__construct($nodes, ['only' => (bool) $only, 'ignore_missing' => (bool) $ignoreMissing], $lineno, $tag); + } + + public function compile(Compiler $compiler): void + { + $compiler->addDebugInfo($this); + + if ($this->getAttribute('ignore_missing')) { + $template = $compiler->getVarName(); + + $compiler + ->write(sprintf("$%s = null;\n", $template)) + ->write("try {\n") + ->indent() + ->write(sprintf('$%s = ', $template)) + ; + + $this->addGetTemplate($compiler); + + $compiler + ->raw(";\n") + ->outdent() + ->write("} catch (LoaderError \$e) {\n") + ->indent() + ->write("// ignore missing template\n") + ->outdent() + ->write("}\n") + ->write(sprintf("if ($%s) {\n", $template)) + ->indent() + ->write(sprintf('$%s->display(', $template)) + ; + $this->addTemplateArguments($compiler); + $compiler + ->raw(");\n") + ->outdent() + ->write("}\n") + ; + } else { + $this->addGetTemplate($compiler); + $compiler->raw('->display('); + $this->addTemplateArguments($compiler); + $compiler->raw(");\n"); + } + } + + protected function addGetTemplate(Compiler $compiler) + { + $compiler + ->write('$this->loadTemplate(') + ->subcompile($this->getNode('expr')) + ->raw(', ') + ->repr($this->getTemplateName()) + ->raw(', ') + ->repr($this->getTemplateLine()) + ->raw(')') + ; + } + + protected function addTemplateArguments(Compiler $compiler) + { + if (!$this->hasNode('variables')) { + $compiler->raw(false === $this->getAttribute('only') ? '$context' : '[]'); + } elseif (false === $this->getAttribute('only')) { + $compiler + ->raw('twig_array_merge($context, ') + ->subcompile($this->getNode('variables')) + ->raw(')') + ; + } else { + $compiler->raw('twig_to_array('); + $compiler->subcompile($this->getNode('variables')); + $compiler->raw(')'); + } + } +} diff --git a/site/plugins/kirby-twig/vendor/twig/twig/src/Node/MacroNode.php b/site/plugins/kirby-twig/vendor/twig/twig/src/Node/MacroNode.php new file mode 100644 index 0000000..cfe38f8 --- /dev/null +++ b/site/plugins/kirby-twig/vendor/twig/twig/src/Node/MacroNode.php @@ -0,0 +1,113 @@ + + */ +class MacroNode extends Node +{ + const VARARGS_NAME = 'varargs'; + + public function __construct(string $name, Node $body, Node $arguments, int $lineno, string $tag = null) + { + foreach ($arguments as $argumentName => $argument) { + if (self::VARARGS_NAME === $argumentName) { + throw new SyntaxError(sprintf('The argument "%s" in macro "%s" cannot be defined because the variable "%s" is reserved for arbitrary arguments.', self::VARARGS_NAME, $name, self::VARARGS_NAME), $argument->getTemplateLine(), $argument->getSourceContext()); + } + } + + parent::__construct(['body' => $body, 'arguments' => $arguments], ['name' => $name], $lineno, $tag); + } + + public function compile(Compiler $compiler): void + { + $compiler + ->addDebugInfo($this) + ->write(sprintf('public function macro_%s(', $this->getAttribute('name'))) + ; + + $count = \count($this->getNode('arguments')); + $pos = 0; + foreach ($this->getNode('arguments') as $name => $default) { + $compiler + ->raw('$__'.$name.'__ = ') + ->subcompile($default) + ; + + if (++$pos < $count) { + $compiler->raw(', '); + } + } + + if ($count) { + $compiler->raw(', '); + } + + $compiler + ->raw('...$__varargs__') + ->raw(")\n") + ->write("{\n") + ->indent() + ->write("\$macros = \$this->macros;\n") + ->write("\$context = \$this->env->mergeGlobals([\n") + ->indent() + ; + + foreach ($this->getNode('arguments') as $name => $default) { + $compiler + ->write('') + ->string($name) + ->raw(' => $__'.$name.'__') + ->raw(",\n") + ; + } + + $compiler + ->write('') + ->string(self::VARARGS_NAME) + ->raw(' => ') + ; + + $compiler + ->raw("\$__varargs__,\n") + ->outdent() + ->write("]);\n\n") + ->write("\$blocks = [];\n\n") + ; + if ($compiler->getEnvironment()->isDebug()) { + $compiler->write("ob_start();\n"); + } else { + $compiler->write("ob_start(function () { return ''; });\n"); + } + $compiler + ->write("try {\n") + ->indent() + ->subcompile($this->getNode('body')) + ->raw("\n") + ->write("return ('' === \$tmp = ob_get_contents()) ? '' : new Markup(\$tmp, \$this->env->getCharset());\n") + ->outdent() + ->write("} finally {\n") + ->indent() + ->write("ob_end_clean();\n") + ->outdent() + ->write("}\n") + ->outdent() + ->write("}\n\n") + ; + } +} diff --git a/site/plugins/kirby-twig/vendor/twig/twig/src/Node/ModuleNode.php b/site/plugins/kirby-twig/vendor/twig/twig/src/Node/ModuleNode.php new file mode 100644 index 0000000..e972b6b --- /dev/null +++ b/site/plugins/kirby-twig/vendor/twig/twig/src/Node/ModuleNode.php @@ -0,0 +1,464 @@ + + */ +final class ModuleNode extends Node +{ + public function __construct(Node $body, ?AbstractExpression $parent, Node $blocks, Node $macros, Node $traits, $embeddedTemplates, Source $source) + { + $nodes = [ + 'body' => $body, + 'blocks' => $blocks, + 'macros' => $macros, + 'traits' => $traits, + 'display_start' => new Node(), + 'display_end' => new Node(), + 'constructor_start' => new Node(), + 'constructor_end' => new Node(), + 'class_end' => new Node(), + ]; + if (null !== $parent) { + $nodes['parent'] = $parent; + } + + // embedded templates are set as attributes so that they are only visited once by the visitors + parent::__construct($nodes, [ + 'index' => null, + 'embedded_templates' => $embeddedTemplates, + ], 1); + + // populate the template name of all node children + $this->setSourceContext($source); + } + + public function setIndex($index) + { + $this->setAttribute('index', $index); + } + + public function compile(Compiler $compiler): void + { + $this->compileTemplate($compiler); + + foreach ($this->getAttribute('embedded_templates') as $template) { + $compiler->subcompile($template); + } + } + + protected function compileTemplate(Compiler $compiler) + { + if (!$this->getAttribute('index')) { + $compiler->write('compileClassHeader($compiler); + + $this->compileConstructor($compiler); + + $this->compileGetParent($compiler); + + $this->compileDisplay($compiler); + + $compiler->subcompile($this->getNode('blocks')); + + $this->compileMacros($compiler); + + $this->compileGetTemplateName($compiler); + + $this->compileIsTraitable($compiler); + + $this->compileDebugInfo($compiler); + + $this->compileGetSourceContext($compiler); + + $this->compileClassFooter($compiler); + } + + protected function compileGetParent(Compiler $compiler) + { + if (!$this->hasNode('parent')) { + return; + } + $parent = $this->getNode('parent'); + + $compiler + ->write("protected function doGetParent(array \$context)\n", "{\n") + ->indent() + ->addDebugInfo($parent) + ->write('return ') + ; + + if ($parent instanceof ConstantExpression) { + $compiler->subcompile($parent); + } else { + $compiler + ->raw('$this->loadTemplate(') + ->subcompile($parent) + ->raw(', ') + ->repr($this->getSourceContext()->getName()) + ->raw(', ') + ->repr($parent->getTemplateLine()) + ->raw(')') + ; + } + + $compiler + ->raw(";\n") + ->outdent() + ->write("}\n\n") + ; + } + + protected function compileClassHeader(Compiler $compiler) + { + $compiler + ->write("\n\n") + ; + if (!$this->getAttribute('index')) { + $compiler + ->write("use Twig\Environment;\n") + ->write("use Twig\Error\LoaderError;\n") + ->write("use Twig\Error\RuntimeError;\n") + ->write("use Twig\Extension\SandboxExtension;\n") + ->write("use Twig\Markup;\n") + ->write("use Twig\Sandbox\SecurityError;\n") + ->write("use Twig\Sandbox\SecurityNotAllowedTagError;\n") + ->write("use Twig\Sandbox\SecurityNotAllowedFilterError;\n") + ->write("use Twig\Sandbox\SecurityNotAllowedFunctionError;\n") + ->write("use Twig\Source;\n") + ->write("use Twig\Template;\n\n") + ; + } + $compiler + // if the template name contains */, add a blank to avoid a PHP parse error + ->write('/* '.str_replace('*/', '* /', $this->getSourceContext()->getName())." */\n") + ->write('class '.$compiler->getEnvironment()->getTemplateClass($this->getSourceContext()->getName(), $this->getAttribute('index'))) + ->raw(" extends Template\n") + ->write("{\n") + ->indent() + ->write("private \$source;\n") + ->write("private \$macros = [];\n\n") + ; + } + + protected function compileConstructor(Compiler $compiler) + { + $compiler + ->write("public function __construct(Environment \$env)\n", "{\n") + ->indent() + ->subcompile($this->getNode('constructor_start')) + ->write("parent::__construct(\$env);\n\n") + ->write("\$this->source = \$this->getSourceContext();\n\n") + ; + + // parent + if (!$this->hasNode('parent')) { + $compiler->write("\$this->parent = false;\n\n"); + } + + $countTraits = \count($this->getNode('traits')); + if ($countTraits) { + // traits + foreach ($this->getNode('traits') as $i => $trait) { + $node = $trait->getNode('template'); + + $compiler + ->addDebugInfo($node) + ->write(sprintf('$_trait_%s = $this->loadTemplate(', $i)) + ->subcompile($node) + ->raw(', ') + ->repr($node->getTemplateName()) + ->raw(', ') + ->repr($node->getTemplateLine()) + ->raw(");\n") + ->write(sprintf("if (!\$_trait_%s->isTraitable()) {\n", $i)) + ->indent() + ->write("throw new RuntimeError('Template \"'.") + ->subcompile($trait->getNode('template')) + ->raw(".'\" cannot be used as a trait.', ") + ->repr($node->getTemplateLine()) + ->raw(", \$this->source);\n") + ->outdent() + ->write("}\n") + ->write(sprintf("\$_trait_%s_blocks = \$_trait_%s->getBlocks();\n\n", $i, $i)) + ; + + foreach ($trait->getNode('targets') as $key => $value) { + $compiler + ->write(sprintf('if (!isset($_trait_%s_blocks[', $i)) + ->string($key) + ->raw("])) {\n") + ->indent() + ->write("throw new RuntimeError('Block ") + ->string($key) + ->raw(' is not defined in trait ') + ->subcompile($trait->getNode('template')) + ->raw(".', ") + ->repr($node->getTemplateLine()) + ->raw(", \$this->source);\n") + ->outdent() + ->write("}\n\n") + + ->write(sprintf('$_trait_%s_blocks[', $i)) + ->subcompile($value) + ->raw(sprintf('] = $_trait_%s_blocks[', $i)) + ->string($key) + ->raw(sprintf(']; unset($_trait_%s_blocks[', $i)) + ->string($key) + ->raw("]);\n\n") + ; + } + } + + if ($countTraits > 1) { + $compiler + ->write("\$this->traits = array_merge(\n") + ->indent() + ; + + for ($i = 0; $i < $countTraits; ++$i) { + $compiler + ->write(sprintf('$_trait_%s_blocks'.($i == $countTraits - 1 ? '' : ',')."\n", $i)) + ; + } + + $compiler + ->outdent() + ->write(");\n\n") + ; + } else { + $compiler + ->write("\$this->traits = \$_trait_0_blocks;\n\n") + ; + } + + $compiler + ->write("\$this->blocks = array_merge(\n") + ->indent() + ->write("\$this->traits,\n") + ->write("[\n") + ; + } else { + $compiler + ->write("\$this->blocks = [\n") + ; + } + + // blocks + $compiler + ->indent() + ; + + foreach ($this->getNode('blocks') as $name => $node) { + $compiler + ->write(sprintf("'%s' => [\$this, 'block_%s'],\n", $name, $name)) + ; + } + + if ($countTraits) { + $compiler + ->outdent() + ->write("]\n") + ->outdent() + ->write(");\n") + ; + } else { + $compiler + ->outdent() + ->write("];\n") + ; + } + + $compiler + ->subcompile($this->getNode('constructor_end')) + ->outdent() + ->write("}\n\n") + ; + } + + protected function compileDisplay(Compiler $compiler) + { + $compiler + ->write("protected function doDisplay(array \$context, array \$blocks = [])\n", "{\n") + ->indent() + ->write("\$macros = \$this->macros;\n") + ->subcompile($this->getNode('display_start')) + ->subcompile($this->getNode('body')) + ; + + if ($this->hasNode('parent')) { + $parent = $this->getNode('parent'); + + $compiler->addDebugInfo($parent); + if ($parent instanceof ConstantExpression) { + $compiler + ->write('$this->parent = $this->loadTemplate(') + ->subcompile($parent) + ->raw(', ') + ->repr($this->getSourceContext()->getName()) + ->raw(', ') + ->repr($parent->getTemplateLine()) + ->raw(");\n") + ; + $compiler->write('$this->parent'); + } else { + $compiler->write('$this->getParent($context)'); + } + $compiler->raw("->display(\$context, array_merge(\$this->blocks, \$blocks));\n"); + } + + $compiler + ->subcompile($this->getNode('display_end')) + ->outdent() + ->write("}\n\n") + ; + } + + protected function compileClassFooter(Compiler $compiler) + { + $compiler + ->subcompile($this->getNode('class_end')) + ->outdent() + ->write("}\n") + ; + } + + protected function compileMacros(Compiler $compiler) + { + $compiler->subcompile($this->getNode('macros')); + } + + protected function compileGetTemplateName(Compiler $compiler) + { + $compiler + ->write("public function getTemplateName()\n", "{\n") + ->indent() + ->write('return ') + ->repr($this->getSourceContext()->getName()) + ->raw(";\n") + ->outdent() + ->write("}\n\n") + ; + } + + protected function compileIsTraitable(Compiler $compiler) + { + // A template can be used as a trait if: + // * it has no parent + // * it has no macros + // * it has no body + // + // Put another way, a template can be used as a trait if it + // only contains blocks and use statements. + $traitable = !$this->hasNode('parent') && 0 === \count($this->getNode('macros')); + if ($traitable) { + if ($this->getNode('body') instanceof BodyNode) { + $nodes = $this->getNode('body')->getNode(0); + } else { + $nodes = $this->getNode('body'); + } + + if (!\count($nodes)) { + $nodes = new Node([$nodes]); + } + + foreach ($nodes as $node) { + if (!\count($node)) { + continue; + } + + if ($node instanceof TextNode && ctype_space($node->getAttribute('data'))) { + continue; + } + + if ($node instanceof BlockReferenceNode) { + continue; + } + + $traitable = false; + break; + } + } + + if ($traitable) { + return; + } + + $compiler + ->write("public function isTraitable()\n", "{\n") + ->indent() + ->write(sprintf("return %s;\n", $traitable ? 'true' : 'false')) + ->outdent() + ->write("}\n\n") + ; + } + + protected function compileDebugInfo(Compiler $compiler) + { + $compiler + ->write("public function getDebugInfo()\n", "{\n") + ->indent() + ->write(sprintf("return %s;\n", str_replace("\n", '', var_export(array_reverse($compiler->getDebugInfo(), true), true)))) + ->outdent() + ->write("}\n\n") + ; + } + + protected function compileGetSourceContext(Compiler $compiler) + { + $compiler + ->write("public function getSourceContext()\n", "{\n") + ->indent() + ->write('return new Source(') + ->string($compiler->getEnvironment()->isDebug() ? $this->getSourceContext()->getCode() : '') + ->raw(', ') + ->string($this->getSourceContext()->getName()) + ->raw(', ') + ->string($this->getSourceContext()->getPath()) + ->raw(");\n") + ->outdent() + ->write("}\n") + ; + } + + protected function compileLoadTemplate(Compiler $compiler, $node, $var) + { + if ($node instanceof ConstantExpression) { + $compiler + ->write(sprintf('%s = $this->loadTemplate(', $var)) + ->subcompile($node) + ->raw(', ') + ->repr($node->getTemplateName()) + ->raw(', ') + ->repr($node->getTemplateLine()) + ->raw(");\n") + ; + } else { + throw new \LogicException('Trait templates can only be constant nodes.'); + } + } +} diff --git a/site/plugins/kirby-twig/vendor/twig/twig/src/Node/Node.php b/site/plugins/kirby-twig/vendor/twig/twig/src/Node/Node.php new file mode 100644 index 0000000..e974b49 --- /dev/null +++ b/site/plugins/kirby-twig/vendor/twig/twig/src/Node/Node.php @@ -0,0 +1,178 @@ + + */ +class Node implements \Countable, \IteratorAggregate +{ + protected $nodes; + protected $attributes; + protected $lineno; + protected $tag; + + private $name; + private $sourceContext; + + /** + * @param array $nodes An array of named nodes + * @param array $attributes An array of attributes (should not be nodes) + * @param int $lineno The line number + * @param string $tag The tag name associated with the Node + */ + public function __construct(array $nodes = [], array $attributes = [], int $lineno = 0, string $tag = null) + { + foreach ($nodes as $name => $node) { + if (!$node instanceof self) { + throw new \InvalidArgumentException(sprintf('Using "%s" for the value of node "%s" of "%s" is not supported. You must pass a \Twig\Node\Node instance.', \is_object($node) ? \get_class($node) : (null === $node ? 'null' : \gettype($node)), $name, static::class)); + } + } + $this->nodes = $nodes; + $this->attributes = $attributes; + $this->lineno = $lineno; + $this->tag = $tag; + } + + public function __toString() + { + $attributes = []; + foreach ($this->attributes as $name => $value) { + $attributes[] = sprintf('%s: %s', $name, str_replace("\n", '', var_export($value, true))); + } + + $repr = [static::class.'('.implode(', ', $attributes)]; + + if (\count($this->nodes)) { + foreach ($this->nodes as $name => $node) { + $len = \strlen($name) + 4; + $noderepr = []; + foreach (explode("\n", (string) $node) as $line) { + $noderepr[] = str_repeat(' ', $len).$line; + } + + $repr[] = sprintf(' %s: %s', $name, ltrim(implode("\n", $noderepr))); + } + + $repr[] = ')'; + } else { + $repr[0] .= ')'; + } + + return implode("\n", $repr); + } + + /** + * @return void + */ + public function compile(Compiler $compiler) + { + foreach ($this->nodes as $node) { + $node->compile($compiler); + } + } + + public function getTemplateLine(): int + { + return $this->lineno; + } + + public function getNodeTag(): ?string + { + return $this->tag; + } + + public function hasAttribute(string $name): bool + { + return \array_key_exists($name, $this->attributes); + } + + public function getAttribute(string $name) + { + if (!\array_key_exists($name, $this->attributes)) { + throw new \LogicException(sprintf('Attribute "%s" does not exist for Node "%s".', $name, static::class)); + } + + return $this->attributes[$name]; + } + + public function setAttribute(string $name, $value): void + { + $this->attributes[$name] = $value; + } + + public function removeAttribute(string $name): void + { + unset($this->attributes[$name]); + } + + public function hasNode(string $name): bool + { + return isset($this->nodes[$name]); + } + + public function getNode(string $name): self + { + if (!isset($this->nodes[$name])) { + throw new \LogicException(sprintf('Node "%s" does not exist for Node "%s".', $name, static::class)); + } + + return $this->nodes[$name]; + } + + public function setNode(string $name, self $node): void + { + $this->nodes[$name] = $node; + } + + public function removeNode(string $name): void + { + unset($this->nodes[$name]); + } + + /** + * @return int + */ + public function count() + { + return \count($this->nodes); + } + + public function getIterator(): \Traversable + { + return new \ArrayIterator($this->nodes); + } + + public function getTemplateName(): ?string + { + return $this->sourceContext ? $this->sourceContext->getName() : null; + } + + public function setSourceContext(Source $source): void + { + $this->sourceContext = $source; + foreach ($this->nodes as $node) { + $node->setSourceContext($source); + } + } + + public function getSourceContext(): ?Source + { + return $this->sourceContext; + } +} diff --git a/site/plugins/kirby-twig/vendor/twig/twig/src/Node/NodeCaptureInterface.php b/site/plugins/kirby-twig/vendor/twig/twig/src/Node/NodeCaptureInterface.php new file mode 100644 index 0000000..9fb6a0c --- /dev/null +++ b/site/plugins/kirby-twig/vendor/twig/twig/src/Node/NodeCaptureInterface.php @@ -0,0 +1,21 @@ + + */ +interface NodeCaptureInterface +{ +} diff --git a/site/plugins/kirby-twig/vendor/twig/twig/src/Node/NodeOutputInterface.php b/site/plugins/kirby-twig/vendor/twig/twig/src/Node/NodeOutputInterface.php new file mode 100644 index 0000000..5e35b40 --- /dev/null +++ b/site/plugins/kirby-twig/vendor/twig/twig/src/Node/NodeOutputInterface.php @@ -0,0 +1,21 @@ + + */ +interface NodeOutputInterface +{ +} diff --git a/site/plugins/kirby-twig/vendor/twig/twig/src/Node/PrintNode.php b/site/plugins/kirby-twig/vendor/twig/twig/src/Node/PrintNode.php new file mode 100644 index 0000000..60386d2 --- /dev/null +++ b/site/plugins/kirby-twig/vendor/twig/twig/src/Node/PrintNode.php @@ -0,0 +1,39 @@ + + */ +class PrintNode extends Node implements NodeOutputInterface +{ + public function __construct(AbstractExpression $expr, int $lineno, string $tag = null) + { + parent::__construct(['expr' => $expr], [], $lineno, $tag); + } + + public function compile(Compiler $compiler): void + { + $compiler + ->addDebugInfo($this) + ->write('echo ') + ->subcompile($this->getNode('expr')) + ->raw(";\n") + ; + } +} diff --git a/site/plugins/kirby-twig/vendor/twig/twig/src/Node/SandboxNode.php b/site/plugins/kirby-twig/vendor/twig/twig/src/Node/SandboxNode.php new file mode 100644 index 0000000..4d5666b --- /dev/null +++ b/site/plugins/kirby-twig/vendor/twig/twig/src/Node/SandboxNode.php @@ -0,0 +1,52 @@ + + */ +class SandboxNode extends Node +{ + public function __construct(Node $body, int $lineno, string $tag = null) + { + parent::__construct(['body' => $body], [], $lineno, $tag); + } + + public function compile(Compiler $compiler): void + { + $compiler + ->addDebugInfo($this) + ->write("if (!\$alreadySandboxed = \$this->sandbox->isSandboxed()) {\n") + ->indent() + ->write("\$this->sandbox->enableSandbox();\n") + ->outdent() + ->write("}\n") + ->write("try {\n") + ->indent() + ->subcompile($this->getNode('body')) + ->outdent() + ->write("} finally {\n") + ->indent() + ->write("if (!\$alreadySandboxed) {\n") + ->indent() + ->write("\$this->sandbox->disableSandbox();\n") + ->outdent() + ->write("}\n") + ->outdent() + ->write("}\n") + ; + } +} diff --git a/site/plugins/kirby-twig/vendor/twig/twig/src/Node/SetNode.php b/site/plugins/kirby-twig/vendor/twig/twig/src/Node/SetNode.php new file mode 100644 index 0000000..96b6bd8 --- /dev/null +++ b/site/plugins/kirby-twig/vendor/twig/twig/src/Node/SetNode.php @@ -0,0 +1,105 @@ + + */ +class SetNode extends Node implements NodeCaptureInterface +{ + public function __construct(bool $capture, Node $names, Node $values, int $lineno, string $tag = null) + { + parent::__construct(['names' => $names, 'values' => $values], ['capture' => $capture, 'safe' => false], $lineno, $tag); + + /* + * Optimizes the node when capture is used for a large block of text. + * + * {% set foo %}foo{% endset %} is compiled to $context['foo'] = new Twig\Markup("foo"); + */ + if ($this->getAttribute('capture')) { + $this->setAttribute('safe', true); + + $values = $this->getNode('values'); + if ($values instanceof TextNode) { + $this->setNode('values', new ConstantExpression($values->getAttribute('data'), $values->getTemplateLine())); + $this->setAttribute('capture', false); + } + } + } + + public function compile(Compiler $compiler): void + { + $compiler->addDebugInfo($this); + + if (\count($this->getNode('names')) > 1) { + $compiler->write('list('); + foreach ($this->getNode('names') as $idx => $node) { + if ($idx) { + $compiler->raw(', '); + } + + $compiler->subcompile($node); + } + $compiler->raw(')'); + } else { + if ($this->getAttribute('capture')) { + if ($compiler->getEnvironment()->isDebug()) { + $compiler->write("ob_start();\n"); + } else { + $compiler->write("ob_start(function () { return ''; });\n"); + } + $compiler + ->subcompile($this->getNode('values')) + ; + } + + $compiler->subcompile($this->getNode('names'), false); + + if ($this->getAttribute('capture')) { + $compiler->raw(" = ('' === \$tmp = ob_get_clean()) ? '' : new Markup(\$tmp, \$this->env->getCharset())"); + } + } + + if (!$this->getAttribute('capture')) { + $compiler->raw(' = '); + + if (\count($this->getNode('names')) > 1) { + $compiler->write('['); + foreach ($this->getNode('values') as $idx => $value) { + if ($idx) { + $compiler->raw(', '); + } + + $compiler->subcompile($value); + } + $compiler->raw(']'); + } else { + if ($this->getAttribute('safe')) { + $compiler + ->raw("('' === \$tmp = ") + ->subcompile($this->getNode('values')) + ->raw(") ? '' : new Markup(\$tmp, \$this->env->getCharset())") + ; + } else { + $compiler->subcompile($this->getNode('values')); + } + } + } + + $compiler->raw(";\n"); + } +} diff --git a/site/plugins/kirby-twig/vendor/twig/twig/src/Node/TextNode.php b/site/plugins/kirby-twig/vendor/twig/twig/src/Node/TextNode.php new file mode 100644 index 0000000..d74ebe6 --- /dev/null +++ b/site/plugins/kirby-twig/vendor/twig/twig/src/Node/TextNode.php @@ -0,0 +1,38 @@ + + */ +class TextNode extends Node implements NodeOutputInterface +{ + public function __construct(string $data, int $lineno) + { + parent::__construct([], ['data' => $data], $lineno); + } + + public function compile(Compiler $compiler): void + { + $compiler + ->addDebugInfo($this) + ->write('echo ') + ->string($this->getAttribute('data')) + ->raw(";\n") + ; + } +} diff --git a/site/plugins/kirby-twig/vendor/twig/twig/src/Node/WithNode.php b/site/plugins/kirby-twig/vendor/twig/twig/src/Node/WithNode.php new file mode 100644 index 0000000..56a3344 --- /dev/null +++ b/site/plugins/kirby-twig/vendor/twig/twig/src/Node/WithNode.php @@ -0,0 +1,70 @@ + + */ +class WithNode extends Node +{ + public function __construct(Node $body, ?Node $variables, bool $only, int $lineno, string $tag = null) + { + $nodes = ['body' => $body]; + if (null !== $variables) { + $nodes['variables'] = $variables; + } + + parent::__construct($nodes, ['only' => $only], $lineno, $tag); + } + + public function compile(Compiler $compiler): void + { + $compiler->addDebugInfo($this); + + $parentContextName = $compiler->getVarName(); + + $compiler->write(sprintf("\$%s = \$context;\n", $parentContextName)); + + if ($this->hasNode('variables')) { + $node = $this->getNode('variables'); + $varsName = $compiler->getVarName(); + $compiler + ->write(sprintf('$%s = ', $varsName)) + ->subcompile($node) + ->raw(";\n") + ->write(sprintf("if (!twig_test_iterable(\$%s)) {\n", $varsName)) + ->indent() + ->write("throw new RuntimeError('Variables passed to the \"with\" tag must be a hash.', ") + ->repr($node->getTemplateLine()) + ->raw(", \$this->getSourceContext());\n") + ->outdent() + ->write("}\n") + ->write(sprintf("\$%s = twig_to_array(\$%s);\n", $varsName, $varsName)) + ; + + if ($this->getAttribute('only')) { + $compiler->write("\$context = [];\n"); + } + + $compiler->write(sprintf("\$context = \$this->env->mergeGlobals(array_merge(\$context, \$%s));\n", $varsName)); + } + + $compiler + ->subcompile($this->getNode('body')) + ->write(sprintf("\$context = \$%s;\n", $parentContextName)) + ; + } +} diff --git a/site/plugins/kirby-twig/vendor/twig/twig/src/NodeTraverser.php b/site/plugins/kirby-twig/vendor/twig/twig/src/NodeTraverser.php new file mode 100644 index 0000000..47a2d5c --- /dev/null +++ b/site/plugins/kirby-twig/vendor/twig/twig/src/NodeTraverser.php @@ -0,0 +1,76 @@ + + */ +final class NodeTraverser +{ + private $env; + private $visitors = []; + + /** + * @param NodeVisitorInterface[] $visitors + */ + public function __construct(Environment $env, array $visitors = []) + { + $this->env = $env; + foreach ($visitors as $visitor) { + $this->addVisitor($visitor); + } + } + + public function addVisitor(NodeVisitorInterface $visitor): void + { + $this->visitors[$visitor->getPriority()][] = $visitor; + } + + /** + * Traverses a node and calls the registered visitors. + */ + public function traverse(Node $node): Node + { + ksort($this->visitors); + foreach ($this->visitors as $visitors) { + foreach ($visitors as $visitor) { + $node = $this->traverseForVisitor($visitor, $node); + } + } + + return $node; + } + + private function traverseForVisitor(NodeVisitorInterface $visitor, Node $node): ?Node + { + $node = $visitor->enterNode($node, $this->env); + + foreach ($node as $k => $n) { + if (null !== $m = $this->traverseForVisitor($visitor, $n)) { + if ($m !== $n) { + $node->setNode($k, $m); + } + } else { + $node->removeNode($k); + } + } + + return $visitor->leaveNode($node, $this->env); + } +} diff --git a/site/plugins/kirby-twig/vendor/twig/twig/src/NodeVisitor/AbstractNodeVisitor.php b/site/plugins/kirby-twig/vendor/twig/twig/src/NodeVisitor/AbstractNodeVisitor.php new file mode 100644 index 0000000..d7036ae --- /dev/null +++ b/site/plugins/kirby-twig/vendor/twig/twig/src/NodeVisitor/AbstractNodeVisitor.php @@ -0,0 +1,49 @@ + + */ +abstract class AbstractNodeVisitor implements NodeVisitorInterface +{ + final public function enterNode(Node $node, Environment $env): Node + { + return $this->doEnterNode($node, $env); + } + + final public function leaveNode(Node $node, Environment $env): ?Node + { + return $this->doLeaveNode($node, $env); + } + + /** + * Called before child nodes are visited. + * + * @return Node The modified node + */ + abstract protected function doEnterNode(Node $node, Environment $env); + + /** + * Called after child nodes are visited. + * + * @return Node|null The modified node or null if the node must be removed + */ + abstract protected function doLeaveNode(Node $node, Environment $env); +} diff --git a/site/plugins/kirby-twig/vendor/twig/twig/src/NodeVisitor/EscaperNodeVisitor.php b/site/plugins/kirby-twig/vendor/twig/twig/src/NodeVisitor/EscaperNodeVisitor.php new file mode 100644 index 0000000..fafceb4 --- /dev/null +++ b/site/plugins/kirby-twig/vendor/twig/twig/src/NodeVisitor/EscaperNodeVisitor.php @@ -0,0 +1,208 @@ + + * + * @internal + */ +final class EscaperNodeVisitor implements NodeVisitorInterface +{ + private $statusStack = []; + private $blocks = []; + private $safeAnalysis; + private $traverser; + private $defaultStrategy = false; + private $safeVars = []; + + public function __construct() + { + $this->safeAnalysis = new SafeAnalysisNodeVisitor(); + } + + public function enterNode(Node $node, Environment $env): Node + { + if ($node instanceof ModuleNode) { + if ($env->hasExtension(EscaperExtension::class) && $defaultStrategy = $env->getExtension(EscaperExtension::class)->getDefaultStrategy($node->getTemplateName())) { + $this->defaultStrategy = $defaultStrategy; + } + $this->safeVars = []; + $this->blocks = []; + } elseif ($node instanceof AutoEscapeNode) { + $this->statusStack[] = $node->getAttribute('value'); + } elseif ($node instanceof BlockNode) { + $this->statusStack[] = isset($this->blocks[$node->getAttribute('name')]) ? $this->blocks[$node->getAttribute('name')] : $this->needEscaping($env); + } elseif ($node instanceof ImportNode) { + $this->safeVars[] = $node->getNode('var')->getAttribute('name'); + } + + return $node; + } + + public function leaveNode(Node $node, Environment $env): ?Node + { + if ($node instanceof ModuleNode) { + $this->defaultStrategy = false; + $this->safeVars = []; + $this->blocks = []; + } elseif ($node instanceof FilterExpression) { + return $this->preEscapeFilterNode($node, $env); + } elseif ($node instanceof PrintNode && false !== $type = $this->needEscaping($env)) { + $expression = $node->getNode('expr'); + if ($expression instanceof ConditionalExpression && $this->shouldUnwrapConditional($expression, $env, $type)) { + return new DoNode($this->unwrapConditional($expression, $env, $type), $expression->getTemplateLine()); + } + + return $this->escapePrintNode($node, $env, $type); + } + + if ($node instanceof AutoEscapeNode || $node instanceof BlockNode) { + array_pop($this->statusStack); + } elseif ($node instanceof BlockReferenceNode) { + $this->blocks[$node->getAttribute('name')] = $this->needEscaping($env); + } + + return $node; + } + + private function shouldUnwrapConditional(ConditionalExpression $expression, Environment $env, string $type): bool + { + $expr2Safe = $this->isSafeFor($type, $expression->getNode('expr2'), $env); + $expr3Safe = $this->isSafeFor($type, $expression->getNode('expr3'), $env); + + return $expr2Safe !== $expr3Safe; + } + + private function unwrapConditional(ConditionalExpression $expression, Environment $env, string $type): ConditionalExpression + { + // convert "echo a ? b : c" to "a ? echo b : echo c" recursively + $expr2 = $expression->getNode('expr2'); + if ($expr2 instanceof ConditionalExpression && $this->shouldUnwrapConditional($expr2, $env, $type)) { + $expr2 = $this->unwrapConditional($expr2, $env, $type); + } else { + $expr2 = $this->escapeInlinePrintNode(new InlinePrint($expr2, $expr2->getTemplateLine()), $env, $type); + } + $expr3 = $expression->getNode('expr3'); + if ($expr3 instanceof ConditionalExpression && $this->shouldUnwrapConditional($expr3, $env, $type)) { + $expr3 = $this->unwrapConditional($expr3, $env, $type); + } else { + $expr3 = $this->escapeInlinePrintNode(new InlinePrint($expr3, $expr3->getTemplateLine()), $env, $type); + } + + return new ConditionalExpression($expression->getNode('expr1'), $expr2, $expr3, $expression->getTemplateLine()); + } + + private function escapeInlinePrintNode(InlinePrint $node, Environment $env, string $type): Node + { + $expression = $node->getNode('node'); + + if ($this->isSafeFor($type, $expression, $env)) { + return $node; + } + + return new InlinePrint($this->getEscaperFilter($type, $expression), $node->getTemplateLine()); + } + + private function escapePrintNode(PrintNode $node, Environment $env, string $type): Node + { + if (false === $type) { + return $node; + } + + $expression = $node->getNode('expr'); + + if ($this->isSafeFor($type, $expression, $env)) { + return $node; + } + + $class = \get_class($node); + + return new $class($this->getEscaperFilter($type, $expression), $node->getTemplateLine()); + } + + private function preEscapeFilterNode(FilterExpression $filter, Environment $env): FilterExpression + { + $name = $filter->getNode('filter')->getAttribute('value'); + + $type = $env->getFilter($name)->getPreEscape(); + if (null === $type) { + return $filter; + } + + $node = $filter->getNode('node'); + if ($this->isSafeFor($type, $node, $env)) { + return $filter; + } + + $filter->setNode('node', $this->getEscaperFilter($type, $node)); + + return $filter; + } + + private function isSafeFor(string $type, Node $expression, Environment $env): bool + { + $safe = $this->safeAnalysis->getSafe($expression); + + if (null === $safe) { + if (null === $this->traverser) { + $this->traverser = new NodeTraverser($env, [$this->safeAnalysis]); + } + + $this->safeAnalysis->setSafeVars($this->safeVars); + + $this->traverser->traverse($expression); + $safe = $this->safeAnalysis->getSafe($expression); + } + + return \in_array($type, $safe) || \in_array('all', $safe); + } + + private function needEscaping(Environment $env) + { + if (\count($this->statusStack)) { + return $this->statusStack[\count($this->statusStack) - 1]; + } + + return $this->defaultStrategy ? $this->defaultStrategy : false; + } + + private function getEscaperFilter(string $type, Node $node): FilterExpression + { + $line = $node->getTemplateLine(); + $name = new ConstantExpression('escape', $line); + $args = new Node([new ConstantExpression((string) $type, $line), new ConstantExpression(null, $line), new ConstantExpression(true, $line)]); + + return new FilterExpression($node, $name, $args, $line); + } + + public function getPriority(): int + { + return 0; + } +} diff --git a/site/plugins/kirby-twig/vendor/twig/twig/src/NodeVisitor/MacroAutoImportNodeVisitor.php b/site/plugins/kirby-twig/vendor/twig/twig/src/NodeVisitor/MacroAutoImportNodeVisitor.php new file mode 100644 index 0000000..af477e6 --- /dev/null +++ b/site/plugins/kirby-twig/vendor/twig/twig/src/NodeVisitor/MacroAutoImportNodeVisitor.php @@ -0,0 +1,74 @@ + + * + * @internal + */ +final class MacroAutoImportNodeVisitor implements NodeVisitorInterface +{ + private $inAModule = false; + private $hasMacroCalls = false; + + public function enterNode(Node $node, Environment $env): Node + { + if ($node instanceof ModuleNode) { + $this->inAModule = true; + $this->hasMacroCalls = false; + } + + return $node; + } + + public function leaveNode(Node $node, Environment $env): Node + { + if ($node instanceof ModuleNode) { + $this->inAModule = false; + if ($this->hasMacroCalls) { + $node->getNode('constructor_end')->setNode('_auto_macro_import', new ImportNode(new NameExpression('_self', 0), new AssignNameExpression('_self', 0), 0, 'import', true)); + } + } elseif ($this->inAModule) { + if ( + $node instanceof GetAttrExpression && + $node->getNode('node') instanceof NameExpression && + '_self' === $node->getNode('node')->getAttribute('name') && + $node->getNode('attribute') instanceof ConstantExpression + ) { + $this->hasMacroCalls = true; + + $name = $node->getNode('attribute')->getAttribute('value'); + $node = new MethodCallExpression($node->getNode('node'), 'macro_'.$name, $node->getNode('arguments'), $node->getTemplateLine()); + $node->setAttribute('safe', true); + } + } + + return $node; + } + + public function getPriority(): int + { + // we must be ran before auto-escaping + return -10; + } +} diff --git a/site/plugins/kirby-twig/vendor/twig/twig/src/NodeVisitor/NodeVisitorInterface.php b/site/plugins/kirby-twig/vendor/twig/twig/src/NodeVisitor/NodeVisitorInterface.php new file mode 100644 index 0000000..59e836d --- /dev/null +++ b/site/plugins/kirby-twig/vendor/twig/twig/src/NodeVisitor/NodeVisitorInterface.php @@ -0,0 +1,46 @@ + + */ +interface NodeVisitorInterface +{ + /** + * Called before child nodes are visited. + * + * @return Node The modified node + */ + public function enterNode(Node $node, Environment $env): Node; + + /** + * Called after child nodes are visited. + * + * @return Node|null The modified node or null if the node must be removed + */ + public function leaveNode(Node $node, Environment $env): ?Node; + + /** + * Returns the priority for this visitor. + * + * Priority should be between -10 and 10 (0 is the default). + * + * @return int The priority level + */ + public function getPriority(); +} diff --git a/site/plugins/kirby-twig/vendor/twig/twig/src/NodeVisitor/OptimizerNodeVisitor.php b/site/plugins/kirby-twig/vendor/twig/twig/src/NodeVisitor/OptimizerNodeVisitor.php new file mode 100644 index 0000000..c167d26 --- /dev/null +++ b/site/plugins/kirby-twig/vendor/twig/twig/src/NodeVisitor/OptimizerNodeVisitor.php @@ -0,0 +1,217 @@ + + * + * @internal + */ +final class OptimizerNodeVisitor implements NodeVisitorInterface +{ + const OPTIMIZE_ALL = -1; + const OPTIMIZE_NONE = 0; + const OPTIMIZE_FOR = 2; + const OPTIMIZE_RAW_FILTER = 4; + + private $loops = []; + private $loopsTargets = []; + private $optimizers; + + /** + * @param int $optimizers The optimizer mode + */ + public function __construct(int $optimizers = -1) + { + if ($optimizers > (self::OPTIMIZE_FOR | self::OPTIMIZE_RAW_FILTER)) { + throw new \InvalidArgumentException(sprintf('Optimizer mode "%s" is not valid.', $optimizers)); + } + + $this->optimizers = $optimizers; + } + + public function enterNode(Node $node, Environment $env): Node + { + if (self::OPTIMIZE_FOR === (self::OPTIMIZE_FOR & $this->optimizers)) { + $this->enterOptimizeFor($node, $env); + } + + return $node; + } + + public function leaveNode(Node $node, Environment $env): ?Node + { + if (self::OPTIMIZE_FOR === (self::OPTIMIZE_FOR & $this->optimizers)) { + $this->leaveOptimizeFor($node, $env); + } + + if (self::OPTIMIZE_RAW_FILTER === (self::OPTIMIZE_RAW_FILTER & $this->optimizers)) { + $node = $this->optimizeRawFilter($node, $env); + } + + $node = $this->optimizePrintNode($node, $env); + + return $node; + } + + /** + * Optimizes print nodes. + * + * It replaces: + * + * * "echo $this->render(Parent)Block()" with "$this->display(Parent)Block()" + */ + private function optimizePrintNode(Node $node, Environment $env): Node + { + if (!$node instanceof PrintNode) { + return $node; + } + + $exprNode = $node->getNode('expr'); + if ( + $exprNode instanceof BlockReferenceExpression || + $exprNode instanceof ParentExpression + ) { + $exprNode->setAttribute('output', true); + + return $exprNode; + } + + return $node; + } + + /** + * Removes "raw" filters. + */ + private function optimizeRawFilter(Node $node, Environment $env): Node + { + if ($node instanceof FilterExpression && 'raw' == $node->getNode('filter')->getAttribute('value')) { + return $node->getNode('node'); + } + + return $node; + } + + /** + * Optimizes "for" tag by removing the "loop" variable creation whenever possible. + */ + private function enterOptimizeFor(Node $node, Environment $env): void + { + if ($node instanceof ForNode) { + // disable the loop variable by default + $node->setAttribute('with_loop', false); + array_unshift($this->loops, $node); + array_unshift($this->loopsTargets, $node->getNode('value_target')->getAttribute('name')); + array_unshift($this->loopsTargets, $node->getNode('key_target')->getAttribute('name')); + } elseif (!$this->loops) { + // we are outside a loop + return; + } + + // when do we need to add the loop variable back? + + // the loop variable is referenced for the current loop + elseif ($node instanceof NameExpression && 'loop' === $node->getAttribute('name')) { + $node->setAttribute('always_defined', true); + $this->addLoopToCurrent(); + } + + // optimize access to loop targets + elseif ($node instanceof NameExpression && \in_array($node->getAttribute('name'), $this->loopsTargets)) { + $node->setAttribute('always_defined', true); + } + + // block reference + elseif ($node instanceof BlockReferenceNode || $node instanceof BlockReferenceExpression) { + $this->addLoopToCurrent(); + } + + // include without the only attribute + elseif ($node instanceof IncludeNode && !$node->getAttribute('only')) { + $this->addLoopToAll(); + } + + // include function without the with_context=false parameter + elseif ($node instanceof FunctionExpression + && 'include' === $node->getAttribute('name') + && (!$node->getNode('arguments')->hasNode('with_context') + || false !== $node->getNode('arguments')->getNode('with_context')->getAttribute('value') + ) + ) { + $this->addLoopToAll(); + } + + // the loop variable is referenced via an attribute + elseif ($node instanceof GetAttrExpression + && (!$node->getNode('attribute') instanceof ConstantExpression + || 'parent' === $node->getNode('attribute')->getAttribute('value') + ) + && (true === $this->loops[0]->getAttribute('with_loop') + || ($node->getNode('node') instanceof NameExpression + && 'loop' === $node->getNode('node')->getAttribute('name') + ) + ) + ) { + $this->addLoopToAll(); + } + } + + /** + * Optimizes "for" tag by removing the "loop" variable creation whenever possible. + */ + private function leaveOptimizeFor(Node $node, Environment $env): void + { + if ($node instanceof ForNode) { + array_shift($this->loops); + array_shift($this->loopsTargets); + array_shift($this->loopsTargets); + } + } + + private function addLoopToCurrent(): void + { + $this->loops[0]->setAttribute('with_loop', true); + } + + private function addLoopToAll(): void + { + foreach ($this->loops as $loop) { + $loop->setAttribute('with_loop', true); + } + } + + public function getPriority(): int + { + return 255; + } +} diff --git a/site/plugins/kirby-twig/vendor/twig/twig/src/NodeVisitor/SafeAnalysisNodeVisitor.php b/site/plugins/kirby-twig/vendor/twig/twig/src/NodeVisitor/SafeAnalysisNodeVisitor.php new file mode 100644 index 0000000..90d6f2e --- /dev/null +++ b/site/plugins/kirby-twig/vendor/twig/twig/src/NodeVisitor/SafeAnalysisNodeVisitor.php @@ -0,0 +1,160 @@ +safeVars = $safeVars; + } + + public function getSafe(Node $node) + { + $hash = spl_object_hash($node); + if (!isset($this->data[$hash])) { + return; + } + + foreach ($this->data[$hash] as $bucket) { + if ($bucket['key'] !== $node) { + continue; + } + + if (\in_array('html_attr', $bucket['value'])) { + $bucket['value'][] = 'html'; + } + + return $bucket['value']; + } + } + + private function setSafe(Node $node, array $safe): void + { + $hash = spl_object_hash($node); + if (isset($this->data[$hash])) { + foreach ($this->data[$hash] as &$bucket) { + if ($bucket['key'] === $node) { + $bucket['value'] = $safe; + + return; + } + } + } + $this->data[$hash][] = [ + 'key' => $node, + 'value' => $safe, + ]; + } + + public function enterNode(Node $node, Environment $env): Node + { + return $node; + } + + public function leaveNode(Node $node, Environment $env): ?Node + { + if ($node instanceof ConstantExpression) { + // constants are marked safe for all + $this->setSafe($node, ['all']); + } elseif ($node instanceof BlockReferenceExpression) { + // blocks are safe by definition + $this->setSafe($node, ['all']); + } elseif ($node instanceof ParentExpression) { + // parent block is safe by definition + $this->setSafe($node, ['all']); + } elseif ($node instanceof ConditionalExpression) { + // intersect safeness of both operands + $safe = $this->intersectSafe($this->getSafe($node->getNode('expr2')), $this->getSafe($node->getNode('expr3'))); + $this->setSafe($node, $safe); + } elseif ($node instanceof FilterExpression) { + // filter expression is safe when the filter is safe + $name = $node->getNode('filter')->getAttribute('value'); + $args = $node->getNode('arguments'); + if ($filter = $env->getFilter($name)) { + $safe = $filter->getSafe($args); + if (null === $safe) { + $safe = $this->intersectSafe($this->getSafe($node->getNode('node')), $filter->getPreservesSafety()); + } + $this->setSafe($node, $safe); + } else { + $this->setSafe($node, []); + } + } elseif ($node instanceof FunctionExpression) { + // function expression is safe when the function is safe + $name = $node->getAttribute('name'); + $args = $node->getNode('arguments'); + if ($function = $env->getFunction($name)) { + $this->setSafe($node, $function->getSafe($args)); + } else { + $this->setSafe($node, []); + } + } elseif ($node instanceof MethodCallExpression) { + if ($node->getAttribute('safe')) { + $this->setSafe($node, ['all']); + } else { + $this->setSafe($node, []); + } + } elseif ($node instanceof GetAttrExpression && $node->getNode('node') instanceof NameExpression) { + $name = $node->getNode('node')->getAttribute('name'); + if (\in_array($name, $this->safeVars)) { + $this->setSafe($node, ['all']); + } else { + $this->setSafe($node, []); + } + } else { + $this->setSafe($node, []); + } + + return $node; + } + + private function intersectSafe(array $a = null, array $b = null): array + { + if (null === $a || null === $b) { + return []; + } + + if (\in_array('all', $a)) { + return $b; + } + + if (\in_array('all', $b)) { + return $a; + } + + return array_intersect($a, $b); + } + + public function getPriority(): int + { + return 0; + } +} diff --git a/site/plugins/kirby-twig/vendor/twig/twig/src/NodeVisitor/SandboxNodeVisitor.php b/site/plugins/kirby-twig/vendor/twig/twig/src/NodeVisitor/SandboxNodeVisitor.php new file mode 100644 index 0000000..1446cee --- /dev/null +++ b/site/plugins/kirby-twig/vendor/twig/twig/src/NodeVisitor/SandboxNodeVisitor.php @@ -0,0 +1,136 @@ + + * + * @internal + */ +final class SandboxNodeVisitor implements NodeVisitorInterface +{ + private $inAModule = false; + private $tags; + private $filters; + private $functions; + private $needsToStringWrap = false; + + public function enterNode(Node $node, Environment $env): Node + { + if ($node instanceof ModuleNode) { + $this->inAModule = true; + $this->tags = []; + $this->filters = []; + $this->functions = []; + + return $node; + } elseif ($this->inAModule) { + // look for tags + if ($node->getNodeTag() && !isset($this->tags[$node->getNodeTag()])) { + $this->tags[$node->getNodeTag()] = $node; + } + + // look for filters + if ($node instanceof FilterExpression && !isset($this->filters[$node->getNode('filter')->getAttribute('value')])) { + $this->filters[$node->getNode('filter')->getAttribute('value')] = $node; + } + + // look for functions + if ($node instanceof FunctionExpression && !isset($this->functions[$node->getAttribute('name')])) { + $this->functions[$node->getAttribute('name')] = $node; + } + + // the .. operator is equivalent to the range() function + if ($node instanceof RangeBinary && !isset($this->functions['range'])) { + $this->functions['range'] = $node; + } + + if ($node instanceof PrintNode) { + $this->needsToStringWrap = true; + $this->wrapNode($node, 'expr'); + } + + if ($node instanceof SetNode && !$node->getAttribute('capture')) { + $this->needsToStringWrap = true; + } + + // wrap outer nodes that can implicitly call __toString() + if ($this->needsToStringWrap) { + if ($node instanceof ConcatBinary) { + $this->wrapNode($node, 'left'); + $this->wrapNode($node, 'right'); + } + if ($node instanceof FilterExpression) { + $this->wrapNode($node, 'node'); + $this->wrapArrayNode($node, 'arguments'); + } + if ($node instanceof FunctionExpression) { + $this->wrapArrayNode($node, 'arguments'); + } + } + } + + return $node; + } + + public function leaveNode(Node $node, Environment $env): ?Node + { + if ($node instanceof ModuleNode) { + $this->inAModule = false; + + $node->setNode('constructor_end', new Node([new CheckSecurityCallNode(), $node->getNode('constructor_end')])); + $node->setNode('class_end', new Node([new CheckSecurityNode($this->filters, $this->tags, $this->functions), $node->getNode('class_end')])); + } elseif ($this->inAModule) { + if ($node instanceof PrintNode || $node instanceof SetNode) { + $this->needsToStringWrap = false; + } + } + + return $node; + } + + private function wrapNode(Node $node, string $name): void + { + $expr = $node->getNode($name); + if ($expr instanceof NameExpression || $expr instanceof GetAttrExpression) { + $node->setNode($name, new CheckToStringNode($expr)); + } + } + + private function wrapArrayNode(Node $node, string $name): void + { + $args = $node->getNode($name); + foreach ($args as $name => $_) { + $this->wrapNode($args, $name); + } + } + + public function getPriority(): int + { + return 0; + } +} diff --git a/site/plugins/kirby-twig/vendor/twig/twig/src/Parser.php b/site/plugins/kirby-twig/vendor/twig/twig/src/Parser.php new file mode 100644 index 0000000..b8aac05 --- /dev/null +++ b/site/plugins/kirby-twig/vendor/twig/twig/src/Parser.php @@ -0,0 +1,349 @@ + + */ +class Parser +{ + private $stack = []; + private $stream; + private $parent; + private $visitors; + private $expressionParser; + private $blocks; + private $blockStack; + private $macros; + private $env; + private $importedSymbols; + private $traits; + private $embeddedTemplates = []; + private $varNameSalt = 0; + + public function __construct(Environment $env) + { + $this->env = $env; + } + + public function getVarName(): string + { + return sprintf('__internal_%s', hash('sha256', __METHOD__.$this->stream->getSourceContext()->getCode().$this->varNameSalt++)); + } + + public function parse(TokenStream $stream, $test = null, bool $dropNeedle = false): ModuleNode + { + $vars = get_object_vars($this); + unset($vars['stack'], $vars['env'], $vars['handlers'], $vars['visitors'], $vars['expressionParser'], $vars['reservedMacroNames']); + $this->stack[] = $vars; + + // node visitors + if (null === $this->visitors) { + $this->visitors = $this->env->getNodeVisitors(); + } + + if (null === $this->expressionParser) { + $this->expressionParser = new ExpressionParser($this, $this->env); + } + + $this->stream = $stream; + $this->parent = null; + $this->blocks = []; + $this->macros = []; + $this->traits = []; + $this->blockStack = []; + $this->importedSymbols = [[]]; + $this->embeddedTemplates = []; + $this->varNameSalt = 0; + + try { + $body = $this->subparse($test, $dropNeedle); + + if (null !== $this->parent && null === $body = $this->filterBodyNodes($body)) { + $body = new Node(); + } + } catch (SyntaxError $e) { + if (!$e->getSourceContext()) { + $e->setSourceContext($this->stream->getSourceContext()); + } + + if (!$e->getTemplateLine()) { + $e->setTemplateLine($this->stream->getCurrent()->getLine()); + } + + throw $e; + } + + $node = new ModuleNode(new BodyNode([$body]), $this->parent, new Node($this->blocks), new Node($this->macros), new Node($this->traits), $this->embeddedTemplates, $stream->getSourceContext()); + + $traverser = new NodeTraverser($this->env, $this->visitors); + + $node = $traverser->traverse($node); + + // restore previous stack so previous parse() call can resume working + foreach (array_pop($this->stack) as $key => $val) { + $this->$key = $val; + } + + return $node; + } + + public function subparse($test, bool $dropNeedle = false): Node + { + $lineno = $this->getCurrentToken()->getLine(); + $rv = []; + while (!$this->stream->isEOF()) { + switch ($this->getCurrentToken()->getType()) { + case /* Token::TEXT_TYPE */ 0: + $token = $this->stream->next(); + $rv[] = new TextNode($token->getValue(), $token->getLine()); + break; + + case /* Token::VAR_START_TYPE */ 2: + $token = $this->stream->next(); + $expr = $this->expressionParser->parseExpression(); + $this->stream->expect(/* Token::VAR_END_TYPE */ 4); + $rv[] = new PrintNode($expr, $token->getLine()); + break; + + case /* Token::BLOCK_START_TYPE */ 1: + $this->stream->next(); + $token = $this->getCurrentToken(); + + if (/* Token::NAME_TYPE */ 5 !== $token->getType()) { + throw new SyntaxError('A block must start with a tag name.', $token->getLine(), $this->stream->getSourceContext()); + } + + if (null !== $test && $test($token)) { + if ($dropNeedle) { + $this->stream->next(); + } + + if (1 === \count($rv)) { + return $rv[0]; + } + + return new Node($rv, [], $lineno); + } + + if (!$subparser = $this->env->getTokenParser($token->getValue())) { + if (null !== $test) { + $e = new SyntaxError(sprintf('Unexpected "%s" tag', $token->getValue()), $token->getLine(), $this->stream->getSourceContext()); + + if (\is_array($test) && isset($test[0]) && $test[0] instanceof TokenParserInterface) { + $e->appendMessage(sprintf(' (expecting closing tag for the "%s" tag defined near line %s).', $test[0]->getTag(), $lineno)); + } + } else { + $e = new SyntaxError(sprintf('Unknown "%s" tag.', $token->getValue()), $token->getLine(), $this->stream->getSourceContext()); + $e->addSuggestions($token->getValue(), array_keys($this->env->getTokenParsers())); + } + + throw $e; + } + + $this->stream->next(); + + $subparser->setParser($this); + $node = $subparser->parse($token); + if (null !== $node) { + $rv[] = $node; + } + break; + + default: + throw new SyntaxError('Lexer or parser ended up in unsupported state.', $this->getCurrentToken()->getLine(), $this->stream->getSourceContext()); + } + } + + if (1 === \count($rv)) { + return $rv[0]; + } + + return new Node($rv, [], $lineno); + } + + public function getBlockStack(): array + { + return $this->blockStack; + } + + public function peekBlockStack() + { + return isset($this->blockStack[\count($this->blockStack) - 1]) ? $this->blockStack[\count($this->blockStack) - 1] : null; + } + + public function popBlockStack(): void + { + array_pop($this->blockStack); + } + + public function pushBlockStack($name): void + { + $this->blockStack[] = $name; + } + + public function hasBlock(string $name): bool + { + return isset($this->blocks[$name]); + } + + public function getBlock(string $name): Node + { + return $this->blocks[$name]; + } + + public function setBlock(string $name, BlockNode $value): void + { + $this->blocks[$name] = new BodyNode([$value], [], $value->getTemplateLine()); + } + + public function hasMacro(string $name): bool + { + return isset($this->macros[$name]); + } + + public function setMacro(string $name, MacroNode $node): void + { + $this->macros[$name] = $node; + } + + public function addTrait($trait): void + { + $this->traits[] = $trait; + } + + public function hasTraits(): bool + { + return \count($this->traits) > 0; + } + + public function embedTemplate(ModuleNode $template) + { + $template->setIndex(mt_rand()); + + $this->embeddedTemplates[] = $template; + } + + public function addImportedSymbol(string $type, string $alias, string $name = null, AbstractExpression $node = null): void + { + $this->importedSymbols[0][$type][$alias] = ['name' => $name, 'node' => $node]; + } + + public function getImportedSymbol(string $type, string $alias) + { + // if the symbol does not exist in the current scope (0), try in the main/global scope (last index) + return $this->importedSymbols[0][$type][$alias] ?? ($this->importedSymbols[\count($this->importedSymbols) - 1][$type][$alias] ?? null); + } + + public function isMainScope(): bool + { + return 1 === \count($this->importedSymbols); + } + + public function pushLocalScope(): void + { + array_unshift($this->importedSymbols, []); + } + + public function popLocalScope(): void + { + array_shift($this->importedSymbols); + } + + public function getExpressionParser(): ExpressionParser + { + return $this->expressionParser; + } + + public function getParent(): ?Node + { + return $this->parent; + } + + public function setParent(?Node $parent): void + { + $this->parent = $parent; + } + + public function getStream(): TokenStream + { + return $this->stream; + } + + public function getCurrentToken(): Token + { + return $this->stream->getCurrent(); + } + + private function filterBodyNodes(Node $node, bool $nested = false): ?Node + { + // check that the body does not contain non-empty output nodes + if ( + ($node instanceof TextNode && !ctype_space($node->getAttribute('data'))) + || + (!$node instanceof TextNode && !$node instanceof BlockReferenceNode && $node instanceof NodeOutputInterface) + ) { + if (false !== strpos((string) $node, \chr(0xEF).\chr(0xBB).\chr(0xBF))) { + $t = substr($node->getAttribute('data'), 3); + if ('' === $t || ctype_space($t)) { + // bypass empty nodes starting with a BOM + return null; + } + } + + throw new SyntaxError('A template that extends another one cannot include content outside Twig blocks. Did you forget to put the content inside a {% block %} tag?', $node->getTemplateLine(), $this->stream->getSourceContext()); + } + + // bypass nodes that "capture" the output + if ($node instanceof NodeCaptureInterface) { + // a "block" tag in such a node will serve as a block definition AND be displayed in place as well + return $node; + } + + // "block" tags that are not captured (see above) are only used for defining + // the content of the block. In such a case, nesting it does not work as + // expected as the definition is not part of the default template code flow. + if ($nested && $node instanceof BlockReferenceNode) { + throw new SyntaxError('A block definition cannot be nested under non-capturing nodes.', $node->getTemplateLine(), $this->stream->getSourceContext()); + } + + if ($node instanceof NodeOutputInterface) { + return null; + } + + // here, $nested means "being at the root level of a child template" + // we need to discard the wrapping "Node" for the "body" node + $nested = $nested || Node::class !== \get_class($node); + foreach ($node as $k => $n) { + if (null !== $n && null === $this->filterBodyNodes($n, $nested)) { + $node->removeNode($k); + } + } + + return $node; + } +} diff --git a/site/plugins/kirby-twig/vendor/twig/twig/src/Profiler/Dumper/BaseDumper.php b/site/plugins/kirby-twig/vendor/twig/twig/src/Profiler/Dumper/BaseDumper.php new file mode 100644 index 0000000..4da43e4 --- /dev/null +++ b/site/plugins/kirby-twig/vendor/twig/twig/src/Profiler/Dumper/BaseDumper.php @@ -0,0 +1,63 @@ + + */ +abstract class BaseDumper +{ + private $root; + + public function dump(Profile $profile): string + { + return $this->dumpProfile($profile); + } + + abstract protected function formatTemplate(Profile $profile, $prefix): string; + + abstract protected function formatNonTemplate(Profile $profile, $prefix): string; + + abstract protected function formatTime(Profile $profile, $percent): string; + + private function dumpProfile(Profile $profile, $prefix = '', $sibling = false): string + { + if ($profile->isRoot()) { + $this->root = $profile->getDuration(); + $start = $profile->getName(); + } else { + if ($profile->isTemplate()) { + $start = $this->formatTemplate($profile, $prefix); + } else { + $start = $this->formatNonTemplate($profile, $prefix); + } + $prefix .= $sibling ? '│ ' : ' '; + } + + $percent = $this->root ? $profile->getDuration() / $this->root * 100 : 0; + + if ($profile->getDuration() * 1000 < 1) { + $str = $start."\n"; + } else { + $str = sprintf("%s %s\n", $start, $this->formatTime($profile, $percent)); + } + + $nCount = \count($profile->getProfiles()); + foreach ($profile as $i => $p) { + $str .= $this->dumpProfile($p, $prefix, $i + 1 !== $nCount); + } + + return $str; + } +} diff --git a/site/plugins/kirby-twig/vendor/twig/twig/src/Profiler/Dumper/BlackfireDumper.php b/site/plugins/kirby-twig/vendor/twig/twig/src/Profiler/Dumper/BlackfireDumper.php new file mode 100644 index 0000000..3fab5db --- /dev/null +++ b/site/plugins/kirby-twig/vendor/twig/twig/src/Profiler/Dumper/BlackfireDumper.php @@ -0,0 +1,72 @@ + + */ +final class BlackfireDumper +{ + public function dump(Profile $profile): string + { + $data = []; + $this->dumpProfile('main()', $profile, $data); + $this->dumpChildren('main()', $profile, $data); + + $start = sprintf('%f', microtime(true)); + $str = << $values) { + $str .= "{$name}//{$values['ct']} {$values['wt']} {$values['mu']} {$values['pmu']}\n"; + } + + return $str; + } + + private function dumpChildren(string $parent, Profile $profile, &$data) + { + foreach ($profile as $p) { + if ($p->isTemplate()) { + $name = $p->getTemplate(); + } else { + $name = sprintf('%s::%s(%s)', $p->getTemplate(), $p->getType(), $p->getName()); + } + $this->dumpProfile(sprintf('%s==>%s', $parent, $name), $p, $data); + $this->dumpChildren($name, $p, $data); + } + } + + private function dumpProfile(string $edge, Profile $profile, &$data) + { + if (isset($data[$edge])) { + ++$data[$edge]['ct']; + $data[$edge]['wt'] += floor($profile->getDuration() * 1000000); + $data[$edge]['mu'] += $profile->getMemoryUsage(); + $data[$edge]['pmu'] += $profile->getPeakMemoryUsage(); + } else { + $data[$edge] = [ + 'ct' => 1, + 'wt' => floor($profile->getDuration() * 1000000), + 'mu' => $profile->getMemoryUsage(), + 'pmu' => $profile->getPeakMemoryUsage(), + ]; + } + } +} diff --git a/site/plugins/kirby-twig/vendor/twig/twig/src/Profiler/Dumper/HtmlDumper.php b/site/plugins/kirby-twig/vendor/twig/twig/src/Profiler/Dumper/HtmlDumper.php new file mode 100644 index 0000000..1f2433b --- /dev/null +++ b/site/plugins/kirby-twig/vendor/twig/twig/src/Profiler/Dumper/HtmlDumper.php @@ -0,0 +1,47 @@ + + */ +final class HtmlDumper extends BaseDumper +{ + private static $colors = [ + 'block' => '#dfd', + 'macro' => '#ddf', + 'template' => '#ffd', + 'big' => '#d44', + ]; + + public function dump(Profile $profile): string + { + return '
    '.parent::dump($profile).'
    '; + } + + protected function formatTemplate(Profile $profile, $prefix): string + { + return sprintf('%s└ %s', $prefix, self::$colors['template'], $profile->getTemplate()); + } + + protected function formatNonTemplate(Profile $profile, $prefix): string + { + return sprintf('%s└ %s::%s(%s)', $prefix, $profile->getTemplate(), $profile->getType(), isset(self::$colors[$profile->getType()]) ? self::$colors[$profile->getType()] : 'auto', $profile->getName()); + } + + protected function formatTime(Profile $profile, $percent): string + { + return sprintf('%.2fms/%.0f%%', $percent > 20 ? self::$colors['big'] : 'auto', $profile->getDuration() * 1000, $percent); + } +} diff --git a/site/plugins/kirby-twig/vendor/twig/twig/src/Profiler/Dumper/TextDumper.php b/site/plugins/kirby-twig/vendor/twig/twig/src/Profiler/Dumper/TextDumper.php new file mode 100644 index 0000000..31561c4 --- /dev/null +++ b/site/plugins/kirby-twig/vendor/twig/twig/src/Profiler/Dumper/TextDumper.php @@ -0,0 +1,35 @@ + + */ +final class TextDumper extends BaseDumper +{ + protected function formatTemplate(Profile $profile, $prefix): string + { + return sprintf('%s└ %s', $prefix, $profile->getTemplate()); + } + + protected function formatNonTemplate(Profile $profile, $prefix): string + { + return sprintf('%s└ %s::%s(%s)', $prefix, $profile->getTemplate(), $profile->getType(), $profile->getName()); + } + + protected function formatTime(Profile $profile, $percent): string + { + return sprintf('%.2fms/%.0f%%', $profile->getDuration() * 1000, $percent); + } +} diff --git a/site/plugins/kirby-twig/vendor/twig/twig/src/Profiler/Node/EnterProfileNode.php b/site/plugins/kirby-twig/vendor/twig/twig/src/Profiler/Node/EnterProfileNode.php new file mode 100644 index 0000000..1494baf --- /dev/null +++ b/site/plugins/kirby-twig/vendor/twig/twig/src/Profiler/Node/EnterProfileNode.php @@ -0,0 +1,42 @@ + + */ +class EnterProfileNode extends Node +{ + public function __construct(string $extensionName, string $type, string $name, string $varName) + { + parent::__construct([], ['extension_name' => $extensionName, 'name' => $name, 'type' => $type, 'var_name' => $varName]); + } + + public function compile(Compiler $compiler): void + { + $compiler + ->write(sprintf('$%s = $this->extensions[', $this->getAttribute('var_name'))) + ->repr($this->getAttribute('extension_name')) + ->raw("];\n") + ->write(sprintf('$%s->enter($%s = new \Twig\Profiler\Profile($this->getTemplateName(), ', $this->getAttribute('var_name'), $this->getAttribute('var_name').'_prof')) + ->repr($this->getAttribute('type')) + ->raw(', ') + ->repr($this->getAttribute('name')) + ->raw("));\n\n") + ; + } +} diff --git a/site/plugins/kirby-twig/vendor/twig/twig/src/Profiler/Node/LeaveProfileNode.php b/site/plugins/kirby-twig/vendor/twig/twig/src/Profiler/Node/LeaveProfileNode.php new file mode 100644 index 0000000..94cebba --- /dev/null +++ b/site/plugins/kirby-twig/vendor/twig/twig/src/Profiler/Node/LeaveProfileNode.php @@ -0,0 +1,36 @@ + + */ +class LeaveProfileNode extends Node +{ + public function __construct(string $varName) + { + parent::__construct([], ['var_name' => $varName]); + } + + public function compile(Compiler $compiler): void + { + $compiler + ->write("\n") + ->write(sprintf("\$%s->leave(\$%s);\n\n", $this->getAttribute('var_name'), $this->getAttribute('var_name').'_prof')) + ; + } +} diff --git a/site/plugins/kirby-twig/vendor/twig/twig/src/Profiler/NodeVisitor/ProfilerNodeVisitor.php b/site/plugins/kirby-twig/vendor/twig/twig/src/Profiler/NodeVisitor/ProfilerNodeVisitor.php new file mode 100644 index 0000000..bd23b20 --- /dev/null +++ b/site/plugins/kirby-twig/vendor/twig/twig/src/Profiler/NodeVisitor/ProfilerNodeVisitor.php @@ -0,0 +1,76 @@ + + */ +final class ProfilerNodeVisitor implements NodeVisitorInterface +{ + private $extensionName; + + public function __construct(string $extensionName) + { + $this->extensionName = $extensionName; + } + + public function enterNode(Node $node, Environment $env): Node + { + return $node; + } + + public function leaveNode(Node $node, Environment $env): ?Node + { + if ($node instanceof ModuleNode) { + $varName = $this->getVarName(); + $node->setNode('display_start', new Node([new EnterProfileNode($this->extensionName, Profile::TEMPLATE, $node->getTemplateName(), $varName), $node->getNode('display_start')])); + $node->setNode('display_end', new Node([new LeaveProfileNode($varName), $node->getNode('display_end')])); + } elseif ($node instanceof BlockNode) { + $varName = $this->getVarName(); + $node->setNode('body', new BodyNode([ + new EnterProfileNode($this->extensionName, Profile::BLOCK, $node->getAttribute('name'), $varName), + $node->getNode('body'), + new LeaveProfileNode($varName), + ])); + } elseif ($node instanceof MacroNode) { + $varName = $this->getVarName(); + $node->setNode('body', new BodyNode([ + new EnterProfileNode($this->extensionName, Profile::MACRO, $node->getAttribute('name'), $varName), + $node->getNode('body'), + new LeaveProfileNode($varName), + ])); + } + + return $node; + } + + private function getVarName(): string + { + return sprintf('__internal_%s', hash('sha256', $this->extensionName)); + } + + public function getPriority(): int + { + return 0; + } +} diff --git a/site/plugins/kirby-twig/vendor/twig/twig/src/Profiler/Profile.php b/site/plugins/kirby-twig/vendor/twig/twig/src/Profiler/Profile.php new file mode 100644 index 0000000..ffbe74f --- /dev/null +++ b/site/plugins/kirby-twig/vendor/twig/twig/src/Profiler/Profile.php @@ -0,0 +1,181 @@ + + */ +final class Profile implements \IteratorAggregate, \Serializable +{ + const ROOT = 'ROOT'; + const BLOCK = 'block'; + const TEMPLATE = 'template'; + const MACRO = 'macro'; + + private $template; + private $name; + private $type; + private $starts = []; + private $ends = []; + private $profiles = []; + + public function __construct(string $template = 'main', string $type = self::ROOT, string $name = 'main') + { + $this->template = $template; + $this->type = $type; + $this->name = 0 === strpos($name, '__internal_') ? 'INTERNAL' : $name; + $this->enter(); + } + + public function getTemplate(): string + { + return $this->template; + } + + public function getType(): string + { + return $this->type; + } + + public function getName(): string + { + return $this->name; + } + + public function isRoot(): bool + { + return self::ROOT === $this->type; + } + + public function isTemplate(): bool + { + return self::TEMPLATE === $this->type; + } + + public function isBlock(): bool + { + return self::BLOCK === $this->type; + } + + public function isMacro(): bool + { + return self::MACRO === $this->type; + } + + /** + * @return Profile[] + */ + public function getProfiles(): array + { + return $this->profiles; + } + + public function addProfile(self $profile): void + { + $this->profiles[] = $profile; + } + + /** + * Returns the duration in microseconds. + */ + public function getDuration(): float + { + if ($this->isRoot() && $this->profiles) { + // for the root node with children, duration is the sum of all child durations + $duration = 0; + foreach ($this->profiles as $profile) { + $duration += $profile->getDuration(); + } + + return $duration; + } + + return isset($this->ends['wt']) && isset($this->starts['wt']) ? $this->ends['wt'] - $this->starts['wt'] : 0; + } + + /** + * Returns the memory usage in bytes. + */ + public function getMemoryUsage(): int + { + return isset($this->ends['mu']) && isset($this->starts['mu']) ? $this->ends['mu'] - $this->starts['mu'] : 0; + } + + /** + * Returns the peak memory usage in bytes. + */ + public function getPeakMemoryUsage(): int + { + return isset($this->ends['pmu']) && isset($this->starts['pmu']) ? $this->ends['pmu'] - $this->starts['pmu'] : 0; + } + + /** + * Starts the profiling. + */ + public function enter(): void + { + $this->starts = [ + 'wt' => microtime(true), + 'mu' => memory_get_usage(), + 'pmu' => memory_get_peak_usage(), + ]; + } + + /** + * Stops the profiling. + */ + public function leave(): void + { + $this->ends = [ + 'wt' => microtime(true), + 'mu' => memory_get_usage(), + 'pmu' => memory_get_peak_usage(), + ]; + } + + public function reset(): void + { + $this->starts = $this->ends = $this->profiles = []; + $this->enter(); + } + + public function getIterator(): \Traversable + { + return new \ArrayIterator($this->profiles); + } + + public function serialize(): string + { + return serialize($this->__serialize()); + } + + public function unserialize($data): void + { + $this->__unserialize(unserialize($data)); + } + + /** + * @internal + */ + public function __serialize(): array + { + return [$this->template, $this->name, $this->type, $this->starts, $this->ends, $this->profiles]; + } + + /** + * @internal + */ + public function __unserialize(array $data): void + { + list($this->template, $this->name, $this->type, $this->starts, $this->ends, $this->profiles) = $data; + } +} diff --git a/site/plugins/kirby-twig/vendor/twig/twig/src/RuntimeLoader/ContainerRuntimeLoader.php b/site/plugins/kirby-twig/vendor/twig/twig/src/RuntimeLoader/ContainerRuntimeLoader.php new file mode 100644 index 0000000..b360d7b --- /dev/null +++ b/site/plugins/kirby-twig/vendor/twig/twig/src/RuntimeLoader/ContainerRuntimeLoader.php @@ -0,0 +1,37 @@ + + * @author Robin Chalas + */ +class ContainerRuntimeLoader implements RuntimeLoaderInterface +{ + private $container; + + public function __construct(ContainerInterface $container) + { + $this->container = $container; + } + + public function load(string $class) + { + return $this->container->has($class) ? $this->container->get($class) : null; + } +} diff --git a/site/plugins/kirby-twig/vendor/twig/twig/src/RuntimeLoader/FactoryRuntimeLoader.php b/site/plugins/kirby-twig/vendor/twig/twig/src/RuntimeLoader/FactoryRuntimeLoader.php new file mode 100644 index 0000000..1306483 --- /dev/null +++ b/site/plugins/kirby-twig/vendor/twig/twig/src/RuntimeLoader/FactoryRuntimeLoader.php @@ -0,0 +1,41 @@ + + */ +class FactoryRuntimeLoader implements RuntimeLoaderInterface +{ + private $map; + + /** + * @param array $map An array where keys are class names and values factory callables + */ + public function __construct(array $map = []) + { + $this->map = $map; + } + + public function load(string $class) + { + if (!isset($this->map[$class])) { + return null; + } + + $runtimeFactory = $this->map[$class]; + + return $runtimeFactory(); + } +} diff --git a/site/plugins/kirby-twig/vendor/twig/twig/src/RuntimeLoader/RuntimeLoaderInterface.php b/site/plugins/kirby-twig/vendor/twig/twig/src/RuntimeLoader/RuntimeLoaderInterface.php new file mode 100644 index 0000000..9e5b204 --- /dev/null +++ b/site/plugins/kirby-twig/vendor/twig/twig/src/RuntimeLoader/RuntimeLoaderInterface.php @@ -0,0 +1,27 @@ + + */ +interface RuntimeLoaderInterface +{ + /** + * Creates the runtime implementation of a Twig element (filter/function/test). + * + * @return object|null The runtime instance or null if the loader does not know how to create the runtime for this class + */ + public function load(string $class); +} diff --git a/site/plugins/kirby-twig/vendor/twig/twig/src/Sandbox/SecurityError.php b/site/plugins/kirby-twig/vendor/twig/twig/src/Sandbox/SecurityError.php new file mode 100644 index 0000000..30a404f --- /dev/null +++ b/site/plugins/kirby-twig/vendor/twig/twig/src/Sandbox/SecurityError.php @@ -0,0 +1,23 @@ + + */ +class SecurityError extends Error +{ +} diff --git a/site/plugins/kirby-twig/vendor/twig/twig/src/Sandbox/SecurityNotAllowedFilterError.php b/site/plugins/kirby-twig/vendor/twig/twig/src/Sandbox/SecurityNotAllowedFilterError.php new file mode 100644 index 0000000..02d3063 --- /dev/null +++ b/site/plugins/kirby-twig/vendor/twig/twig/src/Sandbox/SecurityNotAllowedFilterError.php @@ -0,0 +1,33 @@ + + */ +final class SecurityNotAllowedFilterError extends SecurityError +{ + private $filterName; + + public function __construct(string $message, string $functionName) + { + parent::__construct($message); + $this->filterName = $functionName; + } + + public function getFilterName(): string + { + return $this->filterName; + } +} diff --git a/site/plugins/kirby-twig/vendor/twig/twig/src/Sandbox/SecurityNotAllowedFunctionError.php b/site/plugins/kirby-twig/vendor/twig/twig/src/Sandbox/SecurityNotAllowedFunctionError.php new file mode 100644 index 0000000..4f76dc6 --- /dev/null +++ b/site/plugins/kirby-twig/vendor/twig/twig/src/Sandbox/SecurityNotAllowedFunctionError.php @@ -0,0 +1,33 @@ + + */ +final class SecurityNotAllowedFunctionError extends SecurityError +{ + private $functionName; + + public function __construct(string $message, string $functionName) + { + parent::__construct($message); + $this->functionName = $functionName; + } + + public function getFunctionName(): string + { + return $this->functionName; + } +} diff --git a/site/plugins/kirby-twig/vendor/twig/twig/src/Sandbox/SecurityNotAllowedMethodError.php b/site/plugins/kirby-twig/vendor/twig/twig/src/Sandbox/SecurityNotAllowedMethodError.php new file mode 100644 index 0000000..8df9d0b --- /dev/null +++ b/site/plugins/kirby-twig/vendor/twig/twig/src/Sandbox/SecurityNotAllowedMethodError.php @@ -0,0 +1,40 @@ + + */ +final class SecurityNotAllowedMethodError extends SecurityError +{ + private $className; + private $methodName; + + public function __construct(string $message, string $className, string $methodName) + { + parent::__construct($message); + $this->className = $className; + $this->methodName = $methodName; + } + + public function getClassName(): string + { + return $this->className; + } + + public function getMethodName() + { + return $this->methodName; + } +} diff --git a/site/plugins/kirby-twig/vendor/twig/twig/src/Sandbox/SecurityNotAllowedPropertyError.php b/site/plugins/kirby-twig/vendor/twig/twig/src/Sandbox/SecurityNotAllowedPropertyError.php new file mode 100644 index 0000000..42ec4f3 --- /dev/null +++ b/site/plugins/kirby-twig/vendor/twig/twig/src/Sandbox/SecurityNotAllowedPropertyError.php @@ -0,0 +1,40 @@ + + */ +final class SecurityNotAllowedPropertyError extends SecurityError +{ + private $className; + private $propertyName; + + public function __construct(string $message, string $className, string $propertyName) + { + parent::__construct($message); + $this->className = $className; + $this->propertyName = $propertyName; + } + + public function getClassName(): string + { + return $this->className; + } + + public function getPropertyName() + { + return $this->propertyName; + } +} diff --git a/site/plugins/kirby-twig/vendor/twig/twig/src/Sandbox/SecurityNotAllowedTagError.php b/site/plugins/kirby-twig/vendor/twig/twig/src/Sandbox/SecurityNotAllowedTagError.php new file mode 100644 index 0000000..4522150 --- /dev/null +++ b/site/plugins/kirby-twig/vendor/twig/twig/src/Sandbox/SecurityNotAllowedTagError.php @@ -0,0 +1,33 @@ + + */ +final class SecurityNotAllowedTagError extends SecurityError +{ + private $tagName; + + public function __construct(string $message, string $tagName) + { + parent::__construct($message); + $this->tagName = $tagName; + } + + public function getTagName(): string + { + return $this->tagName; + } +} diff --git a/site/plugins/kirby-twig/vendor/twig/twig/src/Sandbox/SecurityPolicy.php b/site/plugins/kirby-twig/vendor/twig/twig/src/Sandbox/SecurityPolicy.php new file mode 100644 index 0000000..2fc0d01 --- /dev/null +++ b/site/plugins/kirby-twig/vendor/twig/twig/src/Sandbox/SecurityPolicy.php @@ -0,0 +1,126 @@ + + */ +final class SecurityPolicy implements SecurityPolicyInterface +{ + private $allowedTags; + private $allowedFilters; + private $allowedMethods; + private $allowedProperties; + private $allowedFunctions; + + public function __construct(array $allowedTags = [], array $allowedFilters = [], array $allowedMethods = [], array $allowedProperties = [], array $allowedFunctions = []) + { + $this->allowedTags = $allowedTags; + $this->allowedFilters = $allowedFilters; + $this->setAllowedMethods($allowedMethods); + $this->allowedProperties = $allowedProperties; + $this->allowedFunctions = $allowedFunctions; + } + + public function setAllowedTags(array $tags): void + { + $this->allowedTags = $tags; + } + + public function setAllowedFilters(array $filters): void + { + $this->allowedFilters = $filters; + } + + public function setAllowedMethods(array $methods): void + { + $this->allowedMethods = []; + foreach ($methods as $class => $m) { + $this->allowedMethods[$class] = array_map(function ($value) { return strtr($value, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz'); }, \is_array($m) ? $m : [$m]); + } + } + + public function setAllowedProperties(array $properties): void + { + $this->allowedProperties = $properties; + } + + public function setAllowedFunctions(array $functions): void + { + $this->allowedFunctions = $functions; + } + + public function checkSecurity($tags, $filters, $functions): void + { + foreach ($tags as $tag) { + if (!\in_array($tag, $this->allowedTags)) { + throw new SecurityNotAllowedTagError(sprintf('Tag "%s" is not allowed.', $tag), $tag); + } + } + + foreach ($filters as $filter) { + if (!\in_array($filter, $this->allowedFilters)) { + throw new SecurityNotAllowedFilterError(sprintf('Filter "%s" is not allowed.', $filter), $filter); + } + } + + foreach ($functions as $function) { + if (!\in_array($function, $this->allowedFunctions)) { + throw new SecurityNotAllowedFunctionError(sprintf('Function "%s" is not allowed.', $function), $function); + } + } + } + + public function checkMethodAllowed($obj, $method): void + { + if ($obj instanceof Template || $obj instanceof Markup) { + return; + } + + $allowed = false; + $method = strtr($method, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz'); + foreach ($this->allowedMethods as $class => $methods) { + if ($obj instanceof $class) { + $allowed = \in_array($method, $methods); + + break; + } + } + + if (!$allowed) { + $class = \get_class($obj); + throw new SecurityNotAllowedMethodError(sprintf('Calling "%s" method on a "%s" object is not allowed.', $method, $class), $class, $method); + } + } + + public function checkPropertyAllowed($obj, $property): void + { + $allowed = false; + foreach ($this->allowedProperties as $class => $properties) { + if ($obj instanceof $class) { + $allowed = \in_array($property, \is_array($properties) ? $properties : [$properties]); + + break; + } + } + + if (!$allowed) { + $class = \get_class($obj); + throw new SecurityNotAllowedPropertyError(sprintf('Calling "%s" property on a "%s" object is not allowed.', $property, $class), $class, $property); + } + } +} diff --git a/site/plugins/kirby-twig/vendor/twig/twig/src/Sandbox/SecurityPolicyInterface.php b/site/plugins/kirby-twig/vendor/twig/twig/src/Sandbox/SecurityPolicyInterface.php new file mode 100644 index 0000000..4cb479d --- /dev/null +++ b/site/plugins/kirby-twig/vendor/twig/twig/src/Sandbox/SecurityPolicyInterface.php @@ -0,0 +1,35 @@ + + */ +interface SecurityPolicyInterface +{ + /** + * @throws SecurityError + */ + public function checkSecurity($tags, $filters, $functions): void; + + /** + * @throws SecurityNotAllowedMethodError + */ + public function checkMethodAllowed($obj, $method): void; + + /** + * @throws SecurityNotAllowedPropertyError + */ + public function checkPropertyAllowed($obj, $method): void; +} diff --git a/site/plugins/kirby-twig/vendor/twig/twig/src/Source.php b/site/plugins/kirby-twig/vendor/twig/twig/src/Source.php new file mode 100644 index 0000000..3cb0240 --- /dev/null +++ b/site/plugins/kirby-twig/vendor/twig/twig/src/Source.php @@ -0,0 +1,51 @@ + + */ +final class Source +{ + private $code; + private $name; + private $path; + + /** + * @param string $code The template source code + * @param string $name The template logical name + * @param string $path The filesystem path of the template if any + */ + public function __construct(string $code, string $name, string $path = '') + { + $this->code = $code; + $this->name = $name; + $this->path = $path; + } + + public function getCode(): string + { + return $this->code; + } + + public function getName(): string + { + return $this->name; + } + + public function getPath(): string + { + return $this->path; + } +} diff --git a/site/plugins/kirby-twig/vendor/twig/twig/src/Template.php b/site/plugins/kirby-twig/vendor/twig/twig/src/Template.php new file mode 100644 index 0000000..14033ce --- /dev/null +++ b/site/plugins/kirby-twig/vendor/twig/twig/src/Template.php @@ -0,0 +1,422 @@ +load() + * instead, which returns an instance of \Twig\TemplateWrapper. + * + * @author Fabien Potencier + * + * @internal + */ +abstract class Template +{ + const ANY_CALL = 'any'; + const ARRAY_CALL = 'array'; + const METHOD_CALL = 'method'; + + protected $parent; + protected $parents = []; + protected $env; + protected $blocks = []; + protected $traits = []; + protected $extensions = []; + protected $sandbox; + + public function __construct(Environment $env) + { + $this->env = $env; + $this->extensions = $env->getExtensions(); + } + + /** + * Returns the template name. + * + * @return string The template name + */ + abstract public function getTemplateName(); + + /** + * Returns debug information about the template. + * + * @return array Debug information + */ + abstract public function getDebugInfo(); + + /** + * Returns information about the original template source code. + * + * @return Source + */ + abstract public function getSourceContext(); + + /** + * Returns the parent template. + * + * This method is for internal use only and should never be called + * directly. + * + * @return Template|TemplateWrapper|false The parent template or false if there is no parent + */ + public function getParent(array $context) + { + if (null !== $this->parent) { + return $this->parent; + } + + try { + $parent = $this->doGetParent($context); + + if (false === $parent) { + return false; + } + + if ($parent instanceof self || $parent instanceof TemplateWrapper) { + return $this->parents[$parent->getSourceContext()->getName()] = $parent; + } + + if (!isset($this->parents[$parent])) { + $this->parents[$parent] = $this->loadTemplate($parent); + } + } catch (LoaderError $e) { + $e->setSourceContext(null); + $e->guess(); + + throw $e; + } + + return $this->parents[$parent]; + } + + protected function doGetParent(array $context) + { + return false; + } + + public function isTraitable() + { + return true; + } + + /** + * Displays a parent block. + * + * This method is for internal use only and should never be called + * directly. + * + * @param string $name The block name to display from the parent + * @param array $context The context + * @param array $blocks The current set of blocks + */ + public function displayParentBlock($name, array $context, array $blocks = []) + { + if (isset($this->traits[$name])) { + $this->traits[$name][0]->displayBlock($name, $context, $blocks, false); + } elseif (false !== $parent = $this->getParent($context)) { + $parent->displayBlock($name, $context, $blocks, false); + } else { + throw new RuntimeError(sprintf('The template has no parent and no traits defining the "%s" block.', $name), -1, $this->getSourceContext()); + } + } + + /** + * Displays a block. + * + * This method is for internal use only and should never be called + * directly. + * + * @param string $name The block name to display + * @param array $context The context + * @param array $blocks The current set of blocks + * @param bool $useBlocks Whether to use the current set of blocks + */ + public function displayBlock($name, array $context, array $blocks = [], $useBlocks = true, self $templateContext = null) + { + if ($useBlocks && isset($blocks[$name])) { + $template = $blocks[$name][0]; + $block = $blocks[$name][1]; + } elseif (isset($this->blocks[$name])) { + $template = $this->blocks[$name][0]; + $block = $this->blocks[$name][1]; + } else { + $template = null; + $block = null; + } + + // avoid RCEs when sandbox is enabled + if (null !== $template && !$template instanceof self) { + throw new \LogicException('A block must be a method on a \Twig\Template instance.'); + } + + if (null !== $template) { + try { + $template->$block($context, $blocks); + } catch (Error $e) { + if (!$e->getSourceContext()) { + $e->setSourceContext($template->getSourceContext()); + } + + // this is mostly useful for \Twig\Error\LoaderError exceptions + // see \Twig\Error\LoaderError + if (-1 === $e->getTemplateLine()) { + $e->guess(); + } + + throw $e; + } catch (\Exception $e) { + $e = new RuntimeError(sprintf('An exception has been thrown during the rendering of a template ("%s").', $e->getMessage()), -1, $template->getSourceContext(), $e); + $e->guess(); + + throw $e; + } + } elseif (false !== $parent = $this->getParent($context)) { + $parent->displayBlock($name, $context, array_merge($this->blocks, $blocks), false, $templateContext ?? $this); + } elseif (isset($blocks[$name])) { + throw new RuntimeError(sprintf('Block "%s" should not call parent() in "%s" as the block does not exist in the parent template "%s".', $name, $blocks[$name][0]->getTemplateName(), $this->getTemplateName()), -1, $blocks[$name][0]->getSourceContext()); + } else { + throw new RuntimeError(sprintf('Block "%s" on template "%s" does not exist.', $name, $this->getTemplateName()), -1, ($templateContext ?? $this)->getSourceContext()); + } + } + + /** + * Renders a parent block. + * + * This method is for internal use only and should never be called + * directly. + * + * @param string $name The block name to render from the parent + * @param array $context The context + * @param array $blocks The current set of blocks + * + * @return string The rendered block + */ + public function renderParentBlock($name, array $context, array $blocks = []) + { + if ($this->env->isDebug()) { + ob_start(); + } else { + ob_start(function () { return ''; }); + } + $this->displayParentBlock($name, $context, $blocks); + + return ob_get_clean(); + } + + /** + * Renders a block. + * + * This method is for internal use only and should never be called + * directly. + * + * @param string $name The block name to render + * @param array $context The context + * @param array $blocks The current set of blocks + * @param bool $useBlocks Whether to use the current set of blocks + * + * @return string The rendered block + */ + public function renderBlock($name, array $context, array $blocks = [], $useBlocks = true) + { + if ($this->env->isDebug()) { + ob_start(); + } else { + ob_start(function () { return ''; }); + } + $this->displayBlock($name, $context, $blocks, $useBlocks); + + return ob_get_clean(); + } + + /** + * Returns whether a block exists or not in the current context of the template. + * + * This method checks blocks defined in the current template + * or defined in "used" traits or defined in parent templates. + * + * @param string $name The block name + * @param array $context The context + * @param array $blocks The current set of blocks + * + * @return bool true if the block exists, false otherwise + */ + public function hasBlock($name, array $context, array $blocks = []) + { + if (isset($blocks[$name])) { + return $blocks[$name][0] instanceof self; + } + + if (isset($this->blocks[$name])) { + return true; + } + + if (false !== $parent = $this->getParent($context)) { + return $parent->hasBlock($name, $context); + } + + return false; + } + + /** + * Returns all block names in the current context of the template. + * + * This method checks blocks defined in the current template + * or defined in "used" traits or defined in parent templates. + * + * @param array $context The context + * @param array $blocks The current set of blocks + * + * @return array An array of block names + */ + public function getBlockNames(array $context, array $blocks = []) + { + $names = array_merge(array_keys($blocks), array_keys($this->blocks)); + + if (false !== $parent = $this->getParent($context)) { + $names = array_merge($names, $parent->getBlockNames($context)); + } + + return array_unique($names); + } + + /** + * @return Template|TemplateWrapper + */ + protected function loadTemplate($template, $templateName = null, $line = null, $index = null) + { + try { + if (\is_array($template)) { + return $this->env->resolveTemplate($template); + } + + if ($template instanceof self || $template instanceof TemplateWrapper) { + return $template; + } + + if ($template === $this->getTemplateName()) { + $class = static::class; + if (false !== $pos = strrpos($class, '___', -1)) { + $class = substr($class, 0, $pos); + } + } else { + $class = $this->env->getTemplateClass($template); + } + + return $this->env->loadTemplate($class, $template, $index); + } catch (Error $e) { + if (!$e->getSourceContext()) { + $e->setSourceContext($templateName ? new Source('', $templateName) : $this->getSourceContext()); + } + + if ($e->getTemplateLine() > 0) { + throw $e; + } + + if (!$line) { + $e->guess(); + } else { + $e->setTemplateLine($line); + } + + throw $e; + } + } + + /** + * @internal + * + * @return Template + */ + public function unwrap() + { + return $this; + } + + /** + * Returns all blocks. + * + * This method is for internal use only and should never be called + * directly. + * + * @return array An array of blocks + */ + public function getBlocks() + { + return $this->blocks; + } + + public function display(array $context, array $blocks = []) + { + $this->displayWithErrorHandling($this->env->mergeGlobals($context), array_merge($this->blocks, $blocks)); + } + + public function render(array $context) + { + $level = ob_get_level(); + if ($this->env->isDebug()) { + ob_start(); + } else { + ob_start(function () { return ''; }); + } + try { + $this->display($context); + } catch (\Throwable $e) { + while (ob_get_level() > $level) { + ob_end_clean(); + } + + throw $e; + } + + return ob_get_clean(); + } + + protected function displayWithErrorHandling(array $context, array $blocks = []) + { + try { + $this->doDisplay($context, $blocks); + } catch (Error $e) { + if (!$e->getSourceContext()) { + $e->setSourceContext($this->getSourceContext()); + } + + // this is mostly useful for \Twig\Error\LoaderError exceptions + // see \Twig\Error\LoaderError + if (-1 === $e->getTemplateLine()) { + $e->guess(); + } + + throw $e; + } catch (\Exception $e) { + $e = new RuntimeError(sprintf('An exception has been thrown during the rendering of a template ("%s").', $e->getMessage()), -1, $this->getSourceContext(), $e); + $e->guess(); + + throw $e; + } + } + + /** + * Auto-generated method to display the template with the given context. + * + * @param array $context An array of parameters to pass to the template + * @param array $blocks An array of blocks to pass to the template + */ + abstract protected function doDisplay(array $context, array $blocks = []); +} diff --git a/site/plugins/kirby-twig/vendor/twig/twig/src/TemplateWrapper.php b/site/plugins/kirby-twig/vendor/twig/twig/src/TemplateWrapper.php new file mode 100644 index 0000000..c9c6b07 --- /dev/null +++ b/site/plugins/kirby-twig/vendor/twig/twig/src/TemplateWrapper.php @@ -0,0 +1,109 @@ + + */ +final class TemplateWrapper +{ + private $env; + private $template; + + /** + * This method is for internal use only and should never be called + * directly (use Twig\Environment::load() instead). + * + * @internal + */ + public function __construct(Environment $env, Template $template) + { + $this->env = $env; + $this->template = $template; + } + + public function render(array $context = []): string + { + // using func_get_args() allows to not expose the blocks argument + // as it should only be used by internal code + return $this->template->render($context, \func_get_args()[1] ?? []); + } + + public function display(array $context = []) + { + // using func_get_args() allows to not expose the blocks argument + // as it should only be used by internal code + $this->template->display($context, \func_get_args()[1] ?? []); + } + + public function hasBlock(string $name, array $context = []): bool + { + return $this->template->hasBlock($name, $context); + } + + /** + * @return string[] An array of defined template block names + */ + public function getBlockNames(array $context = []): array + { + return $this->template->getBlockNames($context); + } + + public function renderBlock(string $name, array $context = []): string + { + $context = $this->env->mergeGlobals($context); + $level = ob_get_level(); + if ($this->env->isDebug()) { + ob_start(); + } else { + ob_start(function () { return ''; }); + } + try { + $this->template->displayBlock($name, $context); + } catch (\Throwable $e) { + while (ob_get_level() > $level) { + ob_end_clean(); + } + + throw $e; + } + + return ob_get_clean(); + } + + public function displayBlock(string $name, array $context = []) + { + $this->template->displayBlock($name, $this->env->mergeGlobals($context)); + } + + public function getSourceContext(): Source + { + return $this->template->getSourceContext(); + } + + public function getTemplateName(): string + { + return $this->template->getTemplateName(); + } + + /** + * @internal + * + * @return Template + */ + public function unwrap() + { + return $this->template; + } +} diff --git a/site/plugins/kirby-twig/vendor/twig/twig/src/Test/IntegrationTestCase.php b/site/plugins/kirby-twig/vendor/twig/twig/src/Test/IntegrationTestCase.php new file mode 100644 index 0000000..d325874 --- /dev/null +++ b/site/plugins/kirby-twig/vendor/twig/twig/src/Test/IntegrationTestCase.php @@ -0,0 +1,265 @@ + + * @author Karma Dordrak + */ +abstract class IntegrationTestCase extends TestCase +{ + /** + * @return string + */ + abstract protected function getFixturesDir(); + + /** + * @return RuntimeLoaderInterface[] + */ + protected function getRuntimeLoaders() + { + return []; + } + + /** + * @return ExtensionInterface[] + */ + protected function getExtensions() + { + return []; + } + + /** + * @return TwigFilter[] + */ + protected function getTwigFilters() + { + return []; + } + + /** + * @return TwigFunction[] + */ + protected function getTwigFunctions() + { + return []; + } + + /** + * @return TwigTest[] + */ + protected function getTwigTests() + { + return []; + } + + /** + * @dataProvider getTests + */ + public function testIntegration($file, $message, $condition, $templates, $exception, $outputs, $deprecation = '') + { + $this->doIntegrationTest($file, $message, $condition, $templates, $exception, $outputs, $deprecation); + } + + /** + * @dataProvider getLegacyTests + * @group legacy + */ + public function testLegacyIntegration($file, $message, $condition, $templates, $exception, $outputs, $deprecation = '') + { + $this->doIntegrationTest($file, $message, $condition, $templates, $exception, $outputs, $deprecation); + } + + public function getTests($name, $legacyTests = false) + { + $fixturesDir = realpath($this->getFixturesDir()); + $tests = []; + + foreach (new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($fixturesDir), \RecursiveIteratorIterator::LEAVES_ONLY) as $file) { + if (!preg_match('/\.test$/', $file)) { + continue; + } + + if ($legacyTests xor false !== strpos($file->getRealpath(), '.legacy.test')) { + continue; + } + + $test = file_get_contents($file->getRealpath()); + + if (preg_match('/--TEST--\s*(.*?)\s*(?:--CONDITION--\s*(.*))?\s*(?:--DEPRECATION--\s*(.*?))?\s*((?:--TEMPLATE(?:\(.*?\))?--(?:.*?))+)\s*(?:--DATA--\s*(.*))?\s*--EXCEPTION--\s*(.*)/sx', $test, $match)) { + $message = $match[1]; + $condition = $match[2]; + $deprecation = $match[3]; + $templates = self::parseTemplates($match[4]); + $exception = $match[6]; + $outputs = [[null, $match[5], null, '']]; + } elseif (preg_match('/--TEST--\s*(.*?)\s*(?:--CONDITION--\s*(.*))?\s*(?:--DEPRECATION--\s*(.*?))?\s*((?:--TEMPLATE(?:\(.*?\))?--(?:.*?))+)--DATA--.*?--EXPECT--.*/s', $test, $match)) { + $message = $match[1]; + $condition = $match[2]; + $deprecation = $match[3]; + $templates = self::parseTemplates($match[4]); + $exception = false; + preg_match_all('/--DATA--(.*?)(?:--CONFIG--(.*?))?--EXPECT--(.*?)(?=\-\-DATA\-\-|$)/s', $test, $outputs, PREG_SET_ORDER); + } else { + throw new \InvalidArgumentException(sprintf('Test "%s" is not valid.', str_replace($fixturesDir.'/', '', $file))); + } + + $tests[] = [str_replace($fixturesDir.'/', '', $file), $message, $condition, $templates, $exception, $outputs, $deprecation]; + } + + if ($legacyTests && empty($tests)) { + // add a dummy test to avoid a PHPUnit message + return [['not', '-', '', [], '', []]]; + } + + return $tests; + } + + public function getLegacyTests() + { + return $this->getTests('testLegacyIntegration', true); + } + + protected function doIntegrationTest($file, $message, $condition, $templates, $exception, $outputs, $deprecation = '') + { + if (!$outputs) { + $this->markTestSkipped('no tests to run'); + } + + if ($condition) { + eval('$ret = '.$condition.';'); + if (!$ret) { + $this->markTestSkipped($condition); + } + } + + $loader = new ArrayLoader($templates); + + foreach ($outputs as $i => $match) { + $config = array_merge([ + 'cache' => false, + 'strict_variables' => true, + ], $match[2] ? eval($match[2].';') : []); + $twig = new Environment($loader, $config); + $twig->addGlobal('global', 'global'); + foreach ($this->getRuntimeLoaders() as $runtimeLoader) { + $twig->addRuntimeLoader($runtimeLoader); + } + + foreach ($this->getExtensions() as $extension) { + $twig->addExtension($extension); + } + + foreach ($this->getTwigFilters() as $filter) { + $twig->addFilter($filter); + } + + foreach ($this->getTwigTests() as $test) { + $twig->addTest($test); + } + + foreach ($this->getTwigFunctions() as $function) { + $twig->addFunction($function); + } + + // avoid using the same PHP class name for different cases + $p = new \ReflectionProperty($twig, 'templateClassPrefix'); + $p->setAccessible(true); + $p->setValue($twig, '__TwigTemplate_'.hash('sha256', uniqid(mt_rand(), true), false).'_'); + + $deprecations = []; + try { + $prevHandler = set_error_handler(function ($type, $msg, $file, $line, $context = []) use (&$deprecations, &$prevHandler) { + if (E_USER_DEPRECATED === $type) { + $deprecations[] = $msg; + + return true; + } + + return $prevHandler ? $prevHandler($type, $msg, $file, $line, $context) : false; + }); + + $template = $twig->load('index.twig'); + } catch (\Exception $e) { + if (false !== $exception) { + $message = $e->getMessage(); + $this->assertSame(trim($exception), trim(sprintf('%s: %s', \get_class($e), $message))); + $last = substr($message, \strlen($message) - 1); + $this->assertTrue('.' === $last || '?' === $last, 'Exception message must end with a dot or a question mark.'); + + return; + } + + throw new Error(sprintf('%s: %s', \get_class($e), $e->getMessage()), -1, null, $e); + } finally { + restore_error_handler(); + } + + $this->assertSame($deprecation, implode("\n", $deprecations)); + + try { + $output = trim($template->render(eval($match[1].';')), "\n "); + } catch (\Exception $e) { + if (false !== $exception) { + $this->assertSame(trim($exception), trim(sprintf('%s: %s', \get_class($e), $e->getMessage()))); + + return; + } + + $e = new Error(sprintf('%s: %s', \get_class($e), $e->getMessage()), -1, null, $e); + + $output = trim(sprintf('%s: %s', \get_class($e), $e->getMessage())); + } + + if (false !== $exception) { + list($class) = explode(':', $exception); + $constraintClass = class_exists('PHPUnit\Framework\Constraint\Exception') ? 'PHPUnit\Framework\Constraint\Exception' : 'PHPUnit_Framework_Constraint_Exception'; + $this->assertThat(null, new $constraintClass($class)); + } + + $expected = trim($match[3], "\n "); + + if ($expected !== $output) { + printf("Compiled templates that failed on case %d:\n", $i + 1); + + foreach (array_keys($templates) as $name) { + echo "Template: $name\n"; + echo $twig->compile($twig->parse($twig->tokenize($twig->getLoader()->getSourceContext($name)))); + } + } + $this->assertEquals($expected, $output, $message.' (in '.$file.')'); + } + } + + protected static function parseTemplates($test) + { + $templates = []; + preg_match_all('/--TEMPLATE(?:\((.*?)\))?--(.*?)(?=\-\-TEMPLATE|$)/s', $test, $matches, PREG_SET_ORDER); + foreach ($matches as $match) { + $templates[($match[1] ?: 'index.twig')] = $match[2]; + } + + return $templates; + } +} diff --git a/site/plugins/kirby-twig/vendor/twig/twig/src/Test/NodeTestCase.php b/site/plugins/kirby-twig/vendor/twig/twig/src/Test/NodeTestCase.php new file mode 100644 index 0000000..6464024 --- /dev/null +++ b/site/plugins/kirby-twig/vendor/twig/twig/src/Test/NodeTestCase.php @@ -0,0 +1,65 @@ +assertNodeCompilation($source, $node, $environment, $isPattern); + } + + public function assertNodeCompilation($source, Node $node, Environment $environment = null, $isPattern = false) + { + $compiler = $this->getCompiler($environment); + $compiler->compile($node); + + if ($isPattern) { + $this->assertStringMatchesFormat($source, trim($compiler->getSource())); + } else { + $this->assertEquals($source, trim($compiler->getSource())); + } + } + + protected function getCompiler(Environment $environment = null) + { + return new Compiler(null === $environment ? $this->getEnvironment() : $environment); + } + + protected function getEnvironment() + { + return new Environment(new ArrayLoader([])); + } + + protected function getVariableGetter($name, $line = false) + { + $line = $line > 0 ? "// line {$line}\n" : ''; + + return sprintf('%s($context["%s"] ?? null)', $line, $name); + } + + protected function getAttributeGetter() + { + return 'twig_get_attribute($this->env, $this->source, '; + } +} diff --git a/site/plugins/kirby-twig/vendor/twig/twig/src/Token.php b/site/plugins/kirby-twig/vendor/twig/twig/src/Token.php new file mode 100644 index 0000000..7640fc5 --- /dev/null +++ b/site/plugins/kirby-twig/vendor/twig/twig/src/Token.php @@ -0,0 +1,178 @@ + + */ +final class Token +{ + private $value; + private $type; + private $lineno; + + const EOF_TYPE = -1; + const TEXT_TYPE = 0; + const BLOCK_START_TYPE = 1; + const VAR_START_TYPE = 2; + const BLOCK_END_TYPE = 3; + const VAR_END_TYPE = 4; + const NAME_TYPE = 5; + const NUMBER_TYPE = 6; + const STRING_TYPE = 7; + const OPERATOR_TYPE = 8; + const PUNCTUATION_TYPE = 9; + const INTERPOLATION_START_TYPE = 10; + const INTERPOLATION_END_TYPE = 11; + const ARROW_TYPE = 12; + + public function __construct(int $type, $value, int $lineno) + { + $this->type = $type; + $this->value = $value; + $this->lineno = $lineno; + } + + public function __toString() + { + return sprintf('%s(%s)', self::typeToString($this->type, true), $this->value); + } + + /** + * Tests the current token for a type and/or a value. + * + * Parameters may be: + * * just type + * * type and value (or array of possible values) + * * just value (or array of possible values) (NAME_TYPE is used as type) + * + * @param array|string|int $type The type to test + * @param array|string|null $values The token value + */ + public function test($type, $values = null): bool + { + if (null === $values && !\is_int($type)) { + $values = $type; + $type = self::NAME_TYPE; + } + + return ($this->type === $type) && ( + null === $values || + (\is_array($values) && \in_array($this->value, $values)) || + $this->value == $values + ); + } + + public function getLine(): int + { + return $this->lineno; + } + + public function getType(): int + { + return $this->type; + } + + public function getValue() + { + return $this->value; + } + + public static function typeToString(int $type, bool $short = false): string + { + switch ($type) { + case self::EOF_TYPE: + $name = 'EOF_TYPE'; + break; + case self::TEXT_TYPE: + $name = 'TEXT_TYPE'; + break; + case self::BLOCK_START_TYPE: + $name = 'BLOCK_START_TYPE'; + break; + case self::VAR_START_TYPE: + $name = 'VAR_START_TYPE'; + break; + case self::BLOCK_END_TYPE: + $name = 'BLOCK_END_TYPE'; + break; + case self::VAR_END_TYPE: + $name = 'VAR_END_TYPE'; + break; + case self::NAME_TYPE: + $name = 'NAME_TYPE'; + break; + case self::NUMBER_TYPE: + $name = 'NUMBER_TYPE'; + break; + case self::STRING_TYPE: + $name = 'STRING_TYPE'; + break; + case self::OPERATOR_TYPE: + $name = 'OPERATOR_TYPE'; + break; + case self::PUNCTUATION_TYPE: + $name = 'PUNCTUATION_TYPE'; + break; + case self::INTERPOLATION_START_TYPE: + $name = 'INTERPOLATION_START_TYPE'; + break; + case self::INTERPOLATION_END_TYPE: + $name = 'INTERPOLATION_END_TYPE'; + break; + case self::ARROW_TYPE: + $name = 'ARROW_TYPE'; + break; + default: + throw new \LogicException(sprintf('Token of type "%s" does not exist.', $type)); + } + + return $short ? $name : 'Twig\Token::'.$name; + } + + public static function typeToEnglish(int $type): string + { + switch ($type) { + case self::EOF_TYPE: + return 'end of template'; + case self::TEXT_TYPE: + return 'text'; + case self::BLOCK_START_TYPE: + return 'begin of statement block'; + case self::VAR_START_TYPE: + return 'begin of print statement'; + case self::BLOCK_END_TYPE: + return 'end of statement block'; + case self::VAR_END_TYPE: + return 'end of print statement'; + case self::NAME_TYPE: + return 'name'; + case self::NUMBER_TYPE: + return 'number'; + case self::STRING_TYPE: + return 'string'; + case self::OPERATOR_TYPE: + return 'operator'; + case self::PUNCTUATION_TYPE: + return 'punctuation'; + case self::INTERPOLATION_START_TYPE: + return 'begin of string interpolation'; + case self::INTERPOLATION_END_TYPE: + return 'end of string interpolation'; + case self::ARROW_TYPE: + return 'arrow function'; + default: + throw new \LogicException(sprintf('Token of type "%s" does not exist.', $type)); + } + } +} diff --git a/site/plugins/kirby-twig/vendor/twig/twig/src/TokenParser/AbstractTokenParser.php b/site/plugins/kirby-twig/vendor/twig/twig/src/TokenParser/AbstractTokenParser.php new file mode 100644 index 0000000..720ea67 --- /dev/null +++ b/site/plugins/kirby-twig/vendor/twig/twig/src/TokenParser/AbstractTokenParser.php @@ -0,0 +1,32 @@ + + */ +abstract class AbstractTokenParser implements TokenParserInterface +{ + /** + * @var Parser + */ + protected $parser; + + public function setParser(Parser $parser): void + { + $this->parser = $parser; + } +} diff --git a/site/plugins/kirby-twig/vendor/twig/twig/src/TokenParser/ApplyTokenParser.php b/site/plugins/kirby-twig/vendor/twig/twig/src/TokenParser/ApplyTokenParser.php new file mode 100644 index 0000000..4dbf304 --- /dev/null +++ b/site/plugins/kirby-twig/vendor/twig/twig/src/TokenParser/ApplyTokenParser.php @@ -0,0 +1,60 @@ +getLine(); + $name = $this->parser->getVarName(); + + $ref = new TempNameExpression($name, $lineno); + $ref->setAttribute('always_defined', true); + + $filter = $this->parser->getExpressionParser()->parseFilterExpressionRaw($ref, $this->getTag()); + + $this->parser->getStream()->expect(Token::BLOCK_END_TYPE); + $body = $this->parser->subparse([$this, 'decideApplyEnd'], true); + $this->parser->getStream()->expect(Token::BLOCK_END_TYPE); + + return new Node([ + new SetNode(true, $ref, $body, $lineno, $this->getTag()), + new PrintNode($filter, $lineno, $this->getTag()), + ]); + } + + public function decideApplyEnd(Token $token): bool + { + return $token->test('endapply'); + } + + public function getTag(): string + { + return 'apply'; + } +} diff --git a/site/plugins/kirby-twig/vendor/twig/twig/src/TokenParser/AutoEscapeTokenParser.php b/site/plugins/kirby-twig/vendor/twig/twig/src/TokenParser/AutoEscapeTokenParser.php new file mode 100644 index 0000000..b674bea --- /dev/null +++ b/site/plugins/kirby-twig/vendor/twig/twig/src/TokenParser/AutoEscapeTokenParser.php @@ -0,0 +1,58 @@ +getLine(); + $stream = $this->parser->getStream(); + + if ($stream->test(/* Token::BLOCK_END_TYPE */ 3)) { + $value = 'html'; + } else { + $expr = $this->parser->getExpressionParser()->parseExpression(); + if (!$expr instanceof ConstantExpression) { + throw new SyntaxError('An escaping strategy must be a string or false.', $stream->getCurrent()->getLine(), $stream->getSourceContext()); + } + $value = $expr->getAttribute('value'); + } + + $stream->expect(/* Token::BLOCK_END_TYPE */ 3); + $body = $this->parser->subparse([$this, 'decideBlockEnd'], true); + $stream->expect(/* Token::BLOCK_END_TYPE */ 3); + + return new AutoEscapeNode($value, $body, $lineno, $this->getTag()); + } + + public function decideBlockEnd(Token $token): bool + { + return $token->test('endautoescape'); + } + + public function getTag(): string + { + return 'autoescape'; + } +} diff --git a/site/plugins/kirby-twig/vendor/twig/twig/src/TokenParser/BlockTokenParser.php b/site/plugins/kirby-twig/vendor/twig/twig/src/TokenParser/BlockTokenParser.php new file mode 100644 index 0000000..5878131 --- /dev/null +++ b/site/plugins/kirby-twig/vendor/twig/twig/src/TokenParser/BlockTokenParser.php @@ -0,0 +1,78 @@ + + * {% block title %}{% endblock %} - My Webpage + * {% endblock %} + * + * @internal + */ +final class BlockTokenParser extends AbstractTokenParser +{ + public function parse(Token $token): Node + { + $lineno = $token->getLine(); + $stream = $this->parser->getStream(); + $name = $stream->expect(/* Token::NAME_TYPE */ 5)->getValue(); + if ($this->parser->hasBlock($name)) { + throw new SyntaxError(sprintf("The block '%s' has already been defined line %d.", $name, $this->parser->getBlock($name)->getTemplateLine()), $stream->getCurrent()->getLine(), $stream->getSourceContext()); + } + $this->parser->setBlock($name, $block = new BlockNode($name, new Node([]), $lineno)); + $this->parser->pushLocalScope(); + $this->parser->pushBlockStack($name); + + if ($stream->nextIf(/* Token::BLOCK_END_TYPE */ 3)) { + $body = $this->parser->subparse([$this, 'decideBlockEnd'], true); + if ($token = $stream->nextIf(/* Token::NAME_TYPE */ 5)) { + $value = $token->getValue(); + + if ($value != $name) { + throw new SyntaxError(sprintf('Expected endblock for block "%s" (but "%s" given).', $name, $value), $stream->getCurrent()->getLine(), $stream->getSourceContext()); + } + } + } else { + $body = new Node([ + new PrintNode($this->parser->getExpressionParser()->parseExpression(), $lineno), + ]); + } + $stream->expect(/* Token::BLOCK_END_TYPE */ 3); + + $block->setNode('body', $body); + $this->parser->popBlockStack(); + $this->parser->popLocalScope(); + + return new BlockReferenceNode($name, $lineno, $this->getTag()); + } + + public function decideBlockEnd(Token $token): bool + { + return $token->test('endblock'); + } + + public function getTag(): string + { + return 'block'; + } +} diff --git a/site/plugins/kirby-twig/vendor/twig/twig/src/TokenParser/DeprecatedTokenParser.php b/site/plugins/kirby-twig/vendor/twig/twig/src/TokenParser/DeprecatedTokenParser.php new file mode 100644 index 0000000..31416c7 --- /dev/null +++ b/site/plugins/kirby-twig/vendor/twig/twig/src/TokenParser/DeprecatedTokenParser.php @@ -0,0 +1,43 @@ + + * + * @internal + */ +final class DeprecatedTokenParser extends AbstractTokenParser +{ + public function parse(Token $token): Node + { + $expr = $this->parser->getExpressionParser()->parseExpression(); + + $this->parser->getStream()->expect(Token::BLOCK_END_TYPE); + + return new DeprecatedNode($expr, $token->getLine(), $this->getTag()); + } + + public function getTag(): string + { + return 'deprecated'; + } +} diff --git a/site/plugins/kirby-twig/vendor/twig/twig/src/TokenParser/DoTokenParser.php b/site/plugins/kirby-twig/vendor/twig/twig/src/TokenParser/DoTokenParser.php new file mode 100644 index 0000000..32c8f12 --- /dev/null +++ b/site/plugins/kirby-twig/vendor/twig/twig/src/TokenParser/DoTokenParser.php @@ -0,0 +1,38 @@ +parser->getExpressionParser()->parseExpression(); + + $this->parser->getStream()->expect(/* Token::BLOCK_END_TYPE */ 3); + + return new DoNode($expr, $token->getLine(), $this->getTag()); + } + + public function getTag(): string + { + return 'do'; + } +} diff --git a/site/plugins/kirby-twig/vendor/twig/twig/src/TokenParser/EmbedTokenParser.php b/site/plugins/kirby-twig/vendor/twig/twig/src/TokenParser/EmbedTokenParser.php new file mode 100644 index 0000000..64b4f29 --- /dev/null +++ b/site/plugins/kirby-twig/vendor/twig/twig/src/TokenParser/EmbedTokenParser.php @@ -0,0 +1,73 @@ +parser->getStream(); + + $parent = $this->parser->getExpressionParser()->parseExpression(); + + list($variables, $only, $ignoreMissing) = $this->parseArguments(); + + $parentToken = $fakeParentToken = new Token(/* Token::STRING_TYPE */ 7, '__parent__', $token->getLine()); + if ($parent instanceof ConstantExpression) { + $parentToken = new Token(/* Token::STRING_TYPE */ 7, $parent->getAttribute('value'), $token->getLine()); + } elseif ($parent instanceof NameExpression) { + $parentToken = new Token(/* Token::NAME_TYPE */ 5, $parent->getAttribute('name'), $token->getLine()); + } + + // inject a fake parent to make the parent() function work + $stream->injectTokens([ + new Token(/* Token::BLOCK_START_TYPE */ 1, '', $token->getLine()), + new Token(/* Token::NAME_TYPE */ 5, 'extends', $token->getLine()), + $parentToken, + new Token(/* Token::BLOCK_END_TYPE */ 3, '', $token->getLine()), + ]); + + $module = $this->parser->parse($stream, [$this, 'decideBlockEnd'], true); + + // override the parent with the correct one + if ($fakeParentToken === $parentToken) { + $module->setNode('parent', $parent); + } + + $this->parser->embedTemplate($module); + + $stream->expect(/* Token::BLOCK_END_TYPE */ 3); + + return new EmbedNode($module->getTemplateName(), $module->getAttribute('index'), $variables, $only, $ignoreMissing, $token->getLine(), $this->getTag()); + } + + public function decideBlockEnd(Token $token): bool + { + return $token->test('endembed'); + } + + public function getTag(): string + { + return 'embed'; + } +} diff --git a/site/plugins/kirby-twig/vendor/twig/twig/src/TokenParser/ExtendsTokenParser.php b/site/plugins/kirby-twig/vendor/twig/twig/src/TokenParser/ExtendsTokenParser.php new file mode 100644 index 0000000..0ca46dd --- /dev/null +++ b/site/plugins/kirby-twig/vendor/twig/twig/src/TokenParser/ExtendsTokenParser.php @@ -0,0 +1,52 @@ +parser->getStream(); + + if ($this->parser->peekBlockStack()) { + throw new SyntaxError('Cannot use "extend" in a block.', $token->getLine(), $stream->getSourceContext()); + } elseif (!$this->parser->isMainScope()) { + throw new SyntaxError('Cannot use "extend" in a macro.', $token->getLine(), $stream->getSourceContext()); + } + + if (null !== $this->parser->getParent()) { + throw new SyntaxError('Multiple extends tags are forbidden.', $token->getLine(), $stream->getSourceContext()); + } + $this->parser->setParent($this->parser->getExpressionParser()->parseExpression()); + + $stream->expect(/* Token::BLOCK_END_TYPE */ 3); + + return new Node(); + } + + public function getTag(): string + { + return 'extends'; + } +} diff --git a/site/plugins/kirby-twig/vendor/twig/twig/src/TokenParser/FlushTokenParser.php b/site/plugins/kirby-twig/vendor/twig/twig/src/TokenParser/FlushTokenParser.php new file mode 100644 index 0000000..02c74aa --- /dev/null +++ b/site/plugins/kirby-twig/vendor/twig/twig/src/TokenParser/FlushTokenParser.php @@ -0,0 +1,38 @@ +parser->getStream()->expect(/* Token::BLOCK_END_TYPE */ 3); + + return new FlushNode($token->getLine(), $this->getTag()); + } + + public function getTag(): string + { + return 'flush'; + } +} diff --git a/site/plugins/kirby-twig/vendor/twig/twig/src/TokenParser/ForTokenParser.php b/site/plugins/kirby-twig/vendor/twig/twig/src/TokenParser/ForTokenParser.php new file mode 100644 index 0000000..b6d3b4e --- /dev/null +++ b/site/plugins/kirby-twig/vendor/twig/twig/src/TokenParser/ForTokenParser.php @@ -0,0 +1,79 @@ + + * {% for user in users %} + *
  • {{ user.username|e }}
  • + * {% endfor %} + * + * + * @internal + */ +final class ForTokenParser extends AbstractTokenParser +{ + public function parse(Token $token): Node + { + $lineno = $token->getLine(); + $stream = $this->parser->getStream(); + $targets = $this->parser->getExpressionParser()->parseAssignmentExpression(); + $stream->expect(/* Token::OPERATOR_TYPE */ 8, 'in'); + $seq = $this->parser->getExpressionParser()->parseExpression(); + + $stream->expect(/* Token::BLOCK_END_TYPE */ 3); + $body = $this->parser->subparse([$this, 'decideForFork']); + if ('else' == $stream->next()->getValue()) { + $stream->expect(/* Token::BLOCK_END_TYPE */ 3); + $else = $this->parser->subparse([$this, 'decideForEnd'], true); + } else { + $else = null; + } + $stream->expect(/* Token::BLOCK_END_TYPE */ 3); + + if (\count($targets) > 1) { + $keyTarget = $targets->getNode(0); + $keyTarget = new AssignNameExpression($keyTarget->getAttribute('name'), $keyTarget->getTemplateLine()); + $valueTarget = $targets->getNode(1); + $valueTarget = new AssignNameExpression($valueTarget->getAttribute('name'), $valueTarget->getTemplateLine()); + } else { + $keyTarget = new AssignNameExpression('_key', $lineno); + $valueTarget = $targets->getNode(0); + $valueTarget = new AssignNameExpression($valueTarget->getAttribute('name'), $valueTarget->getTemplateLine()); + } + + return new ForNode($keyTarget, $valueTarget, $seq, null, $body, $else, $lineno, $this->getTag()); + } + + public function decideForFork(Token $token): bool + { + return $token->test(['else', 'endfor']); + } + + public function decideForEnd(Token $token): bool + { + return $token->test('endfor'); + } + + public function getTag(): string + { + return 'for'; + } +} diff --git a/site/plugins/kirby-twig/vendor/twig/twig/src/TokenParser/FromTokenParser.php b/site/plugins/kirby-twig/vendor/twig/twig/src/TokenParser/FromTokenParser.php new file mode 100644 index 0000000..35098c2 --- /dev/null +++ b/site/plugins/kirby-twig/vendor/twig/twig/src/TokenParser/FromTokenParser.php @@ -0,0 +1,66 @@ +parser->getExpressionParser()->parseExpression(); + $stream = $this->parser->getStream(); + $stream->expect(/* Token::NAME_TYPE */ 5, 'import'); + + $targets = []; + do { + $name = $stream->expect(/* Token::NAME_TYPE */ 5)->getValue(); + + $alias = $name; + if ($stream->nextIf('as')) { + $alias = $stream->expect(/* Token::NAME_TYPE */ 5)->getValue(); + } + + $targets[$name] = $alias; + + if (!$stream->nextIf(/* Token::PUNCTUATION_TYPE */ 9, ',')) { + break; + } + } while (true); + + $stream->expect(/* Token::BLOCK_END_TYPE */ 3); + + $var = new AssignNameExpression($this->parser->getVarName(), $token->getLine()); + $node = new ImportNode($macro, $var, $token->getLine(), $this->getTag(), $this->parser->isMainScope()); + + foreach ($targets as $name => $alias) { + $this->parser->addImportedSymbol('function', $alias, 'macro_'.$name, $var); + } + + return $node; + } + + public function getTag(): string + { + return 'from'; + } +} diff --git a/site/plugins/kirby-twig/vendor/twig/twig/src/TokenParser/IfTokenParser.php b/site/plugins/kirby-twig/vendor/twig/twig/src/TokenParser/IfTokenParser.php new file mode 100644 index 0000000..c0fe6df --- /dev/null +++ b/site/plugins/kirby-twig/vendor/twig/twig/src/TokenParser/IfTokenParser.php @@ -0,0 +1,89 @@ + + * {% for user in users %} + *
  • {{ user.username|e }}
  • + * {% endfor %} + * + * {% endif %} + * + * @internal + */ +final class IfTokenParser extends AbstractTokenParser +{ + public function parse(Token $token): Node + { + $lineno = $token->getLine(); + $expr = $this->parser->getExpressionParser()->parseExpression(); + $stream = $this->parser->getStream(); + $stream->expect(/* Token::BLOCK_END_TYPE */ 3); + $body = $this->parser->subparse([$this, 'decideIfFork']); + $tests = [$expr, $body]; + $else = null; + + $end = false; + while (!$end) { + switch ($stream->next()->getValue()) { + case 'else': + $stream->expect(/* Token::BLOCK_END_TYPE */ 3); + $else = $this->parser->subparse([$this, 'decideIfEnd']); + break; + + case 'elseif': + $expr = $this->parser->getExpressionParser()->parseExpression(); + $stream->expect(/* Token::BLOCK_END_TYPE */ 3); + $body = $this->parser->subparse([$this, 'decideIfFork']); + $tests[] = $expr; + $tests[] = $body; + break; + + case 'endif': + $end = true; + break; + + default: + throw new SyntaxError(sprintf('Unexpected end of template. Twig was looking for the following tags "else", "elseif", or "endif" to close the "if" block started at line %d).', $lineno), $stream->getCurrent()->getLine(), $stream->getSourceContext()); + } + } + + $stream->expect(/* Token::BLOCK_END_TYPE */ 3); + + return new IfNode(new Node($tests), $else, $lineno, $this->getTag()); + } + + public function decideIfFork(Token $token): bool + { + return $token->test(['elseif', 'else', 'endif']); + } + + public function decideIfEnd(Token $token): bool + { + return $token->test(['endif']); + } + + public function getTag(): string + { + return 'if'; + } +} diff --git a/site/plugins/kirby-twig/vendor/twig/twig/src/TokenParser/ImportTokenParser.php b/site/plugins/kirby-twig/vendor/twig/twig/src/TokenParser/ImportTokenParser.php new file mode 100644 index 0000000..44cb4da --- /dev/null +++ b/site/plugins/kirby-twig/vendor/twig/twig/src/TokenParser/ImportTokenParser.php @@ -0,0 +1,44 @@ +parser->getExpressionParser()->parseExpression(); + $this->parser->getStream()->expect(/* Token::NAME_TYPE */ 5, 'as'); + $var = new AssignNameExpression($this->parser->getStream()->expect(/* Token::NAME_TYPE */ 5)->getValue(), $token->getLine()); + $this->parser->getStream()->expect(/* Token::BLOCK_END_TYPE */ 3); + + $this->parser->addImportedSymbol('template', $var->getAttribute('name')); + + return new ImportNode($macro, $var, $token->getLine(), $this->getTag(), $this->parser->isMainScope()); + } + + public function getTag(): string + { + return 'import'; + } +} diff --git a/site/plugins/kirby-twig/vendor/twig/twig/src/TokenParser/IncludeTokenParser.php b/site/plugins/kirby-twig/vendor/twig/twig/src/TokenParser/IncludeTokenParser.php new file mode 100644 index 0000000..28beb8a --- /dev/null +++ b/site/plugins/kirby-twig/vendor/twig/twig/src/TokenParser/IncludeTokenParser.php @@ -0,0 +1,69 @@ +parser->getExpressionParser()->parseExpression(); + + list($variables, $only, $ignoreMissing) = $this->parseArguments(); + + return new IncludeNode($expr, $variables, $only, $ignoreMissing, $token->getLine(), $this->getTag()); + } + + protected function parseArguments() + { + $stream = $this->parser->getStream(); + + $ignoreMissing = false; + if ($stream->nextIf(/* Token::NAME_TYPE */ 5, 'ignore')) { + $stream->expect(/* Token::NAME_TYPE */ 5, 'missing'); + + $ignoreMissing = true; + } + + $variables = null; + if ($stream->nextIf(/* Token::NAME_TYPE */ 5, 'with')) { + $variables = $this->parser->getExpressionParser()->parseExpression(); + } + + $only = false; + if ($stream->nextIf(/* Token::NAME_TYPE */ 5, 'only')) { + $only = true; + } + + $stream->expect(/* Token::BLOCK_END_TYPE */ 3); + + return [$variables, $only, $ignoreMissing]; + } + + public function getTag(): string + { + return 'include'; + } +} diff --git a/site/plugins/kirby-twig/vendor/twig/twig/src/TokenParser/MacroTokenParser.php b/site/plugins/kirby-twig/vendor/twig/twig/src/TokenParser/MacroTokenParser.php new file mode 100644 index 0000000..f584927 --- /dev/null +++ b/site/plugins/kirby-twig/vendor/twig/twig/src/TokenParser/MacroTokenParser.php @@ -0,0 +1,66 @@ + + * {% endmacro %} + * + * @internal + */ +final class MacroTokenParser extends AbstractTokenParser +{ + public function parse(Token $token): Node + { + $lineno = $token->getLine(); + $stream = $this->parser->getStream(); + $name = $stream->expect(/* Token::NAME_TYPE */ 5)->getValue(); + + $arguments = $this->parser->getExpressionParser()->parseArguments(true, true); + + $stream->expect(/* Token::BLOCK_END_TYPE */ 3); + $this->parser->pushLocalScope(); + $body = $this->parser->subparse([$this, 'decideBlockEnd'], true); + if ($token = $stream->nextIf(/* Token::NAME_TYPE */ 5)) { + $value = $token->getValue(); + + if ($value != $name) { + throw new SyntaxError(sprintf('Expected endmacro for macro "%s" (but "%s" given).', $name, $value), $stream->getCurrent()->getLine(), $stream->getSourceContext()); + } + } + $this->parser->popLocalScope(); + $stream->expect(/* Token::BLOCK_END_TYPE */ 3); + + $this->parser->setMacro($name, new MacroNode($name, new BodyNode([$body]), $arguments, $lineno, $this->getTag())); + + return new Node(); + } + + public function decideBlockEnd(Token $token): bool + { + return $token->test('endmacro'); + } + + public function getTag(): string + { + return 'macro'; + } +} diff --git a/site/plugins/kirby-twig/vendor/twig/twig/src/TokenParser/SandboxTokenParser.php b/site/plugins/kirby-twig/vendor/twig/twig/src/TokenParser/SandboxTokenParser.php new file mode 100644 index 0000000..c919556 --- /dev/null +++ b/site/plugins/kirby-twig/vendor/twig/twig/src/TokenParser/SandboxTokenParser.php @@ -0,0 +1,66 @@ +parser->getStream(); + $stream->expect(/* Token::BLOCK_END_TYPE */ 3); + $body = $this->parser->subparse([$this, 'decideBlockEnd'], true); + $stream->expect(/* Token::BLOCK_END_TYPE */ 3); + + // in a sandbox tag, only include tags are allowed + if (!$body instanceof IncludeNode) { + foreach ($body as $node) { + if ($node instanceof TextNode && ctype_space($node->getAttribute('data'))) { + continue; + } + + if (!$node instanceof IncludeNode) { + throw new SyntaxError('Only "include" tags are allowed within a "sandbox" section.', $node->getTemplateLine(), $stream->getSourceContext()); + } + } + } + + return new SandboxNode($body, $token->getLine(), $this->getTag()); + } + + public function decideBlockEnd(Token $token): bool + { + return $token->test('endsandbox'); + } + + public function getTag(): string + { + return 'sandbox'; + } +} diff --git a/site/plugins/kirby-twig/vendor/twig/twig/src/TokenParser/SetTokenParser.php b/site/plugins/kirby-twig/vendor/twig/twig/src/TokenParser/SetTokenParser.php new file mode 100644 index 0000000..2fbdfe0 --- /dev/null +++ b/site/plugins/kirby-twig/vendor/twig/twig/src/TokenParser/SetTokenParser.php @@ -0,0 +1,73 @@ +getLine(); + $stream = $this->parser->getStream(); + $names = $this->parser->getExpressionParser()->parseAssignmentExpression(); + + $capture = false; + if ($stream->nextIf(/* Token::OPERATOR_TYPE */ 8, '=')) { + $values = $this->parser->getExpressionParser()->parseMultitargetExpression(); + + $stream->expect(/* Token::BLOCK_END_TYPE */ 3); + + if (\count($names) !== \count($values)) { + throw new SyntaxError('When using set, you must have the same number of variables and assignments.', $stream->getCurrent()->getLine(), $stream->getSourceContext()); + } + } else { + $capture = true; + + if (\count($names) > 1) { + throw new SyntaxError('When using set with a block, you cannot have a multi-target.', $stream->getCurrent()->getLine(), $stream->getSourceContext()); + } + + $stream->expect(/* Token::BLOCK_END_TYPE */ 3); + + $values = $this->parser->subparse([$this, 'decideBlockEnd'], true); + $stream->expect(/* Token::BLOCK_END_TYPE */ 3); + } + + return new SetNode($capture, $names, $values, $lineno, $this->getTag()); + } + + public function decideBlockEnd(Token $token): bool + { + return $token->test('endset'); + } + + public function getTag(): string + { + return 'set'; + } +} diff --git a/site/plugins/kirby-twig/vendor/twig/twig/src/TokenParser/TokenParserInterface.php b/site/plugins/kirby-twig/vendor/twig/twig/src/TokenParser/TokenParserInterface.php new file mode 100644 index 0000000..bb8db3e --- /dev/null +++ b/site/plugins/kirby-twig/vendor/twig/twig/src/TokenParser/TokenParserInterface.php @@ -0,0 +1,46 @@ + + */ +interface TokenParserInterface +{ + /** + * Sets the parser associated with this token parser. + */ + public function setParser(Parser $parser): void; + + /** + * Parses a token and returns a node. + * + * @return Node + * + * @throws SyntaxError + */ + public function parse(Token $token); + + /** + * Gets the tag name associated with this token parser. + * + * @return string + */ + public function getTag(); +} diff --git a/site/plugins/kirby-twig/vendor/twig/twig/src/TokenParser/UseTokenParser.php b/site/plugins/kirby-twig/vendor/twig/twig/src/TokenParser/UseTokenParser.php new file mode 100644 index 0000000..d0a2de4 --- /dev/null +++ b/site/plugins/kirby-twig/vendor/twig/twig/src/TokenParser/UseTokenParser.php @@ -0,0 +1,73 @@ +parser->getExpressionParser()->parseExpression(); + $stream = $this->parser->getStream(); + + if (!$template instanceof ConstantExpression) { + throw new SyntaxError('The template references in a "use" statement must be a string.', $stream->getCurrent()->getLine(), $stream->getSourceContext()); + } + + $targets = []; + if ($stream->nextIf('with')) { + do { + $name = $stream->expect(/* Token::NAME_TYPE */ 5)->getValue(); + + $alias = $name; + if ($stream->nextIf('as')) { + $alias = $stream->expect(/* Token::NAME_TYPE */ 5)->getValue(); + } + + $targets[$name] = new ConstantExpression($alias, -1); + + if (!$stream->nextIf(/* Token::PUNCTUATION_TYPE */ 9, ',')) { + break; + } + } while (true); + } + + $stream->expect(/* Token::BLOCK_END_TYPE */ 3); + + $this->parser->addTrait(new Node(['template' => $template, 'targets' => new Node($targets)])); + + return new Node(); + } + + public function getTag(): string + { + return 'use'; + } +} diff --git a/site/plugins/kirby-twig/vendor/twig/twig/src/TokenParser/WithTokenParser.php b/site/plugins/kirby-twig/vendor/twig/twig/src/TokenParser/WithTokenParser.php new file mode 100644 index 0000000..7d8cbe2 --- /dev/null +++ b/site/plugins/kirby-twig/vendor/twig/twig/src/TokenParser/WithTokenParser.php @@ -0,0 +1,56 @@ + + * + * @internal + */ +final class WithTokenParser extends AbstractTokenParser +{ + public function parse(Token $token): Node + { + $stream = $this->parser->getStream(); + + $variables = null; + $only = false; + if (!$stream->test(/* Token::BLOCK_END_TYPE */ 3)) { + $variables = $this->parser->getExpressionParser()->parseExpression(); + $only = (bool) $stream->nextIf(/* Token::NAME_TYPE */ 5, 'only'); + } + + $stream->expect(/* Token::BLOCK_END_TYPE */ 3); + + $body = $this->parser->subparse([$this, 'decideWithEnd'], true); + + $stream->expect(/* Token::BLOCK_END_TYPE */ 3); + + return new WithNode($body, $variables, $only, $token->getLine(), $this->getTag()); + } + + public function decideWithEnd(Token $token): bool + { + return $token->test('endwith'); + } + + public function getTag(): string + { + return 'with'; + } +} diff --git a/site/plugins/kirby-twig/vendor/twig/twig/src/TokenStream.php b/site/plugins/kirby-twig/vendor/twig/twig/src/TokenStream.php new file mode 100644 index 0000000..1eac11a --- /dev/null +++ b/site/plugins/kirby-twig/vendor/twig/twig/src/TokenStream.php @@ -0,0 +1,132 @@ + + */ +final class TokenStream +{ + private $tokens; + private $current = 0; + private $source; + + public function __construct(array $tokens, Source $source = null) + { + $this->tokens = $tokens; + $this->source = $source ?: new Source('', ''); + } + + public function __toString() + { + return implode("\n", $this->tokens); + } + + public function injectTokens(array $tokens) + { + $this->tokens = array_merge(\array_slice($this->tokens, 0, $this->current), $tokens, \array_slice($this->tokens, $this->current)); + } + + /** + * Sets the pointer to the next token and returns the old one. + */ + public function next(): Token + { + if (!isset($this->tokens[++$this->current])) { + throw new SyntaxError('Unexpected end of template.', $this->tokens[$this->current - 1]->getLine(), $this->source); + } + + return $this->tokens[$this->current - 1]; + } + + /** + * Tests a token, sets the pointer to the next one and returns it or throws a syntax error. + * + * @return Token|null The next token if the condition is true, null otherwise + */ + public function nextIf($primary, $secondary = null) + { + if ($this->tokens[$this->current]->test($primary, $secondary)) { + return $this->next(); + } + } + + /** + * Tests a token and returns it or throws a syntax error. + */ + public function expect($type, $value = null, string $message = null): Token + { + $token = $this->tokens[$this->current]; + if (!$token->test($type, $value)) { + $line = $token->getLine(); + throw new SyntaxError(sprintf('%sUnexpected token "%s"%s ("%s" expected%s).', + $message ? $message.'. ' : '', + Token::typeToEnglish($token->getType()), + $token->getValue() ? sprintf(' of value "%s"', $token->getValue()) : '', + Token::typeToEnglish($type), $value ? sprintf(' with value "%s"', $value) : ''), + $line, + $this->source + ); + } + $this->next(); + + return $token; + } + + /** + * Looks at the next token. + */ + public function look(int $number = 1): Token + { + if (!isset($this->tokens[$this->current + $number])) { + throw new SyntaxError('Unexpected end of template.', $this->tokens[$this->current + $number - 1]->getLine(), $this->source); + } + + return $this->tokens[$this->current + $number]; + } + + /** + * Tests the current token. + */ + public function test($primary, $secondary = null): bool + { + return $this->tokens[$this->current]->test($primary, $secondary); + } + + /** + * Checks if end of stream was reached. + */ + public function isEOF(): bool + { + return /* Token::EOF_TYPE */ -1 === $this->tokens[$this->current]->getType(); + } + + public function getCurrent(): Token + { + return $this->tokens[$this->current]; + } + + /** + * Gets the source associated with this stream. + * + * @internal + */ + public function getSourceContext(): Source + { + return $this->source; + } +} diff --git a/site/plugins/kirby-twig/vendor/twig/twig/src/TwigFilter.php b/site/plugins/kirby-twig/vendor/twig/twig/src/TwigFilter.php new file mode 100644 index 0000000..94e5f9b --- /dev/null +++ b/site/plugins/kirby-twig/vendor/twig/twig/src/TwigFilter.php @@ -0,0 +1,134 @@ + + * + * @see https://twig.symfony.com/doc/templates.html#filters + */ +final class TwigFilter +{ + private $name; + private $callable; + private $options; + private $arguments = []; + + /** + * @param callable|null $callable A callable implementing the filter. If null, you need to overwrite the "node_class" option to customize compilation. + */ + public function __construct(string $name, $callable = null, array $options = []) + { + $this->name = $name; + $this->callable = $callable; + $this->options = array_merge([ + 'needs_environment' => false, + 'needs_context' => false, + 'is_variadic' => false, + 'is_safe' => null, + 'is_safe_callback' => null, + 'pre_escape' => null, + 'preserves_safety' => null, + 'node_class' => FilterExpression::class, + 'deprecated' => false, + 'alternative' => null, + ], $options); + } + + public function getName(): string + { + return $this->name; + } + + /** + * Returns the callable to execute for this filter. + * + * @return callable|null + */ + public function getCallable() + { + return $this->callable; + } + + public function getNodeClass(): string + { + return $this->options['node_class']; + } + + public function setArguments(array $arguments): void + { + $this->arguments = $arguments; + } + + public function getArguments(): array + { + return $this->arguments; + } + + public function needsEnvironment(): bool + { + return $this->options['needs_environment']; + } + + public function needsContext(): bool + { + return $this->options['needs_context']; + } + + public function getSafe(Node $filterArgs): ?array + { + if (null !== $this->options['is_safe']) { + return $this->options['is_safe']; + } + + if (null !== $this->options['is_safe_callback']) { + return $this->options['is_safe_callback']($filterArgs); + } + + return null; + } + + public function getPreservesSafety(): ?array + { + return $this->options['preserves_safety']; + } + + public function getPreEscape(): ?string + { + return $this->options['pre_escape']; + } + + public function isVariadic(): bool + { + return $this->options['is_variadic']; + } + + public function isDeprecated(): bool + { + return (bool) $this->options['deprecated']; + } + + public function getDeprecatedVersion(): string + { + return \is_bool($this->options['deprecated']) ? '' : $this->options['deprecated']; + } + + public function getAlternative(): ?string + { + return $this->options['alternative']; + } +} diff --git a/site/plugins/kirby-twig/vendor/twig/twig/src/TwigFunction.php b/site/plugins/kirby-twig/vendor/twig/twig/src/TwigFunction.php new file mode 100644 index 0000000..494d45b --- /dev/null +++ b/site/plugins/kirby-twig/vendor/twig/twig/src/TwigFunction.php @@ -0,0 +1,122 @@ + + * + * @see https://twig.symfony.com/doc/templates.html#functions + */ +final class TwigFunction +{ + private $name; + private $callable; + private $options; + private $arguments = []; + + /** + * @param callable|null $callable A callable implementing the function. If null, you need to overwrite the "node_class" option to customize compilation. + */ + public function __construct(string $name, $callable = null, array $options = []) + { + $this->name = $name; + $this->callable = $callable; + $this->options = array_merge([ + 'needs_environment' => false, + 'needs_context' => false, + 'is_variadic' => false, + 'is_safe' => null, + 'is_safe_callback' => null, + 'node_class' => FunctionExpression::class, + 'deprecated' => false, + 'alternative' => null, + ], $options); + } + + public function getName(): string + { + return $this->name; + } + + /** + * Returns the callable to execute for this function. + * + * @return callable|null + */ + public function getCallable() + { + return $this->callable; + } + + public function getNodeClass(): string + { + return $this->options['node_class']; + } + + public function setArguments(array $arguments): void + { + $this->arguments = $arguments; + } + + public function getArguments(): array + { + return $this->arguments; + } + + public function needsEnvironment(): bool + { + return $this->options['needs_environment']; + } + + public function needsContext(): bool + { + return $this->options['needs_context']; + } + + public function getSafe(Node $functionArgs): ?array + { + if (null !== $this->options['is_safe']) { + return $this->options['is_safe']; + } + + if (null !== $this->options['is_safe_callback']) { + return $this->options['is_safe_callback']($functionArgs); + } + + return []; + } + + public function isVariadic(): bool + { + return (bool) $this->options['is_variadic']; + } + + public function isDeprecated(): bool + { + return (bool) $this->options['deprecated']; + } + + public function getDeprecatedVersion(): string + { + return \is_bool($this->options['deprecated']) ? '' : $this->options['deprecated']; + } + + public function getAlternative(): ?string + { + return $this->options['alternative']; + } +} diff --git a/site/plugins/kirby-twig/vendor/twig/twig/src/TwigTest.php b/site/plugins/kirby-twig/vendor/twig/twig/src/TwigTest.php new file mode 100644 index 0000000..4c18632 --- /dev/null +++ b/site/plugins/kirby-twig/vendor/twig/twig/src/TwigTest.php @@ -0,0 +1,100 @@ + + * + * @see https://twig.symfony.com/doc/templates.html#test-operator + */ +final class TwigTest +{ + private $name; + private $callable; + private $options; + private $arguments = []; + + /** + * @param callable|null $callable A callable implementing the test. If null, you need to overwrite the "node_class" option to customize compilation. + */ + public function __construct(string $name, $callable = null, array $options = []) + { + $this->name = $name; + $this->callable = $callable; + $this->options = array_merge([ + 'is_variadic' => false, + 'node_class' => TestExpression::class, + 'deprecated' => false, + 'alternative' => null, + 'one_mandatory_argument' => false, + ], $options); + } + + public function getName(): string + { + return $this->name; + } + + /** + * Returns the callable to execute for this test. + * + * @return callable|null + */ + public function getCallable() + { + return $this->callable; + } + + public function getNodeClass(): string + { + return $this->options['node_class']; + } + + public function setArguments(array $arguments): void + { + $this->arguments = $arguments; + } + + public function getArguments(): array + { + return $this->arguments; + } + + public function isVariadic(): bool + { + return (bool) $this->options['is_variadic']; + } + + public function isDeprecated(): bool + { + return (bool) $this->options['deprecated']; + } + + public function getDeprecatedVersion(): string + { + return \is_bool($this->options['deprecated']) ? '' : $this->options['deprecated']; + } + + public function getAlternative(): ?string + { + return $this->options['alternative']; + } + + public function hasOneMandatoryArgument(): bool + { + return (bool) $this->options['one_mandatory_argument']; + } +} diff --git a/site/plugins/kirby-twig/vendor/twig/twig/src/Util/DeprecationCollector.php b/site/plugins/kirby-twig/vendor/twig/twig/src/Util/DeprecationCollector.php new file mode 100644 index 0000000..51345e0 --- /dev/null +++ b/site/plugins/kirby-twig/vendor/twig/twig/src/Util/DeprecationCollector.php @@ -0,0 +1,77 @@ + + */ +final class DeprecationCollector +{ + private $twig; + + public function __construct(Environment $twig) + { + $this->twig = $twig; + } + + /** + * Returns deprecations for templates contained in a directory. + * + * @param string $dir A directory where templates are stored + * @param string $ext Limit the loaded templates by extension + * + * @return array An array of deprecations + */ + public function collectDir(string $dir, string $ext = '.twig'): array + { + $iterator = new \RegexIterator( + new \RecursiveIteratorIterator( + new \RecursiveDirectoryIterator($dir), \RecursiveIteratorIterator::LEAVES_ONLY + ), '{'.preg_quote($ext).'$}' + ); + + return $this->collect(new TemplateDirIterator($iterator)); + } + + /** + * Returns deprecations for passed templates. + * + * @param \Traversable $iterator An iterator of templates (where keys are template names and values the contents of the template) + * + * @return array An array of deprecations + */ + public function collect(\Traversable $iterator): array + { + $deprecations = []; + set_error_handler(function ($type, $msg) use (&$deprecations) { + if (E_USER_DEPRECATED === $type) { + $deprecations[] = $msg; + } + }); + + foreach ($iterator as $name => $contents) { + try { + $this->twig->parse($this->twig->tokenize(new Source($contents, $name))); + } catch (SyntaxError $e) { + // ignore templates containing syntax errors + } + } + + restore_error_handler(); + + return $deprecations; + } +} diff --git a/site/plugins/kirby-twig/vendor/twig/twig/src/Util/TemplateDirIterator.php b/site/plugins/kirby-twig/vendor/twig/twig/src/Util/TemplateDirIterator.php new file mode 100644 index 0000000..c7339fd --- /dev/null +++ b/site/plugins/kirby-twig/vendor/twig/twig/src/Util/TemplateDirIterator.php @@ -0,0 +1,28 @@ + + */ +class TemplateDirIterator extends \IteratorIterator +{ + public function current() + { + return file_get_contents(parent::current()); + } + + public function key() + { + return (string) parent::key(); + } +} diff --git a/site/sessions/index.html b/site/sessions/index.html new file mode 100644 index 0000000..e69de29 diff --git a/site/snippets/index.html b/site/snippets/index.html new file mode 100644 index 0000000..e69de29 diff --git a/site/templates/default.php b/site/templates/default.php new file mode 100644 index 0000000..74e38ae --- /dev/null +++ b/site/templates/default.php @@ -0,0 +1 @@ +

    title() ?>