0?w($,--T):0,A--,10===O&&(A=1,S--),O}function z(){return O=T2||F(O)>3?"":" "}function q(e,t){for(;--t&&z()&&!(O<48||O>102||O>57&&O<65||O>70&&O<97););return N(e,M()+(t<6&&32==j()&&32==z()))}function W(e){for(;z();)switch(O){case e:return T;case 34:case 39:34!==e&&39!==e&&W(O);break;case 40:41===e&&W(e);break;case 92:z()}return T}function G(e,t){for(;z()&&e+O!==57&&(e+O!==84||47!==j()););return"/*"+N(t,T-1)+"*"+m(47===e?e:z())}function U(e){for(;!F(j());)z();return N(e,T)}var V="-ms-",X="-moz-",Y="-webkit-",K="comm",Z="rule",J="decl",Q="@keyframes";function ee(e,t){for(var r="",n=_(e),a=0;a6)switch(w(e,t+1)){case 109:if(45!==w(e,t+4))break;case 102:return v(e,/(.+:)(.+)-([^]+)/,"$1-webkit-$2-$3$1"+X+(108==w(e,t+3)?"$3":"$2-$3"))+e;case 115:return~b(e,"stretch")?re(v(e,"stretch","fill-available"),t)+e:e}break;case 4949:if(115!==w(e,t+1))break;case 6444:switch(w(e,x(e)-3-(~b(e,"!important")&&10))){case 107:return v(e,":",":"+Y)+e;case 101:return v(e,/(.+:)([^;!]+)(;|!.+)?/,"$1"+Y+(45===w(e,14)?"inline-":"")+"box$3$1"+Y+"$2$3$1"+V+"$2box$3")+e}break;case 5936:switch(w(e,t+11)){case 114:return Y+e+V+v(e,/[svh]\w+-[tblr]{2}/,"tb")+e;case 108:return Y+e+V+v(e,/[svh]\w+-[tblr]{2}/,"tb-rl")+e;case 45:return Y+e+V+v(e,/[svh]\w+-[tblr]{2}/,"lr")+e}return Y+e+V+e+e}return e}function ne(e){return D(ae("",null,null,null,[""],e=L(e),0,[0],e))}function ae(e,t,r,n,a,i,o,s,c){for(var l=0,u=0,f=o,d=0,p=0,h=0,g=1,y=1,w=1,k=0,_="",S=a,A=i,P=n,T=_;y;)switch(h=k,k=z()){case 40:if(108!=h&&58==T.charCodeAt(f-1)){-1!=b(T+=v(H(k),"&","&\f"),"&\f")&&(w=-1);break}case 34:case 39:case 91:T+=H(k);break;case 9:case 10:case 13:case 32:T+=B(h);break;case 92:T+=q(M()-1,7);continue;case 47:switch(j()){case 42:case 47:C(oe(G(z(),M()),t,r),c);break;default:T+="/"}break;case 123*g:s[l++]=x(T)*w;case 125*g:case 59:case 0:switch(k){case 0:case 125:y=0;case 59+u:p>0&&x(T)-f&&C(p>32?se(T+";",n,r,f-1):se(v(T," ","")+";",n,r,f-2),c);break;case 59:T+=";";default:if(C(P=ie(T,t,r,l,u,a,s,_,S=[],A=[],f),i),123===k)if(0===u)ae(T,t,P,P,S,i,f,s,A);else switch(d){case 100:case 109:case 115:ae(e,P,P,n&&C(ie(e,P,P,0,0,a,s,_,a,S=[],f),A),a,A,f,s,n?S:A);break;default:ae(T,P,P,P,[""],A,0,s,A)}}l=u=p=0,g=w=1,_=T="",f=o;break;case 58:f=1+x(T),p=h;default:if(g<1)if(123==k)--g;else if(125==k&&0==g++&&125==I())continue;switch(T+=m(k),k*g){case 38:w=u>0?1:(T+="\f",-1);break;case 44:s[l++]=(x(T)-1)*w,w=1;break;case 64:45===j()&&(T+=H(z())),d=j(),u=f=x(_=T+=U(M())),k++;break;case 45:45===h&&2==x(T)&&(g=0)}}return i}function ie(e,t,r,n,a,i,o,s,c,l,u){for(var f=a-1,d=0===a?i:[""],p=_(d),m=0,g=0,b=0;m0?d[w]+" "+x:v(x,/&\f/g,d[w])))&&(c[b++]=C);return E(e,t,r,0===a?Z:s,c,l,u)}function oe(e,t,r){return E(e,t,r,K,m(O),k(e,2,-2),0)}function se(e,t,r,n){return E(e,t,r,J,k(e,0,n),k(e,n+1,-1),n)}var ce=function(e,t,r){for(var n=0,a=0;n=a,a=j(),38===n&&12===a&&(t[r]=1),!F(a);)z();return N(e,T)},le=new WeakMap,ue=function(e){if("rule"===e.type&&e.parent&&!(e.length<1)){for(var t=e.value,r=e.parent,n=e.column===r.column&&e.line===r.line;"rule"!==r.type;)if(!(r=r.parent))return;if((1!==e.props.length||58===t.charCodeAt(0)||le.get(r))&&!n){le.set(e,!0);for(var a=[],i=function(e,t){return D(function(e,t){var r=-1,n=44;do{switch(F(n)){case 0:38===n&&12===j()&&(t[r]=1),e[r]+=ce(T-1,t,r);break;case 2:e[r]+=H(n);break;case 4:if(44===n){e[++r]=58===j()?"&\f":"",t[r]=e[r].length;break}default:e[r]+=m(n)}}while(n=z());return e}(L(e),t))}(t,a),o=r.props,s=0,c=0;s-1&&!e.return)switch(e.type){case J:e.return=re(e.value,e.length);break;case Q:return ee([R(e,{value:v(e.value,"@","@"+Y)})],n);case Z:if(e.length)return function(e,t){return e.map(t).join("")}(e.props,(function(t){switch(function(e,t){return(e=/(::plac\w+|:read-\w+)/.exec(e))?e[0]:e}(t)){case":read-only":case":read-write":return ee([R(e,{props:[v(t,/:(read-\w+)/,":-moz-$1")]})],n);case"::placeholder":return ee([R(e,{props:[v(t,/:(plac\w+)/,":-webkit-input-$1")]}),R(e,{props:[v(t,/:(plac\w+)/,":-moz-$1")]}),R(e,{props:[v(t,/:(plac\w+)/,V+"input-$1")]})],n)}return""}))}}],pe=function(e){var t=e.key;if("css"===t){var r=document.querySelectorAll("style[data-emotion]:not([data-s])");Array.prototype.forEach.call(r,(function(e){-1!==e.getAttribute("data-emotion").indexOf(" ")&&(document.head.appendChild(e),e.setAttribute("data-s",""))}))}var n,a,i=e.stylisPlugins||de,o={},s=[];n=e.container||document.head,Array.prototype.forEach.call(document.querySelectorAll('style[data-emotion^="'+t+' "]'),(function(e){for(var t=e.getAttribute("data-emotion").split(" "),r=1;r=4;++n,a-=4)t=1540483477*(65535&(t=255&e.charCodeAt(n)|(255&e.charCodeAt(++n))<<8|(255&e.charCodeAt(++n))<<16|(255&e.charCodeAt(++n))<<24))+(59797*(t>>>16)<<16),r=1540483477*(65535&(t^=t>>>24))+(59797*(t>>>16)<<16)^1540483477*(65535&r)+(59797*(r>>>16)<<16);switch(a){case 3:r^=(255&e.charCodeAt(n+2))<<16;case 2:r^=(255&e.charCodeAt(n+1))<<8;case 1:r=1540483477*(65535&(r^=255&e.charCodeAt(n)))+(59797*(r>>>16)<<16)}return(((r=1540483477*(65535&(r^=r>>>13))+(59797*(r>>>16)<<16))^r>>>15)>>>0).toString(36)},me={animationIterationCount:1,borderImageOutset:1,borderImageSlice:1,borderImageWidth:1,boxFlex:1,boxFlexGroup:1,boxOrdinalGroup:1,columnCount:1,columns:1,flex:1,flexGrow:1,flexPositive:1,flexShrink:1,flexNegative:1,flexOrder:1,gridRow:1,gridRowEnd:1,gridRowSpan:1,gridRowStart:1,gridColumn:1,gridColumnEnd:1,gridColumnSpan:1,gridColumnStart:1,msGridRow:1,msGridRowSpan:1,msGridColumn:1,msGridColumnSpan:1,fontWeight:1,lineHeight:1,opacity:1,order:1,orphans:1,tabSize:1,widows:1,zIndex:1,zoom:1,WebkitLineClamp:1,fillOpacity:1,floodOpacity:1,stopOpacity:1,strokeDasharray:1,strokeDashoffset:1,strokeMiterlimit:1,strokeOpacity:1,strokeWidth:1},ge=/[A-Z]|^ms/g,ye=/_EMO_([^_]+?)_([^]*?)_EMO_/g,ve=function(e){return 45===e.charCodeAt(1)},be=function(e){return null!=e&&"boolean"!=typeof e},we=u((function(e){return ve(e)?e:e.replace(ge,"-$&").toLowerCase()})),ke=function(e,t){switch(e){case"animation":case"animationName":if("string"==typeof t)return t.replace(ye,(function(e,t,r){return _e={name:t,styles:r,next:_e},t}))}return 1===me[e]||ve(e)||"number"!=typeof t||0===t?t:t+"px"};function xe(e,t,r){if(null==r)return"";if(void 0!==r.__emotion_styles)return r;switch(typeof r){case"boolean":return"";case"object":if(1===r.anim)return _e={name:r.name,styles:r.styles,next:_e},r.name;if(void 0!==r.styles){var n=r.next;if(void 0!==n)for(;void 0!==n;)_e={name:n.name,styles:n.styles,next:_e},n=n.next;return r.styles+";"}return function(e,t,r){var n="";if(Array.isArray(r))for(var a=0;a96?Ee:Re},ze=function(e,t,r){var n;if(t){var a=t.shouldForwardProp;n=e.__emotion_forwardProp&&a?function(t){return e.__emotion_forwardProp(t)&&a(t)}:a}return"function"!=typeof n&&r&&(n=e.__emotion_forwardProp),n},je=c.useInsertionEffect?c.useInsertionEffect:function(e){e()},Me=function(e){var t=e.cache,r=e.serialized,n=e.isStringTag;return $e(t,r,n),je((function(){return function(e,t,r){$e(e,t,r);var n=e.key+"-"+t.name;if(void 0===e.inserted[t.name]){var a=t;do{e.insert(t===a?"."+n:"",a,e.sheet,!0),a=a.next}while(void 0!==a)}}(t,r,n)})),null},Ne=function e(t,r){var n,a,i=t.__emotion_real===t,o=i&&t.__emotion_base||t;void 0!==r&&(n=r.label,a=r.target);var s=ze(t,r,i),u=s||Ie(o),f=!u("as");return function(){var d=arguments,p=i&&void 0!==t.__emotion_styles?t.__emotion_styles.slice(0):[];if(void 0!==n&&p.push("label:"+n+";"),null==d[0]||void 0===d[0].raw)p.push.apply(p,d);else{p.push(d[0][0]);for(var h=d.length,m=1;m{var e={184:(e,t)=>{var r;!function(){"use strict";var n={}.hasOwnProperty;function a(){for(var e=[],t=0;t{var t=e&&e.__esModule?()=>e.default:()=>e;return r.d(t,{a:t}),t},r.d=(e,t)=>{for(var n in t)r.o(t,n)&&!r.o(e,n)&&Object.defineProperty(e,n,{enumerable:!0,get:t[n]})},r.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t),(()=>{"use strict";const e=window.wp.blocks,t=JSON.parse('{"$schema":"https://schemas.wp.org/trunk/block.json","apiVersion":2,"name":"llms/certificate-title","title":"Certificate Title","category":"text","description":"Displays the title of a certificate.","textdomain":"lifterlms","attributes":{"textAlign":{"type":"string","default":"center"},"content":{"type":"string","source":"html","selector":"h1,h2,h3,h4,h5,h6","default":"","__experimentalRole":"content"},"level":{"type":"number","default":1},"placeholder":{"type":"string"},"fontFamily":{"type":"string","default":"default"}},"supports":{"align":["wide","full"],"anchor":true,"className":false,"color":{"link":true},"spacing":{"margin":true},"typography":{"fontSize":true,"lineHeight":true,"__experimentalFontStyle":true,"__experimentalFontFamily":true,"__experimentalFontWeight":true,"__experimentalLetterSpacing":true,"__experimentalTextTransform":true,"__experimentalDefaultControls":{"fontSize":true,"fontAppearance":true,"textTransform":true}},"__experimentalSelector":"h1,h2,h3,h4,h5,h6","__unstablePasteTextInline":true,"__experimentalSlashInserter":true,"multiple":false,"llms_visibility":false},"editorScript":"file:./index.js"}'),n=window.wp.element,a=window.wp.i18n,i=window.wp.data,o=window.wp.editor,s=window.wp.components;window.wp.compose;const c=window.React;function l(){return l=Object.assign?Object.assign.bind():function(e){for(var t=1;t0?w($,--T):0,A--,10===O&&(A=1,S--),O}function j(){return O=T2||F(O)>3?"":" "}function q(e,t){for(;--t&&j()&&!(O<48||O>102||O>57&&O<65||O>70&&O<97););return N(e,M()+(t<6&&32==z()&&32==j()))}function W(e){for(;j();)switch(O){case e:return T;case 34:case 39:34!==e&&39!==e&&W(O);break;case 40:41===e&&W(e);break;case 92:j()}return T}function G(e,t){for(;j()&&e+O!==57&&(e+O!==84||47!==z()););return"/*"+N(t,T-1)+"*"+m(47===e?e:j())}function U(e){for(;!F(z());)j();return N(e,T)}var V="-ms-",X="-moz-",Y="-webkit-",K="comm",Z="rule",J="decl",Q="@keyframes";function ee(e,t){for(var r="",n=_(e),a=0;a6)switch(w(e,t+1)){case 109:if(45!==w(e,t+4))break;case 102:return v(e,/(.+:)(.+)-([^]+)/,"$1-webkit-$2-$3$1"+X+(108==w(e,t+3)?"$3":"$2-$3"))+e;case 115:return~b(e,"stretch")?re(v(e,"stretch","fill-available"),t)+e:e}break;case 4949:if(115!==w(e,t+1))break;case 6444:switch(w(e,x(e)-3-(~b(e,"!important")&&10))){case 107:return v(e,":",":"+Y)+e;case 101:return v(e,/(.+:)([^;!]+)(;|!.+)?/,"$1"+Y+(45===w(e,14)?"inline-":"")+"box$3$1"+Y+"$2$3$1"+V+"$2box$3")+e}break;case 5936:switch(w(e,t+11)){case 114:return Y+e+V+v(e,/[svh]\w+-[tblr]{2}/,"tb")+e;case 108:return Y+e+V+v(e,/[svh]\w+-[tblr]{2}/,"tb-rl")+e;case 45:return Y+e+V+v(e,/[svh]\w+-[tblr]{2}/,"lr")+e}return Y+e+V+e+e}return e}function ne(e){return D(ae("",null,null,null,[""],e=L(e),0,[0],e))}function ae(e,t,r,n,a,i,o,s,c){for(var l=0,u=0,d=o,f=0,p=0,h=0,g=1,y=1,w=1,k=0,_="",S=a,A=i,P=n,T=_;y;)switch(h=k,k=j()){case 40:if(108!=h&&58==T.charCodeAt(d-1)){-1!=b(T+=v(H(k),"&","&\f"),"&\f")&&(w=-1);break}case 34:case 39:case 91:T+=H(k);break;case 9:case 10:case 13:case 32:T+=B(h);break;case 92:T+=q(M()-1,7);continue;case 47:switch(z()){case 42:case 47:C(oe(G(j(),M()),t,r),c);break;default:T+="/"}break;case 123*g:s[l++]=x(T)*w;case 125*g:case 59:case 0:switch(k){case 0:case 125:y=0;case 59+u:p>0&&x(T)-d&&C(p>32?se(T+";",n,r,d-1):se(v(T," ","")+";",n,r,d-2),c);break;case 59:T+=";";default:if(C(P=ie(T,t,r,l,u,a,s,_,S=[],A=[],d),i),123===k)if(0===u)ae(T,t,P,P,S,i,d,s,A);else switch(f){case 100:case 109:case 115:ae(e,P,P,n&&C(ie(e,P,P,0,0,a,s,_,a,S=[],d),A),a,A,d,s,n?S:A);break;default:ae(T,P,P,P,[""],A,0,s,A)}}l=u=p=0,g=w=1,_=T="",d=o;break;case 58:d=1+x(T),p=h;default:if(g<1)if(123==k)--g;else if(125==k&&0==g++&&125==I())continue;switch(T+=m(k),k*g){case 38:w=u>0?1:(T+="\f",-1);break;case 44:s[l++]=(x(T)-1)*w,w=1;break;case 64:45===z()&&(T+=H(j())),f=z(),u=d=x(_=T+=U(M())),k++;break;case 45:45===h&&2==x(T)&&(g=0)}}return i}function ie(e,t,r,n,a,i,o,s,c,l,u){for(var d=a-1,f=0===a?i:[""],p=_(f),m=0,g=0,b=0;m0?f[w]+" "+x:v(x,/&\f/g,f[w])))&&(c[b++]=C);return E(e,t,r,0===a?Z:s,c,l,u)}function oe(e,t,r){return E(e,t,r,K,m(O),k(e,2,-2),0)}function se(e,t,r,n){return E(e,t,r,J,k(e,0,n),k(e,n+1,-1),n)}var ce=function(e,t,r){for(var n=0,a=0;n=a,a=z(),38===n&&12===a&&(t[r]=1),!F(a);)j();return N(e,T)},le=new WeakMap,ue=function(e){if("rule"===e.type&&e.parent&&!(e.length<1)){for(var t=e.value,r=e.parent,n=e.column===r.column&&e.line===r.line;"rule"!==r.type;)if(!(r=r.parent))return;if((1!==e.props.length||58===t.charCodeAt(0)||le.get(r))&&!n){le.set(e,!0);for(var a=[],i=function(e,t){return D(function(e,t){var r=-1,n=44;do{switch(F(n)){case 0:38===n&&12===z()&&(t[r]=1),e[r]+=ce(T-1,t,r);break;case 2:e[r]+=H(n);break;case 4:if(44===n){e[++r]=58===z()?"&\f":"",t[r]=e[r].length;break}default:e[r]+=m(n)}}while(n=j());return e}(L(e),t))}(t,a),o=r.props,s=0,c=0;s-1&&!e.return)switch(e.type){case J:e.return=re(e.value,e.length);break;case Q:return ee([R(e,{value:v(e.value,"@","@"+Y)})],n);case Z:if(e.length)return function(e,t){return e.map(t).join("")}(e.props,(function(t){switch(function(e,t){return(e=/(::plac\w+|:read-\w+)/.exec(e))?e[0]:e}(t)){case":read-only":case":read-write":return ee([R(e,{props:[v(t,/:(read-\w+)/,":-moz-$1")]})],n);case"::placeholder":return ee([R(e,{props:[v(t,/:(plac\w+)/,":-webkit-input-$1")]}),R(e,{props:[v(t,/:(plac\w+)/,":-moz-$1")]}),R(e,{props:[v(t,/:(plac\w+)/,V+"input-$1")]})],n)}return""}))}}];const pe=function(e){var t=e.key;if("css"===t){var r=document.querySelectorAll("style[data-emotion]:not([data-s])");Array.prototype.forEach.call(r,(function(e){-1!==e.getAttribute("data-emotion").indexOf(" ")&&(document.head.appendChild(e),e.setAttribute("data-s",""))}))}var n,a,i=e.stylisPlugins||fe,o={},s=[];n=e.container||document.head,Array.prototype.forEach.call(document.querySelectorAll('style[data-emotion^="'+t+' "]'),(function(e){for(var t=e.getAttribute("data-emotion").split(" "),r=1;r=4;++n,a-=4)t=1540483477*(65535&(t=255&e.charCodeAt(n)|(255&e.charCodeAt(++n))<<8|(255&e.charCodeAt(++n))<<16|(255&e.charCodeAt(++n))<<24))+(59797*(t>>>16)<<16),r=1540483477*(65535&(t^=t>>>24))+(59797*(t>>>16)<<16)^1540483477*(65535&r)+(59797*(r>>>16)<<16);switch(a){case 3:r^=(255&e.charCodeAt(n+2))<<16;case 2:r^=(255&e.charCodeAt(n+1))<<8;case 1:r=1540483477*(65535&(r^=255&e.charCodeAt(n)))+(59797*(r>>>16)<<16)}return(((r=1540483477*(65535&(r^=r>>>13))+(59797*(r>>>16)<<16))^r>>>15)>>>0).toString(36)},me={animationIterationCount:1,borderImageOutset:1,borderImageSlice:1,borderImageWidth:1,boxFlex:1,boxFlexGroup:1,boxOrdinalGroup:1,columnCount:1,columns:1,flex:1,flexGrow:1,flexPositive:1,flexShrink:1,flexNegative:1,flexOrder:1,gridRow:1,gridRowEnd:1,gridRowSpan:1,gridRowStart:1,gridColumn:1,gridColumnEnd:1,gridColumnSpan:1,gridColumnStart:1,msGridRow:1,msGridRowSpan:1,msGridColumn:1,msGridColumnSpan:1,fontWeight:1,lineHeight:1,opacity:1,order:1,orphans:1,tabSize:1,widows:1,zIndex:1,zoom:1,WebkitLineClamp:1,fillOpacity:1,floodOpacity:1,stopOpacity:1,strokeDasharray:1,strokeDashoffset:1,strokeMiterlimit:1,strokeOpacity:1,strokeWidth:1};var ge=/[A-Z]|^ms/g,ye=/_EMO_([^_]+?)_([^]*?)_EMO_/g,ve=function(e){return 45===e.charCodeAt(1)},be=function(e){return null!=e&&"boolean"!=typeof e},we=u((function(e){return ve(e)?e:e.replace(ge,"-$&").toLowerCase()})),ke=function(e,t){switch(e){case"animation":case"animationName":if("string"==typeof t)return t.replace(ye,(function(e,t,r){return _e={name:t,styles:r,next:_e},t}))}return 1===me[e]||ve(e)||"number"!=typeof t||0===t?t:t+"px"};function xe(e,t,r){if(null==r)return"";if(void 0!==r.__emotion_styles)return r;switch(typeof r){case"boolean":return"";case"object":if(1===r.anim)return _e={name:r.name,styles:r.styles,next:_e},r.name;if(void 0!==r.styles){var n=r.next;if(void 0!==n)for(;void 0!==n;)_e={name:n.name,styles:n.styles,next:_e},n=n.next;return r.styles+";"}return function(e,t,r){var n="";if(Array.isArray(r))for(var a=0;a96?Ee:Re},je=function(e,t,r){var n;if(t){var a=t.shouldForwardProp;n=e.__emotion_forwardProp&&a?function(t){return e.__emotion_forwardProp(t)&&a(t)}:a}return"function"!=typeof n&&r&&(n=e.__emotion_forwardProp),n},ze=c.useInsertionEffect?c.useInsertionEffect:function(e){e()},Me=function(e){var t=e.cache,r=e.serialized,n=e.isStringTag;return $e(t,r,n),ze((function(){return function(e,t,r){$e(e,t,r);var n=e.key+"-"+t.name;if(void 0===e.inserted[t.name]){var a=t;do{e.insert(t===a?"."+n:"",a,e.sheet,!0),a=a.next}while(void 0!==a)}}(t,r,n)})),null};var Ne=function e(t,r){var n,a,i=t.__emotion_real===t,o=i&&t.__emotion_base||t;void 0!==r&&(n=r.label,a=r.target);var s=je(t,r,i),u=s||Ie(o),d=!u("as");return function(){var f=arguments,p=i&&void 0!==t.__emotion_styles?t.__emotion_styles.slice(0):[];if(void 0!==n&&p.push("label:"+n+";"),null==f[0]||void 0===f[0].raw)p.push.apply(p,f);else{p.push(f[0][0]);for(var h=f.length,m=1;m{const{content:t}=e;return void 0!==t&&function(e){let t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:null;if(!t){const{getCurrentPostType:e}=(0,i.select)(o.store);t=e()}const{editPost:r}=(0,i.dispatch)(o.store),n={};"llms_certificate"===t?n.certificate_title=e:"llms_my_certificate"===t&&(n.title=e),r(n)}(t),s(e)},mergeBlocks:c,onReplace:l,style:u,clientId:f}))},save:function(e){let{attributes:t}=e;const{textAlign:r,content:a,level:i}=t,o="h"+i,s=Le()({[`has-text-align-${r}`]:r});return(0,n.createElement)(o,De.useBlockProps.save({className:s}),(0,n.createElement)(De.RichText.Content,{value:a}))}})}()}();
\ No newline at end of file
+`,window.wp.coreData;var Fe=r(184),Le=r.n(Fe);const De=window.wp.blockEditor,{name:He}=t;(0,e.registerBlockType)(He,{icon:{foreground:"#466dd8",src:"awards"},edit:function(t){let{attributes:r,setAttributes:s,mergeBlocks:c,onReplace:l,style:u,clientId:d}=t;const{getBlockType:f}=(0,i.useSelect)(e.store),{getEditedPostAttribute:p,getCurrentPostType:h}=(0,i.useSelect)(o.store),{edit:m}=f("core/heading"),g="llms_certificate"===h()?"certificate_title":"title";return r.placeholder=r.placeholder||(0,a.__)("Certificate of Achievement","lifterlms"),r.content=r.content||p(g),(0,n.createElement)(n.Fragment,null,(0,n.createElement)(m,{attributes:r,setAttributes:e=>{const{content:t}=e;return void 0!==t&&function(e){let t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:null;if(!t){const{getCurrentPostType:e}=(0,i.select)(o.store);t=e()}const{editPost:r}=(0,i.dispatch)(o.store),n={};"llms_certificate"===t?n.certificate_title=e:"llms_my_certificate"===t&&(n.title=e),r(n)}(t),s(e)},mergeBlocks:c,onReplace:l,style:u,clientId:d}))},save:function(e){let{attributes:t}=e;const{textAlign:r,content:a,level:i}=t,o="h"+i,s=Le()({[`has-text-align-${r}`]:r});return(0,n.createElement)(o,De.useBlockProps.save({className:s}),(0,n.createElement)(De.RichText.Content,{value:a}))}})})()})();
\ No newline at end of file
diff --git a/class-lifterlms.php b/class-lifterlms.php
index 61a16d07b2..d3d158095c 100644
--- a/class-lifterlms.php
+++ b/class-lifterlms.php
@@ -34,7 +34,7 @@ final class LifterLMS {
*
* @var string
*/
- public $version = '6.9.0';
+ public $version = '6.10.0';
/**
* LLMS_Assets instance
diff --git a/composer.json b/composer.json
deleted file mode 100644
index b250378375..0000000000
--- a/composer.json
+++ /dev/null
@@ -1,143 +0,0 @@
-{
- "name": "gocodebox/lifterlms",
- "description": "LifterLMS, the #1 WordPress LMS solution, makes it easy to create, sell, and protect engaging online courses.",
- "keywords": [
- "WordPress",
- "LMS"
- ],
- "homepage": "https://lifterlms.com",
- "license": "GPL-3.0+",
- "authors": [
- {
- "name": "LifterLMS",
- "email": "help@lifterlms.com",
- "homepage": "https://lifterlms.com"
- }
- ],
- "type": "wordpress-plugin",
- "support": {
- "forum": "https://wordpress.org/support/plugin/lifterlms",
- "issues": "https://github.com/gocodebox/lifterlms/issues",
- "source": "https://github.com/gocodebox/lifterlms"
- },
- "autoload": {
- "psr-4": {
- "LLMS\\": "includes"
- }
- },
- "require": {
- "php": ">=7.4",
- "composer/installers": "~1.9.0",
- "deliciousbrains/wp-background-processing": "1.0.2",
- "lifterlms/lifterlms-blocks": "2.4.3",
- "lifterlms/lifterlms-cli": "0.0.3",
- "lifterlms/lifterlms-helper": "3.4.2",
- "lifterlms/lifterlms-rest": "1.0.0-beta.25",
- "woocommerce/action-scheduler": "3.4.2"
- },
- "require-dev": {
- "lifterlms/lifterlms-tests": "^3.3.1",
- "lifterlms/lifterlms-cs": "dev-trunk"
- },
- "archive": {
- "exclude": [
- ".*",
- "*.lock",
- "*.xml",
- "*.xml.dist",
- "*.config.js",
-
- "CHANGELOG.md",
- "composer.json",
- "docker-compose.yml",
- "lerna.json",
- "package.json",
- "package-lock.json",
- "README.md",
-
- "/assets/scss",
-
- "_private",
- "dist",
- "docs",
- "gulpfile.js",
- "node_modules",
- "packages",
- "src",
- "tests",
- "tmp",
- "wordpress",
- "!/vendor",
-
- "!/libraries",
- "/libraries/README.md",
- "/libraries/**/composer.*",
- "/libraries/**/i18n",
-
- "/vendor/bin",
- "/vendor/**/**/composer.*",
- "/vendor/**/**/*.md",
- "/vendor/**/**/.*",
- "/vendor/composer/installers",
- "/vendor/composer/lifters",
-
- "!/assets/maps/js/vendor",
- "!/assets/vendor",
- "!/assets/js/vendor",
- "!/assets/js/builder/vendor"
- ]
- },
- "scripts": {
- "check-cs": "\"vendor/bin/phpcs\" --colors",
- "check-cs-errors": "\"vendor/bin/phpcs\" --colors --error-severity=1 --warning-severity=6",
- "config-cs": [
- "\"vendor/bin/phpcs\" --config-set installed_paths ../../../vendor/wp-coding-standards/wpcs,../../../vendor/lifterlms/lifterlms-cs,../../../vendor/phpcompatibility/php-compatibility,../../../vendor/phpcompatibility/phpcompatibility-paragonie,../../../vendor/phpcompatibility/phpcompatibility-wp",
- "\"vendor/bin/phpcs\" --config-set default_standard 'LifterLMS Core'",
- "\"vendor/bin/phpcs\" --config-set ignore_warnings_on_exit 1"
- ],
- "env": "\"vendor/bin/llms-env\"",
- "env:setup": "./tests/bin/setup-e2e.sh",
- "fix-cs": "\"vendor/bin/phpcbf\"",
- "post-install-cmd": "@post-update-install-cmd",
- "post-update-cmd": "@post-update-install-cmd",
- "post-update-install-cmd": [
- "@config-cs",
- "rm -rf ./wp-content/",
- "rm composer.lock"
- ],
- "tests-remove": "\"vendor/bin/llms-tests\" teardown ${TESTS_DB_NAME:-llms_tests} ${TESTS_DB_USER:-root} \"${TESTS_DB_PASS-password}\" ${TESTS_DB_HOST:-127.0.0.1}",
- "tests-install": "\"vendor/bin/llms-tests\" install ${TESTS_DB_NAME:-llms_tests} ${TESTS_DB_USER:-root} \"${TESTS_DB_PASS-password}\" ${TESTS_DB_HOST:-127.0.0.1} ${WP_VERSION:-latest} false \"${WP_TESTS_VERSION:-false}\"",
- "tests-reinstall": [
- "@tests-remove",
- "@tests-install"
- ],
- "tests": [
- "Composer\\Config::disableProcessTimeout",
- "\"vendor/bin/phpunit\""
- ],
- "tests-run": [
- "Composer\\Config::disableProcessTimeout",
- "\"vendor/bin/phpunit\""
- ],
- "install-php8": "composer install --ignore-platform-reqs"
- },
- "extra": {
- "installer-paths": {
- "libraries/{$name}": [
- "lifterlms/lifterlms-blocks",
- "lifterlms/lifterlms-cli",
- "lifterlms/lifterlms-helper",
- "lifterlms/lifterlms-rest"
- ],
- "vendor/{$vendor}/{$name}": [
- "type:wordpress-plugin"
- ]
- }
- },
- "config": {
- "allow-plugins": {
- "dealerdirect/phpcodesniffer-composer-installer": true,
- "composer/installers": true
- }
- }
-}
diff --git a/docker-compose.yml b/docker-compose.yml
deleted file mode 100644
index b773b52d93..0000000000
--- a/docker-compose.yml
+++ /dev/null
@@ -1,5 +0,0 @@
-version: '3.1'
-services:
- wordpress:
- volumes:
- - ./:/var/www/html/wp-content/plugins/lifterlms:rw
diff --git a/docs/coding-standards.md b/docs/coding-standards.md
deleted file mode 100644
index e25d33af7f..0000000000
--- a/docs/coding-standards.md
+++ /dev/null
@@ -1,141 +0,0 @@
-LifterLMS Coding Standards
-==========================
-
-The purpose of the LifterLMS Coding Standards is to create a baseline for collaboration and review within the open source LifterLMS codebase, project, and community.
-
-The WordPress community has developed coding standards and documented them in the [WordPress codex](https://developer.wordpress.org/coding-standards/wordpress-coding-standards/). Wherever possible, the LifterLMS Coding Standards aim to obey these coding standards.
-
-## Naming Conventions
-
-### camelCase should not be used.
-
-LifterLMS avoids `camelCase` for class names, class methods, functions, and variables. Words should instead be separated by underscores.
-
-### Class Names
-
-Class names should use capitalized words separated by underscores.
-LifterLMS core class names should be prefixed with `LLMS_`.
-
-
-```php
-class LLMS_Student extends LLMS_Abstract_User_Data { [...] }
-class LLMS_Data { [...] }
-```
-
-LifterLMS add-on class names should be prefixed with `LLMS_` as well as an additional add-on prefix.
-
-```php
-class LLMS_AQ_Question_Types { [...] }
-class LLMS_SL_Story extends LLMS_Abstract_Database_Store { [...] }
-```
-
-### Trait Names
-
-Trait names should use capitalized words separated by underscores.
-LifterLMS core trait names should be prefixed with `LLMS_Trait`.
-
-```php
-trait LLMS_Trait_Singleton { [...] }
-```
-
-### Constants
-
-Constants should be in all upper-case with underscores separating words.
-LifterLMS core constants should be prefixed with `LLMS_`.
-
-```php
-define( 'LLMS_PLUGIN_FILE', __FILE__ );
-```
-
-LifterLMS add-on class names should be prefixed with `LLMS_` as well as an additional add-on prefix.
-
-```php
-define( 'LLMS_FORMIDABLE_FORMS_PLUGIN_FILE', __FILE__ );
-```
-
-### File names
-
-Files should be named descriptively using lower case letters. Hyphens should be used to separate words.
-
-```
-my-plugin-file.php
-```
-
-Class file names should be based on the class name with `class-` prepended and the underscores in the class name replaced with hyphens, for example `LLMS_Data` becomes:
-
-```
-class-llms-data.php
-```
-
-Files containing model classes should prepend `model-` instead of `class-`. For example the `LLMS_Student` model class becomes:
-
-```
-model-llms-student.php
-```
-
-Trait file names should be based on the trait name with underscores replaced by hyphens and the file stored in the
-`includes/traits` directory. For example `LLMS_Trait_Singleton` becomes:
-
-```
-includes/traits/llms-trait-singleton.php
-```
-
-### Functions & Variables
-
-Lowercase letters should be used for function names and variables. Separate words with underscores.
-LifterLMS core functions should be prepended with the prefix `llms_`.
-
-```php
-llms_current_time( $type, $gmt = 0 ) { [...] }
-```
-
-LifterLMS add-on function names should be prefixed with `llms_` as well as an additional add-on prefix.
-
-```php
-llms_ck_consent_form_field() { [...] }
-```
-
-### Hooks: Actions & Filters
-
-Lowercase letters should be used for hook names. Separate words with underscores.
-LifterLMS core hooks should be prepended with the prefix `llms_`.
-
-```php
-do_action( 'llms_user_enrolled_in_course', [...] );
-apply_filters( 'llms_get_enrollment_status', [...] );
-```
-
-LifterLMS add-on hook names should be prefixed with `llms_` as well as an additional add-on prefix.
-
-```php
-do_action( 'llms_pa_post_created_from_automation', [...] );
-apply_filters( 'llms_sl_story_can_user_manage', [...] );
-```
-
-When actions are set to run before and after items (templates, as an example) it is acceptable to use additional prefixes `before_` and `after_` prior to the `llms_` prefix.
-
-There are a number of legacy hooks which use the prefix `lifterlms_` instead of `llms_`. These are retained for backwards compatibility but should not be used as an example of an acceptable naming convention for new code.
-
-### CSS Classes and IDs
-
-Class names and IDs should be lowercase and prefixed with `llms-`.
-
-Words should be separated with hyphens (AKA "kebab case").
-
-```html
-
-```
-
-### Form Element `name` attributes
-
-The `name` attribute of HTML form elements should be prefixed with `llms_`.
-
-Lowercase letters should be used and words should be separated by underscores.
-
-```html
-
-```
-
-
diff --git a/docs/contributing.md b/docs/contributing.md
deleted file mode 100644
index aca594f40c..0000000000
--- a/docs/contributing.md
+++ /dev/null
@@ -1,4 +0,0 @@
-Contributor Guidelines
-----------------------
-
-See contributing guidelines at https://github.com/gocodebox/lifterlms/blob/trunk/.github/CONTRIBUTING.md
diff --git a/docs/documentation-standards.md b/docs/documentation-standards.md
deleted file mode 100644
index d9ff0eef6c..0000000000
--- a/docs/documentation-standards.md
+++ /dev/null
@@ -1,395 +0,0 @@
-LifterLMS Inline Documentation Standards
-========================================
-
-The LifterLMS documentation standard is heavily inspired by the [WordPress core's documentation standards][wp-core-docs]. We have made customizations to these standards in areas where it aids our core team's development and release workflows. By using the WordPress core documentation standard as a starting point any contributor already familiar with the WordPress core should be able to quickly add inline documentation to LifterLMS without the need to study our standards at length.
-
-## What should be documented
-
-The following elements should be documented using formatted documentation blocks (DocBlocks):
-
-+ Functions
-+ Classes
-+ Class methods
-+ Class members (including properties and constants)
-+ Requires and includes
-+ Hooks (actions and filters)
-+ File headers
-
-## DocBlock Formatting Guidelines
-
-Inline documentation in the LifterLMS code base is automatically parsed and output to the code reference [developer.lifterlms.com][llms-dev]. Adhering to these guidelines is essential to ensure optimum readability via the code reference.
-
-
-### Spacing
-
-DocBlocks should directly precede the element (hook, function, method, class, etc...). There should not be any opening/closing tags, white space, or anything else between the DocBlock and the declarations. This will ensure the parser can correctly associate the DocBlock with it's element.
-
-
-### Summary
-
-A short piece of text, usually one line, providing the basic function of the associated element. A good summary concisely describes what the element does and should not attempt to describe why the element exists.
-
-HTML may not be used in the summary. For example, if the function outputs an ` ` tag, the summary should read ```Outputs an image tag.``` instead of ```Outputs an ` ` tag.```.
-
-
-### Description
-
-An optional longer piece of text providing more details on the associated element’s function.
-
-HTML may not be used in the summary but markdown can be used to format a complicated description.
-
-**1. Lists**
-
-Use a hyphen (`-`) to create an unordered list, with a blank line before and after.
-
-```
- * Description which includes an unordered list:
- *
- * - This is item 1.
- * - This is item 2.
- * - This is item 3.
- *
- * The description continues on ...
-```
-
-Use numbers to create an ordered list, with a blank line before and after.
-
-```
- * Description which includes an ordered list:
- *
- * 1. This is item 1.
- * 2. This is item 2.
- * 3. This is item 3.
- *
- * The description continues on ...
-```
-
-**2. Code Samples**
-
-A code sample may be created by indenting every line of the code by 4 spaces, with a blank line before and after. Blank lines in code samples also need to be indented by four spaces. Note that examples added in this way will be output in `` tags and are not syntax-highlighted in the code reference.
-
-```
- * Description including a code sample:
- *
- * $status = array(
- * 'draft' => __( 'Draft' ),
- * 'pending' => __( 'Pending Review' ),
- * 'private' => __( 'Private' ),
- * 'publish' => __( 'Published' )
- * );
- *
- * The description continues on ...
-```
-
-**3. Links**
-
-A link in the form of a URL, such as related GitHub issue or other documentation, should be added in the appropriate place in the DocBlock using the `@link` tag.
-
-```
- * Description text.
- *
- * @link https://github.com/gocodebox/lifterlms/issues/1234567890
-```
-
-### Changelogs
-
-Whenever any code is changed within an element, a `@since`, `@version`, or `@deprecated` tag should be added to the element to document the change(s) which have been made.
-
-No HTML should be used in the descriptions for these tags, though limited Markdown can be used as necessary, such as for adding backticks around variables, e.g. `$variable`.
-
-All descriptions for any of these tags should be a full sentence ending with a full stop (a period, for example).
-
-#### Changes Warranting a Changelog Entry
-
-Most code changes warrant a changelog entry to be recorded for the element but there are some exceptions.
-
-+ **Classes**: Any breaking changes, deprecations, or the introduction of new class elements (elements which do not have their own changelog, such as class properties) require an accompanying `@since` tag entry. Changes to a class method should be recorded on the method's changelog, not on the class changelog.
-+ **Functions and class methods**: Any change made requires an accompanying `@since` tag entry
-
-Changes which do not affect the functionality or execution of the element *should not* be recorded on the element's changelog. For example, a coding standards change such as alignment or spacing should not be recorded.
-
-#### Recording the Version Number
-
-Versions should be expressed in the 3-digit `x.x.x` style.
-
-```
- * @since 3.29.0
-```
-
-When any change has been made to the element an additional `@since` tag can be added with a short description of the changes which were made.
-
-```
- * @since 3.3.0
- * @since 3.5.0 Added optional 3rd argument.
-```
-
-#### Deprecations
-
-When an element is marked for deprecation this should be recorded at the end of the changelog with an `@deprecated` tag.
-
-A short description may be added to provide additional information about the deprecation. If a replacement function has been added in it's place, note as much with an `@see` tag.
-
-```
- * @since 3.3.0
- * @since 3.5.0 Added optional 3rd argument.
- * @deprecated 3.10.0 Use `llms_new_function_name()` instead.
- *
- * @see llms_new_function_name()
-```
-
-When adding documentation on an existing element which does not yet have a changelog (common in code added prior to the creation and enforcement of these standards) if it is impossible to determine when the element was added the version may be expressed with `Unknown` instead of the `x.x.x` version number.
-
-#### File Headers
-
-Whenever an element within a file is updated, the `@version` tag in the header should be updated to the current version of the codebase.
-
-#### Tag alignment and order
-
-All changelog tags, `@since`, `@version`, and `@deprecated` should be grouped together with a space before the first `@since` tag and after the last tag in the group.
-
-```
- * @since 3.3.0
- * @since 3.5.0 Changelog entry description.
- * @deprecated 3.10.0 Use `llms_new_function_name()` instead.
-```
-
-When multiple lines are required for a single entry, subsequent lines should be indented to match the starting point of the description.
-
-```
- * @since 3.3.0
- * @since 3.5.0 Changelog entry description.
- A second entry aligned to with the first entry.
-```
-
-Multiple logs with version numbers of differing lengths should not be aligned to one another.
-
-```
- * @since 3.3.0
- * @since 3.25.0 Changelog entry description.
- * @since 4.0.0 This entry should not be aligned with the 3.25.0 entry above it.
-```
-
-#### Using Placeholders
-
-When contributing code we recommend using the placeholder `[version]` in favor of trying to guess what version the element will be released with.
-
-Our release workflow automatically replaces with `@since`, `@version`, and `@deprecated` followed by `[version]` with the actual version of the release being packaged.
-
-For a new element:
-
-```
- * @since [version]
-```
-
-When updating an existing element:
-
-```
- * @since 3.5.0
- * @since [version] Updated element.
-```
-
-
-### Additional Tags
-
-#### 1. Parameters and Returns
-
-Functions and methods should define all parameter arguments and returns with the `@param` and `@return` tags.
-
-No HTML should be used in the descriptions for these tags, though limited Markdown can be used as necessary, such as for adding backticks around variables, e.g. `$variable`.
-
-All descriptions for any of these tags should be a full sentence ending with a full stop (a period, for example).
-
-```
- * @param string $var1 Description of the argument.
- * @param bool $var2 Description of the argument.
- * @return string
- */
-function my_function( $var1, $var2 = false ) {
- ...
- return $var1;
-}
-```
-
-Parameters that are arrays should be documented using WordPress’ flavor of hash notation style, each array value beginning with the `@type` tag, and and describing the value as follows:
-
-```
- * @type type $key Description. Default 'value'. Accepts 'value', 'value'.
- * (aligned with Description, if wraps to a new line)
-```
-
-A full array parameter would look like this:
-
-```
- * @param array $args {
- * Optional. An array of arguments.
- *
- * @type type $key Description. Default 'value'. Accepts 'value', 'value'.
- * (aligned with Description, if wraps to a new line)
- * @type type $key Description.
- * }
-```
-
-#### 2. Types
-
-Variables, constants, and class members should use the `@var` tag to describe the member's type.
-
-```
- * @var string
- */
-public $var = 'text';
-```
-
-#### 3. Relations and References
-
-Use `@see` to perform automatic links to other areas of the codebase. For example `{@see 'is_lifterlms'}` to link to the filter `is_lifterlms`.
-
-
-#### 4. Thrown Exceptions
-
-A function or method which throws an exception should document the thrown exception using an `@throws` tag.
-
-When present, the `@throws` tag should be added to the end of the docblock below the `@return` tag. An empty line should separate the `@return` and `@throws` tag.
-
-```
- * @return string
- *
- * @throws Exception A description of the raised exception.
- */
-```
-
-## DocBlock Examples
-
-
-### Functions and Class Methods
-
-Functions and class methods should be formatted as follows:
-
-+ Summary
-+ Description (optional)
-+ Changelog
-+ Links and References (where appropriate)
-+ Parameters
-+ Return
-
-```
-/**
- * Summary.
- *
- * Description.
- *
- * @since x.x.x
- * @since x.x.x Description of function/method changes.
- *
- * @see Function/method/class relied on
- * @link URL
- *
- * @param type $var Description.
- * @param type $var Optional. Description. Default.
- * @return type Description.
- */
-```
-
-
-### Classes
-
-Class DocBlocks should be formatted as follows:
-
-+ Summary
-+ Description (Optional)
-+ Links and References (as an example use `@see` to reference a super class when documenting a sub class)
-+ Changelog
-
-```
-/**
- * Summary.
- *
- * Description.
- *
- * @see Super_Class
- *
- * @since x.x.x
- * @since x.x.x Description of class changes.
- */
-```
-
-
-### Class Members
-
-Class properties and constants should be formatted as follows:
-
-+ Summary
-+ Changelog
-+ Type
-
-```
-/**
- * Summary.
- *
- * @since x.x.x
- * @since x.x.x Description of member changes.
- * @var type Optional description.
- */
-```
-
-
-### Hooks (Actions and Filters)
-
-Both action and filter hooks should be documented on the line immediately preceding the call to `do_action()` or `do_action_ref_array()`, `apply_filters()`, or `apply_filters_ref_array()`, and formatted as follows:
-
-+ Summary
-+ Description (Optional)
-+ Changelog
-+ Parameters
-
-Note that `@return` is not used for hook documentation, because action hooks return nothing, and filter hooks always return their first parameter.
-
-```
-/**
- * Summary.
- *
- * Description.
- *
- * @since x.x.x
- * @since x.x.x Description of hook changes.
- *
- * @param type $var Description.
- * @param array $args {
- * Short description about this hash.
- *
- * @type type $var Description.
- * @type type $var Description.
- * }
- * @param type $var Description.
- */
-```
-
-
-### File Headers
-
-The file header DocBlock is used to give an overview of what is contained in the file and should be formatted as follows:
-
-+ Summary
-+ Description (optional)
-+ Links and references
-+ Package
-+ Changelog
-
-```
-/**
- * Summary (no period for file headers)
- *
- * Description. (use period)
- *
- * @link URL
- *
- * @package LifterLMS/SecondaryPackage/TertiaryPackage
- *
- * @since x.x.x
- * @since x.x.x Description of file changes.
- * @version x.x.x
- */
-```
-
-
-[llms-dev]: https://developer.lifterlms.com/reference/
-[wp-core-docs]: https://developer.wordpress.org/coding-standards/inline-documentation-standards/
diff --git a/docs/e2e-tests-real.md b/docs/e2e-tests-real.md
deleted file mode 100644
index d4b4c2ba65..0000000000
--- a/docs/e2e-tests-real.md
+++ /dev/null
@@ -1,72 +0,0 @@
-Running E2E (End to End) Tests Against a Real Website
-=====================================================
-
-_The core E2E test suite is primarily designed to be run locally against managed Docker containers. However, it is possible to run the test suite against any WordPress website with a publicly accessible URL by following this guide._
-
-_To run tests locally against managed Docker containers, see the [E2E Testing README](../tests/e2e/README.md)._
-
-**NOTE: This is an experimental process! Proceed with caution. We are developing this process for internal use and thought it might be useful to some other folks.**
-
-**Another note: This process will import courses, create fake users, and add other data to your website and there is no cleanup proccess. If you choose to use this against a live production site that means that the database will have a bunch of fake test data added to it. So don't run this against a real production website. Use a staging website instead!**
-
-## Prerequisites
-
-+ Ability to use a terminal
-+ git
-+ node.js
-+ npm
-
-
-## Setup your local environment
-
-+ Install the LifterLMS repo: `git clone https://github.com/gocodebox/lifterlms`
-+ Move into the cloned directory: `cd liferlms`
-+ Install node packages: `npm ci`
-+ Create a new file in the created directory named `.llmsenv`.
-+ Use your favorite text editor to edit the file and add the following to the file (replacing the example data with your site's information):
-
-```
-WP_BASE_URL=https://yourwebsiteurl.tld
-WP_USERNAME=adminusername
-WP_PASSWORD=adminpassword
-```
-
-**This will store a password in a PLAIN TEXT which we know is wrong. Our internal use case uses this process with temporary sites which are regularly destroyed so the danger is acceptable to our use case. If you decide to use this process on a real website with real user information you have been warned that storing your production site's WP admin password in a plain text file in order to use this process is a bad idea. We recommend instead using environment variables to pass your password to the script later and removing the WP_PASSWORD from the `.llmsenv` file.**
-
-+ Save the file
-
-
-## Setup your production site
-
-+ Install and activate the LifterLMS plugin on your site
-
-
-## Run the tests
-
-There are two ways to run the E2E tests:
-
-### Headless mode
-
-Runs the tests and shows you the results.
-
-If errors are encountered, a screenshot of the page will be taken and saved in the `tmp/e2e-screenshots/` directory so you can see what the page looked like when things went sour.
-
-Error logs will be output in your terminal to review.
-
-Run headless tests by executing `npm run tests` in your terminal.
-
-
-### Interactive mode
-
-Launches an automated Chromium browser and runs the tests in "slow motion" so you can watch as the tests run.
-
-No screenshots are takeng in interactive mode.
-
-Error logs are output to the terminal for review.
-
-Run interactive tests by executing `npm run tests:dev` in your terminal.
-
-
-### Using environment variables
-
-If you don't want to store you admin password in a plaintext file you can define the WP_PASSWORD variable at runtime `WP_PASSWORD=yourpassword npm run tests`
diff --git a/docs/installing.md b/docs/installing.md
deleted file mode 100644
index 6bb012f3c3..0000000000
--- a/docs/installing.md
+++ /dev/null
@@ -1,80 +0,0 @@
-Installing for Development
-==========================
-
-## Requirements
-
-In order to build and develop LifterLMS locally, you'll need the following:
-
-+ PHP
-+ MySQL / MariaDB
-+ [Composer](https://getcomposer.org/download/)
-+ [Node.js](https://nodejs.org/en/download/)
-+ [npm](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm)
-
-
-## Building LifterLMS
-
-### 1. Clone source from GitHub
-
-```sh
-$ git clone https://github.com/gocodebox/lifterlms
-$ cd lifterlms
-```
-
-If you're planning to contribute code, you should fork this repository and clone your fork instead.
-
-
-### 2. Install composer dependencies:
-
-```sh
-$ composer install
-```
-
-### 3. Install npm dependencies:
-
-```sh
-$ npm install --global gulp
-$ npm install
-```
-
-### 4. Build static assets
-
-```sh
-$ gulp build
-```
-
-The `lifterlms` directory is now an installable plugin that can be moved into your local server's `wp-content/plugins` directory.
-
-
-## Running PHPCS
-
-When contributing you should ensure your contributions follow our [coding](./coding-standards.md) and [documentation](./documentation-standards.md) standards.
-
-To check for errors and warnings in your code, run PHPCS:
-
-```sh
-$ composer run check-cs
-```
-
-To check for errors only:
-
-```sh
-$ composer run check-cs-errors
-```
-
-These reports may include issues that can be automatically fixed using PHPCBF:
-
-```sh
-$ composer run fix-cs
-```
-
-## Running Test Suites
-
-New code should also strive to be covered by automated tests.
-
-LifterLMS has unit and integration tests via phpunit and End-to-End tests via Jest and Puppeteer.
-
-For guides on running and contributing tests, see the relevant guides:
-
-+ [phpunit](../tests/phpunit/README.md)
-+ [e2e](../tests/e2e/README.md)
diff --git a/docs/releases.md b/docs/releases.md
deleted file mode 100644
index 6520df1f77..0000000000
--- a/docs/releases.md
+++ /dev/null
@@ -1,62 +0,0 @@
-Releasing LifterLMS Builds
-==========================
-
-This document outlines the workflow used by LifterLMS core maintainers to build and publish LifterLMS releases.
-
-This document assumes you have already installed LifterLMS for development following the [Installing for Development guide](./installing.md).
-
-## 1. Build the Release
-
-Prepare the release: `npm run dev release prepare`:
-
-When running this command, the following happens:
-
-1. Determines the version number based on the significance values found in `.changelogs/` files. Unless `-F` is passed to the command to force a specific version number.
-2. Write the changelog entries to `CHANGELOG.md`.
-3. Updates version numbers of placeholder `[version]` tags, `package.json`, etc...
-4. Runs the release build command, `npm run build`.
-
-## 2. Run tests and coding standards checks
-
-1. Ensure phpunit tests pass: `composer run tests-run`.
-2. Ensure phpcs checks pass: `composer run check-cs-errors`.
-3. Ensure e2e tests pass: `npm run test`.
-4. Ensure eslint checks pass: `npm run lint:js`.
-
-## 3. Commit and push
-
-After building and testing the built release, all changes should be committed and pushed to GitHub.
-
-## 3. Generate the Distribution Archive
-
-Run `npm run dev release archive`.
-
-## 4. Run pre-release tests on the archived
-
-Install and activate the zip file on a temporary sandbox site.
-
- 1. Run the setup wizard.
- 2. Import sample course
- 3. Enroll a student into the course.
- 4. Complete a lesson.
-
-_This manual testing ensures no errors occurred in the build steps above._
-
-## 5. Publish the Release
-
-Run `npm run dev release create`.
-
-The following steps are performed automatically by the above task:
-
-1. Publish to GitHub
- A. The contents of the distribution archive is force-pushed to the `release` branch.
- B. A new release tag draft is created for the current version number using `release` as the commit target.
- C. The distribution archive is uploaded to the release.
- D. The release is published.
- E. A webhook ping notifies the `llms-releaser` server which performs the remaining steps of the release:
-2. Publish to WordPress plugin repository
- A. Create a new SVN tag using the release asset (distribution archive) as the base.
- B. Update the `trunk` branch to match the new tag.
-3. A changelog blog post is published to make.lifterlms.com.
-4. The number is updated at LifterLMS.com
-5. The distribution archive is synced to the release asset bucket in AWS S3 as a backup.
diff --git a/gulpfile.js/index.js b/gulpfile.js/index.js
deleted file mode 100644
index 6b6010bbd7..0000000000
--- a/gulpfile.js/index.js
+++ /dev/null
@@ -1,13 +0,0 @@
-/**
- * Main Gulp File
- *
- * Requires all task files
- */
-var gulp = require('gulp');
-
-// All custom tasks.
-require( './tasks/js-additional' );
-require( './tasks/js-builder' );
-
-// All tasks from lib-tasks.
-require( 'lifterlms-lib-tasks' )( gulp );
diff --git a/gulpfile.js/tasks/js-additional.js b/gulpfile.js/tasks/js-additional.js
deleted file mode 100644
index 6e13545bab..0000000000
--- a/gulpfile.js/tasks/js-additional.js
+++ /dev/null
@@ -1,49 +0,0 @@
-var gulp = require( 'gulp' )
- , header = require( 'gulp-header' )
- , include = require( 'gulp-include' )
- , maps = require( 'gulp-sourcemaps' )
- , pump = require( 'pump' )
- , rename = require( 'gulp-rename' )
- , uglify = require( 'gulp-uglify' )
- , gulpignore = require( 'gulp-ignore' )
-
- , path = require( 'path' )
-;
-
-gulp.task( 'js-additional', function( cb ) {
-
- var notice = [
- '/****************************************************************',
- ' *',
- ' * Contributor\'s Notice',
- ' * ',
- ' * This is a compiled file and should not be edited directly!',
- ' * The uncompiled script is located in the "assets/private" directory',
- ' * ',
- ' ****************************************************************/',
- '',
- '',
- ];
-
- pump( [
- gulp.src( 'assets/js/private/**/*.js' ),
- include(),
- maps.init(),
- header( notice.join( '\n' ) ),
- maps.write('../maps/js', { destPath: 'assets/js' } ),
- gulp.dest( 'assets/js' ),
-
- // Don't pass maps any further.
- gulpignore.exclude( file => '.js' !== path.extname( file.basename ) ),
-
- uglify(),
- rename( {
- suffix: '.min',
- } ),
- maps.write('../maps/js', { destPath: 'assets/js' } ),
- gulp.dest( 'assets/js' )
- ],
- cb
- );
-
-} );
diff --git a/gulpfile.js/tasks/js-builder.js b/gulpfile.js/tasks/js-builder.js
deleted file mode 100644
index 22be2d3059..0000000000
--- a/gulpfile.js/tasks/js-builder.js
+++ /dev/null
@@ -1,57 +0,0 @@
-/**
- * -----------------------------------------------------------
- * js-builder
- * -----------------------------------------------------------
- * Compile Admin builder Javascript
- */
-
-var gulp = require( 'gulp' )
- , requirejsOptimize = require( 'gulp-requirejs-optimize' )
- , rename = require( 'gulp-rename' )
- , sourcemaps = require( 'gulp-sourcemaps' )
-;
-
-gulp.task( 'js-builder', function( cb ) {
-
- gulp.src( 'assets/js/builder/main.js' )
- // unminified
- .pipe( sourcemaps.init() )
- .pipe( requirejsOptimize( function( file ) {
- return {
- name: 'vendor/almond',
- optimize: 'none',
- wrap: {
- start: "(function($){",
- end: "}(jQuery));"
- },
- baseUrl: 'assets/js/builder/',
- include: [ 'main' ],
- preserveLicenseComments: false
- };
- } ).on( 'error', ( err ) => console.log( err ) ) )
- .pipe( rename( 'llms-builder.js' ) )
- .pipe( sourcemaps.write( '../maps/js', { destPath: 'assets/js' } ) )
- .pipe( gulp.dest( 'assets/js/' ) )
-
- // minified
- .pipe( sourcemaps.init() )
- .pipe( requirejsOptimize( function( file ) {
- return {
- name: 'vendor/almond',
- optimize: 'uglify2',
- wrap: {
- start: "(function($){",
- end: "}(jQuery));"
- },
- baseUrl: 'assets/js/builder/',
- include: [ 'main' ],
- preserveLicenseComments: false
- };
- } ).on( 'error', ( err ) => console.log( err ) ) )
- .pipe( rename( 'llms-builder.min.js' ) )
- .pipe( sourcemaps.write( '../maps/js', { destPath: 'assets/js' } ) )
- .pipe( gulp.dest( 'assets/js/' ) );
-
- cb();
-
-});
diff --git a/includes/abstracts/abstract.llms.post.model.php b/includes/abstracts/abstract.llms.post.model.php
old mode 100755
new mode 100644
diff --git a/includes/admin/class.llms.admin.assets.php b/includes/admin/class.llms.admin.assets.php
index ce4b51821f..ba7ebb59aa 100644
--- a/includes/admin/class.llms.admin.assets.php
+++ b/includes/admin/class.llms.admin.assets.php
@@ -5,7 +5,7 @@
* @package LifterLMS/Admin/Classes
*
* @since 1.0.0
- * @version 6.5.0
+ * @version 6.10.0
*/
defined( 'ABSPATH' ) || exit;
@@ -481,23 +481,19 @@ protected function maybe_enqueue_reporting( $screen ) {
*
* @since 3.16.0
* @since 3.17.8 Unknown.
+ * @since 6.10.0 Load modules using `llms()->assets`.
*
* @return void
*/
public static function register_quill( $modules = array() ) {
if ( ! wp_script_is( 'llms-quill', 'registered' ) ) {
-
wp_register_script( 'llms-quill', LLMS_PLUGIN_URL . 'assets/vendor/quill/quill' . LLMS_ASSETS_SUFFIX . '.js', array(), '1.3.5', true );
wp_register_style( 'llms-quill-bubble', LLMS_PLUGIN_URL . 'assets/vendor/quill/quill.bubble' . LLMS_ASSETS_SUFFIX . '.css', array(), '1.3.5', 'screen' );
-
}
foreach ( $modules as $module ) {
-
- $url = LLMS_PLUGIN_URL . 'assets/vendor/quill/quill.module.' . $module . LLMS_ASSETS_SUFFIX . '.js';
- wp_register_script( 'llms-quill-' . $module, $url, array( 'llms-quill' ), llms()->version, true );
-
+ llms()->assets->register_script( "llms-quill-{$module}" );
}
}
diff --git a/includes/admin/post-types/post-tables/class-llms-admin-post-table-awards.php b/includes/admin/post-types/post-tables/class-llms-admin-post-table-awards.php
index 4767fe0e27..47b6a43a5f 100644
--- a/includes/admin/post-types/post-tables/class-llms-admin-post-table-awards.php
+++ b/includes/admin/post-types/post-tables/class-llms-admin-post-table-awards.php
@@ -5,7 +5,7 @@
* @package LifterLMS/Admin/PostTypes/PostTables/Classes
*
* @since 6.0.0
- * @version 6.4.0
+ * @version 6.10.0
*/
defined( 'ABSPATH' ) || exit;
@@ -147,14 +147,16 @@ public function date_col_status( $text, $post, $column_name ) {
*
* @since 6.0.0
* @since 6.4.0 Stop using deprecated `FILTER_SANITIZE_STRING`.
+ * @since 6.10.0 When no INPUT_GET `post_type` variable set, retrieve the post_type from the `$id` (WP_Post ID) parameter.
*
- * @param int $id WP_Post id.
+ * @param int $id WP_Post ID.
* @param boolean $template Whether or not a template is being requested.
* @return LLMS_User_Achievement|LLMS_User_Certificate|boolean Returns the object or `false` for invalid post types.
*/
private function get_object( $id, $template = false ) {
$post_type = llms_filter_input( INPUT_GET, 'post_type' );
+ $post_type = $post_type ? $post_type : get_post_type( $id );
if ( 'llms_my_achievement' === $post_type ) {
return new LLMS_User_Achievement( $id );
@@ -305,6 +307,7 @@ public function parse_query( $query ) {
* Modify post row actions.
*
* @since 6.0.0
+ * @since 6.10.0 Added missing textdomain for the 'Move {post_title} to the Trash' string.
*
* @param array $actions Existing actions.
* @param WP_Post $post Post object.
@@ -318,7 +321,7 @@ public function row_actions( $actions, $post ) {
'%s ',
get_delete_post_link( $post->ID ),
// Translators: %s: Post title.
- esc_attr( sprintf( __( 'Move “%s” to the Trash' ), _draft_or_post_title( $post ) ) ),
+ esc_attr( sprintf( __( 'Move “%s” to the Trash', 'lifterlms' ), _draft_or_post_title( $post ) ) ),
__( 'Delete Permanently', 'lifterlms' )
);
diff --git a/includes/assets/llms-assets-scripts.php b/includes/assets/llms-assets-scripts.php
index 6f8ddb4175..0a41e21bd5 100644
--- a/includes/assets/llms-assets-scripts.php
+++ b/includes/assets/llms-assets-scripts.php
@@ -18,7 +18,7 @@
* @package LifterLMS/Assets
*
* @since 4.4.0
- * @version 6.0.0
+ * @version 6.10.0
*/
defined( 'ABSPATH' ) || exit;
@@ -31,6 +31,7 @@
* @since 5.0.0 Added llms-select2.
* @since 5.5.0 Added llms-addons.
* @since 6.0.0 Added llms-admin-certificate-editor.
+ * @since 6.10.0 Added llms-quill-wordcount.
*/
return array(
@@ -83,6 +84,13 @@
'suffix' => '',
),
+ // Quill Modules.
+ 'llms-quill-wordcount' => array(
+ 'asset_file' => true,
+ 'suffix' => '',
+ 'dependencies' => array( 'llms-quill' ),
+ ),
+
// Vendor.
'llms-iziModal' => array(
'file_name' => 'iziModal',
diff --git a/includes/controllers/class.llms.controller.lesson.progression.php b/includes/controllers/class.llms.controller.lesson.progression.php
index d54fea4976..a5f19d1fd0 100644
--- a/includes/controllers/class.llms.controller.lesson.progression.php
+++ b/includes/controllers/class.llms.controller.lesson.progression.php
@@ -5,7 +5,7 @@
* @package LifterLMS/Controllers/Classes
*
* @since 3.17.1
- * @version 5.9.0
+ * @version 6.10.0
*/
defined( 'ABSPATH' ) || exit;
@@ -80,6 +80,7 @@ private function get_lesson_id_from_form_data( $action ) {
*
* @since 3.29.0
* @since 5.9.0 Stop using deprecated `FILTER_SANITIZE_STRING`.
+ * @since 6.10.0 Check the current user can edit the lesson they're going to mark complete/incomplete.
*
* @return void
*/
@@ -94,7 +95,7 @@ public function handle_admin_managment_forms() {
$student_id = absint( llms_filter_input( INPUT_POST, 'student_id' ) );
// Missing required data.
- if ( empty( $action ) || empty( $lesson_id ) || empty( $student_id ) ) {
+ if ( empty( $action ) || empty( $lesson_id ) || empty( $student_id ) || ! current_user_can( 'edit_post', $lesson_id ) ) {
return;
}
diff --git a/includes/functions/updates/llms-functions-updates-6100.php b/includes/functions/updates/llms-functions-updates-6100.php
new file mode 100644
index 0000000000..a16226a363
--- /dev/null
+++ b/includes/functions/updates/llms-functions-updates-6100.php
@@ -0,0 +1,106 @@
+ 'O', // Asturias.
+ 'CB' => 'S', // Cantabria.
+ 'RI' => 'LO', // 'La Rioja'.
+ 'MD' => 'M', // Madrid.
+ 'MC' => 'MU', // 'Murcia'.
+ 'NC' => 'NA', // 'Navarra'.
+ 'VC' => 'V', // 'Valencia'.
+ // No map.
+ 'AN' => '',
+ 'AR' => '',
+ 'PV' => '',
+ 'CN' => '',
+ 'CL' => '',
+ 'CM' => '',
+ 'CT' => '',
+ 'EX' => '',
+ 'GA' => '',
+ 'SA' => '',
+ );
+
+ $query = new \WP_User_Query(
+ array(
+ 'orderby' => array(
+ 'ID' => 'ASC',
+ ),
+ 'meta_query' => array(
+ 'relation' => 'AND',
+ array(
+ 'key' => 'llms_billing_country',
+ 'value' => 'ES',
+ 'compare' => '=',
+ ),
+ array(
+ 'key' => 'llms_billing_state',
+ 'value' => array_keys( $states_migration_map ),
+ 'compare' => 'IN',
+ ),
+ ),
+ 'posts_per_page' => $per_page,
+ 'no_found_rows' => true, // We don't care about found rows since we'll run the query as many times as needed anyway.
+ )
+ );
+
+ $users = $query->get_results();
+ if ( $users ) {
+ foreach ( $users as $user ) {
+ $new_state = $states_migration_map[ \get_user_meta( $user->ID, 'llms_billing_state', true ) ] ?? '';
+ \update_user_meta( $user->ID, 'llms_billing_state', $new_state );
+ }
+ }
+
+ // If there was `$per_page` results assume there's another page and run again, otherwise we're done.
+ return ( count( $users ) === $per_page );
+
+}
+
+/**
+ * Update db version to 6.10.0.
+ *
+ * @since 6.10.0
+ *
+ * @return fasle.
+ */
+function update_db_version() {
+ \LLMS_Install::update_db_version( _get_db_version() );
+ return false;
+}
diff --git a/includes/models/model.llms.user.certificate.php b/includes/models/model.llms.user.certificate.php
index 7c989b60ce..85dd66d611 100644
--- a/includes/models/model.llms.user.certificate.php
+++ b/includes/models/model.llms.user.certificate.php
@@ -452,7 +452,7 @@ function( $margin ) {
*
* @since 6.0.0
* @since 6.1.0 Added `{earned_date}` merge code.
- * Allowed `{current_date}` to be mocked.
+ * Allowed `{current_date}` to be mocked.
*
* @return string[] Array mapping merge codes to the merge data.
*/
@@ -491,6 +491,21 @@ protected function get_merge_data() {
'llms_certificate_merge_data'
);
+ /**
+ * Filters the certificate merge data.
+ *
+ * @since 6.0.0
+ *
+ * @param array $codes {
+ * Merge codes and data.
+ *
+ * @type string $code The merge code. E.g. {first_name}.
+ * @type int|string|bool $data The merga data to replace the merge code with. E.g. 'Dude'.
+ * }
+ * @param int $user_id WP User ID of the user who earned the certificate.
+ * @param int $template_id WP_Post ID of the certificate template.
+ * @param int $related_id WP Post ID of the post which triggered the certificate to be awarded.
+ */
return apply_filters( 'llms_certificate_merge_data', $codes, $user_id, $template_id, $related_id );
}
diff --git a/includes/schemas/llms-db-updates.php b/includes/schemas/llms-db-updates.php
index 45e3a7c5c6..b414f34939 100644
--- a/includes/schemas/llms-db-updates.php
+++ b/includes/schemas/llms-db-updates.php
@@ -164,4 +164,12 @@
'update_db_version',
),
),
+ '6.10.0' => array(
+ 'type' => 'auto',
+ 'namespace' => true,
+ 'updates' => array(
+ 'migrate_spanish_users',
+ 'update_db_version',
+ ),
+ ),
);
diff --git a/languages/README.md b/languages/README.md
deleted file mode 100644
index 4858d32b7a..0000000000
--- a/languages/README.md
+++ /dev/null
@@ -1,24 +0,0 @@
-LifterLMS Localization and Language Files
-=========================================
-
-This directory contains localization and language files for the LifterLMS plugin.
-
-## Translating LifterLMS
-
-LifterLMS is fully translatable. The main `.pot` file contained in this directory ([lifterlms.pot](lifterlms.pot)) contains all translatable strings available in the source code. This file is automatically generated on release.
-
-
-## Localization Information Files
-
-The `.php` files contained within this directory contain lists of localization information (such as country, address, and currency formatting data). These files are loaded by LifterLMS core functions to various areas of the LifterLMS plugin.
-
-The data contained within these files is compiled from regularly updated sources and converted into a format used by our internal API. These files are automatically generated during a release step.
-
-Information for these files is derived from the following projects and sources:
-
-+ [Countries States Cities Database](https://github.com/dr5hn/countries-states-cities-database)
-+ [Currency Formatter](https://github.com/smirzaei/currency-formatter)
-+ [addressfield.json](https://github.com/tableau-mkt/addressfield.json)
-+ [LocalePlanet](https://www.localeplanet.com/)
-
-If you locate any incorrect information in any of these files, please let us know by opening [a new issue](https://github.com/gocodebox/lifterlms/issues/new/choose).
diff --git a/languages/lifterlms.pot b/languages/lifterlms.pot
index 9622edb384..a4666a7a85 100644
--- a/languages/lifterlms.pot
+++ b/languages/lifterlms.pot
@@ -2,16 +2,16 @@
# This file is distributed under the GPLv3.
msgid ""
msgstr ""
-"Project-Id-Version: LifterLMS 6.9.0\n"
+"Project-Id-Version: LifterLMS 6.10.0\n"
"Report-Msgid-Bugs-To: https://lifterlms.com/my-account/my-tickets\n"
"Last-Translator: Team LifterLMS \n"
"Language-Team: Team LifterLMS \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-"POT-Creation-Date: 2022-07-28T12:48:36-06:00\n"
+"POT-Creation-Date: 2022-08-29T11:17:38-06:00\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
-"X-Generator: llms/dev 0.0.5\n"
+"X-Generator: llms/dev 0.1.0\n"
"X-Domain: lifterlms\n"
#. Plugin Name of the plugin
@@ -2510,7 +2510,7 @@ msgid "Template"
msgstr ""
#: includes/admin/post-types/post-tables/class-llms-admin-post-table-awards.php:117
-#: includes/admin/post-types/post-tables/class-llms-admin-post-table-awards.php:322
+#: includes/admin/post-types/post-tables/class-llms-admin-post-table-awards.php:325
msgid "Delete Permanently"
msgstr ""
@@ -2520,6 +2520,11 @@ msgstr ""
msgid "Awarded"
msgstr ""
+#. Translators: %s: Post title.
+#: includes/admin/post-types/post-tables/class-llms-admin-post-table-awards.php:324
+msgid "Move “%s” to the Trash"
+msgstr ""
+
#: includes/admin/post-types/post-tables/class-llms-admin-post-table-certificates.php:77
msgid "Migrate legacy certificate"
msgstr ""
@@ -7918,7 +7923,7 @@ msgid "An error occurred, please try again."
msgstr ""
#. Translators: %s is the title of the lesson.
-#: includes/controllers/class.llms.controller.lesson.progression.php:187
+#: includes/controllers/class.llms.controller.lesson.progression.php:188
msgid "The lesson %s is now marked as incomplete."
msgstr ""
@@ -9089,7 +9094,7 @@ msgstr ""
msgid "Trial"
msgstr ""
-#: includes/models/model.llms.user.certificate.php:732
+#: includes/models/model.llms.user.certificate.php:747
msgid "Loading custom HTML from the certificate template is deprecated. All HTML should be added to the certificate directly via the editor or applied via post content filters."
msgstr ""
@@ -10976,7 +10981,7 @@ msgid "Algeria"
msgstr ""
#: languages/countries.php:43
-#: languages/states.php:5048
+#: languages/states.php:5073
msgid "American Samoa"
msgstr ""
@@ -11262,12 +11267,12 @@ msgid "France"
msgstr ""
#: languages/countries.php:114
-#: languages/states.php:1452
+#: languages/states.php:1477
msgid "French Guiana"
msgstr ""
#: languages/countries.php:115
-#: languages/states.php:1453
+#: languages/states.php:1478
msgid "French Polynesia"
msgstr ""
@@ -11280,7 +11285,7 @@ msgid "Gabon"
msgstr ""
#: languages/countries.php:118
-#: languages/states.php:5057
+#: languages/states.php:5082
msgid "Georgia"
msgstr ""
@@ -11309,12 +11314,12 @@ msgid "Grenada"
msgstr ""
#: languages/countries.php:125
-#: languages/states.php:1455
+#: languages/states.php:1480
msgid "Guadeloupe"
msgstr ""
#: languages/countries.php:126
-#: languages/states.php:5058
+#: languages/states.php:5083
msgid "Guam"
msgstr ""
@@ -11516,7 +11521,7 @@ msgid "Marshall Islands"
msgstr ""
#: languages/countries.php:176
-#: languages/states.php:1461
+#: languages/states.php:1486
msgid "Martinique"
msgstr ""
@@ -11529,7 +11534,7 @@ msgid "Mauritius"
msgstr ""
#: languages/countries.php:179
-#: languages/states.php:1462
+#: languages/states.php:1487
msgid "Mayotte"
msgstr ""
@@ -11614,7 +11619,7 @@ msgid "Norfolk Island"
msgstr ""
#: languages/countries.php:200
-#: languages/states.php:5083
+#: languages/states.php:5108
msgid "Northern Mariana Islands"
msgstr ""
@@ -11671,7 +11676,7 @@ msgid "Portugal"
msgstr ""
#: languages/countries.php:214
-#: languages/states.php:5088
+#: languages/states.php:5113
msgid "Puerto Rico"
msgstr ""
@@ -11696,7 +11701,7 @@ msgid "Rwanda"
msgstr ""
#: languages/countries.php:220
-#: languages/states.php:1692
+#: languages/states.php:1717
msgid "Saint Helena"
msgstr ""
@@ -11709,7 +11714,7 @@ msgid "Saint Lucia"
msgstr ""
#: languages/countries.php:223
-#: languages/states.php:1475
+#: languages/states.php:1500
msgid "Saint Pierre and Miquelon"
msgstr ""
@@ -11730,7 +11735,7 @@ msgid "Samoa"
msgstr ""
#: languages/countries.php:228
-#: languages/states.php:4445
+#: languages/states.php:4470
msgid "San Marino"
msgstr ""
@@ -11911,7 +11916,7 @@ msgid "United Arab Emirates"
msgstr ""
#: languages/countries.php:273
-#: languages/states.php:1724
+#: languages/states.php:1749
msgid "United Kingdom"
msgstr ""
@@ -11920,7 +11925,7 @@ msgid "United States"
msgstr ""
#: languages/countries.php:275
-#: languages/states.php:5094
+#: languages/states.php:5119
msgid "United States Minor Outlying Islands"
msgstr ""
@@ -13328,19 +13333,19 @@ msgstr ""
#: languages/states.php:97
#: languages/states.php:1164
-#: languages/states.php:1746
-#: languages/states.php:5147
+#: languages/states.php:1771
+#: languages/states.php:5172
msgid "Saint George Parish"
msgstr ""
#: languages/states.php:98
#: languages/states.php:1165
-#: languages/states.php:1747
+#: languages/states.php:1772
msgid "Saint John Parish"
msgstr ""
#: languages/states.php:99
-#: languages/states.php:2363
+#: languages/states.php:2388
msgid "Saint Mary Parish"
msgstr ""
@@ -13816,7 +13821,7 @@ msgid "Tasmania"
msgstr ""
#: languages/states.php:232
-#: languages/states.php:3301
+#: languages/states.php:3326
msgid "Victoria"
msgstr ""
@@ -14189,7 +14194,7 @@ msgid "Christ Church"
msgstr ""
#: languages/states.php:333
-#: languages/states.php:2360
+#: languages/states.php:2385
msgid "Saint Andrew"
msgstr ""
@@ -14526,7 +14531,7 @@ msgid "Hainaut"
msgstr ""
#: languages/states.php:421
-#: languages/states.php:3568
+#: languages/states.php:3593
msgid "Limburg"
msgstr ""
@@ -14600,7 +14605,7 @@ msgstr ""
#: languages/states.php:442
#: languages/states.php:875
-#: languages/states.php:2000
+#: languages/states.php:2025
msgid "Centre"
msgstr ""
@@ -14893,7 +14898,7 @@ msgid "Vratsa Province"
msgstr ""
#: languages/states.php:519
-#: languages/states.php:2584
+#: languages/states.php:2609
msgid "Capital Governorate"
msgstr ""
@@ -15106,8 +15111,8 @@ msgid "Cochabamba Department"
msgstr ""
#: languages/states.php:583
-#: languages/states.php:1968
-#: languages/states.php:4516
+#: languages/states.php:1993
+#: languages/states.php:4541
msgid "La Paz Department"
msgstr ""
@@ -15144,8 +15149,8 @@ msgid "Amapá"
msgstr ""
#: languages/states.php:595
-#: languages/states.php:3689
-#: languages/states.php:5151
+#: languages/states.php:3714
+#: languages/states.php:5176
msgid "Amazonas"
msgstr ""
@@ -15487,7 +15492,7 @@ msgid "Gasa District"
msgstr ""
#: languages/states.php:687
-#: languages/states.php:2126
+#: languages/states.php:2151
msgid "Central District"
msgstr ""
@@ -15524,7 +15529,7 @@ msgid "South-East District"
msgstr ""
#: languages/states.php:696
-#: languages/states.php:2130
+#: languages/states.php:2155
msgid "Southern District"
msgstr ""
@@ -16061,7 +16066,7 @@ msgid "Sassandra-Marahoué District"
msgstr ""
#: languages/states.php:847
-#: languages/states.php:4581
+#: languages/states.php:4606
msgid "Savanes Region"
msgstr ""
@@ -16166,7 +16171,7 @@ msgid "Littoral"
msgstr ""
#: languages/states.php:879
-#: languages/states.php:1942
+#: languages/states.php:1967
msgid "North"
msgstr ""
@@ -17187,14 +17192,14 @@ msgid "Region Zealand"
msgstr ""
#: languages/states.php:1162
-#: languages/states.php:1744
-#: languages/states.php:5145
+#: languages/states.php:1769
+#: languages/states.php:5170
msgid "Saint Andrew Parish"
msgstr ""
#: languages/states.php:1163
-#: languages/states.php:1745
-#: languages/states.php:5146
+#: languages/states.php:1770
+#: languages/states.php:5171
msgid "Saint David Parish"
msgstr ""
@@ -17207,13 +17212,13 @@ msgid "Saint Luke Parish"
msgstr ""
#: languages/states.php:1168
-#: languages/states.php:1748
+#: languages/states.php:1773
msgid "Saint Mark Parish"
msgstr ""
#: languages/states.php:1169
-#: languages/states.php:1749
-#: languages/states.php:5148
+#: languages/states.php:1774
+#: languages/states.php:5173
msgid "Saint Patrick Parish"
msgstr ""
@@ -17849,14394 +17854,14490 @@ msgstr ""
msgid "Southern Red Sea Region"
msgstr ""
-#: languages/states.php:1344
-msgid "Andalusia"
-msgstr ""
-
#: languages/states.php:1345
-msgid "Aragon"
+msgid "A Coruña"
msgstr ""
#: languages/states.php:1346
-msgid "Asturias"
+msgid "Araba/Álava"
msgstr ""
#: languages/states.php:1347
-msgid "Balearic Islands"
+msgid "Albacete"
msgstr ""
#: languages/states.php:1348
-msgid "Basque Country"
+msgid "Alicante"
msgstr ""
#: languages/states.php:1349
-msgid "Burgos Province"
+msgid "Almería"
msgstr ""
#: languages/states.php:1350
-msgid "Canary Islands"
+msgid "Asturias"
msgstr ""
#: languages/states.php:1351
-msgid "Cantabria"
+msgid "Ávila"
msgstr ""
#: languages/states.php:1352
-msgid "Castile and León"
+msgid "Badajoz"
msgstr ""
#: languages/states.php:1353
-msgid "Castilla La Mancha"
+msgid "Baleares"
msgstr ""
#: languages/states.php:1354
-msgid "Catalonia"
+msgid "Barcelona"
msgstr ""
#: languages/states.php:1355
-msgid "Ceuta"
+msgid "Burgos"
msgstr ""
#: languages/states.php:1356
-msgid "Extremadura"
+msgid "Cáceres"
msgstr ""
#: languages/states.php:1357
-msgid "Galicia"
+msgid "Cádiz"
msgstr ""
#: languages/states.php:1358
-msgid "La Rioja"
+msgid "Cantabria"
msgstr ""
#: languages/states.php:1359
-msgid "Léon"
+msgid "Castellón"
msgstr ""
#: languages/states.php:1360
-msgid "Madrid"
+msgid "Ceuta"
msgstr ""
#: languages/states.php:1361
-msgid "Melilla"
+msgid "Ciudad Real"
msgstr ""
#: languages/states.php:1362
-msgid "Murcia"
+msgid "Córdoba"
msgstr ""
#: languages/states.php:1363
-msgid "Navarra"
+msgid "Cuenca"
msgstr ""
#: languages/states.php:1364
-msgid "Palencia Province"
+msgid "Girona"
msgstr ""
#: languages/states.php:1365
-msgid "Salamanca Province"
+msgid "Granada"
msgstr ""
#: languages/states.php:1366
-msgid "Segovia Province"
+msgid "Guadalajara"
msgstr ""
#: languages/states.php:1367
-msgid "Soria Province"
+msgid "Gipuzkoa"
msgstr ""
#: languages/states.php:1368
-msgid "Valencia"
+msgid "Huelva"
msgstr ""
#: languages/states.php:1369
-msgid "Valladolid Province"
+msgid "Huesca"
msgstr ""
#: languages/states.php:1370
-msgid "Zamora Province"
+msgid "Jaén"
msgstr ""
#: languages/states.php:1371
-msgid "Ávila"
+msgid "La Rioja"
+msgstr ""
+
+#: languages/states.php:1372
+msgid "Las Palmas"
+msgstr ""
+
+#: languages/states.php:1373
+msgid "León"
msgstr ""
#: languages/states.php:1374
-msgid "Addis Ababa"
+msgid "Lleida"
msgstr ""
#: languages/states.php:1375
-msgid "Afar Region"
+msgid "Lugo"
msgstr ""
#: languages/states.php:1376
-msgid "Amhara Region"
+msgid "Madrid"
msgstr ""
#: languages/states.php:1377
-msgid "Benishangul-Gumuz Region"
+msgid "Málaga"
msgstr ""
#: languages/states.php:1378
-msgid "Dire Dawa"
+msgid "Melilla"
msgstr ""
#: languages/states.php:1379
-msgid "Gambela Region"
+msgid "Murcia"
msgstr ""
#: languages/states.php:1380
-msgid "Harari Region"
+msgid "Navarra"
msgstr ""
#: languages/states.php:1381
-msgid "Oromia Region"
+msgid "Ourense"
msgstr ""
#: languages/states.php:1382
-msgid "Somali Region"
+msgid "Palencia"
msgstr ""
#: languages/states.php:1383
-msgid "Southern Nations, Nationalities, and Peoples' Region"
+msgid "Pontevedra"
msgstr ""
#: languages/states.php:1384
-msgid "Tigray Region"
+msgid "Salamanca"
+msgstr ""
+
+#: languages/states.php:1385
+msgid "Santa Cruz de Tenerife"
+msgstr ""
+
+#: languages/states.php:1386
+msgid "Segovia"
msgstr ""
#: languages/states.php:1387
-msgid "Pirkanmaa"
+msgid "Sevilla"
msgstr ""
#: languages/states.php:1388
-msgid "Ostrobothnia"
+msgid "Soria"
msgstr ""
#: languages/states.php:1389
-msgid "North Karelia"
+msgid "Tarragona"
msgstr ""
#: languages/states.php:1390
-msgid "Northern Ostrobothnia"
+msgid "Teruel"
msgstr ""
#: languages/states.php:1391
-msgid "Northern Savonia"
+msgid "Toledo"
msgstr ""
#: languages/states.php:1392
-msgid "Päijänne Tavastia"
+msgid "Valencia"
msgstr ""
#: languages/states.php:1393
-msgid "Satakunta"
+msgid "Valladolid"
msgstr ""
#: languages/states.php:1394
-msgid "Uusimaa"
+msgid "Biscay"
msgstr ""
#: languages/states.php:1395
-msgid "Finland Proper"
+msgid "Zamora"
msgstr ""
#: languages/states.php:1396
+msgid "Zaragoza"
+msgstr ""
+
+#: languages/states.php:1399
+msgid "Addis Ababa"
+msgstr ""
+
+#: languages/states.php:1400
+msgid "Afar Region"
+msgstr ""
+
+#: languages/states.php:1401
+msgid "Amhara Region"
+msgstr ""
+
+#: languages/states.php:1402
+msgid "Benishangul-Gumuz Region"
+msgstr ""
+
+#: languages/states.php:1403
+msgid "Dire Dawa"
+msgstr ""
+
+#: languages/states.php:1404
+msgid "Gambela Region"
+msgstr ""
+
+#: languages/states.php:1405
+msgid "Harari Region"
+msgstr ""
+
+#: languages/states.php:1406
+msgid "Oromia Region"
+msgstr ""
+
+#: languages/states.php:1407
+msgid "Somali Region"
+msgstr ""
+
+#: languages/states.php:1408
+msgid "Southern Nations, Nationalities, and Peoples' Region"
+msgstr ""
+
+#: languages/states.php:1409
+msgid "Tigray Region"
+msgstr ""
+
+#: languages/states.php:1412
+msgid "Pirkanmaa"
+msgstr ""
+
+#: languages/states.php:1413
+msgid "Ostrobothnia"
+msgstr ""
+
+#: languages/states.php:1414
+msgid "North Karelia"
+msgstr ""
+
+#: languages/states.php:1415
+msgid "Northern Ostrobothnia"
+msgstr ""
+
+#: languages/states.php:1416
+msgid "Northern Savonia"
+msgstr ""
+
+#: languages/states.php:1417
+msgid "Päijänne Tavastia"
+msgstr ""
+
+#: languages/states.php:1418
+msgid "Satakunta"
+msgstr ""
+
+#: languages/states.php:1419
+msgid "Uusimaa"
+msgstr ""
+
+#: languages/states.php:1420
+msgid "Finland Proper"
+msgstr ""
+
+#: languages/states.php:1421
msgid "Central Finland"
msgstr ""
-#: languages/states.php:1397
+#: languages/states.php:1422
msgid "Central Ostrobothnia"
msgstr ""
-#: languages/states.php:1398
+#: languages/states.php:1423
msgid "Eastern Finland Province"
msgstr ""
-#: languages/states.php:1399
+#: languages/states.php:1424
msgid "Kainuu"
msgstr ""
-#: languages/states.php:1400
+#: languages/states.php:1425
msgid "Kymenlaakso"
msgstr ""
-#: languages/states.php:1401
+#: languages/states.php:1426
msgid "Lapland"
msgstr ""
-#: languages/states.php:1402
+#: languages/states.php:1427
msgid "Oulu Province"
msgstr ""
-#: languages/states.php:1403
+#: languages/states.php:1428
msgid "South Karelia"
msgstr ""
-#: languages/states.php:1404
+#: languages/states.php:1429
msgid "Southern Ostrobothnia"
msgstr ""
-#: languages/states.php:1405
+#: languages/states.php:1430
msgid "Southern Savonia"
msgstr ""
-#: languages/states.php:1406
+#: languages/states.php:1431
msgid "Tavastia Proper"
msgstr ""
-#: languages/states.php:1407
+#: languages/states.php:1432
msgid "Åland Islands"
msgstr ""
-#: languages/states.php:1410
+#: languages/states.php:1435
msgid "Namosi"
msgstr ""
-#: languages/states.php:1411
+#: languages/states.php:1436
msgid "Ra"
msgstr ""
-#: languages/states.php:1412
+#: languages/states.php:1437
msgid "Rewa"
msgstr ""
-#: languages/states.php:1413
+#: languages/states.php:1438
msgid "Serua"
msgstr ""
-#: languages/states.php:1414
+#: languages/states.php:1439
msgid "Tailevu"
msgstr ""
-#: languages/states.php:1415
+#: languages/states.php:1440
msgid "Ba"
msgstr ""
-#: languages/states.php:1416
+#: languages/states.php:1441
msgid "Bua"
msgstr ""
-#: languages/states.php:1417
+#: languages/states.php:1442
msgid "Cakaudrove"
msgstr ""
-#: languages/states.php:1418
+#: languages/states.php:1443
msgid "Central Division"
msgstr ""
-#: languages/states.php:1419
+#: languages/states.php:1444
msgid "Eastern Division"
msgstr ""
-#: languages/states.php:1420
+#: languages/states.php:1445
msgid "Kadavu"
msgstr ""
-#: languages/states.php:1421
+#: languages/states.php:1446
msgid "Lau"
msgstr ""
-#: languages/states.php:1422
+#: languages/states.php:1447
msgid "Lomaiviti"
msgstr ""
-#: languages/states.php:1423
+#: languages/states.php:1448
msgid "Macuata"
msgstr ""
-#: languages/states.php:1424
+#: languages/states.php:1449
msgid "Nadroga-Navosa"
msgstr ""
-#: languages/states.php:1425
+#: languages/states.php:1450
msgid "Naitasiri"
msgstr ""
-#: languages/states.php:1426
+#: languages/states.php:1451
msgid "Northern Division"
msgstr ""
-#: languages/states.php:1427
+#: languages/states.php:1452
msgid "Rotuma"
msgstr ""
-#: languages/states.php:1428
+#: languages/states.php:1453
msgid "Western Division"
msgstr ""
-#: languages/states.php:1432
+#: languages/states.php:1457
msgid "Chuuk State"
msgstr ""
-#: languages/states.php:1433
+#: languages/states.php:1458
msgid "Kosrae State"
msgstr ""
-#: languages/states.php:1434
+#: languages/states.php:1459
msgid "Pohnpei State"
msgstr ""
-#: languages/states.php:1435
+#: languages/states.php:1460
msgid "Yap State"
msgstr ""
-#: languages/states.php:1439
+#: languages/states.php:1464
msgid "Paris"
msgstr ""
-#: languages/states.php:1440
+#: languages/states.php:1465
msgid "Alo"
msgstr ""
-#: languages/states.php:1441
+#: languages/states.php:1466
msgid "Alsace"
msgstr ""
-#: languages/states.php:1442
+#: languages/states.php:1467
msgid "Aquitaine"
msgstr ""
-#: languages/states.php:1443
+#: languages/states.php:1468
msgid "Auvergne"
msgstr ""
-#: languages/states.php:1444
+#: languages/states.php:1469
msgid "Auvergne-Rhône-Alpes"
msgstr ""
-#: languages/states.php:1445
+#: languages/states.php:1470
msgid "Bourgogne-Franche-Comté"
msgstr ""
-#: languages/states.php:1446
+#: languages/states.php:1471
msgid "Brittany"
msgstr ""
-#: languages/states.php:1447
+#: languages/states.php:1472
msgid "Burgundy"
msgstr ""
-#: languages/states.php:1448
+#: languages/states.php:1473
msgid "Centre-Val de Loire"
msgstr ""
-#: languages/states.php:1449
+#: languages/states.php:1474
msgid "Champagne-Ardenne"
msgstr ""
-#: languages/states.php:1450
+#: languages/states.php:1475
msgid "Corsica"
msgstr ""
-#: languages/states.php:1451
+#: languages/states.php:1476
msgid "Franche-Comté"
msgstr ""
-#: languages/states.php:1454
+#: languages/states.php:1479
msgid "Grand Est"
msgstr ""
-#: languages/states.php:1456
+#: languages/states.php:1481
msgid "Hauts-de-France"
msgstr ""
-#: languages/states.php:1457
+#: languages/states.php:1482
msgid "Languedoc-Roussillon"
msgstr ""
-#: languages/states.php:1458
+#: languages/states.php:1483
msgid "Limousin"
msgstr ""
-#: languages/states.php:1459
+#: languages/states.php:1484
msgid "Lorraine"
msgstr ""
-#: languages/states.php:1460
+#: languages/states.php:1485
msgid "Lower Normandy"
msgstr ""
-#: languages/states.php:1463
+#: languages/states.php:1488
msgid "Nord-Pas-de-Calais"
msgstr ""
-#: languages/states.php:1464
+#: languages/states.php:1489
msgid "Normandy"
msgstr ""
-#: languages/states.php:1465
+#: languages/states.php:1490
msgid "Nouvelle-Aquitaine"
msgstr ""
-#: languages/states.php:1466
+#: languages/states.php:1491
msgid "Occitania"
msgstr ""
-#: languages/states.php:1467
+#: languages/states.php:1492
msgid "Pays de la Loire"
msgstr ""
-#: languages/states.php:1468
+#: languages/states.php:1493
msgid "Picardy"
msgstr ""
-#: languages/states.php:1469
+#: languages/states.php:1494
msgid "Poitou-Charentes"
msgstr ""
-#: languages/states.php:1470
+#: languages/states.php:1495
msgid "Provence-Alpes-Côte d'Azur"
msgstr ""
-#: languages/states.php:1471
+#: languages/states.php:1496
msgid "Rhône-Alpes"
msgstr ""
-#: languages/states.php:1472
+#: languages/states.php:1497
msgid "Réunion"
msgstr ""
-#: languages/states.php:1473
+#: languages/states.php:1498
msgid "Saint Barthélemy"
msgstr ""
-#: languages/states.php:1474
+#: languages/states.php:1499
msgid "Saint Martin"
msgstr ""
-#: languages/states.php:1476
+#: languages/states.php:1501
msgid "Sigave"
msgstr ""
-#: languages/states.php:1477
+#: languages/states.php:1502
msgid "Upper Normandy"
msgstr ""
-#: languages/states.php:1478
+#: languages/states.php:1503
msgid "Uvea"
msgstr ""
-#: languages/states.php:1479
+#: languages/states.php:1504
msgid "Wallis and Futuna"
msgstr ""
-#: languages/states.php:1480
+#: languages/states.php:1505
msgid "Île-de-France"
msgstr ""
-#: languages/states.php:1483
+#: languages/states.php:1508
msgid "Estuaire Province"
msgstr ""
-#: languages/states.php:1484
+#: languages/states.php:1509
msgid "Haut-Ogooué Province"
msgstr ""
-#: languages/states.php:1485
+#: languages/states.php:1510
msgid "Moyen-Ogooué Province"
msgstr ""
-#: languages/states.php:1486
+#: languages/states.php:1511
msgid "Ngounié Province"
msgstr ""
-#: languages/states.php:1487
+#: languages/states.php:1512
msgid "Nyanga Province"
msgstr ""
-#: languages/states.php:1488
+#: languages/states.php:1513
msgid "Ogooué-Ivindo Province"
msgstr ""
-#: languages/states.php:1489
+#: languages/states.php:1514
msgid "Ogooué-Lolo Province"
msgstr ""
-#: languages/states.php:1490
+#: languages/states.php:1515
msgid "Ogooué-Maritime Province"
msgstr ""
-#: languages/states.php:1491
+#: languages/states.php:1516
msgid "Woleu-Ntem Province"
msgstr ""
-#: languages/states.php:1494
+#: languages/states.php:1519
msgid "Aberdeen"
msgstr ""
-#: languages/states.php:1495
+#: languages/states.php:1520
msgid "Aberdeenshire"
msgstr ""
-#: languages/states.php:1496
+#: languages/states.php:1521
msgid "Angus"
msgstr ""
-#: languages/states.php:1497
+#: languages/states.php:1522
msgid "Antrim"
msgstr ""
-#: languages/states.php:1498
+#: languages/states.php:1523
msgid "Antrim and Newtownabbey"
msgstr ""
-#: languages/states.php:1499
+#: languages/states.php:1524
msgid "Ards"
msgstr ""
-#: languages/states.php:1500
+#: languages/states.php:1525
msgid "Ards and North Down"
msgstr ""
-#: languages/states.php:1501
+#: languages/states.php:1526
msgid "Argyll and Bute"
msgstr ""
-#: languages/states.php:1502
+#: languages/states.php:1527
msgid "Armagh City and District Council"
msgstr ""
-#: languages/states.php:1503
+#: languages/states.php:1528
msgid "Armagh, Banbridge and Craigavon"
msgstr ""
-#: languages/states.php:1504
+#: languages/states.php:1529
msgid "Ascension Island"
msgstr ""
-#: languages/states.php:1505
+#: languages/states.php:1530
msgid "Ballymena Borough"
msgstr ""
-#: languages/states.php:1506
+#: languages/states.php:1531
msgid "Ballymoney"
msgstr ""
-#: languages/states.php:1507
+#: languages/states.php:1532
msgid "Banbridge"
msgstr ""
-#: languages/states.php:1508
+#: languages/states.php:1533
msgid "Barnsley"
msgstr ""
-#: languages/states.php:1509
+#: languages/states.php:1534
msgid "Bath and North East Somerset"
msgstr ""
-#: languages/states.php:1510
+#: languages/states.php:1535
msgid "Bedford"
msgstr ""
-#: languages/states.php:1511
+#: languages/states.php:1536
msgid "Belfast district"
msgstr ""
-#: languages/states.php:1512
+#: languages/states.php:1537
msgid "Birmingham"
msgstr ""
-#: languages/states.php:1513
+#: languages/states.php:1538
msgid "Blackburn with Darwen"
msgstr ""
-#: languages/states.php:1514
+#: languages/states.php:1539
msgid "Blackpool"
msgstr ""
-#: languages/states.php:1515
+#: languages/states.php:1540
msgid "Blaenau Gwent County Borough"
msgstr ""
-#: languages/states.php:1516
+#: languages/states.php:1541
msgid "Bolton"
msgstr ""
-#: languages/states.php:1517
+#: languages/states.php:1542
msgid "Bournemouth"
msgstr ""
-#: languages/states.php:1518
+#: languages/states.php:1543
msgid "Bracknell Forest"
msgstr ""
-#: languages/states.php:1519
+#: languages/states.php:1544
msgid "Bradford"
msgstr ""
-#: languages/states.php:1520
+#: languages/states.php:1545
msgid "Bridgend County Borough"
msgstr ""
-#: languages/states.php:1521
+#: languages/states.php:1546
msgid "Brighton and Hove"
msgstr ""
-#: languages/states.php:1522
+#: languages/states.php:1547
msgid "Buckinghamshire"
msgstr ""
-#: languages/states.php:1523
+#: languages/states.php:1548
msgid "Bury"
msgstr ""
-#: languages/states.php:1524
+#: languages/states.php:1549
msgid "Caerphilly County Borough"
msgstr ""
-#: languages/states.php:1525
+#: languages/states.php:1550
msgid "Calderdale"
msgstr ""
-#: languages/states.php:1526
+#: languages/states.php:1551
msgid "Cambridgeshire"
msgstr ""
-#: languages/states.php:1527
+#: languages/states.php:1552
msgid "Carmarthenshire"
msgstr ""
-#: languages/states.php:1528
+#: languages/states.php:1553
msgid "Carrickfergus Borough Council"
msgstr ""
-#: languages/states.php:1529
+#: languages/states.php:1554
msgid "Castlereagh"
msgstr ""
-#: languages/states.php:1530
+#: languages/states.php:1555
msgid "Causeway Coast and Glens"
msgstr ""
-#: languages/states.php:1531
+#: languages/states.php:1556
msgid "Central Bedfordshire"
msgstr ""
-#: languages/states.php:1532
+#: languages/states.php:1557
msgid "Ceredigion"
msgstr ""
-#: languages/states.php:1533
+#: languages/states.php:1558
msgid "Cheshire East"
msgstr ""
-#: languages/states.php:1534
+#: languages/states.php:1559
msgid "Cheshire West and Chester"
msgstr ""
-#: languages/states.php:1535
+#: languages/states.php:1560
msgid "City and County of Cardiff"
msgstr ""
-#: languages/states.php:1536
+#: languages/states.php:1561
msgid "City and County of Swansea"
msgstr ""
-#: languages/states.php:1537
+#: languages/states.php:1562
msgid "City of Bristol"
msgstr ""
-#: languages/states.php:1538
+#: languages/states.php:1563
msgid "City of Derby"
msgstr ""
-#: languages/states.php:1539
+#: languages/states.php:1564
msgid "City of Kingston upon Hull"
msgstr ""
-#: languages/states.php:1540
+#: languages/states.php:1565
msgid "City of Leicester"
msgstr ""
-#: languages/states.php:1541
+#: languages/states.php:1566
msgid "City of London"
msgstr ""
-#: languages/states.php:1542
+#: languages/states.php:1567
msgid "City of Nottingham"
msgstr ""
-#: languages/states.php:1543
+#: languages/states.php:1568
msgid "City of Peterborough"
msgstr ""
-#: languages/states.php:1544
+#: languages/states.php:1569
msgid "City of Plymouth"
msgstr ""
-#: languages/states.php:1545
+#: languages/states.php:1570
msgid "City of Portsmouth"
msgstr ""
-#: languages/states.php:1546
+#: languages/states.php:1571
msgid "City of Southampton"
msgstr ""
-#: languages/states.php:1547
+#: languages/states.php:1572
msgid "City of Stoke-on-Trent"
msgstr ""
-#: languages/states.php:1548
+#: languages/states.php:1573
msgid "City of Sunderland"
msgstr ""
-#: languages/states.php:1549
+#: languages/states.php:1574
msgid "City of Westminster"
msgstr ""
-#: languages/states.php:1550
+#: languages/states.php:1575
msgid "City of Wolverhampton"
msgstr ""
-#: languages/states.php:1551
+#: languages/states.php:1576
msgid "City of York"
msgstr ""
-#: languages/states.php:1552
+#: languages/states.php:1577
msgid "Clackmannanshire"
msgstr ""
-#: languages/states.php:1553
+#: languages/states.php:1578
msgid "Coleraine Borough Council"
msgstr ""
-#: languages/states.php:1554
+#: languages/states.php:1579
msgid "Conwy County Borough"
msgstr ""
-#: languages/states.php:1555
+#: languages/states.php:1580
msgid "Cookstown District Council"
msgstr ""
-#: languages/states.php:1556
+#: languages/states.php:1581
msgid "Cornwall"
msgstr ""
-#: languages/states.php:1557
+#: languages/states.php:1582
msgid "County Durham"
msgstr ""
-#: languages/states.php:1558
+#: languages/states.php:1583
msgid "Coventry"
msgstr ""
-#: languages/states.php:1559
+#: languages/states.php:1584
msgid "Craigavon Borough Council"
msgstr ""
-#: languages/states.php:1560
+#: languages/states.php:1585
msgid "Cumbria"
msgstr ""
-#: languages/states.php:1561
+#: languages/states.php:1586
msgid "Darlington"
msgstr ""
-#: languages/states.php:1562
+#: languages/states.php:1587
msgid "Denbighshire"
msgstr ""
-#: languages/states.php:1563
+#: languages/states.php:1588
msgid "Derbyshire"
msgstr ""
-#: languages/states.php:1564
+#: languages/states.php:1589
msgid "Derry City Council"
msgstr ""
-#: languages/states.php:1565
+#: languages/states.php:1590
msgid "Derry City and Strabane"
msgstr ""
-#: languages/states.php:1566
+#: languages/states.php:1591
msgid "Devon"
msgstr ""
-#: languages/states.php:1567
+#: languages/states.php:1592
msgid "Doncaster"
msgstr ""
-#: languages/states.php:1568
+#: languages/states.php:1593
msgid "Dorset"
msgstr ""
-#: languages/states.php:1569
+#: languages/states.php:1594
msgid "Down District Council"
msgstr ""
-#: languages/states.php:1570
+#: languages/states.php:1595
msgid "Dudley"
msgstr ""
-#: languages/states.php:1571
+#: languages/states.php:1596
msgid "Dumfries and Galloway"
msgstr ""
-#: languages/states.php:1572
+#: languages/states.php:1597
msgid "Dundee"
msgstr ""
-#: languages/states.php:1573
+#: languages/states.php:1598
msgid "Dungannon and South Tyrone Borough Council"
msgstr ""
-#: languages/states.php:1574
+#: languages/states.php:1599
msgid "East Ayrshire"
msgstr ""
-#: languages/states.php:1575
+#: languages/states.php:1600
msgid "East Dunbartonshire"
msgstr ""
-#: languages/states.php:1576
+#: languages/states.php:1601
msgid "East Lothian"
msgstr ""
-#: languages/states.php:1577
+#: languages/states.php:1602
msgid "East Renfrewshire"
msgstr ""
-#: languages/states.php:1578
+#: languages/states.php:1603
msgid "East Riding of Yorkshire"
msgstr ""
-#: languages/states.php:1579
+#: languages/states.php:1604
msgid "East Sussex"
msgstr ""
-#: languages/states.php:1580
+#: languages/states.php:1605
msgid "Edinburgh"
msgstr ""
-#: languages/states.php:1581
+#: languages/states.php:1606
msgid "England"
msgstr ""
-#: languages/states.php:1582
+#: languages/states.php:1607
msgid "Essex"
msgstr ""
-#: languages/states.php:1583
+#: languages/states.php:1608
msgid "Falkirk"
msgstr ""
-#: languages/states.php:1584
+#: languages/states.php:1609
msgid "Fermanagh District Council"
msgstr ""
-#: languages/states.php:1585
+#: languages/states.php:1610
msgid "Fermanagh and Omagh"
msgstr ""
-#: languages/states.php:1586
+#: languages/states.php:1611
msgid "Fife"
msgstr ""
-#: languages/states.php:1587
+#: languages/states.php:1612
msgid "Flintshire"
msgstr ""
-#: languages/states.php:1588
+#: languages/states.php:1613
msgid "Gateshead"
msgstr ""
-#: languages/states.php:1589
+#: languages/states.php:1614
msgid "Glasgow"
msgstr ""
-#: languages/states.php:1590
+#: languages/states.php:1615
msgid "Gloucestershire"
msgstr ""
-#: languages/states.php:1591
+#: languages/states.php:1616
msgid "Gwynedd"
msgstr ""
-#: languages/states.php:1592
+#: languages/states.php:1617
msgid "Halton"
msgstr ""
-#: languages/states.php:1593
+#: languages/states.php:1618
msgid "Hampshire"
msgstr ""
-#: languages/states.php:1594
+#: languages/states.php:1619
msgid "Hartlepool"
msgstr ""
-#: languages/states.php:1595
+#: languages/states.php:1620
msgid "Herefordshire"
msgstr ""
-#: languages/states.php:1596
+#: languages/states.php:1621
msgid "Hertfordshire"
msgstr ""
-#: languages/states.php:1597
+#: languages/states.php:1622
msgid "Highland"
msgstr ""
-#: languages/states.php:1598
+#: languages/states.php:1623
msgid "Inverclyde"
msgstr ""
-#: languages/states.php:1599
+#: languages/states.php:1624
msgid "Isle of Wight"
msgstr ""
-#: languages/states.php:1600
+#: languages/states.php:1625
msgid "Isles of Scilly"
msgstr ""
-#: languages/states.php:1601
+#: languages/states.php:1626
msgid "Kent"
msgstr ""
-#: languages/states.php:1602
+#: languages/states.php:1627
msgid "Kirklees"
msgstr ""
-#: languages/states.php:1603
+#: languages/states.php:1628
msgid "Knowsley"
msgstr ""
-#: languages/states.php:1604
+#: languages/states.php:1629
msgid "Lancashire"
msgstr ""
-#: languages/states.php:1605
+#: languages/states.php:1630
msgid "Larne Borough Council"
msgstr ""
-#: languages/states.php:1606
+#: languages/states.php:1631
msgid "Leeds"
msgstr ""
-#: languages/states.php:1607
+#: languages/states.php:1632
msgid "Leicestershire"
msgstr ""
-#: languages/states.php:1608
+#: languages/states.php:1633
msgid "Limavady Borough Council"
msgstr ""
-#: languages/states.php:1609
+#: languages/states.php:1634
msgid "Lincolnshire"
msgstr ""
-#: languages/states.php:1610
+#: languages/states.php:1635
msgid "Lisburn City Council"
msgstr ""
-#: languages/states.php:1611
+#: languages/states.php:1636
msgid "Lisburn and Castlereagh"
msgstr ""
-#: languages/states.php:1612
+#: languages/states.php:1637
msgid "Liverpool"
msgstr ""
-#: languages/states.php:1613
+#: languages/states.php:1638
msgid "London Borough of Barking and Dagenham"
msgstr ""
-#: languages/states.php:1614
+#: languages/states.php:1639
msgid "London Borough of Barnet"
msgstr ""
-#: languages/states.php:1615
+#: languages/states.php:1640
msgid "London Borough of Bexley"
msgstr ""
-#: languages/states.php:1616
+#: languages/states.php:1641
msgid "London Borough of Brent"
msgstr ""
-#: languages/states.php:1617
+#: languages/states.php:1642
msgid "London Borough of Bromley"
msgstr ""
-#: languages/states.php:1618
+#: languages/states.php:1643
msgid "London Borough of Camden"
msgstr ""
-#: languages/states.php:1619
+#: languages/states.php:1644
msgid "London Borough of Croydon"
msgstr ""
-#: languages/states.php:1620
+#: languages/states.php:1645
msgid "London Borough of Ealing"
msgstr ""
-#: languages/states.php:1621
+#: languages/states.php:1646
msgid "London Borough of Enfield"
msgstr ""
-#: languages/states.php:1622
+#: languages/states.php:1647
msgid "London Borough of Hackney"
msgstr ""
-#: languages/states.php:1623
+#: languages/states.php:1648
msgid "London Borough of Hammersmith and Fulham"
msgstr ""
-#: languages/states.php:1624
+#: languages/states.php:1649
msgid "London Borough of Haringey"
msgstr ""
-#: languages/states.php:1625
+#: languages/states.php:1650
msgid "London Borough of Harrow"
msgstr ""
-#: languages/states.php:1626
+#: languages/states.php:1651
msgid "London Borough of Havering"
msgstr ""
-#: languages/states.php:1627
+#: languages/states.php:1652
msgid "London Borough of Hillingdon"
msgstr ""
-#: languages/states.php:1628
+#: languages/states.php:1653
msgid "London Borough of Hounslow"
msgstr ""
-#: languages/states.php:1629
+#: languages/states.php:1654
msgid "London Borough of Islington"
msgstr ""
-#: languages/states.php:1630
+#: languages/states.php:1655
msgid "London Borough of Lambeth"
msgstr ""
-#: languages/states.php:1631
+#: languages/states.php:1656
msgid "London Borough of Lewisham"
msgstr ""
-#: languages/states.php:1632
+#: languages/states.php:1657
msgid "London Borough of Merton"
msgstr ""
-#: languages/states.php:1633
+#: languages/states.php:1658
msgid "London Borough of Newham"
msgstr ""
-#: languages/states.php:1634
+#: languages/states.php:1659
msgid "London Borough of Redbridge"
msgstr ""
-#: languages/states.php:1635
+#: languages/states.php:1660
msgid "London Borough of Richmond upon Thames"
msgstr ""
-#: languages/states.php:1636
+#: languages/states.php:1661
msgid "London Borough of Southwark"
msgstr ""
-#: languages/states.php:1637
+#: languages/states.php:1662
msgid "London Borough of Sutton"
msgstr ""
-#: languages/states.php:1638
+#: languages/states.php:1663
msgid "London Borough of Tower Hamlets"
msgstr ""
-#: languages/states.php:1639
+#: languages/states.php:1664
msgid "London Borough of Waltham Forest"
msgstr ""
-#: languages/states.php:1640
+#: languages/states.php:1665
msgid "London Borough of Wandsworth"
msgstr ""
-#: languages/states.php:1641
+#: languages/states.php:1666
msgid "Magherafelt District Council"
msgstr ""
-#: languages/states.php:1642
+#: languages/states.php:1667
msgid "Manchester"
msgstr ""
-#: languages/states.php:1643
+#: languages/states.php:1668
msgid "Medway"
msgstr ""
-#: languages/states.php:1644
+#: languages/states.php:1669
msgid "Merthyr Tydfil County Borough"
msgstr ""
-#: languages/states.php:1645
+#: languages/states.php:1670
msgid "Metropolitan Borough of Wigan"
msgstr ""
-#: languages/states.php:1646
+#: languages/states.php:1671
msgid "Mid Ulster"
msgstr ""
-#: languages/states.php:1647
+#: languages/states.php:1672
msgid "Mid and East Antrim"
msgstr ""
-#: languages/states.php:1648
+#: languages/states.php:1673
msgid "Middlesbrough"
msgstr ""
-#: languages/states.php:1649
+#: languages/states.php:1674
msgid "Midlothian"
msgstr ""
-#: languages/states.php:1650
+#: languages/states.php:1675
msgid "Milton Keynes"
msgstr ""
-#: languages/states.php:1651
+#: languages/states.php:1676
msgid "Monmouthshire"
msgstr ""
-#: languages/states.php:1652
+#: languages/states.php:1677
msgid "Moray"
msgstr ""
-#: languages/states.php:1653
+#: languages/states.php:1678
msgid "Moyle District Council"
msgstr ""
-#: languages/states.php:1654
+#: languages/states.php:1679
msgid "Neath Port Talbot County Borough"
msgstr ""
-#: languages/states.php:1655
+#: languages/states.php:1680
msgid "Newcastle upon Tyne"
msgstr ""
-#: languages/states.php:1656
+#: languages/states.php:1681
msgid "Newport"
msgstr ""
-#: languages/states.php:1657
+#: languages/states.php:1682
msgid "Newry and Mourne District Council"
msgstr ""
-#: languages/states.php:1658
+#: languages/states.php:1683
msgid "Newry, Mourne and Down"
msgstr ""
-#: languages/states.php:1659
+#: languages/states.php:1684
msgid "Newtownabbey Borough Council"
msgstr ""
-#: languages/states.php:1660
+#: languages/states.php:1685
msgid "Norfolk"
msgstr ""
-#: languages/states.php:1661
+#: languages/states.php:1686
msgid "North Ayrshire"
msgstr ""
-#: languages/states.php:1662
+#: languages/states.php:1687
msgid "North Down Borough Council"
msgstr ""
-#: languages/states.php:1663
+#: languages/states.php:1688
msgid "North East Lincolnshire"
msgstr ""
-#: languages/states.php:1664
+#: languages/states.php:1689
msgid "North Lanarkshire"
msgstr ""
-#: languages/states.php:1665
+#: languages/states.php:1690
msgid "North Lincolnshire"
msgstr ""
-#: languages/states.php:1666
+#: languages/states.php:1691
msgid "North Somerset"
msgstr ""
-#: languages/states.php:1667
+#: languages/states.php:1692
msgid "North Tyneside"
msgstr ""
-#: languages/states.php:1668
+#: languages/states.php:1693
msgid "North Yorkshire"
msgstr ""
-#: languages/states.php:1669
+#: languages/states.php:1694
msgid "Northamptonshire"
msgstr ""
-#: languages/states.php:1670
+#: languages/states.php:1695
msgid "Northern Ireland"
msgstr ""
-#: languages/states.php:1671
+#: languages/states.php:1696
msgid "Northumberland"
msgstr ""
-#: languages/states.php:1672
+#: languages/states.php:1697
msgid "Nottinghamshire"
msgstr ""
-#: languages/states.php:1673
+#: languages/states.php:1698
msgid "Oldham"
msgstr ""
-#: languages/states.php:1674
+#: languages/states.php:1699
msgid "Omagh District Council"
msgstr ""
-#: languages/states.php:1675
+#: languages/states.php:1700
msgid "Orkney Islands"
msgstr ""
-#: languages/states.php:1676
+#: languages/states.php:1701
msgid "Outer Hebrides"
msgstr ""
-#: languages/states.php:1677
+#: languages/states.php:1702
msgid "Oxfordshire"
msgstr ""
-#: languages/states.php:1678
+#: languages/states.php:1703
msgid "Pembrokeshire"
msgstr ""
-#: languages/states.php:1679
+#: languages/states.php:1704
msgid "Perth and Kinross"
msgstr ""
-#: languages/states.php:1680
+#: languages/states.php:1705
msgid "Poole"
msgstr ""
-#: languages/states.php:1681
+#: languages/states.php:1706
msgid "Powys"
msgstr ""
-#: languages/states.php:1682
+#: languages/states.php:1707
msgid "Reading"
msgstr ""
-#: languages/states.php:1683
+#: languages/states.php:1708
msgid "Redcar and Cleveland"
msgstr ""
-#: languages/states.php:1684
+#: languages/states.php:1709
msgid "Renfrewshire"
msgstr ""
-#: languages/states.php:1685
+#: languages/states.php:1710
msgid "Rhondda Cynon Taf"
msgstr ""
-#: languages/states.php:1686
+#: languages/states.php:1711
msgid "Rochdale"
msgstr ""
-#: languages/states.php:1687
+#: languages/states.php:1712
msgid "Rotherham"
msgstr ""
-#: languages/states.php:1688
+#: languages/states.php:1713
msgid "Royal Borough of Greenwich"
msgstr ""
-#: languages/states.php:1689
+#: languages/states.php:1714
msgid "Royal Borough of Kensington and Chelsea"
msgstr ""
-#: languages/states.php:1690
+#: languages/states.php:1715
msgid "Royal Borough of Kingston upon Thames"
msgstr ""
-#: languages/states.php:1691
+#: languages/states.php:1716
msgid "Rutland"
msgstr ""
-#: languages/states.php:1693
+#: languages/states.php:1718
msgid "Salford"
msgstr ""
-#: languages/states.php:1694
+#: languages/states.php:1719
msgid "Sandwell"
msgstr ""
-#: languages/states.php:1695
+#: languages/states.php:1720
msgid "Scotland"
msgstr ""
-#: languages/states.php:1696
+#: languages/states.php:1721
msgid "Scottish Borders"
msgstr ""
-#: languages/states.php:1697
+#: languages/states.php:1722
msgid "Sefton"
msgstr ""
-#: languages/states.php:1698
+#: languages/states.php:1723
msgid "Sheffield"
msgstr ""
-#: languages/states.php:1699
+#: languages/states.php:1724
msgid "Shetland Islands"
msgstr ""
-#: languages/states.php:1700
+#: languages/states.php:1725
msgid "Shropshire"
msgstr ""
-#: languages/states.php:1701
+#: languages/states.php:1726
msgid "Slough"
msgstr ""
-#: languages/states.php:1702
+#: languages/states.php:1727
msgid "Solihull"
msgstr ""
-#: languages/states.php:1703
+#: languages/states.php:1728
msgid "Somerset"
msgstr ""
-#: languages/states.php:1704
+#: languages/states.php:1729
msgid "South Ayrshire"
msgstr ""
-#: languages/states.php:1705
+#: languages/states.php:1730
msgid "South Gloucestershire"
msgstr ""
-#: languages/states.php:1706
+#: languages/states.php:1731
msgid "South Lanarkshire"
msgstr ""
-#: languages/states.php:1707
+#: languages/states.php:1732
msgid "South Tyneside"
msgstr ""
-#: languages/states.php:1708
+#: languages/states.php:1733
msgid "Southend-on-Sea"
msgstr ""
-#: languages/states.php:1709
+#: languages/states.php:1734
msgid "St Helens"
msgstr ""
-#: languages/states.php:1710
+#: languages/states.php:1735
msgid "Staffordshire"
msgstr ""
-#: languages/states.php:1711
+#: languages/states.php:1736
msgid "Stirling"
msgstr ""
-#: languages/states.php:1712
+#: languages/states.php:1737
msgid "Stockport"
msgstr ""
-#: languages/states.php:1713
+#: languages/states.php:1738
msgid "Stockton-on-Tees"
msgstr ""
-#: languages/states.php:1714
+#: languages/states.php:1739
msgid "Strabane District Council"
msgstr ""
-#: languages/states.php:1715
+#: languages/states.php:1740
msgid "Suffolk"
msgstr ""
-#: languages/states.php:1716
+#: languages/states.php:1741
msgid "Surrey"
msgstr ""
-#: languages/states.php:1717
+#: languages/states.php:1742
msgid "Swindon"
msgstr ""
-#: languages/states.php:1718
+#: languages/states.php:1743
msgid "Tameside"
msgstr ""
-#: languages/states.php:1719
+#: languages/states.php:1744
msgid "Telford and Wrekin"
msgstr ""
-#: languages/states.php:1720
+#: languages/states.php:1745
msgid "Thurrock"
msgstr ""
-#: languages/states.php:1721
+#: languages/states.php:1746
msgid "Torbay"
msgstr ""
-#: languages/states.php:1722
+#: languages/states.php:1747
msgid "Torfaen"
msgstr ""
-#: languages/states.php:1723
+#: languages/states.php:1748
msgid "Trafford"
msgstr ""
-#: languages/states.php:1725
+#: languages/states.php:1750
msgid "Vale of Glamorgan"
msgstr ""
-#: languages/states.php:1726
+#: languages/states.php:1751
msgid "Wakefield"
msgstr ""
-#: languages/states.php:1727
+#: languages/states.php:1752
msgid "Wales"
msgstr ""
-#: languages/states.php:1728
+#: languages/states.php:1753
msgid "Walsall"
msgstr ""
-#: languages/states.php:1729
+#: languages/states.php:1754
msgid "Warrington"
msgstr ""
-#: languages/states.php:1730
+#: languages/states.php:1755
msgid "Warwickshire"
msgstr ""
-#: languages/states.php:1731
+#: languages/states.php:1756
msgid "West Berkshire"
msgstr ""
-#: languages/states.php:1732
+#: languages/states.php:1757
msgid "West Dunbartonshire"
msgstr ""
-#: languages/states.php:1733
+#: languages/states.php:1758
msgid "West Lothian"
msgstr ""
-#: languages/states.php:1734
+#: languages/states.php:1759
msgid "West Sussex"
msgstr ""
-#: languages/states.php:1735
+#: languages/states.php:1760
msgid "Wiltshire"
msgstr ""
-#: languages/states.php:1736
+#: languages/states.php:1761
msgid "Windsor and Maidenhead"
msgstr ""
-#: languages/states.php:1737
+#: languages/states.php:1762
msgid "Wirral"
msgstr ""
-#: languages/states.php:1738
+#: languages/states.php:1763
msgid "Wokingham"
msgstr ""
-#: languages/states.php:1739
+#: languages/states.php:1764
msgid "Worcestershire"
msgstr ""
-#: languages/states.php:1740
+#: languages/states.php:1765
msgid "Wrexham County Borough"
msgstr ""
-#: languages/states.php:1743
+#: languages/states.php:1768
msgid "Carriacou and Petite Martinique"
msgstr ""
-#: languages/states.php:1752
+#: languages/states.php:1777
msgid "Khelvachauri Municipality"
msgstr ""
-#: languages/states.php:1753
+#: languages/states.php:1778
msgid "Senaki Municipality"
msgstr ""
-#: languages/states.php:1754
+#: languages/states.php:1779
msgid "Adjara"
msgstr ""
-#: languages/states.php:1755
+#: languages/states.php:1780
msgid "Autonomous Republic of Abkhazia"
msgstr ""
-#: languages/states.php:1756
+#: languages/states.php:1781
msgid "Guria"
msgstr ""
-#: languages/states.php:1757
+#: languages/states.php:1782
msgid "Imereti"
msgstr ""
-#: languages/states.php:1758
+#: languages/states.php:1783
msgid "Kakheti"
msgstr ""
-#: languages/states.php:1759
+#: languages/states.php:1784
msgid "Kvemo Kartli"
msgstr ""
-#: languages/states.php:1760
+#: languages/states.php:1785
msgid "Mtskheta-Mtianeti"
msgstr ""
-#: languages/states.php:1761
+#: languages/states.php:1786
msgid "Racha-Lechkhumi and Kvemo Svaneti"
msgstr ""
-#: languages/states.php:1762
+#: languages/states.php:1787
msgid "Samegrelo-Zemo Svaneti"
msgstr ""
-#: languages/states.php:1763
+#: languages/states.php:1788
msgid "Samtskhe-Javakheti"
msgstr ""
-#: languages/states.php:1764
+#: languages/states.php:1789
msgid "Shida Kartli"
msgstr ""
-#: languages/states.php:1765
+#: languages/states.php:1790
msgid "Tbilisi"
msgstr ""
-#: languages/states.php:1770
+#: languages/states.php:1795
msgid "Ashanti Region"
msgstr ""
-#: languages/states.php:1771
+#: languages/states.php:1796
msgid "Brong-Ahafo Region"
msgstr ""
-#: languages/states.php:1772
-#: languages/states.php:3384
-#: languages/states.php:3602
-#: languages/states.php:5039
+#: languages/states.php:1797
+#: languages/states.php:3409
+#: languages/states.php:3627
+#: languages/states.php:5064
msgid "Central Region"
msgstr ""
-#: languages/states.php:1773
-#: languages/states.php:2233
-#: languages/states.php:5040
+#: languages/states.php:1798
+#: languages/states.php:2258
+#: languages/states.php:5065
msgid "Eastern Region"
msgstr ""
-#: languages/states.php:1774
+#: languages/states.php:1799
msgid "Greater Accra Region"
msgstr ""
-#: languages/states.php:1775
-#: languages/states.php:3402
-#: languages/states.php:5041
+#: languages/states.php:1800
+#: languages/states.php:3427
+#: languages/states.php:5066
msgid "Northern Region"
msgstr ""
-#: languages/states.php:1776
+#: languages/states.php:1801
msgid "Upper East Region"
msgstr ""
-#: languages/states.php:1777
+#: languages/states.php:1802
msgid "Upper West Region"
msgstr ""
-#: languages/states.php:1778
+#: languages/states.php:1803
msgid "Volta Region"
msgstr ""
-#: languages/states.php:1779
-#: languages/states.php:2229
-#: languages/states.php:3604
-#: languages/states.php:5042
+#: languages/states.php:1804
+#: languages/states.php:2254
+#: languages/states.php:3629
+#: languages/states.php:5067
msgid "Western Region"
msgstr ""
-#: languages/states.php:1784
+#: languages/states.php:1809
msgid "Banjul"
msgstr ""
-#: languages/states.php:1785
+#: languages/states.php:1810
msgid "Central River Division"
msgstr ""
-#: languages/states.php:1786
+#: languages/states.php:1811
msgid "Lower River Division"
msgstr ""
-#: languages/states.php:1787
+#: languages/states.php:1812
msgid "North Bank Division"
msgstr ""
-#: languages/states.php:1788
+#: languages/states.php:1813
msgid "Upper River Division"
msgstr ""
-#: languages/states.php:1789
+#: languages/states.php:1814
msgid "West Coast Division"
msgstr ""
-#: languages/states.php:1792
+#: languages/states.php:1817
msgid "Beyla Prefecture"
msgstr ""
-#: languages/states.php:1793
+#: languages/states.php:1818
msgid "Boffa Prefecture"
msgstr ""
-#: languages/states.php:1794
+#: languages/states.php:1819
msgid "Boké Prefecture"
msgstr ""
-#: languages/states.php:1795
+#: languages/states.php:1820
msgid "Boké Region"
msgstr ""
-#: languages/states.php:1796
+#: languages/states.php:1821
msgid "Conakry"
msgstr ""
-#: languages/states.php:1797
+#: languages/states.php:1822
msgid "Coyah Prefecture"
msgstr ""
-#: languages/states.php:1798
+#: languages/states.php:1823
msgid "Dabola Prefecture"
msgstr ""
-#: languages/states.php:1799
+#: languages/states.php:1824
msgid "Dalaba Prefecture"
msgstr ""
-#: languages/states.php:1800
+#: languages/states.php:1825
msgid "Dinguiraye Prefecture"
msgstr ""
-#: languages/states.php:1801
+#: languages/states.php:1826
msgid "Dubréka Prefecture"
msgstr ""
-#: languages/states.php:1802
+#: languages/states.php:1827
msgid "Faranah Prefecture"
msgstr ""
-#: languages/states.php:1803
+#: languages/states.php:1828
msgid "Forécariah Prefecture"
msgstr ""
-#: languages/states.php:1804
+#: languages/states.php:1829
msgid "Fria Prefecture"
msgstr ""
-#: languages/states.php:1805
+#: languages/states.php:1830
msgid "Gaoual Prefecture"
msgstr ""
-#: languages/states.php:1806
+#: languages/states.php:1831
msgid "Guéckédou Prefecture"
msgstr ""
-#: languages/states.php:1807
+#: languages/states.php:1832
msgid "Kankan Prefecture"
msgstr ""
-#: languages/states.php:1808
+#: languages/states.php:1833
msgid "Kankan Region"
msgstr ""
-#: languages/states.php:1809
+#: languages/states.php:1834
msgid "Kindia Prefecture"
msgstr ""
-#: languages/states.php:1810
+#: languages/states.php:1835
msgid "Kindia Region"
msgstr ""
-#: languages/states.php:1811
+#: languages/states.php:1836
msgid "Kissidougou Prefecture"
msgstr ""
-#: languages/states.php:1812
+#: languages/states.php:1837
msgid "Koubia Prefecture"
msgstr ""
-#: languages/states.php:1813
+#: languages/states.php:1838
msgid "Koundara Prefecture"
msgstr ""
-#: languages/states.php:1814
+#: languages/states.php:1839
msgid "Kouroussa Prefecture"
msgstr ""
-#: languages/states.php:1815
+#: languages/states.php:1840
msgid "Kérouané Prefecture"
msgstr ""
-#: languages/states.php:1816
+#: languages/states.php:1841
msgid "Labé Prefecture"
msgstr ""
-#: languages/states.php:1817
+#: languages/states.php:1842
msgid "Labé Region"
msgstr ""
-#: languages/states.php:1818
+#: languages/states.php:1843
msgid "Lola Prefecture"
msgstr ""
-#: languages/states.php:1819
+#: languages/states.php:1844
msgid "Lélouma Prefecture"
msgstr ""
-#: languages/states.php:1820
+#: languages/states.php:1845
msgid "Macenta Prefecture"
msgstr ""
-#: languages/states.php:1821
+#: languages/states.php:1846
msgid "Mali Prefecture"
msgstr ""
-#: languages/states.php:1822
+#: languages/states.php:1847
msgid "Mamou Prefecture"
msgstr ""
-#: languages/states.php:1823
+#: languages/states.php:1848
msgid "Mamou Region"
msgstr ""
-#: languages/states.php:1824
+#: languages/states.php:1849
msgid "Mandiana Prefecture"
msgstr ""
-#: languages/states.php:1825
+#: languages/states.php:1850
msgid "Nzérékoré Prefecture"
msgstr ""
-#: languages/states.php:1826
+#: languages/states.php:1851
msgid "Nzérékoré Region"
msgstr ""
-#: languages/states.php:1827
+#: languages/states.php:1852
msgid "Pita Prefecture"
msgstr ""
-#: languages/states.php:1828
+#: languages/states.php:1853
msgid "Siguiri Prefecture"
msgstr ""
-#: languages/states.php:1829
+#: languages/states.php:1854
msgid "Tougué Prefecture"
msgstr ""
-#: languages/states.php:1830
+#: languages/states.php:1855
msgid "Télimélé Prefecture"
msgstr ""
-#: languages/states.php:1831
+#: languages/states.php:1856
msgid "Yomou Prefecture"
msgstr ""
-#: languages/states.php:1835
+#: languages/states.php:1860
msgid "Annobón Province"
msgstr ""
-#: languages/states.php:1836
+#: languages/states.php:1861
msgid "Bioko Norte Province"
msgstr ""
-#: languages/states.php:1837
+#: languages/states.php:1862
msgid "Bioko Sur Province"
msgstr ""
-#: languages/states.php:1838
+#: languages/states.php:1863
msgid "Centro Sur Province"
msgstr ""
-#: languages/states.php:1839
+#: languages/states.php:1864
msgid "Insular Region"
msgstr ""
-#: languages/states.php:1840
+#: languages/states.php:1865
msgid "Kié-Ntem Province"
msgstr ""
-#: languages/states.php:1841
+#: languages/states.php:1866
msgid "Litoral Province"
msgstr ""
-#: languages/states.php:1842
+#: languages/states.php:1867
msgid "Río Muni"
msgstr ""
-#: languages/states.php:1843
+#: languages/states.php:1868
msgid "Wele-Nzas Province"
msgstr ""
-#: languages/states.php:1846
+#: languages/states.php:1871
msgid "Argolis Regional Unit"
msgstr ""
-#: languages/states.php:1847
+#: languages/states.php:1872
msgid "Arcadia Prefecture"
msgstr ""
-#: languages/states.php:1848
+#: languages/states.php:1873
msgid "Achaea Regional Unit"
msgstr ""
-#: languages/states.php:1849
+#: languages/states.php:1874
msgid "Corinthia Regional Unit"
msgstr ""
-#: languages/states.php:1850
+#: languages/states.php:1875
msgid "Laconia"
msgstr ""
-#: languages/states.php:1851
+#: languages/states.php:1876
msgid "Corfu Prefecture"
msgstr ""
-#: languages/states.php:1852
+#: languages/states.php:1877
msgid "Kefalonia Prefecture"
msgstr ""
-#: languages/states.php:1853
+#: languages/states.php:1878
msgid "Lefkada Regional Unit"
msgstr ""
-#: languages/states.php:1854
+#: languages/states.php:1879
msgid "Ioannina Regional Unit"
msgstr ""
-#: languages/states.php:1855
+#: languages/states.php:1880
msgid "Preveza Prefecture"
msgstr ""
-#: languages/states.php:1856
+#: languages/states.php:1881
msgid "Karditsa Regional Unit"
msgstr ""
-#: languages/states.php:1857
+#: languages/states.php:1882
msgid "Larissa Prefecture"
msgstr ""
-#: languages/states.php:1858
+#: languages/states.php:1883
msgid "Grevena Prefecture"
msgstr ""
-#: languages/states.php:1859
+#: languages/states.php:1884
msgid "Drama Regional Unit"
msgstr ""
-#: languages/states.php:1860
+#: languages/states.php:1885
msgid "Imathia Regional Unit"
msgstr ""
-#: languages/states.php:1861
+#: languages/states.php:1886
msgid "Thessaloniki Regional Unit"
msgstr ""
-#: languages/states.php:1862
+#: languages/states.php:1887
msgid "Kastoria Regional Unit"
msgstr ""
-#: languages/states.php:1863
+#: languages/states.php:1888
msgid "Kilkis Regional Unit"
msgstr ""
-#: languages/states.php:1864
+#: languages/states.php:1889
msgid "Kozani Prefecture"
msgstr ""
-#: languages/states.php:1865
+#: languages/states.php:1890
msgid "Pella Regional Unit"
msgstr ""
-#: languages/states.php:1866
+#: languages/states.php:1891
msgid "Serres Prefecture"
msgstr ""
-#: languages/states.php:1867
+#: languages/states.php:1892
msgid "Chania Regional Unit"
msgstr ""
-#: languages/states.php:1868
+#: languages/states.php:1893
msgid "Aetolia-Acarnania Regional Unit"
msgstr ""
-#: languages/states.php:1869
+#: languages/states.php:1894
msgid "Attica Region"
msgstr ""
-#: languages/states.php:1870
+#: languages/states.php:1895
msgid "Boeotia Regional Unit"
msgstr ""
-#: languages/states.php:1871
+#: languages/states.php:1896
msgid "Central Greece Region"
msgstr ""
-#: languages/states.php:1872
+#: languages/states.php:1897
msgid "Central Macedonia"
msgstr ""
-#: languages/states.php:1873
+#: languages/states.php:1898
msgid "Crete Region"
msgstr ""
-#: languages/states.php:1874
+#: languages/states.php:1899
msgid "East Attica Regional Unit"
msgstr ""
-#: languages/states.php:1875
+#: languages/states.php:1900
msgid "East Macedonia and Thrace"
msgstr ""
-#: languages/states.php:1876
+#: languages/states.php:1901
msgid "Epirus Region"
msgstr ""
-#: languages/states.php:1877
+#: languages/states.php:1902
msgid "Euboea"
msgstr ""
-#: languages/states.php:1878
+#: languages/states.php:1903
msgid "Ionian Islands Region"
msgstr ""
-#: languages/states.php:1879
+#: languages/states.php:1904
msgid "Peloponnese Region"
msgstr ""
-#: languages/states.php:1880
+#: languages/states.php:1905
msgid "Phthiotis Prefecture"
msgstr ""
-#: languages/states.php:1881
+#: languages/states.php:1906
msgid "South Aegean"
msgstr ""
-#: languages/states.php:1882
+#: languages/states.php:1907
msgid "West Greece Region"
msgstr ""
-#: languages/states.php:1883
+#: languages/states.php:1908
msgid "West Macedonia Region"
msgstr ""
-#: languages/states.php:1887
+#: languages/states.php:1912
msgid "Alta Verapaz Department"
msgstr ""
-#: languages/states.php:1888
+#: languages/states.php:1913
msgid "Baja Verapaz Department"
msgstr ""
-#: languages/states.php:1889
+#: languages/states.php:1914
msgid "Chimaltenango Department"
msgstr ""
-#: languages/states.php:1890
+#: languages/states.php:1915
msgid "Chiquimula Department"
msgstr ""
-#: languages/states.php:1891
+#: languages/states.php:1916
msgid "El Progreso Department"
msgstr ""
-#: languages/states.php:1892
+#: languages/states.php:1917
msgid "Escuintla Department"
msgstr ""
-#: languages/states.php:1893
+#: languages/states.php:1918
msgid "Guatemala Department"
msgstr ""
-#: languages/states.php:1894
+#: languages/states.php:1919
msgid "Huehuetenango Department"
msgstr ""
-#: languages/states.php:1895
+#: languages/states.php:1920
msgid "Izabal Department"
msgstr ""
-#: languages/states.php:1896
+#: languages/states.php:1921
msgid "Jalapa Department"
msgstr ""
-#: languages/states.php:1897
+#: languages/states.php:1922
msgid "Jutiapa Department"
msgstr ""
-#: languages/states.php:1898
+#: languages/states.php:1923
msgid "Petén Department"
msgstr ""
-#: languages/states.php:1899
+#: languages/states.php:1924
msgid "Quetzaltenango Department"
msgstr ""
-#: languages/states.php:1900
+#: languages/states.php:1925
msgid "Quiché Department"
msgstr ""
-#: languages/states.php:1901
+#: languages/states.php:1926
msgid "Retalhuleu Department"
msgstr ""
-#: languages/states.php:1902
+#: languages/states.php:1927
msgid "Sacatepéquez Department"
msgstr ""
-#: languages/states.php:1903
+#: languages/states.php:1928
msgid "San Marcos Department"
msgstr ""
-#: languages/states.php:1904
+#: languages/states.php:1929
msgid "Santa Rosa Department"
msgstr ""
-#: languages/states.php:1905
+#: languages/states.php:1930
msgid "Sololá Department"
msgstr ""
-#: languages/states.php:1906
+#: languages/states.php:1931
msgid "Suchitepéquez Department"
msgstr ""
-#: languages/states.php:1907
+#: languages/states.php:1932
msgid "Totonicapán Department"
msgstr ""
-#: languages/states.php:1911
+#: languages/states.php:1936
msgid "Bafatá"
msgstr ""
-#: languages/states.php:1912
+#: languages/states.php:1937
msgid "Biombo Region"
msgstr ""
-#: languages/states.php:1913
+#: languages/states.php:1938
msgid "Bolama Region"
msgstr ""
-#: languages/states.php:1914
+#: languages/states.php:1939
msgid "Cacheu Region"
msgstr ""
-#: languages/states.php:1915
+#: languages/states.php:1940
msgid "Gabú Region"
msgstr ""
-#: languages/states.php:1916
+#: languages/states.php:1941
msgid "Leste Province"
msgstr ""
-#: languages/states.php:1917
+#: languages/states.php:1942
msgid "Norte Province"
msgstr ""
-#: languages/states.php:1918
+#: languages/states.php:1943
msgid "Oio Region"
msgstr ""
-#: languages/states.php:1919
+#: languages/states.php:1944
msgid "Quinara Region"
msgstr ""
-#: languages/states.php:1920
+#: languages/states.php:1945
msgid "Sul Province"
msgstr ""
-#: languages/states.php:1921
+#: languages/states.php:1946
msgid "Tombali Region"
msgstr ""
-#: languages/states.php:1924
+#: languages/states.php:1949
msgid "Barima-Waini"
msgstr ""
-#: languages/states.php:1925
+#: languages/states.php:1950
msgid "Cuyuni-Mazaruni"
msgstr ""
-#: languages/states.php:1926
+#: languages/states.php:1951
msgid "Demerara-Mahaica"
msgstr ""
-#: languages/states.php:1927
+#: languages/states.php:1952
msgid "East Berbice-Corentyne"
msgstr ""
-#: languages/states.php:1928
+#: languages/states.php:1953
msgid "Essequibo Islands-West Demerara"
msgstr ""
-#: languages/states.php:1929
+#: languages/states.php:1954
msgid "Mahaica-Berbice"
msgstr ""
-#: languages/states.php:1930
+#: languages/states.php:1955
msgid "Pomeroon-Supenaam"
msgstr ""
-#: languages/states.php:1931
+#: languages/states.php:1956
msgid "Potaro-Siparuni"
msgstr ""
-#: languages/states.php:1932
+#: languages/states.php:1957
msgid "Upper Demerara-Berbice"
msgstr ""
-#: languages/states.php:1933
+#: languages/states.php:1958
msgid "Upper Takutu-Upper Essequibo"
msgstr ""
-#: languages/states.php:1936
+#: languages/states.php:1961
msgid "Central and Western District"
msgstr ""
-#: languages/states.php:1937
+#: languages/states.php:1962
msgid "Eastern"
msgstr ""
-#: languages/states.php:1938
+#: languages/states.php:1963
msgid "Islands District"
msgstr ""
-#: languages/states.php:1939
+#: languages/states.php:1964
msgid "Kowloon City"
msgstr ""
-#: languages/states.php:1940
+#: languages/states.php:1965
msgid "Kwai Tsing"
msgstr ""
-#: languages/states.php:1941
+#: languages/states.php:1966
msgid "Kwun Tong"
msgstr ""
-#: languages/states.php:1943
+#: languages/states.php:1968
msgid "Sai Kung District"
msgstr ""
-#: languages/states.php:1944
+#: languages/states.php:1969
msgid "Sha Tin"
msgstr ""
-#: languages/states.php:1945
+#: languages/states.php:1970
msgid "Sham Shui Po"
msgstr ""
-#: languages/states.php:1946
+#: languages/states.php:1971
msgid "Southern"
msgstr ""
-#: languages/states.php:1947
+#: languages/states.php:1972
msgid "Tai Po District"
msgstr ""
-#: languages/states.php:1948
+#: languages/states.php:1973
msgid "Tsuen Wan District"
msgstr ""
-#: languages/states.php:1949
+#: languages/states.php:1974
msgid "Tuen Mun"
msgstr ""
-#: languages/states.php:1950
+#: languages/states.php:1975
msgid "Wan Chai"
msgstr ""
-#: languages/states.php:1951
+#: languages/states.php:1976
msgid "Wong Tai Sin"
msgstr ""
-#: languages/states.php:1952
+#: languages/states.php:1977
msgid "Yau Tsim Mong"
msgstr ""
-#: languages/states.php:1953
+#: languages/states.php:1978
msgid "Yuen Long District"
msgstr ""
-#: languages/states.php:1957
+#: languages/states.php:1982
msgid "Atlántida Department"
msgstr ""
-#: languages/states.php:1958
+#: languages/states.php:1983
msgid "Bay Islands Department"
msgstr ""
-#: languages/states.php:1959
+#: languages/states.php:1984
msgid "Choluteca Department"
msgstr ""
-#: languages/states.php:1960
+#: languages/states.php:1985
msgid "Colón Department"
msgstr ""
-#: languages/states.php:1961
+#: languages/states.php:1986
msgid "Comayagua Department"
msgstr ""
-#: languages/states.php:1962
+#: languages/states.php:1987
msgid "Copán Department"
msgstr ""
-#: languages/states.php:1963
+#: languages/states.php:1988
msgid "Cortés Department"
msgstr ""
-#: languages/states.php:1964
+#: languages/states.php:1989
msgid "El Paraíso Department"
msgstr ""
-#: languages/states.php:1965
+#: languages/states.php:1990
msgid "Francisco Morazán Department"
msgstr ""
-#: languages/states.php:1966
+#: languages/states.php:1991
msgid "Gracias a Dios Department"
msgstr ""
-#: languages/states.php:1967
+#: languages/states.php:1992
msgid "Intibucá Department"
msgstr ""
-#: languages/states.php:1969
+#: languages/states.php:1994
msgid "Lempira Department"
msgstr ""
-#: languages/states.php:1970
+#: languages/states.php:1995
msgid "Ocotepeque Department"
msgstr ""
-#: languages/states.php:1971
+#: languages/states.php:1996
msgid "Olancho Department"
msgstr ""
-#: languages/states.php:1972
+#: languages/states.php:1997
msgid "Santa Bárbara Department"
msgstr ""
-#: languages/states.php:1973
+#: languages/states.php:1998
msgid "Valle Department"
msgstr ""
-#: languages/states.php:1974
+#: languages/states.php:1999
msgid "Yoro Department"
msgstr ""
-#: languages/states.php:1977
+#: languages/states.php:2002
msgid "Virovitica-Podravina County"
msgstr ""
-#: languages/states.php:1978
+#: languages/states.php:2003
msgid "Požega-Slavonia County"
msgstr ""
-#: languages/states.php:1979
+#: languages/states.php:2004
msgid "Brod-Posavina County"
msgstr ""
-#: languages/states.php:1980
+#: languages/states.php:2005
msgid "Zadar County"
msgstr ""
-#: languages/states.php:1981
+#: languages/states.php:2006
msgid "Osijek-Baranja County"
msgstr ""
-#: languages/states.php:1982
+#: languages/states.php:2007
msgid "Šibenik-Knin County"
msgstr ""
-#: languages/states.php:1983
+#: languages/states.php:2008
msgid "Vukovar-Syrmia County"
msgstr ""
-#: languages/states.php:1984
+#: languages/states.php:2009
msgid "Split-Dalmatia County"
msgstr ""
-#: languages/states.php:1985
+#: languages/states.php:2010
msgid "Istria County"
msgstr ""
-#: languages/states.php:1986
+#: languages/states.php:2011
msgid "Dubrovnik-Neretva County"
msgstr ""
-#: languages/states.php:1987
+#: languages/states.php:2012
msgid "Međimurje County"
msgstr ""
-#: languages/states.php:1988
+#: languages/states.php:2013
msgid "Zagreb"
msgstr ""
-#: languages/states.php:1989
+#: languages/states.php:2014
msgid "Bjelovar-Bilogora County"
msgstr ""
-#: languages/states.php:1990
+#: languages/states.php:2015
msgid "Koprivnica-Križevci County"
msgstr ""
-#: languages/states.php:1991
+#: languages/states.php:2016
msgid "Krapina-Zagorje County"
msgstr ""
-#: languages/states.php:1992
+#: languages/states.php:2017
msgid "Lika-Senj County"
msgstr ""
-#: languages/states.php:1993
+#: languages/states.php:2018
msgid "Primorje-Gorski Kotar County"
msgstr ""
-#: languages/states.php:1994
+#: languages/states.php:2019
msgid "Sisak-Moslavina County"
msgstr ""
-#: languages/states.php:1995
+#: languages/states.php:2020
msgid "Varaždin County"
msgstr ""
-#: languages/states.php:1996
+#: languages/states.php:2021
msgid "Zagreb County"
msgstr ""
-#: languages/states.php:1999
+#: languages/states.php:2024
msgid "Artibonite"
msgstr ""
-#: languages/states.php:2001
+#: languages/states.php:2026
msgid "Grand'Anse"
msgstr ""
-#: languages/states.php:2002
+#: languages/states.php:2027
msgid "Nippes"
msgstr ""
-#: languages/states.php:2003
+#: languages/states.php:2028
msgid "Nord"
msgstr ""
-#: languages/states.php:2004
+#: languages/states.php:2029
msgid "Nord-Est"
msgstr ""
-#: languages/states.php:2005
+#: languages/states.php:2030
msgid "Nord-Ouest"
msgstr ""
-#: languages/states.php:2006
+#: languages/states.php:2031
msgid "Ouest"
msgstr ""
-#: languages/states.php:2007
+#: languages/states.php:2032
msgid "Sud"
msgstr ""
-#: languages/states.php:2008
+#: languages/states.php:2033
msgid "Sud-Est"
msgstr ""
-#: languages/states.php:2011
+#: languages/states.php:2036
msgid "Baranya County"
msgstr ""
-#: languages/states.php:2012
+#: languages/states.php:2037
msgid "Borsod-Abaúj-Zemplén County"
msgstr ""
-#: languages/states.php:2013
+#: languages/states.php:2038
msgid "Budapest"
msgstr ""
-#: languages/states.php:2014
+#: languages/states.php:2039
msgid "Bács-Kiskun County"
msgstr ""
-#: languages/states.php:2015
+#: languages/states.php:2040
msgid "Békés County"
msgstr ""
-#: languages/states.php:2016
+#: languages/states.php:2041
msgid "Békéscsaba"
msgstr ""
-#: languages/states.php:2017
+#: languages/states.php:2042
msgid "Csongrád County"
msgstr ""
-#: languages/states.php:2018
+#: languages/states.php:2043
msgid "Debrecen"
msgstr ""
-#: languages/states.php:2019
+#: languages/states.php:2044
msgid "Dunaújváros"
msgstr ""
-#: languages/states.php:2020
+#: languages/states.php:2045
msgid "Eger"
msgstr ""
-#: languages/states.php:2021
+#: languages/states.php:2046
msgid "Fejér County"
msgstr ""
-#: languages/states.php:2022
+#: languages/states.php:2047
msgid "Győr"
msgstr ""
-#: languages/states.php:2023
+#: languages/states.php:2048
msgid "Győr-Moson-Sopron County"
msgstr ""
-#: languages/states.php:2024
+#: languages/states.php:2049
msgid "Hajdú-Bihar County"
msgstr ""
-#: languages/states.php:2025
+#: languages/states.php:2050
msgid "Heves County"
msgstr ""
-#: languages/states.php:2026
+#: languages/states.php:2051
msgid "Hódmezővásárhely"
msgstr ""
-#: languages/states.php:2027
+#: languages/states.php:2052
msgid "Jász-Nagykun-Szolnok County"
msgstr ""
-#: languages/states.php:2028
+#: languages/states.php:2053
msgid "Kaposvár"
msgstr ""
-#: languages/states.php:2029
+#: languages/states.php:2054
msgid "Kecskemét"
msgstr ""
-#: languages/states.php:2030
+#: languages/states.php:2055
msgid "Miskolc"
msgstr ""
-#: languages/states.php:2031
+#: languages/states.php:2056
msgid "Nagykanizsa"
msgstr ""
-#: languages/states.php:2032
+#: languages/states.php:2057
msgid "Nyíregyháza"
msgstr ""
-#: languages/states.php:2033
+#: languages/states.php:2058
msgid "Nógrád County"
msgstr ""
-#: languages/states.php:2034
+#: languages/states.php:2059
msgid "Pest County"
msgstr ""
-#: languages/states.php:2035
+#: languages/states.php:2060
msgid "Pécs"
msgstr ""
-#: languages/states.php:2036
+#: languages/states.php:2061
msgid "Salgótarján"
msgstr ""
-#: languages/states.php:2037
+#: languages/states.php:2062
msgid "Somogy County"
msgstr ""
-#: languages/states.php:2038
+#: languages/states.php:2063
msgid "Sopron"
msgstr ""
-#: languages/states.php:2039
+#: languages/states.php:2064
msgid "Szabolcs-Szatmár-Bereg County"
msgstr ""
-#: languages/states.php:2040
+#: languages/states.php:2065
msgid "Szeged"
msgstr ""
-#: languages/states.php:2041
+#: languages/states.php:2066
msgid "Szekszárd"
msgstr ""
-#: languages/states.php:2042
+#: languages/states.php:2067
msgid "Szolnok"
msgstr ""
-#: languages/states.php:2043
+#: languages/states.php:2068
msgid "Szombathely"
msgstr ""
-#: languages/states.php:2044
+#: languages/states.php:2069
msgid "Székesfehérvár"
msgstr ""
-#: languages/states.php:2045
+#: languages/states.php:2070
msgid "Tatabánya"
msgstr ""
-#: languages/states.php:2046
+#: languages/states.php:2071
msgid "Tolna County"
msgstr ""
-#: languages/states.php:2047
+#: languages/states.php:2072
msgid "Vas County"
msgstr ""
-#: languages/states.php:2048
+#: languages/states.php:2073
msgid "Veszprém"
msgstr ""
-#: languages/states.php:2049
+#: languages/states.php:2074
msgid "Veszprém County"
msgstr ""
-#: languages/states.php:2050
+#: languages/states.php:2075
msgid "Zala County"
msgstr ""
-#: languages/states.php:2051
+#: languages/states.php:2076
msgid "Zalaegerszeg"
msgstr ""
-#: languages/states.php:2052
+#: languages/states.php:2077
msgid "Érd"
msgstr ""
-#: languages/states.php:2055
+#: languages/states.php:2080
msgid "Aceh"
msgstr ""
-#: languages/states.php:2056
+#: languages/states.php:2081
msgid "Bali"
msgstr ""
-#: languages/states.php:2057
+#: languages/states.php:2082
msgid "Bangka Belitung Islands"
msgstr ""
-#: languages/states.php:2058
+#: languages/states.php:2083
msgid "Banten"
msgstr ""
-#: languages/states.php:2059
+#: languages/states.php:2084
msgid "Bengkulu"
msgstr ""
-#: languages/states.php:2060
+#: languages/states.php:2085
msgid "Central Java"
msgstr ""
-#: languages/states.php:2061
+#: languages/states.php:2086
msgid "Central Kalimantan"
msgstr ""
-#: languages/states.php:2062
+#: languages/states.php:2087
msgid "Central Sulawesi"
msgstr ""
-#: languages/states.php:2063
+#: languages/states.php:2088
msgid "East Java"
msgstr ""
-#: languages/states.php:2064
+#: languages/states.php:2089
msgid "East Kalimantan"
msgstr ""
-#: languages/states.php:2065
+#: languages/states.php:2090
msgid "East Nusa Tenggara"
msgstr ""
-#: languages/states.php:2066
+#: languages/states.php:2091
msgid "Gorontalo"
msgstr ""
-#: languages/states.php:2067
+#: languages/states.php:2092
msgid "Jakarta"
msgstr ""
-#: languages/states.php:2068
+#: languages/states.php:2093
msgid "Jambi"
msgstr ""
-#: languages/states.php:2069
+#: languages/states.php:2094
msgid "Kalimantan"
msgstr ""
-#: languages/states.php:2070
+#: languages/states.php:2095
msgid "Lampung"
msgstr ""
-#: languages/states.php:2071
+#: languages/states.php:2096
msgid "Lesser Sunda Islands"
msgstr ""
-#: languages/states.php:2072
+#: languages/states.php:2097
msgid "Maluku"
msgstr ""
-#: languages/states.php:2073
+#: languages/states.php:2098
msgid "Maluku Islands"
msgstr ""
-#: languages/states.php:2074
+#: languages/states.php:2099
msgid "North Kalimantan"
msgstr ""
-#: languages/states.php:2075
+#: languages/states.php:2100
msgid "North Maluku"
msgstr ""
-#: languages/states.php:2076
+#: languages/states.php:2101
msgid "North Sulawesi"
msgstr ""
-#: languages/states.php:2077
+#: languages/states.php:2102
msgid "North Sumatra"
msgstr ""
-#: languages/states.php:2078
+#: languages/states.php:2103
msgid "Papua"
msgstr ""
-#: languages/states.php:2079
+#: languages/states.php:2104
msgid "Riau"
msgstr ""
-#: languages/states.php:2080
+#: languages/states.php:2105
msgid "Riau Islands"
msgstr ""
-#: languages/states.php:2081
+#: languages/states.php:2106
msgid "South Kalimantan"
msgstr ""
-#: languages/states.php:2082
+#: languages/states.php:2107
msgid "South Sulawesi"
msgstr ""
-#: languages/states.php:2083
+#: languages/states.php:2108
msgid "South Sumatra"
msgstr ""
-#: languages/states.php:2084
+#: languages/states.php:2109
msgid "Southeast Sulawesi"
msgstr ""
-#: languages/states.php:2085
+#: languages/states.php:2110
msgid "Special Region of Yogyakarta"
msgstr ""
-#: languages/states.php:2086
+#: languages/states.php:2111
msgid "Sulawesi"
msgstr ""
-#: languages/states.php:2087
+#: languages/states.php:2112
msgid "Sumatra"
msgstr ""
-#: languages/states.php:2088
+#: languages/states.php:2113
msgid "West Java"
msgstr ""
-#: languages/states.php:2089
+#: languages/states.php:2114
msgid "West Nusa Tenggara"
msgstr ""
-#: languages/states.php:2090
+#: languages/states.php:2115
msgid "West Papua"
msgstr ""
-#: languages/states.php:2091
+#: languages/states.php:2116
msgid "West Sulawesi"
msgstr ""
-#: languages/states.php:2092
+#: languages/states.php:2117
msgid "West Sumatra"
msgstr ""
-#: languages/states.php:2095
+#: languages/states.php:2120
msgid "Connacht"
msgstr ""
-#: languages/states.php:2096
+#: languages/states.php:2121
msgid "County Carlow"
msgstr ""
-#: languages/states.php:2097
+#: languages/states.php:2122
msgid "County Cavan"
msgstr ""
-#: languages/states.php:2098
+#: languages/states.php:2123
msgid "County Clare"
msgstr ""
-#: languages/states.php:2099
+#: languages/states.php:2124
msgid "County Cork"
msgstr ""
-#: languages/states.php:2100
+#: languages/states.php:2125
msgid "County Donegal"
msgstr ""
-#: languages/states.php:2101
+#: languages/states.php:2126
msgid "County Dublin"
msgstr ""
-#: languages/states.php:2102
+#: languages/states.php:2127
msgid "County Galway"
msgstr ""
-#: languages/states.php:2103
+#: languages/states.php:2128
msgid "County Kerry"
msgstr ""
-#: languages/states.php:2104
+#: languages/states.php:2129
msgid "County Kildare"
msgstr ""
-#: languages/states.php:2105
+#: languages/states.php:2130
msgid "County Kilkenny"
msgstr ""
-#: languages/states.php:2106
+#: languages/states.php:2131
msgid "County Laois"
msgstr ""
-#: languages/states.php:2107
+#: languages/states.php:2132
msgid "County Limerick"
msgstr ""
-#: languages/states.php:2108
+#: languages/states.php:2133
msgid "County Longford"
msgstr ""
-#: languages/states.php:2109
+#: languages/states.php:2134
msgid "County Louth"
msgstr ""
-#: languages/states.php:2110
+#: languages/states.php:2135
msgid "County Mayo"
msgstr ""
-#: languages/states.php:2111
+#: languages/states.php:2136
msgid "County Meath"
msgstr ""
-#: languages/states.php:2112
+#: languages/states.php:2137
msgid "County Monaghan"
msgstr ""
-#: languages/states.php:2113
+#: languages/states.php:2138
msgid "County Offaly"
msgstr ""
-#: languages/states.php:2114
+#: languages/states.php:2139
msgid "County Roscommon"
msgstr ""
-#: languages/states.php:2115
+#: languages/states.php:2140
msgid "County Sligo"
msgstr ""
-#: languages/states.php:2116
+#: languages/states.php:2141
msgid "County Tipperary"
msgstr ""
-#: languages/states.php:2117
+#: languages/states.php:2142
msgid "County Waterford"
msgstr ""
-#: languages/states.php:2118
+#: languages/states.php:2143
msgid "County Westmeath"
msgstr ""
-#: languages/states.php:2119
+#: languages/states.php:2144
msgid "County Wexford"
msgstr ""
-#: languages/states.php:2120
+#: languages/states.php:2145
msgid "County Wicklow"
msgstr ""
-#: languages/states.php:2121
+#: languages/states.php:2146
msgid "Leinster"
msgstr ""
-#: languages/states.php:2122
+#: languages/states.php:2147
msgid "Munster"
msgstr ""
-#: languages/states.php:2123
+#: languages/states.php:2148
msgid "Ulster"
msgstr ""
-#: languages/states.php:2127
+#: languages/states.php:2152
msgid "Haifa District"
msgstr ""
-#: languages/states.php:2128
+#: languages/states.php:2153
msgid "Jerusalem District"
msgstr ""
-#: languages/states.php:2129
+#: languages/states.php:2154
msgid "Northern District"
msgstr ""
-#: languages/states.php:2131
+#: languages/states.php:2156
msgid "Tel Aviv District"
msgstr ""
-#: languages/states.php:2135
+#: languages/states.php:2160
msgid "Andaman and Nicobar Islands"
msgstr ""
-#: languages/states.php:2136
+#: languages/states.php:2161
msgid "Andhra Pradesh"
msgstr ""
-#: languages/states.php:2137
+#: languages/states.php:2162
msgid "Arunachal Pradesh"
msgstr ""
-#: languages/states.php:2138
+#: languages/states.php:2163
msgid "Assam"
msgstr ""
-#: languages/states.php:2139
+#: languages/states.php:2164
msgid "Bihar"
msgstr ""
-#: languages/states.php:2140
+#: languages/states.php:2165
msgid "Chandigarh"
msgstr ""
-#: languages/states.php:2141
+#: languages/states.php:2166
msgid "Chhattisgarh"
msgstr ""
-#: languages/states.php:2142
+#: languages/states.php:2167
msgid "Dadra and Nagar Haveli and Daman and Diu"
msgstr ""
-#: languages/states.php:2143
+#: languages/states.php:2168
msgid "Delhi"
msgstr ""
-#: languages/states.php:2144
+#: languages/states.php:2169
msgid "Goa"
msgstr ""
-#: languages/states.php:2145
+#: languages/states.php:2170
msgid "Gujarat"
msgstr ""
-#: languages/states.php:2146
+#: languages/states.php:2171
msgid "Haryana"
msgstr ""
-#: languages/states.php:2147
+#: languages/states.php:2172
msgid "Himachal Pradesh"
msgstr ""
-#: languages/states.php:2148
+#: languages/states.php:2173
msgid "Jammu and Kashmir"
msgstr ""
-#: languages/states.php:2149
+#: languages/states.php:2174
msgid "Jharkhand"
msgstr ""
-#: languages/states.php:2150
+#: languages/states.php:2175
msgid "Karnataka"
msgstr ""
-#: languages/states.php:2151
+#: languages/states.php:2176
msgid "Kerala"
msgstr ""
-#: languages/states.php:2152
+#: languages/states.php:2177
msgid "Ladakh"
msgstr ""
-#: languages/states.php:2153
+#: languages/states.php:2178
msgid "Lakshadweep"
msgstr ""
-#: languages/states.php:2154
+#: languages/states.php:2179
msgid "Madhya Pradesh"
msgstr ""
-#: languages/states.php:2155
+#: languages/states.php:2180
msgid "Maharashtra"
msgstr ""
-#: languages/states.php:2156
+#: languages/states.php:2181
msgid "Manipur"
msgstr ""
-#: languages/states.php:2157
+#: languages/states.php:2182
msgid "Meghalaya"
msgstr ""
-#: languages/states.php:2158
+#: languages/states.php:2183
msgid "Mizoram"
msgstr ""
-#: languages/states.php:2159
+#: languages/states.php:2184
msgid "Nagaland"
msgstr ""
-#: languages/states.php:2160
+#: languages/states.php:2185
msgid "Odisha"
msgstr ""
-#: languages/states.php:2161
+#: languages/states.php:2186
msgid "Puducherry"
msgstr ""
-#: languages/states.php:2162
-#: languages/states.php:3844
+#: languages/states.php:2187
+#: languages/states.php:3869
msgid "Punjab"
msgstr ""
-#: languages/states.php:2163
+#: languages/states.php:2188
msgid "Rajasthan"
msgstr ""
-#: languages/states.php:2164
+#: languages/states.php:2189
msgid "Sikkim"
msgstr ""
-#: languages/states.php:2165
+#: languages/states.php:2190
msgid "Tamil Nadu"
msgstr ""
-#: languages/states.php:2166
+#: languages/states.php:2191
msgid "Telangana"
msgstr ""
-#: languages/states.php:2167
+#: languages/states.php:2192
msgid "Tripura"
msgstr ""
-#: languages/states.php:2168
+#: languages/states.php:2193
msgid "Uttar Pradesh"
msgstr ""
-#: languages/states.php:2169
+#: languages/states.php:2194
msgid "Uttarakhand"
msgstr ""
-#: languages/states.php:2170
+#: languages/states.php:2195
msgid "West Bengal"
msgstr ""
-#: languages/states.php:2174
+#: languages/states.php:2199
msgid "Al Anbar Governorate"
msgstr ""
-#: languages/states.php:2175
+#: languages/states.php:2200
msgid "Al Muthanna Governorate"
msgstr ""
-#: languages/states.php:2176
+#: languages/states.php:2201
msgid "Al-Qādisiyyah Governorate"
msgstr ""
-#: languages/states.php:2177
+#: languages/states.php:2202
msgid "Babylon Governorate"
msgstr ""
-#: languages/states.php:2178
+#: languages/states.php:2203
msgid "Baghdad Governorate"
msgstr ""
-#: languages/states.php:2179
+#: languages/states.php:2204
msgid "Basra Governorate"
msgstr ""
-#: languages/states.php:2180
+#: languages/states.php:2205
msgid "Dhi Qar Governorate"
msgstr ""
-#: languages/states.php:2181
+#: languages/states.php:2206
msgid "Diyala Governorate"
msgstr ""
-#: languages/states.php:2182
+#: languages/states.php:2207
msgid "Dohuk Governorate"
msgstr ""
-#: languages/states.php:2183
+#: languages/states.php:2208
msgid "Erbil Governorate"
msgstr ""
-#: languages/states.php:2184
+#: languages/states.php:2209
msgid "Karbala Governorate"
msgstr ""
-#: languages/states.php:2185
+#: languages/states.php:2210
msgid "Kirkuk Governorate"
msgstr ""
-#: languages/states.php:2186
+#: languages/states.php:2211
msgid "Maysan Governorate"
msgstr ""
-#: languages/states.php:2187
+#: languages/states.php:2212
msgid "Najaf Governorate"
msgstr ""
-#: languages/states.php:2188
+#: languages/states.php:2213
msgid "Nineveh Governorate"
msgstr ""
-#: languages/states.php:2189
+#: languages/states.php:2214
msgid "Saladin Governorate"
msgstr ""
-#: languages/states.php:2190
+#: languages/states.php:2215
msgid "Sulaymaniyah Governorate"
msgstr ""
-#: languages/states.php:2191
+#: languages/states.php:2216
msgid "Wasit Governorate"
msgstr ""
-#: languages/states.php:2194
+#: languages/states.php:2219
msgid "Isfahan Province"
msgstr ""
-#: languages/states.php:2195
+#: languages/states.php:2220
msgid "Sistan and Baluchestan"
msgstr ""
-#: languages/states.php:2196
+#: languages/states.php:2221
msgid "Kurdistan Province"
msgstr ""
-#: languages/states.php:2197
+#: languages/states.php:2222
msgid "Hamadan Province"
msgstr ""
-#: languages/states.php:2198
+#: languages/states.php:2223
msgid "Chaharmahal and Bakhtiari Province"
msgstr ""
-#: languages/states.php:2199
+#: languages/states.php:2224
msgid "Lorestan Province"
msgstr ""
-#: languages/states.php:2200
+#: languages/states.php:2225
msgid "Ilam Province"
msgstr ""
-#: languages/states.php:2201
+#: languages/states.php:2226
msgid "Kohgiluyeh and Boyer-Ahmad Province"
msgstr ""
-#: languages/states.php:2202
+#: languages/states.php:2227
msgid "Bushehr Province"
msgstr ""
-#: languages/states.php:2203
+#: languages/states.php:2228
msgid "Zanjan Province"
msgstr ""
-#: languages/states.php:2204
+#: languages/states.php:2229
msgid "Semnan Province"
msgstr ""
-#: languages/states.php:2205
+#: languages/states.php:2230
msgid "Yazd Province"
msgstr ""
-#: languages/states.php:2206
+#: languages/states.php:2231
msgid "Hormozgan Province"
msgstr ""
-#: languages/states.php:2207
+#: languages/states.php:2232
msgid "Tehran Province"
msgstr ""
-#: languages/states.php:2208
+#: languages/states.php:2233
msgid "Ardabil Province"
msgstr ""
-#: languages/states.php:2209
+#: languages/states.php:2234
msgid "Qom Province"
msgstr ""
-#: languages/states.php:2210
+#: languages/states.php:2235
msgid "Qazvin Province"
msgstr ""
-#: languages/states.php:2211
+#: languages/states.php:2236
msgid "Golestan Province"
msgstr ""
-#: languages/states.php:2212
+#: languages/states.php:2237
msgid "North Khorasan Province"
msgstr ""
-#: languages/states.php:2213
+#: languages/states.php:2238
msgid "South Khorasan Province"
msgstr ""
-#: languages/states.php:2214
+#: languages/states.php:2239
msgid "Alborz Province"
msgstr ""
-#: languages/states.php:2215
+#: languages/states.php:2240
msgid "East Azerbaijan Province"
msgstr ""
-#: languages/states.php:2216
+#: languages/states.php:2241
msgid "Fars Province"
msgstr ""
-#: languages/states.php:2217
+#: languages/states.php:2242
msgid "Gilan Province"
msgstr ""
-#: languages/states.php:2218
+#: languages/states.php:2243
msgid "Kerman Province"
msgstr ""
-#: languages/states.php:2219
+#: languages/states.php:2244
msgid "Kermanshah Province"
msgstr ""
-#: languages/states.php:2220
+#: languages/states.php:2245
msgid "Khuzestan Province"
msgstr ""
-#: languages/states.php:2221
+#: languages/states.php:2246
msgid "Markazi Province"
msgstr ""
-#: languages/states.php:2222
+#: languages/states.php:2247
msgid "Mazandaran Province"
msgstr ""
-#: languages/states.php:2223
+#: languages/states.php:2248
msgid "Razavi Khorasan Province"
msgstr ""
-#: languages/states.php:2224
+#: languages/states.php:2249
msgid "West Azarbaijan Province"
msgstr ""
-#: languages/states.php:2227
+#: languages/states.php:2252
msgid "Capital Region"
msgstr ""
-#: languages/states.php:2228
+#: languages/states.php:2253
msgid "Southern Peninsula Region"
msgstr ""
-#: languages/states.php:2230
+#: languages/states.php:2255
msgid "Westfjords"
msgstr ""
-#: languages/states.php:2231
+#: languages/states.php:2256
msgid "Northwestern Region"
msgstr ""
-#: languages/states.php:2232
+#: languages/states.php:2257
msgid "Northeastern Region"
msgstr ""
-#: languages/states.php:2234
-#: languages/states.php:3409
+#: languages/states.php:2259
+#: languages/states.php:3434
msgid "Southern Region"
msgstr ""
-#: languages/states.php:2237
+#: languages/states.php:2262
msgid "Agrigento"
msgstr ""
-#: languages/states.php:2238
+#: languages/states.php:2263
msgid "Alessandria"
msgstr ""
-#: languages/states.php:2239
+#: languages/states.php:2264
msgid "Ancona"
msgstr ""
-#: languages/states.php:2241
+#: languages/states.php:2266
msgid "Aosta"
msgstr ""
-#: languages/states.php:2242
+#: languages/states.php:2267
msgid "Ascoli Piceno"
msgstr ""
-#: languages/states.php:2244
+#: languages/states.php:2269
msgid "Arezzo"
msgstr ""
-#: languages/states.php:2245
+#: languages/states.php:2270
msgid "Asti"
msgstr ""
-#: languages/states.php:2246
+#: languages/states.php:2271
msgid "Avellino"
msgstr ""
-#: languages/states.php:2247
-#: languages/states.php:4468
+#: languages/states.php:2272
+#: languages/states.php:4493
msgid "Bari"
msgstr ""
-#: languages/states.php:2248
+#: languages/states.php:2273
msgid "Barletta-Andria-Trani"
msgstr ""
-#: languages/states.php:2249
+#: languages/states.php:2274
msgid "Belluno"
msgstr ""
-#: languages/states.php:2250
+#: languages/states.php:2275
msgid "Benevento Province"
msgstr ""
-#: languages/states.php:2251
+#: languages/states.php:2276
msgid "Bergamo"
msgstr ""
-#: languages/states.php:2252
+#: languages/states.php:2277
msgid "Biella"
msgstr ""
-#: languages/states.php:2253
+#: languages/states.php:2278
msgid "Bologna"
msgstr ""
-#: languages/states.php:2254
+#: languages/states.php:2279
msgid "Brescia"
msgstr ""
-#: languages/states.php:2255
+#: languages/states.php:2280
msgid "Brindisi"
msgstr ""
-#: languages/states.php:2256
+#: languages/states.php:2281
msgid "Cagliari"
msgstr ""
-#: languages/states.php:2257
+#: languages/states.php:2282
msgid "Caltanissetta"
msgstr ""
-#: languages/states.php:2258
+#: languages/states.php:2283
msgid "Campobasso"
msgstr ""
-#: languages/states.php:2259
+#: languages/states.php:2284
msgid "Carbonia-Iglesias"
msgstr ""
-#: languages/states.php:2260
+#: languages/states.php:2285
msgid "Caserta"
msgstr ""
-#: languages/states.php:2261
+#: languages/states.php:2286
msgid "Catania"
msgstr ""
-#: languages/states.php:2262
+#: languages/states.php:2287
msgid "Catanzaro"
msgstr ""
-#: languages/states.php:2263
+#: languages/states.php:2288
msgid "Chieti"
msgstr ""
-#: languages/states.php:2264
+#: languages/states.php:2289
msgid "Como"
msgstr ""
-#: languages/states.php:2265
+#: languages/states.php:2290
msgid "Cosenza"
msgstr ""
-#: languages/states.php:2266
+#: languages/states.php:2291
msgid "Cremona"
msgstr ""
-#: languages/states.php:2267
+#: languages/states.php:2292
msgid "Crotone"
msgstr ""
-#: languages/states.php:2268
+#: languages/states.php:2293
msgid "Cuneo"
msgstr ""
-#: languages/states.php:2269
+#: languages/states.php:2294
msgid "Enna"
msgstr ""
-#: languages/states.php:2270
+#: languages/states.php:2295
msgid "Fermo"
msgstr ""
-#: languages/states.php:2271
+#: languages/states.php:2296
msgid "Ferrara"
msgstr ""
-#: languages/states.php:2272
+#: languages/states.php:2297
msgid "Florence"
msgstr ""
-#: languages/states.php:2273
+#: languages/states.php:2298
msgid "Foggia"
msgstr ""
-#: languages/states.php:2274
+#: languages/states.php:2299
msgid "Forlì-Cesena"
msgstr ""
-#: languages/states.php:2275
+#: languages/states.php:2300
msgid "Frosinone"
msgstr ""
-#: languages/states.php:2276
+#: languages/states.php:2301
msgid "Genoa"
msgstr ""
-#: languages/states.php:2277
+#: languages/states.php:2302
msgid "Gorizia"
msgstr ""
-#: languages/states.php:2278
+#: languages/states.php:2303
msgid "Grosseto"
msgstr ""
-#: languages/states.php:2279
+#: languages/states.php:2304
msgid "Imperia"
msgstr ""
-#: languages/states.php:2280
+#: languages/states.php:2305
msgid "Isernia"
msgstr ""
-#: languages/states.php:2281
+#: languages/states.php:2306
msgid "L'Aquila"
msgstr ""
-#: languages/states.php:2282
+#: languages/states.php:2307
msgid "La Spezia"
msgstr ""
-#: languages/states.php:2283
+#: languages/states.php:2308
msgid "Latina"
msgstr ""
-#: languages/states.php:2284
+#: languages/states.php:2309
msgid "Lecce"
msgstr ""
-#: languages/states.php:2285
+#: languages/states.php:2310
msgid "Lecco"
msgstr ""
-#: languages/states.php:2286
+#: languages/states.php:2311
msgid "Livorno"
msgstr ""
-#: languages/states.php:2287
+#: languages/states.php:2312
msgid "Lodi"
msgstr ""
-#: languages/states.php:2288
+#: languages/states.php:2313
msgid "Lucca"
msgstr ""
-#: languages/states.php:2289
+#: languages/states.php:2314
msgid "Macerata"
msgstr ""
-#: languages/states.php:2290
+#: languages/states.php:2315
msgid "Mantua"
msgstr ""
-#: languages/states.php:2291
+#: languages/states.php:2316
msgid "Massa and Carrara"
msgstr ""
-#: languages/states.php:2292
+#: languages/states.php:2317
msgid "Matera"
msgstr ""
-#: languages/states.php:2293
+#: languages/states.php:2318
msgid "Medio Campidano"
msgstr ""
-#: languages/states.php:2294
+#: languages/states.php:2319
msgid "Messina"
msgstr ""
-#: languages/states.php:2295
+#: languages/states.php:2320
msgid "Milan"
msgstr ""
-#: languages/states.php:2296
+#: languages/states.php:2321
msgid "Modena"
msgstr ""
-#: languages/states.php:2297
+#: languages/states.php:2322
msgid "Monza and Brianza"
msgstr ""
-#: languages/states.php:2298
+#: languages/states.php:2323
msgid "Naples"
msgstr ""
-#: languages/states.php:2299
+#: languages/states.php:2324
msgid "Novara"
msgstr ""
-#: languages/states.php:2300
+#: languages/states.php:2325
msgid "Nuoro"
msgstr ""
-#: languages/states.php:2301
+#: languages/states.php:2326
msgid "Ogliastra"
msgstr ""
-#: languages/states.php:2302
+#: languages/states.php:2327
msgid "Olbia-Tempio"
msgstr ""
-#: languages/states.php:2303
+#: languages/states.php:2328
msgid "Oristano"
msgstr ""
-#: languages/states.php:2304
+#: languages/states.php:2329
msgid "Padua"
msgstr ""
-#: languages/states.php:2305
+#: languages/states.php:2330
msgid "Palermo"
msgstr ""
-#: languages/states.php:2306
+#: languages/states.php:2331
msgid "Parma"
msgstr ""
-#: languages/states.php:2307
+#: languages/states.php:2332
msgid "Pavia"
msgstr ""
-#: languages/states.php:2308
+#: languages/states.php:2333
msgid "Perugia"
msgstr ""
-#: languages/states.php:2309
+#: languages/states.php:2334
msgid "Pesaro and Urbino Province"
msgstr ""
-#: languages/states.php:2310
+#: languages/states.php:2335
msgid "Pescara"
msgstr ""
-#: languages/states.php:2311
+#: languages/states.php:2336
msgid "Piacenza"
msgstr ""
-#: languages/states.php:2312
+#: languages/states.php:2337
msgid "Pisa"
msgstr ""
-#: languages/states.php:2313
+#: languages/states.php:2338
msgid "Pistoia"
msgstr ""
-#: languages/states.php:2314
+#: languages/states.php:2339
msgid "Pordenone"
msgstr ""
-#: languages/states.php:2315
+#: languages/states.php:2340
msgid "Potenza"
msgstr ""
-#: languages/states.php:2316
+#: languages/states.php:2341
msgid "Prato"
msgstr ""
-#: languages/states.php:2317
+#: languages/states.php:2342
msgid "Ragusa"
msgstr ""
-#: languages/states.php:2318
+#: languages/states.php:2343
msgid "Ravenna"
msgstr ""
-#: languages/states.php:2319
+#: languages/states.php:2344
msgid "Reggio Calabria"
msgstr ""
-#: languages/states.php:2320
+#: languages/states.php:2345
msgid "Reggio Emilia"
msgstr ""
-#: languages/states.php:2321
+#: languages/states.php:2346
msgid "Rieti"
msgstr ""
-#: languages/states.php:2322
+#: languages/states.php:2347
msgid "Rimini"
msgstr ""
-#: languages/states.php:2323
+#: languages/states.php:2348
msgid "Rome"
msgstr ""
-#: languages/states.php:2324
+#: languages/states.php:2349
msgid "Rovigo"
msgstr ""
-#: languages/states.php:2325
+#: languages/states.php:2350
msgid "Salerno"
msgstr ""
-#: languages/states.php:2326
+#: languages/states.php:2351
msgid "Sassari"
msgstr ""
-#: languages/states.php:2327
+#: languages/states.php:2352
msgid "Savona"
msgstr ""
-#: languages/states.php:2328
+#: languages/states.php:2353
msgid "Siena"
msgstr ""
-#: languages/states.php:2329
+#: languages/states.php:2354
msgid "Siracusa"
msgstr ""
-#: languages/states.php:2330
+#: languages/states.php:2355
msgid "Sondrio"
msgstr ""
-#: languages/states.php:2331
+#: languages/states.php:2356
msgid "South Tyrol"
msgstr ""
-#: languages/states.php:2332
+#: languages/states.php:2357
msgid "Taranto"
msgstr ""
-#: languages/states.php:2333
+#: languages/states.php:2358
msgid "Teramo"
msgstr ""
-#: languages/states.php:2334
+#: languages/states.php:2359
msgid "Terni"
msgstr ""
-#: languages/states.php:2335
+#: languages/states.php:2360
msgid "Trapani"
msgstr ""
-#: languages/states.php:2336
+#: languages/states.php:2361
msgid "Trentino"
msgstr ""
-#: languages/states.php:2337
+#: languages/states.php:2362
msgid "Treviso"
msgstr ""
-#: languages/states.php:2338
+#: languages/states.php:2363
msgid "Trieste"
msgstr ""
-#: languages/states.php:2339
+#: languages/states.php:2364
msgid "Turin"
msgstr ""
-#: languages/states.php:2340
+#: languages/states.php:2365
msgid "Udine"
msgstr ""
-#: languages/states.php:2341
+#: languages/states.php:2366
msgid "Varese"
msgstr ""
-#: languages/states.php:2342
+#: languages/states.php:2367
msgid "Venice"
msgstr ""
-#: languages/states.php:2343
+#: languages/states.php:2368
msgid "Verbano-Cusio-Ossola"
msgstr ""
-#: languages/states.php:2344
+#: languages/states.php:2369
msgid "Vercelli"
msgstr ""
-#: languages/states.php:2345
+#: languages/states.php:2370
msgid "Verona"
msgstr ""
-#: languages/states.php:2346
+#: languages/states.php:2371
msgid "Vibo Valentia"
msgstr ""
-#: languages/states.php:2347
+#: languages/states.php:2372
msgid "Vicenza"
msgstr ""
-#: languages/states.php:2348
+#: languages/states.php:2373
msgid "Viterbo"
msgstr ""
-#: languages/states.php:2352
+#: languages/states.php:2377
msgid "Westmoreland Parish"
msgstr ""
-#: languages/states.php:2353
+#: languages/states.php:2378
msgid "Saint Elizabeth Parish"
msgstr ""
-#: languages/states.php:2354
+#: languages/states.php:2379
msgid "Manchester Parish"
msgstr ""
-#: languages/states.php:2355
+#: languages/states.php:2380
msgid "Clarendon Parish"
msgstr ""
-#: languages/states.php:2356
+#: languages/states.php:2381
msgid "Saint Catherine Parish"
msgstr ""
-#: languages/states.php:2357
+#: languages/states.php:2382
msgid "Hanover Parish"
msgstr ""
-#: languages/states.php:2358
+#: languages/states.php:2383
msgid "Kingston Parish"
msgstr ""
-#: languages/states.php:2359
+#: languages/states.php:2384
msgid "Portland Parish"
msgstr ""
-#: languages/states.php:2361
+#: languages/states.php:2386
msgid "Saint Ann Parish"
msgstr ""
-#: languages/states.php:2362
+#: languages/states.php:2387
msgid "Saint James Parish"
msgstr ""
-#: languages/states.php:2364
+#: languages/states.php:2389
msgid "Saint Thomas Parish"
msgstr ""
-#: languages/states.php:2365
+#: languages/states.php:2390
msgid "Trelawny Parish"
msgstr ""
-#: languages/states.php:2368
+#: languages/states.php:2393
msgid "Ajloun Governorate"
msgstr ""
-#: languages/states.php:2369
+#: languages/states.php:2394
msgid "Amman Governorate"
msgstr ""
-#: languages/states.php:2370
+#: languages/states.php:2395
msgid "Aqaba Governorate"
msgstr ""
-#: languages/states.php:2371
+#: languages/states.php:2396
msgid "Balqa Governorate"
msgstr ""
-#: languages/states.php:2372
+#: languages/states.php:2397
msgid "Irbid Governorate"
msgstr ""
-#: languages/states.php:2373
+#: languages/states.php:2398
msgid "Jerash Governorate"
msgstr ""
-#: languages/states.php:2374
+#: languages/states.php:2399
msgid "Karak Governorate"
msgstr ""
-#: languages/states.php:2375
+#: languages/states.php:2400
msgid "Ma'an Governorate"
msgstr ""
-#: languages/states.php:2376
+#: languages/states.php:2401
msgid "Madaba Governorate"
msgstr ""
-#: languages/states.php:2377
+#: languages/states.php:2402
msgid "Mafraq Governorate"
msgstr ""
-#: languages/states.php:2378
+#: languages/states.php:2403
msgid "Tafilah Governorate"
msgstr ""
-#: languages/states.php:2379
+#: languages/states.php:2404
msgid "Zarqa Governorate"
msgstr ""
-#: languages/states.php:2382
+#: languages/states.php:2407
msgid "Gunma Prefecture"
msgstr ""
-#: languages/states.php:2383
+#: languages/states.php:2408
msgid "Saitama Prefecture"
msgstr ""
-#: languages/states.php:2384
+#: languages/states.php:2409
msgid "Chiba Prefecture"
msgstr ""
-#: languages/states.php:2385
+#: languages/states.php:2410
msgid "Tokyo"
msgstr ""
-#: languages/states.php:2386
+#: languages/states.php:2411
msgid "Kanagawa Prefecture"
msgstr ""
-#: languages/states.php:2387
+#: languages/states.php:2412
msgid "Niigata Prefecture"
msgstr ""
-#: languages/states.php:2388
+#: languages/states.php:2413
msgid "Toyama Prefecture"
msgstr ""
-#: languages/states.php:2389
+#: languages/states.php:2414
msgid "Ishikawa Prefecture"
msgstr ""
-#: languages/states.php:2390
+#: languages/states.php:2415
msgid "Fukui Prefecture"
msgstr ""
-#: languages/states.php:2391
+#: languages/states.php:2416
msgid "Yamanashi Prefecture"
msgstr ""
-#: languages/states.php:2392
+#: languages/states.php:2417
msgid "Nagano Prefecture"
msgstr ""
-#: languages/states.php:2393
+#: languages/states.php:2418
msgid "Gifu Prefecture"
msgstr ""
-#: languages/states.php:2394
+#: languages/states.php:2419
msgid "Shizuoka Prefecture"
msgstr ""
-#: languages/states.php:2395
+#: languages/states.php:2420
msgid "Aichi Prefecture"
msgstr ""
-#: languages/states.php:2396
+#: languages/states.php:2421
msgid "Mie Prefecture"
msgstr ""
-#: languages/states.php:2397
+#: languages/states.php:2422
msgid "Shiga Prefecture"
msgstr ""
-#: languages/states.php:2398
+#: languages/states.php:2423
msgid "Kyōto Prefecture"
msgstr ""
-#: languages/states.php:2399
+#: languages/states.php:2424
msgid "Ōsaka Prefecture"
msgstr ""
-#: languages/states.php:2400
+#: languages/states.php:2425
msgid "Hyōgo Prefecture"
msgstr ""
-#: languages/states.php:2401
+#: languages/states.php:2426
msgid "Nara Prefecture"
msgstr ""
-#: languages/states.php:2402
+#: languages/states.php:2427
msgid "Wakayama Prefecture"
msgstr ""
-#: languages/states.php:2403
+#: languages/states.php:2428
msgid "Tottori Prefecture"
msgstr ""
-#: languages/states.php:2404
+#: languages/states.php:2429
msgid "Shimane Prefecture"
msgstr ""
-#: languages/states.php:2405
+#: languages/states.php:2430
msgid "Okayama Prefecture"
msgstr ""
-#: languages/states.php:2406
+#: languages/states.php:2431
msgid "Hiroshima Prefecture"
msgstr ""
-#: languages/states.php:2407
+#: languages/states.php:2432
msgid "Yamaguchi Prefecture"
msgstr ""
-#: languages/states.php:2408
+#: languages/states.php:2433
msgid "Tokushima Prefecture"
msgstr ""
-#: languages/states.php:2409
+#: languages/states.php:2434
msgid "Kagawa Prefecture"
msgstr ""
-#: languages/states.php:2410
+#: languages/states.php:2435
msgid "Ehime Prefecture"
msgstr ""
-#: languages/states.php:2411
+#: languages/states.php:2436
msgid "Fukuoka Prefecture"
msgstr ""
-#: languages/states.php:2412
+#: languages/states.php:2437
msgid "Saga Prefecture"
msgstr ""
-#: languages/states.php:2413
+#: languages/states.php:2438
msgid "Nagasaki Prefecture"
msgstr ""
-#: languages/states.php:2414
+#: languages/states.php:2439
msgid "Kumamoto Prefecture"
msgstr ""
-#: languages/states.php:2415
+#: languages/states.php:2440
msgid "Ōita Prefecture"
msgstr ""
-#: languages/states.php:2416
+#: languages/states.php:2441
msgid "Miyazaki Prefecture"
msgstr ""
-#: languages/states.php:2417
+#: languages/states.php:2442
msgid "Kagoshima Prefecture"
msgstr ""
-#: languages/states.php:2418
+#: languages/states.php:2443
msgid "Okinawa Prefecture"
msgstr ""
-#: languages/states.php:2419
+#: languages/states.php:2444
msgid "Akita Prefecture"
msgstr ""
-#: languages/states.php:2420
+#: languages/states.php:2445
msgid "Aomori Prefecture"
msgstr ""
-#: languages/states.php:2421
+#: languages/states.php:2446
msgid "Fukushima Prefecture"
msgstr ""
-#: languages/states.php:2422
+#: languages/states.php:2447
msgid "Hokkaidō Prefecture"
msgstr ""
-#: languages/states.php:2423
+#: languages/states.php:2448
msgid "Ibaraki Prefecture"
msgstr ""
-#: languages/states.php:2424
+#: languages/states.php:2449
msgid "Iwate Prefecture"
msgstr ""
-#: languages/states.php:2425
+#: languages/states.php:2450
msgid "Miyagi Prefecture"
msgstr ""
-#: languages/states.php:2426
+#: languages/states.php:2451
msgid "Tochigi Prefecture"
msgstr ""
-#: languages/states.php:2427
+#: languages/states.php:2452
msgid "Yamagata Prefecture"
msgstr ""
-#: languages/states.php:2430
+#: languages/states.php:2455
msgid "Kajiado County"
msgstr ""
-#: languages/states.php:2431
+#: languages/states.php:2456
msgid "Kakamega County"
msgstr ""
-#: languages/states.php:2432
+#: languages/states.php:2457
msgid "Kericho County"
msgstr ""
-#: languages/states.php:2433
+#: languages/states.php:2458
msgid "Kiambu County"
msgstr ""
-#: languages/states.php:2434
+#: languages/states.php:2459
msgid "Kilifi County"
msgstr ""
-#: languages/states.php:2435
+#: languages/states.php:2460
msgid "Kirinyaga County"
msgstr ""
-#: languages/states.php:2436
+#: languages/states.php:2461
msgid "Kisii County"
msgstr ""
-#: languages/states.php:2437
+#: languages/states.php:2462
msgid "Kisumu County"
msgstr ""
-#: languages/states.php:2438
+#: languages/states.php:2463
msgid "Kitui County"
msgstr ""
-#: languages/states.php:2439
+#: languages/states.php:2464
msgid "Kwale County"
msgstr ""
-#: languages/states.php:2440
+#: languages/states.php:2465
msgid "Laikipia County"
msgstr ""
-#: languages/states.php:2441
+#: languages/states.php:2466
msgid "Lamu County"
msgstr ""
-#: languages/states.php:2442
+#: languages/states.php:2467
msgid "Machakos County"
msgstr ""
-#: languages/states.php:2443
+#: languages/states.php:2468
msgid "Makueni County"
msgstr ""
-#: languages/states.php:2444
+#: languages/states.php:2469
msgid "Mandera County"
msgstr ""
-#: languages/states.php:2445
+#: languages/states.php:2470
msgid "Marsabit County"
msgstr ""
-#: languages/states.php:2446
+#: languages/states.php:2471
msgid "Meru County"
msgstr ""
-#: languages/states.php:2447
+#: languages/states.php:2472
msgid "Migori County"
msgstr ""
-#: languages/states.php:2448
+#: languages/states.php:2473
msgid "Mombasa County"
msgstr ""
-#: languages/states.php:2449
+#: languages/states.php:2474
msgid "Muranga County"
msgstr ""
-#: languages/states.php:2450
+#: languages/states.php:2475
msgid "Nakuru District"
msgstr ""
-#: languages/states.php:2451
+#: languages/states.php:2476
msgid "Nandi District"
msgstr ""
-#: languages/states.php:2452
+#: languages/states.php:2477
msgid "Narok County"
msgstr ""
-#: languages/states.php:2453
+#: languages/states.php:2478
msgid "Nyamira District"
msgstr ""
-#: languages/states.php:2454
+#: languages/states.php:2479
msgid "Nyandarua County"
msgstr ""
-#: languages/states.php:2455
+#: languages/states.php:2480
msgid "Nyeri County"
msgstr ""
-#: languages/states.php:2456
+#: languages/states.php:2481
msgid "Samburu County"
msgstr ""
-#: languages/states.php:2457
+#: languages/states.php:2482
msgid "Siaya County"
msgstr ""
-#: languages/states.php:2458
+#: languages/states.php:2483
msgid "Taita–Taveta County"
msgstr ""
-#: languages/states.php:2459
+#: languages/states.php:2484
msgid "Tana River County"
msgstr ""
-#: languages/states.php:2460
+#: languages/states.php:2485
msgid "Tharaka Nithi County"
msgstr ""
-#: languages/states.php:2461
+#: languages/states.php:2486
msgid "Trans-Nzoia District"
msgstr ""
-#: languages/states.php:2462
+#: languages/states.php:2487
msgid "Turkana County"
msgstr ""
-#: languages/states.php:2463
+#: languages/states.php:2488
msgid "Uasin Gishu District"
msgstr ""
-#: languages/states.php:2464
+#: languages/states.php:2489
msgid "Vihiga District"
msgstr ""
-#: languages/states.php:2465
+#: languages/states.php:2490
msgid "Wajir County"
msgstr ""
-#: languages/states.php:2466
+#: languages/states.php:2491
msgid "West Pokot County"
msgstr ""
-#: languages/states.php:2467
+#: languages/states.php:2492
msgid "Nairobi"
msgstr ""
-#: languages/states.php:2468
-#: languages/states.php:2668
-#: languages/states.php:3368
-#: languages/states.php:3717
-#: languages/states.php:4120
-#: languages/states.php:5312
+#: languages/states.php:2493
+#: languages/states.php:2693
+#: languages/states.php:3393
+#: languages/states.php:3742
+#: languages/states.php:4145
+#: languages/states.php:5337
msgid "Central Province"
msgstr ""
-#: languages/states.php:2469
+#: languages/states.php:2494
msgid "Coast Province"
msgstr ""
-#: languages/states.php:2470
-#: languages/states.php:2671
-#: languages/states.php:4098
-#: languages/states.php:4111
-#: languages/states.php:4432
-#: languages/states.php:5314
+#: languages/states.php:2495
+#: languages/states.php:2696
+#: languages/states.php:4123
+#: languages/states.php:4136
+#: languages/states.php:4457
+#: languages/states.php:5339
msgid "Eastern Province"
msgstr ""
-#: languages/states.php:2471
+#: languages/states.php:2496
msgid "North Eastern Province"
msgstr ""
-#: languages/states.php:2472
+#: languages/states.php:2497
msgid "Nyanza Province"
msgstr ""
-#: languages/states.php:2473
+#: languages/states.php:2498
msgid "Rift Valley Province"
msgstr ""
-#: languages/states.php:2474
-#: languages/states.php:2667
-#: languages/states.php:3736
-#: languages/states.php:4102
-#: languages/states.php:4129
-#: languages/states.php:5320
+#: languages/states.php:2499
+#: languages/states.php:2692
+#: languages/states.php:3761
+#: languages/states.php:4127
+#: languages/states.php:4154
+#: languages/states.php:5345
msgid "Western Province"
msgstr ""
-#: languages/states.php:2475
+#: languages/states.php:2500
msgid "Baringo County"
msgstr ""
-#: languages/states.php:2476
+#: languages/states.php:2501
msgid "Bomet County"
msgstr ""
-#: languages/states.php:2477
+#: languages/states.php:2502
msgid "Bungoma County"
msgstr ""
-#: languages/states.php:2478
+#: languages/states.php:2503
msgid "Busia County"
msgstr ""
-#: languages/states.php:2479
+#: languages/states.php:2504
msgid "Elgeyo-Marakwet County"
msgstr ""
-#: languages/states.php:2480
+#: languages/states.php:2505
msgid "Embu County"
msgstr ""
-#: languages/states.php:2481
+#: languages/states.php:2506
msgid "Garissa County"
msgstr ""
-#: languages/states.php:2482
+#: languages/states.php:2507
msgid "Homa Bay County"
msgstr ""
-#: languages/states.php:2483
+#: languages/states.php:2508
msgid "Isiolo County"
msgstr ""
-#: languages/states.php:2486
+#: languages/states.php:2511
msgid "Batken Region"
msgstr ""
-#: languages/states.php:2487
+#: languages/states.php:2512
msgid "Bishkek"
msgstr ""
-#: languages/states.php:2488
+#: languages/states.php:2513
msgid "Chuy Region"
msgstr ""
-#: languages/states.php:2489
+#: languages/states.php:2514
msgid "Issyk-Kul Region"
msgstr ""
-#: languages/states.php:2490
+#: languages/states.php:2515
msgid "Jalal-Abad Region"
msgstr ""
-#: languages/states.php:2491
+#: languages/states.php:2516
msgid "Naryn Region"
msgstr ""
-#: languages/states.php:2492
+#: languages/states.php:2517
msgid "Osh"
msgstr ""
-#: languages/states.php:2493
+#: languages/states.php:2518
msgid "Osh Region"
msgstr ""
-#: languages/states.php:2494
+#: languages/states.php:2519
msgid "Talas Region"
msgstr ""
-#: languages/states.php:2497
+#: languages/states.php:2522
msgid "Banteay Meanchey Province"
msgstr ""
-#: languages/states.php:2498
+#: languages/states.php:2523
msgid "Battambang Province"
msgstr ""
-#: languages/states.php:2499
+#: languages/states.php:2524
msgid "Kampong Cham Province"
msgstr ""
-#: languages/states.php:2500
+#: languages/states.php:2525
msgid "Kampong Chhnang Province"
msgstr ""
-#: languages/states.php:2501
+#: languages/states.php:2526
msgid "Kampong Speu Province"
msgstr ""
-#: languages/states.php:2502
+#: languages/states.php:2527
msgid "Kampot Province"
msgstr ""
-#: languages/states.php:2503
+#: languages/states.php:2528
msgid "Kandal Province"
msgstr ""
-#: languages/states.php:2504
+#: languages/states.php:2529
msgid "Koh Kong Province"
msgstr ""
-#: languages/states.php:2505
+#: languages/states.php:2530
msgid "Kratié Province"
msgstr ""
-#: languages/states.php:2506
+#: languages/states.php:2531
msgid "Mondulkiri Province"
msgstr ""
-#: languages/states.php:2507
+#: languages/states.php:2532
msgid "Phnom Penh"
msgstr ""
-#: languages/states.php:2508
+#: languages/states.php:2533
msgid "Preah Vihear Province"
msgstr ""
-#: languages/states.php:2509
+#: languages/states.php:2534
msgid "Prey Veng Province"
msgstr ""
-#: languages/states.php:2510
+#: languages/states.php:2535
msgid "Pursat Province"
msgstr ""
-#: languages/states.php:2511
+#: languages/states.php:2536
msgid "Ratanakiri Province"
msgstr ""
-#: languages/states.php:2512
+#: languages/states.php:2537
msgid "Siem Reap Province"
msgstr ""
-#: languages/states.php:2513
+#: languages/states.php:2538
msgid "Sihanoukville Province"
msgstr ""
-#: languages/states.php:2514
+#: languages/states.php:2539
msgid "Stung Treng Province"
msgstr ""
-#: languages/states.php:2515
+#: languages/states.php:2540
msgid "Svay Rieng Province"
msgstr ""
-#: languages/states.php:2516
+#: languages/states.php:2541
msgid "Takéo Province"
msgstr ""
-#: languages/states.php:2517
+#: languages/states.php:2542
msgid "Oddar Meanchey Province"
msgstr ""
-#: languages/states.php:2518
+#: languages/states.php:2543
msgid "Kep Province"
msgstr ""
-#: languages/states.php:2519
+#: languages/states.php:2544
msgid "Pailin Province"
msgstr ""
-#: languages/states.php:2522
+#: languages/states.php:2547
msgid "Gilbert Islands"
msgstr ""
-#: languages/states.php:2523
+#: languages/states.php:2548
msgid "Line Islands"
msgstr ""
-#: languages/states.php:2524
+#: languages/states.php:2549
msgid "Phoenix Islands"
msgstr ""
-#: languages/states.php:2527
+#: languages/states.php:2552
msgid "Anjouan"
msgstr ""
-#: languages/states.php:2528
+#: languages/states.php:2553
msgid "Grande Comore"
msgstr ""
-#: languages/states.php:2529
+#: languages/states.php:2554
msgid "Mohéli"
msgstr ""
-#: languages/states.php:2532
+#: languages/states.php:2557
msgid "Saint Paul Charlestown Parish"
msgstr ""
-#: languages/states.php:2533
+#: languages/states.php:2558
msgid "Saint Peter Basseterre Parish"
msgstr ""
-#: languages/states.php:2534
+#: languages/states.php:2559
msgid "Saint Thomas Lowland Parish"
msgstr ""
-#: languages/states.php:2535
+#: languages/states.php:2560
msgid "Saint Thomas Middle Island Parish"
msgstr ""
-#: languages/states.php:2536
+#: languages/states.php:2561
msgid "Trinity Palmetto Point Parish"
msgstr ""
-#: languages/states.php:2537
+#: languages/states.php:2562
msgid "Christ Church Nichola Town Parish"
msgstr ""
-#: languages/states.php:2538
+#: languages/states.php:2563
msgid "Nevis"
msgstr ""
-#: languages/states.php:2539
+#: languages/states.php:2564
msgid "Saint Anne Sandy Point Parish"
msgstr ""
-#: languages/states.php:2540
+#: languages/states.php:2565
msgid "Saint George Gingerland Parish"
msgstr ""
-#: languages/states.php:2541
+#: languages/states.php:2566
msgid "Saint James Windward Parish"
msgstr ""
-#: languages/states.php:2542
+#: languages/states.php:2567
msgid "Saint John Capisterre Parish"
msgstr ""
-#: languages/states.php:2543
+#: languages/states.php:2568
msgid "Saint John Figtree Parish"
msgstr ""
-#: languages/states.php:2544
+#: languages/states.php:2569
msgid "Saint Kitts"
msgstr ""
-#: languages/states.php:2545
+#: languages/states.php:2570
msgid "Saint Mary Cayon Parish"
msgstr ""
-#: languages/states.php:2546
+#: languages/states.php:2571
msgid "Saint Paul Capisterre Parish"
msgstr ""
-#: languages/states.php:2549
+#: languages/states.php:2574
msgid "Ryanggang Province"
msgstr ""
-#: languages/states.php:2550
+#: languages/states.php:2575
msgid "Rason"
msgstr ""
-#: languages/states.php:2551
+#: languages/states.php:2576
msgid "Chagang Province"
msgstr ""
-#: languages/states.php:2552
+#: languages/states.php:2577
msgid "Kangwon Province"
msgstr ""
-#: languages/states.php:2553
+#: languages/states.php:2578
msgid "North Hamgyong Province"
msgstr ""
-#: languages/states.php:2554
+#: languages/states.php:2579
msgid "North Hwanghae Province"
msgstr ""
-#: languages/states.php:2555
+#: languages/states.php:2580
msgid "North Pyongan Province"
msgstr ""
-#: languages/states.php:2556
+#: languages/states.php:2581
msgid "Pyongyang"
msgstr ""
-#: languages/states.php:2557
+#: languages/states.php:2582
msgid "South Hamgyong Province"
msgstr ""
-#: languages/states.php:2558
+#: languages/states.php:2583
msgid "South Hwanghae Province"
msgstr ""
-#: languages/states.php:2559
+#: languages/states.php:2584
msgid "South Pyongan Province"
msgstr ""
-#: languages/states.php:2562
+#: languages/states.php:2587
msgid "Seoul"
msgstr ""
-#: languages/states.php:2563
+#: languages/states.php:2588
msgid "Busan"
msgstr ""
-#: languages/states.php:2564
+#: languages/states.php:2589
msgid "Daegu"
msgstr ""
-#: languages/states.php:2565
+#: languages/states.php:2590
msgid "Incheon"
msgstr ""
-#: languages/states.php:2566
+#: languages/states.php:2591
msgid "Gwangju"
msgstr ""
-#: languages/states.php:2567
+#: languages/states.php:2592
msgid "Daejeon"
msgstr ""
-#: languages/states.php:2568
+#: languages/states.php:2593
msgid "Ulsan"
msgstr ""
-#: languages/states.php:2569
+#: languages/states.php:2594
msgid "Gyeonggi Province"
msgstr ""
-#: languages/states.php:2570
+#: languages/states.php:2595
msgid "Gangwon Province"
msgstr ""
-#: languages/states.php:2571
+#: languages/states.php:2596
msgid "North Chungcheong Province"
msgstr ""
-#: languages/states.php:2572
+#: languages/states.php:2597
msgid "South Chungcheong Province"
msgstr ""
-#: languages/states.php:2573
+#: languages/states.php:2598
msgid "North Jeolla Province"
msgstr ""
-#: languages/states.php:2574
+#: languages/states.php:2599
msgid "South Jeolla Province"
msgstr ""
-#: languages/states.php:2575
+#: languages/states.php:2600
msgid "North Gyeongsang Province"
msgstr ""
-#: languages/states.php:2576
+#: languages/states.php:2601
msgid "South Gyeongsang Province"
msgstr ""
-#: languages/states.php:2577
+#: languages/states.php:2602
msgid "Jeju"
msgstr ""
-#: languages/states.php:2578
+#: languages/states.php:2603
msgid "Sejong City"
msgstr ""
-#: languages/states.php:2581
+#: languages/states.php:2606
msgid "Al Ahmadi Governorate"
msgstr ""
-#: languages/states.php:2582
+#: languages/states.php:2607
msgid "Al Farwaniyah Governorate"
msgstr ""
-#: languages/states.php:2583
+#: languages/states.php:2608
msgid "Al Jahra Governorate"
msgstr ""
-#: languages/states.php:2585
+#: languages/states.php:2610
msgid "Hawalli Governorate"
msgstr ""
-#: languages/states.php:2586
+#: languages/states.php:2611
msgid "Mubarak Al-Kabeer Governorate"
msgstr ""
-#: languages/states.php:2590
+#: languages/states.php:2615
msgid "Akmola Region"
msgstr ""
-#: languages/states.php:2591
+#: languages/states.php:2616
msgid "Aktobe Region"
msgstr ""
-#: languages/states.php:2592
+#: languages/states.php:2617
msgid "Almaty"
msgstr ""
-#: languages/states.php:2593
+#: languages/states.php:2618
msgid "Almaty Region"
msgstr ""
-#: languages/states.php:2594
+#: languages/states.php:2619
msgid "Atyrau Region"
msgstr ""
-#: languages/states.php:2595
+#: languages/states.php:2620
msgid "Baikonur"
msgstr ""
-#: languages/states.php:2596
+#: languages/states.php:2621
msgid "East Kazakhstan Region"
msgstr ""
-#: languages/states.php:2597
+#: languages/states.php:2622
msgid "Jambyl Region"
msgstr ""
-#: languages/states.php:2598
+#: languages/states.php:2623
msgid "Karaganda Region"
msgstr ""
-#: languages/states.php:2599
+#: languages/states.php:2624
msgid "Kostanay Region"
msgstr ""
-#: languages/states.php:2600
+#: languages/states.php:2625
msgid "Kyzylorda Region"
msgstr ""
-#: languages/states.php:2601
+#: languages/states.php:2626
msgid "Mangystau Region"
msgstr ""
-#: languages/states.php:2602
+#: languages/states.php:2627
msgid "North Kazakhstan Region"
msgstr ""
-#: languages/states.php:2603
+#: languages/states.php:2628
msgid "Nur-Sultan"
msgstr ""
-#: languages/states.php:2604
+#: languages/states.php:2629
msgid "Pavlodar Region"
msgstr ""
-#: languages/states.php:2605
+#: languages/states.php:2630
msgid "Turkestan Region"
msgstr ""
-#: languages/states.php:2606
+#: languages/states.php:2631
msgid "West Kazakhstan Province"
msgstr ""
-#: languages/states.php:2609
+#: languages/states.php:2634
msgid "Attapeu Province"
msgstr ""
-#: languages/states.php:2610
+#: languages/states.php:2635
msgid "Bokeo Province"
msgstr ""
-#: languages/states.php:2611
+#: languages/states.php:2636
msgid "Bolikhamsai Province"
msgstr ""
-#: languages/states.php:2612
+#: languages/states.php:2637
msgid "Champasak Province"
msgstr ""
-#: languages/states.php:2613
+#: languages/states.php:2638
msgid "Houaphanh Province"
msgstr ""
-#: languages/states.php:2614
+#: languages/states.php:2639
msgid "Khammouane Province"
msgstr ""
-#: languages/states.php:2615
+#: languages/states.php:2640
msgid "Luang Namtha Province"
msgstr ""
-#: languages/states.php:2616
+#: languages/states.php:2641
msgid "Luang Prabang Province"
msgstr ""
-#: languages/states.php:2617
+#: languages/states.php:2642
msgid "Oudomxay Province"
msgstr ""
-#: languages/states.php:2618
+#: languages/states.php:2643
msgid "Phongsaly Province"
msgstr ""
-#: languages/states.php:2619
+#: languages/states.php:2644
msgid "Sainyabuli Province"
msgstr ""
-#: languages/states.php:2620
+#: languages/states.php:2645
msgid "Salavan Province"
msgstr ""
-#: languages/states.php:2621
+#: languages/states.php:2646
msgid "Savannakhet Province"
msgstr ""
-#: languages/states.php:2622
+#: languages/states.php:2647
msgid "Sekong Province"
msgstr ""
-#: languages/states.php:2623
+#: languages/states.php:2648
msgid "Vientiane Prefecture"
msgstr ""
-#: languages/states.php:2624
+#: languages/states.php:2649
msgid "Vientiane Province"
msgstr ""
-#: languages/states.php:2625
+#: languages/states.php:2650
msgid "Xaisomboun"
msgstr ""
-#: languages/states.php:2626
+#: languages/states.php:2651
msgid "Xaisomboun Province"
msgstr ""
-#: languages/states.php:2627
+#: languages/states.php:2652
msgid "Xiangkhouang Province"
msgstr ""
-#: languages/states.php:2630
+#: languages/states.php:2655
msgid "Akkar Governorate"
msgstr ""
-#: languages/states.php:2631
+#: languages/states.php:2656
msgid "Baalbek-Hermel Governorate"
msgstr ""
-#: languages/states.php:2632
+#: languages/states.php:2657
msgid "Beirut Governorate"
msgstr ""
-#: languages/states.php:2633
+#: languages/states.php:2658
msgid "Beqaa Governorate"
msgstr ""
-#: languages/states.php:2634
+#: languages/states.php:2659
msgid "Mount Lebanon Governorate"
msgstr ""
-#: languages/states.php:2635
+#: languages/states.php:2660
msgid "Nabatieh Governorate"
msgstr ""
-#: languages/states.php:2636
+#: languages/states.php:2661
msgid "North Governorate"
msgstr ""
-#: languages/states.php:2637
+#: languages/states.php:2662
msgid "South Governorate"
msgstr ""
-#: languages/states.php:2640
+#: languages/states.php:2665
msgid "Soufrière Quarter"
msgstr ""
-#: languages/states.php:2641
+#: languages/states.php:2666
msgid "Vieux Fort Quarter"
msgstr ""
-#: languages/states.php:2642
+#: languages/states.php:2667
msgid "Canaries"
msgstr ""
-#: languages/states.php:2643
+#: languages/states.php:2668
msgid "Anse la Raye Quarter"
msgstr ""
-#: languages/states.php:2644
+#: languages/states.php:2669
msgid "Castries Quarter"
msgstr ""
-#: languages/states.php:2645
+#: languages/states.php:2670
msgid "Choiseul Quarter"
msgstr ""
-#: languages/states.php:2646
+#: languages/states.php:2671
msgid "Dauphin Quarter"
msgstr ""
-#: languages/states.php:2647
+#: languages/states.php:2672
msgid "Dennery Quarter"
msgstr ""
-#: languages/states.php:2648
+#: languages/states.php:2673
msgid "Gros Islet Quarter"
msgstr ""
-#: languages/states.php:2649
+#: languages/states.php:2674
msgid "Laborie Quarter"
msgstr ""
-#: languages/states.php:2650
+#: languages/states.php:2675
msgid "Micoud Quarter"
msgstr ""
-#: languages/states.php:2651
+#: languages/states.php:2676
msgid "Praslin Quarter"
msgstr ""
-#: languages/states.php:2654
+#: languages/states.php:2679
msgid "Triesenberg"
msgstr ""
-#: languages/states.php:2655
+#: languages/states.php:2680
msgid "Vaduz"
msgstr ""
-#: languages/states.php:2656
+#: languages/states.php:2681
msgid "Balzers"
msgstr ""
-#: languages/states.php:2657
+#: languages/states.php:2682
msgid "Eschen"
msgstr ""
-#: languages/states.php:2658
+#: languages/states.php:2683
msgid "Gamprin"
msgstr ""
-#: languages/states.php:2659
+#: languages/states.php:2684
msgid "Mauren"
msgstr ""
-#: languages/states.php:2660
+#: languages/states.php:2685
msgid "Planken"
msgstr ""
-#: languages/states.php:2661
+#: languages/states.php:2686
msgid "Ruggell"
msgstr ""
-#: languages/states.php:2662
+#: languages/states.php:2687
msgid "Schaan"
msgstr ""
-#: languages/states.php:2663
+#: languages/states.php:2688
msgid "Schellenberg"
msgstr ""
-#: languages/states.php:2664
+#: languages/states.php:2689
msgid "Triesen"
msgstr ""
-#: languages/states.php:2669
-#: languages/states.php:4101
-#: languages/states.php:4434
-#: languages/states.php:5319
+#: languages/states.php:2694
+#: languages/states.php:4126
+#: languages/states.php:4459
+#: languages/states.php:5344
msgid "Southern Province"
msgstr ""
-#: languages/states.php:2670
-#: languages/states.php:4100
-#: languages/states.php:4433
-#: languages/states.php:5317
+#: languages/states.php:2695
+#: languages/states.php:4125
+#: languages/states.php:4458
+#: languages/states.php:5342
msgid "Northern Province"
msgstr ""
-#: languages/states.php:2672
+#: languages/states.php:2697
msgid "North Western Province"
msgstr ""
-#: languages/states.php:2673
-#: languages/states.php:3373
+#: languages/states.php:2698
+#: languages/states.php:3398
msgid "North Central Province"
msgstr ""
-#: languages/states.php:2674
+#: languages/states.php:2699
msgid "Uva Province"
msgstr ""
-#: languages/states.php:2675
+#: languages/states.php:2700
msgid "Sabaragamuwa Province"
msgstr ""
-#: languages/states.php:2676
+#: languages/states.php:2701
msgid "Colombo District"
msgstr ""
-#: languages/states.php:2677
+#: languages/states.php:2702
msgid "Gampaha District"
msgstr ""
-#: languages/states.php:2678
+#: languages/states.php:2703
msgid "Kalutara District"
msgstr ""
-#: languages/states.php:2679
+#: languages/states.php:2704
msgid "Kandy District"
msgstr ""
-#: languages/states.php:2680
+#: languages/states.php:2705
msgid "Matale District"
msgstr ""
-#: languages/states.php:2681
+#: languages/states.php:2706
msgid "Nuwara Eliya District"
msgstr ""
-#: languages/states.php:2682
+#: languages/states.php:2707
msgid "Galle District"
msgstr ""
-#: languages/states.php:2683
+#: languages/states.php:2708
msgid "Matara District"
msgstr ""
-#: languages/states.php:2684
+#: languages/states.php:2709
msgid "Hambantota District"
msgstr ""
-#: languages/states.php:2685
+#: languages/states.php:2710
msgid "Jaffna District"
msgstr ""
-#: languages/states.php:2686
+#: languages/states.php:2711
msgid "Kilinochchi District"
msgstr ""
-#: languages/states.php:2687
+#: languages/states.php:2712
msgid "Mannar District"
msgstr ""
-#: languages/states.php:2688
+#: languages/states.php:2713
msgid "Vavuniya District"
msgstr ""
-#: languages/states.php:2689
+#: languages/states.php:2714
msgid "Mullaitivu District"
msgstr ""
-#: languages/states.php:2690
+#: languages/states.php:2715
msgid "Batticaloa District"
msgstr ""
-#: languages/states.php:2691
+#: languages/states.php:2716
msgid "Ampara District"
msgstr ""
-#: languages/states.php:2692
+#: languages/states.php:2717
msgid "Trincomalee District"
msgstr ""
-#: languages/states.php:2693
+#: languages/states.php:2718
msgid "Puttalam District"
msgstr ""
-#: languages/states.php:2694
+#: languages/states.php:2719
msgid "Anuradhapura District"
msgstr ""
-#: languages/states.php:2695
+#: languages/states.php:2720
msgid "Polonnaruwa District"
msgstr ""
-#: languages/states.php:2696
+#: languages/states.php:2721
msgid "Badulla District"
msgstr ""
-#: languages/states.php:2697
+#: languages/states.php:2722
msgid "Monaragala District"
msgstr ""
-#: languages/states.php:2698
+#: languages/states.php:2723
msgid "Ratnapura district"
msgstr ""
-#: languages/states.php:2699
+#: languages/states.php:2724
msgid "Kegalle District"
msgstr ""
-#: languages/states.php:2702
+#: languages/states.php:2727
msgid "Bomi County"
msgstr ""
-#: languages/states.php:2703
+#: languages/states.php:2728
msgid "Bong County"
msgstr ""
-#: languages/states.php:2704
+#: languages/states.php:2729
msgid "Gbarpolu County"
msgstr ""
-#: languages/states.php:2705
+#: languages/states.php:2730
msgid "Grand Bassa County"
msgstr ""
-#: languages/states.php:2706
+#: languages/states.php:2731
msgid "Grand Cape Mount County"
msgstr ""
-#: languages/states.php:2707
+#: languages/states.php:2732
msgid "Grand Gedeh County"
msgstr ""
-#: languages/states.php:2708
+#: languages/states.php:2733
msgid "Grand Kru County"
msgstr ""
-#: languages/states.php:2709
+#: languages/states.php:2734
msgid "Lofa County"
msgstr ""
-#: languages/states.php:2710
+#: languages/states.php:2735
msgid "Margibi County"
msgstr ""
-#: languages/states.php:2711
+#: languages/states.php:2736
msgid "Maryland County"
msgstr ""
-#: languages/states.php:2712
+#: languages/states.php:2737
msgid "Montserrado County"
msgstr ""
-#: languages/states.php:2713
+#: languages/states.php:2738
msgid "Nimba"
msgstr ""
-#: languages/states.php:2714
+#: languages/states.php:2739
msgid "River Cess County"
msgstr ""
-#: languages/states.php:2715
+#: languages/states.php:2740
msgid "River Gee County"
msgstr ""
-#: languages/states.php:2716
+#: languages/states.php:2741
msgid "Sinoe County"
msgstr ""
-#: languages/states.php:2719
+#: languages/states.php:2744
msgid "Berea District"
msgstr ""
-#: languages/states.php:2720
+#: languages/states.php:2745
msgid "Butha-Buthe District"
msgstr ""
-#: languages/states.php:2721
+#: languages/states.php:2746
msgid "Leribe District"
msgstr ""
-#: languages/states.php:2722
+#: languages/states.php:2747
msgid "Mafeteng District"
msgstr ""
-#: languages/states.php:2723
+#: languages/states.php:2748
msgid "Maseru District"
msgstr ""
-#: languages/states.php:2724
+#: languages/states.php:2749
msgid "Mohale's Hoek District"
msgstr ""
-#: languages/states.php:2725
+#: languages/states.php:2750
msgid "Mokhotlong District"
msgstr ""
-#: languages/states.php:2726
+#: languages/states.php:2751
msgid "Qacha's Nek District"
msgstr ""
-#: languages/states.php:2727
+#: languages/states.php:2752
msgid "Quthing District"
msgstr ""
-#: languages/states.php:2728
+#: languages/states.php:2753
msgid "Thaba-Tseka District"
msgstr ""
-#: languages/states.php:2731
+#: languages/states.php:2756
msgid "Jonava District Municipality"
msgstr ""
-#: languages/states.php:2732
+#: languages/states.php:2757
msgid "Joniškis District Municipality"
msgstr ""
-#: languages/states.php:2733
+#: languages/states.php:2758
msgid "Jurbarkas District Municipality"
msgstr ""
-#: languages/states.php:2734
+#: languages/states.php:2759
msgid "Kaišiadorys District Municipality"
msgstr ""
-#: languages/states.php:2735
+#: languages/states.php:2760
msgid "Kalvarija municipality"
msgstr ""
-#: languages/states.php:2736
+#: languages/states.php:2761
msgid "Kaunas City Municipality"
msgstr ""
-#: languages/states.php:2737
+#: languages/states.php:2762
msgid "Kaunas District Municipality"
msgstr ""
-#: languages/states.php:2738
+#: languages/states.php:2763
msgid "Kazlų Rūda municipality"
msgstr ""
-#: languages/states.php:2739
+#: languages/states.php:2764
msgid "Kėdainiai District Municipality"
msgstr ""
-#: languages/states.php:2740
+#: languages/states.php:2765
msgid "Kelmė District Municipality"
msgstr ""
-#: languages/states.php:2741
+#: languages/states.php:2766
msgid "Klaipeda City Municipality"
msgstr ""
-#: languages/states.php:2742
+#: languages/states.php:2767
msgid "Klaipėda District Municipality"
msgstr ""
-#: languages/states.php:2743
+#: languages/states.php:2768
msgid "Kretinga District Municipality"
msgstr ""
-#: languages/states.php:2744
+#: languages/states.php:2769
msgid "Kupiškis District Municipality"
msgstr ""
-#: languages/states.php:2745
+#: languages/states.php:2770
msgid "Lazdijai District Municipality"
msgstr ""
-#: languages/states.php:2746
+#: languages/states.php:2771
msgid "Marijampolė Municipality"
msgstr ""
-#: languages/states.php:2747
+#: languages/states.php:2772
msgid "Mažeikiai District Municipality"
msgstr ""
-#: languages/states.php:2748
+#: languages/states.php:2773
msgid "Molėtai District Municipality"
msgstr ""
-#: languages/states.php:2749
+#: languages/states.php:2774
msgid "Neringa Municipality"
msgstr ""
-#: languages/states.php:2750
+#: languages/states.php:2775
msgid "Pagėgiai municipality"
msgstr ""
-#: languages/states.php:2751
+#: languages/states.php:2776
msgid "Pakruojis District Municipality"
msgstr ""
-#: languages/states.php:2752
+#: languages/states.php:2777
msgid "Palanga City Municipality"
msgstr ""
-#: languages/states.php:2753
+#: languages/states.php:2778
msgid "Panevėžys City Municipality"
msgstr ""
-#: languages/states.php:2754
+#: languages/states.php:2779
msgid "Panevėžys District Municipality"
msgstr ""
-#: languages/states.php:2755
+#: languages/states.php:2780
msgid "Pasvalys District Municipality"
msgstr ""
-#: languages/states.php:2756
+#: languages/states.php:2781
msgid "Plungė District Municipality"
msgstr ""
-#: languages/states.php:2757
+#: languages/states.php:2782
msgid "Prienai District Municipality"
msgstr ""
-#: languages/states.php:2758
+#: languages/states.php:2783
msgid "Radviliškis District Municipality"
msgstr ""
-#: languages/states.php:2759
+#: languages/states.php:2784
msgid "Raseiniai District Municipality"
msgstr ""
-#: languages/states.php:2760
+#: languages/states.php:2785
msgid "Rietavas municipality"
msgstr ""
-#: languages/states.php:2761
+#: languages/states.php:2786
msgid "Rokiškis District Municipality"
msgstr ""
-#: languages/states.php:2762
+#: languages/states.php:2787
msgid "Šakiai District Municipality"
msgstr ""
-#: languages/states.php:2763
+#: languages/states.php:2788
msgid "Šalčininkai District Municipality"
msgstr ""
-#: languages/states.php:2764
+#: languages/states.php:2789
msgid "Šiauliai City Municipality"
msgstr ""
-#: languages/states.php:2765
+#: languages/states.php:2790
msgid "Šiauliai District Municipality"
msgstr ""
-#: languages/states.php:2766
+#: languages/states.php:2791
msgid "Šilalė District Municipality"
msgstr ""
-#: languages/states.php:2767
+#: languages/states.php:2792
msgid "Šilutė District Municipality"
msgstr ""
-#: languages/states.php:2768
+#: languages/states.php:2793
msgid "Širvintos District Municipality"
msgstr ""
-#: languages/states.php:2769
+#: languages/states.php:2794
msgid "Skuodas District Municipality"
msgstr ""
-#: languages/states.php:2770
+#: languages/states.php:2795
msgid "Švenčionys District Municipality"
msgstr ""
-#: languages/states.php:2771
+#: languages/states.php:2796
msgid "Tauragė District Municipality"
msgstr ""
-#: languages/states.php:2772
+#: languages/states.php:2797
msgid "Telšiai District Municipality"
msgstr ""
-#: languages/states.php:2773
+#: languages/states.php:2798
msgid "Trakai District Municipality"
msgstr ""
-#: languages/states.php:2774
+#: languages/states.php:2799
msgid "Ukmergė District Municipality"
msgstr ""
-#: languages/states.php:2775
+#: languages/states.php:2800
msgid "Utena District Municipality"
msgstr ""
-#: languages/states.php:2776
+#: languages/states.php:2801
msgid "Varėna District Municipality"
msgstr ""
-#: languages/states.php:2777
+#: languages/states.php:2802
msgid "Vilkaviškis District Municipality"
msgstr ""
-#: languages/states.php:2778
+#: languages/states.php:2803
msgid "Vilnius City Municipality"
msgstr ""
-#: languages/states.php:2779
+#: languages/states.php:2804
msgid "Vilnius District Municipality"
msgstr ""
-#: languages/states.php:2780
+#: languages/states.php:2805
msgid "Visaginas Municipality"
msgstr ""
-#: languages/states.php:2781
+#: languages/states.php:2806
msgid "Zarasai District Municipality"
msgstr ""
-#: languages/states.php:2782
+#: languages/states.php:2807
msgid "Akmenė District Municipality"
msgstr ""
-#: languages/states.php:2783
+#: languages/states.php:2808
msgid "Alytus City Municipality"
msgstr ""
-#: languages/states.php:2784
+#: languages/states.php:2809
msgid "Alytus County"
msgstr ""
-#: languages/states.php:2785
+#: languages/states.php:2810
msgid "Alytus District Municipality"
msgstr ""
-#: languages/states.php:2786
+#: languages/states.php:2811
msgid "Birštonas Municipality"
msgstr ""
-#: languages/states.php:2787
+#: languages/states.php:2812
msgid "Biržai District Municipality"
msgstr ""
-#: languages/states.php:2788
+#: languages/states.php:2813
msgid "Druskininkai municipality"
msgstr ""
-#: languages/states.php:2789
+#: languages/states.php:2814
msgid "Elektrėnai municipality"
msgstr ""
-#: languages/states.php:2790
+#: languages/states.php:2815
msgid "Ignalina District Municipality"
msgstr ""
-#: languages/states.php:2791
+#: languages/states.php:2816
msgid "Kaunas County"
msgstr ""
-#: languages/states.php:2792
+#: languages/states.php:2817
msgid "Klaipėda County"
msgstr ""
-#: languages/states.php:2793
+#: languages/states.php:2818
msgid "Marijampolė County"
msgstr ""
-#: languages/states.php:2794
+#: languages/states.php:2819
msgid "Panevėžys County"
msgstr ""
-#: languages/states.php:2795
+#: languages/states.php:2820
msgid "Tauragė County"
msgstr ""
-#: languages/states.php:2796
+#: languages/states.php:2821
msgid "Telšiai County"
msgstr ""
-#: languages/states.php:2797
+#: languages/states.php:2822
msgid "Utena County"
msgstr ""
-#: languages/states.php:2798
+#: languages/states.php:2823
msgid "Vilnius County"
msgstr ""
-#: languages/states.php:2799
+#: languages/states.php:2824
msgid "Šiauliai County"
msgstr ""
-#: languages/states.php:2802
+#: languages/states.php:2827
msgid "Canton of Capellen"
msgstr ""
-#: languages/states.php:2803
+#: languages/states.php:2828
msgid "Canton of Clervaux"
msgstr ""
-#: languages/states.php:2804
+#: languages/states.php:2829
msgid "Canton of Diekirch"
msgstr ""
-#: languages/states.php:2805
+#: languages/states.php:2830
msgid "Canton of Echternach"
msgstr ""
-#: languages/states.php:2806
+#: languages/states.php:2831
msgid "Canton of Esch-sur-Alzette"
msgstr ""
-#: languages/states.php:2807
+#: languages/states.php:2832
msgid "Canton of Grevenmacher"
msgstr ""
-#: languages/states.php:2808
+#: languages/states.php:2833
msgid "Canton of Luxembourg"
msgstr ""
-#: languages/states.php:2809
+#: languages/states.php:2834
msgid "Canton of Mersch"
msgstr ""
-#: languages/states.php:2810
+#: languages/states.php:2835
msgid "Canton of Redange"
msgstr ""
-#: languages/states.php:2811
+#: languages/states.php:2836
msgid "Canton of Remich"
msgstr ""
-#: languages/states.php:2812
+#: languages/states.php:2837
msgid "Canton of Vianden"
msgstr ""
-#: languages/states.php:2813
+#: languages/states.php:2838
msgid "Canton of Wiltz"
msgstr ""
-#: languages/states.php:2814
+#: languages/states.php:2839
msgid "Diekirch District"
msgstr ""
-#: languages/states.php:2815
+#: languages/states.php:2840
msgid "Grevenmacher District"
msgstr ""
-#: languages/states.php:2816
+#: languages/states.php:2841
msgid "Luxembourg District"
msgstr ""
-#: languages/states.php:2819
+#: languages/states.php:2844
msgid "Vaiņode Municipality"
msgstr ""
-#: languages/states.php:2820
+#: languages/states.php:2845
msgid "Valka Municipality"
msgstr ""
-#: languages/states.php:2821
+#: languages/states.php:2846
msgid "Varakļāni Municipality"
msgstr ""
-#: languages/states.php:2822
+#: languages/states.php:2847
msgid "Vārkava Municipality"
msgstr ""
-#: languages/states.php:2823
+#: languages/states.php:2848
msgid "Vecpiebalga Municipality"
msgstr ""
-#: languages/states.php:2824
+#: languages/states.php:2849
msgid "Vecumnieki Municipality"
msgstr ""
-#: languages/states.php:2825
+#: languages/states.php:2850
msgid "Ventspils Municipality"
msgstr ""
-#: languages/states.php:2826
+#: languages/states.php:2851
msgid "Viesīte Municipality"
msgstr ""
-#: languages/states.php:2827
+#: languages/states.php:2852
msgid "Viļaka Municipality"
msgstr ""
-#: languages/states.php:2828
+#: languages/states.php:2853
msgid "Viļāni Municipality"
msgstr ""
-#: languages/states.php:2829
+#: languages/states.php:2854
msgid "Zilupe Municipality"
msgstr ""
-#: languages/states.php:2830
+#: languages/states.php:2855
msgid "Aglona Municipality"
msgstr ""
-#: languages/states.php:2831
+#: languages/states.php:2856
msgid "Aizkraukle Municipality"
msgstr ""
-#: languages/states.php:2832
+#: languages/states.php:2857
msgid "Aizpute Municipality"
msgstr ""
-#: languages/states.php:2833
+#: languages/states.php:2858
msgid "Aknīste Municipality"
msgstr ""
-#: languages/states.php:2834
+#: languages/states.php:2859
msgid "Aloja Municipality"
msgstr ""
-#: languages/states.php:2835
+#: languages/states.php:2860
msgid "Alsunga Municipality"
msgstr ""
-#: languages/states.php:2836
+#: languages/states.php:2861
msgid "Alūksne Municipality"
msgstr ""
-#: languages/states.php:2837
+#: languages/states.php:2862
msgid "Amata Municipality"
msgstr ""
-#: languages/states.php:2838
+#: languages/states.php:2863
msgid "Ape Municipality"
msgstr ""
-#: languages/states.php:2839
+#: languages/states.php:2864
msgid "Auce Municipality"
msgstr ""
-#: languages/states.php:2840
+#: languages/states.php:2865
msgid "Babīte Municipality"
msgstr ""
-#: languages/states.php:2841
+#: languages/states.php:2866
msgid "Baldone Municipality"
msgstr ""
-#: languages/states.php:2842
+#: languages/states.php:2867
msgid "Baltinava Municipality"
msgstr ""
-#: languages/states.php:2843
+#: languages/states.php:2868
msgid "Balvi Municipality"
msgstr ""
-#: languages/states.php:2844
+#: languages/states.php:2869
msgid "Bauska Municipality"
msgstr ""
-#: languages/states.php:2845
+#: languages/states.php:2870
msgid "Beverīna Municipality"
msgstr ""
-#: languages/states.php:2846
+#: languages/states.php:2871
msgid "Brocēni Municipality"
msgstr ""
-#: languages/states.php:2847
+#: languages/states.php:2872
msgid "Burtnieki Municipality"
msgstr ""
-#: languages/states.php:2848
+#: languages/states.php:2873
msgid "Carnikava Municipality"
msgstr ""
-#: languages/states.php:2849
+#: languages/states.php:2874
msgid "Cesvaine Municipality"
msgstr ""
-#: languages/states.php:2850
+#: languages/states.php:2875
msgid "Cibla Municipality"
msgstr ""
-#: languages/states.php:2851
+#: languages/states.php:2876
msgid "Cēsis Municipality"
msgstr ""
-#: languages/states.php:2852
+#: languages/states.php:2877
msgid "Dagda Municipality"
msgstr ""
-#: languages/states.php:2853
+#: languages/states.php:2878
msgid "Daugavpils"
msgstr ""
-#: languages/states.php:2854
+#: languages/states.php:2879
msgid "Daugavpils Municipality"
msgstr ""
-#: languages/states.php:2855
+#: languages/states.php:2880
msgid "Dobele Municipality"
msgstr ""
-#: languages/states.php:2856
+#: languages/states.php:2881
msgid "Dundaga Municipality"
msgstr ""
-#: languages/states.php:2857
+#: languages/states.php:2882
msgid "Durbe Municipality"
msgstr ""
-#: languages/states.php:2858
+#: languages/states.php:2883
msgid "Engure Municipality"
msgstr ""
-#: languages/states.php:2859
+#: languages/states.php:2884
msgid "Garkalne Municipality"
msgstr ""
-#: languages/states.php:2860
+#: languages/states.php:2885
msgid "Grobiņa Municipality"
msgstr ""
-#: languages/states.php:2861
+#: languages/states.php:2886
msgid "Gulbene Municipality"
msgstr ""
-#: languages/states.php:2862
+#: languages/states.php:2887
msgid "Iecava Municipality"
msgstr ""
-#: languages/states.php:2863
+#: languages/states.php:2888
msgid "Ikšķile Municipality"
msgstr ""
-#: languages/states.php:2864
+#: languages/states.php:2889
msgid "Ilūkste Municipality"
msgstr ""
-#: languages/states.php:2865
+#: languages/states.php:2890
msgid "Inčukalns Municipality"
msgstr ""
-#: languages/states.php:2866
+#: languages/states.php:2891
msgid "Jaunjelgava Municipality"
msgstr ""
-#: languages/states.php:2867
+#: languages/states.php:2892
msgid "Jaunpiebalga Municipality"
msgstr ""
-#: languages/states.php:2868
+#: languages/states.php:2893
msgid "Jaunpils Municipality"
msgstr ""
-#: languages/states.php:2869
+#: languages/states.php:2894
msgid "Jelgava"
msgstr ""
-#: languages/states.php:2870
+#: languages/states.php:2895
msgid "Jelgava Municipality"
msgstr ""
-#: languages/states.php:2871
+#: languages/states.php:2896
msgid "Jēkabpils"
msgstr ""
-#: languages/states.php:2872
+#: languages/states.php:2897
msgid "Jēkabpils Municipality"
msgstr ""
-#: languages/states.php:2873
+#: languages/states.php:2898
msgid "Jūrmala"
msgstr ""
-#: languages/states.php:2874
+#: languages/states.php:2899
msgid "Kandava Municipality"
msgstr ""
-#: languages/states.php:2875
+#: languages/states.php:2900
msgid "Kocēni Municipality"
msgstr ""
-#: languages/states.php:2876
+#: languages/states.php:2901
msgid "Koknese Municipality"
msgstr ""
-#: languages/states.php:2877
+#: languages/states.php:2902
msgid "Krimulda Municipality"
msgstr ""
-#: languages/states.php:2878
+#: languages/states.php:2903
msgid "Krustpils Municipality"
msgstr ""
-#: languages/states.php:2879
+#: languages/states.php:2904
msgid "Krāslava Municipality"
msgstr ""
-#: languages/states.php:2880
+#: languages/states.php:2905
msgid "Kuldīga Municipality"
msgstr ""
-#: languages/states.php:2881
+#: languages/states.php:2906
msgid "Kārsava Municipality"
msgstr ""
-#: languages/states.php:2882
+#: languages/states.php:2907
msgid "Lielvārde Municipality"
msgstr ""
-#: languages/states.php:2883
+#: languages/states.php:2908
msgid "Liepāja"
msgstr ""
-#: languages/states.php:2884
+#: languages/states.php:2909
msgid "Limbaži Municipality"
msgstr ""
-#: languages/states.php:2885
+#: languages/states.php:2910
msgid "Lubāna Municipality"
msgstr ""
-#: languages/states.php:2886
+#: languages/states.php:2911
msgid "Ludza Municipality"
msgstr ""
-#: languages/states.php:2887
+#: languages/states.php:2912
msgid "Līgatne Municipality"
msgstr ""
-#: languages/states.php:2888
+#: languages/states.php:2913
msgid "Līvāni Municipality"
msgstr ""
-#: languages/states.php:2889
+#: languages/states.php:2914
msgid "Madona Municipality"
msgstr ""
-#: languages/states.php:2890
+#: languages/states.php:2915
msgid "Mazsalaca Municipality"
msgstr ""
-#: languages/states.php:2891
+#: languages/states.php:2916
msgid "Mālpils Municipality"
msgstr ""
-#: languages/states.php:2892
+#: languages/states.php:2917
msgid "Mārupe Municipality"
msgstr ""
-#: languages/states.php:2893
+#: languages/states.php:2918
msgid "Mērsrags Municipality"
msgstr ""
-#: languages/states.php:2894
+#: languages/states.php:2919
msgid "Naukšēni Municipality"
msgstr ""
-#: languages/states.php:2895
+#: languages/states.php:2920
msgid "Nereta Municipality"
msgstr ""
-#: languages/states.php:2896
+#: languages/states.php:2921
msgid "Nīca Municipality"
msgstr ""
-#: languages/states.php:2897
+#: languages/states.php:2922
msgid "Ogre Municipality"
msgstr ""
-#: languages/states.php:2898
+#: languages/states.php:2923
msgid "Olaine Municipality"
msgstr ""
-#: languages/states.php:2899
+#: languages/states.php:2924
msgid "Ozolnieki Municipality"
msgstr ""
-#: languages/states.php:2900
+#: languages/states.php:2925
msgid "Preiļi Municipality"
msgstr ""
-#: languages/states.php:2901
+#: languages/states.php:2926
msgid "Priekule Municipality"
msgstr ""
-#: languages/states.php:2902
+#: languages/states.php:2927
msgid "Priekuļi Municipality"
msgstr ""
-#: languages/states.php:2903
+#: languages/states.php:2928
msgid "Pārgauja Municipality"
msgstr ""
-#: languages/states.php:2904
+#: languages/states.php:2929
msgid "Pāvilosta Municipality"
msgstr ""
-#: languages/states.php:2905
+#: languages/states.php:2930
msgid "Pļaviņas Municipality"
msgstr ""
-#: languages/states.php:2906
+#: languages/states.php:2931
msgid "Rauna Municipality"
msgstr ""
-#: languages/states.php:2907
+#: languages/states.php:2932
msgid "Riebiņi Municipality"
msgstr ""
-#: languages/states.php:2908
+#: languages/states.php:2933
msgid "Riga"
msgstr ""
-#: languages/states.php:2909
+#: languages/states.php:2934
msgid "Roja Municipality"
msgstr ""
-#: languages/states.php:2910
+#: languages/states.php:2935
msgid "Ropaži Municipality"
msgstr ""
-#: languages/states.php:2911
+#: languages/states.php:2936
msgid "Rucava Municipality"
msgstr ""
-#: languages/states.php:2912
+#: languages/states.php:2937
msgid "Rugāji Municipality"
msgstr ""
-#: languages/states.php:2913
+#: languages/states.php:2938
msgid "Rundāle Municipality"
msgstr ""
-#: languages/states.php:2914
+#: languages/states.php:2939
msgid "Rēzekne"
msgstr ""
-#: languages/states.php:2915
+#: languages/states.php:2940
msgid "Rēzekne Municipality"
msgstr ""
-#: languages/states.php:2916
+#: languages/states.php:2941
msgid "Rūjiena Municipality"
msgstr ""
-#: languages/states.php:2917
+#: languages/states.php:2942
msgid "Sala Municipality"
msgstr ""
-#: languages/states.php:2918
+#: languages/states.php:2943
msgid "Salacgrīva Municipality"
msgstr ""
-#: languages/states.php:2919
+#: languages/states.php:2944
msgid "Salaspils Municipality"
msgstr ""
-#: languages/states.php:2920
+#: languages/states.php:2945
msgid "Saldus Municipality"
msgstr ""
-#: languages/states.php:2921
+#: languages/states.php:2946
msgid "Saulkrasti Municipality"
msgstr ""
-#: languages/states.php:2922
+#: languages/states.php:2947
msgid "Sigulda Municipality"
msgstr ""
-#: languages/states.php:2923
+#: languages/states.php:2948
msgid "Skrunda Municipality"
msgstr ""
-#: languages/states.php:2924
+#: languages/states.php:2949
msgid "Skrīveri Municipality"
msgstr ""
-#: languages/states.php:2925
+#: languages/states.php:2950
msgid "Smiltene Municipality"
msgstr ""
-#: languages/states.php:2926
+#: languages/states.php:2951
msgid "Stopiņi Municipality"
msgstr ""
-#: languages/states.php:2927
+#: languages/states.php:2952
msgid "Strenči Municipality"
msgstr ""
-#: languages/states.php:2928
+#: languages/states.php:2953
msgid "Sēja Municipality"
msgstr ""
-#: languages/states.php:2929
+#: languages/states.php:2954
msgid "Talsi Municipality"
msgstr ""
-#: languages/states.php:2930
+#: languages/states.php:2955
msgid "Tukums Municipality"
msgstr ""
-#: languages/states.php:2931
+#: languages/states.php:2956
msgid "Tērvete Municipality"
msgstr ""
-#: languages/states.php:2932
+#: languages/states.php:2957
msgid "Valmiera"
msgstr ""
-#: languages/states.php:2933
+#: languages/states.php:2958
msgid "Ventspils"
msgstr ""
-#: languages/states.php:2934
+#: languages/states.php:2959
msgid "Ērgļi Municipality"
msgstr ""
-#: languages/states.php:2935
+#: languages/states.php:2960
msgid "Ķegums Municipality"
msgstr ""
-#: languages/states.php:2936
+#: languages/states.php:2961
msgid "Ķekava Municipality"
msgstr ""
-#: languages/states.php:2939
+#: languages/states.php:2964
msgid "Al Wahat District"
msgstr ""
-#: languages/states.php:2940
+#: languages/states.php:2965
msgid "Benghazi"
msgstr ""
-#: languages/states.php:2941
+#: languages/states.php:2966
msgid "Derna District"
msgstr ""
-#: languages/states.php:2942
+#: languages/states.php:2967
msgid "Ghat District"
msgstr ""
-#: languages/states.php:2943
+#: languages/states.php:2968
msgid "Jabal al Akhdar"
msgstr ""
-#: languages/states.php:2944
+#: languages/states.php:2969
msgid "Jabal al Gharbi District"
msgstr ""
-#: languages/states.php:2945
+#: languages/states.php:2970
msgid "Jafara"
msgstr ""
-#: languages/states.php:2946
+#: languages/states.php:2971
msgid "Jufra"
msgstr ""
-#: languages/states.php:2947
+#: languages/states.php:2972
msgid "Kufra District"
msgstr ""
-#: languages/states.php:2948
+#: languages/states.php:2973
msgid "Marj District"
msgstr ""
-#: languages/states.php:2949
+#: languages/states.php:2974
msgid "Misrata District"
msgstr ""
-#: languages/states.php:2950
+#: languages/states.php:2975
msgid "Murqub"
msgstr ""
-#: languages/states.php:2951
+#: languages/states.php:2976
msgid "Murzuq District"
msgstr ""
-#: languages/states.php:2952
+#: languages/states.php:2977
msgid "Nalut District"
msgstr ""
-#: languages/states.php:2953
+#: languages/states.php:2978
msgid "Nuqat al Khams"
msgstr ""
-#: languages/states.php:2954
+#: languages/states.php:2979
msgid "Sabha District"
msgstr ""
-#: languages/states.php:2955
+#: languages/states.php:2980
msgid "Sirte District"
msgstr ""
-#: languages/states.php:2956
+#: languages/states.php:2981
msgid "Tripoli District"
msgstr ""
-#: languages/states.php:2957
+#: languages/states.php:2982
msgid "Wadi al Hayaa District"
msgstr ""
-#: languages/states.php:2958
+#: languages/states.php:2983
msgid "Wadi al Shatii District"
msgstr ""
-#: languages/states.php:2959
+#: languages/states.php:2984
msgid "Zawiya District"
msgstr ""
-#: languages/states.php:2962
+#: languages/states.php:2987
msgid "Guelmim-Oued Noun"
msgstr ""
-#: languages/states.php:2963
+#: languages/states.php:2988
msgid "Laâyoune-Sakia El Hamra"
msgstr ""
-#: languages/states.php:2964
+#: languages/states.php:2989
msgid "Dakhla-Oued Ed-Dahab"
msgstr ""
-#: languages/states.php:2965
+#: languages/states.php:2990
msgid "Al Haouz Province"
msgstr ""
-#: languages/states.php:2966
+#: languages/states.php:2991
msgid "Al Hoceïma Province"
msgstr ""
-#: languages/states.php:2967
+#: languages/states.php:2992
msgid "Aousserd Province"
msgstr ""
-#: languages/states.php:2968
+#: languages/states.php:2993
msgid "Assa-Zag Province"
msgstr ""
-#: languages/states.php:2969
+#: languages/states.php:2994
msgid "Azilal Province"
msgstr ""
-#: languages/states.php:2970
+#: languages/states.php:2995
msgid "Ben Slimane Province"
msgstr ""
-#: languages/states.php:2971
+#: languages/states.php:2996
msgid "Berkane Province"
msgstr ""
-#: languages/states.php:2972
+#: languages/states.php:2997
msgid "Boujdour Province"
msgstr ""
-#: languages/states.php:2973
+#: languages/states.php:2998
msgid "Boulemane Province"
msgstr ""
-#: languages/states.php:2974
+#: languages/states.php:2999
msgid "Béni Mellal-Khénifra"
msgstr ""
-#: languages/states.php:2975
+#: languages/states.php:3000
msgid "Béni-Mellal Province"
msgstr ""
-#: languages/states.php:2976
+#: languages/states.php:3001
msgid "Casablanca-Settat"
msgstr ""
-#: languages/states.php:2977
+#: languages/states.php:3002
msgid "Chefchaouen Province"
msgstr ""
-#: languages/states.php:2978
+#: languages/states.php:3003
msgid "Chichaoua Province"
msgstr ""
-#: languages/states.php:2979
+#: languages/states.php:3004
msgid "Drâa-Tafilalet"
msgstr ""
-#: languages/states.php:2980
+#: languages/states.php:3005
msgid "El Hajeb Province"
msgstr ""
-#: languages/states.php:2981
+#: languages/states.php:3006
msgid "El Jadida Province"
msgstr ""
-#: languages/states.php:2982
+#: languages/states.php:3007
msgid "Errachidia Province"
msgstr ""
-#: languages/states.php:2983
+#: languages/states.php:3008
msgid "Es Semara Province"
msgstr ""
-#: languages/states.php:2984
+#: languages/states.php:3009
msgid "Essaouira Province"
msgstr ""
-#: languages/states.php:2985
+#: languages/states.php:3010
msgid "Fahs Anjra Province"
msgstr ""
-#: languages/states.php:2986
+#: languages/states.php:3011
msgid "Figuig Province"
msgstr ""
-#: languages/states.php:2987
+#: languages/states.php:3012
msgid "Fès-Meknès"
msgstr ""
-#: languages/states.php:2988
+#: languages/states.php:3013
msgid "Guelmim Province"
msgstr ""
-#: languages/states.php:2989
+#: languages/states.php:3014
msgid "Ifrane Province"
msgstr ""
-#: languages/states.php:2990
+#: languages/states.php:3015
msgid "Inezgane-Aït Melloul Prefecture"
msgstr ""
-#: languages/states.php:2991
+#: languages/states.php:3016
msgid "Jerada Province"
msgstr ""
-#: languages/states.php:2992
+#: languages/states.php:3017
msgid "Kelaat Sraghna Province"
msgstr ""
-#: languages/states.php:2993
+#: languages/states.php:3018
msgid "Khouribga Province"
msgstr ""
-#: languages/states.php:2994
+#: languages/states.php:3019
msgid "Khémisset Province"
msgstr ""
-#: languages/states.php:2995
+#: languages/states.php:3020
msgid "Khénifra Province"
msgstr ""
-#: languages/states.php:2996
+#: languages/states.php:3021
msgid "Kénitra Province"
msgstr ""
-#: languages/states.php:2997
+#: languages/states.php:3022
msgid "Larache Province"
msgstr ""
-#: languages/states.php:2998
+#: languages/states.php:3023
msgid "Laâyoune Province"
msgstr ""
-#: languages/states.php:2999
+#: languages/states.php:3024
msgid "Marrakesh-Safi"
msgstr ""
-#: languages/states.php:3000
+#: languages/states.php:3025
msgid "Mediouna Province"
msgstr ""
-#: languages/states.php:3001
+#: languages/states.php:3026
msgid "Moulay Yacoub Province"
msgstr ""
-#: languages/states.php:3002
+#: languages/states.php:3027
msgid "Nador Province"
msgstr ""
-#: languages/states.php:3003
+#: languages/states.php:3028
msgid "Nouaceur Province"
msgstr ""
-#: languages/states.php:3004
+#: languages/states.php:3029
msgid "Oriental"
msgstr ""
-#: languages/states.php:3005
+#: languages/states.php:3030
msgid "Ouarzazate Province"
msgstr ""
-#: languages/states.php:3006
+#: languages/states.php:3031
msgid "Oued Ed-Dahab Province"
msgstr ""
-#: languages/states.php:3007
+#: languages/states.php:3032
msgid "Safi Province"
msgstr ""
-#: languages/states.php:3008
+#: languages/states.php:3033
msgid "Sefrou Province"
msgstr ""
-#: languages/states.php:3009
+#: languages/states.php:3034
msgid "Settat Province"
msgstr ""
-#: languages/states.php:3010
+#: languages/states.php:3035
msgid "Shtouka Ait Baha Province"
msgstr ""
-#: languages/states.php:3011
+#: languages/states.php:3036
msgid "Sidi Kacem Province"
msgstr ""
-#: languages/states.php:3012
+#: languages/states.php:3037
msgid "Sidi Youssef Ben Ali"
msgstr ""
-#: languages/states.php:3013
+#: languages/states.php:3038
msgid "Souss-Massa"
msgstr ""
-#: languages/states.php:3014
+#: languages/states.php:3039
msgid "Tan-Tan Province"
msgstr ""
-#: languages/states.php:3015
+#: languages/states.php:3040
msgid "Tanger-Tétouan-Al Hoceïma"
msgstr ""
-#: languages/states.php:3016
+#: languages/states.php:3041
msgid "Taounate Province"
msgstr ""
-#: languages/states.php:3017
+#: languages/states.php:3042
msgid "Taourirt Province"
msgstr ""
-#: languages/states.php:3018
+#: languages/states.php:3043
msgid "Taroudant Province"
msgstr ""
-#: languages/states.php:3019
+#: languages/states.php:3044
msgid "Tata Province"
msgstr ""
-#: languages/states.php:3020
+#: languages/states.php:3045
msgid "Taza Province"
msgstr ""
-#: languages/states.php:3021
+#: languages/states.php:3046
msgid "Tiznit Province"
msgstr ""
-#: languages/states.php:3022
+#: languages/states.php:3047
msgid "Tétouan Province"
msgstr ""
-#: languages/states.php:3023
+#: languages/states.php:3048
msgid "Zagora Province"
msgstr ""
-#: languages/states.php:3026
+#: languages/states.php:3051
msgid "La Colle"
msgstr ""
-#: languages/states.php:3027
+#: languages/states.php:3052
msgid "La Condamine"
msgstr ""
-#: languages/states.php:3028
+#: languages/states.php:3053
msgid "Moneghetti"
msgstr ""
-#: languages/states.php:3031
+#: languages/states.php:3056
msgid "Anenii Noi District"
msgstr ""
-#: languages/states.php:3032
+#: languages/states.php:3057
msgid "Basarabeasca District"
msgstr ""
-#: languages/states.php:3033
+#: languages/states.php:3058
msgid "Bender Municipality"
msgstr ""
-#: languages/states.php:3034
+#: languages/states.php:3059
msgid "Briceni District"
msgstr ""
-#: languages/states.php:3035
+#: languages/states.php:3060
msgid "Bălți Municipality"
msgstr ""
-#: languages/states.php:3036
+#: languages/states.php:3061
msgid "Cahul District"
msgstr ""
-#: languages/states.php:3037
+#: languages/states.php:3062
msgid "Cantemir District"
msgstr ""
-#: languages/states.php:3038
+#: languages/states.php:3063
msgid "Chișinău Municipality"
msgstr ""
-#: languages/states.php:3039
+#: languages/states.php:3064
msgid "Cimișlia District"
msgstr ""
-#: languages/states.php:3040
+#: languages/states.php:3065
msgid "Criuleni District"
msgstr ""
-#: languages/states.php:3041
+#: languages/states.php:3066
msgid "Călărași District"
msgstr ""
-#: languages/states.php:3042
+#: languages/states.php:3067
msgid "Căușeni District"
msgstr ""
-#: languages/states.php:3043
+#: languages/states.php:3068
msgid "Dondușeni District"
msgstr ""
-#: languages/states.php:3044
+#: languages/states.php:3069
msgid "Drochia District"
msgstr ""
-#: languages/states.php:3045
+#: languages/states.php:3070
msgid "Dubăsari District"
msgstr ""
-#: languages/states.php:3046
+#: languages/states.php:3071
msgid "Edineț District"
msgstr ""
-#: languages/states.php:3047
+#: languages/states.php:3072
msgid "Florești District"
msgstr ""
-#: languages/states.php:3048
+#: languages/states.php:3073
msgid "Fălești District"
msgstr ""
-#: languages/states.php:3049
+#: languages/states.php:3074
msgid "Gagauzia"
msgstr ""
-#: languages/states.php:3050
+#: languages/states.php:3075
msgid "Glodeni District"
msgstr ""
-#: languages/states.php:3051
+#: languages/states.php:3076
msgid "Hîncești District"
msgstr ""
-#: languages/states.php:3052
+#: languages/states.php:3077
msgid "Ialoveni District"
msgstr ""
-#: languages/states.php:3053
+#: languages/states.php:3078
msgid "Nisporeni District"
msgstr ""
-#: languages/states.php:3054
+#: languages/states.php:3079
msgid "Ocnița District"
msgstr ""
-#: languages/states.php:3055
+#: languages/states.php:3080
msgid "Orhei District"
msgstr ""
-#: languages/states.php:3056
+#: languages/states.php:3081
msgid "Rezina District"
msgstr ""
-#: languages/states.php:3057
+#: languages/states.php:3082
msgid "Rîșcani District"
msgstr ""
-#: languages/states.php:3058
+#: languages/states.php:3083
msgid "Soroca District"
msgstr ""
-#: languages/states.php:3059
+#: languages/states.php:3084
msgid "Strășeni District"
msgstr ""
-#: languages/states.php:3060
+#: languages/states.php:3085
msgid "Sîngerei District"
msgstr ""
-#: languages/states.php:3061
+#: languages/states.php:3086
msgid "Taraclia District"
msgstr ""
-#: languages/states.php:3062
+#: languages/states.php:3087
msgid "Telenești District"
msgstr ""
-#: languages/states.php:3063
+#: languages/states.php:3088
msgid "Transnistria autonomous territorial unit"
msgstr ""
-#: languages/states.php:3064
+#: languages/states.php:3089
msgid "Ungheni District"
msgstr ""
-#: languages/states.php:3065
+#: languages/states.php:3090
msgid "Șoldănești District"
msgstr ""
-#: languages/states.php:3066
+#: languages/states.php:3091
msgid "Ștefan Vodă District"
msgstr ""
-#: languages/states.php:3069
+#: languages/states.php:3094
msgid "Kotor Municipality"
msgstr ""
-#: languages/states.php:3070
+#: languages/states.php:3095
msgid "Mojkovac Municipality"
msgstr ""
-#: languages/states.php:3071
+#: languages/states.php:3096
msgid "Nikšić Municipality"
msgstr ""
-#: languages/states.php:3072
+#: languages/states.php:3097
msgid "Plav Municipality"
msgstr ""
-#: languages/states.php:3073
+#: languages/states.php:3098
msgid "Pljevlja Municipality"
msgstr ""
-#: languages/states.php:3074
+#: languages/states.php:3099
msgid "Plužine Municipality"
msgstr ""
-#: languages/states.php:3075
+#: languages/states.php:3100
msgid "Podgorica Municipality"
msgstr ""
-#: languages/states.php:3076
+#: languages/states.php:3101
msgid "Rožaje Municipality"
msgstr ""
-#: languages/states.php:3077
+#: languages/states.php:3102
msgid "Šavnik Municipality"
msgstr ""
-#: languages/states.php:3078
+#: languages/states.php:3103
msgid "Tivat Municipality"
msgstr ""
-#: languages/states.php:3079
+#: languages/states.php:3104
msgid "Ulcinj Municipality"
msgstr ""
-#: languages/states.php:3080
+#: languages/states.php:3105
msgid "Žabljak Municipality"
msgstr ""
-#: languages/states.php:3081
+#: languages/states.php:3106
msgid "Gusinje Municipality"
msgstr ""
-#: languages/states.php:3082
+#: languages/states.php:3107
msgid "Petnjica Municipality"
msgstr ""
-#: languages/states.php:3083
+#: languages/states.php:3108
msgid "Andrijevica Municipality"
msgstr ""
-#: languages/states.php:3084
+#: languages/states.php:3109
msgid "Bar Municipality"
msgstr ""
-#: languages/states.php:3085
+#: languages/states.php:3110
msgid "Berane Municipality"
msgstr ""
-#: languages/states.php:3086
+#: languages/states.php:3111
msgid "Bijelo Polje Municipality"
msgstr ""
-#: languages/states.php:3087
+#: languages/states.php:3112
msgid "Budva Municipality"
msgstr ""
-#: languages/states.php:3088
+#: languages/states.php:3113
msgid "Danilovgrad Municipality"
msgstr ""
-#: languages/states.php:3089
+#: languages/states.php:3114
msgid "Kolašin Municipality"
msgstr ""
-#: languages/states.php:3090
+#: languages/states.php:3115
msgid "Old Royal Capital Cetinje"
msgstr ""
-#: languages/states.php:3094
+#: languages/states.php:3119
msgid "Antananarivo Province"
msgstr ""
-#: languages/states.php:3095
+#: languages/states.php:3120
msgid "Antsiranana Province"
msgstr ""
-#: languages/states.php:3096
+#: languages/states.php:3121
msgid "Fianarantsoa Province"
msgstr ""
-#: languages/states.php:3097
+#: languages/states.php:3122
msgid "Mahajanga Province"
msgstr ""
-#: languages/states.php:3098
+#: languages/states.php:3123
msgid "Toamasina Province"
msgstr ""
-#: languages/states.php:3099
+#: languages/states.php:3124
msgid "Toliara Province"
msgstr ""
-#: languages/states.php:3102
+#: languages/states.php:3127
msgid "Ralik Chain"
msgstr ""
-#: languages/states.php:3103
+#: languages/states.php:3128
msgid "Ratak Chain"
msgstr ""
-#: languages/states.php:3106
+#: languages/states.php:3131
msgid "Valandovo Municipality"
msgstr ""
-#: languages/states.php:3107
+#: languages/states.php:3132
msgid "Vasilevo Municipality"
msgstr ""
-#: languages/states.php:3108
+#: languages/states.php:3133
msgid "Vevčani Municipality"
msgstr ""
-#: languages/states.php:3109
+#: languages/states.php:3134
msgid "Veles Municipality"
msgstr ""
-#: languages/states.php:3110
+#: languages/states.php:3135
msgid "Vinica Municipality"
msgstr ""
-#: languages/states.php:3111
+#: languages/states.php:3136
msgid "Vraneštica Municipality"
msgstr ""
-#: languages/states.php:3112
+#: languages/states.php:3137
msgid "Vrapčište Municipality"
msgstr ""
-#: languages/states.php:3113
+#: languages/states.php:3138
msgid "Gazi Baba Municipality"
msgstr ""
-#: languages/states.php:3114
+#: languages/states.php:3139
msgid "Gevgelija Municipality"
msgstr ""
-#: languages/states.php:3115
+#: languages/states.php:3140
msgid "Gostivar Municipality"
msgstr ""
-#: languages/states.php:3116
+#: languages/states.php:3141
msgid "Gradsko Municipality"
msgstr ""
-#: languages/states.php:3117
+#: languages/states.php:3142
msgid "Debarca Municipality"
msgstr ""
-#: languages/states.php:3118
+#: languages/states.php:3143
msgid "Delčevo Municipality"
msgstr ""
-#: languages/states.php:3119
+#: languages/states.php:3144
msgid "Demir Kapija Municipality"
msgstr ""
-#: languages/states.php:3120
+#: languages/states.php:3145
msgid "Demir Hisar Municipality"
msgstr ""
-#: languages/states.php:3121
+#: languages/states.php:3146
msgid "Dojran Municipality"
msgstr ""
-#: languages/states.php:3122
+#: languages/states.php:3147
msgid "Dolneni Municipality"
msgstr ""
-#: languages/states.php:3123
+#: languages/states.php:3148
msgid "Drugovo Municipality"
msgstr ""
-#: languages/states.php:3124
+#: languages/states.php:3149
msgid "Gjorče Petrov Municipality"
msgstr ""
-#: languages/states.php:3125
+#: languages/states.php:3150
msgid "Želino Municipality"
msgstr ""
-#: languages/states.php:3126
+#: languages/states.php:3151
msgid "Zajas Municipality"
msgstr ""
-#: languages/states.php:3127
+#: languages/states.php:3152
msgid "Zelenikovo Municipality"
msgstr ""
-#: languages/states.php:3128
+#: languages/states.php:3153
msgid "Zrnovci Municipality"
msgstr ""
-#: languages/states.php:3129
+#: languages/states.php:3154
msgid "Ilinden Municipality"
msgstr ""
-#: languages/states.php:3130
+#: languages/states.php:3155
msgid "Jegunovce Municipality"
msgstr ""
-#: languages/states.php:3131
+#: languages/states.php:3156
msgid "Kavadarci Municipality"
msgstr ""
-#: languages/states.php:3132
+#: languages/states.php:3157
msgid "Karbinci"
msgstr ""
-#: languages/states.php:3133
+#: languages/states.php:3158
msgid "Karpoš Municipality"
msgstr ""
-#: languages/states.php:3134
+#: languages/states.php:3159
msgid "Kisela Voda Municipality"
msgstr ""
-#: languages/states.php:3135
+#: languages/states.php:3160
msgid "Kičevo Municipality"
msgstr ""
-#: languages/states.php:3136
+#: languages/states.php:3161
msgid "Konče Municipality"
msgstr ""
-#: languages/states.php:3137
+#: languages/states.php:3162
msgid "Kočani Municipality"
msgstr ""
-#: languages/states.php:3138
+#: languages/states.php:3163
msgid "Kratovo Municipality"
msgstr ""
-#: languages/states.php:3139
+#: languages/states.php:3164
msgid "Kriva Palanka Municipality"
msgstr ""
-#: languages/states.php:3140
+#: languages/states.php:3165
msgid "Krivogaštani Municipality"
msgstr ""
-#: languages/states.php:3141
+#: languages/states.php:3166
msgid "Kruševo Municipality"
msgstr ""
-#: languages/states.php:3142
+#: languages/states.php:3167
msgid "Kumanovo Municipality"
msgstr ""
-#: languages/states.php:3143
+#: languages/states.php:3168
msgid "Lipkovo Municipality"
msgstr ""
-#: languages/states.php:3144
+#: languages/states.php:3169
msgid "Lozovo Municipality"
msgstr ""
-#: languages/states.php:3145
+#: languages/states.php:3170
msgid "Mavrovo and Rostuša Municipality"
msgstr ""
-#: languages/states.php:3146
+#: languages/states.php:3171
msgid "Makedonska Kamenica Municipality"
msgstr ""
-#: languages/states.php:3147
+#: languages/states.php:3172
msgid "Makedonski Brod Municipality"
msgstr ""
-#: languages/states.php:3148
+#: languages/states.php:3173
msgid "Mogila Municipality"
msgstr ""
-#: languages/states.php:3149
+#: languages/states.php:3174
msgid "Negotino Municipality"
msgstr ""
-#: languages/states.php:3150
+#: languages/states.php:3175
msgid "Novaci Municipality"
msgstr ""
-#: languages/states.php:3151
+#: languages/states.php:3176
msgid "Novo Selo Municipality"
msgstr ""
-#: languages/states.php:3152
+#: languages/states.php:3177
msgid "Oslomej Municipality"
msgstr ""
-#: languages/states.php:3153
+#: languages/states.php:3178
msgid "Ohrid Municipality"
msgstr ""
-#: languages/states.php:3154
+#: languages/states.php:3179
msgid "Petrovec Municipality"
msgstr ""
-#: languages/states.php:3155
+#: languages/states.php:3180
msgid "Pehčevo Municipality"
msgstr ""
-#: languages/states.php:3156
+#: languages/states.php:3181
msgid "Plasnica Municipality"
msgstr ""
-#: languages/states.php:3157
+#: languages/states.php:3182
msgid "Prilep Municipality"
msgstr ""
-#: languages/states.php:3158
+#: languages/states.php:3183
msgid "Probištip Municipality"
msgstr ""
-#: languages/states.php:3159
+#: languages/states.php:3184
msgid "Radoviš Municipality"
msgstr ""
-#: languages/states.php:3160
+#: languages/states.php:3185
msgid "Rankovce Municipality"
msgstr ""
-#: languages/states.php:3161
+#: languages/states.php:3186
msgid "Resen Municipality"
msgstr ""
-#: languages/states.php:3162
+#: languages/states.php:3187
msgid "Rosoman Municipality"
msgstr ""
-#: languages/states.php:3163
+#: languages/states.php:3188
msgid "Saraj Municipality"
msgstr ""
-#: languages/states.php:3164
+#: languages/states.php:3189
msgid "Sveti Nikole Municipality"
msgstr ""
-#: languages/states.php:3165
+#: languages/states.php:3190
msgid "Sopište Municipality"
msgstr ""
-#: languages/states.php:3166
+#: languages/states.php:3191
msgid "Staro Nagoričane Municipality"
msgstr ""
-#: languages/states.php:3167
+#: languages/states.php:3192
msgid "Struga Municipality"
msgstr ""
-#: languages/states.php:3168
+#: languages/states.php:3193
msgid "Strumica Municipality"
msgstr ""
-#: languages/states.php:3169
+#: languages/states.php:3194
msgid "Studeničani Municipality"
msgstr ""
-#: languages/states.php:3170
+#: languages/states.php:3195
msgid "Tearce Municipality"
msgstr ""
-#: languages/states.php:3171
+#: languages/states.php:3196
msgid "Tetovo Municipality"
msgstr ""
-#: languages/states.php:3172
+#: languages/states.php:3197
msgid "Centar Municipality"
msgstr ""
-#: languages/states.php:3173
+#: languages/states.php:3198
msgid "Centar Župa Municipality"
msgstr ""
-#: languages/states.php:3174
+#: languages/states.php:3199
msgid "Čair Municipality"
msgstr ""
-#: languages/states.php:3175
+#: languages/states.php:3200
msgid "Čaška Municipality"
msgstr ""
-#: languages/states.php:3176
+#: languages/states.php:3201
msgid "Češinovo-Obleševo Municipality"
msgstr ""
-#: languages/states.php:3177
+#: languages/states.php:3202
msgid "Čučer-Sandevo Municipality"
msgstr ""
-#: languages/states.php:3178
+#: languages/states.php:3203
msgid "Štip Municipality"
msgstr ""
-#: languages/states.php:3179
+#: languages/states.php:3204
msgid "Šuto Orizari Municipality"
msgstr ""
-#: languages/states.php:3180
+#: languages/states.php:3205
msgid "Greater Skopje"
msgstr ""
-#: languages/states.php:3181
+#: languages/states.php:3206
msgid "Aerodrom Municipality"
msgstr ""
-#: languages/states.php:3182
+#: languages/states.php:3207
msgid "Aračinovo Municipality"
msgstr ""
-#: languages/states.php:3183
+#: languages/states.php:3208
msgid "Berovo Municipality"
msgstr ""
-#: languages/states.php:3184
+#: languages/states.php:3209
msgid "Bitola Municipality"
msgstr ""
-#: languages/states.php:3185
+#: languages/states.php:3210
msgid "Bogdanci Municipality"
msgstr ""
-#: languages/states.php:3186
+#: languages/states.php:3211
msgid "Bogovinje Municipality"
msgstr ""
-#: languages/states.php:3187
+#: languages/states.php:3212
msgid "Bosilovo Municipality"
msgstr ""
-#: languages/states.php:3188
+#: languages/states.php:3213
msgid "Brvenica Municipality"
msgstr ""
-#: languages/states.php:3189
+#: languages/states.php:3214
msgid "Butel Municipality"
msgstr ""
-#: languages/states.php:3192
+#: languages/states.php:3217
msgid "Kayes Region"
msgstr ""
-#: languages/states.php:3193
+#: languages/states.php:3218
msgid "Koulikoro Region"
msgstr ""
-#: languages/states.php:3194
+#: languages/states.php:3219
msgid "Sikasso Region"
msgstr ""
-#: languages/states.php:3195
+#: languages/states.php:3220
msgid "Ségou Region"
msgstr ""
-#: languages/states.php:3196
+#: languages/states.php:3221
msgid "Mopti Region"
msgstr ""
-#: languages/states.php:3197
+#: languages/states.php:3222
msgid "Tombouctou Region"
msgstr ""
-#: languages/states.php:3198
+#: languages/states.php:3223
msgid "Gao Region"
msgstr ""
-#: languages/states.php:3199
+#: languages/states.php:3224
msgid "Kidal Region"
msgstr ""
-#: languages/states.php:3200
+#: languages/states.php:3225
msgid "Ménaka Region"
msgstr ""
-#: languages/states.php:3201
+#: languages/states.php:3226
msgid "Taoudénit Region"
msgstr ""
-#: languages/states.php:3202
+#: languages/states.php:3227
msgid "Bamako"
msgstr ""
-#: languages/states.php:3205
+#: languages/states.php:3230
msgid "Kachin State"
msgstr ""
-#: languages/states.php:3206
+#: languages/states.php:3231
msgid "Kayah State"
msgstr ""
-#: languages/states.php:3207
+#: languages/states.php:3232
msgid "Kayin State"
msgstr ""
-#: languages/states.php:3208
+#: languages/states.php:3233
msgid "Chin State"
msgstr ""
-#: languages/states.php:3209
+#: languages/states.php:3234
msgid "Mon State"
msgstr ""
-#: languages/states.php:3210
+#: languages/states.php:3235
msgid "Rakhine State"
msgstr ""
-#: languages/states.php:3211
+#: languages/states.php:3236
msgid "Shan State"
msgstr ""
-#: languages/states.php:3212
+#: languages/states.php:3237
msgid "Naypyidaw Union Territory"
msgstr ""
-#: languages/states.php:3213
+#: languages/states.php:3238
msgid "Ayeyarwady Region"
msgstr ""
-#: languages/states.php:3214
+#: languages/states.php:3239
msgid "Bago"
msgstr ""
-#: languages/states.php:3215
+#: languages/states.php:3240
msgid "Magway Region"
msgstr ""
-#: languages/states.php:3216
+#: languages/states.php:3241
msgid "Mandalay Region"
msgstr ""
-#: languages/states.php:3217
+#: languages/states.php:3242
msgid "Sagaing Region"
msgstr ""
-#: languages/states.php:3218
+#: languages/states.php:3243
msgid "Tanintharyi Region"
msgstr ""
-#: languages/states.php:3219
+#: languages/states.php:3244
msgid "Yangon Region"
msgstr ""
-#: languages/states.php:3222
+#: languages/states.php:3247
msgid "Arkhangai Province"
msgstr ""
-#: languages/states.php:3223
+#: languages/states.php:3248
msgid "Bayan-Ölgii Province"
msgstr ""
-#: languages/states.php:3224
+#: languages/states.php:3249
msgid "Bayankhongor Province"
msgstr ""
-#: languages/states.php:3225
+#: languages/states.php:3250
msgid "Bulgan Province"
msgstr ""
-#: languages/states.php:3226
+#: languages/states.php:3251
msgid "Darkhan-Uul Province"
msgstr ""
-#: languages/states.php:3227
+#: languages/states.php:3252
msgid "Dornod Province"
msgstr ""
-#: languages/states.php:3228
+#: languages/states.php:3253
msgid "Dornogovi Province"
msgstr ""
-#: languages/states.php:3229
+#: languages/states.php:3254
msgid "Dundgovi Province"
msgstr ""
-#: languages/states.php:3230
+#: languages/states.php:3255
msgid "Govi-Altai Province"
msgstr ""
-#: languages/states.php:3231
+#: languages/states.php:3256
msgid "Govisümber Province"
msgstr ""
-#: languages/states.php:3232
+#: languages/states.php:3257
msgid "Khentii Province"
msgstr ""
-#: languages/states.php:3233
+#: languages/states.php:3258
msgid "Khovd Province"
msgstr ""
-#: languages/states.php:3234
+#: languages/states.php:3259
msgid "Khövsgöl Province"
msgstr ""
-#: languages/states.php:3235
+#: languages/states.php:3260
msgid "Orkhon Province"
msgstr ""
-#: languages/states.php:3236
+#: languages/states.php:3261
msgid "Selenge Province"
msgstr ""
-#: languages/states.php:3237
+#: languages/states.php:3262
msgid "Sükhbaatar Province"
msgstr ""
-#: languages/states.php:3238
+#: languages/states.php:3263
msgid "Töv Province"
msgstr ""
-#: languages/states.php:3239
+#: languages/states.php:3264
msgid "Uvs Province"
msgstr ""
-#: languages/states.php:3240
+#: languages/states.php:3265
msgid "Zavkhan Province"
msgstr ""
-#: languages/states.php:3241
+#: languages/states.php:3266
msgid "Ömnögovi Province"
msgstr ""
-#: languages/states.php:3242
+#: languages/states.php:3267
msgid "Övörkhangai Province"
msgstr ""
-#: languages/states.php:3248
+#: languages/states.php:3273
msgid "Guidimaka Region"
msgstr ""
-#: languages/states.php:3249
+#: languages/states.php:3274
msgid "Tiris Zemmour Region"
msgstr ""
-#: languages/states.php:3250
+#: languages/states.php:3275
msgid "Inchiri Region"
msgstr ""
-#: languages/states.php:3251
+#: languages/states.php:3276
msgid "Nouakchott-Ouest Region"
msgstr ""
-#: languages/states.php:3252
+#: languages/states.php:3277
msgid "Nouakchott-Nord Region"
msgstr ""
-#: languages/states.php:3253
+#: languages/states.php:3278
msgid "Nouakchott-Sud Region"
msgstr ""
-#: languages/states.php:3254
+#: languages/states.php:3279
msgid "Adrar Region"
msgstr ""
-#: languages/states.php:3255
+#: languages/states.php:3280
msgid "Assaba Region"
msgstr ""
-#: languages/states.php:3256
+#: languages/states.php:3281
msgid "Brakna Region"
msgstr ""
-#: languages/states.php:3257
+#: languages/states.php:3282
msgid "Dakhlet Nouadhibou"
msgstr ""
-#: languages/states.php:3258
+#: languages/states.php:3283
msgid "Gorgol Region"
msgstr ""
-#: languages/states.php:3259
+#: languages/states.php:3284
msgid "Hodh Ech Chargui Region"
msgstr ""
-#: languages/states.php:3260
+#: languages/states.php:3285
msgid "Hodh El Gharbi Region"
msgstr ""
-#: languages/states.php:3261
+#: languages/states.php:3286
msgid "Tagant Region"
msgstr ""
-#: languages/states.php:3262
+#: languages/states.php:3287
msgid "Trarza Region"
msgstr ""
-#: languages/states.php:3266
+#: languages/states.php:3291
msgid "Fontana"
msgstr ""
-#: languages/states.php:3267
+#: languages/states.php:3292
msgid "Gudja"
msgstr ""
-#: languages/states.php:3268
+#: languages/states.php:3293
msgid "Gżira"
msgstr ""
-#: languages/states.php:3269
+#: languages/states.php:3294
msgid "Għajnsielem"
msgstr ""
-#: languages/states.php:3270
+#: languages/states.php:3295
msgid "Għarb"
msgstr ""
-#: languages/states.php:3271
+#: languages/states.php:3296
msgid "Għargħur"
msgstr ""
-#: languages/states.php:3272
+#: languages/states.php:3297
msgid "Għasri"
msgstr ""
-#: languages/states.php:3273
+#: languages/states.php:3298
msgid "Għaxaq"
msgstr ""
-#: languages/states.php:3274
+#: languages/states.php:3299
msgid "Ħamrun"
msgstr ""
-#: languages/states.php:3275
+#: languages/states.php:3300
msgid "Iklin"
msgstr ""
-#: languages/states.php:3276
+#: languages/states.php:3301
msgid "Senglea"
msgstr ""
-#: languages/states.php:3277
+#: languages/states.php:3302
msgid "Kalkara"
msgstr ""
-#: languages/states.php:3278
+#: languages/states.php:3303
msgid "Kerċem"
msgstr ""
-#: languages/states.php:3279
+#: languages/states.php:3304
msgid "Kirkop"
msgstr ""
-#: languages/states.php:3280
+#: languages/states.php:3305
msgid "Lija"
msgstr ""
-#: languages/states.php:3281
+#: languages/states.php:3306
msgid "Luqa"
msgstr ""
-#: languages/states.php:3282
+#: languages/states.php:3307
msgid "Marsa"
msgstr ""
-#: languages/states.php:3283
+#: languages/states.php:3308
msgid "Marsaskala"
msgstr ""
-#: languages/states.php:3284
+#: languages/states.php:3309
msgid "Marsaxlokk"
msgstr ""
-#: languages/states.php:3285
+#: languages/states.php:3310
msgid "Mdina"
msgstr ""
-#: languages/states.php:3286
+#: languages/states.php:3311
msgid "Mellieħa"
msgstr ""
-#: languages/states.php:3287
+#: languages/states.php:3312
msgid "Mġarr"
msgstr ""
-#: languages/states.php:3288
+#: languages/states.php:3313
msgid "Mosta"
msgstr ""
-#: languages/states.php:3289
+#: languages/states.php:3314
msgid "Mqabba"
msgstr ""
-#: languages/states.php:3290
+#: languages/states.php:3315
msgid "Msida"
msgstr ""
-#: languages/states.php:3291
+#: languages/states.php:3316
msgid "Mtarfa"
msgstr ""
-#: languages/states.php:3292
+#: languages/states.php:3317
msgid "Munxar"
msgstr ""
-#: languages/states.php:3293
+#: languages/states.php:3318
msgid "Nadur"
msgstr ""
-#: languages/states.php:3294
+#: languages/states.php:3319
msgid "Naxxar"
msgstr ""
-#: languages/states.php:3295
+#: languages/states.php:3320
msgid "Paola"
msgstr ""
-#: languages/states.php:3296
+#: languages/states.php:3321
msgid "Pembroke"
msgstr ""
-#: languages/states.php:3297
+#: languages/states.php:3322
msgid "Pietà"
msgstr ""
-#: languages/states.php:3298
+#: languages/states.php:3323
msgid "Qala"
msgstr ""
-#: languages/states.php:3299
+#: languages/states.php:3324
msgid "Qormi"
msgstr ""
-#: languages/states.php:3300
+#: languages/states.php:3325
msgid "Qrendi"
msgstr ""
-#: languages/states.php:3302
+#: languages/states.php:3327
msgid "Rabat"
msgstr ""
-#: languages/states.php:3303
+#: languages/states.php:3328
msgid "St. Julian's"
msgstr ""
-#: languages/states.php:3304
+#: languages/states.php:3329
msgid "San Ġwann"
msgstr ""
-#: languages/states.php:3305
+#: languages/states.php:3330
msgid "Saint Lawrence"
msgstr ""
-#: languages/states.php:3306
+#: languages/states.php:3331
msgid "St. Paul's Bay"
msgstr ""
-#: languages/states.php:3307
+#: languages/states.php:3332
msgid "Sannat"
msgstr ""
-#: languages/states.php:3308
+#: languages/states.php:3333
msgid "Santa Luċija"
msgstr ""
-#: languages/states.php:3309
+#: languages/states.php:3334
msgid "Santa Venera"
msgstr ""
-#: languages/states.php:3310
+#: languages/states.php:3335
msgid "Siġġiewi"
msgstr ""
-#: languages/states.php:3311
+#: languages/states.php:3336
msgid "Sliema"
msgstr ""
-#: languages/states.php:3312
+#: languages/states.php:3337
msgid "Swieqi"
msgstr ""
-#: languages/states.php:3313
+#: languages/states.php:3338
msgid "Ta' Xbiex"
msgstr ""
-#: languages/states.php:3314
+#: languages/states.php:3339
msgid "Tarxien"
msgstr ""
-#: languages/states.php:3315
+#: languages/states.php:3340
msgid "Valletta"
msgstr ""
-#: languages/states.php:3316
+#: languages/states.php:3341
msgid "Xagħra"
msgstr ""
-#: languages/states.php:3317
+#: languages/states.php:3342
msgid "Xewkija"
msgstr ""
-#: languages/states.php:3318
+#: languages/states.php:3343
msgid "Xgħajra"
msgstr ""
-#: languages/states.php:3319
+#: languages/states.php:3344
msgid "Żabbar"
msgstr ""
-#: languages/states.php:3320
+#: languages/states.php:3345
msgid "Żebbuġ Gozo"
msgstr ""
-#: languages/states.php:3321
+#: languages/states.php:3346
msgid "Żebbuġ Malta"
msgstr ""
-#: languages/states.php:3322
+#: languages/states.php:3347
msgid "Żejtun"
msgstr ""
-#: languages/states.php:3323
+#: languages/states.php:3348
msgid "Żurrieq"
msgstr ""
-#: languages/states.php:3324
+#: languages/states.php:3349
msgid "Attard"
msgstr ""
-#: languages/states.php:3325
+#: languages/states.php:3350
msgid "Balzan"
msgstr ""
-#: languages/states.php:3326
+#: languages/states.php:3351
msgid "Birgu"
msgstr ""
-#: languages/states.php:3327
+#: languages/states.php:3352
msgid "Birkirkara"
msgstr ""
-#: languages/states.php:3328
+#: languages/states.php:3353
msgid "Birżebbuġa"
msgstr ""
-#: languages/states.php:3329
+#: languages/states.php:3354
msgid "Cospicua"
msgstr ""
-#: languages/states.php:3330
+#: languages/states.php:3355
msgid "Dingli"
msgstr ""
-#: languages/states.php:3331
+#: languages/states.php:3356
msgid "Fgura"
msgstr ""
-#: languages/states.php:3332
+#: languages/states.php:3357
msgid "Floriana"
msgstr ""
-#: languages/states.php:3335
+#: languages/states.php:3360
msgid "Agaléga"
msgstr ""
-#: languages/states.php:3336
+#: languages/states.php:3361
msgid "Beau Bassin-Rose Hill"
msgstr ""
-#: languages/states.php:3337
+#: languages/states.php:3362
msgid "Cargados Carajos"
msgstr ""
-#: languages/states.php:3338
+#: languages/states.php:3363
msgid "Curepipe"
msgstr ""
-#: languages/states.php:3339
+#: languages/states.php:3364
msgid "Flacq District"
msgstr ""
-#: languages/states.php:3340
+#: languages/states.php:3365
msgid "Grand Port District"
msgstr ""
-#: languages/states.php:3341
+#: languages/states.php:3366
msgid "Moka District"
msgstr ""
-#: languages/states.php:3342
+#: languages/states.php:3367
msgid "Pamplemousses District"
msgstr ""
-#: languages/states.php:3343
+#: languages/states.php:3368
msgid "Plaines Wilhems District"
msgstr ""
-#: languages/states.php:3344
+#: languages/states.php:3369
msgid "Port Louis"
msgstr ""
-#: languages/states.php:3345
+#: languages/states.php:3370
msgid "Port Louis District"
msgstr ""
-#: languages/states.php:3346
+#: languages/states.php:3371
msgid "Quatre Bornes"
msgstr ""
-#: languages/states.php:3347
+#: languages/states.php:3372
msgid "Rivière Noire District"
msgstr ""
-#: languages/states.php:3348
+#: languages/states.php:3373
msgid "Rivière du Rempart District"
msgstr ""
-#: languages/states.php:3349
+#: languages/states.php:3374
msgid "Rodrigues"
msgstr ""
-#: languages/states.php:3350
+#: languages/states.php:3375
msgid "Savanne District"
msgstr ""
-#: languages/states.php:3351
+#: languages/states.php:3376
msgid "Vacoas-Phoenix"
msgstr ""
-#: languages/states.php:3354
+#: languages/states.php:3379
msgid "Meemu Atoll"
msgstr ""
-#: languages/states.php:3355
+#: languages/states.php:3380
msgid "Raa Atoll"
msgstr ""
-#: languages/states.php:3356
+#: languages/states.php:3381
msgid "Faafu Atoll"
msgstr ""
-#: languages/states.php:3357
+#: languages/states.php:3382
msgid "Dhaalu Atoll"
msgstr ""
-#: languages/states.php:3358
+#: languages/states.php:3383
msgid "Haa Dhaalu Atoll"
msgstr ""
-#: languages/states.php:3359
+#: languages/states.php:3384
msgid "Shaviyani Atoll"
msgstr ""
-#: languages/states.php:3360
+#: languages/states.php:3385
msgid "Noonu Atoll"
msgstr ""
-#: languages/states.php:3361
+#: languages/states.php:3386
msgid "Kaafu Atoll"
msgstr ""
-#: languages/states.php:3362
+#: languages/states.php:3387
msgid "Gaafu Alif Atoll"
msgstr ""
-#: languages/states.php:3363
+#: languages/states.php:3388
msgid "Gaafu Dhaalu Atoll"
msgstr ""
-#: languages/states.php:3364
+#: languages/states.php:3389
msgid "Gnaviyani Atoll"
msgstr ""
-#: languages/states.php:3365
+#: languages/states.php:3390
msgid "Addu Atoll"
msgstr ""
-#: languages/states.php:3366
+#: languages/states.php:3391
msgid "Alif Alif Atoll"
msgstr ""
-#: languages/states.php:3367
+#: languages/states.php:3392
msgid "Alif Dhaal Atoll"
msgstr ""
-#: languages/states.php:3369
+#: languages/states.php:3394
msgid "Haa Alif Atoll"
msgstr ""
-#: languages/states.php:3370
+#: languages/states.php:3395
msgid "Laamu Atoll"
msgstr ""
-#: languages/states.php:3371
+#: languages/states.php:3396
msgid "Lhaviyani Atoll"
msgstr ""
-#: languages/states.php:3372
+#: languages/states.php:3397
msgid "Malé"
msgstr ""
-#: languages/states.php:3374
+#: languages/states.php:3399
msgid "North Province"
msgstr ""
-#: languages/states.php:3375
+#: languages/states.php:3400
msgid "South Central Province"
msgstr ""
-#: languages/states.php:3376
+#: languages/states.php:3401
msgid "South Province"
msgstr ""
-#: languages/states.php:3377
+#: languages/states.php:3402
msgid "Thaa Atoll"
msgstr ""
-#: languages/states.php:3378
+#: languages/states.php:3403
msgid "Upper South Province"
msgstr ""
-#: languages/states.php:3379
+#: languages/states.php:3404
msgid "Vaavu Atoll"
msgstr ""
-#: languages/states.php:3382
+#: languages/states.php:3407
msgid "Balaka District"
msgstr ""
-#: languages/states.php:3383
+#: languages/states.php:3408
msgid "Blantyre District"
msgstr ""
-#: languages/states.php:3385
+#: languages/states.php:3410
msgid "Chikwawa District"
msgstr ""
-#: languages/states.php:3386
+#: languages/states.php:3411
msgid "Chiradzulu District"
msgstr ""
-#: languages/states.php:3387
+#: languages/states.php:3412
msgid "Chitipa district"
msgstr ""
-#: languages/states.php:3388
+#: languages/states.php:3413
msgid "Dedza District"
msgstr ""
-#: languages/states.php:3389
+#: languages/states.php:3414
msgid "Dowa District"
msgstr ""
-#: languages/states.php:3390
+#: languages/states.php:3415
msgid "Karonga District"
msgstr ""
-#: languages/states.php:3391
+#: languages/states.php:3416
msgid "Kasungu District"
msgstr ""
-#: languages/states.php:3392
+#: languages/states.php:3417
msgid "Likoma District"
msgstr ""
-#: languages/states.php:3393
+#: languages/states.php:3418
msgid "Lilongwe District"
msgstr ""
-#: languages/states.php:3394
+#: languages/states.php:3419
msgid "Machinga District"
msgstr ""
-#: languages/states.php:3395
+#: languages/states.php:3420
msgid "Mangochi District"
msgstr ""
-#: languages/states.php:3396
+#: languages/states.php:3421
msgid "Mchinji District"
msgstr ""
-#: languages/states.php:3397
+#: languages/states.php:3422
msgid "Mulanje District"
msgstr ""
-#: languages/states.php:3398
+#: languages/states.php:3423
msgid "Mwanza District"
msgstr ""
-#: languages/states.php:3399
+#: languages/states.php:3424
msgid "Mzimba District"
msgstr ""
-#: languages/states.php:3400
+#: languages/states.php:3425
msgid "Nkhata Bay District"
msgstr ""
-#: languages/states.php:3401
+#: languages/states.php:3426
msgid "Nkhotakota District"
msgstr ""
-#: languages/states.php:3403
+#: languages/states.php:3428
msgid "Nsanje District"
msgstr ""
-#: languages/states.php:3404
+#: languages/states.php:3429
msgid "Ntcheu District"
msgstr ""
-#: languages/states.php:3405
+#: languages/states.php:3430
msgid "Ntchisi District"
msgstr ""
-#: languages/states.php:3406
+#: languages/states.php:3431
msgid "Phalombe District"
msgstr ""
-#: languages/states.php:3407
+#: languages/states.php:3432
msgid "Rumphi District"
msgstr ""
-#: languages/states.php:3408
+#: languages/states.php:3433
msgid "Salima District"
msgstr ""
-#: languages/states.php:3410
+#: languages/states.php:3435
msgid "Thyolo District"
msgstr ""
-#: languages/states.php:3411
+#: languages/states.php:3436
msgid "Zomba District"
msgstr ""
-#: languages/states.php:3414
+#: languages/states.php:3439
msgid "Aguascalientes"
msgstr ""
-#: languages/states.php:3415
+#: languages/states.php:3440
msgid "Baja California"
msgstr ""
-#: languages/states.php:3416
+#: languages/states.php:3441
msgid "Baja California Sur"
msgstr ""
-#: languages/states.php:3417
+#: languages/states.php:3442
msgid "Campeche"
msgstr ""
-#: languages/states.php:3418
+#: languages/states.php:3443
msgid "Chiapas"
msgstr ""
-#: languages/states.php:3419
+#: languages/states.php:3444
msgid "Chihuahua"
msgstr ""
-#: languages/states.php:3420
+#: languages/states.php:3445
msgid "Coahuila"
msgstr ""
-#: languages/states.php:3421
+#: languages/states.php:3446
msgid "Colima"
msgstr ""
-#: languages/states.php:3422
+#: languages/states.php:3447
msgid "Durango"
msgstr ""
-#: languages/states.php:3423
+#: languages/states.php:3448
msgid "Guanajuato"
msgstr ""
-#: languages/states.php:3424
+#: languages/states.php:3449
msgid "Guerrero"
msgstr ""
-#: languages/states.php:3425
+#: languages/states.php:3450
msgid "Hidalgo"
msgstr ""
-#: languages/states.php:3426
+#: languages/states.php:3451
msgid "Jalisco"
msgstr ""
-#: languages/states.php:3427
+#: languages/states.php:3452
msgid "Mexico City"
msgstr ""
-#: languages/states.php:3428
+#: languages/states.php:3453
msgid "Michoacán"
msgstr ""
-#: languages/states.php:3429
+#: languages/states.php:3454
msgid "Morelos"
msgstr ""
-#: languages/states.php:3430
+#: languages/states.php:3455
msgid "México"
msgstr ""
-#: languages/states.php:3431
+#: languages/states.php:3456
msgid "Nayarit"
msgstr ""
-#: languages/states.php:3432
+#: languages/states.php:3457
msgid "Nuevo León"
msgstr ""
-#: languages/states.php:3433
+#: languages/states.php:3458
msgid "Oaxaca"
msgstr ""
-#: languages/states.php:3434
+#: languages/states.php:3459
msgid "Puebla"
msgstr ""
-#: languages/states.php:3435
+#: languages/states.php:3460
msgid "Querétaro"
msgstr ""
-#: languages/states.php:3436
+#: languages/states.php:3461
msgid "Quintana Roo"
msgstr ""
-#: languages/states.php:3437
+#: languages/states.php:3462
msgid "San Luis Potosí"
msgstr ""
-#: languages/states.php:3438
+#: languages/states.php:3463
msgid "Sinaloa"
msgstr ""
-#: languages/states.php:3439
+#: languages/states.php:3464
msgid "Sonora"
msgstr ""
-#: languages/states.php:3440
+#: languages/states.php:3465
msgid "Tabasco"
msgstr ""
-#: languages/states.php:3441
+#: languages/states.php:3466
msgid "Tamaulipas"
msgstr ""
-#: languages/states.php:3442
+#: languages/states.php:3467
msgid "Tlaxcala"
msgstr ""
-#: languages/states.php:3443
+#: languages/states.php:3468
msgid "Veracruz"
msgstr ""
-#: languages/states.php:3444
+#: languages/states.php:3469
msgid "Yucatán"
msgstr ""
-#: languages/states.php:3445
+#: languages/states.php:3470
msgid "Zacatecas"
msgstr ""
-#: languages/states.php:3448
+#: languages/states.php:3473
msgid "Selangor"
msgstr ""
-#: languages/states.php:3449
+#: languages/states.php:3474
msgid "Terengganu"
msgstr ""
-#: languages/states.php:3450
+#: languages/states.php:3475
msgid "Sabah"
msgstr ""
-#: languages/states.php:3451
+#: languages/states.php:3476
msgid "Sarawak"
msgstr ""
-#: languages/states.php:3452
+#: languages/states.php:3477
msgid "Kuala Lumpur"
msgstr ""
-#: languages/states.php:3453
+#: languages/states.php:3478
msgid "Labuan"
msgstr ""
-#: languages/states.php:3454
+#: languages/states.php:3479
msgid "Putrajaya"
msgstr ""
-#: languages/states.php:3455
+#: languages/states.php:3480
msgid "Johor"
msgstr ""
-#: languages/states.php:3456
+#: languages/states.php:3481
msgid "Kedah"
msgstr ""
-#: languages/states.php:3457
+#: languages/states.php:3482
msgid "Kelantan"
msgstr ""
-#: languages/states.php:3458
+#: languages/states.php:3483
msgid "Malacca"
msgstr ""
-#: languages/states.php:3459
+#: languages/states.php:3484
msgid "Negeri Sembilan"
msgstr ""
-#: languages/states.php:3460
+#: languages/states.php:3485
msgid "Pahang"
msgstr ""
-#: languages/states.php:3461
+#: languages/states.php:3486
msgid "Penang"
msgstr ""
-#: languages/states.php:3462
+#: languages/states.php:3487
msgid "Perak"
msgstr ""
-#: languages/states.php:3463
+#: languages/states.php:3488
msgid "Perlis"
msgstr ""
-#: languages/states.php:3466
+#: languages/states.php:3491
msgid "Cabo Delgado Province"
msgstr ""
-#: languages/states.php:3467
+#: languages/states.php:3492
msgid "Gaza Province"
msgstr ""
-#: languages/states.php:3468
+#: languages/states.php:3493
msgid "Inhambane Province"
msgstr ""
-#: languages/states.php:3469
+#: languages/states.php:3494
msgid "Manica Province"
msgstr ""
-#: languages/states.php:3470
+#: languages/states.php:3495
msgid "Maputo"
msgstr ""
-#: languages/states.php:3471
+#: languages/states.php:3496
msgid "Maputo Province"
msgstr ""
-#: languages/states.php:3472
+#: languages/states.php:3497
msgid "Nampula Province"
msgstr ""
-#: languages/states.php:3473
+#: languages/states.php:3498
msgid "Niassa Province"
msgstr ""
-#: languages/states.php:3474
+#: languages/states.php:3499
msgid "Sofala Province"
msgstr ""
-#: languages/states.php:3475
+#: languages/states.php:3500
msgid "Tete Province"
msgstr ""
-#: languages/states.php:3476
+#: languages/states.php:3501
msgid "Zambezia Province"
msgstr ""
-#: languages/states.php:3479
+#: languages/states.php:3504
msgid "Erongo Region"
msgstr ""
-#: languages/states.php:3480
+#: languages/states.php:3505
msgid "Hardap Region"
msgstr ""
-#: languages/states.php:3481
+#: languages/states.php:3506
msgid "Karas Region"
msgstr ""
-#: languages/states.php:3482
+#: languages/states.php:3507
msgid "Kavango East Region"
msgstr ""
-#: languages/states.php:3483
+#: languages/states.php:3508
msgid "Kavango West Region"
msgstr ""
-#: languages/states.php:3484
+#: languages/states.php:3509
msgid "Khomas Region"
msgstr ""
-#: languages/states.php:3485
+#: languages/states.php:3510
msgid "Kunene Region"
msgstr ""
-#: languages/states.php:3486
+#: languages/states.php:3511
msgid "Ohangwena Region"
msgstr ""
-#: languages/states.php:3487
+#: languages/states.php:3512
msgid "Omaheke Region"
msgstr ""
-#: languages/states.php:3488
+#: languages/states.php:3513
msgid "Omusati Region"
msgstr ""
-#: languages/states.php:3489
+#: languages/states.php:3514
msgid "Oshana Region"
msgstr ""
-#: languages/states.php:3490
+#: languages/states.php:3515
msgid "Oshikoto Region"
msgstr ""
-#: languages/states.php:3491
+#: languages/states.php:3516
msgid "Otjozondjupa Region"
msgstr ""
-#: languages/states.php:3492
+#: languages/states.php:3517
msgid "Zambezi Region"
msgstr ""
-#: languages/states.php:3496
+#: languages/states.php:3521
msgid "Agadez Region"
msgstr ""
-#: languages/states.php:3497
+#: languages/states.php:3522
msgid "Diffa Region"
msgstr ""
-#: languages/states.php:3498
+#: languages/states.php:3523
msgid "Dosso Region"
msgstr ""
-#: languages/states.php:3499
+#: languages/states.php:3524
msgid "Maradi Region"
msgstr ""
-#: languages/states.php:3500
+#: languages/states.php:3525
msgid "Tahoua Region"
msgstr ""
-#: languages/states.php:3501
+#: languages/states.php:3526
msgid "Tillabéri Region"
msgstr ""
-#: languages/states.php:3502
+#: languages/states.php:3527
msgid "Zinder Region"
msgstr ""
-#: languages/states.php:3506
+#: languages/states.php:3531
msgid "Abia State"
msgstr ""
-#: languages/states.php:3507
+#: languages/states.php:3532
msgid "Adamawa State"
msgstr ""
-#: languages/states.php:3508
+#: languages/states.php:3533
msgid "Akwa Ibom State"
msgstr ""
-#: languages/states.php:3509
+#: languages/states.php:3534
msgid "Anambra State"
msgstr ""
-#: languages/states.php:3510
+#: languages/states.php:3535
msgid "Bauchi State"
msgstr ""
-#: languages/states.php:3511
+#: languages/states.php:3536
msgid "Bayelsa State"
msgstr ""
-#: languages/states.php:3512
+#: languages/states.php:3537
msgid "Benue State"
msgstr ""
-#: languages/states.php:3513
+#: languages/states.php:3538
msgid "Borno State"
msgstr ""
-#: languages/states.php:3514
+#: languages/states.php:3539
msgid "Cross River State"
msgstr ""
-#: languages/states.php:3515
+#: languages/states.php:3540
msgid "Delta State"
msgstr ""
-#: languages/states.php:3516
+#: languages/states.php:3541
msgid "Ebonyi State"
msgstr ""
-#: languages/states.php:3517
+#: languages/states.php:3542
msgid "Edo State"
msgstr ""
-#: languages/states.php:3518
+#: languages/states.php:3543
msgid "Ekiti State"
msgstr ""
-#: languages/states.php:3519
+#: languages/states.php:3544
msgid "Enugu State"
msgstr ""
-#: languages/states.php:3520
+#: languages/states.php:3545
msgid "Federal Capital Territory"
msgstr ""
-#: languages/states.php:3521
+#: languages/states.php:3546
msgid "Gombe State"
msgstr ""
-#: languages/states.php:3522
+#: languages/states.php:3547
msgid "Imo State"
msgstr ""
-#: languages/states.php:3523
+#: languages/states.php:3548
msgid "Jigawa State"
msgstr ""
-#: languages/states.php:3524
+#: languages/states.php:3549
msgid "Kaduna State"
msgstr ""
-#: languages/states.php:3525
+#: languages/states.php:3550
msgid "Kano State"
msgstr ""
-#: languages/states.php:3526
+#: languages/states.php:3551
msgid "Katsina State"
msgstr ""
-#: languages/states.php:3527
+#: languages/states.php:3552
msgid "Kebbi State"
msgstr ""
-#: languages/states.php:3528
+#: languages/states.php:3553
msgid "Kogi State"
msgstr ""
-#: languages/states.php:3529
+#: languages/states.php:3554
msgid "Kwara State"
msgstr ""
-#: languages/states.php:3530
+#: languages/states.php:3555
msgid "Lagos"
msgstr ""
-#: languages/states.php:3531
+#: languages/states.php:3556
msgid "Nasarawa State"
msgstr ""
-#: languages/states.php:3532
+#: languages/states.php:3557
msgid "Niger State"
msgstr ""
-#: languages/states.php:3533
+#: languages/states.php:3558
msgid "Ogun State"
msgstr ""
-#: languages/states.php:3534
+#: languages/states.php:3559
msgid "Ondo State"
msgstr ""
-#: languages/states.php:3535
+#: languages/states.php:3560
msgid "Osun State"
msgstr ""
-#: languages/states.php:3536
+#: languages/states.php:3561
msgid "Oyo State"
msgstr ""
-#: languages/states.php:3537
+#: languages/states.php:3562
msgid "Plateau State"
msgstr ""
-#: languages/states.php:3538
+#: languages/states.php:3563
msgid "Sokoto State"
msgstr ""
-#: languages/states.php:3539
+#: languages/states.php:3564
msgid "Taraba State"
msgstr ""
-#: languages/states.php:3540
+#: languages/states.php:3565
msgid "Yobe State"
msgstr ""
-#: languages/states.php:3541
+#: languages/states.php:3566
msgid "Zamfara State"
msgstr ""
-#: languages/states.php:3544
+#: languages/states.php:3569
msgid "Boaco Department"
msgstr ""
-#: languages/states.php:3545
+#: languages/states.php:3570
msgid "Carazo Department"
msgstr ""
-#: languages/states.php:3546
+#: languages/states.php:3571
msgid "Chinandega Department"
msgstr ""
-#: languages/states.php:3547
+#: languages/states.php:3572
msgid "Chontales Department"
msgstr ""
-#: languages/states.php:3548
+#: languages/states.php:3573
msgid "Estelí Department"
msgstr ""
-#: languages/states.php:3549
+#: languages/states.php:3574
msgid "Granada Department"
msgstr ""
-#: languages/states.php:3550
+#: languages/states.php:3575
msgid "Jinotega Department"
msgstr ""
-#: languages/states.php:3551
+#: languages/states.php:3576
msgid "León Department"
msgstr ""
-#: languages/states.php:3552
+#: languages/states.php:3577
msgid "Madriz Department"
msgstr ""
-#: languages/states.php:3553
+#: languages/states.php:3578
msgid "Managua Department"
msgstr ""
-#: languages/states.php:3554
+#: languages/states.php:3579
msgid "Masaya Department"
msgstr ""
-#: languages/states.php:3555
+#: languages/states.php:3580
msgid "Matagalpa Department"
msgstr ""
-#: languages/states.php:3556
+#: languages/states.php:3581
msgid "North Caribbean Coast Autonomous Region"
msgstr ""
-#: languages/states.php:3557
+#: languages/states.php:3582
msgid "Rivas Department"
msgstr ""
-#: languages/states.php:3558
+#: languages/states.php:3583
msgid "Río San Juan Department"
msgstr ""
-#: languages/states.php:3559
+#: languages/states.php:3584
msgid "South Caribbean Coast Autonomous Region"
msgstr ""
-#: languages/states.php:3562
+#: languages/states.php:3587
msgid "Bonaire"
msgstr ""
-#: languages/states.php:3563
+#: languages/states.php:3588
msgid "Drenthe"
msgstr ""
-#: languages/states.php:3564
+#: languages/states.php:3589
msgid "Flevoland"
msgstr ""
-#: languages/states.php:3565
+#: languages/states.php:3590
msgid "Friesland"
msgstr ""
-#: languages/states.php:3566
+#: languages/states.php:3591
msgid "Gelderland"
msgstr ""
-#: languages/states.php:3567
+#: languages/states.php:3592
msgid "Groningen"
msgstr ""
-#: languages/states.php:3569
+#: languages/states.php:3594
msgid "North Brabant"
msgstr ""
-#: languages/states.php:3570
+#: languages/states.php:3595
msgid "North Holland"
msgstr ""
-#: languages/states.php:3571
+#: languages/states.php:3596
msgid "Overijssel"
msgstr ""
-#: languages/states.php:3572
+#: languages/states.php:3597
msgid "Saba"
msgstr ""
-#: languages/states.php:3573
+#: languages/states.php:3598
msgid "Sint Eustatius"
msgstr ""
-#: languages/states.php:3574
+#: languages/states.php:3599
msgid "South Holland"
msgstr ""
-#: languages/states.php:3575
+#: languages/states.php:3600
msgid "Utrecht"
msgstr ""
-#: languages/states.php:3576
+#: languages/states.php:3601
msgid "Zeeland"
msgstr ""
-#: languages/states.php:3579
+#: languages/states.php:3604
msgid "Vest-Agder"
msgstr ""
-#: languages/states.php:3580
+#: languages/states.php:3605
msgid "Rogaland"
msgstr ""
-#: languages/states.php:3581
+#: languages/states.php:3606
msgid "Hordaland"
msgstr ""
-#: languages/states.php:3582
+#: languages/states.php:3607
msgid "Sogn og Fjordane"
msgstr ""
-#: languages/states.php:3583
+#: languages/states.php:3608
msgid "Møre og Romsdal"
msgstr ""
-#: languages/states.php:3584
+#: languages/states.php:3609
msgid "Sør-Trøndelag"
msgstr ""
-#: languages/states.php:3585
+#: languages/states.php:3610
msgid "Nord-Trøndelag"
msgstr ""
-#: languages/states.php:3586
+#: languages/states.php:3611
msgid "Nordland"
msgstr ""
-#: languages/states.php:3587
+#: languages/states.php:3612
msgid "Troms"
msgstr ""
-#: languages/states.php:3588
+#: languages/states.php:3613
msgid "Finnmark"
msgstr ""
-#: languages/states.php:3589
+#: languages/states.php:3614
msgid "Svalbard"
msgstr ""
-#: languages/states.php:3590
+#: languages/states.php:3615
msgid "Jan Mayen"
msgstr ""
-#: languages/states.php:3591
+#: languages/states.php:3616
msgid "Trøndelag"
msgstr ""
-#: languages/states.php:3592
+#: languages/states.php:3617
msgid "Akershus"
msgstr ""
-#: languages/states.php:3593
+#: languages/states.php:3618
msgid "Buskerud"
msgstr ""
-#: languages/states.php:3594
+#: languages/states.php:3619
msgid "Hedmark"
msgstr ""
-#: languages/states.php:3595
+#: languages/states.php:3620
msgid "Oppland"
msgstr ""
-#: languages/states.php:3596
+#: languages/states.php:3621
msgid "Oslo"
msgstr ""
-#: languages/states.php:3597
+#: languages/states.php:3622
msgid "Telemark"
msgstr ""
-#: languages/states.php:3598
+#: languages/states.php:3623
msgid "Vestfold"
msgstr ""
-#: languages/states.php:3599
+#: languages/states.php:3624
msgid "Østfold"
msgstr ""
-#: languages/states.php:3603
+#: languages/states.php:3628
msgid "Mid-Western Region"
msgstr ""
-#: languages/states.php:3605
+#: languages/states.php:3630
msgid "Eastern Development Region"
msgstr ""
-#: languages/states.php:3606
+#: languages/states.php:3631
msgid "Far-Western Development Region"
msgstr ""
-#: languages/states.php:3607
+#: languages/states.php:3632
msgid "Bagmati Zone"
msgstr ""
-#: languages/states.php:3608
+#: languages/states.php:3633
msgid "Bheri Zone"
msgstr ""
-#: languages/states.php:3609
+#: languages/states.php:3634
msgid "Dhaulagiri Zone"
msgstr ""
-#: languages/states.php:3610
+#: languages/states.php:3635
msgid "Gandaki Zone"
msgstr ""
-#: languages/states.php:3611
+#: languages/states.php:3636
msgid "Janakpur Zone"
msgstr ""
-#: languages/states.php:3612
+#: languages/states.php:3637
msgid "Karnali Zone"
msgstr ""
-#: languages/states.php:3613
+#: languages/states.php:3638
msgid "Kosi Zone"
msgstr ""
-#: languages/states.php:3614
+#: languages/states.php:3639
msgid "Lumbini Zone"
msgstr ""
-#: languages/states.php:3615
+#: languages/states.php:3640
msgid "Mahakali Zone"
msgstr ""
-#: languages/states.php:3616
+#: languages/states.php:3641
msgid "Mechi Zone"
msgstr ""
-#: languages/states.php:3617
+#: languages/states.php:3642
msgid "Narayani Zone"
msgstr ""
-#: languages/states.php:3618
+#: languages/states.php:3643
msgid "Rapti Zone"
msgstr ""
-#: languages/states.php:3619
+#: languages/states.php:3644
msgid "Sagarmatha Zone"
msgstr ""
-#: languages/states.php:3620
+#: languages/states.php:3645
msgid "Seti Zone"
msgstr ""
-#: languages/states.php:3623
+#: languages/states.php:3648
msgid "Ijuw District"
msgstr ""
-#: languages/states.php:3624
+#: languages/states.php:3649
msgid "Meneng District"
msgstr ""
-#: languages/states.php:3625
+#: languages/states.php:3650
msgid "Nibok District"
msgstr ""
-#: languages/states.php:3626
+#: languages/states.php:3651
msgid "Uaboe District"
msgstr ""
-#: languages/states.php:3627
+#: languages/states.php:3652
msgid "Yaren District"
msgstr ""
-#: languages/states.php:3628
+#: languages/states.php:3653
msgid "Aiwo District"
msgstr ""
-#: languages/states.php:3629
+#: languages/states.php:3654
msgid "Anabar District"
msgstr ""
-#: languages/states.php:3630
+#: languages/states.php:3655
msgid "Anetan District"
msgstr ""
-#: languages/states.php:3631
+#: languages/states.php:3656
msgid "Anibare District"
msgstr ""
-#: languages/states.php:3632
+#: languages/states.php:3657
msgid "Baiti District"
msgstr ""
-#: languages/states.php:3633
+#: languages/states.php:3658
msgid "Boe District"
msgstr ""
-#: languages/states.php:3634
+#: languages/states.php:3659
msgid "Buada District"
msgstr ""
-#: languages/states.php:3635
+#: languages/states.php:3660
msgid "Denigomodu District"
msgstr ""
-#: languages/states.php:3636
+#: languages/states.php:3661
msgid "Ewa District"
msgstr ""
-#: languages/states.php:3640
+#: languages/states.php:3665
msgid "Auckland Region"
msgstr ""
-#: languages/states.php:3641
+#: languages/states.php:3666
msgid "Bay of Plenty Region"
msgstr ""
-#: languages/states.php:3642
+#: languages/states.php:3667
msgid "Canterbury Region"
msgstr ""
-#: languages/states.php:3643
+#: languages/states.php:3668
msgid "Chatham Islands"
msgstr ""
-#: languages/states.php:3644
+#: languages/states.php:3669
msgid "Gisborne District"
msgstr ""
-#: languages/states.php:3645
+#: languages/states.php:3670
msgid "Hawke's Bay Region"
msgstr ""
-#: languages/states.php:3646
+#: languages/states.php:3671
msgid "Manawatu-Wanganui Region"
msgstr ""
-#: languages/states.php:3647
+#: languages/states.php:3672
msgid "Marlborough Region"
msgstr ""
-#: languages/states.php:3648
+#: languages/states.php:3673
msgid "Nelson Region"
msgstr ""
-#: languages/states.php:3649
+#: languages/states.php:3674
msgid "Northland Region"
msgstr ""
-#: languages/states.php:3650
+#: languages/states.php:3675
msgid "Otago Region"
msgstr ""
-#: languages/states.php:3651
+#: languages/states.php:3676
msgid "Southland Region"
msgstr ""
-#: languages/states.php:3652
+#: languages/states.php:3677
msgid "Taranaki Region"
msgstr ""
-#: languages/states.php:3653
+#: languages/states.php:3678
msgid "Tasman District"
msgstr ""
-#: languages/states.php:3654
+#: languages/states.php:3679
msgid "Waikato Region"
msgstr ""
-#: languages/states.php:3655
+#: languages/states.php:3680
msgid "Wellington Region"
msgstr ""
-#: languages/states.php:3656
+#: languages/states.php:3681
msgid "West Coast Region"
msgstr ""
-#: languages/states.php:3659
+#: languages/states.php:3684
msgid "Ad Dakhiliyah Governorate"
msgstr ""
-#: languages/states.php:3660
+#: languages/states.php:3685
msgid "Ad Dhahirah Governorate"
msgstr ""
-#: languages/states.php:3661
+#: languages/states.php:3686
msgid "Al Batinah North Governorate"
msgstr ""
-#: languages/states.php:3662
+#: languages/states.php:3687
msgid "Al Batinah Region"
msgstr ""
-#: languages/states.php:3663
+#: languages/states.php:3688
msgid "Al Batinah South Governorate"
msgstr ""
-#: languages/states.php:3664
+#: languages/states.php:3689
msgid "Al Buraimi Governorate"
msgstr ""
-#: languages/states.php:3665
+#: languages/states.php:3690
msgid "Al Wusta Governorate"
msgstr ""
-#: languages/states.php:3666
+#: languages/states.php:3691
msgid "Ash Sharqiyah North Governorate"
msgstr ""
-#: languages/states.php:3667
+#: languages/states.php:3692
msgid "Ash Sharqiyah Region"
msgstr ""
-#: languages/states.php:3668
+#: languages/states.php:3693
msgid "Ash Sharqiyah South Governorate"
msgstr ""
-#: languages/states.php:3669
+#: languages/states.php:3694
msgid "Dhofar Governorate"
msgstr ""
-#: languages/states.php:3670
+#: languages/states.php:3695
msgid "Musandam Governorate"
msgstr ""
-#: languages/states.php:3671
+#: languages/states.php:3696
msgid "Muscat Governorate"
msgstr ""
-#: languages/states.php:3674
+#: languages/states.php:3699
msgid "Bocas del Toro Province"
msgstr ""
-#: languages/states.php:3675
+#: languages/states.php:3700
msgid "Coclé Province"
msgstr ""
-#: languages/states.php:3676
+#: languages/states.php:3701
msgid "Colón Province"
msgstr ""
-#: languages/states.php:3677
+#: languages/states.php:3702
msgid "Chiriquí Province"
msgstr ""
-#: languages/states.php:3678
+#: languages/states.php:3703
msgid "Darién Province"
msgstr ""
-#: languages/states.php:3679
+#: languages/states.php:3704
msgid "Herrera Province"
msgstr ""
-#: languages/states.php:3680
+#: languages/states.php:3705
msgid "Los Santos Province"
msgstr ""
-#: languages/states.php:3681
+#: languages/states.php:3706
msgid "Panamá Province"
msgstr ""
-#: languages/states.php:3682
+#: languages/states.php:3707
msgid "Veraguas Province"
msgstr ""
-#: languages/states.php:3683
+#: languages/states.php:3708
msgid "Panamá Oeste Province"
msgstr ""
-#: languages/states.php:3684
+#: languages/states.php:3709
msgid "Emberá-Wounaan Comarca"
msgstr ""
-#: languages/states.php:3685
+#: languages/states.php:3710
msgid "Guna Yala"
msgstr ""
-#: languages/states.php:3686
+#: languages/states.php:3711
msgid "Ngöbe-Buglé Comarca"
msgstr ""
-#: languages/states.php:3690
+#: languages/states.php:3715
msgid "Apurímac"
msgstr ""
-#: languages/states.php:3691
+#: languages/states.php:3716
msgid "Arequipa"
msgstr ""
-#: languages/states.php:3692
+#: languages/states.php:3717
msgid "Ayacucho"
msgstr ""
-#: languages/states.php:3693
+#: languages/states.php:3718
msgid "Cajamarca"
msgstr ""
-#: languages/states.php:3694
+#: languages/states.php:3719
msgid "Callao"
msgstr ""
-#: languages/states.php:3695
+#: languages/states.php:3720
msgid "Cusco"
msgstr ""
-#: languages/states.php:3696
+#: languages/states.php:3721
msgid "Huancavelica"
msgstr ""
-#: languages/states.php:3697
+#: languages/states.php:3722
msgid "Huanuco"
msgstr ""
-#: languages/states.php:3698
+#: languages/states.php:3723
msgid "Ica"
msgstr ""
-#: languages/states.php:3699
+#: languages/states.php:3724
msgid "Junín"
msgstr ""
-#: languages/states.php:3700
+#: languages/states.php:3725
msgid "La Libertad"
msgstr ""
-#: languages/states.php:3701
+#: languages/states.php:3726
msgid "Lambayeque"
msgstr ""
-#: languages/states.php:3702
+#: languages/states.php:3727
msgid "Lima"
msgstr ""
-#: languages/states.php:3703
+#: languages/states.php:3728
msgid "Madre de Dios"
msgstr ""
-#: languages/states.php:3704
+#: languages/states.php:3729
msgid "Moquegua"
msgstr ""
-#: languages/states.php:3705
+#: languages/states.php:3730
msgid "Pasco"
msgstr ""
-#: languages/states.php:3706
+#: languages/states.php:3731
msgid "Piura"
msgstr ""
-#: languages/states.php:3707
+#: languages/states.php:3732
msgid "Puno"
msgstr ""
-#: languages/states.php:3708
+#: languages/states.php:3733
msgid "San Martín"
msgstr ""
-#: languages/states.php:3709
+#: languages/states.php:3734
msgid "Tacna"
msgstr ""
-#: languages/states.php:3710
+#: languages/states.php:3735
msgid "Tumbes"
msgstr ""
-#: languages/states.php:3711
+#: languages/states.php:3736
msgid "Ucayali"
msgstr ""
-#: languages/states.php:3712
+#: languages/states.php:3737
msgid "Áncash"
msgstr ""
-#: languages/states.php:3716
+#: languages/states.php:3741
msgid "Bougainville"
msgstr ""
-#: languages/states.php:3718
+#: languages/states.php:3743
msgid "Chimbu Province"
msgstr ""
-#: languages/states.php:3719
+#: languages/states.php:3744
msgid "East New Britain"
msgstr ""
-#: languages/states.php:3720
+#: languages/states.php:3745
msgid "Eastern Highlands Province"
msgstr ""
-#: languages/states.php:3721
+#: languages/states.php:3746
msgid "Enga Province"
msgstr ""
-#: languages/states.php:3722
+#: languages/states.php:3747
msgid "Gulf"
msgstr ""
-#: languages/states.php:3723
+#: languages/states.php:3748
msgid "Hela"
msgstr ""
-#: languages/states.php:3724
+#: languages/states.php:3749
msgid "Jiwaka Province"
msgstr ""
-#: languages/states.php:3725
+#: languages/states.php:3750
msgid "Madang Province"
msgstr ""
-#: languages/states.php:3726
+#: languages/states.php:3751
msgid "Manus Province"
msgstr ""
-#: languages/states.php:3727
+#: languages/states.php:3752
msgid "Milne Bay Province"
msgstr ""
-#: languages/states.php:3728
+#: languages/states.php:3753
msgid "Morobe Province"
msgstr ""
-#: languages/states.php:3729
+#: languages/states.php:3754
msgid "New Ireland Province"
msgstr ""
-#: languages/states.php:3730
+#: languages/states.php:3755
msgid "Oro Province"
msgstr ""
-#: languages/states.php:3731
+#: languages/states.php:3756
msgid "Port Moresby"
msgstr ""
-#: languages/states.php:3732
+#: languages/states.php:3757
msgid "Sandaun Province"
msgstr ""
-#: languages/states.php:3733
+#: languages/states.php:3758
msgid "Southern Highlands Province"
msgstr ""
-#: languages/states.php:3734
+#: languages/states.php:3759
msgid "West New Britain Province"
msgstr ""
-#: languages/states.php:3735
+#: languages/states.php:3760
msgid "Western Highlands Province"
msgstr ""
-#: languages/states.php:3739
+#: languages/states.php:3764
msgid "Northern Mindanao"
msgstr ""
-#: languages/states.php:3740
+#: languages/states.php:3765
msgid "Davao Region"
msgstr ""
-#: languages/states.php:3741
+#: languages/states.php:3766
msgid "Soccsksargen"
msgstr ""
-#: languages/states.php:3742
+#: languages/states.php:3767
msgid "Caraga"
msgstr ""
-#: languages/states.php:3743
+#: languages/states.php:3768
msgid "Autonomous Region in Muslim Mindanao"
msgstr ""
-#: languages/states.php:3744
+#: languages/states.php:3769
msgid "Cordillera Administrative Region"
msgstr ""
-#: languages/states.php:3745
+#: languages/states.php:3770
msgid "Calabarzon"
msgstr ""
-#: languages/states.php:3746
+#: languages/states.php:3771
msgid "Mimaropa"
msgstr ""
-#: languages/states.php:3747
+#: languages/states.php:3772
msgid "Abra"
msgstr ""
-#: languages/states.php:3748
+#: languages/states.php:3773
msgid "Agusan del Norte"
msgstr ""
-#: languages/states.php:3749
+#: languages/states.php:3774
msgid "Agusan del Sur"
msgstr ""
-#: languages/states.php:3750
+#: languages/states.php:3775
msgid "Aklan"
msgstr ""
-#: languages/states.php:3751
+#: languages/states.php:3776
msgid "Albay"
msgstr ""
-#: languages/states.php:3752
+#: languages/states.php:3777
msgid "Antique"
msgstr ""
-#: languages/states.php:3753
+#: languages/states.php:3778
msgid "Apayao"
msgstr ""
-#: languages/states.php:3754
+#: languages/states.php:3779
msgid "Aurora"
msgstr ""
-#: languages/states.php:3755
+#: languages/states.php:3780
msgid "Basilan"
msgstr ""
-#: languages/states.php:3756
+#: languages/states.php:3781
msgid "Bataan"
msgstr ""
-#: languages/states.php:3757
+#: languages/states.php:3782
msgid "Batanes"
msgstr ""
-#: languages/states.php:3758
+#: languages/states.php:3783
msgid "Batangas"
msgstr ""
-#: languages/states.php:3759
+#: languages/states.php:3784
msgid "Benguet"
msgstr ""
-#: languages/states.php:3760
+#: languages/states.php:3785
msgid "Bicol Region"
msgstr ""
-#: languages/states.php:3761
+#: languages/states.php:3786
msgid "Biliran"
msgstr ""
-#: languages/states.php:3762
+#: languages/states.php:3787
msgid "Bohol"
msgstr ""
-#: languages/states.php:3763
+#: languages/states.php:3788
msgid "Bukidnon"
msgstr ""
-#: languages/states.php:3764
+#: languages/states.php:3789
msgid "Bulacan"
msgstr ""
-#: languages/states.php:3765
+#: languages/states.php:3790
msgid "Cagayan"
msgstr ""
-#: languages/states.php:3766
+#: languages/states.php:3791
msgid "Cagayan Valley"
msgstr ""
-#: languages/states.php:3767
+#: languages/states.php:3792
msgid "Camarines Norte"
msgstr ""
-#: languages/states.php:3768
+#: languages/states.php:3793
msgid "Camarines Sur"
msgstr ""
-#: languages/states.php:3769
+#: languages/states.php:3794
msgid "Camiguin"
msgstr ""
-#: languages/states.php:3770
+#: languages/states.php:3795
msgid "Capiz"
msgstr ""
-#: languages/states.php:3771
+#: languages/states.php:3796
msgid "Catanduanes"
msgstr ""
-#: languages/states.php:3772
+#: languages/states.php:3797
msgid "Cavite"
msgstr ""
-#: languages/states.php:3773
+#: languages/states.php:3798
msgid "Cebu"
msgstr ""
-#: languages/states.php:3774
+#: languages/states.php:3799
msgid "Central Luzon"
msgstr ""
-#: languages/states.php:3775
+#: languages/states.php:3800
msgid "Central Visayas"
msgstr ""
-#: languages/states.php:3776
+#: languages/states.php:3801
msgid "Compostela Valley"
msgstr ""
-#: languages/states.php:3777
+#: languages/states.php:3802
msgid "Cotabato"
msgstr ""
-#: languages/states.php:3778
+#: languages/states.php:3803
msgid "Davao Occidental"
msgstr ""
-#: languages/states.php:3779
+#: languages/states.php:3804
msgid "Davao Oriental"
msgstr ""
-#: languages/states.php:3780
+#: languages/states.php:3805
msgid "Davao del Norte"
msgstr ""
-#: languages/states.php:3781
+#: languages/states.php:3806
msgid "Davao del Sur"
msgstr ""
-#: languages/states.php:3782
+#: languages/states.php:3807
msgid "Dinagat Islands"
msgstr ""
-#: languages/states.php:3783
+#: languages/states.php:3808
msgid "Eastern Samar"
msgstr ""
-#: languages/states.php:3784
+#: languages/states.php:3809
msgid "Eastern Visayas"
msgstr ""
-#: languages/states.php:3785
+#: languages/states.php:3810
msgid "Guimaras"
msgstr ""
-#: languages/states.php:3786
+#: languages/states.php:3811
msgid "Ifugao"
msgstr ""
-#: languages/states.php:3787
+#: languages/states.php:3812
msgid "Ilocos Norte"
msgstr ""
-#: languages/states.php:3788
+#: languages/states.php:3813
msgid "Ilocos Region"
msgstr ""
-#: languages/states.php:3789
+#: languages/states.php:3814
msgid "Ilocos Sur"
msgstr ""
-#: languages/states.php:3790
+#: languages/states.php:3815
msgid "Iloilo"
msgstr ""
-#: languages/states.php:3791
+#: languages/states.php:3816
msgid "Isabela"
msgstr ""
-#: languages/states.php:3792
+#: languages/states.php:3817
msgid "Kalinga"
msgstr ""
-#: languages/states.php:3793
+#: languages/states.php:3818
msgid "La Union"
msgstr ""
-#: languages/states.php:3794
+#: languages/states.php:3819
msgid "Laguna"
msgstr ""
-#: languages/states.php:3795
+#: languages/states.php:3820
msgid "Lanao del Norte"
msgstr ""
-#: languages/states.php:3796
+#: languages/states.php:3821
msgid "Lanao del Sur"
msgstr ""
-#: languages/states.php:3797
+#: languages/states.php:3822
msgid "Leyte"
msgstr ""
-#: languages/states.php:3798
+#: languages/states.php:3823
msgid "Maguindanao"
msgstr ""
-#: languages/states.php:3799
+#: languages/states.php:3824
msgid "Marinduque"
msgstr ""
-#: languages/states.php:3800
+#: languages/states.php:3825
msgid "Masbate"
msgstr ""
-#: languages/states.php:3801
+#: languages/states.php:3826
msgid "Metro Manila"
msgstr ""
-#: languages/states.php:3802
+#: languages/states.php:3827
msgid "Misamis Occidental"
msgstr ""
-#: languages/states.php:3803
+#: languages/states.php:3828
msgid "Misamis Oriental"
msgstr ""
-#: languages/states.php:3804
+#: languages/states.php:3829
msgid "Mountain Province"
msgstr ""
-#: languages/states.php:3805
+#: languages/states.php:3830
msgid "Negros Occidental"
msgstr ""
-#: languages/states.php:3806
+#: languages/states.php:3831
msgid "Negros Oriental"
msgstr ""
-#: languages/states.php:3807
+#: languages/states.php:3832
msgid "Northern Samar"
msgstr ""
-#: languages/states.php:3808
+#: languages/states.php:3833
msgid "Nueva Ecija"
msgstr ""
-#: languages/states.php:3809
+#: languages/states.php:3834
msgid "Nueva Vizcaya"
msgstr ""
-#: languages/states.php:3810
+#: languages/states.php:3835
msgid "Occidental Mindoro"
msgstr ""
-#: languages/states.php:3811
+#: languages/states.php:3836
msgid "Oriental Mindoro"
msgstr ""
-#: languages/states.php:3812
+#: languages/states.php:3837
msgid "Palawan"
msgstr ""
-#: languages/states.php:3813
+#: languages/states.php:3838
msgid "Pampanga"
msgstr ""
-#: languages/states.php:3814
+#: languages/states.php:3839
msgid "Pangasinan"
msgstr ""
-#: languages/states.php:3815
+#: languages/states.php:3840
msgid "Quezon"
msgstr ""
-#: languages/states.php:3816
+#: languages/states.php:3841
msgid "Quirino"
msgstr ""
-#: languages/states.php:3817
+#: languages/states.php:3842
msgid "Rizal"
msgstr ""
-#: languages/states.php:3818
+#: languages/states.php:3843
msgid "Romblon"
msgstr ""
-#: languages/states.php:3819
+#: languages/states.php:3844
msgid "Sarangani"
msgstr ""
-#: languages/states.php:3820
+#: languages/states.php:3845
msgid "Siquijor"
msgstr ""
-#: languages/states.php:3821
+#: languages/states.php:3846
msgid "Sorsogon"
msgstr ""
-#: languages/states.php:3822
+#: languages/states.php:3847
msgid "South Cotabato"
msgstr ""
-#: languages/states.php:3823
+#: languages/states.php:3848
msgid "Southern Leyte"
msgstr ""
-#: languages/states.php:3824
+#: languages/states.php:3849
msgid "Sultan Kudarat"
msgstr ""
-#: languages/states.php:3825
+#: languages/states.php:3850
msgid "Sulu"
msgstr ""
-#: languages/states.php:3826
+#: languages/states.php:3851
msgid "Surigao del Norte"
msgstr ""
-#: languages/states.php:3827
+#: languages/states.php:3852
msgid "Surigao del Sur"
msgstr ""
-#: languages/states.php:3828
+#: languages/states.php:3853
msgid "Tarlac"
msgstr ""
-#: languages/states.php:3829
+#: languages/states.php:3854
msgid "Tawi-Tawi"
msgstr ""
-#: languages/states.php:3830
+#: languages/states.php:3855
msgid "Western Visayas"
msgstr ""
-#: languages/states.php:3831
+#: languages/states.php:3856
msgid "Zambales"
msgstr ""
-#: languages/states.php:3832
+#: languages/states.php:3857
msgid "Zamboanga Peninsula"
msgstr ""
-#: languages/states.php:3833
+#: languages/states.php:3858
msgid "Zamboanga Sibugay"
msgstr ""
-#: languages/states.php:3834
+#: languages/states.php:3859
msgid "Zamboanga del Norte"
msgstr ""
-#: languages/states.php:3835
+#: languages/states.php:3860
msgid "Zamboanga del Sur"
msgstr ""
-#: languages/states.php:3838
+#: languages/states.php:3863
msgid "Azad Kashmir"
msgstr ""
-#: languages/states.php:3839
+#: languages/states.php:3864
msgid "Balochistan"
msgstr ""
-#: languages/states.php:3840
+#: languages/states.php:3865
msgid "Federally Administered Tribal Areas"
msgstr ""
-#: languages/states.php:3841
+#: languages/states.php:3866
msgid "Gilgit-Baltistan"
msgstr ""
-#: languages/states.php:3842
+#: languages/states.php:3867
msgid "Islamabad Capital Territory"
msgstr ""
-#: languages/states.php:3843
+#: languages/states.php:3868
msgid "Khyber Pakhtunkhwa"
msgstr ""
-#: languages/states.php:3845
+#: languages/states.php:3870
msgid "Sindh"
msgstr ""
-#: languages/states.php:3848
+#: languages/states.php:3873
msgid "Greater Poland Voivodeship"
msgstr ""
-#: languages/states.php:3849
+#: languages/states.php:3874
msgid "Kuyavian-Pomeranian Voivodeship"
msgstr ""
-#: languages/states.php:3850
+#: languages/states.php:3875
msgid "Lesser Poland Voivodeship"
msgstr ""
-#: languages/states.php:3851
+#: languages/states.php:3876
msgid "Lower Silesian Voivodeship"
msgstr ""
-#: languages/states.php:3852
+#: languages/states.php:3877
msgid "Lublin Voivodeship"
msgstr ""
-#: languages/states.php:3853
+#: languages/states.php:3878
msgid "Lubusz Voivodeship"
msgstr ""
-#: languages/states.php:3854
+#: languages/states.php:3879
msgid "Masovian Voivodeship"
msgstr ""
-#: languages/states.php:3855
+#: languages/states.php:3880
msgid "Opole Voivodeship"
msgstr ""
-#: languages/states.php:3856
+#: languages/states.php:3881
msgid "Podkarpackie Voivodeship"
msgstr ""
-#: languages/states.php:3857
+#: languages/states.php:3882
msgid "Podlaskie Voivodeship"
msgstr ""
-#: languages/states.php:3858
+#: languages/states.php:3883
msgid "Pomeranian Voivodeship"
msgstr ""
-#: languages/states.php:3859
+#: languages/states.php:3884
msgid "Silesian Voivodeship"
msgstr ""
-#: languages/states.php:3860
+#: languages/states.php:3885
msgid "Warmian-Masurian Voivodeship"
msgstr ""
-#: languages/states.php:3861
+#: languages/states.php:3886
msgid "West Pomeranian Voivodeship"
msgstr ""
-#: languages/states.php:3862
+#: languages/states.php:3887
msgid "Łódź Voivodeship"
msgstr ""
-#: languages/states.php:3863
+#: languages/states.php:3888
msgid "Świętokrzyskie Voivodeship"
msgstr ""
-#: languages/states.php:3870
+#: languages/states.php:3895
msgid "Leiria"
msgstr ""
-#: languages/states.php:3871
+#: languages/states.php:3896
msgid "Lisbon"
msgstr ""
-#: languages/states.php:3872
+#: languages/states.php:3897
msgid "Portalegre"
msgstr ""
-#: languages/states.php:3873
+#: languages/states.php:3898
msgid "Porto"
msgstr ""
-#: languages/states.php:3874
+#: languages/states.php:3899
msgid "Santarém"
msgstr ""
-#: languages/states.php:3875
+#: languages/states.php:3900
msgid "Setúbal"
msgstr ""
-#: languages/states.php:3876
+#: languages/states.php:3901
msgid "Viana do Castelo"
msgstr ""
-#: languages/states.php:3877
+#: languages/states.php:3902
msgid "Vila Real"
msgstr ""
-#: languages/states.php:3878
+#: languages/states.php:3903
msgid "Viseu"
msgstr ""
-#: languages/states.php:3879
+#: languages/states.php:3904
msgid "Açores"
msgstr ""
-#: languages/states.php:3880
+#: languages/states.php:3905
msgid "Madeira"
msgstr ""
-#: languages/states.php:3881
+#: languages/states.php:3906
msgid "Aveiro"
msgstr ""
-#: languages/states.php:3882
+#: languages/states.php:3907
msgid "Beja"
msgstr ""
-#: languages/states.php:3883
+#: languages/states.php:3908
msgid "Braga"
msgstr ""
-#: languages/states.php:3884
+#: languages/states.php:3909
msgid "Bragança"
msgstr ""
-#: languages/states.php:3885
+#: languages/states.php:3910
msgid "Castelo Branco"
msgstr ""
-#: languages/states.php:3886
+#: languages/states.php:3911
msgid "Coimbra"
msgstr ""
-#: languages/states.php:3887
+#: languages/states.php:3912
msgid "Faro"
msgstr ""
-#: languages/states.php:3888
+#: languages/states.php:3913
msgid "Guarda"
msgstr ""
-#: languages/states.php:3889
+#: languages/states.php:3914
msgid "Évora"
msgstr ""
-#: languages/states.php:3892
+#: languages/states.php:3917
msgid "Kayangel"
msgstr ""
-#: languages/states.php:3893
+#: languages/states.php:3918
msgid "Koror"
msgstr ""
-#: languages/states.php:3894
+#: languages/states.php:3919
msgid "Melekeok"
msgstr ""
-#: languages/states.php:3895
+#: languages/states.php:3920
msgid "Ngaraard"
msgstr ""
-#: languages/states.php:3896
+#: languages/states.php:3921
msgid "Ngarchelong"
msgstr ""
-#: languages/states.php:3897
+#: languages/states.php:3922
msgid "Ngardmau"
msgstr ""
-#: languages/states.php:3898
+#: languages/states.php:3923
msgid "Ngatpang"
msgstr ""
-#: languages/states.php:3899
+#: languages/states.php:3924
msgid "Ngchesar"
msgstr ""
-#: languages/states.php:3900
+#: languages/states.php:3925
msgid "Ngeremlengui"
msgstr ""
-#: languages/states.php:3901
+#: languages/states.php:3926
msgid "Ngiwal"
msgstr ""
-#: languages/states.php:3902
+#: languages/states.php:3927
msgid "Peleliu"
msgstr ""
-#: languages/states.php:3903
+#: languages/states.php:3928
msgid "Sonsorol"
msgstr ""
-#: languages/states.php:3904
+#: languages/states.php:3929
msgid "Aimeliik"
msgstr ""
-#: languages/states.php:3905
+#: languages/states.php:3930
msgid "Airai"
msgstr ""
-#: languages/states.php:3906
+#: languages/states.php:3931
msgid "Angaur"
msgstr ""
-#: languages/states.php:3907
+#: languages/states.php:3932
msgid "Hatohobei"
msgstr ""
-#: languages/states.php:3910
+#: languages/states.php:3935
msgid "Concepción Department"
msgstr ""
-#: languages/states.php:3911
+#: languages/states.php:3936
msgid "San Pedro Department"
msgstr ""
-#: languages/states.php:3912
+#: languages/states.php:3937
msgid "Cordillera Department"
msgstr ""
-#: languages/states.php:3913
+#: languages/states.php:3938
msgid "Guairá Department"
msgstr ""
-#: languages/states.php:3914
+#: languages/states.php:3939
msgid "Caaguazú"
msgstr ""
-#: languages/states.php:3915
+#: languages/states.php:3940
msgid "Caazapá"
msgstr ""
-#: languages/states.php:3916
+#: languages/states.php:3941
msgid "Itapúa"
msgstr ""
-#: languages/states.php:3917
+#: languages/states.php:3942
msgid "Misiones Department"
msgstr ""
-#: languages/states.php:3918
+#: languages/states.php:3943
msgid "Paraguarí Department"
msgstr ""
-#: languages/states.php:3919
+#: languages/states.php:3944
msgid "Alto Paraná Department"
msgstr ""
-#: languages/states.php:3920
+#: languages/states.php:3945
msgid "Central Department"
msgstr ""
-#: languages/states.php:3921
+#: languages/states.php:3946
msgid "Ñeembucú Department"
msgstr ""
-#: languages/states.php:3922
+#: languages/states.php:3947
msgid "Amambay Department"
msgstr ""
-#: languages/states.php:3923
+#: languages/states.php:3948
msgid "Canindeyú"
msgstr ""
-#: languages/states.php:3924
+#: languages/states.php:3949
msgid "Presidente Hayes Department"
msgstr ""
-#: languages/states.php:3925
+#: languages/states.php:3950
msgid "Alto Paraguay Department"
msgstr ""
-#: languages/states.php:3926
+#: languages/states.php:3951
msgid "Boquerón Department"
msgstr ""
-#: languages/states.php:3929
+#: languages/states.php:3954
msgid "Al Daayen"
msgstr ""
-#: languages/states.php:3930
+#: languages/states.php:3955
msgid "Al Khor"
msgstr ""
-#: languages/states.php:3931
+#: languages/states.php:3956
msgid "Al Rayyan Municipality"
msgstr ""
-#: languages/states.php:3932
+#: languages/states.php:3957
msgid "Al Wakrah"
msgstr ""
-#: languages/states.php:3933
+#: languages/states.php:3958
msgid "Al-Shahaniya"
msgstr ""
-#: languages/states.php:3934
+#: languages/states.php:3959
msgid "Doha"
msgstr ""
-#: languages/states.php:3935
+#: languages/states.php:3960
msgid "Madinat ash Shamal"
msgstr ""
-#: languages/states.php:3936
+#: languages/states.php:3961
msgid "Umm Salal Municipality"
msgstr ""
-#: languages/states.php:3940
+#: languages/states.php:3965
msgid "Alba"
msgstr ""
-#: languages/states.php:3941
+#: languages/states.php:3966
msgid "Arad County"
msgstr ""
-#: languages/states.php:3942
+#: languages/states.php:3967
msgid "Arges"
msgstr ""
-#: languages/states.php:3943
+#: languages/states.php:3968
msgid "Bacău County"
msgstr ""
-#: languages/states.php:3944
+#: languages/states.php:3969
msgid "Bihor County"
msgstr ""
-#: languages/states.php:3945
+#: languages/states.php:3970
msgid "Bistrița-Năsăud County"
msgstr ""
-#: languages/states.php:3946
+#: languages/states.php:3971
msgid "Botoșani County"
msgstr ""
-#: languages/states.php:3947
+#: languages/states.php:3972
msgid "Braila"
msgstr ""
-#: languages/states.php:3948
+#: languages/states.php:3973
msgid "Brașov County"
msgstr ""
-#: languages/states.php:3949
+#: languages/states.php:3974
msgid "Bucharest"
msgstr ""
-#: languages/states.php:3950
+#: languages/states.php:3975
msgid "Buzău County"
msgstr ""
-#: languages/states.php:3951
+#: languages/states.php:3976
msgid "Caraș-Severin County"
msgstr ""
-#: languages/states.php:3952
+#: languages/states.php:3977
msgid "Cluj County"
msgstr ""
-#: languages/states.php:3953
+#: languages/states.php:3978
msgid "Constanța County"
msgstr ""
-#: languages/states.php:3954
+#: languages/states.php:3979
msgid "Covasna County"
msgstr ""
-#: languages/states.php:3955
+#: languages/states.php:3980
msgid "Călărași County"
msgstr ""
-#: languages/states.php:3956
+#: languages/states.php:3981
msgid "Dolj County"
msgstr ""
-#: languages/states.php:3957
+#: languages/states.php:3982
msgid "Dâmbovița County"
msgstr ""
-#: languages/states.php:3958
+#: languages/states.php:3983
msgid "Galați County"
msgstr ""
-#: languages/states.php:3959
+#: languages/states.php:3984
msgid "Giurgiu County"
msgstr ""
-#: languages/states.php:3960
+#: languages/states.php:3985
msgid "Gorj County"
msgstr ""
-#: languages/states.php:3961
+#: languages/states.php:3986
msgid "Harghita County"
msgstr ""
-#: languages/states.php:3962
+#: languages/states.php:3987
msgid "Hunedoara County"
msgstr ""
-#: languages/states.php:3963
+#: languages/states.php:3988
msgid "Ialomița County"
msgstr ""
-#: languages/states.php:3964
+#: languages/states.php:3989
msgid "Iași County"
msgstr ""
-#: languages/states.php:3965
+#: languages/states.php:3990
msgid "Ilfov County"
msgstr ""
-#: languages/states.php:3966
+#: languages/states.php:3991
msgid "Maramureș County"
msgstr ""
-#: languages/states.php:3967
+#: languages/states.php:3992
msgid "Mehedinți County"
msgstr ""
-#: languages/states.php:3968
+#: languages/states.php:3993
msgid "Mureș County"
msgstr ""
-#: languages/states.php:3969
+#: languages/states.php:3994
msgid "Neamț County"
msgstr ""
-#: languages/states.php:3970
+#: languages/states.php:3995
msgid "Olt County"
msgstr ""
-#: languages/states.php:3971
+#: languages/states.php:3996
msgid "Prahova County"
msgstr ""
-#: languages/states.php:3972
+#: languages/states.php:3997
msgid "Satu Mare County"
msgstr ""
-#: languages/states.php:3973
+#: languages/states.php:3998
msgid "Sibiu County"
msgstr ""
-#: languages/states.php:3974
+#: languages/states.php:3999
msgid "Suceava County"
msgstr ""
-#: languages/states.php:3975
+#: languages/states.php:4000
msgid "Sălaj County"
msgstr ""
-#: languages/states.php:3976
+#: languages/states.php:4001
msgid "Teleorman County"
msgstr ""
-#: languages/states.php:3977
+#: languages/states.php:4002
msgid "Timiș County"
msgstr ""
-#: languages/states.php:3978
+#: languages/states.php:4003
msgid "Tulcea County"
msgstr ""
-#: languages/states.php:3979
+#: languages/states.php:4004
msgid "Vaslui County"
msgstr ""
-#: languages/states.php:3980
+#: languages/states.php:4005
msgid "Vrancea County"
msgstr ""
-#: languages/states.php:3981
+#: languages/states.php:4006
msgid "Vâlcea County"
msgstr ""
-#: languages/states.php:3984
+#: languages/states.php:4009
msgid "Podunavlje District"
msgstr ""
-#: languages/states.php:3985
+#: languages/states.php:4010
msgid "Braničevo District"
msgstr ""
-#: languages/states.php:3986
+#: languages/states.php:4011
msgid "Šumadija District"
msgstr ""
-#: languages/states.php:3987
+#: languages/states.php:4012
msgid "Pomoravlje District"
msgstr ""
-#: languages/states.php:3988
+#: languages/states.php:4013
msgid "Bor District"
msgstr ""
-#: languages/states.php:3989
+#: languages/states.php:4014
msgid "Zaječar District"
msgstr ""
-#: languages/states.php:3990
+#: languages/states.php:4015
msgid "Zlatibor District"
msgstr ""
-#: languages/states.php:3991
+#: languages/states.php:4016
msgid "Moravica District"
msgstr ""
-#: languages/states.php:3992
+#: languages/states.php:4017
msgid "Raška District"
msgstr ""
-#: languages/states.php:3993
+#: languages/states.php:4018
msgid "Rasina District"
msgstr ""
-#: languages/states.php:3994
+#: languages/states.php:4019
msgid "Nišava District"
msgstr ""
-#: languages/states.php:3995
+#: languages/states.php:4020
msgid "Toplica District"
msgstr ""
-#: languages/states.php:3996
+#: languages/states.php:4021
msgid "Pirot District"
msgstr ""
-#: languages/states.php:3997
+#: languages/states.php:4022
msgid "Jablanica District"
msgstr ""
-#: languages/states.php:3998
+#: languages/states.php:4023
msgid "Pčinja District"
msgstr ""
-#: languages/states.php:3999
+#: languages/states.php:4024
msgid "Belgrade"
msgstr ""
-#: languages/states.php:4000
+#: languages/states.php:4025
msgid "Central Banat District"
msgstr ""
-#: languages/states.php:4001
+#: languages/states.php:4026
msgid "Kolubara District"
msgstr ""
-#: languages/states.php:4002
+#: languages/states.php:4027
msgid "Mačva District"
msgstr ""
-#: languages/states.php:4003
+#: languages/states.php:4028
msgid "North Banat District"
msgstr ""
-#: languages/states.php:4004
+#: languages/states.php:4029
msgid "North Bačka District"
msgstr ""
-#: languages/states.php:4005
+#: languages/states.php:4030
msgid "South Banat District"
msgstr ""
-#: languages/states.php:4006
+#: languages/states.php:4031
msgid "South Bačka District"
msgstr ""
-#: languages/states.php:4007
+#: languages/states.php:4032
msgid "Srem District"
msgstr ""
-#: languages/states.php:4008
+#: languages/states.php:4033
msgid "Vojvodina"
msgstr ""
-#: languages/states.php:4009
+#: languages/states.php:4034
msgid "West Bačka District"
msgstr ""
-#: languages/states.php:4012
+#: languages/states.php:4037
msgid "Altai Krai"
msgstr ""
-#: languages/states.php:4013
+#: languages/states.php:4038
msgid "Altai Republic"
msgstr ""
-#: languages/states.php:4014
+#: languages/states.php:4039
msgid "Amur Oblast"
msgstr ""
-#: languages/states.php:4015
+#: languages/states.php:4040
msgid "Arkhangelsk"
msgstr ""
-#: languages/states.php:4016
+#: languages/states.php:4041
msgid "Astrakhan Oblast"
msgstr ""
-#: languages/states.php:4017
+#: languages/states.php:4042
msgid "Belgorod Oblast"
msgstr ""
-#: languages/states.php:4018
+#: languages/states.php:4043
msgid "Bryansk Oblast"
msgstr ""
-#: languages/states.php:4019
+#: languages/states.php:4044
msgid "Chechen Republic"
msgstr ""
-#: languages/states.php:4020
+#: languages/states.php:4045
msgid "Chelyabinsk Oblast"
msgstr ""
-#: languages/states.php:4021
+#: languages/states.php:4046
msgid "Chukotka Autonomous Okrug"
msgstr ""
-#: languages/states.php:4022
+#: languages/states.php:4047
msgid "Chuvash Republic"
msgstr ""
-#: languages/states.php:4023
+#: languages/states.php:4048
msgid "Irkutsk"
msgstr ""
-#: languages/states.php:4024
+#: languages/states.php:4049
msgid "Ivanovo Oblast"
msgstr ""
-#: languages/states.php:4025
+#: languages/states.php:4050
msgid "Jewish Autonomous Oblast"
msgstr ""
-#: languages/states.php:4026
+#: languages/states.php:4051
msgid "Kabardino-Balkar Republic"
msgstr ""
-#: languages/states.php:4027
+#: languages/states.php:4052
msgid "Kaliningrad"
msgstr ""
-#: languages/states.php:4028
+#: languages/states.php:4053
msgid "Kaluga Oblast"
msgstr ""
-#: languages/states.php:4029
+#: languages/states.php:4054
msgid "Kamchatka Krai"
msgstr ""
-#: languages/states.php:4030
+#: languages/states.php:4055
msgid "Karachay-Cherkess Republic"
msgstr ""
-#: languages/states.php:4031
+#: languages/states.php:4056
msgid "Kemerovo Oblast"
msgstr ""
-#: languages/states.php:4032
+#: languages/states.php:4057
msgid "Khabarovsk Krai"
msgstr ""
-#: languages/states.php:4033
+#: languages/states.php:4058
msgid "Khanty-Mansi Autonomous Okrug"
msgstr ""
-#: languages/states.php:4034
+#: languages/states.php:4059
msgid "Kirov Oblast"
msgstr ""
-#: languages/states.php:4035
+#: languages/states.php:4060
msgid "Komi Republic"
msgstr ""
-#: languages/states.php:4036
+#: languages/states.php:4061
msgid "Kostroma Oblast"
msgstr ""
-#: languages/states.php:4037
+#: languages/states.php:4062
msgid "Krasnodar Krai"
msgstr ""
-#: languages/states.php:4038
+#: languages/states.php:4063
msgid "Krasnoyarsk Krai"
msgstr ""
-#: languages/states.php:4039
+#: languages/states.php:4064
msgid "Kurgan Oblast"
msgstr ""
-#: languages/states.php:4040
+#: languages/states.php:4065
msgid "Kursk Oblast"
msgstr ""
-#: languages/states.php:4041
+#: languages/states.php:4066
msgid "Leningrad Oblast"
msgstr ""
-#: languages/states.php:4042
+#: languages/states.php:4067
msgid "Lipetsk Oblast"
msgstr ""
-#: languages/states.php:4043
+#: languages/states.php:4068
msgid "Magadan Oblast"
msgstr ""
-#: languages/states.php:4044
+#: languages/states.php:4069
msgid "Mari El Republic"
msgstr ""
-#: languages/states.php:4045
+#: languages/states.php:4070
msgid "Moscow"
msgstr ""
-#: languages/states.php:4046
+#: languages/states.php:4071
msgid "Moscow Oblast"
msgstr ""
-#: languages/states.php:4047
+#: languages/states.php:4072
msgid "Murmansk Oblast"
msgstr ""
-#: languages/states.php:4048
+#: languages/states.php:4073
msgid "Nenets Autonomous Okrug"
msgstr ""
-#: languages/states.php:4049
+#: languages/states.php:4074
msgid "Nizhny Novgorod Oblast"
msgstr ""
-#: languages/states.php:4050
+#: languages/states.php:4075
msgid "Novgorod Oblast"
msgstr ""
-#: languages/states.php:4051
+#: languages/states.php:4076
msgid "Novosibirsk"
msgstr ""
-#: languages/states.php:4052
+#: languages/states.php:4077
msgid "Omsk Oblast"
msgstr ""
-#: languages/states.php:4053
+#: languages/states.php:4078
msgid "Orenburg Oblast"
msgstr ""
-#: languages/states.php:4054
+#: languages/states.php:4079
msgid "Oryol Oblast"
msgstr ""
-#: languages/states.php:4055
+#: languages/states.php:4080
msgid "Penza Oblast"
msgstr ""
-#: languages/states.php:4056
+#: languages/states.php:4081
msgid "Perm Krai"
msgstr ""
-#: languages/states.php:4057
+#: languages/states.php:4082
msgid "Primorsky Krai"
msgstr ""
-#: languages/states.php:4058
+#: languages/states.php:4083
msgid "Pskov Oblast"
msgstr ""
-#: languages/states.php:4059
+#: languages/states.php:4084
msgid "Republic of Adygea"
msgstr ""
-#: languages/states.php:4060
+#: languages/states.php:4085
msgid "Republic of Bashkortostan"
msgstr ""
-#: languages/states.php:4061
+#: languages/states.php:4086
msgid "Republic of Buryatia"
msgstr ""
-#: languages/states.php:4062
+#: languages/states.php:4087
msgid "Republic of Dagestan"
msgstr ""
-#: languages/states.php:4063
+#: languages/states.php:4088
msgid "Republic of Ingushetia"
msgstr ""
-#: languages/states.php:4064
+#: languages/states.php:4089
msgid "Republic of Kalmykia"
msgstr ""
-#: languages/states.php:4065
+#: languages/states.php:4090
msgid "Republic of Karelia"
msgstr ""
-#: languages/states.php:4066
+#: languages/states.php:4091
msgid "Republic of Khakassia"
msgstr ""
-#: languages/states.php:4067
+#: languages/states.php:4092
msgid "Republic of Mordovia"
msgstr ""
-#: languages/states.php:4068
+#: languages/states.php:4093
msgid "Republic of North Ossetia-Alania"
msgstr ""
-#: languages/states.php:4069
+#: languages/states.php:4094
msgid "Republic of Tatarstan"
msgstr ""
-#: languages/states.php:4070
+#: languages/states.php:4095
msgid "Rostov Oblast"
msgstr ""
-#: languages/states.php:4071
+#: languages/states.php:4096
msgid "Ryazan Oblast"
msgstr ""
-#: languages/states.php:4072
+#: languages/states.php:4097
msgid "Saint Petersburg"
msgstr ""
-#: languages/states.php:4073
+#: languages/states.php:4098
msgid "Sakha Republic"
msgstr ""
-#: languages/states.php:4074
+#: languages/states.php:4099
msgid "Sakhalin"
msgstr ""
-#: languages/states.php:4075
+#: languages/states.php:4100
msgid "Samara Oblast"
msgstr ""
-#: languages/states.php:4076
+#: languages/states.php:4101
msgid "Saratov Oblast"
msgstr ""
-#: languages/states.php:4077
+#: languages/states.php:4102
msgid "Sevastopol"
msgstr ""
-#: languages/states.php:4078
+#: languages/states.php:4103
msgid "Smolensk Oblast"
msgstr ""
-#: languages/states.php:4079
+#: languages/states.php:4104
msgid "Stavropol Krai"
msgstr ""
-#: languages/states.php:4080
+#: languages/states.php:4105
msgid "Sverdlovsk"
msgstr ""
-#: languages/states.php:4081
+#: languages/states.php:4106
msgid "Tambov Oblast"
msgstr ""
-#: languages/states.php:4082
+#: languages/states.php:4107
msgid "Tomsk Oblast"
msgstr ""
-#: languages/states.php:4083
+#: languages/states.php:4108
msgid "Tula Oblast"
msgstr ""
-#: languages/states.php:4084
+#: languages/states.php:4109
msgid "Tuva Republic"
msgstr ""
-#: languages/states.php:4085
+#: languages/states.php:4110
msgid "Tver Oblast"
msgstr ""
-#: languages/states.php:4086
+#: languages/states.php:4111
msgid "Tyumen Oblast"
msgstr ""
-#: languages/states.php:4087
+#: languages/states.php:4112
msgid "Udmurt Republic"
msgstr ""
-#: languages/states.php:4088
+#: languages/states.php:4113
msgid "Ulyanovsk Oblast"
msgstr ""
-#: languages/states.php:4089
+#: languages/states.php:4114
msgid "Vladimir Oblast"
msgstr ""
-#: languages/states.php:4090
+#: languages/states.php:4115
msgid "Volgograd Oblast"
msgstr ""
-#: languages/states.php:4091
+#: languages/states.php:4116
msgid "Vologda Oblast"
msgstr ""
-#: languages/states.php:4092
+#: languages/states.php:4117
msgid "Voronezh Oblast"
msgstr ""
-#: languages/states.php:4093
+#: languages/states.php:4118
msgid "Yamalo-Nenets Autonomous Okrug"
msgstr ""
-#: languages/states.php:4094
+#: languages/states.php:4119
msgid "Yaroslavl Oblast"
msgstr ""
-#: languages/states.php:4095
+#: languages/states.php:4120
msgid "Zabaykalsky Krai"
msgstr ""
-#: languages/states.php:4099
+#: languages/states.php:4124
msgid "Kigali district"
msgstr ""
-#: languages/states.php:4105
+#: languages/states.php:4130
msgid "Najran Region"
msgstr ""
-#: languages/states.php:4106
+#: languages/states.php:4131
msgid "Al Bahah Region"
msgstr ""
-#: languages/states.php:4107
+#: languages/states.php:4132
msgid "Al Jawf Region"
msgstr ""
-#: languages/states.php:4108
+#: languages/states.php:4133
msgid "'Asir Region"
msgstr ""
-#: languages/states.php:4109
+#: languages/states.php:4134
msgid "Al Madinah Region"
msgstr ""
-#: languages/states.php:4110
+#: languages/states.php:4135
msgid "Al-Qassim Region"
msgstr ""
-#: languages/states.php:4112
+#: languages/states.php:4137
msgid "Ha'il Region"
msgstr ""
-#: languages/states.php:4113
+#: languages/states.php:4138
msgid "Jizan Region"
msgstr ""
-#: languages/states.php:4114
+#: languages/states.php:4139
msgid "Makkah Region"
msgstr ""
-#: languages/states.php:4115
+#: languages/states.php:4140
msgid "Northern Borders Region"
msgstr ""
-#: languages/states.php:4116
+#: languages/states.php:4141
msgid "Riyadh Region"
msgstr ""
-#: languages/states.php:4117
+#: languages/states.php:4142
msgid "Tabuk Region"
msgstr ""
-#: languages/states.php:4121
+#: languages/states.php:4146
msgid "Choiseul Province"
msgstr ""
-#: languages/states.php:4122
+#: languages/states.php:4147
msgid "Guadalcanal Province"
msgstr ""
-#: languages/states.php:4123
+#: languages/states.php:4148
msgid "Honiara"
msgstr ""
-#: languages/states.php:4124
+#: languages/states.php:4149
msgid "Isabel Province"
msgstr ""
-#: languages/states.php:4125
+#: languages/states.php:4150
msgid "Makira-Ulawa Province"
msgstr ""
-#: languages/states.php:4126
+#: languages/states.php:4151
msgid "Malaita Province"
msgstr ""
-#: languages/states.php:4127
+#: languages/states.php:4152
msgid "Rennell and Bellona Province"
msgstr ""
-#: languages/states.php:4128
+#: languages/states.php:4153
msgid "Temotu Province"
msgstr ""
-#: languages/states.php:4132
+#: languages/states.php:4157
msgid "Bel Ombre"
msgstr ""
-#: languages/states.php:4133
+#: languages/states.php:4158
msgid "Cascade"
msgstr ""
-#: languages/states.php:4134
+#: languages/states.php:4159
msgid "Glacis"
msgstr ""
-#: languages/states.php:4135
+#: languages/states.php:4160
msgid "Grand'Anse Mahé"
msgstr ""
-#: languages/states.php:4136
+#: languages/states.php:4161
msgid "Grand'Anse Praslin"
msgstr ""
-#: languages/states.php:4137
+#: languages/states.php:4162
msgid "La Digue"
msgstr ""
-#: languages/states.php:4138
+#: languages/states.php:4163
msgid "La Rivière Anglaise"
msgstr ""
-#: languages/states.php:4139
+#: languages/states.php:4164
msgid "Mont Buxton"
msgstr ""
-#: languages/states.php:4140
+#: languages/states.php:4165
msgid "Mont Fleuri"
msgstr ""
-#: languages/states.php:4141
+#: languages/states.php:4166
msgid "Plaisance"
msgstr ""
-#: languages/states.php:4142
+#: languages/states.php:4167
msgid "Pointe La Rue"
msgstr ""
-#: languages/states.php:4143
+#: languages/states.php:4168
msgid "Port Glaud"
msgstr ""
-#: languages/states.php:4144
+#: languages/states.php:4169
msgid "Saint Louis"
msgstr ""
-#: languages/states.php:4145
+#: languages/states.php:4170
msgid "Takamaka"
msgstr ""
-#: languages/states.php:4146
+#: languages/states.php:4171
msgid "Les Mamelles"
msgstr ""
-#: languages/states.php:4147
+#: languages/states.php:4172
msgid "Roche Caiman"
msgstr ""
-#: languages/states.php:4148
+#: languages/states.php:4173
msgid "Anse Boileau"
msgstr ""
-#: languages/states.php:4149
+#: languages/states.php:4174
msgid "Anse Royale"
msgstr ""
-#: languages/states.php:4150
+#: languages/states.php:4175
msgid "Anse-aux-Pins"
msgstr ""
-#: languages/states.php:4151
+#: languages/states.php:4176
msgid "Au Cap"
msgstr ""
-#: languages/states.php:4152
+#: languages/states.php:4177
msgid "Baie Lazare"
msgstr ""
-#: languages/states.php:4153
+#: languages/states.php:4178
msgid "Baie Sainte Anne"
msgstr ""
-#: languages/states.php:4154
+#: languages/states.php:4179
msgid "Beau Vallon"
msgstr ""
-#: languages/states.php:4155
+#: languages/states.php:4180
msgid "Bel Air"
msgstr ""
-#: languages/states.php:4158
+#: languages/states.php:4183
msgid "Al Jazirah"
msgstr ""
-#: languages/states.php:4159
+#: languages/states.php:4184
msgid "Al Qadarif"
msgstr ""
-#: languages/states.php:4160
+#: languages/states.php:4185
msgid "Blue Nile"
msgstr ""
-#: languages/states.php:4161
+#: languages/states.php:4186
msgid "Central Darfur"
msgstr ""
-#: languages/states.php:4162
+#: languages/states.php:4187
msgid "East Darfur"
msgstr ""
-#: languages/states.php:4163
+#: languages/states.php:4188
msgid "Kassala"
msgstr ""
-#: languages/states.php:4164
+#: languages/states.php:4189
msgid "Khartoum"
msgstr ""
-#: languages/states.php:4165
+#: languages/states.php:4190
msgid "North Darfur"
msgstr ""
-#: languages/states.php:4166
+#: languages/states.php:4191
msgid "North Kordofan"
msgstr ""
-#: languages/states.php:4167
+#: languages/states.php:4192
msgid "Northern"
msgstr ""
-#: languages/states.php:4168
+#: languages/states.php:4193
msgid "Red Sea"
msgstr ""
-#: languages/states.php:4169
+#: languages/states.php:4194
msgid "River Nile"
msgstr ""
-#: languages/states.php:4170
+#: languages/states.php:4195
msgid "Sennar"
msgstr ""
-#: languages/states.php:4171
+#: languages/states.php:4196
msgid "South Darfur"
msgstr ""
-#: languages/states.php:4172
+#: languages/states.php:4197
msgid "South Kordofan"
msgstr ""
-#: languages/states.php:4173
+#: languages/states.php:4198
msgid "West Darfur"
msgstr ""
-#: languages/states.php:4174
+#: languages/states.php:4199
msgid "West Kordofan"
msgstr ""
-#: languages/states.php:4175
+#: languages/states.php:4200
msgid "White Nile"
msgstr ""
-#: languages/states.php:4178
+#: languages/states.php:4203
msgid "Blekinge"
msgstr ""
-#: languages/states.php:4179
+#: languages/states.php:4204
msgid "Dalarna County"
msgstr ""
-#: languages/states.php:4180
+#: languages/states.php:4205
msgid "Gotland County"
msgstr ""
-#: languages/states.php:4181
+#: languages/states.php:4206
msgid "Gävleborg County"
msgstr ""
-#: languages/states.php:4182
+#: languages/states.php:4207
msgid "Halland County"
msgstr ""
-#: languages/states.php:4183
+#: languages/states.php:4208
msgid "Jönköping County"
msgstr ""
-#: languages/states.php:4184
+#: languages/states.php:4209
msgid "Kalmar County"
msgstr ""
-#: languages/states.php:4185
+#: languages/states.php:4210
msgid "Kronoberg County"
msgstr ""
-#: languages/states.php:4186
+#: languages/states.php:4211
msgid "Norrbotten County"
msgstr ""
-#: languages/states.php:4187
+#: languages/states.php:4212
msgid "Skåne County"
msgstr ""
-#: languages/states.php:4188
+#: languages/states.php:4213
msgid "Stockholm County"
msgstr ""
-#: languages/states.php:4189
+#: languages/states.php:4214
msgid "Södermanland County"
msgstr ""
-#: languages/states.php:4190
+#: languages/states.php:4215
msgid "Uppsala County"
msgstr ""
-#: languages/states.php:4191
+#: languages/states.php:4216
msgid "Värmland County"
msgstr ""
-#: languages/states.php:4192
+#: languages/states.php:4217
msgid "Västerbotten County"
msgstr ""
-#: languages/states.php:4193
+#: languages/states.php:4218
msgid "Västernorrland County"
msgstr ""
-#: languages/states.php:4194
+#: languages/states.php:4219
msgid "Västmanland County"
msgstr ""
-#: languages/states.php:4195
+#: languages/states.php:4220
msgid "Västra Götaland County"
msgstr ""
-#: languages/states.php:4196
+#: languages/states.php:4221
msgid "Örebro County"
msgstr ""
-#: languages/states.php:4197
+#: languages/states.php:4222
msgid "Östergötland County"
msgstr ""
-#: languages/states.php:4200
+#: languages/states.php:4225
msgid "Central Singapore Community Development Council"
msgstr ""
-#: languages/states.php:4201
+#: languages/states.php:4226
msgid "North East Community Development Council"
msgstr ""
-#: languages/states.php:4202
+#: languages/states.php:4227
msgid "North West Community Development Council"
msgstr ""
-#: languages/states.php:4203
+#: languages/states.php:4228
msgid "South East Community Development Council"
msgstr ""
-#: languages/states.php:4204
+#: languages/states.php:4229
msgid "South West Community Development Council"
msgstr ""
-#: languages/states.php:4208
+#: languages/states.php:4233
msgid "Radenci Municipality"
msgstr ""
-#: languages/states.php:4209
+#: languages/states.php:4234
msgid "Radlje ob Dravi Municipality"
msgstr ""
-#: languages/states.php:4210
+#: languages/states.php:4235
msgid "Radovljica Municipality"
msgstr ""
-#: languages/states.php:4211
+#: languages/states.php:4236
msgid "Ravne na Koroškem Municipality"
msgstr ""
-#: languages/states.php:4212
+#: languages/states.php:4237
msgid "Ribnica Municipality"
msgstr ""
-#: languages/states.php:4213
+#: languages/states.php:4238
msgid "Rogašovci Municipality"
msgstr ""
-#: languages/states.php:4214
+#: languages/states.php:4239
msgid "Rogaška Slatina Municipality"
msgstr ""
-#: languages/states.php:4215
+#: languages/states.php:4240
msgid "Rogatec Municipality"
msgstr ""
-#: languages/states.php:4216
+#: languages/states.php:4241
msgid "Ruše Municipality"
msgstr ""
-#: languages/states.php:4217
+#: languages/states.php:4242
msgid "Semič Municipality"
msgstr ""
-#: languages/states.php:4218
+#: languages/states.php:4243
msgid "Sevnica Municipality"
msgstr ""
-#: languages/states.php:4219
+#: languages/states.php:4244
msgid "Sežana Municipality"
msgstr ""
-#: languages/states.php:4220
+#: languages/states.php:4245
msgid "Slovenj Gradec City Municipality"
msgstr ""
-#: languages/states.php:4221
+#: languages/states.php:4246
msgid "Slovenska Bistrica Municipality"
msgstr ""
-#: languages/states.php:4222
+#: languages/states.php:4247
msgid "Slovenske Konjice Municipality"
msgstr ""
-#: languages/states.php:4223
+#: languages/states.php:4248
msgid "Starše Municipality"
msgstr ""
-#: languages/states.php:4224
+#: languages/states.php:4249
msgid "Sveti Jurij ob Ščavnici Municipality"
msgstr ""
-#: languages/states.php:4225
+#: languages/states.php:4250
msgid "Šenčur Municipality"
msgstr ""
-#: languages/states.php:4226
+#: languages/states.php:4251
msgid "Šentilj Municipality"
msgstr ""
-#: languages/states.php:4227
+#: languages/states.php:4252
msgid "Šentjernej Municipality"
msgstr ""
-#: languages/states.php:4228
+#: languages/states.php:4253
msgid "Šentjur Municipality"
msgstr ""
-#: languages/states.php:4229
+#: languages/states.php:4254
msgid "Škocjan Municipality"
msgstr ""
-#: languages/states.php:4230
+#: languages/states.php:4255
msgid "Škofja Loka Municipality"
msgstr ""
-#: languages/states.php:4231
+#: languages/states.php:4256
msgid "Municipality of Škofljica"
msgstr ""
-#: languages/states.php:4232
+#: languages/states.php:4257
msgid "Šmarje pri Jelšah Municipality"
msgstr ""
-#: languages/states.php:4233
+#: languages/states.php:4258
msgid "Šmartno ob Paki Municipality"
msgstr ""
-#: languages/states.php:4234
+#: languages/states.php:4259
msgid "Šoštanj Municipality"
msgstr ""
-#: languages/states.php:4235
+#: languages/states.php:4260
msgid "Štore Municipality"
msgstr ""
-#: languages/states.php:4236
+#: languages/states.php:4261
msgid "Tolmin Municipality"
msgstr ""
-#: languages/states.php:4237
+#: languages/states.php:4262
msgid "Trbovlje Municipality"
msgstr ""
-#: languages/states.php:4238
+#: languages/states.php:4263
msgid "Trebnje Municipality"
msgstr ""
-#: languages/states.php:4239
+#: languages/states.php:4264
msgid "Tržič Municipality"
msgstr ""
-#: languages/states.php:4240
+#: languages/states.php:4265
msgid "Turnišče Municipality"
msgstr ""
-#: languages/states.php:4241
+#: languages/states.php:4266
msgid "Velike Lašče Municipality"
msgstr ""
-#: languages/states.php:4242
+#: languages/states.php:4267
msgid "Videm Municipality"
msgstr ""
-#: languages/states.php:4243
+#: languages/states.php:4268
msgid "Vipava Municipality"
msgstr ""
-#: languages/states.php:4244
+#: languages/states.php:4269
msgid "Vitanje Municipality"
msgstr ""
-#: languages/states.php:4245
+#: languages/states.php:4270
msgid "Vodice Municipality"
msgstr ""
-#: languages/states.php:4246
+#: languages/states.php:4271
msgid "Vojnik Municipality"
msgstr ""
-#: languages/states.php:4247
+#: languages/states.php:4272
msgid "Vrhnika Municipality"
msgstr ""
-#: languages/states.php:4248
+#: languages/states.php:4273
msgid "Vuzenica Municipality"
msgstr ""
-#: languages/states.php:4249
+#: languages/states.php:4274
msgid "Zagorje ob Savi Municipality"
msgstr ""
-#: languages/states.php:4250
+#: languages/states.php:4275
msgid "Zavrč Municipality"
msgstr ""
-#: languages/states.php:4251
+#: languages/states.php:4276
msgid "Zreče Municipality"
msgstr ""
-#: languages/states.php:4252
+#: languages/states.php:4277
msgid "Železniki Municipality"
msgstr ""
-#: languages/states.php:4253
+#: languages/states.php:4278
msgid "Žiri Municipality"
msgstr ""
-#: languages/states.php:4254
+#: languages/states.php:4279
msgid "Benedikt Municipality"
msgstr ""
-#: languages/states.php:4255
+#: languages/states.php:4280
msgid "Bistrica ob Sotli Municipality"
msgstr ""
-#: languages/states.php:4256
+#: languages/states.php:4281
msgid "Bloke Municipality"
msgstr ""
-#: languages/states.php:4257
+#: languages/states.php:4282
msgid "Braslovče Municipality"
msgstr ""
-#: languages/states.php:4258
+#: languages/states.php:4283
msgid "Cankova Municipality"
msgstr ""
-#: languages/states.php:4259
+#: languages/states.php:4284
msgid "Cerkvenjak Municipality"
msgstr ""
-#: languages/states.php:4260
+#: languages/states.php:4285
msgid "Dobje Municipality"
msgstr ""
-#: languages/states.php:4261
+#: languages/states.php:4286
msgid "Dobrna Municipality"
msgstr ""
-#: languages/states.php:4262
+#: languages/states.php:4287
msgid "Dobrovnik Municipality"
msgstr ""
-#: languages/states.php:4263
+#: languages/states.php:4288
msgid "Dolenjske Toplice Municipality"
msgstr ""
-#: languages/states.php:4264
+#: languages/states.php:4289
msgid "Grad Municipality"
msgstr ""
-#: languages/states.php:4265
+#: languages/states.php:4290
msgid "Hajdina Municipality"
msgstr ""
-#: languages/states.php:4266
+#: languages/states.php:4291
msgid "Hoče–Slivnica Municipality"
msgstr ""
-#: languages/states.php:4267
+#: languages/states.php:4292
msgid "Hodoš Municipality"
msgstr ""
-#: languages/states.php:4268
+#: languages/states.php:4293
msgid "Horjul Municipality"
msgstr ""
-#: languages/states.php:4269
+#: languages/states.php:4294
msgid "Jezersko Municipality"
msgstr ""
-#: languages/states.php:4270
+#: languages/states.php:4295
msgid "Komenda Municipality"
msgstr ""
-#: languages/states.php:4271
+#: languages/states.php:4296
msgid "Kostel Municipality"
msgstr ""
-#: languages/states.php:4272
+#: languages/states.php:4297
msgid "Križevci Municipality"
msgstr ""
-#: languages/states.php:4273
+#: languages/states.php:4298
msgid "Lovrenc na Pohorju Municipality"
msgstr ""
-#: languages/states.php:4274
+#: languages/states.php:4299
msgid "Markovci Municipality"
msgstr ""
-#: languages/states.php:4275
+#: languages/states.php:4300
msgid "Miklavž na Dravskem Polju Municipality"
msgstr ""
-#: languages/states.php:4276
+#: languages/states.php:4301
msgid "Mirna Peč Municipality"
msgstr ""
-#: languages/states.php:4277
+#: languages/states.php:4302
msgid "Oplotnica"
msgstr ""
-#: languages/states.php:4278
+#: languages/states.php:4303
msgid "Podlehnik Municipality"
msgstr ""
-#: languages/states.php:4279
+#: languages/states.php:4304
msgid "Polzela Municipality"
msgstr ""
-#: languages/states.php:4280
+#: languages/states.php:4305
msgid "Prebold Municipality"
msgstr ""
-#: languages/states.php:4281
+#: languages/states.php:4306
msgid "Prevalje Municipality"
msgstr ""
-#: languages/states.php:4282
+#: languages/states.php:4307
msgid "Razkrižje Municipality"
msgstr ""
-#: languages/states.php:4283
+#: languages/states.php:4308
msgid "Ribnica na Pohorju Municipality"
msgstr ""
-#: languages/states.php:4284
+#: languages/states.php:4309
msgid "Selnica ob Dravi Municipality"
msgstr ""
-#: languages/states.php:4285
+#: languages/states.php:4310
msgid "Sodražica Municipality"
msgstr ""
-#: languages/states.php:4286
+#: languages/states.php:4311
msgid "Solčava Municipality"
msgstr ""
-#: languages/states.php:4287
+#: languages/states.php:4312
msgid "Sveta Ana Municipality"
msgstr ""
-#: languages/states.php:4288
+#: languages/states.php:4313
msgid "Sveti Andraž v Slovenskih Goricah Municipality"
msgstr ""
-#: languages/states.php:4289
+#: languages/states.php:4314
msgid "Šempeter–Vrtojba Municipality"
msgstr ""
-#: languages/states.php:4290
+#: languages/states.php:4315
msgid "Tabor Municipality"
msgstr ""
-#: languages/states.php:4291
+#: languages/states.php:4316
msgid "Trnovska Vas Municipality"
msgstr ""
-#: languages/states.php:4292
+#: languages/states.php:4317
msgid "Trzin Municipality"
msgstr ""
-#: languages/states.php:4293
+#: languages/states.php:4318
msgid "Velika Polana Municipality"
msgstr ""
-#: languages/states.php:4294
+#: languages/states.php:4319
msgid "Veržej Municipality"
msgstr ""
-#: languages/states.php:4295
+#: languages/states.php:4320
msgid "Vransko Municipality"
msgstr ""
-#: languages/states.php:4296
+#: languages/states.php:4321
msgid "Žalec Municipality"
msgstr ""
-#: languages/states.php:4297
+#: languages/states.php:4322
msgid "Žetale Municipality"
msgstr ""
-#: languages/states.php:4298
+#: languages/states.php:4323
msgid "Žirovnica Municipality"
msgstr ""
-#: languages/states.php:4299
+#: languages/states.php:4324
msgid "Žužemberk Municipality"
msgstr ""
-#: languages/states.php:4300
+#: languages/states.php:4325
msgid "Šmartno pri Litiji Municipality"
msgstr ""
-#: languages/states.php:4301
+#: languages/states.php:4326
msgid "Municipality of Apače"
msgstr ""
-#: languages/states.php:4302
+#: languages/states.php:4327
msgid "Municipality of Cirkulane"
msgstr ""
-#: languages/states.php:4303
+#: languages/states.php:4328
msgid "Kostanjevica na Krki Municipality"
msgstr ""
-#: languages/states.php:4304
+#: languages/states.php:4329
msgid "Makole Municipality"
msgstr ""
-#: languages/states.php:4305
+#: languages/states.php:4330
msgid "Mokronog–Trebelno Municipality"
msgstr ""
-#: languages/states.php:4306
+#: languages/states.php:4331
msgid "Poljčane Municipality"
msgstr ""
-#: languages/states.php:4307
+#: languages/states.php:4332
msgid "Renče–Vogrsko Municipality"
msgstr ""
-#: languages/states.php:4308
+#: languages/states.php:4333
msgid "Središče ob Dravi"
msgstr ""
-#: languages/states.php:4309
+#: languages/states.php:4334
msgid "Straža Municipality"
msgstr ""
-#: languages/states.php:4310
+#: languages/states.php:4335
msgid "Sveta Trojica v Slovenskih Goricah Municipality"
msgstr ""
-#: languages/states.php:4311
+#: languages/states.php:4336
msgid "Sveti Tomaž Municipality"
msgstr ""
-#: languages/states.php:4312
+#: languages/states.php:4337
msgid "Šmarješke Toplice Municipality"
msgstr ""
-#: languages/states.php:4313
+#: languages/states.php:4338
msgid "Gorje Municipality"
msgstr ""
-#: languages/states.php:4314
+#: languages/states.php:4339
msgid "Log–Dragomer Municipality"
msgstr ""
-#: languages/states.php:4315
+#: languages/states.php:4340
msgid "Rečica ob Savinji Municipality"
msgstr ""
-#: languages/states.php:4316
+#: languages/states.php:4341
msgid "Sveti Jurij v Slovenskih Goricah Municipality"
msgstr ""
-#: languages/states.php:4317
+#: languages/states.php:4342
msgid "Šentrupert Municipality"
msgstr ""
-#: languages/states.php:4318
+#: languages/states.php:4343
msgid "Mirna Municipality"
msgstr ""
-#: languages/states.php:4319
+#: languages/states.php:4344
msgid "Ankaran Municipality"
msgstr ""
-#: languages/states.php:4320
+#: languages/states.php:4345
msgid "Ajdovščina Municipality"
msgstr ""
-#: languages/states.php:4321
+#: languages/states.php:4346
msgid "Beltinci Municipality"
msgstr ""
-#: languages/states.php:4322
+#: languages/states.php:4347
msgid "Bled Municipality"
msgstr ""
-#: languages/states.php:4323
+#: languages/states.php:4348
msgid "Bohinj Municipality"
msgstr ""
-#: languages/states.php:4324
+#: languages/states.php:4349
msgid "Borovnica Municipality"
msgstr ""
-#: languages/states.php:4325
+#: languages/states.php:4350
msgid "Bovec Municipality"
msgstr ""
-#: languages/states.php:4326
+#: languages/states.php:4351
msgid "Brda Municipality"
msgstr ""
-#: languages/states.php:4327
+#: languages/states.php:4352
msgid "Brezovica Municipality"
msgstr ""
-#: languages/states.php:4328
+#: languages/states.php:4353
msgid "Brežice Municipality"
msgstr ""
-#: languages/states.php:4329
+#: languages/states.php:4354
msgid "Cerklje na Gorenjskem Municipality"
msgstr ""
-#: languages/states.php:4330
+#: languages/states.php:4355
msgid "Cerknica Municipality"
msgstr ""
-#: languages/states.php:4331
+#: languages/states.php:4356
msgid "Cerkno Municipality"
msgstr ""
-#: languages/states.php:4332
+#: languages/states.php:4357
msgid "City Municipality of Celje"
msgstr ""
-#: languages/states.php:4333
+#: languages/states.php:4358
msgid "City Municipality of Novo Mesto"
msgstr ""
-#: languages/states.php:4334
+#: languages/states.php:4359
msgid "Destrnik Municipality"
msgstr ""
-#: languages/states.php:4335
+#: languages/states.php:4360
msgid "Divača Municipality"
msgstr ""
-#: languages/states.php:4336
+#: languages/states.php:4361
msgid "Dobrepolje Municipality"
msgstr ""
-#: languages/states.php:4337
+#: languages/states.php:4362
msgid "Dobrova–Polhov Gradec Municipality"
msgstr ""
-#: languages/states.php:4338
+#: languages/states.php:4363
msgid "Dol pri Ljubljani Municipality"
msgstr ""
-#: languages/states.php:4339
+#: languages/states.php:4364
msgid "Domžale Municipality"
msgstr ""
-#: languages/states.php:4340
+#: languages/states.php:4365
msgid "Dornava Municipality"
msgstr ""
-#: languages/states.php:4341
+#: languages/states.php:4366
msgid "Dravograd Municipality"
msgstr ""
-#: languages/states.php:4342
+#: languages/states.php:4367
msgid "Duplek Municipality"
msgstr ""
-#: languages/states.php:4343
+#: languages/states.php:4368
msgid "Gorenja Vas–Poljane Municipality"
msgstr ""
-#: languages/states.php:4344
+#: languages/states.php:4369
msgid "Gorišnica Municipality"
msgstr ""
-#: languages/states.php:4345
+#: languages/states.php:4370
msgid "Gornja Radgona Municipality"
msgstr ""
-#: languages/states.php:4346
+#: languages/states.php:4371
msgid "Gornji Grad Municipality"
msgstr ""
-#: languages/states.php:4347
+#: languages/states.php:4372
msgid "Gornji Petrovci Municipality"
msgstr ""
-#: languages/states.php:4348
+#: languages/states.php:4373
msgid "Grosuplje Municipality"
msgstr ""
-#: languages/states.php:4349
+#: languages/states.php:4374
msgid "Hrastnik Municipality"
msgstr ""
-#: languages/states.php:4350
+#: languages/states.php:4375
msgid "Hrpelje–Kozina Municipality"
msgstr ""
-#: languages/states.php:4351
+#: languages/states.php:4376
msgid "Idrija Municipality"
msgstr ""
-#: languages/states.php:4352
+#: languages/states.php:4377
msgid "Ig Municipality"
msgstr ""
-#: languages/states.php:4353
+#: languages/states.php:4378
msgid "Ivančna Gorica Municipality"
msgstr ""
-#: languages/states.php:4354
+#: languages/states.php:4379
msgid "Izola Municipality"
msgstr ""
-#: languages/states.php:4355
+#: languages/states.php:4380
msgid "Jesenice Municipality"
msgstr ""
-#: languages/states.php:4356
+#: languages/states.php:4381
msgid "Juršinci Municipality"
msgstr ""
-#: languages/states.php:4357
+#: languages/states.php:4382
msgid "Kamnik Municipality"
msgstr ""
-#: languages/states.php:4358
+#: languages/states.php:4383
msgid "Kanal ob Soči Municipality"
msgstr ""
-#: languages/states.php:4359
+#: languages/states.php:4384
msgid "Kidričevo Municipality"
msgstr ""
-#: languages/states.php:4360
+#: languages/states.php:4385
msgid "Kobarid Municipality"
msgstr ""
-#: languages/states.php:4361
+#: languages/states.php:4386
msgid "Kobilje Municipality"
msgstr ""
-#: languages/states.php:4362
+#: languages/states.php:4387
msgid "Komen Municipality"
msgstr ""
-#: languages/states.php:4363
+#: languages/states.php:4388
msgid "Koper City Municipality"
msgstr ""
-#: languages/states.php:4364
+#: languages/states.php:4389
msgid "Kozje Municipality"
msgstr ""
-#: languages/states.php:4365
+#: languages/states.php:4390
msgid "Kočevje Municipality"
msgstr ""
-#: languages/states.php:4366
+#: languages/states.php:4391
msgid "Kranj City Municipality"
msgstr ""
-#: languages/states.php:4367
+#: languages/states.php:4392
msgid "Kranjska Gora Municipality"
msgstr ""
-#: languages/states.php:4368
+#: languages/states.php:4393
msgid "Kungota"
msgstr ""
-#: languages/states.php:4369
+#: languages/states.php:4394
msgid "Kuzma Municipality"
msgstr ""
-#: languages/states.php:4370
+#: languages/states.php:4395
msgid "Laško Municipality"
msgstr ""
-#: languages/states.php:4371
+#: languages/states.php:4396
msgid "Lenart Municipality"
msgstr ""
-#: languages/states.php:4372
+#: languages/states.php:4397
msgid "Lendava Municipality"
msgstr ""
-#: languages/states.php:4373
+#: languages/states.php:4398
msgid "Litija Municipality"
msgstr ""
-#: languages/states.php:4374
+#: languages/states.php:4399
msgid "Ljubljana City Municipality"
msgstr ""
-#: languages/states.php:4375
+#: languages/states.php:4400
msgid "Ljubno Municipality"
msgstr ""
-#: languages/states.php:4376
+#: languages/states.php:4401
msgid "Ljutomer Municipality"
msgstr ""
-#: languages/states.php:4377
+#: languages/states.php:4402
msgid "Logatec Municipality"
msgstr ""
-#: languages/states.php:4378
+#: languages/states.php:4403
msgid "Loška Dolina Municipality"
msgstr ""
-#: languages/states.php:4379
+#: languages/states.php:4404
msgid "Loški Potok Municipality"
msgstr ""
-#: languages/states.php:4380
+#: languages/states.php:4405
msgid "Lukovica Municipality"
msgstr ""
-#: languages/states.php:4381
+#: languages/states.php:4406
msgid "Luče Municipality"
msgstr ""
-#: languages/states.php:4382
+#: languages/states.php:4407
msgid "Majšperk Municipality"
msgstr ""
-#: languages/states.php:4383
+#: languages/states.php:4408
msgid "Maribor City Municipality"
msgstr ""
-#: languages/states.php:4384
+#: languages/states.php:4409
msgid "Medvode Municipality"
msgstr ""
-#: languages/states.php:4385
+#: languages/states.php:4410
msgid "Mengeš Municipality"
msgstr ""
-#: languages/states.php:4386
+#: languages/states.php:4411
msgid "Metlika Municipality"
msgstr ""
-#: languages/states.php:4387
+#: languages/states.php:4412
msgid "Mežica Municipality"
msgstr ""
-#: languages/states.php:4388
+#: languages/states.php:4413
msgid "Miren–Kostanjevica Municipality"
msgstr ""
-#: languages/states.php:4389
+#: languages/states.php:4414
msgid "Mislinja Municipality"
msgstr ""
-#: languages/states.php:4390
+#: languages/states.php:4415
msgid "Moravske Toplice Municipality"
msgstr ""
-#: languages/states.php:4391
+#: languages/states.php:4416
msgid "Moravče Municipality"
msgstr ""
-#: languages/states.php:4392
+#: languages/states.php:4417
msgid "Mozirje Municipality"
msgstr ""
-#: languages/states.php:4393
+#: languages/states.php:4418
msgid "Municipality of Ilirska Bistrica"
msgstr ""
-#: languages/states.php:4394
+#: languages/states.php:4419
msgid "Municipality of Krško"
msgstr ""
-#: languages/states.php:4395
+#: languages/states.php:4420
msgid "Murska Sobota City Municipality"
msgstr ""
-#: languages/states.php:4396
+#: languages/states.php:4421
msgid "Muta Municipality"
msgstr ""
-#: languages/states.php:4397
+#: languages/states.php:4422
msgid "Naklo Municipality"
msgstr ""
-#: languages/states.php:4398
+#: languages/states.php:4423
msgid "Nazarje Municipality"
msgstr ""
-#: languages/states.php:4399
+#: languages/states.php:4424
msgid "Nova Gorica City Municipality"
msgstr ""
-#: languages/states.php:4400
+#: languages/states.php:4425
msgid "Odranci Municipality"
msgstr ""
-#: languages/states.php:4401
+#: languages/states.php:4426
msgid "Ormož Municipality"
msgstr ""
-#: languages/states.php:4402
+#: languages/states.php:4427
msgid "Osilnica Municipality"
msgstr ""
-#: languages/states.php:4403
+#: languages/states.php:4428
msgid "Pesnica Municipality"
msgstr ""
-#: languages/states.php:4404
+#: languages/states.php:4429
msgid "Piran Municipality"
msgstr ""
-#: languages/states.php:4405
+#: languages/states.php:4430
msgid "Pivka Municipality"
msgstr ""
-#: languages/states.php:4406
+#: languages/states.php:4431
msgid "Podvelka Municipality"
msgstr ""
-#: languages/states.php:4407
+#: languages/states.php:4432
msgid "Podčetrtek Municipality"
msgstr ""
-#: languages/states.php:4408
+#: languages/states.php:4433
msgid "Postojna Municipality"
msgstr ""
-#: languages/states.php:4409
+#: languages/states.php:4434
msgid "Preddvor Municipality"
msgstr ""
-#: languages/states.php:4410
+#: languages/states.php:4435
msgid "Ptuj City Municipality"
msgstr ""
-#: languages/states.php:4411
+#: languages/states.php:4436
msgid "Puconci Municipality"
msgstr ""
-#: languages/states.php:4412
+#: languages/states.php:4437
msgid "Radeče Municipality"
msgstr ""
-#: languages/states.php:4413
+#: languages/states.php:4438
msgid "Rače–Fram Municipality"
msgstr ""
-#: languages/states.php:4414
+#: languages/states.php:4439
msgid "Tišina Municipality"
msgstr ""
-#: languages/states.php:4415
+#: languages/states.php:4440
msgid "Črenšovci Municipality"
msgstr ""
-#: languages/states.php:4416
+#: languages/states.php:4441
msgid "Črna na Koroškem Municipality"
msgstr ""
-#: languages/states.php:4417
+#: languages/states.php:4442
msgid "Črnomelj Municipality"
msgstr ""
-#: languages/states.php:4418
+#: languages/states.php:4443
msgid "Šalovci Municipality"
msgstr ""
-#: languages/states.php:4422
+#: languages/states.php:4447
msgid "Banská Bystrica Region"
msgstr ""
-#: languages/states.php:4423
+#: languages/states.php:4448
msgid "Bratislava Region"
msgstr ""
-#: languages/states.php:4424
+#: languages/states.php:4449
msgid "Košice Region"
msgstr ""
-#: languages/states.php:4425
+#: languages/states.php:4450
msgid "Nitra Region"
msgstr ""
-#: languages/states.php:4426
+#: languages/states.php:4451
msgid "Prešov Region"
msgstr ""
-#: languages/states.php:4427
+#: languages/states.php:4452
msgid "Trenčín Region"
msgstr ""
-#: languages/states.php:4428
+#: languages/states.php:4453
msgid "Trnava Region"
msgstr ""
-#: languages/states.php:4429
+#: languages/states.php:4454
msgid "Žilina Region"
msgstr ""
-#: languages/states.php:4435
+#: languages/states.php:4460
msgid "Western Area"
msgstr ""
-#: languages/states.php:4438
+#: languages/states.php:4463
msgid "Acquaviva"
msgstr ""
-#: languages/states.php:4439
+#: languages/states.php:4464
msgid "Borgo Maggiore"
msgstr ""
-#: languages/states.php:4440
+#: languages/states.php:4465
msgid "Chiesanuova"
msgstr ""
-#: languages/states.php:4441
+#: languages/states.php:4466
msgid "Domagnano"
msgstr ""
-#: languages/states.php:4442
+#: languages/states.php:4467
msgid "Faetano"
msgstr ""
-#: languages/states.php:4443
+#: languages/states.php:4468
msgid "Fiorentino"
msgstr ""
-#: languages/states.php:4444
+#: languages/states.php:4469
msgid "Montegiardino"
msgstr ""
-#: languages/states.php:4446
+#: languages/states.php:4471
msgid "Serravalle"
msgstr ""
-#: languages/states.php:4449
+#: languages/states.php:4474
msgid "Dakar"
msgstr ""
-#: languages/states.php:4450
+#: languages/states.php:4475
msgid "Diourbel Region"
msgstr ""
-#: languages/states.php:4451
+#: languages/states.php:4476
msgid "Fatick"
msgstr ""
-#: languages/states.php:4452
+#: languages/states.php:4477
msgid "Kaffrine"
msgstr ""
-#: languages/states.php:4453
+#: languages/states.php:4478
msgid "Kaolack"
msgstr ""
-#: languages/states.php:4454
+#: languages/states.php:4479
msgid "Kolda"
msgstr ""
-#: languages/states.php:4455
+#: languages/states.php:4480
msgid "Kédougou"
msgstr ""
-#: languages/states.php:4456
+#: languages/states.php:4481
msgid "Louga"
msgstr ""
-#: languages/states.php:4457
+#: languages/states.php:4482
msgid "Matam"
msgstr ""
-#: languages/states.php:4458
+#: languages/states.php:4483
msgid "Saint-Louis"
msgstr ""
-#: languages/states.php:4459
+#: languages/states.php:4484
msgid "Sédhiou"
msgstr ""
-#: languages/states.php:4460
+#: languages/states.php:4485
msgid "Tambacounda Region"
msgstr ""
-#: languages/states.php:4461
+#: languages/states.php:4486
msgid "Thiès Region"
msgstr ""
-#: languages/states.php:4462
+#: languages/states.php:4487
msgid "Ziguinchor"
msgstr ""
-#: languages/states.php:4465
+#: languages/states.php:4490
msgid "Awdal Region"
msgstr ""
-#: languages/states.php:4466
+#: languages/states.php:4491
msgid "Bakool"
msgstr ""
-#: languages/states.php:4467
+#: languages/states.php:4492
msgid "Banaadir"
msgstr ""
-#: languages/states.php:4469
+#: languages/states.php:4494
msgid "Bay"
msgstr ""
-#: languages/states.php:4470
+#: languages/states.php:4495
msgid "Galguduud"
msgstr ""
-#: languages/states.php:4471
+#: languages/states.php:4496
msgid "Gedo"
msgstr ""
-#: languages/states.php:4472
+#: languages/states.php:4497
msgid "Hiran"
msgstr ""
-#: languages/states.php:4473
+#: languages/states.php:4498
msgid "Lower Juba"
msgstr ""
-#: languages/states.php:4474
+#: languages/states.php:4499
msgid "Lower Shebelle"
msgstr ""
-#: languages/states.php:4475
+#: languages/states.php:4500
msgid "Middle Juba"
msgstr ""
-#: languages/states.php:4476
+#: languages/states.php:4501
msgid "Middle Shebelle"
msgstr ""
-#: languages/states.php:4477
+#: languages/states.php:4502
msgid "Mudug"
msgstr ""
-#: languages/states.php:4478
+#: languages/states.php:4503
msgid "Nugal"
msgstr ""
-#: languages/states.php:4479
+#: languages/states.php:4504
msgid "Sanaag Region"
msgstr ""
-#: languages/states.php:4480
+#: languages/states.php:4505
msgid "Togdheer Region"
msgstr ""
-#: languages/states.php:4483
+#: languages/states.php:4508
msgid "Brokopondo District"
msgstr ""
-#: languages/states.php:4484
+#: languages/states.php:4509
msgid "Commewijne District"
msgstr ""
-#: languages/states.php:4485
+#: languages/states.php:4510
msgid "Coronie District"
msgstr ""
-#: languages/states.php:4486
+#: languages/states.php:4511
msgid "Marowijne District"
msgstr ""
-#: languages/states.php:4487
+#: languages/states.php:4512
msgid "Nickerie District"
msgstr ""
-#: languages/states.php:4488
+#: languages/states.php:4513
msgid "Para District"
msgstr ""
-#: languages/states.php:4489
+#: languages/states.php:4514
msgid "Paramaribo District"
msgstr ""
-#: languages/states.php:4490
+#: languages/states.php:4515
msgid "Saramacca District"
msgstr ""
-#: languages/states.php:4491
+#: languages/states.php:4516
msgid "Sipaliwini District"
msgstr ""
-#: languages/states.php:4492
+#: languages/states.php:4517
msgid "Wanica District"
msgstr ""
-#: languages/states.php:4495
+#: languages/states.php:4520
msgid "Central Equatoria"
msgstr ""
-#: languages/states.php:4496
+#: languages/states.php:4521
msgid "Eastern Equatoria"
msgstr ""
-#: languages/states.php:4497
+#: languages/states.php:4522
msgid "Jonglei State"
msgstr ""
-#: languages/states.php:4498
+#: languages/states.php:4523
msgid "Lakes"
msgstr ""
-#: languages/states.php:4499
+#: languages/states.php:4524
msgid "Northern Bahr el Ghazal"
msgstr ""
-#: languages/states.php:4500
+#: languages/states.php:4525
msgid "Unity"
msgstr ""
-#: languages/states.php:4501
+#: languages/states.php:4526
msgid "Upper Nile"
msgstr ""
-#: languages/states.php:4502
+#: languages/states.php:4527
msgid "Warrap"
msgstr ""
-#: languages/states.php:4503
+#: languages/states.php:4528
msgid "Western Bahr el Ghazal"
msgstr ""
-#: languages/states.php:4504
+#: languages/states.php:4529
msgid "Western Equatoria"
msgstr ""
-#: languages/states.php:4507
+#: languages/states.php:4532
msgid "Príncipe Province"
msgstr ""
-#: languages/states.php:4508
+#: languages/states.php:4533
msgid "São Tomé Province"
msgstr ""
-#: languages/states.php:4511
+#: languages/states.php:4536
msgid "Ahuachapán Department"
msgstr ""
-#: languages/states.php:4512
+#: languages/states.php:4537
msgid "Cabañas Department"
msgstr ""
-#: languages/states.php:4513
+#: languages/states.php:4538
msgid "Chalatenango Department"
msgstr ""
-#: languages/states.php:4514
+#: languages/states.php:4539
msgid "Cuscatlán Department"
msgstr ""
-#: languages/states.php:4515
+#: languages/states.php:4540
msgid "La Libertad Department"
msgstr ""
-#: languages/states.php:4517
+#: languages/states.php:4542
msgid "La Unión Department"
msgstr ""
-#: languages/states.php:4518
+#: languages/states.php:4543
msgid "Morazán Department"
msgstr ""
-#: languages/states.php:4519
+#: languages/states.php:4544
msgid "San Miguel Department"
msgstr ""
-#: languages/states.php:4520
+#: languages/states.php:4545
msgid "San Salvador Department"
msgstr ""
-#: languages/states.php:4521
+#: languages/states.php:4546
msgid "San Vicente Department"
msgstr ""
-#: languages/states.php:4522
+#: languages/states.php:4547
msgid "Santa Ana Department"
msgstr ""
-#: languages/states.php:4523
+#: languages/states.php:4548
msgid "Sonsonate Department"
msgstr ""
-#: languages/states.php:4524
+#: languages/states.php:4549
msgid "Usulután Department"
msgstr ""
-#: languages/states.php:4528
+#: languages/states.php:4553
msgid "Al-Hasakah Governorate"
msgstr ""
-#: languages/states.php:4529
+#: languages/states.php:4554
msgid "Al-Raqqah Governorate"
msgstr ""
-#: languages/states.php:4530
+#: languages/states.php:4555
msgid "Aleppo Governorate"
msgstr ""
-#: languages/states.php:4531
+#: languages/states.php:4556
msgid "As-Suwayda Governorate"
msgstr ""
-#: languages/states.php:4532
+#: languages/states.php:4557
msgid "Damascus Governorate"
msgstr ""
-#: languages/states.php:4533
+#: languages/states.php:4558
msgid "Daraa Governorate"
msgstr ""
-#: languages/states.php:4534
+#: languages/states.php:4559
msgid "Deir ez-Zor Governorate"
msgstr ""
-#: languages/states.php:4535
+#: languages/states.php:4560
msgid "Hama Governorate"
msgstr ""
-#: languages/states.php:4536
+#: languages/states.php:4561
msgid "Homs Governorate"
msgstr ""
-#: languages/states.php:4537
+#: languages/states.php:4562
msgid "Idlib Governorate"
msgstr ""
-#: languages/states.php:4538
+#: languages/states.php:4563
msgid "Latakia Governorate"
msgstr ""
-#: languages/states.php:4539
+#: languages/states.php:4564
msgid "Quneitra Governorate"
msgstr ""
-#: languages/states.php:4540
+#: languages/states.php:4565
msgid "Rif Dimashq Governorate"
msgstr ""
-#: languages/states.php:4541
+#: languages/states.php:4566
msgid "Tartus Governorate"
msgstr ""
-#: languages/states.php:4544
+#: languages/states.php:4569
msgid "Hhohho District"
msgstr ""
-#: languages/states.php:4545
+#: languages/states.php:4570
msgid "Lubombo District"
msgstr ""
-#: languages/states.php:4546
+#: languages/states.php:4571
msgid "Manzini District"
msgstr ""
-#: languages/states.php:4547
+#: languages/states.php:4572
msgid "Shiselweni District"
msgstr ""
-#: languages/states.php:4551
+#: languages/states.php:4576
msgid "Bahr el Gazel"
msgstr ""
-#: languages/states.php:4552
+#: languages/states.php:4577
msgid "Batha Region"
msgstr ""
-#: languages/states.php:4553
+#: languages/states.php:4578
msgid "Borkou"
msgstr ""
-#: languages/states.php:4554
+#: languages/states.php:4579
msgid "Ennedi Region"
msgstr ""
-#: languages/states.php:4555
+#: languages/states.php:4580
msgid "Ennedi-Est"
msgstr ""
-#: languages/states.php:4556
+#: languages/states.php:4581
msgid "Ennedi-Ouest"
msgstr ""
-#: languages/states.php:4557
+#: languages/states.php:4582
msgid "Guéra Region"
msgstr ""
-#: languages/states.php:4558
+#: languages/states.php:4583
msgid "Hadjer-Lamis"
msgstr ""
-#: languages/states.php:4559
+#: languages/states.php:4584
msgid "Kanem Region"
msgstr ""
-#: languages/states.php:4560
+#: languages/states.php:4585
msgid "Lac Region"
msgstr ""
-#: languages/states.php:4561
+#: languages/states.php:4586
msgid "Logone Occidental Region"
msgstr ""
-#: languages/states.php:4562
+#: languages/states.php:4587
msgid "Logone Oriental Region"
msgstr ""
-#: languages/states.php:4563
+#: languages/states.php:4588
msgid "Mandoul Region"
msgstr ""
-#: languages/states.php:4564
+#: languages/states.php:4589
msgid "Mayo-Kebbi Est Region"
msgstr ""
-#: languages/states.php:4565
+#: languages/states.php:4590
msgid "Mayo-Kebbi Ouest Region"
msgstr ""
-#: languages/states.php:4566
+#: languages/states.php:4591
msgid "Moyen-Chari Region"
msgstr ""
-#: languages/states.php:4567
+#: languages/states.php:4592
msgid "N'Djamena"
msgstr ""
-#: languages/states.php:4568
+#: languages/states.php:4593
msgid "Ouaddaï Region"
msgstr ""
-#: languages/states.php:4569
+#: languages/states.php:4594
msgid "Salamat Region"
msgstr ""
-#: languages/states.php:4570
+#: languages/states.php:4595
msgid "Sila Region"
msgstr ""
-#: languages/states.php:4571
+#: languages/states.php:4596
msgid "Tandjilé Region"
msgstr ""
-#: languages/states.php:4572
+#: languages/states.php:4597
msgid "Tibesti Region"
msgstr ""
-#: languages/states.php:4573
+#: languages/states.php:4598
msgid "Wadi Fira Region"
msgstr ""
-#: languages/states.php:4577
+#: languages/states.php:4602
msgid "Centrale Region"
msgstr ""
-#: languages/states.php:4578
+#: languages/states.php:4603
msgid "Kara Region"
msgstr ""
-#: languages/states.php:4579
+#: languages/states.php:4604
msgid "Maritime"
msgstr ""
-#: languages/states.php:4580
+#: languages/states.php:4605
msgid "Plateaux Region"
msgstr ""
-#: languages/states.php:4584
+#: languages/states.php:4609
msgid "Bangkok"
msgstr ""
-#: languages/states.php:4585
+#: languages/states.php:4610
msgid "Samut Prakan"
msgstr ""
-#: languages/states.php:4586
+#: languages/states.php:4611
msgid "Nonthaburi"
msgstr ""
-#: languages/states.php:4587
+#: languages/states.php:4612
msgid "Pathum Thani"
msgstr ""
-#: languages/states.php:4588
+#: languages/states.php:4613
msgid "Phra Nakhon Si Ayutthaya"
msgstr ""
-#: languages/states.php:4589
+#: languages/states.php:4614
msgid "Ang Thong"
msgstr ""
-#: languages/states.php:4590
+#: languages/states.php:4615
msgid "Lopburi"
msgstr ""
-#: languages/states.php:4591
+#: languages/states.php:4616
msgid "Sing Buri"
msgstr ""
-#: languages/states.php:4592
+#: languages/states.php:4617
msgid "Chai Nat"
msgstr ""
-#: languages/states.php:4593
+#: languages/states.php:4618
msgid "Saraburi"
msgstr ""
-#: languages/states.php:4594
+#: languages/states.php:4619
msgid "Chon Buri"
msgstr ""
-#: languages/states.php:4595
+#: languages/states.php:4620
msgid "Rayong"
msgstr ""
-#: languages/states.php:4596
+#: languages/states.php:4621
msgid "Chanthaburi"
msgstr ""
-#: languages/states.php:4597
+#: languages/states.php:4622
msgid "Trat"
msgstr ""
-#: languages/states.php:4598
+#: languages/states.php:4623
msgid "Chachoengsao"
msgstr ""
-#: languages/states.php:4599
+#: languages/states.php:4624
msgid "Prachin Buri"
msgstr ""
-#: languages/states.php:4600
+#: languages/states.php:4625
msgid "Nakhon Nayok"
msgstr ""
-#: languages/states.php:4601
+#: languages/states.php:4626
msgid "Sa Kaeo"
msgstr ""
-#: languages/states.php:4602
+#: languages/states.php:4627
msgid "Nakhon Ratchasima"
msgstr ""
-#: languages/states.php:4603
+#: languages/states.php:4628
msgid "Buri Ram"
msgstr ""
-#: languages/states.php:4604
+#: languages/states.php:4629
msgid "Surin"
msgstr ""
-#: languages/states.php:4605
+#: languages/states.php:4630
msgid "Si Sa Ket"
msgstr ""
-#: languages/states.php:4606
+#: languages/states.php:4631
msgid "Ubon Ratchathani"
msgstr ""
-#: languages/states.php:4607
+#: languages/states.php:4632
msgid "Yasothon"
msgstr ""
-#: languages/states.php:4608
+#: languages/states.php:4633
msgid "Amnat Charoen"
msgstr ""
-#: languages/states.php:4609
+#: languages/states.php:4634
msgid "Bueng Kan"
msgstr ""
-#: languages/states.php:4610
+#: languages/states.php:4635
msgid "Nong Bua Lam Phu"
msgstr ""
-#: languages/states.php:4611
+#: languages/states.php:4636
msgid "Khon Kaen"
msgstr ""
-#: languages/states.php:4612
+#: languages/states.php:4637
msgid "Udon Thani"
msgstr ""
-#: languages/states.php:4613
+#: languages/states.php:4638
msgid "Loei"
msgstr ""
-#: languages/states.php:4614
+#: languages/states.php:4639
msgid "Nong Khai"
msgstr ""
-#: languages/states.php:4615
+#: languages/states.php:4640
msgid "Maha Sarakham"
msgstr ""
-#: languages/states.php:4616
+#: languages/states.php:4641
msgid "Roi Et"
msgstr ""
-#: languages/states.php:4617
+#: languages/states.php:4642
msgid "Kalasin"
msgstr ""
-#: languages/states.php:4618
+#: languages/states.php:4643
msgid "Sakon Nakhon"
msgstr ""
-#: languages/states.php:4619
+#: languages/states.php:4644
msgid "Nakhon Phanom"
msgstr ""
-#: languages/states.php:4620
+#: languages/states.php:4645
msgid "Mukdahan"
msgstr ""
-#: languages/states.php:4621
+#: languages/states.php:4646
msgid "Chiang Mai"
msgstr ""
-#: languages/states.php:4622
+#: languages/states.php:4647
msgid "Lamphun"
msgstr ""
-#: languages/states.php:4623
+#: languages/states.php:4648
msgid "Lampang"
msgstr ""
-#: languages/states.php:4624
+#: languages/states.php:4649
msgid "Uttaradit"
msgstr ""
-#: languages/states.php:4625
+#: languages/states.php:4650
msgid "Phrae"
msgstr ""
-#: languages/states.php:4626
+#: languages/states.php:4651
msgid "Nan"
msgstr ""
-#: languages/states.php:4627
+#: languages/states.php:4652
msgid "Phayao"
msgstr ""
-#: languages/states.php:4628
+#: languages/states.php:4653
msgid "Chiang Rai"
msgstr ""
-#: languages/states.php:4629
+#: languages/states.php:4654
msgid "Mae Hong Son"
msgstr ""
-#: languages/states.php:4630
+#: languages/states.php:4655
msgid "Nakhon Sawan"
msgstr ""
-#: languages/states.php:4631
+#: languages/states.php:4656
msgid "Uthai Thani"
msgstr ""
-#: languages/states.php:4632
+#: languages/states.php:4657
msgid "Kamphaeng Phet"
msgstr ""
-#: languages/states.php:4633
+#: languages/states.php:4658
msgid "Tak"
msgstr ""
-#: languages/states.php:4634
+#: languages/states.php:4659
msgid "Sukhothai"
msgstr ""
-#: languages/states.php:4635
+#: languages/states.php:4660
msgid "Phitsanulok"
msgstr ""
-#: languages/states.php:4636
+#: languages/states.php:4661
msgid "Phichit"
msgstr ""
-#: languages/states.php:4637
+#: languages/states.php:4662
msgid "Phetchabun"
msgstr ""
-#: languages/states.php:4638
+#: languages/states.php:4663
msgid "Ratchaburi"
msgstr ""
-#: languages/states.php:4639
+#: languages/states.php:4664
msgid "Kanchanaburi"
msgstr ""
-#: languages/states.php:4640
+#: languages/states.php:4665
msgid "Suphanburi"
msgstr ""
-#: languages/states.php:4641
+#: languages/states.php:4666
msgid "Nakhon Pathom"
msgstr ""
-#: languages/states.php:4642
+#: languages/states.php:4667
msgid "Samut Sakhon"
msgstr ""
-#: languages/states.php:4643
+#: languages/states.php:4668
msgid "Samut Songkhram"
msgstr ""
-#: languages/states.php:4644
+#: languages/states.php:4669
msgid "Phetchaburi"
msgstr ""
-#: languages/states.php:4645
+#: languages/states.php:4670
msgid "Prachuap Khiri Khan"
msgstr ""
-#: languages/states.php:4646
+#: languages/states.php:4671
msgid "Nakhon Si Thammarat"
msgstr ""
-#: languages/states.php:4647
+#: languages/states.php:4672
msgid "Krabi"
msgstr ""
-#: languages/states.php:4648
+#: languages/states.php:4673
msgid "Phang Nga"
msgstr ""
-#: languages/states.php:4649
+#: languages/states.php:4674
msgid "Phuket"
msgstr ""
-#: languages/states.php:4650
+#: languages/states.php:4675
msgid "Surat Thani"
msgstr ""
-#: languages/states.php:4651
+#: languages/states.php:4676
msgid "Ranong"
msgstr ""
-#: languages/states.php:4652
+#: languages/states.php:4677
msgid "Chumphon"
msgstr ""
-#: languages/states.php:4653
+#: languages/states.php:4678
msgid "Songkhla"
msgstr ""
-#: languages/states.php:4654
+#: languages/states.php:4679
msgid "Satun"
msgstr ""
-#: languages/states.php:4655
+#: languages/states.php:4680
msgid "Trang"
msgstr ""
-#: languages/states.php:4656
+#: languages/states.php:4681
msgid "Phatthalung"
msgstr ""
-#: languages/states.php:4657
+#: languages/states.php:4682
msgid "Pattani"
msgstr ""
-#: languages/states.php:4658
+#: languages/states.php:4683
msgid "Yala"
msgstr ""
-#: languages/states.php:4659
+#: languages/states.php:4684
msgid "Narathiwat"
msgstr ""
-#: languages/states.php:4660
+#: languages/states.php:4685
msgid "Pattaya"
msgstr ""
-#: languages/states.php:4663
+#: languages/states.php:4688
msgid "Gorno-Badakhshan Autonomous Province"
msgstr ""
-#: languages/states.php:4664
+#: languages/states.php:4689
msgid "Khatlon Province"
msgstr ""
-#: languages/states.php:4665
+#: languages/states.php:4690
msgid "Sughd Province"
msgstr ""
-#: languages/states.php:4666
+#: languages/states.php:4691
msgid "districts of Republican Subordination"
msgstr ""
-#: languages/states.php:4670
+#: languages/states.php:4695
msgid "Aileu municipality"
msgstr ""
-#: languages/states.php:4671
+#: languages/states.php:4696
msgid "Ainaro Municipality"
msgstr ""
-#: languages/states.php:4672
+#: languages/states.php:4697
msgid "Baucau Municipality"
msgstr ""
-#: languages/states.php:4673
+#: languages/states.php:4698
msgid "Bobonaro Municipality"
msgstr ""
-#: languages/states.php:4674
+#: languages/states.php:4699
msgid "Cova Lima Municipality"
msgstr ""
-#: languages/states.php:4675
+#: languages/states.php:4700
msgid "Dili municipality"
msgstr ""
-#: languages/states.php:4676
+#: languages/states.php:4701
msgid "Ermera District"
msgstr ""
-#: languages/states.php:4677
+#: languages/states.php:4702
msgid "Lautém Municipality"
msgstr ""
-#: languages/states.php:4678
+#: languages/states.php:4703
msgid "Liquiçá Municipality"
msgstr ""
-#: languages/states.php:4679
+#: languages/states.php:4704
msgid "Manatuto District"
msgstr ""
-#: languages/states.php:4680
+#: languages/states.php:4705
msgid "Manufahi Municipality"
msgstr ""
-#: languages/states.php:4681
+#: languages/states.php:4706
msgid "Viqueque Municipality"
msgstr ""
-#: languages/states.php:4684
+#: languages/states.php:4709
msgid "Ahal Region"
msgstr ""
-#: languages/states.php:4685
+#: languages/states.php:4710
msgid "Ashgabat"
msgstr ""
-#: languages/states.php:4686
+#: languages/states.php:4711
msgid "Balkan Region"
msgstr ""
-#: languages/states.php:4687
+#: languages/states.php:4712
msgid "Daşoguz Region"
msgstr ""
-#: languages/states.php:4688
+#: languages/states.php:4713
msgid "Lebap Region"
msgstr ""
-#: languages/states.php:4689
+#: languages/states.php:4714
msgid "Mary Region"
msgstr ""
-#: languages/states.php:4692
+#: languages/states.php:4717
msgid "Tunis Governorate"
msgstr ""
-#: languages/states.php:4693
+#: languages/states.php:4718
msgid "Ariana Governorate"
msgstr ""
-#: languages/states.php:4694
+#: languages/states.php:4719
msgid "Ben Arous Governorate"
msgstr ""
-#: languages/states.php:4695
+#: languages/states.php:4720
msgid "Manouba Governorate"
msgstr ""
-#: languages/states.php:4696
+#: languages/states.php:4721
msgid "Zaghouan Governorate"
msgstr ""
-#: languages/states.php:4697
+#: languages/states.php:4722
msgid "Bizerte Governorate"
msgstr ""
-#: languages/states.php:4698
+#: languages/states.php:4723
msgid "Kassrine"
msgstr ""
-#: languages/states.php:4699
+#: languages/states.php:4724
msgid "Jendouba Governorate"
msgstr ""
-#: languages/states.php:4700
+#: languages/states.php:4725
msgid "Kef Governorate"
msgstr ""
-#: languages/states.php:4701
+#: languages/states.php:4726
msgid "Siliana Governorate"
msgstr ""
-#: languages/states.php:4702
+#: languages/states.php:4727
msgid "Kairouan Governorate"
msgstr ""
-#: languages/states.php:4703
+#: languages/states.php:4728
msgid "Kasserine Governorate"
msgstr ""
-#: languages/states.php:4704
+#: languages/states.php:4729
msgid "Sidi Bouzid Governorate"
msgstr ""
-#: languages/states.php:4705
+#: languages/states.php:4730
msgid "Sousse Governorate"
msgstr ""
-#: languages/states.php:4706
+#: languages/states.php:4731
msgid "Monastir Governorate"
msgstr ""
-#: languages/states.php:4707
+#: languages/states.php:4732
msgid "Mahdia Governorate"
msgstr ""
-#: languages/states.php:4708
+#: languages/states.php:4733
msgid "Sfax Governorate"
msgstr ""
-#: languages/states.php:4709
+#: languages/states.php:4734
msgid "Gafsa Governorate"
msgstr ""
-#: languages/states.php:4710
+#: languages/states.php:4735
msgid "Tozeur Governorate"
msgstr ""
-#: languages/states.php:4711
+#: languages/states.php:4736
msgid "Kebili Governorate"
msgstr ""
-#: languages/states.php:4712
+#: languages/states.php:4737
msgid "Gabès Governorate"
msgstr ""
-#: languages/states.php:4713
+#: languages/states.php:4738
msgid "Medenine Governorate"
msgstr ""
-#: languages/states.php:4714
+#: languages/states.php:4739
msgid "Tataouine Governorate"
msgstr ""
-#: languages/states.php:4717
+#: languages/states.php:4742
msgid "Haʻapai"
msgstr ""
-#: languages/states.php:4718
+#: languages/states.php:4743
msgid "Niuas"
msgstr ""
-#: languages/states.php:4719
+#: languages/states.php:4744
msgid "Tongatapu"
msgstr ""
-#: languages/states.php:4720
+#: languages/states.php:4745
msgid "Vavaʻu"
msgstr ""
-#: languages/states.php:4721
+#: languages/states.php:4746
msgid "ʻEua"
msgstr ""
-#: languages/states.php:4724
+#: languages/states.php:4749
msgid "Balıkesir Province"
msgstr ""
-#: languages/states.php:4725
+#: languages/states.php:4750
msgid "Bilecik Province"
msgstr ""
-#: languages/states.php:4726
+#: languages/states.php:4751
msgid "Bingöl Province"
msgstr ""
-#: languages/states.php:4727
+#: languages/states.php:4752
msgid "Bitlis Province"
msgstr ""
-#: languages/states.php:4728
+#: languages/states.php:4753
msgid "Bolu Province"
msgstr ""
-#: languages/states.php:4729
+#: languages/states.php:4754
msgid "Burdur Province"
msgstr ""
-#: languages/states.php:4730
+#: languages/states.php:4755
msgid "Bursa Province"
msgstr ""
-#: languages/states.php:4731
+#: languages/states.php:4756
msgid "Çanakkale Province"
msgstr ""
-#: languages/states.php:4732
+#: languages/states.php:4757
msgid "Çankırı Province"
msgstr ""
-#: languages/states.php:4733
+#: languages/states.php:4758
msgid "Çorum Province"
msgstr ""
-#: languages/states.php:4734
+#: languages/states.php:4759
msgid "Denizli Province"
msgstr ""
-#: languages/states.php:4735
+#: languages/states.php:4760
msgid "Diyarbakır Province"
msgstr ""
-#: languages/states.php:4736
+#: languages/states.php:4761
msgid "Edirne Province"
msgstr ""
-#: languages/states.php:4737
+#: languages/states.php:4762
msgid "Elazığ Province"
msgstr ""
-#: languages/states.php:4738
+#: languages/states.php:4763
msgid "Erzincan Province"
msgstr ""
-#: languages/states.php:4739
+#: languages/states.php:4764
msgid "Erzurum Province"
msgstr ""
-#: languages/states.php:4740
+#: languages/states.php:4765
msgid "Eskişehir Province"
msgstr ""
-#: languages/states.php:4741
+#: languages/states.php:4766
msgid "Gaziantep Province"
msgstr ""
-#: languages/states.php:4742
+#: languages/states.php:4767
msgid "Giresun Province"
msgstr ""
-#: languages/states.php:4743
+#: languages/states.php:4768
msgid "Gümüşhane Province"
msgstr ""
-#: languages/states.php:4744
+#: languages/states.php:4769
msgid "Hakkâri Province"
msgstr ""
-#: languages/states.php:4745
+#: languages/states.php:4770
msgid "Hatay Province"
msgstr ""
-#: languages/states.php:4746
+#: languages/states.php:4771
msgid "Isparta Province"
msgstr ""
-#: languages/states.php:4747
+#: languages/states.php:4772
msgid "Mersin Province"
msgstr ""
-#: languages/states.php:4748
+#: languages/states.php:4773
msgid "Istanbul Province"
msgstr ""
-#: languages/states.php:4749
+#: languages/states.php:4774
msgid "İzmir Province"
msgstr ""
-#: languages/states.php:4750
+#: languages/states.php:4775
msgid "Kars Province"
msgstr ""
-#: languages/states.php:4751
+#: languages/states.php:4776
msgid "Kastamonu Province"
msgstr ""
-#: languages/states.php:4752
+#: languages/states.php:4777
msgid "Kayseri Province"
msgstr ""
-#: languages/states.php:4753
+#: languages/states.php:4778
msgid "Kırklareli Province"
msgstr ""
-#: languages/states.php:4754
+#: languages/states.php:4779
msgid "Kırşehir Province"
msgstr ""
-#: languages/states.php:4755
+#: languages/states.php:4780
msgid "Kocaeli Province"
msgstr ""
-#: languages/states.php:4756
+#: languages/states.php:4781
msgid "Konya Province"
msgstr ""
-#: languages/states.php:4757
+#: languages/states.php:4782
msgid "Kütahya Province"
msgstr ""
-#: languages/states.php:4758
+#: languages/states.php:4783
msgid "Malatya Province"
msgstr ""
-#: languages/states.php:4759
+#: languages/states.php:4784
msgid "Manisa Province"
msgstr ""
-#: languages/states.php:4760
+#: languages/states.php:4785
msgid "Kahramanmaraş Province"
msgstr ""
-#: languages/states.php:4761
+#: languages/states.php:4786
msgid "Mardin Province"
msgstr ""
-#: languages/states.php:4762
+#: languages/states.php:4787
msgid "Muğla Province"
msgstr ""
-#: languages/states.php:4763
+#: languages/states.php:4788
msgid "Muş Province"
msgstr ""
-#: languages/states.php:4764
+#: languages/states.php:4789
msgid "Nevşehir Province"
msgstr ""
-#: languages/states.php:4765
+#: languages/states.php:4790
msgid "Niğde Province"
msgstr ""
-#: languages/states.php:4766
+#: languages/states.php:4791
msgid "Ordu Province"
msgstr ""
-#: languages/states.php:4767
+#: languages/states.php:4792
msgid "Rize Province"
msgstr ""
-#: languages/states.php:4768
+#: languages/states.php:4793
msgid "Sakarya Province"
msgstr ""
-#: languages/states.php:4769
+#: languages/states.php:4794
msgid "Samsun Province"
msgstr ""
-#: languages/states.php:4770
+#: languages/states.php:4795
msgid "Siirt Province"
msgstr ""
-#: languages/states.php:4771
+#: languages/states.php:4796
msgid "Sinop Province"
msgstr ""
-#: languages/states.php:4772
+#: languages/states.php:4797
msgid "Sivas Province"
msgstr ""
-#: languages/states.php:4773
+#: languages/states.php:4798
msgid "Tekirdağ Province"
msgstr ""
-#: languages/states.php:4774
+#: languages/states.php:4799
msgid "Tokat Province"
msgstr ""
-#: languages/states.php:4775
+#: languages/states.php:4800
msgid "Trabzon Province"
msgstr ""
-#: languages/states.php:4776
+#: languages/states.php:4801
msgid "Tunceli Province"
msgstr ""
-#: languages/states.php:4777
+#: languages/states.php:4802
msgid "Şanlıurfa Province"
msgstr ""
-#: languages/states.php:4778
+#: languages/states.php:4803
msgid "Uşak Province"
msgstr ""
-#: languages/states.php:4779
+#: languages/states.php:4804
msgid "Van Province"
msgstr ""
-#: languages/states.php:4780
+#: languages/states.php:4805
msgid "Yozgat Province"
msgstr ""
-#: languages/states.php:4781
+#: languages/states.php:4806
msgid "Zonguldak Province"
msgstr ""
-#: languages/states.php:4782
+#: languages/states.php:4807
msgid "Aksaray Province"
msgstr ""
-#: languages/states.php:4783
+#: languages/states.php:4808
msgid "Bayburt Province"
msgstr ""
-#: languages/states.php:4784
+#: languages/states.php:4809
msgid "Karaman Province"
msgstr ""
-#: languages/states.php:4785
+#: languages/states.php:4810
msgid "Kırıkkale Province"
msgstr ""
-#: languages/states.php:4786
+#: languages/states.php:4811
msgid "Batman Province"
msgstr ""
-#: languages/states.php:4787
+#: languages/states.php:4812
msgid "Şırnak Province"
msgstr ""
-#: languages/states.php:4788
+#: languages/states.php:4813
msgid "Bartın Province"
msgstr ""
-#: languages/states.php:4789
+#: languages/states.php:4814
msgid "Ardahan Province"
msgstr ""
-#: languages/states.php:4790
+#: languages/states.php:4815
msgid "Iğdır Province"
msgstr ""
-#: languages/states.php:4791
+#: languages/states.php:4816
msgid "Yalova Province"
msgstr ""
-#: languages/states.php:4792
+#: languages/states.php:4817
msgid "Karabük Province"
msgstr ""
-#: languages/states.php:4793
+#: languages/states.php:4818
msgid "Kilis Province"
msgstr ""
-#: languages/states.php:4794
+#: languages/states.php:4819
msgid "Osmaniye Province"
msgstr ""
-#: languages/states.php:4795
+#: languages/states.php:4820
msgid "Düzce Province"
msgstr ""
-#: languages/states.php:4796
+#: languages/states.php:4821
msgid "Adana Province"
msgstr ""
-#: languages/states.php:4797
+#: languages/states.php:4822
msgid "Adıyaman Province"
msgstr ""
-#: languages/states.php:4798
+#: languages/states.php:4823
msgid "Afyonkarahisar Province"
msgstr ""
-#: languages/states.php:4799
+#: languages/states.php:4824
msgid "Amasya Province"
msgstr ""
-#: languages/states.php:4800
+#: languages/states.php:4825
msgid "Ankara Province"
msgstr ""
-#: languages/states.php:4801
+#: languages/states.php:4826
msgid "Antalya Province"
msgstr ""
-#: languages/states.php:4802
+#: languages/states.php:4827
msgid "Artvin Province"
msgstr ""
-#: languages/states.php:4803
+#: languages/states.php:4828
msgid "Aydın Province"
msgstr ""
-#: languages/states.php:4804
+#: languages/states.php:4829
msgid "Ağrı Province"
msgstr ""
-#: languages/states.php:4807
+#: languages/states.php:4832
msgid "Arima"
msgstr ""
-#: languages/states.php:4808
+#: languages/states.php:4833
msgid "Chaguanas"
msgstr ""
-#: languages/states.php:4809
+#: languages/states.php:4834
msgid "Couva-Tabaquite-Talparo Regional Corporation"
msgstr ""
-#: languages/states.php:4810
+#: languages/states.php:4835
msgid "Diego Martin Regional Corporation"
msgstr ""
-#: languages/states.php:4811
+#: languages/states.php:4836
msgid "Eastern Tobago"
msgstr ""
-#: languages/states.php:4812
+#: languages/states.php:4837
msgid "Penal-Debe Regional Corporation"
msgstr ""
-#: languages/states.php:4813
+#: languages/states.php:4838
msgid "Point Fortin"
msgstr ""
-#: languages/states.php:4814
+#: languages/states.php:4839
msgid "Port of Spain"
msgstr ""
-#: languages/states.php:4815
+#: languages/states.php:4840
msgid "Princes Town Regional Corporation"
msgstr ""
-#: languages/states.php:4816
+#: languages/states.php:4841
msgid "Rio Claro-Mayaro Regional Corporation"
msgstr ""
-#: languages/states.php:4817
+#: languages/states.php:4842
msgid "San Fernando"
msgstr ""
-#: languages/states.php:4818
+#: languages/states.php:4843
msgid "San Juan-Laventille Regional Corporation"
msgstr ""
-#: languages/states.php:4819
+#: languages/states.php:4844
msgid "Sangre Grande Regional Corporation"
msgstr ""
-#: languages/states.php:4820
+#: languages/states.php:4845
msgid "Siparia Regional Corporation"
msgstr ""
-#: languages/states.php:4821
+#: languages/states.php:4846
msgid "Tunapuna-Piarco Regional Corporation"
msgstr ""
-#: languages/states.php:4822
+#: languages/states.php:4847
msgid "Western Tobago"
msgstr ""
-#: languages/states.php:4825
+#: languages/states.php:4850
msgid "Funafuti"
msgstr ""
-#: languages/states.php:4826
+#: languages/states.php:4851
msgid "Nanumanga"
msgstr ""
-#: languages/states.php:4827
+#: languages/states.php:4852
msgid "Nanumea"
msgstr ""
-#: languages/states.php:4828
+#: languages/states.php:4853
msgid "Niutao Island Council"
msgstr ""
-#: languages/states.php:4829
+#: languages/states.php:4854
msgid "Nui"
msgstr ""
-#: languages/states.php:4830
+#: languages/states.php:4855
msgid "Nukufetau"
msgstr ""
-#: languages/states.php:4831
+#: languages/states.php:4856
msgid "Nukulaelae"
msgstr ""
-#: languages/states.php:4832
+#: languages/states.php:4857
msgid "Vaitupu"
msgstr ""
-#: languages/states.php:4835
+#: languages/states.php:4860
msgid "Changhua County"
msgstr ""
-#: languages/states.php:4836
+#: languages/states.php:4861
msgid "Chiayi City"
msgstr ""
-#: languages/states.php:4837
+#: languages/states.php:4862
msgid "Chiayi County"
msgstr ""
-#: languages/states.php:4838
+#: languages/states.php:4863
msgid "Hsinchu"
msgstr ""
-#: languages/states.php:4839
+#: languages/states.php:4864
msgid "Hsinchu County"
msgstr ""
-#: languages/states.php:4840
+#: languages/states.php:4865
msgid "Hualien County"
msgstr ""
-#: languages/states.php:4841
+#: languages/states.php:4866
msgid "Kaohsiung"
msgstr ""
-#: languages/states.php:4842
+#: languages/states.php:4867
msgid "Kaohsiung County"
msgstr ""
-#: languages/states.php:4843
+#: languages/states.php:4868
msgid "Kinmen"
msgstr ""
-#: languages/states.php:4844
+#: languages/states.php:4869
msgid "Lienchiang County"
msgstr ""
-#: languages/states.php:4845
+#: languages/states.php:4870
msgid "Miaoli County"
msgstr ""
-#: languages/states.php:4846
+#: languages/states.php:4871
msgid "Nantou County"
msgstr ""
-#: languages/states.php:4847
+#: languages/states.php:4872
msgid "Penghu County"
msgstr ""
-#: languages/states.php:4848
+#: languages/states.php:4873
msgid "Pingtung County"
msgstr ""
-#: languages/states.php:4849
+#: languages/states.php:4874
msgid "Taichung"
msgstr ""
-#: languages/states.php:4850
+#: languages/states.php:4875
msgid "Taichung County"
msgstr ""
-#: languages/states.php:4851
+#: languages/states.php:4876
msgid "Tainan"
msgstr ""
-#: languages/states.php:4852
+#: languages/states.php:4877
msgid "Tainan County"
msgstr ""
-#: languages/states.php:4853
+#: languages/states.php:4878
msgid "Taipei"
msgstr ""
-#: languages/states.php:4854
+#: languages/states.php:4879
msgid "Taitung County"
msgstr ""
-#: languages/states.php:4855
+#: languages/states.php:4880
msgid "Taoyuan City"
msgstr ""
-#: languages/states.php:4856
+#: languages/states.php:4881
msgid "Yilan County"
msgstr ""
-#: languages/states.php:4857
+#: languages/states.php:4882
msgid "Yunlin County"
msgstr ""
-#: languages/states.php:4860
+#: languages/states.php:4885
msgid "South Pemba Region"
msgstr ""
-#: languages/states.php:4861
+#: languages/states.php:4886
msgid "Zanzibar Central/South Region"
msgstr ""
-#: languages/states.php:4862
+#: languages/states.php:4887
msgid "Lindi Region"
msgstr ""
-#: languages/states.php:4863
+#: languages/states.php:4888
msgid "Mara Region"
msgstr ""
-#: languages/states.php:4864
+#: languages/states.php:4889
msgid "Zanzibar Urban/West Region"
msgstr ""
-#: languages/states.php:4865
+#: languages/states.php:4890
msgid "Morogoro Region"
msgstr ""
-#: languages/states.php:4866
+#: languages/states.php:4891
msgid "Mtwara Region"
msgstr ""
-#: languages/states.php:4867
+#: languages/states.php:4892
msgid "Mwanza Region"
msgstr ""
-#: languages/states.php:4868
+#: languages/states.php:4893
msgid "Pwani Region"
msgstr ""
-#: languages/states.php:4869
+#: languages/states.php:4894
msgid "Rukwa Region"
msgstr ""
-#: languages/states.php:4870
+#: languages/states.php:4895
msgid "Ruvuma Region"
msgstr ""
-#: languages/states.php:4871
+#: languages/states.php:4896
msgid "Shinyanga Region"
msgstr ""
-#: languages/states.php:4872
+#: languages/states.php:4897
msgid "Singida Region"
msgstr ""
-#: languages/states.php:4873
+#: languages/states.php:4898
msgid "Tabora Region"
msgstr ""
-#: languages/states.php:4874
+#: languages/states.php:4899
msgid "Tanga Region"
msgstr ""
-#: languages/states.php:4875
+#: languages/states.php:4900
msgid "Manyara Region"
msgstr ""
-#: languages/states.php:4876
+#: languages/states.php:4901
msgid "Geita Region"
msgstr ""
-#: languages/states.php:4877
+#: languages/states.php:4902
msgid "Katavi Region"
msgstr ""
-#: languages/states.php:4878
+#: languages/states.php:4903
msgid "Njombe Region"
msgstr ""
-#: languages/states.php:4879
+#: languages/states.php:4904
msgid "Simiyu Region"
msgstr ""
-#: languages/states.php:4880
+#: languages/states.php:4905
msgid "Arusha Region"
msgstr ""
-#: languages/states.php:4881
+#: languages/states.php:4906
msgid "Dar es Salaam Region"
msgstr ""
-#: languages/states.php:4882
+#: languages/states.php:4907
msgid "Dodoma Region"
msgstr ""
-#: languages/states.php:4883
+#: languages/states.php:4908
msgid "Iringa Region"
msgstr ""
-#: languages/states.php:4884
+#: languages/states.php:4909
msgid "Kagera Region"
msgstr ""
-#: languages/states.php:4885
+#: languages/states.php:4910
msgid "Kigoma Region"
msgstr ""
-#: languages/states.php:4886
+#: languages/states.php:4911
msgid "Kilimanjaro Region"
msgstr ""
-#: languages/states.php:4887
+#: languages/states.php:4912
msgid "North Pemba Region"
msgstr ""
-#: languages/states.php:4888
+#: languages/states.php:4913
msgid "Zanzibar North Region"
msgstr ""
-#: languages/states.php:4891
+#: languages/states.php:4916
msgid "Dnipropetrovsk Oblast"
msgstr ""
-#: languages/states.php:4892
+#: languages/states.php:4917
msgid "Donetsk Oblast"
msgstr ""
-#: languages/states.php:4893
+#: languages/states.php:4918
msgid "Zhytomyr Oblast"
msgstr ""
-#: languages/states.php:4894
+#: languages/states.php:4919
msgid "Zakarpattia Oblast"
msgstr ""
-#: languages/states.php:4895
+#: languages/states.php:4920
msgid "Zaporizhzhya Oblast"
msgstr ""
-#: languages/states.php:4896
+#: languages/states.php:4921
msgid "Ivano-Frankivsk Oblast"
msgstr ""
-#: languages/states.php:4897
+#: languages/states.php:4922
msgid "Kiev"
msgstr ""
-#: languages/states.php:4898
+#: languages/states.php:4923
msgid "Kyiv Oblast"
msgstr ""
-#: languages/states.php:4899
+#: languages/states.php:4924
msgid "Kirovohrad Oblast"
msgstr ""
-#: languages/states.php:4900
+#: languages/states.php:4925
msgid "Autonomous Republic of Crimea"
msgstr ""
-#: languages/states.php:4901
+#: languages/states.php:4926
msgid "Lviv Oblast"
msgstr ""
-#: languages/states.php:4902
+#: languages/states.php:4927
msgid "Mykolaiv Oblast"
msgstr ""
-#: languages/states.php:4903
+#: languages/states.php:4928
msgid "Odessa Oblast"
msgstr ""
-#: languages/states.php:4904
+#: languages/states.php:4929
msgid "Rivne Oblast"
msgstr ""
-#: languages/states.php:4905
+#: languages/states.php:4930
msgid "Sumy Oblast"
msgstr ""
-#: languages/states.php:4906
+#: languages/states.php:4931
msgid "Ternopil Oblast"
msgstr ""
-#: languages/states.php:4907
+#: languages/states.php:4932
msgid "Kharkiv Oblast"
msgstr ""
-#: languages/states.php:4908
+#: languages/states.php:4933
msgid "Kherson Oblast"
msgstr ""
-#: languages/states.php:4909
+#: languages/states.php:4934
msgid "Khmelnytsky Oblast"
msgstr ""
-#: languages/states.php:4910
+#: languages/states.php:4935
msgid "Cherkasy Oblast"
msgstr ""
-#: languages/states.php:4911
+#: languages/states.php:4936
msgid "Chernihiv Oblast"
msgstr ""
-#: languages/states.php:4912
+#: languages/states.php:4937
msgid "Chernivtsi Oblast"
msgstr ""
-#: languages/states.php:4913
+#: languages/states.php:4938
msgid "Luhansk Oblast"
msgstr ""
-#: languages/states.php:4914
+#: languages/states.php:4939
msgid "Vinnytsia Oblast"
msgstr ""
-#: languages/states.php:4915
+#: languages/states.php:4940
msgid "Volyn Oblast"
msgstr ""
-#: languages/states.php:4918
+#: languages/states.php:4943
msgid "Kalangala District"
msgstr ""
-#: languages/states.php:4919
+#: languages/states.php:4944
msgid "Kampala District"
msgstr ""
-#: languages/states.php:4920
+#: languages/states.php:4945
msgid "Kiboga District"
msgstr ""
-#: languages/states.php:4921
+#: languages/states.php:4946
msgid "Luwero District"
msgstr ""
-#: languages/states.php:4922
+#: languages/states.php:4947
msgid "Masaka District"
msgstr ""
-#: languages/states.php:4923
+#: languages/states.php:4948
msgid "Mpigi District"
msgstr ""
-#: languages/states.php:4924
+#: languages/states.php:4949
msgid "Mubende District"
msgstr ""
-#: languages/states.php:4925
+#: languages/states.php:4950
msgid "Mukono District"
msgstr ""
-#: languages/states.php:4926
+#: languages/states.php:4951
msgid "Nakasongola District"
msgstr ""
-#: languages/states.php:4927
+#: languages/states.php:4952
msgid "Rakai District"
msgstr ""
-#: languages/states.php:4928
+#: languages/states.php:4953
msgid "Sembabule District"
msgstr ""
-#: languages/states.php:4929
+#: languages/states.php:4954
msgid "Kayunga District"
msgstr ""
-#: languages/states.php:4930
+#: languages/states.php:4955
msgid "Wakiso District"
msgstr ""
-#: languages/states.php:4931
+#: languages/states.php:4956
msgid "Lyantonde District"
msgstr ""
-#: languages/states.php:4932
+#: languages/states.php:4957
msgid "Mityana District"
msgstr ""
-#: languages/states.php:4933
+#: languages/states.php:4958
msgid "Nakaseke District"
msgstr ""
-#: languages/states.php:4934
+#: languages/states.php:4959
msgid "Buikwe District"
msgstr ""
-#: languages/states.php:4935
+#: languages/states.php:4960
msgid "Bukomansimbi District"
msgstr ""
-#: languages/states.php:4936
+#: languages/states.php:4961
msgid "Butambala District"
msgstr ""
-#: languages/states.php:4937
+#: languages/states.php:4962
msgid "Buvuma District"
msgstr ""
-#: languages/states.php:4938
+#: languages/states.php:4963
msgid "Gomba District"
msgstr ""
-#: languages/states.php:4939
+#: languages/states.php:4964
msgid "Kalungu District"
msgstr ""
-#: languages/states.php:4940
+#: languages/states.php:4965
msgid "Kyankwanzi District"
msgstr ""
-#: languages/states.php:4941
+#: languages/states.php:4966
msgid "Lwengo District"
msgstr ""
-#: languages/states.php:4942
+#: languages/states.php:4967
msgid "Kyotera District"
msgstr ""
-#: languages/states.php:4943
+#: languages/states.php:4968
msgid "Bugiri District"
msgstr ""
-#: languages/states.php:4944
+#: languages/states.php:4969
msgid "Busia District"
msgstr ""
-#: languages/states.php:4945
+#: languages/states.php:4970
msgid "Iganga District"
msgstr ""
-#: languages/states.php:4946
+#: languages/states.php:4971
msgid "Jinja District"
msgstr ""
-#: languages/states.php:4947
+#: languages/states.php:4972
msgid "Kamuli District"
msgstr ""
-#: languages/states.php:4948
+#: languages/states.php:4973
msgid "Kapchorwa District"
msgstr ""
-#: languages/states.php:4949
+#: languages/states.php:4974
msgid "Katakwi District"
msgstr ""
-#: languages/states.php:4950
+#: languages/states.php:4975
msgid "Kumi District"
msgstr ""
-#: languages/states.php:4951
+#: languages/states.php:4976
msgid "Mbale District"
msgstr ""
-#: languages/states.php:4952
+#: languages/states.php:4977
msgid "Pallisa District"
msgstr ""
-#: languages/states.php:4953
+#: languages/states.php:4978
msgid "Soroti District"
msgstr ""
-#: languages/states.php:4954
+#: languages/states.php:4979
msgid "Tororo District"
msgstr ""
-#: languages/states.php:4955
+#: languages/states.php:4980
msgid "Kaberamaido District"
msgstr ""
-#: languages/states.php:4956
+#: languages/states.php:4981
msgid "Mayuge District"
msgstr ""
-#: languages/states.php:4957
+#: languages/states.php:4982
msgid "Sironko District"
msgstr ""
-#: languages/states.php:4958
+#: languages/states.php:4983
msgid "Amuria District"
msgstr ""
-#: languages/states.php:4959
+#: languages/states.php:4984
msgid "Budaka District"
msgstr ""
-#: languages/states.php:4960
+#: languages/states.php:4985
msgid "Bududa District"
msgstr ""
-#: languages/states.php:4961
+#: languages/states.php:4986
msgid "Bukedea District"
msgstr ""
-#: languages/states.php:4962
+#: languages/states.php:4987
msgid "Bukwo District"
msgstr ""
-#: languages/states.php:4963
+#: languages/states.php:4988
msgid "Butaleja District"
msgstr ""
-#: languages/states.php:4964
+#: languages/states.php:4989
msgid "Kaliro District"
msgstr ""
-#: languages/states.php:4965
+#: languages/states.php:4990
msgid "Manafwa District"
msgstr ""
-#: languages/states.php:4966
+#: languages/states.php:4991
msgid "Namutumba District"
msgstr ""
-#: languages/states.php:4967
+#: languages/states.php:4992
msgid "Bulambuli District"
msgstr ""
-#: languages/states.php:4968
+#: languages/states.php:4993
msgid "Buyende District"
msgstr ""
-#: languages/states.php:4969
+#: languages/states.php:4994
msgid "Kibuku District"
msgstr ""
-#: languages/states.php:4970
+#: languages/states.php:4995
msgid "Kween District"
msgstr ""
-#: languages/states.php:4971
+#: languages/states.php:4996
msgid "Luuka District"
msgstr ""
-#: languages/states.php:4972
+#: languages/states.php:4997
msgid "Namayingo District"
msgstr ""
-#: languages/states.php:4973
+#: languages/states.php:4998
msgid "Ngora District"
msgstr ""
-#: languages/states.php:4974
+#: languages/states.php:4999
msgid "Serere District"
msgstr ""
-#: languages/states.php:4975
+#: languages/states.php:5000
msgid "Butebo District"
msgstr ""
-#: languages/states.php:4976
+#: languages/states.php:5001
msgid "Namisindwa District"
msgstr ""
-#: languages/states.php:4977
+#: languages/states.php:5002
msgid "Adjumani District"
msgstr ""
-#: languages/states.php:4978
+#: languages/states.php:5003
msgid "Apac District"
msgstr ""
-#: languages/states.php:4979
+#: languages/states.php:5004
msgid "Arua District"
msgstr ""
-#: languages/states.php:4980
+#: languages/states.php:5005
msgid "Gulu District"
msgstr ""
-#: languages/states.php:4981
+#: languages/states.php:5006
msgid "Kitgum District"
msgstr ""
-#: languages/states.php:4982
+#: languages/states.php:5007
msgid "Kotido District"
msgstr ""
-#: languages/states.php:4983
+#: languages/states.php:5008
msgid "Lira District"
msgstr ""
-#: languages/states.php:4984
+#: languages/states.php:5009
msgid "Moroto District"
msgstr ""
-#: languages/states.php:4985
+#: languages/states.php:5010
msgid "Moyo District"
msgstr ""
-#: languages/states.php:4986
+#: languages/states.php:5011
msgid "Nebbi District"
msgstr ""
-#: languages/states.php:4987
+#: languages/states.php:5012
msgid "Nakapiripirit District"
msgstr ""
-#: languages/states.php:4988
+#: languages/states.php:5013
msgid "Pader District"
msgstr ""
-#: languages/states.php:4989
+#: languages/states.php:5014
msgid "Yumbe District"
msgstr ""
-#: languages/states.php:4990
+#: languages/states.php:5015
msgid "Abim District"
msgstr ""
-#: languages/states.php:4991
+#: languages/states.php:5016
msgid "Amolatar District"
msgstr ""
-#: languages/states.php:4992
+#: languages/states.php:5017
msgid "Amuru District"
msgstr ""
-#: languages/states.php:4993
+#: languages/states.php:5018
msgid "Dokolo District"
msgstr ""
-#: languages/states.php:4994
+#: languages/states.php:5019
msgid "Kaabong District"
msgstr ""
-#: languages/states.php:4995
+#: languages/states.php:5020
msgid "Koboko District"
msgstr ""
-#: languages/states.php:4996
+#: languages/states.php:5021
msgid "Maracha District"
msgstr ""
-#: languages/states.php:4997
+#: languages/states.php:5022
msgid "Oyam District"
msgstr ""
-#: languages/states.php:4998
+#: languages/states.php:5023
msgid "Agago District"
msgstr ""
-#: languages/states.php:4999
+#: languages/states.php:5024
msgid "Alebtong District"
msgstr ""
-#: languages/states.php:5000
+#: languages/states.php:5025
msgid "Amudat District"
msgstr ""
-#: languages/states.php:5001
+#: languages/states.php:5026
msgid "Kole District"
msgstr ""
-#: languages/states.php:5002
+#: languages/states.php:5027
msgid "Lamwo District"
msgstr ""
-#: languages/states.php:5003
+#: languages/states.php:5028
msgid "Napak District"
msgstr ""
-#: languages/states.php:5004
+#: languages/states.php:5029
msgid "Nwoya District"
msgstr ""
-#: languages/states.php:5005
+#: languages/states.php:5030
msgid "Otuke District"
msgstr ""
-#: languages/states.php:5006
+#: languages/states.php:5031
msgid "Zombo District"
msgstr ""
-#: languages/states.php:5007
+#: languages/states.php:5032
msgid "Omoro District"
msgstr ""
-#: languages/states.php:5008
+#: languages/states.php:5033
msgid "Pakwach District"
msgstr ""
-#: languages/states.php:5009
+#: languages/states.php:5034
msgid "Bundibugyo District"
msgstr ""
-#: languages/states.php:5010
+#: languages/states.php:5035
msgid "Bushenyi District"
msgstr ""
-#: languages/states.php:5011
+#: languages/states.php:5036
msgid "Kabale District"
msgstr ""
-#: languages/states.php:5012
+#: languages/states.php:5037
msgid "Kabarole District"
msgstr ""
-#: languages/states.php:5013
+#: languages/states.php:5038
msgid "Kasese District"
msgstr ""
-#: languages/states.php:5014
+#: languages/states.php:5039
msgid "Kibaale District"
msgstr ""
-#: languages/states.php:5015
+#: languages/states.php:5040
msgid "Kisoro District"
msgstr ""
-#: languages/states.php:5016
+#: languages/states.php:5041
msgid "Masindi District"
msgstr ""
-#: languages/states.php:5017
+#: languages/states.php:5042
msgid "Mbarara District"
msgstr ""
-#: languages/states.php:5018
+#: languages/states.php:5043
msgid "Ntungamo District"
msgstr ""
-#: languages/states.php:5019
+#: languages/states.php:5044
msgid "Rukungiri District"
msgstr ""
-#: languages/states.php:5020
+#: languages/states.php:5045
msgid "Kamwenge District"
msgstr ""
-#: languages/states.php:5021
+#: languages/states.php:5046
msgid "Kanungu District"
msgstr ""
-#: languages/states.php:5022
+#: languages/states.php:5047
msgid "Kyenjojo District"
msgstr ""
-#: languages/states.php:5023
+#: languages/states.php:5048
msgid "Buliisa District"
msgstr ""
-#: languages/states.php:5024
+#: languages/states.php:5049
msgid "Ibanda District"
msgstr ""
-#: languages/states.php:5025
+#: languages/states.php:5050
msgid "Isingiro District"
msgstr ""
-#: languages/states.php:5026
+#: languages/states.php:5051
msgid "Kiruhura District"
msgstr ""
-#: languages/states.php:5027
+#: languages/states.php:5052
msgid "Buhweju District"
msgstr ""
-#: languages/states.php:5028
+#: languages/states.php:5053
msgid "Kiryandongo District"
msgstr ""
-#: languages/states.php:5029
+#: languages/states.php:5054
msgid "Kyegegwa District"
msgstr ""
-#: languages/states.php:5030
+#: languages/states.php:5055
msgid "Mitooma District"
msgstr ""
-#: languages/states.php:5031
+#: languages/states.php:5056
msgid "Ntoroko District"
msgstr ""
-#: languages/states.php:5032
+#: languages/states.php:5057
msgid "Rubirizi District"
msgstr ""
-#: languages/states.php:5033
+#: languages/states.php:5058
msgid "Sheema District"
msgstr ""
-#: languages/states.php:5034
+#: languages/states.php:5059
msgid "Kagadi District"
msgstr ""
-#: languages/states.php:5035
+#: languages/states.php:5060
msgid "Kakumiro District"
msgstr ""
-#: languages/states.php:5036
+#: languages/states.php:5061
msgid "Rubanda District"
msgstr ""
-#: languages/states.php:5037
+#: languages/states.php:5062
msgid "Bunyangabu District"
msgstr ""
-#: languages/states.php:5038
+#: languages/states.php:5063
msgid "Rukiga District"
msgstr ""
-#: languages/states.php:5046
+#: languages/states.php:5071
msgid "Alabama"
msgstr ""
-#: languages/states.php:5047
+#: languages/states.php:5072
msgid "Alaska"
msgstr ""
-#: languages/states.php:5049
+#: languages/states.php:5074
msgid "Arizona"
msgstr ""
-#: languages/states.php:5050
+#: languages/states.php:5075
msgid "Arkansas"
msgstr ""
-#: languages/states.php:5051
+#: languages/states.php:5076
msgid "California"
msgstr ""
-#: languages/states.php:5052
+#: languages/states.php:5077
msgid "Colorado"
msgstr ""
-#: languages/states.php:5053
+#: languages/states.php:5078
msgid "Connecticut"
msgstr ""
-#: languages/states.php:5054
+#: languages/states.php:5079
msgid "Delaware"
msgstr ""
-#: languages/states.php:5055
+#: languages/states.php:5080
msgid "District of Columbia"
msgstr ""
-#: languages/states.php:5056
+#: languages/states.php:5081
msgid "Florida"
msgstr ""
-#: languages/states.php:5059
+#: languages/states.php:5084
msgid "Hawaii"
msgstr ""
-#: languages/states.php:5060
+#: languages/states.php:5085
msgid "Idaho"
msgstr ""
-#: languages/states.php:5061
+#: languages/states.php:5086
msgid "Illinois"
msgstr ""
-#: languages/states.php:5062
+#: languages/states.php:5087
msgid "Indiana"
msgstr ""
-#: languages/states.php:5063
+#: languages/states.php:5088
msgid "Iowa"
msgstr ""
-#: languages/states.php:5064
+#: languages/states.php:5089
msgid "Kansas"
msgstr ""
-#: languages/states.php:5065
+#: languages/states.php:5090
msgid "Kentucky"
msgstr ""
-#: languages/states.php:5066
+#: languages/states.php:5091
msgid "Louisiana"
msgstr ""
-#: languages/states.php:5067
+#: languages/states.php:5092
msgid "Maine"
msgstr ""
-#: languages/states.php:5068
+#: languages/states.php:5093
msgid "Maryland"
msgstr ""
-#: languages/states.php:5069
+#: languages/states.php:5094
msgid "Massachusetts"
msgstr ""
-#: languages/states.php:5070
+#: languages/states.php:5095
msgid "Michigan"
msgstr ""
-#: languages/states.php:5071
+#: languages/states.php:5096
msgid "Minnesota"
msgstr ""
-#: languages/states.php:5072
+#: languages/states.php:5097
msgid "Mississippi"
msgstr ""
-#: languages/states.php:5073
+#: languages/states.php:5098
msgid "Missouri"
msgstr ""
-#: languages/states.php:5074
+#: languages/states.php:5099
msgid "Montana"
msgstr ""
-#: languages/states.php:5075
+#: languages/states.php:5100
msgid "Nebraska"
msgstr ""
-#: languages/states.php:5076
+#: languages/states.php:5101
msgid "Nevada"
msgstr ""
-#: languages/states.php:5077
+#: languages/states.php:5102
msgid "New Hampshire"
msgstr ""
-#: languages/states.php:5078
+#: languages/states.php:5103
msgid "New Jersey"
msgstr ""
-#: languages/states.php:5079
+#: languages/states.php:5104
msgid "New Mexico"
msgstr ""
-#: languages/states.php:5080
+#: languages/states.php:5105
msgid "New York"
msgstr ""
-#: languages/states.php:5081
+#: languages/states.php:5106
msgid "North Carolina"
msgstr ""
-#: languages/states.php:5082
+#: languages/states.php:5107
msgid "North Dakota"
msgstr ""
-#: languages/states.php:5084
+#: languages/states.php:5109
msgid "Ohio"
msgstr ""
-#: languages/states.php:5085
+#: languages/states.php:5110
msgid "Oklahoma"
msgstr ""
-#: languages/states.php:5086
+#: languages/states.php:5111
msgid "Oregon"
msgstr ""
-#: languages/states.php:5087
+#: languages/states.php:5112
msgid "Pennsylvania"
msgstr ""
-#: languages/states.php:5089
+#: languages/states.php:5114
msgid "Rhode Island"
msgstr ""
-#: languages/states.php:5090
+#: languages/states.php:5115
msgid "South Carolina"
msgstr ""
-#: languages/states.php:5091
+#: languages/states.php:5116
msgid "South Dakota"
msgstr ""
-#: languages/states.php:5092
+#: languages/states.php:5117
msgid "Tennessee"
msgstr ""
-#: languages/states.php:5093
+#: languages/states.php:5118
msgid "Texas"
msgstr ""
-#: languages/states.php:5095
+#: languages/states.php:5120
msgid "United States Virgin Islands"
msgstr ""
-#: languages/states.php:5096
+#: languages/states.php:5121
msgid "Utah"
msgstr ""
-#: languages/states.php:5097
+#: languages/states.php:5122
msgid "Vermont"
msgstr ""
-#: languages/states.php:5098
+#: languages/states.php:5123
msgid "Virginia"
msgstr ""
-#: languages/states.php:5099
+#: languages/states.php:5124
msgid "Washington"
msgstr ""
-#: languages/states.php:5100
+#: languages/states.php:5125
msgid "West Virginia"
msgstr ""
-#: languages/states.php:5101
+#: languages/states.php:5126
msgid "Wisconsin"
msgstr ""
-#: languages/states.php:5102
+#: languages/states.php:5127
msgid "Wyoming"
msgstr ""
-#: languages/states.php:5105
+#: languages/states.php:5130
msgid "Artigas Department"
msgstr ""
-#: languages/states.php:5106
+#: languages/states.php:5131
msgid "Canelones Department"
msgstr ""
-#: languages/states.php:5107
+#: languages/states.php:5132
msgid "Cerro Largo Department"
msgstr ""
-#: languages/states.php:5108
+#: languages/states.php:5133
msgid "Colonia Department"
msgstr ""
-#: languages/states.php:5109
+#: languages/states.php:5134
msgid "Durazno Department"
msgstr ""
-#: languages/states.php:5110
+#: languages/states.php:5135
msgid "Flores Department"
msgstr ""
-#: languages/states.php:5111
+#: languages/states.php:5136
msgid "Florida Department"
msgstr ""
-#: languages/states.php:5112
+#: languages/states.php:5137
msgid "Lavalleja Department"
msgstr ""
-#: languages/states.php:5113
+#: languages/states.php:5138
msgid "Maldonado Department"
msgstr ""
-#: languages/states.php:5114
+#: languages/states.php:5139
msgid "Montevideo Department"
msgstr ""
-#: languages/states.php:5115
+#: languages/states.php:5140
msgid "Paysandú Department"
msgstr ""
-#: languages/states.php:5116
+#: languages/states.php:5141
msgid "Rivera Department"
msgstr ""
-#: languages/states.php:5117
+#: languages/states.php:5142
msgid "Rocha Department"
msgstr ""
-#: languages/states.php:5118
+#: languages/states.php:5143
msgid "Río Negro Department"
msgstr ""
-#: languages/states.php:5119
+#: languages/states.php:5144
msgid "Salto Department"
msgstr ""
-#: languages/states.php:5120
+#: languages/states.php:5145
msgid "San José Department"
msgstr ""
-#: languages/states.php:5121
+#: languages/states.php:5146
msgid "Soriano Department"
msgstr ""
-#: languages/states.php:5122
+#: languages/states.php:5147
msgid "Tacuarembó Department"
msgstr ""
-#: languages/states.php:5123
+#: languages/states.php:5148
msgid "Treinta y Tres Department"
msgstr ""
-#: languages/states.php:5126
+#: languages/states.php:5151
msgid "Andijan Region"
msgstr ""
-#: languages/states.php:5127
+#: languages/states.php:5152
msgid "Bukhara Region"
msgstr ""
-#: languages/states.php:5128
+#: languages/states.php:5153
msgid "Fergana Region"
msgstr ""
-#: languages/states.php:5129
+#: languages/states.php:5154
msgid "Jizzakh Region"
msgstr ""
-#: languages/states.php:5130
+#: languages/states.php:5155
msgid "Karakalpakstan"
msgstr ""
-#: languages/states.php:5131
+#: languages/states.php:5156
msgid "Namangan Region"
msgstr ""
-#: languages/states.php:5132
+#: languages/states.php:5157
msgid "Navoiy Region"
msgstr ""
-#: languages/states.php:5133
+#: languages/states.php:5158
msgid "Qashqadaryo Region"
msgstr ""
-#: languages/states.php:5134
+#: languages/states.php:5159
msgid "Samarqand Region"
msgstr ""
-#: languages/states.php:5135
+#: languages/states.php:5160
msgid "Sirdaryo Region"
msgstr ""
-#: languages/states.php:5136
+#: languages/states.php:5161
msgid "Surxondaryo Region"
msgstr ""
-#: languages/states.php:5137
+#: languages/states.php:5162
msgid "Tashkent"
msgstr ""
-#: languages/states.php:5138
+#: languages/states.php:5163
msgid "Tashkent Region"
msgstr ""
-#: languages/states.php:5139
+#: languages/states.php:5164
msgid "Xorazm Region"
msgstr ""
-#: languages/states.php:5143
+#: languages/states.php:5168
msgid "Charlotte Parish"
msgstr ""
-#: languages/states.php:5144
+#: languages/states.php:5169
msgid "Grenadines Parish"
msgstr ""
-#: languages/states.php:5152
+#: languages/states.php:5177
msgid "Anzoátegui"
msgstr ""
-#: languages/states.php:5153
+#: languages/states.php:5178
msgid "Apure"
msgstr ""
-#: languages/states.php:5154
+#: languages/states.php:5179
msgid "Aragua"
msgstr ""
-#: languages/states.php:5155
+#: languages/states.php:5180
msgid "Barinas"
msgstr ""
-#: languages/states.php:5156
+#: languages/states.php:5181
msgid "Bolívar"
msgstr ""
-#: languages/states.php:5157
+#: languages/states.php:5182
msgid "Capital District"
msgstr ""
-#: languages/states.php:5158
+#: languages/states.php:5183
msgid "Carabobo"
msgstr ""
-#: languages/states.php:5159
+#: languages/states.php:5184
msgid "Cojedes"
msgstr ""
-#: languages/states.php:5160
+#: languages/states.php:5185
msgid "Delta Amacuro"
msgstr ""
-#: languages/states.php:5161
+#: languages/states.php:5186
msgid "Falcón"
msgstr ""
-#: languages/states.php:5162
+#: languages/states.php:5187
msgid "Federal Dependencies of Venezuela"
msgstr ""
-#: languages/states.php:5163
+#: languages/states.php:5188
msgid "Guárico"
msgstr ""
-#: languages/states.php:5164
+#: languages/states.php:5189
msgid "Lara"
msgstr ""
-#: languages/states.php:5165
+#: languages/states.php:5190
msgid "Miranda"
msgstr ""
-#: languages/states.php:5166
+#: languages/states.php:5191
msgid "Monagas"
msgstr ""
-#: languages/states.php:5167
+#: languages/states.php:5192
msgid "Mérida"
msgstr ""
-#: languages/states.php:5168
+#: languages/states.php:5193
msgid "Nueva Esparta"
msgstr ""
-#: languages/states.php:5169
+#: languages/states.php:5194
msgid "Portuguesa"
msgstr ""
-#: languages/states.php:5170
+#: languages/states.php:5195
msgid "Sucre"
msgstr ""
-#: languages/states.php:5171
+#: languages/states.php:5196
msgid "Trujillo"
msgstr ""
-#: languages/states.php:5172
+#: languages/states.php:5197
msgid "Táchira"
msgstr ""
-#: languages/states.php:5173
+#: languages/states.php:5198
msgid "Vargas"
msgstr ""
-#: languages/states.php:5174
+#: languages/states.php:5199
msgid "Yaracuy"
msgstr ""
-#: languages/states.php:5175
+#: languages/states.php:5200
msgid "Zulia"
msgstr ""
-#: languages/states.php:5180
+#: languages/states.php:5205
msgid "Quảng Ninh"
msgstr ""
-#: languages/states.php:5181
+#: languages/states.php:5206
msgid "Hòa Bình"
msgstr ""
-#: languages/states.php:5182
+#: languages/states.php:5207
msgid "Hà Tây"
msgstr ""
-#: languages/states.php:5183
+#: languages/states.php:5208
msgid "Ninh Bình"
msgstr ""
-#: languages/states.php:5184
+#: languages/states.php:5209
msgid "Thái Bình"
msgstr ""
-#: languages/states.php:5185
+#: languages/states.php:5210
msgid "Thanh Hóa"
msgstr ""
-#: languages/states.php:5186
+#: languages/states.php:5211
msgid "Nghệ An"
msgstr ""
-#: languages/states.php:5187
+#: languages/states.php:5212
msgid "Hà Tĩnh"
msgstr ""
-#: languages/states.php:5188
+#: languages/states.php:5213
msgid "Quảng Bình"
msgstr ""
-#: languages/states.php:5189
+#: languages/states.php:5214
msgid "Quảng Trị"
msgstr ""
-#: languages/states.php:5190
+#: languages/states.php:5215
msgid "Thừa Thiên-Huế"
msgstr ""
-#: languages/states.php:5191
+#: languages/states.php:5216
msgid "Quảng Nam"
msgstr ""
-#: languages/states.php:5192
+#: languages/states.php:5217
msgid "Kon Tum"
msgstr ""
-#: languages/states.php:5193
+#: languages/states.php:5218
msgid "Quảng Ngãi"
msgstr ""
-#: languages/states.php:5194
+#: languages/states.php:5219
msgid "Gia Lai"
msgstr ""
-#: languages/states.php:5195
+#: languages/states.php:5220
msgid "Bình Định"
msgstr ""
-#: languages/states.php:5196
+#: languages/states.php:5221
msgid "Phú Yên"
msgstr ""
-#: languages/states.php:5197
+#: languages/states.php:5222
msgid "Đắk Lắk"
msgstr ""
-#: languages/states.php:5198
+#: languages/states.php:5223
msgid "Khánh Hòa"
msgstr ""
-#: languages/states.php:5199
+#: languages/states.php:5224
msgid "Lâm Đồng"
msgstr ""
-#: languages/states.php:5200
+#: languages/states.php:5225
msgid "Ninh Thuận"
msgstr ""
-#: languages/states.php:5201
+#: languages/states.php:5226
msgid "Tây Ninh"
msgstr ""
-#: languages/states.php:5202
+#: languages/states.php:5227
msgid "Đồng Nai"
msgstr ""
-#: languages/states.php:5203
+#: languages/states.php:5228
msgid "Bình Thuận"
msgstr ""
-#: languages/states.php:5204
+#: languages/states.php:5229
msgid "Long An"
msgstr ""
-#: languages/states.php:5205
+#: languages/states.php:5230
msgid "Bà Rịa-Vũng Tàu"
msgstr ""
-#: languages/states.php:5206
+#: languages/states.php:5231
msgid "An Giang"
msgstr ""
-#: languages/states.php:5207
+#: languages/states.php:5232
msgid "Đồng Tháp"
msgstr ""
-#: languages/states.php:5208
+#: languages/states.php:5233
msgid "Tiền Giang"
msgstr ""
-#: languages/states.php:5209
+#: languages/states.php:5234
msgid "Kiên Giang"
msgstr ""
-#: languages/states.php:5210
+#: languages/states.php:5235
msgid "Vĩnh Long"
msgstr ""
-#: languages/states.php:5211
+#: languages/states.php:5236
msgid "Bến Tre"
msgstr ""
-#: languages/states.php:5212
+#: languages/states.php:5237
msgid "Trà Vinh"
msgstr ""
-#: languages/states.php:5213
+#: languages/states.php:5238
msgid "Sóc Trăng"
msgstr ""
-#: languages/states.php:5214
+#: languages/states.php:5239
msgid "Bắc Kạn"
msgstr ""
-#: languages/states.php:5215
+#: languages/states.php:5240
msgid "Bắc Giang"
msgstr ""
-#: languages/states.php:5216
+#: languages/states.php:5241
msgid "Bạc Liêu"
msgstr ""
-#: languages/states.php:5217
+#: languages/states.php:5242
msgid "Bắc Ninh"
msgstr ""
-#: languages/states.php:5218
+#: languages/states.php:5243
msgid "Bình Dương"
msgstr ""
-#: languages/states.php:5219
+#: languages/states.php:5244
msgid "Bình Phước"
msgstr ""
-#: languages/states.php:5220
+#: languages/states.php:5245
msgid "Cà Mau"
msgstr ""
-#: languages/states.php:5221
+#: languages/states.php:5246
msgid "Hải Dương"
msgstr ""
-#: languages/states.php:5222
+#: languages/states.php:5247
msgid "Hà Nam"
msgstr ""
-#: languages/states.php:5223
+#: languages/states.php:5248
msgid "Hưng Yên"
msgstr ""
-#: languages/states.php:5224
+#: languages/states.php:5249
msgid "Nam Định"
msgstr ""
-#: languages/states.php:5225
+#: languages/states.php:5250
msgid "Phú Thọ"
msgstr ""
-#: languages/states.php:5226
+#: languages/states.php:5251
msgid "Thái Nguyên"
msgstr ""
-#: languages/states.php:5227
+#: languages/states.php:5252
msgid "Vĩnh Phúc"
msgstr ""
-#: languages/states.php:5228
+#: languages/states.php:5253
msgid "Điện Biên"
msgstr ""
-#: languages/states.php:5229
+#: languages/states.php:5254
msgid "Đắk Nông"
msgstr ""
-#: languages/states.php:5230
+#: languages/states.php:5255
msgid "Hậu Giang"
msgstr ""
-#: languages/states.php:5231
+#: languages/states.php:5256
msgid "Cao Bằng"
msgstr ""
-#: languages/states.php:5232
+#: languages/states.php:5257
msgid "Da Nang"
msgstr ""
-#: languages/states.php:5233
+#: languages/states.php:5258
msgid "Haiphong"
msgstr ""
-#: languages/states.php:5234
+#: languages/states.php:5259
msgid "Hanoi"
msgstr ""
-#: languages/states.php:5235
+#: languages/states.php:5260
msgid "Ho Chi Minh City"
msgstr ""
-#: languages/states.php:5236
+#: languages/states.php:5261
msgid "Hà Giang"
msgstr ""
-#: languages/states.php:5237
+#: languages/states.php:5262
msgid "Lai Châu"
msgstr ""
-#: languages/states.php:5238
+#: languages/states.php:5263
msgid "Lào Cai"
msgstr ""
-#: languages/states.php:5239
+#: languages/states.php:5264
msgid "Lạng Sơn"
msgstr ""
-#: languages/states.php:5240
+#: languages/states.php:5265
msgid "Sơn La"
msgstr ""
-#: languages/states.php:5241
+#: languages/states.php:5266
msgid "Tuyên Quang"
msgstr ""
-#: languages/states.php:5242
+#: languages/states.php:5267
msgid "Yên Bái"
msgstr ""
-#: languages/states.php:5245
+#: languages/states.php:5270
msgid "Malampa"
msgstr ""
-#: languages/states.php:5246
+#: languages/states.php:5271
msgid "Penama"
msgstr ""
-#: languages/states.php:5247
+#: languages/states.php:5272
msgid "Sanma"
msgstr ""
-#: languages/states.php:5248
+#: languages/states.php:5273
msgid "Shefa"
msgstr ""
-#: languages/states.php:5249
+#: languages/states.php:5274
msgid "Tafea"
msgstr ""
-#: languages/states.php:5250
+#: languages/states.php:5275
msgid "Torba"
msgstr ""
-#: languages/states.php:5254
+#: languages/states.php:5279
msgid "A'ana"
msgstr ""
-#: languages/states.php:5255
+#: languages/states.php:5280
msgid "Aiga-i-le-Tai"
msgstr ""
-#: languages/states.php:5256
+#: languages/states.php:5281
msgid "Atua"
msgstr ""
-#: languages/states.php:5257
+#: languages/states.php:5282
msgid "Fa'asaleleaga"
msgstr ""
-#: languages/states.php:5258
+#: languages/states.php:5283
msgid "Gaga'emauga"
msgstr ""
-#: languages/states.php:5259
+#: languages/states.php:5284
msgid "Gaga'ifomauga"
msgstr ""
-#: languages/states.php:5260
+#: languages/states.php:5285
msgid "Palauli"
msgstr ""
-#: languages/states.php:5261
+#: languages/states.php:5286
msgid "Satupa'itea"
msgstr ""
-#: languages/states.php:5262
+#: languages/states.php:5287
msgid "Tuamasaga"
msgstr ""
-#: languages/states.php:5263
+#: languages/states.php:5288
msgid "Va'a-o-Fonoti"
msgstr ""
-#: languages/states.php:5264
+#: languages/states.php:5289
msgid "Vaisigano"
msgstr ""
-#: languages/states.php:5267
+#: languages/states.php:5292
msgid "Gjilan District"
msgstr ""
-#: languages/states.php:5268
+#: languages/states.php:5293
msgid "Kosovska Mitrovica District"
msgstr ""
-#: languages/states.php:5269
+#: languages/states.php:5294
msgid "Peć District"
msgstr ""
-#: languages/states.php:5270
+#: languages/states.php:5295
msgid "Pristina (Priştine)"
msgstr ""
-#: languages/states.php:5271
+#: languages/states.php:5296
msgid "Prizren District"
msgstr ""
-#: languages/states.php:5272
+#: languages/states.php:5297
msgid "Uroševac District (Ferizaj)"
msgstr ""
-#: languages/states.php:5273
+#: languages/states.php:5298
msgid "Đakovica District (Gjakove)"
msgstr ""
-#: languages/states.php:5276
+#: languages/states.php:5301
msgid "'Adan Governorate"
msgstr ""
-#: languages/states.php:5277
+#: languages/states.php:5302
msgid "'Amran Governorate"
msgstr ""
-#: languages/states.php:5278
+#: languages/states.php:5303
msgid "Abyan Governorate"
msgstr ""
-#: languages/states.php:5279
+#: languages/states.php:5304
msgid "Al Bayda' Governorate"
msgstr ""
-#: languages/states.php:5280
+#: languages/states.php:5305
msgid "Al Hudaydah Governorate"
msgstr ""
-#: languages/states.php:5281
+#: languages/states.php:5306
msgid "Al Jawf Governorate"
msgstr ""
-#: languages/states.php:5282
+#: languages/states.php:5307
msgid "Al Mahrah Governorate"
msgstr ""
-#: languages/states.php:5283
+#: languages/states.php:5308
msgid "Al Mahwit Governorate"
msgstr ""
-#: languages/states.php:5284
+#: languages/states.php:5309
msgid "Dhamar Governorate"
msgstr ""
-#: languages/states.php:5285
+#: languages/states.php:5310
msgid "Hadhramaut Governorate"
msgstr ""
-#: languages/states.php:5286
+#: languages/states.php:5311
msgid "Hajjah Governorate"
msgstr ""
-#: languages/states.php:5287
+#: languages/states.php:5312
msgid "Ibb Governorate"
msgstr ""
-#: languages/states.php:5288
+#: languages/states.php:5313
msgid "Lahij Governorate"
msgstr ""
-#: languages/states.php:5289
+#: languages/states.php:5314
msgid "Ma'rib Governorate"
msgstr ""
-#: languages/states.php:5290
+#: languages/states.php:5315
msgid "Raymah Governorate"
msgstr ""
-#: languages/states.php:5291
+#: languages/states.php:5316
msgid "Saada Governorate"
msgstr ""
-#: languages/states.php:5292
+#: languages/states.php:5317
msgid "Sana'a"
msgstr ""
-#: languages/states.php:5293
+#: languages/states.php:5318
msgid "Sana'a Governorate"
msgstr ""
-#: languages/states.php:5294
+#: languages/states.php:5319
msgid "Shabwah Governorate"
msgstr ""
-#: languages/states.php:5295
+#: languages/states.php:5320
msgid "Socotra Governorate"
msgstr ""
-#: languages/states.php:5296
+#: languages/states.php:5321
msgid "Ta'izz Governorate"
msgstr ""
-#: languages/states.php:5300
+#: languages/states.php:5325
msgid "Eastern Cape"
msgstr ""
-#: languages/states.php:5301
+#: languages/states.php:5326
msgid "Free State"
msgstr ""
-#: languages/states.php:5302
+#: languages/states.php:5327
msgid "Gauteng"
msgstr ""
-#: languages/states.php:5303
+#: languages/states.php:5328
msgid "KwaZulu-Natal"
msgstr ""
-#: languages/states.php:5304
+#: languages/states.php:5329
msgid "Limpopo"
msgstr ""
-#: languages/states.php:5305
+#: languages/states.php:5330
msgid "Mpumalanga"
msgstr ""
-#: languages/states.php:5306
+#: languages/states.php:5331
msgid "North West"
msgstr ""
-#: languages/states.php:5307
+#: languages/states.php:5332
msgid "Northern Cape"
msgstr ""
-#: languages/states.php:5308
+#: languages/states.php:5333
msgid "Western Cape"
msgstr ""
-#: languages/states.php:5311
+#: languages/states.php:5336
msgid "Muchinga Province"
msgstr ""
-#: languages/states.php:5313
+#: languages/states.php:5338
msgid "Copperbelt Province"
msgstr ""
-#: languages/states.php:5315
+#: languages/states.php:5340
msgid "Luapula Province"
msgstr ""
-#: languages/states.php:5316
+#: languages/states.php:5341
msgid "Lusaka Province"
msgstr ""
-#: languages/states.php:5318
+#: languages/states.php:5343
msgid "Northwestern Province"
msgstr ""
-#: languages/states.php:5323
+#: languages/states.php:5348
msgid "Bulawayo Province"
msgstr ""
-#: languages/states.php:5324
+#: languages/states.php:5349
msgid "Harare Province"
msgstr ""
-#: languages/states.php:5325
+#: languages/states.php:5350
msgid "Manicaland"
msgstr ""
-#: languages/states.php:5326
+#: languages/states.php:5351
msgid "Mashonaland Central Province"
msgstr ""
-#: languages/states.php:5327
+#: languages/states.php:5352
msgid "Mashonaland East Province"
msgstr ""
-#: languages/states.php:5328
+#: languages/states.php:5353
msgid "Mashonaland West Province"
msgstr ""
-#: languages/states.php:5329
+#: languages/states.php:5354
msgid "Masvingo Province"
msgstr ""
-#: languages/states.php:5330
+#: languages/states.php:5355
msgid "Matabeleland North Province"
msgstr ""
-#: languages/states.php:5331
+#: languages/states.php:5356
msgid "Matabeleland South Province"
msgstr ""
-#: languages/states.php:5332
+#: languages/states.php:5357
msgid "Midlands Province"
msgstr ""
@@ -34933,7 +35034,7 @@ msgctxt "User search result label"
msgid "%1$s (ID# %2$d)"
msgstr ""
-#: blocks/certificate-title/index.js:1
+#: blocks/certificate-title/index.js:13
msgid "Certificate of Achievement"
msgstr ""
diff --git a/languages/states.php b/languages/states.php
index bc3c6337fc..46e325c045 100644
--- a/languages/states.php
+++ b/languages/states.php
@@ -32,7 +32,7 @@
* @see llms_get_states()
*
* @since 5.0.0
- * @version 6.9.0
+ * @version 6.10.0
*/
defined( 'ABSPATH' ) || exit;
@@ -1340,35 +1340,60 @@
'SK' => __( 'Northern Red Sea Region', 'lifterlms' ),
'DK' => __( 'Southern Red Sea Region', 'lifterlms' ),
),
+ // Spanish states are taken from WooCommerce {@see https://github.com/gocodebox/lifterlms/issues/2243}.
'ES' => array(
- 'AN' => __( 'Andalusia', 'lifterlms' ),
- 'AR' => __( 'Aragon', 'lifterlms' ),
- 'AS' => __( 'Asturias', 'lifterlms' ),
- 'PM' => __( 'Balearic Islands', 'lifterlms' ),
- 'PV' => __( 'Basque Country', 'lifterlms' ),
- 'BU' => __( 'Burgos Province', 'lifterlms' ),
- 'CN' => __( 'Canary Islands', 'lifterlms' ),
- 'CB' => __( 'Cantabria', 'lifterlms' ),
- 'CL' => __( 'Castile and León', 'lifterlms' ),
- 'CM' => __( 'Castilla La Mancha', 'lifterlms' ),
- 'CT' => __( 'Catalonia', 'lifterlms' ),
+ 'C' => __( 'A Coruña', 'lifterlms' ),
+ 'VI' => __( 'Araba/Álava', 'lifterlms' ),
+ 'AB' => __( 'Albacete', 'lifterlms' ),
+ 'A' => __( 'Alicante', 'lifterlms' ),
+ 'AL' => __( 'Almería', 'lifterlms' ),
+ 'O' => __( 'Asturias', 'lifterlms' ),
+ 'AV' => __( 'Ávila', 'lifterlms' ),
+ 'BA' => __( 'Badajoz', 'lifterlms' ),
+ 'PM' => __( 'Baleares', 'lifterlms' ),
+ 'B' => __( 'Barcelona', 'lifterlms' ),
+ 'BU' => __( 'Burgos', 'lifterlms' ),
+ 'CC' => __( 'Cáceres', 'lifterlms' ),
+ 'CA' => __( 'Cádiz', 'lifterlms' ),
+ 'S' => __( 'Cantabria', 'lifterlms' ),
+ 'CS' => __( 'Castellón', 'lifterlms' ),
'CE' => __( 'Ceuta', 'lifterlms' ),
- 'EX' => __( 'Extremadura', 'lifterlms' ),
- 'GA' => __( 'Galicia', 'lifterlms' ),
- 'RI' => __( 'La Rioja', 'lifterlms' ),
- 'LE' => __( 'Léon', 'lifterlms' ),
- 'MD' => __( 'Madrid', 'lifterlms' ),
+ 'CR' => __( 'Ciudad Real', 'lifterlms' ),
+ 'CO' => __( 'Córdoba', 'lifterlms' ),
+ 'CU' => __( 'Cuenca', 'lifterlms' ),
+ 'GI' => __( 'Girona', 'lifterlms' ),
+ 'GR' => __( 'Granada', 'lifterlms' ),
+ 'GU' => __( 'Guadalajara', 'lifterlms' ),
+ 'SS' => __( 'Gipuzkoa', 'lifterlms' ),
+ 'H' => __( 'Huelva', 'lifterlms' ),
+ 'HU' => __( 'Huesca', 'lifterlms' ),
+ 'J' => __( 'Jaén', 'lifterlms' ),
+ 'LO' => __( 'La Rioja', 'lifterlms' ),
+ 'GC' => __( 'Las Palmas', 'lifterlms' ),
+ 'LE' => __( 'León', 'lifterlms' ),
+ 'L' => __( 'Lleida', 'lifterlms' ),
+ 'LU' => __( 'Lugo', 'lifterlms' ),
+ 'M' => __( 'Madrid', 'lifterlms' ),
+ 'MA' => __( 'Málaga', 'lifterlms' ),
'ML' => __( 'Melilla', 'lifterlms' ),
- 'MC' => __( 'Murcia', 'lifterlms' ),
- 'NC' => __( 'Navarra', 'lifterlms' ),
- 'P' => __( 'Palencia Province', 'lifterlms' ),
- 'SA' => __( 'Salamanca Province', 'lifterlms' ),
- 'SG' => __( 'Segovia Province', 'lifterlms' ),
- 'SO' => __( 'Soria Province', 'lifterlms' ),
- 'VC' => __( 'Valencia', 'lifterlms' ),
- 'VA' => __( 'Valladolid Province', 'lifterlms' ),
- 'ZA' => __( 'Zamora Province', 'lifterlms' ),
- 'AV' => __( 'Ávila', 'lifterlms' ),
+ 'MU' => __( 'Murcia', 'lifterlms' ),
+ 'NA' => __( 'Navarra', 'lifterlms' ),
+ 'OR' => __( 'Ourense', 'lifterlms' ),
+ 'P' => __( 'Palencia', 'lifterlms' ),
+ 'PO' => __( 'Pontevedra', 'lifterlms' ),
+ 'SA' => __( 'Salamanca', 'lifterlms' ),
+ 'TF' => __( 'Santa Cruz de Tenerife', 'lifterlms' ),
+ 'SG' => __( 'Segovia', 'lifterlms' ),
+ 'SE' => __( 'Sevilla', 'lifterlms' ),
+ 'SO' => __( 'Soria', 'lifterlms' ),
+ 'T' => __( 'Tarragona', 'lifterlms' ),
+ 'TE' => __( 'Teruel', 'lifterlms' ),
+ 'TO' => __( 'Toledo', 'lifterlms' ),
+ 'V' => __( 'Valencia', 'lifterlms' ),
+ 'VA' => __( 'Valladolid', 'lifterlms' ),
+ 'BI' => __( 'Biscay', 'lifterlms' ),
+ 'ZA' => __( 'Zamora', 'lifterlms' ),
+ 'Z' => __( 'Zaragoza', 'lifterlms' ),
),
'ET' => array(
'AA' => __( 'Addis Ababa', 'lifterlms' ),
diff --git a/lerna.json b/lerna.json
deleted file mode 100644
index a2bb50ba7c..0000000000
--- a/lerna.json
+++ /dev/null
@@ -1,6 +0,0 @@
-{
- "packages": [
- "packages/*"
- ],
- "version": "independent"
-}
diff --git a/libraries/README.md b/libraries/README.md
deleted file mode 100644
index 44ab13842b..0000000000
--- a/libraries/README.md
+++ /dev/null
@@ -1,6 +0,0 @@
-External Libraries
-==================
-
-Installation directory for plugin libraries included in the core plugin but developed outside of this repository.
-
-See [Installing for Development](../docs/installing.md) for installation instructions.
diff --git a/libraries/lifterlms-blocks/CHANGELOG.md b/libraries/lifterlms-blocks/CHANGELOG.md
new file mode 100644
index 0000000000..4a3c2c5cc6
--- /dev/null
+++ b/libraries/lifterlms-blocks/CHANGELOG.md
@@ -0,0 +1,479 @@
+LifterLMS Blocks Changelog
+==========================
+
+v2.4.3 - 2022-06-09
+-------------------
+
+##### Bug Fixes
+
++ Fixed an issue that prevented editing form confirmation fields when running WordPress 6.0. [#170](https://github.com/gocodebox/lifterlms-blocks#170)
++ Fixed field columns sizing in the block editor.
+
+
+v2.4.2 - 2022-04-07
+-------------------
+
+##### Bug Fixes
+
++ Fixed issue where the User Login form field was shown to logged-in users. [gocodebox/lifterlms#2071](https://github.com/gocodebox/lifterlms#2071)
+
+
+v2.4.1 - 2022-03-30
+-------------------
+
+##### Bug Fixes
+
++ Fixed issue when adding two custom fields of the same type resulting in the first changing its usermeta key. [#160](https://github.com/gocodebox/lifterlms-blocks/issues/160)
+
+
+v2.4.0 - 2022-02-25
+-------------------
+
+##### Updates and Enhancements
+
++ Components added to `window.llms.components` are now aware of components added to the object from other sources.
+
+##### Bug Fixes
+
++ Fixed access to non-existing variable when current user tries to edit course/membership instructors without proper permissions. [#140](https://github.com/gocodebox/lifterlms-blocks#140)
+
+
+v2.3.2 - 2022-02-22
+-------------------
+
+##### Updates and Enhancements
+
++ Added an option to specify a custom checkout form title for free access plans.
+
+
+v2.3.1 - 2022-01-26
+-------------------
+
+##### Updates and Enhancements
+
++ Resolved PHP 8.1 deprecation warnings.
+
+
+v2.3.0 - 2022-01-25
+-------------------
+
+##### New Features
+
++ Added the llms/php-template block, used by the Site Editor to load php templates.
+
+##### Updates and Enhancements
+
++ Adds support for WordPress 5.9.
++ The minimum required WordPress version is now 5.5.
+
+
+v2.2.1 - 2021-09-29
+-------------------
+
++ Bugfix: Fixed deprecated filter warning encountered when using certain development versions of the WordPress core.
+
+
+v2.2.0 - 2021-07-19
+-------------------
+
+##### Updates
+
++ **Increases minimum WordPress Core version requirement to version 5.4!**.
++ Tested and compatible with WordPress core 5.8
++ Don't load block editor assets on the "blockified" widgets screen.
++ Remove timeouts and subscription debouncing used by blocks watcher which handles the `llms/user-info-fields` redux store.
++ Stop debouncing the blocks watcher.
+
+##### Bug fixes
+
++ Confirm group blocks now configure the block's id, name, and match attributes instead of being configured in the block render via the `blocks/form-fields/group-data` module.
++ Don't define the `match` attribute during creation of a user password block.
+
+
+v2.1.1 - 2021-07-08
+-------------------
+
++ Fixed issue causing visibility controls to display for blocks which have no visibility attributes defined.
+
+
+v2.1.0 - 2021-06-28
+-------------------
+
+##### Updates
+
++ Adjusted priority of block editor JS assets to load at priority `5` instead of `999`. Resolves plugin conflicts encountered when using block-level visibility on blocks registered after visibility filters are applied.
++ Removed usage of [react-sortable-hoc](https://github.com/clauderic/react-sortable-hoc) and replaced with [dndkit](https://github.com/clauderic/dnd-kit) for drag and drop UX within the editor.
++ Refactored the instructors sidebar (on courses and memberships) as well as the option shorting (for fields with options) to utilize `dndkit`.
+
+##### Bugfixes
+
++ Fixed an issue encountered on password confirmation fields when adjusting the minimum password length option on the user password block.
+
+
+v2.0.1 - 2021-06-21
+-------------------
+
++ Use non-unique error notice IDs for reusable multiple error notice.
+
+
+v2.0.0 - 2021-06-21
+-------------------
+
+##### Updates
+
++ Adds LifterLMS User Information form building via the block editor.
++ Initially compatibility for WordPress 5.8 (full site editing). Ensures core functionality but doesn't add any exciting features.
++ Improve the visual feedback inside the editor for a block with visibility restrictions.
++ Added reusable block support for form fields.
++ Adds a user information (`[llms-user]`) shortcode inserter to rich text block toolbars.
++ Use rich text `allowedFormats` in favor of deprecated `formattingControls`
++ Improved localization of Javascript files.
+
+##### Bug Fixes
+
++ Fixed issue encountered when using lesson progression blocks outside of a lesson, thanks [@reedhewitt](https://github.com/reedhewitt)!
++ Fixed fatal errors encountered if LifterLMS core isn't active when this plugin is activated.
++ Currently selected instructors are excluded from queries for instructor users.
++ Fixed issue encountered on courses and memberships when attempting to edit instructor information.
+
+##### Backwards Incompatible Changes
+
++ Major refactor of all field-related blocks.
++ The names of many field blocks have changed.
++ Use `getDisallowedBlocks()` in favor of removed `getBlacklist()` in `block-visibility/check`.
++ Blocks restricted to specific posts have had the post object stored on the block attribute reduced to include only the minimum required properties.
++ The `Search`, `SearchPost`, and `SearchUser` components have had major changes to make them more extendable.
++ Don't render InspectorControls since the block doesn't have any actual settings.
+
+
+v2.0.0-rc.2 - 2021-06-18
+------------------------
+
++ Only load the plugin if LifterLMS is loaded
++ Update version checking method.
++ Fixed typo causing errors on WP 5.6 and earlier.
++ Fix WP 5.7 compatibility issues
++ Fixed issue encountered when using lesson progression blocks outside of a lesson, thanks [@reedhewitt](https://github.com/reedhewitt)!
+
+
+v2.0.0-rc.1 - 2021-06-15
+------------------------
+
++ Fixes issue encountered when adding a confirm group
++ Stop using merge codes in the password block
++ Improve block duplication handlers
++ Prevent confirm fields from being manually pasted outside of a confirm group
++ Adds the `llms/user-information-fields` redux store to allow for better field validation and handling
++ Improves and adds field attribute validation
++ Use rich text `allowedFormats` in favor of deprecated `formattingControls`
++ Remove the now unnecessary `uuid` field block attribute.
++ Adds WP core 5.8 compatibility on the widget and customizer screens.
++ Exclude LifterLMS field block reusables from the widgets reusable blocks screen.
++ Adds backwards compatibility for WordPress < 5.6
+
+
+v2.0.0-beta.6 - 2021-06-01
+--------------------------
+
++ (Re-)introduces user information shortcode through a block editor rich text area format button.
++ Prevent usage the "User Login" block on account edit forms (usersnames cannot be edited in WordPress).
++ Only prevent form posts from being made "draft" status on the "core" forms.
++ Modifies field localization data strategy for field validation and others.
+
+
+v2.0.0-beta.5 - 2021-05-18
+--------------------------
+
++ Add WP core 5.8 compatibility for deprecated filter `block_categories`.
++ Fixed issue encountered on courses and memberships when attempting to edit instructor information.
++ Added validation to ensure all fields have unique HTML name attributes.
++ Simplified field data storage interface to enable saving only to the usermeta table.
+
+
+v2.0.0-beta.4 - 2021-05-07
+--------------------------
+
++ Fixed error encountered when opening the block editor options menu on an `llms_form` post type.
++ Added UUID generation to all form field blocks.
++ Fixed visual issues encountered with form field blocks on wide screens in the block editor.
++ Fixed issue preventing column widths from being set after switching from a stacked layout to a columns layout for a field group.
++ Added CSS classes to various option elements in the block editor
++ Moved most inline css in the editor into a static file
++ Fixed issue encountered when reverting a form to it's default
++ Fixed dynamic block rendering errors encountered when the block is restricted to specific courses/memberships.
++ Added CSS to make input placeholder text look like a placeholder
+
+
+v2.0.0-beta.3 - 2021-04-26
+--------------------------
+
++ All form field blocks refactored and many were removed or renamed.
++ Added column support to form field blocks.
++ Added reusable block support to form field blocks.
++ Removed support for block visibility on required field blocks (email and password).
++ Added reusable block filtering to only show "supported" reusable blocks when editing a form.
++ Added utility function support for reusable blocks.
++ Fixed issues related to visual rendering of checkboxes / radio elements on custom fields.
+
+
+v2.0.0-beta.2 - 2021-03-22
+--------------------------
+
++ Fixed block editor visual issues encountered on certain blocks when block-level visibility restrictions are enabled.
+
+
+v2.0.0-beta.1 - 2021-03-22
+--------------------------
+
++ Improved Javascript localization.
++ Updated JS source files to follow (slightly modified) eslint standards as defined by `@wordpress/eslint-plugin/recommended`.
++ Disabled import of incomplete module `./formats/merge-codes`.
++ Improved the information displayed for a restricted block.
++ Don't render `InspectorControls` for the Course Syllabus block since it doesn't have any actual settings to inspect.
++ Improved the Search, SearchPost, and SearchUser components and made backwards incompatible changes to their usage.
+
+
+v1.12.0 - 2021-01-07
+--------------------
+
++ Various form and field updates in preparation for LifterLMS 5.0.0.
+
+
+v1.11.1 - 2021-01-05
+--------------------
+
++ Update the hook used for the Instructors block when displayed on membership post types.
+
+
+v1.11.0 - 2020-12-29
+--------------------
+
++ Allow the "Instructors" block to be used for memberships, thanks [@alaa-alshamy](https://github.com/alaa-alshamy)!
+
+
+v1.10.0 - 2020-11-24
+--------------------
+
++ Use the `LLMS_Assets` class to define, register, and enqueue plugin assets.
++ Added Javascript localization for block editor scripts.
+
+
+v1.9.1 - 2020-04-29
+-------------------
+
++ Fix course progress block template used when migrating a course to the block editor.
+
+
+v1.9.0 - 2020-04-29
+-------------------
+
++ Converted the course progress block into a dynamic block. Fixes an issue allowing the progress block to be visible to non-enrolled students.
++ Added a filter on the output of the Pricing Table block: `llms_blocks_render_pricing_table_block`.
+
+
+v1.8.0 - 2020-04-28
+-------------------
+
+##### Updates
+
++ Improved script dependencies definitions.
++ Updated asset paths for consistency with other LifterLMS projects.
++ Updated various WP Core references that have been deprecated (maintains backwards compatibility).
++ The Lesson Progression block is no longer rendered server-side in the block editor (minor performance improvement).
+
+##### Changes to the Classic Editor Block
+
++ The classic editor block will no longer show block visibility settings because it is impossible to use those settings to filter the block on the frontend.
++ In order to apply visibility settings to the classic editor block, place the Classic Editor within a "Group" block and apply visibility settings to the Group.
+
+##### Bug fixes
+
++ Fixed an issue encountered when using the WP Core "Table" block.
++ Fixed a few areas where `class` was being used instead of `className` to define CSS classes on elements in the block editor.
++ Fixed a user-experience issues encountered on the Course Information block when all possible information is disabled.
++ Fixed an issue causing visibility attributes to render on blocks that don't support them.
++ Fixed an issue preventing 3rd party blocks from modifying default block visibility settings.
++ Fixed a spelling error visible inside the block editor.
++ Fixed an issue causing the "Course Progress" block to be shown to non-enrolled students and visitors.
++ Removed redundant CSS from frontend.
++ Stop outputting editor CSS on the frontend.
++ Dynamic blocks with no content to render will now only output their empty render messages inside the block editor, not on the frontend.
+
+
+v1.7.3 - 2019-12-19
+-------------------
+
++ Move form ready event from domReady to block registration to ensure blocks are exposed before blocks are parsed.
+
+
+v1.7.2 - 2019-12-09
+-------------------
+
++ Bug fix: fix issue causing the block editor to encounter a fatal error when using custom post types that don't support custom fields.
+
+
+v1.7.1 - 2019-12-05
+-------------------
+
++ Bug fix: Fixed a WordPress 5.3 issues with JSON data affecting the ability to save course/membership instructors.
++ Update: Added filter, `llms_block_supports_visibility` to allow modification of the return of the check.
++ Update: Disabled block visibility on registration & account forms to prevent a potentially confusing form creation experience.
++ Update: Added block editor rendering for password type fields.
+
+
+v1.7.0 - 2019-11-08
+-------------------
+
+##### Updates
+
++ Membership post types can now use the LifterLMS Pricing Table block.
++ Membership post types are automatically migrated to the block editor (use the pricing table block instead of the pricing table action).
++ Added a block editor template for the Membership post type.
++ The block 'llms/form-field-redeem-voucher' is now only available on registration forms.
+
+##### Bug Fixes
+
++ Backwards compatibility fixes for WP Core 5.2 and earlier.
++ Perform post migrations on `current_screen` instead of `admin_enqueue_scripts`.
++ Fix an issue causing "No HTML Returned" to be displayed in place of the Lesson Progression block on free lessons when viewed by a logged-out user.
++ Import `InspectorControls` from `wp.blockEditor` and fallback to `wp.editor` to maintain backwards compatibility.
++ Fall back to `wp.editor` for `RichText` import when `wp.blockEditor` is not found.
++ Import from `wp.editor` when `wp.blockEditor` is not available.
++ Return early during renders on WP Core 5.2 and earlier where the `PluginDocumentSettingPanel` doesn't exist.
+
+
+v1.6.0 - 2019-10-24
+-------------------
+
++ Feature: Added form field blocks for use on the Forms manager.
++ Feature: Add logic for `logged_in` and `logged_out` block visibility options.
++ Update: Added isDisabled property to Search component.
++ Update: Adjusted priority of `render_block` filter to 20.
++ Bug fix: Import `InspectorControls` from `wp.blockEditor` in favor of deprecated `wp.editor`
++ Bug fix: Automatically store course/membership instructor with `post_author` data when the post is created.
++ Bug fix: Pass style rules as camelCase.
+
+
+v1.5.2 - 2019-08-14
+-------------------
+
++ Only enable REST for authenticated users with the `lifterlms_instructor` capability.
+
+
+v1.5.1 - 2019-05-17
+-------------------
+
++ Only register block visibility settings on static blocks. Fixes an issue causing core (or 3rd party) dynamic blocks from being managed within the block editor.
+
+
+v1.5.0 - 2019-05-16
+-------------------
+
++ All blocks are now registered only for post types where they can actually be used.
+
+
+v1.4.1 - 2019-05-13
+-------------------
+
++ Fixed double slashes in asset path of CSS and JS files, thanks [@pondermatic](https://github.com/pondermatic)!
+
+
+v1.4.0 - 2019-04-26
+-------------------
+
++ Added an "unmigration" utility to LifterLMS -> Status -> Tools & Utilities which can be used to remove LifterLMS blocks from courses and lessons which were migrated to the block editor structure. This tool is only available when the Classic Editor plugin is installed and enabled and it will remove blocks from ALL courses and lessons regardless of whether or not the block editor is being utilized on that post.
+
+
+v1.3.8 - 2019-03-19
+-------------------
+
++ Explicitly import jQuery when used within blocks.
+
+
+v1.3.7 - 2019-02-27
+-------------------
+
++ Fixed an issue preventing "Pricing Table" blocks from displaying on the admin panel when the current user was enrolled in the course or no payment gateways were enabled on the site.
+
+
+v1.3.6 - 2019-02-22
+-------------------
+
++ Updated the editor icons to use the new LifterLMS Icon
++ Change method for Pricing Table block re-rendering to prevent an issue resulting it always appearing that the post has unsaved data.
+
+
+v1.3.5 - 2019-02-21
+-------------------
+
++ Automatically re-renders Pricing Table blocks when access plans are saved or deleted via the course / membership access plan metabox.
+
+
+v1.3.4 - 2019-01-30
+-------------------
+
++ Add support for the Divi Builder's "Classic Editor" mode
++ Skip post migration when "Classic" mode is enabled
+
+
+v1.3.3 - 2019-01-23
+-------------------
+
++ Add conditions to check for Classic Editor settings configured to enforce classic/block for all posts.
+
+
+v1.3.2 - 2019-01-16
+-------------------
+
++ Fix issue preventing template actions from being removed from migrated courses & lessons.
+
+
+v1.3.1 - 2019-01-15
+-------------------
+
++ Move post migration checks to a callable function `llms_blocks_is_post_migrated()`
+
+
+v1.3.0 - 2019-01-09
+-------------------
+
++ Add course and membership catalog visibility settings into the block editor.
++ Fixed issue preventing the course instructors metabox from displaying when using the classic editor plugin.
+
+v1.2.0 - 2018-12-27
+-------------------
+
++ Add conditional support for page builders: Beaver Builder, Divi Builder, and Elementor.
++ Fixed issue causing LifterLMS core sales pages from outputting automatic content (like pricing tables) on migrated posts.
+
+
+v1.1.2 - 2018-12-17
+-------------------
+
++ Add a filter to the migration check on lessons & courses.
+
+
+v1.1.1 - 2018-12-14
+-------------------
+
++ Fix issue causing LifterLMS Core Actions to be removed when using the Classic Editor plugin.
+
+
+v1.1.0 - 2018-12-12
+-------------------
+
++ Editor blocks now display a lock icon when hovering/selecting a block which corresponds to the enrollment visibility settings of the block.
++ Removal of core actions is now handled by a general migrator function instead of by individual blocks.
++ Fix issue causing block visibility options to not be properly set when enrollment visibility is first enabled for a block.
+
+
+v1.0.1 - 2018-12-05
+-------------------
+
++ Made plugin url relative
+
+
+v1.0.0 - 2018-12-05
+-------------------
+
++ Initial public release
diff --git a/libraries/lifterlms-blocks/assets/css/llms-blocks-rtl.css b/libraries/lifterlms-blocks/assets/css/llms-blocks-rtl.css
new file mode 100644
index 0000000000..f31b1b16d1
--- /dev/null
+++ b/libraries/lifterlms-blocks/assets/css/llms-blocks-rtl.css
@@ -0,0 +1 @@
+.llms-cols:after,.llms-cols:before{content:" ";display:table}.llms-cols:after{clear:both}.llms-cols .llms-col{width:100%}@media (min-width:600px){.llms-cols [class*=llms-col-]{float:right}.llms-cols .llms-col-1{width:100%}.llms-cols .llms-col-2{width:50%}.llms-cols .llms-col-3{width:33.3333333333%}.llms-cols .llms-col-4{width:25%}.llms-cols .llms-col-5{width:20%}.llms-cols .llms-col-6{width:16.6666666667%}.llms-cols .llms-col-7{width:14.2857142857%}.llms-cols .llms-col-8{width:12.5%}.llms-cols .llms-col-9{width:11.1111111111%}.llms-cols .llms-col-10{width:10%}.llms-cols .llms-col-11{width:9.0909090909%}.llms-cols .llms-col-12{width:8.3333333333%}.edit-post-visual-editor .editor-block-list__block .editor-block-list__block-edit{padding-right:0;padding-left:0}}.llms-block-visibility{margin-right:auto;margin-left:auto;max-width:840px;position:relative}.llms-block-visibility>:first-child{margin-bottom:28px;margin-top:28px}.llms-block-visibility:before{border:1px solid #e0e0e0;bottom:-6px;content:"";right:-6px;position:absolute;left:-6px;top:-6px}.llms-block-visibility .llms-block-visibility--indicator{border-top:1px solid #e0e0e0;color:#555d66;margin-top:-22px;padding:0 6px}.llms-block-visibility .llms-block-visibility--indicator .dashicon,.llms-block-visibility .llms-block-visibility--indicator .llms-block-visibility--msg{vertical-align:middle}.llms-block-visibility .llms-block-visibility--indicator .llms-block-visibility--msg{font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen-Sans,Ubuntu,Cantarell,Helvetica Neue,sans-serif;font-size:13px;font-style:italic;line-height:1.4;margin-right:6px}.edit-post-settings-sidebar__panel-block .components-panel__body .llms-search input,.edit-post-sidebar .components-panel__body .llms-search input{box-shadow:none}.llms-search__menu{background:#fff!important;z-index:9999999!important}.llms-search__value-container{width:100%}#wpwrap .edit-post-visual-editor .wp-block-llms-course-information ul{list-style-type:none;margin-right:0;margin-top:.5em}.wp-block-llms-course-progress{display:flex}.wp-block-llms-course-progress .progress-bar{background:#dedede;border-radius:4px;flex:1;margin:10px 0;overflow:hidden}.wp-block-llms-course-progress .progress-bar .progress--fill{background:#2295ff;height:100%;width:50%}.wp-block-llms-course-progress span{padding-right:5px;vertical-align:middle}.llms-syllabus-wrapper{margin:15px;text-align:center}.llms-syllabus-wrapper .llms-section-title{margin:25px 0 0}.llms-course-navigation:after,.llms-course-navigation:before{content:" ";display:table}.llms-course-navigation:after{clear:both}.llms-course-navigation .llms-back-to-course,.llms-course-navigation .llms-next-lesson,.llms-course-navigation .llms-prev-lesson{width:49%}.llms-course-navigation .llms-back-to-course,.llms-course-navigation .llms-prev-lesson{float:right;margin-left:.5%}.llms-course-navigation .llms-next-lesson,.llms-course-navigation .llms-prev-lesson+.llms-back-to-course{float:left;margin-right:.5%}.llms-lesson-preview{display:inline-block;margin-top:15px;max-width:100%;position:relative;width:480px}.llms-lesson-preview .llms-lesson-link{background:#f1f1f1;color:#212121;display:block;padding:15px;text-decoration:none}.llms-lesson-preview .llms-lesson-link:after,.llms-lesson-preview .llms-lesson-link:before{content:" ";display:table}.llms-lesson-preview .llms-lesson-link:after{clear:both}.llms-lesson-preview .llms-lesson-link:hover{background:#eaeaea}.llms-lesson-preview .llms-lesson-link:visited{color:#212121}.llms-lesson-preview .llms-lesson-thumbnail{margin-bottom:10px}.llms-lesson-preview .llms-lesson-thumbnail img{display:block;width:100%}.llms-lesson-preview .llms-pre-text{text-align:right}.llms-lesson-preview .llms-lesson-title{font-weight:700;margin:0 auto 10px;text-align:right}.llms-lesson-preview .llms-lesson-title:last-child{margin-bottom:0}.llms-lesson-preview .llms-lesson-excerpt{text-align:right}.llms-lesson-preview .llms-main{float:right;width:100%}.llms-lesson-preview .llms-extra{float:left;width:15%}.llms-lesson-preview .llms-extra+.llms-main{width:85%}.llms-lesson-preview .llms-free-lesson-svg,.llms-lesson-preview .llms-lesson-complete,.llms-lesson-preview .llms-lesson-complete-placeholder,.llms-lesson-preview .llms-lesson-counter{display:block;font-size:32px;margin-bottom:15px}.llms-lesson-preview.is-complete .llms-lesson-complete,.llms-lesson-preview.is-free .llms-lesson-complete{color:#2295ff}.llms-lesson-preview .llms-icon-free{background:#2295ff;border-radius:4px;color:#f1f1f1;display:inline-block;font-size:14px;line-height:1;padding:5px 6px 4px}.llms-lesson-preview.is-incomplete .llms-lesson-complete{color:#cacaca}.llms-lesson-preview .llms-lesson-counter{font-size:16px;line-height:1}.llms-lesson-preview .llms-free-lesson-svg{fill:currentColor;height:23px;width:50px}.llms-lesson-preview p{margin-bottom:0;margin-top:0}.llms-author .label,.llms-author .name{margin-right:5px}.llms-author .avatar{border-radius:50%}.llms-author .bio{margin-top:5px}.llms-instructor-info .llms-instructors .llms-col:first-child .llms-author{margin-right:0}.llms-instructor-info .llms-instructors .llms-col:last-child .llms-author{margin-left:0}.llms-instructor-info .llms-instructors .llms-author{background:#f5f5f5;border-top:4px solid #2295ff;margin:45px 5px 5px;padding:0 10px 10px;text-align:center}.llms-instructor-info .llms-instructors .llms-author .avatar{background:#2295ff;border:4px solid #2295ff;display:block;margin:-35px auto 10px}.llms-instructor-info .llms-instructors .llms-author .llms-author-info{display:block}.llms-instructor-info .llms-instructors .llms-author .llms-author-info.name{font-weight:700}.llms-instructor-info .llms-instructors .llms-author .llms-author-info.label{font-size:85%}.llms-instructor-info .llms-instructors .llms-author .llms-author-info.bio{font-size:90%;margin-bottom:0}.wp-block[data-type="llms/lesson-progression"]{text-align:center}.wp-block[data-type="llms/lesson-progression"] button{margin:0 2px}.llms-access-plans:after,.llms-access-plans:before{content:" ";display:table}.llms-access-plans:after{clear:both}@media (min-width:600px){.llms-access-plans.cols-1 .llms-access-plan{width:100%}.llms-access-plans.cols-2 .llms-access-plan{width:50%}.llms-access-plans.cols-3 .llms-access-plan{width:33.3333333333%}.llms-access-plans.cols-4 .llms-access-plan{width:25%}.llms-access-plans.cols-5 .llms-access-plan{width:20%}}.llms-free-enroll-form{margin-bottom:0}.llms-access-plan{box-sizing:border-box;float:right;text-align:center;width:100%}.llms-access-plan .llms-access-plan-content,.llms-access-plan .llms-access-plan-footer{background:#f1f1f1}.llms-access-plan.featured .llms-access-plan-featured{background:#4ba9ff}.llms-access-plan.featured .llms-access-plan-content,.llms-access-plan.featured .llms-access-plan-footer{border-right:3px solid #2295ff;border-left:3px solid #2295ff}.llms-access-plan.featured .llms-access-plan-footer{border-bottom-color:#2295ff}.llms-access-plan.on-sale .price-regular{text-decoration:line-through}.llms-access-plan .stamp{background:#2295ff;color:#fff;font-size:11px;font-style:normal;font-weight:300;padding:2px 3px;vertical-align:top}.llms-access-plan .llms-access-plan-restrictions ul{margin:0}.llms-access-plan-featured{color:#fff;font-size:14px;font-weight:400;margin:0 2px}.llms-access-plan-content{margin:0 2px}.llms-access-plan-content .llms-access-plan-pricing{padding:10px 0 0}.llms-access-plan-title{background:#2295ff;color:#fff;margin-bottom:0;padding:10px}.llms-access-plan-pricing .llms-price-currency-symbol{font-size:14px;vertical-align:top}.llms-access-plan-price{font-size:18px;font-variant:small-caps;line-height:20px}.llms-access-plan-price .lifterlms-price{font-weight:700}.llms-access-plan-price.sale{border-bottom:1px solid #d0d0d0;border-top:1px solid #d0d0d0;padding:5px 0}.llms-access-plan-expiration,.llms-access-plan-sale-end,.llms-access-plan-schedule,.llms-access-plan-trial{font-size:15px;font-variant:small-caps;line-height:1.2}.llms-access-plan-description{font-size:16px;padding:10px 10px 0}.llms-access-plan-description ul{margin:0}.llms-access-plan-description ul li{border-bottom:1px solid #d0d0d0;list-style-type:none}.llms-access-plan-description ul li:last-child{border-bottom:none}.llms-access-plan-description div:last-child,.llms-access-plan-description img:last-child,.llms-access-plan-description li:last-child,.llms-access-plan-description p:last-child,.llms-access-plan-description ul:last-child{margin-bottom:0}.llms-access-plan-restrictions .stamp{vertical-align:baseline}.llms-access-plan-restrictions ul{margin:0}.llms-access-plan-restrictions ul li{font-size:12px;line-height:14px;list-style-type:none}.llms-access-plan-restrictions a{color:#f8954f}.llms-access-plan-restrictions a:hover{color:#f67d28}.llms-access-plan-footer{border-bottom:3px solid #f1f1f1;margin:0 2px 2px;padding:10px}.llms-access-plan-footer .llms-access-plan-pricing{padding:0 0 10px}.llms-invalid-control{margin-bottom:24px}.llms-invalid-control .components-base-control{margin-bottom:0}.llms-invalid-control .components-base-control .components-text-control__input{background-color:rgba(204,24,24,.05);border-color:#cc1818}.llms-invalid-control .llms-invalid-control--msg{background-color:rgba(204,24,24,.05);border-right:4px solid #cc1818;color:#cc1818;font-size:12px;font-style:italic;margin-bottom:0;padding:6px 8px 6px 2px}.llms-pwd-meter{border:1px solid #e35b5b;border-radius:4px;margin-top:5px;overflow:hidden}.llms-pwd-meter>div{background:rgba(227,91,91,.25);font-size:75%;padding:0 5px;width:25%}.llms-field-group .block-editor-block-list__layout *{box-sizing:border-box}.llms-fields input,.llms-fields textarea{border:1px solid #999;color:#757575;padding:4px 8px}.llms-fields input:focus::-moz-placeholder,.llms-fields textarea:focus::-moz-placeholder{opacity:0}.llms-fields input:focus:-ms-input-placeholder,.llms-fields textarea:focus:-ms-input-placeholder{opacity:0}.llms-fields input:focus::placeholder,.llms-fields textarea:focus::placeholder{opacity:0}.llms-fields input::-moz-placeholder,.llms-fields textarea::-moz-placeholder{color:#757575}.llms-fields input:-ms-input-placeholder,.llms-fields textarea:-ms-input-placeholder{color:#757575}.llms-fields input::placeholder,.llms-fields textarea::placeholder{color:#757575}.llms-fields input:not([type=radio]):not([type=checkbox]),.llms-fields select,.llms-fields textarea{width:100%}.llms-fields input:not([type=radio]){border-radius:4px}.llms-fields textarea{resize:none}.llms-fields select{max-Width:none;pointer-events:none}.llms-fields .llms-field .block-editor-rich-text__editable{display:block}.llms-fields .llms-field label.llms-is-required>div{display:inline}.llms-fields .llms-field label.llms-is-required:after{color:#dc5757;content:" *"}.llms-field-option{align-items:top;display:flex;margin-bottom:4px}.llms-field-option.llms-sort-helper{background:#fff;border:1px solid #dedede;height:auto!important;padding:5px 10px;z-index:999}.llms-field-option .llms-field-opt-default{margin-top:6px}.llms-field-option .llms-field-opt-default .components-radio-control__input{margin-left:0}.llms-field-option .llms-field-opt-default,.llms-field-option .llms-field-opt-text,.llms-field-option .llms-field-opt-text .components-base-control__field{margin-bottom:0!important}.llms-field-option .llms-field-opt-db-key{display:flex;margin-top:2px}.llms-field-option .llms-field-opt-db-key .dashicon{color:#5a5a5a;margin-top:5px}.llms-field-option .llms-field-opt-db-key .components-text-control__input{background:#f5f5f5;font-family:monospace}.llms-field-option .llms-drag-handle{cursor:-webkit-grab;cursor:grab;flex:.8;margin-top:3px;padding-top:6px}.llms-field-option .llms-del-field-opt-wrap,.llms-field-option .llms-field-opt-default-wrap{flex:1;height:-webkit-fit-content;height:-moz-fit-content;height:fit-content}.llms-field-option .llms-del-field-opt-wrap{margin-right:4px}.llms-field-option .llms-del-field-opt-wrap button{margin-top:3px}.llms-field-option .llms-del-field-opt-wrap button:hover,.llms-field-option .llms-del-field-opt-wrap button[aria-expanded=true]{color:#cc1818}.llms-field-option .llms-field-opt-text-wrap{flex:7}.llms-field-options--footer{margin-top:10px}.llms-cols-12 .llms-field{width:100%}.llms-cols-9 .llms-field{width:75%}.llms-cols-8 .llms-field{width:66.66%}.llms-cols-6 .llms-field{width:50%}.llms-cols-4 .llms-field{width:33.33%}.llms-cols-3 .llms-field{width:25%}.llms-field-group[data-field-layout=columns] .llms-cols-12,.llms-field-group[data-field-layout=columns] [class*=llms-cols-] .llms-field{width:100%}.llms-field-group[data-field-layout=columns] .llms-cols-9{width:75%}.llms-field-group[data-field-layout=columns] .llms-cols-8{width:66.66%}.llms-field-group[data-field-layout=columns] .llms-cols-6{width:50%}.llms-field-group[data-field-layout=columns] .llms-cols-4{width:33.33%}.llms-field-group[data-field-layout=columns] .llms-cols-3{width:25%}.llms-field-group[data-field-layout=columns] .block-editor-block-list__layout>.wp-block.llms-fields{display:inline-block}.llms-field-group[data-field-layout=columns] .block-editor-block-list__layout>.wp-block.llms-fields:nth-child(odd){padding-left:28px}.llms-field-group[data-field-layout=columns] .block-editor-block-list__layout>.wp-block.llms-fields:nth-child(2n){padding-right:28px}.llms-shortcodes-modal{width:800px}.llms-shortcodes-modal .llms-shortcodes-modal--main{display:flex}.llms-shortcodes-modal .llms-shortcodes-modal--main aside{flex:1;padding-left:16px}.llms-shortcodes-modal .llms-shortcodes-modal--main section{flex:2;padding-right:16px}.llms-shortcodes-modal .llms-shortcodes-modal--main .llms-table tr td,.llms-shortcodes-modal .llms-shortcodes-modal--main .llms-table tr th{text-align:right}.llms-instructor{border:1px solid #dedede;margin-bottom:-1px;padding:10px;position:relative;z-index:100}.llms-instructor .llms-instructor--header{align-items:center;display:flex}.llms-instructor .llms-instructor--header section{flex:2}.llms-instructor .llms-instructor--header section small{margin-right:3px}.llms-instructor .llms-instructor--header aside{flex:1;text-align:left}.llms-instructor .llms-instructor--header .components-button.is-small.has-icon:not(.has-text){min-width:24px;padding:0}.llms-instructor .llms-instructor--header .dashicons-star-filled{color:#ffb900;margin:2px 0 0 2px}.llms-instructor.llms-is-dragging{background:#fff;border:1px solid #dedede;box-shadow:0 4px 8px 2px #dedede;z-index:999}.llms-instructor .llms-instructor--settings{margin-top:10px}
\ No newline at end of file
diff --git a/libraries/lifterlms-blocks/assets/css/llms-blocks.css b/libraries/lifterlms-blocks/assets/css/llms-blocks.css
new file mode 100644
index 0000000000..2022ccb145
--- /dev/null
+++ b/libraries/lifterlms-blocks/assets/css/llms-blocks.css
@@ -0,0 +1 @@
+.llms-cols:after,.llms-cols:before{content:" ";display:table}.llms-cols:after{clear:both}.llms-cols .llms-col{width:100%}@media (min-width:600px){.llms-cols [class*=llms-col-]{float:left}.llms-cols .llms-col-1{width:100%}.llms-cols .llms-col-2{width:50%}.llms-cols .llms-col-3{width:33.3333333333%}.llms-cols .llms-col-4{width:25%}.llms-cols .llms-col-5{width:20%}.llms-cols .llms-col-6{width:16.6666666667%}.llms-cols .llms-col-7{width:14.2857142857%}.llms-cols .llms-col-8{width:12.5%}.llms-cols .llms-col-9{width:11.1111111111%}.llms-cols .llms-col-10{width:10%}.llms-cols .llms-col-11{width:9.0909090909%}.llms-cols .llms-col-12{width:8.3333333333%}.edit-post-visual-editor .editor-block-list__block .editor-block-list__block-edit{padding-left:0;padding-right:0}}.llms-block-visibility{margin-left:auto;margin-right:auto;max-width:840px;position:relative}.llms-block-visibility>:first-child{margin-bottom:28px;margin-top:28px}.llms-block-visibility:before{border:1px solid #e0e0e0;bottom:-6px;content:"";left:-6px;position:absolute;right:-6px;top:-6px}.llms-block-visibility .llms-block-visibility--indicator{border-top:1px solid #e0e0e0;color:#555d66;margin-top:-22px;padding:0 6px}.llms-block-visibility .llms-block-visibility--indicator .dashicon,.llms-block-visibility .llms-block-visibility--indicator .llms-block-visibility--msg{vertical-align:middle}.llms-block-visibility .llms-block-visibility--indicator .llms-block-visibility--msg{font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen-Sans,Ubuntu,Cantarell,Helvetica Neue,sans-serif;font-size:13px;font-style:italic;line-height:1.4;margin-left:6px}.edit-post-settings-sidebar__panel-block .components-panel__body .llms-search input,.edit-post-sidebar .components-panel__body .llms-search input{box-shadow:none}.llms-search__menu{background:#fff!important;z-index:9999999!important}.llms-search__value-container{width:100%}#wpwrap .edit-post-visual-editor .wp-block-llms-course-information ul{list-style-type:none;margin-left:0;margin-top:.5em}.wp-block-llms-course-progress{display:flex}.wp-block-llms-course-progress .progress-bar{background:#dedede;border-radius:4px;flex:1;margin:10px 0;overflow:hidden}.wp-block-llms-course-progress .progress-bar .progress--fill{background:#2295ff;height:100%;width:50%}.wp-block-llms-course-progress span{padding-left:5px;vertical-align:middle}.llms-syllabus-wrapper{margin:15px;text-align:center}.llms-syllabus-wrapper .llms-section-title{margin:25px 0 0}.llms-course-navigation:after,.llms-course-navigation:before{content:" ";display:table}.llms-course-navigation:after{clear:both}.llms-course-navigation .llms-back-to-course,.llms-course-navigation .llms-next-lesson,.llms-course-navigation .llms-prev-lesson{width:49%}.llms-course-navigation .llms-back-to-course,.llms-course-navigation .llms-prev-lesson{float:left;margin-right:.5%}.llms-course-navigation .llms-next-lesson,.llms-course-navigation .llms-prev-lesson+.llms-back-to-course{float:right;margin-left:.5%}.llms-lesson-preview{display:inline-block;margin-top:15px;max-width:100%;position:relative;width:480px}.llms-lesson-preview .llms-lesson-link{background:#f1f1f1;color:#212121;display:block;padding:15px;text-decoration:none}.llms-lesson-preview .llms-lesson-link:after,.llms-lesson-preview .llms-lesson-link:before{content:" ";display:table}.llms-lesson-preview .llms-lesson-link:after{clear:both}.llms-lesson-preview .llms-lesson-link:hover{background:#eaeaea}.llms-lesson-preview .llms-lesson-link:visited{color:#212121}.llms-lesson-preview .llms-lesson-thumbnail{margin-bottom:10px}.llms-lesson-preview .llms-lesson-thumbnail img{display:block;width:100%}.llms-lesson-preview .llms-pre-text{text-align:left}.llms-lesson-preview .llms-lesson-title{font-weight:700;margin:0 auto 10px;text-align:left}.llms-lesson-preview .llms-lesson-title:last-child{margin-bottom:0}.llms-lesson-preview .llms-lesson-excerpt{text-align:left}.llms-lesson-preview .llms-main{float:left;width:100%}.llms-lesson-preview .llms-extra{float:right;width:15%}.llms-lesson-preview .llms-extra+.llms-main{width:85%}.llms-lesson-preview .llms-free-lesson-svg,.llms-lesson-preview .llms-lesson-complete,.llms-lesson-preview .llms-lesson-complete-placeholder,.llms-lesson-preview .llms-lesson-counter{display:block;font-size:32px;margin-bottom:15px}.llms-lesson-preview.is-complete .llms-lesson-complete,.llms-lesson-preview.is-free .llms-lesson-complete{color:#2295ff}.llms-lesson-preview .llms-icon-free{background:#2295ff;border-radius:4px;color:#f1f1f1;display:inline-block;font-size:14px;line-height:1;padding:5px 6px 4px}.llms-lesson-preview.is-incomplete .llms-lesson-complete{color:#cacaca}.llms-lesson-preview .llms-lesson-counter{font-size:16px;line-height:1}.llms-lesson-preview .llms-free-lesson-svg{fill:currentColor;height:23px;width:50px}.llms-lesson-preview p{margin-bottom:0;margin-top:0}.llms-author .label,.llms-author .name{margin-left:5px}.llms-author .avatar{border-radius:50%}.llms-author .bio{margin-top:5px}.llms-instructor-info .llms-instructors .llms-col:first-child .llms-author{margin-left:0}.llms-instructor-info .llms-instructors .llms-col:last-child .llms-author{margin-right:0}.llms-instructor-info .llms-instructors .llms-author{background:#f5f5f5;border-top:4px solid #2295ff;margin:45px 5px 5px;padding:0 10px 10px;text-align:center}.llms-instructor-info .llms-instructors .llms-author .avatar{background:#2295ff;border:4px solid #2295ff;display:block;margin:-35px auto 10px}.llms-instructor-info .llms-instructors .llms-author .llms-author-info{display:block}.llms-instructor-info .llms-instructors .llms-author .llms-author-info.name{font-weight:700}.llms-instructor-info .llms-instructors .llms-author .llms-author-info.label{font-size:85%}.llms-instructor-info .llms-instructors .llms-author .llms-author-info.bio{font-size:90%;margin-bottom:0}.wp-block[data-type="llms/lesson-progression"]{text-align:center}.wp-block[data-type="llms/lesson-progression"] button{margin:0 2px}.llms-access-plans:after,.llms-access-plans:before{content:" ";display:table}.llms-access-plans:after{clear:both}@media (min-width:600px){.llms-access-plans.cols-1 .llms-access-plan{width:100%}.llms-access-plans.cols-2 .llms-access-plan{width:50%}.llms-access-plans.cols-3 .llms-access-plan{width:33.3333333333%}.llms-access-plans.cols-4 .llms-access-plan{width:25%}.llms-access-plans.cols-5 .llms-access-plan{width:20%}}.llms-free-enroll-form{margin-bottom:0}.llms-access-plan{box-sizing:border-box;float:left;text-align:center;width:100%}.llms-access-plan .llms-access-plan-content,.llms-access-plan .llms-access-plan-footer{background:#f1f1f1}.llms-access-plan.featured .llms-access-plan-featured{background:#4ba9ff}.llms-access-plan.featured .llms-access-plan-content,.llms-access-plan.featured .llms-access-plan-footer{border-left:3px solid #2295ff;border-right:3px solid #2295ff}.llms-access-plan.featured .llms-access-plan-footer{border-bottom-color:#2295ff}.llms-access-plan.on-sale .price-regular{text-decoration:line-through}.llms-access-plan .stamp{background:#2295ff;color:#fff;font-size:11px;font-style:normal;font-weight:300;padding:2px 3px;vertical-align:top}.llms-access-plan .llms-access-plan-restrictions ul{margin:0}.llms-access-plan-featured{color:#fff;font-size:14px;font-weight:400;margin:0 2px}.llms-access-plan-content{margin:0 2px}.llms-access-plan-content .llms-access-plan-pricing{padding:10px 0 0}.llms-access-plan-title{background:#2295ff;color:#fff;margin-bottom:0;padding:10px}.llms-access-plan-pricing .llms-price-currency-symbol{font-size:14px;vertical-align:top}.llms-access-plan-price{font-size:18px;font-variant:small-caps;line-height:20px}.llms-access-plan-price .lifterlms-price{font-weight:700}.llms-access-plan-price.sale{border-bottom:1px solid #d0d0d0;border-top:1px solid #d0d0d0;padding:5px 0}.llms-access-plan-expiration,.llms-access-plan-sale-end,.llms-access-plan-schedule,.llms-access-plan-trial{font-size:15px;font-variant:small-caps;line-height:1.2}.llms-access-plan-description{font-size:16px;padding:10px 10px 0}.llms-access-plan-description ul{margin:0}.llms-access-plan-description ul li{border-bottom:1px solid #d0d0d0;list-style-type:none}.llms-access-plan-description ul li:last-child{border-bottom:none}.llms-access-plan-description div:last-child,.llms-access-plan-description img:last-child,.llms-access-plan-description li:last-child,.llms-access-plan-description p:last-child,.llms-access-plan-description ul:last-child{margin-bottom:0}.llms-access-plan-restrictions .stamp{vertical-align:baseline}.llms-access-plan-restrictions ul{margin:0}.llms-access-plan-restrictions ul li{font-size:12px;line-height:14px;list-style-type:none}.llms-access-plan-restrictions a{color:#f8954f}.llms-access-plan-restrictions a:hover{color:#f67d28}.llms-access-plan-footer{border-bottom:3px solid #f1f1f1;margin:0 2px 2px;padding:10px}.llms-access-plan-footer .llms-access-plan-pricing{padding:0 0 10px}.llms-invalid-control{margin-bottom:24px}.llms-invalid-control .components-base-control{margin-bottom:0}.llms-invalid-control .components-base-control .components-text-control__input{background-color:rgba(204,24,24,.05);border-color:#cc1818}.llms-invalid-control .llms-invalid-control--msg{background-color:rgba(204,24,24,.05);border-left:4px solid #cc1818;color:#cc1818;font-size:12px;font-style:italic;margin-bottom:0;padding:6px 2px 6px 8px}.llms-pwd-meter{border:1px solid #e35b5b;border-radius:4px;margin-top:5px;overflow:hidden}.llms-pwd-meter>div{background:rgba(227,91,91,.25);font-size:75%;padding:0 5px;width:25%}.llms-field-group .block-editor-block-list__layout *{box-sizing:border-box}.llms-fields input,.llms-fields textarea{border:1px solid #999;color:#757575;padding:4px 8px}.llms-fields input:focus::-moz-placeholder,.llms-fields textarea:focus::-moz-placeholder{opacity:0}.llms-fields input:focus:-ms-input-placeholder,.llms-fields textarea:focus:-ms-input-placeholder{opacity:0}.llms-fields input:focus::placeholder,.llms-fields textarea:focus::placeholder{opacity:0}.llms-fields input::-moz-placeholder,.llms-fields textarea::-moz-placeholder{color:#757575}.llms-fields input:-ms-input-placeholder,.llms-fields textarea:-ms-input-placeholder{color:#757575}.llms-fields input::placeholder,.llms-fields textarea::placeholder{color:#757575}.llms-fields input:not([type=radio]):not([type=checkbox]),.llms-fields select,.llms-fields textarea{width:100%}.llms-fields input:not([type=radio]){border-radius:4px}.llms-fields textarea{resize:none}.llms-fields select{max-Width:none;pointer-events:none}.llms-fields .llms-field .block-editor-rich-text__editable{display:block}.llms-fields .llms-field label.llms-is-required>div{display:inline}.llms-fields .llms-field label.llms-is-required:after{color:#dc5757;content:" *"}.llms-field-option{align-items:top;display:flex;margin-bottom:4px}.llms-field-option.llms-sort-helper{background:#fff;border:1px solid #dedede;height:auto!important;padding:5px 10px;z-index:999}.llms-field-option .llms-field-opt-default{margin-top:6px}.llms-field-option .llms-field-opt-default .components-radio-control__input{margin-right:0}.llms-field-option .llms-field-opt-default,.llms-field-option .llms-field-opt-text,.llms-field-option .llms-field-opt-text .components-base-control__field{margin-bottom:0!important}.llms-field-option .llms-field-opt-db-key{display:flex;margin-top:2px}.llms-field-option .llms-field-opt-db-key .dashicon{color:#5a5a5a;margin-top:5px}.llms-field-option .llms-field-opt-db-key .components-text-control__input{background:#f5f5f5;font-family:monospace}.llms-field-option .llms-drag-handle{cursor:-webkit-grab;cursor:grab;flex:.8;margin-top:3px;padding-top:6px}.llms-field-option .llms-del-field-opt-wrap,.llms-field-option .llms-field-opt-default-wrap{flex:1;height:-webkit-fit-content;height:-moz-fit-content;height:fit-content}.llms-field-option .llms-del-field-opt-wrap{margin-left:4px}.llms-field-option .llms-del-field-opt-wrap button{margin-top:3px}.llms-field-option .llms-del-field-opt-wrap button:hover,.llms-field-option .llms-del-field-opt-wrap button[aria-expanded=true]{color:#cc1818}.llms-field-option .llms-field-opt-text-wrap{flex:7}.llms-field-options--footer{margin-top:10px}.llms-cols-12 .llms-field{width:100%}.llms-cols-9 .llms-field{width:75%}.llms-cols-8 .llms-field{width:66.66%}.llms-cols-6 .llms-field{width:50%}.llms-cols-4 .llms-field{width:33.33%}.llms-cols-3 .llms-field{width:25%}.llms-field-group[data-field-layout=columns] .llms-cols-12,.llms-field-group[data-field-layout=columns] [class*=llms-cols-] .llms-field{width:100%}.llms-field-group[data-field-layout=columns] .llms-cols-9{width:75%}.llms-field-group[data-field-layout=columns] .llms-cols-8{width:66.66%}.llms-field-group[data-field-layout=columns] .llms-cols-6{width:50%}.llms-field-group[data-field-layout=columns] .llms-cols-4{width:33.33%}.llms-field-group[data-field-layout=columns] .llms-cols-3{width:25%}.llms-field-group[data-field-layout=columns] .block-editor-block-list__layout>.wp-block.llms-fields{display:inline-block}.llms-field-group[data-field-layout=columns] .block-editor-block-list__layout>.wp-block.llms-fields:nth-child(odd){padding-right:28px}.llms-field-group[data-field-layout=columns] .block-editor-block-list__layout>.wp-block.llms-fields:nth-child(2n){padding-left:28px}.llms-shortcodes-modal{width:800px}.llms-shortcodes-modal .llms-shortcodes-modal--main{display:flex}.llms-shortcodes-modal .llms-shortcodes-modal--main aside{flex:1;padding-right:16px}.llms-shortcodes-modal .llms-shortcodes-modal--main section{flex:2;padding-left:16px}.llms-shortcodes-modal .llms-shortcodes-modal--main .llms-table tr td,.llms-shortcodes-modal .llms-shortcodes-modal--main .llms-table tr th{text-align:left}.llms-instructor{border:1px solid #dedede;margin-bottom:-1px;padding:10px;position:relative;z-index:100}.llms-instructor .llms-instructor--header{align-items:center;display:flex}.llms-instructor .llms-instructor--header section{flex:2}.llms-instructor .llms-instructor--header section small{margin-left:3px}.llms-instructor .llms-instructor--header aside{flex:1;text-align:right}.llms-instructor .llms-instructor--header .components-button.is-small.has-icon:not(.has-text){min-width:24px;padding:0}.llms-instructor .llms-instructor--header .dashicons-star-filled{color:#ffb900;margin:2px 2px 0 0}.llms-instructor.llms-is-dragging{background:#fff;border:1px solid #dedede;box-shadow:0 4px 8px 2px #dedede;z-index:999}.llms-instructor .llms-instructor--settings{margin-top:10px}
\ No newline at end of file
diff --git a/libraries/lifterlms-blocks/assets/js/llms-blocks-backwards-compat.asset.php b/libraries/lifterlms-blocks/assets/js/llms-blocks-backwards-compat.asset.php
new file mode 100644
index 0000000000..2bf63b8a9a
--- /dev/null
+++ b/libraries/lifterlms-blocks/assets/js/llms-blocks-backwards-compat.asset.php
@@ -0,0 +1 @@
+ array('lodash', 'wp-polyfill', 'wp-redux-routine'), 'version' => '5f9ccb06c2e41ea67be1');
diff --git a/libraries/lifterlms-blocks/assets/js/llms-blocks-backwards-compat.js b/libraries/lifterlms-blocks/assets/js/llms-blocks-backwards-compat.js
new file mode 100644
index 0000000000..6613118add
--- /dev/null
+++ b/libraries/lifterlms-blocks/assets/js/llms-blocks-backwards-compat.js
@@ -0,0 +1 @@
+(()=>{var e={909:e=>{"use strict";function t(e){return t="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},t(e)}function r(e,t){for(var r=0;r1&&void 0!==arguments[1]?arguments[1]:this;this._map.forEach((function(o,i){null!==i&&"object"===t(i)&&(o=o[1]),e.call(n,o,i,r)}))}},{key:"clear",value:function(){this._map=new Map,this._arrayTreeMap=new Map,this._objectTreeMap=new Map}},{key:"size",get:function(){return this._map.size}}],i&&r(o.prototype,i),e}();e.exports=o},884:e=>{e.exports=function(e){var t,r=Object.keys(e);return t=function(){var e,t,n;for(e="return {",t=0;t{var t=e&&e.__esModule?()=>e.default:()=>e;return r.d(t,{a:t}),t},r.d=(e,t)=>{for(var n in t)r.o(t,n)&&!r.o(e,n)&&Object.defineProperty(e,n,{enumerable:!0,get:t[n]})},r.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t),r.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},(()=>{"use strict";var e={};r.r(e),r.d(e,{getCachedResolvers:()=>U,getIsResolving:()=>N,hasFinishedResolution:()=>j,hasStartedResolution:()=>A,isResolving:()=>L});var t={};r.r(t),r.d(t,{finishResolution:()=>k,finishResolutions:()=>x,invalidateResolution:()=>V,invalidateResolutionForStore:()=>C,invalidateResolutionForStoreSelector:()=>D,startResolution:()=>P,startResolutions:()=>F});var n=r(884),o=r.n(n);const i=window.lodash;function s(e,t,r){return t in e?Object.defineProperty(e,t,{value:r,enumerable:!0,configurable:!0,writable:!0}):e[t]=r,e}function c(e,t){var r=Object.keys(e);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(e);t&&(n=n.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),r.push.apply(r,n)}return r}function u(e){for(var t=1;t({storeKey:t,selectorName:r,args:n})=>e.select(t)[r](...n))),"@@data/RESOLVE_SELECT":_((e=>({storeKey:t,selectorName:r,args:n})=>{const o=e.select(t)[r].hasResolver?"resolveSelect":"select";return e[o](t)[r](...n)})),"@@data/DISPATCH":_((e=>({storeKey:t,actionName:r,args:n})=>e.dispatch(t)[r](...n)))},w=()=>e=>t=>{return!(r=t)||"object"!=typeof r&&"function"!=typeof r||"function"!=typeof r.then?e(t):t.then((t=>{if(t)return e(t)}));var r},E="core/data",m=(e,t)=>()=>r=>n=>{const o=e.select(E).getCachedResolvers(t);return Object.entries(o).forEach((([r,o])=>{const s=(0,i.get)(e.stores,[t,"resolvers",r]);s&&s.shouldInvalidate&&o.forEach(((o,i)=>{!1===o&&s.shouldInvalidate(n,...i)&&e.dispatch(E).invalidateResolution(t,r,i)}))})),r(n)},T=("selectorName",e=>(t={},r)=>{const n=r.selectorName;if(void 0===n)return t;const o=e(t[n],r);return o===t[n]?t:{...t,[n]:o}})(((e=new(S()),t)=>{switch(t.type){case"START_RESOLUTION":case"FINISH_RESOLUTION":{const r="START_RESOLUTION"===t.type,n=new(S())(e);return n.set(t.args,r),n}case"START_RESOLUTIONS":case"FINISH_RESOLUTIONS":{const r="START_RESOLUTIONS"===t.type,n=new(S())(e);for(const e of t.args)n.set(e,r);return n}case"INVALIDATE_RESOLUTION":{const r=new(S())(e);return r.delete(t.args),r}}return e}));const I=(e={},t)=>{switch(t.type){case"INVALIDATE_RESOLUTION_FOR_STORE":return{};case"INVALIDATE_RESOLUTION_FOR_STORE_SELECTOR":return(0,i.has)(e,[t.selectorName])?(0,i.omit)(e,[t.selectorName]):e;case"START_RESOLUTION":case"FINISH_RESOLUTION":case"START_RESOLUTIONS":case"FINISH_RESOLUTIONS":case"INVALIDATE_RESOLUTION":return T(e,t)}return e};function N(e,t,r){const n=(0,i.get)(e,[t]);if(n)return n.get(r)}function A(e,t,r=[]){return void 0!==N(e,t,r)}function j(e,t,r=[]){return!1===N(e,t,r)}function L(e,t,r=[]){return!0===N(e,t,r)}function U(e){return e}function P(e,t){return{type:"START_RESOLUTION",selectorName:e,args:t}}function k(e,t){return{type:"FINISH_RESOLUTION",selectorName:e,args:t}}function F(e,t){return{type:"START_RESOLUTIONS",selectorName:e,args:t}}function x(e,t){return{type:"FINISH_RESOLUTIONS",selectorName:e,args:t}}function V(e,t){return{type:"INVALIDATE_RESOLUTION",selectorName:e,args:t}}function C(){return{type:"INVALIDATE_RESOLUTION_FOR_STORE"}}function D(e){return{type:"INVALIDATE_RESOLUTION_FOR_STORE_SELECTOR",selectorName:e}}function M(r,n){return{name:r,instantiate:s=>{const c=n.reducer,u=function(e,t,r,n){const s={...t.controls,...R},c=(0,i.mapValues)(s,(e=>e.isRegistryControl?e(r):e)),u=[m(r,e),w,b()(c)];var a;t.__experimentalUseThunks&&u.push((a=n,()=>e=>t=>"function"==typeof t?t(a):e(t)));const l=[y(...u)];"undefined"!=typeof window&&window.__REDUX_DEVTOOLS_EXTENSION__&&l.push(window.__REDUX_DEVTOOLS_EXTENSION__({name:e,instanceId:e}));const{reducer:f,initialState:p}=t;return h(o()({metadata:I,root:f}),{root:p},(0,i.flowRight)(l))}(r,n,s,{registry:s,get dispatch(){return Object.assign((e=>u.dispatch(e)),v())},get select(){return Object.assign((e=>e(u.__unstableOriginalGetState())),g())},get resolveSelect(){return O()}}),a=function(){const e={};return{isRunning:(t,r)=>e[t]&&e[t].get(r),clear(t,r){e[t]&&e[t].delete(r)},markAsRunning(t,r){e[t]||(e[t]=new(S())),e[t].set(r,!0)}}}();let l;const f=function(e,t){return(0,i.mapValues)(e,(e=>(...r)=>Promise.resolve(t.dispatch(e(...r)))))}({...t,...n.actions},u);let p=function(e,t){return(0,i.mapValues)(e,(e=>{const r=function(){const r=arguments.length,n=new Array(r+1);n[0]=t.__unstableOriginalGetState();for(let e=0;e(t,...r)=>e(t.metadata,...r))),...(0,i.mapValues)(n.selectors,(e=>(e.isRegistrySelector&&(e.registry=s),(t,...r)=>e(t.root,...r))))},u);if(n.resolvers){const e=function(e,t,r,n){const o=(0,i.mapValues)(e,(e=>e.fulfill?e:{...e,fulfill:e}));return{resolvers:o,selectors:(0,i.mapValues)(t,((t,s)=>{const c=e[s];if(!c)return t.hasResolver=!1,t;const u=(...e)=>(async function(){const t=r.getState();if(n.isRunning(s,e)||"function"==typeof c.isFulfilled&&c.isFulfilled(t,...e))return;const{metadata:u}=r.__unstableOriginalGetState();A(u,s,e)||(n.markAsRunning(s,e),setTimeout((async()=>{n.clear(s,e),r.dispatch(P(s,e)),await async function(e,t,r,...n){const o=(0,i.get)(t,[r]);if(!o)return;const s=o.fulfill(...n);s&&await e.dispatch(s)}(r,o,s,...e),r.dispatch(k(s,e))})))}(...e),t(...e));return u.hasResolver=!0,u}))}}(n.resolvers,p,u,a);l=e.resolvers,p=e.selectors}const d=function(e,t){return(0,i.mapValues)((0,i.omit)(e,["getIsResolving","hasStartedResolution","hasFinishedResolution","isResolving","getCachedResolvers"]),((r,n)=>(...o)=>new Promise((i=>{const s=()=>e.hasFinishedResolution(n,o),c=()=>r.apply(null,o),u=c();if(s())return i(u);const a=t.subscribe((()=>{s()&&(a(),i(c()))}))}))))}(p,u),g=()=>p,v=()=>f,O=()=>d;u.__unstableOriginalGetState=u.getState,u.getState=()=>u.__unstableOriginalGetState().root;const _=u&&(e=>{let t=u.__unstableOriginalGetState();return u.subscribe((()=>{const r=u.__unstableOriginalGetState(),n=r!==t;t=r,n&&e()}))});return{reducer:c,store:u,actions:f,selectors:p,resolvers:l,getSelectors:g,getResolveSelectors:O,getActions:v,subscribe:_}}}}const G=function(e={},t=null){const r={};let n=[];const o=new Set;function s(){n.forEach((e=>e()))}const c=e=>(n.push(e),()=>{n=(0,i.without)(n,e)});function u(e,t){if("function"!=typeof t.getSelectors)throw new TypeError("config.getSelectors must be a function");if("function"!=typeof t.getActions)throw new TypeError("config.getActions must be a function");if("function"!=typeof t.subscribe)throw new TypeError("config.subscribe must be a function");r[e]=t,t.subscribe(s)}let a={registerGenericStore:u,stores:r,namespaces:r,subscribe:c,select:function(e){const n=(0,i.isObject)(e)?e.name:e;o.add(n);const s=r[n];return s?s.getSelectors():t&&t.select(n)},resolveSelect:function(e){const n=(0,i.isObject)(e)?e.name:e;o.add(n);const s=r[n];return s?s.getResolveSelectors():t&&t.resolveSelect(n)},dispatch:function(e){const n=(0,i.isObject)(e)?e.name:e,o=r[n];return o?o.getActions():t&&t.dispatch(n)},use:function(e,t){return a={...a,...e(a,t)},a},register:function(e){u(e.name,e.instantiate(a))},__experimentalMarkListeningStores:function(e,t){o.clear();const r=e.call(this);return t.current=Array.from(o),r},__experimentalSubscribeStore:function(e,n){return e in r?r[e].subscribe(n):t?t.__experimentalSubscribeStore(e,n):c(n)},registerStore:(e,t)=>{if(!t.reducer)throw new TypeError("Must specify store reducer");const r=M(e,t).instantiate(a);return u(e,r),r.store}};return u(E,function(e){const t=t=>(r,...n)=>e.select(r)[t](...n),r=t=>(r,...n)=>e.dispatch(r)[t](...n);return{getSelectors:()=>["getIsResolving","hasStartedResolution","hasFinishedResolution","isResolving","getCachedResolvers"].reduce(((e,r)=>({...e,[r]:t(r)})),{}),getActions:()=>["startResolution","finishResolution","invalidateResolution","invalidateResolutionForStore","invalidateResolutionForStoreSelector"].reduce(((e,t)=>({...e,[t]:r(t)})),{}),subscribe:()=>()=>{}}}(a)),Object.entries(e).forEach((([e,t])=>a.registerStore(e,t))),t&&t.subscribe(s),l=a,(0,i.mapValues)(l,((e,t)=>"function"!=typeof e?e:function(){return a[t].apply(null,arguments)}));var l}(),H=(G.select,G.resolveSelect,G.dispatch,G.subscribe,G.registerGenericStore,G.registerStore,G.use,G.register);window.wp.blockEditor.store="core/block-editor",window.wp.editor.store="core/editor",window.wp.notices.store="core/notices",window.wp.data={...window.wp.data,createReduxStore:M,register:H}})()})();
\ No newline at end of file
diff --git a/libraries/lifterlms-blocks/assets/js/llms-blocks.asset.php b/libraries/lifterlms-blocks/assets/js/llms-blocks.asset.php
new file mode 100644
index 0000000000..6e0c534b34
--- /dev/null
+++ b/libraries/lifterlms-blocks/assets/js/llms-blocks.asset.php
@@ -0,0 +1 @@
+ array('jquery', 'lodash', 'react', 'react-dom', 'wp-block-editor', 'wp-blocks', 'wp-components', 'wp-compose', 'wp-data', 'wp-dom-ready', 'wp-edit-post', 'wp-editor', 'wp-element', 'wp-hooks', 'wp-i18n', 'wp-notices', 'wp-plugins', 'wp-polyfill', 'wp-rich-text', 'wp-server-side-render', 'wp-url'), 'version' => 'c66faf9c88a830073675');
diff --git a/libraries/lifterlms-blocks/assets/js/llms-blocks.js b/libraries/lifterlms-blocks/assets/js/llms-blocks.js
new file mode 100644
index 0000000000..5fa55c432a
--- /dev/null
+++ b/libraries/lifterlms-blocks/assets/js/llms-blocks.js
@@ -0,0 +1,24 @@
+(()=>{var e={679:(e,t,n)=>{"use strict";var r=n(296),o={childContextTypes:!0,contextType:!0,contextTypes:!0,defaultProps:!0,displayName:!0,getDefaultProps:!0,getDerivedStateFromError:!0,getDerivedStateFromProps:!0,mixins:!0,propTypes:!0,type:!0},s={name:!0,length:!0,prototype:!0,caller:!0,callee:!0,arguments:!0,arity:!0},l={$$typeof:!0,compare:!0,defaultProps:!0,displayName:!0,propTypes:!0,type:!0},i={};function a(e){return r.isMemo(e)?l:i[e.$$typeof]||o}i[r.ForwardRef]={$$typeof:!0,render:!0,defaultProps:!0,displayName:!0,propTypes:!0},i[r.Memo]=l;var c=Object.defineProperty,u=Object.getOwnPropertyNames,d=Object.getOwnPropertySymbols,p=Object.getOwnPropertyDescriptor,f=Object.getPrototypeOf,m=Object.prototype;e.exports=function e(t,n,r){if("string"!=typeof n){if(m){var o=f(n);o&&o!==m&&e(t,o,r)}var l=u(n);d&&(l=l.concat(d(n)));for(var i=a(t),h=a(n),g=0;g{"use strict";var n="function"==typeof Symbol&&Symbol.for,r=n?Symbol.for("react.element"):60103,o=n?Symbol.for("react.portal"):60106,s=n?Symbol.for("react.fragment"):60107,l=n?Symbol.for("react.strict_mode"):60108,i=n?Symbol.for("react.profiler"):60114,a=n?Symbol.for("react.provider"):60109,c=n?Symbol.for("react.context"):60110,u=n?Symbol.for("react.async_mode"):60111,d=n?Symbol.for("react.concurrent_mode"):60111,p=n?Symbol.for("react.forward_ref"):60112,f=n?Symbol.for("react.suspense"):60113,m=n?Symbol.for("react.suspense_list"):60120,h=n?Symbol.for("react.memo"):60115,g=n?Symbol.for("react.lazy"):60116,v=n?Symbol.for("react.block"):60121,b=n?Symbol.for("react.fundamental"):60117,_=n?Symbol.for("react.responder"):60118,y=n?Symbol.for("react.scope"):60119;function w(e){if("object"==typeof e&&null!==e){var t=e.$$typeof;switch(t){case r:switch(e=e.type){case u:case d:case s:case i:case l:case f:return e;default:switch(e=e&&e.$$typeof){case c:case p:case g:case h:case a:return e;default:return t}}case o:return t}}}function E(e){return w(e)===d}t.AsyncMode=u,t.ConcurrentMode=d,t.ContextConsumer=c,t.ContextProvider=a,t.Element=r,t.ForwardRef=p,t.Fragment=s,t.Lazy=g,t.Memo=h,t.Portal=o,t.Profiler=i,t.StrictMode=l,t.Suspense=f,t.isAsyncMode=function(e){return E(e)||w(e)===u},t.isConcurrentMode=E,t.isContextConsumer=function(e){return w(e)===c},t.isContextProvider=function(e){return w(e)===a},t.isElement=function(e){return"object"==typeof e&&null!==e&&e.$$typeof===r},t.isForwardRef=function(e){return w(e)===p},t.isFragment=function(e){return w(e)===s},t.isLazy=function(e){return w(e)===g},t.isMemo=function(e){return w(e)===h},t.isPortal=function(e){return w(e)===o},t.isProfiler=function(e){return w(e)===i},t.isStrictMode=function(e){return w(e)===l},t.isSuspense=function(e){return w(e)===f},t.isValidElementType=function(e){return"string"==typeof e||"function"==typeof e||e===s||e===d||e===i||e===l||e===f||e===m||"object"==typeof e&&null!==e&&(e.$$typeof===g||e.$$typeof===h||e.$$typeof===a||e.$$typeof===c||e.$$typeof===p||e.$$typeof===b||e.$$typeof===_||e.$$typeof===y||e.$$typeof===v)},t.typeOf=w},296:(e,t,n)=>{"use strict";e.exports=n(103)},703:(e,t,n)=>{"use strict";var r=n(414);function o(){}function s(){}s.resetWarningCache=o,e.exports=function(){function e(e,t,n,o,s,l){if(l!==r){var i=new Error("Calling PropTypes validators directly is not supported by the `prop-types` package. Use PropTypes.checkPropTypes() to call them. Read more at http://fb.me/use-check-prop-types");throw i.name="Invariant Violation",i}}function t(){return e}e.isRequired=e;var n={array:e,bigint:e,bool:e,func:e,number:e,object:e,string:e,symbol:e,any:e,arrayOf:t,element:e,elementType:e,instanceOf:t,node:e,objectOf:t,oneOf:t,oneOfType:t,shape:t,exact:t,checkPropTypes:s,resetWarningCache:o};return n.PropTypes=n,n}},581:(e,t,n)=>{e.exports=n(703)()},414:e=>{"use strict";e.exports="SECRET_DO_NOT_PASS_THIS_OR_YOU_WILL_BE_FIRED"},639:(e,t,n)=>{"use strict";var r=Object.assign||function(e){for(var t=1;t=0||Object.prototype.hasOwnProperty.call(e,r)&&(n[r]=e[r]);return n}(this.props,[]);return function(e){u.forEach((function(t){return delete e[t]}))}(o),o.className=this.props.inputClassName,o.id=this.state.inputId,o.style=n,l.default.createElement("div",{className:this.props.className,style:t},this.renderStyles(),l.default.createElement("input",r({},o,{ref:this.inputRef})),l.default.createElement("div",{ref:this.sizerRef,style:c},e),this.props.placeholder?l.default.createElement("div",{ref:this.placeHolderSizerRef,style:c},this.props.placeholder):null)}}]),t}(s.Component);m.propTypes={className:i.default.string,defaultValue:i.default.any,extraWidth:i.default.oneOfType([i.default.number,i.default.string]),id:i.default.string,injectStyles:i.default.bool,inputClassName:i.default.string,inputRef:i.default.func,inputStyle:i.default.object,minWidth:i.default.oneOfType([i.default.number,i.default.string]),onAutosize:i.default.func,onChange:i.default.func,placeholder:i.default.string,placeholderIsMinWidth:i.default.bool,style:i.default.object,value:i.default.any},m.defaultProps={minWidth:1,injectStyles:!0},t.Z=m},774:function(e,t){!function(e){"use strict";function t(e,t,n,r){var o,s=!1,l=0;function i(){o&&clearTimeout(o)}function a(){for(var a=arguments.length,c=new Array(a),u=0;ue?f():!0!==t&&(o=setTimeout(r?m:f,void 0===r?e-p:e)))}return"boolean"!=typeof t&&(r=n,n=t,t=void 0),a.cancel=function(){i(),s=!0},a}e.debounce=function(e,n,r){return void 0===r?t(e,n,!1):t(e,r,!1!==n)},e.throttle=t,Object.defineProperty(e,"__esModule",{value:!0})}(t)},196:e=>{"use strict";e.exports=window.React}},t={};function n(r){var o=t[r];if(void 0!==o)return o.exports;var s=t[r]={exports:{}};return e[r].call(s.exports,s,s.exports,n),s.exports}n.n=e=>{var t=e&&e.__esModule?()=>e.default:()=>e;return n.d(t,{a:t}),t},n.d=(e,t)=>{for(var r in t)n.o(t,r)&&!n.o(e,r)&&Object.defineProperty(e,r,{enumerable:!0,get:t[r]})},n.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t),n.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},(()=>{"use strict";var e={};n.r(e),n.d(e,{addField:()=>oo,deleteField:()=>so,editField:()=>lo,loadField:()=>io,receiveFields:()=>co,renameField:()=>uo,resetFields:()=>po,unloadField:()=>ao});var t={};n.r(t),n.d(t,{fieldExists:()=>fo,getField:()=>mo,getFieldBy:()=>ho,getFields:()=>go,getLoadedFields:()=>vo,isDuplicate:()=>bo,isLoaded:()=>_o});var r={};n.r(r),n.d(r,{name:()=>So,postTypes:()=>Oo,settings:()=>Io});var o={};n.r(o),n.d(o,{name:()=>Po,postTypes:()=>Ro,settings:()=>Mo});var s={};n.r(s),n.d(s,{name:()=>Ao,postTypes:()=>Lo,settings:()=>No});var l={};n.r(l),n.d(l,{name:()=>Bo,postTypes:()=>$o,settings:()=>Ho});var i={};n.r(i),n.d(i,{name:()=>qo,postTypes:()=>zo,settings:()=>Wo});var a={};n.r(a),n.d(a,{name:()=>Go,postTypes:()=>Ko,settings:()=>Yo});var c={};n.r(c),n.d(c,{name:()=>Xo,postTypes:()=>Qo,settings:()=>Zo});var u={};n.r(u),n.d(u,{name:()=>ns,postTypes:()=>rs,settings:()=>os});var d={};n.r(d),n.d(d,{name:()=>ss,settings:()=>ls});var p={};n.r(p),n.d(p,{Search:()=>Vr,SearchPost:()=>Br,SearchUser:()=>ys,SortableDragHandle:()=>vi,SortableList:()=>_i});var f={};n.r(f),n.d(f,{composed:()=>Vi,name:()=>Ni,postTypes:()=>Fi,settings:()=>zi});var m={};n.r(m),n.d(m,{composed:()=>Yi,name:()=>Gi,postTypes:()=>Ki,settings:()=>Xi});var h={};n.r(h),n.d(h,{composed:()=>ea,name:()=>Zi,postTypes:()=>Ji,settings:()=>ta});var g={};n.r(g),n.d(g,{composed:()=>sa,name:()=>ra,postTypes:()=>oa,settings:()=>la});var v={};n.r(v),n.d(v,{composed:()=>pa,name:()=>ua,postTypes:()=>da,settings:()=>ma});var b={};n.r(b),n.d(b,{composed:()=>ga,name:()=>ha,postTypes:()=>da,settings:()=>va});var _={};n.r(_),n.d(_,{composed:()=>_a,name:()=>ba,postTypes:()=>da,settings:()=>ya});var y={};n.r(y),n.d(y,{composed:()=>Ea,name:()=>wa,postTypes:()=>da,settings:()=>xa});var w={};n.r(w),n.d(w,{composed:()=>ka,name:()=>Ca,postTypes:()=>da,settings:()=>Sa});var E={};n.r(E),n.d(E,{composed:()=>Ia,name:()=>Oa,postTypes:()=>da,settings:()=>Ta});var x={};n.r(x),n.d(x,{composed:()=>Pa,name:()=>Da,postTypes:()=>da,settings:()=>Ra});var C={};n.r(C),n.d(C,{composed:()=>La,name:()=>Ma,postTypes:()=>da,settings:()=>Aa});var k={};n.r(k),n.d(k,{composed:()=>Va,name:()=>Na,postTypes:()=>Fa,settings:()=>Ba});var S={};n.r(S),n.d(S,{composed:()=>Ha,name:()=>$a,postTypes:()=>da,settings:()=>ja});var O={};n.r(O),n.d(O,{composed:()=>Wa,name:()=>qa,postTypes:()=>za,settings:()=>Ga});var I={};n.r(I),n.d(I,{composed:()=>Xa,name:()=>Ka,postTypes:()=>Ya,settings:()=>Qa});var T={};n.r(T),n.d(T,{composed:()=>Ja,name:()=>Za,postTypes:()=>da,settings:()=>ec});var D={};n.r(D),n.d(D,{composed:()=>nc,name:()=>tc,postTypes:()=>da,settings:()=>rc});var P={};n.r(P),n.d(P,{composed:()=>sc,name:()=>oc,postTypes:()=>da,settings:()=>lc});var R={};n.r(R),n.d(R,{composed:()=>ac,name:()=>ic,postTypes:()=>oa,settings:()=>cc});var M={};n.r(M),n.d(M,{composed:()=>pc,name:()=>uc,postTypes:()=>dc,settings:()=>fc});var L={};n.r(L),n.d(L,{composed:()=>hc,name:()=>mc,postTypes:()=>oa,settings:()=>gc});var A={};n.r(A),n.d(A,{composed:()=>bc,name:()=>vc,postTypes:()=>da,settings:()=>_c});var N={};n.r(N),n.d(N,{composed:()=>wc,name:()=>yc,postTypes:()=>da,settings:()=>Ec});var F={};n.r(F),n.d(F,{checkboxes:()=>m,confirmGroup:()=>f,radio:()=>h,redeemVoucher:()=>_,select:()=>g,text:()=>v,textarea:()=>b,userAddress:()=>O,userAddressCity:()=>P,userAddressCountry:()=>R,userAddressPostalCode:()=>A,userAddressRegion:()=>M,userAddressState:()=>L,userAddressStreet:()=>I,userAddressStreetPrimary:()=>T,userAddressStreetSecondary:()=>D,userDisplayName:()=>y,userEmail:()=>E,userFirstName:()=>x,userLastName:()=>C,userLogin:()=>w,userNames:()=>k,userPassword:()=>S,userPhone:()=>N});const V=window.wp.hooks;function B(e,t){let n=!0;return(-1!==window.llms.dynamic_blocks.indexOf(t)||e.supports&&!1===e.supports.llms_visibility||(0,V.applyFilters)("llms_block_visibility_disallowed_blocks",["core/freeform","llms/php-template"]).includes(t))&&(n=!1),(0,V.applyFilters)("llms_block_supports_visibility",n,e,t)}const $=window.wp.element,H=window.wp.i18n,U=window.wp.compose,j=window.wp.blockEditor,q=window.wp.components,z={all:(0,H.__)("everyone","lifterlms"),enrolled:(0,H.__)("enrolled users","lifterlms"),not_enrolled:(0,H.__)("non-enrolled users or visitors","lifterlms"),logged_in:(0,H.__)("logged in users","lifterlms"),logged_out:(0,H.__)("logged out users","lifterlms")},W=Object.keys(z).map((e=>({label:z[e],value:e})));class G extends $.Component{render(){const{llms_visibility:e}=this.props.attributes,{children:t}=this.props;return"all"===e?t:(0,$.createElement)("div",{className:"llms-block-visibility"},t,(0,$.createElement)("div",{className:"llms-block-visibility--indicator"},(0,$.createElement)(q.Dashicon,{icon:"visibility"}),(0,$.createElement)("span",{className:"llms-block-visibility--msg"},(0,H.sprintf)(// Translators: %s = visibility setting label.
+(0,H.__)("This block is only visible to %s","lifterlms"),z[n=e]||n))));var n}}function K(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}var Y=n(774);function X(){return X=Object.assign?Object.assign.bind():function(e){for(var t=1;t=0||(o[n]=e[n]);return o}(e,t);if(Object.getOwnPropertySymbols){var s=Object.getOwnPropertySymbols(e);for(r=0;r=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(o[n]=e[n])}return o}var Z=n(196),J=n.n(Z),ee=function(){function e(e){var t=this;this._insertTag=function(e){var n;n=0===t.tags.length?t.insertionPoint?t.insertionPoint.nextSibling:t.prepend?t.container.firstChild:t.before:t.tags[t.tags.length-1].nextSibling,t.container.insertBefore(e,n),t.tags.push(e)},this.isSpeedy=void 0===e.speedy||e.speedy,this.tags=[],this.ctr=0,this.nonce=e.nonce,this.key=e.key,this.container=e.container,this.prepend=e.prepend,this.insertionPoint=e.insertionPoint,this.before=null}var t=e.prototype;return t.hydrate=function(e){e.forEach(this._insertTag)},t.insert=function(e){this.ctr%(this.isSpeedy?65e3:1)==0&&this._insertTag(function(e){var t=document.createElement("style");return t.setAttribute("data-emotion",e.key),void 0!==e.nonce&&t.setAttribute("nonce",e.nonce),t.appendChild(document.createTextNode("")),t.setAttribute("data-s",""),t}(this));var t=this.tags[this.tags.length-1];if(this.isSpeedy){var n=function(e){if(e.sheet)return e.sheet;for(var t=0;t0?ie(ve,--he):0,fe--,10===ge&&(fe=1,pe--),ge}function we(){return ge=he2||ke(ge)>3?"":" "}function De(e,t){for(;--t&&we()&&!(ge<48||ge>102||ge>57&&ge<65||ge>70&&ge<97););return Ce(e,xe()+(t<6&&32==Ee()&&32==we()))}function Pe(e){for(;we();)switch(ge){case e:return he;case 34:case 39:34!==e&&39!==e&&Pe(ge);break;case 40:41===e&&Pe(e);break;case 92:we()}return he}function Re(e,t){for(;we()&&e+ge!==57&&(e+ge!==84||47!==Ee()););return"/*"+Ce(t,he-1)+"*"+ne(47===e?e:we())}function Me(e){for(;!ke(Ee());)we();return Ce(e,he)}var Le="-ms-",Ae="-moz-",Ne="-webkit-",Fe="comm",Ve="rule",Be="decl",$e="@keyframes";function He(e,t){for(var n="",r=ue(e),o=0;o6)switch(ie(e,t+1)){case 109:if(45!==ie(e,t+4))break;case 102:return se(e,/(.+:)(.+)-([^]+)/,"$1-webkit-$2-$3$1"+Ae+(108==ie(e,t+3)?"$3":"$2-$3"))+e;case 115:return~le(e,"stretch")?je(se(e,"stretch","fill-available"),t)+e:e}break;case 4949:if(115!==ie(e,t+1))break;case 6444:switch(ie(e,ce(e)-3-(~le(e,"!important")&&10))){case 107:return se(e,":",":"+Ne)+e;case 101:return se(e,/(.+:)([^;!]+)(;|!.+)?/,"$1"+Ne+(45===ie(e,14)?"inline-":"")+"box$3$1"+Ne+"$2$3$1"+Le+"$2box$3")+e}break;case 5936:switch(ie(e,t+11)){case 114:return Ne+e+Le+se(e,/[svh]\w+-[tblr]{2}/,"tb")+e;case 108:return Ne+e+Le+se(e,/[svh]\w+-[tblr]{2}/,"tb-rl")+e;case 45:return Ne+e+Le+se(e,/[svh]\w+-[tblr]{2}/,"lr")+e}return Ne+e+Le+e+e}return e}function qe(e){return Oe(ze("",null,null,null,[""],e=Se(e),0,[0],e))}function ze(e,t,n,r,o,s,l,i,a){for(var c=0,u=0,d=l,p=0,f=0,m=0,h=1,g=1,v=1,b=0,_="",y=o,w=s,E=r,x=_;g;)switch(m=b,b=we()){case 40:if(108!=m&&58==x.charCodeAt(d-1)){-1!=le(x+=se(Ie(b),"&","&\f"),"&\f")&&(v=-1);break}case 34:case 39:case 91:x+=Ie(b);break;case 9:case 10:case 13:case 32:x+=Te(m);break;case 92:x+=De(xe()-1,7);continue;case 47:switch(Ee()){case 42:case 47:de(Ge(Re(we(),xe()),t,n),a);break;default:x+="/"}break;case 123*h:i[c++]=ce(x)*v;case 125*h:case 59:case 0:switch(b){case 0:case 125:g=0;case 59+u:f>0&&ce(x)-d&&de(f>32?Ke(x+";",r,n,d-1):Ke(se(x," ","")+";",r,n,d-2),a);break;case 59:x+=";";default:if(de(E=We(x,t,n,c,u,o,i,_,y=[],w=[],d),s),123===b)if(0===u)ze(x,t,E,E,y,s,d,i,w);else switch(p){case 100:case 109:case 115:ze(e,E,E,r&&de(We(e,E,E,0,0,o,i,_,o,y=[],d),w),o,w,d,i,r?y:w);break;default:ze(x,E,E,E,[""],w,0,i,w)}}c=u=f=0,h=v=1,_=x="",d=l;break;case 58:d=1+ce(x),f=m;default:if(h<1)if(123==b)--h;else if(125==b&&0==h++&&125==ye())continue;switch(x+=ne(b),b*h){case 38:v=u>0?1:(x+="\f",-1);break;case 44:i[c++]=(ce(x)-1)*v,v=1;break;case 64:45===Ee()&&(x+=Ie(we())),p=Ee(),u=d=ce(_=x+=Me(xe())),b++;break;case 45:45===m&&2==ce(x)&&(h=0)}}return s}function We(e,t,n,r,o,s,l,i,a,c,u){for(var d=o-1,p=0===o?s:[""],f=ue(p),m=0,h=0,g=0;m0?p[v]+" "+b:se(b,/&\f/g,p[v])))&&(a[g++]=_);return be(e,t,n,0===o?Ve:i,a,c,u)}function Ge(e,t,n){return be(e,t,n,Fe,ne(ge),ae(e,2,-2),0)}function Ke(e,t,n,r){return be(e,t,n,Be,ae(e,0,r),ae(e,r+1,-1),r)}var Ye=function(e,t,n){for(var r=0,o=0;r=o,o=Ee(),38===r&&12===o&&(t[n]=1),!ke(o);)we();return Ce(e,he)},Xe=new WeakMap,Qe=function(e){if("rule"===e.type&&e.parent&&!(e.length<1)){for(var t=e.value,n=e.parent,r=e.column===n.column&&e.line===n.line;"rule"!==n.type;)if(!(n=n.parent))return;if((1!==e.props.length||58===t.charCodeAt(0)||Xe.get(n))&&!r){Xe.set(e,!0);for(var o=[],s=function(e,t){return Oe(function(e,t){var n=-1,r=44;do{switch(ke(r)){case 0:38===r&&12===Ee()&&(t[n]=1),e[n]+=Ye(he-1,t,n);break;case 2:e[n]+=Ie(r);break;case 4:if(44===r){e[++n]=58===Ee()?"&\f":"",t[n]=e[n].length;break}default:e[n]+=ne(r)}}while(r=we());return e}(Se(e),t))}(t,o),l=n.props,i=0,a=0;i-1&&!e.return)switch(e.type){case Be:e.return=je(e.value,e.length);break;case $e:return He([_e(e,{value:se(e.value,"@","@"+Ne)})],r);case Ve:if(e.length)return function(e,t){return e.map(t).join("")}(e.props,(function(t){switch(function(e,t){return(e=/(::plac\w+|:read-\w+)/.exec(e))?e[0]:e}(t)){case":read-only":case":read-write":return He([_e(e,{props:[se(t,/:(read-\w+)/,":-moz-$1")]})],r);case"::placeholder":return He([_e(e,{props:[se(t,/:(plac\w+)/,":-webkit-input-$1")]}),_e(e,{props:[se(t,/:(plac\w+)/,":-moz-$1")]}),_e(e,{props:[se(t,/:(plac\w+)/,Le+"input-$1")]})],r)}return""}))}}];const et=function(e){var t=e.key;if("css"===t){var n=document.querySelectorAll("style[data-emotion]:not([data-s])");Array.prototype.forEach.call(n,(function(e){-1!==e.getAttribute("data-emotion").indexOf(" ")&&(document.head.appendChild(e),e.setAttribute("data-s",""))}))}var r,o,s=e.stylisPlugins||Je,l={},i=[];r=e.container||document.head,Array.prototype.forEach.call(document.querySelectorAll('style[data-emotion^="'+t+' "]'),(function(e){for(var t=e.getAttribute("data-emotion").split(" "),n=1;n=4;++r,o-=4)t=1540483477*(65535&(t=255&e.charCodeAt(r)|(255&e.charCodeAt(++r))<<8|(255&e.charCodeAt(++r))<<16|(255&e.charCodeAt(++r))<<24))+(59797*(t>>>16)<<16),n=1540483477*(65535&(t^=t>>>24))+(59797*(t>>>16)<<16)^1540483477*(65535&n)+(59797*(n>>>16)<<16);switch(o){case 3:n^=(255&e.charCodeAt(r+2))<<16;case 2:n^=(255&e.charCodeAt(r+1))<<8;case 1:n=1540483477*(65535&(n^=255&e.charCodeAt(r)))+(59797*(n>>>16)<<16)}return(((n=1540483477*(65535&(n^=n>>>13))+(59797*(n>>>16)<<16))^n>>>15)>>>0).toString(36)},st={animationIterationCount:1,borderImageOutset:1,borderImageSlice:1,borderImageWidth:1,boxFlex:1,boxFlexGroup:1,boxOrdinalGroup:1,columnCount:1,columns:1,flex:1,flexGrow:1,flexPositive:1,flexShrink:1,flexNegative:1,flexOrder:1,gridRow:1,gridRowEnd:1,gridRowSpan:1,gridRowStart:1,gridColumn:1,gridColumnEnd:1,gridColumnSpan:1,gridColumnStart:1,msGridRow:1,msGridRowSpan:1,msGridColumn:1,msGridColumnSpan:1,fontWeight:1,lineHeight:1,opacity:1,order:1,orphans:1,tabSize:1,widows:1,zIndex:1,zoom:1,WebkitLineClamp:1,fillOpacity:1,floodOpacity:1,stopOpacity:1,strokeDasharray:1,strokeDashoffset:1,strokeMiterlimit:1,strokeOpacity:1,strokeWidth:1};var lt=/[A-Z]|^ms/g,it=/_EMO_([^_]+?)_([^]*?)_EMO_/g,at=function(e){return 45===e.charCodeAt(1)},ct=function(e){return null!=e&&"boolean"!=typeof e},ut=function(e){var t=Object.create(null);return function(e){return void 0===t[e]&&(t[e]=at(n=e)?n:n.replace(lt,"-$&").toLowerCase()),t[e];var n}}(),dt=function(e,t){switch(e){case"animation":case"animationName":if("string"==typeof t)return t.replace(it,(function(e,t,n){return ft={name:t,styles:n,next:ft},t}))}return 1===st[e]||at(e)||"number"!=typeof t||0===t?t:t+"px"};function pt(e,t,n){if(null==n)return"";if(void 0!==n.__emotion_styles)return n;switch(typeof n){case"boolean":return"";case"object":if(1===n.anim)return ft={name:n.name,styles:n.styles,next:ft},n.name;if(void 0!==n.styles){var r=n.next;if(void 0!==r)for(;void 0!==r;)ft={name:r.name,styles:r.styles,next:ft},r=r.next;return n.styles+";"}return function(e,t,n){var r="";if(Array.isArray(n))for(var o=0;o-1}function Zt(e){return Qt(e)?window.pageYOffset:e.scrollTop}function Jt(e,t){Qt(e)?window.scrollTo(0,t):e.scrollTop=t}function en(e,t,n,r){return n*((e=e/r-1)*e*e+1)+t}function tn(e,t){var n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:200,r=arguments.length>3&&void 0!==arguments[3]?arguments[3]:Wt,o=Zt(e),s=t-o,l=10,i=0;function a(){var t=en(i+=l,o,s,n);Jt(e,t),i=f)return{placement:"bottom",maxHeight:t};if(x>=f&&!l)return s&&tn(a,C,S),{placement:"bottom",maxHeight:t};if(!l&&x>=r||l&&w>=r)return s&&tn(a,C,S),{placement:"bottom",maxHeight:l?w-b:x-b};if("auto"===o||l){var O=t,I=l?y:E;return I>=r&&(O=Math.min(I-b-i.controlHeight,t)),{placement:"top",maxHeight:O}}if("bottom"===o)return s&&Jt(a,C),{placement:"bottom",maxHeight:t};break;case"top":if(y>=f)return{placement:"top",maxHeight:t};if(E>=f&&!l)return s&&tn(a,k,S),{placement:"top",maxHeight:t};if(!l&&E>=r||l&&y>=r){var T=t;return(!l&&E>=r||l&&y>=r)&&(T=l?y-_:E-_),s&&tn(a,k,S),{placement:"top",maxHeight:T}}return{placement:"bottom",maxHeight:t};default:throw new Error('Invalid placement provided "'.concat(o,'".'))}return c}var cn=function(e){return"auto"===e?"bottom":e},un=(0,Z.createContext)({getPortalPlacement:null}),dn=function(e){Vt(n,e);var t=zt(n);function n(){var e;Lt(this,n);for(var r=arguments.length,o=new Array(r),s=0;se.length)&&(t=e.length);for(var n=0,r=new Array(t);n0,h=d-p-u,g=!1;h>t&&l.current&&(r&&r(e),l.current=!1),m&&i.current&&(s&&s(e),i.current=!1),m&&t>h?(n&&!l.current&&n(e),f.scrollTop=d,g=!0,l.current=!0):!m&&-t>u&&(o&&!i.current&&o(e),f.scrollTop=0,g=!0,i.current=!0),g&&function(e){e.preventDefault(),e.stopPropagation()}(e)}}),[]),d=(0,Z.useCallback)((function(e){u(e,e.deltaY)}),[u]),p=(0,Z.useCallback)((function(e){a.current=e.changedTouches[0].clientY}),[]),f=(0,Z.useCallback)((function(e){var t=a.current-e.changedTouches[0].clientY;u(e,t)}),[u]),m=(0,Z.useCallback)((function(e){if(e){var t=!!ln&&{passive:!1};"function"==typeof e.addEventListener&&e.addEventListener("wheel",d,t),"function"==typeof e.addEventListener&&e.addEventListener("touchstart",p,t),"function"==typeof e.addEventListener&&e.addEventListener("touchmove",f,t)}}),[f,p,d]),h=(0,Z.useCallback)((function(e){e&&("function"==typeof e.removeEventListener&&e.removeEventListener("wheel",d,!1),"function"==typeof e.removeEventListener&&e.removeEventListener("touchstart",p,!1),"function"==typeof e.removeEventListener&&e.removeEventListener("touchmove",f,!1))}),[f,p,d]);return(0,Z.useEffect)((function(){if(t){var e=c.current;return m(e),function(){h(e)}}}),[t,m,h]),function(e){c.current=e}}({isEnabled:void 0===r||r,onBottomArrive:e.onBottomArrive,onBottomLeave:e.onBottomLeave,onTopArrive:e.onTopArrive,onTopLeave:e.onTopLeave}),s=function(e){var t=e.isEnabled,n=e.accountForScrollbars,r=void 0===n||n,o=(0,Z.useRef)({}),s=(0,Z.useRef)(null),l=(0,Z.useCallback)((function(e){if(cr){var t=document.body,n=t&&t.style;if(r&&rr.forEach((function(e){var t=n&&n[e];o.current[e]=t})),r&&ur<1){var s=parseInt(o.current.paddingRight,10)||0,l=document.body?document.body.clientWidth:0,i=window.innerWidth-l+s||0;Object.keys(or).forEach((function(e){var t=or[e];n&&(n[e]=t)})),n&&(n.paddingRight="".concat(i,"px"))}t&&ar()&&(t.addEventListener("touchmove",sr,dr),e&&(e.addEventListener("touchstart",ir,dr),e.addEventListener("touchmove",lr,dr))),ur+=1}}),[]),i=(0,Z.useCallback)((function(e){if(cr){var t=document.body,n=t&&t.style;ur=Math.max(ur-1,0),r&&ur<1&&rr.forEach((function(e){var t=o.current[e];n&&(n[e]=t)})),t&&ar()&&(t.removeEventListener("touchmove",sr,dr),e&&(e.removeEventListener("touchstart",ir,dr),e.removeEventListener("touchmove",lr,dr)))}}),[]);return(0,Z.useEffect)((function(){if(t){var e=s.current;return l(e),function(){i(e)}}}),[t,l,i]),function(e){s.current=e}}({isEnabled:n});return St(J().Fragment,null,n&&St("div",{onClick:pr,css:fr}),t((function(e){o(e),s(e)})))}var hr={clearIndicator:In,container:function(e){var t=e.isDisabled;return{label:"container",direction:e.isRtl?"rtl":null,pointerEvents:t?"none":null,position:"relative"}},control:function(e){var t=e.isDisabled,n=e.isFocused,r=e.theme,o=r.colors,s=r.borderRadius,l=r.spacing;return{label:"control",alignItems:"center",backgroundColor:t?o.neutral5:o.neutral0,borderColor:t?o.neutral10:n?o.primary:o.neutral20,borderRadius:s,borderStyle:"solid",borderWidth:1,boxShadow:n?"0 0 0 1px ".concat(o.primary):null,cursor:"default",display:"flex",flexWrap:"wrap",justifyContent:"space-between",minHeight:l.controlHeight,outline:"0 !important",position:"relative",transition:"all 100ms","&:hover":{borderColor:n?o.primary:o.neutral30}}},dropdownIndicator:On,group:function(e){var t=e.theme.spacing;return{paddingBottom:2*t.baseUnit,paddingTop:2*t.baseUnit}},groupHeading:function(e){var t=e.theme.spacing;return{label:"group",color:"#999",cursor:"default",display:"block",fontSize:"75%",fontWeight:"500",marginBottom:"0.25em",paddingLeft:3*t.baseUnit,paddingRight:3*t.baseUnit,textTransform:"uppercase"}},indicatorsContainer:function(){return{alignItems:"center",alignSelf:"stretch",display:"flex",flexShrink:0}},indicatorSeparator:function(e){var t=e.isDisabled,n=e.theme,r=n.spacing.baseUnit,o=n.colors;return{label:"indicatorSeparator",alignSelf:"stretch",backgroundColor:t?o.neutral10:o.neutral20,marginBottom:2*r,marginTop:2*r,width:1}},input:function(e){var t=e.isDisabled,n=e.theme,r=n.spacing,o=n.colors;return{margin:r.baseUnit/2,paddingBottom:r.baseUnit/2,paddingTop:r.baseUnit/2,visibility:t?"hidden":"visible",color:o.neutral80}},loadingIndicator:function(e){var t=e.isFocused,n=e.size,r=e.theme,o=r.colors,s=r.spacing.baseUnit;return{label:"loadingIndicator",color:t?o.neutral60:o.neutral20,display:"flex",padding:2*s,transition:"color 150ms",alignSelf:"center",fontSize:n,lineHeight:1,marginRight:n,textAlign:"center",verticalAlign:"middle"}},loadingMessage:mn,menu:function(e){var t,n=e.placement,r=e.theme,o=r.borderRadius,s=r.spacing,l=r.colors;return K(t={label:"menu"},function(e){return e?{bottom:"top",top:"bottom"}[e]:"bottom"}(n),"100%"),K(t,"backgroundColor",l.neutral0),K(t,"borderRadius",o),K(t,"boxShadow","0 0 0 1px hsla(0, 0%, 0%, 0.1), 0 4px 11px hsla(0, 0%, 0%, 0.1)"),K(t,"marginBottom",s.menuGutter),K(t,"marginTop",s.menuGutter),K(t,"position","absolute"),K(t,"width","100%"),K(t,"zIndex",1),t},menuList:function(e){var t=e.maxHeight,n=e.theme.spacing.baseUnit;return{maxHeight:t,overflowY:"auto",paddingBottom:n,paddingTop:n,position:"relative",WebkitOverflowScrolling:"touch"}},menuPortal:function(e){var t=e.rect,n=e.offset,r=e.position;return{left:t.left,position:r,top:n,width:t.width,zIndex:1}},multiValue:function(e){var t=e.theme,n=t.spacing,r=t.borderRadius;return{label:"multiValue",backgroundColor:t.colors.neutral10,borderRadius:r/2,display:"flex",margin:n.baseUnit/2,minWidth:0}},multiValueLabel:function(e){var t=e.theme,n=t.borderRadius,r=t.colors,o=e.cropWithEllipsis;return{borderRadius:n/2,color:r.neutral80,fontSize:"85%",overflow:"hidden",padding:3,paddingLeft:6,textOverflow:o?"ellipsis":null,whiteSpace:"nowrap"}},multiValueRemove:function(e){var t=e.theme,n=t.spacing,r=t.borderRadius,o=t.colors;return{alignItems:"center",borderRadius:r/2,backgroundColor:e.isFocused&&o.dangerLight,display:"flex",paddingLeft:n.baseUnit,paddingRight:n.baseUnit,":hover":{backgroundColor:o.dangerLight,color:o.danger}}},noOptionsMessage:fn,option:function(e){var t=e.isDisabled,n=e.isFocused,r=e.isSelected,o=e.theme,s=o.spacing,l=o.colors;return{label:"option",backgroundColor:r?l.primary:n?l.primary25:"transparent",color:t?l.neutral20:r?l.neutral0:"inherit",cursor:"default",display:"block",fontSize:"inherit",padding:"".concat(2*s.baseUnit,"px ").concat(3*s.baseUnit,"px"),width:"100%",userSelect:"none",WebkitTapHighlightColor:"rgba(0, 0, 0, 0)",":active":{backgroundColor:!t&&(r?l.primary:l.primary50)}}},placeholder:function(e){var t=e.theme,n=t.spacing;return{label:"placeholder",color:t.colors.neutral50,marginLeft:n.baseUnit/2,marginRight:n.baseUnit/2,position:"absolute",top:"50%",transform:"translateY(-50%)"}},singleValue:function(e){var t=e.isDisabled,n=e.theme,r=n.spacing,o=n.colors;return{label:"singleValue",color:t?o.neutral40:o.neutral80,marginLeft:r.baseUnit/2,marginRight:r.baseUnit/2,maxWidth:"calc(100% - ".concat(2*r.baseUnit,"px)"),overflow:"hidden",position:"absolute",textOverflow:"ellipsis",whiteSpace:"nowrap",top:"50%",transform:"translateY(-50%)"}},valueContainer:function(e){var t=e.theme.spacing;return{alignItems:"center",display:"flex",flex:1,flexWrap:"wrap",padding:"".concat(t.baseUnit/2,"px ").concat(2*t.baseUnit,"px"),WebkitOverflowScrolling:"touch",position:"relative",overflow:"hidden"}}},gr={borderRadius:4,colors:{primary:"#2684FF",primary75:"#4C9AFF",primary50:"#B2D4FF",primary25:"#DEEBFF",danger:"#DE350B",dangerLight:"#FFBDAD",neutral0:"hsl(0, 0%, 100%)",neutral5:"hsl(0, 0%, 95%)",neutral10:"hsl(0, 0%, 90%)",neutral20:"hsl(0, 0%, 80%)",neutral30:"hsl(0, 0%, 70%)",neutral40:"hsl(0, 0%, 60%)",neutral50:"hsl(0, 0%, 50%)",neutral60:"hsl(0, 0%, 40%)",neutral70:"hsl(0, 0%, 30%)",neutral80:"hsl(0, 0%, 20%)",neutral90:"hsl(0, 0%, 10%)"},spacing:{baseUnit:4,controlHeight:38,menuGutter:8}},vr={"aria-live":"polite",backspaceRemovesValue:!0,blurInputOnSelect:nn(),captureMenuScroll:!nn(),closeMenuOnSelect:!0,closeMenuOnScroll:!1,components:{},controlShouldRenderValue:!0,escapeClearsValue:!1,filterOption:function(e,t){var n=Ut({ignoreCase:!0,ignoreAccents:!0,stringify:tr,trim:!0,matchFrom:"any"},undefined),r=n.ignoreCase,o=n.ignoreAccents,s=n.stringify,l=n.trim,i=n.matchFrom,a=l?er(t):t,c=l?er(s(e)):s(e);return r&&(a=a.toLowerCase(),c=c.toLowerCase()),o&&(a=Jn(a),c=Zn(c)),"start"===i?c.substr(0,a.length)===a:c.indexOf(a)>-1},formatGroupLabel:function(e){return e.label},getOptionLabel:function(e){return e.label},getOptionValue:function(e){return e.value},isDisabled:!1,isLoading:!1,isMulti:!1,isRtl:!1,isSearchable:!0,isOptionDisabled:function(e){return!!e.isDisabled},loadingMessage:function(){return"Loading..."},maxMenuHeight:300,minMenuHeight:140,menuIsOpen:!1,menuPlacement:"bottom",menuPosition:"absolute",menuShouldBlockScroll:!1,menuShouldScrollIntoView:!function(){try{return/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent)}catch(e){return!1}}(),noOptionsMessage:function(){return"No options"},openMenuOnFocus:!1,openMenuOnClick:!0,options:[],pageSize:5,placeholder:"Select...",screenReaderStatus:function(e){var t=e.count;return"".concat(t," result").concat(1!==t?"s":""," available")},styles:{},tabIndex:"0",tabSelectsValue:!0};function br(e,t,n,r){return{type:"option",data:t,isDisabled:Cr(e,t,n),isSelected:kr(e,t,n),label:Er(e,t),value:xr(e,t),index:r}}function _r(e,t){return e.options.map((function(n,r){if(n.options){var o=n.options.map((function(n,r){return br(e,n,t,r)})).filter((function(t){return wr(e,t)}));return o.length>0?{type:"group",data:n,options:o,index:r}:void 0}var s=br(e,n,t,r);return wr(e,s)?s:void 0})).filter((function(e){return!!e}))}function yr(e){return e.reduce((function(e,t){return"group"===t.type?e.push.apply(e,Bn(t.options.map((function(e){return e.data})))):e.push(t.data),e}),[])}function wr(e,t){var n=e.inputValue,r=void 0===n?"":n,o=t.data,s=t.isSelected,l=t.label,i=t.value;return(!Or(e)||!s)&&Sr(e,{label:l,value:i,data:o},r)}var Er=function(e,t){return e.getOptionLabel(t)},xr=function(e,t){return e.getOptionValue(t)};function Cr(e,t,n){return"function"==typeof e.isOptionDisabled&&e.isOptionDisabled(t,n)}function kr(e,t,n){if(n.indexOf(t)>-1)return!0;if("function"==typeof e.isOptionSelected)return e.isOptionSelected(t,n);var r=xr(e,t);return n.some((function(t){return xr(e,t)===r}))}function Sr(e,t,n){return!e.filterOption||e.filterOption(t,n)}var Or=function(e){var t=e.hideSelectedOptions,n=e.isMulti;return void 0===t?n:t},Ir=1,Tr=function(e){Vt(n,e);var t=zt(n);function n(e){var r;return Lt(this,n),(r=t.call(this,e)).state={ariaSelection:null,focusedOption:null,focusedValue:null,inputIsHidden:!1,isFocused:!1,selectValue:[],clearFocusValueOnUpdate:!1,inputIsHiddenAfterUpdate:void 0,prevProps:void 0},r.blockOptionHover=!1,r.isComposing=!1,r.commonProps=void 0,r.initialTouchX=0,r.initialTouchY=0,r.instancePrefix="",r.openAfterFocus=!1,r.scrollToFocusedOptionOnUpdate=!1,r.userIsDragging=void 0,r.controlRef=null,r.getControlRef=function(e){r.controlRef=e},r.focusedOptionRef=null,r.getFocusedOptionRef=function(e){r.focusedOptionRef=e},r.menuListRef=null,r.getMenuListRef=function(e){r.menuListRef=e},r.inputRef=null,r.getInputRef=function(e){r.inputRef=e},r.focus=r.focusInput,r.blur=r.blurInput,r.onChange=function(e,t){var n=r.props,o=n.onChange,s=n.name;t.name=s,r.ariaOnChange(e,t),o(e,t)},r.setValue=function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"set-value",n=arguments.length>2?arguments[2]:void 0,o=r.props,s=o.closeMenuOnSelect,l=o.isMulti;r.onInputChange("",{action:"set-value"}),s&&(r.setState({inputIsHiddenAfterUpdate:!l}),r.onMenuClose()),r.setState({clearFocusValueOnUpdate:!0}),r.onChange(e,{action:t,option:n})},r.selectOption=function(e){var t=r.props,n=t.blurInputOnSelect,o=t.isMulti,s=t.name,l=r.state.selectValue,i=o&&r.isOptionSelected(e,l),a=r.isOptionDisabled(e,l);if(i){var c=r.getOptionValue(e);r.setValue(l.filter((function(e){return r.getOptionValue(e)!==c})),"deselect-option",e)}else{if(a)return void r.ariaOnChange(e,{action:"select-option",name:s});o?r.setValue([].concat(Bn(l),[e]),"select-option",e):r.setValue(e,"select-option")}n&&r.blurInput()},r.removeValue=function(e){var t=r.props.isMulti,n=r.state.selectValue,o=r.getOptionValue(e),s=n.filter((function(e){return r.getOptionValue(e)!==o})),l=t?s:s[0]||null;r.onChange(l,{action:"remove-value",removedValue:e}),r.focusInput()},r.clearValue=function(){var e=r.state.selectValue;r.onChange(r.props.isMulti?[]:null,{action:"clear",removedValues:e})},r.popValue=function(){var e=r.props.isMulti,t=r.state.selectValue,n=t[t.length-1],o=t.slice(0,t.length-1),s=e?o:o[0]||null;r.onChange(s,{action:"pop-value",removedValue:n})},r.getValue=function(){return r.state.selectValue},r.cx=function(){for(var e=arguments.length,t=new Array(e),n=0;n5||s>5}},r.onTouchEnd=function(e){r.userIsDragging||(r.controlRef&&!r.controlRef.contains(e.target)&&r.menuListRef&&!r.menuListRef.contains(e.target)&&r.blurInput(),r.initialTouchX=0,r.initialTouchY=0)},r.onControlTouchEnd=function(e){r.userIsDragging||r.onControlMouseDown(e)},r.onClearIndicatorTouchEnd=function(e){r.userIsDragging||r.onClearIndicatorMouseDown(e)},r.onDropdownIndicatorTouchEnd=function(e){r.userIsDragging||r.onDropdownIndicatorMouseDown(e)},r.handleInputChange=function(e){var t=e.currentTarget.value;r.setState({inputIsHiddenAfterUpdate:!1}),r.onInputChange(t,{action:"input-change"}),r.props.menuIsOpen||r.onMenuOpen()},r.onInputFocus=function(e){r.props.onFocus&&r.props.onFocus(e),r.setState({inputIsHiddenAfterUpdate:!1,isFocused:!0}),(r.openAfterFocus||r.props.openMenuOnFocus)&&r.openMenu("first"),r.openAfterFocus=!1},r.onInputBlur=function(e){r.menuListRef&&r.menuListRef.contains(document.activeElement)?r.inputRef.focus():(r.props.onBlur&&r.props.onBlur(e),r.onInputChange("",{action:"input-blur"}),r.onMenuClose(),r.setState({focusedValue:null,isFocused:!1}))},r.onOptionHover=function(e){r.blockOptionHover||r.state.focusedOption===e||r.setState({focusedOption:e})},r.shouldHideSelectedOptions=function(){return Or(r.props)},r.onKeyDown=function(e){var t=r.props,n=t.isMulti,o=t.backspaceRemovesValue,s=t.escapeClearsValue,l=t.inputValue,i=t.isClearable,a=t.isDisabled,c=t.menuIsOpen,u=t.onKeyDown,d=t.tabSelectsValue,p=t.openMenuOnFocus,f=r.state,m=f.focusedOption,h=f.focusedValue,g=f.selectValue;if(!(a||"function"==typeof u&&(u(e),e.defaultPrevented))){switch(r.blockOptionHover=!0,e.key){case"ArrowLeft":if(!n||l)return;r.focusValue("previous");break;case"ArrowRight":if(!n||l)return;r.focusValue("next");break;case"Delete":case"Backspace":if(l)return;if(h)r.removeValue(h);else{if(!o)return;n?r.popValue():i&&r.clearValue()}break;case"Tab":if(r.isComposing)return;if(e.shiftKey||!c||!d||!m||p&&r.isOptionSelected(m,g))return;r.selectOption(m);break;case"Enter":if(229===e.keyCode)break;if(c){if(!m)return;if(r.isComposing)return;r.selectOption(m);break}return;case"Escape":c?(r.setState({inputIsHiddenAfterUpdate:!1}),r.onInputChange("",{action:"menu-close"}),r.onMenuClose()):i&&s&&r.clearValue();break;case" ":if(l)return;if(!c){r.openMenu("first");break}if(!m)return;r.selectOption(m);break;case"ArrowUp":c?r.focusOption("up"):r.openMenu("last");break;case"ArrowDown":c?r.focusOption("down"):r.openMenu("first");break;case"PageUp":if(!c)return;r.focusOption("pageup");break;case"PageDown":if(!c)return;r.focusOption("pagedown");break;case"Home":if(!c)return;r.focusOption("first");break;case"End":if(!c)return;r.focusOption("last");break;default:return}e.preventDefault()}},r.instancePrefix="react-select-"+(r.props.instanceId||++Ir),r.state.selectValue=Yt(e.value),r}return Nt(n,[{key:"componentDidMount",value:function(){this.startListeningComposition(),this.startListeningToTouch(),this.props.closeMenuOnScroll&&document&&document.addEventListener&&document.addEventListener("scroll",this.onScroll,!0),this.props.autoFocus&&this.focusInput()}},{key:"componentDidUpdate",value:function(e){var t,n,r,o,s,l=this.props,i=l.isDisabled,a=l.menuIsOpen,c=this.state.isFocused;(c&&!i&&e.isDisabled||c&&a&&!e.menuIsOpen)&&this.focusInput(),c&&i&&!e.isDisabled&&this.setState({isFocused:!1},this.onMenuClose),this.menuListRef&&this.focusedOptionRef&&this.scrollToFocusedOptionOnUpdate&&(t=this.menuListRef,n=this.focusedOptionRef,r=t.getBoundingClientRect(),o=n.getBoundingClientRect(),s=n.offsetHeight/3,o.bottom+s>r.bottom?Jt(t,Math.min(n.offsetTop+n.clientHeight-t.offsetHeight+s,t.scrollHeight)):o.top-s-1&&(l=i)}this.scrollToFocusedOptionOnUpdate=!(o&&this.menuListRef),this.setState({inputIsHiddenAfterUpdate:!1,focusedValue:null,focusedOption:s[l]},(function(){return t.onMenuOpen()}))}},{key:"focusValue",value:function(e){var t=this.state,n=t.selectValue,r=t.focusedValue;if(this.props.isMulti){this.setState({focusedOption:null});var o=n.indexOf(r);r||(o=-1);var s=n.length-1,l=-1;if(n.length){switch(e){case"previous":l=0===o?0:-1===o?s:o-1;break;case"next":o>-1&&o0&&void 0!==arguments[0]?arguments[0]:"first",t=this.props.pageSize,n=this.state.focusedOption,r=this.getFocusableOptions();if(r.length){var o=0,s=r.indexOf(n);n||(s=-1),"up"===e?o=s>0?s-1:r.length-1:"down"===e?o=(s+1)%r.length:"pageup"===e?(o=s-t)<0&&(o=0):"pagedown"===e?(o=s+t)>r.length-1&&(o=r.length-1):"last"===e&&(o=r.length-1),this.scrollToFocusedOptionOnUpdate=!0,this.setState({focusedOption:r[o],focusedValue:null})}}},{key:"getTheme",value:function(){return this.props.theme?"function"==typeof this.props.theme?this.props.theme(gr):Ut(Ut({},gr),this.props.theme):gr}},{key:"getCommonProps",value:function(){var e=this.clearValue,t=this.cx,n=this.getStyles,r=this.getValue,o=this.selectOption,s=this.setValue,l=this.props,i=l.isMulti,a=l.isRtl,c=l.options;return{clearValue:e,cx:t,getStyles:n,getValue:r,hasValue:this.hasValue(),isMulti:i,isRtl:a,options:c,selectOption:o,selectProps:l,setValue:s,theme:this.getTheme()}}},{key:"hasValue",value:function(){return this.state.selectValue.length>0}},{key:"hasOptions",value:function(){return!!this.getFocusableOptions().length}},{key:"isClearable",value:function(){var e=this.props,t=e.isClearable,n=e.isMulti;return void 0===t?n:t}},{key:"isOptionDisabled",value:function(e,t){return Cr(this.props,e,t)}},{key:"isOptionSelected",value:function(e,t){return kr(this.props,e,t)}},{key:"filterOption",value:function(e,t){return Sr(this.props,e,t)}},{key:"formatOptionLabel",value:function(e,t){if("function"==typeof this.props.formatOptionLabel){var n=this.props.inputValue,r=this.state.selectValue;return this.props.formatOptionLabel(e,{context:t,inputValue:n,selectValue:r})}return this.getOptionLabel(e)}},{key:"formatGroupLabel",value:function(e){return this.props.formatGroupLabel(e)}},{key:"startListeningComposition",value:function(){document&&document.addEventListener&&(document.addEventListener("compositionstart",this.onCompositionStart,!1),document.addEventListener("compositionend",this.onCompositionEnd,!1))}},{key:"stopListeningComposition",value:function(){document&&document.removeEventListener&&(document.removeEventListener("compositionstart",this.onCompositionStart),document.removeEventListener("compositionend",this.onCompositionEnd))}},{key:"startListeningToTouch",value:function(){document&&document.addEventListener&&(document.addEventListener("touchstart",this.onTouchStart,!1),document.addEventListener("touchmove",this.onTouchMove,!1),document.addEventListener("touchend",this.onTouchEnd,!1))}},{key:"stopListeningToTouch",value:function(){document&&document.removeEventListener&&(document.removeEventListener("touchstart",this.onTouchStart),document.removeEventListener("touchmove",this.onTouchMove),document.removeEventListener("touchend",this.onTouchEnd))}},{key:"renderInput",value:function(){var e=this.props,t=e.isDisabled,n=e.isSearchable,r=e.inputId,o=e.inputValue,s=e.tabIndex,l=e.form,i=this.getComponents().Input,a=this.state.inputIsHidden,c=this.commonProps,u=r||this.getElementId("input"),d={"aria-autocomplete":"list","aria-label":this.props["aria-label"],"aria-labelledby":this.props["aria-labelledby"]};return n?J().createElement(i,X({},c,{autoCapitalize:"none",autoComplete:"off",autoCorrect:"off",id:u,innerRef:this.getInputRef,isDisabled:t,isHidden:a,onBlur:this.onInputBlur,onChange:this.handleInputChange,onFocus:this.onInputFocus,spellCheck:"false",tabIndex:s,form:l,type:"text",value:o},d)):J().createElement(nr,X({id:u,innerRef:this.getInputRef,onBlur:this.onInputBlur,onChange:Wt,onFocus:this.onInputFocus,readOnly:!0,disabled:t,tabIndex:s,form:l,value:""},d))}},{key:"renderPlaceholderOrValue",value:function(){var e=this,t=this.getComponents(),n=t.MultiValue,r=t.MultiValueContainer,o=t.MultiValueLabel,s=t.MultiValueRemove,l=t.SingleValue,i=t.Placeholder,a=this.commonProps,c=this.props,u=c.controlShouldRenderValue,d=c.isDisabled,p=c.isMulti,f=c.inputValue,m=c.placeholder,h=this.state,g=h.selectValue,v=h.focusedValue,b=h.isFocused;if(!this.hasValue()||!u)return f?null:J().createElement(i,X({},a,{key:"placeholder",isDisabled:d,isFocused:b}),m);if(p)return g.map((function(t,l){var i=t===v;return J().createElement(n,X({},a,{components:{Container:r,Label:o,Remove:s},isFocused:i,isDisabled:d,key:"".concat(e.getOptionValue(t)).concat(l),index:l,removeProps:{onClick:function(){return e.removeValue(t)},onTouchEnd:function(){return e.removeValue(t)},onMouseDown:function(e){e.preventDefault(),e.stopPropagation()}},data:t}),e.formatOptionLabel(t,"value"))}));if(f)return null;var _=g[0];return J().createElement(l,X({},a,{data:_,isDisabled:d}),this.formatOptionLabel(_,"value"))}},{key:"renderClearIndicator",value:function(){var e=this.getComponents().ClearIndicator,t=this.commonProps,n=this.props,r=n.isDisabled,o=n.isLoading,s=this.state.isFocused;if(!this.isClearable()||!e||r||!this.hasValue()||o)return null;var l={onMouseDown:this.onClearIndicatorMouseDown,onTouchEnd:this.onClearIndicatorTouchEnd,"aria-hidden":"true"};return J().createElement(e,X({},t,{innerProps:l,isFocused:s}))}},{key:"renderLoadingIndicator",value:function(){var e=this.getComponents().LoadingIndicator,t=this.commonProps,n=this.props,r=n.isDisabled,o=n.isLoading,s=this.state.isFocused;return e&&o?J().createElement(e,X({},t,{innerProps:{"aria-hidden":"true"},isDisabled:r,isFocused:s})):null}},{key:"renderIndicatorSeparator",value:function(){var e=this.getComponents(),t=e.DropdownIndicator,n=e.IndicatorSeparator;if(!t||!n)return null;var r=this.commonProps,o=this.props.isDisabled,s=this.state.isFocused;return J().createElement(n,X({},r,{isDisabled:o,isFocused:s}))}},{key:"renderDropdownIndicator",value:function(){var e=this.getComponents().DropdownIndicator;if(!e)return null;var t=this.commonProps,n=this.props.isDisabled,r=this.state.isFocused,o={onMouseDown:this.onDropdownIndicatorMouseDown,onTouchEnd:this.onDropdownIndicatorTouchEnd,"aria-hidden":"true"};return J().createElement(e,X({},t,{innerProps:o,isDisabled:n,isFocused:r}))}},{key:"renderMenu",value:function(){var e=this,t=this.getComponents(),n=t.Group,r=t.GroupHeading,o=t.Menu,s=t.MenuList,l=t.MenuPortal,i=t.LoadingMessage,a=t.NoOptionsMessage,c=t.Option,u=this.commonProps,d=this.state.focusedOption,p=this.props,f=p.captureMenuScroll,m=p.inputValue,h=p.isLoading,g=p.loadingMessage,v=p.minMenuHeight,b=p.maxMenuHeight,_=p.menuIsOpen,y=p.menuPlacement,w=p.menuPosition,E=p.menuPortalTarget,x=p.menuShouldBlockScroll,C=p.menuShouldScrollIntoView,k=p.noOptionsMessage,S=p.onMenuScrollToTop,O=p.onMenuScrollToBottom;if(!_)return null;var I,T=function(t,n){var r=t.type,o=t.data,s=t.isDisabled,l=t.isSelected,i=t.label,a=t.value,p=d===o,f=s?void 0:function(){return e.onOptionHover(o)},m=s?void 0:function(){return e.selectOption(o)},h="".concat(e.getElementId("option"),"-").concat(n),g={id:h,onClick:m,onMouseMove:f,onMouseOver:f,tabIndex:-1};return J().createElement(c,X({},u,{innerProps:g,data:o,isDisabled:s,isSelected:l,key:h,label:i,type:r,value:a,isFocused:p,innerRef:p?e.getFocusedOptionRef:void 0}),e.formatOptionLabel(t.data,"menu"))};if(this.hasOptions())I=this.getCategorizedOptions().map((function(t){if("group"===t.type){var o=t.data,s=t.options,l=t.index,i="".concat(e.getElementId("group"),"-").concat(l),a="".concat(i,"-heading");return J().createElement(n,X({},u,{key:i,data:o,options:s,Heading:r,headingProps:{id:a,data:t.data},label:e.formatGroupLabel(t.data)}),t.options.map((function(e){return T(e,"".concat(l,"-").concat(e.index))})))}if("option"===t.type)return T(t,"".concat(t.index))}));else if(h){var D=g({inputValue:m});if(null===D)return null;I=J().createElement(i,u,D)}else{var P=k({inputValue:m});if(null===P)return null;I=J().createElement(a,u,P)}var R={minMenuHeight:v,maxMenuHeight:b,menuPlacement:y,menuPosition:w,menuShouldScrollIntoView:C},M=J().createElement(dn,X({},u,R),(function(t){var n=t.ref,r=t.placerProps,l=r.placement,i=r.maxHeight;return J().createElement(o,X({},u,R,{innerRef:n,innerProps:{onMouseDown:e.onMenuMouseDown,onMouseMove:e.onMenuMouseMove},isLoading:h,placement:l}),J().createElement(mr,{captureEnabled:f,onTopArrive:S,onBottomArrive:O,lockEnabled:x},(function(t){return J().createElement(s,X({},u,{innerRef:function(n){e.getMenuListRef(n),t(n)},isLoading:h,maxHeight:i,focusedOption:d}),I)})))}));return E||"fixed"===w?J().createElement(l,X({},u,{appendTo:E,controlElement:this.controlRef,menuPlacement:y,menuPosition:w}),M):M}},{key:"renderFormField",value:function(){var e=this,t=this.props,n=t.delimiter,r=t.isDisabled,o=t.isMulti,s=t.name,l=this.state.selectValue;if(s&&!r){if(o){if(n){var i=l.map((function(t){return e.getOptionValue(t)})).join(n);return J().createElement("input",{name:s,type:"hidden",value:i})}var a=l.length>0?l.map((function(t,n){return J().createElement("input",{key:"i-".concat(n),name:s,type:"hidden",value:e.getOptionValue(t)})})):J().createElement("input",{name:s,type:"hidden"});return J().createElement("div",null,a)}var c=l[0]?this.getOptionValue(l[0]):"";return J().createElement("input",{name:s,type:"hidden",value:c})}}},{key:"renderLiveRegion",value:function(){var e=this.commonProps,t=this.state,n=t.ariaSelection,r=t.focusedOption,o=t.focusedValue,s=t.isFocused,l=t.selectValue,i=this.getFocusableOptions();return J().createElement(zn,X({},e,{ariaSelection:n,focusedOption:r,focusedValue:o,isFocused:s,selectValue:l,focusableOptions:i}))}},{key:"render",value:function(){var e=this.getComponents(),t=e.Control,n=e.IndicatorsContainer,r=e.SelectContainer,o=e.ValueContainer,s=this.props,l=s.className,i=s.id,a=s.isDisabled,c=s.menuIsOpen,u=this.state.isFocused,d=this.commonProps=this.getCommonProps();return J().createElement(r,X({},d,{className:l,innerProps:{id:i,onKeyDown:this.onKeyDown},isDisabled:a,isFocused:u}),this.renderLiveRegion(),J().createElement(t,X({},d,{innerRef:this.getControlRef,innerProps:{onMouseDown:this.onControlMouseDown,onTouchEnd:this.onControlTouchEnd},isDisabled:a,isFocused:u,menuIsOpen:c}),J().createElement(o,X({},d,{isDisabled:a}),this.renderPlaceholderOrValue(),this.renderInput()),J().createElement(n,X({},d,{isDisabled:a}),this.renderClearIndicator(),this.renderLoadingIndicator(),this.renderIndicatorSeparator(),this.renderDropdownIndicator())),this.renderMenu(),this.renderFormField())}}],[{key:"getDerivedStateFromProps",value:function(e,t){var n=t.prevProps,r=t.clearFocusValueOnUpdate,o=t.inputIsHiddenAfterUpdate,s=e.options,l=e.value,i=e.menuIsOpen,a=e.inputValue,c={};if(n&&(l!==n.value||s!==n.options||i!==n.menuIsOpen||a!==n.inputValue)){var u=Yt(l),d=i?function(e,t){return yr(_r(e,t))}(e,u):[],p=r?function(e,t){var n=e.focusedValue,r=e.selectValue.indexOf(n);if(r>-1){if(t.indexOf(n)>-1)return n;if(r-1?n:t[0]}(t,d);c={selectValue:u,focusedOption:f,focusedValue:p,clearFocusValueOnUpdate:!1}}var m=null!=o&&e!==n?{inputIsHidden:o,inputIsHiddenAfterUpdate:void 0}:{};return Ut(Ut(Ut({},c),m),{},{prevProps:e})}}]),n}(Z.Component);Tr.defaultProps=vr;var Dr,Pr,Rr,Mr={cacheOptions:!1,defaultOptions:!1,filterOption:null,isLoading:!1},Lr=(Dr=Tr,Rr=Pr=function(e){Vt(n,e);var t=zt(n);function n(){var e;Lt(this,n);for(var r=arguments.length,o=new Array(r),s=0;s1?n-1:0),o=1;o"llms-search")),K(this,"getSearchPath",(()=>this.props.searchPath)),K(this,"getSearchUrl",(e=>wp.url.addQueryArgs(this.getSearchPath(),this.getSearchArgs(e)))),K(this,"formatSearchResultLabel",(e=>e.id)),K(this,"formatSearchResultValue",(e=>e.id)),K(this,"onSearch",(0,Y.debounce)(300,((e,t)=>{wp.apiFetch({path:this.getSearchUrl(e)}).then((e=>{t(this.formatSearchResults(e))}))})))}getSearchArgs(e){return Object.assign({per_page:20,search:encodeURI(e)},this.props.searchArgs)}formatSearchResults(e){return e.map((e=>({...e,label:this.formatSearchResultLabel(e),value:this.formatSearchResultValue(e)})))}render(){const{label:e,isMulti:t,isDisabled:n,onChange:r,placeholder:o,selected:s}=this.props,l=this.props.className||this.getDefaultClassName();return(0,$.createElement)(q.BaseControl,{id:(0,Fr.uniqueId)(`${l}--`),label:e},(0,$.createElement)(Nr,{ref:e=>this.selectRef=e,className:l,classNamePrefix:"llms-search",isMulti:t,isDisabled:n,value:this.formatSearchResults(s||[]),defaultOptions:s,placeholder:o,loadOptions:this.onSearch,onChange:r,styles:{control:e=>({...e,borderColor:"#8d96a0","&:hover":{...e["&:hover"],borderColor:"#8d96a0"}})},theme:e=>({...e,colors:{...e.colors,primary:"#008dbe",primary25:"#ccf2ff",primary50:"#b3ecff",primary75:"#4dd2ff"},spacing:{...e.spacing,baseUnit:2,controlHeight:28,menuGutter:4}})}))}}class Br extends Vr{constructor(){super(...arguments),K(this,"getDefaultClassName",(()=>`llms-search--${this.props.postType.replace("llms_","")}`)),K(this,"getSearchPath",(()=>this.props.searchPath||`/wp/v2/${this.props.postType}`)),K(this,"formatSearchResultLabel",(e=>(0,H.sprintf)(// Translators: %1$s = Post title; %2$ = post id.
+(0,H._x)("%1$s (ID# %2$d)","Search result label","lifterlms"),e.title.rendered,e.id)))}}const $r=(0,U.createHigherOrderComponent)((e=>t=>{if(!B(wp.blocks.getBlockType(t.name),t.name))return(0,$.createElement)(e,t);const{attributes:{llms_visibility:n,llms_visibility_in:r},setAttributes:o}=t;if(!n||"off"===n)return(0,$.createElement)(e,t);let{llms_visibility_posts:s}=t.attributes;void 0===s&&(s="[]"),s=JSON.parse(s);const l=()=>{const e=wp.data.select("core/editor").getCurrentPost(),t=[];return-1!==["course","lesson"].indexOf(e.type)&&t.push({value:"this",label:(0,H.__)("in this course","lifterlms")}),t.push({value:"any_course",label:(0,H.__)("in any course","lifterlms")}),-1!==["llms_membership"].indexOf(e.type)&&t.push({value:"this",label:(0,H.__)("in this membership","lifterlms")}),t.push({value:"any_membership",label:(0,H.__)("in any membership","lifterlms")},{value:"any",label:(0,H.__)("in any course or membership","lifterlms")},{value:"list_all",label:(0,H.__)("in all of the selected courses or memberships","lifterlms")},{value:"list_any",label:(0,H.__)("in any of the selected courses or memberships","lifterlms")}),(0,V.applyFilters)("llms_blocks_block_visibility_in_options",t,e)},i=(e,t)=>{"select-option"===t.action?a(t.option):"remove-value"===t.action&&c(t.removedValue)},a=e=>{s.map((e=>{let{id:t}=e;return t})).includes(e.id)||s.push(e),u()},c=e=>{s.splice(s.map((e=>{let{id:t}=e;return t})).indexOf(e.id),1),u()},u=()=>{const e=s.map((e=>{const{id:t,title:n,type:r}=e,o={id:t,title:n,type:r};return(0,V.applyFilters)("llms_block_visibility_stored_post_props",o,e)}));o({llms_visibility_posts:JSON.stringify(e)})};return(0,$.createElement)($.Fragment,null,(0,$.createElement)(G,t,(0,$.createElement)(e,t)),(0,$.createElement)(j.InspectorControls,null,(0,$.createElement)(q.PanelBody,{title:(0,H.__)("Enrollment Visibility","lifterlms")},(0,$.createElement)(q.SelectControl,{className:"llms-visibility-select",label:(0,H.__)("Display to","lifterlms"),value:n,onChange:e=>{o({llms_visibility:e,llms_visibility_in:l()[0].value})},options:(0,V.applyFilters)("llms_block_visibility_settings_options",W)}),-1===["all","logged_in","logged_out"].indexOf(n)&&(0,$.createElement)($.Fragment,null,(0,$.createElement)(q.SelectControl,{className:"llms-visibility-select--in",label:(d=n,"enrolled"===d?(0,H.__)("Enrolled In","lifterlms"):(0,H.__)("Not Enrolled In","lifterlms")),value:r,onChange:e=>o({llms_visibility_in:e}),options:l()}),("list_all"===r||"list_any"===r)&&(0,$.createElement)("div",null,(0,$.createElement)(Br,{isMulti:!0,postType:"course",label:(0,H.__)("Courses","lifterlms"),placeholder:(0,H.__)("Search by course title…","lifterlms"),onChange:i,selected:s.filter((e=>"course"===e.type))}),(0,$.createElement)(Br,{isMulti:!0,postType:"llms_membership",label:(0,H.__)("Memberships","lifterlms"),placeholder:(0,H.__)("Search by membership title…","lifterlms"),onChange:i,selected:s.filter((e=>"llms_membership"===e.type))}))))));var d}),"withInspectorControl");(0,V.addFilter)("blocks.registerBlockType","llms/visibility-attributes",(function(e,t){if(!B(e,t))return e;e.attributes||(e.attributes={});const n={llms_visibility:{default:"all",type:"string"},llms_visibility_in:{default:"",type:"string"},llms_visibility_posts:{default:"[]",type:"string"}};return Object.keys(n).forEach((t=>{var r,o,s;e.attributes=(r=e.attributes,s=n[t],r[o=t]&&r[o].default?r[o].type=s.type:r[o]=s,r)})),e})),(0,V.addFilter)("editor.BlockEdit","llms/visibility-controls",$r);const Hr=window.wp.domReady;var Ur=n.n(Hr);const jr=window.wp.data,qr=window.wp.editor,zr=window.wp.blocks,Wr=e=>{let t=[];return e.forEach((e=>{if("core/block"===e.name){const{getBlocks:n}=(0,jr.select)(j.store);t=t.concat(Wr(n(e.clientId)))}else e.innerBlocks.length?t=t.concat(Wr(e.innerBlocks)):t.push(e)})),t},Gr=()=>{const{getBlocks:e}=(0,jr.select)(j.store);return Wr(e())},Kr=()=>!!(window.llms&&window.llms.post&&window.llms.post.post_type)&&window.llms.post.post_type;const Yr=()=>{!function(){const{_llms_form_location:e}=(0,jr.select)(qr.store).getEditedPostAttribute("meta");["registration","account"].includes(e)&&(0,V.addFilter)("llms_block_supports_visibility","llms/form-block-visibility",(()=>!1))}(),function(){const e={"llms/form-field-user-email":["all","logged_out"],"llms/form-field-user-password":["all","logged_out"],"llms/form-field-user-login":["logged_out"]},t=Object.keys(e);(0,V.addFilter)("llms_block_visibility_settings_options","llms/form-block-visibility-options",(n=>{const{getSelectedBlock:r}=(0,jr.select)(j.store),o=r();return o&&(e=>{let{name:n,innerBlocks:r}=e;return"llms/form-field-confirm-group"===n?(0,Fr.some)(r,(e=>t.includes(e.name))):t.includes(n)})(o)?n.filter((n=>{let{value:r}=n;return(n=>{let{name:r,innerBlocks:o}=n,s=r;if("llms/form-field-confirm-group"===r){const e=o.find((e=>t.includes(e.name)));s=e?e.name:s}return e[s]||[]})(o).includes(r)})):n}))}(),function(){const{_llms_form_is_core:e}=(0,jr.select)(qr.store).getEditedPostAttribute("meta"),t=[".edit-post-layout .components-panel__body.edit-post-post-status"];"yes"===e&&t.push(".edit-post-layout button.editor-post-switch-to-draft"),(0,jr.subscribe)((()=>{setTimeout((()=>{document.querySelectorAll(t.join(",")).forEach((e=>{e.style.display="none"}))}),1)}))}(),function(){const e="llms/form-field-user-email",t="llms-forms-no-email-error-notice",{getNotices:n}=(0,jr.select)("core/notices"),{createErrorNotice:r,removeNotice:o}=(0,jr.dispatch)("core/notices");(0,jr.subscribe)((0,Fr.debounce)((()=>{const s=(0,jr.select)("core/editor").getCurrentPost(),l=Gr().map((e=>e.name));if(!s.content.includes("\x3c!-- wp:")||!l.length)return;const i=n().map((e=>e.id)).includes(t),a=document.querySelector("button.editor-post-publish-button");l.includes(e)||i?l.includes(e)&&i&&(o(t),a.disabled=!1):(r((0,H.__)("User Email is a required field.","lifterlms"),{id:t,isDismissible:!1,actions:[{label:(0,H.__)("Restore user email field?","lifterlms"),onClick:()=>{((0,jr.dispatch)("core/block-editor")||(0,jr.dispatch)("core/editor")).insertBlock((0,zr.createBlock)(e),0)}}]}),a.disabled=!0)}),500))}()};function Xr(e){return e.reduce(((e,t)=>({...e,[t.name]:t})),{})}function Qr(e){return Object.values(e)}const Zr=Xr(window.llms.userInfoFields.map((e=>({...e,isPersisted:!0}))));function Jr(e,t){return{...e,[t.name]:{...t}}}function eo(e,t){return e=Qr(e).filter((e=>{let{name:n}=e;return n!==t})),Xr(e)}function to(e,t,n){return{...e,[t]:{...e[t],...n}}}function no(e,t,n){const r={...e[t]};return Jr(e=eo(e,t),{...r,name:n})}function ro(){return Zr}function oo(e){return{type:"ADD_FIELD",field:e}}function so(e){return{type:"DELETE_FIELD",name:e}}function lo(e,t){return{type:"EDIT_FIELD",name:e,edits:t}}function io(e,t){return{type:"EDIT_FIELD",name:e,edits:{clientId:t}}}function ao(e){return{type:"EDIT_FIELD",name:e,edits:{clientId:null}}}function co(e){return{type:"RECEIVE_FIELDS",fields:e}}function uo(e,t){return{type:"RENAME_FIELD",oldName:e,newName:t}}function po(){return{type:"RESET_FIELDS"}}function fo(e,t){let{fields:n}=e;return!!n[t]}function mo(e,t){let{fields:n}=e;return n[t]||null}function ho(e,t,n){let r=arguments.length>3&&void 0!==arguments[3]?arguments[3]:"global";const o="global"===r?e.fields:vo(e);return Qr(o).find((e=>e[n]===t))||null}function go(e){let{fields:t}=e;return t}function vo(e){let{fields:t}=e;return Xr(Qr(t).filter((e=>{let{clientId:t}=e;return t})))}function bo(e,t,n){const r=mo(e,t);return!(!r||!r.clientId||r.clientId===n)}function _o(e,t){return!!ho(e,t,"clientId","local")}const yo={reducer:(0,jr.combineReducers)({fields:function(){let e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:Zr,t=arguments.length>1?arguments[1]:void 0;const{type:n}=t;switch(n){case"ADD_FIELD":return Jr(e,t.field);case"DELETE_FIELD":return eo(e,t.name);case"EDIT_FIELD":return to(e,t.name,t.edits);case"RECEIVE_FIELDS":return Xr(t.fields);case"RENAME_FIELD":return no(e,t.oldName,t.newName);case"RESET_FIELDS":return ro();default:return e}}}),actions:{...e},selectors:{...t}},wo=(0,jr.createReduxStore)("llms/user-info-fields",yo);(0,jr.register)(wo);let Eo=[];const xo=(e,t)=>(0,Fr.differenceBy)(e,t,"clientId").filter((e=>{let{name:t}=e;return 0===t.indexOf("llms/form-")}));function Co(){const e=Gr(),t=xo(Eo,e),n=xo(e,Eo);Eo=e,(e=>{e.forEach((e=>{let{attributes:t}=e;const{name:n}=t,{getField:r}=(0,jr.select)(wo),{deleteField:o,unloadField:s}=(0,jr.dispatch)(wo),l=r(n);l&&(l.isPersisted?s(n):o(n))}))})(t),setTimeout((()=>{(e=>{const{fieldExists:t}=(0,jr.select)(wo),{loadField:n,addField:r}=(0,jr.dispatch)(wo);e.forEach((e=>{let{attributes:o,clientId:s}=e;const{name:l}=o;t(l)?n(l,s):r({name:l,clientId:s,id:o.id,label:o.label,data_store:o.data_store,data_store_key:o.data_store_key})}))})(n)}),100)}function ko(){(0,jr.subscribe)(Co)}const So="llms/course-continue-button",Oo=["course"],Io={title:(0,H.__)("Course Continue Button","lifterlms"),icon:{foreground:"#2295ff",src:"migrate"},category:"llms-blocks",keywords:[(0,H.__)("LifterLMS","lifterlms")],edit:e=>(0,$.createElement)("div",{className:e.className},(0,$.createElement)("p",{style:{textAlign:"center"}},(0,$.createElement)(q.Button,{isPrimary:!0,isLarge:!0},(0,H.__)("Continue","lifterlms")))),save:e=>(0,$.createElement)("div",{className:e.className,style:{textAlign:"center"}},"[lifterlms_course_continue_button]")};class To extends $.Component{render(){const{attributes:{length:e,show_cats:t,show_difficulty:n,show_length:r,show_tags:o,show_tracks:s,title_size:l},setAttributes:i}=this.props;return(0,$.createElement)(j.InspectorControls,null,(0,$.createElement)(q.PanelBody,{title:(0,H.__)("Course Information Options","lifterlms")},(0,$.createElement)(q.SelectControl,{label:(0,H.__)("Title Headline Size","lifterlms"),value:l,onChange:e=>i({title_size:e}),help:(0,H.__)("Headline size for the information title element.","lifterlms"),options:[{value:"h1",label:"h1"},{value:"h2",label:"h2"},{value:"h3",label:"h3"},{value:"h4",label:"h4"},{value:"h5",label:"h5"},{value:"h6",label:"h6"}]}),(0,$.createElement)(q.TextControl,{label:(0,H.__)("Estimated Completion Time","lifterlms"),value:e,onChange:e=>i({length:e}),help:(0,H.__)("How many hours, days, weeks, etc… should a student expect to spend in order to complete this course.","lifterlms")}),(0,$.createElement)(q.ToggleControl,{label:(0,H.__)("Display Estimated Time","lifterlms"),checked:!!r,onChange:()=>i({show_length:!r}),help:r?(0,H.__)("Displaying estimated time","lifterlms"):(0,H.__)("Hiding estimated time","lifterlms")}),(0,$.createElement)(q.ToggleControl,{label:(0,H.__)("Display Difficulty","lifterlms"),checked:!!n,onChange:()=>i({show_difficulty:!n}),help:n?(0,H.__)("Displaying difficulty","lifterlms"):(0,H.__)("Hiding difficulty","lifterlms")}),(0,$.createElement)(q.ToggleControl,{label:(0,H.__)("Display Tracks","lifterlms"),checked:!!s,onChange:()=>i({show_tracks:!s}),help:s?(0,H.__)("Displaying tracks list","lifterlms"):(0,H.__)("Hiding tracks list","lifterlms")}),(0,$.createElement)(q.ToggleControl,{label:(0,H.__)("Display Categories","lifterlms"),checked:!!t,onChange:()=>i({show_cats:!t}),help:t?(0,H.__)("Displaying categories list","lifterlms"):(0,H.__)("Hiding categories list","lifterlms")}),(0,$.createElement)(q.ToggleControl,{label:(0,H.__)("Display Tags","lifterlms"),checked:!!o,onChange:()=>i({show_tags:!o}),help:o?(0,H.__)("Displaying tags list","lifterlms"):(0,H.__)("Hiding tags list","lifterlms")})))}}class Do extends $.Component{constructor(){super(...arguments),K(this,"state",{terms:!1})}getTerms(){const{currentPost:e,taxonomy:t}=this.props,n=e._links["wp:term"].filter((e=>e.taxonomy===t))[0].href;wp.apiFetch({url:wp.url.addQueryArgs(n,{per_page:-1})}).then((e=>{this.setState({terms:e})}))}componentDidUpdate(e){e.currentPost[this.props.taxonomy]!==this.props.currentPost[this.props.taxonomy]&&this.getTerms()}componentWillMount(){this.getTerms()}renderTerms(e){const t=e.length-1;return(0,$.createElement)($.Fragment,null,e?e.map(((e,n)=>this.renderTerm(e,t===n))):(0,H.__)("Loading…","lifterlms"))}renderTerm(e,t){return(0,$.createElement)($.Fragment,null,(0,$.createElement)("a",{href:e.link,target:"_blank",rel:"noreferrer"},e.name),t?"":", ")}render(){const{terms:e}=this.state,{taxonomyName:t}=this.props;return Array.isArray(e)&&!e.length?"":(0,$.createElement)("li",null,(0,$.createElement)("strong",null,t),": ",this.renderTerms(e))}}const Po="llms/course-information",Ro=["course"],Mo={title:(0,H.__)("Course Information","lifterlms"),icon:{foreground:"#2295ff",src:"list-view"},category:"llms-blocks",keywords:[(0,H.__)("LifterLMS","lifterlms")],attributes:{title:{type:"string",default:(0,H.__)("Course Information","lifterlms")},title_size:{type:"string",default:"h3"},length:{type:"string",default:"",source:"meta",meta:"_llms_length"},show_cats:{type:"boolean",default:!0},show_difficulty:{type:"boolean",default:!0},show_length:{type:"boolean",default:!0},show_tags:{type:"boolean",default:!0},show_tracks:{type:"boolean",default:!0}},supports:{multiple:!1},edit:e=>{const{attributes:t,setAttributes:n}=e,{length:r,show_cats:o,show_difficulty:s,show_length:l,show_tags:i,show_tracks:a,title:c,title_size:u}=t,d=wp.data.select("core/editor").getCurrentPost(),p=l||s||a||o||i;return(0,$.createElement)($.Fragment,null,(0,$.createElement)(To,{attributes:t,setAttributes:n}),(0,$.createElement)("div",{className:e.className},(0,$.createElement)(j.RichText,{tagName:u,value:c,onChange:e=>n({title:e})}),p&&(0,$.createElement)($.Fragment,null,(0,$.createElement)("ul",null,l&&r&&(0,$.createElement)("li",null,(0,$.createElement)("strong",null,(0,H.__)("Estimated Time","lifterlms")),": ",r),s&&(0,$.createElement)(Do,{currentPost:d,taxonomy:"course_difficulty",taxonomyName:(0,H.__)("Difficulty","lifterlms")}),a&&(0,$.createElement)(Do,{currentPost:d,taxonomy:"course_track",taxonomyName:(0,H.__)("Tracks","lifterlms")}),o&&(0,$.createElement)(Do,{currentPost:d,taxonomy:"course_cat",taxonomyName:(0,H.__)("Categories","lifterlms")}),i&&(0,$.createElement)(Do,{currentPost:d,taxonomy:"course_tag",taxonomyName:(0,H.__)("Tags","lifterlms")})))))},save:()=>null},Lo=["course"],Ao="llms/course-progress",No={title:(0,H.__)("Course Progress","lifterlms"),icon:{foreground:"#2295ff",src:"chart-area"},category:"llms-blocks",keywords:[(0,H.__)("LifterLMS","lifterlms")],supports:{llms_visibility:!1},edit:e=>(0,$.createElement)("div",{className:e.className},(0,$.createElement)("div",{className:"progress-bar",value:"50",max:"100"},(0,$.createElement)("div",{className:"progress--fill"})),(0,$.createElement)("span",null,"50%")),save:()=>null,deprecated:[{save:e=>(0,$.createElement)("div",{className:e.className},"[lifterlms_course_progress]")}]},Fo=window.wp.serverSideRender;var Vo=n.n(Fo);const Bo="llms/course-syllabus",$o=["course"],Ho={title:(0,H.__)("Course Syllabus","lifterlms"),icon:{foreground:"#2295ff",src:"grid-view"},category:"llms-blocks",keywords:[(0,H.__)("LifterLMS","lifterlms")],attributes:{course_id:{type:"int",default:0}},edit:e=>{const t=wp.data.select("core/editor").getCurrentPost(),{attributes:n}=e;return(0,$.createElement)($.Fragment,null,(0,$.createElement)(Vo(),{block:Bo,attributes:n,urlQueryArgs:{post_id:t.id}}))},save:()=>null};class Uo extends $.Component{constructor(){super(...arguments),K(this,"render",(()=>{const{name:e,attributes:t,post_id:n}=this.props;return(0,$.createElement)($.Fragment,null,(0,$.createElement)(Vo(),{block:e,attributes:t,urlQueryArgs:{post_id:n}}))})),this.state={instructors:this.props.instructors}}}const jo=(0,U.compose)([(0,jr.withSelect)((e=>{const{getEditedPostAttribute:t,getCurrentPostId:n}=e("core/editor");return{post_id:n(),instructors:t("instructors")}}))])(Uo),qo="llms/instructors",zo=["course","llms_membership"],Wo={title:(0,H.__)("Instructors","lifterlms"),icon:{foreground:"#2295ff",src:"groups"},category:"llms-blocks",keywords:[(0,H.__)("LifterLMS","lifterlms"),(0,H.__)("Course","lifterlms"),(0,H.__)("Memebership","lifterlms")],attributes:{post_id:{type:"int",default:0}},edit:jo,save:()=>null},Go="llms/lesson-navigation",Ko=["lesson"],Yo={title:(0,H.__)("Lesson Navigation","lifterlms"),icon:{foreground:"#2295ff",src:"leftright"},category:"llms-blocks",keywords:[(0,H.__)("LifterLMS","lifterlms")],edit(e){const t=wp.data.select("core/editor").getCurrentPost(),{attributes:n}=e;return(0,$.createElement)($.Fragment,null,(0,$.createElement)(Vo(),{block:Go,attributes:n,urlQueryArgs:{post_id:t.id}}))},save:()=>null},Xo="llms/lesson-progression",Qo=["lesson"],Zo={title:(0,H.__)("Lesson Progression (Mark Complete)","lifterlms"),icon:{foreground:"#2295ff",src:"yes"},category:"llms-blocks",keywords:[(0,H.__)("LifterLMS","lifterlms")],supports:{llms_visibility:!1},edit(){const e=1*(0,jr.select)("core/editor").getCurrentPost().meta._llms_quiz;let t=!e;return t=(0,V.applyFilters)("llms.lessonProgressBlock.showMainBtn",t),(0,$.createElement)($.Fragment,null,!!e&&(0,$.createElement)(q.Button,{className:"llms-prog-btn--quiz",isPrimary:!0},(0,H.__)("Take Quiz","lifterlms")),t&&(0,$.createElement)(q.Button,{className:"llms-prog-btn--complete",isPrimary:!0},(0,H.__)("Mark Complete","lifterlms")))},save:()=>null},Jo=window.jQuery;var es=n.n(Jo);let ts=null;(0,jr.subscribe)((()=>{const{getCurrentPostLastRevisionId:e,isCurrentPostPublished:t,isSavingPost:n,isPublishingPost:r}=(0,jr.select)("core/editor");if(!t())return;const o=es()("#llms-save-access-plans");o.length&&ts!==e()&&"disabled"!==o.attr("disabled")&&(n()||r())&&(ts=e(),o.trigger("click"))})),es()(document).on("llms-access-plan-validation-errors",(function(){(0,jr.dispatch)("core/notices").createErrorNotice((0,H.__)("Validation errors were encountered while attempting to save your access plans.","lifterlms"),{id:"llms-access-plan-error-notice"})}));const ns="llms/pricing-table",rs=["course","llms_membership"],os={title:(0,H.__)("LifterLMS Pricing Table","lifterlms"),icon:{foreground:"#2295ff",src:"cart"},category:"llms-blocks",keywords:[(0,H.__)("LifterLMS","lifterlms")],attributes:{post_id:{type:"int",default:0}},edit:e=>{const{attributes:t}=e;return es()(document).one("llms-access-plans-updated",(function(){(0,jr.dispatch)("core/editor").replaceBlock(e.clientId,(0,zr.createBlock)(ns)),setTimeout((function(){(0,jr.dispatch)("core/editor").savePost()}),500)})),(0,$.createElement)($.Fragment,null,(0,$.createElement)(Vo(),{block:ns,attributes:t,urlQueryArgs:{post_id:(0,jr.select)("core/editor").getCurrentPostId()}}))},save:()=>null},ss="llms/php-template",ls={title:(0,H.__)("LifterLMS PHP Template","lifterlms"),category:"llms-blocks",keywords:[(0,H.__)("LifterLMS","lifterlms")],attributes:{template:{type:"string",default:""},title:{type:"string",default:""}},supports:{html:!1,multiple:!1,reusable:!1,inserter:!1},edit:function(e){const{attributes:t}=e,{template:n}=t,r=(0,j.useBlockProps)();let{title:o}=t;if(!o){const e=window.llmsBlockTemplatesL10n;o=e&&e[n]?e[n]:n}return(0,$.createElement)("div",r,(0,$.createElement)(q.Placeholder,{label:o,className:"wp-block-liftelrms-php-template__placeholder"},(0,$.createElement)("div",{className:"wp-block-liftelrms-php-template__placeholder-copy"},(0,$.createElement)("p",{className:"wp-block-liftelrms-php-template__placeholder-warning"},(0,$.createElement)("strong",null,(0,H.__)("Attention: Do not remove this block!","lifterlms"))," ",(0,H.__)("Removal will cause unintended effects on your LMS site.","lifterlms")),(0,$.createElement)("p",null,(0,H.sprintf)(
+/* translators: %s is the template title */
+(0,H.__)("This is an editor placeholder for the %s. On your site this will be replaced by the relevant template. You can move this placeholder around and add further blocks around it to extend the template.","lifterlms"),o)))))},save:()=>null};function is(e){if(!e)return[e];if(e.innerBlocks.length)return as(e.innerBlocks);if("core/block"===e.name){const{blocks:t}=(0,jr.select)("core").getEditedEntityRecord("postType","wp_block",e.attributes.ref);return as(t)}return-1===e.name.indexOf("llms/form-field")?[]:[e]}function as(){let e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:[],t=[];return(0,Fr.forEach)(e,(e=>{const n=is(e);n.length&&(t=t.concat(n))})),t}const cs=function(e,t){let n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:"global";const{getFieldBy:r}=(0,jr.select)(wo);return!r(t,e,n)};if("wp_block"===Kr()){let e="";(0,jr.subscribe)((()=>{const t=(0,jr.select)("core/editor").getEditedPostContent();if(void 0===t||t===e)return;e=t;const n=t.includes("\x3c!-- wp:llms/form-field")?"yes":"no";(0,jr.dispatch)("core/editor").editPost({is_llms_field:n})}))}(0,V.addFilter)("blocks.getSaveElement","llms/core-block/save",((e,t,n)=>{if("core/block"!==t.name)return e;const{ref:r}=n;if((0,jr.select)("core").hasFinishedResolution("getEntityRecord",["postType","wp_block",r])){const e=function(){let e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:[];return Array.isArray(e)||(e=[e]),as(e)}(function(e){let t=!1;return(0,Fr.some)((0,jr.select)("core/block-editor").getBlocks(),(n=>{const r=n.attributes.ref===e;return r&&(t=n),r})),t}(r));e.length&&setTimeout((()=>{(0,jr.dispatch)("core").editEntityRecord("postType","wp_block",n.ref,{is_llms_field:e.length>0?"yes":"no"})}))}return e}));const us=(0,U.withInstanceId)((function(e){let{options:t,fieldType:n,instanceId:r}=e;return(0,$.createElement)($.Fragment,null,t.map(((e,t)=>(0,$.createElement)("label",{htmlFor:`llms-${n}-${r}-${t}`,key:t,style:{display:"block",pointerEvents:"none"}},(0,$.createElement)("input",{id:`llms-${n}-${r}-${t}`,type:n,checked:"yes"===e.default,readOnly:!0})," ",e.text))))}));class ds extends $.Component{getFieldType(){const{attributes:{field:e}}=this.props;return-1!==["email","text","number","url","tel"].indexOf(e)?"input":e}render(){const{attributes:e,setAttributes:t,block:n,clientId:r}=this.props,{description:o,label:s,options:l,placeholder:i,required:a}=e,c=n.supports.llms_edit_fill,u=[];a&&u.push("llms-is-required");const d=this.getFieldType();return(0,$.createElement)($.Fragment,null,(0,$.createElement)("div",{className:"llms-field"},"html"!==d&&(0,$.createElement)(j.RichText,{tagName:"label",className:u.join(" "),value:s,onChange:e=>{t({label:e})},allowedFormats:["bold","italic"],"aria-label":s?(0,H.__)("Field label"):(0,H.__)("Empty field label; start writing to add a label"),placeholder:(0,H.__)("Enter a label")}),"input"===d&&(0,$.createElement)("input",{onChange:e=>t({placeholder:e.target.value}),value:i,placeholder:(0,H.__)("Add optional placeholder text","lifterlms")}),"password"===d&&(0,$.createElement)("input",{disabled:"disabed",type:"password",value:"F4K3p4$50Rd"}),"textarea"===d&&(0,$.createElement)("textarea",{rows:this.props.attributes.html_attrs.rows,onChange:e=>t({placeholder:e.target.value}),value:i,placeholder:(0,H.__)("Add optional placeholder text","lifterlms")}),"select"===d&&(0,$.createElement)("select",null,(0,$.createElement)("option",null,(()=>{if(i)return i;if(!l.length)return"";let e=l[0].text;const t=l.filter((e=>"yes"===e.default));return t.length&&(e=t[0].text),e})())),(0,$.createElement)(j.RichText,{tagName:"span",value:o,onChange:e=>{t({description:e})},allowedFormats:["bold","strikethrough","link"],"aria-label":s?(0,H.__)("Optional field description"):(0,H.__)("Empty field description; start writing to add a description"),placeholder:(0,H.__)("Add optional description text"),style:{color:"#808285",fontStyle:"italic"}}),("radio"===d||"checkbox"===d)&&(0,$.createElement)(us,{options:l,fieldType:d})),c.after&&(0,$.createElement)(q.Slot,{name:`llmsEditFill.after.${c.after}.${r}`}))}}var ps,fs=new Uint8Array(16);function ms(){if(!ps&&!(ps="undefined"!=typeof crypto&&crypto.getRandomValues&&crypto.getRandomValues.bind(crypto)||"undefined"!=typeof msCrypto&&"function"==typeof msCrypto.getRandomValues&&msCrypto.getRandomValues.bind(msCrypto)))throw new Error("crypto.getRandomValues() not supported. See https://github.com/uuidjs/uuid#getrandomvalues-not-supported");return ps(fs)}const hs=/^(?:[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}|00000000-0000-0000-0000-000000000000)$/i,gs=function(e){return"string"==typeof e&&hs.test(e)};for(var vs=[],bs=0;bs<256;++bs)vs.push((bs+256).toString(16).substr(1));const _s=function(e,t,n){var r=(e=e||{}).random||(e.rng||ms)();if(r[6]=15&r[6]|64,r[8]=63&r[8]|128,t){n=n||0;for(var o=0;o<16;++o)t[n+o]=r[o];return t}return function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:0,n=(vs[e[t+0]]+vs[e[t+1]]+vs[e[t+2]]+vs[e[t+3]]+"-"+vs[e[t+4]]+vs[e[t+5]]+"-"+vs[e[t+6]]+vs[e[t+7]]+"-"+vs[e[t+8]]+vs[e[t+9]]+"-"+vs[e[t+10]]+vs[e[t+11]]+vs[e[t+12]]+vs[e[t+13]]+vs[e[t+14]]+vs[e[t+15]]).toLowerCase();if(!gs(n))throw TypeError("Stringified UUID is invalid");return n}(r)};class ys extends Vr{constructor(){super(...arguments),K(this,"getDefaultClassName",(()=>"llms-search--user")),K(this,"getSearchPath",(()=>this.props.searchPath||"/wp/v2/users")),K(this,"formatSearchResultLabel",(e=>(0,H.sprintf)(// Translators: %1$s = User's name; %2$s = User's id.
+(0,H._x)("%1$s (ID# %2$d)","User search result label","lifterlms"),e.name,e.id)))}getSearchArgs(e){const t=super.getSearchArgs(e),{roles:n}=this.props;return n&&(t.roles=Array.isArray(n)?n.join(","):n),t}}const ws="undefined"!=typeof window&&void 0!==window.document&&void 0!==window.document.createElement,Es=ws?Z.useLayoutEffect:Z.useEffect;function xs(e,t){const n=(0,Z.useRef)();return(0,Z.useMemo)((()=>{const t=e(n.current);return n.current=t,t}),[...t])}function Cs(){const e=(0,Z.useRef)(null),t=(0,Z.useCallback)((t=>{e.current=t}),[]);return[e,t]}let ks={};function Ss(e,t){return(0,Z.useMemo)((()=>{if(t)return t;const n=null==ks[e]?0:ks[e]+1;return ks[e]=n,`${e}-${n}`}),[e,t])}function Os(e){return(t,...n)=>n.reduce(((t,n)=>{const r=Object.entries(n);for(const[n,o]of r){const r=t[n];null!=r&&(t[n]=r+e*o)}return t}),{...t})}const Is=Os(1),Ts=Os(-1),Ds=Object.freeze({Translate:{toString(e){if(!e)return;const{x:t,y:n}=e;return`translate3d(${t?Math.round(t):0}px, ${n?Math.round(n):0}px, 0)`}},Scale:{toString(e){if(!e)return;const{scaleX:t,scaleY:n}=e;return`scaleX(${t}) scaleY(${n})`}},Transform:{toString(e){if(e)return[Ds.Translate.toString(e),Ds.Scale.toString(e)].join(" ")}},Transition:{toString:({property:e,duration:t,easing:n})=>`${e} ${t}ms ${n}`}}),Ps={display:"none"};function Rs(e){let{id:t,value:n}=e;return J().createElement("div",{id:t,style:Ps},n)}const Ms={position:"fixed",width:1,height:1,margin:-1,border:0,padding:0,overflow:"hidden",clip:"rect(0 0 0 0)",clipPath:"inset(100%)",whiteSpace:"nowrap"};function Ls(e){let{id:t,announcement:n}=e;return J().createElement("div",{id:t,style:Ms,role:"status","aria-live":"assertive","aria-atomic":!0},n)}const As={draggable:"\n To pick up a draggable item, press the space bar.\n While dragging, use the arrow keys to move the item.\n Press space again to drop the item in its new position, or press escape to cancel.\n "},Ns={onDragStart:e=>`Picked up draggable item ${e}.`,onDragOver:(e,t)=>t?`Draggable item ${e} was moved over droppable area ${t}.`:`Draggable item ${e} is no longer over a droppable area.`,onDragEnd:(e,t)=>t?`Draggable item ${e} was dropped over droppable area ${t}`:`Draggable item ${e} was dropped.`,onDragCancel:e=>`Dragging was cancelled. Draggable item ${e} was dropped.`};var Fs;!function(e){e.DragStart="dragStart",e.DragMove="dragMove",e.DragEnd="dragEnd",e.DragCancel="dragCancel",e.DragOver="dragOver",e.RegisterDroppable="registerDroppable",e.SetDroppableDisabled="setDroppableDisabled",e.UnregisterDroppable="unregisterDroppable"}(Fs||(Fs={}));const Vs=e=>Bs(e,((e,t)=>e{const n=Ws(t,t.left,t.top),r=e.map((([e,t])=>qs(Ws(t),n))),o=Vs(r);return e[o]?e[o][0]:null};function Ks(e){return function(t,...n){return n.reduce(((t,n)=>({...t,top:t.top+e*n.y,bottom:t.bottom+e*n.y,left:t.left+e*n.x,right:t.right+e*n.x,offsetLeft:t.offsetLeft+e*n.x,offsetTop:t.offsetTop+e*n.y})),{...t})}}const Ys=Ks(1);function Xs(e){const t=[];return e?function e(n){return n?n instanceof Document&&null!=n.scrollingElement?(t.push(n.scrollingElement),t):!(n instanceof HTMLElement)||n instanceof SVGElement?t:(function(e){const t=window.getComputedStyle(e),n=/(auto|scroll|overlay)/;return null!=["overflow","overflowX","overflowY"].find((e=>{const r=t[e];return"string"==typeof r&&n.test(r)}))}(n)&&t.push(n),e(n.parentNode)):t}(e.parentNode):t}function Qs(e){return ws?e===document.scrollingElement||e instanceof Document?window:e instanceof HTMLElement?e:null:null}function Zs(e){return e instanceof Window?{x:e.scrollX,y:e.scrollY}:{x:e.scrollLeft,y:e.scrollTop}}var Js;function el(e){const t={x:0,y:0},n={x:e.scrollWidth-e.clientWidth,y:e.scrollHeight-e.clientHeight};return{isTop:e.scrollTop<=t.y,isLeft:e.scrollLeft<=t.x,isBottom:e.scrollTop>=n.y,isRight:e.scrollLeft>=n.x,maxScroll:n,minScroll:t}}!function(e){e[e.Forward=1]="Forward",e[e.Backward=-1]="Backward"}(Js||(Js={}));const tl={x:.2,y:.2};function nl(e,t,{top:n,left:r,right:o,bottom:s},l=10,i=tl){const{clientHeight:a,clientWidth:c}=e,u=(d=e,ws&&d&&d===document.scrollingElement?{top:0,left:0,right:c,bottom:a,width:c,height:a}:t);var d;const{isTop:p,isBottom:f,isLeft:m,isRight:h}=el(e),g={x:0,y:0},v={x:0,y:0},b=u.height*i.y,_=u.width*i.x;return!p&&n<=u.top+b?(g.y=Js.Backward,v.y=l*Math.abs((u.top+b-n)/b)):!f&&s>=u.bottom-b&&(g.y=Js.Forward,v.y=l*Math.abs((u.bottom-b-s)/b)),!h&&o>=u.right-_?(g.x=Js.Forward,v.x=l*Math.abs((u.right-_-o)/_)):!m&&r<=u.left+_&&(g.x=Js.Backward,v.x=l*Math.abs((u.left+_-r)/_)),{direction:g,speed:v}}function rl(e){if(e===document.scrollingElement){const{innerWidth:e,innerHeight:t}=window;return{top:0,left:0,right:e,bottom:t,width:e,height:t}}const{top:t,left:n,right:r,bottom:o}=e.getBoundingClientRect();return{top:t,left:n,right:r,bottom:o,width:e.clientWidth,height:e.clientHeight}}function ol(e){return e.reduce(((e,t)=>Is(e,Zs(t))),js)}function sl(e,t,n=js){if(!(e&&e instanceof HTMLElement))return n;const r={x:n.x+e.offsetLeft,y:n.y+e.offsetTop};return e.offsetParent===t?r:sl(e.offsetParent,t,r)}function ll(e){const{offsetWidth:t,offsetHeight:n}=e,{x:r,y:o}=sl(e,null);return{width:t,height:n,offsetTop:o,offsetLeft:r}}function il(e){if(e instanceof Window){const e=window.innerWidth,t=window.innerHeight;return{top:0,left:0,right:e,bottom:t,width:e,height:t,offsetTop:0,offsetLeft:0}}const{offsetTop:t,offsetLeft:n}=ll(e),{width:r,height:o,top:s,bottom:l,left:i,right:a}=e.getBoundingClientRect();return{width:r,height:o,top:s,bottom:l,right:a,left:i,offsetTop:t,offsetLeft:n}}function al(e){const{width:t,height:n,offsetTop:r,offsetLeft:o}=ll(e),s=ol(Xs(e)),l=r-s.y,i=o-s.x;return{width:t,height:n,top:l,bottom:l+n,right:i+t,left:i,offsetTop:r,offsetLeft:o}}function cl(e){return"top"in e}function ul(e,t=e.offsetLeft,n=e.offsetTop){return[{x:t,y:n},{x:t+e.width,y:n},{x:t,y:n+e.height},{x:t+e.width,y:n+e.height}]}const dl=(e,t)=>{const n=e.map((([e,n])=>function(e,t){const n=Math.max(t.top,e.offsetTop),r=Math.max(t.left,e.offsetLeft),o=Math.min(t.left+t.width,e.offsetLeft+e.width),s=Math.min(t.top+t.height,e.offsetTop+e.height),l=o-r,i=s-n;if(re>t));return n[r]<=0?null:e[r]?e[r][0]:null};function pl(e){return e instanceof HTMLElement?e.ownerDocument:document}function fl(){return{draggable:{active:null,initialCoordinates:{x:0,y:0},nodes:{},translate:{x:0,y:0}},droppable:{containers:{}}}}function ml(e,t){switch(t.type){case Fs.DragStart:return{...e,draggable:{...e.draggable,initialCoordinates:t.initialCoordinates,active:t.active}};case Fs.DragMove:return e.draggable.active?{...e,draggable:{...e.draggable,translate:{x:t.coordinates.x-e.draggable.initialCoordinates.x,y:t.coordinates.y-e.draggable.initialCoordinates.y}}}:e;case Fs.DragEnd:case Fs.DragCancel:return{...e,draggable:{...e.draggable,active:null,initialCoordinates:{x:0,y:0},translate:{x:0,y:0}}};case Fs.RegisterDroppable:{const{element:n}=t,{id:r}=n;return{...e,droppable:{...e.droppable,containers:{...e.droppable.containers,[r]:n}}}}case Fs.SetDroppableDisabled:{const{id:n,disabled:r}=t,o=e.droppable.containers[n];return o?{...e,droppable:{...e.droppable,containers:{...e.droppable.containers,[n]:{...o,disabled:r}}}}:e}case Fs.UnregisterDroppable:{const{id:n}=t;return{...e,droppable:{...e.droppable,containers:Hs(n,e.droppable.containers)}}}default:return e}}const hl=(0,Z.createContext)({type:null,event:null});function gl({announcements:e=Ns,hiddenTextDescribedById:t,screenReaderInstructions:n}){const{announce:r,announcement:o}=function(){const[e,t]=(0,Z.useState)("");return{announce:(0,Z.useCallback)((e=>{null!=e&&t(e)}),[]),announcement:e}}(),s=Ss("DndLiveRegion"),[l,i]=(0,Z.useState)(!1);return(0,Z.useEffect)((()=>{i(!0)}),[]),function({onDragStart:e,onDragMove:t,onDragOver:n,onDragEnd:r,onDragCancel:o}){const s=(0,Z.useContext)(hl),l=(0,Z.useRef)(s);(0,Z.useEffect)((()=>{if(s!==l.current){const{type:i,event:a}=s;switch(i){case Fs.DragStart:null==e||e(a);break;case Fs.DragMove:null==t||t(a);break;case Fs.DragOver:null==n||n(a);break;case Fs.DragCancel:null==o||o(a);break;case Fs.DragEnd:null==r||r(a)}l.current=s}}),[s,e,t,n,r,o])}((0,Z.useMemo)((()=>({onDragStart({active:t}){r(e.onDragStart(t.id))},onDragMove({active:t,over:n}){e.onDragMove&&r(e.onDragMove(t.id,null==n?void 0:n.id))},onDragOver({active:t,over:n}){r(e.onDragOver(t.id,null==n?void 0:n.id))},onDragEnd({active:t,over:n}){r(e.onDragEnd(t.id,null==n?void 0:n.id))},onDragCancel({active:t}){r(e.onDragCancel(t.id))}})),[r,e])),l?(0,Bt.createPortal)(J().createElement(J().Fragment,null,J().createElement(Rs,{id:t,value:n.draggable}),J().createElement(Ls,{id:s,announcement:o})),document.body):null}var vl,bl,_l,yl;function wl(e){const t=(0,Z.useRef)(e);return Es((()=>{t.current!==e&&(t.current=e)}),[e]),t}!function(e){e[e.Pointer=0]="Pointer",e[e.DraggableRect=1]="DraggableRect"}(vl||(vl={})),function(e){e[e.TreeOrder=0]="TreeOrder",e[e.ReversedTreeOrder=1]="ReversedTreeOrder"}(bl||(bl={})),function(e){e[e.Always=0]="Always",e[e.BeforeDragging=1]="BeforeDragging",e[e.WhileDragging=2]="WhileDragging"}(_l||(_l={})),function(e){e.Optimized="optimized"}(yl||(yl={}));const El=new Map;const xl={strategy:_l.WhileDragging,frequency:yl.Optimized},Cl=[],kl=Il(il),Sl=Tl(il),Ol=Il(al);function Il(e){return function(t,n){const r=(0,Z.useRef)(t);return xs((o=>t?n||!o&&t||t!==r.current?t instanceof HTMLElement&&null==t.parentNode?null:e(t):null!=o?o:null:null),[t,n])}}function Tl(e){const t=[];return function(n,r){const o=(0,Z.useRef)(n);return xs((s=>n.length?r||!s&&n.length||n!==o.current?n.map((t=>e(t))):null!=s?s:t:t),[n,r])}}function Dl(e,t){return(0,Z.useMemo)((()=>({sensor:e,options:null!=t?t:{}})),[e,t])}class Pl{constructor(e){this.target=e,this.listeners=[]}add(e,t,n){this.target.addEventListener(e,t,n),this.listeners.push({eventName:e,handler:t})}removeAll(){this.listeners.forEach((({eventName:e,handler:t})=>this.target.removeEventListener(e,t)))}}function Rl(e,t){const n=Math.abs(e.x),r=Math.abs(e.y);return"number"==typeof t?Math.sqrt(n**2+r**2)>t:"x"in t&&"y"in t?n>t.x&&r>t.y:"x"in t?n>t.x:"y"in t&&r>t.y}var Ml;!function(e){e.Space="Space",e.Down="ArrowDown",e.Right="ArrowRight",e.Left="ArrowLeft",e.Up="ArrowUp",e.Esc="Escape",e.Enter="Enter"}(Ml||(Ml={}));const Ll={start:[Ml.Space,Ml.Enter],cancel:[Ml.Esc],end:[Ml.Space,Ml.Enter]},Al=(e,{currentCoordinates:t})=>{switch(e.code){case Ml.Right:return{...t,x:t.x+25};case Ml.Left:return{...t,x:t.x-25};case Ml.Down:return{...t,y:t.y+25};case Ml.Up:return{...t,y:t.y-25}}};class Nl{constructor(e){this.props=e,this.autoScrollEnabled=!1,this.coordinates=js;const{event:{target:t}}=e;this.props=e,this.listeners=new Pl(pl(t)),this.windowListeners=new Pl(function(e){var t;return null!=(t=pl(e).defaultView)?t:window}(t)),this.handleKeyDown=this.handleKeyDown.bind(this),this.handleCancel=this.handleCancel.bind(this),this.attach()}attach(){this.handleStart(),setTimeout((()=>{this.listeners.add("keydown",this.handleKeyDown),this.windowListeners.add("resize",this.handleCancel)}))}handleStart(){const{activeNode:e,onStart:t}=this.props;if(!e.node.current)throw new Error("Active draggable node is undefined");const n=il(e.node.current),r={x:n.left,y:n.top};this.coordinates=r,t(r)}handleKeyDown(e){if(e instanceof KeyboardEvent){const{coordinates:t}=this,{active:n,context:r,options:o}=this.props,{keyboardCodes:s=Ll,coordinateGetter:l=Al,scrollBehavior:i="smooth"}=o,{code:a}=e;if(s.end.includes(a))return void this.handleEnd(e);if(s.cancel.includes(a))return void this.handleCancel(e);const c=l(e,{active:n,context:r.current,currentCoordinates:t});if(c){const n={x:0,y:0},{scrollableAncestors:o}=r.current;for(const r of o){const o=e.code,s=Ts(c,t),{isTop:l,isRight:a,isLeft:u,isBottom:d,maxScroll:p,minScroll:f}=el(r),m=rl(r),h={x:Math.min(o===Ml.Right?m.right-m.width/2:m.right,Math.max(o===Ml.Right?m.left:m.left+m.width/2,c.x)),y:Math.min(o===Ml.Down?m.bottom-m.height/2:m.bottom,Math.max(o===Ml.Down?m.top:m.top+m.height/2,c.y))},g=o===Ml.Right&&!a||o===Ml.Left&&!u,v=o===Ml.Down&&!d||o===Ml.Up&&!l;if(g&&h.x!==c.x){if(o===Ml.Right&&r.scrollLeft+s.x<=p.x||o===Ml.Left&&r.scrollLeft+s.x>=f.x)return void r.scrollBy({left:s.x,behavior:i});n.x=o===Ml.Right?r.scrollLeft-p.x:r.scrollLeft-f.x,r.scrollBy({left:-n.x,behavior:i});break}if(v&&h.y!==c.y){if(o===Ml.Down&&r.scrollTop+s.y<=p.y||o===Ml.Up&&r.scrollTop+s.y>=f.y)return void r.scrollBy({top:s.y,behavior:i});n.y=o===Ml.Down?r.scrollTop-p.y:r.scrollTop-f.y,r.scrollBy({top:-n.y,behavior:i});break}}this.handleMove(e,Is(c,n))}}}handleMove(e,t){const{onMove:n}=this.props;e.preventDefault(),n(t),this.coordinates=t}handleEnd(e){const{onEnd:t}=this.props;e.preventDefault(),this.detach(),t()}handleCancel(e){const{onCancel:t}=this.props;e.preventDefault(),this.detach(),t()}detach(){this.listeners.removeAll(),this.windowListeners.removeAll()}}function Fl(e){return Boolean(e&&"distance"in e)}function Vl(e){return Boolean(e&&"delay"in e)}var Bl;Nl.activators=[{eventName:"onKeyDown",handler:(e,{keyboardCodes:t=Ll,onActivation:n})=>{const{code:r}=e.nativeEvent;return!!t.start.includes(r)&&(e.preventDefault(),null==n||n({event:e.nativeEvent}),!0)}}],function(e){e.Keydown="keydown"}(Bl||(Bl={}));class $l{constructor(e,t,n=function(e){return e instanceof EventTarget?e:pl(e)}(e.event.target)){this.props=e,this.events=t,this.autoScrollEnabled=!0,this.activated=!1,this.timeoutId=null;const{event:r}=e;this.props=e,this.events=t,this.ownerDocument=pl(r.target),this.listeners=new Pl(n),this.initialCoordinates=zs(r),this.handleStart=this.handleStart.bind(this),this.handleMove=this.handleMove.bind(this),this.handleEnd=this.handleEnd.bind(this),this.handleKeydown=this.handleKeydown.bind(this),this.attach()}attach(){const{events:e,props:{options:{activationConstraint:t}}}=this;if(this.listeners.add(e.move.name,this.handleMove,!1),this.listeners.add(e.end.name,this.handleEnd),this.ownerDocument.addEventListener(Bl.Keydown,this.handleKeydown),t){if(Fl(t))return;if(Vl(t))return void(this.timeoutId=setTimeout(this.handleStart,t.delay))}this.handleStart()}detach(){this.listeners.removeAll(),this.ownerDocument.removeEventListener(Bl.Keydown,this.handleKeydown),null!==this.timeoutId&&(clearTimeout(this.timeoutId),this.timeoutId=null)}handleStart(){const{initialCoordinates:e}=this,{onStart:t}=this.props;e&&(this.activated=!0,t(e))}handleMove(e){const{activated:t,initialCoordinates:n,props:r}=this,{onMove:o,options:{activationConstraint:s}}=r;if(!n)return;const l=zs(e),i=Ts(n,l);if(!t&&s){if(Vl(s))return Rl(i,s.tolerance)?this.handleCancel():void 0;if(Fl(s))return Rl(i,s.distance)?this.handleStart():void 0}e.cancelable&&e.preventDefault(),o(l)}handleEnd(){const{onEnd:e}=this.props;this.detach(),e()}handleCancel(){const{onCancel:e}=this.props;this.detach(),e()}handleKeydown(e){e.code===Ml.Esc&&this.handleCancel()}}const Hl={move:{name:"pointermove"},end:{name:"pointerup"}};class Ul extends $l{constructor(e){const{event:t}=e,n=pl(t.target);super(e,Hl,n)}}Ul.activators=[{eventName:"onPointerDown",handler:({nativeEvent:e},{onActivation:t})=>!(!e.isPrimary||0!==e.button||(null==t||t({event:e}),0))}];const jl={move:{name:"mousemove"},end:{name:"mouseup"}};var ql;!function(e){e[e.RightClick=2]="RightClick"}(ql||(ql={})),class extends $l{constructor(e){super(e,jl,pl(e.event.target))}}.activators=[{eventName:"onMouseDown",handler:({nativeEvent:e},{onActivation:t})=>e.button!==ql.RightClick&&(null==t||t({event:e}),!0)}];const zl={move:{name:"touchmove"},end:{name:"touchend"}};(class extends $l{constructor(e){super(e,zl)}}).activators=[{eventName:"onTouchStart",handler:({nativeEvent:e},{onActivation:t})=>{const{touches:n}=e;return!(n.length>1||(null==t||t({event:e}),0))}}];const Wl=[{sensor:Ul,options:{}},{sensor:Nl,options:{}}],Gl={current:{}},Kl=(0,Z.createContext)({...js,scaleX:1,scaleY:1}),Yl=(0,Z.memo)((function({id:e,autoScroll:t=!0,announcements:n,children:r,sensors:o=Wl,collisionDetection:s=dl,layoutMeasuring:l,modifiers:i,screenReaderInstructions:a=As,...c}){var u,d,p;const f=(0,Z.useReducer)(ml,void 0,fl),[m,h]=f,[g,v]=(0,Z.useState)((()=>({type:null,event:null}))),{draggable:{active:b,nodes:_,translate:y},droppable:{containers:w}}=m,E=b?_[b]:null,x=(0,Z.useRef)({initial:null,translated:null}),C=(0,Z.useMemo)((()=>{var e;return null!=b?{id:b,data:null!=(e=null==E?void 0:E.data)?e:Gl,rect:x}:null}),[b,E]),k=(0,Z.useRef)(null),[S,O]=(0,Z.useState)(null),[I,T]=(0,Z.useState)(null),D=(0,Z.useRef)(c),P=Ss("DndDescribedBy",e),{layoutRectMap:R,recomputeLayouts:M,willRecomputeLayouts:L}=function(e,{dragging:t,dependencies:n,config:r}){const[o,s]=(0,Z.useState)(!1),{frequency:l,strategy:i}=(a=r)?{...xl,...a}:xl;var a;const c=(0,Z.useRef)(e),u=(0,Z.useCallback)((()=>s(!0)),[]),d=(0,Z.useRef)(null),p=function(){switch(i){case _l.Always:return!1;case _l.BeforeDragging:return t;default:return!t}}(),f=xs((n=>{if(p&&!t)return El;if(!n||n===El||c.current!==e||o){for(let t of Object.values(e))t&&(t.rect.current=t.node.current?ll(t.node.current):null);return function(e){const t=new Map;if(e)for(const n of Object.values(e)){if(!n)continue;const{id:e,rect:r,disabled:o}=n;o||null==r.current||t.set(e,r.current)}return t}(e)}return n}),[e,t,p,o]);return(0,Z.useEffect)((()=>{c.current=e}),[e]),(0,Z.useEffect)((()=>{o&&s(!1)}),[o]),(0,Z.useEffect)((function(){p||requestAnimationFrame(u)}),[t,p]),(0,Z.useEffect)((function(){p||"number"!=typeof l||null!==d.current||(d.current=setTimeout((()=>{u(),d.current=null}),l))}),[l,p,u,...n]),{layoutRectMap:f,recomputeLayouts:u,willRecomputeLayouts:o}}(w,{dragging:null!=b,dependencies:[y.x,y.y],config:l}),A=function(e,t){const n=null!==t?e[t]:void 0,r=n?n.node.current:null;return xs((e=>{var n;return null===t?null:null!=(n=null!=r?r:e)?n:null}),[r,t])}(_,b),N=I?zs(I):null,F=Ol(A),V=kl(A),B=(0,Z.useRef)(null),$=(U=B.current,(H=F)&&U?{x:H.left-U.left,y:H.top-U.top}:js);var H,U;const j=(0,Z.useRef)({active:null,activeNode:A,collisionRect:null,droppableRects:R,draggableNodes:_,draggingNodeRect:null,droppableContainers:w,over:null,scrollableAncestors:[],scrollAdjustedTranslate:null,translatedRect:null}),q=function(e,t){var n,r;return e&&null!=(n=null==(r=t[e])?void 0:r.node.current)?n:null}(null!=(u=null==(d=j.current.over)?void 0:d.id)?u:null,w),z=kl(A?A.ownerDocument.defaultView:null),W=kl(A?A.parentElement:null),G=function(e){const t=(0,Z.useRef)(e),n=xs((n=>e?n&&e&&t.current&&e.parentNode===t.current.parentNode?n:Xs(e):Cl),[e]);return(0,Z.useEffect)((()=>{t.current=e}),[e]),n}(b?null!=q?q:A:null),K=Sl(G),[Y,X]=Cs(),Q=kl(b?Y.current:null,L),ee=null!=Q?Q:V,te=function(e,{transform:t,...n}){return(null==e?void 0:e.length)?e.reduce(((e,t)=>t({transform:e,...n})),t):t}(i,{transform:{x:y.x-$.x,y:y.y-$.y,scaleX:1,scaleY:1},active:C,over:j.current.over,activeNodeRect:V,draggingNodeRect:ee,containerNodeRect:W,overlayNodeRect:Q,scrollableAncestors:G,scrollableAncestorRects:K,windowRect:z}),ne=N?Is(N,y):null,re=function(e){const[t,n]=(0,Z.useState)(null),r=(0,Z.useRef)(e),o=(0,Z.useCallback)((e=>{const t=Qs(e.target);t&&n((e=>e?(e.set(t,Zs(t)),new Map(e)):null))}),[]);return(0,Z.useEffect)((()=>{const t=r.current;if(e!==t){s(t);const l=e.map((e=>{const t=Qs(e);return t?(t.addEventListener("scroll",o,{passive:!0}),[t,Zs(t)]):null})).filter((e=>null!=e));n(l.length?new Map(l):null),r.current=e}return()=>{s(e),s(t)};function s(e){e.forEach((e=>{const t=Qs(e);null==t||t.removeEventListener("scroll",o)}))}}),[o,e]),(0,Z.useMemo)((()=>e.length?t?Array.from(t.values()).reduce(((e,t)=>Is(e,t)),js):ol(e):js),[e,t])}(G),oe=Is(te,re),se=F?Ys(F,te):null,le=se?Ys(se,re):null,ie=function(e,t){var n;return e&&null!=(n=t[e])?n:null}(C&&le?s(Array.from(R.entries()),le):null,w),ae=(0,Z.useMemo)((()=>ie&&ie.rect.current?{id:ie.id,rect:ie.rect.current,data:ie.data,disabled:ie.disabled}:null),[ie]),ce=function(e,t,n){return{...e,scaleX:t&&n?t.width/n.width:1,scaleY:t&&n?t.height/n.height:1}}(te,null!=(p=null==ie?void 0:ie.rect.current)?p:null,F),ue=(0,Z.useCallback)(((e,{sensor:t,options:n})=>{if(!k.current)return;const r=_[k.current];if(!r)return;const o=new t({active:k.current,activeNode:r,event:e.nativeEvent,options:n,context:j,onStart(e){const t=k.current;if(!t)return;const n=_[t];if(!n)return;const{onDragStart:r}=D.current,o={active:{id:t,data:n.data,rect:x}};h({type:Fs.DragStart,initialCoordinates:e,active:t}),v({type:Fs.DragStart,event:o}),null==r||r(o)},onMove(e){h({type:Fs.DragMove,coordinates:e})},onEnd:s(Fs.DragEnd),onCancel:s(Fs.DragCancel)});function s(e){return async function(){const{active:t,over:n,scrollAdjustedTranslate:r}=j.current;let o=null;if(t&&r){const{cancelDrop:s}=D.current;o={active:t,delta:r,over:n},e===Fs.DragEnd&&"function"==typeof s&&await Promise.resolve(s(o))&&(e=Fs.DragCancel)}if(k.current=null,h({type:e}),O(null),T(null),o){const{onDragCancel:t,onDragEnd:n}=D.current,r=e===Fs.DragEnd?n:t;v({type:e,event:o}),null==r||r(o)}}}O(o),T(e.nativeEvent)}),[h,_]),de=(0,Z.useCallback)(((e,t)=>(n,r)=>{const o=n.nativeEvent;null!==k.current||o.dndKit||o.defaultPrevented||!0===e(n,t.options)&&(o.dndKit={capturedBy:t.sensor},k.current=r,ue(n,t))}),[ue]),pe=function(e,t){return(0,Z.useMemo)((()=>e.reduce(((e,n)=>{const{sensor:r}=n;return[...e,...r.activators.map((e=>({eventName:e.eventName,handler:t(e.handler,n)})))]}),[])),[e,t])}(o,de);Es((()=>{D.current=c}),Object.values(c)),(0,Z.useEffect)((()=>{C||(B.current=null),C&&F&&!B.current&&(B.current=F)}),[F,C]),(0,Z.useEffect)((()=>{const{onDragMove:e}=D.current,{active:t,over:n}=j.current;if(!t)return;const r={active:t,delta:{x:oe.x,y:oe.y},over:n};v({type:Fs.DragMove,event:r}),null==e||e(r)}),[oe.x,oe.y]),(0,Z.useEffect)((()=>{const{active:e,scrollAdjustedTranslate:t}=j.current;if(!e||!k.current||!t)return;const{onDragOver:n}=D.current,r={active:e,delta:{x:t.x,y:t.y},over:ae};v({type:Fs.DragOver,event:r}),null==n||n(r)}),[null==ae?void 0:ae.id]),Es((()=>{j.current={active:C,activeNode:A,collisionRect:le,droppableRects:R,draggableNodes:_,draggingNodeRect:ee,droppableContainers:w,over:ae,scrollableAncestors:G,scrollAdjustedTranslate:oe,translatedRect:se},x.current={initial:ee,translated:se}}),[C,A,le,_,ee,R,w,ae,G,oe,se]),function({acceleration:e,activator:t=vl.Pointer,canScroll:n,draggingRect:r,enabled:o,interval:s=5,order:l=bl.TreeOrder,pointerCoordinates:i,scrollableAncestors:a,scrollableAncestorRects:c,threshold:u}){const[d,p]=function(){const e=(0,Z.useRef)(null);return[(0,Z.useCallback)(((t,n)=>{e.current=setInterval(t,n)}),[]),(0,Z.useCallback)((()=>{null!==e.current&&(clearInterval(e.current),e.current=null)}),[])]}(),f=(0,Z.useRef)({x:1,y:1}),m=(0,Z.useMemo)((()=>{switch(t){case vl.Pointer:return i?{top:i.y,bottom:i.y,left:i.x,right:i.x}:null;case vl.DraggableRect:return r}return null}),[t,r,i]),h=(0,Z.useRef)(js),g=(0,Z.useRef)(null),v=(0,Z.useCallback)((()=>{const e=g.current;if(!e)return;const t=f.current.x*h.current.x,n=f.current.y*h.current.y;e.scrollBy(t,n)}),[]),b=(0,Z.useMemo)((()=>l===bl.TreeOrder?[...a].reverse():a),[l,a]);(0,Z.useEffect)((()=>{if(o&&a.length&&m){for(const t of b){if(!1===(null==n?void 0:n(t)))continue;const r=a.indexOf(t),o=c[r];if(!o)continue;const{direction:l,speed:i}=nl(t,o,m,e,u);if(i.x>0||i.y>0)return p(),g.current=t,d(v,s),f.current=i,void(h.current=l)}f.current={x:0,y:0},h.current={x:0,y:0},p()}else p()}),[e,v,n,p,o,s,JSON.stringify(m),d,a,b,c,JSON.stringify(u)])}({...function(){const e=!1===(null==S?void 0:S.autoScrollEnabled),n="object"==typeof t?!1===t.enabled:!1===t,r=!e&&!n;return"object"==typeof t?{...t,enabled:r}:{enabled:r}}(),draggingRect:se,pointerCoordinates:ne,scrollableAncestors:G,scrollableAncestorRects:K});const fe=(0,Z.useMemo)((()=>({active:C,activeNode:A,activeNodeRect:F,activeNodeClientRect:V,activatorEvent:I,activators:pe,ariaDescribedById:{draggable:P},overlayNode:{nodeRef:Y,rect:Q,setRef:X},containerNodeRect:W,dispatch:h,draggableNodes:_,droppableContainers:w,droppableRects:R,over:ae,recomputeLayouts:M,scrollableAncestors:G,scrollableAncestorRects:K,willRecomputeLayouts:L,windowRect:z})),[C,A,V,F,I,pe,W,Q,Y,h,_,P,w,R,ae,M,G,K,X,L,z]);return J().createElement(hl.Provider,{value:g},J().createElement(Us.Provider,{value:fe},J().createElement(Kl.Provider,{value:ce},r)),J().createElement(gl,{announcements:n,hiddenTextDescribedById:P,screenReaderInstructions:a}))})),Xl=(0,Z.createContext)(null),Ql="button";function Zl(e,t,n){const r=e.slice();return r.splice(n<0?r.length+n:n,0,r.splice(t,1)[0]),r}function Jl(e){return null!==e&&e>=0}const ei=({layoutRects:e,activeIndex:t,overIndex:n,index:r})=>{const o=Zl(e,n,t),s=e[r],l=o[r];return l&&s?{x:l.offsetLeft-s.offsetLeft,y:l.offsetTop-s.offsetTop,scaleX:l.width/s.width,scaleY:l.height/s.height}:null},ti={scaleX:1,scaleY:1},ni=({activeIndex:e,activeNodeRect:t,index:n,layoutRects:r,overIndex:o})=>{var s;const l=null!=(s=r[e])?s:t;if(!l)return null;if(n===e){const t=r[o];return t?{x:0,y:ee&&n<=o?{x:0,y:-l.height-i,...ti}:n=o?{x:0,y:l.height+i,...ti}:{x:0,y:0,...ti}},ri="Sortable",oi=J().createContext({activeIndex:-1,containerId:ri,disableTransforms:!1,items:[],overIndex:-1,useDragOverlay:!1,sortedRects:[],strategy:ei,wasSorting:{current:!1}});function si({children:e,id:t,items:n,strategy:r=ei}){const{active:o,overlayNode:s,droppableRects:l,over:i,recomputeLayouts:a,willRecomputeLayouts:c}=(0,Z.useContext)(Us),u=Ss(ri,t),d=Boolean(null!==s.rect),p=(0,Z.useMemo)((()=>n.map((e=>"string"==typeof e?e:e.id))),[n]),f=o?p.indexOf(o.id):-1,m=-1!==f,h=(0,Z.useRef)(m),g=i?p.indexOf(i.id):-1,v=(0,Z.useRef)(p),b=function(e,t){return e.reduce(((e,n,r)=>{const o=t.get(n);return o&&(e[r]=o),e}),Array(e.length))}(p,l),_=(y=p,w=v.current,!(y.join()===w.join()));var y,w;const E=-1!==g&&-1===f||_;Es((()=>{_&&m&&!c&&a()}),[_,m,a,c]),(0,Z.useEffect)((()=>{v.current=p}),[p]),(0,Z.useEffect)((()=>{requestAnimationFrame((()=>{h.current=m}))}),[m]);const x=(0,Z.useMemo)((()=>({activeIndex:f,containerId:u,disableTransforms:E,items:p,overIndex:g,useDragOverlay:d,sortedRects:b,strategy:r,wasSorting:h})),[f,u,E,p,g,b,d,r,h]);return J().createElement(oi.Provider,{value:x},e)}const li=({isSorting:e,index:t,newIndex:n,transition:r})=>!(!r||!e&&n===t),ii={duration:200,easing:"ease"},ai="transform",ci=Ds.Transition.toString({property:ai,duration:0,easing:"linear"}),ui={roleDescription:"sortable"};function di({animateLayoutChanges:e=li,attributes:t,disabled:n,data:r,id:o,strategy:s,transition:l=ii}){const{items:i,containerId:a,activeIndex:c,disableTransforms:u,sortedRects:d,overIndex:p,useDragOverlay:f,strategy:m,wasSorting:h}=(0,Z.useContext)(oi),g=i.indexOf(o),v=(0,Z.useMemo)((()=>({sortable:{containerId:a,index:g,items:i},...r})),[a,r,g,i]),{rect:b,node:_,setNodeRef:y}=function({data:e,disabled:t=!1,id:n}){const{active:r,dispatch:o,over:s}=(0,Z.useContext)(Us),l=(0,Z.useRef)(null),[i,a]=Cs(),c=wl(e);return Es((()=>(o({type:Fs.RegisterDroppable,element:{id:n,disabled:t,node:i,rect:l,data:c}}),()=>o({type:Fs.UnregisterDroppable,id:n}))),[n]),(0,Z.useEffect)((()=>{o({type:Fs.SetDroppableDisabled,id:n,disabled:t})}),[t]),{active:r,rect:l,isOver:(null==s?void 0:s.id)===n,node:i,over:s,setNodeRef:a}}({id:o,data:v}),{active:w,activeNodeRect:E,activatorEvent:x,attributes:C,setNodeRef:k,listeners:S,isDragging:O,over:I,transform:T}=function({id:e,data:t,disabled:n=!1,attributes:r}){const{active:o,activeNodeRect:s,activatorEvent:l,ariaDescribedById:i,draggableNodes:a,droppableRects:c,activators:u,over:d}=(0,Z.useContext)(Us),{role:p=Ql,roleDescription:f="draggable",tabIndex:m=0}=null!=r?r:{},h=(null==o?void 0:o.id)===e,g=(0,Z.useContext)(h?Kl:Xl),[v,b]=Cs(),_=function(e,t){return(0,Z.useMemo)((()=>e.reduce(((e,{eventName:n,handler:r})=>(e[n]=e=>{r(e,t)},e)),{})),[e,t])}(u,e),y=wl(t);return(0,Z.useEffect)((()=>(a[e]={node:v,data:y},()=>{delete a[e]})),[a,e]),{active:o,activeNodeRect:s,activatorEvent:l,attributes:(0,Z.useMemo)((()=>({role:p,tabIndex:m,"aria-pressed":!(!h||p!==Ql)||void 0,"aria-roledescription":f,"aria-describedby":i.draggable})),[p,m,h,f,i.draggable]),droppableRects:c,isDragging:h,listeners:n?void 0:_,node:v,over:d,setNodeRef:b,transform:g}}({id:o,data:v,attributes:{...ui,...t},disabled:n}),D=function(...e){return(0,Z.useMemo)((()=>t=>{e.forEach((e=>e(t)))}),e)}(y,k),P=Boolean(w),R=P&&h.current&&!u&&Jl(c)&&Jl(p),M=!f&&O,L=M&&R?T:null,A=R?null!=L?L:(null!=s?s:m)({layoutRects:d,activeNodeRect:E,activeIndex:c,overIndex:p,index:g}):null,N=Jl(c)&&Jl(p)?Zl(i,c,p).indexOf(o):g,F=(0,Z.useRef)(N),V=e({active:w,isDragging:O,isSorting:P,id:o,index:g,items:i,newIndex:F.current,transition:l,wasSorting:h.current}),B=function({rect:e,disabled:t,index:n,node:r}){const[o,s]=(0,Z.useState)(null),l=(0,Z.useRef)(n);return(0,Z.useEffect)((()=>{if(!t&&n!==l.current&&r.current){const t=e.current;if(t){const e=il(r.current),n={x:t.offsetLeft-e.offsetLeft,y:t.offsetTop-e.offsetTop,scaleX:t.width/e.width,scaleY:t.height/e.height};(n.x||n.y)&&s(n)}}n!==l.current&&(l.current=n)}),[t,n,r,e]),(0,Z.useEffect)((()=>{o&&requestAnimationFrame((()=>{s(null)}))}),[o]),o}({disabled:!V,index:g,node:_,rect:b});return(0,Z.useEffect)((()=>{P&&(F.current=N)}),[P,N]),{active:w,attributes:C,activatorEvent:x,rect:b,index:g,isSorting:P,isDragging:O,listeners:S,node:_,overIndex:p,over:I,setNodeRef:D,setDroppableNodeRef:y,setDraggableNodeRef:k,transform:null!=B?B:A,transition:B?ci:M||!l?null:P||V?Ds.Transition.toString({...l,property:ai}):null}}const pi=[Ml.Down,Ml.Right,Ml.Up,Ml.Left],fi=(e,{context:{droppableContainers:t,translatedRect:n,scrollableAncestors:r}})=>{if(pi.includes(e.code)){if(e.preventDefault(),!n)return;const s=[];Object.entries(t).forEach((([t,r])=>{if(null==r?void 0:r.disabled)return;const o=null==r?void 0:r.node.current;if(!o)return;const l=al(o);switch(e.code){case Ml.Down:n.top+n.height<=l.top&&s.push([t,l]);break;case Ml.Up:n.top>=l.top+l.height&&s.push([t,l]);break;case Ml.Left:n.left>=l.left+l.width&&s.push([t,l]);break;case Ml.Right:n.left+n.width<=l.left&&s.push([t,l])}}));const l=((e,t)=>{const n=ul(t,t.left,t.top),r=e.map((([e,t])=>{const r=ul(t,cl(t)?t.left:void 0,cl(t)?t.top:void 0),o=n.reduce(((e,t,n)=>e+qs(r[n],t)),0);return Number((o/4).toFixed(4))})),o=Vs(r);return e[o]?e[o][0]:null})(s,n);if(l){var o;const e=null==(o=t[l])?void 0:o.node.current;if(e){const t=Xs(e).some(((e,t)=>r[t]!==e)),o=al(e),s=t?{x:0,y:0}:{x:n.width-o.width,y:n.height-o.height};return{x:o.left-s.x,y:o.top-s.y}}}}};const mi=({transform:e})=>({...e,x:0}),hi=({transform:e,activeNodeRect:t,windowRect:n})=>t&&n?function(e,t,n){const r={...e};return t.top+e.y<=n.top?r.y=n.top-t.top:t.bottom+e.y>=n.top+n.height&&(r.y=n.top+n.height-t.bottom),t.left+e.x<=n.left?r.x=n.left-t.left:t.right+e.x>=n.left+n.width&&(r.x=n.left+n.width-t.right),r}(e,t,n):e,gi=(0,$.createElement)("svg",{width:"18",height:"18",xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 18 18"},(0,$.createElement)("path",{d:"M5 4h2V2H5v2zm6-2v2h2V2h-2zm-6 8h2V8H5v2zm6 0h2V8h-2v2zm-6 6h2v-2H5v2zm6 0h2v-2h-2v2z"}));function vi(e){let{label:t,setNodeRef:n,listeners:r}=e;return t=t||(0,H.__)("Reorder instructor","lifterlms"),(0,$.createElement)(q.Button,X({isSmall:!0,showTooltip:!0,label:t,icon:gi,ref:n,className:"llms-drag-handle"},r))}function bi(e){let{id:t,index:n,item:r,isDragging:o,dragHandle:s,ListItem:l,itemClassName:i="",manageState:a,extraProps:c={}}=e;const{attributes:u,listeners:d,setNodeRef:p,transform:f,transition:m}=di({id:t}),h={transform:Ds.Transform.toString(f),transition:m};return o&&f&&f.scaleX&&f.scaleY&&(f.scaleX=.9,f.scaleY=.9),o&&(i+=" llms-is-dragging"),(0,$.createElement)("div",X({style:h,ref:s?void 0:p,className:`llms-sortable-list--item ${i}`},u,s?{}:d),(0,$.createElement)(l,{id:t,item:r,index:n,isDragging:o,setNodeRef:p,listeners:d,manageState:a,extraProps:c}))}function _i(e){let{ListItem:t,manageState:n,items:r=[],sortableStrategy:o=ni,ctxModifiers:s=[mi,hi],dragHandle:l=!0,listClassName:i="",itemClassName:a="",extraProps:c={}}=e;const[u,d]=(0,$.useState)(!1),p=function(...e){return(0,Z.useMemo)((()=>[...e].filter((e=>null!=e))),[...e])}(Dl(Ul),Dl(Nl,{coordinateGetter:fi}));return(0,$.createElement)(Yl,{sensors:p,collisionDetection:Gs,onDragStart:function(e){d(e.active.id)},onDragEnd:function(e){d(!1);const{active:t,over:o}=e;if(t.id!==o.id){const e=(0,Fr.findIndex)(r,{id:t.id}),s=(0,Fr.findIndex)(r,{id:o.id});n.updateItems(Zl(r,e,s))}},modifiers:s},(0,$.createElement)("div",{className:`llms-sortable-list ${i}`},(0,$.createElement)(si,{items:r,strategy:o},r.map(((e,r)=>(0,$.createElement)(bi,{id:e.id,key:e.id,index:r,item:e,isDragging:e.id===u,dragHandle:l,ListItem:t,itemClassName:a,manageState:n,extraProps:c}))))))}function yi(e){let{id:t,item:n,extraProps:r,manageState:o,listeners:s,setNodeRef:l}=e;const{showKeys:i,type:a,optionCount:c}=r,{updateItem:u,deleteItem:d}=o;return(0,$.createElement)($.Fragment,null,(0,$.createElement)(vi,{label:(0,H.__)("Reorder option","lifterlms"),setNodeRef:l,listeners:s}),(0,$.createElement)(q.Tooltip,{text:(0,H.__)("Make default","lifterlms")},(0,$.createElement)("div",{className:"llms-field-opt-default-wrap"},"checkbox"===a&&(0,$.createElement)((()=>(0,$.createElement)(q.CheckboxControl,{className:"llms-field-opt-default",checked:"yes"===n.default,onChange:e=>{u(t,{...n,default:!0===e?"yes":"no"})},tabIndex:"-1"})),null),"checkbox"!==a&&(0,$.createElement)((()=>(0,$.createElement)(q.RadioControl,{className:"llms-field-opt-default",selected:n.default,onChange:e=>{u(t,{...n,default:e})},options:[{label:"",value:"yes"}],tabIndex:"-1"})),null))),(0,$.createElement)("div",{className:"llms-field-opt-text-wrap"},(0,$.createElement)(q.TextControl,{className:"llms-field-opt-text",value:n.text,onChange:e=>u(t,{...n,text:e}),placeholder:(0,H.__)("Option label","lifterlms")}),i&&(0,$.createElement)("div",{className:"llms-field-opt-db-key"},(0,$.createElement)(q.Tooltip,{text:(0,H.__)("Database key value","lifterlms")},(0,$.createElement)(q.Dashicon,{icon:"database"})),(0,$.createElement)(q.TextControl,{className:"llms-field-opt-text ",value:n.key,onChange:e=>u(t,{...n,key:e}),placeholder:(0,H.__)("Database key value","lifterlms")}))),c>1&&(0,$.createElement)("div",{className:"llms-del-field-opt-wrap"},(0,$.createElement)(q.Button,{style:{flex:1},icon:"trash",label:(0,H.__)("Delete Option","lifterlms"),onClick:()=>d(t),tabIndex:"-1",isSmall:!0})))}class wi extends $.Component{constructor(){super(...arguments),K(this,"addOption",(()=>{const{options:e}=this.state,{length:t}=e,[n,r]=this.getUniqueKeyNumber(t+1),o={key:n,id:_s(),
+// Translators: %d = Option index in the list of options.
+text:(0,H.sprintf)((0,H.__)("Option %d","lifterlms"),r),default:"no"};e.push(o),this.updateOptions(e)})),K(this,"getManageState",(()=>({createItem:this.addOption,deleteItem:this.removeOption,updateItem:this.updateOption,updateItems:this.updateOptions}))),K(this,"getUniqueKeyNumber",(e=>{const t=e=>{const{options:t}=this.state;return-1===t.findIndex((t=>{let{key:n}=t;return n===e}))};// Translators: %d = Option index in the list of options.
+let n=(0,H.sprintf)((0,H.__)("option_%d","lifterlms"),e);for(;!t(n);)[n,e]=this.getUniqueKeyNumber(++e);return[n,e]})),K(this,"updateOption",((e,t)=>{const{options:n}=this.state,{field:r}=this.props.attributes,o="yes"===t.default&&"checkbox"!==r,s=n.map((n=>(e===n.id?n={...n,...t}:o&&(n={...n,default:"no"}),n)));this.updateOptions(s)})),K(this,"updateOptions",(e=>{const{setAttributes:t}=this.props;this.setState({options:e}),t({options:e.map((e=>{let{id:t,...n}=e;return n}))})})),K(this,"removeOption",(e=>{const{options:t}=this.state,{field:n}=this.props.attributes;let r=null;if("checkbox"!==n){const n=t.find((t=>{let{id:n}=t;return n===e}));r="yes"===n.default}this.updateOptions(t.filter((t=>{let{id:n}=t;return n!==e})).map(((e,t)=>(r&&0===t&&(e={...e,default:"yes"}),e))))}));const{options:e}=this.props.attributes;this.state={showKeys:!1,options:e.map((e=>({...e,id:_s()})))}}render(){const{props:e,state:t}=this,{attributes:n}=e,{id:r,field:o}=n,{options:s,showKeys:l}=t,{length:i}=s;return(0,$.createElement)(q.BaseControl,{id:r,label:(0,H.__)("Options","lifterlms")},(0,$.createElement)(_i,{ListItem:yi,items:s,itemClassName:"llms-field-option",manageState:this.getManageState(),extraProps:{type:o,showKeys:l,optionCount:i}}),(0,$.createElement)("div",{className:"llms-field-options--footer"},(0,$.createElement)(q.Button,{isSecondary:!0,onClick:this.addOption},(0,H.__)("Add option","lifterlms")),(0,$.createElement)(q.Button,{isTertiary:!0,onClick:()=>this.setState({showKeys:!l})},l?(0,H.__)("Hide keys","lifterlms"):(0,H.__)("Show keys","lifterlms"))))}}class Ei extends $.Component{constructor(e){super(e),this.state={validationErrors:{}}}getBlockByFieldId(e){const t=Gr().filter((t=>e===t.attributes.id));return!!t&&t[0]}getColumnsOptions(e){let t=[];return e&&(!e||e["llms/fieldGroup/fieldLayout"]&&"stacked"!==e["llms/fieldGroup/fieldLayout"])||t.push({value:12,label:(0,H.__)("100%","lifterlms")}),t=t.concat([{value:9,label:(0,H.__)("75%","lifterlms")},{value:8,label:(0,H.__)("66.66%","lifterlms")},{value:6,label:(0,H.__)("50%","lifterlms")},{value:4,label:(0,H.__)("33.33%","lifterlms")},{value:3,label:(0,H.__)("25%","lifterlms")}]),t}getMatchFieldOptions(){const{clientId:e,name:t}=this.props;return[{value:"",label:(0,H.__)("Select a field","lifterlms")}].concat(Gr().filter((n=>n.clientId!==e&&-1!==t.indexOf("llms/form-field-"))).map((e=>{const{id:t,label:n}=e.attributes;return{value:t,label:`${n} (${t})`}})))}hasInspectorSupport(){const{inspectorSupports:e}=this.props;return Object.keys(e).filter((t=>e[t])).length>=1}hasInspectorControlSupport(e){const{inspectorSupports:t}=this.props;return t[e]}canTransformToGroup(e){return!(!e||this.isInAConfirmGroup(e))&&(0,zr.getPossibleBlockTransformations)([e]).map((e=>{let{name:t}=e;return t})).includes("llms/form-field-confirm-group")}isInAConfirmGroup(e){return!!this.getParentGroupClientId(e)}getParentGroupClientId(e){if(!e)return!1;const{clientId:t}=e,{getBlockParentsByBlockName:n}=(0,jr.select)(j.store),r=n(t,"llms/form-field-confirm-group");return!!r.length&&r[0]}getBlockSiblings(e){const t=this.getParentGroupClientId(e);if(!t)return[];const{getBlock:n}=(0,jr.select)(j.store);return n(t).innerBlocks.filter((t=>{let{clientId:n}=t;return n!==e.clientId}))}getValidationErrText(e){let t="";const n=this.state.validationErrors[e];if(n)if(this.containsInvalidCharacters(n))
+// Translators: %s = user-submitted value.
+t=(0,H.__)('The value "%s" contains invalid characters. Only letters, numbers, underscores, and hyphens are allowed.',"lifterlms");else switch(e){case"data_store_key":
+// Translators: %s = user-submitted value.
+t=(0,H.__)('The user meta key "%s" is not unique. Please choose a unique value.',"lifterlms");break;case"id":
+// Translators: %s = user-submitted value.
+t=(0,H.__)('The ID "%s" is not unique. Please choose a unique field ID.',"lifterlms");break;case"name":
+// Translators: %s = user-submitted value.
+t=(0,H.__)('The name "%s" is not unique. Please choose a globally unique field name.',"lifterlms");break;default:
+// Translators: %s = user-submitted value.
+t=(0,H.__)('The chosen value "%s" is invalid.',"lifterlms")}else t=(0,H.__)("The value cannot be blank.","lifterlms");return(0,H.sprintf)(t,n)}containsInvalidCharacters(e){return!!e.match(/[^A-Za-z0-9\-\_]/g)}setValidationError(e){let t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:null;this.setState({validationErrors:{...this.state.validationErrors,[e]:t}})}hasValidationErr(e){return"string"==typeof this.state.validationErrors[e]}ValidatedTextControl(e){let{parent:t,attrKey:n,label:r,help:o}=e;const{attributes:s}=t.props,l=s[n],i=t.hasValidationErr(n),a=i?"llms-invalid-control":"";return(0,$.createElement)("div",{className:a},(0,$.createElement)(q.TextControl,{label:r,help:o,value:l,onChange:e=>t.updateValueWithValidation(n,e,"name"===n?"global":"local")}),i&&(0,$.createElement)("p",{className:"llms-invalid-control--msg"},t.getValidationErrText(n)))}updateValueWithValidation(e,t){let n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:"local";const{clientId:r,attributes:o,setAttributes:s}=this.props,{name:l}=o,i=o[e],{editField:a,renameField:c}=(0,jr.dispatch)(wo),{lockPostSaving:u,unlockPostSaving:d}=(0,jr.dispatch)(qr.store),p=`llms-${e}-validation-err-${r}-${l}`;if(t===i)return;const f=!t,m=this.containsInvalidCharacters(t),h=cs(t,e,n),g=!f&&!m&&h;if(this.setValidationError(e),d(p),!g){if(this.setValidationError(e,t),f)return;u(p)}"name"===e?(h||(t=t.slice(0,-1)),c(o.name,t)):a(o.name,{[e]:t}),s({[e]:t})}render(){if(!this.hasInspectorSupport())return"";const{attributes:e,setAttributes:t,clientId:n,context:r}=this.props,o=(0,jr.select)(j.store).getBlock(n),{required:s,placeholder:l,columns:i,isConfirmationField:a,isConfirmationControlField:c}=e,u=this.canTransformToGroup(o),d=this.isInAConfirmGroup(o);return(0,$.createElement)($.Fragment,null,(0,$.createElement)(j.InspectorControls,null,(0,$.createElement)(q.PanelBody,null,!a&&this.hasInspectorControlSupport("required")&&(0,$.createElement)(q.ToggleControl,{className:"llms-required-field-toggle",label:(0,H.__)("Required","lifterlms"),checked:!!s,onChange:()=>t({required:!s}),help:s?(0,H.__)("Field is required.","lifterlms"):(0,H.__)("Field is optional.","lifterlms")}),(0,$.createElement)(q.SelectControl,{className:"llms-field-width-select",label:(0,H.__)("Field Width","lifterlms"),onChange:e=>{e=parseInt(e,10),t({columns:e});const n=this.getBlockSiblings(o);if(n.length&&e+n[0].attributes.columns>12){const{updateBlockAttributes:t}=(0,jr.dispatch)(j.store);t(n[0].clientId,{columns:12-e})}},help:(0,H.__)("Determines the width of the form field.","lifterlms"),value:i,options:this.getColumnsOptions(r)}),this.hasInspectorControlSupport("options")&&(0,$.createElement)(wi,{attributes:e,setAttributes:t}),this.hasInspectorControlSupport("placeholder")&&(0,$.createElement)(q.TextControl,{label:(0,H.__)("Placeholder","lifterlms"),value:l,onChange:e=>t({placeholder:e}),help:(0,H.__)("Displays a placeholder option as the selected instead of a default value.","lifterlms")}),(u||c&&d)&&(0,$.createElement)(q.ToggleControl,{className:"llms-confirmation-field-toggle",label:(0,H.__)("Confirmation Field","lifterlms"),checked:d,onChange:()=>{const{replaceBlock:e,selectBlock:t}=(0,jr.dispatch)(j.store),{findControllerBlockIndex:r}=(0,zr.getBlockType)("llms/form-field-confirm-group"),{getBlock:s}=(0,jr.select)(j.store);let l=n,i="llms/form-field-confirm-group",a=o,c=null;d&&(l=this.getParentGroupClientId(o),a=s(l),i=o.name);const u=(0,zr.switchToBlockType)(a,i);if(e(l,u),d)c=u[0].clientId;else{const{innerBlocks:e}=u[0];c=e[r(e)].clientId}t(c)},help:d?(0,H.__)("A Confirmation field is active.","lifterlms"):(0,H.__)("No confirmation field.","lifterlms")}),this.hasInspectorControlSupport("customFill")&&(0,$.createElement)(q.Slot,{name:`llmsInspectorControlsFill.${this.hasInspectorControlSupport("customFill")}.${n}`})),!a&&this.hasInspectorControlSupport("storage")&&(0,$.createElement)(q.PanelBody,{title:(0,H.__)("Data Storage","lifterlms")},(0,$.createElement)(this.ValidatedTextControl,{parent:this,attrKey:"data_store_key",label:(0,H.__)("Usermeta Key","lifterlms"),help:(0,H.__)("Database field key name. Only accepts alphanumeric characters, hyphens, and underscores.","lifterlms")}))),(0,$.createElement)(j.InspectorAdvancedControls,null,!a&&this.hasInspectorControlSupport("name")&&(0,$.createElement)(this.ValidatedTextControl,{parent:this,attrKey:"name",label:(0,H.__)("Field Name","lifterlms"),help:(0,H.__)("The field's HTML name attribute.","lifterlms")}),!a&&this.hasInspectorControlSupport("id")&&(0,$.createElement)(this.ValidatedTextControl,{parent:this,attrKey:"id",label:(0,H.__)("Field ID","lifterlms"),help:(0,H.__)("The field's HTML id attribute.","lifterlms")})))}}function xi(e){const t=function(e){const{getBlock:t,getBlockParentsByBlockName:n}=(0,jr.select)("core/block-editor");return t(n(e,function(){const{getBlockTypes:e,hasBlockSupport:t}=(0,jr.select)("core/blocks");return e().filter((e=>t(e,"llms_field_group")))}().map((e=>{let{name:t}=e;return t}))))}(e);return t&&t.innerBlocks.length?(0,Fr.find)(t.innerBlocks,(t=>t.clientId!==e)):null}function Ci(){let{setAttributes:e,currentUpdates:t,siblingClientId:n,siblingUpdates:r}=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};const{updateBlockAttributes:o}=(0,jr.dispatch)("core/block-editor");setTimeout((()=>{(0,Fr.isEmpty)(t)||e(t),n&&!(0,Fr.isEmpty)(r)&&o(n,r)}))}function ki(e,t){const n={};return e.required!==t.required&&(n.required=e.required),e.field!==t.field&&(n.field=e.field),{currentUpdates:{},siblingUpdates:n}}function Si(e){const{attributes:t,block:n,setAttributes:r}=e,{fieldLayout:o}=t,{innerBlocks:s}=n;return(0,$.createElement)(q.RadioControl,{label:(0,H.__)("Field Layout","lifterlms"),selected:o,onChange:e=>function(e){let{fieldLayout:t,setAttributes:n,innerBlocks:r}=e;const{updateBlockAttributes:o}=(0,jr.dispatch)(j.store);n({fieldLayout:t});const s="columns"===t?6:12;r.forEach(((e,n)=>{let{clientId:r}=e,l=1===n;0===n&&"stacked"===t&&(l=!0),o(r,{columns:s,last_column:l})}))}({fieldLayout:e,setAttributes:r,innerBlocks:s}),options:[{value:"columns",label:(0,H.__)("Columns","lifterlms")},{value:"stacked",label:(0,H.__)("Stacked","lifterlms")}]})}const Oi=e=>{const{getCurrentPostId:t}=(0,jr.select)("core/editor");return(0,Fr.snakeCase)((0,Fr.uniqueId)(`${e}_${t()}_`))},Ii=e=>(0,Fr.kebabCase)(e),Ti={apiVersion:2,icon:{foreground:"#466dd8"},category:"llms-user-info-fields",keywords:[(0,H.__)("LifterLMS","lifterlms"),"llms"],attributes:{},supports:{llms_visibility:!0},example:{},fillInspectorControls(e,t,n){},fillEditAfter(e,t,n){}},Di={attributes:{description:{type:"string",__default:""},field:{type:"string",__default:"text"},required:{type:"boolean",__default:!1},label:{type:"string",__default:""},label_show_empty:{type:"string",__default:!1},match:{type:"string",__default:""},options:{type:"array",__default:[]},options_preset:{type:"string",__default:""},placeholder:{type:"string",__default:""},columns:{type:"integer",__default:12},last_column:{type:"boolean",__default:!0},name:{type:"string",__default:""},id:{type:"string",__default:""},data_store:{type:"string",__default:"usermeta"},data_store_key:{type:"string",__default:""},html_attrs:{type:"object",__default:{}},isConfirmationField:{type:"boolean",__default:!1},isConfirmationControlField:{type:"boolean",__default:!1}},supports:{llms_field_inspector:{id:!0,name:!0,options:!1,placeholder:!1,required:!0,customFill:!1,storage:!0},llms_edit_fill:{after:!1},llms_field_group:!1},edit:function(e){let{attributes:t}=e,n=!0;const{name:r}=e,o=(0,zr.getBlockType)(r),{clientId:s,context:l,setAttributes:i}=e,a=o.supports.llms_field_inspector,c=o.supports.llms_edit_fill,{fillEditAfter:u,fillInspectorControls:d}=o,{getSelectedBlockClientId:p}=(0,jr.select)(j.store),{isDuplicate:f}=(0,jr.select)(wo),m=!!l["llms/fieldGroup/fieldLayout"],h=t.name&&f(t.name,s),g=!m&&t.isConfirmationField;g&&(n=!1),t=n?((e,t,n)=>{if(Object.keys(t).forEach((n=>{const r=t[n].__default;void 0!==r&&void 0===e[n]&&(e[n]=r)})),!e.name||n&&!e.isConfirmationField){let t=Oi(e.field);for(;!cs("name",t);)t=Oi(e.field);e.name=t}if(!e.id||n&&!e.isConfirmationField){let t=Ii(e.name);for(;!cs("id",t,"local");)t=Ii((0,Fr.uniqueId)(`${e.field}-field-`));e.id=t}return(""===e.data_store_key||n&&!e.isConfirmationField)&&(e.data_store_key=e.name),e})(t,o.attributes,h):t,m&&n&&function(){let{attributes:e,clientId:t,setAttributes:n}=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};const r=xi(t);let o={},s={};if(!r)return;const l=r.clientId;if(e.isConfirmationControlField||e.isConfirmationField){const t=ki(e,r.attributes);o=(0,Fr.merge)(o,t.currentUpdates),s=(0,Fr.merge)(s,t.siblingUpdates)}Ci({setAttributes:n,currentUpdates:o,siblingClientId:l,siblingUpdates:s})}(e);const v=(0,j.useBlockProps)({className:`llms-fields llms-cols-${t.columns}`});return(0,$.useEffect)((()=>{if(o.variations&&o.variations.length&&s===p()){const e=setInterval((()=>{const n=document.querySelector(".block-editor-block-inspector .block-editor-block-variation-transforms");return n&&(n.style.display=t.isConfirmationField?"none":"inline-block",clearInterval(e)),()=>{clearInterval(e)}}),10)}})),g?(setTimeout((()=>{(0,jr.dispatch)(j.store).removeBlock(s)}),10),null):(0,$.createElement)("div",v,(0,$.createElement)(Ei,{attributes:t,clientId:s,name:r,setAttributes:i,inspectorSupports:a,context:l}),(0,$.createElement)(ds,{attributes:t,setAttributes:i,block:o,clientId:s,context:l}),a.customFill&&(0,$.createElement)(q.Fill,{name:`llmsInspectorControlsFill.${a.customFill}.${s}`},d(t,i,e)),c.after&&(0,$.createElement)(q.Fill,{name:`llmsEditFill.after.${c.after}.${s}`},u(t,i,e)))},save:function(e){const{attributes:t}=e;return t}},Pi={attributes:{fieldLayout:{type:"string",default:"columns"}},supports:{llms_field_group:!0,llms_field_inspector:!1},providesContext:{"llms/fieldGroup/fieldLayout":"fieldLayout"},llmsInnerBlocks:{template:[],allowed:[],lock:"insert"},edit:function(e){const{attributes:t,clientId:n,name:r,setAttributes:o}=e,{fieldLayout:s}=t,{getBlock:l}=(0,jr.select)(j.store),i=l(n),a=(0,zr.getBlockType)(r),{allowed:c,template:u,lock:d}=a.llmsInnerBlocks,p=i&&i.innerBlocks.length&&"llms/form-field-confirm-group"===i.name?i.innerBlocks[a.findControllerBlockIndex(i.innerBlocks)]:null,f=p?(0,zr.getBlockType)(p.name):null,m=f?f.supports.llms_edit_fill:{after:!1},h=a.supports.llms_field_inspector,g=a.providesContext&&a.providesContext["llms/fieldGroup/fieldLayout"];let v="columns"===s?"horizontal":"vertical";return g||(v="vertical"),(0,$.createElement)("div",(0,j.useBlockProps)(),(0,$.createElement)(j.InspectorControls,null,(0,$.createElement)(q.PanelBody,null,g&&(0,$.createElement)(Si,X({},e,{block:i})),h.customFill&&a.fillInspectorControls(t,o,e))),(0,$.createElement)("div",{className:"llms-field-group","data-field-layout":g?s:"stacked"},(0,$.createElement)(j.InnerBlocks,{allowedBlocks:c,template:"function"==typeof u?u({attributes:t,clientId:n,block:i,blockType:a}):u,templateLock:d,orientation:v})),m.after&&(0,$.createElement)(q.Slot,{name:`llmsEditFill.after.${m.after}.${p.clientId}`}))},save:function(){return(0,$.createElement)(j.InnerBlocks.Content,null)}},Ri=function(){let e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:"field";const t="field"===e?Di:Pi;return(0,Fr.merge)({},(0,Fr.cloneDeep)(Ti),t)};function Mi(){return(0,Fr.cloneDeep)(["llms_form","wp_block"])}function Li(e){let t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:[];e=(0,Fr.cloneDeep)(e);for(let t=0;t0&&void 0!==arguments[0]?arguments[0]:2,t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:1;const n=[];for(let r=1;r<=e;r++)n.push({default:t&&t>0?"yes":"no",
+// Translators: %d = Option index in the list of options.
+text:(0,H.sprintf)((0,H.__)("Option %d","lifterlms"),r),
+// Translators: %d = Option index in the list of options.
+key:(0,H.sprintf)((0,H.__)("option_%d","lifterlms"),r)}),t--;return n}const Ni="llms/form-field-confirm-group",Fi=Mi(),Vi=!0;function Bi(){let e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};const{id:t}=e;let{match:n}=e;return t&&!n&&(n=`${t}_confirm`),{...e,match:n,columns:6,last_column:!1,isConfirmationControlField:!0,llms_visibility:"off"}}function $i(){let e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},{id:t,name:n,match:r}=e;return t&&!r&&(r=t,t=`${t}_confirm`,n=`${n}_confirm`),{...e,id:t,name:n,match:r,label:e.label?// Translators: %s label of the controller field.
+(0,H.sprintf)((0,H.__)("Confirm %s","lifterlms"),e.label):"",columns:6,last_column:!0,data_store:!1,data_store_key:!1,isConfirmationField:!0,llms_visibility:"off"}}function Hi(e){const{unloadField:t}=(0,jr.dispatch)(wo);t(e)}const Ui=["llms/form-field-text","llms/form-field-user-email","llms/form-field-user-login","llms/form-field-user-password"],ji={from:[],to:[]};Ui.forEach((e=>{ji.from.push({type:"block",blocks:[e],transform:t=>{Hi(t.name);const{llms_visibility:n}=t,r=Bi(t),o=$i(t),s=[(0,zr.createBlock)(e,r),(0,zr.createBlock)("llms/form-field-text",o)];return(0,zr.createBlock)(Ni,{llms_visibility:n},(0,H.isRTL)()?s.reverse():s)}}),ji.to.push({type:"block",blocks:[e],isMatch:()=>{const{getSelectedBlock:t}=(0,jr.select)(j.store),{innerBlocks:n}=t(),r=n[qi(n)],{name:o}=r||{};return o===e},transform:(e,t)=>{const{llms_visibility:n}=e,r=t[qi(t)],{name:o,attributes:s}=r;return Hi(s.name),(0,zr.createBlock)(o,{...s,columns:12,last_column:!0,isConfirmationControlField:!1,match:"",llms_visibility:n})}})}));const qi=e=>e.findIndex((e=>{let{attributes:t}=e;return t.isConfirmationControlField})),zi=Li(Ri("group"),{title:(0,H.__)("Input Confirmation Group","lifterlms"),description:(0,H.__)("Adds a required confirmation field to an input field.","lifterlms"),icon:{src:"controls-repeat"},category:"llms-custom-fields",transforms:ji,fillInspectorControls:(e,t,n)=>{const{clientId:r}=n;return(0,$.createElement)(q.Button,{isDestructive:!0,onClick:()=>function(e){const{getBlock:t}=(0,jr.select)(j.store),{replaceBlock:n}=(0,jr.dispatch)(j.store),r=t(e),{innerBlocks:o}=r,{llms_visibility:s}=r.attributes,{name:l,attributes:i}=o[qi(o)];Hi(i.name),n(e,(0,zr.createBlock)(l,{...i,columns:12,last_column:!0,isConfirmationControlField:!1,match:"",llms_visibility:s}))}(r)},(0,H.__)("Remove confirmation field","lifterlms"))},findControllerBlockIndex:qi,supports:{llms_field_inspector:{customFill:"confirmGroupAdditionalControls"},inserter:!1},llmsInnerBlocks:{allowed:Ui,template:e=>{let{block:t}=e,n=null;return t&&t.innerBlocks.length||(n=[["llms/form-field-text",Bi()],["llms/form-field-text",$i()]]),n}}}),Wi=(0,$.createElement)("svg",{version:"1.1",xmlns:"http://www.w3.org/2000/svg",width:"20px",height:"20px",viewBox:"0 0 416 448"},(0,$.createElement)("path",{d:"M352 232.5v79.5q0 29.75-21.125 50.875t-50.875 21.125h-208q-29.75 0-50.875-21.125t-21.125-50.875v-208q0-29.75 21.125-50.875t50.875-21.125h208q15.75 0 29.25 6.25 3.75 1.75 4.5 5.75 0.75 4.25-2.25 7.25l-12.25 12.25q-2.5 2.5-5.75 2.5-0.75 0-2.25-0.5-5.75-1.5-11.25-1.5h-208q-16.5 0-28.25 11.75t-11.75 28.25v208q0 16.5 11.75 28.25t28.25 11.75h208q16.5 0 28.25-11.75t11.75-28.25v-63.5q0-3.25 2.25-5.5l16-16q2.5-2.5 5.75-2.5 1.5 0 3 0.75 5 2 5 7.25zM409.75 110.25l-203.5 203.5q-6 6-14.25 6t-14.25-6l-107.5-107.5q-6-6-6-14.25t6-14.25l27.5-27.5q6-6 14.25-6t14.25 6l65.75 65.75 161.75-161.75q6-6 14.25-6t14.25 6l27.5 27.5q6 6 6 14.25t-6 14.25z"})),Gi="llms/form-field-checkboxes",Ki=Mi(),Yi=!1,Xi=Li(Ri(),{title:(0,H.__)("Checkboxes","lifterlms"),description:(0,H.__)("A single checkbox toggle or a group of multiple checkboxes.","lifterlms"),icon:{src:Wi},category:"llms-custom-fields",supports:{llms_field_inspector:{options:!0}},attributes:{field:{__default:"checkbox"},options:{__default:Ai(2,0)}},transforms:{from:[{type:"block",blocks:["llms/form-field-radio","llms/form-field-select"],transform:e=>(0,zr.createBlock)(Gi,{...e,field:Xi.attributes.field.__default})}]}}),Qi=(0,$.createElement)("svg",{version:"1.1",xmlns:"http://www.w3.org/2000/svg",width:"20px",height:"20px",viewBox:"0 0 768 768"},(0,$.createElement)("path",{d:"M384 640.5c141 0 256.5-115.5 256.5-256.5s-115.5-256.5-256.5-256.5-256.5 115.5-256.5 256.5 115.5 256.5 256.5 256.5zM384 64.5c177 0 319.5 142.5 319.5 319.5s-142.5 319.5-319.5 319.5-319.5-142.5-319.5-319.5 142.5-319.5 319.5-319.5zM384 223.5c88.5 0 160.5 72 160.5 160.5s-72 160.5-160.5 160.5-160.5-72-160.5-160.5 72-160.5 160.5-160.5z"})),Zi="llms/form-field-radio",Ji=Mi(),ea=!1,ta=Li(Xi,{title:(0,H.__)("Radio","lifterlms"),description:(0,H.__)("A group of radio inputs which can be populated with any number of options.","lifterlms"),icon:{src:Qi},attributes:{field:{__default:"radio"},options:{__default:Ai(2,1)}},transforms:{from:[{type:"block",blocks:["llms/form-field-checkboxes","llms/form-field-select"],transform:e=>(0,zr.createBlock)(Zi,{...e,field:ta.attributes.field.__default})}]}}),na=(0,$.createElement)("svg",{version:"1.1",xmlns:"http://www.w3.org/2000/svg",width:"20px",height:"20px",viewBox:"0 0 384 448"},(0,$.createElement)("path",{d:"M286.25 168.75q4.5 8.75-1.25 16.5l-80 112q-4.75 6.75-13 6.75t-13-6.75l-80-112q-5.75-7.75-1.25-16.5 4.25-8.75 14.25-8.75h160q10 0 14.25 8.75zM320 344v-240q0-3.25-2.375-5.625t-5.625-2.375h-240q-3.25 0-5.625 2.375t-2.375 5.625v240q0 3.25 2.375 5.625t5.625 2.375h240q3.25 0 5.625-2.375t2.375-5.625zM384 104v240q0 29.75-21.125 50.875t-50.875 21.125h-240q-29.75 0-50.875-21.125t-21.125-50.875v-240q0-29.75 21.125-50.875t50.875-21.125h240q29.75 0 50.875 21.125t21.125 50.875z"})),ra="llms/form-field-select",oa=Mi(),sa=!1,la=Li(ta,{title:(0,H.__)("Dropdown","lifterlms"),description:(0,H.__)("A select field which can be populated with any number of options.","lifterlms"),icon:{src:na},attributes:{field:{__default:"select"}},supports:{llms_field_inspector:{placeholder:!0}},transforms:{from:[{type:"block",blocks:["llms/form-field-checkboxes","llms/form-field-radio"],transform:e=>(0,zr.createBlock)(ra,{...e,field:la.attributes.field.__default})}]}}),ia=(0,$.createElement)("svg",{version:"1.1",xmlns:"http://www.w3.org/2000/svg",width:"20px",height:"20px",viewBox:"0 0 512 512"},(0,$.createElement)("path",{d:"M448 0h-384c-8.832 0-16 7.168-16 16v96c0 8.832 7.168 16 16 16h16c8.832 0 16-7.168 16-16l32-48h96v384l-80 32c-8.832 0-16 7.152-16 16s7.168 16 16 16h224c8.848 0 16-7.152 16-16s-7.152-16-16-16l-80-32v-384h96l32 48c0 8.832 7.152 16 16 16h16c8.848 0 16-7.168 16-16v-96c0-8.832-7.152-16-16-16z"})),aa=(0,$.createElement)("svg",{version:"1.1",xmlns:"http://www.w3.org/2000/svg",width:"20px",height:"20px",viewBox:"0 0 540 540"},(0,$.createElement)("path",{d:"M247.75 256l16-64h-63.5l-16 64h63.5zM439.75 130l-14 56q-1.75 6-7.75 6h-81.75l-16 64h77.75q3.75 0 6.25 3 2.5 3.5 1.5 7l-14 56q-1.25 6-7.75 6h-81.75l-20.25 82q-1.75 6-7.75 6h-56q-4 0-6.5-3-2.25-3-1.5-7l19.5-78h-63.5l-20.25 82q-1.75 6-7.75 6h-56.25q-3.75 0-6.25-3-2.25-3-1.5-7l19.5-78h-77.75q-3.75 0-6.25-3-2.25-3-1.5-7l14-56q1.75-6 7.75-6h81.75l16-64h-77.75q-3.75 0-6.25-3-2.5-3.5-1.5-7l14-56q1.25-6 7.75-6h81.75l20.25-82q1.75-6 8-6h56q3.75 0 6.25 3 2.25 3 1.5 7l-19.5 78h63.5l20.25-82q1.75-6 8-6h56q3.75 0 6.25 3 2.25 3 1.5 7l-19.5 78h77.75q3.75 0 6.25 3 2.25 3 1.5 7z"})),ca=Ri(),ua="llms/form-field-text",da=Mi(),pa=!0,fa=[{name:"text",title:(0,H.__)("Text","lifterlms"),description:(0,H.__)("An input field which accepts any form of text.","lifterlms"),isDefault:!0,icon:ia},{name:"email",title:(0,H.__)("Email","lifterlms"),description:(0,H.__)("A text input field which only accepts an email address.","lifterlms"),icon:"email-alt"},{name:"password",title:(0,H.__)("Password","lifterlms"),description:(0,H.__)("User password confirmation field.","lifterlms"),icon:"lock",scope:[]},{name:"number",title:(0,H.__)("Number","lifterlms"),description:(0,H.__)("An input field which only accepts numeric input.","lifterlms"),icon:aa,attributes:{html_attrs:{min:"",max:""}}},{name:"tel",title:(0,H.__)("Phone Number","lifterlms"),description:(0,H.__)("An input field which only accepts phone numbers.","lifterlms"),icon:"phone"},{name:"url",title:(0,H.__)("Website Address / URL","lifterlms"),description:(0,H.__)("An input field which only accepts a website address or URL.","lifterlms"),icon:"admin-links"}];fa.forEach((e=>{e.scope=e.scope||["block","inserter","transform"],window.llmsBlocks.variationIconCanBeObject&&(e.icon={...ca.icon,src:e.icon}),e.attributes||(e.attributes={}),e.attributes.field=e.name,e.isActive=(e,t)=>e.field===t.field}));const ma=Li(ca,{title:(0,H.__)("Text","lifterlms"),description:(0,H.__)("A simple text input field.","lifterlms"),icon:{src:ia},usesContext:["llms/fieldGroup/fieldLayout"],supports:{inserter:!1,llms_field_inspector:{customFill:"fieldTextAdditionalControls"}},variations:fa,fillInspectorControls:(e,t)=>{if(e.isConfirmationField||"number"!==e.field)return;const{html_attrs:n}=e,{min:r,max:o}=n;return(0,$.createElement)($.Fragment,null,(0,$.createElement)(q.TextControl,{label:(0,H.__)("Minimum Value","lifterlms"),help:(0,H.__)("Specify the minimum allowed value. Leave blank for no minimum.","lifterlms"),value:r,type:"number",onChange:e=>t({html_attrs:{...n,min:e}})}),(0,$.createElement)(q.TextControl,{label:(0,H.__)("Maximum Value","lifterlms"),help:(0,H.__)("Specify the maximum allowed value. Leave blank for no maximum.","lifterlms"),value:o,type:"number",onChange:e=>t({html_attrs:{...n,max:e}})}))}}),ha="llms/form-field-textarea",ga=!1,va=Li(ma,{title:(0,H.__)("Textarea","lifterlms"),description:(0,H.__)("A text field accepting multiple lines of user information.","lifterlms"),icon:{src:"editor-paragraph"},category:"llms-custom-fields",supports:{inserter:!0,llms_field_inspector:{customFill:"fieldTextarea"}},attributes:{field:{__default:"textarea"},html_attrs:{__default:{rows:4}}},fillInspectorControls:(e,t)=>{const{html_attrs:n}=e,{rows:r}=n;return(0,$.createElement)(q.TextControl,{label:(0,H.__)("Rows","lifterlms"),help:(0,H.__)("Specify the number of text rows for the textarea input.","lifterlms"),value:r,type:"number",onChange:e=>t({html_attrs:{...n,rows:e}}),min:"2",step:"1"})},transforms:{from:[{type:"block",blocks:["llms/form-field-text"],transform:e=>(0,zr.createBlock)(ha,{...e,html_attrs:{...e.html_attrs,rows:4},field:"textarea"})}],to:[{type:"block",blocks:["llms/form-field-text"],transform:e=>(0,zr.createBlock)("llms/form-field-text",{...e,field:"text"})}]}},["transforms","variations"]),ba="llms/form-field-redeem-voucher",_a=!0,ya=Li(ma,{title:(0,H.__)("Voucher Code Redemption","lifterlms"),description:(0,H.__)("Allows user to redeem a voucher code during account registration.","lifterlms"),icon:{src:"tickets-alt"},supports:{inserter:!0,multiple:!1,llms_field_inspector:{id:!1,name:!1,storage:!1,customFill:"redeemVoucher"}},attributes:{id:{__default:"llms_voucher"},field:{__default:"text"},label:{__default:(0,H.__)("Have a voucher?","lifterlms")},name:{__default:"llms_voucher"},placeholder:{__default:(0,H.__)("Voucher Code","lifterlms")},data_store:{__default:!1},data_store_key:{__default:!1},toggleable:{__default:!1}},fillInspectorControls:(e,t)=>{const{toggleable:n,required:r}=e;return r?null:(0,$.createElement)(q.ToggleControl,{label:(0,H.__)("Toggleable","lifterlms"),checked:!!n,onChange:()=>t({toggleable:!n}),help:n?(0,H.__)("Field is revealed when the toggle is clicked.","lifterlms"):(0,H.__)("Field is always visible.","lifterlms")})}},["transforms","variations"]),wa="llms/form-field-user-display-name",Ea=!0,xa=Li(ma,{title:(0,H.__)("User Display Name","lifterlms"),description:(0,H.__)("Allows a user to choose how their name will be displayed publicly on the site.","lifterlms"),icon:{src:"nametag"},supports:{inserter:!0,multiple:!1,llms_field_inspector:{id:!1,name:!1,required:!1,storage:!1}},attributes:{id:{__default:"display_name"},field:{__default:"text"},label:{__default:(0,H.__)("Display Name","lifterlms")},name:{__default:"display_name"},required:{__default:!0},data_store:{__default:"users"},data_store_key:{__default:"display_name"}}},["transforms","variations"]),Ca="llms/form-field-user-login",ka=!0,Sa=Li(ma,{title:(0,H.__)("User Login","lifterlms"),description:(0,H.__)("Field used to collect a user's account username. If this field is omitted a username will be automatically generated based off their email address. Users can always login using either their email address or username.","lifterlms"),icon:{src:"admin-users"},supports:{inserter:!0,multiple:!1,llms_field_inspector:{id:!1,name:!1,required:!1,storage:!1}},attributes:{id:{__default:"user_login"},field:{__default:"text"},label:{__default:(0,H.__)("Username","lifterlms")},name:{__default:"user_login"},required:{__default:!0},data_store:{__default:"users"},data_store_key:{__default:"user_login"},llms_visibility:{default:"logged_out"}}},["transforms","variations"]),Oa="llms/form-field-user-email",Ia=!0,Ta=Li(ma,{title:(0,H.__)("User Email","lifterlms"),description:(0,H.__)("A special field used to collect a user's account email address.","lifterlms"),icon:{src:"email-alt"},supports:{inserter:!0,multiple:!1,llms_field_inspector:{id:!1,name:!1,required:!1,storage:!1}},attributes:{id:{__default:"email_address"},field:{__default:"email"},label:{__default:(0,H.__)("Email Address","lifterlms")},name:{__default:"email_address"},required:{__default:!0},data_store:{__default:"users"},data_store_key:{__default:"user_email"}}},["transforms","variations"]),Da="llms/form-field-user-first-name",Pa=!0,Ra=Li(ma,{title:(0,H.__)("First Name","lifterlms"),description:(0,H.__)("A special field used to collect a user's first name.","lifterlms"),icon:{src:"admin-users"},supports:{multiple:!1,llms_field_inspector:{id:!1,name:!1,required:!0,storage:!1}},attributes:{id:{__default:"first_name"},field:{__default:"text"},label:{__default:(0,H.__)("First Name","lifterlms")},name:{__default:"first_name"},required:{__default:!0},data_store:{__default:"usermeta"},data_store_key:{__default:"first_name"}},parent:["llms/form-field-user-name"],usesContext:["llms/fieldGroup/fieldLayout"]},["transforms","variations"]),Ma="llms/form-field-user-last-name",La=!0,Aa=Li(Ra,{title:(0,H.__)("Last Name","lifterlms"),description:(0,H.__)("A special field used to collect a user's last name.","lifterlms"),attributes:{id:{__default:"last_name"},label:{__default:(0,H.__)("Last Name","lifterlms")},name:{__default:"last_name"},data_store_key:{__default:"last_name"}}}),Na="llms/form-field-user-name",Fa=Mi(),Va=!0,Ba=Li(Ri("group"),{title:(0,H.__)("User name","lifterlms"),description:(0,H.__)("A special field used to collect a user's first and last name.","lifterlms"),icon:{src:"admin-users"},supports:{inserter:!0,multiple:!1},llmsInnerBlocks:{allowed:["llms/form-field-user-first-name","llms/form-field-user-last-name"],template:[["llms/form-field-user-first-name",{columns:6,last_column:!1}],["llms/form-field-user-last-name",{columns:6,last_column:!0}]]}}),$a="llms/form-field-user-password",Ha=!0;function Ua(e,t){return{html_attrs:{...e,minlength:t}}}const ja=Li(ma,{title:(0,H.__)("User Password","lifterlms"),description:(0,H.__)("A special field used to collect a user's account password.","lifterlms"),icon:{src:"lock"},supports:{inserter:!0,multiple:!1,llms_field_inspector:{id:!1,name:!1,required:!1,storage:!1,customFill:"userPassAdditionalControls"},llms_edit_fill:{after:"userPassStrengthMeter"}},attributes:{id:{__default:"password"},field:{__default:"password"},label:{__default:(0,H.__)("Password","lifterlms")},name:{__default:"password"},required:{__default:!0},data_store:{__default:"users"},data_store_key:{__default:"user_pass"},meter:{type:"boolean",__default:!0},meter_description:{type:"string",__default:(0,H.__)("A strong password is required with at least 8 characters. To make it stronger, use both upper and lower case letters, numbers, and symbols.","lifterlms")},min_strength:{type:"string",__default:"strong"},html_attrs:{__default:{minlength:8}}},fillEditAfter:(e,t)=>{const{meter:n,meter_description:r}=e;return n?(0,$.createElement)($.Fragment,null,(0,$.createElement)("div",{className:"llms-pwd-meter"},(0,$.createElement)("div",null,(0,H.__)("Very Weak","lifterlms"))),(0,$.createElement)(j.RichText,{style:{marginTop:0},tagName:"p",value:r,onChange:e=>t({meter_description:e}),allowedFormats:["core/bold","core/italic"],"aria-label":r?(0,H.__)("Password strength meter description","lifterlms"):(0,H.__)("Empty Password strength meter description; start writing to add a label"),placeholder:(0,H.__)("Enter a description for the password strength meter","lifterlms")})):null},fillInspectorControls:(e,t)=>{const{isConfirmationControlField:n,isConfirmationField:r,meter:o,min_strength:s,html_attrs:l}=e,{minlength:i}=l;if(r)return;return(0,$.createElement)($.Fragment,null,(0,$.createElement)(q.ToggleControl,{label:(0,H.__)("Password strength meter","lifterlms"),help:o?(0,H.__)("Password strength meter is enabled.","lifterlms"):(0,H.__)("Password strength meter is disabled.","lifterlms"),checked:o,onChange:()=>t({meter:!o})}),o&&(0,$.createElement)(q.SelectControl,{label:(0,H.__)("Minimum Password Strength","lifterlms"),value:s,onChange:e=>t({min_strength:e}),options:[{value:"strong",label:(0,H.__)("Strong","lifterlms")},{value:"medium",label:(0,H.__)("Medium","lifterlms")},{value:"weak",label:(0,H.__)("Weak","lifterlms")}]}),(0,$.createElement)(q.TextControl,{label:(0,H.__)("Minimum Password Length","lifterlms"),value:i,type:"number",min:"6",onChange:e=>(e=>{t(Ua(l,e)),n&&function(e){const{getSelectedBlockClientId:t}=(0,jr.select)(j.store),{updateBlockAttributes:n}=(0,jr.dispatch)(j.store),r=xi(t()),{attributes:o,clientId:s}=r,{html_attrs:l}=o;n(s,Ua(l,e))}(e)})(1*e)}))}},["transforms","variations"]),qa="llms/form-field-user-address",za=Mi(),Wa=!0,Ga=Li(Ri("group"),{title:(0,H.__)("User Address","lifterlms"),description:(0,H.__)("A group of fields used to collect a user's full address.","lifterlms"),icon:{src:"id-alt"},supports:{inserter:!0,multiple:!1},llmsInnerBlocks:{allowed:["llms/form-field-user-address-street","llms/form-field-user-address-city","llms/form-field-user-address-country","llms/form-field-user-address-region"],template:[["llms/form-field-user-address-street"],["llms/form-field-user-address-city"],["llms/form-field-user-address-country"],["llms/form-field-user-address-region"]]}},["providesContext"]),Ka="llms/form-field-user-address-street",Ya=Mi(),Xa=!0,Qa=Li(Ri("group"),{title:(0,H.__)("User Street Address","lifterlms"),description:(0,H.__)("Collect a user's street address.","lifterlms"),icon:{src:"id-alt"},supports:{multiple:!1},llmsInnerBlocks:{allowed:["llms/form-field-user-address-street-primary","llms/form-field-user-address-street-secondary"],template:[["llms/form-field-user-address-street-primary",{columns:8,last_column:!1}],["llms/form-field-user-address-street-secondary",{columns:4,last_column:!0}]]},parent:["llms/form-field-user-name"]}),Za="llms/form-field-user-address-street-primary",Ja=!0,ec=Li(ma,{title:(0,H.__)("User Street Address","lifterlms"),description:(0,H.__)("A special field used to collect a user's street address.","lifterlms"),icon:{src:"admin-home"},supports:{multiple:!1,llms_field_inspector:{id:!1,name:!1,required:!0,storage:!1}},attributes:{id:{__default:"llms_billing_address_1"},label:{__default:(0,H.__)("Address","lifterlms")},name:{__default:"llms_billing_address_1"},required:{__default:!0},data_store:{__default:"usermeta"},data_store_key:{__default:"llms_billing_address_1"}},parent:["llms/form-field-user-address-street"],usesContext:["llms/fieldGroup/fieldLayout"]},["transforms","variations"]),tc="llms/form-field-user-address-street-secondary",nc=!0,rc=Li(ec,{title:(0,H.__)("User Street Address Additional Information","lifterlms"),description:(0,H.__)("A special field used to collect a user's street address.","lifterlms"),attributes:{id:{__default:"llms_billing_address_2"},label:{__default:""},placeholder:{__default:(0,H.__)("Apartment, suite, etc…","lifterlms")},name:{__default:"llms_billing_address_2"},required:{__default:!1},data_store_key:{__default:"llms_billing_address_2"},label_show_empty:{__default:!0}},usesContext:["llms/fieldGroup/fieldLayout"]},["transforms","variations"]),oc="llms/form-field-user-address-city",sc=!0,lc=Li(ma,{title:(0,H.__)("User City","lifterlms"),description:(0,H.__)("A special field used to collect a user's billing city.","lifterlms"),icon:{src:"location-alt"},supports:{multiple:!1,llms_field_inspector:{id:!1,name:!1,required:!0,match:!1,storage:!1}},attributes:{id:{__default:"llms_billing_city"},label:{__default:(0,H.__)("City","lifterlms")},name:{__default:"llms_billing_city"},required:{__default:!0},data_store:{__default:"usermeta"},data_store_key:{__default:"llms_billing_city"}},parent:["llms/form-field-user-address"]},["transforms","variations"]),ic="llms/form-field-user-address-country",ac=!0,cc=Li(la,{title:(0,H.__)("User Country","lifterlms"),description:(0,H.__)("A special field used to collect a user's billing country.","lifterlms"),icon:{src:"admin-site"},supports:{multiple:!1,llms_field_inspector:{id:!1,name:!1,required:!0,match:!1,storage:!1,options:!1}},attributes:{id:{__default:"llms_billing_country"},label:{__default:(0,H.__)("Country","lifterlms")},name:{__default:"llms_billing_country"},required:{__default:!0},data_store:{__default:"usermeta"},data_store_key:{__default:"llms_billing_country"},options_preset:{__default:"countries"},placeholder:{__default:(0,H.__)("Select a Country","lifterlms")}},parent:["llms/form-field-user-address"]},["transforms"]),uc="llms/form-field-user-address-region",dc=Mi(),pc=!0,fc=Li(Ri("group"),{title:(0,H.__)("User Street Address","lifterlms"),description:(0,H.__)("Collect a user's street address.","lifterlms"),icon:{src:"id-alt"},supports:{multiple:!1},parent:["llms/form-field-user-name"],llmsInnerBlocks:{allowed:["llms/form-field-user-address-state","llms/form-field-user-address-postal-code"],template:[["llms/form-field-user-address-state",{columns:6,last_column:!1}],["llms/form-field-user-address-postal-code",{columns:6,last_column:!0}]]}}),mc="llms/form-field-user-address-state",hc=!0,gc=Li(la,{title:(0,H.__)("User Country","lifterlms"),description:(0,H.__)("A special field used to collect a user's billing country.","lifterlms"),icon:{src:"location"},supports:{multiple:!1,llms_field_inspector:{id:!1,name:!1,required:!0,storage:!1,options:!1}},attributes:{id:{__default:"llms_billing_state"},label:{__default:(0,H.__)("State / Region","lifterlms")},name:{__default:"llms_billing_state"},required:{__default:!0},data_store:{__default:"usermeta"},data_store_key:{__default:"llms_billing_state"},options_preset:{__default:"states"},placeholder:{__default:(0,H.__)("Select a State / Region","lifterlms")}},parent:["llms/form-field-user-address-region"],usesContext:["llms/fieldGroup/fieldLayout"]},["transforms"]),vc="llms/form-field-user-address-postal-code",bc=!0,_c=Li(ma,{title:(0,H.__)("User Postal Code","lifterlms"),description:(0,H.__)("A special field used to collect a user's postal or zip code.","lifterlms"),icon:{src:"post-status"},supports:{multiple:!1,llms_field_inspector:{id:!1,name:!1,required:!0,match:!1,storage:!1}},attributes:{id:{__default:"llms_billing_zip"},label:{__default:(0,H.__)("Postal / Zip Code","lifterlms")},name:{__default:"llms_billing_zip"},required:{__default:!0},data_store:{__default:"usermeta"},data_store_key:{__default:"llms_billing_zip"}},parent:["llms/form-field-user-address-region"],usesContext:["llms/fieldGroup/fieldLayout"]},["transforms","variations"]),yc="llms/form-field-user-phone",wc=!0,Ec=Li(ma,{title:(0,H.__)("User Phone","lifterlms"),description:(0,H.__)("A field used to collect a user's phone number.","lifterlms"),icon:{src:"phone"},supports:{inserter:!0,multiple:!1,llms_field_inspector:{id:!1,name:!1,storage:!1}},attributes:{id:{__default:"llms_phone"},field:{__default:"tel"},label:{__default:(0,H.__)("Phone Number","lifterlms")},name:{__default:"llms_phone"},data_store:{__default:"usermeta"},data_store_key:{__default:"llms_phone"}}},["transforms","variations"]),xc=()=>{const e=(0,V.applyFilters)("llms.formBlocksSafelist",["core/block","core/paragraph","core/heading","core/image","core/html","core/column","core/columns","core/group","core/separator","core/spacer"]),{getCurrentPost:t}=(0,jr.select)(qr.store),{meta:n={}}=t(),{_llms_form_location:r}=n;(0,zr.getBlockTypes)().forEach((t=>{let{name:n}=t;(t=>-1===e.indexOf(t)&&(0===t.indexOf("llms/form-field-redeem-voucher")?"registration"!==r:0===t.indexOf("llms/form-field-user-login")?"account"===r:-1===t.indexOf("llms/form-field")))(n)&&(0,zr.unregisterBlockType)(n)}))};Ur()((()=>{const{getCurrentPost:e}=(0,jr.select)(qr.store);let t=!1;const n=(0,jr.subscribe)((()=>{const r=e();if(!1===t&&0!==Object.keys(r).length){t=!0,n();const{type:e,is_llms_field:o}=r;"llms_form"===e?(Yr(),xc(),ko()):"wp_block"===e&&"yes"===o&&(xc(),ko())}}))}));const Cc=window.wp.plugins,kc=window.wp.editPost,Sc=(0,V.applyFilters)("llms_blocks_post_visibility_options",[{value:"catalog_search",label:(0,H.__)("Visible","lifterlms"),info:(0,H.__)("Visible in the catalog and search results.","lifterlms")},{value:"catalog",label:(0,H.__)("Catalog only","lifterlms"),info:(0,H.__)("Only visible in the catalog.","lifterlms")},{value:"search",label:(0,H.__)("Search only","lifterlms"),info:(0,H.__)("Only visible in search results.","lifterlms")},{value:"hidden",label:(0,H.__)("Hidden","lifterlms"),info:(0,H.__)("Hidden from catalog and search results.","lifterlms")}]),Oc=(0,jr.withSelect)((e=>({visibility:e("core/editor").getEditedPostAttribute("visibility")})))((function(e){let{visibility:t}=e;return Sc.find((e=>{let{value:n}=e;return n===t})).label}));class Ic extends $.Component{render(){const{onUpdateVisibility:e,instanceId:t,visibility:n}=this.props,r={catalog_search:{checked:"catalog_search"===n},catalog:{checked:"catalog"===n},search:{checked:"search"===n},hidden:{checked:"hidden"===n}};return(0,$.createElement)(kc.PluginPostStatusInfo,{className:"llms-post-visibility"},(0,$.createElement)("span",null,(0,H.__)("Catalog & Search Visibility","lifterlms")),(0,$.createElement)("div",null,(0,$.createElement)(q.Dropdown,{className:"llms-post-visibility-dropdown",contentClassName:"llms-post-visibility-content edit-post-post-visibility__dialog",renderToggle:e=>{let{isOpen:t,onToggle:n}=e;return(0,$.createElement)(q.Button,{onClick:n,"aria-expanded":t,isLink:!0},(0,$.createElement)(Oc,null))},renderContent:()=>(0,$.createElement)("fieldset",{key:"visibility-selector",className:"editor-post-visibility__dialog-fieldset"},(0,$.createElement)("legend",{className:"editor-post-visibility__dialog-legend"},(0,H.__)("Catalog Visibility","lifterlms")),Sc.map((n=>{let{value:o,label:s,info:l}=n;return(0,$.createElement)("div",{key:o,className:"editor-post-visibility__choice"},(0,$.createElement)("input",{type:"radio",name:`llms-editor-post-visibility__setting-${t}`,value:o,onChange:()=>e(o),checked:r[o].checked,id:`editor-post-${o}-${t}`,"aria-describedby":`editor-post-${o}-${t}-description`,className:"editor-post-visibility__dialog-radio"}),(0,$.createElement)("label",{htmlFor:`editor-post-${o}-${t}`,className:"editor-post-visibility__dialog-label"},s),(0,$.createElement)("p",{id:`llms-editor-post-${o}-${t}-description`,className:"editor-post-visibility__dialog-info"},l))})))})))}}const Tc=(0,U.compose)([(0,jr.withSelect)((e=>{const{getCurrentPostType:t,getEditedPostAttribute:n}=e("core/editor");return{postType:t(),visibility:n("visibility")}})),(0,jr.withDispatch)((e=>{const{editPost:t}=e("core/editor");return{onUpdateVisibility(e){t({visibility:e})}}})),(0,U.ifCondition)((e=>{let{postType:t}=e;return-1!==["course","llms_membership"].indexOf(t)})),U.withInstanceId])(Ic);(0,Cc.registerPlugin)("llms-post-visibility",{render:Tc});const{Fragment:Dc}=wp.element,Pc={fillRule:"evenodd",clipRule:"evenodd",strokeLinejoin:"round",strokeMiterlimit:1.41421},Rc=()=>(0,$.createElement)(Dc,null,(0,$.createElement)("svg",{width:"20px",height:"20px",viewBox:"0 0 85 85",version:"1.1",style:Pc},(0,$.createElement)("g",{id:"lifterlms-icon"},(0,$.createElement)("path",{d:"M29.061,50.631l-2.258,-1.29l-6.066,10.452c-5.483,-7.613 -6.58,-17.873 -2.322,-26.712l0.064,-0.065c0.258,-0.581 0.581,-1.097 0.839,-1.613c4.323,-7.485 11.873,-12.067 19.873,-12.905c1.42,-1.935 2.969,-3.614 4.711,-5.226c-11.421,-0.645 -22.843,5.032 -28.972,15.615c-7.872,13.679 -4.258,30.841 7.872,40.263l6.065,-18.003c0.065,-0.128 0.13,-0.323 0.194,-0.516m36.908,-16.712c3.227,7.421 3.033,16.195 -1.291,23.681c-0.257,0.516 -0.58,1.031 -0.903,1.548l-0.064,0.066c-5.549,8.129 -14.97,12.323 -24.326,11.355l6.066,-10.453l-2.259,-1.291c-0.129,0.13 -0.258,0.259 -0.387,0.389l-12.518,14.259c14.196,5.808 30.907,0.323 38.779,-13.357c6.13,-10.581 5.356,-23.293 -0.967,-32.842c-0.517,2.257 -1.162,4.516 -2.13,6.645"}),(0,$.createElement)("path",{d:"M44.999,50.243c-1.614,2.13 -4.194,3.228 -6.968,3.485c-0.839,0.065 -1.614,-0.387 -2.001,-1.161c-1.162,-2.517 -1.548,-5.291 -0.451,-7.743l-12.648,-7.291c-0.838,-0.516 -1.225,-1.356 -0.967,-2.258c0.193,-0.904 0.967,-1.55 1.871,-1.55l12.84,-0.451c0.968,-3.936 2.581,-7.678 4.904,-11.163c3.678,-5.484 8.904,-9.549 15.034,-12.001c1.485,-0.581 2.968,-1.096 4.453,-1.484c1.096,-0.258 2.193,0.388 2.451,1.421c0.452,1.482 0.775,3.031 1.033,4.579c0.903,6.582 -0.065,13.163 -2.903,19.099c-1.807,3.743 -4.324,6.97 -7.228,9.808l6.001,11.292c0.452,0.839 0.323,1.807 -0.387,2.452c-0.645,0.645 -1.614,0.71 -2.387,0.258l-12.647,-7.292Zm9.549,-27.035c1.936,1.162 2.581,3.614 1.485,5.549c-1.098,1.936 -3.613,2.582 -5.55,1.485c-1.935,-1.098 -2.58,-3.614 -1.484,-5.55c1.162,-1.935 3.614,-2.581 5.549,-1.484"}),(0,$.createElement)("path",{d:"M26.093,72.118l13.679,-15.551c-0.516,0.065 -1.032,0.129 -1.549,0.194c-2.064,0.129 -4,-0.968 -4.902,-2.903c-0.259,-0.452 -0.453,-0.904 -0.646,-1.42l-6.582,19.68Z"})))),Mc=window.wp.richText;function Lc(e){let{text:t,onSuccess:n}=e;const r=void 0!==U.useCopyToClipboard;return(0,$.createElement)(q.Tooltip,{text:(0,H.__)("Click to copy.","lifterlms")},r&&(0,$.createElement)((()=>{const e=(0,U.useCopyToClipboard)(t,n);return(0,$.createElement)(q.Button,{isLink:!0,ref:e},t)}),null),!r&&(0,$.createElement)((()=>(0,$.createElement)(q.ClipboardButton,{isLink:!0,text:t,onCopy:n},t)),null))}function Ac(e){let{closeModal:t,isActive:n,onChange:r,searchQuery:o,value:s,defaultValue:l}=e,{userInfoFields:i}=window.llms;o&&(i=i.filter((e=>function(e,t){let{label:n,name:r,id:o,data_store_key:s}=t;const l=[n,r,o,s],i=e.toLowerCase();return l.some((e=>e.toLowerCase().includes(i)))}(o,e))));const a=!i.length;a&&i.push({data_store_key:o,label:(0,H.__)("Custom User Information","lifterlms"),id:"custom",name:o});const c=(0,V.applyFilters)("llms/userInfoShortcodes/exclude",["password"]);return i=i.filter((e=>{let{id:t}=e;return!c.includes(t)})),(0,$.createElement)($.Fragment,null,a&&(0,$.createElement)("p",{className:"llms-error"},(0,H.__)("No fields found matching your search but you can use the shortcode below if the meta information exists in the database.","lifterlms")),(0,$.createElement)("table",{className:"llms-table zebra"},(0,$.createElement)("thead",null,(0,$.createElement)("tr",null,(0,$.createElement)("th",null,(0,H.__)("Name","lifterlms")),(0,$.createElement)("th",null,(0,H.__)("Shortcode","lifterlms")),(0,$.createElement)("th",null,(0,H.__)("Insert","lifterlms")))),(0,$.createElement)("tbody",null,i.map((e=>function(e,t,n,r,o,s){let{label:l,name:i,data_store_key:a}=e;const c=function(e){let t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"";return`[llms-user ${e}${t?` or="${t}"`:""}]`}(a,s);return(0,$.createElement)("tr",{key:i},(0,$.createElement)("td",null,l),(0,$.createElement)("td",null,(0,$.createElement)(Lc,{text:c,onSuccess:n})),(0,$.createElement)("td",null,(0,$.createElement)(q.Button,{isSecondary:!0,isSmall:!0,onClick:()=>{const e=(0,Mc.create)({html:`${c} `});n(),r(t?(0,Mc.replace)(o,/\[user .+?\]/,e):(0,Mc.insert)(o,e))}},(0,H.__)("Insert","lifterlms"))))}(e,n,t,r,s,l))))))}(0,Mc.registerFormatType)("llms/user-info-shortcodes",{title:(0,H.__)("LifterLMS User Information Shortcodes","lifterlms"),tagName:"span",className:"llms-user-sc-wrap",edit:function(e){const[t,n]=(0,$.useState)(!1),[r,o]=(0,$.useState)(""),[s,l]=(0,$.useState)(""),i=()=>n(!1),{value:a,onChange:c,isActive:u}=e;return(0,$.createElement)($.Fragment,null,(0,$.createElement)(j.RichTextToolbarButton,{icon:(0,$.createElement)(Rc,null),title:(0,H.__)("Shortcodes","lifterlms"),onClick:()=>n(!0)}),t&&(0,$.createElement)(q.Modal,{className:"llms-shortcodes-modal",title:(0,H.__)("LifterLMS User Information Shortcodes","lifterlms"),onRequestClose:i},(0,$.createElement)("div",{className:"llms-shortcodes-modal--main"},(0,$.createElement)("aside",null,(0,$.createElement)(q.TextControl,{type:"search",label:(0,H.__)("Filter by label, key, or ID…","lifterlms"),onChange:e=>o(e)}),(0,$.createElement)(q.TextControl,{label:(0,H.__)("Default value","lifterlms"),onChange:e=>l(e),help:(0,H.__)("Optional text displayed when no information exists or the user is logged out.","lifterlms")})),(0,$.createElement)("section",null,(0,$.createElement)(Ac,{closeModal:i,isActive:u,onChange:c,searchQuery:r,value:a,defaultValue:s})))))}});const Nc=window.wp.url;function Fc(e){const{id:t,item:n,index:r,setNodeRef:o,listeners:s,manageState:l}=e,{visibility:i,name:a,label:c}=n,{updateItem:u,deleteItem:d}=l,p="visible"===i,f=0===r,[m,h]=(0,$.useState)(!1);return(0,$.createElement)($.Fragment,null,(0,$.createElement)("div",{className:"llms-instructor--header"},(0,$.createElement)("section",null,(0,$.createElement)("strong",null,a),(0,$.createElement)("small",null,"(#",t,")")),(0,$.createElement)("aside",null,f&&(0,$.createElement)(q.Tooltip,{text:(0,H.__)("Primary Instructor","lifterlms")},(0,$.createElement)(q.Dashicon,{icon:"star-filled"})),(0,$.createElement)(vi,{label:(0,H.__)("Reorder instructor","lifterlms"),setNodeRef:o,listeners:s}),(0,$.createElement)(q.Button,{isSmall:!0,showTooltip:!0,label:(0,H.__)("Edit instructor","lifterlms"),icon:m?"arrow-up-alt2":"arrow-down-alt2",onClick:()=>h(!m)}))),m&&(0,$.createElement)("div",{className:"llms-instructor--settings"},(0,$.createElement)(q.ToggleControl,{label:(0,H.__)("Visibility","lifterlms"),help:p?(0,H.__)("Instructor is visible on frontend","lifterlms"):(0,H.__)("Instructor is hidden on frontend","lifterlms"),checked:p,onChange:e=>u(t,{visibility:e?"visible":"hidden"})}),p&&(0,$.createElement)(q.TextControl,{label:(0,H.__)("Label","lifterlms"),value:c,onChange:e=>u(t,{label:e})}),(0,$.createElement)(q.Button,{isSecondary:!0,iconPosition:"right",href:(0,Nc.addQueryArgs)("/wp-admin/user-edit.php",{user_id:t}),target:"_blank",rel:"noreferrer",style:{marginRight:"5px"}},(0,H.__)("Edit","lifterlms"),(0,$.createElement)(q.Dashicon,{icon:"external"})),!f&&(0,$.createElement)(q.Button,{isDestructive:!0,onClick:()=>d(n)},(0,H.__)("Remove","lifterlms"))))}function Vc(e){let{instructors:t,roles:n,addInstructor:r}=e;return(0,$.createElement)(ys,{roles:n,placeholder:(0,H.__)("Search…","lifterlms"),searchArgs:{exclude:t.map((e=>{let{id:t}=e;return t}))},onChange:r})}class Bc extends $.Component{constructor(){super(...arguments),K(this,"onSearchChange",(e=>{this.setState({search:e})})),K(this,"updateInstructor",((e,t)=>{const{instructors:n}=this.state,r=n.map((n=>(e===n.id&&(n={...n,...t}),n)));this.updateInstructors(r)})),K(this,"updateInstructors",(e=>{this.setState({instructors:e}),this.props.updateInstructors(e)})),K(this,"addInstructor",(e=>{let{id:t,name:n}=e;const{instructors:r}=this.state;r.push({...this.getInstructorDefaults(),id:t,name:n}),this.updateInstructors(r)})),K(this,"removeInstructor",(e=>{let{instructors:t}=this.state;t=t.filter((t=>{let{id:n}=t;return e.id!==n})),this.updateInstructors(t)})),K(this,"render",(()=>(0,$.createElement)(q.PanelBody,{title:(0,H.__)("Instructors","lifterlms")},(0,$.createElement)(Vc,{roles:this.getRoles(),instructors:this.state.instructors,addInstructor:this.addInstructor}),(0,$.createElement)(_i,{ListItem:Fc,items:this.state.instructors,itemClassName:"llms-instructor",manageState:{createItem:this.addInstructor,deleteItem:this.removeInstructor,updateItem:this.updateInstructor,updateItems:this.updateInstructors}}))));let{instructors:e}=this.props;e="string"==typeof e?JSON.parse(e):e,this.state={instructors:e||[],search:""}}getRoles(){return(0,V.applyFilters)("llms_instructor_roles",["administrator","lms_manager","instructor","instructors_assistant"])}getInstructorDefaults(){return(0,V.applyFilters)("llms_instructor_defaults",{label:(0,H.__)("Author","lifterlms"),visibility:"visible"})}}const $c=(0,jr.withSelect)((e=>{const{getEditedPostAttribute:t}=e("core/editor");return{instructors:t("instructors")}})),Hc=(0,jr.withDispatch)((e=>{const{editPost:t}=e("core/editor");return{updateInstructors(e){t({instructors:JSON.stringify(e)})}}})),Uc=(0,U.compose)([$c,Hc])(Bc),jc=window.wp.notices,{Fill:qc,Slot:zc}=(0,q.createSlotFill)("LLMSFormDocSettings"),Wc=e=>{let{children:t}=e;return(0,$.createElement)(qc,null,t)};Wc.Slot=zc,window.llms.plugins=window.llms.plugins||{},window.llms.plugins.LLMSFormDocSettings=Wc;const Gc=Wc;class Kc extends $.Component{constructor(){super(...arguments),K(this,"render",(()=>{if(void 0===kc.PluginDocumentSettingPanel)return null;if("llms_form"!==(0,jr.select)(qr.store).getCurrentPostType())return null;const{location:e,link:t,showTitle:n,freeApTitle:r,setFormMetas:o}=this.props,{formLocations:s}=window.llms,l=s[e];function i(e){(0,jr.dispatch)(j.store).replaceBlocks((0,jr.select)(j.store).getBlocks().map((e=>e.clientId)),(0,zr.parse)(e))}function a(){const e="llms-form-restore-default",t=(0,jr.select)(qr.store).getEditedPostAttribute("content"),{createSuccessNotice:n,removeNotice:r}=(0,jr.dispatch)(jc.store),{resetFields:o}=(0,jr.dispatch)(wo);o(),i(l.template),n((0,H.__)("The form has been restored to the default template.","lifterlms"),{id:e,actions:[{label:(0,H.__)("Undo","lifterlms"),onClick:()=>{o(),i(t),r(e)}}]})}return""===n&&o({_llms_form_show_title:"yes"}),(0,$.createElement)($.Fragment,null,(0,$.createElement)(kc.PluginDocumentSettingPanel,{className:"llms-forms-doc-settings",name:"llms-forms-doc-settings",title:(0,H.__)("Form Settings","lifterlms"),opened:!0},(0,$.createElement)(Gc.Slot,null,(s=>(0,$.createElement)($.Fragment,null,(0,$.createElement)(q.PanelRow,null,(0,$.createElement)("strong",null,(0,H.__)("Location","lifterlms")),!t&&(0,$.createElement)("strong",null,l.name),t&&(0,$.createElement)(q.ExternalLink,{href:t},l.name)),(0,$.createElement)("p",{style:{marginTop:"5px"}},(0,$.createElement)("em",null,l.description)),s,(0,$.createElement)("br",null),(0,$.createElement)(q.ToggleControl,{label:(0,H.__)("Display Form Title","lifterlms"),checked:"yes"===n,help:"yes"===n?(0,H.__)("Displaying form title.","lifterlms"):(0,H.__)("Not displaying form title.","lifterlms"),onChange:e=>o({_llms_form_show_title:e?"yes":"no"})}),"checkout"===e&&"yes"===n&&(0,$.createElement)(q.TextControl,{label:(0,H.__)("Free Access Plan Form Title","lifterlms"),value:r,onChange:e=>o({_llms_form_title_free_access_plans:e}),help:(0,H.__)("The form title to be shown for free access plans.","lifterlms")}),(0,$.createElement)("br",null),(0,$.createElement)(q.PanelRow,null,(0,$.createElement)(q.Button,{isDestructive:!0,onClick:a},(0,H.__)("Revert to Default","lifterlms"))),(0,$.createElement)("p",{style:{marginTop:"5px"}},(0,$.createElement)("em",null,(0,H.__)("Replace the existing content of the form with the original default content.","lifterlms"))))))))}))}}const Yc=(0,jr.withSelect)((e=>{const{getCurrentPost:t,getCurrentPostType:n,getEditedPostAttribute:r}=e(qr.store);if("llms_form"!==n())return{};const o=r("meta");return{link:t().link,location:o._llms_form_location,showTitle:o._llms_form_show_title,freeApTitle:o._llms_form_title_free_access_plans}})),Xc=(0,jr.withDispatch)((e=>{const{editPost:t}=e("core/editor");return{setFormMetas(e){t({meta:e})}}})),Qc=(0,U.compose)([Yc,Xc])(Kc);(0,Cc.registerPlugin)("llms",{render:()=>-1!==["course","llms_membership"].indexOf((0,jr.select)("core/editor").getCurrentPostType())?(0,$.createElement)($.Fragment,null,(0,$.createElement)(kc.PluginSidebarMoreMenuItem,{target:"llms-sidebar",icon:(0,$.createElement)(Rc,null)},"LifterLMS"),(0,$.createElement)(kc.PluginSidebar,{name:"llms-sidebar",title:"LifterLMS"},(0,$.createElement)(Uc,null))):null,icon:(0,$.createElement)(Rc,null)}),(0,Cc.registerPlugin)("llms-forms-doc-settings",{render:Qc,icon:""}),(()=>{const e=Kr(),t=[r,o,s,l,i,a,c,u,d];Object.keys(F).forEach((e=>{F[e].composed&&t.push(F[e])})),["llms_form","wp_block"].includes(e)&&(0,V.doAction)("llms_form_fields_ready",F),t.forEach((t=>{const{name:n,postTypes:r,settings:o}=t;r&&-1===r.indexOf(e)||(0,zr.registerBlockType)(n,o)}))})();const{components:Zc={}}=window.llms;window.llms.components={...Zc,...p}})()})();
\ No newline at end of file
diff --git a/libraries/lifterlms-blocks/includes/blocks/class-llms-blocks-course-information-block.php b/libraries/lifterlms-blocks/includes/blocks/class-llms-blocks-course-information-block.php
new file mode 100644
index 0000000000..f1407e9e09
--- /dev/null
+++ b/libraries/lifterlms-blocks/includes/blocks/class-llms-blocks-course-information-block.php
@@ -0,0 +1,169 @@
+ __( 'Course Information', 'lifterlms' ),
+ 'title_size' => 'h3',
+ 'show_length' => true,
+ 'show_difficulty' => true,
+ 'show_tracks' => true,
+ 'show_cats' => true,
+ 'show_tags' => true,
+ )
+ );
+
+ $show_wrappers = false;
+
+ if ( $attributes['show_length'] ) {
+ $show_wrappers = true;
+ add_action( $this->get_render_hook(), 'lifterlms_template_single_length', 10 );
+ }
+
+ if ( $attributes['show_difficulty'] ) {
+ $show_wrappers = true;
+ add_action( $this->get_render_hook(), 'lifterlms_template_single_difficulty', 20 );
+ }
+
+ if ( $attributes['show_tracks'] ) {
+ $show_wrappers = true;
+ add_action( $this->get_render_hook(), 'lifterlms_template_single_course_tracks', 25 );
+ }
+
+ if ( $attributes['show_cats'] ) {
+ $show_wrappers = true;
+ add_action( $this->get_render_hook(), 'lifterlms_template_single_course_categories', 30 );
+ }
+
+ if ( $attributes['show_tags'] ) {
+ $show_wrappers = true;
+ add_action( $this->get_render_hook(), 'lifterlms_template_single_course_tags', 35 );
+ }
+
+ if ( $show_wrappers ) {
+
+ $this->title = $attributes['title'];
+ $this->title_size = $attributes['title_size'];
+
+ add_filter( 'llms_course_meta_info_title', array( $this, 'filter_title' ) );
+ add_filter( 'llms_course_meta_info_title_size', array( $this, 'filter_title_size' ) );
+
+ add_action( $this->get_render_hook(), 'lifterlms_template_single_meta_wrapper_start', 5 );
+ add_action( $this->get_render_hook(), 'lifterlms_template_single_meta_wrapper_end', 50 );
+
+ }
+
+ }
+
+ /**
+ * Filters the title of the course information headline per block settings.
+ *
+ * @param string $title default title.
+ * @return string
+ * @since 1.0.0
+ * @version 1.0.0
+ */
+ public function filter_title( $title ) {
+ return $this->title;
+ }
+
+ /**
+ * Filters the title headline element size of the course information headline per block settings.
+ *
+ * @param string $size default size.
+ * @return string
+ * @since 1.0.0
+ * @version 1.0.0
+ */
+ public function filter_title_size( $size ) {
+ return $this->title_size;
+ }
+
+ /**
+ * Register meta attributes stub.
+ *
+ * Called after registering the block type.
+ *
+ * @return void
+ * @since 1.0.0
+ * @version 1.0.0
+ */
+ public function register_meta() {
+
+ register_meta(
+ 'post',
+ '_llms_length',
+ array(
+ 'object_subtype' => 'course',
+ 'sanitize_callback' => 'sanitize_text_field',
+ 'auth_callback' => array( $this, 'meta_auth_callback' ),
+ 'type' => 'string',
+ 'single' => true,
+ 'show_in_rest' => true,
+ )
+ );
+
+ }
+
+ /**
+ * Meta field update authorization callback.
+ *
+ * @param bool $allowed Is the update allowed.
+ * @param string $meta_key Meta keyname.
+ * @param int $object_id WP Object ID (post,comment,etc)...
+ * @param int $user_id WP User ID.
+ * @param string $cap requested capability.
+ * @param array $caps user capabilities.
+ * @return bool
+ * @since 1.0.0
+ * @version 1.0.0
+ */
+ public function meta_auth_callback( $allowed, $meta_key, $object_id, $user_id, $cap, $caps ) {
+ return true;
+ }
+
+}
+
+return new LLMS_Blocks_Course_Information_Block();
diff --git a/libraries/lifterlms-blocks/includes/blocks/class-llms-blocks-course-progress-block.php b/libraries/lifterlms-blocks/includes/blocks/class-llms-blocks-course-progress-block.php
new file mode 100644
index 0000000000..31541749b5
--- /dev/null
+++ b/libraries/lifterlms-blocks/includes/blocks/class-llms-blocks-course-progress-block.php
@@ -0,0 +1,92 @@
+get_render_hook(), array( $this, 'output' ), 10 );
+
+ }
+
+ /**
+ * Output the course progress bar
+ *
+ * @since 1.9.0
+ *
+ * @param array $attributes Optional. Block attributes. Default empty array.
+ * @return void
+ */
+ public function output( $attributes = array() ) {
+
+ $block_content = '';
+ $progress = do_shortcode( '[lifterlms_course_progress check_enrollment=1]' );
+ $class = empty( $attributes['className'] ) ? '' : $attributes['className'];
+
+ if ( $progress ) {
+ $block_content = sprintf(
+ '%4$s
',
+ $this->vendor,
+ $this->id,
+ // Take into account the custom class attribute.
+ empty( $attributes['className'] ) ? '' : ' ' . esc_attr( $attributes['className'] ),
+ $progress
+ );
+ }
+
+ /**
+ * Filters the block html
+ *
+ * @since 1.9.0
+ *
+ * @param string $block_content The block's html.
+ * @param array $attributes The block's array of attributes.
+ * @param LLMS_Blocks_Course_Progress_Block $block This block object.
+ */
+ $block_content = apply_filters( 'llms_blocks_render_course_progress_block', $block_content, $attributes, $this );
+
+ if ( $block_content ) {
+ echo $block_content;
+ }
+
+ }
+}
+
+return new LLMS_Blocks_Course_Progress_Block();
diff --git a/libraries/lifterlms-blocks/includes/blocks/class-llms-blocks-course-syllabus-block.php b/libraries/lifterlms-blocks/includes/blocks/class-llms-blocks-course-syllabus-block.php
new file mode 100644
index 0000000000..1de10bd48c
--- /dev/null
+++ b/libraries/lifterlms-blocks/includes/blocks/class-llms-blocks-course-syllabus-block.php
@@ -0,0 +1,71 @@
+get_render_hook(), 'lifterlms_template_single_syllabus', 10 );
+
+ }
+
+ /**
+ * Retrieve custom block attributes.
+ * Necessary to override when creating ServerSideRender blocks.
+ *
+ * @return array
+ * @since 1.0.0
+ * @version 1.0.0
+ */
+ public function get_attributes() {
+ return array_merge(
+ parent::get_attributes(),
+ array(
+ 'course_id' => array(
+ 'type' => 'int',
+ 'default' => 0,
+ ),
+ )
+ );
+ }
+
+}
+
+return new LLMS_Blocks_Course_Syllabus_Block();
diff --git a/libraries/lifterlms-blocks/includes/blocks/class-llms-blocks-instructors-block.php b/libraries/lifterlms-blocks/includes/blocks/class-llms-blocks-instructors-block.php
new file mode 100644
index 0000000000..04aed6ec84
--- /dev/null
+++ b/libraries/lifterlms-blocks/includes/blocks/class-llms-blocks-instructors-block.php
@@ -0,0 +1,101 @@
+get_render_hook(), $func, 10 );
+
+ }
+
+ /**
+ * Retrieve custom block attributes.
+ *
+ * Necessary to override when creating ServerSideRender blocks.
+ *
+ * @since 1.0.0
+ *
+ * @return array
+ */
+ public function get_attributes() {
+ return array_merge(
+ parent::get_attributes(),
+ array(
+ 'post_id' => array(
+ 'type' => 'int',
+ 'default' => 0,
+ ),
+ )
+ );
+ }
+
+ /**
+ * Output a message when no HTML was rendered
+ *
+ * @since 1.0.0
+ * @since 1.8.0 Fixed spelling error.
+ *
+ * @return string
+ */
+ public function get_empty_render_message() {
+ return __( 'No visible instructors were found.', 'lifterlms' );
+ }
+
+}
+
+return new LLMS_Blocks_Instructors_Block();
diff --git a/libraries/lifterlms-blocks/includes/blocks/class-llms-blocks-lesson-navigation-block.php b/libraries/lifterlms-blocks/includes/blocks/class-llms-blocks-lesson-navigation-block.php
new file mode 100644
index 0000000000..3a8b287593
--- /dev/null
+++ b/libraries/lifterlms-blocks/includes/blocks/class-llms-blocks-lesson-navigation-block.php
@@ -0,0 +1,71 @@
+get_render_hook(), 'lifterlms_template_lesson_navigation', 10 );
+
+ }
+
+ /**
+ * Retrieve custom block attributes.
+ * Necessary to override when creating ServerSideRender blocks.
+ *
+ * @return array
+ * @since 1.0.0
+ * @version 1.0.0
+ */
+ public function get_attributes() {
+ return array_merge(
+ parent::get_attributes(),
+ array(
+ 'post_id' => array(
+ 'type' => 'int',
+ 'default' => 0,
+ ),
+ )
+ );
+ }
+
+}
+
+return new LLMS_Blocks_Lesson_Navigation_Block();
diff --git a/libraries/lifterlms-blocks/includes/blocks/class-llms-blocks-lesson-progression-block.php b/libraries/lifterlms-blocks/includes/blocks/class-llms-blocks-lesson-progression-block.php
new file mode 100644
index 0000000000..de07ea34d6
--- /dev/null
+++ b/libraries/lifterlms-blocks/includes/blocks/class-llms-blocks-lesson-progression-block.php
@@ -0,0 +1,120 @@
+get_render_hook(), 'lifterlms_template_complete_lesson_link', 10 );
+
+ }
+
+ /**
+ * Output a message when no HTML was rendered
+ *
+ * @since 1.7.0
+ * @since 2.0.0 Ensure the queried object is an `LLMS_Lesson` before checking if it's free.
+ *
+ * @return string
+ */
+ public function get_empty_render_message() {
+ $lesson = llms_get_post( get_the_ID() );
+ if ( $lesson && is_a( $lesson, 'LLMS_Lesson' ) && $lesson->is_free() ) {
+ return '';
+ }
+ return parent::get_empty_render_message();
+ }
+
+ /**
+ * Retrieve custom block attributes.
+ *
+ * Necessary to override when creating ServerSideRender blocks.
+ *
+ * @since 1.0.0
+ *
+ * @return array
+ */
+ public function get_attributes() {
+ return array_merge(
+ parent::get_attributes(),
+ array(
+ 'post_id' => array(
+ 'type' => 'int',
+ 'default' => 0,
+ ),
+ )
+ );
+ }
+
+ /**
+ * Register meta attributes.
+ *
+ * Called after registering the block type.
+ *
+ * @since 1.0.0
+ *
+ * @return void
+ */
+ public function register_meta() {
+
+ register_meta(
+ 'post',
+ '_llms_quiz',
+ array(
+ 'object_subtype' => 'lesson',
+ 'sanitize_callback' => 'absint',
+ 'auth_callback' => '__return_true',
+ 'type' => 'string',
+ 'single' => true,
+ 'show_in_rest' => true,
+ )
+ );
+
+ }
+
+}
+
+return new LLMS_Blocks_Lesson_Progression_Block();
diff --git a/libraries/lifterlms-blocks/includes/blocks/class-llms-blocks-php-template-block.php b/libraries/lifterlms-blocks/includes/blocks/class-llms-blocks-php-template-block.php
new file mode 100644
index 0000000000..901c38c3ef
--- /dev/null
+++ b/libraries/lifterlms-blocks/includes/blocks/class-llms-blocks-php-template-block.php
@@ -0,0 +1,144 @@
+ 'loop-main',
+ 'archive-llms_membership' => 'loop-main',
+ 'taxonomy-course_cat' => 'loop-main',
+ 'taxonomy-course_difficulty' => 'loop-main',
+ 'taxonomy-course_tag' => 'loop-main',
+ 'taxonomy-course_track' => 'loop-main',
+ 'taxonomy-membership_cat' => 'loop-main',
+ 'taxonomy-membership_tag' => 'loop-main',
+ 'single-certificate' => 'content-certificate',
+ 'single-no-access' => 'content-no-access',
+ );
+
+ /**
+ * Add actions attached to the render function action.
+ *
+ * @since 2.3.0
+ *
+ * @param array $attributes Optional. Block attributes. Default empty array.
+ * @param string $content Optional. Block content. Default empty string.
+ * @return void
+ */
+ public function add_hooks( $attributes = array(), $content = '' ) {
+
+ add_action( $this->get_render_hook(), array( $this, 'output' ), 10 );
+
+ }
+
+ /**
+ * Retrieve custom block attributes.
+ *
+ * Necessary to override when creating ServerSideRender blocks.
+ *
+ * @since 2.3.0
+ *
+ * @return array
+ */
+ public function get_attributes() {
+ return array(
+ 'template' => array(
+ 'type' => 'string',
+ 'default' => '',
+ ),
+ 'title' => array(
+ 'type' => 'string',
+ 'default' => '',
+ ),
+ );
+ }
+
+ /**
+ * Output the template.
+ *
+ * @since 2.3.0
+ *
+ * @param array $attributes Optional. Block attributes. Default empty array.
+ * @return void
+ */
+ public function output( $attributes = array() ) {
+
+ if ( empty( $attributes['template'] ) ) {
+ return;
+ }
+
+ /**
+ * Filters the php templates that can be render via this block.
+ *
+ * @since 2.3.0
+ *
+ * @param array $templates Templates map, where the keys are the template attribute value and the values are the php file names (w/o extension).
+ */
+ $templates = apply_filters( 'llms_blocks_php_templates_block', $this->templates );
+
+ if ( ! array_key_exists( $attributes['template'], $templates ) ) {
+ return;
+ }
+
+ ob_start();
+
+ llms_get_template( "{$templates[$attributes['template']]}.php" );
+
+ $block_content = ob_get_clean();
+
+ /**
+ * Filters the block html.
+ *
+ * @since 2.3.0
+ *
+ * @param string $block_content The block's html.
+ * @param array $attributes The block's array of attributes.
+ * @param array $template The template file basename to be rendered.
+ * @param LLMS_Blocks_PHP_Template_Block $block This block object.
+ */
+ $block_content = apply_filters( 'llms_blocks_render_php_template_block', $block_content, $attributes, $templates[ $attributes['template'] ], $this );
+
+ if ( $block_content ) {
+ echo $block_content;
+ }
+
+ }
+
+}
+
+return new LLMS_Blocks_PHP_Template_Block();
diff --git a/libraries/lifterlms-blocks/includes/blocks/class-llms-blocks-pricing-table-block.php b/libraries/lifterlms-blocks/includes/blocks/class-llms-blocks-pricing-table-block.php
new file mode 100644
index 0000000000..a47d848c10
--- /dev/null
+++ b/libraries/lifterlms-blocks/includes/blocks/class-llms-blocks-pricing-table-block.php
@@ -0,0 +1,129 @@
+get_render_hook(), array( $this, 'output' ), 10 );
+
+ }
+
+ /**
+ * Retrieve custom block attributes
+ *
+ * Necessary to override when creating ServerSideRender blocks.
+ *
+ * @since 1.0.0
+ * @since 1.3.6 Unknown.
+ *
+ * @return array
+ */
+ public function get_attributes() {
+ return array_merge(
+ parent::get_attributes(),
+ array(
+ 'post_id' => array(
+ 'type' => 'int',
+ 'default' => 0,
+ ),
+ )
+ );
+ }
+
+ /**
+ * Output the pricing table.
+ *
+ * @since 1.0.0
+ * @since 1.3.7 Unknown.
+ * @since 1.9.0 Added `llms_blocks_render_pricing_table_block` filter.
+ *
+ * @param array $attributes Optional. Block attributes. Default empty array.
+ * @return void
+ */
+ public function output( $attributes = array() ) {
+
+ ob_start();
+ if ( 'edit' === filter_input( INPUT_GET, 'context' ) ) {
+ $id = filter_input( INPUT_GET, 'post_id', FILTER_SANITIZE_NUMBER_INT );
+ if ( $id ) {
+ $product = new LLMS_Product( $id );
+ if ( ! $product->get_access_plans() ) {
+ echo '' . __( 'No access plans found.', 'lifterlms' ) . '
';
+ }
+ }
+
+ // force display of the table on the admin panel.
+ add_filter( 'llms_product_pricing_table_enrollment_status', '__return_false' );
+ add_filter( 'llms_product_is_purchasable', '__return_true' );
+
+ }
+
+ lifterlms_template_pricing_table( $attributes['post_id'] );
+
+ $block_content = ob_get_clean();
+
+ /**
+ * Filters the block html
+ *
+ * @since 1.9.0
+ *
+ * @param string $block_content The block's html.
+ * @param array $attributes The block's array of attributes.
+ * @param LLMS_Blocks_Pricing_Table_Block $block This block object.
+ */
+ $block_content = apply_filters( 'llms_blocks_render_pricing_table_block', $block_content, $attributes, $this );
+
+ remove_filter( 'llms_product_pricing_table_enrollment_status', '__return_false' );
+ remove_filter( 'llms_product_is_purchasable', '__return_true' );
+
+ if ( $block_content ) {
+ echo $block_content;
+ }
+
+ }
+}
+
+return new LLMS_Blocks_Pricing_Table_Block();
diff --git a/libraries/lifterlms-blocks/includes/blocks/index.php b/libraries/lifterlms-blocks/includes/blocks/index.php
new file mode 100644
index 0000000000..82e2315c6b
--- /dev/null
+++ b/libraries/lifterlms-blocks/includes/blocks/index.php
@@ -0,0 +1,2 @@
+is_dynamic ) {
+
+ register_block_type(
+ $this->get_block_id(),
+ array(
+ 'attributes' => $this->get_attributes(),
+ 'render_callback' => array( $this, 'render_callback' ),
+ )
+ );
+
+ }
+
+ $this->register_meta();
+
+ }
+
+ /**
+ * Add hooks stub.
+ * Extending classes can use this class to add hooks attached to the render function action.
+ *
+ * @param array $attributes Optional. Block attributes. Default empty array.
+ * @param string $content Optional. Block content. Default empty string.
+ * @return void
+ * @since 1.0.0
+ * @version 1.0.0
+ */
+ public function add_hooks( $attributes = array(), $content = '' ) {}
+
+ /**
+ * Retrieve custom block attributes.
+ * Necessary to override when creating ServerSideRender blocks.
+ *
+ * @return array
+ * @since 1.0.0
+ * @version 1.0.0
+ */
+ public function get_attributes() {
+ return LLMS_Blocks_Visibility::get_attributes();
+ }
+
+ /**
+ * Retrieve the ID/Name of the block.
+ *
+ * @return string
+ * @since 1.0.0
+ * @version 1.0.0
+ */
+ public function get_block_id() {
+ return sprintf( '%1$s/%2$s', $this->vendor, $this->id );
+ }
+
+ /**
+ * Output a message when no HTML was rendered
+ *
+ * @since 1.0.0
+ * @since 1.8.0 Don't output empty render messages on the frontend.
+ *
+ * @return string
+ */
+ public function get_empty_render_message() {
+ if ( ! is_admin() ) {
+ return '';
+ }
+ return __( 'No HTML was returned.', 'lifterlms' );
+ }
+
+ /**
+ * Retrieve a string which can be used to render the block.
+ *
+ * @return string
+ * @since 1.0.0
+ * @version 1.0.0
+ */
+ public function get_render_hook() {
+ return sprintf( '%1$s_%2$s_block_render', $this->vendor, $this->id );
+ }
+
+ /**
+ * Removed hooks stub.
+ * Extending classes can use this class to remove hooks attached to the render function action.
+ *
+ * @return void
+ * @since 1.0.0
+ * @version 1.0.0
+ */
+ public function remove_hooks() {}
+
+ /**
+ * Renders the block type output for given attributes.
+ *
+ * @param array $attributes Optional. Block attributes. Default empty array.
+ * @param string $content Optional. Block content. Default empty string.
+ * @return string
+ * @since 1.0.0
+ * @version 1.0.0
+ */
+ public function render_callback( $attributes = array(), $content = '' ) {
+
+ $this->add_hooks( $attributes, $content );
+
+ ob_start();
+ do_action( $this->get_render_hook(), $attributes, $content );
+ $ret = ob_get_clean();
+
+ $this->remove_hooks();
+
+ if ( ! $ret ) {
+ $ret = $this->get_empty_render_message();
+ }
+
+ return $ret;
+
+ }
+
+ /**
+ * Register meta attributes stub.
+ *
+ * Called after registering the block type.
+ *
+ * @return void
+ * @since 1.0.0
+ * @version 1.0.0
+ */
+ public function register_meta() {}
+
+}
diff --git a/libraries/lifterlms-blocks/includes/class-llms-blocks-assets.php b/libraries/lifterlms-blocks/includes/class-llms-blocks-assets.php
new file mode 100644
index 0000000000..943da0bd5c
--- /dev/null
+++ b/libraries/lifterlms-blocks/includes/class-llms-blocks-assets.php
@@ -0,0 +1,213 @@
+assets = new LLMS_Assets(
+ 'llms-blocks',
+ array(
+ // Base defaults shared by all asset types.
+ 'base' => array(
+ 'base_file' => LLMS_BLOCKS_PLUGIN_FILE,
+ 'base_url' => LLMS_BLOCKS_PLUGIN_DIR_URL,
+ 'version' => LLMS_BLOCKS_VERSION,
+ 'suffix' => '', // Only minified files are distributed.
+ ),
+ // Script specific defaults.
+ 'script' => array(
+ 'translate' => true, // All scripts in the blocks plugin are translated.
+ ),
+ )
+ );
+
+ // Define plugin assets.
+ $this->define();
+ $this->define_bc();
+
+ // Enqueue editor assets.
+ add_action( 'enqueue_block_editor_assets', array( $this, 'editor_assets' ), 5 );
+
+ }
+
+ /**
+ * Define block plugin assets.
+ *
+ * @since 1.10.0
+ *
+ * @return void
+ */
+ private function define() {
+
+ $asset = include LLMS_BLOCKS_PLUGIN_DIR . '/assets/js/llms-blocks.asset.php';
+
+ $this->assets->define(
+ 'scripts',
+ array(
+ 'llms-blocks-editor' => array(
+ 'dependencies' => $asset['dependencies'],
+ 'file_name' => 'llms-blocks',
+ 'version' => $asset['version'],
+ ),
+ )
+ );
+
+ $this->assets->define(
+ 'styles',
+ array(
+ 'llms-blocks-editor' => array(
+ 'dependencies' => array( 'wp-edit-blocks' ),
+ 'file_name' => 'llms-blocks',
+ 'version' => $asset['version'],
+ ),
+ )
+ );
+
+ }
+
+ /**
+ * Define backwards compatibility assets
+ *
+ * @since 2.0.0
+ *
+ * @return void
+ */
+ protected function define_bc() {
+
+ if ( ! $this->use_bc_assets() ) {
+ return;
+ }
+
+ $asset = include LLMS_BLOCKS_PLUGIN_DIR . '/assets/js/llms-blocks-backwards-compat.asset.php';
+
+ $this->assets->define(
+ 'scripts',
+ array(
+ 'llms-blocks-editor-bc' => array(
+ 'dependencies' => $asset['dependencies'],
+ 'file_name' => 'llms-blocks-backwards-compat',
+ 'version' => $asset['version'],
+ ),
+ )
+ );
+
+ }
+
+ /**
+ * Enqueue block editor assets.
+ *
+ * @since 1.0.0
+ * @since 1.4.1 Fix double slash in asset path.
+ * @since 1.8.0 Update asset paths and improve script dependencies.
+ * @since 1.10.0 Use `LLMS_Assets` class methods for asset enqueues.
+ * @since 2.0.0 Maybe load backwards compatibility script.
+ * @since 2.2.0 Only load assets on post screens.
+ * @since 2.3.0 Also load assets on site editor screen.
+ * @since 2.4.3 Added script localization.
+ *
+ * @return void
+ */
+ public function editor_assets() {
+
+ $screen = get_current_screen();
+ if ( $screen && ! in_array( $screen->base, array( 'post', 'site-editor' ), true ) ) {
+ return;
+ }
+
+ if ( $this->use_bc_assets() ) {
+ $this->assets->enqueue_script( 'llms-blocks-editor-bc' );
+ }
+
+ $this->assets->enqueue_script( 'llms-blocks-editor' );
+ $this->assets->enqueue_style( 'llms-blocks-editor' );
+
+ wp_localize_script(
+ 'llms-blocks-editor',
+ 'llmsBlocks',
+ array(
+ 'variationIconCanBeObject' => self::can_variation_transform_icon_be_an_object(),
+ )
+ );
+
+ }
+
+ /**
+ * Determines if WP Core backwards compatibility scripts should defined & be loaded.
+ *
+ * @since 2.0.0
+ *
+ * @return boolean
+ */
+ private function use_bc_assets() {
+ return ( ! LLMS_Forms::instance()->are_requirements_met() &&
+ /**
+ * Filter allowing opt-out of block editor backwards compatibility scripts.
+ *
+ * @since 2.0.0
+ *
+ * @example
+ * ```
+ * // Disable backwards compatibility scripts.
+ * add_filter( 'llms_blocks_load_bc_scripts', '__return_false' );
+ * ```
+ *
+ * @param boolean $load_scripts Whether or not to load the scripts.
+ */
+ apply_filters( 'llms_blocks_load_bc_scripts', true )
+ );
+ }
+
+ /**
+ * Can a variation transform icon be an object.
+ *
+ * @since 2.4.3
+ *
+ * @link https://github.com/gocodebox/lifterlms-blocks/issues/170
+ *
+ * @return boolean
+ */
+ private static function can_variation_transform_icon_be_an_object() {
+ global $wp_version;
+ return version_compare( $wp_version, '6.0-src', '<' ) && ! defined( 'GUTENBERG_VERSION' )
+ || ( defined( 'GUTENBERG_VERSION' ) && version_compare( GUTENBERG_VERSION, '13.0', '<' ) );
+ }
+
+}
+
+return new LLMS_Blocks_Assets();
diff --git a/libraries/lifterlms-blocks/includes/class-llms-blocks-migrate.php b/libraries/lifterlms-blocks/includes/class-llms-blocks-migrate.php
new file mode 100644
index 0000000000..db8f5ccac9
--- /dev/null
+++ b/libraries/lifterlms-blocks/includes/class-llms-blocks-migrate.php
@@ -0,0 +1,433 @@
+update_post_content( $post->ID, $post->post_content . "\r\r" . $this->get_template( $post->post_type ) ) ) {
+ // Save migration state.
+ $this->update_migration_status( $post->ID );
+ return true;
+ }
+
+ return false;
+
+ }
+
+ /**
+ * Don't remove core template actions when a sales page is enabled and the page is restricted.
+
+ * @since 1.2.0
+ * @since 1.3.1 Unknown.
+ *
+ * @param bool $ret Default migration status.
+ * @param int $post_id WP_Post ID.
+ * @return bool
+ */
+ public static function check_sales_page( $ret, $post_id ) {
+
+ $page_restricted = llms_page_restricted( $post_id );
+ if ( $page_restricted['is_restricted'] ) {
+ $sales_page = get_post_meta( $post_id, '_llms_sales_page_content_type', true );
+ if ( '' === $sales_page || 'content' === $sales_page ) {
+ $ret = false;
+ }
+ }
+
+ return $ret;
+
+ }
+
+ /**
+ * Get an array of post types which can be migrated.
+ *
+ * @since 1.3.3
+ * @since 1.7.0 Memberships are migrateable.
+ *
+ * @return array
+ */
+ public function get_migrateable_post_types() {
+ /**
+ * Filters the post types that can be migrated
+ *
+ * @since 1.3.3
+ *
+ * @param string[] $post_types An array of string representing the post types that can be migrated.
+ */
+ return apply_filters( 'llms_blocks_migrateable_post_types', array( 'course', 'lesson', 'llms_membership' ) );
+ }
+
+ /**
+ * Retrieve a WP_Query for posts which have already been migrated.
+ *
+ * @since 1.4.0
+ *
+ * @param array $args Optional query arguments to pass to WP_Query.
+ * @return WP_Query
+ */
+ public function get_migrated_posts( $args = array() ) {
+
+ return new WP_Query(
+ wp_parse_args(
+ $args,
+ array(
+ 'post_type' => $this->get_migrateable_post_types(),
+ 'meta_query' => array(
+ array(
+ 'key' => '_llms_blocks_migrated',
+ 'value' => 'yes',
+ ),
+ ),
+ )
+ )
+ );
+
+ }
+
+ /**
+ * Retrieve the block template by post type.
+ *
+ * @since 1.0.0
+ * @since 1.7.0 Add membership template.
+ * @since 1.8.0 Updated course progress shortcode and added the `$merge_deprecated_versions` param.
+ * @since 1.9.1 Fix course progress block.
+ *
+ * @param string $post_type WP post type.
+ * @param boolean $merge_deprecated_versions Optional. Whether or not getting the deprecated blocks merged, useful when removing templates. Default `false`.
+ * @return string
+ */
+ private function get_template( $post_type, $merge_deprecated_versions = false ) {
+
+ if ( 'course' === $post_type ) {
+ ob_start();
+
+ ?>
+
+
+
+
+
+
+
+
+
+[lifterlms_course_progress check_enrollment=1]
+[lifterlms_course_progress]
+
+
+
+
+[lifterlms_course_continue_button]
+
+
+
+
+
+
+
+
+
+ should_migrate_post( $post->ID ) ) {
+ return;
+ }
+
+ if ( 'llms_membership' === $post->post_type ) {
+ if ( has_block( 'llms/pricing-table', $post->post_content ) ) {
+ $this->update_migration_status( $post->ID );
+ return;
+ }
+ } elseif ( has_blocks( $post->post_content ) ) {
+ $this->update_migration_status( $post->ID );
+ return;
+ }
+
+ $this->add_template_to_post( $post );
+
+ // Reload.
+ wp_safe_redirect(
+ add_query_arg(
+ array(
+ 'post' => $post->ID,
+ 'action' => 'edit',
+ ),
+ admin_url( 'post.php' )
+ )
+ );
+ exit;
+
+ }
+
+ /**
+ * Remove post type templates and any LifterLMS Blocks from a given post.
+ *
+ * @since 1.4.0
+ * @since 1.8.0 Get all post type's template with deprecated blocks versions merged.
+ *
+ * @param WP_Post $post Post object.
+ * @return bool
+ */
+ private function remove_template_from_post( $post ) {
+
+ $template = $this->get_template( $post->post_type, true );
+ if ( ! $template ) {
+ return;
+ }
+
+ // explicitly remove template pieces.
+ $parts = array_filter( array_map( 'trim', explode( "\n", $template ) ) );
+ $content = str_replace( $parts, '', $post->post_content );
+
+ // replace any remaining LLMS blocks not found in the template (also grabs any openers that have block settings JSON).
+ $content = trim( preg_replace( '##', '', $content ) );
+
+ if ( $this->update_post_content( $post->ID, $content ) ) {
+ $this->update_migration_status( $post->ID, 'no' );
+ return true;
+ }
+
+ return false;
+
+ }
+
+ /**
+ * Removes core template action hooks from posts which have been migrated to the block editor
+ *
+ * @since 1.3.2 Unknown.
+ *
+ * @return void
+ * @since 1.1.0
+ */
+ public function remove_template_hooks() {
+
+ if ( ! llms_blocks_is_post_migrated( get_the_ID() ) ) {
+ return;
+ }
+
+ // Course Information.
+ remove_action( 'lifterlms_single_course_after_summary', 'lifterlms_template_single_meta_wrapper_start', 5 );
+ remove_action( 'lifterlms_single_course_after_summary', 'lifterlms_template_single_length', 10 );
+ remove_action( 'lifterlms_single_course_after_summary', 'lifterlms_template_single_difficulty', 20 );
+ remove_action( 'lifterlms_single_course_after_summary', 'lifterlms_template_single_course_tracks', 25 );
+ remove_action( 'lifterlms_single_course_after_summary', 'lifterlms_template_single_course_categories', 30 );
+ remove_action( 'lifterlms_single_course_after_summary', 'lifterlms_template_single_course_tags', 35 );
+ remove_action( 'lifterlms_single_course_after_summary', 'lifterlms_template_single_meta_wrapper_end', 50 );
+
+ // Remove Course Progress.
+ remove_action( 'lifterlms_single_course_after_summary', 'lifterlms_template_single_course_progress', 60 );
+
+ // Course Syllabus.
+ remove_action( 'lifterlms_single_course_after_summary', 'lifterlms_template_single_syllabus', 90 );
+
+ // Instructors.
+ remove_action( 'lifterlms_single_course_after_summary', 'lifterlms_template_course_author', 40 );
+
+ // Lesson Navigation.
+ remove_action( 'lifterlms_single_lesson_after_summary', 'lifterlms_template_lesson_navigation', 20 );
+
+ // Lesson Progression.
+ remove_action( 'lifterlms_single_lesson_after_summary', 'lifterlms_template_complete_lesson_link', 10 );
+
+ // Pricing Table.
+ remove_action( 'lifterlms_single_course_after_summary', 'lifterlms_template_pricing_table', 60 );
+ remove_action( 'lifterlms_single_membership_after_summary', 'lifterlms_template_pricing_table', 10 );
+
+ }
+
+ /**
+ * Determine if a post should be migrated.
+ *
+ * @since 1.3.3
+ *
+ * @param int $post_id WP_Post ID.
+ * @return bool
+ */
+ public function should_migrate_post( $post_id ) {
+
+ $ret = true;
+
+ // Not a valid post type.
+ if ( ! in_array( get_post_type( $post_id ), $this->get_migrateable_post_types(), true ) ) {
+
+ $ret = false;
+
+ // Classic is enabled, don't migrate.
+ } elseif ( llms_blocks_is_classic_enabled_for_post( $post_id ) ) {
+
+ $ret = false;
+
+ // Already Migrated.
+ } elseif ( llms_parse_bool( get_post_meta( $post_id, '_llms_blocks_migrated', true ) ) ) {
+
+ $ret = false;
+
+ }
+
+ /**
+ * Filters whether or not a post should be migrated
+ *
+ * @since 1.3.3
+ *
+ * @param bool $migrate Whether or not a post should be migrated.
+ * @param int $post_id WP_Post ID.
+ */
+ return apply_filters( 'llms_blocks_should_migrate_post', $ret, $post_id );
+
+ }
+
+ /**
+ * Unmigrates migrated posts.
+ *
+ * @since 1.4.0
+ *
+ * @return void.
+ */
+ public function unmigrate_posts() {
+
+ $posts = $this->get_migrated_posts( array( 'posts_per_page' => 250 ) ); // phpcs:ignore WordPress.WP.PostsPerPage.posts_per_page_posts_per_page
+
+ if ( $posts->posts ) {
+ foreach ( $posts->posts as $post ) {
+ $this->remove_template_from_post( $post );
+ }
+ }
+
+ }
+
+ /**
+ * Update post meta data to signal status of the editor migration.
+ *
+ * @since 1.1.0
+ *
+ * @param int $post_id WP_Post ID.
+ * @param string $status Yes or no.
+ * @return void
+ */
+ private function update_migration_status( $post_id, $status = 'yes' ) {
+ update_post_meta( $post_id, '_llms_blocks_migrated', $status );
+ }
+
+ /**
+ * Update the post content for a given post.
+ *
+ * @since 1.4.0
+ *
+ * @param int $id WP_Post ID.
+ * @param string $content Post content to update.
+ * @return bool
+ */
+ private function update_post_content( $id, $content ) {
+
+ global $wpdb;
+ $update = $wpdb->update(
+ $wpdb->posts,
+ array(
+ 'post_content' => $content,
+ ),
+ array(
+ 'ID' => $id,
+ ),
+ array( '%s' ),
+ array( '%d' )
+ ); // db no-cache okay.
+
+ return false === $update ? false : true;
+
+ }
+
+}
+
+global $llms_blocks_migrate;
+$llms_blocks_migrate = new LLMS_Blocks_Migrate();
+return $llms_blocks_migrate;
diff --git a/libraries/lifterlms-blocks/includes/class-llms-blocks-page-builders.php b/libraries/lifterlms-blocks/includes/class-llms-blocks-page-builders.php
new file mode 100644
index 0000000000..a635193b2c
--- /dev/null
+++ b/libraries/lifterlms-blocks/includes/class-llms-blocks-page-builders.php
@@ -0,0 +1,133 @@
+instructors()->get_instructors( false );
+ foreach ( $ret as &$instructor ) {
+ $name = '';
+ $student = llms_get_student( $instructor['id'] );
+ if ( $student ) {
+ $name = $student->get_name();
+ }
+ $instructor['name'] = $name;
+ }
+ }
+ return $ret;
+
+ }
+
+ /**
+ * Automatically sets instructor data when a new course/membership is created.
+ *
+ * @since 1.6.0
+ *
+ * @link https://developer.wordpress.org/reference/hooks/save_post_post-post_type/
+ *
+ * @param int $post_id WP_Post ID.
+ * @param WP_Post $post Post object.
+ * @param bool $update Whether the save is an update (`true`) or a creation (`false`).
+ * @return bool
+ */
+ public function maybe_set_default_instructor( $post_id, $post, $update ) {
+
+ if ( $update || ! $post->post_author ) {
+ return false;
+ }
+
+ $obj = llms_get_post( $post );
+ if ( ! $obj || ! is_a( $obj, 'LLMS_Post_Model' ) || ! in_array( $obj->get( 'type' ), $this->post_types, true ) ) {
+ return false;
+ }
+
+ remove_action( 'save_post_course', array( $this, 'maybe_set_instructors' ), 50, 3 );
+ $obj->instructors()->set_instructors( array( array( 'id' => $post->post_author ) ) );
+
+ return true;
+
+ }
+
+ /**
+ * Update instructor information for a given object.
+ *
+ * @since 1.0.0
+ * @since 1.7.1 Decode JSON prior to saving.
+ * @since 2.4.0 Fix access to non-existing variable when current user canno edit the course/membership.
+ *
+ * @param string $value Instructor data to add to the object (JSON).
+ * @param WP_Post $object WP_Post object.
+ * @param string $key Name of the field.
+ * @return null|WP_Error
+ */
+ public function update_callback( $value, $object, $key ) {
+
+ if ( ! current_user_can( 'edit_post', $object->ID ) ) {
+ return new WP_Error(
+ 'rest_cannot_update',
+ __( 'Sorry, you are not allowed to edit the object instructors.', 'lifterlms' ),
+ array(
+ 'key' => $key,
+ 'status' => rest_authorization_required_code(),
+ )
+ );
+ }
+
+ $value = json_decode( $value, true );
+
+ $obj = llms_get_post( $object );
+ if ( $obj ) {
+ $obj->instructors()->set_instructors( $value );
+ }
+
+ return null;
+ }
+
+ /**
+ * Register custom meta fields.
+ *
+ * @since 1.0.0
+ * @since 1.6.0 Use `$this->post_types` for loop.
+ * @since 1.7.1 Don't define a schema.
+ *
+ * @return void
+ */
+ public function register_meta() {
+
+ foreach ( $this->post_types as $post_type ) {
+
+ register_rest_field(
+ $post_type,
+ 'instructors',
+ array(
+ 'get_callback' => array( $this, 'get_callback' ),
+ 'update_callback' => array( $this, 'update_callback' ),
+ 'schema' => null,
+ )
+ );
+
+ }
+
+ }
+
+}
+
+return new LLMS_Blocks_Post_Instructors();
diff --git a/libraries/lifterlms-blocks/includes/class-llms-blocks-post-types.php b/libraries/lifterlms-blocks/includes/class-llms-blocks-post-types.php
new file mode 100644
index 0000000000..4fe0c64dab
--- /dev/null
+++ b/libraries/lifterlms-blocks/includes/class-llms-blocks-post-types.php
@@ -0,0 +1,155 @@
+ __( 'Add a short description of your course visible to all visitors...', 'lifterlms' ),
+ ),
+ ),
+ array( 'llms/course-information' ),
+ array( 'llms/instructors' ),
+ array( 'llms/pricing-table' ),
+ array( 'llms/course-progress' ),
+ array( 'llms/course-continue-button' ),
+ array( 'llms/course-syllabus' ),
+ );
+
+ return $post_type;
+
+ }
+
+ /**
+ * Add an editor template for memberships.
+ *
+ * @since 1.7.0
+ * @since 1.11.0 Add instructors block.
+ *
+ * @param array $post_type Post type registration data.
+ * @return array
+ */
+ public function add_membership_template( $post_type ) {
+
+ $post_type['template'] = array(
+ array(
+ 'core/paragraph',
+ array(
+ 'placeholder' => __( 'Add a short description of your membership visible to all visitors...', 'lifterlms' ),
+ ),
+ ),
+ array( 'llms/instructors' ),
+ array( 'llms/pricing-table' ),
+ );
+
+ return $post_type;
+
+ }
+
+ /**
+ * Add an editor template for lessons
+ *
+ * @param array $post_type post type data.
+ * @return array
+ * @since 1.0.0
+ * @version 1.0.0
+ */
+ public function add_lesson_template( $post_type ) {
+
+ $post_type['template'] = array(
+ array(
+ 'core/paragraph',
+ array(
+ 'placeholder' => __( 'Add your lesson content...', 'lifterlms' ),
+ ),
+ ),
+ array( 'llms/lesson-progression' ),
+ array( 'llms/lesson-navigation' ),
+ );
+
+ return $post_type;
+
+ }
+
+}
+
+return new LLMS_Blocks_Post_Types();
diff --git a/libraries/lifterlms-blocks/includes/class-llms-blocks-post-visibility.php b/libraries/lifterlms-blocks/includes/class-llms-blocks-post-visibility.php
new file mode 100644
index 0000000000..cd3b3efe05
--- /dev/null
+++ b/libraries/lifterlms-blocks/includes/class-llms-blocks-post-visibility.php
@@ -0,0 +1,134 @@
+get_catalog_visibility();
+ }
+ return $ret;
+
+ }
+
+ /**
+ * Update visibility information for a given object.
+ *
+ * @param string $value new visibility status value.
+ * @param WP_Post $object WP_Post object.
+ * @param string $key name of the field.
+ * @return null|WP_Error
+ * @since 1.3.0
+ * @version 1.3.0
+ */
+ public function update_callback( $value, $object, $key ) {
+
+ if ( ! current_user_can( 'edit_post', $object->ID ) ) {
+ return new WP_Error(
+ 'rest_cannot_update',
+ __( 'Sorry, you are not allowed to edit the object visibility.', 'lifterlms' ),
+ array(
+ 'key' => $name,
+ 'status' => rest_authorization_required_code(),
+ )
+ );
+ }
+
+ $obj = new LLMS_Product( $object->ID );
+ if ( $obj ) {
+ $obj->set_catalog_visibility( $value );
+ }
+
+ return null;
+ }
+
+ /**
+ * Register custom meta fields.
+ *
+ * @return void
+ * @since 1.3.0
+ * @version 1.3.0
+ */
+ public function register_meta() {
+
+ foreach ( array( 'course', 'llms_membership' ) as $post_type ) {
+
+ register_rest_field(
+ $post_type,
+ 'visibility',
+ array(
+ 'get_callback' => array( $this, 'get_callback' ),
+ 'update_callback' => array( $this, 'update_callback' ),
+ 'schema' => array(
+ 'description' => __( 'Post visibility.', 'lifterlms' ),
+ 'type' => 'object',
+ 'context' => array( 'view', 'edit' ),
+ 'properties' => array(),
+ 'arg_options' => array(
+ 'sanitize_callback' => null,
+ 'validate_callback' => null,
+ ),
+ ),
+ )
+ );
+
+ }
+
+ }
+
+}
+
+return new LLMS_Blocks_Post_Visibility();
diff --git a/libraries/lifterlms-blocks/includes/class-llms-blocks-reusable.php b/libraries/lifterlms-blocks/includes/class-llms-blocks-reusable.php
new file mode 100644
index 0000000000..3a39b7e6e6
--- /dev/null
+++ b/libraries/lifterlms-blocks/includes/class-llms-blocks-reusable.php
@@ -0,0 +1,200 @@
+ID, '_is_llms_field', $value ) ? true : false;
+ }
+
+ /**
+ * Register custom rest fields
+ *
+ * @since 2.0.0
+ *
+ * @return void
+ */
+ public function rest_register_fields() {
+
+ register_rest_field(
+ 'wp_block',
+ 'is_llms_field',
+ array(
+ 'get_callback' => array( $this, 'rest_callback_get' ),
+ 'update_callback' => array( $this, 'rest_callback_update' ),
+ )
+ );
+
+ }
+
+ /**
+ * Modify the rest request query used to list reusable blocks within the block editor
+ *
+ * Ensures that reusable blocks containing LifterLMS Form Fields can only be inserted/viewed
+ * in the context that we allow them to be used within.
+ *
+ * + When viewing a `wp_block` post, all reusable blocks should be displayed.
+ * + When viewing an `llms_form` post, only blocks that specify `is_llms_field` as 'yes' can be displayed.
+ * + When viewing any other post, any post with `is_llms_field` of 'yes' is excluded.
+ *
+ * @since 2.0.0
+ *
+ * @see [Reference]
+ * @link [URL]
+ *
+ * @param arrays $args WP_Query arguments.
+ * @param WP_REST_Request $request Request object.
+ * @return array
+ */
+ public function mod_wp_block_query( $args, $request ) {
+
+ $referer = $request->get_header( 'referer' );
+ $screen = empty( $referer ) ? false : $this->get_screen_from_referer( $referer );
+
+ // We don't care about the screen or it's a reusable block screen.
+ if ( empty( $screen ) || 'wp_block' === $screen ) {
+ return $args;
+ }
+
+ // Add a meta query if it doesn't already exist.
+ if ( empty( $args['meta_query'] ) ) {
+ $args['meta_query'] = array(
+ 'relation' => 'AND',
+ );
+ }
+
+ // Forms should show only blocks with forms and everything else should exclude blocks with forms.
+ $include_fields = 'llms_form' === $screen;
+ $args['meta_query'][] = $this->get_meta_query( $include_fields );
+
+ return $args;
+
+ }
+
+ /**
+ * Retrieve a meta query array depending on the post type of the referring rest request
+ *
+ * @since 2.0.0
+ *
+ * @param boolean $include_fields Whether or not to include form fields.
+ * @return array
+ */
+ private function get_meta_query( $include_fields ) {
+
+ // Default query when including fields.
+ $meta_query = array(
+ 'key' => '_is_llms_field',
+ 'value' => 'yes',
+ );
+
+ // Excluding fields.
+ if ( ! $include_fields ) {
+
+ $meta_query = array(
+ 'relation' => 'OR',
+ wp_parse_args(
+ array(
+ 'compare' => '!=',
+ ),
+ $meta_query
+ ),
+ array(
+ 'key' => '_is_llms_field',
+ 'compare' => 'NOT EXISTS',
+ ),
+ );
+ }
+
+ return $meta_query;
+
+ }
+
+ /**
+ * Determine the screen where a reusable blocks rest query originated
+ *
+ * The screen name will either be "widgets" or the WP_Post name of a registered WP_Post type.
+ *
+ * For any other screen we return `false` because we don't care about it.
+ *
+ * @since 2.0.0
+ * @since 2.3.1 Don't pass `null` to `basename()`.
+ *
+ * @param string $referer Referring URL for the REST request.
+ * @return string|boolean Returns the screen name or `false` if we don't care about the screen.
+ */
+ private function get_screen_from_referer( $referer ) {
+
+ // Blockified widgets screen.
+ $url_path = wp_parse_url( $referer, PHP_URL_PATH );
+ if ( $url_path && 'widgets.php' === basename( $url_path ) ) {
+ return 'widgets';
+ }
+
+ $query_args = array();
+ wp_parse_str( wp_parse_url( $referer, PHP_URL_QUERY ), $query_args );
+
+ // Something else.
+ if ( empty( $query_args['post'] ) ) {
+ return false;
+ }
+
+ // Block editor for a WP_Post.
+ return get_post_type( $query_args['post'] );
+
+ }
+
+}
+
+return new LLMS_Blocks_Reusable();
diff --git a/libraries/lifterlms-blocks/includes/class-llms-blocks-status-tools.php b/libraries/lifterlms-blocks/includes/class-llms-blocks-status-tools.php
new file mode 100644
index 0000000000..66c486f1cb
--- /dev/null
+++ b/libraries/lifterlms-blocks/includes/class-llms-blocks-status-tools.php
@@ -0,0 +1,88 @@
+get_migrated_posts();
+
+ if ( $posts->found_posts ) {
+
+ $desc = __( 'Removes block editor code from all courses and lessons which were migrated to the block editor during an upgrade to WordPress 5.0 or later. If you installed the Classic Editor plugin after upgrading and see duplicate content items (such as the course syllabus or lesson mark complete button) this tool will remove the duplicates.', 'lifterlms' );
+ $desc .= ' ';
+ // Translators: %d = number of affected courses/lessons.
+ $desc .= sprintf( __( 'Currently %d courses and/or lessons are affected.', 'lifterlms' ), $posts->found_posts );
+
+ $tools['blocks-unmigrate'] = array(
+ 'description' => $desc,
+ 'label' => __( 'Remove LifterLMS Block Code', 'lifterlms' ),
+ 'text' => __( 'Remove Block Code', 'lifterlms' ),
+ );
+
+ }
+
+ return $tools;
+
+ }
+
+ /**
+ * Run tool actions on tool page form submission.
+ *
+ * @since 1.4.0
+ *
+ * @param string $tool ID of the tool being run.
+ * @return void
+ */
+ public function maybe_toggle_mode( $tool ) {
+
+ if ( 'blocks-unmigrate' !== $tool ) {
+ return;
+ }
+
+ do_action( 'llms_blocks_unmigrate_posts' );
+
+ }
+
+}
+
+return new LLMS_Blocks_Status_Tools();
diff --git a/libraries/lifterlms-blocks/includes/class-llms-blocks-visibility.php b/libraries/lifterlms-blocks/includes/class-llms-blocks-visibility.php
new file mode 100644
index 0000000000..6ccf56222c
--- /dev/null
+++ b/libraries/lifterlms-blocks/includes/class-llms-blocks-visibility.php
@@ -0,0 +1,266 @@
+ array(
+ 'default' => 'all',
+ 'type' => 'string',
+ ),
+ 'llms_visibility_in' => array(
+ 'default' => '',
+ 'type' => 'string',
+ ),
+ 'llms_visibility_posts' => array(
+ 'default' => '[]',
+ 'type' => 'string',
+ ),
+ );
+ }
+
+ /**
+ * Get the number of enrollments for a user by post type.
+ *
+ * @since 1.0.0
+ *
+ * @param int $uid WP_User ID.
+ * @param string $type Post type.
+ * @return int
+ */
+ private function get_enrollment_count_by_type( $uid, $type ) {
+
+ $found = 0;
+ $student = llms_get_student( $uid );
+
+ $type = str_replace( 'any_', '', $type );
+
+ if ( 'course' === $type || 'membership' === $type ) {
+ $enrollments = $student->get_enrollments( $type, array( 'limit' => 1 ) );
+ $found = $enrollments['found'];
+ } elseif ( 'any' === $type ) {
+ $found = $this->get_enrollment_count_by_type( $uid, 'course' );
+ if ( ! $found ) {
+ $found = $this->get_enrollment_count_by_type( $uid, 'membership' );
+ }
+ }
+
+ return $found;
+
+ }
+
+ /**
+ * Parse post ids from block visibility in attributes.
+ *
+ * @since 1.0.0
+ *
+ * @param array $attrs Block attributes.
+ * @return array
+ */
+ private function get_post_ids_from_block_attributes( $attrs ) {
+
+ $ids = array();
+ if ( 'this' === $attrs['llms_visibility_in'] ) {
+ $ids[] = get_the_ID();
+ } elseif ( ! empty( $attrs['llms_visibility_posts'] ) ) {
+ $ids = wp_list_pluck( json_decode( $attrs['llms_visibility_posts'] ), 'id' );
+ }
+
+ return $ids;
+
+ }
+
+ /**
+ * Filter block output.
+ *
+ * @since 1.0.0
+ * @since 1.6.0 Add logic for `logged_in` and `logged_out` block visibility options.
+ * @since 2.0.0 Added a conditional prior to checking the block's visibility attributes.
+ * @since 2.4.2 Set the `user_login` field block's visibility to its default 'logged_out' if not set.
+ *
+ * @param string $content Block inner content.
+ * @param array $block Block data array.
+ * @return string
+ */
+ public function maybe_filter_block( $content, $block ) {
+
+ // Allow conditionally filtering the block based on external context.
+ if ( ! $this->should_filter_block( $block ) ) {
+ return $content;
+ }
+
+ // Set the `user_login` field block's visibility to its default 'logged_out' if not set.
+ // The WordPress serializer `getCommentAttributes()` function removes the attribute before being
+ // serialized into `post_content` if the attribute can have only one value and it's the default.
+ if ( 'llms/form-field-user-login' === $block['blockName'] && empty( $block['attrs']['llms_visibility'] ) ) {
+ $block['attrs']['llms_visibility'] = 'logged_out';
+ }
+
+ // No attributes or no llms visibility settings (visible to "all").
+ if ( empty( $block['attrs'] ) || empty( $block['attrs']['llms_visibility'] ) ) {
+ return $content;
+ }
+
+ $uid = get_current_user_id();
+
+ // Show only to logged in users.
+ if ( 'logged_in' === $block['attrs']['llms_visibility'] && ! $uid ) {
+
+ $content = '';
+
+ // Show only to logged out users.
+ } elseif ( 'logged_out' === $block['attrs']['llms_visibility'] && $uid ) {
+ $content = '';
+
+ // Enrolled checks.
+ } elseif ( 'enrolled' === $block['attrs']['llms_visibility'] && ! empty( $block['attrs']['llms_visibility_in'] ) ) {
+
+ // Don't have to run any further checks if we don't have a user.
+ if ( ! $uid ) {
+
+ $content = '';
+
+ // Checks for the "any" conditions.
+ } elseif ( in_array( $block['attrs']['llms_visibility_in'], array( 'any', 'any_course', 'any_membership' ), true ) ) {
+
+ $found = $this->get_enrollment_count_by_type( $uid, $block['attrs']['llms_visibility_in'] );
+ if ( ! $found ) {
+ $content = '';
+ }
+
+ // Checks for specifics.
+ } elseif ( in_array( $block['attrs']['llms_visibility_in'], array( 'this', 'list_all', 'list_any' ), true ) ) {
+
+ $relation = 'list_any' === $block['attrs']['llms_visibility_in'] ? 'any' : 'all'; // "this" becomes an "all" relationship
+ if ( ! llms_is_user_enrolled( $uid, $this->get_post_ids_from_block_attributes( $block['attrs'] ), $relation ) ) {
+ $content = '';
+ }
+ }
+
+ // Not-Enrolled checks.
+ } elseif ( 'not_enrolled' === $block['attrs']['llms_visibility'] && ! empty( $block['attrs']['llms_visibility_in'] ) ) {
+
+ // Only need to check logged in users.
+ if ( $uid ) {
+
+ // Checks for the "any" conditions.
+ if ( in_array( $block['attrs']['llms_visibility_in'], array( 'any', 'any_course', 'any_membership' ), true ) ) {
+
+ $found = $this->get_enrollment_count_by_type( $uid, $block['attrs']['llms_visibility_in'] );
+ if ( $found ) {
+ $content = '';
+ }
+
+ // Checks for specifics.
+ } elseif ( in_array( $block['attrs']['llms_visibility_in'], array( 'this', 'list_all', 'list_any' ), true ) ) {
+
+ $relation = 'list_any' === $block['attrs']['llms_visibility_in'] ? 'any' : 'all'; // "this" becomes an "all" relationship
+ if ( llms_is_user_enrolled( $uid, $this->get_post_ids_from_block_attributes( $block['attrs'] ), $relation ) ) {
+ $content = '';
+ }
+ }
+ }
+ }
+
+ /**
+ * Filters a blocks content after it has been run through visibility attribute filters
+ *
+ * @since 1.0.0
+ *
+ * @param string $content The HTML content for a block. May be an empty string if the block should be invisible to the current user.
+ * @param array $block Block data array.
+ */
+ return apply_filters( 'llms_blocks_visibility_render_block', $content, $block );
+
+ }
+
+ /**
+ * Determine whether or not a block's rendering should be modified by block-level visibility settings
+ *
+ * This method does not determine whether or not the block will be rendered, it only determines whether
+ * or not we should check if it should be rendered.
+ *
+ * This method is primarily used to ensure that LifterLMS core dynamic blocks (pricing table, course syllabus, etc...)
+ * are *always* displayed to creators when editing content within the block editor. This parses data from a block-renderer
+ * WP Core API request.
+ *
+ * @since 2.0.0
+ * @since 2.3.1 Don't use deprecated `FILTER_SANITIZE_STRING`.
+ *
+ * @link https://developer.wordpress.org/rest-api/reference/rendered-blocks/
+ *
+ * @param array $block Block data array.
+ * @return boolean If `true`, block filters should be checked, other wise they will be skipped.
+ */
+ private function should_filter_block( $block ) {
+
+ // Always filter unless explicitly told not to.
+ $should_filter = true;
+
+ if ( llms_is_rest() ) {
+
+ $context = llms_filter_input( INPUT_GET, 'context' );
+ $post_id = llms_filter_input( INPUT_GET, 'post_id', FILTER_SANITIZE_NUMBER_INT );
+
+ // Always render blocks when a valid user is requesting the block in the edit context.
+ if ( 'edit' === $context && $post_id && current_user_can( 'edit_post', $post_id ) ) {
+ $should_filter = false;
+ }
+ }
+
+ /**
+ * Filters whether or not a block's rendering should be modified by block-level visibility settings
+ *
+ * This filter does not determine whether or not the block will be rendered, it only determines whether
+ * or not we should check if it should be rendered.
+ *
+ * @since 2.0.0
+ *
+ * @param boolean $should_filter Whether or not to apply visibility filters.
+ * @param array $block Block data array.
+ */
+ return apply_filters( 'llms_blocks_visibility_should_filter_block', $should_filter, $block );
+
+ }
+
+}
+
+return new LLMS_Blocks_Visibility();
diff --git a/libraries/lifterlms-blocks/includes/class-llms-blocks.php b/libraries/lifterlms-blocks/includes/class-llms-blocks.php
new file mode 100644
index 0000000000..beca425dc3
--- /dev/null
+++ b/libraries/lifterlms-blocks/includes/class-llms-blocks.php
@@ -0,0 +1,252 @@
+ 'llms-blocks',
+ 'title' => __( 'LifterLMS Blocks', 'lifterlms' ),
+ );
+
+ array_unshift(
+ $categories,
+ array(
+ 'slug' => 'llms-custom-fields',
+ 'title' => __( 'Custom User Information', 'lifterlms' ),
+ )
+ );
+
+ array_unshift(
+ $categories,
+ array(
+ 'slug' => 'llms-user-info-fields',
+ 'title' => __( 'User Information', 'lifterlms' ),
+ )
+ );
+
+ return $categories;
+ }
+
+
+ /**
+ * Print dynamic block information as a JS variable.
+ *
+ * Allows us to ensure we only add visibility attributes to static blocks.
+ * Prevents an issue causing rest api validation issues during attribute validation
+ * because it's impossible to register custom attributes on a block.
+ *
+ * @link https://github.com/gocodebox/lifterlms-blocks/issues/30
+ *
+ * @since 1.5.1
+ * @since 2.0.0 Since WordPress 5.8 blocks are available in widgets and customizer screen too.
+ *
+ * @return void
+ */
+ public function admin_print_scripts() {
+
+ $screen = get_current_screen();
+ if ( ! $screen || ( empty( $screen->is_block_editor ) && 'customize' !== $screen->base ) ) {
+ return;
+ }
+
+ echo '';
+
+ }
+
+ /**
+ * Retrieve a list of dynamic block names registered with WordPress (excluding LifterLMS blocks).
+ *
+ * @since 1.5.1
+ *
+ * @return array
+ */
+ private function get_dynamic_block_names() {
+ $blocks = array();
+ foreach ( get_dynamic_block_names() as $name ) {
+ if ( 0 !== strpos( $name, 'llms/' ) ) {
+ $blocks[] = $name;
+ }
+ }
+ return apply_filters( 'llms_blocks_get_dynamic_block_names', $blocks );
+ }
+
+ /**
+ * Include all files.
+ *
+ * @since 2.0.0
+ * @since 2.3.0 Include php template block file.
+ *
+ * @return void
+ */
+ private function includes() {
+
+ // Functions.
+ require_once LLMS_BLOCKS_PLUGIN_DIR . '/includes/functions-llms-blocks.php';
+
+ // Classes.
+ require_once LLMS_BLOCKS_PLUGIN_DIR . '/includes/class-llms-blocks-assets.php';
+ require_once LLMS_BLOCKS_PLUGIN_DIR . '/includes/class-llms-blocks-abstract-block.php';
+ require_once LLMS_BLOCKS_PLUGIN_DIR . '/includes/class-llms-blocks-migrate.php';
+ require_once LLMS_BLOCKS_PLUGIN_DIR . '/includes/class-llms-blocks-page-builders.php';
+ require_once LLMS_BLOCKS_PLUGIN_DIR . '/includes/class-llms-blocks-post-instructors.php';
+ require_once LLMS_BLOCKS_PLUGIN_DIR . '/includes/class-llms-blocks-post-types.php';
+ require_once LLMS_BLOCKS_PLUGIN_DIR . '/includes/class-llms-blocks-post-visibility.php';
+ require_once LLMS_BLOCKS_PLUGIN_DIR . '/includes/class-llms-blocks-reusable.php';
+ require_once LLMS_BLOCKS_PLUGIN_DIR . '/includes/class-llms-blocks-status-tools.php';
+
+ // Block Visibility Component.
+ require_once LLMS_BLOCKS_PLUGIN_DIR . '/includes/class-llms-blocks-visibility.php';
+
+ // Dynamic Blocks.
+ require_once LLMS_BLOCKS_PLUGIN_DIR . '/includes/blocks/class-llms-blocks-course-information-block.php';
+ require_once LLMS_BLOCKS_PLUGIN_DIR . '/includes/blocks/class-llms-blocks-course-syllabus-block.php';
+ require_once LLMS_BLOCKS_PLUGIN_DIR . '/includes/blocks/class-llms-blocks-course-progress-block.php';
+ require_once LLMS_BLOCKS_PLUGIN_DIR . '/includes/blocks/class-llms-blocks-instructors-block.php';
+ require_once LLMS_BLOCKS_PLUGIN_DIR . '/includes/blocks/class-llms-blocks-lesson-navigation-block.php';
+ require_once LLMS_BLOCKS_PLUGIN_DIR . '/includes/blocks/class-llms-blocks-lesson-progression-block.php';
+ require_once LLMS_BLOCKS_PLUGIN_DIR . '/includes/blocks/class-llms-blocks-pricing-table-block.php';
+ require_once LLMS_BLOCKS_PLUGIN_DIR . '/includes/blocks/class-llms-blocks-php-template-block.php';
+
+ }
+
+ /**
+ * Register all blocks & components.
+ *
+ * @since 1.0.0
+ * @since 1.4.0 Add status tools class.
+ * @since 1.9.0 Added course progress block class.
+ * @since 2.0.0 Return early if LifterLMS isn't installed, move file inclusion to `$this->includes()`,
+ * and moved actions and filters from the constructor.
+ * @since 2.2.1 Handle '-src' in WordPress version numbers.
+ *
+ * @return void
+ */
+ public function init() {
+
+ if ( ! function_exists( 'llms' ) || ! version_compare( '5.0.0-rc.2', llms()->version, '<=' ) ) {
+ return;
+ }
+
+ $this->includes();
+
+ add_action( 'add_meta_boxes', array( $this, 'remove_metaboxes' ), 999, 2 );
+
+ global $wp_version;
+ $filter = version_compare( $wp_version, '5.8-src', '>=' ) ? 'block_categories_all' : 'block_categories';
+
+ add_filter( $filter, array( $this, 'add_block_category' ) );
+ add_action( 'admin_print_scripts', array( $this, 'admin_print_scripts' ), 15 );
+
+ /**
+ * When loaded as a library included by the LifterLMS core localization is handled by the LifterLMS core.
+ *
+ * When the plugin is loaded by itself as a plugin, we must localize it independently.
+ */
+ if ( ! defined( 'LLMS_BLOCKS_LIB' ) || ! LLMS_BLOCKS_LIB ) {
+ add_action( 'init', array( $this, 'load_textdomain' ), 0 );
+ }
+
+ }
+
+ /**
+ * Load l10n files.
+ *
+ * This method is only used when the plugin is loaded as a standalone plugin (for development purposes),
+ * otherwise (when loaded as a library from within the LifterLMS core plugin) the localization
+ * strings are included into the LifterLMS Core plugin's po/mo files and are localized by the LifterLMS
+ * core plugin.
+ *
+ * Files can be found in the following order (The first loaded file takes priority):
+ * 1. WP_LANG_DIR/lifterlms/lifterlms-blocks-LOCALE.mo
+ * 2. WP_LANG_DIR/plugins/lifterlms-blocks-LOCALE.mo
+ * 3. WP_CONTENT_DIR/plugins/lifterlms-blocks/i18n/lifterlms-blocks-LOCALE.mo
+ *
+ * Note: The function `load_plugin_textdomain()` is not used because the same textdomain as the LifterLMS core
+ * is used for this plugin but the file is named `lifterlms-blocks` in order to allow using a separate language
+ * file for each codebase.
+ *
+ * @since 1.10.0
+ *
+ * @return void
+ */
+ public function load_textdomain() {
+
+ // load locale.
+ $locale = apply_filters( 'plugin_locale', get_locale(), 'lifterlms' );
+
+ // Load from the LifterLMS "safe" directory if it exists.
+ load_textdomain( 'lifterlms', WP_LANG_DIR . '/lifterlms/lifterlms-blocks-' . $locale . '.mo' );
+
+ // Load from the default plugins language file directory.
+ load_textdomain( 'lifterlms', WP_LANG_DIR . '/plugins/lifterlms-blocks-' . $locale . '.mo' );
+
+ // Load from the plugin's language file directory.
+ load_textdomain( 'lifterlms', LLMS_BLOCKS_PLUGIN_DIR . '/i18n/lifterlms-blocks-' . $locale . '.mo' );
+
+ }
+
+ /**
+ * Remove deprecated core metaboxes.
+ *
+ * @since 1.0.0
+ * @since 1.3.0 Updated.
+ *
+ * @param string $post_type WP post type of the current post.
+ * @param string $post WP_Post.
+ * @return void
+ */
+ public function remove_metaboxes( $post_type, $post ) {
+
+ if ( ! llms_blocks_is_classic_enabled_for_post( $post ) ) {
+
+ remove_meta_box( 'llms-instructors', 'course', 'normal' );
+ remove_meta_box( 'llms-instructors', 'llms_membership', 'normal' );
+
+ }
+
+ }
+
+}
+
+return new LLMS_Blocks();
diff --git a/libraries/lifterlms-blocks/includes/functions-llms-blocks.php b/libraries/lifterlms-blocks/includes/functions-llms-blocks.php
new file mode 100644
index 0000000000..9102605455
--- /dev/null
+++ b/libraries/lifterlms-blocks/includes/functions-llms-blocks.php
@@ -0,0 +1,75 @@
+ID, 'classic-editor-remember', true ) );
+ }
+
+ // Uses same editor for all posts.
+ } else {
+
+ $ret = ( 'classic' === get_option( 'classic-editor-replace', 'classic' ) );
+
+ }
+ }
+
+ return apply_filters( 'llms_blocks_is_classic_enabled_for_post', $ret, $post );
+
+}
+
+/**
+ * Determine if a post is migrated
+ *
+ * @param mixed $post WP_Post or WP_Post ID.
+ * @return boolean
+ * @since 1.3.1
+ * @version 1.3.1
+ */
+function llms_blocks_is_post_migrated( $post ) {
+
+ $post_id = null;
+ $ret = false;
+
+ $post = get_post( $post );
+ if ( $post ) {
+
+ $post_id = $post->ID;
+
+ // Classic editor is being used for this post.
+ if ( llms_blocks_is_classic_enabled_for_post( $post_id ) ) {
+ $ret = false;
+ } else {
+ $ret = llms_parse_bool( get_post_meta( $post_id, '_llms_blocks_migrated', true ) );
+ }
+ }
+
+ return apply_filters( 'llms_blocks_is_post_migrated', $ret, $post_id );
+
+}
diff --git a/libraries/lifterlms-blocks/includes/index.php b/libraries/lifterlms-blocks/includes/index.php
new file mode 100644
index 0000000000..82e2315c6b
--- /dev/null
+++ b/libraries/lifterlms-blocks/includes/index.php
@@ -0,0 +1,2 @@
+chaining = true;
+ $this->$command( $args, $assoc_args );
+ $this->chaining = false;
+ }
+
+ /**
+ * Retrieve an LLMS_Add_On object for a given add-on by it's slug.
+ *
+ * @since 0.0.1
+ *
+ * @param string $slug An add-on slug. Must be prefixed.
+ * @param bool|WP_Error|string $err If truthy, will return `null` and use log to the console using a WP_CLI method as defined by $err_type.
+ * Pass `true` to output a default error message.
+ * Pass a WP_Error object or string to use as the error.
+ * @param string $err_type Method to pass `$err` to when an error is encountered. Default `\WP_CLI::error()`.
+ * Use `\WP_CLI::warning()` or `\WP_CLI::log()` where appropriate.
+ * @return LLMS_Add_On|boolean|null Returns an add-on object if the add-on can be located or `false` if not found.
+ * Returns `null` when an error is encountered and `$err` is a truthy.
+ */
+ protected function get_addon( $slug, $err = false, $err_type = 'error' ) {
+
+ $addon = llms_get_add_on( $this->prefix_slug( $slug ), 'slug' );
+ $exists = ! empty( $addon->get( 'id' ) );
+
+ if ( ! $exists && $err ) {
+ $err = is_bool( $err ) ? sprintf( 'Invalid slug: %s.', $slug ) : $err;
+ return \WP_CLI::$err_type( $err );
+ }
+
+ return ! $exists ? false : $addon;
+ }
+
+ /**
+ * Prefix an add-on slug with `lifterlms-` if it's not already present.
+ *
+ * @since 0.0.1
+ *
+ * @param string $slug Add-on slug.
+ * @return string
+ */
+ protected function prefix_slug( $slug ) {
+ if ( 0 !== strpos( $slug, 'lifterlms-' ) ) {
+ $slug = "lifterlms-{$slug}";
+ }
+ return $slug;
+ }
+
+}
diff --git a/libraries/lifterlms-cli/src/Commands/AddOn/Activate.php b/libraries/lifterlms-cli/src/Commands/AddOn/Activate.php
new file mode 100644
index 0000000000..a7973ba0dc
--- /dev/null
+++ b/libraries/lifterlms-cli/src/Commands/AddOn/Activate.php
@@ -0,0 +1,101 @@
+...]
+ * : The slug of one or more LifterLMS add-on to install.
+ *
+ * [--all]
+ * : If set, all of the LifterLMS add-ons installed on the site will be activated.
+ *
+ * ## EXAMPLES
+ *
+ * # Activate the LifterLMS Groups add-on.
+ * $ wp llms addon activate lifterlms-groups
+ *
+ * # Activate an add-on without using the `lifterlms-` prefix.
+ * $ wp llms addon activate advanced-videos
+ *
+ * # Activate multiple LifterLMS add-ons.
+ * $ wp llms addon activate lifterlms-groups lifterlms-assignments lifterlms-pdfs
+ *
+ * # Activate all installed LifterLMS add-ons.
+ * $ wp llms addon activate --all
+ *
+ * @since 0.0.1
+ *
+ * @param array $args Indexed array of positional command arguments.
+ * @param array $assoc_args Associative array of command options.
+ * @return null
+ */
+ public function activate( $args, $assoc_args ) {
+
+ if ( ! empty( $assoc_args['all'] ) ) {
+ $args = $this->get_available_addons( 'inactive', false );
+ if ( empty( $args ) ) {
+ return \WP_CLI::warning( 'No add-ons to activate.' );
+ }
+ }
+
+ $results = $this->loop( $args, $assoc_args, 'activate_one' );
+ if ( ! $this->chaining ) {
+ \WP_CLI\Utils\report_batch_operation_results( 'add-on', 'activate', count( $args ), $results['successes'], $results['errors'] );
+ }
+
+ }
+
+ /**
+ * Loop callback function for activate()
+ *
+ * Ensures add-on can be activated and actually activates the add-on.
+ *
+ * @since 0.0.1
+ * @since 0.0.2 Fixed unmerged placeholder in warning message when add-on is not installed.
+ *
+ * @param string $slug Add-on slug.
+ * @param LLMS_Add_On $addon Add-on object.
+ * @param array $assoc_args Associative array of command options.
+ * @return null|true Returns `null` if an error is encountered and `true` on success.
+ */
+ private function activate_one( $slug, $addon, $assoc_args ) {
+
+ if ( $addon->is_active() ) {
+ return \WP_CLI::warning( sprintf( 'Add-on "%s" is already active.', $slug ) );
+ }
+
+ if ( ! $addon->is_installed() ) {
+ return \WP_CLI::warning( sprintf( 'Add-on "%1$s" is not installed. Run \'wp llms addon install %s\' to install it.', $slug ) );
+ }
+
+ $res = $addon->activate();
+ if ( is_wp_error( $res ) ) {
+ return \WP_CLI::warning( $res );
+ }
+
+ \WP_CLI::log( $res );
+
+ return true;
+
+ }
+
+}
diff --git a/libraries/lifterlms-cli/src/Commands/AddOn/ChannelSet.php b/libraries/lifterlms-cli/src/Commands/AddOn/ChannelSet.php
new file mode 100644
index 0000000000..256c6fe07f
--- /dev/null
+++ b/libraries/lifterlms-cli/src/Commands/AddOn/ChannelSet.php
@@ -0,0 +1,61 @@
+
+ * : The slug of the add-on.
+ *
+ * []
+ * : The update channel to subscribe to.
+ * ---
+ * default: 'stable'
+ * options:
+ * - stable
+ * - beta
+ * ---
+ *
+ * ## EXAMPLES
+ *
+ * # Subscribe the Groups add-on to the beta channel.
+ * $ wp llms addon channel-set lifterlms-groups stable
+ *
+ * # Subscribe to the stable channel.
+ * $ wp llms addon channel-set lifterlms-groups stable
+ *
+ * @subcommand channel-set
+ *
+ * @since 0.0.1
+ * @since 0.0.2 Updated success message.
+ *
+ * @param array $args Indexed array of positional command arguments.
+ * @return null
+ */
+ public function channel_set( $args ) {
+
+ $addon = $this->get_addon( $args[0], true );
+ $addon->subscribe_to_channel( $args[1] );
+ return \WP_CLI::success( sprintf( 'Subscribed to the %s channel.', $args[1] ) );
+
+ }
+
+}
diff --git a/libraries/lifterlms-cli/src/Commands/AddOn/Deactivate.php b/libraries/lifterlms-cli/src/Commands/AddOn/Deactivate.php
new file mode 100644
index 0000000000..117e898e4b
--- /dev/null
+++ b/libraries/lifterlms-cli/src/Commands/AddOn/Deactivate.php
@@ -0,0 +1,111 @@
+...]
+ * : The slug of one or more add-on to deactivate.
+ *
+ * [--uninstall]
+ * : Uninstall the add-ons after deactivation.
+ *
+ * [--all]
+ * : If set, all of the plugin add-ons installed on the site will be activated.
+ *
+ * ## EXAMPLES
+ *
+ * # Deactivate the LifterLMS Groups add-on.
+ * $ wp llms addon deactivate lifterlms-groups
+ *
+ * # Deactivate an add-on without using the `lifterlms-` prefix.
+ * $ wp llms addon deactivate advanced-videos
+ *
+ * # Deactivate multiple LifterLMS add-ons.
+ * $ wp llms addon deactivate lifterlms-groups lifterlms-assignments lifterlms-pdfs
+ *
+ * # Deactivate all installed LifterLMS add-ons.
+ * $ wp llms addon deactivate --all
+ *
+ * # Deactivate and uninstall the LifterLMS Groups add-on.
+ * $ wp llms addon deactivate lifterlms-groups --uninstall
+ *
+ * @since 0.0.1
+ * @since 0.0.2 Completion messages use says "deactivate(d)" in favor of "activate(d)".
+ *
+ * @param array $args Indexed array of positional command arguments.
+ * @param array $assoc_args Associative array of command options.
+ * @return null
+ */
+ public function deactivate( $args, $assoc_args ) {
+
+ if ( ! empty( $assoc_args['all'] ) ) {
+ $args = $this->get_available_addons( 'active', false, 'plugin' );
+ if ( empty( $args ) ) {
+ return \WP_CLI::warning( 'No add-ons to deactivate.' );
+ }
+ }
+
+ $results = $this->loop( $args, $assoc_args, 'deactivate_one' );
+ if ( ! $this->chaining ) {
+ \WP_CLI\Utils\report_batch_operation_results( 'add-on', 'deactivate', count( $args ), $results['successes'], $results['errors'] );
+ }
+
+ }
+
+ /**
+ * Loop callback function for deactivate()
+ *
+ * Ensures add-on can be deactivated and actually deactivates (and maybe uninstalls) the add-on.
+ *
+ * @since 0.0.1
+ *
+ * @param string $slug Add-on slug.
+ * @param LLMS_Add_On $addon Add-on object.
+ * @param array $assoc_args Associative array of command options.
+ * @return null|true Returns `null` if an error is encountered and `true` on success.
+ */
+ private function deactivate_one( $slug, $addon, $assoc_args ) {
+
+ if ( ! $addon->is_installed() ) {
+ return \WP_CLI::warning( sprintf( 'Add-on "%1$s" is not installed.', $slug ) );
+ }
+
+ if ( ! $addon->is_active() ) {
+ return \WP_CLI::warning( sprintf( 'Add-on "%s" is already deactivated.', $slug ) );
+ }
+
+ $res = $addon->deactivate();
+ if ( is_wp_error( $res ) ) {
+ return \WP_CLI::warning( $res );
+ }
+
+ if ( ! empty( $assoc_args['uninstall'] ) ) {
+ $this->chain_command( 'uninstall', array( $slug ) );
+ }
+
+ \WP_CLI::log( $res );
+
+ return true;
+
+ }
+
+}
diff --git a/libraries/lifterlms-cli/src/Commands/AddOn/Enumerate.php b/libraries/lifterlms-cli/src/Commands/AddOn/Enumerate.php
new file mode 100644
index 0000000000..d98d9eed07
--- /dev/null
+++ b/libraries/lifterlms-cli/src/Commands/AddOn/Enumerate.php
@@ -0,0 +1,127 @@
+=]
+ * : Filter results based on the value of a field.
+ *
+ * [--field=]
+ * : Prints the value of a single field for each add-on.
+ *
+ * [--fields=]
+ * : Limit the output to only the specified fields. Use "all" to display all available fields.
+ *
+ * [--format=]
+ * : Render output in a particular format.
+ * ---
+ * default: table
+ * options:
+ * - table
+ * - csv
+ * - count
+ * - json
+ * - yaml
+ * ---
+ *
+ * ## AVAILABLE FIELDS
+ *
+ * These fields will be displayed by default for each add-on:
+ *
+ * * name
+ * * status
+ * * update
+ * * version
+ *
+ * These fields are optionally available:
+ *
+ * * update_version
+ * * license
+ * * title
+ * * channel
+ * * type
+ * * file
+ *
+ * ## EXAMPLES
+ *
+ * # List all add-ons.
+ * $ wp llms addon list
+ *
+ * # List all add-ons in JSON format.
+ * $ wp llms addon list --format=json
+ *
+ * # List all add-ons by name only.
+ * $ wp llms addon list --field=name
+ *
+ * # List all add-ons with all available fields.
+ * $ wp llms addon list --fields=all
+ *
+ * # List all add-ons with a custom fields list.
+ * $ wp llms addon list --fields=title,status,version
+ *
+ * # List currently activated add-ons.
+ * $ wp llms addon list --status=active
+ *
+ * # List all theme add-ons.
+ * $ wp llms addon list --type=theme
+ *
+ * # List all add-ons with available updates.
+ * $ wp llms addon list --update=available
+ *
+ * # List all add-ons licensed on the site.
+ * $ wp llms addon list --license=active
+ *
+ * @since 0.0.1
+ *
+ * @param array $args Indexed array of positional command arguments.
+ * @param array $assoc_args Associative array of command options.
+ * @return null
+ */
+ public function list( $args, $assoc_args ) {
+
+ $fields = array( 'name', 'status', 'update', 'version' );
+ $all_fields = array_merge( $fields, array( 'update_version', 'license', 'title', 'channel', 'type', 'file' ) );
+
+ // Determine if there's a user filter submitted through`--=`.
+ $filter_field = array_values( array_intersect( $all_fields, array_keys( $assoc_args ) ) );
+
+ $list = $this->get_filtered_items( $assoc_args, ! empty( $filter_field ) ? $filter_field[0] : '' );
+
+ if ( ! empty( $assoc_args['fields'] ) && 'all' === $assoc_args['fields'] ) {
+ $assoc_args['fields'] = $all_fields;
+ }
+
+ $formatter = new Formatter( $assoc_args, $fields );
+ return $formatter->display_items( $list );
+
+ }
+
+}
diff --git a/libraries/lifterlms-cli/src/Commands/AddOn/Get.php b/libraries/lifterlms-cli/src/Commands/AddOn/Get.php
new file mode 100644
index 0000000000..0ea53099ee
--- /dev/null
+++ b/libraries/lifterlms-cli/src/Commands/AddOn/Get.php
@@ -0,0 +1,120 @@
+
+ * : The slug of the add-on to get information about.
+ *
+ * ## OPTIONS
+ *
+ * [--field=]
+ * : Retrieve a single piece of information about the add-on.
+ *
+ * [--fields=]
+ * : Limit the output to only the specified fields. Use "all" to display all available fields.
+ *
+ * [--format=]
+ * : Render output in a particular format.
+ * ---
+ * default: table
+ * options:
+ * - table
+ * - csv
+ * - json
+ * - yaml
+ * ---
+ *
+ * ## AVAILABLE FIELDS
+ *
+ * These fields will be displayed by default for each add-on:
+ *
+ * * name
+ * * title
+ * * version
+ * * description
+ * * status
+ *
+ * These fields are optionally available:
+ *
+ * * update
+ * * update_version
+ * * license
+ * * title
+ * * channel
+ * * type
+ * * file
+ * * permalink
+ * * changelog
+ * * documentation
+ *
+ * @since 0.0.1
+ *
+ * @param array $args Indexed array of positional command arguments.
+ * @param array $assoc_args Associative array of command options.
+ * @return null
+ */
+ public function get( $args, $assoc_args ) {
+
+ $addon = $this->get_addon( $args[0], true );
+ $fields = array( 'name', 'title', 'version', 'description', 'status' );
+ $all_fields = array_merge( $fields, array( 'update', 'update_version', 'license', 'title', 'channel', 'type', 'file', 'permalink', 'changelog', 'documentation' ) );
+
+ if ( ! empty( $assoc_args['fields'] ) ) {
+ $assoc_args['fields'] = 'all' === $assoc_args['fields'] ? $all_fields : $assoc_args['fields'];
+ } else {
+ $assoc_args['fields'] = $fields;
+ }
+
+ // Get formatted item.
+ $item = $this->format_item( $addon );
+
+ // Put the keys in the order defined by input args.
+ $item = array_merge( array_flip( $assoc_args['fields'] ), $item );
+
+ // Pass the item as an array and all fields for proper formatting when --field= is passed.
+ $list = array( $item );
+ $format_fields = $all_fields;
+
+ // Format when displaying multiple fields.
+ if ( empty( $assoc_args['field'] ) ) {
+
+ $list = array();
+ foreach ( $item as $Field => $Value ) { // phpcs:ignore WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase
+ if ( ! in_array( $Field, $assoc_args['fields'], true ) ) { // phpcs:ignore WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase
+ continue;
+ }
+ $list[] = compact( 'Field', 'Value' );
+ }
+ $format_fields = array( 'Field', 'Value' );
+ unset( $assoc_args['fields'] );
+
+ }
+
+ $formatter = new Formatter( $assoc_args, $format_fields );
+ return $formatter->display_items( $list );
+
+ }
+
+}
diff --git a/libraries/lifterlms-cli/src/Commands/AddOn/Install.php b/libraries/lifterlms-cli/src/Commands/AddOn/Install.php
new file mode 100644
index 0000000000..91892b1df0
--- /dev/null
+++ b/libraries/lifterlms-cli/src/Commands/AddOn/Install.php
@@ -0,0 +1,106 @@
+...]
+ * : The slug of one or more add-on to install.
+ *
+ * [--key=]
+ * : If set, will attempt to activate and use the provided license key.
+ *
+ * [--activate]
+ * : If set, the add-on(s) will be activated immediately after install.
+ *
+ * [--all]
+ * : If set, all of the add-ons available to the site will be installed.
+ * All existing license keys stored on the site will be queried for the list of available add-ons.
+ *
+ * [--type=]
+ * : When using '--all', determines the type of add-on to be installed.
+ * ---
+ * default: 'all'
+ * options:
+ * - all
+ * - plugin
+ * - theme
+ * ---
+ *
+ * @since 0.0.1
+ *
+ * @param array $args Indexed array of positional command arguments.
+ * @param array $assoc_args Associative array of command options.
+ * @return null
+ */
+ public function install( $args, $assoc_args ) {
+
+ // If a key is provided, activate it first.
+ if ( ! empty( $assoc_args['key'] ) ) {
+ \WP_CLI::runcommand( "llms license activate {$assoc_args['key']}" );
+ }
+
+ if ( ! empty( $assoc_args['all'] ) ) {
+ $args = $this->get_available_addons( 'uninstalled', true, $assoc_args['type'] );
+ if ( empty( $args ) ) {
+ return \WP_CLI::warning( 'No add-ons to install.' );
+ }
+ }
+
+ $results = $this->loop( $args, $assoc_args, 'install_one' );
+ \WP_CLI\Utils\report_batch_operation_results( 'add-on', 'install', count( $args ), $results['successes'], $results['errors'] );
+
+ }
+
+ /**
+ * Loop callback function for install()
+ *
+ * Ensures add-on can be installed and actually installs (and maybe activates) the add-on.
+ *
+ * @since 0.0.1
+ *
+ * @param string $slug Add-on slug.
+ * @param LLMS_Add_On $addon Add-on object.
+ * @param array $assoc_args Associative array of command options.
+ * @return null|true Returns `null` if an error is encountered and `true` on success.
+ */
+ private function install_one( $slug, $addon, $assoc_args ) {
+
+ if ( $addon->is_installed() ) {
+ return \WP_CLI::warning( sprintf( 'Add-on "%s" is already installed.', $slug ) );
+ }
+
+ \WP_CLI::log( sprintf( 'Installing add-on: %s...', $addon->get( 'title' ) ) );
+ $res = $addon->install();
+ if ( is_wp_error( $res ) ) {
+ return \WP_CLI::warning( $res );
+ }
+
+ \WP_CLI::log( $res );
+ if ( ! empty( $assoc_args['activate'] ) ) {
+ $this->chain_command( 'activate', array( $slug ) );
+ }
+
+ return true;
+
+ }
+
+}
diff --git a/libraries/lifterlms-cli/src/Commands/AddOn/Main.php b/libraries/lifterlms-cli/src/Commands/AddOn/Main.php
new file mode 100644
index 0000000000..b5beab0b95
--- /dev/null
+++ b/libraries/lifterlms-cli/src/Commands/AddOn/Main.php
@@ -0,0 +1,202 @@
+ $addon->get( 'slug' ),
+ 'description' => $addon->get( 'description' ),
+ 'status' => $addon->get_status(),
+ 'license' => str_replace( 'license_', '', $addon->get_license_status() ),
+ 'update' => $addon->has_available_update() ? 'available' : 'none',
+ 'version' => $addon->is_installed() ? $addon->get_installed_version() : 'N/A',
+ 'update_version' => $addon->get( 'version' ),
+ 'title' => $addon->get( 'title' ),
+ 'channel' => $addon->get_channel_subscription(),
+ 'type' => $addon->get( 'type' ),
+ 'file' => $addon->get( 'update_file' ),
+ 'permalink' => $addon->get( 'permalink' ),
+ 'changelog' => $addon->get( 'changelog' ),
+ 'documentation' => $addon->get( 'documentation' ),
+ );
+
+ return $formatted;
+ }
+
+ /**
+ * Retrieve an array of available add-on slugs based on the supplied query criteria.
+ *
+ * This function passes data to `wp llms addon list` with specific filters and returns an associative
+ * array of add-on slugs from that list.
+ *
+ * This is used, mostly, to generate a list of available addons for various commands which provide an `--all` flag/option.
+ *
+ * @since 0.0.1
+ *
+ * @param string $status Add-on status, passed as the `--status` option to `llms addon list`.
+ * @param bool $check_license Whether or not the add-on should be licensed. This is used to determine what is installable / upgradeable.
+ * @param string $type Add-on type. Accepts 'all' (default), 'plugin' or 'theme'.
+ * @return string[] Array of add-on slugs meeting the specified filters.
+ */
+ private function get_available_addons( $status, $check_license, $type = 'all' ) {
+
+ $list = \WP_CLI::runcommand(
+ "llms addon list --format=json --status={$status} --fields=name,license,type",
+ array(
+ 'return' => true,
+ )
+ );
+ $list = array_filter(
+ json_decode( $list, true ),
+ function( $item ) use ( $check_license, $type ) {
+ return ( ( $check_license && 'active' === $item['license'] ) || ! $check_license ) && ( 'all' === $type || $type === $item['type'] );
+ }
+ );
+
+ return wp_list_pluck( $list, 'name' );
+
+ }
+
+ /**
+ * Retrieves an optionally filtered list of add-ons for use in the `list` command.
+ *
+ * @since 0.0.1
+ *
+ * @param array $assoc_args Associative array of command options.
+ * @param string $filter_field The optional name of the field to filter results by.
+ * @return array[] Array of add-on items.
+ */
+ private function get_filtered_items( $assoc_args, $filter_field = '' ) {
+
+ $addons = llms_get_add_ons();
+
+ $list = array_filter(
+ $addons['items'],
+ function( $item ) {
+ return // Skip anything without a slug.
+ ! empty( $item['slug'] ) &&
+ // Skip the LifterLMS core.
+ 'lifterlms' !== $item['slug'] &&
+ // Skip third party add-ons.
+ ! in_array( 'third-party', array_keys( $item['categories'] ), true );
+ }
+ );
+
+ // Format remaining items.
+ $list = array_map( array( $this, 'format_item' ), $list );
+
+ // Filter by field value.
+ if ( $filter_field ) {
+ $field_val = $assoc_args[ $filter_field ];
+ $list = array_filter(
+ $list,
+ function( $item ) use ( $filter_field, $field_val ) {
+ return $item[ $filter_field ] === $field_val;
+ }
+ );
+ }
+
+ // Alpha sort the list by slug.
+ usort(
+ $list,
+ function( $a, $b ) {
+ return strcmp( $a['name'], $b['name'] );
+ }
+ );
+
+ return $list;
+
+ }
+
+ /**
+ * Reusable loop function for handling commands which accept one or more slugs as the commands first argument
+ *
+ * @since 0.0.1
+ *
+ * @param string[] $slugs Array of add-on slugs, with or without the `lifterlms-` prefix.
+ * @param array $assoc_args Associative array of command options from the original command.
+ * @param string $callback Name of the method to use for handling a single add-on for the given command.
+ * The callback should accept three arguments:
+ * + @type string $slug Add-on slug for the current item.
+ * + @type LLMS_Add_On $addon Add-on object for the current item.
+ * + @type array $assoc_args Array of arguments from the initial command.
+ * The callback should return a truthy to signal success and
+ * a falsy to signal an error.
+ * @return array {
+ * Associative arrays containing details on the errors and successes encountered during the loop.
+ *
+ * @type int $errors Number of errors encountered in the loop.
+ * @type int $successes Number of success encountered in the loop.
+ * }
+ */
+ private function loop( $slugs, $assoc_args, $callback ) {
+
+ $successes = 0;
+ $errors = 0;
+
+ foreach ( $slugs as $slug ) {
+
+ if ( empty( $slug ) ) {
+ \WP_CLI::warning( 'Ignoring ambiguous empty slug value.' );
+ continue;
+ }
+
+ $addon = $this->get_addon( $slug, true, 'warning' );
+ if ( empty( $addon ) ) {
+ $errors++;
+ continue;
+ }
+
+ if ( ! $this->$callback( $slug, $addon, $assoc_args ) ) {
+ $errors++;
+ continue;
+ }
+
+ $successes++;
+
+ }
+
+ return compact( 'errors', 'successes' );
+
+ }
+
+}
diff --git a/libraries/lifterlms-cli/src/Commands/AddOn/Uninstall.php b/libraries/lifterlms-cli/src/Commands/AddOn/Uninstall.php
new file mode 100644
index 0000000000..101a92202e
--- /dev/null
+++ b/libraries/lifterlms-cli/src/Commands/AddOn/Uninstall.php
@@ -0,0 +1,104 @@
+...]
+ * : The slug of one or more add-on to install.
+ *
+ * [--deactivate]
+ * : If set, the plugin add-on(s) will be deactivated prior to uninstalling. Default behavior is to warn and skip if the plugin is active.
+ * Themes cannot be deactivated, another theme must be activated and then an add-on theme can be uninstalled.
+ *
+ * [--all]
+ * : If set, all of the add-ons available to the site will be uninstalled.
+ *
+ * [--type=]
+ * : When using '--all', determines the type of add-on to be uninstalled.
+ * ---
+ * default: 'all'
+ * options:
+ * - all
+ * - plugin
+ * - theme
+ * ---
+ *
+ * @since 0.0.1
+ *
+ * @param array $args Indexed array of positional command arguments.
+ * @param array $assoc_args Associative array of command options.
+ * @return null
+ */
+ public function uninstall( $args, $assoc_args ) {
+
+ if ( ! empty( $assoc_args['all'] ) ) {
+ $args = $this->get_available_addons( 'inactive', false, $assoc_args['type'] );
+ if ( empty( $args ) ) {
+ return \WP_CLI::warning( 'No add-ons to uninstall.' );
+ }
+ }
+
+ $results = $this->loop( $args, $assoc_args, 'uninstall_one' );
+ if ( ! $this->chaining ) {
+ \WP_CLI\Utils\report_batch_operation_results( 'add-on', 'uninstall', count( $args ), $results['successes'], $results['errors'] );
+ }
+
+ }
+
+ /**
+ * Loop callback function for uninstall()
+ *
+ * Ensures add-on can be uninstalled and actually installs (and maybe deactivates) the add-on.
+ *
+ * @since 0.0.1
+ *
+ * @param string $slug Add-on slug.
+ * @param LLMS_Add_On $addon Add-on object.
+ * @param array $assoc_args Associative array of command options.
+ * @return null|true Returns `null` if an error is encountered and `true` on success.
+ */
+ private function uninstall_one( $slug, $addon, $assoc_args ) {
+
+ if ( ! $addon->is_installed() ) {
+ return \WP_CLI::warning( sprintf( 'Add-on "%s" is not installed.', $slug ) );
+ }
+
+ if ( $addon->is_active() ) {
+ if ( ! empty( $assoc_args['deactivate'] ) ) {
+ $this->chain_command( 'deactivate', array( $slug ) );
+ } else {
+ return \WP_CLI::warning( sprintf( 'Add-on "%s" is active.', $slug ) );
+ }
+ }
+
+ $res = $addon->uninstall();
+ if ( is_wp_error( $res ) ) {
+ return \WP_CLI::warning( $res );
+ }
+
+ \WP_CLI::log( $res );
+
+ return true;
+
+ }
+
+}
diff --git a/libraries/lifterlms-cli/src/Commands/AddOn/Update.php b/libraries/lifterlms-cli/src/Commands/AddOn/Update.php
new file mode 100644
index 0000000000..b179dbdcee
--- /dev/null
+++ b/libraries/lifterlms-cli/src/Commands/AddOn/Update.php
@@ -0,0 +1,165 @@
+...]
+ * : The slug of one or more add-on to update.
+ *
+ * [--exclude]
+ * : A comma-separated list of add-on slugs which should be excluded from updating.
+ *
+ * [--all]
+ * : If set, all of the add-ons available to the site will be uninstalled.
+ *
+ * [--type=]
+ * : When using '--all', determines the type of add-on to be uninstalled.
+ * ---
+ * default: 'all'
+ * options:
+ * - all
+ * - plugin
+ * - theme
+ * ---
+ *
+ * [--format=]
+ * : Render output in a particular format.
+ * ---
+ * default: table
+ * options:
+ * - table
+ * - csv
+ * - json
+ * - yaml
+ * ---
+ *
+ * [--dry-run]
+ * : Preview which plugins would be updated.
+ *
+ * @since 0.0.1
+ *
+ * @param array $include List of add-on slugs to be updated.
+ * @param array $assoc_args Associative array of command options.
+ * @return null
+ */
+ public function update( $include, $assoc_args ) {
+
+ $include = array_map( array( $this, 'prefix_slug' ), $include );
+
+ $fields = array( 'name', 'status', 'version', 'update_version' );
+
+ $exclude = ! empty( $assoc_args['exclude'] ) ? array_map( array( $this, 'prefix_slug' ), explode( ',', $assoc_args['exclude'] ) ) : array();
+
+ // Retrieve all available updates and we'll filter it down.
+ $list = \WP_CLI::runcommand(
+ "llms addon list --format=json {$fieldopt}--update=available --fields=name,status,version,update_version",
+ array(
+ 'return' => true,
+ )
+ );
+ $list = array_filter(
+ json_decode( $list, true ),
+ function( $item ) use ( $include, $exclude ) {
+ // Add-on is active and an update is available.
+ return // Add-on is installed.
+ in_array( $item['status'], array( 'active', 'inactive' ), true ) &&
+ // Not excluded.
+ ! in_array( $item['name'], $exclude, true ) &&
+ // No add-ons specified or the add-on is in the specified list.
+ ( empty( $include ) || in_array( $item['name'], $include, true ) );
+ }
+ );
+
+ // WP-CLI `wp plugin update` shows a string when displaying table and no output for other formats.
+ if ( empty( $list ) ) {
+ if ( 'table' === $assoc_args['format'] ) {
+ return \WP_CLI::log( 'No add-on updates available.' );
+ }
+ return;
+ }
+
+ /**
+ * The WP Core upgrader pulls information from the site transient.
+ * If the update check cron or a manual visit to an update screen on the admin panel
+ * hasn't recently occurred the transient won't be set and we'll know there's an update
+ * but the transient will not and the upgrader won't be able to upgrade.
+ *
+ * So we'll force a redundant check to take place here to ensure that we can upgrade.
+ */
+ wp_update_plugins();
+ wp_update_themes();
+
+ if ( empty( $assoc_args['dry-run'] ) ) {
+
+ $fields = array( 'name', 'status', 'old_version', 'new_version' );
+
+ $errors = 0;
+ $successes = 0;
+ foreach ( $list as &$item ) {
+
+ if ( $this->update_one( $item ) ) {
+ $successes++;
+ } else {
+ $errors++;
+ }
+ }
+
+ \WP_CLI\Utils\report_batch_operation_results( 'add-on', 'update', count( $list ), $successes, $errors );
+
+ }
+
+ $formatter = new Formatter( $assoc_args, $fields );
+ return $formatter->display_items( $list );
+
+ }
+
+
+ /**
+ * Update a single add-on
+ *
+ * @since 0.0.1
+ *
+ * @param array $item Associative array of add-on data.
+ * @return boolean Returns `false` when an error is encountered and `true` otherwise.
+ */
+ private function update_one( &$item ) {
+
+ $addon = $this->get_addon( $item['name'] );
+
+ \WP_CLI::log( sprintf( 'Updating add-on: %s...', $addon->get( 'title' ) ) );
+ $res = $addon->update();
+ if ( is_wp_error( $res ) ) {
+ \WP_CLI::warning( $res );
+ return false;
+ }
+
+ $item['old_version'] = $item['version'];
+ $item['new_version'] = $item['update_version'];
+
+ \WP_CLI::log( $res );
+ return true;
+
+ }
+
+}
diff --git a/libraries/lifterlms-cli/src/Commands/License.php b/libraries/lifterlms-cli/src/Commands/License.php
new file mode 100644
index 0000000000..650ec36dc9
--- /dev/null
+++ b/libraries/lifterlms-cli/src/Commands/License.php
@@ -0,0 +1,105 @@
+]
+ * : The license key to be activated.
+ *
+ * @since 0.0.1
+ *
+ * @param array $args Indexed array of positional command arguments.
+ * @return null
+ */
+ public function activate( $args ) {
+
+ $res = \LLMS_Helper_Keys::activate_keys( $args[0] );
+ if ( ! empty( $res['data']['errors'] ) ) {
+ return \WP_CLI::error( $res['data']['errors'][0] );
+ } elseif ( ! empty( $res['data']['activations'] ) ) {
+ \LLMS_Helper_Keys::add_license_key( $res['data']['activations'][0] );
+ return \WP_CLI::success( sprintf( 'License key "%s" has been activated on this site.', $args[0] ) );
+ }
+
+ return \WP_CLI::error( 'An unknown error was encountered.' );
+
+ }
+
+ /**
+ * Deactivate a license key.
+ *
+ * ## OPTIONS
+ *
+ * []
+ * : The license key to be deactivated.
+ *
+ * @since 0.0.1
+ * @since 0.0.2 Use a strict comparison when checking response status.
+ *
+ * @param array $args Indexed array of positional command arguments.
+ * @return null
+ */
+ public function deactivate( $args ) {
+
+ $res = \LLMS_Helper_Keys::deactivate_keys( array( $args[0] ) );
+ if ( ! empty( $res['data']['errors'] ) ) {
+ return \WP_CLI::error( $res['data']['errors'][0] );
+ } elseif ( ! empty( $res['data']['deactivations'] ) ) {
+ \LLMS_Helper_Keys::remove_license_key( $args[0] );
+ return \WP_CLI::success( sprintf( 'License key "%s" has been deactivated from this site.', $args[0] ) );
+ } elseif ( ! empty( $res['data']['status'] ) && 200 === absint( $res['data']['status'] ) ) {
+ return \WP_CLI::error( sprintf( 'License key "%s" was not active on this site.', $args[0] ) );
+ }
+
+ return \WP_CLI::error( 'An unknown error was encountered.' );
+
+ }
+
+ /**
+ * List activated license keys.
+ *
+ * ## OPTIONS
+ *
+ * []
+ * : The license key to be deactivated.
+ *
+ * @since 0.0.1
+ *
+ * @return null
+ */
+ public function list() {
+
+ $list = array_keys( llms_helper_options()->get_license_keys() );
+
+ if ( 0 === count( $list ) ) {
+ return \WP_CLI::warning( 'No license keys found on this site.' );
+ }
+
+ foreach ( $list as $key ) {
+ \WP_CLI::log( $key );
+ }
+
+ }
+
+}
diff --git a/libraries/lifterlms-cli/src/Commands/Restful/Command.php b/libraries/lifterlms-cli/src/Commands/Restful/Command.php
new file mode 100644
index 0000000000..7204a6d949
--- /dev/null
+++ b/libraries/lifterlms-cli/src/Commands/Restful/Command.php
@@ -0,0 +1,670 @@
+name = $name;
+ $parsed_args = preg_match_all( '#\([^\)]+\)#', $route, $matches );
+ $this->resource_identifier = ! empty( $matches[0] ) ? array_pop( $matches[0] ) : null;
+ $this->route = rtrim( $route );
+ $this->schema = $schema;
+ }
+
+ /**
+ * Create a new item.
+ *
+ * @subcommand create
+ */
+ public function create_item( $args, $assoc_args ) {
+ list( $status, $body ) = $this->do_request( 'POST', $this->get_base_route(), $assoc_args );
+ if ( \WP_CLI\Utils\get_flag_value( $assoc_args, 'porcelain' ) ) {
+ \WP_CLI::line( $body['id'] );
+ } else {
+ \WP_CLI::success( "Created {$this->name} {$body['id']}." );
+ }
+ }
+
+ /**
+ * Generate some items.
+ *
+ * @subcommand generate
+ */
+ public function generate_items( $args, $assoc_args ) {
+
+ $count = $assoc_args['count'];
+ unset( $assoc_args['count'] );
+ $format = $assoc_args['format'];
+ unset( $assoc_args['format'] );
+
+ $notify = false;
+ if ( 'progress' === $format ) {
+ $notify = \WP_CLI\Utils\make_progress_bar( 'Generating items', $count );
+ }
+
+ for ( $i = 0; $i < $count; $i++ ) {
+
+ list( $status, $body ) = $this->do_request( 'POST', $this->get_base_route(), $assoc_args );
+
+ if ( 'progress' === $format ) {
+ $notify->tick();
+ } elseif ( 'ids' === $format ) {
+ echo $body['id'];
+ if ( $i < $count - 1 ) {
+ echo ' ';
+ }
+ }
+ }
+
+ if ( 'progress' === $format ) {
+ $notify->finish();
+ }
+ }
+
+ /**
+ * Delete an existing item.
+ *
+ * @subcommand delete
+ */
+ public function delete_item( $args, $assoc_args ) {
+ list( $status, $body ) = $this->do_request( 'DELETE', $this->get_filled_route( $args ), $assoc_args );
+ $id = isset( $body['previous'] ) ? $body['previous']['id'] : $body['id'];
+ if ( \WP_CLI\Utils\get_flag_value( $assoc_args, 'porcelain' ) ) {
+ \WP_CLI::line( $id );
+ } else {
+ if ( empty( $assoc_args['force'] ) ) {
+ \WP_CLI::success( "Trashed {$this->name} {$id}." );
+ } else {
+ \WP_CLI::success( "Deleted {$this->name} {$id}." );
+ }
+ }
+ }
+
+ /**
+ * Get a single item.
+ *
+ * @subcommand get
+ */
+ public function get_item( $args, $assoc_args ) {
+ list( $status, $body, $headers ) = $this->do_request( 'GET', $this->get_filled_route( $args ), $assoc_args );
+
+ if ( ! empty( $assoc_args['fields'] ) ) {
+ $body = self::limit_item_to_fields( $body, $fields );
+ }
+
+ if ( 'headers' === $assoc_args['format'] ) {
+ echo json_encode( $headers );
+ } elseif ( 'body' === $assoc_args['format'] ) {
+ echo json_encode( $body );
+ } elseif ( 'envelope' === $assoc_args['format'] ) {
+ echo json_encode(
+ array(
+ 'body' => $body,
+ 'headers' => $headers,
+ 'status' => $status,
+ 'api_url' => $this->api_url,
+ )
+ );
+ } else {
+ $formatter = $this->get_formatter( $assoc_args );
+ $formatter->display_item( $body );
+ }
+ }
+
+ /**
+ * List all items.
+ *
+ * @subcommand list
+ */
+ public function list_items( $args, $assoc_args ) {
+ if ( ! empty( $assoc_args['format'] ) && 'count' === $assoc_args['format'] ) {
+ $method = 'HEAD';
+ } else {
+ $method = 'GET';
+ }
+ list( $status, $body, $headers ) = $this->do_request( $method, $this->get_base_route(), $assoc_args );
+ if ( ! empty( $assoc_args['format'] ) && 'ids' === $assoc_args['format'] ) {
+ $items = array_column( $body, 'id' );
+ } else {
+ $items = $body;
+ }
+
+ if ( ! empty( $assoc_args['fields'] ) ) {
+ foreach ( $items as $key => $item ) {
+ $items[ $key ] = self::limit_item_to_fields( $item, $fields );
+ }
+ }
+
+ if ( ! empty( $assoc_args['format'] ) && 'count' === $assoc_args['format'] ) {
+ echo (int) $headers['X-WP-Total'];
+ } elseif ( 'headers' === $assoc_args['format'] ) {
+ echo json_encode( $headers );
+ } elseif ( 'body' === $assoc_args['format'] ) {
+ echo json_encode( $body );
+ } elseif ( 'envelope' === $assoc_args['format'] ) {
+ echo json_encode(
+ array(
+ 'body' => $body,
+ 'headers' => $headers,
+ 'status' => $status,
+ 'api_url' => $this->api_url,
+ )
+ );
+ } else {
+ $formatter = $this->get_formatter( $assoc_args );
+ $formatter->display_items( $items );
+ }
+ }
+
+ /**
+ * Compare items between environments.
+ *
+ *
+ * : Alias for the WordPress site to compare to.
+ *
+ * []
+ * : Limit comparison to a specific resource, instead of the collection.
+ *
+ * [--fields=]
+ * : Limit comparison to specific fields.
+ *
+ * @subcommand diff
+ */
+ public function diff_items( $args, $assoc_args ) {
+
+ list( $alias ) = $args;
+ if ( ! array_key_exists( $alias, \WP_CLI::get_runner()->aliases ) ) {
+ \WP_CLI::error( "Alias '{$alias}' not found." );
+ }
+ $resource = isset( $args[1] ) ? $args[1] : null;
+ $fields = \WP_CLI\Utils\get_flag_value( $assoc_args, 'fields', null );
+
+ list( $from_status, $from_body, $from_headers ) = $this->do_request( 'GET', $this->get_base_route(), array() );
+
+ $php_bin = \WP_CLI::get_php_binary();
+ $script_path = $GLOBALS['argv'][0];
+ $other_args = implode( ' ', array_map( 'escapeshellarg', array( $alias, 'rest', $this->name, 'list' ) ) );
+ $other_assoc_args = \WP_CLI\Utils\assoc_args_to_str( array( 'format' => 'envelope' ) );
+ $full_command = "{$php_bin} {$script_path} {$other_args} {$other_assoc_args}";
+ $process = \WP_CLI\Process::create(
+ $full_command,
+ null,
+ array(
+ 'HOME' => getenv( 'HOME' ),
+ 'WP_CLI_PACKAGES_DIR' => getenv( 'WP_CLI_PACKAGES_DIR' ),
+ 'WP_CLI_CONFIG_PATH' => getenv( 'WP_CLI_CONFIG_PATH' ),
+ )
+ );
+ $result = $process->run();
+ $response = json_decode( $result->stdout, true );
+ $to_headers = $response['headers'];
+ $to_body = $response['body'];
+ $to_api_url = $response['api_url'];
+
+ if ( ! is_null( $resource ) ) {
+ $field = is_numeric( $resource ) ? 'id' : 'slug';
+ $callback = function( $value ) use ( $field, $resource ) {
+ if ( isset( $value[ $field ] ) && $resource == $value[ $field ] ) {
+ return true;
+ }
+ return false;
+ };
+ foreach ( array( 'to_body', 'from_body' ) as $response_type ) {
+ $$response_type = array_filter( $$response_type, $callback );
+ }
+ }
+
+ $display_items = array();
+ do {
+ $from_item = $to_item = array();
+ if ( ! empty( $from_body ) ) {
+ $from_item = array_shift( $from_body );
+ if ( ! empty( $to_body ) && ! empty( $from_item['slug'] ) ) {
+ foreach ( $to_body as $i => $item ) {
+ if ( ! empty( $item['slug'] ) && $item['slug'] === $from_item['slug'] ) {
+ $to_item = $item;
+ unset( $to_body[ $i ] );
+ break;
+ }
+ }
+ }
+ } elseif ( ! empty( $to_body ) ) {
+ $to_item = array_shift( $to_body );
+ }
+
+ if ( ! empty( $to_item ) ) {
+ foreach ( array( 'to_item', 'from_item' ) as $item ) {
+ if ( isset( $$item['_links'] ) ) {
+ unset( $$item['_links'] );
+ }
+ }
+ $display_items[] = array(
+ 'from' => self::limit_item_to_fields( $from_item, $fields ),
+ 'to' => self::limit_item_to_fields( $to_item, $fields ),
+ );
+ }
+ } while ( count( $from_body ) || count( $to_body ) );
+
+ \WP_CLI::line( \cli\Colors::colorize( "%R(-) {$this->api_url} %G(+) {$to_api_url}%n" ) );
+ foreach ( $display_items as $display_item ) {
+ $this->show_difference(
+ $this->name,
+ array(
+ 'from' => $display_item['from'],
+ 'to' => $display_item['to'],
+ )
+ );
+ }
+ }
+
+ /**
+ * Update an existing item.
+ *
+ * @subcommand update
+ */
+ public function update_item( $args, $assoc_args ) {
+ list( $status, $body ) = $this->do_request( 'POST', $this->get_filled_route( $args ), $assoc_args );
+ if ( \WP_CLI\Utils\get_flag_value( $assoc_args, 'porcelain' ) ) {
+ \WP_CLI::line( $body['id'] );
+ } else {
+ \WP_CLI::success( "Updated {$this->name} {$body['id']}." );
+ }
+ }
+
+ /**
+ * Open an existing item in the editor
+ *
+ * @subcommand edit
+ */
+ public function edit_item( $args, $assoc_args ) {
+ $assoc_args['context'] = 'edit';
+ list( $status, $options_body ) = $this->do_request( 'OPTIONS', $this->get_filled_route( $args ), $assoc_args );
+ if ( empty( $options_body['schema'] ) ) {
+ \WP_CLI::error( 'Cannot edit - no schema found for resource.' );
+ }
+ $schema = $options_body['schema'];
+ list( $status, $resource_fields ) = $this->do_request( 'GET', $this->get_filled_route( $args ), $assoc_args );
+ $editable_fields = array();
+ foreach ( $resource_fields as $key => $value ) {
+ if ( ! isset( $schema['properties'][ $key ] ) || ! empty( $schema['properties'][ $key ]['readonly'] ) ) {
+ continue;
+ }
+ $properties = $schema['properties'][ $key ];
+ if ( isset( $properties['properties'] ) ) {
+ $parent_key = $key;
+ $properties = $properties['properties'];
+ foreach ( $value as $key => $value ) {
+ if ( isset( $properties[ $key ] ) && empty( $properties[ $key ]['readonly'] ) ) {
+ if ( ! isset( $editable_fields[ $parent_key ] ) ) {
+ $editable_fields[ $parent_key ] = array();
+ }
+ $editable_fields[ $parent_key ][ $key ] = $value;
+ }
+ }
+ continue;
+ }
+ if ( empty( $properties['readonly'] ) ) {
+ $editable_fields[ $key ] = $value;
+ }
+ }
+ if ( empty( $editable_fields ) ) {
+ \WP_CLI::error( 'Cannot edit - no editable fields found on schema.' );
+ }
+ $ret = \WP_CLI\Utils\launch_editor_for_input( \Spyc::YAMLDump( $editable_fields ), sprintf( 'Editing %s %s', $schema['title'], $args[0] ) );
+ if ( false === $ret ) {
+ \WP_CLI::warning( 'No edits made.' );
+ } else {
+ list( $status, $body ) = $this->do_request( 'POST', $this->get_filled_route( $args ), \Spyc::YAMLLoadString( $ret ) );
+ \WP_CLI::success( "Updated {$schema['title']} {$args[0]}." );
+ }
+ }
+
+ /**
+ * Do a REST Request
+ *
+ * @param string $method
+ */
+ private function do_request( $method, $route, $assoc_args ) {
+ if ( 'internal' === $this->scope ) {
+ if ( ! defined( 'REST_REQUEST' ) ) {
+ define( 'REST_REQUEST', true );
+ }
+ $request = new \WP_REST_Request( $method, $route );
+ if ( in_array( $method, array( 'POST', 'PUT' ) ) ) {
+ $request->set_body_params( $assoc_args );
+ } else {
+ foreach ( $assoc_args as $key => $value ) {
+ $request->set_param( $key, $value );
+ }
+ }
+ if ( defined( 'SAVEQUERIES' ) && SAVEQUERIES ) {
+ $original_queries = is_array( $GLOBALS['wpdb']->queries ) ? array_keys( $GLOBALS['wpdb']->queries ) : array();
+ }
+ $response = rest_do_request( $request );
+ if ( defined( 'SAVEQUERIES' ) && SAVEQUERIES ) {
+ $performed_queries = array();
+ foreach ( (array) $GLOBALS['wpdb']->queries as $key => $query ) {
+ if ( in_array( $key, $original_queries ) ) {
+ continue;
+ }
+ $performed_queries[] = $query;
+ }
+ usort(
+ $performed_queries,
+ function( $a, $b ) {
+ if ( $a[1] === $b[1] ) {
+ return 0;
+ }
+ return ( $a[1] > $b[1] ) ? -1 : 1;
+ }
+ );
+
+ $query_count = count( $performed_queries );
+ $query_total_time = 0;
+ foreach ( $performed_queries as $query ) {
+ $query_total_time += $query[1];
+ }
+ $slow_query_message = '';
+ if ( $performed_queries && 'rest' === \WP_CLI::get_config( 'debug' ) ) {
+ $slow_query_message .= '. Ordered by slowness, the queries are:' . PHP_EOL;
+ foreach ( $performed_queries as $i => $query ) {
+ $i++;
+ $bits = explode( ', ', $query[2] );
+ $backtrace = implode( ', ', array_slice( $bits, 13 ) );
+ $seconds = round( $query[1], 6 );
+ $slow_query_message .= <<as_error() ) {
+ \WP_CLI::error( $error );
+ }
+ return array( $response->get_status(), $response->get_data(), $response->get_headers() );
+ } elseif ( 'http' === $this->scope ) {
+ $headers = array();
+ if ( ! empty( $this->auth ) && 'basic' === $this->auth['type'] ) {
+ $headers['Authorization'] = 'Basic ' . base64_encode( $this->auth['username'] . ':' . $this->auth['password'] );
+ }
+ if ( 'OPTIONS' === $method ) {
+ $method = 'GET';
+ $assoc_args['_method'] = 'OPTIONS';
+ }
+ $response = \WP_CLI\Utils\http_request( $method, rtrim( $this->api_url, '/' ) . $route, $assoc_args, $headers );
+ $body = json_decode( $response->body, true );
+ if ( $response->status_code >= 400 ) {
+ if ( ! empty( $body['message'] ) ) {
+ \WP_CLI::error( $body['message'] . ' ' . json_encode( array( 'status' => $response->status_code ) ) );
+ } else {
+ switch ( $response->status_code ) {
+ case 404:
+ \WP_CLI::error( "No {$this->name} found." );
+ break;
+ default:
+ \WP_CLI::error( 'Could not complete request.' );
+ break;
+ }
+ }
+ }
+ return array( $response->status_code, json_decode( $response->body, true ), $response->headers->getAll() );
+ }
+ \WP_CLI::error( 'Invalid scope for REST command.' );
+ }
+
+ /**
+ * Get Formatter object based on supplied parameters.
+ *
+ * @param array $assoc_args Parameters passed to command. Determines formatting.
+ * @return \WP_CLI\Formatter
+ */
+ protected function get_formatter( &$assoc_args ) {
+ if ( ! empty( $assoc_args['fields'] ) ) {
+ if ( is_string( $assoc_args['fields'] ) ) {
+ $fields = explode( ',', $assoc_args['fields'] );
+ } else {
+ $fields = $assoc_args['fields'];
+ }
+ } else {
+ if ( ! empty( $assoc_args['context'] ) ) {
+ $fields = $this->get_context_fields( $assoc_args['context'] );
+ } else {
+ $fields = $this->get_context_fields( 'view' );
+ }
+ }
+ return new \WP_CLI\Formatter( $assoc_args, $fields );
+ }
+
+ /**
+ * Get a list of fields present in a given context
+ *
+ * @param string $context
+ * @return array
+ */
+ private function get_context_fields( $context ) {
+ $fields = array();
+ foreach ( $this->schema['properties'] as $key => $args ) {
+ if ( empty( $args['context'] ) || in_array( $context, $args['context'] ) ) {
+ $fields[] = $key;
+ }
+ }
+ return $fields;
+ }
+
+ /**
+ * Get the base route for this resource
+ *
+ * @return string
+ */
+ private function get_base_route() {
+ return substr( $this->route, 0, strlen( $this->route ) - strlen( $this->resource_identifier ) );
+ }
+
+ /**
+ * Fill the route based on provided $args
+ */
+ private function get_filled_route( $args ) {
+ return rtrim( $this->get_base_route(), '/' ) . '/' . $args[0];
+ }
+
+ /**
+ * Visually depict the difference between "dictated" and "current"
+ *
+ * @param array
+ */
+ private function show_difference( $slug, $difference ) {
+ $this->output_nesting_level = 0;
+ $this->nested_line( $slug . ': ' );
+ $this->recursively_show_difference( $difference['to'], $difference['from'] );
+ $this->output_nesting_level = 0;
+ }
+
+ /**
+ * Recursively output the difference between "dictated" and "current"
+ */
+ private function recursively_show_difference( $dictated, $current = null ) {
+
+ $this->output_nesting_level++;
+
+ if ( $this->is_assoc_array( $dictated ) ) {
+
+ foreach ( $dictated as $key => $value ) {
+
+ if ( $this->is_assoc_array( $value ) || is_array( $value ) ) {
+
+ $new_current = isset( $current[ $key ] ) ? $current[ $key ] : null;
+ if ( $new_current ) {
+ $this->nested_line( $key . ': ' );
+ } else {
+ $this->add_line( $key . ': ' );
+ }
+
+ $this->recursively_show_difference( $value, $new_current );
+
+ } elseif ( is_string( $value ) ) {
+
+ $pre = $key . ': ';
+
+ if ( isset( $current[ $key ] ) && $current[ $key ] !== $value ) {
+
+ $this->remove_line( $pre . $current[ $key ] );
+ $this->add_line( $pre . $value );
+
+ } elseif ( ! isset( $current[ $key ] ) ) {
+
+ $this->add_line( $pre . $value );
+
+ }
+ }
+ }
+ } elseif ( is_array( $dictated ) ) {
+
+ foreach ( $dictated as $value ) {
+
+ if ( ! $current
+ || ! in_array( $value, $current ) ) {
+ $this->add_line( '- ' . $value );
+ }
+ }
+ } elseif ( is_string( $value ) ) {
+
+ $pre = $key . ': ';
+
+ if ( isset( $current[ $key ] ) && $current[ $key ] !== $value ) {
+
+ $this->remove_line( $pre . $current[ $key ] );
+ $this->add_line( $pre . $value );
+
+ } elseif ( ! isset( $current[ $key ] ) ) {
+
+ $this->add_line( $pre . $value );
+
+ } else {
+
+ $this->nested_line( $pre );
+
+ }
+ }
+
+ $this->output_nesting_level--;
+
+ }
+
+ /**
+ * Output a line to be added
+ *
+ * @param string
+ */
+ private function add_line( $line ) {
+ $this->nested_line( $line, 'add' );
+ }
+
+ /**
+ * Output a line to be removed
+ *
+ * @param string
+ */
+ private function remove_line( $line ) {
+ $this->nested_line( $line, 'remove' );
+ }
+
+ /**
+ * Output a line that's appropriately nested
+ */
+ private function nested_line( $line, $change = false ) {
+
+ if ( 'add' == $change ) {
+ $color = '%G';
+ $label = '+ ';
+ } elseif ( 'remove' == $change ) {
+ $color = '%R';
+ $label = '- ';
+ } else {
+ $color = false;
+ $label = false;
+ }
+
+ $spaces = ( $this->output_nesting_level * 2 ) + 2;
+ if ( $color && $label ) {
+ $line = \cli\Colors::colorize( "{$color}{$label}" ) . $line . \cli\Colors::colorize( '%n' );
+ $spaces = $spaces - 2;
+ }
+ \WP_CLI::line( str_pad( ' ', $spaces ) . $line );
+ }
+
+ /**
+ * Whether or not this is an associative array
+ *
+ * @param array
+ * @return bool
+ */
+ private function is_assoc_array( $array ) {
+
+ if ( ! is_array( $array ) ) {
+ return false;
+ }
+
+ return array_keys( $array ) !== range( 0, count( $array ) - 1 );
+ }
+
+ /**
+ * Reduce an item to specific fields.
+ *
+ * @param array $item
+ * @param array $fields
+ * @return array
+ */
+ private static function limit_item_to_fields( $item, $fields ) {
+ if ( empty( $fields ) ) {
+ return $item;
+ }
+ if ( is_string( $fields ) ) {
+ $fields = explode( ',', $fields );
+ }
+ foreach ( $item as $i => $field ) {
+ if ( ! in_array( $i, $fields ) ) {
+ unset( $item[ $i ] );
+ }
+ }
+ return $item;
+ }
+
+}
diff --git a/libraries/lifterlms-cli/src/Commands/Restful/Runner.php b/libraries/lifterlms-cli/src/Commands/Restful/Runner.php
new file mode 100644
index 0000000000..a6d2a3f739
--- /dev/null
+++ b/libraries/lifterlms-cli/src/Commands/Restful/Runner.php
@@ -0,0 +1,391 @@
+set_param( 'context', 'help' );
+
+ $response = $wp_rest_server->dispatch( $request );
+ $response_data = $response->get_data();
+ if ( empty( $response_data ) ) {
+ return;
+ }
+
+ foreach ( $response_data['routes'] as $route => $route_data ) {
+
+ // Skip non LifterLMS routes.
+ if ( 0 !== strpos( $route, '/llms/' ) ) {
+ continue;
+ }
+
+ if ( empty( $route_data['schema']['title'] ) ) {
+ \WP_CLI::debug( "No schema title found for {$route}, skipping LifterLMS CLI REST command registration.", 'lifterlms' );
+ continue;
+ }
+
+ $name = $route_data['schema']['title'];
+ $rest_command = new Command( $name, $route, $route_data['schema'] );
+ self::register_route_commands( $rest_command, $route, $route_data );
+
+ }
+
+ }
+
+
+ private static function get_command_root_desc( $resource ) {
+ $resource = str_replace( array( '-', 'students', 'api' ), array( ' ', 'student', 'API' ), $resource );
+ if ( 's' !== substr( $resource, -1 ) ) {
+ $resource .= 's';
+ }
+ return sprintf( 'Manage %s.', $resource );
+ }
+
+ private static function get_command_short_desc( $command, $resource ) {
+
+ $before = '';
+ $after = '';
+
+
+ switch ( $command ) {
+ case 'create':
+ $before = 'Creates a new';
+ break;
+
+ case 'delete':
+ $before = 'Deletes an existing';
+ break;
+
+ case 'diff':
+ $before = 'Compare';
+ $resource = self::pluralize_resource( $resource );
+ $after = 'between environments';
+ break;
+
+ case 'edit':
+ $before = 'Launches system editor to edit the';
+ $after = 'content';
+ break;
+
+ case 'generate':
+ $before = 'Generates some';
+ $resource = self::pluralize_resource( $resource );
+ break;
+
+ case 'get':
+ $before = 'Gets details about a';
+ break;
+
+ case 'list':
+ $before = 'Gets a list of ';
+ $resource = self::pluralize_resource( $resource );
+ break;
+
+ case 'update':
+ $before = 'Updates an existing';
+ break;
+ }
+
+ return trim( implode( ' ', array( $before, $resource, $after ) ) ) . '.';
+ }
+
+ private static function pluralize_resource( $resource ) {
+
+ switch ( $resource ) {
+ default:
+ $resource .= 's';
+ }
+
+ return $resource;
+ }
+
+ private static function get_supported_commands( $route, $route_data ) {
+
+ $supported_commands = array();
+ foreach ( $route_data['endpoints'] as $endpoint ) {
+
+ $parsed_args = preg_match_all( '#\([^\)]+\)#', $route, $matches );
+ $resource_id = ! empty( $matches[0] ) ? array_pop( $matches[0] ) : null;
+ $trimmed_route = rtrim( $route );
+ $is_singular = $resource_id === substr( $trimmed_route, - strlen( $resource_id ) );
+
+ // List a collection
+ if ( array( 'GET' ) == $endpoint['methods']
+ && ! $is_singular ) {
+ $supported_commands['list'] = ! empty( $endpoint['args'] ) ? $endpoint['args'] : array();
+ }
+
+ // Create a specific resource
+ if ( array( 'POST' ) == $endpoint['methods']
+ && ! $is_singular ) {
+ $supported_commands['create'] = ! empty( $endpoint['args'] ) ? $endpoint['args'] : array();
+ }
+
+ // Get a specific resource
+ if ( array( 'GET' ) == $endpoint['methods']
+ && $is_singular ) {
+ $supported_commands['get'] = ! empty( $endpoint['args'] ) ? $endpoint['args'] : array();
+ }
+
+ // Update a specific resource
+ if ( in_array( 'POST', $endpoint['methods'] )
+ && $is_singular ) {
+ $supported_commands['update'] = ! empty( $endpoint['args'] ) ? $endpoint['args'] : array();
+ }
+
+ // Delete a specific resource
+ if ( array( 'DELETE' ) == $endpoint['methods']
+ && $is_singular ) {
+ $supported_commands['delete'] = ! empty( $endpoint['args'] ) ? $endpoint['args'] : array();
+ }
+ }
+
+ return $supported_commands;
+
+ }
+
+ public static function before_invoke_command() {
+
+ /**
+ * If `--user` was passed the user will already be set, otherwise there won't be a user.
+ *
+ * It is "safe" to assume that someone using the CLI has admin access and we'll set the current
+ * user to be the first admin we find in the DB that has the `manage_options` cap.
+ */
+ if ( ! get_current_user_id() ) {
+ $user = \LLMS_Install::get_can_install_user_id();
+ if ( $user ) {
+ wp_set_current_user( $user );
+ }
+ }
+
+ if ( \WP_CLI::get_config( 'debug' ) && ! defined( 'SAVEQUERIES' ) ) {
+ define( 'SAVEQUERIES', true );
+ }
+
+ }
+
+ /**
+ * Register WP-CLI commands for all endpoints on a route
+ *
+ * @param string
+ * @param array $endpoints
+ */
+ private static function register_route_commands( $rest_command, $route, $route_data ) {
+
+ $resource = str_replace( array( 'llms_', '_' ), array( '', '-' ), $route_data['schema']['title'] );
+ $parent = "llms {$resource}";
+
+ $supported_commands = self::get_supported_commands( $route, $route_data );
+ foreach ( $supported_commands as $command => $endpoint_args ) {
+
+ $synopsis = array();
+ if ( in_array( $command, array( 'delete', 'get', 'update' ) ) ) {
+ $synopsis[] = array(
+ 'name' => 'id',
+ 'type' => 'positional',
+ 'description' => 'The id for the resource.',
+ 'optional' => false,
+ );
+ }
+
+ foreach ( $endpoint_args as $name => $args ) {
+ $arg_reg = array(
+ 'name' => $name,
+ 'type' => 'assoc',
+ 'description' => ! empty( $args['description'] ) ? $args['description'] : '',
+ 'optional' => empty( $args['required'] ) ? true : false,
+ );
+ foreach ( array( 'enum', 'default' ) as $key ) {
+ if ( isset( $args[ $key ] ) ) {
+ $new_key = 'enum' === $key ? 'options' : $key;
+ $arg_reg[ $new_key ] = $args[ $key ];
+ }
+ }
+ $synopsis[] = $arg_reg;
+ }
+
+ if ( in_array( $command, array( 'list', 'get' ) ) ) {
+ $synopsis[] = array(
+ 'name' => 'fields',
+ 'type' => 'assoc',
+ 'description' => 'Limit response to specific fields. Defaults to all fields.',
+ 'optional' => true,
+ );
+ $synopsis[] = array(
+ 'name' => 'field',
+ 'type' => 'assoc',
+ 'description' => 'Get the value of an individual field.',
+ 'optional' => true,
+ );
+ $synopsis[] = array(
+ 'name' => 'format',
+ 'type' => 'assoc',
+ 'description' => 'Render response in a particular format.',
+ 'optional' => true,
+ 'default' => 'table',
+ 'options' => array(
+ 'table',
+ 'json',
+ 'csv',
+ 'ids',
+ 'yaml',
+ 'count',
+ 'headers',
+ 'body',
+ 'envelope',
+ ),
+ );
+ }
+
+ if ( in_array( $command, array( 'create', 'update', 'delete' ) ) ) {
+ $synopsis[] = array(
+ 'name' => 'porcelain',
+ 'type' => 'flag',
+ 'description' => 'Output just the id when the operation is successful.',
+ 'optional' => true,
+ );
+ }
+
+ $methods = array(
+ 'list' => 'list_items',
+ 'create' => 'create_item',
+ 'delete' => 'delete_item',
+ 'get' => 'get_item',
+ 'update' => 'update_item',
+ );
+
+ // Add the root command, eg: wp llms course.
+ \WP_CLI::add_command(
+ "{$parent}",
+ $rest_command,
+ array(
+ 'shortdesc' => self::get_command_root_desc( $resource ),
+ )
+ );
+
+ // Register main subcommands, eg: wp llms course create, wp llms course delete, etc...
+ \WP_CLI::add_command(
+ "{$parent} {$command}",
+ array( $rest_command, $methods[ $command ] ),
+ array(
+ 'shortdesc' => self::get_command_short_desc( $command, $resource ),
+ 'synopsis' => $synopsis,
+ 'before_invoke' => array( __CLASS__, 'before_invoke_command' ),
+ )
+ );
+
+ // If listing is supported, add the diff command.
+ if ( 'list' === $command ) {
+ \WP_CLI::add_command(
+ "{$parent} diff",
+ array( $rest_command, 'diff_items' ),
+ array(
+ 'shortdesc' => self::get_command_short_desc( 'diff', $resource ),
+ 'before_invoke' => array( __CLASS__, 'before_invoke_command' ),
+ )
+ );
+ }
+
+ // If creation is supported, add the generate command.
+ if ( 'create' === $command ) {
+ \WP_CLI::add_command(
+ "{$parent} generate",
+ array( $rest_command, 'generate_items' ),
+ array(
+ 'shortdesc' => self::get_command_short_desc( 'generate', $resource ),
+ 'synopsis' => self::get_generate_command_synopsis( $synopsis ),
+ 'before_invoke' => array( __CLASS__, 'before_invoke_command' ),
+ )
+ );
+ }
+
+
+ // If updating and getting is supported, add the edit command.
+ if ( 'update' === $command && array_key_exists( 'get', $supported_commands ) ) {
+ $synopsis = array();
+ $synopsis[] = array(
+ 'name' => 'id',
+ 'type' => 'positional',
+ 'description' => 'The id for the resource.',
+ 'optional' => false,
+ );
+ \WP_CLI::add_command(
+ "{$parent} edit",
+ array( $rest_command, 'edit_item' ),
+ array(
+ 'shortdesc' => self::get_command_short_desc( 'edit', $resource ),
+ 'synopsis' => $synopsis,
+ 'before_invoke' => array( __CLASS__, 'before_invoke_command' ),
+ )
+ );
+ }
+ }
+ }
+
+ private static function get_generate_command_synopsis( $create_synopsis ) {
+
+ $generate_synopsis = array(
+ array(
+ 'name' => 'count',
+ 'type' => 'assoc',
+ 'description' => 'Number of items to generate.',
+ 'optional' => true,
+ 'default' => 10,
+ ),
+ array(
+ 'name' => 'format',
+ 'type' => 'assoc',
+ 'description' => 'Render generation in specific format.',
+ 'optional' => true,
+ 'default' => 'progress',
+ 'options' => array(
+ 'progress',
+ 'ids',
+ ),
+ ),
+ );
+
+ return array_merge( $generate_synopsis, $create_synopsis );
+
+ }
+
+}
diff --git a/libraries/lifterlms-cli/src/Commands/Root.php b/libraries/lifterlms-cli/src/Commands/Root.php
new file mode 100644
index 0000000000..0da54b4a48
--- /dev/null
+++ b/libraries/lifterlms-cli/src/Commands/Root.php
@@ -0,0 +1,83 @@
+]
+ * : The slug of the LifterLMS plugin or theme. Default: lifterlms.
+ *
+ * ## EXAMPLES
+ *
+ * # Show the LifterLMS core plugin version
+ * wp llms version
+ *
+ * # Show the LifterLMS core plugin version
+ * wp llms version core
+ *
+ * # Show an add-on version without the "lifterlms-" prefix.
+ * wp llms version groups
+ *
+ * # Show an add-on version with the "lifterlms-" prefix.
+ * wp llms version lifterlms-assignments
+ *
+ * @since 0.0.1
+ * @since 0.0.2 Remove `--db` option. This will be implemented in a separate command.
+ *
+ * @param array $args Indexed array of positional command arguments.
+ * @param array $assoc_args Associative array of command options.
+ * @return null
+ */
+ public function version( $args, $assoc_args ) {
+
+ $slug = empty( $args[0] ) ? 'core' : $args[0];
+ if ( in_array( $slug, array( 'core', 'lifterlms' ), true ) ) {
+ return \WP_CLI::log( llms()->version );
+ }
+
+ $addon = $this->get_addon( $slug );
+ if ( empty( $addon ) ) {
+ return \WP_CLI::error( 'Invalid slug.' );
+ }
+
+ if ( $addon->is_installed() ) {
+ return \WP_CLI::log( $addon->get_installed_version() );
+ }
+
+ return \WP_CLI::error(
+ sprintf(
+ "The requested add-on is not installed. Run 'wp llms addon install %s.' to install it.",
+ $args[0]
+ )
+ );
+
+ }
+
+}
diff --git a/libraries/lifterlms-cli/src/Main.php b/libraries/lifterlms-cli/src/Main.php
new file mode 100644
index 0000000000..4cd19b55e3
--- /dev/null
+++ b/libraries/lifterlms-cli/src/Main.php
@@ -0,0 +1,140 @@
+version );
+ }
+
+ // Get started (after REST).
+ add_action( 'plugins_loaded', array( $this, 'init' ) );
+
+ }
+
+ /**
+ * Add all LifterLMS CLI commands
+ *
+ * This includes a separate file so that commands can be included on their own
+ * when generating documentation.
+ *
+ * @since 0.0.1
+ *
+ * @return void
+ */
+ public function commands() {
+ require_once LLMS_CLI_PLUGIN_DIR . 'src/commands.php';
+ }
+
+ /**
+ * Register WP_CLI hooks
+ *
+ * Loads all commands and sets up license and addon commands to be aborted
+ * if the LifterLMS Helper is not present.
+ *
+ * @since 0.0.1
+ *
+ * @return void
+ */
+ private function hooks() {
+
+ \WP_CLI::add_hook( 'after_wp_load', array( $this, 'commands' ) );
+
+ // If the Helper doesn't exist abort command addition.
+ if ( ! class_exists( 'LifterLMS_Helper' ) ) {
+ $helper_commands = array(
+ 'license',
+ 'addon install',
+ 'addon uninstall',
+ 'addon activate',
+ 'addon deactivate',
+ 'addon update',
+ );
+ foreach ( $helper_commands as $command ) {
+ \WP_CLI::add_hook(
+ "before_add_command:llms {$command}",
+ function( CommandAddition $command_addition ) {
+ $command_addition->abort( 'The LifterLMS Helper is required to use this command.' );
+ }
+ );
+ }
+ }
+
+ }
+ /**
+ * Include all required files and classes
+ *
+ * @since [version
+ *
+ * @return void
+ */
+ public function init() {
+
+ // Only load if we have the minimum LifterLMS version installed & activated.
+ if ( function_exists( 'llms' ) && version_compare( '5.0.0', llms()->version, '<=' ) ) {
+
+ $this->hooks();
+
+ }
+
+ }
+
+}
diff --git a/libraries/lifterlms-cli/src/commands.php b/libraries/lifterlms-cli/src/commands.php
new file mode 100644
index 0000000000..e97b5f5d89
--- /dev/null
+++ b/libraries/lifterlms-cli/src/commands.php
@@ -0,0 +1,42 @@
+
+ * Jordi Boggiano
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Composer\Autoload;
+
+/**
+ * ClassLoader implements a PSR-0, PSR-4 and classmap class loader.
+ *
+ * $loader = new \Composer\Autoload\ClassLoader();
+ *
+ * // register classes with namespaces
+ * $loader->add('Symfony\Component', __DIR__.'/component');
+ * $loader->add('Symfony', __DIR__.'/framework');
+ *
+ * // activate the autoloader
+ * $loader->register();
+ *
+ * // to enable searching the include path (eg. for PEAR packages)
+ * $loader->setUseIncludePath(true);
+ *
+ * In this example, if you try to use a class in the Symfony\Component
+ * namespace or one of its children (Symfony\Component\Console for instance),
+ * the autoloader will first look for the class under the component/
+ * directory, and it will then fallback to the framework/ directory if not
+ * found before giving up.
+ *
+ * This class is loosely based on the Symfony UniversalClassLoader.
+ *
+ * @author Fabien Potencier
+ * @author Jordi Boggiano
+ * @see https://www.php-fig.org/psr/psr-0/
+ * @see https://www.php-fig.org/psr/psr-4/
+ */
+class ClassLoader
+{
+ /** @var ?string */
+ private $vendorDir;
+
+ // PSR-4
+ /**
+ * @var array[]
+ * @psalm-var array>
+ */
+ private $prefixLengthsPsr4 = array();
+ /**
+ * @var array[]
+ * @psalm-var array>
+ */
+ private $prefixDirsPsr4 = array();
+ /**
+ * @var array[]
+ * @psalm-var array
+ */
+ private $fallbackDirsPsr4 = array();
+
+ // PSR-0
+ /**
+ * @var array[]
+ * @psalm-var array>
+ */
+ private $prefixesPsr0 = array();
+ /**
+ * @var array[]
+ * @psalm-var array
+ */
+ private $fallbackDirsPsr0 = array();
+
+ /** @var bool */
+ private $useIncludePath = false;
+
+ /**
+ * @var string[]
+ * @psalm-var array
+ */
+ private $classMap = array();
+
+ /** @var bool */
+ private $classMapAuthoritative = false;
+
+ /**
+ * @var bool[]
+ * @psalm-var array
+ */
+ private $missingClasses = array();
+
+ /** @var ?string */
+ private $apcuPrefix;
+
+ /**
+ * @var self[]
+ */
+ private static $registeredLoaders = array();
+
+ /**
+ * @param ?string $vendorDir
+ */
+ public function __construct($vendorDir = null)
+ {
+ $this->vendorDir = $vendorDir;
+ }
+
+ /**
+ * @return string[]
+ */
+ public function getPrefixes()
+ {
+ if (!empty($this->prefixesPsr0)) {
+ return call_user_func_array('array_merge', array_values($this->prefixesPsr0));
+ }
+
+ return array();
+ }
+
+ /**
+ * @return array[]
+ * @psalm-return array>
+ */
+ public function getPrefixesPsr4()
+ {
+ return $this->prefixDirsPsr4;
+ }
+
+ /**
+ * @return array[]
+ * @psalm-return array
+ */
+ public function getFallbackDirs()
+ {
+ return $this->fallbackDirsPsr0;
+ }
+
+ /**
+ * @return array[]
+ * @psalm-return array
+ */
+ public function getFallbackDirsPsr4()
+ {
+ return $this->fallbackDirsPsr4;
+ }
+
+ /**
+ * @return string[] Array of classname => path
+ * @psalm-var array
+ */
+ public function getClassMap()
+ {
+ return $this->classMap;
+ }
+
+ /**
+ * @param string[] $classMap Class to filename map
+ * @psalm-param array $classMap
+ *
+ * @return void
+ */
+ public function addClassMap(array $classMap)
+ {
+ if ($this->classMap) {
+ $this->classMap = array_merge($this->classMap, $classMap);
+ } else {
+ $this->classMap = $classMap;
+ }
+ }
+
+ /**
+ * Registers a set of PSR-0 directories for a given prefix, either
+ * appending or prepending to the ones previously set for this prefix.
+ *
+ * @param string $prefix The prefix
+ * @param string[]|string $paths The PSR-0 root directories
+ * @param bool $prepend Whether to prepend the directories
+ *
+ * @return void
+ */
+ public function add($prefix, $paths, $prepend = false)
+ {
+ if (!$prefix) {
+ if ($prepend) {
+ $this->fallbackDirsPsr0 = array_merge(
+ (array) $paths,
+ $this->fallbackDirsPsr0
+ );
+ } else {
+ $this->fallbackDirsPsr0 = array_merge(
+ $this->fallbackDirsPsr0,
+ (array) $paths
+ );
+ }
+
+ return;
+ }
+
+ $first = $prefix[0];
+ if (!isset($this->prefixesPsr0[$first][$prefix])) {
+ $this->prefixesPsr0[$first][$prefix] = (array) $paths;
+
+ return;
+ }
+ if ($prepend) {
+ $this->prefixesPsr0[$first][$prefix] = array_merge(
+ (array) $paths,
+ $this->prefixesPsr0[$first][$prefix]
+ );
+ } else {
+ $this->prefixesPsr0[$first][$prefix] = array_merge(
+ $this->prefixesPsr0[$first][$prefix],
+ (array) $paths
+ );
+ }
+ }
+
+ /**
+ * Registers a set of PSR-4 directories for a given namespace, either
+ * appending or prepending to the ones previously set for this namespace.
+ *
+ * @param string $prefix The prefix/namespace, with trailing '\\'
+ * @param string[]|string $paths The PSR-4 base directories
+ * @param bool $prepend Whether to prepend the directories
+ *
+ * @throws \InvalidArgumentException
+ *
+ * @return void
+ */
+ public function addPsr4($prefix, $paths, $prepend = false)
+ {
+ if (!$prefix) {
+ // Register directories for the root namespace.
+ if ($prepend) {
+ $this->fallbackDirsPsr4 = array_merge(
+ (array) $paths,
+ $this->fallbackDirsPsr4
+ );
+ } else {
+ $this->fallbackDirsPsr4 = array_merge(
+ $this->fallbackDirsPsr4,
+ (array) $paths
+ );
+ }
+ } elseif (!isset($this->prefixDirsPsr4[$prefix])) {
+ // Register directories for a new namespace.
+ $length = strlen($prefix);
+ if ('\\' !== $prefix[$length - 1]) {
+ throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
+ }
+ $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
+ $this->prefixDirsPsr4[$prefix] = (array) $paths;
+ } elseif ($prepend) {
+ // Prepend directories for an already registered namespace.
+ $this->prefixDirsPsr4[$prefix] = array_merge(
+ (array) $paths,
+ $this->prefixDirsPsr4[$prefix]
+ );
+ } else {
+ // Append directories for an already registered namespace.
+ $this->prefixDirsPsr4[$prefix] = array_merge(
+ $this->prefixDirsPsr4[$prefix],
+ (array) $paths
+ );
+ }
+ }
+
+ /**
+ * Registers a set of PSR-0 directories for a given prefix,
+ * replacing any others previously set for this prefix.
+ *
+ * @param string $prefix The prefix
+ * @param string[]|string $paths The PSR-0 base directories
+ *
+ * @return void
+ */
+ public function set($prefix, $paths)
+ {
+ if (!$prefix) {
+ $this->fallbackDirsPsr0 = (array) $paths;
+ } else {
+ $this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths;
+ }
+ }
+
+ /**
+ * Registers a set of PSR-4 directories for a given namespace,
+ * replacing any others previously set for this namespace.
+ *
+ * @param string $prefix The prefix/namespace, with trailing '\\'
+ * @param string[]|string $paths The PSR-4 base directories
+ *
+ * @throws \InvalidArgumentException
+ *
+ * @return void
+ */
+ public function setPsr4($prefix, $paths)
+ {
+ if (!$prefix) {
+ $this->fallbackDirsPsr4 = (array) $paths;
+ } else {
+ $length = strlen($prefix);
+ if ('\\' !== $prefix[$length - 1]) {
+ throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
+ }
+ $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
+ $this->prefixDirsPsr4[$prefix] = (array) $paths;
+ }
+ }
+
+ /**
+ * Turns on searching the include path for class files.
+ *
+ * @param bool $useIncludePath
+ *
+ * @return void
+ */
+ public function setUseIncludePath($useIncludePath)
+ {
+ $this->useIncludePath = $useIncludePath;
+ }
+
+ /**
+ * Can be used to check if the autoloader uses the include path to check
+ * for classes.
+ *
+ * @return bool
+ */
+ public function getUseIncludePath()
+ {
+ return $this->useIncludePath;
+ }
+
+ /**
+ * Turns off searching the prefix and fallback directories for classes
+ * that have not been registered with the class map.
+ *
+ * @param bool $classMapAuthoritative
+ *
+ * @return void
+ */
+ public function setClassMapAuthoritative($classMapAuthoritative)
+ {
+ $this->classMapAuthoritative = $classMapAuthoritative;
+ }
+
+ /**
+ * Should class lookup fail if not found in the current class map?
+ *
+ * @return bool
+ */
+ public function isClassMapAuthoritative()
+ {
+ return $this->classMapAuthoritative;
+ }
+
+ /**
+ * APCu prefix to use to cache found/not-found classes, if the extension is enabled.
+ *
+ * @param string|null $apcuPrefix
+ *
+ * @return void
+ */
+ public function setApcuPrefix($apcuPrefix)
+ {
+ $this->apcuPrefix = function_exists('apcu_fetch') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN) ? $apcuPrefix : null;
+ }
+
+ /**
+ * The APCu prefix in use, or null if APCu caching is not enabled.
+ *
+ * @return string|null
+ */
+ public function getApcuPrefix()
+ {
+ return $this->apcuPrefix;
+ }
+
+ /**
+ * Registers this instance as an autoloader.
+ *
+ * @param bool $prepend Whether to prepend the autoloader or not
+ *
+ * @return void
+ */
+ public function register($prepend = false)
+ {
+ spl_autoload_register(array($this, 'loadClass'), true, $prepend);
+
+ if (null === $this->vendorDir) {
+ return;
+ }
+
+ if ($prepend) {
+ self::$registeredLoaders = array($this->vendorDir => $this) + self::$registeredLoaders;
+ } else {
+ unset(self::$registeredLoaders[$this->vendorDir]);
+ self::$registeredLoaders[$this->vendorDir] = $this;
+ }
+ }
+
+ /**
+ * Unregisters this instance as an autoloader.
+ *
+ * @return void
+ */
+ public function unregister()
+ {
+ spl_autoload_unregister(array($this, 'loadClass'));
+
+ if (null !== $this->vendorDir) {
+ unset(self::$registeredLoaders[$this->vendorDir]);
+ }
+ }
+
+ /**
+ * Loads the given class or interface.
+ *
+ * @param string $class The name of the class
+ * @return true|null True if loaded, null otherwise
+ */
+ public function loadClass($class)
+ {
+ if ($file = $this->findFile($class)) {
+ includeFile($file);
+
+ return true;
+ }
+
+ return null;
+ }
+
+ /**
+ * Finds the path to the file where the class is defined.
+ *
+ * @param string $class The name of the class
+ *
+ * @return string|false The path if found, false otherwise
+ */
+ public function findFile($class)
+ {
+ // class map lookup
+ if (isset($this->classMap[$class])) {
+ return $this->classMap[$class];
+ }
+ if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) {
+ return false;
+ }
+ if (null !== $this->apcuPrefix) {
+ $file = apcu_fetch($this->apcuPrefix.$class, $hit);
+ if ($hit) {
+ return $file;
+ }
+ }
+
+ $file = $this->findFileWithExtension($class, '.php');
+
+ // Search for Hack files if we are running on HHVM
+ if (false === $file && defined('HHVM_VERSION')) {
+ $file = $this->findFileWithExtension($class, '.hh');
+ }
+
+ if (null !== $this->apcuPrefix) {
+ apcu_add($this->apcuPrefix.$class, $file);
+ }
+
+ if (false === $file) {
+ // Remember that this class does not exist.
+ $this->missingClasses[$class] = true;
+ }
+
+ return $file;
+ }
+
+ /**
+ * Returns the currently registered loaders indexed by their corresponding vendor directories.
+ *
+ * @return self[]
+ */
+ public static function getRegisteredLoaders()
+ {
+ return self::$registeredLoaders;
+ }
+
+ /**
+ * @param string $class
+ * @param string $ext
+ * @return string|false
+ */
+ private function findFileWithExtension($class, $ext)
+ {
+ // PSR-4 lookup
+ $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext;
+
+ $first = $class[0];
+ if (isset($this->prefixLengthsPsr4[$first])) {
+ $subPath = $class;
+ while (false !== $lastPos = strrpos($subPath, '\\')) {
+ $subPath = substr($subPath, 0, $lastPos);
+ $search = $subPath . '\\';
+ if (isset($this->prefixDirsPsr4[$search])) {
+ $pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1);
+ foreach ($this->prefixDirsPsr4[$search] as $dir) {
+ if (file_exists($file = $dir . $pathEnd)) {
+ return $file;
+ }
+ }
+ }
+ }
+ }
+
+ // PSR-4 fallback dirs
+ foreach ($this->fallbackDirsPsr4 as $dir) {
+ if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) {
+ return $file;
+ }
+ }
+
+ // PSR-0 lookup
+ if (false !== $pos = strrpos($class, '\\')) {
+ // namespaced class name
+ $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1)
+ . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR);
+ } else {
+ // PEAR-like class name
+ $logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext;
+ }
+
+ if (isset($this->prefixesPsr0[$first])) {
+ foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) {
+ if (0 === strpos($class, $prefix)) {
+ foreach ($dirs as $dir) {
+ if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
+ return $file;
+ }
+ }
+ }
+ }
+ }
+
+ // PSR-0 fallback dirs
+ foreach ($this->fallbackDirsPsr0 as $dir) {
+ if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
+ return $file;
+ }
+ }
+
+ // PSR-0 include paths.
+ if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) {
+ return $file;
+ }
+
+ return false;
+ }
+}
+
+/**
+ * Scope isolated include.
+ *
+ * Prevents access to $this/self from included files.
+ *
+ * @param string $file
+ * @return void
+ * @private
+ */
+function includeFile($file)
+{
+ include $file;
+}
diff --git a/libraries/lifterlms-cli/vendor/composer/InstalledVersions.php b/libraries/lifterlms-cli/vendor/composer/InstalledVersions.php
new file mode 100644
index 0000000000..d50e0c9fcc
--- /dev/null
+++ b/libraries/lifterlms-cli/vendor/composer/InstalledVersions.php
@@ -0,0 +1,350 @@
+
+ * Jordi Boggiano
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Composer;
+
+use Composer\Autoload\ClassLoader;
+use Composer\Semver\VersionParser;
+
+/**
+ * This class is copied in every Composer installed project and available to all
+ *
+ * See also https://getcomposer.org/doc/07-runtime.md#installed-versions
+ *
+ * To require its presence, you can require `composer-runtime-api ^2.0`
+ */
+class InstalledVersions
+{
+ /**
+ * @var mixed[]|null
+ * @psalm-var array{root: array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string, type: string}, versions: array}|array{}|null
+ */
+ private static $installed;
+
+ /**
+ * @var bool|null
+ */
+ private static $canGetVendors;
+
+ /**
+ * @var array[]
+ * @psalm-var array}>
+ */
+ private static $installedByVendor = array();
+
+ /**
+ * Returns a list of all package names which are present, either by being installed, replaced or provided
+ *
+ * @return string[]
+ * @psalm-return list
+ */
+ public static function getInstalledPackages()
+ {
+ $packages = array();
+ foreach (self::getInstalled() as $installed) {
+ $packages[] = array_keys($installed['versions']);
+ }
+
+ if (1 === \count($packages)) {
+ return $packages[0];
+ }
+
+ return array_keys(array_flip(\call_user_func_array('array_merge', $packages)));
+ }
+
+ /**
+ * Returns a list of all package names with a specific type e.g. 'library'
+ *
+ * @param string $type
+ * @return string[]
+ * @psalm-return list
+ */
+ public static function getInstalledPackagesByType($type)
+ {
+ $packagesByType = array();
+
+ foreach (self::getInstalled() as $installed) {
+ foreach ($installed['versions'] as $name => $package) {
+ if (isset($package['type']) && $package['type'] === $type) {
+ $packagesByType[] = $name;
+ }
+ }
+ }
+
+ return $packagesByType;
+ }
+
+ /**
+ * Checks whether the given package is installed
+ *
+ * This also returns true if the package name is provided or replaced by another package
+ *
+ * @param string $packageName
+ * @param bool $includeDevRequirements
+ * @return bool
+ */
+ public static function isInstalled($packageName, $includeDevRequirements = true)
+ {
+ foreach (self::getInstalled() as $installed) {
+ if (isset($installed['versions'][$packageName])) {
+ return $includeDevRequirements || empty($installed['versions'][$packageName]['dev_requirement']);
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Checks whether the given package satisfies a version constraint
+ *
+ * e.g. If you want to know whether version 2.3+ of package foo/bar is installed, you would call:
+ *
+ * Composer\InstalledVersions::satisfies(new VersionParser, 'foo/bar', '^2.3')
+ *
+ * @param VersionParser $parser Install composer/semver to have access to this class and functionality
+ * @param string $packageName
+ * @param string|null $constraint A version constraint to check for, if you pass one you have to make sure composer/semver is required by your package
+ * @return bool
+ */
+ public static function satisfies(VersionParser $parser, $packageName, $constraint)
+ {
+ $constraint = $parser->parseConstraints($constraint);
+ $provided = $parser->parseConstraints(self::getVersionRanges($packageName));
+
+ return $provided->matches($constraint);
+ }
+
+ /**
+ * Returns a version constraint representing all the range(s) which are installed for a given package
+ *
+ * It is easier to use this via isInstalled() with the $constraint argument if you need to check
+ * whether a given version of a package is installed, and not just whether it exists
+ *
+ * @param string $packageName
+ * @return string Version constraint usable with composer/semver
+ */
+ public static function getVersionRanges($packageName)
+ {
+ foreach (self::getInstalled() as $installed) {
+ if (!isset($installed['versions'][$packageName])) {
+ continue;
+ }
+
+ $ranges = array();
+ if (isset($installed['versions'][$packageName]['pretty_version'])) {
+ $ranges[] = $installed['versions'][$packageName]['pretty_version'];
+ }
+ if (array_key_exists('aliases', $installed['versions'][$packageName])) {
+ $ranges = array_merge($ranges, $installed['versions'][$packageName]['aliases']);
+ }
+ if (array_key_exists('replaced', $installed['versions'][$packageName])) {
+ $ranges = array_merge($ranges, $installed['versions'][$packageName]['replaced']);
+ }
+ if (array_key_exists('provided', $installed['versions'][$packageName])) {
+ $ranges = array_merge($ranges, $installed['versions'][$packageName]['provided']);
+ }
+
+ return implode(' || ', $ranges);
+ }
+
+ throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
+ }
+
+ /**
+ * @param string $packageName
+ * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present
+ */
+ public static function getVersion($packageName)
+ {
+ foreach (self::getInstalled() as $installed) {
+ if (!isset($installed['versions'][$packageName])) {
+ continue;
+ }
+
+ if (!isset($installed['versions'][$packageName]['version'])) {
+ return null;
+ }
+
+ return $installed['versions'][$packageName]['version'];
+ }
+
+ throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
+ }
+
+ /**
+ * @param string $packageName
+ * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present
+ */
+ public static function getPrettyVersion($packageName)
+ {
+ foreach (self::getInstalled() as $installed) {
+ if (!isset($installed['versions'][$packageName])) {
+ continue;
+ }
+
+ if (!isset($installed['versions'][$packageName]['pretty_version'])) {
+ return null;
+ }
+
+ return $installed['versions'][$packageName]['pretty_version'];
+ }
+
+ throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
+ }
+
+ /**
+ * @param string $packageName
+ * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as reference
+ */
+ public static function getReference($packageName)
+ {
+ foreach (self::getInstalled() as $installed) {
+ if (!isset($installed['versions'][$packageName])) {
+ continue;
+ }
+
+ if (!isset($installed['versions'][$packageName]['reference'])) {
+ return null;
+ }
+
+ return $installed['versions'][$packageName]['reference'];
+ }
+
+ throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
+ }
+
+ /**
+ * @param string $packageName
+ * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as install path. Packages of type metapackages also have a null install path.
+ */
+ public static function getInstallPath($packageName)
+ {
+ foreach (self::getInstalled() as $installed) {
+ if (!isset($installed['versions'][$packageName])) {
+ continue;
+ }
+
+ return isset($installed['versions'][$packageName]['install_path']) ? $installed['versions'][$packageName]['install_path'] : null;
+ }
+
+ throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
+ }
+
+ /**
+ * @return array
+ * @psalm-return array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string, type: string}
+ */
+ public static function getRootPackage()
+ {
+ $installed = self::getInstalled();
+
+ return $installed[0]['root'];
+ }
+
+ /**
+ * Returns the raw installed.php data for custom implementations
+ *
+ * @deprecated Use getAllRawData() instead which returns all datasets for all autoloaders present in the process. getRawData only returns the first dataset loaded, which may not be what you expect.
+ * @return array[]
+ * @psalm-return array{root: array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string, type: string}, versions: array}
+ */
+ public static function getRawData()
+ {
+ @trigger_error('getRawData only returns the first dataset loaded, which may not be what you expect. Use getAllRawData() instead which returns all datasets for all autoloaders present in the process.', E_USER_DEPRECATED);
+
+ if (null === self::$installed) {
+ // only require the installed.php file if this file is loaded from its dumped location,
+ // and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937
+ if (substr(__DIR__, -8, 1) !== 'C') {
+ self::$installed = include __DIR__ . '/installed.php';
+ } else {
+ self::$installed = array();
+ }
+ }
+
+ return self::$installed;
+ }
+
+ /**
+ * Returns the raw data of all installed.php which are currently loaded for custom implementations
+ *
+ * @return array[]
+ * @psalm-return list}>
+ */
+ public static function getAllRawData()
+ {
+ return self::getInstalled();
+ }
+
+ /**
+ * Lets you reload the static array from another file
+ *
+ * This is only useful for complex integrations in which a project needs to use
+ * this class but then also needs to execute another project's autoloader in process,
+ * and wants to ensure both projects have access to their version of installed.php.
+ *
+ * A typical case would be PHPUnit, where it would need to make sure it reads all
+ * the data it needs from this class, then call reload() with
+ * `require $CWD/vendor/composer/installed.php` (or similar) as input to make sure
+ * the project in which it runs can then also use this class safely, without
+ * interference between PHPUnit's dependencies and the project's dependencies.
+ *
+ * @param array[] $data A vendor/composer/installed.php data set
+ * @return void
+ *
+ * @psalm-param array{root: array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string, type: string}, versions: array} $data
+ */
+ public static function reload($data)
+ {
+ self::$installed = $data;
+ self::$installedByVendor = array();
+ }
+
+ /**
+ * @return array[]
+ * @psalm-return list}>
+ */
+ private static function getInstalled()
+ {
+ if (null === self::$canGetVendors) {
+ self::$canGetVendors = method_exists('Composer\Autoload\ClassLoader', 'getRegisteredLoaders');
+ }
+
+ $installed = array();
+
+ if (self::$canGetVendors) {
+ foreach (ClassLoader::getRegisteredLoaders() as $vendorDir => $loader) {
+ if (isset(self::$installedByVendor[$vendorDir])) {
+ $installed[] = self::$installedByVendor[$vendorDir];
+ } elseif (is_file($vendorDir.'/composer/installed.php')) {
+ $installed[] = self::$installedByVendor[$vendorDir] = require $vendorDir.'/composer/installed.php';
+ if (null === self::$installed && strtr($vendorDir.'/composer', '\\', '/') === strtr(__DIR__, '\\', '/')) {
+ self::$installed = $installed[count($installed) - 1];
+ }
+ }
+ }
+ }
+
+ if (null === self::$installed) {
+ // only require the installed.php file if this file is loaded from its dumped location,
+ // and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937
+ if (substr(__DIR__, -8, 1) !== 'C') {
+ self::$installed = require __DIR__ . '/installed.php';
+ } else {
+ self::$installed = array();
+ }
+ }
+ $installed[] = self::$installed;
+
+ return $installed;
+ }
+}
diff --git a/libraries/lifterlms-cli/vendor/composer/LICENSE b/libraries/lifterlms-cli/vendor/composer/LICENSE
new file mode 100644
index 0000000000..f27399a042
--- /dev/null
+++ b/libraries/lifterlms-cli/vendor/composer/LICENSE
@@ -0,0 +1,21 @@
+
+Copyright (c) Nils Adermann, Jordi Boggiano
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is furnished
+to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+
diff --git a/libraries/lifterlms-cli/vendor/composer/autoload_classmap.php b/libraries/lifterlms-cli/vendor/composer/autoload_classmap.php
new file mode 100644
index 0000000000..b26f1b13b1
--- /dev/null
+++ b/libraries/lifterlms-cli/vendor/composer/autoload_classmap.php
@@ -0,0 +1,10 @@
+ $vendorDir . '/composer/InstalledVersions.php',
+);
diff --git a/libraries/lifterlms-cli/vendor/composer/autoload_namespaces.php b/libraries/lifterlms-cli/vendor/composer/autoload_namespaces.php
new file mode 100644
index 0000000000..b7fc0125db
--- /dev/null
+++ b/libraries/lifterlms-cli/vendor/composer/autoload_namespaces.php
@@ -0,0 +1,9 @@
+ array($baseDir . '/src'),
+);
diff --git a/libraries/lifterlms-cli/vendor/composer/autoload_real.php b/libraries/lifterlms-cli/vendor/composer/autoload_real.php
new file mode 100644
index 0000000000..78fa4409f3
--- /dev/null
+++ b/libraries/lifterlms-cli/vendor/composer/autoload_real.php
@@ -0,0 +1,57 @@
+= 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded());
+ if ($useStaticLoader) {
+ require __DIR__ . '/autoload_static.php';
+
+ call_user_func(\Composer\Autoload\ComposerStaticInitaa475372d1afb7f112bf50e9b8859e3a::getInitializer($loader));
+ } else {
+ $map = require __DIR__ . '/autoload_namespaces.php';
+ foreach ($map as $namespace => $path) {
+ $loader->set($namespace, $path);
+ }
+
+ $map = require __DIR__ . '/autoload_psr4.php';
+ foreach ($map as $namespace => $path) {
+ $loader->setPsr4($namespace, $path);
+ }
+
+ $classMap = require __DIR__ . '/autoload_classmap.php';
+ if ($classMap) {
+ $loader->addClassMap($classMap);
+ }
+ }
+
+ $loader->register(true);
+
+ return $loader;
+ }
+}
diff --git a/libraries/lifterlms-cli/vendor/composer/autoload_static.php b/libraries/lifterlms-cli/vendor/composer/autoload_static.php
new file mode 100644
index 0000000000..2a3b404912
--- /dev/null
+++ b/libraries/lifterlms-cli/vendor/composer/autoload_static.php
@@ -0,0 +1,36 @@
+
+ array (
+ 'LifterLMS\\CLI\\' => 14,
+ ),
+ );
+
+ public static $prefixDirsPsr4 = array (
+ 'LifterLMS\\CLI\\' =>
+ array (
+ 0 => __DIR__ . '/../..' . '/src',
+ ),
+ );
+
+ public static $classMap = array (
+ 'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php',
+ );
+
+ public static function getInitializer(ClassLoader $loader)
+ {
+ return \Closure::bind(function () use ($loader) {
+ $loader->prefixLengthsPsr4 = ComposerStaticInitaa475372d1afb7f112bf50e9b8859e3a::$prefixLengthsPsr4;
+ $loader->prefixDirsPsr4 = ComposerStaticInitaa475372d1afb7f112bf50e9b8859e3a::$prefixDirsPsr4;
+ $loader->classMap = ComposerStaticInitaa475372d1afb7f112bf50e9b8859e3a::$classMap;
+
+ }, null, ClassLoader::class);
+ }
+}
diff --git a/libraries/lifterlms-cli/vendor/composer/installed.json b/libraries/lifterlms-cli/vendor/composer/installed.json
new file mode 100644
index 0000000000..f20a6c47c6
--- /dev/null
+++ b/libraries/lifterlms-cli/vendor/composer/installed.json
@@ -0,0 +1,5 @@
+{
+ "packages": [],
+ "dev": false,
+ "dev-package-names": []
+}
diff --git a/libraries/lifterlms-cli/vendor/composer/installed.php b/libraries/lifterlms-cli/vendor/composer/installed.php
new file mode 100644
index 0000000000..252bd7959e
--- /dev/null
+++ b/libraries/lifterlms-cli/vendor/composer/installed.php
@@ -0,0 +1,23 @@
+ array(
+ 'pretty_version' => 'dev-trunk',
+ 'version' => 'dev-trunk',
+ 'type' => 'wordpress-plugin',
+ 'install_path' => __DIR__ . '/../../',
+ 'aliases' => array(),
+ 'reference' => '82418fb524fe978ee668d33ff54bfd5d6e125cb8',
+ 'name' => 'lifterlms/lifterlms-cli',
+ 'dev' => false,
+ ),
+ 'versions' => array(
+ 'lifterlms/lifterlms-cli' => array(
+ 'pretty_version' => 'dev-trunk',
+ 'version' => 'dev-trunk',
+ 'type' => 'wordpress-plugin',
+ 'install_path' => __DIR__ . '/../../',
+ 'aliases' => array(),
+ 'reference' => '82418fb524fe978ee668d33ff54bfd5d6e125cb8',
+ 'dev_requirement' => false,
+ ),
+ ),
+);
diff --git a/libraries/lifterlms-cli/vendor/composer/platform_check.php b/libraries/lifterlms-cli/vendor/composer/platform_check.php
new file mode 100644
index 0000000000..92370c5a0c
--- /dev/null
+++ b/libraries/lifterlms-cli/vendor/composer/platform_check.php
@@ -0,0 +1,26 @@
+= 70300)) {
+ $issues[] = 'Your Composer dependencies require a PHP version ">= 7.3.0". You are running ' . PHP_VERSION . '.';
+}
+
+if ($issues) {
+ if (!headers_sent()) {
+ header('HTTP/1.1 500 Internal Server Error');
+ }
+ if (!ini_get('display_errors')) {
+ if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') {
+ fwrite(STDERR, 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . implode(PHP_EOL, $issues) . PHP_EOL.PHP_EOL);
+ } elseif (!headers_sent()) {
+ echo 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . str_replace('You are running '.PHP_VERSION.'.', '', implode(PHP_EOL, $issues)) . PHP_EOL.PHP_EOL;
+ }
+ }
+ trigger_error(
+ 'Composer detected issues in your platform: ' . implode(' ', $issues),
+ E_USER_ERROR
+ );
+}
diff --git a/libraries/lifterlms-helper/CHANGELOG.md b/libraries/lifterlms-helper/CHANGELOG.md
new file mode 100644
index 0000000000..bd9d95f6d9
--- /dev/null
+++ b/libraries/lifterlms-helper/CHANGELOG.md
@@ -0,0 +1,223 @@
+LifterLMS Helper Changelog
+==========================
+
+v3.4.2 - 2022-04-01
+-------------------
+
+##### Bug Fixes
+
++ Fixed an issue where adding new license keys with an end-of-line symbol after the last key would result in an invalid license key error.
++ Fixed an issue that caused PHP warnings in the "Plugins -> Add New" page because the `plugin` property was missing. [#36](https://github.com/gocodebox/lifterlms-helper/issues/36)
+
+
+v3.4.1 - 2021-08-17
+-------------------
+
++ Fixed undefined index error encountered when programmatically deactivating a key that was not previously activated on the site.
+
+
+v3.4.0 - 2021-08-04
+-------------------
+
+##### Localization updates
+
++ Only runs localization functions when loaded as an independent plugin.
++ Replace the textdoman 'lifterlms-helper' with 'lifterlms'.
+
+##### Updates
+
++ Use `llms_helper()` in favor of deprecated `LLMS_Helper()` in various locations.
+
+##### Bugfix
+
++ Don't attempt to run migrations from versions less than 3.0.0 during first run when loaded as a library.
+
+
+v3.3.1 - 2021-07-26
+-------------------
+
++ Load `llms_helper()->upgrader` WP_CLI context in preparation for forthcoming the `lifterlms-cli`.
+
+
+v3.3.0 - 2021-06-14
+-------------------
+
++ This plugin is now included by default via the LifterLMS core in versions 5.0+. Installing this plugin directly will use the plugin version instead of the version included with the core. Direct installation is likely only required for development purposes when using LifterLMS 5.0+.
++ The main function `llms_helper()` is declared conditionally when the class `LifterLMS_Helper` is not yet declared.
++ Added a constant `LLMS_HELPER_DISABLE` which allows disabling of the plugin.
++ Distribution release zips now include a `composer.json` file to allow for installation via composer.
+
+
+v3.2.1 - 2021-06-03
+-------------------
+
+##### Updates
+
++ Flush cached update and add-on data when adding or removing license keys and when changing channel subscription for a package.
++ Enable updating to beta versions of packages that don't require a license when no license is present.
+
+
+v3.2.0 - 2020-12-02
+-------------------
+
+##### Updates
+
++ Moved the class `LifterLMS_Helper` class to its own file from `lifterlms-helper.php`.
++ Use `self::$instance` in favor of `self::$_instance`.
++ Use `llms()` in favor of deprecated `LLMS()`.
++ Use `llms_filter_input()` to access `$_POST` data in various places.
++ Use strict comparison for `in_array()`.
+
+##### Bug fixes
+
++ Fixed usage of incorrect textdomain in various places.
+
+##### Deprecations
+
++ Replaced usage of protected class property `$instance` in favor of `$_instance` in various singleton classes.
++ Function `LLMS_Helper()` is deprecated in favor of `llms_helper()`.
++ File `includes/model-llms-helper-add-on.php` is deprecated, use `includes/models/class-llms-helper-add-on.php` instead.
+
+
+v3.1.0 - 2020-05-22
+-------------------
+
++ Load changelogs from the make.lifterlms.com release notes archive in favor of from static html files.
++ Remove reliance on `file_get_contents()` causing errors on servers without access to the function.
+
+
+v3.0.2 - 2018-08-29
+-------------------
+
++ Fixed fatal errors encountered as a result of failed API calls
++ Fixed broken links output on the plugins update screen when an add-on is unlicensed and has an update available
++ Fixed issue causing non-beta versions of the LifterLMS core to be served from LifterLMS.com instead of from WordPress.org
+
+
+v3.0.1 - 2018-08-02
+-------------------
+
++ Fixed an issue causing key migration to run on the frontend resulting in a fatal error related to missing admin-only functions
++ Fixed an issue causing multiple submitted keys to not work properly on certain environments
++ Fixed issue causing installation script to make an activation API call even when no keys exist
++ Improved installation script message to only display a migration message when keys are actually migrated
+
+
+v3.0.0 - 2018-08-01
+-------------------
+
++ **This is nearly a complete rewrite of the codebase. Things have moved but no features have been removed.**
++ Requires LifterLMS version 3.22.0 or later
++ License key activation is now on a per-site basis as opposed to a per product basis. This means that if you have a license key for a bundle you don't have to enter the key for each add-on, you enter the key only once and it will activate ALL the add-ons.
++ The "Licenses" tab has been removed and your add-ons and licenses are now managed via LifterLMS -> Add-ons & More
++ A migration script exists to move license keys from previous versions of the helper to this version. After upgrading check LifterLMS -> Add-ons & More to ensure your keys were successfully migrated.
++ You can now install add-ons through the this plugin without having to download and install them manually. Enter your license key(s) and select the add-ons you wish to install to have them installed automatically. You can bulk install as well.
++ You can now subscribe to beta channels of LifterLMS and any LifterLMS add-ons. Visit the LifterLMS -> Status -> Betas screen to subscribe to betas. Always use betas at your own risk, by nature they're unstable!
++ Uses the LifterLMS.com v3 REST api for all API calls
++ Added RTL language support
++ Added i18n support
++ Removed and replaced various functions
++ Fixes many bugs and almost certainly introduces some new ones
+
+
+v2.5.1 - 2017-11-08
+-------------------
+
++ Fix issue causing false activations which cannot be deactivated due to blank activation keys
+
+
+v2.5.0 - 2017-07-18
+-------------------
+
++ Allow add-ons to be bulk deactivated
++ Integrates with LifterLMS site clone detection in order to automatically activate plugins on your new URL when cloning to staging / production.
++ Following clone detection if activation fails the plugin will no longer show the add-ons as activated (since they're not activated on the new URL)
++ Minor admin-panel performance improvements
++ Now uses minified JS and CSS assets
++ Now fully translateable!
+
+
+v2.4.3 - 2017-02-09
+-------------------
+
++ Handle undefined errors during post plugin install from zip file
+
+
+v2.4.2 - 2017-01-20
+-------------------
+
++ Handle failed api calls gracefully
+
+
+v2.4.1 - 2016-12-30
+-------------------
+
++ Cache add-on list prior to filtering
+
+
+v2.4.0 - 2016-12-20
+-------------------
+
++ Added a unified Helper sceen accessible via LifterLMS -> Settings -> Helper
++ Activate multiple addons simultaneously via one API call
++ Site deactivation now deactivates from remote activation server in addition to local deactivation
++ Upgraded database key handling prevents accidental duplicate activation attempts
++ Fixed several undefined index warnings
++ Normalized option fields keys
+
+
+v2.3.1 - 2016-10-12
+-------------------
+
++ Fixes issue with theme upgrade post install not working resulting in themes existing in the wrong directory after an upgrade
+
+
+v2.3.0 - 2016-10-10
+-------------------
+
++ Significantly upgrades the speed of version checks. Previously checked each LifterLMS Add-on separately, now makes one API call to retreive versions of all installed LifterLMS Add-ons.
++ Adds support for the Universe Bundle which is one key associated with multiple products
+
+
+v2.2.0 - 2016-07-06
+-------------------
+
++ After updates, clear cached update data so the upgrade doesn't still appear as pending
++ After changing license keys, clear cahced data so the next upgrade attempt will not fail again (unless it's still supposed to fail)
++ After updating the currently active theme, correctly reactivate the theme
+
+
+v2.1.0 - 2016-06-14
+-------------------
+
++ Prevent hijacking the LifterLMS Core lightbox data when attempting to view update details on the plugin update screen.
++ Added [Parsedown](https://github.com/erusev/parsedown) to render Markdown style changelogs into HTML when viewing extension changelogs in the the lightbox on plugin update screens.
+
+
+v2.0.0 - 2016-04-08
+-------------------
+
++ Includes theme-related APIs for serving updates for themes
++ Better error reporting and handling
++ A few very exciting performance enhancements
+
+
+v1.0.2 - 2016-03-07
+-------------------
+
++ Fixed an undefined variable which produced a php warning when `WP_DEBUG` was enabled
++ Resolved an issue that caused the LifterLMS Helper to hijack the "details" and related plugin screens that display inside a lightbox in the plugins admin page.
++ Added a .editorconfig file
++ Added changelog file
+
+
+v1.0.1 - 2016-02-11
+-------------------
+
++ Actual public release
+
+
+v1.0.0 - 2016-02-10
+-------------------
+
++ Initial public release
diff --git a/libraries/lifterlms-helper/assets/css/llms-helper-rtl.css b/libraries/lifterlms-helper/assets/css/llms-helper-rtl.css
new file mode 100644
index 0000000000..939e4bba1d
--- /dev/null
+++ b/libraries/lifterlms-helper/assets/css/llms-helper-rtl.css
@@ -0,0 +1,77 @@
+.wrap.lifterlms-addons .llms-licenses {
+ display: inline-block;
+ margin-right: 20px;
+ position: relative;
+ vertical-align: middle;
+ z-index: 1;
+}
+.wrap.lifterlms-addons .llms-licenses .llms-license-header {
+ margin: 0;
+}
+.wrap.lifterlms-addons .llms-licenses label {
+ display: block;
+ margin-top: 20px;
+}
+.wrap.lifterlms-addons .llms-licenses label:first-child {
+ margin-top: 0;
+}
+.wrap.lifterlms-addons .llms-licenses .llms-active-keys {
+ list-style-type: none;
+ margin: 5px 0;
+ padding: 0;
+}
+.wrap.lifterlms-addons .llms-licenses .llms-active-keys li {
+ margin: 0 0 5px;
+ padding: 0;
+}
+.wrap.lifterlms-addons .llms-licenses .llms-active-keys li input:checked + span {
+ color: #e5554e;
+ font-style: italic;
+}
+.wrap.lifterlms-addons .llms-licenses .fa-chevron-down {
+ margin-right: 10px;
+}
+.wrap.lifterlms-addons .llms-licenses .llms-key-field {
+ background: #fff;
+ border: 1px solid #ddd;
+ display: none;
+ margin-right: 0;
+ position: absolute;
+ right: -1px;
+ padding: 20px;
+ top: calc( 100% - 2px );
+ width: 340px;
+}
+.wrap.lifterlms-addons .llms-licenses .llms-key-field textarea {
+ display: inline-block;
+ height: 86px;
+ font-size: 14px;
+ font-family: monospace;
+ line-height: 1.8;
+ margin: 5px 0;
+ padding: 5px 10px;
+ resize: none;
+ vertical-align: middle;
+ width: 100%;
+}
+
+@media only screen and (min-width: 800px) {
+ .llms-status--betas .llms-beta-main {
+ display: flex;
+ }
+ .llms-status--betas .llms-beta-table {
+ flex: 2;
+ }
+ .llms-status--betas .llms-beta-aside {
+ flex: 1;
+ margin-left: 20px;
+ }
+}
+.llms-status--betas .llms-beta-aside {
+ background: #fef7f7;
+ border: 1px solid #e5554e;
+ padding: 20px;
+}
+.llms-status--betas .llms-beta-aside h1 {
+ padding-top: 0;
+}
diff --git a/libraries/lifterlms-helper/assets/css/llms-helper-rtl.min.css b/libraries/lifterlms-helper/assets/css/llms-helper-rtl.min.css
new file mode 100644
index 0000000000..be40f5e9ce
--- /dev/null
+++ b/libraries/lifterlms-helper/assets/css/llms-helper-rtl.min.css
@@ -0,0 +1 @@
+.wrap.lifterlms-addons .llms-licenses{display:inline-block;margin-right:20px;position:relative;vertical-align:middle;z-index:1}.wrap.lifterlms-addons .llms-licenses .llms-license-header{margin:0}.wrap.lifterlms-addons .llms-licenses label{display:block;margin-top:20px}.wrap.lifterlms-addons .llms-licenses label:first-child{margin-top:0}.wrap.lifterlms-addons .llms-licenses .llms-active-keys{list-style-type:none;margin:5px 0;padding:0}.wrap.lifterlms-addons .llms-licenses .llms-active-keys li{margin:0 0 5px;padding:0}.wrap.lifterlms-addons .llms-licenses .llms-active-keys li input:checked+span{color:#e5554e;font-style:italic}.wrap.lifterlms-addons .llms-licenses .fa-chevron-down{margin-right:10px}.wrap.lifterlms-addons .llms-licenses .llms-key-field{background:#fff;border:1px solid #ddd;display:none;margin-right:0;position:absolute;right:-1px;padding:20px;top:calc( 100% - 2px );width:340px}.wrap.lifterlms-addons .llms-licenses .llms-key-field textarea{display:inline-block;height:86px;font-size:14px;font-family:monospace;line-height:1.8;margin:5px 0;padding:5px 10px;resize:none;vertical-align:middle;width:100%}@media only screen and (min-width: 800px){.llms-status--betas .llms-beta-main{display:flex}.llms-status--betas .llms-beta-table{flex:2}.llms-status--betas .llms-beta-aside{flex:1;margin-left:20px}}.llms-status--betas .llms-beta-aside{background:#fef7f7;border:1px solid #e5554e;padding:20px}.llms-status--betas .llms-beta-aside h1{padding-top:0}
diff --git a/libraries/lifterlms-helper/assets/css/llms-helper.css b/libraries/lifterlms-helper/assets/css/llms-helper.css
new file mode 100644
index 0000000000..adba48fc1c
--- /dev/null
+++ b/libraries/lifterlms-helper/assets/css/llms-helper.css
@@ -0,0 +1,79 @@
+.wrap.lifterlms-addons .llms-licenses {
+ display: inline-block;
+ margin-left: 20px;
+ position: relative;
+ vertical-align: middle;
+ z-index: 1;
+}
+.wrap.lifterlms-addons .llms-licenses .llms-license-header {
+ margin: 0;
+}
+.wrap.lifterlms-addons .llms-licenses label {
+ display: block;
+ margin-top: 20px;
+}
+.wrap.lifterlms-addons .llms-licenses label:first-child {
+ margin-top: 0;
+}
+.wrap.lifterlms-addons .llms-licenses .llms-active-keys {
+ list-style-type: none;
+ margin: 5px 0;
+ padding: 0;
+}
+.wrap.lifterlms-addons .llms-licenses .llms-active-keys li {
+ margin: 0 0 5px;
+ padding: 0;
+}
+.wrap.lifterlms-addons .llms-licenses .llms-active-keys li input:checked + span {
+ color: #e5554e;
+ font-style: italic;
+}
+.wrap.lifterlms-addons .llms-licenses .fa-chevron-down {
+ margin-left: 10px;
+}
+.wrap.lifterlms-addons .llms-licenses .llms-key-field {
+ background: #fff;
+ border: 1px solid #ddd;
+ display: none;
+ margin-left: 0;
+ position: absolute;
+ left: -1px;
+ padding: 20px;
+ top: calc( 100% - 2px );
+ width: 340px;
+}
+.wrap.lifterlms-addons .llms-licenses .llms-key-field textarea {
+ display: inline-block;
+ height: 86px;
+ font-size: 14px;
+ font-family: monospace;
+ line-height: 1.8;
+ margin: 5px 0;
+ padding: 5px 10px;
+ resize: none;
+ vertical-align: middle;
+ width: 100%;
+}
+
+@media only screen and (min-width: 800px) {
+ .llms-status--betas .llms-beta-main {
+ display: flex;
+ }
+ .llms-status--betas .llms-beta-table {
+ flex: 2;
+ }
+ .llms-status--betas .llms-beta-aside {
+ flex: 1;
+ margin-right: 20px;
+ }
+}
+.llms-status--betas .llms-beta-aside {
+ background: #fef7f7;
+ border: 1px solid #e5554e;
+ padding: 20px;
+}
+.llms-status--betas .llms-beta-aside h1 {
+ padding-top: 0;
+}
+
+/*# sourceMappingURL=llms-helper.css.map */
diff --git a/libraries/lifterlms-helper/assets/css/llms-helper.css.map b/libraries/lifterlms-helper/assets/css/llms-helper.css.map
new file mode 100644
index 0000000000..9009cad586
--- /dev/null
+++ b/libraries/lifterlms-helper/assets/css/llms-helper.css.map
@@ -0,0 +1 @@
+{"version":3,"sourceRoot":"","sources":["../scss/llms-helper.scss"],"names":[],"mappings":"AAKC;EACC;EACA;EACA;EACA;EACA;;AAEA;EACC;;AAGD;EACC;EACA;;AACA;EAAgB;;AAGjB;EACC;EACA;EACA;;AACA;EACC;EACA;;AACA;EACC,OA9BO;EA+BP;;AAKH;EAAmB;;AAEnB;EACC;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACC;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;;AAWH;EAEC;IACC;;EAED;IACC;;EAED;IACC;IACA;;;AAKF;EACC;EACA;EACA;;AAEA;EACC","file":"llms-helper.css"}
\ No newline at end of file
diff --git a/libraries/lifterlms-helper/assets/css/llms-helper.min.css b/libraries/lifterlms-helper/assets/css/llms-helper.min.css
new file mode 100644
index 0000000000..385cae954e
--- /dev/null
+++ b/libraries/lifterlms-helper/assets/css/llms-helper.min.css
@@ -0,0 +1 @@
+.wrap.lifterlms-addons .llms-licenses{display:inline-block;margin-left:20px;position:relative;vertical-align:middle;z-index:1}.wrap.lifterlms-addons .llms-licenses .llms-license-header{margin:0}.wrap.lifterlms-addons .llms-licenses label{display:block;margin-top:20px}.wrap.lifterlms-addons .llms-licenses label:first-child{margin-top:0}.wrap.lifterlms-addons .llms-licenses .llms-active-keys{list-style-type:none;margin:5px 0;padding:0}.wrap.lifterlms-addons .llms-licenses .llms-active-keys li{margin:0 0 5px;padding:0}.wrap.lifterlms-addons .llms-licenses .llms-active-keys li input:checked+span{color:#e5554e;font-style:italic}.wrap.lifterlms-addons .llms-licenses .fa-chevron-down{margin-left:10px}.wrap.lifterlms-addons .llms-licenses .llms-key-field{background:#fff;border:1px solid #ddd;display:none;margin-left:0;position:absolute;left:-1px;padding:20px;top:calc( 100% - 2px );width:340px}.wrap.lifterlms-addons .llms-licenses .llms-key-field textarea{display:inline-block;height:86px;font-size:14px;font-family:monospace;line-height:1.8;margin:5px 0;padding:5px 10px;resize:none;vertical-align:middle;width:100%}@media only screen and (min-width: 800px){.llms-status--betas .llms-beta-main{display:flex}.llms-status--betas .llms-beta-table{flex:2}.llms-status--betas .llms-beta-aside{flex:1;margin-right:20px}}.llms-status--betas .llms-beta-aside{background:#fef7f7;border:1px solid #e5554e;padding:20px}.llms-status--betas .llms-beta-aside h1{padding-top:0}/*# sourceMappingURL=llms-helper.min.css.map */
diff --git a/libraries/lifterlms-helper/assets/css/llms-helper.min.css.map b/libraries/lifterlms-helper/assets/css/llms-helper.min.css.map
new file mode 100644
index 0000000000..cd6ffcdd0f
--- /dev/null
+++ b/libraries/lifterlms-helper/assets/css/llms-helper.min.css.map
@@ -0,0 +1 @@
+{"version":3,"sourceRoot":"","sources":["../scss/llms-helper.scss"],"names":[],"mappings":"AAKC,sCACC,qBACA,iBACA,kBACA,sBACA,UAEA,2DACC,SAGD,4CACC,cACA,gBACA,qEAGD,wDACC,qBACA,aACA,UACA,2DACC,eACA,UACA,8EACC,MA9BO,QA+BP,kBAKH,wEAEA,sDACC,gBACA,sBACA,aACA,cACA,kBACA,UACA,aACA,uBACA,YAEA,+DACC,qBACA,YACA,eACA,sBACA,gBACA,aACA,iBACA,YACA,sBACA,WAWH,0CAEC,oCACC,aAED,qCACC,OAED,qCACC,OACA,mBAKF,qCACC,mBACA,yBACA,aAEA,wCACC","file":"llms-helper.min.css"}
\ No newline at end of file
diff --git a/libraries/lifterlms-helper/class-lifterlms-helper.php b/libraries/lifterlms-helper/class-lifterlms-helper.php
new file mode 100644
index 0000000000..f04329e08b
--- /dev/null
+++ b/libraries/lifterlms-helper/class-lifterlms-helper.php
@@ -0,0 +1,216 @@
+upgrader().
+ *
+ * @var null|LLMS_Helper_Upgrader
+ */
+ private $upgrader = null;
+
+ /**
+ * Retrieve the main Instance of LifterLMS_Helper
+ *
+ * @since 3.0.0
+ * @since 3.2.0 Use `self::$instance` in favor of `self::$_instance`.
+ *
+ * @return LifterLMS_Helper
+ */
+ public static function instance() {
+ if ( is_null( self::$instance ) ) {
+ self::$instance = new self();
+ }
+ return self::$instance;
+ }
+
+ /**
+ * Constructor, get things started!
+ *
+ * @since 1.0.0
+ * @since 3.4.0 Only localize when loaded as an independent plugin.
+ *
+ * @return void
+ */
+ private function __construct() {
+
+ // Define class constants.
+ $this->define_constants();
+
+ /**
+ * When loaded as a library included by the LifterLMS core localization is handled by the LifterLMS core.
+ *
+ * When the plugin is loaded by itself as a plugin, we must localize it independently.
+ */
+ if ( ! defined( 'LLMS_HELPER_LIB' ) || ! LLMS_HELPER_LIB ) {
+ add_action( 'init', array( $this, 'load_textdomain' ), 0 );
+ }
+
+ add_action( 'plugins_loaded', array( $this, 'init' ) );
+
+ }
+
+ /**
+ * Inititalize the Plugin
+ *
+ * @since 1.0.0
+ * @since 3.0.0 Unknown.
+ * @since 3.2.0 Use `llms()` in favor of deprecated `LLMS()`.
+ * @since 3.3.1 Load the upgrader instance in WP_CLI context.
+ *
+ * @return void
+ */
+ public function init() {
+
+ // Only load if we have the minimum LifterLMS version installed & activated.
+ if ( function_exists( 'llms' ) && version_compare( '3.22.0', llms()->version, '<=' ) ) {
+
+ $this->includes();
+ $this->crons();
+
+ if ( is_admin() || ( defined( 'WP_CLI' ) && WP_CLI ) ) {
+ $this->upgrader = LLMS_Helper_Upgrader::instance();
+ }
+ }
+
+ }
+
+ /**
+ * Schedule and handle cron functions
+ *
+ * @since 3.0.0
+ *
+ * @return void
+ */
+ private function crons() {
+
+ add_action( 'llms_helper_check_license_keys', array( 'LLMS_Helper_Keys', 'check_keys' ) );
+
+ if ( ! wp_next_scheduled( 'llms_helper_check_license_keys' ) ) {
+ wp_schedule_event( time(), 'daily', 'llms_helper_check_license_keys' );
+ }
+
+ }
+
+ /**
+ * Define constants for plugin
+ *
+ * @since 1.0.0
+ *
+ * @return void
+ */
+ private function define_constants() {
+
+ if ( ! defined( 'LLMS_HELPER_VERSION' ) ) {
+ define( 'LLMS_HELPER_VERSION', $this->version );
+ }
+
+ }
+
+ /**
+ * Include all clasess required by the plugin
+ *
+ * @since 1.0.0
+ * @since 3.0.0 Include new files.
+ *
+ * @return void
+ */
+ private function includes() {
+
+ require_once LLMS_HELPER_PLUGIN_DIR . 'includes/class-llms-helper-admin-add-ons.php';
+ require_once LLMS_HELPER_PLUGIN_DIR . 'includes/class-llms-helper-assets.php';
+ require_once LLMS_HELPER_PLUGIN_DIR . 'includes/class-llms-helper-betas.php';
+ require_once LLMS_HELPER_PLUGIN_DIR . 'includes/class-llms-helper-cloned.php';
+ require_once LLMS_HELPER_PLUGIN_DIR . 'includes/class-llms-helper-install.php';
+ require_once LLMS_HELPER_PLUGIN_DIR . 'includes/class-llms-helper-keys.php';
+ require_once LLMS_HELPER_PLUGIN_DIR . 'includes/class-llms-helper-options.php';
+ require_once LLMS_HELPER_PLUGIN_DIR . 'includes/class-llms-helper-upgrader.php';
+
+ require_once LLMS_HELPER_PLUGIN_DIR . 'includes/models/class-llms-helper-add-on.php';
+
+ require_once LLMS_HELPER_PLUGIN_DIR . 'includes/functions-llms-helper.php';
+
+ }
+
+ /**
+ * Load l10n files.
+ *
+ * This method is only used when the plugin is loaded as a standalone plugin (for development purposes),
+ * otherwise (when loaded as a library from within the LifterLMS core plugin) the localization
+ * strings are included into the LifterLMS Core plugin's po/mo files and are localized by the LifterLMS
+ * core plugin.
+ *
+ * Files can be found in the following order (The first loaded file takes priority):
+ * 1. WP_LANG_DIR/lifterlms/lifterlms-rest-LOCALE.mo
+ * 2. WP_LANG_DIR/plugins/lifterlms-rest-LOCALE.mo
+ * 3. WP_CONTENT_DIR/plugins/lifterlms-rest/i18n/lifterlms-rest-LOCALE.mo
+ *
+ * Note: The function `load_plugin_textdomain()` is not used because the same textdomain as the LifterLMS core
+ * is used for this plugin but the file is named `lifterlms-rest` in order to allow using a separate language
+ * file for each codebase.
+ *
+ * @since 2.5.0
+ * @since 3.4.0 Updated to the core textdomain.
+ *
+ * @return void
+ */
+ public function load_textdomain() {
+
+ // Load locale.
+ $locale = apply_filters( 'plugin_locale', get_locale(), 'lifterlms' );
+
+ // Load from the LifterLMS "safe" directory if it exists.
+ load_textdomain( 'lifterlms', WP_LANG_DIR . '/lifterlms/lifterlms-helper-' . $locale . '.mo' );
+
+ // Load from the default plugins language file directory.
+ load_textdomain( 'lifterlms', WP_LANG_DIR . '/plugins/lifterlms-helper-' . $locale . '.mo' );
+
+ // Load from the plugin's language file directory.
+ load_textdomain( 'lifterlms', LLMS_HELPER_PLUGIN_DIR . '/i18n/lifterlms-helper-' . $locale . '.mo' );
+ }
+
+ /**
+ * Return the singleton instance of the LLMS_Helper_Upgader
+ *
+ * @since 3.0.0
+ *
+ * @return LLMS_Helper_Upgrader
+ */
+ public function upgrader() {
+ return $this->upgrader;
+ }
+
+}
diff --git a/libraries/lifterlms-helper/includes/class-llms-helper-admin-add-ons.php b/libraries/lifterlms-helper/includes/class-llms-helper-admin-add-ons.php
new file mode 100644
index 0000000000..49f3a54642
--- /dev/null
+++ b/libraries/lifterlms-helper/includes/class-llms-helper-admin-add-ons.php
@@ -0,0 +1,405 @@
+has_keys() to retrieve the value.
+ *
+ * @var bool
+ */
+ private $has_keys = null;
+
+ /**
+ * Constructor
+ *
+ * @since 3.0.0
+ */
+ public function __construct() {
+
+ add_action( 'admin_init', array( $this, 'handle_actions' ) );
+
+ // Output navigation items.
+ add_action( 'lifterlms_before_addons_nav', array( $this, 'output_navigation_items' ) );
+
+ // Output the license manager interface button / dropdown.
+ add_action( 'llms_addons_page_after_title', array( $this, 'output_license_manager' ) );
+
+ // Filter current section default.
+ add_filter( 'llms_admin_add_ons_get_current_section', array( $this, 'filter_get_current_section' ) );
+
+ // Filter the content display for a section.
+ add_filter( 'llms_admin_add_ons_get_current_section_default_content', array( $this, 'filter_get_current_section_content' ), 10, 2 );
+
+ // Add install & update actions to the list of available management actions powered by the bulk actions functions in core.
+ add_filter( 'llms_admin_add_ons_manage_actions', array( $this, 'filter_manage_actions' ) );
+
+ // Output html for helper-powered actions (install & update).
+ add_action( 'llms_add_ons_single_item_actions', array( $this, 'output_single_install_action' ), 5, 2 );
+ add_action( 'llms_add_ons_single_item_after_actions', array( $this, 'output_single_update_action' ), 5, 2 );
+
+ add_filter( 'llms_admin_addon_features_exclude_ids', array( $this, 'filter_feature_exclude_ids' ) );
+
+ }
+
+ /**
+ * Change the default section from "All" to "Mine" but only if license keys have been saved
+ *
+ * @since 3.0.0
+ *
+ * @param string $section Section slug.
+ * @return string
+ */
+ public function filter_get_current_section( $section ) {
+
+ if ( 'all' === $section && empty( $_GET['section'] ) && $this->has_keys() ) {
+ return 'mine';
+ }
+
+ return $section;
+
+ }
+
+ /**
+ * Add "mine" tab content
+ *
+ * @since 3.0.0
+ * @since 3.0.2 Unknown.
+ *
+ * @param array $content Default items to display.
+ * @param string $section Current tab slug.
+ * @return array
+ */
+ public function filter_get_current_section_content( $content, $section ) {
+
+ if ( 'mine' === $section ) {
+ $mine = llms_helper_get_available_add_ons();
+ $addons = llms_get_add_ons();
+ if ( ! is_wp_error( $addons ) && isset( $addons['items'] ) ) {
+ foreach ( $addons['items'] as $item ) {
+ if ( in_array( $item['id'], $mine ) ) {
+ $content[] = $item;
+ }
+ }
+ }
+ }
+
+ return $content;
+
+ }
+
+ /**
+ * Exclude IDs for all add-ons that are currently available on the site
+ *
+ * @since 3.0.0
+ *
+ * @param array $ids Existing product ids to exclude.
+ * @return array
+ */
+ public function filter_feature_exclude_ids( $ids ) {
+ return array_unique( array_merge( $ids, llms_helper_get_available_add_ons( false ) ) );
+ }
+
+ /**
+ * Add installatino & update actions to the list of available management actions
+ *
+ * @since 3.0.0
+ *
+ * @param array $actions List of available actions, the action should correspond to a method in the LLMS_Helper_Add_On class.
+ * @return array
+ */
+ public function filter_manage_actions( $actions ) {
+ return array_merge( array( 'install', 'update' ), $actions );
+ }
+
+ /**
+ * Handle form submission actions
+ *
+ * @since 3.0.0
+ * @since 3.2.0 Let the LifterLMS Core output flashed notices
+ * @since 3.2.1 Flush cached addon and package update data when adding or removing keys.
+ *
+ * @return void
+ */
+ public function handle_actions() {
+
+ // License key addition & removal.
+ if ( ! llms_verify_nonce( '_llms_manage_keys_nonce', 'llms_manage_keys' ) ) {
+ return;
+ }
+
+ $flush = false;
+
+ if ( isset( $_POST['llms_activate_keys'] ) && ! empty( $_POST['llms_add_keys'] ) ) {
+
+ $flush = true;
+ $this->handle_activations();
+
+ } elseif ( isset( $_POST['llms_deactivate_keys'] ) && ! empty( $_POST['llms_remove_keys'] ) ) {
+
+ $flush = true;
+ $this->handle_deactivations();
+
+ }
+
+ if ( $flush ) {
+ llms_helper_flush_cache();
+ }
+
+ }
+
+ /**
+ * Activate license keys with LifterLMS.com api
+ *
+ * Output errors / successes & saves successful keys to the db.
+ *
+ * @since 3.0.0
+ * @since 3.2.0 Don't access $_POST directly.
+ * @since 3.4.0 Use core textdomain.
+ *
+ * @return void
+ */
+ private function handle_activations() {
+
+ $res = LLMS_Helper_Keys::activate_keys( llms_filter_input( INPUT_POST, 'llms_add_keys', FILTER_SANITIZE_STRING ) );
+
+ if ( is_wp_error( $res ) ) {
+ LLMS_Admin_Notices::flash_notice( $res->get_error_message(), 'error' );
+ return;
+ }
+
+ $data = $res['data'];
+ if ( isset( $data['errors'] ) ) {
+ foreach ( $data['errors'] as $error ) {
+ LLMS_Admin_Notices::flash_notice( make_clickable( $error ), 'error' );
+ }
+ }
+
+ if ( isset( $data['activations'] ) ) {
+ foreach ( $data['activations'] as $activation ) {
+ LLMS_Helper_Keys::add_license_key( $activation );
+ // Translators: %s = License key.
+ LLMS_Admin_Notices::flash_notice( sprintf( __( '"%s" has been saved!', 'lifterlms' ), $activation['license_key'] ), 'success' );
+ }
+ }
+
+ }
+
+ /**
+ * Deactivate license keys with LifterLMS.com api
+ *
+ * Output errors / successes & removes keys from the db.
+ *
+ * @since 3.0.0
+ * @since 3.2.0 Don't access $_POST directly.
+ * @since 3.4.0 Use core textdomain.
+ *
+ * @return void
+ */
+ private function handle_deactivations() {
+
+ $keys = llms_filter_input( INPUT_POST, 'llms_remove_keys', FILTER_UNSAFE_RAW, FILTER_REQUIRE_ARRAY );
+ $res = LLMS_Helper_Keys::deactivate_keys( $keys );
+
+ if ( is_wp_error( $res ) ) {
+ LLMS_Admin_Notices::flash_notice( $res->get_error_message(), 'error' );
+ return;
+ }
+
+ foreach ( $keys as $key ) {
+ LLMS_Helper_Keys::remove_license_key( $key );
+ /* Translators: %s = License Key */
+ LLMS_Admin_Notices::flash_notice( sprintf( __( 'License key "%s" was removed from this site.', 'lifterlms' ), $key ), 'info' );
+ }
+
+ if ( isset( $data['errors'] ) ) {
+ foreach ( $data['errors'] as $error ) {
+ LLMS_Admin_Notices::flash_notice( make_clickable( $error ), 'error' );
+ }
+ }
+
+ }
+
+ /**
+ * Determine if the current site has active license keys
+ *
+ * @since 3.0.0
+ *
+ * @return bool
+ */
+ public function has_keys() {
+
+ if ( is_null( $this->has_keys ) ) {
+ $this->has_keys = ( count( llms_helper_options()->get_license_keys() ) );
+ }
+
+ return $this->has_keys;
+
+ }
+
+ /**
+ * Output the HTML for the license manager area
+ *
+ * @since 3.0.0
+ * @since 3.4.0 Use core textdomain.
+ *
+ * @return void
+ */
+ public function output_license_manager() {
+
+ $my_keys = llms_helper_options()->get_license_keys();
+ if ( $my_keys ) {
+ wp_enqueue_style( 'plugin-install' );
+ wp_enqueue_script( 'plugin-install' );
+ add_thickbox();
+ }
+
+ ?>
+
+
+ is_installable() && ! $addon->is_installed() && ( ! $addon->requires_license() || $addon->is_licensed() ) ) {
+ ?>
+
+
+
+
+
+
+
+
+
+ is_installable() && $addon->is_installed() && ( ! $addon->requires_license() || $addon->is_licensed() ) && $addon->has_available_update() ) {
+ ?>
+
+
+
+
+
+
+
+
+
+ has_keys() ) {
+ return;
+ }
+
+ ?>
+
+
+
+ id && isset( $_GET['tab'] ) && 'betas' === $_GET['tab'] ) {
+ $load = true;
+ } elseif ( 'lifterlms_page_llms-add-ons' === $screen->id ) {
+ $load = true;
+ }
+
+ if ( ! $load ) {
+ return;
+ }
+
+ wp_register_style( 'llms-helper', LLMS_HELPER_PLUGIN_URL . 'assets/css/llms-helper' . LLMS_ASSETS_SUFFIX . '.css', array(), LLMS_HELPER_VERSION );
+ wp_enqueue_style( 'llms-helper' );
+
+ wp_style_add_data( 'llms-sl', 'rtl', 'replace' );
+ wp_style_add_data( 'llms-sl', 'suffix', LLMS_ASSETS_SUFFIX );
+
+ }
+
+}
+return new LLMS_Helper_Assets();
diff --git a/libraries/lifterlms-helper/includes/class-llms-helper-betas.php b/libraries/lifterlms-helper/includes/class-llms-helper-betas.php
new file mode 100644
index 0000000000..2395272650
--- /dev/null
+++ b/libraries/lifterlms-helper/includes/class-llms-helper-betas.php
@@ -0,0 +1,111 @@
+ $channel ) {
+
+ $addon = llms_get_add_on( $id );
+ if ( 'channel' !== $addon->get_channel_subscription() ) {
+ $addon->subscribe_to_channel( sanitize_text_field( $channel ) );
+ $new_subscription = true;
+ }
+ }
+
+ // When a channel subscription changes also flush caches so we'll get the most recent add-on data immediately and allow upgrading immediately from wp core update screens.
+ if ( $new_subscription ) {
+ llms_helper_flush_cache();
+ }
+
+ return $subs;
+
+ }
+
+ /**
+ * Output content for the beta testing screen
+ *
+ * @since 3.0.0
+ *
+ * @param string $curr_tab Current status screen tab.
+ * @return void
+ */
+ public function output_tab( $curr_tab ) {
+
+ if ( 'betas' !== $curr_tab ) {
+ return;
+ }
+
+ $addons = llms_helper_get_available_add_ons();
+ array_unshift( $addons, 'lifterlms-com-lifterlms', 'lifterlms-com-lifterlms-helper' );
+ include 'views/beta-testing.php';
+
+ }
+
+}
+return new LLMS_Helper_Betas();
diff --git a/libraries/lifterlms-helper/includes/class-llms-helper-cloned.php b/libraries/lifterlms-helper/includes/class-llms-helper-cloned.php
new file mode 100644
index 0000000000..507d4cda32
--- /dev/null
+++ b/libraries/lifterlms-helper/includes/class-llms-helper-cloned.php
@@ -0,0 +1,68 @@
+get_license_keys();
+
+ if ( ! $keys ) {
+ return;
+ }
+
+ $res = LLMS_Helper_Keys::activate_keys( array_keys( $keys ) );
+
+ if ( ! is_wp_error( $res ) ) {
+
+ $data = $res['data'];
+ if ( isset( $data['activations'] ) ) {
+ foreach ( $data['activations'] as $activation ) {
+ LLMS_Helper_Keys::add_license_key( $activation );
+ }
+ }
+ }
+
+ }
+
+}
+
+return new LLMS_Helper_Cloned();
diff --git a/libraries/lifterlms-helper/includes/class-llms-helper-install.php b/libraries/lifterlms-helper/includes/class-llms-helper-install.php
new file mode 100644
index 0000000000..2421738030
--- /dev/null
+++ b/libraries/lifterlms-helper/includes/class-llms-helper-install.php
@@ -0,0 +1,174 @@
+version ) {
+
+ self::install();
+
+ /**
+ * Action run after the helper library is updated.
+ *
+ * @since 3.0.0
+ */
+ do_action( 'llms_helper_updated' );
+
+ }
+ }
+
+ /**
+ * Core install function
+ *
+ * @since 3.0.0
+ * @since 3.4.0 Skip migration when loaded as a library.
+ *
+ * @return void
+ */
+ public static function install() {
+
+ if ( ! is_blog_installed() ) {
+ return;
+ }
+
+ do_action( 'llms_helper_before_install' );
+
+ if ( ( ! defined( 'LLMS_HELPER_LIB' ) || ! LLMS_HELPER_LIB ) && ! get_option( 'llms_helper_version', '' ) ) {
+ self::_migrate_300();
+ }
+
+ self::update_version();
+
+ do_action( 'llms_helper_after_install' );
+ }
+
+ /**
+ * Update the LifterLMS version record to the latest version
+ *
+ * @since 3.0.0
+ * @since 3.4.0 Use llms_helper() in favor of deprecated LLMS_Helper().
+ *
+ * @param string $version version number.
+ * @return void
+ */
+ public static function update_version( $version = null ) {
+ delete_option( 'llms_helper_version' );
+ add_option( 'llms_helper_version', is_null( $version ) ? llms_helper()->version : $version );
+ }
+
+ /**
+ * Migrate to version 3.0.0
+ *
+ * @since 3.0.0
+ * @since 3.0.2 Unknown.
+ * @since 3.4.0 Use core textdomain.
+ *
+ * @return void
+ */
+ private static function _migrate_300() {
+
+ $text = '' . __( 'Welcome to the LifterLMS Helper', 'lifterlms' ) . '
';
+ $text .= '' . __( 'This plugin allows your website to interact with your subscriptions at LifterLMS.com to ensure your add-ons stay up to date.', 'lifterlms' ) . '
';
+ // Translators: %1$s = Opening anchor tag; %2$s = closing anchor tag.
+ $text .= '' . sprintf( __( 'You can activate your add-ons from the %1$sAdd-Ons & More%2$s screen.', 'lifterlms' ), '', ' ' ) . '
';
+
+ $keys = array();
+ $addons = llms_get_add_ons();
+ if ( ! is_wp_error( $addons ) && isset( $addons['items'] ) ) {
+ foreach ( $addons['items'] as $addon ) {
+
+ $addon = llms_get_add_on( $addon );
+
+ if ( ! $addon->is_installable() ) {
+ continue;
+ }
+
+ $option_name = sprintf( '%s_activation_key', $addon->get( 'slug' ) );
+
+ $key = get_option( $option_name );
+ if ( $key ) {
+ $keys[] = get_option( $option_name );
+ }
+
+ delete_option( $option_name );
+ delete_option( sprintf( '%s_update_key', $addon->get( 'slug' ) ) );
+
+ }
+ }
+
+ if ( $keys ) {
+
+ $res = LLMS_Helper_Keys::activate_keys( $keys );
+
+ if ( ! is_wp_error( $res ) ) {
+
+ $data = $res['data'];
+ if ( isset( $data['activations'] ) ) {
+
+ // Translators: %d = Number of keys that have been migrated.
+ $text .= '' . sprintf( _n( '%d license has been automatically migrated from the previous version of the LifterLMS Helper', '%d licenses have been automatically migrated from the previous version of the LifterLMS Helper.', count( $data['activations'] ), 'lifterlms' ), count( $data['activations'] ) ) . ':
';
+
+ foreach ( $data['activations'] as $activation ) {
+ LLMS_Helper_Keys::add_license_key( $activation );
+ $text .= '' . $activation['license_key'] . '
';
+ }
+ }
+ }
+ }
+
+ LLMS_Admin_Notices::flash_notice( $text, 'info' );
+
+ // Clean up legacy options.
+ $remove = array(
+ 'lifterlms_stripe_activation_key',
+ 'lifterlms_paypal_activation_key',
+ 'lifterlms_gravityforms_activation_key',
+ 'lifterlms_mailchimp_activation_key',
+ 'llms_helper_key_migration',
+ );
+
+ foreach ( $remove as $opt ) {
+ delete_option( $opt );
+ }
+
+ }
+
+}
+
+LLMS_Helper_Install::init();
diff --git a/libraries/lifterlms-helper/includes/class-llms-helper-keys.php b/libraries/lifterlms-helper/includes/class-llms-helper-keys.php
new file mode 100644
index 0000000000..ad0e384433
--- /dev/null
+++ b/libraries/lifterlms-helper/includes/class-llms-helper-keys.php
@@ -0,0 +1,232 @@
+ $keys,
+ 'url' => get_site_url(),
+ );
+
+ $req = new LLMS_Dot_Com_API( '/license/activate', $data );
+ return $req->get_result();
+
+ }
+
+ /**
+ * Add a single license key
+ *
+ * @since 3.0.0
+ *
+ * @param string $activation_data Array of activation details from api call.
+ * @return boolean True if option value has changed, false if not or if update failed.
+ */
+ public static function add_license_key( $activation_data ) {
+
+ $keys = llms_helper_options()->get_license_keys();
+ $keys[ $activation_data['license_key'] ] = array(
+ 'product_id' => $activation_data['id'],
+ 'status' => 1,
+ 'license_key' => $activation_data['license_key'],
+ 'update_key' => $activation_data['update_key'],
+ 'addons' => $activation_data['addons'],
+ );
+
+ return llms_helper_options()->set_license_keys( $keys );
+
+ }
+
+ /**
+ * Check all saved keys to ensure they're still active
+ *
+ * Outputs warnings if the key has expired or the status has changed remotely.
+ *
+ * Runs on daily cron (`llms_check_license_keys`).
+ *
+ * Only make api calls to check once / week.
+ *
+ * @since 3.0.0
+ * @since 3.4.0 Use core textdomain.
+ *
+ * @param bool $force Ignore the once/week setting and force a check.
+ * @return void
+ */
+ public static function check_keys( $force = false ) {
+
+ // Don't trigger during AJAX Requests.
+ if ( defined( 'DOING_AJAX' ) && DOING_AJAX ) {
+ return;
+ }
+
+ // Don't proceed if we don't have any keys to check.
+ $keys = llms_helper_options()->get_license_keys();
+ if ( ! $keys ) {
+ return;
+ }
+
+ if ( ! $force ) {
+ // Only check keys once a week.
+ $last_send = llms_helper_options()->get_last_keys_cron_check();
+ if ( $last_send > apply_filters( 'llms_check_license_keys_interval', strtotime( '-1 week' ) ) ) {
+ return;
+ }
+ }
+
+ // Record check time.
+ llms_helper_options()->set_last_keys_cron_check( time() );
+
+ $data = array(
+ 'keys' => array(),
+ 'url' => get_site_url(),
+ );
+
+ foreach ( $keys as $key ) {
+ $data['keys'][ $key['license_key'] ] = $key['update_key'];
+ }
+
+ $req = new LLMS_Dot_Com_API( '/license/status', $data );
+ if ( ! $req->is_error() ) {
+
+ $res = $req->get_result();
+ include_once LLMS_PLUGIN_DIR . 'includes/admin/class.llms.admin.notices.php';
+
+ /* Translators: %s = License Key */
+ $msg = __( 'The license "%s" is no longer valid and was deactivated. Please visit your account dashboard at https://lifterlms.com/my-account for more information.', 'lifterlms' );
+
+ // Output error responses.
+ if ( isset( $res['data']['errors'] ) ) {
+ foreach ( array_keys( $res['data']['errors'] ) as $key ) {
+ self::remove_license_key( $key );
+ LLMS_Admin_Notices::add_notice(
+ 'key_check_' . sanitize_text_field( $key ),
+ make_clickable( sprintf( $msg, $key ) ),
+ array(
+ 'type' => 'error',
+ 'dismiss_for_days' => 0,
+ )
+ );
+ }
+ }
+
+ // Check status of keys, if the status has changed remove it locally.
+ if ( isset( $res['data']['keys'] ) ) {
+ foreach ( $res['data']['keys'] as $key => $data ) {
+
+ if ( $data['status'] ) {
+ continue;
+ }
+
+ self::remove_license_key( $key );
+ LLMS_Admin_Notices::add_notice(
+ 'key_check_' . sanitize_text_field( $key ),
+ make_clickable( sprintf( $msg, $key ) ),
+ array(
+ 'type' => 'error',
+ 'dismiss_for_days' => 0,
+ )
+ );
+
+ }
+ }
+ }
+ }
+
+ /**
+ * Deactivate LifterLMS API keys with remote server
+ *
+ * @since 3.0.0
+ * @since 3.4.1 Ensure key exists before attempting to deactivate it.
+ *
+ * @param array $keys Array of keys.
+ * @return array
+ */
+ public static function deactivate_keys( $keys ) {
+
+ $keys = array_map( 'sanitize_text_field', $keys );
+ $keys = array_map( 'trim', $keys );
+
+ $data = array(
+ 'keys' => array(),
+ 'url' => get_site_url(),
+ );
+
+ $saved = llms_helper_options()->get_license_keys();
+ foreach ( $keys as $key ) {
+ if ( isset( $saved[ $key ] ) && $saved[ $key ]['update_key'] ) {
+ $data['keys'][ $key ] = $saved[ $key ]['update_key'];
+ }
+ }
+
+ $req = new LLMS_Dot_Com_API( '/license/deactivate', $data );
+ return $req->get_result();
+
+ }
+
+ /**
+ * Retrieve stored information about a key by the license key
+ *
+ * @since 3.3.1
+ *
+ * @param string $key License key.
+ * @return array|false Associative array of license key information. Returns `false` if the provided license key was not found.
+ */
+ public static function get( $key ) {
+
+ $saved = llms_helper_options()->get_license_keys();
+ return isset( $saved[ $key ] ) ? $saved[ $key ] : false;
+
+ }
+
+ /**
+ * Remove a single license key
+ *
+ * @since 3.0.0
+ *
+ * @param string $key License key.
+ * @return boolean True if option value has changed, false if not or if update failed.
+ */
+ public static function remove_license_key( $key ) {
+ $keys = llms_helper_options()->get_license_keys();
+ if ( isset( $keys[ $key ] ) ) {
+ unset( $keys[ $key ] );
+ }
+ return llms_helper_options()->set_license_keys( $keys );
+ }
+
+}
diff --git a/libraries/lifterlms-helper/includes/class-llms-helper-options.php b/libraries/lifterlms-helper/includes/class-llms-helper-options.php
new file mode 100644
index 0000000000..6a75a74b23
--- /dev/null
+++ b/libraries/lifterlms-helper/includes/class-llms-helper-options.php
@@ -0,0 +1,161 @@
+get_options();
+
+ if ( isset( $options[ $key ] ) ) {
+ return $options[ $key ];
+ }
+
+ return $default;
+
+ }
+
+ /**
+ * Retrieve all upgrader options array
+ *
+ * @since 3.0.0
+ *
+ * @return array
+ */
+ private function get_options() {
+ return get_option( 'llms_helper_options', array() );
+ }
+
+ /**
+ * Update the value of an option
+ *
+ * @since 3.0.0
+ *
+ * @param string $key Option name.
+ * @param mixed $val Option value.
+ * @return boolean True if option value has changed, false if not or if update failed.
+ */
+ private function set_option( $key, $val ) {
+
+ $options = $this->get_options();
+ $options[ $key ] = $val;
+ return update_option( 'llms_helper_options', $options, false );
+
+ }
+
+ /**
+ * Get info about addon channel subscriptions
+ *
+ * @since 3.0.0
+ *
+ * @return array
+ */
+ public function get_channels() {
+ return $this->get_option( 'channels', array() );
+ }
+
+ /**
+ * Set info about addon channel subscriptions
+ *
+ * @since 3.0.0
+ *
+ * @param array $channels Array of channel information.
+ * @return boolean True if option value has changed, false if not or if update failed.
+ */
+ public function set_channels( $channels ) {
+ return $this->set_option( 'channels', $channels );
+ }
+
+ /**
+ * Retrieve a timestamp for the last time the keys check cron was run
+ *
+ * @since 3.0.0
+ *
+ * @return int
+ */
+ public function get_last_keys_cron_check() {
+ return $this->get_option( 'last_keys_cron_check', 0 );
+ }
+
+ /**
+ * Set the last cron check time
+ *
+ * @since 3.0.0
+ *
+ * @param int $time Timestamp.
+ * @return boolean True if option value has changed, false if not or if update failed.
+ */
+ public function set_last_keys_cron_check( $time ) {
+ return $this->set_option( 'last_keys_cron_check', $time );
+ }
+
+ /**
+ * Retrieve saved license key data
+ *
+ * @since 3.0.0
+ *
+ * @return array
+ */
+ public function get_license_keys() {
+ return $this->get_option( 'license_keys', array() );
+ }
+
+ /**
+ * Update saved license key data
+ *
+ * @since 3.0.0
+ *
+ * @param array $keys Key data to save.
+ * @return boolean True if option value has changed, false if not or if update failed.
+ */
+ public function set_license_keys( $keys ) {
+ return $this->set_option( 'license_keys', $keys );
+ }
+
+}
diff --git a/libraries/lifterlms-helper/includes/class-llms-helper-upgrader.php b/libraries/lifterlms-helper/includes/class-llms-helper-upgrader.php
new file mode 100644
index 0000000000..d04ec65f3b
--- /dev/null
+++ b/libraries/lifterlms-helper/includes/class-llms-helper-upgrader.php
@@ -0,0 +1,514 @@
+is_installable() ) {
+ return new WP_Error( 'not_installable', __( 'Add-on cannot be installable.', 'lifterlms' ) );
+ }
+
+ // Make sure it's not already installed.
+ if ( 'install' === $action && $addon->is_installed() ) {
+ // Translators: %s = Add-on name.
+ return new WP_Error( 'installed', sprintf( __( '%s is already installed', 'lifterlms' ), $addon->get( 'title' ) ) );
+ }
+
+ // Get download info via llms.com api.
+ $dl_info = $addon->get_download_info();
+ if ( is_wp_error( $dl_info ) ) {
+ return $dl_info;
+ }
+ if ( ! isset( $dl_info['data']['url'] ) ) {
+ return new WP_Error( 'no_url', __( 'An error occured while attempting to retrieve add-on download information. Please try again.', 'lifterlms' ) );
+ }
+
+ require_once ABSPATH . 'wp-admin/includes/file.php';
+ include_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php';
+ WP_Filesystem();
+
+ $skin = new Automatic_Upgrader_Skin();
+
+ if ( 'plugin' === $addon->get_type() ) {
+
+ $upgrader = new Plugin_Upgrader( $skin );
+
+ } elseif ( 'theme' === $addon->get_type() ) {
+
+ $upgrader = new Theme_Upgrader( $skin );
+
+ } else {
+
+ return new WP_Error( 'inconceivable', __( 'The requested action is not possible.', 'lifterlms' ) );
+
+ }
+
+ if ( 'install' === $action ) {
+ remove_filter( 'upgrader_package_options', array( $this, 'upgrader_package_options' ) );
+ $result = $upgrader->install( $dl_info['data']['url'] );
+ add_filter( 'upgrader_package_options', array( $this, 'upgrader_package_options' ) );
+ } elseif ( 'update' === $action ) {
+ $result = $upgrader->upgrade( $addon->get( 'update_file' ) );
+ }
+
+ if ( is_wp_error( $result ) ) {
+ return $result;
+ } elseif ( is_wp_error( $skin->result ) ) {
+ return $skin->result;
+ } elseif ( is_null( $result ) ) {
+ return new WP_Error( 'filesystem', __( 'Unable to connect to the filesystem. Please confirm your credentials.', 'lifterlms' ) );
+ }
+
+ return true;
+
+ }
+
+ /**
+ * Output additional information on plugins update screen when updates are available for an unlicensed addon
+ *
+ * @since 3.0.0
+ * @since 3.0.2 Unknown.
+ * @since 3.4.0 Use core textdomain.
+ *
+ * @param array $plugin_data Array of plugin data.
+ * @param array $res Response data.
+ * @return void
+ */
+ public function in_plugin_update_message( $plugin_data, $res ) {
+
+ if ( empty( $plugin_data['package'] ) ) {
+
+ echo '';
+
+ echo '';
+ _e( 'Your LifterLMS add-on is currently unlicensed and cannot be updated!', 'lifterlms' );
+ echo '
';
+
+ echo '';
+ // Translators: %1$s = Opening anchor tag; %2$s = Closing anchor tag.
+ printf( __( 'If you already have a license, you can activate it on the %1$sadd-ons management screen%2$s.', 'lifterlms' ), '', ' ' );
+ echo '
';
+
+ echo '';
+ // Translators: %s = URI to licensing FAQ.
+ printf( __( 'Learn more about LifterLMS add-on licensing at %s.', 'lifterlms' ), make_clickable( 'https://lifterlms.com/docs/lifterlms-helper/' ) );
+ echo '
';
+
+ }
+
+ }
+
+ /**
+ * Filter API calls to get plugin information and replace it with data from LifterLMS.com API for our addons
+ *
+ * @since 3.0.0
+ *
+ * @param bool $response False (denotes API call should be made to wp.org for plugin info).
+ * @param string $action Name of the API action.
+ * @param obj $args Additional API call args.
+ * @return false|obj
+ */
+ public function plugins_api( $response, $action = '', $args = null ) {
+
+ if ( 'plugin_information' !== $action ) {
+ return $response;
+ }
+
+ if ( empty( $args->slug ) ) {
+ return $response;
+ }
+
+ $core = false;
+
+ if ( 'lifterlms' === $args->slug ) {
+ remove_filter( 'plugins_api', array( $this, 'plugins_api' ), 10, 3 );
+ $args->slug = 'lifterlms-com-lifterlms';
+ $core = true;
+ }
+
+ if ( 0 !== strpos( $args->slug, 'lifterlms-com-' ) ) {
+ return $response;
+ }
+
+ $response = $this->set_plugins_api( $args->slug, true );
+
+ if ( $core ) {
+ add_filter( 'plugins_api', array( $this, 'plugins_api' ), 10, 3 );
+ }
+
+ return $response;
+
+ }
+
+ /**
+ * Handle setting the site transient for plugin updates
+ *
+ * @since 3.0.0
+ * @since 3.0.2 Unknown.
+ *
+ * @param obj $value Transient value.
+ * @return obj
+ */
+ public function pre_set_site_transient_update_things( $value ) {
+
+ if ( empty( $value ) ) {
+ return $value;
+ }
+
+ $which = current_filter();
+ if ( 'pre_set_site_transient_update_plugins' === $which ) {
+ $type = 'plugin';
+ } elseif ( 'pre_set_site_transient_update_themes' === $which ) {
+ $type = 'theme';
+ } else {
+ return $value;
+ }
+
+ $all_products = llms_get_add_ons( false );
+ if ( is_wp_error( $all_products ) || ! isset( $all_products['items'] ) ) {
+ return $value;
+ }
+
+ foreach ( $all_products['items'] as $addon_data ) {
+
+ $addon = llms_get_add_on( $addon_data );
+
+ if ( ! $addon->is_installable() || ! $addon->is_installed() ) {
+ continue;
+ }
+
+ if ( $type !== $addon->get_type() ) {
+ continue;
+ }
+
+ $file = $addon->get( 'update_file' );
+
+ if ( 'plugin' === $type ) {
+
+ if ( 'lifterlms-com-lifterlms' === $addon->get( 'id' ) ) {
+ if ( 'stable' === $addon->get_channel_subscription() || ! $addon->get( 'version_beta' ) ) {
+ continue;
+ }
+ }
+
+ $item = (object) $this->set_plugins_api( $addon->get( 'id' ), false );
+
+ } elseif ( 'theme' === $type ) {
+
+ $item = array(
+ 'theme' => $file,
+ 'new_version' => $addon->get_latest_version(),
+ 'url' => $addon->get_permalink(),
+ 'package' => true,
+ );
+ }
+
+ if ( $addon->has_available_update() ) {
+
+ $value->response[ $file ] = $item;
+ unset( $value->no_update[ $file ] );
+
+ } else {
+
+ $value->no_update[ $file ] = $item;
+ unset( $value->response[ $file ] );
+
+ }
+ }
+
+ return $value;
+
+ }
+
+ /**
+ * Setup an object of addon data for use when requesting plugin information normally acquired from wp.org.
+ *
+ * @since 3.0.0
+ * @since 3.2.1 Set package to `true` for add-ons which don't require a license.
+ * @since 3.4.2 Added a `plugin` property to the returned plugin object,
+ * which is required by `WP_Plugin_Install_List_Table::prepare_items()`.
+ *
+ * @param string $id Addon id.
+ * @param bool $include_sections Whether or not to include additional sections like the description and changelog.
+ * @return object
+ */
+ private function set_plugins_api( $id, $include_sections = true ) {
+
+ $addon = llms_get_add_on( $id );
+
+ if ( 'lifterlms-com-lifterlms' === $id && false !== strpos( $addon->get_latest_version(), 'beta' ) ) {
+
+ require_once ABSPATH . 'wp-admin/includes/plugin-install.php';
+ $item = plugins_api(
+ 'plugin_information',
+ array(
+ 'slug' => 'lifterlms',
+ 'fields' => array(
+ 'banners' => true,
+ 'icons' => true,
+ ),
+ )
+ );
+ $item->version = $addon->get_latest_version();
+ $item->new_version = $addon->get_latest_version();
+ $item->package = true;
+
+ unset( $item->versions );
+
+ $item->sections['changelog'] = $this->get_changelog_for_api( $addon );
+
+ return $item;
+
+ }
+
+ $item = array(
+ 'name' => $addon->get( 'title' ),
+ 'slug' => $id,
+ 'plugin' => $addon->get( 'update_file' ),
+ 'version' => $addon->get_latest_version(),
+ 'new_version' => $addon->get_latest_version(),
+ 'author' => '' . $addon->get( 'author' )['name'] . ' ',
+ 'author_profile' => $addon->get( 'author' )['link'],
+ 'requires' => $addon->get( 'version_wp' ),
+ 'tested' => '',
+ 'requires_php' => $addon->get( 'version_php' ),
+ 'compatibility' => '',
+ 'homepage' => $addon->get( 'permalink' ),
+ 'download_link' => '',
+ 'package' => ( $addon->is_licensed() || ! $addon->requires_license() ),
+ 'banners' => array(
+ 'low' => $addon->get( 'image' ),
+ ),
+ );
+
+ if ( $include_sections ) {
+
+ $item['sections'] = array(
+ 'description' => $addon->get( 'description' ),
+ 'changelog' => $this->get_changelog_for_api( $addon ),
+ );
+
+ }
+
+ return (object) $item;
+
+ }
+
+ /**
+ * Retrieve the changelog for an addon
+ *
+ * Attempts to retrieve changelog HTML from the make blog.
+ *
+ * If the add-on's changelog is empty or a static html file, returns an error
+ * with a link to the release notes category on the make blog.
+ *
+ * @since 3.0.0
+ * @since 3.1.0 Retrieve changelog from the make blog in favor of legacy static html changelogs.
+ * @since 3.2.0 Fix usage of incorrect textdomain.
+ *
+ * @param LLMS_Add_On $addon Add-on object.
+ * @return string
+ */
+ private function get_changelog_for_api( $addon ) {
+
+ $src = $addon->get( 'changelog' );
+ $split = array_filter( explode( '/', $src ) );
+ $tag = end( $split );
+
+ $logs = false;
+ if ( ! empty( $tag ) && false === strpos( $tag, '.html' ) ) {
+ $logs = $this->get_changelog_html( $tag, $src );
+ }
+
+ // Translators: %s = URL for the changelog website.
+ return $logs ? $logs : make_clickable( sprintf( __( 'There was an error retrieving the changelog. Try visiting %s for recent changelogs.', 'lifterlms' ), 'https://make.lifterlms.com/category/release-notes/' ) );
+
+ }
+
+ /**
+ * Retrieve changelog information from the make blog
+ *
+ * Retrieves the most recent 10 changelog entries for the add-on, formats the returned information
+ * into a format suitable to display within the thickbox, adds a link to the full changelog,
+ * and returns the html string.
+ *
+ * If an error is encountered, returns an empty string.
+ *
+ * @since 3.1.0
+ * @since 3.2.0 Fix usage of incorrect textdomain.
+ *
+ * @param string $tag Tag slug for the add-on on the blog.
+ * @param string $url Full URL to the changelog entries for the add-on.
+ * @return string
+ */
+ private function get_changelog_html( $tag, $url ) {
+
+ $ret = '';
+ $req = wp_remote_get( add_query_arg( 'slug', $tag, 'https://make.lifterlms.com/wp-json/wp/v2/tags' ) );
+ $body = json_decode( wp_remote_retrieve_body( $req ), true );
+
+ if ( ! empty( $body ) && ! empty( $body[0]['_links']['wp:post_type'][0]['href'] ) ) {
+
+ $logs_url = $body[0]['_links']['wp:post_type'][0]['href'];
+ $logs_req = wp_remote_get( $logs_url );
+ $logs = json_decode( wp_remote_retrieve_body( $logs_req ), true );
+
+ if ( ! empty( $logs ) && is_array( $logs ) ) {
+ foreach ( $logs as $log ) {
+ $ts = strtotime( $log['date_gmt'] );
+ $date = function_exists( 'wp_date' ) ? wp_date( 'Y-m-d', $ts ) : gmdate( 'Y-m-d', $ts );
+ $split = array_filter( explode( ' ', $log['title']['rendered'] ) );
+ $ver = end( $split );
+ // Translators: %1$s - Version number; %2$s - Release date.
+ $ret .= '
' . sprintf( __( 'Version %1$s - %2$s', 'lifterlms' ), sanitize_text_field( wp_strip_all_tags( trim( $ver ) ) ), $date ) . ' ';
+ $ret .= strip_tags( $log['content']['rendered'], '' );
+ }
+ }
+
+ $ret .= ' ';
+ // Translators: %s = URL to the full changelog.
+ $ret .= ' ' . make_clickable( sprintf( __( 'View the full changelog at %s.', 'lifterlms' ), $url ) ) . '
';
+
+ }
+
+ return $ret;
+
+ }
+
+ /**
+ * Get a real package download url for a LifterLMS add-on
+ *
+ * This is called immediately prior to package upgrades.
+ *
+ * @since 3.0.0
+ * @since 3.0.2 Unknown.
+ * @since 3.2.1 Correctly process addons which do not require a license (e.g. free products).
+ *
+ * @param array $options Package option data.
+ * @return array
+ */
+ public function upgrader_package_options( $options ) {
+
+ if ( ! isset( $options['hook_extra'] ) ) {
+ return $options;
+ }
+
+ if ( isset( $options['hook_extra']['plugin'] ) ) {
+ $file = $options['hook_extra']['plugin'];
+ } elseif ( isset( $options['hook_extra']['theme'] ) ) {
+ $file = $options['hook_extra']['theme'];
+ } else {
+ return $options;
+ }
+
+ $addon = llms_get_add_on( $file, 'update_file' );
+ if ( ! $addon || ! $addon->is_installable() || ( $addon->requires_license() && ! $addon->is_licensed() ) ) {
+ return $options;
+ }
+
+ $info = $addon->get_download_info();
+ if ( is_wp_error( $info ) || ! isset( $info['data'] ) || ! isset( $info['data']['url'] ) ) {
+ return $options;
+ }
+
+ if ( true === $options['package'] ) {
+ $options['package'] = $info['data']['url'];
+ }
+
+ return $options;
+
+ }
+
+}
diff --git a/libraries/lifterlms-helper/includes/functions-llms-helper.php b/libraries/lifterlms-helper/includes/functions-llms-helper.php
new file mode 100644
index 0000000000..2d43ae31af
--- /dev/null
+++ b/libraries/lifterlms-helper/includes/functions-llms-helper.php
@@ -0,0 +1,61 @@
+get_license_keys() as $key ) {
+ if ( 1 == $key['status'] ) {
+ $ids = array_merge( $ids, $key['addons'] );
+ }
+ if ( false === $installable_only ) {
+ $ids[] = $key['product_id'];
+ }
+ }
+
+ return array_unique( $ids );
+
+}
+
+/**
+ * Deletes transient data related to plugin and theme updates
+ *
+ * @since 3.2.1
+ *
+ * @return void
+ */
+function llms_helper_flush_cache() {
+
+ delete_transient( 'llms_products_api_result' );
+ delete_site_transient( 'update_plugins' );
+ delete_site_transient( 'update_themes' );
+
+}
diff --git a/libraries/lifterlms-helper/includes/model-llms-helper-add-on.php b/libraries/lifterlms-helper/includes/model-llms-helper-add-on.php
new file mode 100644
index 0000000000..a45a23ab46
--- /dev/null
+++ b/libraries/lifterlms-helper/includes/model-llms-helper-add-on.php
@@ -0,0 +1,18 @@
+requires_license();
+
+ $id = $this->get( 'id' );
+ foreach ( llms_helper_options()->get_license_keys() as $data ) {
+ /**
+ * 1. If license is not required, return the first license found.
+ * 2. If the addon matches the licensed product
+ * 3. If the addon is included in the licensed bundle product.
+ */
+ if ( ! $requires_license || $id === $data['product_id'] || in_array( $id, $data['addons'], true ) ) {
+ return $data;
+ }
+ }
+
+ return false;
+
+ }
+
+ /**
+ * Retrieve the update channel for the addon
+ *
+ * @since 3.0.0
+ *
+ * @return string
+ */
+ public function get_channel_subscription() {
+ $channels = llms_helper_options()->get_channels();
+ return isset( $channels[ $this->get( 'id' ) ] ) ? $channels[ $this->get( 'id' ) ] : 'stable';
+ }
+
+ /**
+ * Retrieve download information for an add-on
+ *
+ * @since 3.0.0
+ * @since 3.2.1 Allow getting download info for add-ons which do not require licenses.
+ * @since 3.4.0 Use core textdomain.
+ *
+ * @return WP_Error|array
+ */
+ public function get_download_info() {
+
+ $key = $this->find_license();
+
+ if ( $this->requires_license() && ! $key ) {
+ return new WP_Error( 'no_license', __( 'Unable to locate a license key for the selected add-on.', 'lifterlms' ) );
+ }
+
+ $args = array(
+ 'url' => get_site_url(),
+ 'add_on_slug' => $this->get( 'slug' ),
+ 'channel' => $this->get_channel_subscription(),
+ );
+
+ if ( $key ) {
+ $args['license_key'] = $key['license_key'];
+ $args['update_key'] = $key['update_key'];
+ }
+
+ $req = new LLMS_Dot_Com_API(
+ '/license/download',
+ $args
+ );
+
+ $data = $req->get_result();
+
+ if ( $req->is_error() ) {
+ return $data;
+ }
+
+ return $data;
+
+ }
+
+ /**
+ * Translate strings
+ *
+ * @since 3.0.0
+ * @since 3.4.0 Use core textdomain.
+ *
+ * @param string $string Untranslated string / key.
+ * @return string
+ */
+ public function get_l10n( $string ) {
+
+ $strings = array(
+
+ 'active' => __( 'Active', 'lifterlms' ),
+ 'inactive' => __( 'Inactive', 'lifterlms' ),
+
+ 'installed' => __( 'Installed', 'lifterlms' ),
+ 'uninstalled' => __( 'Not Installed', 'lifterlms' ),
+
+ 'activate' => __( 'Activate', 'lifterlms' ),
+ 'deactivate' => __( 'Deactivate', 'lifterlms' ),
+ 'install' => __( 'Install', 'lifterlms' ),
+
+ 'none' => __( 'N/A', 'lifterlms' ),
+
+ 'license_active' => __( 'Licensed', 'lifterlms' ),
+ 'license_inactive' => __( 'Unlicensed', 'lifterlms' ),
+
+ );
+
+ return $strings[ $string ];
+
+ }
+
+ /**
+ * Determine the status of an addon's license
+ *
+ * @since 3.0.0
+ * @since 3.2.1 Use `requires_license()` instead of checking `has_license` prop directly.
+ *
+ * @param bool $translate If true, returns the translated string for on-screen display.
+ * @return string
+ */
+ public function get_license_status( $translate = false ) {
+
+ if ( ! $this->requires_license() ) {
+ $ret = 'none';
+ } else {
+ $ret = $this->is_licensed() ? 'license_active' : 'license_inactive';
+ }
+
+ return $translate ? $this->get_l10n( $ret ) : $ret;
+
+ }
+
+ /**
+ * Install the add-on via LifterLMS.com
+ *
+ * @since 3.0.0
+ * @since 3.4.0 Use core textdomain.
+ *
+ * @return string|WP_Error
+ */
+ public function install() {
+
+ $ret = LLMS_Helper()->upgrader()->install_addon( $this );
+
+ if ( true === $ret ) {
+
+ /* Translators: %s = Add-on name */
+ return sprintf( __( '%s was successfully installed.', 'lifterlms' ), $this->get( 'title' ) );
+
+ } elseif ( is_wp_error( $ret ) ) {
+
+ return $ret;
+
+ }
+
+ /* Translators: %s = Add-on name */
+ return new WP_Error( 'activation', sprintf( __( 'Could not install %s.', 'lifterlms' ), $this->get( 'title' ) ) );
+
+ }
+
+ /**
+ * Determines if the add-on is licensed
+ *
+ * @since 3.0.0
+ *
+ * @return bool
+ */
+ public function is_licensed() {
+ return ( false !== $this->find_license() );
+ }
+
+ /**
+ * Determines if the add-on requires a license
+ *
+ * @since 3.2.1
+ *
+ * @return bool
+ */
+ public function requires_license() {
+ return llms_parse_bool( $this->get( 'has_license' ) );
+ }
+
+ /**
+ * Update the addons update channel subscription
+ *
+ * @since 3.0.0
+ *
+ * @param string $channel Channel name [stable|beta].
+ * @return boolean
+ */
+ public function subscribe_to_channel( $channel = 'stable' ) {
+
+ $channels = llms_helper_options()->get_channels();
+ $channels[ $this->get( 'id' ) ] = $channel;
+ return llms_helper_options()->set_channels( $channels );
+
+ }
+
+ /**
+ * Install the add-on via LifterLMS.com
+ *
+ * @since 3.0.0
+ * @since 3.4.0 Use core textdomain.
+ *
+ * @return string|WP_Error
+ */
+ public function update() {
+
+ $ret = LLMS_Helper()->upgrader()->install_addon( $this, 'update' );
+
+ if ( true === $ret ) {
+
+ /* Translators: %s = Add-on name */
+ return sprintf( __( '%s was successfully updated.', 'lifterlms' ), $this->get( 'title' ) );
+
+ } elseif ( is_wp_error( $ret ) ) {
+
+ return $ret;
+
+ }
+
+ /* Translators: %s = Add-on name */
+ return new WP_Error( 'activation', sprintf( __( 'Could not update %s.', 'lifterlms' ), $this->get( 'title' ) ) );
+
+ }
+
+}
diff --git a/libraries/lifterlms-helper/includes/models/index.php b/libraries/lifterlms-helper/includes/models/index.php
new file mode 100644
index 0000000000..ff2b6071fd
--- /dev/null
+++ b/libraries/lifterlms-helper/includes/models/index.php
@@ -0,0 +1 @@
+
+
+
+ for LifterLMS or any available add-ons will allow you to automatically update to the latest beta release for the given plugin or theme.', 'lifterlms' ); ?>
+
+
+
+
+
+
+
+
+ ', '
' );
+ ?>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ get( 'title' ); ?>
+
+
+ get_channel_subscription() ); ?>>
+ get_channel_subscription() ); ?>>
+
+
+ get_installed_version(); ?>
+ get( 'version_beta' ) ? $addon->get( 'version_beta' ) : __( 'N/A', 'lifterlms' ); ?>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+version );
+ }
+
+ /**
+ * When loaded as a library included by the LifterLMS core localization is handled by the LifterLMS core.
+ *
+ * When the plugin is loaded by itself as a plugin, we must localize it independently.
+ */
+ if ( ! defined( 'LLMS_REST_API_LIB' ) || ! LLMS_REST_API_LIB ) {
+ add_action( 'init', array( $this, 'load_textdomain' ), 0 );
+ }
+
+ // Authentication needs to run early to handle basic auth.
+ include_once LLMS_REST_API_PLUGIN_DIR . 'includes/class-llms-rest-authentication.php';
+
+ // Load everything else.
+ add_action( 'plugins_loaded', array( $this, 'init' ), 10 );
+
+ }
+
+ /**
+ * Include files and instantiate classes.
+ *
+ * @since 1.0.0-beta.1
+ * @since 1.0.0-beta.4 Load authentication early.
+ *
+ * @return void
+ */
+ public function includes() {
+
+ // Abstracts.
+ include_once LLMS_REST_API_PLUGIN_DIR . 'includes/abstracts/class-llms-rest-database-resource.php';
+ include_once LLMS_REST_API_PLUGIN_DIR . 'includes/abstracts/class-llms-rest-webhook-data.php';
+
+ // Functions.
+ include_once LLMS_REST_API_PLUGIN_DIR . 'includes/llms-rest-functions.php';
+ include_once LLMS_REST_API_PLUGIN_DIR . 'includes/server/llms-rest-server-functions.php';
+
+ // Models.
+ include_once LLMS_REST_API_PLUGIN_DIR . 'includes/models/class-llms-rest-api-key.php';
+ include_once LLMS_REST_API_PLUGIN_DIR . 'includes/models/class-llms-rest-webhook.php';
+
+ // Classes.
+ include_once LLMS_REST_API_PLUGIN_DIR . 'includes/class-llms-rest-api-keys.php';
+ include_once LLMS_REST_API_PLUGIN_DIR . 'includes/class-llms-rest-api-keys-query.php';
+ include_once LLMS_REST_API_PLUGIN_DIR . 'includes/class-llms-rest-capabilities.php';
+ include_once LLMS_REST_API_PLUGIN_DIR . 'includes/class-llms-rest-install.php';
+ include_once LLMS_REST_API_PLUGIN_DIR . 'includes/class-llms-rest-webhooks.php';
+ include_once LLMS_REST_API_PLUGIN_DIR . 'includes/class-llms-rest-webhooks-query.php';
+
+ // Include admin classes.
+ if ( is_admin() ) {
+ include_once LLMS_REST_API_PLUGIN_DIR . 'includes/admin/class-llms-rest-admin-settings.php';
+ include_once LLMS_REST_API_PLUGIN_DIR . 'includes/admin/class-llms-rest-admin-form-controller.php';
+ }
+
+ add_action( 'rest_api_init', array( $this, 'rest_api_includes' ), 5 );
+ add_action( 'rest_api_init', array( $this, 'rest_api_controllers_init' ), 10 );
+
+ }
+
+ /**
+ * Retrieve an instance of the API Keys management singleton.
+ *
+ * @example $keys = LLMS_REST_API()->keys();
+ *
+ * @since 1.0.0-beta.1
+ *
+ * @return LLMS_REST_API_Keys
+ */
+ public function keys() {
+ return LLMS_REST_API_Keys::instance();
+ }
+
+ /**
+ * Include REST api specific files.
+ *
+ * @since 1.0.0-beta.1
+ * @since 1.0.0-beta.9 Include memberships controller class file.
+ * @since 1.0.0-beta.18 Include access plans controller class file.
+ *
+ * @return void
+ */
+ public function rest_api_includes() {
+
+ $includes = array(
+
+ // Abstracts first.
+ 'abstracts/class-llms-rest-controller-stubs',
+ 'abstracts/class-llms-rest-controller',
+ 'abstracts/class-llms-rest-users-controller',
+ 'abstracts/class-llms-rest-posts-controller',
+
+ // Functions.
+ 'server/llms-rest-server-functions',
+
+ // Controllers.
+ 'server/class-llms-rest-api-keys-controller',
+ 'server/class-llms-rest-access-plans-controller',
+ 'server/class-llms-rest-courses-controller',
+ 'server/class-llms-rest-sections-controller',
+ 'server/class-llms-rest-lessons-controller',
+ 'server/class-llms-rest-memberships-controller',
+ 'server/class-llms-rest-enrollments-controller',
+ 'server/class-llms-rest-instructors-controller',
+ 'server/class-llms-rest-students-controller',
+ 'server/class-llms-rest-students-progress-controller',
+ 'server/class-llms-rest-webhooks-controller',
+
+ );
+
+ foreach ( $includes as $include ) {
+ include_once LLMS_REST_API_PLUGIN_DIR . 'includes/' . $include . '.php';
+ }
+ }
+
+ /**
+ * Instantiate REST api Controllers.
+ *
+ * @since 1.0.0-beta.1
+ * @since 1.0.0-beta.9 Init memberships controller.
+ * @since 1.0.0-beta.18 Init access plans controller.
+ *
+ * @return void
+ */
+ public function rest_api_controllers_init() {
+
+ $controllers = array(
+ 'LLMS_REST_API_Keys_Controller',
+ 'LLMS_REST_Courses_Controller',
+ 'LLMS_REST_Sections_Controller',
+ 'LLMS_REST_Lessons_Controller',
+ 'LLMS_REST_Memberships_Controller',
+ 'LLMS_REST_Instructors_Controller',
+ 'LLMS_REST_Students_Controller',
+ 'LLMS_REST_Students_Progress_Controller',
+ 'LLMS_REST_Enrollments_Controller',
+ 'LLMS_REST_Webhooks_Controller',
+ 'LLMS_REST_Access_Plans_Controller',
+ );
+
+ foreach ( $controllers as $controller ) {
+ $controller_instance = new $controller();
+ $controller_instance->register_routes();
+ }
+
+ }
+
+ /**
+ * Include all required files and classes.
+ *
+ * @since 1.0.0-beta.1
+ * @since 1.0.0-beta.6 Load webhooks actions at init 1 instead of init 10.
+ * @since 1.0.0-beta.8 Load webhooks actions a little bit later: at init 6 instead of init 10,
+ * just after all the db tables are created (init 5),
+ * to avoid PHP warnings on first plugin activation.
+ * @since 1.0.0-beta.22 Bump minimum required version to 6.0.0-alpha.1.
+ * Use `llms()` in favor of deprecated `LLMS()`.
+ * @since 1.0.0-beta.25 Perform some db clean-up on user deletion.
+ * Bump minimum required version to 6.5.0.
+ *
+ * @return void
+ */
+ public function init() {
+
+ // Only load if we have the minimum LifterLMS version installed & activated.
+ if ( ! function_exists( 'llms' ) || version_compare( '6.5.0', llms()->version, '>' ) ) {
+ return;
+ }
+
+ // Load includes.
+ $this->includes();
+
+ add_action( 'init', array( $this->webhooks(), 'load' ), 6 );
+ add_action( 'deleted_user', array( $this, 'on_user_deletion' ) );
+
+ }
+
+ /**
+ * When a user is deleted in WordPress, delete corresponding LifterLMS REST API data.
+ *
+ * @since 1.0.0-beta.25
+ *
+ * @param int $user_id The ID of the just deleted WP_User.
+ * @return void
+ */
+ public function on_user_deletion( $user_id ) {
+
+ global $wpdb;
+
+ // Delete user's API keys.
+ $wpdb->delete(
+ "{$wpdb->prefix}lifterlms_api_keys",
+ array(
+ 'user_id' => $user_id,
+ ),
+ array( '%d' )
+ );// db-cache ok.
+ }
+
+ /**
+ * Load l10n files.
+ *
+ * This method is only used when the plugin is loaded as a standalone plugin (for development purposes),
+ * otherwise (when loaded as a library from within the LifterLMS core plugin) the localization
+ * strings are included into the LifterLMS Core plugin's po/mo files and are localized by the LifterLMS
+ * core plugin.
+ *
+ * Files can be found in the following order (The first loaded file takes priority):
+ * 1. WP_LANG_DIR/lifterlms/lifterlms-rest-LOCALE.mo
+ * 2. WP_LANG_DIR/plugins/lifterlms-rest-LOCALE.mo
+ * 3. WP_CONTENT_DIR/plugins/lifterlms-rest/i18n/lifterlms-rest-LOCALE.mo
+ *
+ * Note: The function `load_plugin_textdomain()` is not used because the same textdomain as the LifterLMS core
+ * is used for this plugin but the file is named `lifterlms-rest` in order to allow using a separate language
+ * file for each codebase.
+ *
+ * @since 1.0.0-beta.1
+ * @since 1.0.0-beta.17 Fixed the name of the MO loaded from the safe directory: `lifterlms-{$locale}.mo` to `lifterlms-rest-{$locale}.mo`.
+ * Fixed double slash typo in plugin textdomain path argument.
+ * Fixed issue causing language files to not load properly.
+ *
+ * @return void
+ */
+ public function load_textdomain() {
+
+ // Load locale.
+ $locale = apply_filters( 'plugin_locale', get_locale(), 'lifterlms' );
+
+ // Load from the LifterLMS "safe" directory if it exists.
+ load_textdomain( 'lifterlms', WP_LANG_DIR . '/lifterlms/lifterlms-rest-' . $locale . '.mo' );
+
+ // Load from the default plugins language file directory.
+ load_textdomain( 'lifterlms', WP_LANG_DIR . '/plugins/lifterlms-rest-' . $locale . '.mo' );
+
+ // Load from the plugin's language file directory.
+ load_textdomain( 'lifterlms', LLMS_REST_API_PLUGIN_DIR . '/i18n/lifterlms-rest-' . $locale . '.mo' );
+
+ }
+
+ /**
+ * Retrieve an instance of the webhooks management singleton.
+ *
+ * @example $webhooks = LLMS_REST_API()->webhooks();
+ *
+ * @since 1.0.0-beta.1
+ *
+ * @return LLMS_REST_Webhooks
+ */
+ public function webhooks() {
+ return LLMS_REST_Webhooks::instance();
+ }
+
+}
diff --git a/libraries/lifterlms-rest/includes/abstracts/class-llms-rest-controller-stubs.php b/libraries/lifterlms-rest/includes/abstracts/class-llms-rest-controller-stubs.php
new file mode 100644
index 0000000000..64f9012c2c
--- /dev/null
+++ b/libraries/lifterlms-rest/includes/abstracts/class-llms-rest-controller-stubs.php
@@ -0,0 +1,245 @@
+get_object().
+ */
+ protected function create_object( $prepared, $request ) {
+
+ // @todo: add version to message.
+
+ // Translators: %s = method name.
+ _doing_it_wrong( 'LLMS_REST_Controller::create_object', sprintf( __( "Method '%s' must be overridden.", 'lifterlms' ), __METHOD__ ), '1.0.0-beta.1' );
+
+ // For example.
+ return $this->get_object( $this->get_object_id( $prepared ) );
+
+ }
+
+ /**
+ * Retrieve an ID from the object
+ *
+ * @since 1.0.0-beta.1
+ *
+ * @param obj $object Item object.
+ * @return int
+ */
+ protected function get_object_id( $object ) {
+ if ( is_object( $object ) && ! empty( $object->id ) ) {
+ return $object->id;
+ } elseif ( is_array( $object ) && ! empty( $object['id'] ) ) {
+ return $object['id'];
+ } elseif ( method_exists( $object, 'get_id' ) ) {
+ return $object->get_id();
+ } elseif ( method_exists( $object, 'get' ) ) {
+ return $object->get( 'id' );
+ }
+
+ // @todo: add version to message.
+
+ // Translators: %s = method name.
+ _doing_it_wrong( 'LLMS_REST_Controller::get_object_id', sprintf( __( "Method '%s' must be overridden.", 'lifterlms' ), __METHOD__ ), '1.0.0-beta.1' );
+
+ // For example.
+ return 0;
+
+ }
+
+ /**
+ * Retrieve a query object based on arguments from a `get_items()` (collection) request.
+ *
+ * @since 1.0.0-beta.1
+ *
+ * @param array $prepared Array of collection arguments.
+ * @param WP_REST_Request $request Request object.
+ * @return object
+ */
+ protected function get_objects_query( $prepared, $request ) {
+
+ // Translators: %s = method name.
+ _doing_it_wrong( 'LLMS_REST_Controller::get_objects_query', sprintf( __( "Method '%s' must be overridden.", 'lifterlms' ), __METHOD__ ), '1.0.0-beta.1' );
+
+ // For example.
+ return new WP_Query( $prepared );
+
+ }
+
+ /**
+ * Retrieve an array of objects from the result of $this->get_objects_query().
+ *
+ * @since 1.0.0-beta.1
+ *
+ * @param obj $query Objects query result.
+ * @return obj[]
+ */
+ protected function get_objects_from_query( $query ) {
+
+ // Translators: %s = method name.
+ _doing_it_wrong( 'LLMS_REST_Controller::get_objects_from_query', sprintf( __( "Method '%s' must be overridden.", 'lifterlms' ), __METHOD__ ), '1.0.0-beta.1' );
+
+ // For example.
+ return array();
+
+ }
+
+ /**
+ * Retrieve pagination information from an objects query.
+ *
+ * @since 1.0.0-beta.1
+ *
+ * @param obj $query Objects query result.
+ * @param array $prepared Array of collection arguments.
+ * @param WP_REST_Request $request Request object.
+ * @return array {
+ * Array of pagination information.
+ *
+ * @type int $current_page Current page number.
+ * @type int $total_results Total number of results.
+ * @type int $total_pages Total number of results pages.
+ * }
+ */
+ protected function get_pagination_data_from_query( $query, $prepared, $request ) {
+
+ // Translators: %s = method name.
+ _doing_it_wrong( 'LLMS_REST_Controller::get_pagination_data_from_query', sprintf( __( "Method '%s' must be overridden.", 'lifterlms' ), __METHOD__ ), '1.0.0-beta.1' );
+
+ // For example.
+ return array(
+ 'current_page' => 1,
+ 'total_results' => 1,
+ 'total_pages' => 1,
+ );
+
+ }
+
+ /**
+ * Prepare an object for response.
+ *
+ * @since 1.0.0-beta.1
+ * @since 1.0.0-beta.3 Conditionally throw `_doing_it_wrong()`.
+ *
+ * @param LLMS_Abstract_User_Data $object User object.
+ * @param WP_REST_Request $request Request object.
+ * @return array
+ */
+ protected function prepare_object_for_response( $object, $request ) {
+
+ if ( ! method_exists( $object, 'get' ) ) {
+ // Translators: %s = method name.
+ _doing_it_wrong( 'LLMS_REST_Controller::prepare_object_for_response', sprintf( __( "Method '%s' must be overridden.", 'lifterlms' ), __METHOD__ ), '1.0.0-beta.1' );
+ }
+
+ $prepared = array();
+ $map = array_flip( $this->map_schema_to_database() );
+ $fields = $this->get_fields_for_response( $request );
+
+ foreach ( $map as $db_key => $schema_key ) {
+ if ( in_array( $schema_key, $fields, true ) ) {
+ $prepared[ $schema_key ] = $object->get( $db_key );
+ }
+ }
+
+ return $prepared;
+
+ }
+
+ /**
+ * Update the object in the database with prepared data.
+ *
+ * @since 1.0.0-beta.1
+ *
+ * @param array $prepared Prepared item data.
+ * @param WP_REST_Request $request Request object.
+ * @return obj Object Instance of object from $this->get_object().
+ */
+ protected function update_object( $prepared, $request ) {
+
+ // @todo: add version to message.
+
+ // Translators: %s = method name.
+ _doing_it_wrong( 'LLMS_REST_Controller::update_object', sprintf( __( "Method '%s' must be overridden.", 'lifterlms' ), __METHOD__ ), '1.0.0-beta.1' );
+
+ // For example.
+ return $this->get_object( $prepared['id'] );
+
+ }
+
+}
diff --git a/libraries/lifterlms-rest/includes/abstracts/class-llms-rest-controller.php b/libraries/lifterlms-rest/includes/abstracts/class-llms-rest-controller.php
new file mode 100644
index 0000000000..5ea8d2c24f
--- /dev/null
+++ b/libraries/lifterlms-rest/includes/abstracts/class-llms-rest-controller.php
@@ -0,0 +1,767 @@
+prepare_item_for_database( $request );
+ $object = $this->create_object( $item, $request );
+ $schema = $this->get_item_schema();
+
+ if ( is_wp_error( $object ) ) {
+ return $object;
+ }
+
+ $this->object_inserted( $object, $request, $schema, true );
+
+ $fields_update = $this->update_additional_fields_for_object( $item, $request );
+ if ( is_wp_error( $fields_update ) ) {
+ return $fields_update;
+ }
+
+ $this->object_completely_inserted( $object, $request, $schema, true );
+
+ $request->set_param( 'context', 'edit' );
+
+ $response = $this->prepare_item_for_response( $object, $request );
+ $response = rest_ensure_response( $response );
+
+ $response->set_status( 201 );
+ $response->header( 'Location', rest_url( sprintf( '%s/%s/%d', $this->namespace, $this->rest_base, $this->get_object_id( $object ) ) ) );
+
+ return $response;
+
+ }
+
+ /**
+ * Called right after a resource is inserted (created/updated).
+ *
+ * @since 1.0.0-beta.12
+ *
+ * @param object $object Inserted or updated object.
+ * @param WP_REST_Request $request Request object.
+ * @param array $schema The item schema.
+ * @param bool $creating True when creating a post, false when updating.
+ */
+ protected function object_inserted( $object, $request, $schema, $creating ) {
+
+ $type = $this->get_object_type();
+ /**
+ * Fires after a single llms resource is created or updated via the REST API.
+ *
+ * The dynamic portion of the hook name, `$type`, refers to the object type this controller is responsible for managing.
+ *
+ * @since 1.0.0-beta.12
+ *
+ * @param object $object Inserted or updated object.
+ * @param WP_REST_Request $request Request object.
+ * @param array $schema The item schema.
+ * @param bool $creating True when creating a post, false when updating.
+ */
+ do_action( "llms_rest_insert_{$type}", $object, $request, $schema, $creating );
+ }
+
+ /**
+ * Called right after a resource is completely inserted (created/updated).
+ *
+ * @since 1.0.0-beta.12
+ *
+ * @param LLMS_Post $object Inserted or updated object.
+ * @param WP_REST_Request $request Request object.
+ * @param array $schema The item schema.
+ * @param bool $creating True when creating a post, false when updating.
+ */
+ protected function object_completely_inserted( $object, $request, $schema, $creating ) {
+
+ $type = $this->get_object_type();
+ /**
+ * Fires after a single llms resource is completely created or updated via the REST API.
+ *
+ * The dynamic portion of the hook name, `$type`, refers to the object type this controller is responsible for managing.
+ *
+ * @since 1.0.0-beta.12
+ *
+ * @param object $object Inserted or updated object.
+ * @param WP_REST_Request $request Request object.
+ * @param array $schema The item schema.
+ * @param bool $creating True when creating a post, false when updating.
+ */
+ do_action( "llms_rest_after_insert_{$type}", $object, $request, $schema, $creating );
+ }
+
+ /**
+ * Delete the item.
+ *
+ * @since 1.0.0-beta.1
+ *
+ * @param WP_REST_Request $request Request object.
+ * @return WP_REST_Response|WP_Error
+ */
+ public function delete_item( $request ) {
+
+ $object = $this->get_object( $request['id'], false );
+
+ // We don't return 404s for items that are not found.
+ if ( ! is_wp_error( $object ) ) {
+
+ // If there was an error deleting the object return the error. If the error is that the object doesn't exist return 204 below!
+ $del = $this->delete_object( $object, $request );
+ if ( is_wp_error( $del ) ) {
+ return $del;
+ }
+ }
+
+ $response = rest_ensure_response( null );
+ $response->set_status( 204 );
+
+ return $response;
+
+ }
+
+ /**
+ * Retrieves the query params for the objects collection.
+ *
+ * @since 1.0.0-beta.1
+ * @since 1.0.0-beta.12 Added `search_columns` collection param for searchable resources.
+ *
+ * @return array Collection parameters.
+ */
+ public function get_collection_params() {
+
+ $query_params = parent::get_collection_params();
+
+ $query_params['context']['default'] = 'view';
+
+ // We're not currently implementing searching for all of our controllers.
+ if ( empty( $this->is_searchable ) ) {
+ unset( $query_params['search'] );
+ } elseif ( ! empty( $this->search_columns_mapping ) ) {
+
+ $search_columns = array_keys( $this->search_columns_mapping );
+
+ $query_params['search_columns'] = array(
+ 'description' => __( 'Column names to be searched. Accepts a single column or a comma separated list of columns.', 'lifterlms' ),
+ 'type' => 'array',
+ 'items' => array(
+ 'type' => 'string',
+ 'enum' => $search_columns,
+ ),
+ 'default' => $search_columns,
+ );
+ }
+
+ // page and per_page params are already specified in WP_Rest_Controller->get_collection_params().
+
+ $query_params['order'] = array(
+ 'description' => __( 'Order sort attribute ascending or descending.', 'lifterlms' ),
+ 'type' => 'string',
+ 'default' => 'asc',
+ 'enum' => array( 'asc', 'desc' ),
+ 'validate_callback' => 'rest_validate_request_arg',
+ );
+
+ $query_params['orderby'] = array(
+ 'description' => __( 'Sort collection by object attribute.', 'lifterlms' ),
+ 'type' => 'string',
+ 'default' => $this->orderby_properties[0],
+ 'enum' => $this->orderby_properties,
+ 'validate_callback' => 'rest_validate_request_arg',
+ );
+
+ $query_params['include'] = array(
+ 'description' => __( 'Limit results to a list of ids. Accepts a single id or a comma separated list of ids.', 'lifterlms' ),
+ 'type' => 'array',
+ 'items' => array(
+ 'type' => 'integer',
+ ),
+ 'validate_callback' => 'rest_validate_request_arg',
+ );
+
+ $query_params['exclude'] = array(
+ 'description' => __( 'Exclude a list of ids from results. Accepts a single id or a comma separated list of ids.', 'lifterlms' ),
+ 'type' => 'array',
+ 'items' => array(
+ 'type' => 'integer',
+ ),
+ 'validate_callback' => 'rest_validate_request_arg',
+ );
+
+ return $query_params;
+ }
+
+ /**
+ * Get a single item.
+ *
+ * @since 1.0.0-beta.1
+ *
+ * @param WP_REST_Request $request Full details about the request.
+ * @return WP_Error|WP_REST_Response
+ */
+ public function get_item( $request ) {
+
+ $object = $this->get_object( (int) $request['id'] );
+ if ( is_wp_error( $object ) ) {
+ return $object;
+ }
+
+ $response = $this->prepare_item_for_response( $object, $request );
+
+ return rest_ensure_response( $response );
+
+ }
+
+ /**
+ * Retrieves all items
+ *
+ * @since 1.0.0-beta.1
+ * @since 1.0.0-beta.3 Fix an issue displaying a last page for lists with 0 possible results.
+ * @since 1.0.0-beta.7 Broken into several methods so to improve abstraction.
+ * @since 1.0.0-beta.12 Return early if `prepare_collection_query_args()` is a `WP_Error`.
+ *
+ * @param WP_REST_Request $request Full details about the request.
+ * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
+ */
+ public function get_items( $request ) {
+
+ $prepared = $this->prepare_collection_query_args( $request );
+ if ( is_wp_error( $prepared ) ) {
+ return $prepared;
+ }
+
+ $query = $this->get_objects_query( $prepared, $request );
+ $pagination = $this->get_pagination_data_from_query( $query, $prepared, $request );
+
+ // Out-of-bounds, run the query again on page one to get a proper total count.
+ if ( $pagination['total_results'] < 1 ) {
+
+ $prepared_for_total_count = $this->prepare_args_for_total_count_query( $prepared, $request );
+ $count_query = $this->get_objects_query( $prepared_for_total_count, $request );
+ $count_results = $this->get_pagination_data_from_query( $count_query, $prepared_for_total_count, $request );
+
+ $pagination['total_results'] = $count_results['total_results'];
+ }
+
+ if ( $pagination['current_page'] > $pagination['total_pages'] && $pagination['total_results'] > 0 ) {
+ return llms_rest_bad_request_error( __( 'The page number requested is larger than the number of pages available.', 'lifterlms' ) );
+ }
+
+ $objects = $this->get_objects_from_query( $query );
+ $items = $this->prepare_collection_items_for_response( $objects, $request );
+
+ $response = rest_ensure_response( $items );
+ $response = $this->add_header_pagination( $response, $pagination, $request );
+
+ return $response;
+
+ }
+
+ /**
+ * Format query arguments to retrieve a collection of objects.
+ *
+ * @since 1.0.0-beta.7
+ * @since 1.0.0-beta.12 Prepare args for search and call collection params to query args map method.
+ *
+ * @param WP_REST_Request $request Full details about the request.
+ * @return array|WP_Error
+ */
+ protected function prepare_collection_query_args( $request ) {
+
+ // Prepare all set args.
+ $registered = $this->get_collection_params();
+ $prepared = array();
+
+ foreach ( $registered as $key => $value ) {
+ if ( isset( $request[ $key ] ) ) {
+ $prepared[ $key ] = $request[ $key ];
+ }
+ }
+
+ $prepared = $this->prepare_collection_query_search_args( $prepared, $request );
+ if ( is_wp_error( $prepared ) ) {
+ return $prepared;
+ }
+
+ $prepared = $this->map_params_to_query_args( $prepared, $registered, $request );
+
+ return $prepared;
+
+ }
+
+ /**
+ * Map schema to query arguments to retrieve a collection of objects.
+ *
+ * @since 1.0.0-beta.12
+ *
+ * @param array $prepared Array of collection arguments.
+ * @param array $registered Registered collection params.
+ * @param WP_REST_Request $request Full details about the request.
+ * @return array|WP_Error
+ */
+ protected function map_params_to_query_args( $prepared, $registered, $request ) {
+ return $prepared;
+ }
+
+ /**
+ * Format search query arguments to retrieve a collection of objects.
+ *
+ * @since 1.0.0-beta.12
+ * @since 1.0.0-beta.21 Return an error if requesting a list ordered by 'relevance' without providing a search string.
+ *
+ * @param array $prepared Array of collection arguments.
+ * @param WP_REST_Request $request Request object.
+ * @return array|WP_Error
+ */
+ protected function prepare_collection_query_search_args( $prepared, $request ) {
+
+ // Search?
+ if ( ! empty( $prepared['search'] ) ) {
+
+ if ( ! empty( $this->search_columns_mapping ) ) {
+
+ if ( empty( $prepared['search_columns'] ) ) {
+ return llms_rest_bad_request_error( __( 'You must provide a valid set of columns to search into.', 'lifterlms' ) );
+ }
+
+ // Filter search columns by context.
+ $search_columns = array_keys( $this->filter_response_by_context( array_flip( $prepared['search_columns'] ), $request['context'] ) );
+
+ // Check if one of more unallowed search columns have been provided as request query params (not merged with defaults).
+ if ( ! empty( $request->get_query_params()['search_columns'] ) ) {
+
+ $forbidden_columns = array_diff( $prepared['search_columns'], $search_columns );
+
+ if ( ! empty( $forbidden_columns ) ) {
+ return llms_rest_authorization_required_error(
+ sprintf(
+ // Translators: %1$s comma separated list of search columns.
+ __( 'You are not allowed to search into the provided column(s): %1$s', 'lifterlms' ),
+ implode( ',', $forbidden_columns )
+ )
+ );
+ }
+ }
+
+ $prepared['search_columns'] = array();
+
+ // Map our search columns into query compatible ones.
+ foreach ( $search_columns as $search_column ) {
+ if ( isset( $this->search_columns_mapping[ $search_column ] ) ) {
+ $prepared['search_columns'][] = $this->search_columns_mapping[ $search_column ];
+ }
+ }
+
+ if ( empty( $prepared['search_columns'] ) ) {
+ return llms_rest_bad_request_error( __( 'You must provide a valid set of columns to search into.', 'lifterlms' ) );
+ }
+ }
+
+ $prepared['search'] = '*' . $prepared['search'] . '*';
+
+ } else {
+
+ // Ensure a search string is set in case the orderby is set to 'relevance'.
+ if ( ! empty( $request['orderby'] ) && 'relevance' === $request['orderby'] ) {
+ return llms_rest_bad_request_error(
+ __( 'You need to define a search term to order by relevance.', 'lifterlms' )
+ );
+ }
+ }
+
+ return $prepared;
+ }
+
+ /**
+ * Prepare query args for total count query.
+ *
+ * @since 1.0.0-beta.7
+ *
+ * @param array $args Array of query args.
+ * @param WP_REST_Request $request Full details about the request.
+ * @return array
+ */
+ protected function prepare_args_for_total_count_query( $args, $request ) {
+ // Run the query again without pagination to get a proper total count.
+ unset( $args['paged'], $args['page'] );
+ return $args;
+ }
+
+ /**
+ * Prepare collection items for response.
+ *
+ * @since 1.0.0-beta.7
+ *
+ * @param array $objects Array of objects to be prepared for response.
+ * @param WP_REST_Request $request Full details about the request.
+ * @return array
+ */
+ protected function prepare_collection_items_for_response( $objects, $request ) {
+
+ $items = array();
+
+ foreach ( $objects as $object ) {
+ $object = $this->get_object( $object, false );
+
+ if ( ! $this->check_read_object_permissions( $object ) ) {
+ continue;
+ }
+
+ $item = $this->prepare_item_for_response( $object, $request );
+ if ( ! is_wp_error( $item ) ) {
+ $items[] = $this->prepare_response_for_collection( $item );
+ }
+ }
+
+ return $items;
+ }
+
+ /**
+ * Add pagination info and links to the response header.
+ *
+ * @since 1.0.0-beta.7
+ *
+ * @param WP_REST_Response $response Current response being served.
+ * @param array $pagination Pagination array.
+ * @param WP_REST_Request $request Full details about the request.
+ * @return WP_REST_Response
+ */
+ protected function add_header_pagination( $response, $pagination, $request ) {
+
+ $response->header( 'X-WP-Total', $pagination['total_results'] );
+ $response->header( 'X-WP-TotalPages', $pagination['total_pages'] );
+
+ $base = add_query_arg( urlencode_deep( $request->get_query_params() ), rest_url( $request->get_route() ) );
+
+ // First page link.
+ if ( 1 !== $pagination['current_page'] ) {
+ $first_link = add_query_arg( 'page', 1, $base );
+ $response->link_header( 'first', $first_link );
+ }
+
+ // Previous page link.
+ if ( $pagination['current_page'] > 1 ) {
+ $prev_page = $pagination['current_page'] - 1;
+ if ( $prev_page > $pagination['total_pages'] ) {
+ $prev_page = $pagination['total_pages'];
+ }
+ $prev_link = add_query_arg( 'page', $prev_page, $base );
+ $response->link_header( 'prev', $prev_link );
+ }
+
+ // Next page link.
+ if ( $pagination['total_pages'] > $pagination['current_page'] ) {
+ $next_link = add_query_arg( 'page', $pagination['current_page'] + 1, $base );
+ $response->link_header( 'next', $next_link );
+ }
+
+ // Last page link.
+ if ( $pagination['total_pages'] && $pagination['total_pages'] !== $pagination['current_page'] ) {
+ $last_link = add_query_arg( 'page', $pagination['total_pages'], $base );
+ $response->link_header( 'last', $last_link );
+ }
+
+ return $response;
+
+ }
+
+ /**
+ * Retrieves the query params for retrieving a single resource.
+ *
+ * @since 1.0.0-beta.1
+ *
+ * @return array
+ */
+ public function get_get_item_params() {
+
+ return array(
+ 'context' => $this->get_context_param(
+ array(
+ 'default' => 'view',
+ )
+ ),
+ );
+
+ }
+
+ /**
+ * Retrieve arguments for deleting a resource.
+ *
+ * @since 1.0.0-beta.1
+ *
+ * @return array
+ */
+ public function get_delete_item_args() {
+ return array();
+ }
+
+ /**
+ * Map request keys to database keys for insertion.
+ *
+ * Array keys are the request fields (as defined in the schema) and
+ * array values are the database fields.
+ *
+ * @since 1.0.0-beta.1
+ *
+ * @return array
+ */
+ protected function map_schema_to_database() {
+
+ $schema = $this->get_item_schema();
+ $keys = array_keys( $schema['properties'] );
+ return array_combine( $keys, $keys );
+
+ }
+
+ /**
+ * Prepare request arguments for a database insert/update.
+ *
+ * @since 1.0.0-beta.1
+ *
+ * @param WP_Rest_Request $request Request object.
+ * @return array
+ */
+ protected function prepare_item_for_database( $request ) {
+
+ $prepared = array();
+ $map = $this->map_schema_to_database();
+ $schema = $this->get_item_schema();
+
+ foreach ( $map as $req_key => $db_key ) {
+ if ( ! empty( $request[ $req_key ] ) ) {
+ $prepared[ $db_key ] = $request[ $req_key ];
+ }
+ }
+
+ return $prepared;
+
+ }
+
+ /**
+ * Prepares a single object for response.
+ *
+ * @since 1.0.0-beta.1
+ * @since 1.0.0-beta.3 Return early with a WP_Error if `$object` is a WP_Error.
+ * @since 1.0.0-beta.14 Pass the `$request` parameter to `prepare_links()`.
+ *
+ * @param obj $object Raw object from database.
+ * @param WP_REST_Request $request Request object.
+ * @return WP_Error|WP_REST_Response
+ */
+ public function prepare_item_for_response( $object, $request ) {
+
+ if ( is_wp_error( $object ) ) {
+ return $object;
+ }
+
+ $data = $this->prepare_object_for_response( $object, $request );
+
+ $context = ! empty( $request['context'] ) ? $request['context'] : 'view';
+
+ $data = $this->add_additional_fields_to_object( $data, $request );
+ $data = $this->filter_response_by_context( $data, $context );
+
+ // Wrap the data in a response object.
+ $response = rest_ensure_response( $data );
+
+ // Add links.
+ $response->add_links( $this->prepare_links( $object, $request ) );
+
+ return $response;
+
+ }
+
+ /**
+ * Prepare links for the request.
+ *
+ * @since 1.0.0-beta.1
+ * @since 1.0.0-beta.14 Added $request parameter.
+ *
+ * @param obj $object Item object.
+ * @param WP_REST_Request $request Request object.
+ * @return array
+ */
+ protected function prepare_links( $object, $request ) {
+
+ $base = rest_url( sprintf( '/%1$s/%2$s', $this->namespace, $this->rest_base ) );
+
+ $links = array(
+ 'self' => array(
+ 'href' => sprintf( '%1$s/%2$d', $base, $this->get_object_id( $object ) ),
+ ),
+ 'collection' => array(
+ 'href' => $base,
+ ),
+ );
+
+ return $links;
+
+ }
+
+ /**
+ * Register routes.
+ *
+ * @since 1.0.0-beta.1
+ *
+ * @return void
+ */
+ public function register_routes() {
+
+ register_rest_route(
+ $this->namespace,
+ '/' . $this->rest_base,
+ array(
+ array(
+ 'methods' => WP_REST_Server::READABLE,
+ 'callback' => array( $this, 'get_items' ),
+ 'permission_callback' => array( $this, 'get_items_permissions_check' ),
+ 'args' => $this->get_collection_params(),
+ ),
+ array(
+ 'methods' => WP_REST_Server::CREATABLE,
+ 'callback' => array( $this, 'create_item' ),
+ 'permission_callback' => array( $this, 'create_item_permissions_check' ),
+ 'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::CREATABLE ),
+ ),
+ 'schema' => array( $this, 'get_public_item_schema' ),
+ )
+ );
+
+ register_rest_route(
+ $this->namespace,
+ '/' . $this->rest_base . '/(?P[\d]+)',
+ array(
+ 'args' => array(
+ 'id' => array(
+ 'description' => __( 'Unique identifier for the resource.', 'lifterlms' ),
+ 'type' => 'integer',
+ ),
+ ),
+ array(
+ 'methods' => WP_REST_Server::READABLE,
+ 'callback' => array( $this, 'get_item' ),
+ 'permission_callback' => array( $this, 'get_item_permissions_check' ),
+ 'args' => $this->get_get_item_params(),
+ ),
+ array(
+ 'methods' => WP_REST_Server::EDITABLE,
+ 'callback' => array( $this, 'update_item' ),
+ 'permission_callback' => array( $this, 'update_item_permissions_check' ),
+ 'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ), // see class-wp-rest-controller.php.
+ ),
+ array(
+ 'methods' => WP_REST_Server::DELETABLE,
+ 'callback' => array( $this, 'delete_item' ),
+ 'permission_callback' => array( $this, 'delete_item_permissions_check' ),
+ 'args' => $this->get_delete_item_args(),
+ ),
+ 'schema' => array( $this, 'get_public_item_schema' ),
+ )
+ );
+
+ }
+
+ /**
+ * Update item.
+ *
+ * @since 1.0.0-beta.1
+ * @since 1.0.0-beta.12 Call `object_inserted` and `object_completely_inserted` after an object is
+ * respectively inserted in the DB and all its additional fields have been
+ * updated as well (completely inserted).
+ *
+ * @param WP_REST_Request $request Request object.
+ * @return WP_REST_Response|WP_Error Response object or WP_Error on failure.
+ */
+ public function update_item( $request ) {
+
+ $object = $this->get_object( $request['id'] );
+ if ( is_wp_error( $object ) ) {
+ return $object;
+ }
+
+ $item = $this->prepare_item_for_database( $request );
+ $object = $this->update_object( $item, $request );
+ $schema = $this->get_item_schema();
+
+ if ( is_wp_error( $object ) ) {
+ return $object;
+ }
+
+ $this->object_inserted( $object, $request, $schema, false );
+
+ $fields_update = $this->update_additional_fields_for_object( $item, $request );
+ if ( is_wp_error( $fields_update ) ) {
+ return $fields_update;
+ }
+
+ $this->object_completely_inserted( $object, $request, $schema, false );
+
+ $request->set_param( 'context', 'edit' );
+
+ $response = $this->prepare_item_for_response( $object, $request );
+ $response = rest_ensure_response( $response );
+
+ return $response;
+
+ }
+
+}
diff --git a/libraries/lifterlms-rest/includes/abstracts/class-llms-rest-database-resource.php b/libraries/lifterlms-rest/includes/abstracts/class-llms-rest-database-resource.php
new file mode 100644
index 0000000000..f16dec60f4
--- /dev/null
+++ b/libraries/lifterlms-rest/includes/abstracts/class-llms-rest-database-resource.php
@@ -0,0 +1,273 @@
+create_prepare( $data );
+ if ( is_wp_error( $data ) ) {
+ return $data;
+ }
+
+ return $this->save( new $this->model(), $data );
+
+ }
+
+ /**
+ * Prepare data for creation.
+ *
+ * @since 1.0.0-beta.1
+ *
+ * @param array $data Array of data.
+ * @return array
+ */
+ public function create_prepare( $data ) {
+
+ if ( ! empty( $data['id'] ) ) {
+ // Translators: %s = name of the resource type (for example: "API Key").
+ return new WP_Error( 'llms_rest_' . $this->id . '_exists', sprintf( __( 'Cannot create a new %s with a pre-defined ID.', 'lifterlms' ), $this->get_i18n_name() ) );
+ }
+
+ // Merge in default values.
+ $data = wp_parse_args( array_filter( $data ), $this->get_default_column_values() );
+
+ // Required Fields.
+ foreach ( $this->required_columns as $key ) {
+
+ if ( empty( $data[ $key ] ) ) {
+ return new WP_Error(
+ 'llms_rest_' . $this->id . '_missing_' . $key,
+ // Translators: %1$s = name of the resource type; %2$s = field name.
+ sprintf( __( '%1$s "%2$s" is required.', 'lifterlms' ), $this->get_i18n_name(), $key )
+ );
+ }
+ }
+
+ $err = $this->is_data_valid( $data );
+ if ( is_wp_error( $err ) ) {
+ return $err;
+ }
+
+ return $data;
+
+ }
+
+ /**
+ * Delete a the resource.
+ *
+ * @since 1.0.0-beta.1
+ *
+ * @param int $id Resource ID.
+ * @return bool `true` on success, `false` if the resource couldn't be found or an error was encountered during deletion.
+ */
+ public function delete( $id ) {
+ $obj = $this->get( $id, false );
+ if ( $obj ) {
+ return $obj->delete();
+ }
+ return false;
+ }
+
+ /**
+ * Retrieve an API Key object instance.
+ *
+ * @since 1.0.0-beta.1
+ *
+ * @param int $id API Key ID.
+ * @param bool $hydrate If true, pulls all key data from the database on instantiation.
+ * @return obj|false
+ */
+ public function get( $id, $hydrate = true ) {
+ $obj = new $this->model( $id, $hydrate );
+ if ( $obj && $obj->exists() ) {
+ return $obj;
+ }
+ return false;
+ }
+
+ /**
+ * Get default column values.
+ *
+ * @since 1.0.0-beta.1
+ *
+ * @return array
+ */
+ public function get_default_column_values() {
+
+ /**
+ * Allow customization of default Resource values.
+ *
+ * @since 1.0.0-beta.1
+ *
+ * @param array $values An associative array of default values.
+ */
+ return apply_filters( 'llms_rest_' . $this->id . '_default_properties', $this->default_column_values );
+
+ }
+
+ /**
+ * Retrieve the translated resource name.
+ *
+ * @since 1.0.0-beta.1
+ *
+ * @return string
+ */
+ protected function get_i18n_name() {
+ return __( 'Resource', 'lifterlms' );
+ }
+
+ /**
+ * Update a resource.
+ *
+ * @since 1.0.0-beta.1
+ *
+ * @param array $data {
+ * Array of data to update.
+ *
+ * @type int $id (Required). Resource ID.
+ * }
+ * @return [type]
+ */
+ public function update( $data ) {
+
+ if ( empty( $data['id'] ) ) {
+ // Translators: %s = name of the resource type (for example: "API Key").
+ return new WP_Error( 'llms_rest_' . $this->id . '_missing_id', sprintf( __( 'No %s ID was supplied.', 'lifterlms' ), $this->get_i18n_name() ) );
+ }
+
+ $obj = $this->get( $data['id'] );
+ if ( ! $obj || ! $obj->exists() ) {
+ // Translators: %s = name of the resource type (for example: "API Key").
+ return new WP_Error( 'llms_rest_' . $this->id . '_invalid_' . $this->id, sprintf( __( 'The requested %s could not be located.', 'lifterlms' ), $this->get_i18n_name() ) );
+ }
+
+ $data = $this->update_prepare( $data );
+ if ( is_wp_error( $data ) ) {
+ return $data;
+ }
+
+ return $this->save( $obj, $data );
+
+ }
+
+ /**
+ * Prepare data for an update.
+ *
+ * @since 1.0.0-beta.1
+ *
+ * @param array $data Associative array of data to set to a resources properties.
+ * @return object|WP_Error
+ */
+ protected function update_prepare( $data ) {
+
+ // Filter out write-protected keys.
+ $data = array_diff_key(
+ $data,
+ array_fill_keys( $this->read_only_columns, false )
+ );
+
+ $err = $this->is_data_valid( $data );
+ if ( is_wp_error( $err ) ) {
+ return $err;
+ }
+
+ return $data;
+
+ }
+
+ /**
+ * Persist data.
+ *
+ * This method assumes the supplied data has already been validated and sanitized.
+ *
+ * @since 1.0.0-beta.1
+ *
+ * @param obj $obj Instantiated object.
+ * @param array $data Associative array of data to persist.
+ * @return obj
+ */
+ protected function save( $obj, $data ) {
+
+ $obj->setup( $data )->save();
+ return $obj;
+
+ }
+
+}
diff --git a/libraries/lifterlms-rest/includes/abstracts/class-llms-rest-posts-controller.php b/libraries/lifterlms-rest/includes/abstracts/class-llms-rest-posts-controller.php
new file mode 100644
index 0000000000..c5d22330bc
--- /dev/null
+++ b/libraries/lifterlms-rest/includes/abstracts/class-llms-rest-posts-controller.php
@@ -0,0 +1,1827 @@
+set_bulk()` when there's no data to update.
+ * Fix wp:featured_media link, we don't expose any embeddable field.
+ * Also `self` and `collection` links prepared in the parent class.
+ * Added `"llms_rest_insert_{$this->post_type}"` and `"llms_rest_insert_{$this->post_type}"` action hooks:
+ * fired after inserting/updating an llms post into the database.
+ * @since 1.0.0-beta.8 Return links to those taxonomies which have an accessible rest route.
+ * Initialize `$prepared_item` array before adding values to it.
+ * @since 1.0.0-beta.9 Implemented a generic way to create and get an llms post object instance given a `post_type`.
+ * In `get_objects_from_query()` avoid performing an additional query, just return the already retrieved posts.
+ * Removed `"llms_rest_{$this->post_type}_filters_removed_for_response"` filter hooks,
+ * `"llms_rest_{$this->post_type}_filters_removed_for_response"` added.
+ * @since 1.0.0-beta.11 Fixed `"llms_rest_insert_{$this->post_type}"` and `"llms_rest_insert_{$this->post_type}"` action hooks fourth param:
+ * must be false when updating.
+ * @since 1.0.0-beta.12 Moved parameters to query args mapping from `$this->prepare_collection_params()` to `$this->map_params_to_query_args()`.
+ * @since 1.0.0-beta.14 Update `prepare_links()` to accept a second parameter, `WP_REST_Request`.
+ * @since 1.0.0-beta.21 Enable search.
+ */
+abstract class LLMS_REST_Posts_Controller extends LLMS_REST_Controller {
+
+ /**
+ * Post type.
+ *
+ * @var string
+ */
+ protected $post_type;
+
+ /**
+ * Route base.
+ *
+ * @var string
+ */
+ protected $collection_route_base_for_pagination;
+
+ /**
+ * Schema properties available for ordering the collection.
+ *
+ * @var string[]
+ */
+ protected $orderby_properties = array(
+ 'id',
+ 'title',
+ 'date_created',
+ 'date_updated',
+ 'menu_order',
+ 'relevance',
+ );
+
+ /**
+ * Whether search is allowed
+ *
+ * @var boolean
+ */
+ protected $is_searchable = true;
+
+ /**
+ * LLMS post class name.
+ *
+ * @since 1.0.0-beta.9
+ * @var string;
+ */
+ protected $llms_post_class;
+
+ /**
+ * Retrieves an array of arguments for the delete endpoint.
+ *
+ * @since 1.0.0-beta.1
+ *
+ * @return array Delete endpoint arguments.
+ */
+ public function get_delete_item_args() {
+
+ return array(
+ 'force' => array(
+ 'description' => __( 'Bypass the trash and force course deletion.', 'lifterlms' ),
+ 'type' => 'boolean',
+ 'default' => false,
+ ),
+ );
+
+ }
+
+ /**
+ * Retrieves the query params for retrieving a single resource.
+ *
+ * @since 1.0.0-beta.1
+ *
+ * @return array
+ */
+ public function get_get_item_params() {
+
+ $params = parent::get_get_item_params();
+ $schema = $this->get_item_schema();
+
+ if ( isset( $schema['properties']['password'] ) ) {
+ $params['password'] = array(
+ 'description' => __( 'Post password. Required if the post is password protected.', 'lifterlms' ),
+ 'type' => 'string',
+ );
+ }
+
+ return $params;
+
+ }
+
+ /**
+ * Determine if the current user can view the object.
+ *
+ * @since 1.0.0-beta.7
+ *
+ * @param object $object Object.
+ * @return bool
+ */
+ protected function check_read_object_permissions( $object ) {
+ return $this->check_read_permission( $object );
+ }
+
+ /**
+ * Check if a given request has access to read items.
+ *
+ * @since 1.0.0-beta.1
+ *
+ * @param WP_REST_Request $request Full details about the request.
+ * @return WP_Error|boolean
+ */
+ public function get_items_permissions_check( $request ) {
+
+ // Everybody can list llms posts (in read mode).
+ if ( 'edit' === $request['context'] && ! $this->check_update_permission() ) {
+ return llms_rest_authorization_required_error();
+ }
+
+ return true;
+
+ }
+
+ /**
+ * Retrieve pagination information from an objects query.
+ *
+ * @since 1.0.0-beta.7
+ *
+ * @param WP_Query $query Objects query result returned by {@see LLMS_REST_Posts_Controller::get_objects_query()}.
+ * @param array $prepared Array of collection arguments.
+ * @param WP_REST_Request $request Request object.
+ * @return array {
+ * Array of pagination information.
+ *
+ * @type int $current_page Current page number.
+ * @type int $total_results Total number of results.
+ * @type int $total_pages Total number of results pages.
+ * }
+ */
+ protected function get_pagination_data_from_query( $query, $prepared, $request ) {
+
+ $total_results = (int) $query->found_posts;
+ $current_page = isset( $prepared['paged'] ) ? (int) $prepared['paged'] : 1;
+ $total_pages = (int) ceil( $total_results / (int) $query->get( 'posts_per_page' ) );
+
+ return compact( 'current_page', 'total_results', 'total_pages' );
+
+ }
+
+ /**
+ * Check if a given request has access to create an item.
+ *
+ * @since 1.0.0-beta.1
+ * @since 1.0.0-beta.18 Use plural post type name.
+ *
+ * @param WP_REST_Request $request Full details about the request.
+ * @return WP_Error|boolean
+ */
+ public function create_item_permissions_check( $request ) {
+
+ $post_type_object = get_post_type_object( $this->post_type );
+ $post_type_name = $post_type_object->labels->name;
+
+ if ( ! empty( $request['id'] ) ) {
+ // Translators: %s = The post type name.
+ return llms_rest_bad_request_error( sprintf( __( 'Cannot create existing %s.', 'lifterlms' ), $post_type_name ) );
+ }
+
+ if ( ! $this->check_create_permission() ) {
+ // Translators: %s = The post type name.
+ return llms_rest_authorization_required_error( sprintf( __( 'Sorry, you are not allowed to create %s as this user.', 'lifterlms' ), $post_type_name ) );
+ }
+
+ if ( ! $this->check_assign_terms_permission( $request ) ) {
+ return llms_rest_authorization_required_error( __( 'Sorry, you are not allowed to assign the provided terms.', 'lifterlms' ) );
+ }
+
+ return true;
+ }
+
+
+ /**
+ * Creates a single LLMS post.
+ *
+ * Extending classes can add additional object fields by overriding the method `update_additional_object_fields()`.
+ *
+ * @since 1.0.0-beta.1
+ * @since 1.0.0-beta.7 Added `"llms_rest_insert_{$this->post_type}"` and `"llms_rest_insert_{$this->post_type}"` action hooks:
+ * fired after inserting/uodateing an llms post into the database.
+ * @since 1.0.0-beta.25 Allow updating meta with the same value as the stored one.
+ *
+ * @param WP_REST_Request $request Full details about the request.
+ * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
+ */
+ public function create_item( $request ) {
+
+ $prepared_item = $this->prepare_item_for_database( $request );
+ if ( is_wp_error( $prepared_item ) ) {
+ return $prepared_item;
+ }
+
+ $object = $this->create_llms_post( $prepared_item );
+ if ( is_wp_error( $object ) ) {
+
+ if ( 'db_insert_error' === $object->get_error_code() ) {
+ $object->add_data( array( 'status' => 500 ) );
+ } else {
+ $object->add_data( array( 'status' => 400 ) );
+ }
+
+ return $object;
+ }
+
+ $schema = $this->get_item_schema();
+
+ /**
+ * Fires after a single llms post is created or updated via the REST API.
+ *
+ * The dynamic portion of the hook name, `$this->post_type`, refers to the post type slug.
+ *
+ * @since 1.0.0-beta.7
+ *
+ * @param LLMS_Post $object Inserted or updated llms object.
+ * @param WP_REST_Request $request Request object.
+ * @param array $schema The item schema.
+ * @param bool $creating True when creating a post, false when updating.
+ */
+ do_action( "llms_rest_insert_{$this->post_type}", $object, $request, $schema, true );
+
+ // Set all the other properties.
+ // TODO: maybe we want to filter the post properties that have already been inserted before.
+ $set_bulk_result = $object->set_bulk( $prepared_item, true, true );
+ if ( is_wp_error( $set_bulk_result ) ) {
+
+ if ( 'db_update_error' === $set_bulk_result->get_error_code() ) {
+ $set_bulk_result->add_data( array( 'status' => 500 ) );
+ } else {
+ $set_bulk_result->add_data( array( 'status' => 400 ) );
+ }
+
+ return $set_bulk_result;
+ }
+
+ $object_id = $object->get( 'id' );
+
+ $additional_fields = $this->update_additional_object_fields( $object, $request, $schema, $prepared_item );
+ if ( is_wp_error( $additional_fields ) ) {
+ return $additional_fields;
+ }
+
+ if ( ! empty( $schema['properties']['featured_media'] ) && isset( $request['featured_media'] ) ) {
+ $this->handle_featured_media( $request['featured_media'], $object_id );
+ }
+
+ $terms_update = $this->handle_terms( $object_id, $request );
+ if ( is_wp_error( $terms_update ) ) {
+ return $terms_update;
+ }
+
+ /**
+ * TODO: understand how to treat possible conflicting properties => instructors are registered as additional rest field by llms_blocks
+ */
+ // $fields_update = $this->update_additional_fields_for_object( $object, $request );
+ // if ( is_wp_error( $fields_update ) ) {
+ // return $fields_update;
+ // }
+ $request->set_param( 'context', 'edit' );
+
+ /**
+ * Fires after a single llms post is completely created or updated via the REST API.
+ *
+ * The dynamic portion of the hook name, `$this->post_type`, refers to the post type slug.
+ *
+ * @since 1.0.0-beta.7
+ *
+ * @param LLMS_Post $object Inserted or updated llms object.
+ * @param WP_REST_Request $request Request object.
+ * @param array $schema The item schema.
+ * @param bool $creating True when creating a post, false when updating.
+ */
+ do_action( "llms_rest_after_insert_{$this->post_type}", $object, $request, $schema, true );
+
+ $response = $this->prepare_item_for_response( $object, $request );
+
+ $response->set_status( 201 );
+
+ $response->header( 'Location', rest_url( sprintf( '%s/%s/%d', $this->namespace, $this->rest_base, $object_id ) ) );
+
+ return $response;
+ }
+
+ /**
+ * Check if a given request has access to read an item.
+ *
+ * @since 1.0.0-beta.1
+ *
+ * @param WP_REST_Request $request Full details about the request.
+ * @return WP_Error|boolean
+ */
+ public function get_item_permissions_check( $request ) {
+
+ $object = $this->get_object( (int) $request['id'] );
+ if ( is_wp_error( $object ) ) {
+ return $object;
+ }
+
+ if ( 'edit' === $request['context'] && ! $this->check_update_permission( $object ) ) {
+ return llms_rest_authorization_required_error();
+ }
+
+ if ( ! empty( $request['password'] ) ) {
+ // Check post password, and return error if invalid.
+ if ( ! hash_equals( $object->get( 'password' ), $request['password'] ) ) {
+ return llms_rest_authorization_required_error( __( 'Incorrect password.', 'lifterlms' ) );
+ }
+ }
+
+ // Allow access to all password protected posts if the context is edit.
+ if ( 'edit' === $request['context'] ) {
+ add_filter( 'post_password_required', '__return_false' );
+ }
+
+ if ( ! $this->check_read_permission( $object ) ) {
+ return llms_rest_authorization_required_error();
+ }
+
+ return true;
+ }
+
+ /**
+ * Retrieves the query params for the objects collection
+ *
+ * @since 1.0.0-beta.19
+ *
+ * @return array Collection parameters.
+ */
+ public function get_collection_params() {
+
+ $query_params = parent::get_collection_params();
+ $schema = $this->get_item_schema();
+
+ if ( isset( $schema['properties']['status'] ) ) {
+ $query_params['status'] = array(
+ 'default' => 'publish',
+ 'description' => __( 'Limit result set to posts assigned one or more statuses.', 'lifterlms' ),
+ 'type' => 'array',
+ 'items' => array(
+ 'enum' => array_merge(
+ array_keys(
+ get_post_stati()
+ ),
+ array(
+ 'any',
+ )
+ ),
+ 'type' => 'string',
+ ),
+ 'sanitize_callback' => array( $this, 'sanitize_post_statuses' ),
+ );
+ }
+
+ return $query_params;
+
+ }
+
+ /**
+ * Format query arguments to retrieve a collection of objects.
+ *
+ * @since 1.0.0-beta.7
+ * @since 1.0.0-beta.12 Moved parameters to query args mapping into a different method.
+ * @since 1.0.0-beta.18 Correctly return errors.
+ *
+ * @param WP_REST_Request $request Full details about the request.
+ * @return array|WP_Error
+ */
+ protected function prepare_collection_query_args( $request ) {
+
+ $prepared = parent::prepare_collection_query_args( $request );
+ if ( is_wp_error( $prepared ) ) {
+ return $prepared;
+ }
+
+ // Force the post_type argument, since it's not a user input variable.
+ $prepared['post_type'] = $this->post_type;
+
+ $query_args = $this->prepare_items_query( $prepared, $request );
+
+ return $query_args;
+
+ }
+
+ /**
+ * Map schema to query arguments to retrieve a collection of objects.
+ *
+ * @since 1.0.0-beta.12
+ * @since 1.0.0-beta.19 Map 'status' collection param to to 'post_status' query arg.
+ *
+ * @param array $prepared Array of collection arguments.
+ * @param array $registered Registered collection params.
+ * @param WP_REST_Request $request Full details about the request.
+ * @return array|WP_Error
+ */
+ protected function map_params_to_query_args( $prepared, $registered, $request ) {
+
+ $args = array();
+
+ /*
+ * This array defines mappings between public API query parameters whose
+ * values are accepted as-passed, and their internal WP_Query parameter
+ * name equivalents (some are the same). Only values which are also
+ * present in $registered will be set.
+ */
+ $parameter_mappings = array(
+ 'order' => 'order',
+ 'orderby' => 'orderby',
+ 'page' => 'paged',
+ 'exclude' => 'post__not_in',
+ 'include' => 'post__in',
+ 'search' => 's',
+ 'status' => 'post_status',
+ );
+
+ /*
+ * For each known parameter which is both registered and present in the request,
+ * set the parameter's value on the query $args.
+ */
+ foreach ( $parameter_mappings as $api_param => $wp_param ) {
+ if ( isset( $registered[ $api_param ], $request[ $api_param ] ) ) {
+ $args[ $wp_param ] = $request[ $api_param ];
+ }
+ }
+
+ // Ensure our per_page parameter overrides any provided posts_per_page filter.
+ if ( isset( $registered['per_page'] ) ) {
+ $args['posts_per_page'] = $request['per_page'];
+ }
+
+ return $args;
+ }
+
+ /**
+ * Check if a given request has access to update an item.
+ *
+ * @since 1.0.0-beta.1
+ * @since 1.0.0-beta.18 Use plural post type name.
+ *
+ * @param WP_REST_Request $request Full details about the request.
+ * @return WP_Error|boolean
+ */
+ public function update_item_permissions_check( $request ) {
+
+ $object = $this->get_object( (int) $request['id'] );
+ if ( is_wp_error( $object ) ) {
+ return $object;
+ }
+
+ $post_type_object = get_post_type_object( $this->post_type );
+ $post_type_name = $post_type_object->labels->name;
+
+ if ( ! $this->check_update_permission( $object ) ) {
+ // Translators: %s = The post type name.
+ return llms_rest_authorization_required_error( sprintf( __( 'Sorry, you are not allowed to update %s as this user.', 'lifterlms' ), $post_type_name ) );
+ }
+
+ if ( ! $this->check_assign_terms_permission( $request ) ) {
+ return llms_rest_authorization_required_error( __( 'Sorry, you are not allowed to assign the provided terms.', 'lifterlms' ) );
+ }
+
+ return true;
+ }
+
+ /**
+ * Updates a single llms post.
+ *
+ * Extending classes can add additional object fields by overriding the method `update_additional_object_fields()`.
+ *
+ * @since 1.0.0-beta.1
+ * @since 1.0.0-beta.7 Don't execute `$object->set_bulk()` when there's no data to update:
+ * this fixes an issue when updating only properties which are not handled in `prepare_item_for_database()`.
+ * Added `"llms_rest_insert_{$this->post_type}"` and `"llms_rest_insert_{$this->post_type}"` action hooks:
+ * fired after inserting/uodateing an llms post into the database.
+ * @since 1.0.0-beta.11 Fixed `"llms_rest_insert_{$this->post_type}"` and `"llms_rest_insert_{$this->post_type}"` action hooks fourth param:
+ * must be false when updating.
+ * @since 1.0.0-beta.25 Allow updating meta with the same value as the stored one.
+ *
+ * @param WP_REST_Request $request Full details about the request.
+ * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
+ */
+ public function update_item( $request ) {
+
+ $object = $this->get_object( (int) $request['id'] );
+ if ( is_wp_error( $object ) ) {
+ return $object;
+ }
+
+ $prepared_item = $this->prepare_item_for_database( $request );
+ if ( is_wp_error( $prepared_item ) ) {
+ return $prepared_item;
+ }
+
+ $update_result = empty( array_diff_key( $prepared_item, array_flip( array( 'id' ) ) ) ) ? false : $object->set_bulk( $prepared_item, true, true );
+ if ( is_wp_error( $update_result ) ) {
+
+ if ( 'db_update_error' === $update_result->get_error_code() ) {
+ $update_result->add_data( array( 'status' => 500 ) );
+ } else {
+ $update_result->add_data( array( 'status' => 400 ) );
+ }
+
+ return $update_result;
+ }
+
+ $schema = $this->get_item_schema();
+
+ /**
+ * Fires after a single llms post is created or updated via the REST API.
+ *
+ * The dynamic portion of the hook name, `$this->post_type`, refers to the post type slug.
+ *
+ * @since 1.0.0-beta.7
+ *
+ * @param LLMS_Post $object Inserted or updated llms object.
+ * @param WP_REST_Request $request Request object.
+ * @param array $schema The item schema.
+ * @param bool $creating True when creating a post, false when updating.
+ */
+ do_action( "llms_rest_insert_{$this->post_type}", $object, $request, $schema, false );
+
+ $object_id = $object->get( 'id' );
+
+ $additional_fields = $this->update_additional_object_fields( $object, $request, $schema, $prepared_item, false );
+ if ( is_wp_error( $additional_fields ) ) {
+ return $additional_fields;
+ }
+
+ if ( ! empty( $schema['properties']['featured_media'] ) && isset( $request['featured_media'] ) ) {
+ $this->handle_featured_media( $request['featured_media'], $object_id );
+ }
+
+ $terms_update = $this->handle_terms( $object_id, $request );
+ if ( is_wp_error( $terms_update ) ) {
+ return $terms_update;
+ }
+
+ /**
+ * TODO: understand how to treat possible conflicting properties => instructors are registered as additional rest field by llms_blocks
+ */
+ // $fields_update = $this->update_additional_fields_for_object( $object, $request );
+ // if ( is_wp_error( $fields_update ) ) {
+ // return $fields_update;
+ // }
+ $request->set_param( 'context', 'edit' );
+
+ /**
+ * Fires after a single llms post is completely created or updated via the REST API.
+ *
+ * The dynamic portion of the hook name, `$this->post_type`, refers to the post type slug.
+ *
+ * @since 1.0.0-beta.7
+ *
+ * @param LLMS_Post $object Inserted or updated llms object.
+ * @param WP_REST_Request $request Request object.
+ * @param array $schema The item schema.
+ * @param bool $creating True when creating a post, false when updating.
+ */
+ do_action( "llms_rest_after_insert_{$this->post_type}", $object, $request, $schema, false );
+
+ return $this->prepare_item_for_response( $object, $request );
+
+ }
+
+ /**
+ * Updates a single llms post.
+ *
+ * @since 1.0.0-beta.1
+ * @since 1.0.0-beta.7 return description updated.
+ *
+ * @param LLMS_Post_Model $object LMMS_Post_Model instance.
+ * @param array $prepared_item Array.
+ * @param WP_REST_Request $request Full details about the request.
+ * @param array $schema The item schema.
+ * @return bool|WP_Error True on success or false if nothing to update, WP_Error object if something went wrong during the update.
+ */
+ protected function update_additional_object_fields( $object, $prepared_item, $request, $schema ) {
+ return true;
+ }
+
+ /**
+ * Check if a given request has access to delete an item.
+ *
+ * @since 1.0.0-beta.1
+ * @since 1.0.0-beta.18 Provide a more significant error message when trying to delete an item without permissions.
+ *
+ * @param WP_REST_Request $request Full details about the request.
+ * @return bool|WP_Error
+ */
+ public function delete_item_permissions_check( $request ) {
+
+ $object = $this->get_object( (int) $request['id'] );
+ if ( is_wp_error( $object ) ) {
+ // LLMS_Post not found, we don't return a 404.
+ if ( in_array( 'llms_rest_not_found', $object->get_error_codes(), true ) ) {
+ return true;
+ }
+
+ return $object;
+ }
+
+ if ( ! $this->check_delete_permission( $object ) ) {
+ return llms_rest_authorization_required_error(
+ sprintf(
+ // Translators: %s = The post type name.
+ __( 'Sorry, you are not allowed to delete %s as this user.', 'lifterlms' ),
+ get_post_type_object( $this->post_type )->labels->name
+ )
+ );
+ }
+
+ return true;
+
+ }
+
+ /**
+ * Deletes a single llms post.
+ *
+ * @since 1.0.0-beta.1
+ *
+ * @param WP_REST_Request $request Full details about the request.
+ * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
+ */
+ public function delete_item( $request ) {
+
+ $object = $this->get_object( (int) $request['id'] );
+ $response = new WP_REST_Response();
+ $response->set_status( 204 );
+
+ if ( is_wp_error( $object ) ) {
+ // Course not found, we don't return a 404.
+ if ( in_array( 'llms_rest_not_found', $object->get_error_codes(), true ) ) {
+ return $response;
+ }
+
+ return $object;
+ }
+
+ $post_type_object = get_post_type_object( $this->post_type );
+ $post_type_name = $post_type_object->labels->singular_name;
+
+ $id = $object->get( 'id' );
+ $force = $this->is_delete_forced( $request );
+
+ // If we're forcing, then delete permanently.
+ if ( $force ) {
+ $result = wp_delete_post( $id, true );
+ } else {
+
+ $supports_trash = $this->is_trash_supported();
+
+ // If we don't support trashing for this type, error out.
+ if ( ! $supports_trash ) {
+ return new WP_Error(
+ 'llms_rest_trash_not_supported',
+ /* translators: %1$s: post type name, %2$s: force=true */
+ sprintf( __( 'The %1$s does not support trashing. Set \'%2$s\' to delete.', 'lifterlms' ), $post_type_name, 'force=true' ),
+ array( 'status' => 501 )
+ );
+ }
+
+ // Otherwise, only trash if we haven't already.
+ if ( 'trash' !== $object->get( 'status' ) ) {
+ // (Note that internally this falls through to `wp_delete_post` if
+ // the trash is disabled.)
+ $result = wp_trash_post( $id );
+ } else {
+ $result = true;
+ }
+
+ $request->set_param( 'context', 'edit' );
+ $object = $this->get_object( $id );
+ $response = $this->prepare_item_for_response( $object, $request );
+
+ }
+
+ if ( ! $result ) {
+ return new WP_Error(
+ 'llms_rest_cannot_delete',
+ /* translators: %s: post type name */
+ sprintf( __( 'The %s cannot be deleted.', 'lifterlms' ), $post_type_name ),
+ array( 'status' => 500 )
+ );
+ }
+
+ return $response;
+
+ }
+
+ /**
+ * Whether the delete should be forced.
+ *
+ * @since 1.0.0-beta.1
+ *
+ * @param WP_REST_Request $request Full details about the request.
+ * @return bool True if the delete should be forced, false otherwise.
+ */
+ protected function is_delete_forced( $request ) {
+ return isset( $request['force'] ) && (bool) $request['force'];
+ }
+
+ /**
+ * Whether the trash is supported.
+ *
+ * @since 1.0.0-beta.1
+ *
+ * @return bool True if the trash is supported, false otherwise.
+ */
+ protected function is_trash_supported() {
+ return ( EMPTY_TRASH_DAYS > 0 );
+ }
+
+
+ /**
+ * Retrieve a query object based on arguments from a `get_items()` (collection) request.
+ *
+ * @since 1.0.0-beta.7
+ *
+ * @param array $prepared Array of collection arguments.
+ * @param WP_REST_Request $request Full details about the request.
+ * @return WP_Query
+ */
+ protected function get_objects_query( $prepared, $request ) {
+
+ return new WP_Query( $prepared );
+
+ }
+
+ /**
+ * Retrieve an array of objects from the result of `$this->get_objects_query()`.
+ *
+ * @since 1.0.0-beta.7
+ * @since 1.0.0-beta.9 Avoid performing an additional query, just return the already retrieved posts.
+ *
+ * @param WP_Query $query WP_Query query result.
+ * @return WP_Post[]
+ */
+ protected function get_objects_from_query( $query ) {
+
+ return $query->posts;
+
+ }
+
+ /**
+ * Prepare collection items for response.
+ *
+ * @since 1.0.0-beta.7
+ *
+ * @param array $objects Array of objects to be prepared for response.
+ * @param WP_REST_Request $request Full details about the request.
+ * @return array
+ */
+ protected function prepare_collection_items_for_response( $objects, $request ) {
+
+ $items = array();
+
+ // Allow access to all password protected posts if the context is edit.
+ if ( 'edit' === $request['context'] ) {
+ add_filter( 'post_password_required', '__return_false' );
+ }
+
+ $items = parent::prepare_collection_items_for_response( $objects, $request );
+
+ // Reset filter.
+ if ( 'edit' === $request['context'] ) {
+ remove_filter( 'post_password_required', '__return_false' );
+ }
+
+ return $items;
+
+ }
+
+ /**
+ * Prepare a single object output for response.
+ *
+ * @since 1.0.0-beta.1
+ *
+ * @param LLMS_Post_Model $object object object.
+ * @param WP_REST_Request $request Full details about the request.
+ * @return array
+ */
+ protected function prepare_object_for_response( $object, $request ) {
+
+ $object_id = $object->get( 'id' );
+ $password_required = post_password_required( $object_id );
+ $password = $object->get( 'password' );
+
+ $data = array(
+ 'id' => $object->get( 'id' ),
+ 'date_created' => $object->get_date( 'date', 'Y-m-d H:i:s' ),
+ 'date_created_gmt' => $object->get_date( 'date_gmt', 'Y-m-d H:i:s' ),
+ 'date_updated' => $object->get_date( 'modified', 'Y-m-d H:i:s' ),
+ 'date_updated_gmt' => $object->get_date( 'modified_gmt', 'Y-m-d H:i:s' ),
+ 'menu_order' => $object->get( 'menu_order' ),
+ 'title' => array(
+ 'raw' => $object->get( 'title', true ),
+ 'rendered' => $object->get( 'title' ),
+ ),
+ 'password' => $password,
+ 'slug' => $object->get( 'name' ),
+ 'post_type' => $this->post_type,
+ 'permalink' => get_permalink( $object_id ),
+ 'status' => $object->get( 'status' ),
+ 'featured_media' => (int) get_post_thumbnail_id( $object_id ),
+ 'comment_status' => $object->get( 'comment_status' ),
+ 'ping_status' => $object->get( 'ping_status' ),
+ 'content' => array(
+ 'raw' => $object->get( 'content', true ),
+ 'rendered' => $password_required ? '' : apply_filters( 'the_content', $object->get( 'content', true ) ),
+ 'protected' => (bool) $password,
+ ),
+ 'excerpt' => array(
+ 'raw' => $object->get( 'excerpt', true ),
+ 'rendered' => $password_required ? '' : apply_filters( 'the_excerpt', $object->get( 'excerpt' ) ),
+ 'protected' => (bool) $password,
+ ),
+ );
+
+ return $data;
+
+ }
+
+ /**
+ * Prepare a single item for the REST response
+ *
+ * @since 1.0.0-beta.1
+ * @since 1.0.0-beta.14 Pass the `$request` parameter to `prepare_links()`.
+ *
+ * @param LLMS_Post_Model $object LLMS post object.
+ * @param WP_REST_Request $request Request object.
+ * @return WP_Error|WP_REST_Response Response object on success, or WP_Error object on failure.
+ */
+ public function prepare_item_for_response( $object, $request ) {
+
+ $context = ! empty( $request['context'] ) ? $request['context'] : 'view';
+
+ // Need to set the global $post because of references to the global $post when e.g. filtering the content, or processing blocks/shortcodes.
+ global $post;
+ $temp = $post;
+ $post = $object->get( 'post' ); // phpcs:ignore
+ setup_postdata( $post );
+
+ $removed_filters_for_response = $this->maybe_remove_filters_for_response( $object );
+
+ $has_password_filter = false;
+
+ if ( $this->can_access_password_content( $object, $request ) ) {
+ // Allow access to the post, permissions already checked before.
+ add_filter( 'post_password_required', '__return_false' );
+ $has_password_filter = true;
+ }
+
+ $data = $this->prepare_object_for_response( $object, $request );
+
+ if ( $has_password_filter ) {
+ // Reset filter.
+ remove_filter( 'post_password_required', '__return_false' );
+ }
+
+ $this->maybe_add_removed_filters_for_response( $removed_filters_for_response );
+ $post = $temp; // phpcs:ignore
+ wp_reset_postdata();
+
+ // Filter data including only schema props.
+ $data = array_intersect_key( $data, array_flip( $this->get_fields_for_response( $request ) ) );
+
+ // Filter data by context. E.g. in "view" mode the password property won't be allowed.
+ $data = $this->filter_response_by_context( $data, $context );
+
+ // Wrap the data in a response object.
+ $response = rest_ensure_response( $data );
+
+ $response->add_links( $this->prepare_links( $object, $request ) );
+
+ return $response;
+ }
+
+ /**
+ * Determines the allowed query_vars for a get_items() response and prepares
+ * them for WP_Query.
+ *
+ * @since 1.0.0-beta.1
+ *
+ * @param array $prepared_args Optional. Prepared WP_Query arguments. Default empty array.
+ * @param WP_REST_Request $request Optional. Full details about the request.
+ * @return array Items query arguments.
+ */
+ protected function prepare_items_query( $prepared_args = array(), $request = null ) {
+
+ $query_args = array();
+
+ foreach ( $prepared_args as $key => $value ) {
+ $query_args[ $key ] = $value;
+ }
+
+ $query_args = $this->prepare_items_query_orderby_mappings( $query_args, $request );
+
+ // Turn exclude and include params into proper arrays.
+ foreach ( array( 'post__in', 'post__not_in' ) as $arg ) {
+ if ( isset( $query_args[ $arg ] ) && ! is_array( $query_args[ $arg ] ) ) {
+ $query_args[ $arg ] = array_map( 'absint', explode( ',', $query_args[ $arg ] ) );
+ }
+ }
+
+ return $query_args;
+
+ }
+
+ /**
+ * Map to proper WP_Query orderby param.
+ *
+ * @since 1.0.0-beta.1
+ *
+ * @param array $query_args WP_Query arguments.
+ * @param WP_REST_Request $request Full details about the request.
+ * @return array Query arguments.
+ */
+ protected function prepare_items_query_orderby_mappings( $query_args, $request ) {
+
+ // Map to proper WP_Query orderby param.
+ if ( isset( $query_args['orderby'] ) && isset( $request['orderby'] ) ) {
+ $orderby_mappings = array(
+ 'id' => 'ID',
+ 'title' => 'title',
+ 'data_created' => 'post_date',
+ 'date_updated' => 'post_modified',
+ );
+
+ if ( isset( $orderby_mappings[ $request['orderby'] ] ) ) {
+ $query_args['orderby'] = $orderby_mappings[ $request['orderby'] ];
+ }
+ }
+
+ return $query_args;
+
+ }
+
+ /**
+ * Prepares a single post for create or update.
+ *
+ * @since 1.0.0-beta.1
+ * @since 1.0.0-beta.8 Initialize `$prepared_item` array before adding values to it.
+ *
+ * @param WP_REST_Request $request Request object.
+ * @return array|WP_Error Array of llms post args or WP_Error.
+ */
+ protected function prepare_item_for_database( $request ) {
+
+ $prepared_item = array();
+
+ // LLMS Post ID.
+ if ( isset( $request['id'] ) ) {
+ $existing_object = $this->get_object( absint( $request['id'] ) );
+ if ( is_wp_error( $existing_object ) ) {
+ return $existing_object;
+ }
+
+ $prepared_item['id'] = absint( $request['id'] );
+ }
+
+ $schema = $this->get_item_schema();
+
+ // LLMS Post title.
+ if ( ! empty( $schema['properties']['title'] ) && isset( $request['title'] ) ) {
+ if ( is_string( $request['title'] ) ) {
+ $prepared_item['post_title'] = $request['title'];
+ } elseif ( ! empty( $request['title']['raw'] ) ) {
+ $prepared_item['post_title'] = $request['title']['raw'];
+ }
+ }
+
+ // LLMS Post content.
+ if ( ! empty( $schema['properties']['content'] ) && isset( $request['content'] ) ) {
+ if ( is_string( $request['content'] ) ) {
+ $prepared_item['post_content'] = $request['content'];
+ } elseif ( isset( $request['content']['raw'] ) ) {
+ $prepared_item['post_content'] = $request['content']['raw'];
+ }
+ }
+
+ // LLMS Post excerpt.
+ if ( ! empty( $schema['properties']['excerpt'] ) && isset( $request['excerpt'] ) ) {
+ if ( is_string( $request['excerpt'] ) ) {
+ $prepared_item['post_excerpt'] = $request['excerpt'];
+ } elseif ( isset( $request['excerpt']['raw'] ) ) {
+ $prepared_item['post_excerpt'] = $request['excerpt']['raw'];
+ }
+ }
+
+ // LLMS Post status.
+ if ( ! empty( $schema['properties']['status'] ) && isset( $request['status'] ) ) {
+ $status = $this->handle_status_param( $request['status'] );
+ if ( is_wp_error( $status ) ) {
+ return $status;
+ }
+
+ $prepared_item['post_status'] = $status;
+ }
+
+ // LLMS Post date.
+ if ( ! empty( $schema['properties']['date_created'] ) && ! empty( $request['date_created'] ) ) {
+ $date_data = rest_get_date_with_gmt( $request['date_created'] );
+
+ if ( ! empty( $date_data ) ) {
+ list( $prepared_item['post_date'], $prepared_item['post_date_gmt'] ) = $date_data;
+ $prepared_item['edit_date'] = true;
+ }
+ } elseif ( ! empty( $schema['properties']['date_gmt'] ) && ! empty( $request['date_gmt'] ) ) {
+ $date_data = rest_get_date_with_gmt( $request['date_created_gmt'], true );
+
+ if ( ! empty( $date_data ) ) {
+ list( $prepared_item['post_date'], $prepared_item['post_date_gmt'] ) = $date_data;
+ $prepared_item['edit_date'] = true;
+ }
+ }
+
+ // LLMS Post slug.
+ if ( ! empty( $schema['properties']['slug'] ) && isset( $request['slug'] ) ) {
+ $prepared_item['post_name'] = $request['slug'];
+ }
+
+ // LLMS Post password.
+ if ( ! empty( $schema['properties']['password'] ) && isset( $request['password'] ) ) {
+ $prepared_item['post_password'] = $request['password'];
+ }
+
+ // LLMS Post Menu order.
+ if ( ! empty( $schema['properties']['menu_order'] ) && isset( $request['menu_order'] ) ) {
+ $prepared_item['menu_order'] = (int) $request['menu_order'];
+ }
+
+ // LLMS Post Comment status.
+ if ( ! empty( $schema['properties']['comment_status'] ) && ! empty( $request['comment_status'] ) ) {
+ $prepared_item['comment_status'] = $request['comment_status'];
+ }
+
+ // LLMS Post Ping status.
+ if ( ! empty( $schema['properties']['ping_status'] ) && ! empty( $request['ping_status'] ) ) {
+ $prepared_item['ping_status'] = $request['ping_status'];
+ }
+
+ return $prepared_item;
+
+ }
+
+ /**
+ * Get the LLMS Posts's schema, conforming to JSON Schema.
+ *
+ * @since 1.0.0-beta.1
+ * @since 1.0.0-beta.19 Allow only _built_in and not internal post status (see WordPress `get_post_stati()` ).
+ *
+ * @return array
+ */
+ public function get_item_schema() {
+
+ $schema = array(
+ '$schema' => 'http://json-schema.org/draft-04/schema#',
+ 'title' => $this->post_type,
+ 'type' => 'object',
+ 'properties' => array(
+ 'id' => array(
+ 'description' => __( 'Unique Identifier. The WordPress Post ID.', 'lifterlms' ),
+ 'type' => 'integer',
+ 'context' => array( 'view', 'edit' ),
+ 'readonly' => true,
+ ),
+ 'date_created' => array(
+ 'description' => __( 'Creation date. Format: Y-m-d H:i:s', 'lifterlms' ),
+ 'type' => 'string',
+ 'context' => array( 'view', 'edit' ),
+ ),
+ 'date_created_gmt' => array(
+ 'description' => __( 'Creation date (in GMT). Format: Y-m-d H:i:s', 'lifterlms' ),
+ 'type' => 'string',
+ 'context' => array( 'view', 'edit' ),
+ ),
+ 'date_updated' => array(
+ 'description' => __( 'Date last modified. Format: Y-m-d H:i:s', 'lifterlms' ),
+ 'type' => 'string',
+ 'context' => array( 'view', 'edit' ),
+ 'readonly' => true,
+ ),
+ 'date_updated_gmt' => array(
+ 'description' => __( 'Date last modified (in GMT). Format: Y-m-d H:i:s', 'lifterlms' ),
+ 'type' => 'string',
+ 'context' => array( 'view', 'edit' ),
+ 'readonly' => true,
+ ),
+ 'menu_order' => array(
+ 'description' => __( 'Creation date (in GMT). Format: Y-m-d H:i:s', 'lifterlms' ),
+ 'type' => 'integer',
+ 'default' => 0,
+ 'context' => array( 'view', 'edit' ),
+ 'arg_options' => array(
+ 'sanitize_callback' => 'absint',
+ ),
+ ),
+ 'title' => array(
+ 'description' => __( 'Post title.', 'lifterlms' ),
+ 'type' => 'object',
+ 'context' => array( 'view', 'edit' ),
+ 'arg_options' => array(
+ 'sanitize_callback' => null, // Note: sanitization implemented in self::prepare_item_for_database().
+ 'validate_callback' => null, // Note: validation implemented in self::prepare_item_for_database().
+ ),
+ 'required' => true,
+ 'properties' => array(
+ 'raw' => array(
+ 'description' => __( 'Raw title. Useful when displaying title in the WP Block Editor. Only returned in edit context.', 'lifterlms' ),
+ 'type' => 'string',
+ 'context' => array( 'edit' ),
+ ),
+ 'rendered' => array(
+ 'description' => __( 'Rendered title.', 'lifterlms' ),
+ 'type' => 'string',
+ 'context' => array( 'view', 'edit' ),
+ 'readonly' => true,
+ ),
+ ),
+ ),
+ 'content' => array(
+ 'type' => 'object',
+ 'description' => __( 'The HTML content of the post.', 'lifterlms' ),
+ 'context' => array( 'view', 'edit' ),
+ 'arg_options' => array(
+ 'sanitize_callback' => null, // Note: sanitization implemented in self::prepare_item_for_database().
+ 'validate_callback' => null, // Note: validation implemented in self::prepare_item_for_database().
+ ),
+ 'required' => true,
+ 'properties' => array(
+ 'rendered' => array(
+ 'description' => __( 'Rendered HTML content.', 'lifterlms' ),
+ 'type' => 'string',
+ 'context' => array( 'view', 'edit' ),
+ 'readonly' => true,
+ ),
+ 'raw' => array(
+ 'description' => __( 'Raw HTML content. Useful when displaying title in the WP Block Editor. Only returned in edit context.', 'lifterlms' ),
+ 'type' => 'string',
+ 'context' => array( 'edit' ),
+ ),
+ 'protected' => array(
+ 'description' => __( 'Whether the content is protected with a password.', 'lifterlms' ),
+ 'type' => 'boolean',
+ 'context' => array( 'view', 'edit' ),
+ 'readonly' => true,
+ ),
+ ),
+ ),
+ 'excerpt' => array(
+ 'type' => 'object',
+ 'description' => __( 'The HTML excerpt of the post.', 'lifterlms' ),
+ 'context' => array( 'view', 'edit' ),
+ 'arg_options' => array(
+ 'sanitize_callback' => null, // Note: sanitization implemented in self::prepare_item_for_database().
+ 'validate_callback' => null, // Note: validation implemented in self::prepare_item_for_database().
+ ),
+ 'properties' => array(
+ 'rendered' => array(
+ 'description' => __( 'Rendered HTML excerpt.', 'lifterlms' ),
+ 'type' => 'string',
+ 'context' => array( 'view', 'edit' ),
+ 'readonly' => true,
+ ),
+ 'raw' => array(
+ 'description' => __( 'Raw HTML excerpt. Useful when displaying title in the WP Block Editor. Only returned in edit context.', 'lifterlms' ),
+ 'type' => 'string',
+ 'context' => array( 'edit' ),
+ ),
+ 'protected' => array(
+ 'description' => __( 'Whether the excerpt is protected with a password.', 'lifterlms' ),
+ 'type' => 'boolean',
+ 'context' => array( 'view', 'edit' ),
+ 'readonly' => true,
+ ),
+ ),
+ ),
+ 'permalink' => array(
+ 'description' => __( 'Post URL.', 'lifterlms' ),
+ 'type' => 'string',
+ 'format' => 'uri',
+ 'context' => array( 'view', 'edit' ),
+ 'readonly' => true,
+ ),
+ 'slug' => array(
+ 'description' => __( 'Post URL slug.', 'lifterlms' ),
+ 'type' => 'string',
+ 'context' => array( 'view', 'edit' ),
+ 'arg_options' => array(
+ 'sanitize_callback' => array( $this, 'sanitize_slug' ),
+ ),
+ ),
+ 'post_type' => array(
+ 'description' => __( 'LifterLMS custom post type', 'lifterlms' ),
+ 'type' => 'string',
+ 'readonly' => true,
+ 'context' => array( 'view', 'edit' ),
+ ),
+ 'status' => array(
+ 'description' => __( 'The publication status of the post.', 'lifterlms' ),
+ 'type' => 'string',
+ 'default' => 'publish',
+ 'enum' => array_keys(
+ get_post_stati(
+ array(
+ '_builtin' => true,
+ 'internal' => false,
+ )
+ )
+ ),
+ 'context' => array( 'view', 'edit' ),
+ ),
+ 'password' => array(
+ 'description' => __( 'Password used to protect access to the content.', 'lifterlms' ),
+ 'type' => 'string',
+ 'context' => array( 'edit' ),
+ ),
+ 'featured_media' => array(
+ 'description' => __( 'Featured image ID.', 'lifterlms' ),
+ 'type' => 'integer',
+ 'context' => array( 'view', 'edit' ),
+ ),
+ 'comment_status' => array(
+ 'description' => __( 'Post comment status. Default comment status dependent upon general WordPress post discussion settings.', 'lifterlms' ),
+ 'type' => 'string',
+ 'default' => 'open',
+ 'enum' => array( 'open', 'closed' ),
+ 'context' => array( 'view', 'edit' ),
+ ),
+ 'ping_status' => array(
+ 'description' => __( 'Post ping status. Default ping status dependent upon general WordPress post discussion settings.', 'lifterlms' ),
+ 'type' => 'string',
+ 'default' => 'open',
+ 'enum' => array( 'open', 'closed' ),
+ 'context' => array( 'view', 'edit' ),
+ ),
+ ),
+ );
+
+ /**
+ * TODO: understand how to treat possible conflicting properties => instructors are registered as additional rest field by llms_blocks.
+ */
+ // $schema = $this->add_additional_fields_schema( $schema );
+ return $schema;
+ }
+
+ /**
+ * Get object.
+ *
+ * @since 1.0.0-beta.9
+ *
+ * @param int $id Object ID.
+ * @return LLMS_Course|WP_Error
+ */
+ protected function get_object( $id ) {
+
+ $class = $this->llms_post_class_from_post_type();
+
+ if ( ! $class ) {
+ return new WP_Error(
+ 'llms_rest_cannot_get_object',
+ /* translators: %s: post type */
+ sprintf( __( 'The %s cannot be retrieved.', 'lifterlms' ), $this->post_type ),
+ array( 'status' => 500 )
+ );
+ }
+
+ $object = llms_get_post( $id );
+ return $object && is_a( $object, $class ) ? $object : llms_rest_not_found_error();
+ }
+
+ /**
+ * Create an LLMS_Post_Model
+ *
+ * @since 1.0.0-beta.1
+ * @since 1.0.0-beta.9 Implement generic llms post creation.
+ *
+ * @param array $object_args Object args.
+ * @return LLMS_Post_Model|WP_Error
+ */
+ protected function create_llms_post( $object_args ) {
+
+ $class = $this->llms_post_class_from_post_type();
+
+ if ( ! $class ) {
+ return new WP_Error(
+ 'llms_rest_cannot_create_object',
+ /* translators: %s: post type */
+ sprintf( __( 'The %s cannot be created.', 'lifterlms' ), $this->post_type ),
+ array( 'status' => 500 )
+ );
+ }
+
+ $object = new $class( 'new', $object_args );
+ return $object && is_a( $object, $class ) ? $object : llms_rest_not_found_error();
+ }
+
+ /**
+ * Prepare links for the request.
+ *
+ * @since 1.0.0-beta.1
+ * @since 1.0.0-beta.2 Filter taxonomies by `public` property instead of `show_in_rest`.
+ * @since 1.0.0-beta.3 Filter taxonomies by `show_in_llms_rest` property instead of `public`.
+ * @since 1.0.0-beta.7 `self` and `collection` links prepared in the parent class.
+ * Fix wp:featured_media link, we don't expose any embeddable field.
+ * @since 1.0.0-beta.8 Return links to those taxonomies which have an accessible rest route.
+ * @since 1.0.0-beta.14 Added $request parameter.
+ *
+ * @param LLMS_Post_Model $object Object data.
+ * @param WP_REST_Request $request Request object.
+ * @return array Links for the given object.
+ */
+ protected function prepare_links( $object, $request ) {
+
+ $links = parent::prepare_links( $object, $request );
+
+ $object_id = $object->get( 'id' );
+
+ // Content.
+ $links['content'] = array(
+ 'href' => rest_url( sprintf( '/%s/%s/%d/%s', $this->namespace, $this->rest_base, $object_id, 'content' ) ),
+ );
+
+ // If we have a featured media, add that.
+ $featured_media = get_post_thumbnail_id( $object_id );
+ if ( $featured_media ) {
+ $image_url = rest_url( 'wp/v2/media/' . $featured_media );
+
+ $links['https://api.w.org/featuredmedia'] = array(
+ 'href' => $image_url,
+ );
+ }
+
+ $taxonomies = get_object_taxonomies( $this->post_type );
+
+ if ( ! empty( $taxonomies ) ) {
+ $links['https://api.w.org/term'] = array();
+
+ foreach ( $taxonomies as $tax ) {
+ $taxonomy_obj = get_taxonomy( $tax );
+
+ // Skip taxonomies that are not set to be shown in REST and LLMS REST.
+ if ( empty( $taxonomy_obj->show_in_rest ) || empty( $taxonomy_obj->show_in_llms_rest ) ) {
+ continue;
+ }
+
+ $tax_base = ! empty( $taxonomy_obj->rest_base ) ? $taxonomy_obj->rest_base : $tax;
+
+ $terms_url = add_query_arg(
+ 'post',
+ $object_id,
+ rest_url( 'wp/v2/' . $tax_base )
+ );
+
+ $links['https://api.w.org/term'][] = array(
+ 'href' => $terms_url,
+ 'taxonomy' => $tax,
+ );
+ }
+ }
+
+ return $links;
+
+ }
+
+ /**
+ * Re-add filters previously removed
+ *
+ * @since 1.0.0-beta.1
+ *
+ * @param LLMS_Post_Model $object Object.
+ * @return array Array of filters removed for response.
+ */
+ protected function maybe_remove_filters_for_response( $object ) {
+
+ $filters_to_be_removed = $this->get_filters_to_be_removed_for_response( $object );
+ $filters_removed = array();
+
+ // Need to remove some filters.
+ foreach ( $filters_to_be_removed as $hook => $filters ) {
+ foreach ( $filters as $filter_data ) {
+ $has_filter = has_filter( $hook, $filter_data['callback'] );
+
+ if ( false !== $has_filter && $filter_data['priority'] === $has_filter ) {
+ remove_filter( $hook, $filter_data['callback'], $filter_data['priority'] );
+ if ( ! isset( $filters_removed[ $hook ] ) ) {
+ $filters_removed[ $hook ] = array();
+ }
+ $filters_removed[ $hook ][] = $filter_data;
+
+ }
+ }
+ }
+
+ return $filters_removed;
+
+ }
+
+ /**
+ * Re-add filters previously removed
+ *
+ * @since 1.0.0-beta.1
+ *
+ * @param array $filters_removed Array of filters removed to be re-added.
+ * @return void
+ */
+ protected function maybe_add_removed_filters_for_response( $filters_removed ) {
+
+ if ( ! empty( $filters_removed ) ) {
+ foreach ( $filters_removed as $hook => $filters ) {
+ foreach ( $filters as $filter_data ) {
+ add_filter(
+ $hook,
+ $filter_data['callback'],
+ $filter_data['priority'],
+ isset( $filter_data['accepted_args'] ) ? $filter_data['accepted_args'] : 1
+ );
+ }
+ }
+ }
+ }
+
+ /**
+ * Get action/filters to be removed before preparing the item for response.
+ *
+ * @since 1.0.0-beta.1
+ * @since 1.0.0-beta.9 Removed `"llms_rest_{$this->post_type}_filters_removed_for_reponse"` filter hooks,
+ * `"llms_rest_{$this->post_type}_filters_removed_for_response"` added.
+ *
+ * @param LLMS_Post_Model $object LLMS_Post_Model object.
+ * @return array Array of action/filters to be removed for response.
+ */
+ protected function get_filters_to_be_removed_for_response( $object ) {
+
+ /**
+ * Modify the array of filters to be removed before building the response.
+ *
+ * The dynamic portion of the hook name, `$this->post_type`, refers to the post type slug.
+ *
+ * @since 1.0.0-beta.9
+ *
+ * @param array $filters Array of filters to be removed.
+ * @param LLMS_Post_Model $object LLMS_Post_Model object.
+ */
+ return apply_filters( "llms_rest_{$this->post_type}_filters_removed_for_response", array(), $object );
+
+ }
+
+ /**
+ * Determines validity and normalizes the given status parameter.
+ * Heavily based on WP_REST_Posts_Controller::handle_status_param().
+ *
+ * @since 1.0.0-beta.1
+ * @since 1.0.0-beta.18 Use plural post type name.
+ *
+ * @param string $status Status.
+ * @return string|WP_Error Status or WP_Error if lacking the proper permission.
+ */
+ protected function handle_status_param( $status ) {
+
+ $post_type_object = get_post_type_object( $this->post_type );
+ $post_type_name = $post_type_object->labels->name;
+
+ switch ( $status ) {
+ case 'draft':
+ case 'pending':
+ break;
+ case 'private':
+ if ( ! current_user_can( $post_type_object->cap->publish_posts ) ) {
+ // Translators: %s = The post type name.
+ return llms_rest_authorization_required_error( sprintf( __( 'Sorry, you are not allowed to create private %s.', 'lifterlms' ), $post_type_name ) );
+ }
+ break;
+ case 'publish':
+ case 'future':
+ if ( ! current_user_can( $post_type_object->cap->publish_posts ) ) {
+ // Translators: $s = The post type name.
+ return llms_rest_authorization_required_error( sprintf( __( 'Sorry, you are not allowed to publish %s.', 'lifterlms' ), $post_type_name ) );
+ }
+ break;
+ default:
+ if ( ! get_post_status_object( $status ) ) {
+ $status = 'draft';
+ }
+ break;
+ }
+
+ return $status;
+ }
+
+ /**
+ * Determines the featured media based on a request param
+ *
+ * Heavily based on WP_REST_Posts_Controller::handle_featured_media().
+ *
+ * @since 1.0.0-beta.1
+ * @since 1.0.0-beta.18 Fixed call to undefined function `llms_bad_request_error()`, must be `llms_rest_bad_request_error()`.
+ *
+ * @param int $featured_media Featured Media ID.
+ * @param int $object_id LLMS object ID.
+ * @return bool|WP_Error Whether the post thumbnail was successfully deleted, otherwise WP_Error.
+ */
+ protected function handle_featured_media( $featured_media, $object_id ) {
+
+ $featured_media = (int) $featured_media;
+ if ( $featured_media ) {
+ $result = set_post_thumbnail( $object_id, $featured_media );
+ if ( $result ) {
+ return true;
+ } else {
+ return llms_rest_bad_request_error( __( 'Invalid featured media ID.', 'lifterlms' ) );
+ }
+ } else {
+ return delete_post_thumbnail( $object_id );
+ }
+
+ }
+
+ /**
+ * Updates the post's terms from a REST request.
+ *
+ * Heavily based on WP_REST_Posts_Controller::handle_terms().
+ *
+ * @since 1.0.0-beta.1
+ * @since 1.0.0-beta.2 Filter taxonomies by `public` property instead of `show_in_rest`.
+ * @since 1.0.0-beta.3 Filter taxonomies by `show_in_llms_rest` property instead of `public`.
+ *
+ * @param int $object_id The post ID to update the terms form.
+ * @param WP_REST_Request $request The request object with post and terms data.
+ * @return null|WP_Error WP_Error on an error assigning any of the terms, otherwise null.
+ */
+ protected function handle_terms( $object_id, $request ) {
+
+ $taxonomies = wp_list_filter( get_object_taxonomies( $this->post_type, 'objects' ), array( 'show_in_llms_rest' => true ) );
+
+ foreach ( $taxonomies as $taxonomy ) {
+ $base = $this->get_taxonomy_rest_base( $taxonomy );
+
+ if ( ! isset( $request[ $base ] ) ) {
+ continue;
+ }
+
+ // We could use LLMS_Post_Model::set_terms() but it doesn't return a WP_Error which can be useful here.
+ $result = wp_set_object_terms( $object_id, $request[ $base ], $taxonomy->name );
+ if ( is_wp_error( $result ) ) {
+ return $result;
+ }
+ }
+ }
+
+ /**
+ * Checks whether current user can assign all terms sent with the current request.
+ *
+ * Heavily based on WP_REST_Posts_Controller::check_assign_terms_permission().
+ *
+ * @since 1.0.0-beta.1
+ * @since 1.0.0-beta.3 Filter taxonomies by `show_in_llms_rest` property instead of `public`.
+ *
+ * @param WP_REST_Request $request The request object with post and terms data.
+ * @return bool Whether the current user can assign the provided terms.
+ */
+ protected function check_assign_terms_permission( $request ) {
+ $taxonomies = wp_list_filter( get_object_taxonomies( $this->post_type, 'objects' ), array( 'show_in_llms_rest' => true ) );
+ foreach ( $taxonomies as $taxonomy ) {
+ $base = $this->get_taxonomy_rest_base( $taxonomy );
+
+ if ( ! isset( $request[ $base ] ) ) {
+ continue;
+ }
+
+ foreach ( $request[ $base ] as $term_id ) {
+ // Invalid terms will be rejected later.
+ if ( ! get_term( $term_id, $taxonomy->name ) ) {
+ continue;
+ }
+
+ if ( ! current_user_can( 'assign_term', (int) $term_id ) ) {
+ return false;
+ }
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Maps a taxonomy name to the relative rest base
+ *
+ * @since 1.0.0-beta.1
+ *
+ * @param object $taxonomy The taxonomy object.
+ * @return string The taxonomy rest base.
+ */
+ protected function get_taxonomy_rest_base( $taxonomy ) {
+
+ return ! empty( $taxonomy->rest_base ) ? $taxonomy->rest_base : $taxonomy->name;
+
+ }
+
+ /**
+ * Checks if a post can be edited.
+ *
+ * @since 1.0.0-beta.1
+ *
+ * @return bool Whether the post can be created
+ */
+ protected function check_create_permission() {
+
+ $post_type = get_post_type_object( $this->post_type );
+ return current_user_can( $post_type->cap->publish_posts );
+
+ }
+
+ /**
+ * Checks if an llms post can be edited.
+ *
+ * @since 1.0.0-beta.1
+ *
+ * @param LLMS_Post_Model $object Optional. The LLMS_Post_model object. Default null.
+ * @return bool Whether the post can be edited.
+ */
+ protected function check_update_permission( $object = null ) {
+
+ $post_type = get_post_type_object( $this->post_type );
+ return is_null( $object ) ? current_user_can( $post_type->cap->edit_posts ) : current_user_can( $post_type->cap->edit_post, $object->get( 'id' ) );
+
+ }
+
+ /**
+ * Checks if an llms post can be deleted.
+ *
+ * @since 1.0.0-beta.1
+ *
+ * @param LLMS_Post_Model $object The LLMS_Post_model object.
+ * @return bool Whether the post can be deleted.
+ */
+ protected function check_delete_permission( $object ) {
+
+ $post_type = get_post_type_object( $this->post_type );
+ return current_user_can( $post_type->cap->delete_post, $object->get( 'id' ) );
+
+ }
+
+ /**
+ * Checks if an llms post can be read.
+ *
+ * @since 1.0.0-beta.1
+ *
+ * @param LLMS_Post_Model $object The LLMS_Post_model object.
+ * @return bool Whether the post can be read.
+ */
+ protected function check_read_permission( $object ) {
+
+ $post_type = get_post_type_object( $this->post_type );
+ $status = $object->get( 'status' );
+ $id = $object->get( 'id' );
+ $wp_post = $object->get( 'post' );
+
+ // Is the post readable?
+ if ( 'publish' === $status || current_user_can( $post_type->cap->read_post, $id ) ) {
+ return true;
+ }
+
+ $post_status_obj = get_post_status_object( $status );
+ if ( $post_status_obj && $post_status_obj->public ) {
+ return true;
+ }
+
+ // Can we read the parent if we're inheriting?
+ if ( 'inherit' === $status && $wp_post->post_parent > 0 ) {
+ $parent = get_post( $wp_post->post_parent );
+ if ( $parent ) {
+ return $this->check_read_permission( $parent );
+ }
+ }
+
+ /*
+ * If there isn't a parent, but the status is set to inherit, assume
+ * it's published (as per get_post_status()).
+ */
+ if ( 'inherit' === $status ) {
+ return true;
+ }
+
+ return false;
+
+ }
+
+
+ /**
+ * Checks if the user can access password-protected content.
+ *
+ * @since 1.0.0-beta.1
+ *
+ * @param LLMS_Post_Model $object The LLMS_Post_model object.
+ * @param WP_REST_Request $request Request data to check.
+ * @return bool True if the user can access password-protected content, otherwise false.
+ */
+ public function can_access_password_content( $object, $request ) {
+
+ if ( empty( $object->get( 'password' ) ) ) {
+ // No filter required.
+ return false;
+ }
+
+ // Edit context always gets access to password-protected posts.
+ if ( 'edit' === $request['context'] ) {
+ return true;
+ }
+
+ // No password, no auth.
+ if ( empty( $request['password'] ) ) {
+ return false;
+ }
+
+ // Double-check the request password.
+ return hash_equals( $object->get( 'password' ), $request['password'] );
+ }
+
+ /**
+ * Get the llms post model class from the controller post type.
+ *
+ * @since 1.0.0-beta.9
+ *
+ * @return string|bool The llms post model class name if it exists or FALSE if it doesn't.
+ */
+ protected function llms_post_class_from_post_type() {
+
+ if ( isset( $this->llms_post_class ) ) {
+ return $this->llms_post_class;
+ }
+
+ $post_type = explode( '_', str_replace( 'llms_', '', $this->post_type ) );
+ $class = 'LLMS';
+
+ foreach ( $post_type as $part ) {
+ $class .= '_' . ucfirst( $part );
+ }
+
+ if ( class_exists( $class ) ) {
+ $this->llms_post_class = $class;
+ } else {
+ $this->llms_post_class = false;
+ }
+
+ return $this->llms_post_class;
+ }
+
+ /**
+ * Sanitizes and validates the list of post statuses, including whether the user can query private statuses
+ *
+ * Heavily based on the WordPress WP_REST_Posts_Controller::sanitize_post_statuses().
+ *
+ * @since 1.0.0-beta.19
+ *
+ * @param string|array $statuses One or more post statuses.
+ * @param WP_REST_Request $request Full details about the request.
+ * @param string $parameter Additional parameter to pass to validation.
+ * @return array|WP_Error A list of valid statuses, otherwise WP_Error object.
+ */
+ public function sanitize_post_statuses( $statuses, $request, $parameter ) {
+ $statuses = wp_parse_slug_list( $statuses );
+
+ $attributes = $request->get_attributes();
+ $default_status = $attributes['args']['status']['default'];
+
+ foreach ( $statuses as $status ) {
+ if ( $status === $default_status ) {
+ continue;
+ }
+
+ $post_type_obj = get_post_type_object( $this->post_type );
+
+ if ( current_user_can( $post_type_obj->cap->edit_posts ) || 'private' === $status && current_user_can( $post_type_obj->cap->read_private_posts ) ) {
+ $result = rest_validate_request_arg( $status, $request, $parameter );
+ if ( is_wp_error( $result ) ) {
+ return $result;
+ }
+ } else {
+ return llms_rest_authorization_required_error( __( 'Status is forbidden.', 'lifterlms' ) );
+ }
+ }
+
+ return $statuses;
+ }
+
+}
diff --git a/libraries/lifterlms-rest/includes/abstracts/class-llms-rest-users-controller.php b/libraries/lifterlms-rest/includes/abstracts/class-llms-rest-users-controller.php
new file mode 100644
index 0000000000..3ac79d800c
--- /dev/null
+++ b/libraries/lifterlms-rest/includes/abstracts/class-llms-rest-users-controller.php
@@ -0,0 +1,777 @@
+ 'ID',
+ 'username' => 'user_login',
+ 'email' => 'user_email',
+ 'url' => 'user_url',
+ 'name' => 'display_name',
+ );
+
+ /**
+ * Determine if the current user has permissions to manage the role(s) present in a request
+ *
+ * @since 1.0.0-beta.1
+ *
+ * @param WP_REST_Request $request Request object.
+ * @return true|WP_Error
+ */
+ protected function check_roles_permissions( $request ) {
+
+ global $wp_roles;
+
+ $schema = $this->get_item_schema();
+ $roles = array();
+ if ( ! empty( $request['roles'] ) ) {
+ $roles = $request['roles'];
+ } elseif ( ! empty( $schema['properties']['roles']['default'] ) ) {
+ $roles = $schema['properties']['roles']['default'];
+ }
+
+ foreach ( $roles as $role ) {
+
+ if ( ! isset( $wp_roles->role_objects[ $role ] ) ) {
+ // Translators: %s = role key.
+ return llms_rest_bad_request_error( sprintf( __( 'The role %s does not exist.', 'lifterlms' ), $role ) );
+ }
+
+ $potential_role = $wp_roles->role_objects[ $role ];
+
+ /*
+ * Don't let anyone with 'edit_users' (admins) edit their own role to something without it.
+ * Multisite super admins can freely edit their blog roles -- they possess all caps.
+ */
+ if ( ! ( is_multisite()
+ && current_user_can( 'manage_sites' ) )
+ && get_current_user_id() === $request['id']
+ && ! $potential_role->has_cap( 'edit_users' )
+ ) {
+ return llms_rest_authorization_required_error( __( 'You are not allowed to give users this role.', 'lifterlms' ) );
+ }
+
+ // Include admin functions to get access to `get_editable_roles()`.
+ require_once ABSPATH . 'wp-admin/includes/admin.php';
+
+ // The new role must be editable by the logged-in user.
+ $editable_roles = get_editable_roles();
+
+ if ( empty( $editable_roles[ $role ] ) ) {
+ return llms_rest_authorization_required_error( __( 'You are not allowed to give users this role.', 'lifterlms' ) );
+ }
+ }
+
+ return true;
+
+ }
+
+ /**
+ * Insert the prepared data into the database
+ *
+ * @since 1.0.0-beta.1
+ *
+ * @param array $prepared Prepared item data.
+ * @param WP_REST_Request $request Request object.
+ * @return obj Object Instance of object from `$this->get_object()`.
+ */
+ protected function create_object( $prepared, $request ) {
+
+ $object_id = wp_insert_user( $prepared );
+
+ if ( is_wp_error( $object_id ) ) {
+ return $object_id;
+ }
+
+ return $this->update_additional_data( $object_id, $prepared, $request );
+
+ }
+
+
+ /**
+ * Delete the object
+ *
+ * Note: we do not return 404s when the resource to delete cannot be found. We assume it's already been deleted and respond with 204.
+ * Errors returned by this method should be any error other than a 404!
+ *
+ * @since 1.0.0-beta.1
+ *
+ * @param obj $object Instance of the object from `$this->get_object()`.
+ * @param WP_REST_Request $request Request object.
+ * @return true|WP_Error `true` when the object is removed, `WP_Error` on failure.
+ */
+ protected function delete_object( $object, $request ) {
+
+ $id = $object->get( 'id' );
+ $reassign = 0 === $request['reassign'] ? null : $request['reassign'];
+
+ if ( ! empty( $reassign ) ) {
+ if ( $reassign === $id || ! get_userdata( $reassign ) ) {
+ return llms_rest_bad_request_error( __( 'Invalid user ID for reassignment.', 'lifterlms' ) );
+ }
+ }
+
+ // Include admin user functions to get access to `wp_delete_user()`.
+ require_once ABSPATH . 'wp-admin/includes/user.php';
+
+ $result = wp_delete_user( $id, $reassign );
+
+ if ( ! $result ) {
+ return llms_rest_server_error( __( 'The user could not be deleted.', 'lifterlms' ) );
+ }
+
+ return true;
+
+ }
+
+ /**
+ * Determine if the current user can view the object
+ *
+ * @since 1.0.0-beta.7
+ *
+ * @param object $object Object.
+ * @return bool
+ */
+ protected function check_read_object_permissions( $object ) {
+ return $this->check_read_item_permissions( $this->get_object_id( $object ) );
+ }
+
+ /**
+ * Retrieves the query params for the objects collection
+ *
+ * @since 1.0.0-beta.1
+ *
+ * @return array Collection parameters.
+ */
+ public function get_collection_params() {
+
+ $params = parent::get_collection_params();
+
+ $params['roles'] = array(
+ 'description' => __( 'Include only users keys matching matching a specific role. Accepts a single role or a comma separated list of roles.', 'lifterlms' ),
+ 'type' => 'array',
+ 'items' => array(
+ 'type' => 'string',
+ 'enum' => $this->get_enum_roles(),
+ ),
+ );
+
+ return $params;
+
+ }
+
+ /**
+ * Retrieve arguments for deleting a resource
+ *
+ * @since 1.0.0-beta.1
+ *
+ * @return array
+ */
+ public function get_delete_item_args() {
+ return array(
+ 'reassign' => array(
+ 'type' => 'integer',
+ 'description' => __( 'Reassign the deleted user\'s posts and links to this user ID.', 'lifterlms' ),
+ 'default' => 0,
+ 'sanitize_callback' => 'absint',
+ ),
+ );
+ }
+
+ /**
+ * Retrieve an array of allowed user role values
+ *
+ * @since 1.0.0-beta.1
+ *
+ * @return string[]
+ */
+ protected function get_enum_roles() {
+
+ global $wp_roles;
+ return array_keys( $wp_roles->roles );
+
+ }
+
+ /**
+ * Get the item schema
+ *
+ * @since 1.0.0-beta.1
+ *
+ * @return array
+ */
+ public function get_item_schema() {
+
+ $schema = array(
+ '$schema' => 'http://json-schema.org/draft-04/schema#',
+ 'title' => $this->resource_name,
+ 'type' => 'object',
+ 'properties' => array(
+ 'id' => array(
+ 'description' => __( 'Unique identifier for the user.', 'lifterlms' ),
+ 'type' => 'integer',
+ 'context' => array( 'view', 'edit' ),
+ 'readonly' => true,
+ ),
+ 'username' => array(
+ 'description' => __( 'Login name for the user.', 'lifterlms' ),
+ 'type' => 'string',
+ 'context' => array( 'edit' ),
+ 'arg_options' => array(
+ 'sanitize_callback' => array( $this, 'sanitize_username' ),
+ ),
+ ),
+ 'name' => array(
+ 'description' => __( 'Display name for the user.', 'lifterlms' ),
+ 'type' => 'string',
+ 'context' => array( 'view', 'edit' ),
+ 'arg_options' => array(
+ 'sanitize_callback' => 'sanitize_text_field',
+ ),
+ ),
+ 'first_name' => array(
+ 'description' => __( 'First name for the user.', 'lifterlms' ),
+ 'type' => 'string',
+ 'context' => array( 'edit' ),
+ 'arg_options' => array(
+ 'sanitize_callback' => 'sanitize_text_field',
+ ),
+ ),
+ 'last_name' => array(
+ 'description' => __( 'Last name for the user.', 'lifterlms' ),
+ 'type' => 'string',
+ 'context' => array( 'edit' ),
+ 'arg_options' => array(
+ 'sanitize_callback' => 'sanitize_text_field',
+ ),
+ ),
+ 'email' => array(
+ 'description' => __( 'The email address for the user.', 'lifterlms' ),
+ 'type' => 'string',
+ 'format' => 'email',
+ 'context' => array( 'edit' ),
+ 'required' => true,
+ ),
+ 'url' => array(
+ 'description' => __( 'URL of the user.', 'lifterlms' ),
+ 'type' => 'string',
+ 'format' => 'uri',
+ 'context' => array( 'view', 'edit' ),
+ ),
+ 'description' => array(
+ 'description' => __( 'Description of the user.', 'lifterlms' ),
+ 'type' => 'string',
+ 'context' => array( 'view', 'edit' ),
+ ),
+ 'nickname' => array(
+ 'description' => __( 'The nickname for the user.', 'lifterlms' ),
+ 'type' => 'string',
+ 'context' => array( 'edit' ),
+ 'arg_options' => array(
+ 'sanitize_callback' => 'sanitize_text_field',
+ ),
+ ),
+ 'registered_date' => array(
+ 'description' => __( 'Registration date for the user.', 'lifterlms' ),
+ 'type' => 'string',
+ 'format' => 'date-time',
+ 'context' => array( 'edit' ),
+ 'readonly' => true,
+ ),
+ 'roles' => array(
+ 'description' => __( 'Roles assigned to the user.', 'lifterlms' ),
+ 'type' => 'array',
+ 'items' => array(
+ 'type' => 'string',
+ 'enum' => $this->get_enum_roles(),
+ ),
+ 'context' => array( 'edit' ),
+ 'default' => array( 'student' ),
+ ),
+ 'password' => array(
+ 'description' => __( 'Password for the user (never included).', 'lifterlms' ),
+ 'type' => 'string',
+ 'context' => array(), // Password is never displayed.
+ 'arg_options' => array(
+ 'sanitize_callback' => array( $this, 'sanitize_password' ),
+ ),
+ ),
+ 'billing_address_1' => array(
+ 'description' => __( 'User address line 1.', 'lifterlms' ),
+ 'type' => 'string',
+ 'context' => array( 'edit' ),
+ 'arg_options' => array(
+ 'sanitize_callback' => 'sanitize_text_field',
+ ),
+ ),
+ 'billing_address_2' => array(
+ 'description' => __( 'User address line 2.', 'lifterlms' ),
+ 'type' => 'string',
+ 'context' => array( 'edit' ),
+ 'arg_options' => array(
+ 'sanitize_callback' => 'sanitize_text_field',
+ ),
+ ),
+ 'billing_city' => array(
+ 'description' => __( 'User address city name.', 'lifterlms' ),
+ 'type' => 'string',
+ 'context' => array( 'edit' ),
+ 'arg_options' => array(
+ 'sanitize_callback' => 'sanitize_text_field',
+ ),
+ ),
+ 'billing_state' => array(
+ 'description' => __( 'User address ISO code for the state, province, or district.', 'lifterlms' ),
+ 'type' => 'string',
+ 'context' => array( 'edit' ),
+ 'arg_options' => array(
+ 'sanitize_callback' => 'sanitize_text_field',
+ ),
+ ),
+ 'billing_postcode' => array(
+ 'description' => __( 'User address postal code.', 'lifterlms' ),
+ 'type' => 'string',
+ 'context' => array( 'edit' ),
+ 'arg_options' => array(
+ 'sanitize_callback' => 'sanitize_text_field',
+ ),
+ ),
+ 'billing_country' => array(
+ 'description' => __( 'User address ISO code for the country.', 'lifterlms' ),
+ 'type' => 'string',
+ 'context' => array( 'edit' ),
+ 'arg_options' => array(
+ 'sanitize_callback' => 'sanitize_text_field',
+ ),
+ ),
+ ),
+ );
+
+ if ( get_option( 'show_avatars' ) ) {
+
+ $avatar_properties = array();
+ foreach ( rest_get_avatar_sizes() as $size ) {
+ $avatar_properties[ $size ] = array(
+ // Translators: %d = avatar image size in pixels.
+ 'description' => sprintf( __( 'Avatar URL with image size of %d pixels.', 'lifterlms' ), $size ),
+ 'type' => 'string',
+ 'format' => 'uri',
+ 'context' => array( 'view', 'edit' ),
+ );
+ }
+
+ $schema['properties']['avatar_urls'] = array(
+ 'description' => __( 'Avatar URLs for the user.', 'lifterlms' ),
+ 'type' => 'object',
+ 'context' => array( 'view', 'edit' ),
+ 'readonly' => true,
+ 'properties' => $avatar_properties,
+ );
+
+ }
+
+ return $schema;
+
+ }
+
+ /**
+ * Retrieve a query object based on arguments from a `get_items()` (collection) request
+ *
+ * @since 1.0.0-beta.1
+ * @since 1.0.0-beta.12 Parse `search` and `search_columns` args.
+ *
+ * @param array $prepared Array of collection arguments.
+ * @param WP_REST_Request $request Request object.
+ * @return WP_User_Query
+ */
+ protected function get_objects_query( $prepared, $request ) {
+
+ if ( 'id' === $prepared['orderby'] ) {
+ $prepared['orderby'] = 'ID';
+ } elseif ( 'registered_date' === $prepared['orderby'] ) {
+ $prepared['orderby'] = 'registered';
+ }
+
+ $args = array(
+ 'paged' => $prepared['page'],
+ 'number' => $prepared['per_page'],
+ 'order' => strtoupper( $prepared['order'] ),
+ 'orderby' => $prepared['orderby'],
+ );
+
+ if ( ! empty( $prepared['roles'] ) ) {
+ $args['role__in'] = $prepared['roles'];
+ }
+
+ if ( ! empty( $prepared['include'] ) ) {
+ $args['include'] = $prepared['include'];
+ }
+
+ if ( ! empty( $prepared['exclude'] ) ) {
+ $args['exclude'] = $prepared['exclude'];
+ }
+
+ if ( ! empty( $prepared['search'] ) ) {
+ $args['search'] = $prepared['search'];
+ }
+
+ if ( ! empty( $prepared['search_columns'] ) ) {
+ $args['search_columns'] = $prepared['search_columns'];
+ }
+
+ return new WP_User_Query( $args );
+
+ }
+
+
+ /**
+ * Retrieve an array of objects from the result of `$this->get_objects_query()`
+ *
+ * @since 1.0.0-beta.1
+ *
+ * @param obj $query Objects query result.
+ * @return WP_User[]
+ */
+ protected function get_objects_from_query( $query ) {
+ return $query->get_results();
+ }
+
+ /**
+ * Retrieve pagination information from an objects query.
+ *
+ * @since 1.0.0-beta.1
+ *
+ * @param WP_User_Query $query Objects query result returned by {@see LLMS_REST_Users_Controller::get_objects_query()}.
+ * @param array $prepared Array of collection arguments.
+ * @param WP_REST_Request $request Request object.
+ * @return array {
+ * Array of pagination information.
+ *
+ * @type int $current_page Current page number.
+ * @type int $total_results Total number of results.
+ * @type int $total_pages Total number of results pages.
+ * }
+ */
+ protected function get_pagination_data_from_query( $query, $prepared, $request ) {
+
+ $current_page = absint( $prepared['page'] );
+ $total_results = $query->get_total();
+ $total_pages = absint( ceil( $total_results / $prepared['per_page'] ) );
+
+ return compact( 'current_page', 'total_results', 'total_pages' );
+
+ }
+
+ /**
+ * Map request keys to database keys for insertion
+ *
+ * Array keys are the request fields (as defined in the schema) and
+ * array values are the database fields.
+ *
+ * @since 1.0.0-beta.1
+ * @since 1.0.0-beta.11 Correctly map request's `billing_postcode` param to `billing_zip` meta.
+ *
+ * @return array
+ */
+ protected function map_schema_to_database() {
+
+ $map = parent::map_schema_to_database();
+
+ $map['username'] = 'user_login';
+ $map['password'] = 'user_pass';
+ $map['name'] = 'display_name';
+ $map['email'] = 'user_email';
+ $map['url'] = 'user_url';
+ $map['registered_date'] = 'user_registered';
+ $map['billing_postcode'] = 'billing_zip';
+
+ // Not inserted/read via database calls.
+ unset( $map['roles'], $map['avatar_urls'] );
+
+ return $map;
+
+ }
+
+ /**
+ * Prepare request arguments for a database insert/update
+ *
+ * @since 1.0.0-beta.1
+ *
+ * @param WP_Rest_Request $request Request object.
+ * @return array
+ */
+ protected function prepare_item_for_database( $request ) {
+
+ $prepared = parent::prepare_item_for_database( $request );
+
+ // If we're creating a new item, maybe add some defaults.
+ if ( empty( $prepared['id'] ) ) {
+
+ // Pass an explicit false to `wp_insert_user()`.
+ $prepared['role'] = false;
+
+ if ( empty( $prepared['user_pass'] ) ) {
+ $prepared['user_pass'] = wp_generate_password( 22 );
+ }
+
+ if ( empty( $prepared['user_login'] ) ) {
+ $prepared['user_login'] = LLMS_Person_Handler::generate_username( $prepared['user_email'] );
+ }
+ }
+
+ return $prepared;
+
+ }
+
+ /**
+ * Prepare an object for response
+ *
+ * @since 1.0.0-beta.1
+ * @since 1.0.0-beta.14 Only add remapped keys to the response when the schema key is present in the expected response fields array.
+ *
+ * @param LLMS_Abstract_User_Data $object User object.
+ * @param WP_REST_Request $request Request object.
+ * @return array
+ */
+ protected function prepare_object_for_response( $object, $request ) {
+
+ $prepared = array();
+ $map = array_flip( $this->map_schema_to_database() );
+ $fields = $this->get_fields_for_response( $request );
+
+ // Write Only.
+ unset( $map['user_pass'] );
+
+ foreach ( $map as $db_key => $schema_key ) {
+ if ( in_array( $schema_key, $fields, true ) ) {
+ $prepared[ $schema_key ] = $object->get( $db_key );
+ }
+ }
+
+ if ( in_array( 'roles', $fields, true ) ) {
+ $prepared['roles'] = $object->get_user()->roles;
+ }
+
+ if ( in_array( 'avatar_urls', $fields, true ) ) {
+ $prepared['avatar_urls'] = rest_get_avatar_urls( $object->get( 'user_email' ) );
+ }
+
+ return $prepared;
+
+ }
+
+ /**
+ * Validate a username is valid and allowed
+ *
+ * @since 1.0.0-beta.1
+ *
+ * @param string $value User-submitted username.
+ * @param WP_REST_Request $request Request object.
+ * @param string $param Parameter name.
+ * @return WP_Error|string Sanitized username if valid or error object.
+ */
+ public function sanitize_password( $value, $request, $param ) {
+
+ $password = (string) $value;
+
+ if ( false !== strpos( $password, '\\' ) ) {
+ return llms_rest_bad_request_error( __( 'Passwords cannot contain the "\\" character.', 'lifterlms' ) );
+ }
+
+ // @todo: Should validate against password strength too, maybe?
+
+ return $password;
+
+ }
+
+ /**
+ * Validate a username is valid and allowed
+ *
+ * @since 1.0.0-beta.1
+ *
+ * @param string $value User-submitted username.
+ * @param WP_REST_Request $request Request object.
+ * @param string $param Parameter name.
+ * @return WP_Error|string Sanitized username if valid or error object.
+ */
+ public function sanitize_username( $value, $request, $param ) {
+
+ $username = (string) $value;
+
+ if ( ! validate_username( $username ) ) {
+ return llms_rest_bad_request_error( __( 'Username contains invalid characters.', 'lifterlms' ) );
+ }
+
+ /**
+ * Filter defined in WP Core.
+ *
+ * @link https://developer.wordpress.org/reference/hooks/illegal_user_logins/
+ *
+ * @param array $illegal_logins Array of banned usernames.
+ */
+ $illegal_logins = (array) apply_filters( 'illegal_user_logins', array() );
+ if ( in_array( strtolower( $username ), array_map( 'strtolower', $illegal_logins ), true ) ) {
+ return llms_rest_bad_request_error( __( 'Username is not allowed.', 'lifterlms' ) );
+ }
+
+ return $username;
+
+ }
+
+ /**
+ * Updates additional information not handled by WP Core insert/update user functions
+ *
+ * @since 1.0.0-beta.1
+ * @since 1.0.0-beta.10 Fixed setting roles instead of appending them.
+ * @since 1.0.0-beta.11 Made sure to set user's meta with the correct db key.
+ *
+ * @param int $object_id WP User id.
+ * @param array $prepared Prepared item data.
+ * @param WP_REST_Request $request Request object.
+ * @return LLMS_Abstract_User_Data|WP_error
+ */
+ protected function update_additional_data( $object_id, $prepared, $request ) {
+
+ $object = $this->get_object( $object_id );
+
+ if ( is_wp_error( $object ) ) {
+ return $object;
+ }
+
+ $metas = array(
+ 'billing_address_1',
+ 'billing_address_2',
+ 'billing_city',
+ 'billing_state',
+ 'billing_postcode',
+ 'billing_country',
+ );
+
+ $map = $this->map_schema_to_database();
+
+ foreach ( $metas as $meta ) {
+ if ( ! empty( $map[ $meta ] ) && ! empty( $prepared[ $map[ $meta ] ] ) ) {
+ $object->set( $map[ $meta ], $prepared[ $map[ $meta ] ] );
+ }
+ }
+
+ if ( ! empty( $request['roles'] ) ) {
+ $user = $object->get_user();
+ $user->set_role( '' );
+ foreach ( $request['roles'] as $role ) {
+ $user->add_role( $role );
+ }
+ }
+
+ return $object;
+
+ }
+
+ /**
+ * Update item
+ *
+ * @since 1.0.0-beta.1
+ *
+ * @param WP_REST_Request $request Request object.
+ * @return WP_REST_Response|WP_Error Response object or `WP_Error` on failure.
+ */
+ public function update_item( $request ) {
+
+ $object = $this->get_object( $request['id'] );
+ if ( is_wp_error( $object ) ) {
+ return $object;
+ }
+
+ // Ensure we're not trying to update the email to an email that already exists.
+ $owner_id = email_exists( $request['email'] );
+
+ if ( $owner_id && $owner_id !== $request['id'] ) {
+ return llms_rest_bad_request_error( __( 'Invalid email address.', 'lifterlms' ) );
+ }
+
+ // Cannot change a username.
+ if ( ! empty( $request['username'] ) && $request['username'] !== $object->get( 'user_login' ) ) {
+ return llms_rest_bad_request_error( __( 'Username is not editable.', 'lifterlms' ) );
+ }
+
+ return parent::update_item( $request );
+
+ }
+
+ /**
+ * Update the object in the database with prepared data
+ *
+ * @since 1.0.0-beta.1
+ *
+ * @param array $prepared Prepared item data.
+ * @param WP_REST_Request $request Request object.
+ * @return obj Object Instance of object from `$this->get_object()`.
+ */
+ protected function update_object( $prepared, $request ) {
+
+ $prepared['ID'] = $prepared['id'];
+
+ $object_id = wp_update_user( $prepared );
+ if ( is_wp_error( $object_id ) ) {
+ return $object_id;
+ }
+
+ unset( $prepared['ID'] );
+
+ return $this->update_additional_data( $object_id, $prepared, $request );
+
+ }
+
+}
diff --git a/libraries/lifterlms-rest/includes/abstracts/class-llms-rest-webhook-data.php b/libraries/lifterlms-rest/includes/abstracts/class-llms-rest-webhook-data.php
new file mode 100644
index 0000000000..b064d9c743
--- /dev/null
+++ b/libraries/lifterlms-rest/includes/abstracts/class-llms-rest-webhook-data.php
@@ -0,0 +1,323 @@
+ format
+ *
+ * @var string[]
+ */
+ protected $columns = array(
+
+ 'status' => '%s',
+ 'name' => '%s',
+ 'delivery_url' => '%s',
+ 'secret' => '%s',
+ 'topic' => '%s',
+ 'user_id' => '%d',
+ 'created' => '%s',
+ 'updated' => '%s',
+ 'failure_count' => '%d',
+
+ );
+
+ /**
+ * Database Table Name
+ *
+ * @var string
+ */
+ protected $table = 'webhooks';
+
+ /**
+ * The record type
+ *
+ * Used for filters/actions.
+ *
+ * @var string
+ */
+ protected $type = 'webhook';
+
+ /**
+ * Constructor
+ *
+ * @since 1.0.0-beta.1
+ *
+ * @param int $id API Key ID.
+ * @param bool $hydrate If true, hydrates the object on instantiation if an ID is supplied.
+ */
+ public function __construct( $id = null, $hydrate = true ) {
+
+ $this->id = $id;
+ if ( $this->id && $hydrate ) {
+ $this->hydrate();
+ }
+
+ // Adds created and updated dates on instantiation.
+ parent::__construct();
+
+ }
+
+
+ /**
+ * Retrieve an admin nonce url for deleting an API key.
+ *
+ * @since 1.0.0-beta.1
+ *
+ * @return string
+ */
+ public function get_delete_link() {
+
+ return add_query_arg(
+ array(
+ 'section' => 'webhooks',
+ 'delete-webhook' => $this->get( 'id' ),
+ 'delete-webhook-nonce' => wp_create_nonce( 'delete' ),
+ ),
+ LLMS_REST_API()->keys()->get_admin_url()
+ );
+
+ }
+
+ /**
+ * Generate a delivery signature from a delivery payload.
+ *
+ * @since 1.0.0-beta.1
+ *
+ * @param string $payload JSON-encoded payload.
+ * @return string
+ */
+ public function get_delivery_signature( $payload ) {
+
+ /**
+ * Allow overriding of signature generation.
+ *
+ * @since 1.0.0-beta.1
+ *
+ * @param string $signature Custom signature. Return a string to replace the default signature.
+ * @param string $payload JSON-encoded body to be delivered.
+ * @param int $id Webhook id.
+ */
+ $signature = apply_filters( 'llms_rest_webhook_signature_pre', null, $payload, $this->get( 'id' ) );
+ if ( $signature && is_string( $signature ) ) {
+ return $signature;
+ }
+
+ /**
+ * Customize the hash algorithm used to generate the webhook delivery signature.
+ *
+ * @since 1.0.0-beta.1
+ *
+ * @param string $algo Hash algorithm. Defaults to 'sha256'. List of supported algorithms available at https://www.php.net/manual/en/function.hash-hmac-algos.php.
+ * @param string $payload JSON-encoded body to be delivered.
+ * @param int $id Webhook ID.
+ */
+ $hash_algo = apply_filters( 'llms_rest_webhook_hash_algorithm', 'sha256', $payload, $this->get( 'id' ) );
+ $ts = llms_current_time( 'timestamp' );
+ $message = $ts . '.' . $payload;
+ $hash = hash_hmac( $hash_algo, $message, $this->get( 'secret' ) );
+
+ return sprintf( 't=%1$d,v1=%2$s', $ts, $hash );
+
+ }
+
+ /**
+ * Retrieve the admin URL where the api key is managed.
+ *
+ * @since 1.0.0-beta.1
+ *
+ * @return string
+ */
+ public function get_edit_link() {
+ return add_query_arg(
+ array(
+ 'section' => 'webhooks',
+ 'edit-webhook' => $this->get( 'id' ),
+ ),
+ LLMS_REST_API()->keys()->get_admin_url()
+ );
+ }
+
+ /**
+ * Retrieve the topic event
+ *
+ * @since 1.0.0-beta.1
+ *
+ * @return string
+ */
+ public function get_event() {
+
+ $topic = explode( '.', $this->get( 'topic' ) );
+ return apply_filters( 'llms_rest_webhook_get_event', isset( $topic[1] ) ? $topic[1] : '', $this->get( 'id' ) );
+
+ }
+
+ /**
+ * Retrieve an array of hooks for the webhook topic.
+ *
+ * @since 1.0.0-beta.1
+ *
+ * @return string[]
+ */
+ public function get_hooks() {
+
+ if ( 'action' === $this->get_resource() ) {
+ $hooks = array( $this->get_event() => 1 );
+ } else {
+ $all_hooks = LLMS_REST_API()->webhooks()->get_hooks();
+ $topic = $this->get( 'topic' );
+ $hooks = isset( $all_hooks[ $topic ] ) ? $all_hooks[ $topic ] : array();
+ }
+
+ return apply_filters( 'llms_rest_webhook_get_hooks', $hooks, $this->get( 'id' ) );
+
+ }
+
+ /**
+ * Retrieve a payload for webhook delivery.
+ *
+ * @since 1.0.0-beta.1
+ * @since 1.0.0-beta.6 Retrieve proper payload for enrollment and progress resources.
+ *
+ * @param array $args Numeric array of arguments from the originating hook.
+ * @return array
+ */
+ protected function get_payload( $args ) {
+
+ // Switch current user to the user who created the webhook.
+ $current_user = get_current_user_id();
+ wp_set_current_user( $this->get( 'user_id' ) );
+
+ $resource = $this->get_resource();
+ $event = $this->get_event();
+
+ $payload = array();
+ if ( 'deleted' === $event ) {
+
+ if ( in_array( $this->get_resource(), array( 'enrollment', 'progress' ), true ) ) {
+ $payload['student_id'] = $args[0];
+ $payload['post_id'] = $args[1];
+ } else {
+ $payload['id'] = $args[0];
+ }
+ } elseif ( 'action' === $resource ) {
+
+ $payload['action'] = current( $this->get_hooks() );
+ $payload['args'] = $args;
+
+ } else {
+
+ if ( 'enrollment' === $resource ) {
+ $endpoint = sprintf( '/llms/v1/students/%1$d/enrollments/%2$d', $args[0], $args[1] );
+ } elseif ( 'progress' === $resource ) {
+ $endpoint = sprintf( '/llms/v1/students/%1$d/progress/%2$d', $args[0], $args[1] );
+ } else {
+ $endpoint = sprintf( '/llms/v1/%1$ss/%2$d', $resource, $args[0] );
+ }
+
+ $payload = llms_rest_get_api_endpoint_data( $endpoint );
+
+ }
+
+ // Restore the current user.
+ wp_set_current_user( $current_user );
+
+ /**
+ * Filter the webhook payload prior to delivery
+ *
+ * @since 1.0.0-beta.1
+ *
+ * @param array $payload Webhook payload.
+ * @param string $resource Webhook resource.
+ * @param string $event Webhook event.
+ * @param array $args Numeric array of arguments from the originating hook.
+ * @param LLMS_REST_Webhook $this Webhook object.
+ */
+ return apply_filters( 'llms_rest_webhook_get_payload', $payload, $resource, $event, $args, $this );
+
+ }
+
+ /**
+ * Retrieve the topic resource.
+ *
+ * @since 1.0.0-beta.1
+ *
+ * @return string
+ */
+ public function get_resource() {
+
+ $topic = explode( '.', $this->get( 'topic' ) );
+ return apply_filters( 'llms_rest_webhook_get_resource', $topic[0], $this->get( 'id' ) );
+
+ }
+
+ /**
+ * Retrieve a user agent string to use for delivering webhooks.
+ *
+ * @since 1.0.0-beta.1
+ *
+ * @return string
+ */
+ protected function get_user_agent() {
+ global $wp_version;
+ return sprintf( 'LifterLMS/%1$s Hookshot (WordPress/%2$s)', LLMS()->version, $wp_version );
+ }
+
+ /**
+ * Increment delivery failures and after max allowed failures are reached, set status to disabled.
+ *
+ * @since 1.0.0-beta.1
+ *
+ * @return LLMS_REST_Webhook
+ */
+ protected function set_delivery_failure() {
+
+ $failures = absint( $this->get( 'failure_count' ) );
+
+ $this->set( 'failure_count', ++$failures );
+
+ /**
+ * Filter the number of times a webhook is allowed to fail before it is automatically disabled.
+ *
+ * @since 1.0.0-beta.1
+ *
+ * @param int $num Number of allowed failures. Default: 5.
+ */
+ $max_allowed = apply_filters( 'llms_rest_webhook_max_delivery_failures', 5 );
+
+ if ( $failures > $max_allowed ) {
+
+ $this->set( 'status', 'disabled' );
+
+ /**
+ * Fires immediately after a webhook has been disabled due to exceeding its maximum allowed failures.
+ *
+ * @since 1.0.0-beta.1
+ *
+ * @param int $webhook_id ID of the webhook.
+ */
+ do_action( 'llms_rest_webhook_disabled_by_delivery_failures', $this->get( 'id' ) );
+
+ }
+
+ return $this;
+
+ }
+
+}
diff --git a/libraries/lifterlms-rest/includes/abstracts/index.php b/libraries/lifterlms-rest/includes/abstracts/index.php
new file mode 100644
index 0000000000..9c65c1efa6
--- /dev/null
+++ b/libraries/lifterlms-rest/includes/abstracts/index.php
@@ -0,0 +1 @@
+keys()->delete( llms_filter_input( INPUT_GET, 'revoke-key', FILTER_VALIDATE_INT ) );
+ if ( $delete ) {
+ LLMS_Admin_Notices::flash_notice( esc_html__( 'The API Key has been successfully deleted.', 'lifterlms' ), 'success' );
+ return llms_redirect_and_exit( admin_url( 'admin.php?page=llms-settings&tab=rest-api§ion=keys' ) );
+ }
+ } elseif ( llms_verify_nonce( 'llms_rest_webhook_nonce', 'create-update-webhook', 'POST' ) ) {
+ return $this->handle_webhook_upsert();
+ } elseif ( llms_verify_nonce( 'delete-webhook-nonce', 'delete', 'GET' ) ) {
+ $delete = LLMS_REST_API()->webhooks()->delete( llms_filter_input( INPUT_GET, 'delete-webhook', FILTER_VALIDATE_INT ) );
+ if ( $delete ) {
+ LLMS_Admin_Notices::flash_notice( esc_html__( 'The webhook has been successfully deleted.', 'lifterlms' ), 'success' );
+ return llms_redirect_and_exit( admin_url( 'admin.php?page=llms-settings&tab=rest-api§ion=webhooks' ) );
+ }
+ } elseif ( llms_verify_nonce( 'dl-key-nonce', 'dl-key', 'GET' ) ) {
+ return $this->handle_key_download();
+ }
+
+ return false;
+
+ }
+
+ /**
+ * Generate and download a api key credentials file.
+ *
+ * @since 1.0.0-beta.3
+ *
+ * @return false|void
+ */
+ protected function handle_key_download() {
+
+ $info = $this->prepare_key_download();
+ if ( ! $info ) {
+ return false;
+ }
+
+ header( 'Content-type: text/plain' );
+ header( 'Content-Disposition: attachment; filename="' . $info['fn'] );
+ header( 'Pragma: no-cache' );
+ header( 'Expires: 0' );
+
+ // Translators: %s = Consumer Key.
+ printf( __( 'Consumer Key: %s', 'lifterlms' ), $info['ck'] );
+ echo "\r\n";
+ // Translators: %s = Consumer Secret.
+ printf( __( 'Consumer Secret: %s', 'lifterlms' ), $info['cs'] );
+ die();
+
+ }
+
+ /**
+ * Handle creating/updating a webhook via admin interfaces
+ *
+ * @since 1.0.0-beta.1
+ *
+ * @return true|void|WP_Error true on update success, void (redirect) on creation success, WP_Error on failure.
+ */
+ protected function handle_webhook_upsert() {
+
+ $data = array(
+ 'name' => llms_filter_input( INPUT_POST, 'llms_rest_webhook_name', FILTER_SANITIZE_STRING ),
+ 'status' => llms_filter_input( INPUT_POST, 'llms_rest_webhook_status', FILTER_SANITIZE_STRING ),
+ 'topic' => llms_filter_input( INPUT_POST, 'llms_rest_webhook_topic', FILTER_SANITIZE_STRING ),
+ 'delivery_url' => llms_filter_input( INPUT_POST, 'llms_rest_webhook_delivery_url', FILTER_SANITIZE_URL ),
+ 'secret' => llms_filter_input( INPUT_POST, 'llms_rest_webhook_secret', FILTER_SANITIZE_STRING ),
+ );
+
+ if ( 'action' === $data['topic'] ) {
+ $data['topic'] .= '.' . llms_filter_input( INPUT_POST, 'llms_rest_webhook_action', FILTER_SANITIZE_STRING );
+ }
+
+ $hook_id = llms_filter_input( INPUT_POST, 'llms_rest_webhook_id', FILTER_SANITIZE_NUMBER_INT );
+
+ if ( ! $hook_id ) {
+
+ $hook = LLMS_REST_API()->webhooks()->create( $data );
+ if ( ! is_wp_error( $hook ) ) {
+ return llms_redirect_and_exit( $hook->get_edit_link(), array( 'status' => 301 ) );
+ }
+ } else {
+
+ $hook = LLMS_REST_API()->webhooks()->get( $hook_id );
+ if ( ! $hook ) {
+
+ // Translators: %s = Webhook ID.
+ $hook = new WP_Error( 'llms_rest_api_webhook_not_found', sprintf( __( '"%s" is not a valid Webhook.', 'lifterlms' ), $hook_id ) );
+
+ } else {
+
+ $data['id'] = $hook_id;
+ $hook = LLMS_REST_API()->webhooks()->update( $data );
+
+ }
+ }
+
+ if ( is_wp_error( $hook ) ) {
+ // Translators: %1$s = error message; %2$s = error code.
+ LLMS_Admin_Notices::flash_notice( sprintf( __( 'Error: %1$s [Code: %2$s]', 'lifterlms' ), $hook->get_error_message(), $hook->get_error_code() ), 'error' );
+ return $hook;
+ }
+
+ return true;
+
+ }
+
+ /**
+ * Validates `GET` information from the credential download URL and prepares information for generating the file.
+ *
+ * @since 1.0.0-beta.3
+ *
+ * @return false|array
+ */
+ protected function prepare_key_download() {
+
+ $key_id = llms_filter_input( INPUT_GET, 'id', FILTER_SANITIZE_NUMBER_INT );
+ $consumer_key = llms_filter_input( INPUT_GET, 'ck', FILTER_SANITIZE_STRING );
+
+ // return if missing required fields.
+ if ( ! $key_id || ! $consumer_key ) {
+ return false;
+ }
+
+ // return if key doesn't exist.
+ $key = LLMS_REST_API()->keys()->get( $key_id );
+ if ( ! $key ) {
+ return false;
+ }
+
+ // validate the decoded consumer key looks like the stored truncated key.
+ $consumer_key = base64_decode( $consumer_key ); //phpcs:disable WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_decode -- This is benign usage.
+ if ( substr( $consumer_key, -7 ) !== $key->get( 'truncated_key' ) ) {
+ return false;
+ }
+
+ return array(
+ 'fn' => sanitize_file_name( $key->get( 'description' ) ) . '.txt',
+ 'ck' => $consumer_key,
+ 'cs' => $key->get( 'consumer_secret' ),
+ );
+
+ }
+
+}
+
+return new LLMS_REST_Admin_Form_Controller();
diff --git a/libraries/lifterlms-rest/includes/admin/class-llms-rest-admin-settings-api-keys.php b/libraries/lifterlms-rest/includes/admin/class-llms-rest-admin-settings-api-keys.php
new file mode 100644
index 0000000000..e367dcf027
--- /dev/null
+++ b/libraries/lifterlms-rest/includes/admin/class-llms-rest-admin-settings-api-keys.php
@@ -0,0 +1,311 @@
+ 'top',
+ 'id' => 'rest_keys_options_start',
+ 'type' => 'sectionstart',
+ );
+
+ $settings[] = array(
+ 'title' => $key_id || $add_key ? __( 'API Key Details', 'lifterlms' ) : __( 'API Keys', 'lifterlms' ),
+ 'type' => 'title-with-html',
+ 'id' => 'rest_keys_options_title',
+ 'html' => $key_id || $add_key ? '' : '' . __( 'Add API Key', 'lifterlms' ) . ' ',
+ );
+
+ if ( $add_key || $key_id ) {
+
+ $key = $add_key ? false : new LLMS_REST_API_Key( $key_id );
+ if ( self::$generated_key ) {
+ $key = self::$generated_key;
+ }
+ if ( $add_key || $key->exists() ) {
+
+ $user_id = $key ? $key->get( 'user_id' ) : get_current_user_id();
+
+ $settings[] = array(
+ 'title' => __( 'Description', 'lifterlms' ),
+ 'desc' => ' ' . __( 'A friendly, human-readable, name used to identify the key.', 'lifterlms' ),
+ 'id' => 'llms_rest_key_description',
+ 'type' => 'text',
+ 'value' => $key ? $key->get( 'description' ) : '',
+ 'custom_attributes' => array(
+ 'required' => 'required',
+ ),
+ );
+
+ $settings[] = array(
+ 'title' => __( 'User', 'lifterlms' ),
+ 'class' => 'llms-select2-student',
+ 'desc' => sprintf(
+ // Translators: %1$s = opening anchor tag to capabilities doc; %2$s closing anchor tag.
+ __( 'The owner is used to determine what user %1$scapabilities%2$s are available to the API key.', 'lifterlms' ),
+ '',
+ ' '
+ ),
+ 'custom_attributes' => array(
+ 'data-placeholder' => __( 'Select a user', 'lifterlms' ),
+ ),
+ 'id' => 'llms_rest_key_user_id',
+ 'options' => llms_make_select2_student_array( array( $user_id ) ),
+ 'type' => 'select',
+ );
+
+ $settings[] = array(
+ 'title' => __( 'Permissions', 'lifterlms' ),
+ 'desc' => ' ' . sprintf(
+ // Translators: %1$s = opening anchor tag to doc; %2$s closing anchor tag.
+ __( 'Determines what kind of requests can be made with the API key. %1$sRead more%2$s.', 'lifterlms' ),
+ '',
+ ' '
+ ),
+ 'id' => 'llms_rest_key_permissions',
+ 'type' => 'select',
+ 'options' => LLMS_REST_API()->keys()->get_permissions(),
+ 'value' => $key ? $key->get( 'permissions' ) : '',
+ );
+
+ if ( $key && ! self::$generated_key ) {
+
+ $settings[] = array(
+ 'title' => __( 'Consumer key ending in', 'lifterlms' ),
+ 'custom_attributes' => array(
+ 'readonly' => 'readonly',
+ ),
+ 'class' => 'code',
+ 'id' => 'llms_rest_key__read_only_key',
+ 'type' => 'text',
+ 'value' => '…' . $key->get( 'truncated_key' ),
+ );
+
+ $settings[] = array(
+ 'title' => __( 'Last accessed at', 'lifterlms' ),
+ 'custom_attributes' => array(
+ 'readonly' => 'readonly',
+ ),
+ 'id' => 'llms_rest_key__read_only_date',
+ 'type' => 'text',
+ 'value' => $key->get_last_access_date(),
+ );
+
+ } elseif ( self::$generated_key ) {
+
+ $settings[] = array(
+ 'type' => 'custom-html',
+ 'id' => 'llms_rest_key_onetime_notice',
+ 'value' => '' . __( 'Make sure to copy or download the consumer key and consumer secret. After leaving this page they will not be displayed again.', 'lifterlms' ) . '
',
+ );
+
+ $settings[] = array(
+ 'title' => __( 'Consumer key', 'lifterlms' ),
+ 'custom_attributes' => array(
+ 'readonly' => 'readonly',
+ ),
+ 'css' => 'width:400px',
+ 'class' => 'code widefat',
+ 'id' => 'llms_rest_key__read_only_key',
+ 'type' => 'text',
+ 'value' => $key->get( 'consumer_key_one_time' ),
+ );
+
+ $settings[] = array(
+ 'title' => __( 'Consumer secret', 'lifterlms' ),
+ 'custom_attributes' => array(
+ 'readonly' => 'readonly',
+ ),
+ 'css' => 'width:400px',
+ 'class' => 'code widefat',
+ 'id' => 'llms_rest_key__read_only_secret',
+ 'type' => 'text',
+ 'value' => $key->get( 'consumer_secret' ),
+ );
+
+ }
+
+ $buttons = ' ';
+ if ( self::$generated_key ) {
+ $download_url = wp_nonce_url(
+ admin_url(
+ add_query_arg(
+ array(
+ 'id' => $key->get( 'id' ),
+ 'ck' => base64_encode( $key->get( 'consumer_key_one_time' ) ), //phpcs:disable WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode -- This is benign usage.
+ ),
+ 'admin.php'
+ )
+ ),
+ 'dl-key',
+ 'dl-key-nonce'
+ );
+ $buttons .= ' ' . __( 'Download Keys', 'lifterlms' ) . ' ';
+ } else {
+ $buttons .= '' . __( 'Save', 'lifterlms' ) . ' ';
+ }
+ if ( $key ) {
+ $buttons .= $buttons ? ' ' : ' ';
+ $buttons .= '' . __( 'Revoke', 'lifterlms' ) . ' ';
+ }
+ $buttons .= wp_nonce_field( 'lifterlms-settings', '_wpnonce', true, false );
+
+ $settings[] = array(
+ 'type' => 'custom-html',
+ 'id' => 'llms_rest_key_buttons',
+ 'value' => $buttons,
+ );
+
+ } else {
+
+ $settings[] = array(
+ 'id' => 'rest_keys_options_invalid_error',
+ 'type' => 'custom-html',
+ 'value' => __( 'Invalid api key.', 'lifterlms' ),
+ );
+
+ }
+ } else {
+
+ $settings[] = array(
+ 'id' => 'llms_api_keys_table',
+ 'table' => new LLMS_REST_Table_API_Keys(),
+ 'type' => 'table',
+ );
+
+ }
+
+ $settings[] = array(
+ 'id' => 'rest_keys_options_end',
+ 'type' => 'sectionend',
+ );
+
+ return $settings;
+
+ }
+
+ /**
+ * Form handler to save Create / Update an API key.
+ *
+ * @since 1.0.0-beta.1
+ * @since 1.0.0-beta.3 Remove key copy message in favor of message directly above the key fields.
+ *
+ * @return null|LLMS_REST_API_Key|WP_Error
+ */
+ public static function save() {
+
+ $ret = null;
+
+ $key_id = llms_filter_input( INPUT_GET, 'edit-key', FILTER_SANITIZE_NUMBER_INT );
+ if ( $key_id ) {
+ $ret = self::save_update( $key_id );
+ } elseif ( llms_filter_input( INPUT_GET, 'add-key', FILTER_SANITIZE_NUMBER_INT ) ) {
+ $ret = self::save_create();
+ }
+
+ if ( is_wp_error( $ret ) ) {
+ // Translators: %1$s = Error message; %2$s = Error code.
+ LLMS_Admin_Settings::set_error( sprintf( __( 'Error: %1$s [Code: %2$s]', 'lifterlms' ), $ret->get_error_message(), $ret->get_error_code() ) );
+ }
+
+ return $ret;
+
+ }
+
+ /**
+ * Form handler to create a new API key.
+ *
+ * @since 1.0.0-beta.1
+ *
+ * @return LLMS_REST_API_Key|WP_Error
+ */
+ protected static function save_create() {
+
+ $create = LLMS_REST_API()->keys()->create(
+ array(
+ 'description' => llms_filter_input( INPUT_POST, 'llms_rest_key_description', FILTER_SANITIZE_STRING ),
+ 'user_id' => llms_filter_input( INPUT_POST, 'llms_rest_key_user_id', FILTER_SANITIZE_NUMBER_INT ),
+ 'permissions' => llms_filter_input( INPUT_POST, 'llms_rest_key_permissions', FILTER_SANITIZE_STRING ),
+ )
+ );
+
+ if ( ! is_wp_error( $create ) ) {
+ self::$generated_key = $create;
+ }
+
+ return $create;
+
+ }
+
+ /**
+ * Form handler to save an API key.
+ *
+ * @since 1.0.0-beta.1
+ *
+ * @param int $key_id API Key ID.
+ * @return LLMS_REST_API_Key|WP_Error
+ */
+ protected static function save_update( $key_id ) {
+
+ $key = LLMS_REST_API()->keys()->get( $key_id );
+ if ( ! $key ) {
+ // Translators: %s = Invalid API Key ID.
+ return new WP_Error( 'llms_rest_api_key_not_found', sprintf( __( '"%s" is not a valid API Key.', 'lifterlms' ), $key_id ) );
+ }
+
+ $update = LLMS_REST_API()->keys()->update(
+ array(
+ 'id' => $key_id,
+ 'description' => llms_filter_input( INPUT_POST, 'llms_rest_key_description', FILTER_SANITIZE_STRING ),
+ 'user_id' => llms_filter_input( INPUT_POST, 'llms_rest_key_user_id', FILTER_SANITIZE_NUMBER_INT ),
+ 'permissions' => llms_filter_input( INPUT_POST, 'llms_rest_key_permissions', FILTER_SANITIZE_STRING ),
+ )
+ );
+
+ return $update;
+
+ }
+
+}
+
diff --git a/libraries/lifterlms-rest/includes/admin/class-llms-rest-admin-settings-page.php b/libraries/lifterlms-rest/includes/admin/class-llms-rest-admin-settings-page.php
new file mode 100644
index 0000000000..9f715aeadd
--- /dev/null
+++ b/libraries/lifterlms-rest/includes/admin/class-llms-rest-admin-settings-page.php
@@ -0,0 +1,156 @@
+id = 'rest-api';
+ $this->label = __( 'REST API', 'lifterlms' );
+
+ // Output Stuff.
+ add_filter( 'lifterlms_settings_tabs_array', array( $this, 'add_settings_page' ), 20 );
+ add_action( 'lifterlms_sections_' . $this->id, array( $this, 'output_sections_nav' ) );
+ add_action( 'lifterlms_settings_' . $this->id, array( $this, 'output' ) );
+
+ // Maybe Save API Keys.
+ add_action( 'lifterlms_settings_save_' . $this->id, array( 'LLMS_Rest_Admin_Settings_API_Keys', 'save' ) );
+
+ // Disable the default page's save button.
+ add_filter( 'llms_settings_rest-api_has_save_button', '__return_false' );
+
+ add_filter( 'llms_table_get_table_classes', array( $this, 'get_table_classes' ), 10, 2 );
+ add_action( 'lifterlms_admin_field_title-with-html', array( $this, 'output_title_field' ), 10 );
+
+ }
+
+ /**
+ * Retrieve the id of the current tab/section
+ *
+ * Overrides parent function to set "keys" as the default section instead of the nonexistant "main".
+ *
+ * @since 1.0.0-beta.1
+ *
+ * @return string
+ */
+ protected function get_current_section() {
+
+ $current = parent::get_current_section();
+ if ( 'main' === $current ) {
+ $all = array_keys( $this->get_sections() );
+ $current = $all ? $all[0] : 'main';
+ }
+ return $current;
+
+ }
+
+ /**
+ * Get the page sections
+ *
+ * @since 1.0.0-beta.1
+ *
+ * @return array
+ */
+ public function get_sections() {
+
+ $sections = array();
+
+ if ( current_user_can( 'manage_lifterlms_api_keys' ) ) {
+ $sections['keys'] = __( 'API Keys', 'lifterlms' );
+ }
+
+ if ( current_user_can( 'manage_lifterlms_webhooks' ) ) {
+ $sections['webhooks'] = __( 'Webhooks', 'lifterlms' );
+ }
+
+ /**
+ * Modify the available tabs on the REST API settings screen.
+ *
+ * @since 1.0.0-beta.1
+ *
+ * @param array $sections Array of settings page tabs.
+ */
+ return apply_filters( 'llms_rest_api_settings_sections', $sections );
+
+ }
+
+ /**
+ * Get settings array
+ *
+ * @since 1.0.0-beta.1
+ *
+ * @return array
+ */
+ public function get_settings() {
+
+ $curr_section = $this->get_current_section();
+
+ $settings = array();
+ if ( current_user_can( 'manage_lifterlms_api_keys' ) && 'keys' === $curr_section ) {
+ $settings = LLMS_Rest_Admin_Settings_API_Keys::get_fields();
+ } elseif ( current_user_can( 'manage_lifterlms_webhooks' ) && 'webhooks' === $curr_section ) {
+ $settings = LLMS_Rest_Admin_Settings_Webhooks::get_fields();
+ }
+
+ return apply_filters( 'llms_rest_api_settings_' . $curr_section, $settings );
+
+ }
+
+ /**
+ * Add CSS classes to the API Keys Table.
+ *
+ * @since 1.0.0-beta.1
+ *
+ * @param string[] $classes Array of css class names.
+ * @param string $id Table ID.
+ * @return string[]
+ */
+ public function get_table_classes( $classes, $id ) {
+
+ if ( in_array( $id, array( 'rest-api-keys', 'rest-webhooks' ), true ) ) {
+ $classes[] = 'text-left';
+ }
+ return $classes;
+
+ }
+
+ /**
+ * Outputs a custom "title" field with HTML content as the settings section title.
+ *
+ * @since 1.0.0-beta.1
+ *
+ * @param array $field Settings field arguments.
+ * @return void
+ */
+ public function output_title_field( $field ) {
+
+ echo '' . esc_html( $field['title'] ) . ' ' . $field['html'] . '
';
+ echo '