From 14565bd18e57bc361341b5316e7957423ae20777 Mon Sep 17 00:00:00 2001 From: trendschau Date: Mon, 27 Feb 2023 12:06:44 +0100 Subject: [PATCH] Typemill Version 2 first milestone system area --- .gitignore | 1 + system/author/css/a11y-dark.min.css | 7 + system/author/js/highlight.min.js | 709 ++++++++++++++++++ .../Controllers/ControllerApiImage.php | 335 +++++++++ .../Controllers/ControllerApiMediaOld.php | 663 ++++++++++++++++ .../ControllerApiSystemLicense.php | 59 ++ .../Controllers/ControllerWebAuth.php | 117 +++ .../typemill/Middleware/ApiAuthentication.php | 144 ++++ .../typemill/Middleware/ApiAuthorization.php | 39 + .../typemill/Middleware/WebAuthorization.php | 34 + .../Middleware/WebRedirectIfAuthenticated.php | 41 + .../WebRedirectIfUnauthenticated.php | 51 ++ system/typemill/Models/License.php | 258 +++++++ system/typemill/Models/ProcessAssets.php | 243 ++++++ system/typemill/Models/ProcessFile.php | 40 + system/typemill/Models/ProcessImage.php | 585 +++++++++++++++ system/typemill/Static/License.php | 229 ++++++ system/typemill/Static/Slug.php | 45 ++ system/typemill/author/css/a11y-dark.min.css | 7 + system/typemill/author/js/highlight.min.js | 709 ++++++++++++++++++ system/typemill/author/js/vue-license.js | 131 ++++ system/typemill/author/js/vue-user.js | 136 ++++ system/typemill/author/js/vue-usernew.js | 126 ++++ system/typemill/author/system/license.twig | 27 + system/typemill/author/system/user.twig | 21 + system/typemill/author/system/usernew.twig | 21 + system/typemill/settings/license.yaml | 15 + 27 files changed, 4793 insertions(+) create mode 100644 system/author/css/a11y-dark.min.css create mode 100644 system/author/js/highlight.min.js create mode 100644 system/typemill/Controllers/ControllerApiImage.php create mode 100644 system/typemill/Controllers/ControllerApiMediaOld.php create mode 100644 system/typemill/Controllers/ControllerApiSystemLicense.php create mode 100644 system/typemill/Controllers/ControllerWebAuth.php create mode 100644 system/typemill/Middleware/ApiAuthentication.php create mode 100644 system/typemill/Middleware/ApiAuthorization.php create mode 100644 system/typemill/Middleware/WebAuthorization.php create mode 100644 system/typemill/Middleware/WebRedirectIfAuthenticated.php create mode 100644 system/typemill/Middleware/WebRedirectIfUnauthenticated.php create mode 100644 system/typemill/Models/License.php create mode 100644 system/typemill/Models/ProcessAssets.php create mode 100644 system/typemill/Models/ProcessFile.php create mode 100644 system/typemill/Models/ProcessImage.php create mode 100644 system/typemill/Static/License.php create mode 100644 system/typemill/Static/Slug.php create mode 100644 system/typemill/author/css/a11y-dark.min.css create mode 100644 system/typemill/author/js/highlight.min.js create mode 100644 system/typemill/author/js/vue-license.js create mode 100644 system/typemill/author/js/vue-user.js create mode 100644 system/typemill/author/js/vue-usernew.js create mode 100644 system/typemill/author/system/license.twig create mode 100644 system/typemill/author/system/user.twig create mode 100644 system/typemill/author/system/usernew.twig create mode 100644 system/typemill/settings/license.yaml diff --git a/.gitignore b/.gitignore index 902f6fe..3dcc643 100644 --- a/.gitignore +++ b/.gitignore @@ -18,6 +18,7 @@ content/01-cyanine-theme/01-colors-and-fonts.yaml content/01-cyanine-theme/02-footer.yaml content/01-cyanine-theme/03-content-elements.yaml settings/settings.yaml +settings/license.yaml settings/users system/vendor plugins/demo diff --git a/system/author/css/a11y-dark.min.css b/system/author/css/a11y-dark.min.css new file mode 100644 index 0000000..7820d7d --- /dev/null +++ b/system/author/css/a11y-dark.min.css @@ -0,0 +1,7 @@ +pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}/*! + Theme: a11y-dark + Author: @ericwbailey + Maintainer: @ericwbailey + + Based on the Tomorrow Night Eighties theme: https://github.com/isagalaev/highlight.js/blob/master/src/styles/tomorrow-night-eighties.css +*/.hljs{background:#2b2b2b;color:#f8f8f2}.hljs-comment,.hljs-quote{color:#d4d0ab}.hljs-deletion,.hljs-name,.hljs-regexp,.hljs-selector-class,.hljs-selector-id,.hljs-tag,.hljs-template-variable,.hljs-variable{color:#ffa07a}.hljs-built_in,.hljs-link,.hljs-literal,.hljs-meta,.hljs-number,.hljs-params,.hljs-type{color:#f5ab35}.hljs-attribute{color:gold}.hljs-addition,.hljs-bullet,.hljs-string,.hljs-symbol{color:#abe338}.hljs-section,.hljs-title{color:#00e0e0}.hljs-keyword,.hljs-selector-tag{color:#dcc6e0}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:700}@media screen and (-ms-high-contrast:active){.hljs-addition,.hljs-attribute,.hljs-built_in,.hljs-bullet,.hljs-comment,.hljs-link,.hljs-literal,.hljs-meta,.hljs-number,.hljs-params,.hljs-quote,.hljs-string,.hljs-symbol,.hljs-type{color:highlight}.hljs-keyword,.hljs-selector-tag{font-weight:700}} \ No newline at end of file diff --git a/system/author/js/highlight.min.js b/system/author/js/highlight.min.js new file mode 100644 index 0000000..476704d --- /dev/null +++ b/system/author/js/highlight.min.js @@ -0,0 +1,709 @@ +/*! + Highlight.js v11.7.0 (git: 82688fad18) + (c) 2006-2022 undefined and other contributors + License: BSD-3-Clause + */ +var hljs=function(){"use strict";var e={exports:{}};function t(e){ +return e instanceof Map?e.clear=e.delete=e.set=()=>{ +throw Error("map is read-only")}:e instanceof Set&&(e.add=e.clear=e.delete=()=>{ +throw Error("set is read-only") +}),Object.freeze(e),Object.getOwnPropertyNames(e).forEach((n=>{var i=e[n] +;"object"!=typeof i||Object.isFrozen(i)||t(i)})),e} +e.exports=t,e.exports.default=t;class n{constructor(e){ +void 0===e.data&&(e.data={}),this.data=e.data,this.isMatchIgnored=!1} +ignoreMatch(){this.isMatchIgnored=!0}}function i(e){ +return e.replace(/&/g,"&").replace(//g,">").replace(/"/g,""").replace(/'/g,"'") +}function r(e,...t){const n=Object.create(null);for(const t in e)n[t]=e[t] +;return t.forEach((e=>{for(const t in e)n[t]=e[t]})),n} +const s=e=>!!e.scope||e.sublanguage&&e.language;class o{constructor(e,t){ +this.buffer="",this.classPrefix=t.classPrefix,e.walk(this)}addText(e){ +this.buffer+=i(e)}openNode(e){if(!s(e))return;let t="" +;t=e.sublanguage?"language-"+e.language:((e,{prefix:t})=>{if(e.includes(".")){ +const n=e.split(".") +;return[`${t}${n.shift()}`,...n.map(((e,t)=>`${e}${"_".repeat(t+1)}`))].join(" ") +}return`${t}${e}`})(e.scope,{prefix:this.classPrefix}),this.span(t)} +closeNode(e){s(e)&&(this.buffer+="")}value(){return this.buffer}span(e){ +this.buffer+=``}}const a=(e={})=>{const t={children:[]} +;return Object.assign(t,e),t};class c{constructor(){ +this.rootNode=a(),this.stack=[this.rootNode]}get top(){ +return this.stack[this.stack.length-1]}get root(){return this.rootNode}add(e){ +this.top.children.push(e)}openNode(e){const t=a({scope:e}) +;this.add(t),this.stack.push(t)}closeNode(){ +if(this.stack.length>1)return this.stack.pop()}closeAllNodes(){ +for(;this.closeNode(););}toJSON(){return JSON.stringify(this.rootNode,null,4)} +walk(e){return this.constructor._walk(e,this.rootNode)}static _walk(e,t){ +return"string"==typeof t?e.addText(t):t.children&&(e.openNode(t), +t.children.forEach((t=>this._walk(e,t))),e.closeNode(t)),e}static _collapse(e){ +"string"!=typeof e&&e.children&&(e.children.every((e=>"string"==typeof e))?e.children=[e.children.join("")]:e.children.forEach((e=>{ +c._collapse(e)})))}}class l extends c{constructor(e){super(),this.options=e} +addKeyword(e,t){""!==e&&(this.openNode(t),this.addText(e),this.closeNode())} +addText(e){""!==e&&this.add(e)}addSublanguage(e,t){const n=e.root +;n.sublanguage=!0,n.language=t,this.add(n)}toHTML(){ +return new o(this,this.options).value()}finalize(){return!0}}function g(e){ +return e?"string"==typeof e?e:e.source:null}function d(e){return p("(?=",e,")")} +function u(e){return p("(?:",e,")*")}function h(e){return p("(?:",e,")?")} +function p(...e){return e.map((e=>g(e))).join("")}function f(...e){const t=(e=>{ +const t=e[e.length-1] +;return"object"==typeof t&&t.constructor===Object?(e.splice(e.length-1,1),t):{} +})(e);return"("+(t.capture?"":"?:")+e.map((e=>g(e))).join("|")+")"} +function b(e){return RegExp(e.toString()+"|").exec("").length-1} +const m=/\[(?:[^\\\]]|\\.)*\]|\(\??|\\([1-9][0-9]*)|\\./ +;function E(e,{joinWith:t}){let n=0;return e.map((e=>{n+=1;const t=n +;let i=g(e),r="";for(;i.length>0;){const e=m.exec(i);if(!e){r+=i;break} +r+=i.substring(0,e.index), +i=i.substring(e.index+e[0].length),"\\"===e[0][0]&&e[1]?r+="\\"+(Number(e[1])+t):(r+=e[0], +"("===e[0]&&n++)}return r})).map((e=>`(${e})`)).join(t)} +const x="[a-zA-Z]\\w*",w="[a-zA-Z_]\\w*",y="\\b\\d+(\\.\\d+)?",_="(-?)(\\b0[xX][a-fA-F0-9]+|(\\b\\d+(\\.\\d*)?|\\.\\d+)([eE][-+]?\\d+)?)",O="\\b(0b[01]+)",v={ +begin:"\\\\[\\s\\S]",relevance:0},N={scope:"string",begin:"'",end:"'", +illegal:"\\n",contains:[v]},k={scope:"string",begin:'"',end:'"',illegal:"\\n", +contains:[v]},M=(e,t,n={})=>{const i=r({scope:"comment",begin:e,end:t, +contains:[]},n);i.contains.push({scope:"doctag", +begin:"[ ]*(?=(TODO|FIXME|NOTE|BUG|OPTIMIZE|HACK|XXX):)", +end:/(TODO|FIXME|NOTE|BUG|OPTIMIZE|HACK|XXX):/,excludeBegin:!0,relevance:0}) +;const s=f("I","a","is","so","us","to","at","if","in","it","on",/[A-Za-z]+['](d|ve|re|ll|t|s|n)/,/[A-Za-z]+[-][a-z]+/,/[A-Za-z][a-z]{2,}/) +;return i.contains.push({begin:p(/[ ]+/,"(",s,/[.]?[:]?([.][ ]|[ ])/,"){3}")}),i +},S=M("//","$"),R=M("/\\*","\\*/"),j=M("#","$");var A=Object.freeze({ +__proto__:null,MATCH_NOTHING_RE:/\b\B/,IDENT_RE:x,UNDERSCORE_IDENT_RE:w, +NUMBER_RE:y,C_NUMBER_RE:_,BINARY_NUMBER_RE:O, +RE_STARTERS_RE:"!|!=|!==|%|%=|&|&&|&=|\\*|\\*=|\\+|\\+=|,|-|-=|/=|/|:|;|<<|<<=|<=|<|===|==|=|>>>=|>>=|>=|>>>|>>|>|\\?|\\[|\\{|\\(|\\^|\\^=|\\||\\|=|\\|\\||~", +SHEBANG:(e={})=>{const t=/^#![ ]*\// +;return e.binary&&(e.begin=p(t,/.*\b/,e.binary,/\b.*/)),r({scope:"meta",begin:t, +end:/$/,relevance:0,"on:begin":(e,t)=>{0!==e.index&&t.ignoreMatch()}},e)}, +BACKSLASH_ESCAPE:v,APOS_STRING_MODE:N,QUOTE_STRING_MODE:k,PHRASAL_WORDS_MODE:{ +begin:/\b(a|an|the|are|I'm|isn't|don't|doesn't|won't|but|just|should|pretty|simply|enough|gonna|going|wtf|so|such|will|you|your|they|like|more)\b/ +},COMMENT:M,C_LINE_COMMENT_MODE:S,C_BLOCK_COMMENT_MODE:R,HASH_COMMENT_MODE:j, +NUMBER_MODE:{scope:"number",begin:y,relevance:0},C_NUMBER_MODE:{scope:"number", +begin:_,relevance:0},BINARY_NUMBER_MODE:{scope:"number",begin:O,relevance:0}, +REGEXP_MODE:{begin:/(?=\/[^/\n]*\/)/,contains:[{scope:"regexp",begin:/\//, +end:/\/[gimuy]*/,illegal:/\n/,contains:[v,{begin:/\[/,end:/\]/,relevance:0, +contains:[v]}]}]},TITLE_MODE:{scope:"title",begin:x,relevance:0}, +UNDERSCORE_TITLE_MODE:{scope:"title",begin:w,relevance:0},METHOD_GUARD:{ +begin:"\\.\\s*[a-zA-Z_]\\w*",relevance:0},END_SAME_AS_BEGIN:e=>Object.assign(e,{ +"on:begin":(e,t)=>{t.data._beginMatch=e[1]},"on:end":(e,t)=>{ +t.data._beginMatch!==e[1]&&t.ignoreMatch()}})});function I(e,t){ +"."===e.input[e.index-1]&&t.ignoreMatch()}function T(e,t){ +void 0!==e.className&&(e.scope=e.className,delete e.className)}function L(e,t){ +t&&e.beginKeywords&&(e.begin="\\b("+e.beginKeywords.split(" ").join("|")+")(?!\\.)(?=\\b|\\s)", +e.__beforeBegin=I,e.keywords=e.keywords||e.beginKeywords,delete e.beginKeywords, +void 0===e.relevance&&(e.relevance=0))}function B(e,t){ +Array.isArray(e.illegal)&&(e.illegal=f(...e.illegal))}function D(e,t){ +if(e.match){ +if(e.begin||e.end)throw Error("begin & end are not supported with match") +;e.begin=e.match,delete e.match}}function H(e,t){ +void 0===e.relevance&&(e.relevance=1)}const P=(e,t)=>{if(!e.beforeMatch)return +;if(e.starts)throw Error("beforeMatch cannot be used with starts") +;const n=Object.assign({},e);Object.keys(e).forEach((t=>{delete e[t] +})),e.keywords=n.keywords,e.begin=p(n.beforeMatch,d(n.begin)),e.starts={ +relevance:0,contains:[Object.assign(n,{endsParent:!0})] +},e.relevance=0,delete n.beforeMatch +},C=["of","and","for","in","not","or","if","then","parent","list","value"] +;function $(e,t,n="keyword"){const i=Object.create(null) +;return"string"==typeof e?r(n,e.split(" ")):Array.isArray(e)?r(n,e):Object.keys(e).forEach((n=>{ +Object.assign(i,$(e[n],t,n))})),i;function r(e,n){ +t&&(n=n.map((e=>e.toLowerCase()))),n.forEach((t=>{const n=t.split("|") +;i[n[0]]=[e,U(n[0],n[1])]}))}}function U(e,t){ +return t?Number(t):(e=>C.includes(e.toLowerCase()))(e)?0:1}const z={},K=e=>{ +console.error(e)},W=(e,...t)=>{console.log("WARN: "+e,...t)},X=(e,t)=>{ +z[`${e}/${t}`]||(console.log(`Deprecated as of ${e}. ${t}`),z[`${e}/${t}`]=!0) +},G=Error();function Z(e,t,{key:n}){let i=0;const r=e[n],s={},o={} +;for(let e=1;e<=t.length;e++)o[e+i]=r[e],s[e+i]=!0,i+=b(t[e-1]) +;e[n]=o,e[n]._emit=s,e[n]._multi=!0}function F(e){(e=>{ +e.scope&&"object"==typeof e.scope&&null!==e.scope&&(e.beginScope=e.scope, +delete e.scope)})(e),"string"==typeof e.beginScope&&(e.beginScope={ +_wrap:e.beginScope}),"string"==typeof e.endScope&&(e.endScope={_wrap:e.endScope +}),(e=>{if(Array.isArray(e.begin)){ +if(e.skip||e.excludeBegin||e.returnBegin)throw K("skip, excludeBegin, returnBegin not compatible with beginScope: {}"), +G +;if("object"!=typeof e.beginScope||null===e.beginScope)throw K("beginScope must be object"), +G;Z(e,e.begin,{key:"beginScope"}),e.begin=E(e.begin,{joinWith:""})}})(e),(e=>{ +if(Array.isArray(e.end)){ +if(e.skip||e.excludeEnd||e.returnEnd)throw K("skip, excludeEnd, returnEnd not compatible with endScope: {}"), +G +;if("object"!=typeof e.endScope||null===e.endScope)throw K("endScope must be object"), +G;Z(e,e.end,{key:"endScope"}),e.end=E(e.end,{joinWith:""})}})(e)}function V(e){ +function t(t,n){ +return RegExp(g(t),"m"+(e.case_insensitive?"i":"")+(e.unicodeRegex?"u":"")+(n?"g":"")) +}class n{constructor(){ +this.matchIndexes={},this.regexes=[],this.matchAt=1,this.position=0} +addRule(e,t){ +t.position=this.position++,this.matchIndexes[this.matchAt]=t,this.regexes.push([t,e]), +this.matchAt+=b(e)+1}compile(){0===this.regexes.length&&(this.exec=()=>null) +;const e=this.regexes.map((e=>e[1]));this.matcherRe=t(E(e,{joinWith:"|" +}),!0),this.lastIndex=0}exec(e){this.matcherRe.lastIndex=this.lastIndex +;const t=this.matcherRe.exec(e);if(!t)return null +;const n=t.findIndex(((e,t)=>t>0&&void 0!==e)),i=this.matchIndexes[n] +;return t.splice(0,n),Object.assign(t,i)}}class i{constructor(){ +this.rules=[],this.multiRegexes=[], +this.count=0,this.lastIndex=0,this.regexIndex=0}getMatcher(e){ +if(this.multiRegexes[e])return this.multiRegexes[e];const t=new n +;return this.rules.slice(e).forEach((([e,n])=>t.addRule(e,n))), +t.compile(),this.multiRegexes[e]=t,t}resumingScanAtSamePosition(){ +return 0!==this.regexIndex}considerAll(){this.regexIndex=0}addRule(e,t){ +this.rules.push([e,t]),"begin"===t.type&&this.count++}exec(e){ +const t=this.getMatcher(this.regexIndex);t.lastIndex=this.lastIndex +;let n=t.exec(e) +;if(this.resumingScanAtSamePosition())if(n&&n.index===this.lastIndex);else{ +const t=this.getMatcher(0);t.lastIndex=this.lastIndex+1,n=t.exec(e)} +return n&&(this.regexIndex+=n.position+1, +this.regexIndex===this.count&&this.considerAll()),n}} +if(e.compilerExtensions||(e.compilerExtensions=[]), +e.contains&&e.contains.includes("self"))throw Error("ERR: contains `self` is not supported at the top-level of a language. See documentation.") +;return e.classNameAliases=r(e.classNameAliases||{}),function n(s,o){const a=s +;if(s.isCompiled)return a +;[T,D,F,P].forEach((e=>e(s,o))),e.compilerExtensions.forEach((e=>e(s,o))), +s.__beforeBegin=null,[L,B,H].forEach((e=>e(s,o))),s.isCompiled=!0;let c=null +;return"object"==typeof s.keywords&&s.keywords.$pattern&&(s.keywords=Object.assign({},s.keywords), +c=s.keywords.$pattern, +delete s.keywords.$pattern),c=c||/\w+/,s.keywords&&(s.keywords=$(s.keywords,e.case_insensitive)), +a.keywordPatternRe=t(c,!0), +o&&(s.begin||(s.begin=/\B|\b/),a.beginRe=t(a.begin),s.end||s.endsWithParent||(s.end=/\B|\b/), +s.end&&(a.endRe=t(a.end)), +a.terminatorEnd=g(a.end)||"",s.endsWithParent&&o.terminatorEnd&&(a.terminatorEnd+=(s.end?"|":"")+o.terminatorEnd)), +s.illegal&&(a.illegalRe=t(s.illegal)), +s.contains||(s.contains=[]),s.contains=[].concat(...s.contains.map((e=>(e=>(e.variants&&!e.cachedVariants&&(e.cachedVariants=e.variants.map((t=>r(e,{ +variants:null},t)))),e.cachedVariants?e.cachedVariants:q(e)?r(e,{ +starts:e.starts?r(e.starts):null +}):Object.isFrozen(e)?r(e):e))("self"===e?s:e)))),s.contains.forEach((e=>{n(e,a) +})),s.starts&&n(s.starts,o),a.matcher=(e=>{const t=new i +;return e.contains.forEach((e=>t.addRule(e.begin,{rule:e,type:"begin" +}))),e.terminatorEnd&&t.addRule(e.terminatorEnd,{type:"end" +}),e.illegal&&t.addRule(e.illegal,{type:"illegal"}),t})(a),a}(e)}function q(e){ +return!!e&&(e.endsWithParent||q(e.starts))}class J extends Error{ +constructor(e,t){super(e),this.name="HTMLInjectionError",this.html=t}} +const Y=i,Q=r,ee=Symbol("nomatch");var te=(t=>{ +const i=Object.create(null),r=Object.create(null),s=[];let o=!0 +;const a="Could not find the language '{}', did you forget to load/include a language module?",c={ +disableAutodetect:!0,name:"Plain text",contains:[]};let g={ +ignoreUnescapedHTML:!1,throwUnescapedHTML:!1,noHighlightRe:/^(no-?highlight)$/i, +languageDetectRe:/\blang(?:uage)?-([\w-]+)\b/i,classPrefix:"hljs-", +cssSelector:"pre code",languages:null,__emitter:l};function b(e){ +return g.noHighlightRe.test(e)}function m(e,t,n){let i="",r="" +;"object"==typeof t?(i=e, +n=t.ignoreIllegals,r=t.language):(X("10.7.0","highlight(lang, code, ...args) has been deprecated."), +X("10.7.0","Please use highlight(code, options) instead.\nhttps://github.com/highlightjs/highlight.js/issues/2277"), +r=e,i=t),void 0===n&&(n=!0);const s={code:i,language:r};k("before:highlight",s) +;const o=s.result?s.result:E(s.language,s.code,n) +;return o.code=s.code,k("after:highlight",o),o}function E(e,t,r,s){ +const c=Object.create(null);function l(){if(!N.keywords)return void M.addText(S) +;let e=0;N.keywordPatternRe.lastIndex=0;let t=N.keywordPatternRe.exec(S),n="" +;for(;t;){n+=S.substring(e,t.index) +;const r=y.case_insensitive?t[0].toLowerCase():t[0],s=(i=r,N.keywords[i]);if(s){ +const[e,i]=s +;if(M.addText(n),n="",c[r]=(c[r]||0)+1,c[r]<=7&&(R+=i),e.startsWith("_"))n+=t[0];else{ +const n=y.classNameAliases[e]||e;M.addKeyword(t[0],n)}}else n+=t[0] +;e=N.keywordPatternRe.lastIndex,t=N.keywordPatternRe.exec(S)}var i +;n+=S.substring(e),M.addText(n)}function d(){null!=N.subLanguage?(()=>{ +if(""===S)return;let e=null;if("string"==typeof N.subLanguage){ +if(!i[N.subLanguage])return void M.addText(S) +;e=E(N.subLanguage,S,!0,k[N.subLanguage]),k[N.subLanguage]=e._top +}else e=x(S,N.subLanguage.length?N.subLanguage:null) +;N.relevance>0&&(R+=e.relevance),M.addSublanguage(e._emitter,e.language) +})():l(),S=""}function u(e,t){let n=1;const i=t.length-1;for(;n<=i;){ +if(!e._emit[n]){n++;continue}const i=y.classNameAliases[e[n]]||e[n],r=t[n] +;i?M.addKeyword(r,i):(S=r,l(),S=""),n++}}function h(e,t){ +return e.scope&&"string"==typeof e.scope&&M.openNode(y.classNameAliases[e.scope]||e.scope), +e.beginScope&&(e.beginScope._wrap?(M.addKeyword(S,y.classNameAliases[e.beginScope._wrap]||e.beginScope._wrap), +S=""):e.beginScope._multi&&(u(e.beginScope,t),S="")),N=Object.create(e,{parent:{ +value:N}}),N}function p(e,t,i){let r=((e,t)=>{const n=e&&e.exec(t) +;return n&&0===n.index})(e.endRe,i);if(r){if(e["on:end"]){const i=new n(e) +;e["on:end"](t,i),i.isMatchIgnored&&(r=!1)}if(r){ +for(;e.endsParent&&e.parent;)e=e.parent;return e}} +if(e.endsWithParent)return p(e.parent,t,i)}function f(e){ +return 0===N.matcher.regexIndex?(S+=e[0],1):(I=!0,0)}function b(e){ +const n=e[0],i=t.substring(e.index),r=p(N,e,i);if(!r)return ee;const s=N +;N.endScope&&N.endScope._wrap?(d(), +M.addKeyword(n,N.endScope._wrap)):N.endScope&&N.endScope._multi?(d(), +u(N.endScope,e)):s.skip?S+=n:(s.returnEnd||s.excludeEnd||(S+=n), +d(),s.excludeEnd&&(S=n));do{ +N.scope&&M.closeNode(),N.skip||N.subLanguage||(R+=N.relevance),N=N.parent +}while(N!==r.parent);return r.starts&&h(r.starts,e),s.returnEnd?0:n.length} +let m={};function w(i,s){const a=s&&s[0];if(S+=i,null==a)return d(),0 +;if("begin"===m.type&&"end"===s.type&&m.index===s.index&&""===a){ +if(S+=t.slice(s.index,s.index+1),!o){const t=Error(`0 width match regex (${e})`) +;throw t.languageName=e,t.badRule=m.rule,t}return 1} +if(m=s,"begin"===s.type)return(e=>{ +const t=e[0],i=e.rule,r=new n(i),s=[i.__beforeBegin,i["on:begin"]] +;for(const n of s)if(n&&(n(e,r),r.isMatchIgnored))return f(t) +;return i.skip?S+=t:(i.excludeBegin&&(S+=t), +d(),i.returnBegin||i.excludeBegin||(S=t)),h(i,e),i.returnBegin?0:t.length})(s) +;if("illegal"===s.type&&!r){ +const e=Error('Illegal lexeme "'+a+'" for mode "'+(N.scope||"")+'"') +;throw e.mode=N,e}if("end"===s.type){const e=b(s);if(e!==ee)return e} +if("illegal"===s.type&&""===a)return 1 +;if(A>1e5&&A>3*s.index)throw Error("potential infinite loop, way more iterations than matches") +;return S+=a,a.length}const y=O(e) +;if(!y)throw K(a.replace("{}",e)),Error('Unknown language: "'+e+'"') +;const _=V(y);let v="",N=s||_;const k={},M=new g.__emitter(g);(()=>{const e=[] +;for(let t=N;t!==y;t=t.parent)t.scope&&e.unshift(t.scope) +;e.forEach((e=>M.openNode(e)))})();let S="",R=0,j=0,A=0,I=!1;try{ +for(N.matcher.considerAll();;){ +A++,I?I=!1:N.matcher.considerAll(),N.matcher.lastIndex=j +;const e=N.matcher.exec(t);if(!e)break;const n=w(t.substring(j,e.index),e) +;j=e.index+n} +return w(t.substring(j)),M.closeAllNodes(),M.finalize(),v=M.toHTML(),{ +language:e,value:v,relevance:R,illegal:!1,_emitter:M,_top:N}}catch(n){ +if(n.message&&n.message.includes("Illegal"))return{language:e,value:Y(t), +illegal:!0,relevance:0,_illegalBy:{message:n.message,index:j, +context:t.slice(j-100,j+100),mode:n.mode,resultSoFar:v},_emitter:M};if(o)return{ +language:e,value:Y(t),illegal:!1,relevance:0,errorRaised:n,_emitter:M,_top:N} +;throw n}}function x(e,t){t=t||g.languages||Object.keys(i);const n=(e=>{ +const t={value:Y(e),illegal:!1,relevance:0,_top:c,_emitter:new g.__emitter(g)} +;return t._emitter.addText(e),t})(e),r=t.filter(O).filter(N).map((t=>E(t,e,!1))) +;r.unshift(n);const s=r.sort(((e,t)=>{ +if(e.relevance!==t.relevance)return t.relevance-e.relevance +;if(e.language&&t.language){if(O(e.language).supersetOf===t.language)return 1 +;if(O(t.language).supersetOf===e.language)return-1}return 0})),[o,a]=s,l=o +;return l.secondBest=a,l}function w(e){let t=null;const n=(e=>{ +let t=e.className+" ";t+=e.parentNode?e.parentNode.className:"" +;const n=g.languageDetectRe.exec(t);if(n){const t=O(n[1]) +;return t||(W(a.replace("{}",n[1])), +W("Falling back to no-highlight mode for this block.",e)),t?n[1]:"no-highlight"} +return t.split(/\s+/).find((e=>b(e)||O(e)))})(e);if(b(n))return +;if(k("before:highlightElement",{el:e,language:n +}),e.children.length>0&&(g.ignoreUnescapedHTML||(console.warn("One of your code blocks includes unescaped HTML. This is a potentially serious security risk."), +console.warn("https://github.com/highlightjs/highlight.js/wiki/security"), +console.warn("The element with unescaped HTML:"), +console.warn(e)),g.throwUnescapedHTML))throw new J("One of your code blocks includes unescaped HTML.",e.innerHTML) +;t=e;const i=t.textContent,s=n?m(i,{language:n,ignoreIllegals:!0}):x(i) +;e.innerHTML=s.value,((e,t,n)=>{const i=t&&r[t]||n +;e.classList.add("hljs"),e.classList.add("language-"+i) +})(e,n,s.language),e.result={language:s.language,re:s.relevance, +relevance:s.relevance},s.secondBest&&(e.secondBest={ +language:s.secondBest.language,relevance:s.secondBest.relevance +}),k("after:highlightElement",{el:e,result:s,text:i})}let y=!1;function _(){ +"loading"!==document.readyState?document.querySelectorAll(g.cssSelector).forEach(w):y=!0 +}function O(e){return e=(e||"").toLowerCase(),i[e]||i[r[e]]} +function v(e,{languageName:t}){"string"==typeof e&&(e=[e]),e.forEach((e=>{ +r[e.toLowerCase()]=t}))}function N(e){const t=O(e) +;return t&&!t.disableAutodetect}function k(e,t){const n=e;s.forEach((e=>{ +e[n]&&e[n](t)}))} +"undefined"!=typeof window&&window.addEventListener&&window.addEventListener("DOMContentLoaded",(()=>{ +y&&_()}),!1),Object.assign(t,{highlight:m,highlightAuto:x,highlightAll:_, +highlightElement:w, +highlightBlock:e=>(X("10.7.0","highlightBlock will be removed entirely in v12.0"), +X("10.7.0","Please use highlightElement now."),w(e)),configure:e=>{g=Q(g,e)}, +initHighlighting:()=>{ +_(),X("10.6.0","initHighlighting() deprecated. Use highlightAll() now.")}, +initHighlightingOnLoad:()=>{ +_(),X("10.6.0","initHighlightingOnLoad() deprecated. Use highlightAll() now.") +},registerLanguage:(e,n)=>{let r=null;try{r=n(t)}catch(t){ +if(K("Language definition for '{}' could not be registered.".replace("{}",e)), +!o)throw t;K(t),r=c} +r.name||(r.name=e),i[e]=r,r.rawDefinition=n.bind(null,t),r.aliases&&v(r.aliases,{ +languageName:e})},unregisterLanguage:e=>{delete i[e] +;for(const t of Object.keys(r))r[t]===e&&delete r[t]}, +listLanguages:()=>Object.keys(i),getLanguage:O,registerAliases:v, +autoDetection:N,inherit:Q,addPlugin:e=>{(e=>{ +e["before:highlightBlock"]&&!e["before:highlightElement"]&&(e["before:highlightElement"]=t=>{ +e["before:highlightBlock"](Object.assign({block:t.el},t)) +}),e["after:highlightBlock"]&&!e["after:highlightElement"]&&(e["after:highlightElement"]=t=>{ +e["after:highlightBlock"](Object.assign({block:t.el},t))})})(e),s.push(e)} +}),t.debugMode=()=>{o=!1},t.safeMode=()=>{o=!0 +},t.versionString="11.7.0",t.regex={concat:p,lookahead:d,either:f,optional:h, +anyNumberOfTimes:u};for(const t in A)"object"==typeof A[t]&&e.exports(A[t]) +;return Object.assign(t,A),t})({});return te}() +;"object"==typeof exports&&"undefined"!=typeof module&&(module.exports=hljs);/*! `javascript` grammar compiled for Highlight.js 11.7.0 */ +(()=>{var e=(()=>{"use strict" +;const e="[A-Za-z$_][0-9A-Za-z$_]*",n=["as","in","of","if","for","while","finally","var","new","function","do","return","void","else","break","catch","instanceof","with","throw","case","default","try","switch","continue","typeof","delete","let","yield","const","class","debugger","async","await","static","import","from","export","extends"],a=["true","false","null","undefined","NaN","Infinity"],t=["Object","Function","Boolean","Symbol","Math","Date","Number","BigInt","String","RegExp","Array","Float32Array","Float64Array","Int8Array","Uint8Array","Uint8ClampedArray","Int16Array","Int32Array","Uint16Array","Uint32Array","BigInt64Array","BigUint64Array","Set","Map","WeakSet","WeakMap","ArrayBuffer","SharedArrayBuffer","Atomics","DataView","JSON","Promise","Generator","GeneratorFunction","AsyncFunction","Reflect","Proxy","Intl","WebAssembly"],s=["Error","EvalError","InternalError","RangeError","ReferenceError","SyntaxError","TypeError","URIError"],r=["setInterval","setTimeout","clearInterval","clearTimeout","require","exports","eval","isFinite","isNaN","parseFloat","parseInt","decodeURI","decodeURIComponent","encodeURI","encodeURIComponent","escape","unescape"],c=["arguments","this","super","console","window","document","localStorage","module","global"],i=[].concat(r,t,s) +;return o=>{const l=o.regex,b=e,d={begin:/<[A-Za-z0-9\\._:-]+/, +end:/\/[A-Za-z0-9\\._:-]+>|\/>/,isTrulyOpeningTag:(e,n)=>{ +const a=e[0].length+e.index,t=e.input[a] +;if("<"===t||","===t)return void n.ignoreMatch();let s +;">"===t&&(((e,{after:n})=>{const a="",M={ +match:[/const|var|let/,/\s+/,b,/\s*/,/=\s*/,/(async\s*)?/,l.lookahead(C)], +keywords:"async",className:{1:"keyword",3:"title.function"},contains:[S]} +;return{name:"Javascript",aliases:["js","jsx","mjs","cjs"],keywords:g,exports:{ +PARAMS_CONTAINS:p,CLASS_REFERENCE:R},illegal:/#(?![$_A-z])/, +contains:[o.SHEBANG({label:"shebang",binary:"node",relevance:5}),{ +label:"use_strict",className:"meta",relevance:10, +begin:/^\s*['"]use (strict|asm)['"]/ +},o.APOS_STRING_MODE,o.QUOTE_STRING_MODE,y,N,_,h,{match:/\$\d+/},E,R,{ +className:"attr",begin:b+l.lookahead(":"),relevance:0},M,{ +begin:"("+o.RE_STARTERS_RE+"|\\b(case|return|throw)\\b)\\s*", +keywords:"return throw case",relevance:0,contains:[h,o.REGEXP_MODE,{ +className:"function",begin:C,returnBegin:!0,end:"\\s*=>",contains:[{ +className:"params",variants:[{begin:o.UNDERSCORE_IDENT_RE,relevance:0},{ +className:null,begin:/\(\s*\)/,skip:!0},{begin:/\(/,end:/\)/,excludeBegin:!0, +excludeEnd:!0,keywords:g,contains:p}]}]},{begin:/,/,relevance:0},{match:/\s+/, +relevance:0},{variants:[{begin:"<>",end:""},{ +match:/<[A-Za-z0-9\\._:-]+\s*\/>/},{begin:d.begin, +"on:begin":d.isTrulyOpeningTag,end:d.end}],subLanguage:"xml",contains:[{ +begin:d.begin,end:d.end,skip:!0,contains:["self"]}]}]},O,{ +beginKeywords:"while if switch catch for"},{ +begin:"\\b(?!function)"+o.UNDERSCORE_IDENT_RE+"\\([^()]*(\\([^()]*(\\([^()]*\\)[^()]*)*\\)[^()]*)*\\)\\s*\\{", +returnBegin:!0,label:"func.def",contains:[S,o.inherit(o.TITLE_MODE,{begin:b, +className:"title.function"})]},{match:/\.\.\./,relevance:0},x,{match:"\\$"+b, +relevance:0},{match:[/\bconstructor(?=\s*\()/],className:{1:"title.function"}, +contains:[S]},k,{relevance:0,match:/\b[A-Z][A-Z_0-9]+\b/, +className:"variable.constant"},w,T,{match:/\$[(.]/}]}}})() +;hljs.registerLanguage("javascript",e)})();/*! `xml` grammar compiled for Highlight.js 11.7.0 */ +(()=>{var e=(()=>{"use strict";return e=>{ +const a=e.regex,n=a.concat(/[\p{L}_]/u,a.optional(/[\p{L}0-9_.-]*:/u),/[\p{L}0-9_.-]*/u),s={ +className:"symbol",begin:/&[a-z]+;|&#[0-9]+;|&#x[a-f0-9]+;/},t={begin:/\s/, +contains:[{className:"keyword",begin:/#?[a-z_][a-z1-9_-]+/,illegal:/\n/}] +},i=e.inherit(t,{begin:/\(/,end:/\)/}),c=e.inherit(e.APOS_STRING_MODE,{ +className:"string"}),l=e.inherit(e.QUOTE_STRING_MODE,{className:"string"}),r={ +endsWithParent:!0,illegal:/`]+/}]}]}]};return{ +name:"HTML, XML", +aliases:["html","xhtml","rss","atom","xjb","xsd","xsl","plist","wsf","svg"], +case_insensitive:!0,unicodeRegex:!0,contains:[{className:"meta",begin://,relevance:10,contains:[t,l,c,i,{begin:/\[/,end:/\]/,contains:[{ +className:"meta",begin://,contains:[t,i,l,c]}]}] +},e.COMMENT(//,{relevance:10}),{begin://, +relevance:10},s,{className:"meta",end:/\?>/,variants:[{begin:/<\?xml/, +relevance:10,contains:[l]},{begin:/<\?[a-z][a-z0-9]+/}]},{className:"tag", +begin:/)/,end:/>/,keywords:{name:"style"},contains:[r],starts:{ +end:/<\/style>/,returnEnd:!0,subLanguage:["css","xml"]}},{className:"tag", +begin:/)/,end:/>/,keywords:{name:"script"},contains:[r],starts:{ +end:/<\/script>/,returnEnd:!0,subLanguage:["javascript","handlebars","xml"]}},{ +className:"tag",begin:/<>|<\/>/},{className:"tag", +begin:a.concat(//,/>/,/\s/)))), +end:/\/?>/,contains:[{className:"name",begin:n,relevance:0,starts:r}]},{ +className:"tag",begin:a.concat(/<\//,a.lookahead(a.concat(n,/>/))),contains:[{ +className:"name",begin:n,relevance:0},{begin:/>/,relevance:0,endsParent:!0}]}]}} +})();hljs.registerLanguage("xml",e)})();/*! `twig` grammar compiled for Highlight.js 11.7.0 */ +(()=>{var e=(()=>{"use strict";return e=>{ +const a=e.regex,t=["absolute_url","asset|0","asset_version","attribute","block","constant","controller|0","country_timezones","csrf_token","cycle","date","dump","expression","form|0","form_end","form_errors","form_help","form_label","form_rest","form_row","form_start","form_widget","html_classes","include","is_granted","logout_path","logout_url","max","min","parent","path|0","random","range","relative_path","render","render_esi","source","template_from_string","url|0"] +;let r=["apply","autoescape","block","cache","deprecated","do","embed","extends","filter","flush","for","form_theme","from","if","import","include","macro","sandbox","set","stopwatch","trans","trans_default_domain","transchoice","use","verbatim","with"] +;r=r.concat(r.map((e=>"end"+e)));const n={scope:"string",variants:[{begin:/'/, +end:/'/},{begin:/"/,end:/"/}]},o={scope:"number",match:/\d+/},s={begin:/\(/, +end:/\)/,excludeBegin:!0,excludeEnd:!0,contains:[n,o]},c={ +beginKeywords:t.join(" "),keywords:{name:t},relevance:0,contains:[s]},m={ +match:/\|(?=[A-Za-z_]+:?)/,beginScope:"punctuation",relevance:0,contains:[{ +match:/[A-Za-z_]+:?/, +keywords:["abs","abbr_class","abbr_method","batch","capitalize","column","convert_encoding","country_name","currency_name","currency_symbol","data_uri","date","date_modify","default","escape","file_excerpt","file_link","file_relative","filter","first","format","format_args","format_args_as_text","format_currency","format_date","format_datetime","format_file","format_file_from_text","format_number","format_time","html_to_markdown","humanize","inky_to_html","inline_css","join","json_encode","keys","language_name","last","length","locale_name","lower","map","markdown","markdown_to_html","merge","nl2br","number_format","raw","reduce","replace","reverse","round","slice","slug","sort","spaceless","split","striptags","timezone_name","title","trans","transchoice","trim","u|0","upper","url_encode","yaml_dump","yaml_encode"] +}]},i=(e,{relevance:t})=>({beginScope:{1:"template-tag",3:"name"}, +relevance:t||2,endScope:"template-tag",begin:[/\{%/,/\s*/,a.either(...e)], +end:/%\}/,keywords:"in",contains:[m,c,n,o]}),l=i(r,{relevance:2 +}),_=i([/[a-z_]+/],{relevance:1});return{name:"Twig",aliases:["craftcms"], +case_insensitive:!0,subLanguage:"xml",contains:[e.COMMENT(/\{#/,/#\}/),l,_,{ +className:"template-variable",begin:/\{\{/,end:/\}\}/,contains:["self",m,c,n,o] +}]}}})();hljs.registerLanguage("twig",e)})();/*! `json` grammar compiled for Highlight.js 11.7.0 */ +(()=>{var e=(()=>{"use strict";return e=>{const a=["true","false","null"],n={ +scope:"literal",beginKeywords:a.join(" ")};return{name:"JSON",keywords:{ +literal:a},contains:[{className:"attr",begin:/"(\\.|[^\\"\r\n])*"(?=\s*:)/, +relevance:1.01},{match:/[{}[\],:]/,className:"punctuation",relevance:0 +},e.QUOTE_STRING_MODE,n,e.C_NUMBER_MODE,e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE], +illegal:"\\S"}}})();hljs.registerLanguage("json",e)})();/*! `graphql` grammar compiled for Highlight.js 11.7.0 */ +(()=>{var e=(()=>{"use strict";return e=>{const a=e.regex;return{name:"GraphQL", +aliases:["gql"],case_insensitive:!0,disableAutodetect:!1,keywords:{ +keyword:["query","mutation","subscription","type","input","schema","directive","interface","union","scalar","fragment","enum","on"], +literal:["true","false","null"]}, +contains:[e.HASH_COMMENT_MODE,e.QUOTE_STRING_MODE,e.NUMBER_MODE,{ +scope:"punctuation",match:/[.]{3}/,relevance:0},{scope:"punctuation", +begin:/[\!\(\)\:\=\[\]\{\|\}]{1}/,relevance:0},{scope:"variable",begin:/\$/, +end:/\W/,excludeEnd:!0,relevance:0},{scope:"meta",match:/@\w+/,excludeEnd:!0},{ +scope:"symbol",begin:a.concat(/[_A-Za-z][_0-9A-Za-z]*/,a.lookahead(/\s*:/)), +relevance:0}],illegal:[/[;<']/,/BEGIN/]}}})();hljs.registerLanguage("graphql",e) +})();/*! `css` grammar compiled for Highlight.js 11.7.0 */ +(()=>{var e=(()=>{"use strict" +;const e=["a","abbr","address","article","aside","audio","b","blockquote","body","button","canvas","caption","cite","code","dd","del","details","dfn","div","dl","dt","em","fieldset","figcaption","figure","footer","form","h1","h2","h3","h4","h5","h6","header","hgroup","html","i","iframe","img","input","ins","kbd","label","legend","li","main","mark","menu","nav","object","ol","p","q","quote","samp","section","span","strong","summary","sup","table","tbody","td","textarea","tfoot","th","thead","time","tr","ul","var","video"],i=["any-hover","any-pointer","aspect-ratio","color","color-gamut","color-index","device-aspect-ratio","device-height","device-width","display-mode","forced-colors","grid","height","hover","inverted-colors","monochrome","orientation","overflow-block","overflow-inline","pointer","prefers-color-scheme","prefers-contrast","prefers-reduced-motion","prefers-reduced-transparency","resolution","scan","scripting","update","width","min-width","max-width","min-height","max-height"],r=["active","any-link","blank","checked","current","default","defined","dir","disabled","drop","empty","enabled","first","first-child","first-of-type","fullscreen","future","focus","focus-visible","focus-within","has","host","host-context","hover","indeterminate","in-range","invalid","is","lang","last-child","last-of-type","left","link","local-link","not","nth-child","nth-col","nth-last-child","nth-last-col","nth-last-of-type","nth-of-type","only-child","only-of-type","optional","out-of-range","past","placeholder-shown","read-only","read-write","required","right","root","scope","target","target-within","user-invalid","valid","visited","where"],t=["after","backdrop","before","cue","cue-region","first-letter","first-line","grammar-error","marker","part","placeholder","selection","slotted","spelling-error"],o=["align-content","align-items","align-self","all","animation","animation-delay","animation-direction","animation-duration","animation-fill-mode","animation-iteration-count","animation-name","animation-play-state","animation-timing-function","backface-visibility","background","background-attachment","background-blend-mode","background-clip","background-color","background-image","background-origin","background-position","background-repeat","background-size","block-size","border","border-block","border-block-color","border-block-end","border-block-end-color","border-block-end-style","border-block-end-width","border-block-start","border-block-start-color","border-block-start-style","border-block-start-width","border-block-style","border-block-width","border-bottom","border-bottom-color","border-bottom-left-radius","border-bottom-right-radius","border-bottom-style","border-bottom-width","border-collapse","border-color","border-image","border-image-outset","border-image-repeat","border-image-slice","border-image-source","border-image-width","border-inline","border-inline-color","border-inline-end","border-inline-end-color","border-inline-end-style","border-inline-end-width","border-inline-start","border-inline-start-color","border-inline-start-style","border-inline-start-width","border-inline-style","border-inline-width","border-left","border-left-color","border-left-style","border-left-width","border-radius","border-right","border-right-color","border-right-style","border-right-width","border-spacing","border-style","border-top","border-top-color","border-top-left-radius","border-top-right-radius","border-top-style","border-top-width","border-width","bottom","box-decoration-break","box-shadow","box-sizing","break-after","break-before","break-inside","caption-side","caret-color","clear","clip","clip-path","clip-rule","color","column-count","column-fill","column-gap","column-rule","column-rule-color","column-rule-style","column-rule-width","column-span","column-width","columns","contain","content","content-visibility","counter-increment","counter-reset","cue","cue-after","cue-before","cursor","direction","display","empty-cells","filter","flex","flex-basis","flex-direction","flex-flow","flex-grow","flex-shrink","flex-wrap","float","flow","font","font-display","font-family","font-feature-settings","font-kerning","font-language-override","font-size","font-size-adjust","font-smoothing","font-stretch","font-style","font-synthesis","font-variant","font-variant-caps","font-variant-east-asian","font-variant-ligatures","font-variant-numeric","font-variant-position","font-variation-settings","font-weight","gap","glyph-orientation-vertical","grid","grid-area","grid-auto-columns","grid-auto-flow","grid-auto-rows","grid-column","grid-column-end","grid-column-start","grid-gap","grid-row","grid-row-end","grid-row-start","grid-template","grid-template-areas","grid-template-columns","grid-template-rows","hanging-punctuation","height","hyphens","icon","image-orientation","image-rendering","image-resolution","ime-mode","inline-size","isolation","justify-content","left","letter-spacing","line-break","line-height","list-style","list-style-image","list-style-position","list-style-type","margin","margin-block","margin-block-end","margin-block-start","margin-bottom","margin-inline","margin-inline-end","margin-inline-start","margin-left","margin-right","margin-top","marks","mask","mask-border","mask-border-mode","mask-border-outset","mask-border-repeat","mask-border-slice","mask-border-source","mask-border-width","mask-clip","mask-composite","mask-image","mask-mode","mask-origin","mask-position","mask-repeat","mask-size","mask-type","max-block-size","max-height","max-inline-size","max-width","min-block-size","min-height","min-inline-size","min-width","mix-blend-mode","nav-down","nav-index","nav-left","nav-right","nav-up","none","normal","object-fit","object-position","opacity","order","orphans","outline","outline-color","outline-offset","outline-style","outline-width","overflow","overflow-wrap","overflow-x","overflow-y","padding","padding-block","padding-block-end","padding-block-start","padding-bottom","padding-inline","padding-inline-end","padding-inline-start","padding-left","padding-right","padding-top","page-break-after","page-break-before","page-break-inside","pause","pause-after","pause-before","perspective","perspective-origin","pointer-events","position","quotes","resize","rest","rest-after","rest-before","right","row-gap","scroll-margin","scroll-margin-block","scroll-margin-block-end","scroll-margin-block-start","scroll-margin-bottom","scroll-margin-inline","scroll-margin-inline-end","scroll-margin-inline-start","scroll-margin-left","scroll-margin-right","scroll-margin-top","scroll-padding","scroll-padding-block","scroll-padding-block-end","scroll-padding-block-start","scroll-padding-bottom","scroll-padding-inline","scroll-padding-inline-end","scroll-padding-inline-start","scroll-padding-left","scroll-padding-right","scroll-padding-top","scroll-snap-align","scroll-snap-stop","scroll-snap-type","scrollbar-color","scrollbar-gutter","scrollbar-width","shape-image-threshold","shape-margin","shape-outside","speak","speak-as","src","tab-size","table-layout","text-align","text-align-all","text-align-last","text-combine-upright","text-decoration","text-decoration-color","text-decoration-line","text-decoration-style","text-emphasis","text-emphasis-color","text-emphasis-position","text-emphasis-style","text-indent","text-justify","text-orientation","text-overflow","text-rendering","text-shadow","text-transform","text-underline-position","top","transform","transform-box","transform-origin","transform-style","transition","transition-delay","transition-duration","transition-property","transition-timing-function","unicode-bidi","vertical-align","visibility","voice-balance","voice-duration","voice-family","voice-pitch","voice-range","voice-rate","voice-stress","voice-volume","white-space","widows","width","will-change","word-break","word-spacing","word-wrap","writing-mode","z-index"].reverse() +;return n=>{const a=n.regex,l=(e=>({IMPORTANT:{scope:"meta",begin:"!important"}, +BLOCK_COMMENT:e.C_BLOCK_COMMENT_MODE,HEXCOLOR:{scope:"number", +begin:/#(([0-9a-fA-F]{3,4})|(([0-9a-fA-F]{2}){3,4}))\b/},FUNCTION_DISPATCH:{ +className:"built_in",begin:/[\w-]+(?=\()/},ATTRIBUTE_SELECTOR_MODE:{ +scope:"selector-attr",begin:/\[/,end:/\]/,illegal:"$", +contains:[e.APOS_STRING_MODE,e.QUOTE_STRING_MODE]},CSS_NUMBER_MODE:{ +scope:"number", +begin:e.NUMBER_RE+"(%|em|ex|ch|rem|vw|vh|vmin|vmax|cm|mm|in|pt|pc|px|deg|grad|rad|turn|s|ms|Hz|kHz|dpi|dpcm|dppx)?", +relevance:0},CSS_VARIABLE:{className:"attr",begin:/--[A-Za-z][A-Za-z0-9_-]*/} +}))(n),s=[n.APOS_STRING_MODE,n.QUOTE_STRING_MODE];return{name:"CSS", +case_insensitive:!0,illegal:/[=|'\$]/,keywords:{keyframePosition:"from to"}, +classNameAliases:{keyframePosition:"selector-tag"},contains:[l.BLOCK_COMMENT,{ +begin:/-(webkit|moz|ms|o)-(?=[a-z])/},l.CSS_NUMBER_MODE,{ +className:"selector-id",begin:/#[A-Za-z0-9_-]+/,relevance:0},{ +className:"selector-class",begin:"\\.[a-zA-Z-][a-zA-Z0-9_-]*",relevance:0 +},l.ATTRIBUTE_SELECTOR_MODE,{className:"selector-pseudo",variants:[{ +begin:":("+r.join("|")+")"},{begin:":(:)?("+t.join("|")+")"}]},l.CSS_VARIABLE,{ +className:"attribute",begin:"\\b("+o.join("|")+")\\b"},{begin:/:/,end:/[;}{]/, +contains:[l.BLOCK_COMMENT,l.HEXCOLOR,l.IMPORTANT,l.CSS_NUMBER_MODE,...s,{ +begin:/(url|data-uri)\(/,end:/\)/,relevance:0,keywords:{built_in:"url data-uri" +},contains:[...s,{className:"string",begin:/[^)]/,endsWithParent:!0, +excludeEnd:!0}]},l.FUNCTION_DISPATCH]},{begin:a.lookahead(/@/),end:"[{;]", +relevance:0,illegal:/:/,contains:[{className:"keyword",begin:/@-?\w[\w]*(-\w+)*/ +},{begin:/\s/,endsWithParent:!0,excludeEnd:!0,relevance:0,keywords:{ +$pattern:/[a-z-]+/,keyword:"and or not only",attribute:i.join(" ")},contains:[{ +begin:/[a-z-]+(?=:)/,className:"attribute"},...s,l.CSS_NUMBER_MODE]}]},{ +className:"selector-tag",begin:"\\b("+e.join("|")+")\\b"}]}}})() +;hljs.registerLanguage("css",e)})();/*! `plaintext` grammar compiled for Highlight.js 11.7.0 */ +(()=>{var t=(()=>{"use strict";return t=>({name:"Plain text", +aliases:["text","txt"],disableAutodetect:!0})})() +;hljs.registerLanguage("plaintext",t)})();/*! `scss` grammar compiled for Highlight.js 11.7.0 */ +(()=>{var e=(()=>{"use strict" +;const e=["a","abbr","address","article","aside","audio","b","blockquote","body","button","canvas","caption","cite","code","dd","del","details","dfn","div","dl","dt","em","fieldset","figcaption","figure","footer","form","h1","h2","h3","h4","h5","h6","header","hgroup","html","i","iframe","img","input","ins","kbd","label","legend","li","main","mark","menu","nav","object","ol","p","q","quote","samp","section","span","strong","summary","sup","table","tbody","td","textarea","tfoot","th","thead","time","tr","ul","var","video"],r=["any-hover","any-pointer","aspect-ratio","color","color-gamut","color-index","device-aspect-ratio","device-height","device-width","display-mode","forced-colors","grid","height","hover","inverted-colors","monochrome","orientation","overflow-block","overflow-inline","pointer","prefers-color-scheme","prefers-contrast","prefers-reduced-motion","prefers-reduced-transparency","resolution","scan","scripting","update","width","min-width","max-width","min-height","max-height"],i=["active","any-link","blank","checked","current","default","defined","dir","disabled","drop","empty","enabled","first","first-child","first-of-type","fullscreen","future","focus","focus-visible","focus-within","has","host","host-context","hover","indeterminate","in-range","invalid","is","lang","last-child","last-of-type","left","link","local-link","not","nth-child","nth-col","nth-last-child","nth-last-col","nth-last-of-type","nth-of-type","only-child","only-of-type","optional","out-of-range","past","placeholder-shown","read-only","read-write","required","right","root","scope","target","target-within","user-invalid","valid","visited","where"],t=["after","backdrop","before","cue","cue-region","first-letter","first-line","grammar-error","marker","part","placeholder","selection","slotted","spelling-error"],o=["align-content","align-items","align-self","all","animation","animation-delay","animation-direction","animation-duration","animation-fill-mode","animation-iteration-count","animation-name","animation-play-state","animation-timing-function","backface-visibility","background","background-attachment","background-blend-mode","background-clip","background-color","background-image","background-origin","background-position","background-repeat","background-size","block-size","border","border-block","border-block-color","border-block-end","border-block-end-color","border-block-end-style","border-block-end-width","border-block-start","border-block-start-color","border-block-start-style","border-block-start-width","border-block-style","border-block-width","border-bottom","border-bottom-color","border-bottom-left-radius","border-bottom-right-radius","border-bottom-style","border-bottom-width","border-collapse","border-color","border-image","border-image-outset","border-image-repeat","border-image-slice","border-image-source","border-image-width","border-inline","border-inline-color","border-inline-end","border-inline-end-color","border-inline-end-style","border-inline-end-width","border-inline-start","border-inline-start-color","border-inline-start-style","border-inline-start-width","border-inline-style","border-inline-width","border-left","border-left-color","border-left-style","border-left-width","border-radius","border-right","border-right-color","border-right-style","border-right-width","border-spacing","border-style","border-top","border-top-color","border-top-left-radius","border-top-right-radius","border-top-style","border-top-width","border-width","bottom","box-decoration-break","box-shadow","box-sizing","break-after","break-before","break-inside","caption-side","caret-color","clear","clip","clip-path","clip-rule","color","column-count","column-fill","column-gap","column-rule","column-rule-color","column-rule-style","column-rule-width","column-span","column-width","columns","contain","content","content-visibility","counter-increment","counter-reset","cue","cue-after","cue-before","cursor","direction","display","empty-cells","filter","flex","flex-basis","flex-direction","flex-flow","flex-grow","flex-shrink","flex-wrap","float","flow","font","font-display","font-family","font-feature-settings","font-kerning","font-language-override","font-size","font-size-adjust","font-smoothing","font-stretch","font-style","font-synthesis","font-variant","font-variant-caps","font-variant-east-asian","font-variant-ligatures","font-variant-numeric","font-variant-position","font-variation-settings","font-weight","gap","glyph-orientation-vertical","grid","grid-area","grid-auto-columns","grid-auto-flow","grid-auto-rows","grid-column","grid-column-end","grid-column-start","grid-gap","grid-row","grid-row-end","grid-row-start","grid-template","grid-template-areas","grid-template-columns","grid-template-rows","hanging-punctuation","height","hyphens","icon","image-orientation","image-rendering","image-resolution","ime-mode","inline-size","isolation","justify-content","left","letter-spacing","line-break","line-height","list-style","list-style-image","list-style-position","list-style-type","margin","margin-block","margin-block-end","margin-block-start","margin-bottom","margin-inline","margin-inline-end","margin-inline-start","margin-left","margin-right","margin-top","marks","mask","mask-border","mask-border-mode","mask-border-outset","mask-border-repeat","mask-border-slice","mask-border-source","mask-border-width","mask-clip","mask-composite","mask-image","mask-mode","mask-origin","mask-position","mask-repeat","mask-size","mask-type","max-block-size","max-height","max-inline-size","max-width","min-block-size","min-height","min-inline-size","min-width","mix-blend-mode","nav-down","nav-index","nav-left","nav-right","nav-up","none","normal","object-fit","object-position","opacity","order","orphans","outline","outline-color","outline-offset","outline-style","outline-width","overflow","overflow-wrap","overflow-x","overflow-y","padding","padding-block","padding-block-end","padding-block-start","padding-bottom","padding-inline","padding-inline-end","padding-inline-start","padding-left","padding-right","padding-top","page-break-after","page-break-before","page-break-inside","pause","pause-after","pause-before","perspective","perspective-origin","pointer-events","position","quotes","resize","rest","rest-after","rest-before","right","row-gap","scroll-margin","scroll-margin-block","scroll-margin-block-end","scroll-margin-block-start","scroll-margin-bottom","scroll-margin-inline","scroll-margin-inline-end","scroll-margin-inline-start","scroll-margin-left","scroll-margin-right","scroll-margin-top","scroll-padding","scroll-padding-block","scroll-padding-block-end","scroll-padding-block-start","scroll-padding-bottom","scroll-padding-inline","scroll-padding-inline-end","scroll-padding-inline-start","scroll-padding-left","scroll-padding-right","scroll-padding-top","scroll-snap-align","scroll-snap-stop","scroll-snap-type","scrollbar-color","scrollbar-gutter","scrollbar-width","shape-image-threshold","shape-margin","shape-outside","speak","speak-as","src","tab-size","table-layout","text-align","text-align-all","text-align-last","text-combine-upright","text-decoration","text-decoration-color","text-decoration-line","text-decoration-style","text-emphasis","text-emphasis-color","text-emphasis-position","text-emphasis-style","text-indent","text-justify","text-orientation","text-overflow","text-rendering","text-shadow","text-transform","text-underline-position","top","transform","transform-box","transform-origin","transform-style","transition","transition-delay","transition-duration","transition-property","transition-timing-function","unicode-bidi","vertical-align","visibility","voice-balance","voice-duration","voice-family","voice-pitch","voice-range","voice-rate","voice-stress","voice-volume","white-space","widows","width","will-change","word-break","word-spacing","word-wrap","writing-mode","z-index"].reverse() +;return n=>{const a=(e=>({IMPORTANT:{scope:"meta",begin:"!important"}, +BLOCK_COMMENT:e.C_BLOCK_COMMENT_MODE,HEXCOLOR:{scope:"number", +begin:/#(([0-9a-fA-F]{3,4})|(([0-9a-fA-F]{2}){3,4}))\b/},FUNCTION_DISPATCH:{ +className:"built_in",begin:/[\w-]+(?=\()/},ATTRIBUTE_SELECTOR_MODE:{ +scope:"selector-attr",begin:/\[/,end:/\]/,illegal:"$", +contains:[e.APOS_STRING_MODE,e.QUOTE_STRING_MODE]},CSS_NUMBER_MODE:{ +scope:"number", +begin:e.NUMBER_RE+"(%|em|ex|ch|rem|vw|vh|vmin|vmax|cm|mm|in|pt|pc|px|deg|grad|rad|turn|s|ms|Hz|kHz|dpi|dpcm|dppx)?", +relevance:0},CSS_VARIABLE:{className:"attr",begin:/--[A-Za-z][A-Za-z0-9_-]*/} +}))(n),l=t,s=i,d="@[a-z-]+",c={className:"variable", +begin:"(\\$[a-zA-Z-][a-zA-Z0-9_-]*)\\b",relevance:0};return{name:"SCSS", +case_insensitive:!0,illegal:"[=/|']", +contains:[n.C_LINE_COMMENT_MODE,n.C_BLOCK_COMMENT_MODE,a.CSS_NUMBER_MODE,{ +className:"selector-id",begin:"#[A-Za-z0-9_-]+",relevance:0},{ +className:"selector-class",begin:"\\.[A-Za-z0-9_-]+",relevance:0 +},a.ATTRIBUTE_SELECTOR_MODE,{className:"selector-tag", +begin:"\\b("+e.join("|")+")\\b",relevance:0},{className:"selector-pseudo", +begin:":("+s.join("|")+")"},{className:"selector-pseudo", +begin:":(:)?("+l.join("|")+")"},c,{begin:/\(/,end:/\)/, +contains:[a.CSS_NUMBER_MODE]},a.CSS_VARIABLE,{className:"attribute", +begin:"\\b("+o.join("|")+")\\b"},{ +begin:"\\b(whitespace|wait|w-resize|visible|vertical-text|vertical-ideographic|uppercase|upper-roman|upper-alpha|underline|transparent|top|thin|thick|text|text-top|text-bottom|tb-rl|table-header-group|table-footer-group|sw-resize|super|strict|static|square|solid|small-caps|separate|se-resize|scroll|s-resize|rtl|row-resize|ridge|right|repeat|repeat-y|repeat-x|relative|progress|pointer|overline|outside|outset|oblique|nowrap|not-allowed|normal|none|nw-resize|no-repeat|no-drop|newspaper|ne-resize|n-resize|move|middle|medium|ltr|lr-tb|lowercase|lower-roman|lower-alpha|loose|list-item|line|line-through|line-edge|lighter|left|keep-all|justify|italic|inter-word|inter-ideograph|inside|inset|inline|inline-block|inherit|inactive|ideograph-space|ideograph-parenthesis|ideograph-numeric|ideograph-alpha|horizontal|hidden|help|hand|groove|fixed|ellipsis|e-resize|double|dotted|distribute|distribute-space|distribute-letter|distribute-all-lines|disc|disabled|default|decimal|dashed|crosshair|collapse|col-resize|circle|char|center|capitalize|break-word|break-all|bottom|both|bolder|bold|block|bidi-override|below|baseline|auto|always|all-scroll|absolute|table|table-cell)\\b" +},{begin:/:/,end:/[;}{]/,relevance:0, +contains:[a.BLOCK_COMMENT,c,a.HEXCOLOR,a.CSS_NUMBER_MODE,n.QUOTE_STRING_MODE,n.APOS_STRING_MODE,a.IMPORTANT,a.FUNCTION_DISPATCH] +},{begin:"@(page|font-face)",keywords:{$pattern:d,keyword:"@page @font-face"}},{ +begin:"@",end:"[{;]",returnBegin:!0,keywords:{$pattern:/[a-z-]+/, +keyword:"and or not only",attribute:r.join(" ")},contains:[{begin:d, +className:"keyword"},{begin:/[a-z-]+(?=:)/,className:"attribute" +},c,n.QUOTE_STRING_MODE,n.APOS_STRING_MODE,a.HEXCOLOR,a.CSS_NUMBER_MODE] +},a.FUNCTION_DISPATCH]}}})();hljs.registerLanguage("scss",e)})();/*! `typescript` grammar compiled for Highlight.js 11.7.0 */ +(()=>{var e=(()=>{"use strict" +;const e="[A-Za-z$_][0-9A-Za-z$_]*",n=["as","in","of","if","for","while","finally","var","new","function","do","return","void","else","break","catch","instanceof","with","throw","case","default","try","switch","continue","typeof","delete","let","yield","const","class","debugger","async","await","static","import","from","export","extends"],a=["true","false","null","undefined","NaN","Infinity"],t=["Object","Function","Boolean","Symbol","Math","Date","Number","BigInt","String","RegExp","Array","Float32Array","Float64Array","Int8Array","Uint8Array","Uint8ClampedArray","Int16Array","Int32Array","Uint16Array","Uint32Array","BigInt64Array","BigUint64Array","Set","Map","WeakSet","WeakMap","ArrayBuffer","SharedArrayBuffer","Atomics","DataView","JSON","Promise","Generator","GeneratorFunction","AsyncFunction","Reflect","Proxy","Intl","WebAssembly"],s=["Error","EvalError","InternalError","RangeError","ReferenceError","SyntaxError","TypeError","URIError"],c=["setInterval","setTimeout","clearInterval","clearTimeout","require","exports","eval","isFinite","isNaN","parseFloat","parseInt","decodeURI","decodeURIComponent","encodeURI","encodeURIComponent","escape","unescape"],r=["arguments","this","super","console","window","document","localStorage","module","global"],i=[].concat(c,t,s) +;function o(o){const l=o.regex,d=e,b={begin:/<[A-Za-z0-9\\._:-]+/, +end:/\/[A-Za-z0-9\\._:-]+>|\/>/,isTrulyOpeningTag:(e,n)=>{ +const a=e[0].length+e.index,t=e.input[a] +;if("<"===t||","===t)return void n.ignoreMatch();let s +;">"===t&&(((e,{after:n})=>{const a="",M={ +match:[/const|var|let/,/\s+/,d,/\s*/,/=\s*/,/(async\s*)?/,l.lookahead(T)], +keywords:"async",className:{1:"keyword",3:"title.function"},contains:[S]} +;return{name:"Javascript",aliases:["js","jsx","mjs","cjs"],keywords:g,exports:{ +PARAMS_CONTAINS:v,CLASS_REFERENCE:R},illegal:/#(?![$_A-z])/, +contains:[o.SHEBANG({label:"shebang",binary:"node",relevance:5}),{ +label:"use_strict",className:"meta",relevance:10, +begin:/^\s*['"]use (strict|asm)['"]/ +},o.APOS_STRING_MODE,o.QUOTE_STRING_MODE,A,p,_,N,{match:/\$\d+/},E,R,{ +className:"attr",begin:d+l.lookahead(":"),relevance:0},M,{ +begin:"("+o.RE_STARTERS_RE+"|\\b(case|return|throw)\\b)\\s*", +keywords:"return throw case",relevance:0,contains:[N,o.REGEXP_MODE,{ +className:"function",begin:T,returnBegin:!0,end:"\\s*=>",contains:[{ +className:"params",variants:[{begin:o.UNDERSCORE_IDENT_RE,relevance:0},{ +className:null,begin:/\(\s*\)/,skip:!0},{begin:/\(/,end:/\)/,excludeBegin:!0, +excludeEnd:!0,keywords:g,contains:v}]}]},{begin:/,/,relevance:0},{match:/\s+/, +relevance:0},{variants:[{begin:"<>",end:""},{ +match:/<[A-Za-z0-9\\._:-]+\s*\/>/},{begin:b.begin, +"on:begin":b.isTrulyOpeningTag,end:b.end}],subLanguage:"xml",contains:[{ +begin:b.begin,end:b.end,skip:!0,contains:["self"]}]}]},x,{ +beginKeywords:"while if switch catch for"},{ +begin:"\\b(?!function)"+o.UNDERSCORE_IDENT_RE+"\\([^()]*(\\([^()]*(\\([^()]*\\)[^()]*)*\\)[^()]*)*\\)\\s*\\{", +returnBegin:!0,label:"func.def",contains:[S,o.inherit(o.TITLE_MODE,{begin:d, +className:"title.function"})]},{match:/\.\.\./,relevance:0},I,{match:"\\$"+d, +relevance:0},{match:[/\bconstructor(?=\s*\()/],className:{1:"title.function"}, +contains:[S]},k,{relevance:0,match:/\b[A-Z][A-Z_0-9]+\b/, +className:"variable.constant"},w,C,{match:/\$[(.]/}]}}return t=>{ +const s=o(t),c=["any","void","number","boolean","string","object","never","symbol","bigint","unknown"],l={ +beginKeywords:"namespace",end:/\{/,excludeEnd:!0, +contains:[s.exports.CLASS_REFERENCE]},d={beginKeywords:"interface",end:/\{/, +excludeEnd:!0,keywords:{keyword:"interface extends",built_in:c}, +contains:[s.exports.CLASS_REFERENCE]},b={$pattern:e, +keyword:n.concat(["type","namespace","interface","public","private","protected","implements","declare","abstract","readonly","enum","override"]), +literal:a,built_in:i.concat(c),"variable.language":r},g={className:"meta", +begin:"@[A-Za-z$_][0-9A-Za-z$_]*"},u=(e,n,a)=>{ +const t=e.contains.findIndex((e=>e.label===n)) +;if(-1===t)throw Error("can not find mode to replace");e.contains.splice(t,1,a)} +;return Object.assign(s.keywords,b), +s.exports.PARAMS_CONTAINS.push(g),s.contains=s.contains.concat([g,l,d]), +u(s,"shebang",t.SHEBANG()),u(s,"use_strict",{className:"meta",relevance:10, +begin:/^\s*['"]use strict['"]/ +}),s.contains.find((e=>"func.def"===e.label)).relevance=0,Object.assign(s,{ +name:"TypeScript",aliases:["ts","tsx"]}),s}})() +;hljs.registerLanguage("typescript",e)})();/*! `ruby` grammar compiled for Highlight.js 11.7.0 */ +(()=>{var e=(()=>{"use strict";return e=>{ +const n=e.regex,a="([a-zA-Z_]\\w*[!?=]?|[-+~]@|<<|>>|=~|===?|<=>|[<>]=?|\\*\\*|[-/+%^&*~`|]|\\[\\]=?)",s=n.either(/\b([A-Z]+[a-z0-9]+)+/,/\b([A-Z]+[a-z0-9]+)+[A-Z]+/),i=n.concat(s,/(::\w+)*/),t={ +"variable.constant":["__FILE__","__LINE__","__ENCODING__"], +"variable.language":["self","super"], +keyword:["alias","and","begin","BEGIN","break","case","class","defined","do","else","elsif","end","END","ensure","for","if","in","module","next","not","or","redo","require","rescue","retry","return","then","undef","unless","until","when","while","yield","include","extend","prepend","public","private","protected","raise","throw"], +built_in:["proc","lambda","attr_accessor","attr_reader","attr_writer","define_method","private_constant","module_function"], +literal:["true","false","nil"]},c={className:"doctag",begin:"@[A-Za-z]+"},r={ +begin:"#<",end:">"},b=[e.COMMENT("#","$",{contains:[c] +}),e.COMMENT("^=begin","^=end",{contains:[c],relevance:10 +}),e.COMMENT("^__END__",e.MATCH_NOTHING_RE)],l={className:"subst",begin:/#\{/, +end:/\}/,keywords:t},d={className:"string",contains:[e.BACKSLASH_ESCAPE,l], +variants:[{begin:/'/,end:/'/},{begin:/"/,end:/"/},{begin:/`/,end:/`/},{ +begin:/%[qQwWx]?\(/,end:/\)/},{begin:/%[qQwWx]?\[/,end:/\]/},{ +begin:/%[qQwWx]?\{/,end:/\}/},{begin:/%[qQwWx]?/},{begin:/%[qQwWx]?\//, +end:/\//},{begin:/%[qQwWx]?%/,end:/%/},{begin:/%[qQwWx]?-/,end:/-/},{ +begin:/%[qQwWx]?\|/,end:/\|/},{begin:/\B\?(\\\d{1,3})/},{ +begin:/\B\?(\\x[A-Fa-f0-9]{1,2})/},{begin:/\B\?(\\u\{?[A-Fa-f0-9]{1,6}\}?)/},{ +begin:/\B\?(\\M-\\C-|\\M-\\c|\\c\\M-|\\M-|\\C-\\M-)[\x20-\x7e]/},{ +begin:/\B\?\\(c|C-)[\x20-\x7e]/},{begin:/\B\?\\?\S/},{ +begin:n.concat(/<<[-~]?'?/,n.lookahead(/(\w+)(?=\W)[^\n]*\n(?:[^\n]*\n)*?\s*\1\b/)), +contains:[e.END_SAME_AS_BEGIN({begin:/(\w+)/,end:/(\w+)/, +contains:[e.BACKSLASH_ESCAPE,l]})]}]},o="[0-9](_?[0-9])*",g={className:"number", +relevance:0,variants:[{ +begin:`\\b([1-9](_?[0-9])*|0)(\\.(${o}))?([eE][+-]?(${o})|r)?i?\\b`},{ +begin:"\\b0[dD][0-9](_?[0-9])*r?i?\\b"},{begin:"\\b0[bB][0-1](_?[0-1])*r?i?\\b" +},{begin:"\\b0[oO][0-7](_?[0-7])*r?i?\\b"},{ +begin:"\\b0[xX][0-9a-fA-F](_?[0-9a-fA-F])*r?i?\\b"},{ +begin:"\\b0(_?[0-7])+r?i?\\b"}]},_={variants:[{match:/\(\)/},{ +className:"params",begin:/\(/,end:/(?=\))/,excludeBegin:!0,endsParent:!0, +keywords:t}]},u=[d,{variants:[{match:[/class\s+/,i,/\s+<\s+/,i]},{ +match:[/\b(class|module)\s+/,i]}],scope:{2:"title.class", +4:"title.class.inherited"},keywords:t},{match:[/(include|extend)\s+/,i],scope:{ +2:"title.class"},keywords:t},{relevance:0,match:[i,/\.new[. (]/],scope:{ +1:"title.class"}},{relevance:0,match:/\b[A-Z][A-Z_0-9]+\b/, +className:"variable.constant"},{relevance:0,match:s,scope:"title.class"},{ +match:[/def/,/\s+/,a],scope:{1:"keyword",3:"title.function"},contains:[_]},{ +begin:e.IDENT_RE+"::"},{className:"symbol", +begin:e.UNDERSCORE_IDENT_RE+"(!|\\?)?:",relevance:0},{className:"symbol", +begin:":(?!\\s)",contains:[d,{begin:a}],relevance:0},g,{className:"variable", +begin:"(\\$\\W)|((\\$|@@?)(\\w+))(?=[^@$?])(?![A-Za-z])(?![@$?'])"},{ +className:"params",begin:/\|/,end:/\|/,excludeBegin:!0,excludeEnd:!0, +relevance:0,keywords:t},{begin:"("+e.RE_STARTERS_RE+"|unless)\\s*", +keywords:"unless",contains:[{className:"regexp",contains:[e.BACKSLASH_ESCAPE,l], +illegal:/\n/,variants:[{begin:"/",end:"/[a-z]*"},{begin:/%r\{/,end:/\}[a-z]*/},{ +begin:"%r\\(",end:"\\)[a-z]*"},{begin:"%r!",end:"![a-z]*"},{begin:"%r\\[", +end:"\\][a-z]*"}]}].concat(r,b),relevance:0}].concat(r,b) +;l.contains=u,_.contains=u;const m=[{begin:/^\s*=>/,starts:{end:"$",contains:u} +},{className:"meta.prompt", +begin:"^([>?]>|[\\w#]+\\(\\w+\\):\\d+:\\d+[>*]|(\\w+-)?\\d+\\.\\d+\\.\\d+(p\\d+)?[^\\d][^>]+>)(?=[ ])", +starts:{end:"$",keywords:t,contains:u}}];return b.unshift(r),{name:"Ruby", +aliases:["rb","gemspec","podspec","thor","irb"],keywords:t,illegal:/\/\*/, +contains:[e.SHEBANG({binary:"ruby"})].concat(m).concat(b).concat(u)}}})() +;hljs.registerLanguage("ruby",e)})();/*! `yaml` grammar compiled for Highlight.js 11.7.0 */ +(()=>{var e=(()=>{"use strict";return e=>{ +const n="true false yes no null",a="[\\w#;/?:@&=+$,.~*'()[\\]]+",s={ +className:"string",relevance:0,variants:[{begin:/'/,end:/'/},{begin:/"/,end:/"/ +},{begin:/\S+/}],contains:[e.BACKSLASH_ESCAPE,{className:"template-variable", +variants:[{begin:/\{\{/,end:/\}\}/},{begin:/%\{/,end:/\}/}]}]},i=e.inherit(s,{ +variants:[{begin:/'/,end:/'/},{begin:/"/,end:/"/},{begin:/[^\s,{}[\]]+/}]}),l={ +end:",",endsWithParent:!0,excludeEnd:!0,keywords:n,relevance:0},t={begin:/\{/, +end:/\}/,contains:[l],illegal:"\\n",relevance:0},g={begin:"\\[",end:"\\]", +contains:[l],illegal:"\\n",relevance:0},b=[{className:"attr",variants:[{ +begin:"\\w[\\w :\\/.-]*:(?=[ \t]|$)"},{begin:'"\\w[\\w :\\/.-]*":(?=[ \t]|$)'},{ +begin:"'\\w[\\w :\\/.-]*':(?=[ \t]|$)"}]},{className:"meta",begin:"^---\\s*$", +relevance:10},{className:"string", +begin:"[\\|>]([1-9]?[+-])?[ ]*\\n( +)[^ ][^\\n]*\\n(\\2[^\\n]+\\n?)*"},{ +begin:"<%[%=-]?",end:"[%-]?%>",subLanguage:"ruby",excludeBegin:!0,excludeEnd:!0, +relevance:0},{className:"type",begin:"!\\w+!"+a},{className:"type", +begin:"!<"+a+">"},{className:"type",begin:"!"+a},{className:"type",begin:"!!"+a +},{className:"meta",begin:"&"+e.UNDERSCORE_IDENT_RE+"$"},{className:"meta", +begin:"\\*"+e.UNDERSCORE_IDENT_RE+"$"},{className:"bullet",begin:"-(?=[ ]|$)", +relevance:0},e.HASH_COMMENT_MODE,{beginKeywords:n,keywords:{literal:n}},{ +className:"number", +begin:"\\b[0-9]{4}(-[0-9][0-9]){0,2}([Tt \\t][0-9][0-9]?(:[0-9][0-9]){2})?(\\.[0-9]*)?([ \\t])*(Z|[-+][0-9][0-9]?(:[0-9][0-9])?)?\\b" +},{className:"number",begin:e.C_NUMBER_RE+"\\b",relevance:0},t,g,s],r=[...b] +;return r.pop(),r.push(i),l.contains=r,{name:"YAML",case_insensitive:!0, +aliases:["yml"],contains:b}}})();hljs.registerLanguage("yaml",e)})();/*! `markdown` grammar compiled for Highlight.js 11.7.0 */ +(()=>{var e=(()=>{"use strict";return e=>{const n={begin:/<\/?[A-Za-z_]/, +end:">",subLanguage:"xml",relevance:0},a={variants:[{begin:/\[.+?\]\[.*?\]/, +relevance:0},{ +begin:/\[.+?\]\(((data|javascript|mailto):|(?:http|ftp)s?:\/\/).*?\)/, +relevance:2},{ +begin:e.regex.concat(/\[.+?\]\(/,/[A-Za-z][A-Za-z0-9+.-]*/,/:\/\/.*?\)/), +relevance:2},{begin:/\[.+?\]\([./?&#].*?\)/,relevance:1},{ +begin:/\[.*?\]\(.*?\)/,relevance:0}],returnBegin:!0,contains:[{match:/\[(?=\])/ +},{className:"string",relevance:0,begin:"\\[",end:"\\]",excludeBegin:!0, +returnEnd:!0},{className:"link",relevance:0,begin:"\\]\\(",end:"\\)", +excludeBegin:!0,excludeEnd:!0},{className:"symbol",relevance:0,begin:"\\]\\[", +end:"\\]",excludeBegin:!0,excludeEnd:!0}]},i={className:"strong",contains:[], +variants:[{begin:/_{2}(?!\s)/,end:/_{2}/},{begin:/\*{2}(?!\s)/,end:/\*{2}/}] +},s={className:"emphasis",contains:[],variants:[{begin:/\*(?![*\s])/,end:/\*/},{ +begin:/_(?![_\s])/,end:/_/,relevance:0}]},c=e.inherit(i,{contains:[] +}),t=e.inherit(s,{contains:[]});i.contains.push(t),s.contains.push(c) +;let g=[n,a];return[i,s,c,t].forEach((e=>{e.contains=e.contains.concat(g) +})),g=g.concat(i,s),{name:"Markdown",aliases:["md","mkdown","mkd"],contains:[{ +className:"section",variants:[{begin:"^#{1,6}",end:"$",contains:g},{ +begin:"(?=^.+?\\n[=-]{2,}$)",contains:[{begin:"^[=-]*$"},{begin:"^",end:"\\n", +contains:g}]}]},n,{className:"bullet",begin:"^[ \t]*([*+-]|(\\d+\\.))(?=\\s+)", +end:"\\s+",excludeEnd:!0},i,s,{className:"quote",begin:"^>\\s+",contains:g, +end:"$"},{className:"code",variants:[{begin:"(`{3,})[^`](.|\\n)*?\\1`*[ ]*"},{ +begin:"(~{3,})[^~](.|\\n)*?\\1~*[ ]*"},{begin:"```",end:"```+[ ]*$"},{ +begin:"~~~",end:"~~~+[ ]*$"},{begin:"`.+?`"},{begin:"(?=^( {4}|\\t))", +contains:[{begin:"^( {4}|\\t)",end:"(\\n)$"}],relevance:0}]},{ +begin:"^[-\\*]{3,}",end:"$"},a,{begin:/^\[[^\n]+\]:/,returnBegin:!0,contains:[{ +className:"symbol",begin:/\[/,end:/\]/,excludeBegin:!0,excludeEnd:!0},{ +className:"link",begin:/:\s*/,end:/$/,excludeBegin:!0}]}]}}})() +;hljs.registerLanguage("markdown",e)})(); \ No newline at end of file diff --git a/system/typemill/Controllers/ControllerApiImage.php b/system/typemill/Controllers/ControllerApiImage.php new file mode 100644 index 0000000..dc16154 --- /dev/null +++ b/system/typemill/Controllers/ControllerApiImage.php @@ -0,0 +1,335 @@ +getParsedBody(); + + if(!isset($params['image']) OR !isset($params['name'])) + { + $response->getBody()->write(json_encode([ + 'message' => 'Image or name is missing.', + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(400); + } + + $img = new ProcessImage(); + + if($this->settingActive('allowsvg')) + { + $img->addAllowedExtension('svg'); + } + + # prepare the image + if(!$img->prepareImage($params['image'], $params['name'])) + { + $response->getBody()->write(json_encode([ + 'message' => $img->errors[0], + 'fullerrors' => $img->errors, + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(400); + } + + # check if image name already exisits in live folder and create an unique name (do not overwrite existing files) + $storage = new StorageWrapper('\Typemill\Models\Storage'); + $uniqueImageName = $storage->createUniqueImageName($img->getFilename(), $img->getExtension()); + $img->setFilename($uniqueImageName); + + # store the original image + if(!$img->storeOriginalToTmp()) + { + $response->getBody()->write(json_encode([ + 'message' => $img->errors[0], + 'fullerrors' => $img->errors, + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(400); + } + + # if image is not resizable (animated gif or svg) + if(!$img->isResizable()) + { + if($img->saveOriginalForAll()) + { + $response->getBody()->write(json_encode([ + 'message' => 'Image saved successfully', + 'name' => 'media/live/' . $img->getFullName(), + ])); + + return $response->withHeader('Content-Type', 'application/json'); + } + + $response->getBody()->write(json_encode([ + 'message' => $img->errors[0], + 'fullerrors' => $img->errors, + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(400); + } + + # for all other image types, check if they should be transformed to webp + if($this->settingActive('convertwebp')) + { + $img->setExtension('webp'); + } + + if(!$img->storeRenditionsToTmp($this->settings['images'])) + { + $response->getBody()->write(json_encode([ + 'message' => $img->errors[0], + 'fullerrors' => $img->errors, + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(500); + } + +/* + if(isset($params['publish']) && $params['publish']) + { + if(!$img->publishImage($img->getFullName())) + { + $response->getBody()->write(json_encode([ + 'message' => $img->errors[0], + 'fullerrors' => $img->errors, + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(500); + } + } +*/ + $response->getBody()->write(json_encode([ + 'message' => 'Image saved successfully', + 'name' => 'media/tmp/' . $img->getFullName(), + ])); + + return $response->withHeader('Content-Type', 'application/json'); + + } + + + + + + + + + + + + + public function publishImage(Request $request, Response $response, $args) + { + $params = $request->getParsedBody(); + + $imageProcessor = new ProcessImage($this->settings['images']); + if(!$imageProcessor->checkFolders()) + { + return $response->withJson(['errors' => 'Please check if your media-folder exists and all folders inside are writable.'], 500); + } + + # check the resize modifier in the image markdown, set it to true and delete it from markdown + $noresize = false; + $markdown = isset($params['markdown']) ? $params['markdown'] : false; + + if($markdown && (strlen($markdown) > 9) && (substr($markdown, -9) == '|noresize') ) + { + $noresize = true; + $params['markdown'] = substr($markdown,0,-9); + } + + if($imageProcessor->publishImage($noresize)) + { + $request = $request->withParsedBody($params); + + $block = new ControllerAuthorBlockApi($this->c); + if($params['new']) + { + return $block->addBlock($request, $response, $args); + } + return $block->updateBlock($request, $response, $args); + } + + return $response->withJson(['errors' => 'could not store image to media folder'],500); + } + + + + + public function getMediaLibImages(Request $request, Response $response, $args) + { + # get params from call + $this->params = $request->getParsedBody(); + $this->uri = $request->getUri()->withUserInfo(''); + + $imageProcessor = new ProcessImage($this->settings['images']); + if(!$imageProcessor->checkFolders('images')) + { + return $response->withJson(['errors' => 'Please check if your media-folder exists and all folders inside are writable.'], 500); + } + + $imagelist = $imageProcessor->scanMediaFlat(); + + $response->getBody()->write(json_encode([ + 'images' => $imagelist + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(200); + } + + public function getImage(Request $request, Response $response, $args) + { + # get params from call + $this->params = $request->getParsedBody(); + $this->uri = $request->getUri()->withUserInfo(''); + + $this->setStructureDraft(); + + $imageProcessor = new ProcessImage($this->settings['images']); + if(!$imageProcessor->checkFolders('images')) + { + return $response->withJson(['errors' => 'Please check if your media-folder exists and all folders inside are writable.'], 500); + } + + $imageDetails = $imageProcessor->getImageDetails($this->params['name'], $this->structureDraft); + + if($imageDetails) + { + return $response->withJson(['image' => $imageDetails]); + } + + return $response->withJson(['errors' => 'Image not found or image name not valid.'], 404); + } + + public function deleteImage(Request $request, Response $response, $args) + { + # get params from call + $this->params = $request->getParams(); + $this->uri = $request->getUri()->withUserInfo(''); + + # minimum permission is that user is allowed to delete content + if(!$this->c->acl->isAllowed($_SESSION['role'], 'content', 'delete')) + { + return $response->withJson(array('data' => false, 'errors' => 'You are not allowed to delete images.'), 403); + } + + if(!isset($this->params['name'])) + { + return $response->withJson(['errors' => 'image name is missing'],500); + } + + $imageProcessor = new ProcessImage($this->settings['images']); + if(!$imageProcessor->checkFolders('images')) + { + return $response->withJson(['errors' => 'Please check if your media-folder exists and all folders inside are writable.'], 500); + } + + if($imageProcessor->deleteImage($this->params['name'])) + { + return $response->withJson(['errors' => false]); + } + + return $response->withJson(['errors' => 'Oops, looks like we could not delete all sizes of that image.'], 500); + } + + + public function saveVideoImage(Request $request, Response $response, $args) + { + /* get params from call */ + $this->params = $request->getParams(); + $this->uri = $request->getUri()->withUserInfo(''); + $class = false; + + $imageUrl = $this->params['markdown']; + + if(strpos($imageUrl, 'https://www.youtube.com/watch?v=') !== false) + { + $videoID = str_replace('https://www.youtube.com/watch?v=', '', $imageUrl); + $videoID = strpos($videoID, '&') ? substr($videoID, 0, strpos($videoID, '&')) : $videoID; + $class = 'youtube'; + } + if(strpos($imageUrl, 'https://youtu.be/') !== false) + { + $videoID = str_replace('https://youtu.be/', '', $imageUrl); + $videoID = strpos($videoID, '?') ? substr($videoID, 0, strpos($videoID, '?')) : $videoID; + $class = 'youtube'; + } + + if($class == 'youtube') + { + $videoURLmaxres = 'https://i1.ytimg.com/vi/' . $videoID . '/maxresdefault.jpg'; + $videoURL0 = 'https://i1.ytimg.com/vi/' . $videoID . '/0.jpg'; + } + + $ctx = stream_context_create(array( + 'https' => array( + 'timeout' => 1 + ) + ) + ); + + $imageData = @file_get_contents($videoURLmaxres, 0, $ctx); + if($imageData === false) + { + $imageData = @file_get_contents($videoURL0, 0, $ctx); + if($imageData === false) + { + return $response->withJson(array('errors' => 'could not get the video image')); + } + } + + $imageData64 = 'data:image/jpeg;base64,' . base64_encode($imageData); + $desiredSizes = ['live' => ['width' => 560, 'height' => 315]]; + $imageProcessor = new ProcessImage($this->settings['images']); + if(!$imageProcessor->checkFolders()) + { + return $response->withJson(['errors' => ['message' => 'Please check if your media-folder exists and all folders inside are writable.']], 500); + } + + $tmpImage = $imageProcessor->createImage($imageData64, $videoID, $desiredSizes); + + if(!$tmpImage) + { + return $response->withJson(array('errors' => 'could not create temporary image')); + } + + $imageUrl = $imageProcessor->publishImage(); + if($imageUrl) + { + $this->params['markdown'] = '![' . $class . '-video](' . $imageUrl . ' "click to load video"){#' . $videoID. ' .' . $class . '}'; + + $request = $request->withParsedBody($this->params); + $block = new ControllerAuthorBlockApi($this->c); + if($this->params['new']) + { + return $block->addBlock($request, $response, $args); + } + return $block->updateBlock($request, $response, $args); + } + + return $response->withJson(array('errors' => 'could not store the preview image')); + } +} diff --git a/system/typemill/Controllers/ControllerApiMediaOld.php b/system/typemill/Controllers/ControllerApiMediaOld.php new file mode 100644 index 0000000..3189b9e --- /dev/null +++ b/system/typemill/Controllers/ControllerApiMediaOld.php @@ -0,0 +1,663 @@ +getParsedBody(); + + $imageProcessor = new ProcessImage($this->settings['images']); + + if(!$imageProcessor->checkFolders('images')) + { + $response->getBody()->write(json_encode([ + 'message' => 'Please check if your media-folder exists and all folders inside are writable.' + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(403); + } + + $imageParts = explode(";base64,", $params['image']); + $imageType = explode("/", $imageParts[0]); + + if(!isset($imageType[1])) + { + $response->getBody()->write(json_encode([ + 'message' => 'We did not find an image type, the file might be corrupted.' + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(403); + } + + $acceptedTypes = [ + 'png' => true, + 'jpg' => true, + 'jpeg' => true, + 'gif' => true, + 'webp' => true, + ]; + + if(isset($this->settings['svg']) && $this->settings['svg']) + { + $acceptedTypes['svg+xml'] = true; + } + + if(!isset($acceptedTypes[$imageType[1]])) + { + $response->getBody()->write(json_encode([ + 'message' => 'The image type is not supported.' + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(403); + } + + $imageResult = $imageProcessor->createImage($params['image'], $params['name'], $this->settings['images']); + + if($imageResult) + { + if(is_array($imageResult) && isset($imageResult['errors'])) + { + return $response->withJson($imageResult,422); + } + + # publish image directly, used for example by image field for meta-tabs + if($params['publish']) + { + $imageProcessor->publishImage(); + } + return $response->withJson(['name' => 'media/live/' . $imageProcessor->getFullName(),'errors' => false]); + } + + $response->getBody()->write(json_encode([ + 'message' => 'could not store image to temporary folder.' + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(403); + } + + public function getMediaLibImages(Request $request, Response $response, $args) + { + # get params from call + $this->params = $request->getParsedBody(); + $this->uri = $request->getUri()->withUserInfo(''); + + $imageProcessor = new ProcessImage($this->settings['images']); + if(!$imageProcessor->checkFolders('images')) + { + return $response->withJson(['errors' => 'Please check if your media-folder exists and all folders inside are writable.'], 500); + } + + $imagelist = $imageProcessor->scanMediaFlat(); + + $response->getBody()->write(json_encode([ + 'images' => $imagelist + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(200); + } + + public function getMediaLibFiles(Request $request, Response $response, $args) + { + # get params from call + $this->params = $request->getParsedBody(); + $this->uri = $request->getUri()->withUserInfo(''); + + $fileProcessor = new ProcessFile(); + if(!$fileProcessor->checkFolders()) + { + return $response->withJson(['errors' => 'Please check if your media-folder exists and all folders inside are writable.'], 500); + } + + $filelist = $fileProcessor->scanFilesFlat(); + + $response->getBody()->write(json_encode([ + 'files' => $filelist + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(200); + } + + public function getImage(Request $request, Response $response, $args) + { + # get params from call + $this->params = $request->getParsedBody(); + $this->uri = $request->getUri()->withUserInfo(''); + + $this->setStructureDraft(); + + $imageProcessor = new ProcessImage($this->settings['images']); + if(!$imageProcessor->checkFolders('images')) + { + return $response->withJson(['errors' => 'Please check if your media-folder exists and all folders inside are writable.'], 500); + } + + $imageDetails = $imageProcessor->getImageDetails($this->params['name'], $this->structureDraft); + + if($imageDetails) + { + return $response->withJson(['image' => $imageDetails]); + } + + return $response->withJson(['errors' => 'Image not found or image name not valid.'], 404); + } + + public function getFile(Request $request, Response $response, $args) + { + # get params from call + $this->params = $request->getParams(); + $this->uri = $request->getUri()->withUserInfo(''); + + $this->setStructureDraft(); + + $fileProcessor = new ProcessFile(); + if(!$fileProcessor->checkFolders()) + { + return $response->withJson(['errors' => 'Please check if your media-folder exists and all folders inside are writable.'], 500); + } + + $fileDetails = $fileProcessor->getFileDetails($this->params['name'], $this->structureDraft); + + if($fileDetails) + { + return $response->withJson(['file' => $fileDetails]); + } + + return $response->withJson(['errors' => 'file not found or file name invalid'],404); + } + + public function getFileRestrictions(Request $request, Response $response, $args) + { + # get params from call + $this->params = $request->getParams(); + $this->uri = $request->getUri()->withUserInfo(''); + $restriction = 'all'; + + $userroles = $this->c->acl->getRoles(); + + if(isset($this->params['filename']) && $this->params['filename'] != '') + { + $writeYaml = new WriteYaml(); + $restrictions = $writeYaml->getYaml('media' . DIRECTORY_SEPARATOR . 'files', 'filerestrictions.yaml'); + if(isset($restrictions[$this->params['filename']])) + { + $restriction = $restrictions[$this->params['filename']]; + } + } + + return $response->withJson(['userroles' => $userroles, 'restriction' => $restriction]); + } + + public function updateFileRestrictions(Request $request, Response $response, $args) + { + # get params from call + $this->params = $request->getParams(); + $this->uri = $request->getUri()->withUserInfo(''); + $filename = isset($this->params['filename']) ? $this->params['filename'] : false; + $role = isset($this->params['role']) ? $this->params['role'] : false; + + if(!$filename OR !$role) + { + return $response->withJson(['errors' => ['message' => 'Filename or userrole is missing.']], 422); + } + + $userroles = $this->c->acl->getRoles(); + + if($role != 'all' AND !in_array($role, $userroles)) + { + return $response->withJson(['errors' => ['message' => 'Userrole is unknown.']], 422); + } + + $writeYaml = new WriteYaml(); + $restrictions = $writeYaml->getYaml('media' . DIRECTORY_SEPARATOR . 'files', 'filerestrictions.yaml'); + if(!$restrictions) + { + $restrictions = []; + } + + if($role == 'all') + { + unset($restrictions[$filename]); + } + else + { + $restrictions[$filename] = $role; + } + + $writeYaml->updateYaml('media' . DIRECTORY_SEPARATOR . 'files', 'filerestrictions.yaml', $restrictions); + + return $response->withJson(['restrictions' => $restrictions]); + } + + + public function uploadFile(Request $request, Response $response, $args) + { + # get params from call + $this->params = $request->getParams(); + $this->uri = $request->getUri()->withUserInfo(''); + + if (!isset($this->params['file'])) + { + return $response->withJson(['errors' => 'No file found.'],404); + } + + $size = (int) (strlen(rtrim($this->params['file'], '=')) * 3 / 4); + $extension = pathinfo($this->params['name'], PATHINFO_EXTENSION); + $finfo = finfo_open( FILEINFO_MIME_TYPE ); + $mtype = @finfo_file( $finfo, $this->params['file'] ); + finfo_close($finfo); + + if ($size === 0) + { + return $response->withJson(['errors' => 'File is empty.'],422); + } + + # 20 MB (1 byte * 1024 * 1024 * 20 (for 20 MB)) + if ($size > 20971520) + { + return $response->withJson(['errors' => 'File is bigger than 20MB.'],422); + } + + # check extension first + if (!$this->checkAllowedExtensions($extension)) + { + return $response->withJson(['errors' => 'File is not allowed.'],422); + } + + # check mimetype and extension if there is a mimetype. + # in some environments the finfo_file does not work with a base64 string. + if($mtype) + { + if(!$this->checkAllowedMimeTypes($mtype, $extension)) + { + return $response->withJson(['errors' => 'The mime-type or file extension is not allowed.'],422); + } + } + + $fileProcessor = new ProcessFile(); + + if(!$fileProcessor->checkFolders()) + { + return $response->withJson(['errors' => 'Please check if your media-folder exists and all folders inside are writable.'], 500); + } + + $fileinfo = $fileProcessor->storeFile($this->params['file'], $this->params['name']); + + if($fileinfo) + { + # if the previous check of the mtype with the base64 string failed, then do it now again with the temporary file + if(!$mtype) + { + $filePath = str_replace('media/files', 'media/tmp', $fileinfo['url']); + $fullPath = $this->settings['rootPath'] . $filePath; + $finfo = finfo_open( FILEINFO_MIME_TYPE ); + $mtype = @finfo_file( $finfo, $fullPath ); + finfo_close($finfo); + + if(!$mtype OR !$this->checkAllowedMimeTypes($mtype, $extension)) + { + $fileProcessor->clearTempFolder(); + + return $response->withJson(['errors' => 'The mime-type is missing, not allowed or does not fit to the file extension.'],422); + } + } + + # publish file directly, used for example by file field for meta-tabs + if(isset($this->params['publish']) && $this->params['publish']) + { + $fileProcessor->publishFile(); + } + + return $response->withJson(['errors' => false, 'info' => $fileinfo]); + } + + return $response->withJson(['errors' => 'could not store file to temporary folder'],500); + } + + public function publishImage(Request $request, Response $response, $args) + { + $params = $request->getParsedBody(); + + $imageProcessor = new ProcessImage($this->settings['images']); + if(!$imageProcessor->checkFolders()) + { + return $response->withJson(['errors' => 'Please check if your media-folder exists and all folders inside are writable.'], 500); + } + + # check the resize modifier in the image markdown, set it to true and delete it from markdown + $noresize = false; + $markdown = isset($params['markdown']) ? $params['markdown'] : false; + + if($markdown && (strlen($markdown) > 9) && (substr($markdown, -9) == '|noresize') ) + { + $noresize = true; + $params['markdown'] = substr($markdown,0,-9); + } + + if($imageProcessor->publishImage($noresize)) + { + $request = $request->withParsedBody($params); + + $block = new ControllerAuthorBlockApi($this->c); + if($params['new']) + { + return $block->addBlock($request, $response, $args); + } + return $block->updateBlock($request, $response, $args); + } + + return $response->withJson(['errors' => 'could not store image to media folder'],500); + } + + public function publishFile(Request $request, Response $response, $args) + { + $params = $request->getParsedBody(); + + $fileProcessor = new ProcessFile(); + if(!$fileProcessor->checkFolders()) + { + return $response->withJson(['errors' => 'Please check if your media-folder exists and all folders inside are writable.'], 500); + } + + if($fileProcessor->publishFile()) + { + $request = $request->withParsedBody($params); + + $block = new ControllerAuthorBlockApi($this->c); + if($params['new']) + { + return $block->addBlock($request, $response, $args); + } + return $block->updateBlock($request, $response, $args); + } + + return $response->withJson(['errors' => 'could not store file to media folder'],500); + } + + public function deleteImage(Request $request, Response $response, $args) + { + # get params from call + $this->params = $request->getParams(); + $this->uri = $request->getUri()->withUserInfo(''); + + # minimum permission is that user is allowed to delete content + if(!$this->c->acl->isAllowed($_SESSION['role'], 'content', 'delete')) + { + return $response->withJson(array('data' => false, 'errors' => 'You are not allowed to delete images.'), 403); + } + + if(!isset($this->params['name'])) + { + return $response->withJson(['errors' => 'image name is missing'],500); + } + + $imageProcessor = new ProcessImage($this->settings['images']); + if(!$imageProcessor->checkFolders('images')) + { + return $response->withJson(['errors' => 'Please check if your media-folder exists and all folders inside are writable.'], 500); + } + + if($imageProcessor->deleteImage($this->params['name'])) + { + return $response->withJson(['errors' => false]); + } + + return $response->withJson(['errors' => 'Oops, looks like we could not delete all sizes of that image.'], 500); + } + + public function deleteFile(Request $request, Response $response, $args) + { + # get params from call + $this->params = $request->getParams(); + $this->uri = $request->getUri()->withUserInfo(''); + + # minimum permission is that user is allowed to delete content + if(!$this->c->acl->isAllowed($_SESSION['role'], 'content', 'delete')) + { + return $response->withJson(array('data' => false, 'errors' => 'You are not allowed to delete files.'), 403); + } + + if(!isset($this->params['name'])) + { + return $response->withJson(['errors' => 'file name is missing'],500); + } + + $fileProcessor = new ProcessFile(); + + if($fileProcessor->deleteFile($this->params['name'])) + { + return $response->withJson(['errors' => false]); + } + + return $response->withJson(['errors' => 'could not delete the file'],500); + } + + public function saveVideoImage(Request $request, Response $response, $args) + { + /* get params from call */ + $this->params = $request->getParams(); + $this->uri = $request->getUri()->withUserInfo(''); + $class = false; + + $imageUrl = $this->params['markdown']; + + if(strpos($imageUrl, 'https://www.youtube.com/watch?v=') !== false) + { + $videoID = str_replace('https://www.youtube.com/watch?v=', '', $imageUrl); + $videoID = strpos($videoID, '&') ? substr($videoID, 0, strpos($videoID, '&')) : $videoID; + $class = 'youtube'; + } + if(strpos($imageUrl, 'https://youtu.be/') !== false) + { + $videoID = str_replace('https://youtu.be/', '', $imageUrl); + $videoID = strpos($videoID, '?') ? substr($videoID, 0, strpos($videoID, '?')) : $videoID; + $class = 'youtube'; + } + + if($class == 'youtube') + { + $videoURLmaxres = 'https://i1.ytimg.com/vi/' . $videoID . '/maxresdefault.jpg'; + $videoURL0 = 'https://i1.ytimg.com/vi/' . $videoID . '/0.jpg'; + } + + $ctx = stream_context_create(array( + 'https' => array( + 'timeout' => 1 + ) + ) + ); + + $imageData = @file_get_contents($videoURLmaxres, 0, $ctx); + if($imageData === false) + { + $imageData = @file_get_contents($videoURL0, 0, $ctx); + if($imageData === false) + { + return $response->withJson(array('errors' => 'could not get the video image')); + } + } + + $imageData64 = 'data:image/jpeg;base64,' . base64_encode($imageData); + $desiredSizes = ['live' => ['width' => 560, 'height' => 315]]; + $imageProcessor = new ProcessImage($this->settings['images']); + if(!$imageProcessor->checkFolders()) + { + return $response->withJson(['errors' => ['message' => 'Please check if your media-folder exists and all folders inside are writable.']], 500); + } + + $tmpImage = $imageProcessor->createImage($imageData64, $videoID, $desiredSizes); + + if(!$tmpImage) + { + return $response->withJson(array('errors' => 'could not create temporary image')); + } + + $imageUrl = $imageProcessor->publishImage(); + if($imageUrl) + { + $this->params['markdown'] = '![' . $class . '-video](' . $imageUrl . ' "click to load video"){#' . $videoID. ' .' . $class . '}'; + + $request = $request->withParsedBody($this->params); + $block = new ControllerAuthorBlockApi($this->c); + if($this->params['new']) + { + return $block->addBlock($request, $response, $args); + } + return $block->updateBlock($request, $response, $args); + } + + return $response->withJson(array('errors' => 'could not store the preview image')); + } + + # https://www.sitepoint.com/mime-types-complete-list/ + # https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types/Common_types + # https://wiki.selfhtml.org/wiki/MIME-Type/%C3%9Cbersicht + # http://www.mime-type.net/application/x-latex/ + private function getAllowedMtypes() + { + return array( + 'application/vnd.oasis.opendocument.chart' => 'odc', + 'application/vnd.oasis.opendocument.formula' => 'odf', + 'application/vnd.oasis.opendocument.graphics' => 'odg', + 'application/vnd.oasis.opendocument.image' => 'odi', + 'application/vnd.oasis.opendocument.presentation' => 'odp', + 'application/vnd.oasis.opendocument.spreadsheet' => 'ods', + 'application/vnd.oasis.opendocument.text' => 'odt', + 'application/vnd.oasis.opendocument.text-master' => 'odm', + + 'application/powerpoint' => 'ppt', + 'application/mspowerpoint' => ['ppt','ppz','pps','pot'], + 'application/x-mspowerpoint' => 'ppt', + 'application/vnd.ms-powerpoint' => 'ppt', + 'application/vnd.openxmlformats-officedocument.presentationml.presentation' => 'pptx', + + 'application/x-visio' => ['vsd','vst','msw'], + 'application/vnd.visio' => ['vsd','vst','msw'], + 'application/x-project' => ['mpc','mpt','mpv','mpx'], + 'application/vnd.ms-project' => 'mpp', + + 'application/excel' => ['xla','xlb','xlc','xld','xlk','xll','xlm','xls','xlt','xlv','xlw'], + 'application/msexcel' => ['xls','xla'], + 'application/x-excel' => ['xla','xlb','xlc','xld','xlk','xll','xlm','xls','xlt','xlv','xlw'], + 'application/x-msexcel' => ['xls', 'xla','xlw'], + 'application/vnd.ms-excel' => ['xlb','xlc','xll','xlm','xls','xlw'], + 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' => 'xlsx', + + 'application/mshelp' => ['hlp','chm'], + 'application/msword' => ['doc','dot'], + 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' => 'docx', + + 'application/vnd.apple.keynote' => 'key', + 'application/vnd.apple.numbers' => 'numbers', + 'application/vnd.apple.pages' => 'pages', + + 'application/x-latex' => ['ltx','latex'], + 'application/pdf' => 'pdf', + + 'application/vnd.amazon.mobi8-ebook' => 'azw3', + 'application/x-mobipocket-ebook' => 'mobi', + 'application/epub+zip' => 'epub', + + 'application/x-gtar' => 'gtar', + 'application/x-tar' => 'tar', + 'application/zip' => 'zip', + 'application/gzip' => 'gz', + 'application/x-gzip' => ['gz', 'gzip'], + 'application/x-compressed' => ['gz','tgz','z','zip'], + 'application/x-zip-compressed' => 'zip', + 'application/vnd.rar' => 'rar', + 'application/x-7z-compressed' => '7z', + + 'application/rtf' => 'rtf', + 'application/x-rtf' => 'rtf', + + 'text/calendar' => 'ics', + 'text/comma-separated-values' => 'csv', + 'text/css' => 'css', + 'text/plain' => 'txt', + 'text/richtext' => 'rtx', + 'text/rtf' => 'rtf', + + 'audio/basic' => ['au','snd'], + 'audio/mpeg' => 'mp3', + 'audio/mp4' => 'mp4', + 'audio/ogg' => 'ogg', + 'audio/wav' => 'wav', + 'audio/x-aiff' => ['aif','aiff','aifc'], + 'audio/x-midi' => ['mid','midi'], + 'audio/x-mpeg' => 'mp2', + 'audio/x-pn-realaudio' => ['ram','ra'], + + 'image/png' => 'png', + 'image/jpeg' => ['jpeg','jpe','jpg'], + 'image/gif' => 'gif', + 'image/tiff' => ['tiff','tif'], + 'image/svg+xml' => 'svg', + 'image/x-icon' => 'ico', + 'image/webp' => 'webp', + + 'video/mpeg' => ['mpeg','mpg','mpe'], + 'video/mp4' => 'mp4', + 'video/ogg' => ['ogg','ogv'], + 'video/quicktime' => ['qt','mov'], + 'video/vnd.vivo' => ['viv','vivo'], + 'video/webm' => 'webm', + 'video/x-msvideo' => 'avi', + 'video/x-sgi-movie' => 'movie', + 'video/3gpp' => '3gp', + ); + } + + protected function checkAllowedMimeTypes($mtype, $extension) + { + $allowedMimes = $this->getAllowedMtypes(); + + if(!isset($allowedMimes[$mtype])) + { + return false; + } + + if( + (is_array($allowedMimes[$mtype]) && !in_array($extension, $allowedMimes[$mtype])) OR + (!is_array($allowedMimes[$mtype]) && $allowedMimes[$mtype] != $extension ) + ) + { + return false; + } + + return true; + } + + protected function checkAllowedExtensions($extension) + { + $mtypes = $this->getAllowedMtypes(); + foreach($mtypes as $mtExtension) + { + if(is_array($mtExtension)) + { + if(in_array($extension, $mtExtension)) + { + return true; + } + } + else + { + if($extension == $mtExtension) + { + return true; + } + } + } + + return false; + } +} diff --git a/system/typemill/Controllers/ControllerApiSystemLicense.php b/system/typemill/Controllers/ControllerApiSystemLicense.php new file mode 100644 index 0000000..72055cc --- /dev/null +++ b/system/typemill/Controllers/ControllerApiSystemLicense.php @@ -0,0 +1,59 @@ +getParsedBody(); + + if(!isset($params['license']) OR !is_array($params['license'])) + { + $response->getBody()->write(json_encode([ + 'message' => 'License data missing.', + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(400); + } + + # validate input + $validate = new Validation(); + $validationresult = $validate->newLicense($params['license']); + if($validationresult !== true) + { + $response->getBody()->write(json_encode([ + 'message' => 'Please correct errors in form.', + 'errors' => $validate->returnFirstValidationErrors($validationresult) + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(400); + } + + $license = new License(); + + $licensedata = $license->activateLicense($params['license']); + + if(!$licensedata) + { + $response->getBody()->write(json_encode([ + 'message' => $license->getMessage() + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(400); + } + + $response->getBody()->write(json_encode([ + 'message' => 'Licence has been stored', + 'licensedata' => $license->getLicenseData($this->c->get('urlinfo')) + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(200); + } +} \ No newline at end of file diff --git a/system/typemill/Controllers/ControllerWebAuth.php b/system/typemill/Controllers/ControllerWebAuth.php new file mode 100644 index 0000000..79b7028 --- /dev/null +++ b/system/typemill/Controllers/ControllerWebAuth.php @@ -0,0 +1,117 @@ +c->get('view')->render($response, 'auth/login.twig', [ + #'captcha' => $this->checkIfAddCaptcha(), + ]); + } + + public function login(Request $request, Response $response) + { + if( ( null !== $request->getattribute('csrf_result') ) OR ( $request->getattribute('csrf_result') === false ) ) + { + $this->c->flash->addMessage('error', 'The form has a timeout, please try again.'); + return $response->withHeader('Location', $this->routeParser->urlFor('auth.show')); + } + + $input = $request->getParsedBody(); + $validation = new Validation(); + $settings = $this->c->get('settings'); + + if($validation->signin($input)) + { + $user = new User(); + + if(!$user->setUserWithPassword($input['username'])) + { + # return error + } + + $userdata = $user->getUserData(); + + if($userdata && password_verify($input['password'], $userdata['password'])) + { + # check if user has confirmed the account + if(isset($userdata['optintoken']) && $userdata['optintoken']) + { + $this->c->get('flash')->addMessage('error', 'Your registration is not confirmed yet. Please check your e-mails and use the confirmation link.'); + return $response->withHeader('Location', $this->routeParser->urlFor('auth.show'))->withStatus(302); + } + + $user->login(); + +return $response->withHeader('Location', $this->routeParser->urlFor('settings.show'))->withStatus(302); + +/* + # if user is allowed to view content-area + $acl = $this->c->get('acl'); + if($acl->hasRole($userdata['userrole']) && $acl->isAllowed($userdata['userrole'], 'content', 'view')) + { + $editor = (isset($this->settings['editor']) && $this->settings['editor'] == 'visual') ? 'visual' : 'raw'; + + return $response->withHeader('Location', $this->routeParser->urlFor('content.' . $editor))->withStatus(302); + } + + return $response->withHeader('Location', $this->routeParser->urlFor('user.account'))->withStatus(302); +*/ + } + } + + if(isset($this->settings['securitylog']) && $this->settings['securitylog']) + { + \Typemill\Static\Helpers::addLogEntry('wrong login'); + } + + $this->c->get('flash')->addMessage('error', 'Ups, wrong password or username, please try again.'); + + return $response->withHeader('Location', $this->routeParser->urlFor('auth.show'))->withStatus(302); + } + + /** + * log out a user + * + * @param obj $request the slim request object + * @param obj $response the slim response object + * @return obje $response with redirect to route + */ + + public function logout(Request $request, Response $response) + { + # check https://www.php.net/session_destroy + if(isset($_SESSION)) + { + # Unset all of the session variables. + $_SESSION = array(); + + # If it's desired to kill the session, also delete the session cookie. This will destroy the session, and not just the session data! + if (ini_get("session.use_cookies")) + { + $params = session_get_cookie_params(); + + setcookie( + session_name(), + '', + time() - 42000, + $params["path"], $params["domain"], + $params["secure"], $params["httponly"] + ); + } + + # Finally, destroy the session. + session_destroy(); + } + + return $response->withHeader('Location', $this->routeParser->urlFor('auth.show'))->withStatus(302); + } +} \ No newline at end of file diff --git a/system/typemill/Middleware/ApiAuthentication.php b/system/typemill/Middleware/ApiAuthentication.php new file mode 100644 index 0000000..30247f6 --- /dev/null +++ b/system/typemill/Middleware/ApiAuthentication.php @@ -0,0 +1,144 @@ +getBasePath(); + + # check if it is a session based authentication + if ($request->hasHeader('X-Session-Auth')) + { + session_start(); + + $authenticated = ( + (isset($_SESSION['username'])) && + (isset($_SESSION['login'])) + ) + ? true : false; + + if($authenticated) + { + # here we have to load userdata and pass them through request or response + $user = new User(); + + if($user->setUser($_SESSION['username'])) + { + $userdata = $user->getUserData(); + + $request = $request->withAttribute('c_username', $userdata['username']); + $request = $request->withAttribute('c_userrole', $userdata['userrole']); + + $response = $handler->handle($request); + + return $response; + } + } + else + { + # return error message + } + } + + + # api authentication with basic auth + # inspired by tuupola + $host = $request->getUri()->getHost(); + $scheme = $request->getUri()->getScheme(); + $server_params = $request->getServerParams(); + + /* + # HTTP allowed only if secure is false or server is in relaxed array. + # use own logic for https proto forwarding + if($scheme !== "https" && $this->options["secure"] !== true) + { + $allowedHost = in_array($host, $this->options["relaxed"]); + + # if 'headers' is in the 'relaxed' key, then we check for forwarding + $allowedForward = false; + if (in_array("headers", $this->options["relaxed"])) + { + if ( $request->getHeaderLine("X-Forwarded-Proto") === "https" && $request->getHeaderLine('X-Forwarded-Port') === "443") + { + $allowedForward = true; + } + } + + if (!($allowedHost || $allowedForward)) + { + $message = sprintf("Insecure use of middleware over %s denied by configuration.", strtoupper($scheme)); + throw new \RuntimeException($message); + } + } + */ + + $params = []; + + if (preg_match("/Basic\s+(.*)$/i", $request->getHeaderLine("Authorization"), $matches)) + { + $explodedCredential = explode(":", base64_decode($matches[1]), 2); + if (count($explodedCredential) == 2) + { + [$params["user"], $params["password"]] = $explodedCredential; + } + } + + if(!empty($params)) + { + # load userdata + $user = new User(); + + if($user->setUserWithPassword($params['user'])) + { + $userdata = $user->getUserData(); + + # this might be unsecure, check for === comparator + $apiaccess = ( isset($userdata['apiaccess']) && $userdata['apiaccess'] == true ) ? true : false; + + if($userdata && $apiaccess && password_verify($params['password'], $userdata['password'])) + { + $request = $request->withAttribute('c_username', $userdata['username']); + $request = $request->withAttribute('c_userrole', $userdata['userrole']); + + # this executes code from routes first and then executes middleware + $response = $handler->handle($request); + + return $response; + } + else + { + # if basic auth is set but with wrong credentials + $response = new Response(); + + $response->getBody()->write(json_encode([ + 'message' => 'Authentication failed.' + ])); + + return $response->withHeader('WWW-Authenticate', 'Basic realm=')->withStatus(401); + } + } + } + +# elseif ($request->getHeaderLine('X-Requested-With') === 'XMLHttpRequest') { + # if you use this, then all xhr-calls need a session. + # no direct xhr calls without session are possible + # might increase security, but can have unwanted cases e.g. when you + # want to provide public api accessible for all by javascript (do you ever want??) +# } + + $response = new Response(); + + $response->getBody()->write('Zugriff nicht erlaubt.'); + + return $response->withStatus(401); + } +} \ No newline at end of file diff --git a/system/typemill/Middleware/ApiAuthorization.php b/system/typemill/Middleware/ApiAuthorization.php new file mode 100644 index 0000000..a95d20e --- /dev/null +++ b/system/typemill/Middleware/ApiAuthorization.php @@ -0,0 +1,39 @@ +acl = $acl; + $this->resource = $resource; + $this->action = $action; + } + + public function process(Request $request, RequestHandler $handler) :Response + { + if(!$this->acl->isAllowed($request->getAttribute('c_userrole'), $this->resource, $this->action)) + { + + $message = 'userrole: ' . $request->getAttribute('c_userrole') . ' resource: ' . $this->resource . ' action: ' . $this->action; + $response = new Response(); + + $response->getBody()->write(json_encode([ + 'message' => $message + ])); + + return $response->withStatus(401); + } + + $response = $handler->handle($request); + + return $response; + } +} \ No newline at end of file diff --git a/system/typemill/Middleware/WebAuthorization.php b/system/typemill/Middleware/WebAuthorization.php new file mode 100644 index 0000000..b30b7be --- /dev/null +++ b/system/typemill/Middleware/WebAuthorization.php @@ -0,0 +1,34 @@ +router = $router; + $this->acl = $acl; + $this->resource = $resource; + $this->action = $action; + } + + public function process(Request $request, RequestHandler $handler) :Response + { + if(!$this->acl->isAllowed($request->getAttribute('c_userrole'), $this->resource, $this->action)) + { + $response = new Response(); + + return $response->withHeader('Location', $this->router->urlFor('user.account'))->withStatus(302); + } + + $response = $handler->handle($request); + + return $response; + } +} \ No newline at end of file diff --git a/system/typemill/Middleware/WebRedirectIfAuthenticated.php b/system/typemill/Middleware/WebRedirectIfAuthenticated.php new file mode 100644 index 0000000..90abafb --- /dev/null +++ b/system/typemill/Middleware/WebRedirectIfAuthenticated.php @@ -0,0 +1,41 @@ +router = $router; + $this->settings = $settings; + } + + public function process(Request $request, RequestHandler $handler) :Response + { + $authenticated = ( + (isset($_SESSION['username'])) && + (isset($_SESSION['login'])) + ) + ? true : false; + + if($authenticated) + { + + $editor = (isset($this->settings['editor']) && $this->settings['editor'] == 'visual') ? 'visual' : 'raw'; + + $response = new Response(); + + return $response->withHeader('Location', $this->router->urlFor('content.' . $editor))->withStatus(302); + } + + $response = $handler->handle($request); + + return $response; + } +} \ No newline at end of file diff --git a/system/typemill/Middleware/WebRedirectIfUnauthenticated.php b/system/typemill/Middleware/WebRedirectIfUnauthenticated.php new file mode 100644 index 0000000..2328992 --- /dev/null +++ b/system/typemill/Middleware/WebRedirectIfUnauthenticated.php @@ -0,0 +1,51 @@ +router = $router; + } + + public function process(Request $request, RequestHandler $handler) :response + { + # session authentication + if( + (isset($_SESSION['username'])) && + (isset($_SESSION['login'])) + ) + { + # load userdata + $user = new User(); + + if($user->setUser($_SESSION['username'])) + { + + # pass username and userrole + $userdata = $user->getUserData(); + + $request = $request->withAttribute('c_username', $userdata['username']); + $request = $request->withAttribute('c_userrole', $userdata['userrole']); + + # this executes code from routes first and then executes middleware + $response = $handler->handle($request); + + return $response; + } + } + + # this executes only middleware code and not code from route + $response = new Response(); + + return $response->withHeader('Location', $this->router->urlFor('auth.show'))->withStatus(302); + } +} \ No newline at end of file diff --git a/system/typemill/Models/License.php b/system/typemill/Models/License.php new file mode 100644 index 0000000..f2a3708 --- /dev/null +++ b/system/typemill/Models/License.php @@ -0,0 +1,258 @@ + [ + 'name' => 'MAKER', + 'scope' => ['MAKER' => true] + ], + '33334' => [ + 'name' => 'BUSINESS', + 'scope' => ['MAKER' => true, 'BUSINESS' => true] + ] + ]; + + public function getMessage() + { + return $this->message; + } + + # used for license management in admin settings + public function getLicenseData(array $urlinfo) + { + # returns data for settings page + $licensedata = $this->checkLicense(); + if($licensedata) + { + $licensedata['plan'] = $this->plans[$licensedata['plan']]['name']; + $licensedata['domaincheck'] = $this->checkLicenseDomain($licensedata['domain'], $urlinfo); + $licensedata['datecheck'] = $this->checkLicenseDate($licensedata['payed_until']); + + return $licensedata; + } + + return false; + } + + # used to activate or deactivate features that require a license + public function getLicenseScope(array $urlinfo) + { + $licensedata = $this->checkLicense(); + + if(!$licensedata) + { + return false; + } + + $domain = $this->checkLicenseDomain($licensedata['domain'], $urlinfo); + $date = $this->checkLicenseDate($licensedata['payed_until']); + + $domain = true; + + if($domain && $date) + { + return $this->plans[$licensedata['plan']]['scope']; + } + + return false; + } + + public function refreshLicense() + { + + } + + # check the local licence file (like pem or pub) + private function checkLicense() + { + $storage = new StorageWrapper('\Typemill\Models\Storage'); + + $licensedata = $storage->getYaml('settings', 'license.yaml'); + + if(!$licensedata) + { + $this->message = 'no license found'; + + return false; + } + + if(!isset($licensedata['license'],$licensedata['email'],$licensedata['domain'],$licensedata['plan'],$licensedata['payed_until'],$licensedata['signature'])) + { + $this->message = 'License data incomplete'; + + return false; + } + $licenseStatus = $this->validateLicense($licensedata); + + if($licenseStatus === true) + { + unset($licensedata['signature']); + + # check here if payed until is in past + return $licensedata; + } + + return false; + } + + private function validateLicense($data) + { + $public_key_pem = $this->getPublicKeyPem(); + + $binary_signature = base64_decode($data['signature']); + + $data['email'] = $this->hashMail($data['email']); + unset($data['signature']); + + # test manipulate data + #$data['plan'] = 'wrong'; + + $data = json_encode($data); + + # Check signature + $verified = openssl_verify($data, $binary_signature, $public_key_pem, OPENSSL_ALGO_SHA256); + + if ($verified == 1) + { + return true; + } + elseif ($verified == 0) + { + $this->message = 'License data are invalid'; + + return false; + } + else + { + $this->message = 'There was an error checking the license signature'; + + return false; + } + } + + public function activateLicense($params) + { + # prepare data for call to licence server + $licensedata = [ + 'license' => $params['license'], + 'email' => $this->hashMail($params['email']), + 'domain' => $params['domain'] + ]; + + $postdata = http_build_query($licensedata); + + $authstring = $this->getPublicKeyPem(); + $authstring = hash('sha256', substr($authstring, 0, 50)); + + $options = array ( + 'http' => array ( + 'method' => 'POST', + 'ignore_errors' => true, + 'header' => "Content-Type: application/x-www-form-urlencoded\r\n" . + "Accept: application/json\r\n" . + "Authorization: $authstring\r\n" . + "Connection: close\r\n", + 'content' => $postdata + ) + ); + + $context = stream_context_create($options); + + $response = file_get_contents('https://service.typemill.net/api/v2/activate', false, $context); + + if(substr($http_response_header[0], -6) != "200 OK") + { + $this->message = 'the license server responded with: ' . $http_response_header[0]; + + return false; + } + + $signedLicense = json_decode($response,true); + + if(isset($signedLicense['code'])) + { +# $this->message = 'Something went wrong. Please check your input data or contact the support.'; + $this->message = $signedLicense['code']; + return false; + } + +/* + # check for positive and validate response data + if($signedLicense['license']) + { + $this->message = ; + } +*/ + $signedLicense['license']['email'] = trim($params['email']); + $storage = new StorageWrapper('\Typemill\Models\Storage'); + + $storage->updateYaml('settings', 'license.yaml', $signedLicense['license']); + + return true; + } + + private function updateLicence() + { + # todo + } + + private function checkLicenseDomain(string $licensedomain, array $urlinfo) + { + $licensehost = parse_url($licensedomain, PHP_URL_HOST); + $licensehost = str_replace("www.", "", $licensehost); + + $thishost = parse_url($urlinfo['baseurl'], PHP_URL_HOST); + $thishost = str_replace("www.", "", $thishost); + + $whitelist = ['localhost', '127.0.0.1', 'typemilltest.', $licensehost]; + + foreach($whitelist as $domain) + { + if(substr($thishost, 0, strlen($domain)) == $domain) + { + return true; + } + } + + return false; + } + + private function checkLicenseDate(string $payed_until) + { + if(strtotime($payed_until) > strtotime(date('Y-m-d'))) + { + return true; + } + return false; + } + + private function hashMail(string $mail) + { + return hash('sha256', trim($mail) . 'TYla5xa8JUur'); + } + + private function getPublicKeyPem() + { + $pkeyfile = getcwd() . DIRECTORY_SEPARATOR . 'settings' . DIRECTORY_SEPARATOR . "public_key.pem"; + + if(file_exists($pkeyfile) && is_readable($pkeyfile)) + { + # fetch public key from file and ready it + $fp = fopen($pkeyfile, "r"); + $public_key_pem = fread($fp, 8192); + fclose($fp); + + return $public_key_pem; + } + + return false; + } + +} \ No newline at end of file diff --git a/system/typemill/Models/ProcessAssets.php b/system/typemill/Models/ProcessAssets.php new file mode 100644 index 0000000..448e7b3 --- /dev/null +++ b/system/typemill/Models/ProcessAssets.php @@ -0,0 +1,243 @@ +basepath = getcwd() . DIRECTORY_SEPARATOR; + + $this->tmpFolder = $this->basepath . 'media' . DIRECTORY_SEPARATOR . 'tmp' . DIRECTORY_SEPARATOR; + } + + public function clearTempFolder() + { + $files = scandir($this->tmpFolder); + $now = time(); + $result = true; + + foreach($files as $file) + { + if (!in_array($file, array(".",".."))) + { + $filelink = $this->tmpFolder . $file; + if(file_exists($filelink)) + { + $filetime = filemtime($filelink); + if($now - $filetime > 1800) + { + if(!unlink($filelink)) + { + $result = false; + } + } + } + } + } + + return $result; + } + + + +/* + public function checkFolders($forassets = null) + { + + $folders = [$this->mediaFolder, $this->tmpFolder, $this->fileFolder]; + + if($forassets == 'images') + { + $folders = [$this->mediaFolder, $this->tmpFolder, $this->originalFolder, $this->liveFolder, $this->thumbFolder, $this->customFolder]; + } + + foreach($folders as $folder) + { + if(!file_exists($folder) && !is_dir( $folder )) + { + if(!mkdir($folder, 0755, true)) + { + return false; + } + if($folder == $this->thumbFolder) + { + # cleanup old systems + $this->cleanupLiveFolder(); + + # generate thumbnails from live folder + $this->generateThumbs(); + } + } + elseif(!is_writeable($folder) OR !is_readable($folder)) + { + return false; + } + + # check if thumb-folder is empty, then generate thumbs from live folder + if($folder == $this->thumbFolder && $this->is_dir_empty($folder)) + { + # cleanup old systems + $this->cleanupLiveFolder(); + + # generate thumbnails from live folder + $this->generateThumbs(); + } + } + return true; + } +*/ + + public function is_dir_empty($dir) + { + return (count(scandir($dir)) == 2); + } + +/* + public function setFileName($originalname, $type, $overwrite = NULL) + { + $pathinfo = pathinfo($originalname); + $this->extension = isset($pathinfo['extension']) ? strtolower($pathinfo['extension']) : null; + $this->filename = Folder::createSlug($pathinfo['filename']); + + $filename = $this->filename; + + # check if file name is + if(!$overwrite) + { + $suffix = 1; + + $destination = $this->liveFolder; + if($type == 'file') + { + $destination = $this->fileFolder; + } + + while(file_exists($destination . $filename . '.' . $this->extension)) + { + $filename = $this->filename . '-' . $suffix; + $suffix++; + } + } + + $this->filename = $filename; + + return true; + } +*/ + +/* + public function getName() + { + return $this->filename; + } + + public function setExtension($extension) + { + $this->extension = $extension; + } + + public function getExtension() + { + return $this->extension; + } + + public function getFullName() + { + return $this->filename . '.' . $this->extension; + } + +*/ + +/* + public function cleanupLiveFolder() + { + # delete all old thumbs mlibrary in live folder + foreach(glob($this->liveFolder . '*mlibrary*') as $filename) + { + unlink($filename); + } + + return true; + } +*/ + + public function findPagesWithUrl($structure, $url, $result) + { + foreach ($structure as $key => $item) + { + if($item->elementType == 'folder') + { + $result = $this->findPagesWithUrl($item->folderContent, $url, $result); + } + else + { + $live = getcwd() . DIRECTORY_SEPARATOR . 'content' . $item->pathWithoutType . '.md'; + $draft = getcwd() . DIRECTORY_SEPARATOR . 'content' . $item->pathWithoutType . '.txt'; + + # check live first + if(file_exists($live)) + { + $content = file_get_contents($live); + + if (stripos($content, $url) !== false) + { + $result[] = $item->urlRelWoF; + } + # if not in live, check in draft + elseif(file_exists($draft)) + { + $content = file_get_contents($draft); + + if (stripos($content, $url) !== false) + { + $result[] = $item->urlRelWoF; + } + } + } + } + } + return $result; + } + + public function formatSizeUnits($bytes) + { + if ($bytes >= 1073741824) + { + $bytes = number_format($bytes / 1073741824, 2) . ' GB'; + } + elseif ($bytes >= 1048576) + { + $bytes = number_format($bytes / 1048576, 2) . ' MB'; + } + elseif ($bytes >= 1024) + { + $bytes = number_format($bytes / 1024, 2) . ' KB'; + } + elseif ($bytes > 1) + { + $bytes = $bytes . ' bytes'; + } + elseif ($bytes == 1) + { + $bytes = $bytes . ' byte'; + } + else + { + $bytes = '0 bytes'; + } + + return $bytes; + } +} \ No newline at end of file diff --git a/system/typemill/Models/ProcessFile.php b/system/typemill/Models/ProcessFile.php new file mode 100644 index 0000000..b4d67d6 --- /dev/null +++ b/system/typemill/Models/ProcessFile.php @@ -0,0 +1,40 @@ +getFile($folderName, $yamlFileName); + + if($yaml) + { + return \Symfony\Component\Yaml\Yaml::parse($yaml); + } + + return false; + } + + /** + * Writes a yaml file. + * @param string $fileName is the name of the Yaml Folder. + * @param string $yamlFileName is the name of the Yaml File. + * @param array $contentArray is the content as an array. + */ + public function updateYaml($folderName, $yamlFileName, $contentArray) + { + $yaml = \Symfony\Component\Yaml\Yaml::dump($contentArray,6); + if($this->writeFile($folderName, $yamlFileName, $yaml)) + { + return true; + } + + return false; + } +} \ No newline at end of file diff --git a/system/typemill/Models/ProcessImage.php b/system/typemill/Models/ProcessImage.php new file mode 100644 index 0000000..769a58e --- /dev/null +++ b/system/typemill/Models/ProcessImage.php @@ -0,0 +1,585 @@ + true, 'jpg' => true, 'jpeg' => true, 'webp' => true]; + protected $filename = false; + protected $animated = false; + protected $resizable = true; + protected $sizes = []; + + public function prepareImage($image, $name) + { + # change clear tmp folder and delete only old ones + $this->clearTempFolder(); + #$this->checkFolders('image'); + $this->decode($image); + $this->setPathInfo($name); + $this->checkAllowedExtension(); + + if(empty($this->errors)) + { + return true; + } + + return false; + } + + public function storeOriginalToTmp() + { + # $this->saveName(); + $this->saveOriginal(); + + if(empty($this->errors)) + { + return true; + } + + return false; + } + + public function storeRenditionsToTmp($sizes) + { + # transform image-stream into image + $image = $this->createImage(); + + $originalsize = $this->getImageSize($image); + + foreach($sizes as $destinationfolder => $desiredsize) + { + $desiredsize = $this->calculateSize($originalsize, $desiredsize); + + $resizedImage = $this->resizeImage($image, $desiredsize, $originalsize); + + $this->saveResizedImage($resizedImage, $destinationfolder, $this->extension); + + imagedestroy($resizedImage); + } + + imagedestroy($image); + + if(empty($this->errors)) + { + return true; + } + + return false; + } + + + # decode a base64 image string from js image components + public function decode(string $image) + { + $imageParts = explode(";base64,", $image); + + if(!isset($imageParts[0]) OR !isset($imageParts[1])) + { + $this->errors[] = 'Could not decode image, probably not a base64 encoding.'; + + return false; + } + + $type = explode("/", $imageParts[0]); + $this->type = strtolower($type[0]); + $this->imgstring = base64_decode($imageParts[1]); + + return true; + } + + # set the pathinfo (name and extension) and slugify a unique name if option to overwrite existing files is false + public function setPathInfo(string $name) + { + $pathinfo = pathinfo($name); + if(!$pathinfo) + { + $this->errors[] = 'Could not read pathinfo.'; + + return false; + } + + $this->extension = isset($pathinfo['extension']) ? strtolower($pathinfo['extension']) : false; + $this->filename = Slug::createSlug($pathinfo['filename']); + + if(!$this->extension OR !$this->filename) + { + $this->errors[] = 'Extension or filename are missing.'; + + return false; + } + + return true; + } + + public function getExtension() + { + return $this->extension; + } + + public function getFilename() + { + return $this->filename; + } + + public function setFilename($filename) + { + $this->filename = $filename; + } + + public function getFullName() + { + return $this->filename . '.' . $this->extension; + } + + # add an allowed image extension like svg + public function addAllowedExtension(string $extension) + { + $this->allowedExtensions[$extension] = true; + } + + + # force an image type like webp + public function setExtension(string $extension) + { + $this->extension = $extension; + } + + public function checkAllowedExtension() + { + if(!isset($this->allowedExtensions[$this->extension])) + { + $this->errors[] = 'Images with this extension are not allowed.'; + + return false; + } + + return true; + } + + # check if image should not be resized (animated gif and svg) + public function isResizable() + { + if($this->type == 'gif' && $this->detectAnimatedGif()) + { + $this->resizable = false; + } + + if($this->type == 'svg+xml') + { + $this->resizable = false; + } + + return $this->resizable; + } + + public function detectAnimatedGif() + { + $is_animated = preg_match('#(\x00\x21\xF9\x04.{4}\x00\x2C.*){2,}#s', $this->imgstring); + if ($is_animated == 1) + { + $this->animated = true; + } + + return $this->animated; + } + + # save the original image to temp folder + public function saveOriginal($destinationfolder = 'ORIGINAL') + { + $path = $this->tmpFolder . $destinationfolder . '+' . $this->filename . '.' . $this->extension; + + if(!file_put_contents($path, $this->imgstring)) + { + $this->errors[] = 'could not store the image in the temporary folder'; + } + } + + # save the original image for all sizes/folders + public function saveOriginalForAll() + { + $this->saveOriginal('LIVE'); + $this->saveOriginal('THUMBS'); + } + + public function createImage() + { + return imagecreatefromstring($this->imgstring); + } + + public function getImageSize($image) + { + return ['width' => imagesx($image), 'height' => imagesy($image)]; + } + + public function calculateSize(array $originalsize, array $desiredsize) + { + # if desired size is bigger than the actual image, then drop the desired sizes and use the actual image size instead + if($desiredsize['width'] > $originalsize['width']) + { + return $originalsize; + } + + if(!isset($desiredsize['height'])) + { + $resizeFactor = $originalsize['width'] / $desiredsize['width']; + $desiredsize['height'] = round( ($originalsize['height'] / $resizeFactor), 0); + } + + return $desiredsize; + } + + public function resizeImage($image, array $desired, array $original) + { + # resize + $ratio = max($desired['width']/$original['width'], $desired['height']/$original['height']); + $h = $desired['height'] / $ratio; + $x = ($original['width'] - $desired['width'] / $ratio) / 2; + $y = ($original['height'] - $desired['height'] / $ratio) / 2; + $w = $desired['width'] / $ratio; + + $resizedImage = imagecreatetruecolor($desired['width'], $desired['height']); + + # preserve transparency + if($this->extension == "gif" or $this->extension == "png" or $this->extension == "webp") + { + imagecolortransparent($resizedImage, imagecolorallocatealpha($resizedImage, 0, 0, 0, 127)); + imagealphablending($resizedImage, false); + imagesavealpha($resizedImage, true); + } + + imagecopyresampled($resizedImage, $image, 0, 0, $x, $y, $desired['width'], $desired['height'], $w, $h); + + return $resizedImage; + } + + public function saveResizedImage($resizedImage, string $destinationfolder, string $extension) + { + $destinationfolder = strtoupper($destinationfolder); + + switch($extension) + { + case "png": + $storedImage = imagepng( $resizedImage, $this->tmpFolder . $destinationfolder . '+' . $this->filename . '.png', 9 ); + break; + case "gif": + $storedImage = imagegif( $resizedImage, $this->tmpFolder . $destinationfolder . '+' . $this->filename . '.gif' ); + break; + case "webp": + $storedImage = imagewebp( $resizedImage, $this->tmpFolder . $destinationfolder . '+' . $this->filename . '.webp', 80); + break; + case "jpg": + case "jpeg": + $storedImage = imagejpeg( $resizedImage, $this->tmpFolder . $destinationfolder . '+' . $this->filename . '.' . $extension, 80); + break; + default: + $storedImage = false; + } + + if(!$storedImage) + { + $failedImage = $this->tmpFolder . $destinationfolder . '+' . $this->filename . '.' . $extension; + + $this->errors[] = "Could not store the resized version $failedImage"; + + return false; + } + + return true; + } + + + # publish image function is moved to storage model + + + # MOVE TO STORAGE ?? + public function deleteImage($name) + { + # validate name + $name = basename($name); + + if(!file_exists($this->originalFolder . $name) OR !unlink($this->originalFolder . $name)) + { + $this->errors[] = "We could not delete the original image"; + } + + if(!file_exists($this->liveFolder . $name) OR !unlink($this->liveFolder . $name)) + { + $this->errors[] = "We could not delete the live image"; + } + + if(!file_exists($this->thumbFolder . $name) OR !unlink($this->thumbFolder . $name)) + { + $this->errors[] = "we could not delete the thumb image"; + } + + # delete custom images (resized and grayscaled) array_map('unlink', glob("some/dir/*.txt")); + $pathinfo = pathinfo($name); + foreach(glob($this->customFolder . $pathinfo['filename'] . '\-*.' . $pathinfo['extension']) as $image) + { + # you could check if extension is the same here + if(!unlink($image)) + { + $this->errors[] = "we could not delete a custom image (grayscale or resized)"; + } + } + + if(empty($this->errors)) + { + return true; + } + + return false; + } + + + + # in use ?? + public function deleteImageWithName($name) + { + # e.g. delete $name = 'logo...'; + + $name = basename($name); + + if($name != '' && !in_array($name, array(".",".."))) + { + foreach(glob($this->liveFolder . $name) as $file) + { + unlink($file); + } + foreach(glob($this->originalFolder . $name) as $file) + { + unlink($file); + } + foreach(glob($this->thumbFolder . $name) as $file) + { + unlink($file); + } + } + } + + # in use ?? + public function copyImage($name,$sourcefolder,$targetfolder) + { + copy($sourcefolder . $name, $targetfolder . $name); + } + + + + + + + + + + + + + + + + /** + * Moves the uploaded file to the upload directory. Only used for settings / NON VUE.JS uploads + * + * @param string $directory directory to which the file is moved + * @param UploadedFile $uploadedFile file uploaded file to move + * @return string filename of moved file + */ + public function moveUploadedImage(UploadedFile $uploadedFile, $overwrite = false, $name = false, $folder = NULL) + { + $this->setFileName($uploadedFile->getClientFilename(), 'file'); + + if($name) + { + $this->setFileName($name . '.' . $this->extension, 'file', $overwrite); + } + + if(!$folder) + { + $folder = $this->liveFolder; + } + + $uploadedFile->moveTo($folder . $this->getFullName()); + + return $this->getFullName(); + } + + + + + + + + + + + +/* + # save the image name as txt to temp folder + public function saveName() + { + $path = $this->tmpFolder . $this->filename . '.txt'; + + if(!fopen($path, "w")) + { + $this->errors[] = 'could not store the filename in the temporary folder'; + } + } +*/ + + + + /* + * scans content of a folder (without recursion) + * vars: folder path as string + * returns: one-dimensional array with names of folders and files + */ + public function scanMediaFlat() + { + $thumbs = array_diff(scandir($this->thumbFolder), array('..', '.')); + $imagelist = array(); + + foreach ($thumbs as $key => $name) + { + if (file_exists($this->liveFolder . $name)) + { + $imagelist[] = [ + 'name' => $name, + 'timestamp' => filemtime($this->liveFolder . $name), + 'src_thumb' => 'media/thumbs/' . $name, + 'src_live' => 'media/live/' . $name, + ]; + } + } + + $imagelist = Helpers::array_sort($imagelist, 'timestamp', SORT_DESC); + + return $imagelist; + } + + + # get details from existing image for media library + public function getImageDetails($name, $structure) + { + $name = basename($name); + + if (!in_array($name, array(".","..")) && file_exists($this->liveFolder . $name)) + { + $imageinfo = getimagesize($this->liveFolder . $name); + + if(!$imageinfo && pathinfo($this->liveFolder . $name, PATHINFO_EXTENSION) == 'svg') + { + $imagedetails = [ + 'name' => $name, + 'timestamp' => filemtime($this->liveFolder . $name), + 'bytes' => filesize($this->liveFolder . $name), + 'width' => '---', + 'height' => '---', + 'type' => 'svg', + 'src_thumb' => 'media/thumbs/' . $name, + 'src_live' => 'media/live/' . $name, + 'pages' => $this->findPagesWithUrl($structure, $name, $result = []) + ]; + } + else + { + $imagedetails = [ + 'name' => $name, + 'timestamp' => filemtime($this->liveFolder . $name), + 'bytes' => filesize($this->liveFolder . $name), + 'width' => $imageinfo[0], + 'height' => $imageinfo[1], + 'type' => $imageinfo['mime'], + 'src_thumb' => 'media/thumbs/' . $name, + 'src_live' => 'media/live/' . $name, + 'pages' => $this->findPagesWithUrl($structure, $name, $result = []) + ]; + } + + return $imagedetails; + } + + return false; + } + + public function generateThumbs() + { + # generate images from live folder to 'tmthumbs' + $liveImages = scandir($this->liveFolder); + + $result = false; + + foreach ($liveImages as $key => $name) + { + if (!in_array($name, array(".",".."))) + { + $result = $this->generateThumbFromImageFile($name); + } + } + return $result; + } + + public function generateThumbFromImageFile($filename) + { + $this->setFileName($filename, 'image', $overwrite = true); + + $image = $this->createImageFromPath($this->liveFolder . $filename, $this->extension); + + $originalSize = $this->getImageSize($image); + + $thumbSize = $this->desiredSizes['thumbs']; + + $thumb = $this->imageResize($image, $originalSize, ['thumbs' => $thumbSize ], $this->extension); + + $saveImage = $this->saveImage($this->thumbFolder, $thumb['thumbs'], $this->filename, $this->extension); + if($saveImage) + { + return true; + } + return false; + } + + # filename and imagepath can be a tmp-version after upload. + public function generateSizesFromImageFile($filename, $imagePath) + { + $this->setFileName($filename, 'image'); + + $image = $this->createImageFromPath($imagePath, $this->extension); + + $originalSize = $this->getImageSize($image); + + $resizedImages = $this->imageResize($image, $originalSize, $this->desiredSizes, $this->extension); + + return $resizedImages; + } + + public function grayscale($imagePath, $extension) + { + $image = $this->createImageFromPath($imagePath, $extension); + + imagefilter($image, IMG_FILTER_GRAYSCALE); + + return $image; + } + + public function createImageFromPath($imagePath, $extension) + { + switch($extension) + { + case 'gif': $image = imagecreatefromgif($imagePath); break; + case 'jpg' : + case 'jpeg': $image = imagecreatefromjpeg($imagePath); break; + case 'png': $image = imagecreatefrompng($imagePath); break; + case 'webp': $image = imagecreatefromwebp($imagePath); break; + default: return 'image type not supported'; + } + + return $image; + } +} \ No newline at end of file diff --git a/system/typemill/Static/License.php b/system/typemill/Static/License.php new file mode 100644 index 0000000..cd91f9f --- /dev/null +++ b/system/typemill/Static/License.php @@ -0,0 +1,229 @@ +getYaml('settings', 'license.yaml'); + + if(!$licensedata) + { + return ['result' => false, 'message' => 'no license found']; + } + + if(!isset($licensedata['license'],$licensedata['email'],$licensedata['domain'],$licensedata['plan'],$licensedata['payed_until'],$licensedata['signature'])) + { + return ['result' => false, 'message' => 'License data not complete']; + } + + $licenseStatus = self::validateLicense($licensedata); + + unset($licensedata['signature']); + + if($licenseStatus === false) + { + return ['result' => false, 'message' => 'License data are invalid']; + } + elseif($licenseStatus === true) + { + echo '
';
+			print_r($licensedata);
+			die();
+		}
+		else
+		{
+			die('error checking signature');
+		}
+	}
+
+	public static function validateLicense($data)
+	{
+		$public_key_pem 	= self::getPublicKeyPem();
+
+		$binary_signature 	= base64_decode($data['signature']);
+
+		$data['email'] 		= self::hashMail($data['email']);
+		unset($data['signature']);
+
+		# manipulate data
+		# $data['product'] 	= 'business';
+
+		$data = json_encode($data);
+
+		# Check signature
+		$verified = openssl_verify($data, $binary_signature, $public_key_pem, OPENSSL_ALGO_SHA256);
+
+		if ($verified == 1)
+		{
+		    return true;
+		} 
+		elseif ($verified == 0)
+		{
+		    return false;
+		} 
+		else
+		{
+		    die("ugly, error checking signature");
+		}
+	}
+
+	public static function hashMail($mail)
+	{
+		return hash('sha256', trim($mail) . 'TYla5xa8JUur');
+	}
+
+	public static function getPublicKeyPem()
+	{
+		$pkeyfile = getcwd() . DIRECTORY_SEPARATOR . 'settings' . DIRECTORY_SEPARATOR . "public_key.pem";
+
+		if(file_exists($pkeyfile) && is_readable($pkeyfile))
+		{
+			# fetch public key from file and ready it
+			$fp 				= fopen($pkeyfile, "r");
+			$public_key_pem 	= fread($fp, 8192);
+			fclose($fp);
+
+			return $public_key_pem;
+		}
+
+		return false;
+	}
+}
+
+
+
+/* KIRBY -> source -> cms -> system.php
+
+	/**
+	 * 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('license'));
+		} catch (Throwable) {
+			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
+		$data      = json_encode($data);
+		$signature = hex2bin($license['signature']);
+		if (openssl_verify($data, $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
+		if ($this->app->user()?->isAdmin() === true) {
+			return $license['license'];
+		}
+
+		return true;
+	}
+
+
+	/**
+	 * Validates the license key
+	 * and adds it to the .license file in the config
+	 * folder if possible.
+	 *
+	 * @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']);
+		}
+
+		// @codeCoverageIgnoreStart
+		$response = Remote::get('https://hub.getkirby.com/register', [
+			'data' => [
+				'license' => $license,
+				'email'   => Str::lower(trim($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('license');
+
+		// save the license information
+		Json::write($file, $json);
+
+		if ($this->license() === false) {
+			throw new InvalidArgumentException([
+				'key' => 'license.verification'
+			]);
+		}
+		// @codeCoverageIgnoreEnd
+
+		return true;
+	}
+
+*/
\ No newline at end of file
diff --git a/system/typemill/Static/Slug.php b/system/typemill/Static/Slug.php
new file mode 100644
index 0000000..dd7e9d9
--- /dev/null
+++ b/system/typemill/Static/Slug.php
@@ -0,0 +1,45 @@
+{
+throw Error("map is read-only")}:e instanceof Set&&(e.add=e.clear=e.delete=()=>{
+throw Error("set is read-only")
+}),Object.freeze(e),Object.getOwnPropertyNames(e).forEach((n=>{var i=e[n]
+;"object"!=typeof i||Object.isFrozen(i)||t(i)})),e}
+e.exports=t,e.exports.default=t;class n{constructor(e){
+void 0===e.data&&(e.data={}),this.data=e.data,this.isMatchIgnored=!1}
+ignoreMatch(){this.isMatchIgnored=!0}}function i(e){
+return e.replace(/&/g,"&").replace(//g,">").replace(/"/g,""").replace(/'/g,"'")
+}function r(e,...t){const n=Object.create(null);for(const t in e)n[t]=e[t]
+;return t.forEach((e=>{for(const t in e)n[t]=e[t]})),n}
+const s=e=>!!e.scope||e.sublanguage&&e.language;class o{constructor(e,t){
+this.buffer="",this.classPrefix=t.classPrefix,e.walk(this)}addText(e){
+this.buffer+=i(e)}openNode(e){if(!s(e))return;let t=""
+;t=e.sublanguage?"language-"+e.language:((e,{prefix:t})=>{if(e.includes(".")){
+const n=e.split(".")
+;return[`${t}${n.shift()}`,...n.map(((e,t)=>`${e}${"_".repeat(t+1)}`))].join(" ")
+}return`${t}${e}`})(e.scope,{prefix:this.classPrefix}),this.span(t)}
+closeNode(e){s(e)&&(this.buffer+="")}value(){return this.buffer}span(e){
+this.buffer+=``}}const a=(e={})=>{const t={children:[]}
+;return Object.assign(t,e),t};class c{constructor(){
+this.rootNode=a(),this.stack=[this.rootNode]}get top(){
+return this.stack[this.stack.length-1]}get root(){return this.rootNode}add(e){
+this.top.children.push(e)}openNode(e){const t=a({scope:e})
+;this.add(t),this.stack.push(t)}closeNode(){
+if(this.stack.length>1)return this.stack.pop()}closeAllNodes(){
+for(;this.closeNode(););}toJSON(){return JSON.stringify(this.rootNode,null,4)}
+walk(e){return this.constructor._walk(e,this.rootNode)}static _walk(e,t){
+return"string"==typeof t?e.addText(t):t.children&&(e.openNode(t),
+t.children.forEach((t=>this._walk(e,t))),e.closeNode(t)),e}static _collapse(e){
+"string"!=typeof e&&e.children&&(e.children.every((e=>"string"==typeof e))?e.children=[e.children.join("")]:e.children.forEach((e=>{
+c._collapse(e)})))}}class l extends c{constructor(e){super(),this.options=e}
+addKeyword(e,t){""!==e&&(this.openNode(t),this.addText(e),this.closeNode())}
+addText(e){""!==e&&this.add(e)}addSublanguage(e,t){const n=e.root
+;n.sublanguage=!0,n.language=t,this.add(n)}toHTML(){
+return new o(this,this.options).value()}finalize(){return!0}}function g(e){
+return e?"string"==typeof e?e:e.source:null}function d(e){return p("(?=",e,")")}
+function u(e){return p("(?:",e,")*")}function h(e){return p("(?:",e,")?")}
+function p(...e){return e.map((e=>g(e))).join("")}function f(...e){const t=(e=>{
+const t=e[e.length-1]
+;return"object"==typeof t&&t.constructor===Object?(e.splice(e.length-1,1),t):{}
+})(e);return"("+(t.capture?"":"?:")+e.map((e=>g(e))).join("|")+")"}
+function b(e){return RegExp(e.toString()+"|").exec("").length-1}
+const m=/\[(?:[^\\\]]|\\.)*\]|\(\??|\\([1-9][0-9]*)|\\./
+;function E(e,{joinWith:t}){let n=0;return e.map((e=>{n+=1;const t=n
+;let i=g(e),r="";for(;i.length>0;){const e=m.exec(i);if(!e){r+=i;break}
+r+=i.substring(0,e.index),
+i=i.substring(e.index+e[0].length),"\\"===e[0][0]&&e[1]?r+="\\"+(Number(e[1])+t):(r+=e[0],
+"("===e[0]&&n++)}return r})).map((e=>`(${e})`)).join(t)}
+const x="[a-zA-Z]\\w*",w="[a-zA-Z_]\\w*",y="\\b\\d+(\\.\\d+)?",_="(-?)(\\b0[xX][a-fA-F0-9]+|(\\b\\d+(\\.\\d*)?|\\.\\d+)([eE][-+]?\\d+)?)",O="\\b(0b[01]+)",v={
+begin:"\\\\[\\s\\S]",relevance:0},N={scope:"string",begin:"'",end:"'",
+illegal:"\\n",contains:[v]},k={scope:"string",begin:'"',end:'"',illegal:"\\n",
+contains:[v]},M=(e,t,n={})=>{const i=r({scope:"comment",begin:e,end:t,
+contains:[]},n);i.contains.push({scope:"doctag",
+begin:"[ ]*(?=(TODO|FIXME|NOTE|BUG|OPTIMIZE|HACK|XXX):)",
+end:/(TODO|FIXME|NOTE|BUG|OPTIMIZE|HACK|XXX):/,excludeBegin:!0,relevance:0})
+;const s=f("I","a","is","so","us","to","at","if","in","it","on",/[A-Za-z]+['](d|ve|re|ll|t|s|n)/,/[A-Za-z]+[-][a-z]+/,/[A-Za-z][a-z]{2,}/)
+;return i.contains.push({begin:p(/[ ]+/,"(",s,/[.]?[:]?([.][ ]|[ ])/,"){3}")}),i
+},S=M("//","$"),R=M("/\\*","\\*/"),j=M("#","$");var A=Object.freeze({
+__proto__:null,MATCH_NOTHING_RE:/\b\B/,IDENT_RE:x,UNDERSCORE_IDENT_RE:w,
+NUMBER_RE:y,C_NUMBER_RE:_,BINARY_NUMBER_RE:O,
+RE_STARTERS_RE:"!|!=|!==|%|%=|&|&&|&=|\\*|\\*=|\\+|\\+=|,|-|-=|/=|/|:|;|<<|<<=|<=|<|===|==|=|>>>=|>>=|>=|>>>|>>|>|\\?|\\[|\\{|\\(|\\^|\\^=|\\||\\|=|\\|\\||~",
+SHEBANG:(e={})=>{const t=/^#![ ]*\//
+;return e.binary&&(e.begin=p(t,/.*\b/,e.binary,/\b.*/)),r({scope:"meta",begin:t,
+end:/$/,relevance:0,"on:begin":(e,t)=>{0!==e.index&&t.ignoreMatch()}},e)},
+BACKSLASH_ESCAPE:v,APOS_STRING_MODE:N,QUOTE_STRING_MODE:k,PHRASAL_WORDS_MODE:{
+begin:/\b(a|an|the|are|I'm|isn't|don't|doesn't|won't|but|just|should|pretty|simply|enough|gonna|going|wtf|so|such|will|you|your|they|like|more)\b/
+},COMMENT:M,C_LINE_COMMENT_MODE:S,C_BLOCK_COMMENT_MODE:R,HASH_COMMENT_MODE:j,
+NUMBER_MODE:{scope:"number",begin:y,relevance:0},C_NUMBER_MODE:{scope:"number",
+begin:_,relevance:0},BINARY_NUMBER_MODE:{scope:"number",begin:O,relevance:0},
+REGEXP_MODE:{begin:/(?=\/[^/\n]*\/)/,contains:[{scope:"regexp",begin:/\//,
+end:/\/[gimuy]*/,illegal:/\n/,contains:[v,{begin:/\[/,end:/\]/,relevance:0,
+contains:[v]}]}]},TITLE_MODE:{scope:"title",begin:x,relevance:0},
+UNDERSCORE_TITLE_MODE:{scope:"title",begin:w,relevance:0},METHOD_GUARD:{
+begin:"\\.\\s*[a-zA-Z_]\\w*",relevance:0},END_SAME_AS_BEGIN:e=>Object.assign(e,{
+"on:begin":(e,t)=>{t.data._beginMatch=e[1]},"on:end":(e,t)=>{
+t.data._beginMatch!==e[1]&&t.ignoreMatch()}})});function I(e,t){
+"."===e.input[e.index-1]&&t.ignoreMatch()}function T(e,t){
+void 0!==e.className&&(e.scope=e.className,delete e.className)}function L(e,t){
+t&&e.beginKeywords&&(e.begin="\\b("+e.beginKeywords.split(" ").join("|")+")(?!\\.)(?=\\b|\\s)",
+e.__beforeBegin=I,e.keywords=e.keywords||e.beginKeywords,delete e.beginKeywords,
+void 0===e.relevance&&(e.relevance=0))}function B(e,t){
+Array.isArray(e.illegal)&&(e.illegal=f(...e.illegal))}function D(e,t){
+if(e.match){
+if(e.begin||e.end)throw Error("begin & end are not supported with match")
+;e.begin=e.match,delete e.match}}function H(e,t){
+void 0===e.relevance&&(e.relevance=1)}const P=(e,t)=>{if(!e.beforeMatch)return
+;if(e.starts)throw Error("beforeMatch cannot be used with starts")
+;const n=Object.assign({},e);Object.keys(e).forEach((t=>{delete e[t]
+})),e.keywords=n.keywords,e.begin=p(n.beforeMatch,d(n.begin)),e.starts={
+relevance:0,contains:[Object.assign(n,{endsParent:!0})]
+},e.relevance=0,delete n.beforeMatch
+},C=["of","and","for","in","not","or","if","then","parent","list","value"]
+;function $(e,t,n="keyword"){const i=Object.create(null)
+;return"string"==typeof e?r(n,e.split(" ")):Array.isArray(e)?r(n,e):Object.keys(e).forEach((n=>{
+Object.assign(i,$(e[n],t,n))})),i;function r(e,n){
+t&&(n=n.map((e=>e.toLowerCase()))),n.forEach((t=>{const n=t.split("|")
+;i[n[0]]=[e,U(n[0],n[1])]}))}}function U(e,t){
+return t?Number(t):(e=>C.includes(e.toLowerCase()))(e)?0:1}const z={},K=e=>{
+console.error(e)},W=(e,...t)=>{console.log("WARN: "+e,...t)},X=(e,t)=>{
+z[`${e}/${t}`]||(console.log(`Deprecated as of ${e}. ${t}`),z[`${e}/${t}`]=!0)
+},G=Error();function Z(e,t,{key:n}){let i=0;const r=e[n],s={},o={}
+;for(let e=1;e<=t.length;e++)o[e+i]=r[e],s[e+i]=!0,i+=b(t[e-1])
+;e[n]=o,e[n]._emit=s,e[n]._multi=!0}function F(e){(e=>{
+e.scope&&"object"==typeof e.scope&&null!==e.scope&&(e.beginScope=e.scope,
+delete e.scope)})(e),"string"==typeof e.beginScope&&(e.beginScope={
+_wrap:e.beginScope}),"string"==typeof e.endScope&&(e.endScope={_wrap:e.endScope
+}),(e=>{if(Array.isArray(e.begin)){
+if(e.skip||e.excludeBegin||e.returnBegin)throw K("skip, excludeBegin, returnBegin not compatible with beginScope: {}"),
+G
+;if("object"!=typeof e.beginScope||null===e.beginScope)throw K("beginScope must be object"),
+G;Z(e,e.begin,{key:"beginScope"}),e.begin=E(e.begin,{joinWith:""})}})(e),(e=>{
+if(Array.isArray(e.end)){
+if(e.skip||e.excludeEnd||e.returnEnd)throw K("skip, excludeEnd, returnEnd not compatible with endScope: {}"),
+G
+;if("object"!=typeof e.endScope||null===e.endScope)throw K("endScope must be object"),
+G;Z(e,e.end,{key:"endScope"}),e.end=E(e.end,{joinWith:""})}})(e)}function V(e){
+function t(t,n){
+return RegExp(g(t),"m"+(e.case_insensitive?"i":"")+(e.unicodeRegex?"u":"")+(n?"g":""))
+}class n{constructor(){
+this.matchIndexes={},this.regexes=[],this.matchAt=1,this.position=0}
+addRule(e,t){
+t.position=this.position++,this.matchIndexes[this.matchAt]=t,this.regexes.push([t,e]),
+this.matchAt+=b(e)+1}compile(){0===this.regexes.length&&(this.exec=()=>null)
+;const e=this.regexes.map((e=>e[1]));this.matcherRe=t(E(e,{joinWith:"|"
+}),!0),this.lastIndex=0}exec(e){this.matcherRe.lastIndex=this.lastIndex
+;const t=this.matcherRe.exec(e);if(!t)return null
+;const n=t.findIndex(((e,t)=>t>0&&void 0!==e)),i=this.matchIndexes[n]
+;return t.splice(0,n),Object.assign(t,i)}}class i{constructor(){
+this.rules=[],this.multiRegexes=[],
+this.count=0,this.lastIndex=0,this.regexIndex=0}getMatcher(e){
+if(this.multiRegexes[e])return this.multiRegexes[e];const t=new n
+;return this.rules.slice(e).forEach((([e,n])=>t.addRule(e,n))),
+t.compile(),this.multiRegexes[e]=t,t}resumingScanAtSamePosition(){
+return 0!==this.regexIndex}considerAll(){this.regexIndex=0}addRule(e,t){
+this.rules.push([e,t]),"begin"===t.type&&this.count++}exec(e){
+const t=this.getMatcher(this.regexIndex);t.lastIndex=this.lastIndex
+;let n=t.exec(e)
+;if(this.resumingScanAtSamePosition())if(n&&n.index===this.lastIndex);else{
+const t=this.getMatcher(0);t.lastIndex=this.lastIndex+1,n=t.exec(e)}
+return n&&(this.regexIndex+=n.position+1,
+this.regexIndex===this.count&&this.considerAll()),n}}
+if(e.compilerExtensions||(e.compilerExtensions=[]),
+e.contains&&e.contains.includes("self"))throw Error("ERR: contains `self` is not supported at the top-level of a language.  See documentation.")
+;return e.classNameAliases=r(e.classNameAliases||{}),function n(s,o){const a=s
+;if(s.isCompiled)return a
+;[T,D,F,P].forEach((e=>e(s,o))),e.compilerExtensions.forEach((e=>e(s,o))),
+s.__beforeBegin=null,[L,B,H].forEach((e=>e(s,o))),s.isCompiled=!0;let c=null
+;return"object"==typeof s.keywords&&s.keywords.$pattern&&(s.keywords=Object.assign({},s.keywords),
+c=s.keywords.$pattern,
+delete s.keywords.$pattern),c=c||/\w+/,s.keywords&&(s.keywords=$(s.keywords,e.case_insensitive)),
+a.keywordPatternRe=t(c,!0),
+o&&(s.begin||(s.begin=/\B|\b/),a.beginRe=t(a.begin),s.end||s.endsWithParent||(s.end=/\B|\b/),
+s.end&&(a.endRe=t(a.end)),
+a.terminatorEnd=g(a.end)||"",s.endsWithParent&&o.terminatorEnd&&(a.terminatorEnd+=(s.end?"|":"")+o.terminatorEnd)),
+s.illegal&&(a.illegalRe=t(s.illegal)),
+s.contains||(s.contains=[]),s.contains=[].concat(...s.contains.map((e=>(e=>(e.variants&&!e.cachedVariants&&(e.cachedVariants=e.variants.map((t=>r(e,{
+variants:null},t)))),e.cachedVariants?e.cachedVariants:q(e)?r(e,{
+starts:e.starts?r(e.starts):null
+}):Object.isFrozen(e)?r(e):e))("self"===e?s:e)))),s.contains.forEach((e=>{n(e,a)
+})),s.starts&&n(s.starts,o),a.matcher=(e=>{const t=new i
+;return e.contains.forEach((e=>t.addRule(e.begin,{rule:e,type:"begin"
+}))),e.terminatorEnd&&t.addRule(e.terminatorEnd,{type:"end"
+}),e.illegal&&t.addRule(e.illegal,{type:"illegal"}),t})(a),a}(e)}function q(e){
+return!!e&&(e.endsWithParent||q(e.starts))}class J extends Error{
+constructor(e,t){super(e),this.name="HTMLInjectionError",this.html=t}}
+const Y=i,Q=r,ee=Symbol("nomatch");var te=(t=>{
+const i=Object.create(null),r=Object.create(null),s=[];let o=!0
+;const a="Could not find the language '{}', did you forget to load/include a language module?",c={
+disableAutodetect:!0,name:"Plain text",contains:[]};let g={
+ignoreUnescapedHTML:!1,throwUnescapedHTML:!1,noHighlightRe:/^(no-?highlight)$/i,
+languageDetectRe:/\blang(?:uage)?-([\w-]+)\b/i,classPrefix:"hljs-",
+cssSelector:"pre code",languages:null,__emitter:l};function b(e){
+return g.noHighlightRe.test(e)}function m(e,t,n){let i="",r=""
+;"object"==typeof t?(i=e,
+n=t.ignoreIllegals,r=t.language):(X("10.7.0","highlight(lang, code, ...args) has been deprecated."),
+X("10.7.0","Please use highlight(code, options) instead.\nhttps://github.com/highlightjs/highlight.js/issues/2277"),
+r=e,i=t),void 0===n&&(n=!0);const s={code:i,language:r};k("before:highlight",s)
+;const o=s.result?s.result:E(s.language,s.code,n)
+;return o.code=s.code,k("after:highlight",o),o}function E(e,t,r,s){
+const c=Object.create(null);function l(){if(!N.keywords)return void M.addText(S)
+;let e=0;N.keywordPatternRe.lastIndex=0;let t=N.keywordPatternRe.exec(S),n=""
+;for(;t;){n+=S.substring(e,t.index)
+;const r=y.case_insensitive?t[0].toLowerCase():t[0],s=(i=r,N.keywords[i]);if(s){
+const[e,i]=s
+;if(M.addText(n),n="",c[r]=(c[r]||0)+1,c[r]<=7&&(R+=i),e.startsWith("_"))n+=t[0];else{
+const n=y.classNameAliases[e]||e;M.addKeyword(t[0],n)}}else n+=t[0]
+;e=N.keywordPatternRe.lastIndex,t=N.keywordPatternRe.exec(S)}var i
+;n+=S.substring(e),M.addText(n)}function d(){null!=N.subLanguage?(()=>{
+if(""===S)return;let e=null;if("string"==typeof N.subLanguage){
+if(!i[N.subLanguage])return void M.addText(S)
+;e=E(N.subLanguage,S,!0,k[N.subLanguage]),k[N.subLanguage]=e._top
+}else e=x(S,N.subLanguage.length?N.subLanguage:null)
+;N.relevance>0&&(R+=e.relevance),M.addSublanguage(e._emitter,e.language)
+})():l(),S=""}function u(e,t){let n=1;const i=t.length-1;for(;n<=i;){
+if(!e._emit[n]){n++;continue}const i=y.classNameAliases[e[n]]||e[n],r=t[n]
+;i?M.addKeyword(r,i):(S=r,l(),S=""),n++}}function h(e,t){
+return e.scope&&"string"==typeof e.scope&&M.openNode(y.classNameAliases[e.scope]||e.scope),
+e.beginScope&&(e.beginScope._wrap?(M.addKeyword(S,y.classNameAliases[e.beginScope._wrap]||e.beginScope._wrap),
+S=""):e.beginScope._multi&&(u(e.beginScope,t),S="")),N=Object.create(e,{parent:{
+value:N}}),N}function p(e,t,i){let r=((e,t)=>{const n=e&&e.exec(t)
+;return n&&0===n.index})(e.endRe,i);if(r){if(e["on:end"]){const i=new n(e)
+;e["on:end"](t,i),i.isMatchIgnored&&(r=!1)}if(r){
+for(;e.endsParent&&e.parent;)e=e.parent;return e}}
+if(e.endsWithParent)return p(e.parent,t,i)}function f(e){
+return 0===N.matcher.regexIndex?(S+=e[0],1):(I=!0,0)}function b(e){
+const n=e[0],i=t.substring(e.index),r=p(N,e,i);if(!r)return ee;const s=N
+;N.endScope&&N.endScope._wrap?(d(),
+M.addKeyword(n,N.endScope._wrap)):N.endScope&&N.endScope._multi?(d(),
+u(N.endScope,e)):s.skip?S+=n:(s.returnEnd||s.excludeEnd||(S+=n),
+d(),s.excludeEnd&&(S=n));do{
+N.scope&&M.closeNode(),N.skip||N.subLanguage||(R+=N.relevance),N=N.parent
+}while(N!==r.parent);return r.starts&&h(r.starts,e),s.returnEnd?0:n.length}
+let m={};function w(i,s){const a=s&&s[0];if(S+=i,null==a)return d(),0
+;if("begin"===m.type&&"end"===s.type&&m.index===s.index&&""===a){
+if(S+=t.slice(s.index,s.index+1),!o){const t=Error(`0 width match regex (${e})`)
+;throw t.languageName=e,t.badRule=m.rule,t}return 1}
+if(m=s,"begin"===s.type)return(e=>{
+const t=e[0],i=e.rule,r=new n(i),s=[i.__beforeBegin,i["on:begin"]]
+;for(const n of s)if(n&&(n(e,r),r.isMatchIgnored))return f(t)
+;return i.skip?S+=t:(i.excludeBegin&&(S+=t),
+d(),i.returnBegin||i.excludeBegin||(S=t)),h(i,e),i.returnBegin?0:t.length})(s)
+;if("illegal"===s.type&&!r){
+const e=Error('Illegal lexeme "'+a+'" for mode "'+(N.scope||"")+'"')
+;throw e.mode=N,e}if("end"===s.type){const e=b(s);if(e!==ee)return e}
+if("illegal"===s.type&&""===a)return 1
+;if(A>1e5&&A>3*s.index)throw Error("potential infinite loop, way more iterations than matches")
+;return S+=a,a.length}const y=O(e)
+;if(!y)throw K(a.replace("{}",e)),Error('Unknown language: "'+e+'"')
+;const _=V(y);let v="",N=s||_;const k={},M=new g.__emitter(g);(()=>{const e=[]
+;for(let t=N;t!==y;t=t.parent)t.scope&&e.unshift(t.scope)
+;e.forEach((e=>M.openNode(e)))})();let S="",R=0,j=0,A=0,I=!1;try{
+for(N.matcher.considerAll();;){
+A++,I?I=!1:N.matcher.considerAll(),N.matcher.lastIndex=j
+;const e=N.matcher.exec(t);if(!e)break;const n=w(t.substring(j,e.index),e)
+;j=e.index+n}
+return w(t.substring(j)),M.closeAllNodes(),M.finalize(),v=M.toHTML(),{
+language:e,value:v,relevance:R,illegal:!1,_emitter:M,_top:N}}catch(n){
+if(n.message&&n.message.includes("Illegal"))return{language:e,value:Y(t),
+illegal:!0,relevance:0,_illegalBy:{message:n.message,index:j,
+context:t.slice(j-100,j+100),mode:n.mode,resultSoFar:v},_emitter:M};if(o)return{
+language:e,value:Y(t),illegal:!1,relevance:0,errorRaised:n,_emitter:M,_top:N}
+;throw n}}function x(e,t){t=t||g.languages||Object.keys(i);const n=(e=>{
+const t={value:Y(e),illegal:!1,relevance:0,_top:c,_emitter:new g.__emitter(g)}
+;return t._emitter.addText(e),t})(e),r=t.filter(O).filter(N).map((t=>E(t,e,!1)))
+;r.unshift(n);const s=r.sort(((e,t)=>{
+if(e.relevance!==t.relevance)return t.relevance-e.relevance
+;if(e.language&&t.language){if(O(e.language).supersetOf===t.language)return 1
+;if(O(t.language).supersetOf===e.language)return-1}return 0})),[o,a]=s,l=o
+;return l.secondBest=a,l}function w(e){let t=null;const n=(e=>{
+let t=e.className+" ";t+=e.parentNode?e.parentNode.className:""
+;const n=g.languageDetectRe.exec(t);if(n){const t=O(n[1])
+;return t||(W(a.replace("{}",n[1])),
+W("Falling back to no-highlight mode for this block.",e)),t?n[1]:"no-highlight"}
+return t.split(/\s+/).find((e=>b(e)||O(e)))})(e);if(b(n))return
+;if(k("before:highlightElement",{el:e,language:n
+}),e.children.length>0&&(g.ignoreUnescapedHTML||(console.warn("One of your code blocks includes unescaped HTML. This is a potentially serious security risk."),
+console.warn("https://github.com/highlightjs/highlight.js/wiki/security"),
+console.warn("The element with unescaped HTML:"),
+console.warn(e)),g.throwUnescapedHTML))throw new J("One of your code blocks includes unescaped HTML.",e.innerHTML)
+;t=e;const i=t.textContent,s=n?m(i,{language:n,ignoreIllegals:!0}):x(i)
+;e.innerHTML=s.value,((e,t,n)=>{const i=t&&r[t]||n
+;e.classList.add("hljs"),e.classList.add("language-"+i)
+})(e,n,s.language),e.result={language:s.language,re:s.relevance,
+relevance:s.relevance},s.secondBest&&(e.secondBest={
+language:s.secondBest.language,relevance:s.secondBest.relevance
+}),k("after:highlightElement",{el:e,result:s,text:i})}let y=!1;function _(){
+"loading"!==document.readyState?document.querySelectorAll(g.cssSelector).forEach(w):y=!0
+}function O(e){return e=(e||"").toLowerCase(),i[e]||i[r[e]]}
+function v(e,{languageName:t}){"string"==typeof e&&(e=[e]),e.forEach((e=>{
+r[e.toLowerCase()]=t}))}function N(e){const t=O(e)
+;return t&&!t.disableAutodetect}function k(e,t){const n=e;s.forEach((e=>{
+e[n]&&e[n](t)}))}
+"undefined"!=typeof window&&window.addEventListener&&window.addEventListener("DOMContentLoaded",(()=>{
+y&&_()}),!1),Object.assign(t,{highlight:m,highlightAuto:x,highlightAll:_,
+highlightElement:w,
+highlightBlock:e=>(X("10.7.0","highlightBlock will be removed entirely in v12.0"),
+X("10.7.0","Please use highlightElement now."),w(e)),configure:e=>{g=Q(g,e)},
+initHighlighting:()=>{
+_(),X("10.6.0","initHighlighting() deprecated.  Use highlightAll() now.")},
+initHighlightingOnLoad:()=>{
+_(),X("10.6.0","initHighlightingOnLoad() deprecated.  Use highlightAll() now.")
+},registerLanguage:(e,n)=>{let r=null;try{r=n(t)}catch(t){
+if(K("Language definition for '{}' could not be registered.".replace("{}",e)),
+!o)throw t;K(t),r=c}
+r.name||(r.name=e),i[e]=r,r.rawDefinition=n.bind(null,t),r.aliases&&v(r.aliases,{
+languageName:e})},unregisterLanguage:e=>{delete i[e]
+;for(const t of Object.keys(r))r[t]===e&&delete r[t]},
+listLanguages:()=>Object.keys(i),getLanguage:O,registerAliases:v,
+autoDetection:N,inherit:Q,addPlugin:e=>{(e=>{
+e["before:highlightBlock"]&&!e["before:highlightElement"]&&(e["before:highlightElement"]=t=>{
+e["before:highlightBlock"](Object.assign({block:t.el},t))
+}),e["after:highlightBlock"]&&!e["after:highlightElement"]&&(e["after:highlightElement"]=t=>{
+e["after:highlightBlock"](Object.assign({block:t.el},t))})})(e),s.push(e)}
+}),t.debugMode=()=>{o=!1},t.safeMode=()=>{o=!0
+},t.versionString="11.7.0",t.regex={concat:p,lookahead:d,either:f,optional:h,
+anyNumberOfTimes:u};for(const t in A)"object"==typeof A[t]&&e.exports(A[t])
+;return Object.assign(t,A),t})({});return te}()
+;"object"==typeof exports&&"undefined"!=typeof module&&(module.exports=hljs);/*! `javascript` grammar compiled for Highlight.js 11.7.0 */
+(()=>{var e=(()=>{"use strict"
+;const e="[A-Za-z$_][0-9A-Za-z$_]*",n=["as","in","of","if","for","while","finally","var","new","function","do","return","void","else","break","catch","instanceof","with","throw","case","default","try","switch","continue","typeof","delete","let","yield","const","class","debugger","async","await","static","import","from","export","extends"],a=["true","false","null","undefined","NaN","Infinity"],t=["Object","Function","Boolean","Symbol","Math","Date","Number","BigInt","String","RegExp","Array","Float32Array","Float64Array","Int8Array","Uint8Array","Uint8ClampedArray","Int16Array","Int32Array","Uint16Array","Uint32Array","BigInt64Array","BigUint64Array","Set","Map","WeakSet","WeakMap","ArrayBuffer","SharedArrayBuffer","Atomics","DataView","JSON","Promise","Generator","GeneratorFunction","AsyncFunction","Reflect","Proxy","Intl","WebAssembly"],s=["Error","EvalError","InternalError","RangeError","ReferenceError","SyntaxError","TypeError","URIError"],r=["setInterval","setTimeout","clearInterval","clearTimeout","require","exports","eval","isFinite","isNaN","parseFloat","parseInt","decodeURI","decodeURIComponent","encodeURI","encodeURIComponent","escape","unescape"],c=["arguments","this","super","console","window","document","localStorage","module","global"],i=[].concat(r,t,s)
+;return o=>{const l=o.regex,b=e,d={begin:/<[A-Za-z0-9\\._:-]+/,
+end:/\/[A-Za-z0-9\\._:-]+>|\/>/,isTrulyOpeningTag:(e,n)=>{
+const a=e[0].length+e.index,t=e.input[a]
+;if("<"===t||","===t)return void n.ignoreMatch();let s
+;">"===t&&(((e,{after:n})=>{const a="",M={
+match:[/const|var|let/,/\s+/,b,/\s*/,/=\s*/,/(async\s*)?/,l.lookahead(C)],
+keywords:"async",className:{1:"keyword",3:"title.function"},contains:[S]}
+;return{name:"Javascript",aliases:["js","jsx","mjs","cjs"],keywords:g,exports:{
+PARAMS_CONTAINS:p,CLASS_REFERENCE:R},illegal:/#(?![$_A-z])/,
+contains:[o.SHEBANG({label:"shebang",binary:"node",relevance:5}),{
+label:"use_strict",className:"meta",relevance:10,
+begin:/^\s*['"]use (strict|asm)['"]/
+},o.APOS_STRING_MODE,o.QUOTE_STRING_MODE,y,N,_,h,{match:/\$\d+/},E,R,{
+className:"attr",begin:b+l.lookahead(":"),relevance:0},M,{
+begin:"("+o.RE_STARTERS_RE+"|\\b(case|return|throw)\\b)\\s*",
+keywords:"return throw case",relevance:0,contains:[h,o.REGEXP_MODE,{
+className:"function",begin:C,returnBegin:!0,end:"\\s*=>",contains:[{
+className:"params",variants:[{begin:o.UNDERSCORE_IDENT_RE,relevance:0},{
+className:null,begin:/\(\s*\)/,skip:!0},{begin:/\(/,end:/\)/,excludeBegin:!0,
+excludeEnd:!0,keywords:g,contains:p}]}]},{begin:/,/,relevance:0},{match:/\s+/,
+relevance:0},{variants:[{begin:"<>",end:""},{
+match:/<[A-Za-z0-9\\._:-]+\s*\/>/},{begin:d.begin,
+"on:begin":d.isTrulyOpeningTag,end:d.end}],subLanguage:"xml",contains:[{
+begin:d.begin,end:d.end,skip:!0,contains:["self"]}]}]},O,{
+beginKeywords:"while if switch catch for"},{
+begin:"\\b(?!function)"+o.UNDERSCORE_IDENT_RE+"\\([^()]*(\\([^()]*(\\([^()]*\\)[^()]*)*\\)[^()]*)*\\)\\s*\\{",
+returnBegin:!0,label:"func.def",contains:[S,o.inherit(o.TITLE_MODE,{begin:b,
+className:"title.function"})]},{match:/\.\.\./,relevance:0},x,{match:"\\$"+b,
+relevance:0},{match:[/\bconstructor(?=\s*\()/],className:{1:"title.function"},
+contains:[S]},k,{relevance:0,match:/\b[A-Z][A-Z_0-9]+\b/,
+className:"variable.constant"},w,T,{match:/\$[(.]/}]}}})()
+;hljs.registerLanguage("javascript",e)})();/*! `xml` grammar compiled for Highlight.js 11.7.0 */
+(()=>{var e=(()=>{"use strict";return e=>{
+const a=e.regex,n=a.concat(/[\p{L}_]/u,a.optional(/[\p{L}0-9_.-]*:/u),/[\p{L}0-9_.-]*/u),s={
+className:"symbol",begin:/&[a-z]+;|&#[0-9]+;|&#x[a-f0-9]+;/},t={begin:/\s/,
+contains:[{className:"keyword",begin:/#?[a-z_][a-z1-9_-]+/,illegal:/\n/}]
+},i=e.inherit(t,{begin:/\(/,end:/\)/}),c=e.inherit(e.APOS_STRING_MODE,{
+className:"string"}),l=e.inherit(e.QUOTE_STRING_MODE,{className:"string"}),r={
+endsWithParent:!0,illegal:/`]+/}]}]}]};return{
+name:"HTML, XML",
+aliases:["html","xhtml","rss","atom","xjb","xsd","xsl","plist","wsf","svg"],
+case_insensitive:!0,unicodeRegex:!0,contains:[{className:"meta",begin://,relevance:10,contains:[t,l,c,i,{begin:/\[/,end:/\]/,contains:[{
+className:"meta",begin://,contains:[t,i,l,c]}]}]
+},e.COMMENT(//,{relevance:10}),{begin://,
+relevance:10},s,{className:"meta",end:/\?>/,variants:[{begin:/<\?xml/,
+relevance:10,contains:[l]},{begin:/<\?[a-z][a-z0-9]+/}]},{className:"tag",
+begin:/)/,end:/>/,keywords:{name:"style"},contains:[r],starts:{
+end:/<\/style>/,returnEnd:!0,subLanguage:["css","xml"]}},{className:"tag",
+begin:/)/,end:/>/,keywords:{name:"script"},contains:[r],starts:{
+end:/<\/script>/,returnEnd:!0,subLanguage:["javascript","handlebars","xml"]}},{
+className:"tag",begin:/<>|<\/>/},{className:"tag",
+begin:a.concat(//,/>/,/\s/)))),
+end:/\/?>/,contains:[{className:"name",begin:n,relevance:0,starts:r}]},{
+className:"tag",begin:a.concat(/<\//,a.lookahead(a.concat(n,/>/))),contains:[{
+className:"name",begin:n,relevance:0},{begin:/>/,relevance:0,endsParent:!0}]}]}}
+})();hljs.registerLanguage("xml",e)})();/*! `twig` grammar compiled for Highlight.js 11.7.0 */
+(()=>{var e=(()=>{"use strict";return e=>{
+const a=e.regex,t=["absolute_url","asset|0","asset_version","attribute","block","constant","controller|0","country_timezones","csrf_token","cycle","date","dump","expression","form|0","form_end","form_errors","form_help","form_label","form_rest","form_row","form_start","form_widget","html_classes","include","is_granted","logout_path","logout_url","max","min","parent","path|0","random","range","relative_path","render","render_esi","source","template_from_string","url|0"]
+;let r=["apply","autoescape","block","cache","deprecated","do","embed","extends","filter","flush","for","form_theme","from","if","import","include","macro","sandbox","set","stopwatch","trans","trans_default_domain","transchoice","use","verbatim","with"]
+;r=r.concat(r.map((e=>"end"+e)));const n={scope:"string",variants:[{begin:/'/,
+end:/'/},{begin:/"/,end:/"/}]},o={scope:"number",match:/\d+/},s={begin:/\(/,
+end:/\)/,excludeBegin:!0,excludeEnd:!0,contains:[n,o]},c={
+beginKeywords:t.join(" "),keywords:{name:t},relevance:0,contains:[s]},m={
+match:/\|(?=[A-Za-z_]+:?)/,beginScope:"punctuation",relevance:0,contains:[{
+match:/[A-Za-z_]+:?/,
+keywords:["abs","abbr_class","abbr_method","batch","capitalize","column","convert_encoding","country_name","currency_name","currency_symbol","data_uri","date","date_modify","default","escape","file_excerpt","file_link","file_relative","filter","first","format","format_args","format_args_as_text","format_currency","format_date","format_datetime","format_file","format_file_from_text","format_number","format_time","html_to_markdown","humanize","inky_to_html","inline_css","join","json_encode","keys","language_name","last","length","locale_name","lower","map","markdown","markdown_to_html","merge","nl2br","number_format","raw","reduce","replace","reverse","round","slice","slug","sort","spaceless","split","striptags","timezone_name","title","trans","transchoice","trim","u|0","upper","url_encode","yaml_dump","yaml_encode"]
+}]},i=(e,{relevance:t})=>({beginScope:{1:"template-tag",3:"name"},
+relevance:t||2,endScope:"template-tag",begin:[/\{%/,/\s*/,a.either(...e)],
+end:/%\}/,keywords:"in",contains:[m,c,n,o]}),l=i(r,{relevance:2
+}),_=i([/[a-z_]+/],{relevance:1});return{name:"Twig",aliases:["craftcms"],
+case_insensitive:!0,subLanguage:"xml",contains:[e.COMMENT(/\{#/,/#\}/),l,_,{
+className:"template-variable",begin:/\{\{/,end:/\}\}/,contains:["self",m,c,n,o]
+}]}}})();hljs.registerLanguage("twig",e)})();/*! `json` grammar compiled for Highlight.js 11.7.0 */
+(()=>{var e=(()=>{"use strict";return e=>{const a=["true","false","null"],n={
+scope:"literal",beginKeywords:a.join(" ")};return{name:"JSON",keywords:{
+literal:a},contains:[{className:"attr",begin:/"(\\.|[^\\"\r\n])*"(?=\s*:)/,
+relevance:1.01},{match:/[{}[\],:]/,className:"punctuation",relevance:0
+},e.QUOTE_STRING_MODE,n,e.C_NUMBER_MODE,e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE],
+illegal:"\\S"}}})();hljs.registerLanguage("json",e)})();/*! `graphql` grammar compiled for Highlight.js 11.7.0 */
+(()=>{var e=(()=>{"use strict";return e=>{const a=e.regex;return{name:"GraphQL",
+aliases:["gql"],case_insensitive:!0,disableAutodetect:!1,keywords:{
+keyword:["query","mutation","subscription","type","input","schema","directive","interface","union","scalar","fragment","enum","on"],
+literal:["true","false","null"]},
+contains:[e.HASH_COMMENT_MODE,e.QUOTE_STRING_MODE,e.NUMBER_MODE,{
+scope:"punctuation",match:/[.]{3}/,relevance:0},{scope:"punctuation",
+begin:/[\!\(\)\:\=\[\]\{\|\}]{1}/,relevance:0},{scope:"variable",begin:/\$/,
+end:/\W/,excludeEnd:!0,relevance:0},{scope:"meta",match:/@\w+/,excludeEnd:!0},{
+scope:"symbol",begin:a.concat(/[_A-Za-z][_0-9A-Za-z]*/,a.lookahead(/\s*:/)),
+relevance:0}],illegal:[/[;<']/,/BEGIN/]}}})();hljs.registerLanguage("graphql",e)
+})();/*! `css` grammar compiled for Highlight.js 11.7.0 */
+(()=>{var e=(()=>{"use strict"
+;const e=["a","abbr","address","article","aside","audio","b","blockquote","body","button","canvas","caption","cite","code","dd","del","details","dfn","div","dl","dt","em","fieldset","figcaption","figure","footer","form","h1","h2","h3","h4","h5","h6","header","hgroup","html","i","iframe","img","input","ins","kbd","label","legend","li","main","mark","menu","nav","object","ol","p","q","quote","samp","section","span","strong","summary","sup","table","tbody","td","textarea","tfoot","th","thead","time","tr","ul","var","video"],i=["any-hover","any-pointer","aspect-ratio","color","color-gamut","color-index","device-aspect-ratio","device-height","device-width","display-mode","forced-colors","grid","height","hover","inverted-colors","monochrome","orientation","overflow-block","overflow-inline","pointer","prefers-color-scheme","prefers-contrast","prefers-reduced-motion","prefers-reduced-transparency","resolution","scan","scripting","update","width","min-width","max-width","min-height","max-height"],r=["active","any-link","blank","checked","current","default","defined","dir","disabled","drop","empty","enabled","first","first-child","first-of-type","fullscreen","future","focus","focus-visible","focus-within","has","host","host-context","hover","indeterminate","in-range","invalid","is","lang","last-child","last-of-type","left","link","local-link","not","nth-child","nth-col","nth-last-child","nth-last-col","nth-last-of-type","nth-of-type","only-child","only-of-type","optional","out-of-range","past","placeholder-shown","read-only","read-write","required","right","root","scope","target","target-within","user-invalid","valid","visited","where"],t=["after","backdrop","before","cue","cue-region","first-letter","first-line","grammar-error","marker","part","placeholder","selection","slotted","spelling-error"],o=["align-content","align-items","align-self","all","animation","animation-delay","animation-direction","animation-duration","animation-fill-mode","animation-iteration-count","animation-name","animation-play-state","animation-timing-function","backface-visibility","background","background-attachment","background-blend-mode","background-clip","background-color","background-image","background-origin","background-position","background-repeat","background-size","block-size","border","border-block","border-block-color","border-block-end","border-block-end-color","border-block-end-style","border-block-end-width","border-block-start","border-block-start-color","border-block-start-style","border-block-start-width","border-block-style","border-block-width","border-bottom","border-bottom-color","border-bottom-left-radius","border-bottom-right-radius","border-bottom-style","border-bottom-width","border-collapse","border-color","border-image","border-image-outset","border-image-repeat","border-image-slice","border-image-source","border-image-width","border-inline","border-inline-color","border-inline-end","border-inline-end-color","border-inline-end-style","border-inline-end-width","border-inline-start","border-inline-start-color","border-inline-start-style","border-inline-start-width","border-inline-style","border-inline-width","border-left","border-left-color","border-left-style","border-left-width","border-radius","border-right","border-right-color","border-right-style","border-right-width","border-spacing","border-style","border-top","border-top-color","border-top-left-radius","border-top-right-radius","border-top-style","border-top-width","border-width","bottom","box-decoration-break","box-shadow","box-sizing","break-after","break-before","break-inside","caption-side","caret-color","clear","clip","clip-path","clip-rule","color","column-count","column-fill","column-gap","column-rule","column-rule-color","column-rule-style","column-rule-width","column-span","column-width","columns","contain","content","content-visibility","counter-increment","counter-reset","cue","cue-after","cue-before","cursor","direction","display","empty-cells","filter","flex","flex-basis","flex-direction","flex-flow","flex-grow","flex-shrink","flex-wrap","float","flow","font","font-display","font-family","font-feature-settings","font-kerning","font-language-override","font-size","font-size-adjust","font-smoothing","font-stretch","font-style","font-synthesis","font-variant","font-variant-caps","font-variant-east-asian","font-variant-ligatures","font-variant-numeric","font-variant-position","font-variation-settings","font-weight","gap","glyph-orientation-vertical","grid","grid-area","grid-auto-columns","grid-auto-flow","grid-auto-rows","grid-column","grid-column-end","grid-column-start","grid-gap","grid-row","grid-row-end","grid-row-start","grid-template","grid-template-areas","grid-template-columns","grid-template-rows","hanging-punctuation","height","hyphens","icon","image-orientation","image-rendering","image-resolution","ime-mode","inline-size","isolation","justify-content","left","letter-spacing","line-break","line-height","list-style","list-style-image","list-style-position","list-style-type","margin","margin-block","margin-block-end","margin-block-start","margin-bottom","margin-inline","margin-inline-end","margin-inline-start","margin-left","margin-right","margin-top","marks","mask","mask-border","mask-border-mode","mask-border-outset","mask-border-repeat","mask-border-slice","mask-border-source","mask-border-width","mask-clip","mask-composite","mask-image","mask-mode","mask-origin","mask-position","mask-repeat","mask-size","mask-type","max-block-size","max-height","max-inline-size","max-width","min-block-size","min-height","min-inline-size","min-width","mix-blend-mode","nav-down","nav-index","nav-left","nav-right","nav-up","none","normal","object-fit","object-position","opacity","order","orphans","outline","outline-color","outline-offset","outline-style","outline-width","overflow","overflow-wrap","overflow-x","overflow-y","padding","padding-block","padding-block-end","padding-block-start","padding-bottom","padding-inline","padding-inline-end","padding-inline-start","padding-left","padding-right","padding-top","page-break-after","page-break-before","page-break-inside","pause","pause-after","pause-before","perspective","perspective-origin","pointer-events","position","quotes","resize","rest","rest-after","rest-before","right","row-gap","scroll-margin","scroll-margin-block","scroll-margin-block-end","scroll-margin-block-start","scroll-margin-bottom","scroll-margin-inline","scroll-margin-inline-end","scroll-margin-inline-start","scroll-margin-left","scroll-margin-right","scroll-margin-top","scroll-padding","scroll-padding-block","scroll-padding-block-end","scroll-padding-block-start","scroll-padding-bottom","scroll-padding-inline","scroll-padding-inline-end","scroll-padding-inline-start","scroll-padding-left","scroll-padding-right","scroll-padding-top","scroll-snap-align","scroll-snap-stop","scroll-snap-type","scrollbar-color","scrollbar-gutter","scrollbar-width","shape-image-threshold","shape-margin","shape-outside","speak","speak-as","src","tab-size","table-layout","text-align","text-align-all","text-align-last","text-combine-upright","text-decoration","text-decoration-color","text-decoration-line","text-decoration-style","text-emphasis","text-emphasis-color","text-emphasis-position","text-emphasis-style","text-indent","text-justify","text-orientation","text-overflow","text-rendering","text-shadow","text-transform","text-underline-position","top","transform","transform-box","transform-origin","transform-style","transition","transition-delay","transition-duration","transition-property","transition-timing-function","unicode-bidi","vertical-align","visibility","voice-balance","voice-duration","voice-family","voice-pitch","voice-range","voice-rate","voice-stress","voice-volume","white-space","widows","width","will-change","word-break","word-spacing","word-wrap","writing-mode","z-index"].reverse()
+;return n=>{const a=n.regex,l=(e=>({IMPORTANT:{scope:"meta",begin:"!important"},
+BLOCK_COMMENT:e.C_BLOCK_COMMENT_MODE,HEXCOLOR:{scope:"number",
+begin:/#(([0-9a-fA-F]{3,4})|(([0-9a-fA-F]{2}){3,4}))\b/},FUNCTION_DISPATCH:{
+className:"built_in",begin:/[\w-]+(?=\()/},ATTRIBUTE_SELECTOR_MODE:{
+scope:"selector-attr",begin:/\[/,end:/\]/,illegal:"$",
+contains:[e.APOS_STRING_MODE,e.QUOTE_STRING_MODE]},CSS_NUMBER_MODE:{
+scope:"number",
+begin:e.NUMBER_RE+"(%|em|ex|ch|rem|vw|vh|vmin|vmax|cm|mm|in|pt|pc|px|deg|grad|rad|turn|s|ms|Hz|kHz|dpi|dpcm|dppx)?",
+relevance:0},CSS_VARIABLE:{className:"attr",begin:/--[A-Za-z][A-Za-z0-9_-]*/}
+}))(n),s=[n.APOS_STRING_MODE,n.QUOTE_STRING_MODE];return{name:"CSS",
+case_insensitive:!0,illegal:/[=|'\$]/,keywords:{keyframePosition:"from to"},
+classNameAliases:{keyframePosition:"selector-tag"},contains:[l.BLOCK_COMMENT,{
+begin:/-(webkit|moz|ms|o)-(?=[a-z])/},l.CSS_NUMBER_MODE,{
+className:"selector-id",begin:/#[A-Za-z0-9_-]+/,relevance:0},{
+className:"selector-class",begin:"\\.[a-zA-Z-][a-zA-Z0-9_-]*",relevance:0
+},l.ATTRIBUTE_SELECTOR_MODE,{className:"selector-pseudo",variants:[{
+begin:":("+r.join("|")+")"},{begin:":(:)?("+t.join("|")+")"}]},l.CSS_VARIABLE,{
+className:"attribute",begin:"\\b("+o.join("|")+")\\b"},{begin:/:/,end:/[;}{]/,
+contains:[l.BLOCK_COMMENT,l.HEXCOLOR,l.IMPORTANT,l.CSS_NUMBER_MODE,...s,{
+begin:/(url|data-uri)\(/,end:/\)/,relevance:0,keywords:{built_in:"url data-uri"
+},contains:[...s,{className:"string",begin:/[^)]/,endsWithParent:!0,
+excludeEnd:!0}]},l.FUNCTION_DISPATCH]},{begin:a.lookahead(/@/),end:"[{;]",
+relevance:0,illegal:/:/,contains:[{className:"keyword",begin:/@-?\w[\w]*(-\w+)*/
+},{begin:/\s/,endsWithParent:!0,excludeEnd:!0,relevance:0,keywords:{
+$pattern:/[a-z-]+/,keyword:"and or not only",attribute:i.join(" ")},contains:[{
+begin:/[a-z-]+(?=:)/,className:"attribute"},...s,l.CSS_NUMBER_MODE]}]},{
+className:"selector-tag",begin:"\\b("+e.join("|")+")\\b"}]}}})()
+;hljs.registerLanguage("css",e)})();/*! `plaintext` grammar compiled for Highlight.js 11.7.0 */
+(()=>{var t=(()=>{"use strict";return t=>({name:"Plain text",
+aliases:["text","txt"],disableAutodetect:!0})})()
+;hljs.registerLanguage("plaintext",t)})();/*! `scss` grammar compiled for Highlight.js 11.7.0 */
+(()=>{var e=(()=>{"use strict"
+;const e=["a","abbr","address","article","aside","audio","b","blockquote","body","button","canvas","caption","cite","code","dd","del","details","dfn","div","dl","dt","em","fieldset","figcaption","figure","footer","form","h1","h2","h3","h4","h5","h6","header","hgroup","html","i","iframe","img","input","ins","kbd","label","legend","li","main","mark","menu","nav","object","ol","p","q","quote","samp","section","span","strong","summary","sup","table","tbody","td","textarea","tfoot","th","thead","time","tr","ul","var","video"],r=["any-hover","any-pointer","aspect-ratio","color","color-gamut","color-index","device-aspect-ratio","device-height","device-width","display-mode","forced-colors","grid","height","hover","inverted-colors","monochrome","orientation","overflow-block","overflow-inline","pointer","prefers-color-scheme","prefers-contrast","prefers-reduced-motion","prefers-reduced-transparency","resolution","scan","scripting","update","width","min-width","max-width","min-height","max-height"],i=["active","any-link","blank","checked","current","default","defined","dir","disabled","drop","empty","enabled","first","first-child","first-of-type","fullscreen","future","focus","focus-visible","focus-within","has","host","host-context","hover","indeterminate","in-range","invalid","is","lang","last-child","last-of-type","left","link","local-link","not","nth-child","nth-col","nth-last-child","nth-last-col","nth-last-of-type","nth-of-type","only-child","only-of-type","optional","out-of-range","past","placeholder-shown","read-only","read-write","required","right","root","scope","target","target-within","user-invalid","valid","visited","where"],t=["after","backdrop","before","cue","cue-region","first-letter","first-line","grammar-error","marker","part","placeholder","selection","slotted","spelling-error"],o=["align-content","align-items","align-self","all","animation","animation-delay","animation-direction","animation-duration","animation-fill-mode","animation-iteration-count","animation-name","animation-play-state","animation-timing-function","backface-visibility","background","background-attachment","background-blend-mode","background-clip","background-color","background-image","background-origin","background-position","background-repeat","background-size","block-size","border","border-block","border-block-color","border-block-end","border-block-end-color","border-block-end-style","border-block-end-width","border-block-start","border-block-start-color","border-block-start-style","border-block-start-width","border-block-style","border-block-width","border-bottom","border-bottom-color","border-bottom-left-radius","border-bottom-right-radius","border-bottom-style","border-bottom-width","border-collapse","border-color","border-image","border-image-outset","border-image-repeat","border-image-slice","border-image-source","border-image-width","border-inline","border-inline-color","border-inline-end","border-inline-end-color","border-inline-end-style","border-inline-end-width","border-inline-start","border-inline-start-color","border-inline-start-style","border-inline-start-width","border-inline-style","border-inline-width","border-left","border-left-color","border-left-style","border-left-width","border-radius","border-right","border-right-color","border-right-style","border-right-width","border-spacing","border-style","border-top","border-top-color","border-top-left-radius","border-top-right-radius","border-top-style","border-top-width","border-width","bottom","box-decoration-break","box-shadow","box-sizing","break-after","break-before","break-inside","caption-side","caret-color","clear","clip","clip-path","clip-rule","color","column-count","column-fill","column-gap","column-rule","column-rule-color","column-rule-style","column-rule-width","column-span","column-width","columns","contain","content","content-visibility","counter-increment","counter-reset","cue","cue-after","cue-before","cursor","direction","display","empty-cells","filter","flex","flex-basis","flex-direction","flex-flow","flex-grow","flex-shrink","flex-wrap","float","flow","font","font-display","font-family","font-feature-settings","font-kerning","font-language-override","font-size","font-size-adjust","font-smoothing","font-stretch","font-style","font-synthesis","font-variant","font-variant-caps","font-variant-east-asian","font-variant-ligatures","font-variant-numeric","font-variant-position","font-variation-settings","font-weight","gap","glyph-orientation-vertical","grid","grid-area","grid-auto-columns","grid-auto-flow","grid-auto-rows","grid-column","grid-column-end","grid-column-start","grid-gap","grid-row","grid-row-end","grid-row-start","grid-template","grid-template-areas","grid-template-columns","grid-template-rows","hanging-punctuation","height","hyphens","icon","image-orientation","image-rendering","image-resolution","ime-mode","inline-size","isolation","justify-content","left","letter-spacing","line-break","line-height","list-style","list-style-image","list-style-position","list-style-type","margin","margin-block","margin-block-end","margin-block-start","margin-bottom","margin-inline","margin-inline-end","margin-inline-start","margin-left","margin-right","margin-top","marks","mask","mask-border","mask-border-mode","mask-border-outset","mask-border-repeat","mask-border-slice","mask-border-source","mask-border-width","mask-clip","mask-composite","mask-image","mask-mode","mask-origin","mask-position","mask-repeat","mask-size","mask-type","max-block-size","max-height","max-inline-size","max-width","min-block-size","min-height","min-inline-size","min-width","mix-blend-mode","nav-down","nav-index","nav-left","nav-right","nav-up","none","normal","object-fit","object-position","opacity","order","orphans","outline","outline-color","outline-offset","outline-style","outline-width","overflow","overflow-wrap","overflow-x","overflow-y","padding","padding-block","padding-block-end","padding-block-start","padding-bottom","padding-inline","padding-inline-end","padding-inline-start","padding-left","padding-right","padding-top","page-break-after","page-break-before","page-break-inside","pause","pause-after","pause-before","perspective","perspective-origin","pointer-events","position","quotes","resize","rest","rest-after","rest-before","right","row-gap","scroll-margin","scroll-margin-block","scroll-margin-block-end","scroll-margin-block-start","scroll-margin-bottom","scroll-margin-inline","scroll-margin-inline-end","scroll-margin-inline-start","scroll-margin-left","scroll-margin-right","scroll-margin-top","scroll-padding","scroll-padding-block","scroll-padding-block-end","scroll-padding-block-start","scroll-padding-bottom","scroll-padding-inline","scroll-padding-inline-end","scroll-padding-inline-start","scroll-padding-left","scroll-padding-right","scroll-padding-top","scroll-snap-align","scroll-snap-stop","scroll-snap-type","scrollbar-color","scrollbar-gutter","scrollbar-width","shape-image-threshold","shape-margin","shape-outside","speak","speak-as","src","tab-size","table-layout","text-align","text-align-all","text-align-last","text-combine-upright","text-decoration","text-decoration-color","text-decoration-line","text-decoration-style","text-emphasis","text-emphasis-color","text-emphasis-position","text-emphasis-style","text-indent","text-justify","text-orientation","text-overflow","text-rendering","text-shadow","text-transform","text-underline-position","top","transform","transform-box","transform-origin","transform-style","transition","transition-delay","transition-duration","transition-property","transition-timing-function","unicode-bidi","vertical-align","visibility","voice-balance","voice-duration","voice-family","voice-pitch","voice-range","voice-rate","voice-stress","voice-volume","white-space","widows","width","will-change","word-break","word-spacing","word-wrap","writing-mode","z-index"].reverse()
+;return n=>{const a=(e=>({IMPORTANT:{scope:"meta",begin:"!important"},
+BLOCK_COMMENT:e.C_BLOCK_COMMENT_MODE,HEXCOLOR:{scope:"number",
+begin:/#(([0-9a-fA-F]{3,4})|(([0-9a-fA-F]{2}){3,4}))\b/},FUNCTION_DISPATCH:{
+className:"built_in",begin:/[\w-]+(?=\()/},ATTRIBUTE_SELECTOR_MODE:{
+scope:"selector-attr",begin:/\[/,end:/\]/,illegal:"$",
+contains:[e.APOS_STRING_MODE,e.QUOTE_STRING_MODE]},CSS_NUMBER_MODE:{
+scope:"number",
+begin:e.NUMBER_RE+"(%|em|ex|ch|rem|vw|vh|vmin|vmax|cm|mm|in|pt|pc|px|deg|grad|rad|turn|s|ms|Hz|kHz|dpi|dpcm|dppx)?",
+relevance:0},CSS_VARIABLE:{className:"attr",begin:/--[A-Za-z][A-Za-z0-9_-]*/}
+}))(n),l=t,s=i,d="@[a-z-]+",c={className:"variable",
+begin:"(\\$[a-zA-Z-][a-zA-Z0-9_-]*)\\b",relevance:0};return{name:"SCSS",
+case_insensitive:!0,illegal:"[=/|']",
+contains:[n.C_LINE_COMMENT_MODE,n.C_BLOCK_COMMENT_MODE,a.CSS_NUMBER_MODE,{
+className:"selector-id",begin:"#[A-Za-z0-9_-]+",relevance:0},{
+className:"selector-class",begin:"\\.[A-Za-z0-9_-]+",relevance:0
+},a.ATTRIBUTE_SELECTOR_MODE,{className:"selector-tag",
+begin:"\\b("+e.join("|")+")\\b",relevance:0},{className:"selector-pseudo",
+begin:":("+s.join("|")+")"},{className:"selector-pseudo",
+begin:":(:)?("+l.join("|")+")"},c,{begin:/\(/,end:/\)/,
+contains:[a.CSS_NUMBER_MODE]},a.CSS_VARIABLE,{className:"attribute",
+begin:"\\b("+o.join("|")+")\\b"},{
+begin:"\\b(whitespace|wait|w-resize|visible|vertical-text|vertical-ideographic|uppercase|upper-roman|upper-alpha|underline|transparent|top|thin|thick|text|text-top|text-bottom|tb-rl|table-header-group|table-footer-group|sw-resize|super|strict|static|square|solid|small-caps|separate|se-resize|scroll|s-resize|rtl|row-resize|ridge|right|repeat|repeat-y|repeat-x|relative|progress|pointer|overline|outside|outset|oblique|nowrap|not-allowed|normal|none|nw-resize|no-repeat|no-drop|newspaper|ne-resize|n-resize|move|middle|medium|ltr|lr-tb|lowercase|lower-roman|lower-alpha|loose|list-item|line|line-through|line-edge|lighter|left|keep-all|justify|italic|inter-word|inter-ideograph|inside|inset|inline|inline-block|inherit|inactive|ideograph-space|ideograph-parenthesis|ideograph-numeric|ideograph-alpha|horizontal|hidden|help|hand|groove|fixed|ellipsis|e-resize|double|dotted|distribute|distribute-space|distribute-letter|distribute-all-lines|disc|disabled|default|decimal|dashed|crosshair|collapse|col-resize|circle|char|center|capitalize|break-word|break-all|bottom|both|bolder|bold|block|bidi-override|below|baseline|auto|always|all-scroll|absolute|table|table-cell)\\b"
+},{begin:/:/,end:/[;}{]/,relevance:0,
+contains:[a.BLOCK_COMMENT,c,a.HEXCOLOR,a.CSS_NUMBER_MODE,n.QUOTE_STRING_MODE,n.APOS_STRING_MODE,a.IMPORTANT,a.FUNCTION_DISPATCH]
+},{begin:"@(page|font-face)",keywords:{$pattern:d,keyword:"@page @font-face"}},{
+begin:"@",end:"[{;]",returnBegin:!0,keywords:{$pattern:/[a-z-]+/,
+keyword:"and or not only",attribute:r.join(" ")},contains:[{begin:d,
+className:"keyword"},{begin:/[a-z-]+(?=:)/,className:"attribute"
+},c,n.QUOTE_STRING_MODE,n.APOS_STRING_MODE,a.HEXCOLOR,a.CSS_NUMBER_MODE]
+},a.FUNCTION_DISPATCH]}}})();hljs.registerLanguage("scss",e)})();/*! `typescript` grammar compiled for Highlight.js 11.7.0 */
+(()=>{var e=(()=>{"use strict"
+;const e="[A-Za-z$_][0-9A-Za-z$_]*",n=["as","in","of","if","for","while","finally","var","new","function","do","return","void","else","break","catch","instanceof","with","throw","case","default","try","switch","continue","typeof","delete","let","yield","const","class","debugger","async","await","static","import","from","export","extends"],a=["true","false","null","undefined","NaN","Infinity"],t=["Object","Function","Boolean","Symbol","Math","Date","Number","BigInt","String","RegExp","Array","Float32Array","Float64Array","Int8Array","Uint8Array","Uint8ClampedArray","Int16Array","Int32Array","Uint16Array","Uint32Array","BigInt64Array","BigUint64Array","Set","Map","WeakSet","WeakMap","ArrayBuffer","SharedArrayBuffer","Atomics","DataView","JSON","Promise","Generator","GeneratorFunction","AsyncFunction","Reflect","Proxy","Intl","WebAssembly"],s=["Error","EvalError","InternalError","RangeError","ReferenceError","SyntaxError","TypeError","URIError"],c=["setInterval","setTimeout","clearInterval","clearTimeout","require","exports","eval","isFinite","isNaN","parseFloat","parseInt","decodeURI","decodeURIComponent","encodeURI","encodeURIComponent","escape","unescape"],r=["arguments","this","super","console","window","document","localStorage","module","global"],i=[].concat(c,t,s)
+;function o(o){const l=o.regex,d=e,b={begin:/<[A-Za-z0-9\\._:-]+/,
+end:/\/[A-Za-z0-9\\._:-]+>|\/>/,isTrulyOpeningTag:(e,n)=>{
+const a=e[0].length+e.index,t=e.input[a]
+;if("<"===t||","===t)return void n.ignoreMatch();let s
+;">"===t&&(((e,{after:n})=>{const a="",M={
+match:[/const|var|let/,/\s+/,d,/\s*/,/=\s*/,/(async\s*)?/,l.lookahead(T)],
+keywords:"async",className:{1:"keyword",3:"title.function"},contains:[S]}
+;return{name:"Javascript",aliases:["js","jsx","mjs","cjs"],keywords:g,exports:{
+PARAMS_CONTAINS:v,CLASS_REFERENCE:R},illegal:/#(?![$_A-z])/,
+contains:[o.SHEBANG({label:"shebang",binary:"node",relevance:5}),{
+label:"use_strict",className:"meta",relevance:10,
+begin:/^\s*['"]use (strict|asm)['"]/
+},o.APOS_STRING_MODE,o.QUOTE_STRING_MODE,A,p,_,N,{match:/\$\d+/},E,R,{
+className:"attr",begin:d+l.lookahead(":"),relevance:0},M,{
+begin:"("+o.RE_STARTERS_RE+"|\\b(case|return|throw)\\b)\\s*",
+keywords:"return throw case",relevance:0,contains:[N,o.REGEXP_MODE,{
+className:"function",begin:T,returnBegin:!0,end:"\\s*=>",contains:[{
+className:"params",variants:[{begin:o.UNDERSCORE_IDENT_RE,relevance:0},{
+className:null,begin:/\(\s*\)/,skip:!0},{begin:/\(/,end:/\)/,excludeBegin:!0,
+excludeEnd:!0,keywords:g,contains:v}]}]},{begin:/,/,relevance:0},{match:/\s+/,
+relevance:0},{variants:[{begin:"<>",end:""},{
+match:/<[A-Za-z0-9\\._:-]+\s*\/>/},{begin:b.begin,
+"on:begin":b.isTrulyOpeningTag,end:b.end}],subLanguage:"xml",contains:[{
+begin:b.begin,end:b.end,skip:!0,contains:["self"]}]}]},x,{
+beginKeywords:"while if switch catch for"},{
+begin:"\\b(?!function)"+o.UNDERSCORE_IDENT_RE+"\\([^()]*(\\([^()]*(\\([^()]*\\)[^()]*)*\\)[^()]*)*\\)\\s*\\{",
+returnBegin:!0,label:"func.def",contains:[S,o.inherit(o.TITLE_MODE,{begin:d,
+className:"title.function"})]},{match:/\.\.\./,relevance:0},I,{match:"\\$"+d,
+relevance:0},{match:[/\bconstructor(?=\s*\()/],className:{1:"title.function"},
+contains:[S]},k,{relevance:0,match:/\b[A-Z][A-Z_0-9]+\b/,
+className:"variable.constant"},w,C,{match:/\$[(.]/}]}}return t=>{
+const s=o(t),c=["any","void","number","boolean","string","object","never","symbol","bigint","unknown"],l={
+beginKeywords:"namespace",end:/\{/,excludeEnd:!0,
+contains:[s.exports.CLASS_REFERENCE]},d={beginKeywords:"interface",end:/\{/,
+excludeEnd:!0,keywords:{keyword:"interface extends",built_in:c},
+contains:[s.exports.CLASS_REFERENCE]},b={$pattern:e,
+keyword:n.concat(["type","namespace","interface","public","private","protected","implements","declare","abstract","readonly","enum","override"]),
+literal:a,built_in:i.concat(c),"variable.language":r},g={className:"meta",
+begin:"@[A-Za-z$_][0-9A-Za-z$_]*"},u=(e,n,a)=>{
+const t=e.contains.findIndex((e=>e.label===n))
+;if(-1===t)throw Error("can not find mode to replace");e.contains.splice(t,1,a)}
+;return Object.assign(s.keywords,b),
+s.exports.PARAMS_CONTAINS.push(g),s.contains=s.contains.concat([g,l,d]),
+u(s,"shebang",t.SHEBANG()),u(s,"use_strict",{className:"meta",relevance:10,
+begin:/^\s*['"]use strict['"]/
+}),s.contains.find((e=>"func.def"===e.label)).relevance=0,Object.assign(s,{
+name:"TypeScript",aliases:["ts","tsx"]}),s}})()
+;hljs.registerLanguage("typescript",e)})();/*! `ruby` grammar compiled for Highlight.js 11.7.0 */
+(()=>{var e=(()=>{"use strict";return e=>{
+const n=e.regex,a="([a-zA-Z_]\\w*[!?=]?|[-+~]@|<<|>>|=~|===?|<=>|[<>]=?|\\*\\*|[-/+%^&*~`|]|\\[\\]=?)",s=n.either(/\b([A-Z]+[a-z0-9]+)+/,/\b([A-Z]+[a-z0-9]+)+[A-Z]+/),i=n.concat(s,/(::\w+)*/),t={
+"variable.constant":["__FILE__","__LINE__","__ENCODING__"],
+"variable.language":["self","super"],
+keyword:["alias","and","begin","BEGIN","break","case","class","defined","do","else","elsif","end","END","ensure","for","if","in","module","next","not","or","redo","require","rescue","retry","return","then","undef","unless","until","when","while","yield","include","extend","prepend","public","private","protected","raise","throw"],
+built_in:["proc","lambda","attr_accessor","attr_reader","attr_writer","define_method","private_constant","module_function"],
+literal:["true","false","nil"]},c={className:"doctag",begin:"@[A-Za-z]+"},r={
+begin:"#<",end:">"},b=[e.COMMENT("#","$",{contains:[c]
+}),e.COMMENT("^=begin","^=end",{contains:[c],relevance:10
+}),e.COMMENT("^__END__",e.MATCH_NOTHING_RE)],l={className:"subst",begin:/#\{/,
+end:/\}/,keywords:t},d={className:"string",contains:[e.BACKSLASH_ESCAPE,l],
+variants:[{begin:/'/,end:/'/},{begin:/"/,end:/"/},{begin:/`/,end:/`/},{
+begin:/%[qQwWx]?\(/,end:/\)/},{begin:/%[qQwWx]?\[/,end:/\]/},{
+begin:/%[qQwWx]?\{/,end:/\}/},{begin:/%[qQwWx]?/},{begin:/%[qQwWx]?\//,
+end:/\//},{begin:/%[qQwWx]?%/,end:/%/},{begin:/%[qQwWx]?-/,end:/-/},{
+begin:/%[qQwWx]?\|/,end:/\|/},{begin:/\B\?(\\\d{1,3})/},{
+begin:/\B\?(\\x[A-Fa-f0-9]{1,2})/},{begin:/\B\?(\\u\{?[A-Fa-f0-9]{1,6}\}?)/},{
+begin:/\B\?(\\M-\\C-|\\M-\\c|\\c\\M-|\\M-|\\C-\\M-)[\x20-\x7e]/},{
+begin:/\B\?\\(c|C-)[\x20-\x7e]/},{begin:/\B\?\\?\S/},{
+begin:n.concat(/<<[-~]?'?/,n.lookahead(/(\w+)(?=\W)[^\n]*\n(?:[^\n]*\n)*?\s*\1\b/)),
+contains:[e.END_SAME_AS_BEGIN({begin:/(\w+)/,end:/(\w+)/,
+contains:[e.BACKSLASH_ESCAPE,l]})]}]},o="[0-9](_?[0-9])*",g={className:"number",
+relevance:0,variants:[{
+begin:`\\b([1-9](_?[0-9])*|0)(\\.(${o}))?([eE][+-]?(${o})|r)?i?\\b`},{
+begin:"\\b0[dD][0-9](_?[0-9])*r?i?\\b"},{begin:"\\b0[bB][0-1](_?[0-1])*r?i?\\b"
+},{begin:"\\b0[oO][0-7](_?[0-7])*r?i?\\b"},{
+begin:"\\b0[xX][0-9a-fA-F](_?[0-9a-fA-F])*r?i?\\b"},{
+begin:"\\b0(_?[0-7])+r?i?\\b"}]},_={variants:[{match:/\(\)/},{
+className:"params",begin:/\(/,end:/(?=\))/,excludeBegin:!0,endsParent:!0,
+keywords:t}]},u=[d,{variants:[{match:[/class\s+/,i,/\s+<\s+/,i]},{
+match:[/\b(class|module)\s+/,i]}],scope:{2:"title.class",
+4:"title.class.inherited"},keywords:t},{match:[/(include|extend)\s+/,i],scope:{
+2:"title.class"},keywords:t},{relevance:0,match:[i,/\.new[. (]/],scope:{
+1:"title.class"}},{relevance:0,match:/\b[A-Z][A-Z_0-9]+\b/,
+className:"variable.constant"},{relevance:0,match:s,scope:"title.class"},{
+match:[/def/,/\s+/,a],scope:{1:"keyword",3:"title.function"},contains:[_]},{
+begin:e.IDENT_RE+"::"},{className:"symbol",
+begin:e.UNDERSCORE_IDENT_RE+"(!|\\?)?:",relevance:0},{className:"symbol",
+begin:":(?!\\s)",contains:[d,{begin:a}],relevance:0},g,{className:"variable",
+begin:"(\\$\\W)|((\\$|@@?)(\\w+))(?=[^@$?])(?![A-Za-z])(?![@$?'])"},{
+className:"params",begin:/\|/,end:/\|/,excludeBegin:!0,excludeEnd:!0,
+relevance:0,keywords:t},{begin:"("+e.RE_STARTERS_RE+"|unless)\\s*",
+keywords:"unless",contains:[{className:"regexp",contains:[e.BACKSLASH_ESCAPE,l],
+illegal:/\n/,variants:[{begin:"/",end:"/[a-z]*"},{begin:/%r\{/,end:/\}[a-z]*/},{
+begin:"%r\\(",end:"\\)[a-z]*"},{begin:"%r!",end:"![a-z]*"},{begin:"%r\\[",
+end:"\\][a-z]*"}]}].concat(r,b),relevance:0}].concat(r,b)
+;l.contains=u,_.contains=u;const m=[{begin:/^\s*=>/,starts:{end:"$",contains:u}
+},{className:"meta.prompt",
+begin:"^([>?]>|[\\w#]+\\(\\w+\\):\\d+:\\d+[>*]|(\\w+-)?\\d+\\.\\d+\\.\\d+(p\\d+)?[^\\d][^>]+>)(?=[ ])",
+starts:{end:"$",keywords:t,contains:u}}];return b.unshift(r),{name:"Ruby",
+aliases:["rb","gemspec","podspec","thor","irb"],keywords:t,illegal:/\/\*/,
+contains:[e.SHEBANG({binary:"ruby"})].concat(m).concat(b).concat(u)}}})()
+;hljs.registerLanguage("ruby",e)})();/*! `yaml` grammar compiled for Highlight.js 11.7.0 */
+(()=>{var e=(()=>{"use strict";return e=>{
+const n="true false yes no null",a="[\\w#;/?:@&=+$,.~*'()[\\]]+",s={
+className:"string",relevance:0,variants:[{begin:/'/,end:/'/},{begin:/"/,end:/"/
+},{begin:/\S+/}],contains:[e.BACKSLASH_ESCAPE,{className:"template-variable",
+variants:[{begin:/\{\{/,end:/\}\}/},{begin:/%\{/,end:/\}/}]}]},i=e.inherit(s,{
+variants:[{begin:/'/,end:/'/},{begin:/"/,end:/"/},{begin:/[^\s,{}[\]]+/}]}),l={
+end:",",endsWithParent:!0,excludeEnd:!0,keywords:n,relevance:0},t={begin:/\{/,
+end:/\}/,contains:[l],illegal:"\\n",relevance:0},g={begin:"\\[",end:"\\]",
+contains:[l],illegal:"\\n",relevance:0},b=[{className:"attr",variants:[{
+begin:"\\w[\\w :\\/.-]*:(?=[ \t]|$)"},{begin:'"\\w[\\w :\\/.-]*":(?=[ \t]|$)'},{
+begin:"'\\w[\\w :\\/.-]*':(?=[ \t]|$)"}]},{className:"meta",begin:"^---\\s*$",
+relevance:10},{className:"string",
+begin:"[\\|>]([1-9]?[+-])?[ ]*\\n( +)[^ ][^\\n]*\\n(\\2[^\\n]+\\n?)*"},{
+begin:"<%[%=-]?",end:"[%-]?%>",subLanguage:"ruby",excludeBegin:!0,excludeEnd:!0,
+relevance:0},{className:"type",begin:"!\\w+!"+a},{className:"type",
+begin:"!<"+a+">"},{className:"type",begin:"!"+a},{className:"type",begin:"!!"+a
+},{className:"meta",begin:"&"+e.UNDERSCORE_IDENT_RE+"$"},{className:"meta",
+begin:"\\*"+e.UNDERSCORE_IDENT_RE+"$"},{className:"bullet",begin:"-(?=[ ]|$)",
+relevance:0},e.HASH_COMMENT_MODE,{beginKeywords:n,keywords:{literal:n}},{
+className:"number",
+begin:"\\b[0-9]{4}(-[0-9][0-9]){0,2}([Tt \\t][0-9][0-9]?(:[0-9][0-9]){2})?(\\.[0-9]*)?([ \\t])*(Z|[-+][0-9][0-9]?(:[0-9][0-9])?)?\\b"
+},{className:"number",begin:e.C_NUMBER_RE+"\\b",relevance:0},t,g,s],r=[...b]
+;return r.pop(),r.push(i),l.contains=r,{name:"YAML",case_insensitive:!0,
+aliases:["yml"],contains:b}}})();hljs.registerLanguage("yaml",e)})();/*! `markdown` grammar compiled for Highlight.js 11.7.0 */
+(()=>{var e=(()=>{"use strict";return e=>{const n={begin:/<\/?[A-Za-z_]/,
+end:">",subLanguage:"xml",relevance:0},a={variants:[{begin:/\[.+?\]\[.*?\]/,
+relevance:0},{
+begin:/\[.+?\]\(((data|javascript|mailto):|(?:http|ftp)s?:\/\/).*?\)/,
+relevance:2},{
+begin:e.regex.concat(/\[.+?\]\(/,/[A-Za-z][A-Za-z0-9+.-]*/,/:\/\/.*?\)/),
+relevance:2},{begin:/\[.+?\]\([./?&#].*?\)/,relevance:1},{
+begin:/\[.*?\]\(.*?\)/,relevance:0}],returnBegin:!0,contains:[{match:/\[(?=\])/
+},{className:"string",relevance:0,begin:"\\[",end:"\\]",excludeBegin:!0,
+returnEnd:!0},{className:"link",relevance:0,begin:"\\]\\(",end:"\\)",
+excludeBegin:!0,excludeEnd:!0},{className:"symbol",relevance:0,begin:"\\]\\[",
+end:"\\]",excludeBegin:!0,excludeEnd:!0}]},i={className:"strong",contains:[],
+variants:[{begin:/_{2}(?!\s)/,end:/_{2}/},{begin:/\*{2}(?!\s)/,end:/\*{2}/}]
+},s={className:"emphasis",contains:[],variants:[{begin:/\*(?![*\s])/,end:/\*/},{
+begin:/_(?![_\s])/,end:/_/,relevance:0}]},c=e.inherit(i,{contains:[]
+}),t=e.inherit(s,{contains:[]});i.contains.push(t),s.contains.push(c)
+;let g=[n,a];return[i,s,c,t].forEach((e=>{e.contains=e.contains.concat(g)
+})),g=g.concat(i,s),{name:"Markdown",aliases:["md","mkdown","mkd"],contains:[{
+className:"section",variants:[{begin:"^#{1,6}",end:"$",contains:g},{
+begin:"(?=^.+?\\n[=-]{2,}$)",contains:[{begin:"^[=-]*$"},{begin:"^",end:"\\n",
+contains:g}]}]},n,{className:"bullet",begin:"^[ \t]*([*+-]|(\\d+\\.))(?=\\s+)",
+end:"\\s+",excludeEnd:!0},i,s,{className:"quote",begin:"^>\\s+",contains:g,
+end:"$"},{className:"code",variants:[{begin:"(`{3,})[^`](.|\\n)*?\\1`*[ ]*"},{
+begin:"(~{3,})[^~](.|\\n)*?\\1~*[ ]*"},{begin:"```",end:"```+[ ]*$"},{
+begin:"~~~",end:"~~~+[ ]*$"},{begin:"`.+?`"},{begin:"(?=^( {4}|\\t))",
+contains:[{begin:"^( {4}|\\t)",end:"(\\n)$"}],relevance:0}]},{
+begin:"^[-\\*]{3,}",end:"$"},a,{begin:/^\[[^\n]+\]:/,returnBegin:!0,contains:[{
+className:"symbol",begin:/\[/,end:/\]/,excludeBegin:!0,excludeEnd:!0},{
+className:"link",begin:/:\s*/,end:/$/,excludeBegin:!0}]}]}}})()
+;hljs.registerLanguage("markdown",e)})();
\ No newline at end of file
diff --git a/system/typemill/author/js/vue-license.js b/system/typemill/author/js/vue-license.js
new file mode 100644
index 0000000..644b917
--- /dev/null
+++ b/system/typemill/author/js/vue-license.js
@@ -0,0 +1,131 @@
+const app = Vue.createApp({
+	template: `
+						
+
+

Your license is out of date. Please check if the payments for your subscription were successfull.

+

Your license is only valid for the domain listed in your license data below.

+

Congratulations! Your license is ok and you can enjoy all features.

+
+
+
+
+ +
+
+

{{ licenseData.plan }}-LICENSE

+
+
+
+

License-key:

+

{{ licenseData.license }}

+

Domain:

+

{{ licenseData.domain }}

+

E-Mail:

+

{{ licenseData.email }}

+

Payed until:

+

{{ licenseData.payed_until }}

+
+
+
+
+
+

Buy a typemill-license and enjoy our flatrate-model for premium-plugins and -themes.

We offer two types of subscription-based licenses:

+
+
+

Maker License

+

Use all maker-prodcuts (plugins and themes) for one year. The subscription will automatically renewed after a year.

+ Buy now for 29,00 €/year +
+
+

Business License

+

Use all business- and maker-products (plugins, themes, services) for one year. The subscription will automatically renewed after a year.

+ Buy now for 229,00 €/year +
+
+
+
+
+ {{ fieldDefinition.legend }} + + +
+ + +
+
+
{{ message }}
+ +
+
+
`, + data() { + return { + licenseData: data.licensedata, + formDefinitions: data.licensefields, + formData: {}, + message: '', + messageClass: '', + errors: {}, + src: tmaxios.defaults.baseURL + "/system/author/img/favicon-144.png" + } + }, + mounted() { + eventBus.$on('forminput', formdata => { + this.formData[formdata.name] = formdata.value; + }); + }, + methods: { + selectComponent: function(type) + { + return 'component-'+type; + }, + save: function() + { + this.reset(); + var self = this; + + tmaxios.post('/api/v1/license',{ + 'csrf_name': document.getElementById("csrf_name").value, + 'csrf_value': document.getElementById("csrf_value").value, + 'license': this.formData + }) + .then(function (response) + { + self.messageClass = 'bg-teal-500'; + self.message = response.data.message; + self.licenseData = response.data.licensedata; + }) + .catch(function (error) + { + self.messageClass = 'bg-rose-500'; + self.message = error.response.data.message; + + /* form validation errors */ + if(error.response.data.errors !== undefined) + { + self.errors = error.response.data.errors; + } + }); + }, + reset: function() + { + this.errors = {}; + this.message = ''; + this.messageClass = ''; + } + }, +}) \ No newline at end of file diff --git a/system/typemill/author/js/vue-user.js b/system/typemill/author/js/vue-user.js new file mode 100644 index 0000000..a6637cd --- /dev/null +++ b/system/typemill/author/js/vue-user.js @@ -0,0 +1,136 @@ +const app = Vue.createApp({ + template: ` +
+
+
+
+ {{ fieldDefinition.legend }} + + +
+ + +
+
+
{{ message }}
+ +
+
+
+ + + + + + +
+
+
`, + data() { + return { + formDefinitions: data.userfields, + formData: data.userdata, + userroles: data.userroles, + message: '', + messageClass: '', + errors: {}, + showModal: false, + } + }, + mounted() { + eventBus.$on('forminput', formdata => { + this.formData[formdata.name] = formdata.value; + }); + }, + methods: { + selectComponent: function(type) + { + return 'component-'+type; + }, + changeForm: function() + { + /* change input form if user role changed */ + }, + save: function() + { + this.reset(); + var self = this; + + tmaxios.put('/api/v1/user',{ + 'csrf_name': document.getElementById("csrf_name").value, + 'csrf_value': document.getElementById("csrf_value").value, + 'userdata': this.formData + }) + .then(function (response) + { + self.messageClass = 'bg-teal-500'; + self.message = response.data.message; + }) + .catch(function (error) + { + self.messageClass = 'bg-rose-500'; + self.message = error.response.data.message; + if(error.response.data.errors !== undefined) + { + self.errors = error.response.data.errors; + } + }); + }, + deleteuser: function() + { + this.reset(); + var self = this; + + tmaxios.delete('/api/v1/user',{ + data: { + 'csrf_name': document.getElementById("csrf_name").value, + 'csrf_value': document.getElementById("csrf_value").value, + 'username': this.formData.username + } + }) + .then(function (response) + { + self.showModal = false; + self.messageClass = 'bg-teal-500'; + self.message = response.data.message; + /* redirect to userlist */ + }) + .catch(function (error) + { + self.showModal = false; + self.messageClass = 'bg-rose-500'; + self.message = error.response.data.message; + if(error.response.data.errors !== undefined) + { + self.errors = error.response.data.errors; + } + }); + }, + reset: function() + { + this.errors = {}; + this.message = ''; + this.messageClass = ''; + } + }, +}) \ No newline at end of file diff --git a/system/typemill/author/js/vue-usernew.js b/system/typemill/author/js/vue-usernew.js new file mode 100644 index 0000000..aa1a955 --- /dev/null +++ b/system/typemill/author/js/vue-usernew.js @@ -0,0 +1,126 @@ +const app = Vue.createApp({ + template: ` +
+
+ + +
+
+
+
+ {{ fieldDefinition.legend }} + + +
+ + +
+
+
{{ message }}
+ +
+
+
+
`, + data() { + return { + selectedrole: false, + formDefinitions: false, + formData: {}, + userroles: data.userroles, + message: '', + messageClass: '', + errors: {}, + } + }, + mounted() { + eventBus.$on('forminput', formdata => { + this.formData[formdata.name] = formdata.value; + }); + }, + methods: { + selectComponent: function(type) + { + return 'component-'+type; + }, + generateForm: function() + { + this.reset(); + var self = this; + + tmaxios.get('/api/v1/userform',{ + params: { + 'csrf_name': document.getElementById("csrf_name").value, + 'csrf_value': document.getElementById("csrf_value").value, + 'userrole': this.selectedrole + } + }) + .then(function (response) + { + self.formDefinitions = response.data.userform; + self.formData.userrole = self.selectedrole; + }) + .catch(function (error) + { + self.messageClass = 'bg-rose-500'; + self.message = error.response.data.message; + if(error.response.data.errors !== undefined) + { + self.errors = error.response.data.errors; + } + }); + }, + save: function() + { + this.reset(); + var self = this; + + tmaxios.post('/api/v1/user',{ + 'csrf_name': document.getElementById("csrf_name").value, + 'csrf_value': document.getElementById("csrf_value").value, + 'userdata': this.formData + }) + .then(function (response) + { + self.messageClass = 'bg-teal-500'; + self.message = response.data.message; + + window.location = tmaxios.defaults.baseURL + '/tm/user/' + self.formData.username; + }) + .catch(function (error) + { + self.messageClass = 'bg-rose-500'; + self.message = error.response.data.message; + if(error.response.data.errors !== undefined) + { + self.errors = error.response.data.errors; + } + }); + }, + reset: function() + { + this.errors = {}; + this.message = ''; + this.messageClass = ''; + } + }, +}) \ No newline at end of file diff --git a/system/typemill/author/system/license.twig b/system/typemill/author/system/license.twig new file mode 100644 index 0000000..d5c382f --- /dev/null +++ b/system/typemill/author/system/license.twig @@ -0,0 +1,27 @@ +{% extends 'layouts/layoutSystem.twig' %} +{% block title %}{{ translate('License') }}{% endblock %} + +{% block content %} + +

{{ translate('License') }}

+ +
+ +{% endblock %} + +{% block javascript %} + + + + + + + +{% endblock %} \ No newline at end of file diff --git a/system/typemill/author/system/user.twig b/system/typemill/author/system/user.twig new file mode 100644 index 0000000..7f20a3b --- /dev/null +++ b/system/typemill/author/system/user.twig @@ -0,0 +1,21 @@ +{% extends 'layouts/layoutSystem.twig' %} +{% block title %}{{ translate('Account') }}{% endblock %} + +{% block content %} + +

{{ translate('User') }}

+ +
+ +{% endblock %} + +{% block javascript %} + + + + + + +{% endblock %} diff --git a/system/typemill/author/system/usernew.twig b/system/typemill/author/system/usernew.twig new file mode 100644 index 0000000..d3197a0 --- /dev/null +++ b/system/typemill/author/system/usernew.twig @@ -0,0 +1,21 @@ +{% extends 'layouts/layoutSystem.twig' %} +{% block title %}{{ translate('Create user') }}{% endblock %} + +{% block content %} + +

{{ translate('Create user') }}

+ +
+ +{% endblock %} + +{% block javascript %} + + + + + + +{% endblock %} diff --git a/system/typemill/settings/license.yaml b/system/typemill/settings/license.yaml new file mode 100644 index 0000000..7a3b123 --- /dev/null +++ b/system/typemill/settings/license.yaml @@ -0,0 +1,15 @@ +license: + name: license + label: 'Your license key' + type: 'text' + required: true +email: + name: email + label: 'Your email' + type: 'text' + required: true +domain: + name: domain + label: 'Domain for license' + type: 'text' + required: true \ No newline at end of file