Add support for limiting the number of uploaded files
authorAlexander Ebert <ebert@woltlab.com>
Sat, 18 May 2024 10:46:39 +0000 (12:46 +0200)
committerAlexander Ebert <ebert@woltlab.com>
Sat, 8 Jun 2024 10:19:39 +0000 (12:19 +0200)
16 files changed:
ts/WoltLabSuite/Core/Component/File/Upload.ts
ts/WoltLabSuite/Core/Component/File/woltlab-core-file.ts
ts/WoltLabSuite/WebComponent/woltlab-core-file-upload.ts
ts/global.d.ts
wcfsetup/install/files/js/WoltLabSuite/Core/Component/File/Upload.js
wcfsetup/install/files/js/WoltLabSuite/Core/Component/File/woltlab-core-file.js
wcfsetup/install/files/js/WoltLabSuite/WebComponent.min.js
wcfsetup/install/files/lib/system/endpoint/controller/core/files/PostUpload.class.php
wcfsetup/install/files/lib/system/event/listener/PreloadPhrasesCollectingListener.class.php
wcfsetup/install/files/lib/system/file/processor/AbstractFileProcessor.class.php
wcfsetup/install/files/lib/system/file/processor/AttachmentFileProcessor.class.php
wcfsetup/install/files/lib/system/file/processor/FileProcessor.class.php
wcfsetup/install/files/lib/system/file/processor/IFileProcessor.class.php
wcfsetup/install/lang/de.xml
wcfsetup/install/lang/en.xml
wcfsetup/setup/db/install.sql

index b3f0162dfbb9997dfd105b8210bf1b0b90f93039..ab3c6a354420537f0e7c3ea5cb7529c39c0d8d1e 100644 (file)
@@ -176,6 +176,25 @@ async function resizeImage(element: WoltlabCoreFileUploadElement, file: File): P
   return resizedFile;
 }
 
+function validateFileLimit(element: WoltlabCoreFileUploadElement): boolean {
+  const maximumCount = element.maximumCount;
+  if (maximumCount === -1) {
+    return true;
+  } else if (maximumCount > 0) {
+    return true;
+  }
+
+  const files = Array.from(element.parentElement!.querySelectorAll("woltlab-core-file"));
+  const numberOfUploadedFiles = files.filter((file) => !file.isFailedUpload()).length;
+  if (numberOfUploadedFiles + 1 <= maximumCount) {
+    return true;
+  }
+
+  innerError(element, getPhrase("wcf.upload.error.maximumCountReached", { maximumCount }));
+
+  return false;
+}
+
 function validateFile(element: WoltlabCoreFileUploadElement, file: File): boolean {
   const fileExtensions = (element.dataset.fileExtensions || "*").split(",");
   for (const fileExtension of fileExtensions) {
@@ -198,7 +217,9 @@ export function setup(): void {
 
       clearPreviousErrors(element);
 
-      if (!validateFile(element, file)) {
+      if (!validateFileLimit(element)) {
+        return;
+      } else if (!validateFile(element, file)) {
         return;
       }
 
index c8dbeb48e1a81b82904835a83c07df458662e8d4..2aedd9924363276c44bef1985cdef0a0afb4ddd2 100644 (file)
@@ -315,6 +315,10 @@ export class WoltlabCoreFileElement extends HTMLElement {
     this.#readyResolve();
   }
 
+  isFailedUpload(): boolean {
+    return this.#state === State.Failed;
+  }
+
   set thumbnail(thumbnail: Thumbnail) {
     if (!this.#thumbnails.includes(thumbnail)) {
       return;
index ecd9da278e349c41da24ae414b1c2651af5c595a..a9e3a3984ad280e3dc83d2ada5fede3b7d7ad9ac 100644 (file)
         this.#element.accept = allowedFileExtensions;
       }
 
+      const maximumCount = this.maximumCount;
+      if (maximumCount > 1 || maximumCount === -1) {
+        this.#element.multiple = true;
+      }
+
       const shadow = this.attachShadow({ mode: "open" });
       shadow.append(this.#element);
 
         }
       `;
     }
+
+    get maximumCount(): number {
+      return parseInt(this.dataset.maximumCount || "1");
+    }
+
+    get disabled(): boolean {
+      return this.#element.disabled;
+    }
+
+    set disabled(disabled: boolean) {
+      this.#element.disabled = Boolean(disabled);
+    }
   }
 
   window.customElements.define("woltlab-core-file-upload", WoltlabCoreFileUploadElement);
index a46ca2652812ad3c9a5a0b44ec5e234ca6be7422..ab017351584f4c6a6bbda748c18bed7a43ece2aa 100644 (file)
@@ -92,7 +92,11 @@ declare global {
     set date(date: Date);
   }
 
-  interface WoltlabCoreFileUploadElement extends HTMLElement {}
+  interface WoltlabCoreFileUploadElement extends HTMLElement {
+    get disabled(): boolean;
+    set disabled(disabled: boolean);
+    get maximumCount(): number;
+  }
 
   interface WoltlabCoreLoadingIndicatorElement extends HTMLElement {
     get size(): LoadingIndicatorIconSize;
index 3bb938236fea1d83edd642041fa3844247d6b1f9..2bee244df1120bb6cbf4c85bd6a3e36006ca0f48 100644 (file)
@@ -106,6 +106,22 @@ define(["require", "exports", "tslib", "WoltLabSuite/Core/Helper/Selector", "Wol
         }, file.name, fileType, resizeConfiguration.quality);
         return resizedFile;
     }
+    function validateFileLimit(element) {
+        const maximumCount = element.maximumCount;
+        if (maximumCount === -1) {
+            return true;
+        }
+        else if (maximumCount > 0) {
+            return true;
+        }
+        const files = Array.from(element.parentElement.querySelectorAll("woltlab-core-file"));
+        const numberOfUploadedFiles = files.filter((file) => !file.isFailedUpload()).length;
+        if (numberOfUploadedFiles + 1 <= maximumCount) {
+            return true;
+        }
+        (0, Util_1.innerError)(element, (0, Language_1.getPhrase)("wcf.upload.error.maximumCountReached", { maximumCount }));
+        return false;
+    }
     function validateFile(element, file) {
         const fileExtensions = (element.dataset.fileExtensions || "*").split(",");
         for (const fileExtension of fileExtensions) {
@@ -124,7 +140,10 @@ define(["require", "exports", "tslib", "WoltLabSuite/Core/Helper/Selector", "Wol
             element.addEventListener("upload", (event) => {
                 const file = event.detail;
                 clearPreviousErrors(element);
-                if (!validateFile(element, file)) {
+                if (!validateFileLimit(element)) {
+                    return;
+                }
+                else if (!validateFile(element, file)) {
                     return;
                 }
                 void resizeImage(element, file).then((resizedFile) => {
index 87a725759bfef5d3f247da3299ca6f307eeaca33..a73f8e9220499317dcc9f750b8591b965e6bf29f 100644 (file)
@@ -242,6 +242,9 @@ define(["require", "exports", "WoltLabSuite/Core/FileUtil"], function (require,
             this.#rebuildElement();
             this.#readyResolve();
         }
+        isFailedUpload() {
+            return this.#state === 4 /* State.Failed */;
+        }
         set thumbnail(thumbnail) {
             if (!this.#thumbnails.includes(thumbnail)) {
                 return;
index b9a1f2dbb411e6e3e47266e1cc06da9efba819b7..7666691b3c7cc4652fbbc7a5fa756cfed8246538 100644 (file)
@@ -1,9 +1,9 @@
-"use strict";(()=>{var ke=Object.create;var te=Object.defineProperty;var ye=Object.getOwnPropertyDescriptor;var ve=Object.getOwnPropertyNames;var Ee=Object.getPrototypeOf,xe=Object.prototype.hasOwnProperty;var se=(e=>typeof require<"u"?require:typeof Proxy<"u"?new Proxy(e,{get:(r,i)=>(typeof require<"u"?require:r)[i]}):e)(function(e){if(typeof require<"u")return require.apply(this,arguments);throw Error('Dynamic require of "'+e+'" is not supported')});var Te=(e,r)=>()=>(r||e((r={exports:{}}).exports,r),r.exports),ce=(e,r)=>{for(var i in r)te(e,i,{get:r[i],enumerable:!0})},_e=(e,r,i,c)=>{if(r&&typeof r=="object"||typeof r=="function")for(let t of ve(r))!xe.call(e,t)&&t!==i&&te(e,t,{get:()=>r[t],enumerable:!(c=ye(r,t))||c.enumerable});return e};var Le=(e,r,i)=>(i=e!=null?ke(Ee(e)):{},_e(r||!e||!e.__esModule?te(i,"default",{value:e,enumerable:!0}):i,e));var fe=Te((O,re)=>{"use strict";var Q=function(){var e=function(R,o,f,u){for(f=f||{},u=R.length;u--;f[R[u]]=o);return f},r=[2,44],i=[5,9,11,12,13,18,19,21,22,23,25,26,28,29,30,32,33,34,35,37,39,41],c=[1,25],t=[1,27],a=[1,33],l=[1,31],d=[1,32],g=[1,28],w=[1,29],q=[1,26],S=[1,35],z=[1,41],P=[1,40],h=[11,12,15,42,43,47,49,51,52,54,55],m=[9,11,12,13,18,19,21,23,26,28,30,32,33,34,35,37,39],p=[11,12,15,42,43,46,47,48,49,51,52,54,55],E=[1,64],k=[1,65],x=[18,37,39],D=[12,15],W={trace:function(){},yy:{},symbols_:{error:2,TEMPLATE:3,CHUNK_STAR:4,EOF:5,CHUNK_STAR_repetition0:6,CHUNK:7,PLAIN_ANY:8,T_LITERAL:9,COMMAND:10,T_ANY:11,T_WS:12,"{if":13,COMMAND_PARAMETERS:14,"}":15,COMMAND_repetition0:16,COMMAND_option0:17,"{/if}":18,"{include":19,COMMAND_PARAMETER_LIST:20,"{implode":21,"{/implode}":22,"{foreach":23,COMMAND_option1:24,"{/foreach}":25,"{plural":26,PLURAL_PARAMETER_LIST:27,"{lang}":28,"{/lang}":29,"{":30,VARIABLE:31,"{#":32,"{@":33,"{ldelim}":34,"{rdelim}":35,ELSE:36,"{else}":37,ELSE_IF:38,"{elseif":39,FOREACH_ELSE:40,"{foreachelse}":41,T_VARIABLE:42,T_VARIABLE_NAME:43,VARIABLE_repetition0:44,VARIABLE_SUFFIX:45,"[":46,"]":47,".":48,"(":49,VARIABLE_SUFFIX_option0:50,")":51,"=":52,COMMAND_PARAMETER_VALUE:53,T_QUOTED_STRING:54,T_DIGITS:55,COMMAND_PARAMETERS_repetition_plus0:56,COMMAND_PARAMETER:57,T_PLURAL_PARAMETER_NAME:58,$accept:0,$end:1},terminals_:{2:"error",5:"EOF",9:"T_LITERAL",11:"T_ANY",12:"T_WS",13:"{if",15:"}",18:"{/if}",19:"{include",21:"{implode",22:"{/implode}",23:"{foreach",25:"{/foreach}",26:"{plural",28:"{lang}",29:"{/lang}",30:"{",32:"{#",33:"{@",34:"{ldelim}",35:"{rdelim}",37:"{else}",39:"{elseif",41:"{foreachelse}",42:"T_VARIABLE",43:"T_VARIABLE_NAME",46:"[",47:"]",48:".",49:"(",51:")",52:"=",54:"T_QUOTED_STRING",55:"T_DIGITS"},productions_:[0,[3,2],[4,1],[7,1],[7,1],[7,1],[8,1],[8,1],[10,7],[10,3],[10,5],[10,6],[10,3],[10,3],[10,3],[10,3],[10,3],[10,1],[10,1],[36,2],[38,4],[40,2],[31,3],[45,3],[45,2],[45,3],[20,5],[20,3],[53,1],[53,1],[53,1],[14,1],[57,1],[57,1],[57,1],[57,1],[57,1],[57,1],[57,1],[57,3],[27,5],[27,3],[58,1],[58,1],[6,0],[6,2],[16,0],[16,2],[17,0],[17,1],[24,0],[24,1],[44,0],[44,2],[50,0],[50,1],[56,1],[56,2]],performAction:function(o,f,u,y,b,s,U){var n=s.length-1;switch(b){case 1:return s[n-1]+";";case 2:var C=s[n].reduce(function(_,L){return L.encode&&!_[1]?_[0]+=" + '"+L.value:L.encode&&_[1]?_[0]+=L.value:!L.encode&&_[1]?_[0]+="' + "+L.value:!L.encode&&!_[1]&&(_[0]+=" + "+L.value),_[1]=L.encode,_},["''",!1]);C[1]&&(C[0]+="'"),this.$=C[0];break;case 3:case 4:this.$={encode:!0,value:s[n].replace(/\\/g,"\\\\").replace(/'/g,"\\'").replace(/(\r\n|\n|\r)/g,"\\n")};break;case 5:this.$={encode:!1,value:s[n]};break;case 8:this.$="(function() { if ("+s[n-5]+") { return "+s[n-3]+"; } "+s[n-2].join(" ")+" "+(s[n-1]||"")+" return ''; })()";break;case 9:if(!s[n-1].file)throw new Error("Missing parameter file");this.$=s[n-1].file+".fetch(v)";break;case 10:if(!s[n-3].from)throw new Error("Missing parameter from");if(!s[n-3].item)throw new Error("Missing parameter item");s[n-3].glue||(s[n-3].glue="', '"),this.$="(function() { return "+s[n-3].from+".map(function(item) { v["+s[n-3].item+"] = item; return "+s[n-1]+"; }).join("+s[n-3].glue+"); })()";break;case 11:if(!s[n-4].from)throw new Error("Missing parameter from");if(!s[n-4].item)throw new Error("Missing parameter item");this.$="(function() {var looped = false, result = '';if ("+s[n-4].from+" instanceof Array) {for (var i = 0; i < "+s[n-4].from+".length; i++) { looped = true;v["+s[n-4].key+"] = i;v["+s[n-4].item+"] = "+s[n-4].from+"[i];result += "+s[n-2]+";}} else {for (var key in "+s[n-4].from+") {if (!"+s[n-4].from+".hasOwnProperty(key)) continue;looped = true;v["+s[n-4].key+"] = key;v["+s[n-4].item+"] = "+s[n-4].from+"[key];result += "+s[n-2]+";}}return (looped ? result : "+(s[n-1]||"''")+"); })()";break;case 12:this.$="h.selectPlural({";var B=!1;for(var F in s[n-1])objOwns(s[n-1],F)&&(this.$+=(B?",":"")+F+": "+s[n-1][F],B=!0);this.$+="})";break;case 13:this.$="Language.get("+s[n-1]+", v)";break;case 14:this.$="h.escapeHTML("+s[n-1]+")";break;case 15:this.$="h.formatNumeric("+s[n-1]+")";break;case 16:this.$=s[n-1];break;case 17:this.$="'{'";break;case 18:this.$="'}'";break;case 19:this.$="else { return "+s[n]+"; }";break;case 20:this.$="else if ("+s[n-2]+") { return "+s[n]+"; }";break;case 21:this.$=s[n];break;case 22:this.$="v['"+s[n-1]+"']"+s[n].join("");break;case 23:this.$=s[n-2]+s[n-1]+s[n];break;case 24:this.$="['"+s[n]+"']";break;case 25:case 39:this.$=s[n-2]+(s[n-1]||"")+s[n];break;case 26:case 40:this.$=s[n],this.$[s[n-4]]=s[n-2];break;case 27:case 41:this.$={},this.$[s[n-2]]=s[n];break;case 31:this.$=s[n].join("");break;case 44:case 46:case 52:this.$=[];break;case 45:case 47:case 53:case 57:s[n-1].push(s[n]);break;case 56:this.$=[s[n]];break}},table:[e([5,9,11,12,13,19,21,23,26,28,30,32,33,34,35],r,{3:1,4:2,6:3}),{1:[3]},{5:[1,4]},e([5,18,22,25,29,37,39,41],[2,2],{7:5,8:6,10:8,9:[1,7],11:[1,9],12:[1,10],13:[1,11],19:[1,12],21:[1,13],23:[1,14],26:[1,15],28:[1,16],30:[1,17],32:[1,18],33:[1,19],34:[1,20],35:[1,21]}),{1:[2,1]},e(i,[2,45]),e(i,[2,3]),e(i,[2,4]),e(i,[2,5]),e(i,[2,6]),e(i,[2,7]),{11:c,12:t,14:22,31:30,42:a,43:l,49:d,52:g,54:w,55:q,56:23,57:24},{20:34,43:S},{20:36,43:S},{20:37,43:S},{27:38,43:z,55:P,58:39},e([9,11,12,13,19,21,23,26,28,29,30,32,33,34,35],r,{6:3,4:42}),{31:43,42:a},{31:44,42:a},{31:45,42:a},e(i,[2,17]),e(i,[2,18]),{15:[1,46]},e([15,47,51],[2,31],{31:30,57:47,11:c,12:t,42:a,43:l,49:d,52:g,54:w,55:q}),e(h,[2,56]),e(h,[2,32]),e(h,[2,33]),e(h,[2,34]),e(h,[2,35]),e(h,[2,36]),e(h,[2,37]),e(h,[2,38]),{11:c,12:t,14:48,31:30,42:a,43:l,49:d,52:g,54:w,55:q,56:23,57:24},{43:[1,49]},{15:[1,50]},{52:[1,51]},{15:[1,52]},{15:[1,53]},{15:[1,54]},{52:[1,55]},{52:[2,42]},{52:[2,43]},{29:[1,56]},{15:[1,57]},{15:[1,58]},{15:[1,59]},e(m,r,{6:3,4:60}),e(h,[2,57]),{51:[1,61]},e(p,[2,52],{44:62}),e(i,[2,9]),{31:66,42:a,53:63,54:E,55:k},e([9,11,12,13,19,21,22,23,26,28,30,32,33,34,35],r,{6:3,4:67}),e([9,11,12,13,19,21,23,25,26,28,30,32,33,34,35,41],r,{6:3,4:68}),e(i,[2,12]),{31:66,42:a,53:69,54:E,55:k},e(i,[2,13]),e(i,[2,14]),e(i,[2,15]),e(i,[2,16]),e(x,[2,46],{16:70}),e(h,[2,39]),e([11,12,15,42,43,47,51,52,54,55],[2,22],{45:71,46:[1,72],48:[1,73],49:[1,74]}),{12:[1,75],15:[2,27]},e(D,[2,28]),e(D,[2,29]),e(D,[2,30]),{22:[1,76]},{24:77,25:[2,50],40:78,41:[1,79]},{12:[1,80],15:[2,41]},{17:81,18:[2,48],36:83,37:[1,85],38:82,39:[1,84]},e(p,[2,53]),{11:c,12:t,14:86,31:30,42:a,43:l,49:d,52:g,54:w,55:q,56:23,57:24},{43:[1,87]},{11:c,12:t,14:89,31:30,42:a,43:l,49:d,50:88,51:[2,54],52:g,54:w,55:q,56:23,57:24},{20:90,43:S},e(i,[2,10]),{25:[1,91]},{25:[2,51]},e([9,11,12,13,19,21,23,25,26,28,30,32,33,34,35],r,{6:3,4:92}),{27:93,43:z,55:P,58:39},{18:[1,94]},e(x,[2,47]),{18:[2,49]},{11:c,12:t,14:95,31:30,42:a,43:l,49:d,52:g,54:w,55:q,56:23,57:24},e([9,11,12,13,18,19,21,23,26,28,30,32,33,34,35],r,{6:3,4:96}),{47:[1,97]},e(p,[2,24]),{51:[1,98]},{51:[2,55]},{15:[2,26]},e(i,[2,11]),{25:[2,21]},{15:[2,40]},e(i,[2,8]),{15:[1,99]},{18:[2,19]},e(p,[2,23]),e(p,[2,25]),e(m,r,{6:3,4:100}),e(x,[2,20])],defaultActions:{4:[2,1],40:[2,42],41:[2,43],78:[2,51],83:[2,49],89:[2,55],90:[2,26],92:[2,21],93:[2,40],96:[2,19]},parseError:function(o,f){if(f.recoverable)this.trace(o);else{var u=new Error(o);throw u.hash=f,u}},parse:function(o){var f=this,u=[0],y=[],b=[null],s=[],U=this.table,n="",C=0,B=0,F=0,_=2,L=1,me=s.slice.call(arguments,1),v=Object.create(this.lexer),j={yy:{}};for(var X in this.yy)Object.prototype.hasOwnProperty.call(this.yy,X)&&(j.yy[X]=this.yy[X]);v.setInput(o,j.yy),j.yy.lexer=v,j.yy.parser=this,typeof v.yylloc>"u"&&(v.yylloc={});var J=v.yylloc;s.push(J);var be=v.options&&v.options.ranges;typeof j.yy.parseError=="function"?this.parseError=j.yy.parseError:this.parseError=Object.getPrototypeOf(this).parseError;function Re(M){u.length=u.length-2*M,b.length=b.length-M,s.length=s.length-M}for(var we=function(){var M;return M=v.lex()||L,typeof M!="number"&&(M=f.symbols_[M]||M),M},T,$,H,A,Ce,ee,N={},Y,I,oe,Z;;){if(H=u[u.length-1],this.defaultActions[H]?A=this.defaultActions[H]:((T===null||typeof T>"u")&&(T=we()),A=U[H]&&U[H][T]),typeof A>"u"||!A.length||!A[0]){var ae="";Z=[];for(Y in U[H])this.terminals_[Y]&&Y>_&&Z.push("'"+this.terminals_[Y]+"'");v.showPosition?ae="Parse error on line "+(C+1)+`:
+"use strict";(()=>{var ke=Object.create;var te=Object.defineProperty;var ye=Object.getOwnPropertyDescriptor;var ve=Object.getOwnPropertyNames;var Ee=Object.getPrototypeOf,xe=Object.prototype.hasOwnProperty;var se=(e=>typeof require<"u"?require:typeof Proxy<"u"?new Proxy(e,{get:(r,i)=>(typeof require<"u"?require:r)[i]}):e)(function(e){if(typeof require<"u")return require.apply(this,arguments);throw Error('Dynamic require of "'+e+'" is not supported')});var Te=(e,r)=>()=>(r||e((r={exports:{}}).exports,r),r.exports),ce=(e,r)=>{for(var i in r)te(e,i,{get:r[i],enumerable:!0})},_e=(e,r,i,c)=>{if(r&&typeof r=="object"||typeof r=="function")for(let t of ve(r))!xe.call(e,t)&&t!==i&&te(e,t,{get:()=>r[t],enumerable:!(c=ye(r,t))||c.enumerable});return e};var Le=(e,r,i)=>(i=e!=null?ke(Ee(e)):{},_e(r||!e||!e.__esModule?te(i,"default",{value:e,enumerable:!0}):i,e));var fe=Te((O,re)=>{"use strict";var Q=function(){var e=function(C,o,f,u){for(f=f||{},u=C.length;u--;f[C[u]]=o);return f},r=[2,44],i=[5,9,11,12,13,18,19,21,22,23,25,26,28,29,30,32,33,34,35,37,39,41],c=[1,25],t=[1,27],a=[1,33],l=[1,31],d=[1,32],g=[1,28],w=[1,29],q=[1,26],S=[1,35],z=[1,41],P=[1,40],h=[11,12,15,42,43,47,49,51,52,54,55],m=[9,11,12,13,18,19,21,23,26,28,30,32,33,34,35,37,39],p=[11,12,15,42,43,46,47,48,49,51,52,54,55],E=[1,64],k=[1,65],x=[18,37,39],D=[12,15],W={trace:function(){},yy:{},symbols_:{error:2,TEMPLATE:3,CHUNK_STAR:4,EOF:5,CHUNK_STAR_repetition0:6,CHUNK:7,PLAIN_ANY:8,T_LITERAL:9,COMMAND:10,T_ANY:11,T_WS:12,"{if":13,COMMAND_PARAMETERS:14,"}":15,COMMAND_repetition0:16,COMMAND_option0:17,"{/if}":18,"{include":19,COMMAND_PARAMETER_LIST:20,"{implode":21,"{/implode}":22,"{foreach":23,COMMAND_option1:24,"{/foreach}":25,"{plural":26,PLURAL_PARAMETER_LIST:27,"{lang}":28,"{/lang}":29,"{":30,VARIABLE:31,"{#":32,"{@":33,"{ldelim}":34,"{rdelim}":35,ELSE:36,"{else}":37,ELSE_IF:38,"{elseif":39,FOREACH_ELSE:40,"{foreachelse}":41,T_VARIABLE:42,T_VARIABLE_NAME:43,VARIABLE_repetition0:44,VARIABLE_SUFFIX:45,"[":46,"]":47,".":48,"(":49,VARIABLE_SUFFIX_option0:50,")":51,"=":52,COMMAND_PARAMETER_VALUE:53,T_QUOTED_STRING:54,T_DIGITS:55,COMMAND_PARAMETERS_repetition_plus0:56,COMMAND_PARAMETER:57,T_PLURAL_PARAMETER_NAME:58,$accept:0,$end:1},terminals_:{2:"error",5:"EOF",9:"T_LITERAL",11:"T_ANY",12:"T_WS",13:"{if",15:"}",18:"{/if}",19:"{include",21:"{implode",22:"{/implode}",23:"{foreach",25:"{/foreach}",26:"{plural",28:"{lang}",29:"{/lang}",30:"{",32:"{#",33:"{@",34:"{ldelim}",35:"{rdelim}",37:"{else}",39:"{elseif",41:"{foreachelse}",42:"T_VARIABLE",43:"T_VARIABLE_NAME",46:"[",47:"]",48:".",49:"(",51:")",52:"=",54:"T_QUOTED_STRING",55:"T_DIGITS"},productions_:[0,[3,2],[4,1],[7,1],[7,1],[7,1],[8,1],[8,1],[10,7],[10,3],[10,5],[10,6],[10,3],[10,3],[10,3],[10,3],[10,3],[10,1],[10,1],[36,2],[38,4],[40,2],[31,3],[45,3],[45,2],[45,3],[20,5],[20,3],[53,1],[53,1],[53,1],[14,1],[57,1],[57,1],[57,1],[57,1],[57,1],[57,1],[57,1],[57,3],[27,5],[27,3],[58,1],[58,1],[6,0],[6,2],[16,0],[16,2],[17,0],[17,1],[24,0],[24,1],[44,0],[44,2],[50,0],[50,1],[56,1],[56,2]],performAction:function(o,f,u,y,b,s,U){var n=s.length-1;switch(b){case 1:return s[n-1]+";";case 2:var R=s[n].reduce(function(_,L){return L.encode&&!_[1]?_[0]+=" + '"+L.value:L.encode&&_[1]?_[0]+=L.value:!L.encode&&_[1]?_[0]+="' + "+L.value:!L.encode&&!_[1]&&(_[0]+=" + "+L.value),_[1]=L.encode,_},["''",!1]);R[1]&&(R[0]+="'"),this.$=R[0];break;case 3:case 4:this.$={encode:!0,value:s[n].replace(/\\/g,"\\\\").replace(/'/g,"\\'").replace(/(\r\n|\n|\r)/g,"\\n")};break;case 5:this.$={encode:!1,value:s[n]};break;case 8:this.$="(function() { if ("+s[n-5]+") { return "+s[n-3]+"; } "+s[n-2].join(" ")+" "+(s[n-1]||"")+" return ''; })()";break;case 9:if(!s[n-1].file)throw new Error("Missing parameter file");this.$=s[n-1].file+".fetch(v)";break;case 10:if(!s[n-3].from)throw new Error("Missing parameter from");if(!s[n-3].item)throw new Error("Missing parameter item");s[n-3].glue||(s[n-3].glue="', '"),this.$="(function() { return "+s[n-3].from+".map(function(item) { v["+s[n-3].item+"] = item; return "+s[n-1]+"; }).join("+s[n-3].glue+"); })()";break;case 11:if(!s[n-4].from)throw new Error("Missing parameter from");if(!s[n-4].item)throw new Error("Missing parameter item");this.$="(function() {var looped = false, result = '';if ("+s[n-4].from+" instanceof Array) {for (var i = 0; i < "+s[n-4].from+".length; i++) { looped = true;v["+s[n-4].key+"] = i;v["+s[n-4].item+"] = "+s[n-4].from+"[i];result += "+s[n-2]+";}} else {for (var key in "+s[n-4].from+") {if (!"+s[n-4].from+".hasOwnProperty(key)) continue;looped = true;v["+s[n-4].key+"] = key;v["+s[n-4].item+"] = "+s[n-4].from+"[key];result += "+s[n-2]+";}}return (looped ? result : "+(s[n-1]||"''")+"); })()";break;case 12:this.$="h.selectPlural({";var B=!1;for(var F in s[n-1])Object.hasOwn(s[n-1],F)&&(this.$+=(B?",":"")+F+": "+s[n-1][F],B=!0);this.$+="})";break;case 13:this.$="Language.get("+s[n-1]+", v)";break;case 14:this.$="h.escapeHTML("+s[n-1]+")";break;case 15:this.$="h.formatNumeric("+s[n-1]+")";break;case 16:this.$=s[n-1];break;case 17:this.$="'{'";break;case 18:this.$="'}'";break;case 19:this.$="else { return "+s[n]+"; }";break;case 20:this.$="else if ("+s[n-2]+") { return "+s[n]+"; }";break;case 21:this.$=s[n];break;case 22:this.$="v['"+s[n-1]+"']"+s[n].join("");break;case 23:this.$=s[n-2]+s[n-1]+s[n];break;case 24:this.$="['"+s[n]+"']";break;case 25:case 39:this.$=s[n-2]+(s[n-1]||"")+s[n];break;case 26:case 40:this.$=s[n],this.$[s[n-4]]=s[n-2];break;case 27:case 41:this.$={},this.$[s[n-2]]=s[n];break;case 31:this.$=s[n].join("");break;case 44:case 46:case 52:this.$=[];break;case 45:case 47:case 53:case 57:s[n-1].push(s[n]);break;case 56:this.$=[s[n]];break}},table:[e([5,9,11,12,13,19,21,23,26,28,30,32,33,34,35],r,{3:1,4:2,6:3}),{1:[3]},{5:[1,4]},e([5,18,22,25,29,37,39,41],[2,2],{7:5,8:6,10:8,9:[1,7],11:[1,9],12:[1,10],13:[1,11],19:[1,12],21:[1,13],23:[1,14],26:[1,15],28:[1,16],30:[1,17],32:[1,18],33:[1,19],34:[1,20],35:[1,21]}),{1:[2,1]},e(i,[2,45]),e(i,[2,3]),e(i,[2,4]),e(i,[2,5]),e(i,[2,6]),e(i,[2,7]),{11:c,12:t,14:22,31:30,42:a,43:l,49:d,52:g,54:w,55:q,56:23,57:24},{20:34,43:S},{20:36,43:S},{20:37,43:S},{27:38,43:z,55:P,58:39},e([9,11,12,13,19,21,23,26,28,29,30,32,33,34,35],r,{6:3,4:42}),{31:43,42:a},{31:44,42:a},{31:45,42:a},e(i,[2,17]),e(i,[2,18]),{15:[1,46]},e([15,47,51],[2,31],{31:30,57:47,11:c,12:t,42:a,43:l,49:d,52:g,54:w,55:q}),e(h,[2,56]),e(h,[2,32]),e(h,[2,33]),e(h,[2,34]),e(h,[2,35]),e(h,[2,36]),e(h,[2,37]),e(h,[2,38]),{11:c,12:t,14:48,31:30,42:a,43:l,49:d,52:g,54:w,55:q,56:23,57:24},{43:[1,49]},{15:[1,50]},{52:[1,51]},{15:[1,52]},{15:[1,53]},{15:[1,54]},{52:[1,55]},{52:[2,42]},{52:[2,43]},{29:[1,56]},{15:[1,57]},{15:[1,58]},{15:[1,59]},e(m,r,{6:3,4:60}),e(h,[2,57]),{51:[1,61]},e(p,[2,52],{44:62}),e(i,[2,9]),{31:66,42:a,53:63,54:E,55:k},e([9,11,12,13,19,21,22,23,26,28,30,32,33,34,35],r,{6:3,4:67}),e([9,11,12,13,19,21,23,25,26,28,30,32,33,34,35,41],r,{6:3,4:68}),e(i,[2,12]),{31:66,42:a,53:69,54:E,55:k},e(i,[2,13]),e(i,[2,14]),e(i,[2,15]),e(i,[2,16]),e(x,[2,46],{16:70}),e(h,[2,39]),e([11,12,15,42,43,47,51,52,54,55],[2,22],{45:71,46:[1,72],48:[1,73],49:[1,74]}),{12:[1,75],15:[2,27]},e(D,[2,28]),e(D,[2,29]),e(D,[2,30]),{22:[1,76]},{24:77,25:[2,50],40:78,41:[1,79]},{12:[1,80],15:[2,41]},{17:81,18:[2,48],36:83,37:[1,85],38:82,39:[1,84]},e(p,[2,53]),{11:c,12:t,14:86,31:30,42:a,43:l,49:d,52:g,54:w,55:q,56:23,57:24},{43:[1,87]},{11:c,12:t,14:89,31:30,42:a,43:l,49:d,50:88,51:[2,54],52:g,54:w,55:q,56:23,57:24},{20:90,43:S},e(i,[2,10]),{25:[1,91]},{25:[2,51]},e([9,11,12,13,19,21,23,25,26,28,30,32,33,34,35],r,{6:3,4:92}),{27:93,43:z,55:P,58:39},{18:[1,94]},e(x,[2,47]),{18:[2,49]},{11:c,12:t,14:95,31:30,42:a,43:l,49:d,52:g,54:w,55:q,56:23,57:24},e([9,11,12,13,18,19,21,23,26,28,30,32,33,34,35],r,{6:3,4:96}),{47:[1,97]},e(p,[2,24]),{51:[1,98]},{51:[2,55]},{15:[2,26]},e(i,[2,11]),{25:[2,21]},{15:[2,40]},e(i,[2,8]),{15:[1,99]},{18:[2,19]},e(p,[2,23]),e(p,[2,25]),e(m,r,{6:3,4:100}),e(x,[2,20])],defaultActions:{4:[2,1],40:[2,42],41:[2,43],78:[2,51],83:[2,49],89:[2,55],90:[2,26],92:[2,21],93:[2,40],96:[2,19]},parseError:function(o,f){if(f.recoverable)this.trace(o);else{var u=new Error(o);throw u.hash=f,u}},parse:function(o){var f=this,u=[0],y=[],b=[null],s=[],U=this.table,n="",R=0,B=0,F=0,_=2,L=1,me=s.slice.call(arguments,1),v=Object.create(this.lexer),j={yy:{}};for(var X in this.yy)Object.prototype.hasOwnProperty.call(this.yy,X)&&(j.yy[X]=this.yy[X]);v.setInput(o,j.yy),j.yy.lexer=v,j.yy.parser=this,typeof v.yylloc>"u"&&(v.yylloc={});var J=v.yylloc;s.push(J);var be=v.options&&v.options.ranges;typeof j.yy.parseError=="function"?this.parseError=j.yy.parseError:this.parseError=Object.getPrototypeOf(this).parseError;function Ce(M){u.length=u.length-2*M,b.length=b.length-M,s.length=s.length-M}for(var we=function(){var M;return M=v.lex()||L,typeof M!="number"&&(M=f.symbols_[M]||M),M},T,$,H,A,Re,ee,N={},Y,I,oe,Z;;){if(H=u[u.length-1],this.defaultActions[H]?A=this.defaultActions[H]:((T===null||typeof T>"u")&&(T=we()),A=U[H]&&U[H][T]),typeof A>"u"||!A.length||!A[0]){var ae="";Z=[];for(Y in U[H])this.terminals_[Y]&&Y>_&&Z.push("'"+this.terminals_[Y]+"'");v.showPosition?ae="Parse error on line "+(R+1)+`:
 `+v.showPosition()+`
-Expecting `+Z.join(", ")+", got '"+(this.terminals_[T]||T)+"'":ae="Parse error on line "+(C+1)+": Unexpected "+(T==L?"end of input":"'"+(this.terminals_[T]||T)+"'"),this.parseError(ae,{text:v.match,token:this.terminals_[T]||T,line:v.yylineno,loc:J,expected:Z})}if(A[0]instanceof Array&&A.length>1)throw new Error("Parse Error: multiple actions possible at state: "+H+", token: "+T);switch(A[0]){case 1:u.push(T),b.push(v.yytext),s.push(v.yylloc),u.push(A[1]),T=null,$?(T=$,$=null):(B=v.yyleng,n=v.yytext,C=v.yylineno,J=v.yylloc,F>0&&F--);break;case 2:if(I=this.productions_[A[1]][1],N.$=b[b.length-I],N._$={first_line:s[s.length-(I||1)].first_line,last_line:s[s.length-1].last_line,first_column:s[s.length-(I||1)].first_column,last_column:s[s.length-1].last_column},be&&(N._$.range=[s[s.length-(I||1)].range[0],s[s.length-1].range[1]]),ee=this.performAction.apply(N,[n,B,C,j.yy,A[1],b,s].concat(me)),typeof ee<"u")return ee;I&&(u=u.slice(0,-1*I*2),b=b.slice(0,-1*I),s=s.slice(0,-1*I)),u.push(this.productions_[A[1]][0]),b.push(N.$),s.push(N._$),oe=U[u[u.length-2]][u[u.length-1]],u.push(oe);break;case 3:return!0}}return!0}},ge=function(){var R={EOF:1,parseError:function(f,u){if(this.yy.parser)this.yy.parser.parseError(f,u);else throw new Error(f)},setInput:function(o,f){return this.yy=f||this.yy||{},this._input=o,this._more=this._backtrack=this.done=!1,this.yylineno=this.yyleng=0,this.yytext=this.matched=this.match="",this.conditionStack=["INITIAL"],this.yylloc={first_line:1,first_column:0,last_line:1,last_column:0},this.options.ranges&&(this.yylloc.range=[0,0]),this.offset=0,this},input:function(){var o=this._input[0];this.yytext+=o,this.yyleng++,this.offset++,this.match+=o,this.matched+=o;var f=o.match(/(?:\r\n?|\n).*/g);return f?(this.yylineno++,this.yylloc.last_line++):this.yylloc.last_column++,this.options.ranges&&this.yylloc.range[1]++,this._input=this._input.slice(1),o},unput:function(o){var f=o.length,u=o.split(/(?:\r\n?|\n)/g);this._input=o+this._input,this.yytext=this.yytext.substr(0,this.yytext.length-f),this.offset-=f;var y=this.match.split(/(?:\r\n?|\n)/g);this.match=this.match.substr(0,this.match.length-1),this.matched=this.matched.substr(0,this.matched.length-1),u.length-1&&(this.yylineno-=u.length-1);var b=this.yylloc.range;return this.yylloc={first_line:this.yylloc.first_line,last_line:this.yylineno+1,first_column:this.yylloc.first_column,last_column:u?(u.length===y.length?this.yylloc.first_column:0)+y[y.length-u.length].length-u[0].length:this.yylloc.first_column-f},this.options.ranges&&(this.yylloc.range=[b[0],b[0]+this.yyleng-f]),this.yyleng=this.yytext.length,this},more:function(){return this._more=!0,this},reject:function(){if(this.options.backtrack_lexer)this._backtrack=!0;else return this.parseError("Lexical error on line "+(this.yylineno+1)+`. You can only invoke reject() in the lexer when the lexer is of the backtracking persuasion (options.backtrack_lexer = true).
+Expecting `+Z.join(", ")+", got '"+(this.terminals_[T]||T)+"'":ae="Parse error on line "+(R+1)+": Unexpected "+(T==L?"end of input":"'"+(this.terminals_[T]||T)+"'"),this.parseError(ae,{text:v.match,token:this.terminals_[T]||T,line:v.yylineno,loc:J,expected:Z})}if(A[0]instanceof Array&&A.length>1)throw new Error("Parse Error: multiple actions possible at state: "+H+", token: "+T);switch(A[0]){case 1:u.push(T),b.push(v.yytext),s.push(v.yylloc),u.push(A[1]),T=null,$?(T=$,$=null):(B=v.yyleng,n=v.yytext,R=v.yylineno,J=v.yylloc,F>0&&F--);break;case 2:if(I=this.productions_[A[1]][1],N.$=b[b.length-I],N._$={first_line:s[s.length-(I||1)].first_line,last_line:s[s.length-1].last_line,first_column:s[s.length-(I||1)].first_column,last_column:s[s.length-1].last_column},be&&(N._$.range=[s[s.length-(I||1)].range[0],s[s.length-1].range[1]]),ee=this.performAction.apply(N,[n,B,R,j.yy,A[1],b,s].concat(me)),typeof ee<"u")return ee;I&&(u=u.slice(0,-1*I*2),b=b.slice(0,-1*I),s=s.slice(0,-1*I)),u.push(this.productions_[A[1]][0]),b.push(N.$),s.push(N._$),oe=U[u[u.length-2]][u[u.length-1]],u.push(oe);break;case 3:return!0}}return!0}},ge=function(){var C={EOF:1,parseError:function(f,u){if(this.yy.parser)this.yy.parser.parseError(f,u);else throw new Error(f)},setInput:function(o,f){return this.yy=f||this.yy||{},this._input=o,this._more=this._backtrack=this.done=!1,this.yylineno=this.yyleng=0,this.yytext=this.matched=this.match="",this.conditionStack=["INITIAL"],this.yylloc={first_line:1,first_column:0,last_line:1,last_column:0},this.options.ranges&&(this.yylloc.range=[0,0]),this.offset=0,this},input:function(){var o=this._input[0];this.yytext+=o,this.yyleng++,this.offset++,this.match+=o,this.matched+=o;var f=o.match(/(?:\r\n?|\n).*/g);return f?(this.yylineno++,this.yylloc.last_line++):this.yylloc.last_column++,this.options.ranges&&this.yylloc.range[1]++,this._input=this._input.slice(1),o},unput:function(o){var f=o.length,u=o.split(/(?:\r\n?|\n)/g);this._input=o+this._input,this.yytext=this.yytext.substr(0,this.yytext.length-f),this.offset-=f;var y=this.match.split(/(?:\r\n?|\n)/g);this.match=this.match.substr(0,this.match.length-1),this.matched=this.matched.substr(0,this.matched.length-1),u.length-1&&(this.yylineno-=u.length-1);var b=this.yylloc.range;return this.yylloc={first_line:this.yylloc.first_line,last_line:this.yylineno+1,first_column:this.yylloc.first_column,last_column:u?(u.length===y.length?this.yylloc.first_column:0)+y[y.length-u.length].length-u[0].length:this.yylloc.first_column-f},this.options.ranges&&(this.yylloc.range=[b[0],b[0]+this.yyleng-f]),this.yyleng=this.yytext.length,this},more:function(){return this._more=!0,this},reject:function(){if(this.options.backtrack_lexer)this._backtrack=!0;else return this.parseError("Lexical error on line "+(this.yylineno+1)+`. You can only invoke reject() in the lexer when the lexer is of the backtracking persuasion (options.backtrack_lexer = true).
 `+this.showPosition(),{text:"",token:null,line:this.yylineno});return this},less:function(o){this.unput(this.match.slice(o))},pastInput:function(){var o=this.matched.substr(0,this.matched.length-this.match.length);return(o.length>20?"...":"")+o.substr(-20).replace(/\n/g,"")},upcomingInput:function(){var o=this.match;return o.length<20&&(o+=this._input.substr(0,20-o.length)),(o.substr(0,20)+(o.length>20?"...":"")).replace(/\n/g,"")},showPosition:function(){var o=this.pastInput(),f=new Array(o.length+1).join("-");return o+this.upcomingInput()+`
 `+f+"^"},test_match:function(o,f){var u,y,b;if(this.options.backtrack_lexer&&(b={yylineno:this.yylineno,yylloc:{first_line:this.yylloc.first_line,last_line:this.last_line,first_column:this.yylloc.first_column,last_column:this.yylloc.last_column},yytext:this.yytext,match:this.match,matches:this.matches,matched:this.matched,yyleng:this.yyleng,offset:this.offset,_more:this._more,_input:this._input,yy:this.yy,conditionStack:this.conditionStack.slice(0),done:this.done},this.options.ranges&&(b.yylloc.range=this.yylloc.range.slice(0))),y=o[0].match(/(?:\r\n?|\n).*/g),y&&(this.yylineno+=y.length),this.yylloc={first_line:this.yylloc.last_line,last_line:this.yylineno+1,first_column:this.yylloc.last_column,last_column:y?y[y.length-1].length-y[y.length-1].match(/\r?\n?/)[0].length:this.yylloc.last_column+o[0].length},this.yytext+=o[0],this.match+=o[0],this.matches=o,this.yyleng=this.yytext.length,this.options.ranges&&(this.yylloc.range=[this.offset,this.offset+=this.yyleng]),this._more=!1,this._backtrack=!1,this._input=this._input.slice(o[0].length),this.matched+=o[0],u=this.performAction.call(this,this.yy,this,f,this.conditionStack[this.conditionStack.length-1]),this.done&&this._input&&(this.done=!1),u)return u;if(this._backtrack){for(var s in b)this[s]=b[s];return!1}return!1},next:function(){if(this.done)return this.EOF;this._input||(this.done=!0);var o,f,u,y;this._more||(this.yytext="",this.match="");for(var b=this._currentRules(),s=0;s<b.length;s++)if(u=this._input.match(this.rules[b[s]]),u&&(!f||u[0].length>f[0].length)){if(f=u,y=s,this.options.backtrack_lexer){if(o=this.test_match(u,b[s]),o!==!1)return o;if(this._backtrack){f=!1;continue}else return!1}else if(!this.options.flex)break}return f?(o=this.test_match(f,b[y]),o!==!1?o:!1):this._input===""?this.EOF:this.parseError("Lexical error on line "+(this.yylineno+1)+`. Unrecognized text.
-`+this.showPosition(),{text:"",token:null,line:this.yylineno})},lex:function(){var f=this.next();return f||this.lex()},begin:function(f){this.conditionStack.push(f)},popState:function(){var f=this.conditionStack.length-1;return f>0?this.conditionStack.pop():this.conditionStack[0]},_currentRules:function(){return this.conditionStack.length&&this.conditionStack[this.conditionStack.length-1]?this.conditions[this.conditionStack[this.conditionStack.length-1]].rules:this.conditions.INITIAL.rules},topState:function(f){return f=this.conditionStack.length-1-Math.abs(f||0),f>=0?this.conditionStack[f]:"INITIAL"},pushState:function(f){this.begin(f)},stateStackSize:function(){return this.conditionStack.length},options:{},performAction:function(f,u,y,b){var s=b;switch(y){case 0:break;case 1:return u.yytext=u.yytext.substring(9,u.yytext.length-10),9;break;case 2:return 54;case 3:return 54;case 4:return 42;case 5:return 55;case 6:return 43;case 7:return 48;case 8:return 46;case 9:return 47;case 10:return 49;case 11:return 51;case 12:return 52;case 13:return 34;case 14:return 35;case 15:return this.begin("command"),32;break;case 16:return this.begin("command"),33;break;case 17:return this.begin("command"),13;break;case 18:return this.begin("command"),39;break;case 19:return this.begin("command"),39;break;case 20:return 37;case 21:return 18;case 22:return 28;case 23:return 29;case 24:return this.begin("command"),19;break;case 25:return this.begin("command"),21;break;case 26:return this.begin("command"),26;break;case 27:return 22;case 28:return this.begin("command"),23;break;case 29:return 41;case 30:return 25;case 31:return this.begin("command"),30;break;case 32:return this.popState(),15;break;case 33:return 12;case 34:return 5;case 35:return 11}},rules:[/^(?:\{\*[\s\S]*?\*\})/,/^(?:\{literal\}[\s\S]*?\{\/literal\})/,/^(?:"([^"]|\\\.)*")/,/^(?:'([^']|\\\.)*')/,/^(?:\$)/,/^(?:[0-9]+)/,/^(?:[_a-zA-Z][_a-zA-Z0-9]*)/,/^(?:\.)/,/^(?:\[)/,/^(?:\])/,/^(?:\()/,/^(?:\))/,/^(?:=)/,/^(?:\{ldelim\})/,/^(?:\{rdelim\})/,/^(?:\{#)/,/^(?:\{@)/,/^(?:\{if )/,/^(?:\{else if )/,/^(?:\{elseif )/,/^(?:\{else\})/,/^(?:\{\/if\})/,/^(?:\{lang\})/,/^(?:\{\/lang\})/,/^(?:\{include )/,/^(?:\{implode )/,/^(?:\{plural )/,/^(?:\{\/implode\})/,/^(?:\{foreach )/,/^(?:\{foreachelse\})/,/^(?:\{\/foreach\})/,/^(?:\{(?!\s))/,/^(?:\})/,/^(?:\s+)/,/^(?:$)/,/^(?:[^{])/],conditions:{command:{rules:[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35],inclusive:!0},INITIAL:{rules:[0,1,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,33,34,35],inclusive:!0}}};return R}();W.lexer=ge;function K(){this.yy={}}return K.prototype=W,W.Parser=K,new K}();typeof se<"u"&&typeof O<"u"&&(O.parser=Q,O.Parser=Q.Parser,O.parse=function(){return Q.parse.apply(Q,arguments)},O.main=!0,typeof re<"u"&&se.main===re&&O.main(process.argv.slice(1)))});var ne={};ce(ne,{getPhrase:()=>le,registerPhrase:()=>Ie});var he=Le(fe());var ie={};ce(ie,{add:()=>G,get:()=>le});var ue=new Map;function le(e,r={}){let i=ue.get(e);return i===void 0?e:i(r)}function G(e,r){ue.set(e,r)}function Ae(e){return String(e).replace(/&/g,"&amp;").replace(/"/g,"&quot;").replace(/</g,"&lt;").replace(/>/g,"&gt;")}function de(e){return Number(e).toLocaleString(document.documentElement.lang,{maximumFractionDigits:2}).replace("-","\u2212")}var qe=new Intl.PluralRules(document.documentElement.lang);function Se(e){if(!Object.hasOwn(e,"value"))throw new Error("Missing parameter value");if(!e.other)throw new Error("Missing parameter other");let r=e.value;if(Array.isArray(r)&&(r=r.length),Object.hasOwn(e,r.toString()))return e[r];let i=qe.select(r);e[i]===void 0&&(i="other");let c=e[i];return c.includes("#")?c.replace("#",de(r)):c}function Me(e){let r=`var tmp = {};
+`+this.showPosition(),{text:"",token:null,line:this.yylineno})},lex:function(){var f=this.next();return f||this.lex()},begin:function(f){this.conditionStack.push(f)},popState:function(){var f=this.conditionStack.length-1;return f>0?this.conditionStack.pop():this.conditionStack[0]},_currentRules:function(){return this.conditionStack.length&&this.conditionStack[this.conditionStack.length-1]?this.conditions[this.conditionStack[this.conditionStack.length-1]].rules:this.conditions.INITIAL.rules},topState:function(f){return f=this.conditionStack.length-1-Math.abs(f||0),f>=0?this.conditionStack[f]:"INITIAL"},pushState:function(f){this.begin(f)},stateStackSize:function(){return this.conditionStack.length},options:{},performAction:function(f,u,y,b){var s=b;switch(y){case 0:break;case 1:return u.yytext=u.yytext.substring(9,u.yytext.length-10),9;break;case 2:return 54;case 3:return 54;case 4:return 42;case 5:return 55;case 6:return 43;case 7:return 48;case 8:return 46;case 9:return 47;case 10:return 49;case 11:return 51;case 12:return 52;case 13:return 34;case 14:return 35;case 15:return this.begin("command"),32;break;case 16:return this.begin("command"),33;break;case 17:return this.begin("command"),13;break;case 18:return this.begin("command"),39;break;case 19:return this.begin("command"),39;break;case 20:return 37;case 21:return 18;case 22:return 28;case 23:return 29;case 24:return this.begin("command"),19;break;case 25:return this.begin("command"),21;break;case 26:return this.begin("command"),26;break;case 27:return 22;case 28:return this.begin("command"),23;break;case 29:return 41;case 30:return 25;case 31:return this.begin("command"),30;break;case 32:return this.popState(),15;break;case 33:return 12;case 34:return 5;case 35:return 11}},rules:[/^(?:\{\*[\s\S]*?\*\})/,/^(?:\{literal\}[\s\S]*?\{\/literal\})/,/^(?:"([^"]|\\\.)*")/,/^(?:'([^']|\\\.)*')/,/^(?:\$)/,/^(?:[0-9]+)/,/^(?:[_a-zA-Z][_a-zA-Z0-9]*)/,/^(?:\.)/,/^(?:\[)/,/^(?:\])/,/^(?:\()/,/^(?:\))/,/^(?:=)/,/^(?:\{ldelim\})/,/^(?:\{rdelim\})/,/^(?:\{#)/,/^(?:\{@)/,/^(?:\{if )/,/^(?:\{else if )/,/^(?:\{elseif )/,/^(?:\{else\})/,/^(?:\{\/if\})/,/^(?:\{lang\})/,/^(?:\{\/lang\})/,/^(?:\{include )/,/^(?:\{implode )/,/^(?:\{plural )/,/^(?:\{\/implode\})/,/^(?:\{foreach )/,/^(?:\{foreachelse\})/,/^(?:\{\/foreach\})/,/^(?:\{(?!\s))/,/^(?:\})/,/^(?:\s+)/,/^(?:$)/,/^(?:[^{])/],conditions:{command:{rules:[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35],inclusive:!0},INITIAL:{rules:[0,1,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,33,34,35],inclusive:!0}}};return C}();W.lexer=ge;function K(){this.yy={}}return K.prototype=W,W.Parser=K,new K}();typeof se<"u"&&typeof O<"u"&&(O.parser=Q,O.Parser=Q.Parser,O.parse=function(){return Q.parse.apply(Q,arguments)},O.main=!0,typeof re<"u"&&se.main===re&&O.main(process.argv.slice(1)))});var ne={};ce(ne,{getPhrase:()=>le,registerPhrase:()=>Ie});var he=Le(fe());var ie={};ce(ie,{add:()=>G,get:()=>le});var ue=new Map;function le(e,r={}){let i=ue.get(e);return i===void 0?e:i(r)}function G(e,r){ue.set(e,r)}function Ae(e){return String(e).replace(/&/g,"&amp;").replace(/"/g,"&quot;").replace(/</g,"&lt;").replace(/>/g,"&gt;")}function de(e){return Number(e).toLocaleString(document.documentElement.lang,{maximumFractionDigits:2}).replace("-","\u2212")}var qe=new Intl.PluralRules(document.documentElement.lang);function Se(e){if(!Object.hasOwn(e,"value"))throw new Error("Missing parameter value");if(!e.other)throw new Error("Missing parameter other");let r=e.value;if(Array.isArray(r)&&(r=r.length),Object.hasOwn(e,r.toString()))return e[r];let i=qe.select(r);e[i]===void 0&&(i="other");let c=e[i];return c.includes("#")?c.replace("#",de(r)):c}function Me(e){let r=`var tmp = {};
     for (var key in v) tmp[key] = v[key];
     v = tmp;
     v.__wcf = window.WCF; v.__window = window;
@@ -60,7 +60,7 @@ Expecting `+Z.join(", ")+", got '"+(this.terminals_[T]||T)+"'":ae="Parse error o
             time::after {
               content: " (" attr(title) ")";
             }
-          }`,x.append(D)}m&&(this.#a.dateTime=p.toISOString(),this.#a.title=g.DateAndTime.format(p));let k;if(this.static)k=this.#a.title;else if(E<w.OneMinute)k=window.WoltLabLanguage.getPhrase("wcf.date.relative.now");else if(E<w.OneHour){let x=Math.trunc(E/w.OneMinute);k=g.Minutes.format(x*-1,"minute")}else if(E<w.TwelveHours){let x=Math.trunc(E/w.OneHour);k=g.Hours.format(x*-1,"hour")}else if(E<w.SixDays){let x=g.DayOfWeekAndTime.formatToParts(p);x[0].type==="weekday"?p.getTime()>t?k=this.#t(x,0):p.getTime()>a?k=this.#t(x,-1):k=x.map(W=>W.value).join(""):k=g.DateAndTime.format(p)}else k=g.Date.format(p);k=k.charAt(0).toUpperCase()+k.slice(1),this.#a.textContent=k}#t(m,p){return m.map(k=>k.type==="weekday"?g.TodayOrYesterday.format(p,"day"):k.value).join("")}}window.customElements.define("woltlab-core-date-time",q);let S=()=>{document.querySelectorAll("woltlab-core-date-time").forEach(h=>h.refresh(!1))},z,P=()=>{z=window.setInterval(()=>{l(),S()},6e4)};document.addEventListener("DOMContentLoaded",()=>P(),{once:!0}),document.addEventListener("visibilitychange",()=>{document.hidden?window.clearInterval(z):(S(),P())})}{class e extends HTMLElement{#e;constructor(){super(),this.#e=document.createElement("input"),this.#e.type="file",this.#e.addEventListener("change",()=>{let{files:i}=this.#e;if(!(i===null||i.length===0)){for(let c of i){let t=new CustomEvent("shouldUpload",{cancelable:!0,detail:c});if(this.dispatchEvent(t),t.defaultPrevented)continue;let a=new CustomEvent("upload",{detail:c});this.dispatchEvent(a)}this.#e.value=""}})}connectedCallback(){let i=this.dataset.fileExtensions||"";i!==""&&(this.#e.accept=i),this.attachShadow({mode:"open"}).append(this.#e);let t=document.createElement("style");t.textContent=`
+          }`,x.append(D)}m&&(this.#a.dateTime=p.toISOString(),this.#a.title=g.DateAndTime.format(p));let k;if(this.static)k=this.#a.title;else if(E<w.OneMinute)k=window.WoltLabLanguage.getPhrase("wcf.date.relative.now");else if(E<w.OneHour){let x=Math.trunc(E/w.OneMinute);k=g.Minutes.format(x*-1,"minute")}else if(E<w.TwelveHours){let x=Math.trunc(E/w.OneHour);k=g.Hours.format(x*-1,"hour")}else if(E<w.SixDays){let x=g.DayOfWeekAndTime.formatToParts(p);x[0].type==="weekday"?p.getTime()>t?k=this.#t(x,0):p.getTime()>a?k=this.#t(x,-1):k=x.map(W=>W.value).join(""):k=g.DateAndTime.format(p)}else k=g.Date.format(p);k=k.charAt(0).toUpperCase()+k.slice(1),this.#a.textContent=k}#t(m,p){return m.map(k=>k.type==="weekday"?g.TodayOrYesterday.format(p,"day"):k.value).join("")}}window.customElements.define("woltlab-core-date-time",q);let S=()=>{document.querySelectorAll("woltlab-core-date-time").forEach(h=>h.refresh(!1))},z,P=()=>{z=window.setInterval(()=>{l(),S()},6e4)};document.addEventListener("DOMContentLoaded",()=>P(),{once:!0}),document.addEventListener("visibilitychange",()=>{document.hidden?window.clearInterval(z):(S(),P())})}{class e extends HTMLElement{#e;constructor(){super(),this.#e=document.createElement("input"),this.#e.type="file",this.#e.addEventListener("change",()=>{let{files:i}=this.#e;if(!(i===null||i.length===0)){for(let c of i){let t=new CustomEvent("shouldUpload",{cancelable:!0,detail:c});if(this.dispatchEvent(t),t.defaultPrevented)continue;let a=new CustomEvent("upload",{detail:c});this.dispatchEvent(a)}this.#e.value=""}})}connectedCallback(){let i=this.dataset.fileExtensions||"";i!==""&&(this.#e.accept=i);let c=this.maximumCount;(c>1||c===-1)&&(this.#e.multiple=!0),this.attachShadow({mode:"open"}).append(this.#e);let a=document.createElement("style");a.textContent=`
         :host {
             position: relative;
         }
@@ -70,7 +70,7 @@ Expecting `+Z.join(", ")+", got '"+(this.terminals_[T]||T)+"'":ae="Parse error o
             position: absolute;
             visibility: hidden;
         }
-      `}}window.customElements.define("woltlab-core-file-upload",e)}{let r=[24,48,96];class i extends HTMLElement{#e;#a;connectedCallback(){this.#e===void 0&&this.#t()}attributeChangedCallback(t,a,l){if(t==="size"){let d=parseInt(l||"");if(!r.includes(d)){let g=parseInt(a||"");r.includes(g)||(g=24),this.setAttribute(t,g.toString())}}}#t(){this.classList.add("loading-indicator"),this.hasAttribute("size")||this.setAttribute("size",24 .toString()),this.#e=document.createElement("fa-icon"),this.#e.size=this.size,this.#e.setIcon("spinner"),this.#a=document.createElement("span"),this.#a.classList.add("loading-indicator__text"),this.#a.textContent=window.WoltLabLanguage.getPhrase("wcf.global.loading"),this.#a.hidden=this.hideText;let t=document.createElement("div");t.classList.add("loading-indicator__wrapper"),t.append(this.#e,this.#a),this.append(t)}get size(){return parseInt(this.getAttribute("size"))}set size(t){if(!r.includes(t))throw new TypeError(`The size ${t} is unrecognized, permitted values are ${r.join(", ")}.`);this.setAttribute("size",t.toString()),this.#e&&(this.#e.size=t)}get hideText(){return this.hasAttribute("hide-text")}set hideText(t){t?this.setAttribute("hide-text",""):this.removeAttribute("hide-text"),this.#a&&(this.#a.hidden=t)}static get observedAttributes(){return["size"]}}window.customElements.define("woltlab-core-loading-indicator",i)}{let e;(l=>(l.Info="info",l.Success="success",l.Warning="warning",l.Error="error"))(e||={});class r extends HTMLElement{#e;#a;connectedCallback(){let c=this.attachShadow({mode:"open"});this.#e=document.createElement("fa-icon"),this.#e.size=24,this.#e.setIcon(this.icon,!0),this.#e.slot="icon",this.append(this.#e);let t=document.createElement("style");t.textContent=`
+      `}get maximumCount(){return parseInt(this.dataset.maximumCount||"1")}get disabled(){return this.#e.disabled}set disabled(i){this.#e.disabled=!!i}}window.customElements.define("woltlab-core-file-upload",e)}{let r=[24,48,96];class i extends HTMLElement{#e;#a;connectedCallback(){this.#e===void 0&&this.#t()}attributeChangedCallback(t,a,l){if(t==="size"){let d=parseInt(l||"");if(!r.includes(d)){let g=parseInt(a||"");r.includes(g)||(g=24),this.setAttribute(t,g.toString())}}}#t(){this.classList.add("loading-indicator"),this.hasAttribute("size")||this.setAttribute("size",24 .toString()),this.#e=document.createElement("fa-icon"),this.#e.size=this.size,this.#e.setIcon("spinner"),this.#a=document.createElement("span"),this.#a.classList.add("loading-indicator__text"),this.#a.textContent=window.WoltLabLanguage.getPhrase("wcf.global.loading"),this.#a.hidden=this.hideText;let t=document.createElement("div");t.classList.add("loading-indicator__wrapper"),t.append(this.#e,this.#a),this.append(t)}get size(){return parseInt(this.getAttribute("size"))}set size(t){if(!r.includes(t))throw new TypeError(`The size ${t} is unrecognized, permitted values are ${r.join(", ")}.`);this.setAttribute("size",t.toString()),this.#e&&(this.#e.size=t)}get hideText(){return this.hasAttribute("hide-text")}set hideText(t){t?this.setAttribute("hide-text",""):this.removeAttribute("hide-text"),this.#a&&(this.#a.hidden=t)}static get observedAttributes(){return["size"]}}window.customElements.define("woltlab-core-loading-indicator",i)}{let e;(l=>(l.Info="info",l.Success="success",l.Warning="warning",l.Error="error"))(e||={});class r extends HTMLElement{#e;#a;connectedCallback(){let c=this.attachShadow({mode:"open"});this.#e=document.createElement("fa-icon"),this.#e.size=24,this.#e.setIcon(this.icon,!0),this.#e.slot="icon",this.append(this.#e);let t=document.createElement("style");t.textContent=`
         :host {
           align-items: center;
           display: grid;
index bed6010927d2246861493d4079ec90e75a5aff7f..9d3c1a7d870db10570346ec78d4f9ba22027978f 100644 (file)
@@ -35,6 +35,11 @@ final class PostUpload implements IController
             throw new UserInputException('context', 'invalid');
         }
 
+        // Check if the maximum number of accepted files has already been uploaded.
+        if (FileProcessor::getInstance()->hasReachedUploadLimit($fileProcessor, $decodedContext)) {
+            throw new UserInputException('preflight', 'tooManyFiles');
+        }
+
         $validationResult = $fileProcessor->acceptUpload($parameters->filename, $parameters->fileSize, $decodedContext);
         if (!$validationResult->ok()) {
             match ($validationResult) {
index 8a5625fada29b26037a1427fd598a6857e0372a5..36acc9b5c2d6670d351387a5ecaf0896f3ed1b89 100644 (file)
@@ -151,6 +151,7 @@ final class PreloadPhrasesCollectingListener
 
         $event->preload('wcf.upload.error.fileExtensionNotPermitted');
         $event->preload('wcf.upload.error.fileSizeTooLarge');
+        $event->preload('wcf.upload.error.maximumCountReached');
 
         $event->preload('wcf.user.activityPoint');
         $event->preload('wcf.user.language');
index 71e6109457c536c61b695ee5a476ef02bacdb699..418340b750ac23f0795a73eb2d65c3f5f8cae73c 100644 (file)
@@ -22,6 +22,15 @@ abstract class AbstractFileProcessor implements IFileProcessor
         // There are no thumbnails in the default implementation.
     }
 
+    #[\Override]
+    public function countExistingFiles(array $context): ?int
+    {
+        // Counting of existing files is only required for files that want to
+        // enforce a limit to the concurrent amount of files. This is only ever
+        // relevant for UGC that needs to limit things like attachments.
+        return null;
+    }
+
     #[\Override]
     public function getAllowedFileExtensions(array $context): array
     {
@@ -35,6 +44,13 @@ abstract class AbstractFileProcessor implements IFileProcessor
         return FileCacheDuration::oneYear();
     }
 
+    #[\Override]
+    public function getMaximumCount(array $context): ?int
+    {
+        // Allow only a single file to be uploaded.
+        return 1;
+    }
+
     #[\Override]
     public function getResizeConfiguration(): ResizeConfiguration
     {
index 6eecb66a7db00b86a7caf33fe586c2ed509f4ef0..42c56e39187876229d8b1fd61e8e37995317c6ef 100644 (file)
@@ -104,6 +104,17 @@ final class AttachmentFileProcessor extends AbstractFileProcessor
         return $attachment->canDownload();
     }
 
+    #[\Override]
+    public function getMaximumCount(array $context): ?int
+    {
+        $attachmentHandler = $this->getAttachmentHandlerFromContext($context);
+        if ($attachmentHandler === null) {
+            return 0;
+        }
+
+        return $attachmentHandler->getMaxCount();
+    }
+
     #[\Override]
     public function getUploadResponse(File $file): array
     {
@@ -230,6 +241,13 @@ final class AttachmentFileProcessor extends AbstractFileProcessor
         return FileCacheDuration::shortLived();
     }
 
+    #[\Override]
+    public function countExistingFiles(array $context): ?int
+    {
+        $attachmentHandler = $this->getAttachmentHandlerFromContext($context);
+        return $attachmentHandler?->count();
+    }
+
     private function getAttachmentHandlerFromContext(array $context): ?AttachmentHandler
     {
         try {
index 82e947bcef02b8922f82afa37af974523b9b3cf8..17f3bbd0a8a99ad41f943a274b586b8dcd7660ba 100644 (file)
@@ -76,6 +76,11 @@ final class FileProcessor extends SingletonFactory
             );
         }
 
+        $maximumCount = $fileProcessor->getMaximumCount($context);
+        if ($maximumCount === null) {
+            $maximumCount = -1;
+        }
+
         return \sprintf(
             <<<'HTML'
                 <woltlab-core-file-upload
@@ -83,12 +88,14 @@ final class FileProcessor extends SingletonFactory
                     data-context="%s"
                     data-file-extensions="%s"
                     data-resize-configuration="%s"
+                    data-maximum-count="%d"
                 ></woltlab-core-file-upload>
                 HTML,
             StringUtil::encodeHTML($fileProcessor->getObjectTypeName()),
             StringUtil::encodeHTML(JSON::encode($context)),
             StringUtil::encodeHTML($allowedFileExtensions),
             StringUtil::encodeHTML(JSON::encode($fileProcessor->getResizeConfiguration())),
+            $maximumCount,
         );
     }
 
@@ -178,4 +185,30 @@ final class FileProcessor extends SingletonFactory
             $objectType->getProcessor()->delete($fileIDs, $thumbnailIDs);
         }
     }
+
+    public function hasReachedUploadLimit(IFileProcessor $fileProcessor, array $context): bool
+    {
+        $existingFiles = $fileProcessor->countExistingFiles($context);
+        if ($existingFiles === null) {
+            return false;
+        }
+
+        $maximumCount = $fileProcessor->getMaximumCount($context);
+
+        $sql = "SELECT  COUNT(*)
+                FROM    wcf1_file_temporary
+                WHERE   objectTypeID = ?
+                    AND context = ?";
+        $statement = WCF::getDB()->prepare($sql);
+        $statement->execute([
+            $this->getObjectType($fileProcessor->getObjectTypeName())->objectTypeID,
+            JSON::encode($context),
+        ]);
+        $numberOfTemporaryFiles = $statement->fetchSingleColumn();
+        if ($existingFiles + $numberOfTemporaryFiles >= $maximumCount) {
+            return true;
+        }
+
+        return false;
+    }
 }
index bf8f3218d24506257196b8ccf898fcb82e4df52a..6ddfcfd9f54fad8c60fcc0b4e6f3ee13e955053c 100644 (file)
@@ -57,6 +57,18 @@ interface IFileProcessor
      */
     public function canDownload(File $file): bool;
 
+    /**
+     * Counts the numbers of existing files for the provided context.
+     *
+     * The purpose of this method is to enforce upload limits for UGC, but an
+     * implementation may opt out of this by returning `null` which means that
+     * it does not track this for whatever reason.
+     *
+     * @param array<string,string> $context
+     * @return null|int number of existing files or `null` if this should not be enforced
+     */
+    public function countExistingFiles(array $context): ?int;
+
     /**
      * Notifies the file processor that the list of provided file and thumbnail
      * ids have been deleted.
@@ -81,6 +93,14 @@ interface IFileProcessor
      */
     public function getFileCacheDuration(File $file): FileCacheDuration;
 
+    /**
+     * Limits the maximum number of files that can be uploaded for the provided
+     * context.
+     *
+     * @return null|int Maximum number of files or `null` for an indefinite amount.
+     */
+    public function getMaximumCount(array $context): ?int;
+
     /**
      * Controls the client-side resizing of some types of images before they are
      * being uploaded to the server.
index 9351397bd1dae4b4dbe2fedcdbcd8a957d95f063..2d299a992069500aeb18d889eabc0b2798274651 100644 (file)
@@ -5552,6 +5552,7 @@ Benachrichtigungen auf <a href="{link isHtmlEmail=true}{/link}">{PAGE_TITLE|phra
                
                <item name="wcf.upload.error.fileExtensionNotPermitted"><![CDATA[Der Dateityp von „{$filename}“ ist unzulässig.]]></item>
                <item name="wcf.upload.error.fileSizeTooLarge"><![CDATA[Die Datei ist zu groß.]]></item>
+               <item name="wcf.upload.error.maximumCountReached"><![CDATA[Es {plural value=$maximumCount 1='darf nur eine Datei' other='dürfen nur # Dateien'} hochgeladen werden.]]></item>
        </category>
 </import>
 <delete>
index bd0173a3fe968a80bfcfabce27e92d836217c682..f437ebb5de3104dd42f23da3afd82f0ddff234da 100644 (file)
@@ -5552,8 +5552,9 @@ your notifications on <a href="{link isHtmlEmail=true}{/link}">{PAGE_TITLE|phras
                <item name="wcf.upload.error.reachedRemainingLimit"><![CDATA[You have selected too many files. You can only upload {#$maxFiles} more file{if $maxFiles != 1}s{/if}.]]></item>
                <item name="wcf.upload.error.uploadFailed"><![CDATA[An unknown error occurred during the upload.]]></item>
 
-               <item name="wcf.upload.error.invalidFileExtension"><![CDATA[The file type of “{$filename}” is not permitted.]]></item>
+               <item name="wcf.upload.error.fileExtensionNotPermitted"><![CDATA[The file type of “{$filename}” is not permitted.]]></item>
                <item name="wcf.upload.error.fileSizeTooLarge"><![CDATA[The file is too large.]]></item>
+               <item name="wcf.upload.error.maximumCountReached"><![CDATA[{plural value=$maximumCount 1='Only one file' other='Only up to # files'} may be uploaded.]]></item>
        </category>
 </import>
 <delete>
index 4b7f5867cc28ae8fd7be584793cf7b29e5f5e4d4..49feacfa9c3f16baecdc598914e9da92869f30ce 100644 (file)
@@ -633,7 +633,7 @@ CREATE TABLE wcf1_file_thumbnail (
        fileExtension VARCHAR(10) NOT NULL,
        width INT NOT NULL,
        height INT NOT NULL,
-       formatChecksum CHAR(12),
+       formatChecksum CHAR(12)
 );
 
 /* As the flood control table can be a high traffic table and as it is periodically emptied,