From d6af37f9e5d60a7f531dc28dfc4619bc8eac0a8b Mon Sep 17 00:00:00 2001 From: cherrg Date: Sat, 14 Jul 2018 05:20:12 +0200 Subject: [PATCH 01/42] rebuild + add framework --- .gitignore | 5 + .gitignore~ | 18 + .httaccess | 4 - config/config.routing.php | 27 + img/stura-try.pdf | Bin 5059 -> 0 bytes img/stura.eps | Bin 4009305 -> 0 bytes lib/class.AuthBasicHandler.php | 260 +++++ lib/class.ErrorHandler.php | 256 ++++ lib/class.Helper.php | 208 ++++ lib/class.JsonController.php | 77 ++ lib/class.Router.php | 217 ++++ lib/class.Singleton.php | 107 ++ lib/class.TexBuilder.php | 351 ++++++ lib/class.Validator.php | 1036 +++++++++++++++++ lib/inc.all.php | 17 + lib/interface.AuthHandler.php | 85 ++ auszahlung.tex => old/auszahlung.tex | 0 bewilligung.tex => old/bewilligung.tex | 0 briefkopf.tex => old/briefkopf.tex | 0 .../hiddenAPIKEY.php.sample | 0 index.php => old/index.php | 2 +- old/parameter.tex | 0 .../zahlungsanweisung.tex | 4 +- .../zahlungsanweisung_user.tex | 0 parameter.tex | 14 - public/.htaccess | 4 + public/css/style.css | 42 + public/index.php | 88 ++ template/html/error.phtml | 34 + template/html/footer.phtml | 18 + template/html/header.phtml | 28 + template/tex/belegpdf.phpTex | 217 ++++ 32 files changed, 3098 insertions(+), 21 deletions(-) create mode 100644 .gitignore~ delete mode 100644 .httaccess create mode 100644 config/config.routing.php delete mode 100644 img/stura-try.pdf delete mode 100644 img/stura.eps create mode 100644 lib/class.AuthBasicHandler.php create mode 100644 lib/class.ErrorHandler.php create mode 100644 lib/class.Helper.php create mode 100644 lib/class.JsonController.php create mode 100644 lib/class.Router.php create mode 100644 lib/class.Singleton.php create mode 100644 lib/class.TexBuilder.php create mode 100644 lib/class.Validator.php create mode 100644 lib/inc.all.php create mode 100644 lib/interface.AuthHandler.php rename auszahlung.tex => old/auszahlung.tex (100%) rename bewilligung.tex => old/bewilligung.tex (100%) rename briefkopf.tex => old/briefkopf.tex (100%) rename hiddenAPIKEY.php.sample => old/hiddenAPIKEY.php.sample (100%) rename index.php => old/index.php (99%) create mode 100644 old/parameter.tex rename zahlungsanweisung.tex => old/zahlungsanweisung.tex (98%) rename zahlungsanweisung_user.tex => old/zahlungsanweisung_user.tex (100%) delete mode 100644 parameter.tex create mode 100644 public/.htaccess create mode 100644 public/css/style.css create mode 100644 public/index.php create mode 100644 template/html/error.phtml create mode 100644 template/html/footer.phtml create mode 100644 template/html/header.phtml create mode 100644 template/tex/belegpdf.phpTex diff --git a/.gitignore b/.gitignore index 116738a..443f149 100644 --- a/.gitignore +++ b/.gitignore @@ -6,8 +6,13 @@ *.out *.pdf *.synctex.gz +.project +.buildpath +config/config.php +*.settings/* !/img/stura-try.pdf /hiddenAPIKey.php +old/hiddenAPIKey.php /parameter.tex /.fuse* *.idea diff --git a/.gitignore~ b/.gitignore~ new file mode 100644 index 0000000..0b63a7c --- /dev/null +++ b/.gitignore~ @@ -0,0 +1,18 @@ +# https://git-scm.com/docs/gitignore +# https://help.github.com/articles/ignoring-files +# Example .gitignore files: https://github.com/github/gitignore +*.aux +*.log +*.out +*.pdf +*.synctex.gz +.project +.buildpath +*.settings/* +!/img/stura-try.pdf +/hiddenAPIKey.php +old/hiddenAPIKey.php +/parameter.tex +/.fuse* +*.idea + diff --git a/.httaccess b/.httaccess deleted file mode 100644 index 3b79e6f..0000000 --- a/.httaccess +++ /dev/null @@ -1,4 +0,0 @@ - -Order Allow,Deny -Deny from all - diff --git a/config/config.routing.php b/config/config.routing.php new file mode 100644 index 0000000..47143b0 --- /dev/null +++ b/config/config.routing.php @@ -0,0 +1,27 @@ + '', + 'type' => 'path', + 'controller' => 'error', + 'action' => '404', + 'not_found' => '404', + 'method' => 'POST', + 'children' => [ + [ + 'path' => 'pdfbuilder', + 'controller' => 'pdfbuilder', + 'action' => '', + 'auth' => 'Basic', + 'groups' => 'pdfbuilder', + 'type' => 'path', + ], + [ + 'allowall' => true, + 'path' => 'old', + 'controller' => 'old', + 'action' => '', + 'type' => 'path', + ], + ] +]; + diff --git a/img/stura-try.pdf b/img/stura-try.pdf deleted file mode 100644 index c75bd8d7100b6c57efe8ae98ac3f7b4d592bb6a7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5059 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 T&RAv~p`)t}Qc*FsJO=tNj?P3` diff --git a/img/stura.eps b/img/stura.eps deleted file mode 100644 index 1e0eee07449e345b1ca74f0486a393b9988d5d82..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4009305 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
  • RJcnP`hU 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%0Aw3|QxcLm$(u~$(qO!)_% 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 z z58qY~GL!3bK2USrMtn@sDIuk|$G%Tuy6P1Fq!vpcrzQM_wg~l!c(y1ry{xHsxwh9l z>koR(U@AA5C&k&y#?G$&@NDgtUkL#rAOvP75bvOFVvr@hvRltSyFAS=@t7XHF_PyN z(=xe^CV%WD+kOEUf1(hrT(;^5gx)LNx-%~sI4W4bNBNN!*efN`-E-tbyc3) z!c4AD#Ef0>FcRinKQ3pMgBflwlkCan=c|q6xakjmRSH>kaD?Nt*oNz16JOUF!P}l^ zX&+XrpW%hTb&Kh`K_f40@0@?{R880Vo%X)+P6!BrkO|-cxAWv#tV>}!D{UUV!;2=S z&qThD%+s5){5JYI3wucA1fA+n8#{BAJyypN7pHva$vfxz8|ckoX68@2nM^?8O3DBRYH`D5k z8Z*CbUw+HELB5u~d6i>7+*0fL(vg-40U;130eniYoHnsYXXowo?mCX@Z-Vitb2hzW zcV1Y4Ycyw*%(!1g55=RwlH235KZ@!&a2zk!pW)+J)FVfcSFE>%`R_LNa4mbg^`gEn6(tw^Hin&wB&?`@JJHKOCQuJbOun* z8GtR|9ddar&d%Z2NP=L9RkT2$Y8a zUI*GIPkTQX^W=5CCbsUd{zi00Xc9-qAS16OFJ$a#fs10aWLgEx$r7OJ34%1KYL4}!%?Mw zWssNlXN%E4&d#d%U%EYikmek`z4$&4tI_s6R4Fe_S$GJKBmvj@piV7Ke6oAz9TE3i zK7r7+JZ#lJqMzd!=mTX!|JK4(|PdfYqGo&0zx2k0(d_f=@_NU zdGp!>yQsg?4ol9i&ri~FG&wFUPue%{@~{Z!(;HX%KPWFo?{CkCwCz%HyR5^G?<2YU zZ_j6o;&3_}HZdnBcV6Uw9ousge`MR<@-7d}sB`Je zquw2()K?Goz)nkU+&k<*>r#fr9o_xy(sAvT&olwmJK?<4b~xyZ9`!{xN|o<%=SQ~T z1z$8`Wjjlk7u0&vRC}!?!Tm3)7bSP*i!b0#NZKa^gg|Hn@LBEgOf0Mf=O1-PGAsHU z*a?K#IeyA#B+q{f&2sGb2RlT)r}!26*?UT2B+srL<=r0QusoC7K5x4v(y0MD4?f(# zlvI9s@TlBmS8hd>rh0k7vfXbZFHed@Rm&+4{mm%?5A@R*eKh$oLt6yRBjaY9+`7fhY z96nbEOO9}b{A~O2-U%ce(yMiC^)bJ6{jnTW{}*pQ%@_ zLO=+VhXAfL>le(`e#>F5g!=_+&_Y^XUurj7VxFwLj9zTcD66{caRFwT*pO-#Lxk5k) z2!Y`Q)QyGa;fl=pO;^{G!}<96SG>~s`iI`#w3jbvt8@J%S!Ao#t5MHAzmk^AbT;DT zw?#@|{<;+HD8l}{SB|K0ZXZtm&=X2EIEPm9JeYEV8mlB15 z5O75Rck8>AiD{Sk>Qx?MlD1Gxx;rXmBhMy2G^x$E(|#EBa7`|0+ivE%myjHt^I3wE@?Y4wN%5sP%9W|HnvadiycIjIXYKubyyx z|9r%4|I<>CcS1l2lz{-Ac05m>?jNz|H8}`J?`t?Z9C_m?)Ex6%&N*v&Ms>V59J+b+ z1Sjir$sFl_N?!VjhKc?(uy0qPGrxfs-mr?w^wT)lc z;_0#+^`PT%$+IqdFLet6Ay7sF@i{vt7UuB0o$j5>Y5l(mNB%fDo_Xk$Cyg{s{j@Ib zq;Tlw)f0RyYNvDMhF^*8vlbnn7w}BI1e1W2x%X2bHYOifN zKXs?sDRTNH?$J?dhxY6E+*Mcebd3&3{f0z*6&}JPNgywMV73os@$t-le7s!*Z&OEJ z@{G&JdsW?We6!D0%e1BjM)mpjpXa(FBYS-v)_?VRRx)#Gi~YLm_=i!vCh>IpIkqaA zHOOM_L)sR7ZqxGaqQmURg}l`o{Jq+OMY)fXk5A;ZxX8!o#I)^Ew>h@mCDZcwI&J&s z{eHT3c_##fK==f(ro`(UdZQ#0%S8u_=ORs~i_r&poj*wHsg9Yh2W9*=F4wxatdE2G zSWQ zNN4(1sNK+FVt=+};;Ov6rSSi$^hf;*tt}H-+R~*3ck-tv!Sm6PoZx_uYN4&R?~icY$tP}ut@;(s7jUyxKjLq@Bkg7RPQ$~&EIRCt&Cx^MoS@j}fPbn~Q7giRimwB=nsJc1 z&ZMscyr%K*#rIii6aqq^%mgHMtvz(&zwN@%2K*$^r)2+Jy{@ZQhe0;KUTj{)OlGch zWw73Xo%ZsRKwc$F-Z$lA2jx4}`@Cc{tXy>jzEAr!x0vnKJQc58eaa%P7{)Dxzo|Ry z>FjJW2!W#XK?n!|e+W1{U#ELlV^)712m2$taV^D7@!gCIUo^1qxc16tcdKYOw7JO3 z#KKv$r{%wPu;NJ(FkjmfHL!pF+JHT979OKfe3j1A>)xk)<&~@b)^S??vT#)c?$S?j zx$>J(zN?3F@zf!+_(--`%Y7OrjGdtz)|8I=_NZQ}o&sYefJn(-Zt1K&y$8^R!)UoUWcdaqwj^3$VZckS7{DnIH)m5gJt z#K#`p$}{FLqdqmThdr5LHTJ_h@y+Nl(DOR#w9Z-XhrRp882L2GpQoNy>`8=6Nw|MZ z|GQ6uxqrTv3kzw75D)_46HsSTEVSqS<=P#kc)wOD*4plDr@Z5r)0E#(8^gD!`Fe=6 zC5IG0DGd0cj@ofPvOIoGdpR!-?cX$ZB;RdeVLo|M9&kNQQkUQ%$^&kroP2k;p8=f{ z&ZEZ#OX{KdB;doZ)q{?0wS#p&aFR9(0U;0u0sPqHj^rA9|7cqXxd>W|w#D>^$+dy~ z!myeAdJ$dT&rOIuCiFI#+%oQ_^>5uL4Uz9092(e)W3u{b?5y^~G58?9TKOfO?(uKQ z`GGU@UsPYq&j<1;nW6>sMC{N`+t`HzjiTqNN_}am3lHIuB#@^*s3+sQg2YCbIn>p_ zb4#B5wVd-QA1^YKXe(r4$zv;o!qxY{mNf|8~0;W`_&_J>*%by9sjO{^&I%7 zr7iD-fDj0kK)jnw?(^V#GfzycjdOjpe&qBJq#Zrik8N_`P3u%&8v@rn`+#TT5eNr) z>Cqz_d#&LOd2%ndY%Seb&TRO4+8)DEU-dqagYSi}U4Mcm+EF0qF-^94JnC3~f|(UD znYDaF2nYe!1hB?h%&<422%@}eT+78q{dk(0ENbdd8~qBo3|Y%NLT790lyKx8{hr8q zgq-Kgr-XD5;@Fo(+!2lyPVR|%!f}4E0a%imj-HZ!gTCa0d-hKAHe2(_OLVsE~%Xir(+aJ9JOD6Zgf-m`DX%8=d$xa@v zFy_r9?Qr~SJmuIQ*P{c*w=myNN6n)(waVv)M0^zOZ#O&yc8mT=ddm|jln57atzn8}P!zUHE^^hJFK3nJ^J&z0AV^`SxguOYtcNDb<1lZAOu1ufZmE}6N@zKFG~KtiGh>TebfVP=MU2Q!Q*}8 zSX`U25d(Wqiao|p3B=*5;^a_FP0<~olkVdFmE7Mh=O%>*OMO_vO~u#*uUhnKZm067 z;V@oAq%}f72!u-@_IA$VUp;TjuKpSVSgnM6m~pMCw})#+J8r9d#-%yD6$Qigsjks-HpaqE(dCq#hw41ZE+CO{&&kH)3Mca;3Y# z_h#YA>o>S9zMFB8CuKw4$%_!T*5LFCWsJDUi&;CK;wCK{?NJ?(H#0pG`%C7;XncU3 zY-qZVW#9$7QfEIR)5qN9LJ-%=IeF47X6p6=|Nv#u$GyPy7CQDFGt&};R>y@ zeSM8JxGeA0R>%IhhLbq(H-1a5LO=+FO8|=|_T_k(`plf1jKv0sJOjEcOu`Tx?lr$v#9*SoU|4mIN?$NsmD zcX8k#Z4d%NAanxQQO{PA`1DW)Ew$cv`QyjTWZP0_`_44q_N(3rA|Pn?Db77u*;+B z|6Z8*PTVgp@wj{R{@oLCd|x+#ud|cyKbOhA zrq>^K^6JgvX3?%Tb`AIbgkLJ!Nr(k!PBNFa6t5FCEUKMlJg5n%s$-iRh@ckj|I&AB zc_##fK)3|-Y*AUa5_^0Ti#0sx`nd0skG{lCzI3Lz{2NVxoxC{theotjpjZO<4DI)rihY?FtXyL*SX-<_Y}ba*#HWcclIJ&V_c5gM za8?|lVI(h3{-K7qge2{ieZd;>$NFhV^xugd)NuVuohip#2mvFZB<9b%I z%9^(;>U)XPO0w~smcRS&tN$W%HpB)T_-q{+KEl2Jn-*}fZ`F&KJJiYVYbLIF7`ez* z2nc}?31E+O?92H)`QE6~#D+PvC%V;d$wz0Q#fc~FoB0#JiRRn-=%s#cgoFO;Q4lBn zi9VMve-5SN#CT6bk$?PD#7Y0S*@Lb2L{;3zLHGH!0ZrHXhpYHekroL7ArKY;{Ee(t zniz-m`~+-lR&>&}!`SA^x@AVvnDvv)WK~q&FMB;d3d;u%?~S^U_IJ$AWTlPCZ-q&A z9nfo5-#lCs<*%yc+$UcvQV;?{KnUbZK+hI6u`fCAEcW^^WSvTU*EO;30UlmQPot9JS1#pu7Ya#J$q7%HCa#T}-&+vyR|+c56f*@|kD8ZC4NH zoDbk?W7*r)%2xuMO2=w;zA(F-{P6AN?N5!?#r-iSZRgq+1~&KrS2ofvAs_@oAb=y& z>r+f@O0MZxH1TLe`}iwy9K%k|kNQh9@0R!Xf)zVlVTau(jA&clGT>A`VAhk0K8(@_ z`MZ8%nNmMmxP+6Q^KHxKRqpkx@-PQJ^Ugb!->0Q0JcLJ*fLndQ{S4w>e#DTQ__&sv zTY1e|KaOITAABmd0zY~7E!ckhvAycVqYtsoF&tKxNq_K{YH19M^5(bvxOZ@r&k~+h zFPrs#lBfUcm#tsf_ph^*kF^YZ@#7qLr0K~!As_@oAwVNfx>IFcXJBHFW&J*0@K^3Q z6g6h@d)8l?`L-N~q;t7twYPElT4`gKe5Jsxbo?_G;wM7UhnT}S{m}O{`Fy0lG>Ok? z+%%kXd!ilDajj2ynlmA3n-CBJeiKk1k58g!5c~2W&f8YrjzRHScKN{)?FcfRm%v%7 zDY>8tKB=Mho{p;j-^R77Dta+NFMPOty1t*=U~LZ$?UnXK+~lWp=V>*b zih6rNi@%PajKw};4q5dfs?u(?hs>jc<@_x+K<8B5j!#~6$)3l&-97;KfJ_7MttqDrjB=M zPkG8e?$>a8h)1QOAI$F)Jng1Kdh;1$HN*AGeCkTSxz>+YGPr-HX4DidzrJLAdxfuW zUgcYxrM*I+Xab--gdSO#*rKdUnaIQ2RZG!EeTvz{{uQbZMeDmS^@%;kdOA*DqXypZ zi#%<%Q}1&q`Y~BQ^nGod{*>_Z8h(ZCZR%o~%EG~@JJ%dyHZa8WCu}*c&A=xVF_!_O;so5^;hac`%SqDp$;~J>U0V*0c2>dJ~oy%7eSU zl(&5yd|@_ClV2GEI6A%OvlIJknAW(w7CldOp8C|q@zY>0e$-YJL!75P&0T(<);{48 z1`j#oEMrbE9cNy#$EY9nz+!*2e!N;UZQ>Q;Usv8=)YZ`IeA2As{YoCU#eUci`|IB` zY_wOMk44cDv5^cqP^WV%Q*k#N`q$UymHsQt^6^#%`N0;4`(;x0ykFz;_-QaP&$m{o zVk7TXOY4L{@dTo?`g@Be?4>*ElK1gQ!^8lSJSugZSG=_5uMxYTJ*+=^M3L-Yf$!^h z_5A$`uLL~nNFK{MNl)s3EIip~?@@F$s?6-!H~k5|y{O{FNcIx7vTiDRH|oyqt)1OJ z$T!uGQ@O!@Jh6c*gpLpp0!adrgUAqD?fZ~qex{n_=*w&D$SxmTgRE#-F;>Bvne{TM zqh)x#)fd~#r=99?Sw|?gGBQV6rF_=^%186cSSmiqVEz5Tj@)IGL)Jdy#&`zB(DP^- z=*ZY3D@plY_yj=N!T3%x)Lv*bX<}jWc{*1}JnGQ_{%Hudk0St#FC*JEM5`zI$D-xg#>Z^; z(*N=zx=Wed{Dk{i(eh$fpPVv`-xYlkds+4u4Q!1*=f4l+*n{XY4);%vWVp3SJr<8{ zGd;gA;ir{dx%EKWBLs>hAZrIR`3f5Uj`&_py{|}l*{N$pUqRsLi@rMN-@^6S!4G})vz54xgI~UMm3w-e z=iRFpdx52KD6UMKpa?FX0B8FOLC)bAXf8yPfkuiD*sC-JBG+)r>@&iBG> zy0hB7eEXWl?G={L(o&jbokdio%wqREWNy8Z_6ULe2eIG1~PMcS9+I&IxU_8daFL#v82_7Nu zs2$%q=R6OjNx%O)Uwpo_QwYpQ0EZj9F|qeK?Z@$XJnqf|LK9z~PZmZ0n%((>+tfnQ z40zY4zVA+-z4QlP<}J@+TgTA8=?O;eFX>#jB_1{W3P%^KU*)ciW)pU!8 zXDIvBiQ?S(<=;N&v%^BLi=z5)UGk2TzkHIl@$>JBPLvV-DFK5Tov!wm{PZtu`=e(B z_rAHUcIo?|qIvgg=Jv|OGA+&%Z$vk6*t>@tX=zVL1n@$9x6HwQx+4*b_UJ=-US$X? z4}(@1F_V8MBx}}xjj|d_kx}U z3V0H5A*D^v`PrfPe6T`;)*?wCfC@r&&?r~ zA)H@Ed6l5{`y#M<$aPLoYEe*`Y7i-+pOLoK7L9a@+YTSjq`$e>W`E? ziU5mqVOwhbsP>if@AwZU_Law#VgPj#zx6`*?jx9(JSez@;qLlMJD{28&GP zvnFh-o2GYou(WnGtN%#NH=c%dxZlNDSQI@9?tY~6f`1jR*7EP!;Jyn^?x!sBgGJ9% z9bF%O+v>W(;c@VqdItL^^>cK4;%~|4Nm??mou@wF@nGYo!|k}JxbJG7^6uND`X&~b z%D%035`SUzhpCK;vZI;m(OPc!8I3Rdd-bjOiBR;((i{-C_m^$7=kU`i@hn(&{dUuT z7P{0{ySn#x#?QLG;&;*84I}q?ymj0!Gj(0dTk5w*0Pmvs;&Kizcl+txK}`%T;QH#( zFYQSwkCLL#Vu34v+M`uYfbFdXiVi`o1oqN>>a(bil3yOOmJIjCGmk}|;!(UseY35< zW6PuU#O>%l9Gu5}LCTztfR3)XwkLZ2N$e*xUHe1(HvS*gTWw!Z+^eO2208t4$lWG1 z%?iIin^zTNGc(2bJ#Sk_=eW53Q)jWw4QgGft57r zXV!Cjs!{vD#`)e-w1;$CAB+4T>#;_W^1*>oBbLZi=XKCC{T$!dJIs8g$X-faLLd(U z>RyU7_HuS}*h6>3C%=#Vkl{(!2l9}ajWX4#SYR!$IyTwpr~hSOjnOC8@|y2{YTtj< zA@PKv&x3cXs8jfO$-aEK>043lsbLB4@!zY3(*q{{xr24?n7Wy#+wX6`er!^-SGtpU zkA4b0CvH=}J@Q@?5FUM9<;+9IUNFw9X$OMSF&L)`ck z*&cNv8gp=Tb8nnq{`0oGrrsp_V+f@6HMf1(=arfKew^Jj$!Gs6o*|l+r;N8;@=)to zqEb0x3BO<)Z(W>NAtjIG;jB9ZW&1JLSR+`?&rOm+=nCk&3)cK>a)rw zzu!fFsJ&+FHyQK4CO-l`l^l;YJdjs+G|xt#>UghDQZBZw`ZPlJ_RmY7-1^HJI=C1I z7ON#vTNVP?MbX|R4E*!jEUgXsJ2+7-#a_*6{Ip6tw*QZiIZNNdB!G*wCv$2$;#`4T^RV6W`CCL6h#$k5-QM zL#xWzO0^e@^9Q%7lJ97OcVjWOW)qWic&({taX=ZZLHg_~0bI(+ep0buhjyGxJ2+`b zh_; zw_?q)zA@NO+1UdPWGOfGr_{8my`b8c|0)2hJyV%*!^f>@O;@fpp zjSTwy-Pou4qF3jSt#QrEb^Yq#_;dg^_c)(y^seI9(YV%^>FUQNat~mdA8ezo9>vkq z$y4;*1uu0d^nS1I$0DoU%ioN1ioW7}eR7tT0)u(G$=Fr{4`J`3X-IuSU^)WWAHA3s zGyCCY&sN$KE#k!Ka%?OK~mj zR+${XB>8cdZ>a67sSeJOte`8Kn6_RV6VZ%*lN)sBuQ#unw&kIGAOEUn(Xe6U~NiYhv~5V&8~lGxQ`N%&#ZeT_Zp9DHq?3QEXx&rqX^`=GOigtc*5}Vc zY7hd`6Nq}d$S}5_?$cPt_PuD)=9Hym-`$ zD`?gj$<4%`J(J(O@}Kwj9UfBT-U_@J*!{pyd#QGJ5Z#5lwn+O131BmdKmUBl_-L?Q zMZR}2PdDx#zQ;N@n2cMsaI4xaZnZmJQokl3{9`@P|5wk%qfChh&yjmb+IiO3t-cJ3 z)khrWX`bm;L#$X{T>M4B`k7Uaz4@Ne9xn3a+8!qRBYLA(mhG}m_v1&9$_kd#-*L5% z@%}@d&tp8*r#HU6`g^wZD_M7uuW_~66Vcr~-PjvssXyw_>4Z08=OPJ7T|!_S0o)Lu zK%Yf_ler15-=}sxR(VRdF1AaPOJVrF89g~pGI@Q5k5*`Bd4@c?G0t)Finiv(#D{Y6 zQN3^7I6gkur^EJlq8)vZGlw?%=^uR8f6B`?=p4u!WUU|v8<@2X_@dTB_xIOX+wbr1 z@Cz2_4faN!S_?A#*QVIKoUH6@rkuapo=3~or@h)=GK+oH)%$RkCu?AySB1}E!wKHZ zrx@Z-^RNM5%2cgP_m?a-QqJ!*u$M2gcHK_3R&*`HSk9gub?6M(CN5L^x|Y1uKQ96G zT^_bGZ$EtdJ?=v91XUAr^yW(IwW6CKd(6mFp;H1lHO>usV>i7ggt6`IU`OnhUS~D- z`&6dG&tFnsE6(p?J;_`9o-7>Qa_Nq=R;o}HEiM~Q_ii4 zA~Z$c;*ou>9Oy|fq9?&fo&=qah3wfPuFF+lGT)c&^=S5zY|ld-T^{X&MZ3Bd8pG+` z#Vpm?p?J`vYTynvy1%hZvqzWJ7q=RB94dmmB5N&d<|*83vcul|U~K8eBVeT(=G7b$MC0rsT`qwZNzDGqH!3qzSKS)A4>Fe zKUi)$cZm@F)VoGN4e?SV0#+02DquN#xZIQCn-x?Y!aaT+6U$C;(ckJ2k{ck zx9$=S==>#pGW6w;{m*DDFaPo;fVaiDqJZqD>klX6m(GwwZiWcKHW}UT1 zJcjyrlJEUSzGb@N66pJbPx?Gmey;T|x}i*3W2di`Z(zaXI$X=o+xqoxG28Gk&Wxj# zb|fRE@Fc**UfM3{6gTggX1Vodf&~}1YNDfV#ok(u#qo@_M(RsjPslvfX~FF&+8uq8 zKWMA37=6Yyp81EDR2>_Q7TVH3btpy7Lh_eYBVR)*{P(K2JCbMmY;2O#^0{E-@s=@w|;&;yL7gzv9(56f&{Oy(}8o(GGTM_SwSdq=0U^kzQUT$iHn zv|P%g6KI}@4$F_Alw58C_+UH(h>O0P*t3oHM2-Fv6^@A4N7^i3`-wcuqxOH-o_6BY zs7ia+^+r85_~zaJEaltd<4Z|+iqrkcXMHO7`IWQld-N!cyLx`aXZdIGFP8e(Kntzt zI-G#*XXh@z^pQZkx4CnBY5B-*x;Dq8wN(fHUrjxa&C{`wZwrCR1k}faUJvq8l7N>! zqE0^UkYq1rd#yf7<@8SD@sh(RAK%7qvk~$2S9p7navUY!eE(j8F;YHyPC2elP<|vY zJ!^Gmc0Z&(fIae&gp|si024cDa_2N}`}(nY6T=Dms`sF}2K&rQO@1!~3<3O0{4^M- z-PG_N(^GxL{hs!-hAU?uO{g`vnEKsaZ{}(LmvgW~+Fy$fP0Bd@*Lrj@ghMwXDu0h7 z!`V56uP=G^P`xC2I7ZL(xvp*8kB>zIGeh%|hm^~e024cDaNv{QJk{WHg2Qs29h!^$ zqIySPvib3i>yYyMto5sqc&PUWof{M^f{Rjsc$oo-tGx_h^CADW-`zrVMMGseuL7TMZ zdz;#d@2eBB>oEHb-9JhH2K@=vFtL+52YpP_vGp!m(|_i6J}q3(b+2o<)b~b$Nkl(6@PTFPs840BhDrHZmZDf z#N+qBqnwLJRhwoLU~E8;TUI{=gC@M7B1ra1A_INq%!q;@|E z0Awqz!0n}_x+f1NB@~|w>vg~DB)4a9r8*r4s#IYs3soF6!=4u$?Z ztf#p>9PY(SVUoswLdQJ&dVBs%&-2XNe%ddv6MYrCgeht1RXzmpPkMJaJ%XQC|5*S2 zsg?9_Yg5wFUs-?M$MhGUZwGV*bl$C|Al zy^)r*d!)|R)#+te$A#6R10!`{R@>_8j`kDAKNz1O?+uCgI+_QLDCK^)OGw2VNAsD) zPd?WK!sSP~W4)%-{e66EtZ8oaEm^m{*Z8kASGz}6=B>wC*5lEpnALatr>)szE+vIS z0K3pBNWI)(;P%rQE;ZVzGw}0D%jO?RkMAEY>#+WcJK{)a+2osNf2r)xg{37fdPC=$ zF6z5UYCIDD`xed3TIYhd)aerebMCQo`*^`8YFXN<{=aw!3@AwxLaYs`!-J zDGN>cPT2^klS`9ZZ>ld2m#(7O8{$7$#4A_&uil7qdie~Obx41Wb`MQvO#1VakhH{A zUvMs+M`+QWy(z1sUhj&z)awTU9Ixo4xasd{)_?P?AH1@*GCB-36DyQ!tSv)1G|-No zLgGw{tc2xzArpwM#?nxIp;35ve;+MLJzF-s{*LhnLCQDP-#P-{6$A&R*M*AyrsO}5 zJ^y;Nr;tyw6Tl^ka)c$+%WCCg*(u34%SS-H3w0KDv4sPBTl0d|U!cn18Joj%ezrE~ zn8fWk!WMs{OH!U85nuh_ftRbBy*+y`o4zM=OC8*7q_k2#_z08v{L}X<*++TZ=hwnT zqvdTr@yTBMZPxl5{Z87|{0?8I zbGDXax19A=M|>=_uZ5S5mK@fma|;&g>yBo9@CTOovg>aY+}en~{%&3+`l|IdC;Mvm zNz1GG2;iOyMWIXRPwUY@RAD}j^6QWY;4@6lAABqns_HZ>N^US770ke)P3PoP)u%A9 z18FGQA^jLfK>bfzGst6qtKxD1ca0-*w^9$xY)i%UfyPxc*m`+?vu3=^{rxl^O^7b! zWe@&p2EUf$NhY}v7k=KAzZOv0vpCJ#!XuJL#T2FrL5UT!Qnd*Ko5x8||TUWqYT? z1Sc;p_w)u{sUk=+CR-c_${0sTMAr~4C+|0yxr%q)mTHn&ro%LOG zQ9n0W!g=Z4QLc&I+Uh@*QzH$104K~U75N=^0?}T03DtCN#+Lrr)sV_Crq6$|4Bh zaVGcJJBsLSesxrS*v}1`7^z&-F)0f?^i$#W*p#0frQ{+B;P2F9XjssTRBqar@{su? zx_Bn7Fa45sPV{??=pT!;v%KrV85KI=TjV}b?(_SpFXS!D@|BO@O7E#yGCgkNxbn$d zdOVzfdY*?LuiQGkeZ~HD)_JPxT3I((U|gX_tH3W*ua$KYv+Ap{Dwn@kAE2k4-Yn4q zb*I>P{`gq!Q(5_|pY z|Bd1DlFtnR!$<9dMu~mvlKM_e#PAs8Bk#+?2TM%s;3iGnwLTiXpY9s{5{o9LG;E%> zCM=)Fu~S=os2shn@KzkVdDV2%l5e{wfODcweB!(npA+zVCKwRQIuYrp^*y4DCC52I(W}@3EsDX-X-16QDhKx0UXU!QQAqKhZ7!*P4bj z&KG70c1ezYzOJi-r;$I`@8WF94Hl}GOI$U<_jak1OMPy1hwe*%;JXZ^{qquFv&Z|@ z^Y+Be@72SonP+J^zbn2oI^a>K8Kf`bGqPKj8!O0KD?U$mow@`4=stz9vd<>=$Vx5# z9=5S+KRty>d*<+W9b7z}{5gIbSE zlG2M=3Fv4w3;n-x)*jgS-K_Ie)l<-CRk>z6NBGfB)O=*Hy&rrM>9Nz5dsUF6#9W ze(FnKgSoa}U0m{gARfgnT1s5yV>C$GXGp|XTRa%;wfp^K4{i0&lsTz9^S$Y=v~FT~ zZ^V+$J710^FzSE1Qbz|j;*@Cn>EtQj&WAv>A8sK1=$%wtu+yQj`hNVK6L%iB%ZX6cvmhu5Mn!URlNGA zD0xnjchR5Ip4atgZHSVUK20Wobu@BzG9FrCZ{L{QT3+9vnKI=)H_eAU=pyYRy@VgA zV<69knfEQRZDTl{&Lu1J-fhEj2-V7Ivv9@(G8ZAJ{BnI3E#UB*%I^myhha9#^>1}` z00#{Sw+-s9c&D>6``YM;aOEpK8%;p1M~jKxUo^Uv96zDm9oi;#BiDFzz?4Ic_Oe`x zegJtcEcB@md*f=-i&{U2z*Ik$a~y@W@1@y18V-(Urx(`FUerhG%g4W=ieqQf1h>D! zU!`;Xq96I_$55^{KXt~f#!gpz^hD|PyBd9II%e|i5C~wSUePnu@6cGFz`wVmb2;#w zG_TZru4v{Kljmq@N7h_rZDqdD$kXVayf-A`>#RJ~JMaQ|7QJVg=;t7VI`%V^r{znz zR^d6w)93dKMib_A`ZUY;)rGR3wnM+xRA(A#%Q~-F^tVrMG(YmQWxsbVsq;tk zF%0wKFXhJsisq{hVUioK{~Rh%$g%iVq3 zVQjD~DXBl!*tt1WY4%+m*Qqoc9^m_SN&qy!3JF z(0rg*=Rm9HWj}4_eyzI$_EtdBI0q8Wo+TUl9v`_U(Ci9P)*pO^t(eg^xJ?b_7 zqkdjCvC)#xD<=~1-83{F)UQrNBL!Ngbu|u!%%2~+Cn@c8`88B2A4p@wSP#rKG<~qy zpQwSoM|qd*f5-bHT0n=tX{&6m9ucjV2q5nzUI+7nk{_@#nm4pCUn}0=+u-tRl{X=q$2w}2Bv%W+tG_W zO2JO*2~-2){rueudw9G@_?kQhbGM~>QFxH5t>bUV%f5W*a1gJm*Yi}cTLZrDUsZ} zcTG$EZP@Z~l%4I)(6!Vqk!SC&=8R_xd)U$qyXD}*5ba$vQag}8cP;nX>ThAaT`5ax z;|Sos=)Tg8cZS{fmLMxv1AijKtvc$#!7q-p$l^X5K2H_u#kx&ddYL(_Tf;ri3pU%x)bdUYzUqg-Ie+qE`)Y__3juBa{ft9zNu^XJ3YXFt-{@X> z;*uYXnY-rQmwf*fUPtSGmYlh1idHs4uBp-`wqQ;_r)LF0~X- z0ME>=A9n7yt>XKhtwtOfV2sq~SUTUf*J%W$N9hZ=OML++t5;)(Y&z!tTZ*0msm=Fw zb-gM3g<8)YSeq+qbOo0Lwclg!E@GK`OFbEn;w+bI-v_mErID=rE8b4LTI`ukuQLsG zPoJ*0_w^Re>Gwij6+-}@ksM*qmoDPkVwg)UMH677jo~=Iom0nG3aw87UXgH2(&+$?;THV6`Uu{NP1vAVJR{P{DeG|n?VtJubsuWPQ?HL))^jiT z(rC~dduU*vZ2o4NZ=vH&|9d0%LfcDud+dh4PU>}`^-rsxi(s)|gW3tSe@yIWS@W8f zFMU6H2CwZ~7@jqL&Rl)zFTUOHg}f??0RAoNktoEj} z{C3#9%2>g0eKenDUH3uzf$l(0pa- zX`gb}DES!~%jbqfe6_;^-(-_NN$Z0>znQ(^7MJVU@0Pd4MhM-7SiS~QPz z?eRODq2oa&w!&myt+c1-u|kjs=~kU$@wEy)nbG-P`bm+V715}?gs1TUet{q1OZYU- zjXs3;O7?%J01rdtrYZ8XbrC;3N2a^gY~!`|(s|E0sr2eH7oJ z3?=3NYWftVIG-l^9FGDE)F10}MUA(LUJ(B@OrN7ge9th#PyJaZM(0W%VcK2%mF5c2 zsjI_ycKuS%?|S=r4F{!};^(?AKK7^bY`Q1%t|$WdF%}EIe{`Sr3cO!st#2v1z9{Ze zPcZ~|_(PgkcXKiQ&0Y&WDzPAMmXJy?wXE>F*f8}uk%%WpCs!MG?TAG~dQk zy~&~7ZJbsVd8sE~0$3N%rE%d8X)pUG&By#y#45fJ&r`)$BsUm)4(IWLeoVC3IEB+>AdM)gsf1%NGqi19(0k~gP;G>r133TH`vcPFr2sEy(^m-hO{sC%)%q)z)2q} z3w_U>*DJg#o!jBme|=Xeusw^>)x#)bd%a5DBm3w?1?Uak=l+Sf(6eCp{$rR{Q2&q6 zc^)78A2qRIc%S62#Sox9czd&}ebgWAmu>pP3hg%i34TQ~Sk{6wh=+_o=2{KfF)!*P;mEIa)g>W1_xa?Wp65 zVlVaNLjZ>qcu%O&z8K$A`{&aWTcy%!VcI{+#7-QZztQzHQh)4mMIJE+(Fc$8p^gJ> zd8J9^fYv(eQEi#^Bl@qSDdA1=%s`L$EU6!l?$3Om;5|{>J07#@_XFyeqiA9GXkh)I zFZw}Q-BU*OO8#CH0W6Rmb@fulCtVIp{au}ll7ZA?lYqHT=B&^9yz8G<4z}5H+hxV` zaOu3krP#tfj2U)4leAmpCpy4D2Nv=1q>r*Y&wp^9J$7VsZcsm7T%13uV{sB+5Ba=6 zc1$ZO;(wF+m-Tb?r<5=2oi<+rKe3>CRyd50+Kz#rn9W|=>QhED?COUpbr>)6nZC;B znu5pcPiND+Nj|-5)^Ce`B(234d%?*$=LhwA?VHqL{CULEi=L#j+Ip5E^TOl1rSOP3 zh(3ru=n-@ld#E&UXZn7S_dA@1UL-3^`*c4-(Pugj>=%>i;@kQ>&aCp|!iVx#clVzO zC--vnH?Rh$;MdfzW{LD;F}h+j5&em8`jBYvkH+^*J}rg-S|zflO#YKjPCld<_EL*& z0@#d3PWntSNZg6sPTsOj!jz|u#S5}`R6KIhcOUsr7>Eu`tpiQtPl~(Z0VjKdx%Lq? zd^8K=$_C1dxo+!h^|#bq&xBZ~PRA3}cYpWNoQ+#u-8V@y=&Y;R%65NWd}vbNMEgJS~$q`e$rKK^_UOk38Uq?qmz1SKF6*&8_&`mzFtCmrBjU! zhtb!;oVrJxmQTy*tOu2vE!O)$9?*~^JsMH zrK|jo=j2~5B?E63CPt0HPe6L<#svOXgdJ4N_ z=ZQb_Jb&6!cVUlg{ly;Yf8!_ zCL}!O;<1QR(>|ogchSe_G@stxJ#hPO_%|X`kqZUqPw)U%w_M+e!tn{sCzc)`1GxQj?h{!}Ne-}Ont zWoT=3>)igZK~GFC;4G{Ql^0H;^9bvfo}1^*J;?8ScW;qLJcJe|( z$z}I#Rcx*7GCgPYr6BTUP0Dk&DEHSHSnJWDzqM3@{wuqWT)MlGO*?ROPd+m*(p$YU&p3R1ANqWqty9XbykaqWjW<`UG~Jza895z`;bSg^n-h6PZ|# zv^O356&}Jvd%T?NV-eqW@5v~7)yb)UJkB?4;Zj97)&%Q!r?bJq`~DIh9h}`P`eH1Hll@SB zZ%}sQ-?zzsHq}?LS7`D~OJ~$Otj1?)hHZD}ZkZ<@dq0zl5FWxKnR_dlJ~Z*fD6dic z-?bb9Q;P0l<5S-}kdBYw2%QQWe?-oHITHBv{5~6+0 ze1Go{jbn?DfYdP$frxe=$b5gS)4lXN*l(Wt!uYNFTU1qVmzgP_r{{~ajDJ>_vW1W6 zfGZtX;^f<~Q*e1OTBol5+x(0bt(X^|9VmkZOu2d&n>@ac|9pOHkP>VYz;DsIURU=Z z-t{i2TWr&o^6V1GsJk`#-c{L8da~O_o~7YX^*HU}Msbg1%)~rf%T>6DUi4#4!P`TJ zo0r0VG_!I!*5^I{Dz33Z7<;Nj4Agrry%C`FgYGvA_}a8Og1zG%hJ55#d2HI=%J+*P zfdAN^gW#iHVgdiX2pLEnvk{o2uc(rry#cSEt#luMHO)L&wE;Jh^)$5w$$!E? zbfBy{(D!uH_z(T7{JxLBP)k9`L+_V)xFI}0FFY)E8iZb?@3~9p=F6tj5A5u59=(>Y z|A;MaI-Ys@Gk@BnenE%U}>tr8wF2hoSX^`Vu;Pq_FF{wZ42+UGU@CEQ_`9S(|54-)?rj(@7uaNjIi z6440AU;CJ)$MTyZ2;k*tiGLa4U#hfbcYP63kUFL#fOYx=qZR&8Kim8Y$4ysOxxbC~ z(>^=hU1mncMS7z$`8@U#!a+C$p#yFFFTTDHr+Ce{g++COeY)y-u|2Q#V(?zt!PB#9 ziPs#qpS9QWySxchAoIq?*jCH+IlXzy-^cRR`hmQ*&Ivw(lLz;P^|5cow^_Xb&(Jv> zsr7_I&tsvxnTZ|A=fX>SP0@!~4}7x+T0i_Bi}y6yYsCKeBRy->OZ{r%`h0dT!=HE8 zL6F_~rf<^TA@_?0&CuDD-6qoCj7RNW+D8L>r~4xBI1z~c01dI%A@&-Ncb3#MoTMdf zxm#hQd455ic8qxn@5Ody z08WeAI3K%Y{l0uJI{}(K`UpK0>b>zK7bXlu2ZGaq?7o1*H`R+?Aby{-{(z$a)b@>HDhe7Mmf}-^Ok~?F9h5Zz%I11p`%Z{H8CQ& z#-2eq2!~{>-9LSB?_H*CLFGm)vgS;)?D6qwdo}C#ajc>YA!{BKL6P>2@smsV#jMir z{|?{w*biCWdACnRUfCl+>j;}-$7Aex8`^l6J>F815U@wU{=8Qjc|1vZ!gg#C5-So8 zq641mK;ray`~D*B%WRgf&PSE0?zL&B>+k5E+wAZ7^6(r}7BZJ0%UpuRc&6FB`l3Ef z5!)>Fas1A6McPa3m*(YN#^0+wWuDjs8(UJhh<=T~UkM)+w%q|do3J_M;PjBBf1?sPHw&3Z{U^jOfH5zx=)1SZ)3kQ zOg_;<96gv{BWMr3y0`wzV^1`{w82wIl4+76mCMZvyIf#Mex`=hX4NyyYO}3jt09u%Yrpije4&9_daj=hLKPv%*7o zXuB}E5oY>OkAhgSgEvR5cogiEZwpP_h9lj1XRM?1BfEZ>slLhmq4%_!lFt%;+n&zg zAVvAXRQY`U&!9fEWiLQ`nvi!%(Ql*U)ZcnA-i+#!CCpn7rTi=eik;Vvchk8?4O%lZ zBJL-DpBPK(jTGd2qALaIz@k~>p#4d){=$BJ>SLrg?#@ZAlJEPPN}Mq0h9@qg^OnR1 z`{YaOINqlHsaM1&(ih}I?CM$_2bi&q8~ZV}+xJf)_NVe&;%hS4SzS?O&QuF2*kuR||efdBQ^d3&qFj z`3nz3iIcR8>+i6ditWE14T=An-G9;fSeDlqVVypiQ_ICee_F$d!*p8y#+5*{z1V*S z|5sNn{1#UkO1p$WegxEJEb1968R91L9iQUZ(${Ct4BlCisbsIc`@9rANk zbsn~N*WZ;jt25Z{xf+uF*y@YLnW9D)Jihm~i#wmi(30zX1k@Uj^{vP;O$R@ikF@++ z2>3|=2jFH}OPE;4U}7k8jlHLE5Dv-QN}>8d`2z%eVs7-uSfD&%D_6eytiNmVYL5O{ z&WG*iEDE(N8eL9NUt6{F(H^~OV~TckpKQ5&A3w^6iIkcjfz7M@)we?RzD}9@Kh94M zQnC=3Ou*H=R<#-*q4;_CL<-3jN(@bOfQb&&@O;v1xbhFC4f)iMJ=8_ydm7gF!0UAP zGTnkx^KHK=eU^0@I7~kgioLOBP*+=H8Qa%?)IFB{ukG{b+me1I<>$eJwXJdTg*A0U z9{l@dihlQ5=vTrc&BN`x=n&jV*@*5_p^3$KxUL};LzVW&-e2SybMQ$YqD5Tm)*}>I zJNAw)r9D3z@d@4Y=xfwKoqqppbQ#`QWDjPm3yA zPr7$?=Igus+8qIkLdgE|lk_|Oyo#T@latge1l$wA{_3X`L+YIzZDJ{MeoZ=-D?Efp zGB;yYAD+YO-1`AnEx_sOllXb`RChOiI;}9CRcHKA@7kjCqN)_RYIV=?TfmigD|U0$ z;(6-Dm*Y7ZR`rJ>BU0Yn{;3*$bRNl0{%z&Dey+whd66BincPG922TRwAF|pTol3Y( zZQ?0KX;p{>c+Q)}@#+zZUCS9++2Tkc<))-BE@fV)HD#$Y5qSECz9-JJeQI}XQt!c6 z)Lq!B+E`Lg<6d`E@RP{z2sP@3RJnPrJ%)5M9Ll{4~G0T;;)StyVo}c{jRc3vj z#_zSv8J_v^$(w(f%a?fxQ`lZjC3a5|eUI(N{9kw&5_bGax-?Gj=!B$CRI1RsEp_h{ zCPwemIxKDV(q56D)&mdpw;KJFlLwHtJ=u;c38#20_quxbxW36}{0T^oaH?@v6QAWTQ)!_PD1rcv z#cya8a>K-w3r$>2w-*os~Al7*Bj1d;^!&%?zI>O5M*)7uq(s@RI;z$6DKIxt!X zYWO;T`%ikS{MXf$iHD*&qvB8F5t&mCPvFN!%k$Si+PTf=_4zj$Qt>r^UQ%C@fF0hF zBb?ITwACj?-!%a{`q$TJDX-7Mr&rh)KcxMsX#Y_L^SZkuY!lm(_M~H6!ov$5dL75> zeewX(R{I2^iz!3gi9yQ!=U$Uc1#LMFKgPzL!snI=?Py8X!&k+`GZ+`l_ zDqh4c{>WEaDg^Q;fMe7{bl0xOckU@;ETi)zl0ybP*2Wy#;lM*tfX?K3L=Aoc?V+kc(T+g)9}&iO6x;(1Ap`4B*-V0-g^ zyh#4y#e8HTrIwq3@AGEWKKMSxS7iUua^qYBdC1sN zeGt#k3HFjJ%rmwvW&4#ospA*MBZXz}^Us?X9CfSGb8tbAn z@U+Pl%DR|zZcKQTiboAi6uhH-D1M||{lImo=~0RJhur*wUZ;7ad#%FfbsodyGQ5-b zJ?;rg3v3g>lDGOuwxs>0v-Wpzrfq3Rc|ssG0@z-iPJ1bClU$+pR`Opd8MGDcMJE4* zKo$aOYbkA{DBrlG9?Bwx{J%rF;HJm-1KDvuK##6=BAx7r|Cz}ym#{_ckiXxM8iauF z1h6)GFCM4ZO?L*VoOcs@TocO@9s%=s4v%3c-|bi0UnBwX2f6u!j(T5_k{nkT-h$o< zBVNk8J+5!^nZE?E7&UUUzi!mWOMOq|9>=u5qa;fhV4S2VYc6_Ts@wXoo%&lmMzNRf zjHsDnFp^^{DvmDo%&%)T+HW_N8FXuZvJSux{@9y#G8g-s-~Q4JtJtb`OyAjE>VtZ{ z=l8+i5r5)Rf3zm&Q$OwfP#6C*;12m$K&M%KuOHXYQzy^_cxUe z?&T?0X_XM*NdT+W$#_U|g|XL}F&34Mf788M%3;BvO(Un&{(C%aP+G;80M1iQ7P=gQ zPpMO$ej_(ot6iXXwsFt5Ns-wat>kF-+S5UU*04UV?#9*XQhb8e|DT`^!+z@JShsl< zH;U4pd1u*b_Tr&8)qV--Sj1S4InS+1>J9p9SeTxB43RerP%&{V9e(-t$s< zm%n+HI#F_kEMmp8#KPn^{TP3Z;_4I^wKPm;%hAL4cVsm&#Q-Vo4OMJrq02;)iHRT+KAoN_VIXzCy)MEOWN0Gp{GdjE9rzb#c#S_szxCWQulhHrmL>!L&u?KdP8J%`zRPNI73G|)_#s5t2fAmu{ zs(2t`H&?yfS}nczi2%hhKTmO~w(M~gdmxF;dBEXC($`abvNCC_9|XKUqi(+3-hBCi z7WvJ?dCM3t^?bX&*L7!fsutxlb-!C!RL|f+brZg$K8rKeDR_fA2zym_kr|!5Wg1%R zaGI1}`h8hPn8Dvy)pL+jzbFnZ z|Ac^D0{EQ__9x?w$>WWhdO~K7+SN@dt;__lhIUiCgS=DS?7e4VQF2Y^#)L;MJZiMJ z-_d2ZKq>h~{zdpCX=KwU z+F`4sXvUGm=~po5&^cdUP7)RUO47)tU&&txza$OeXY@gQ&kEl&n?KfmJTY_B@HJW| z?+uCgS~ec(-pjk}1h4_0jvl6Y*?MP>M662YxyPQ%#O{R0+&mi5qc||TX395(fNcVJ zn$EN8jzGkompRm;d$G+#2!WLZ(2U1f;(z8IPgEq#dPG)od#=&?@l#z(L+Td-Wg`%6fsT-t-T&C>f1Ab^ ziJ7Bi(TSu# zNL|V^B;qS~9_iM~JKF@X9`D0Fl@6U-*xPkR=DnwL3&LY156U|D{T2Twn zL?_$PG$}<0~*BV;;)a!An+S(mWbx)}%VVqs!di}UwOEqcFw5zb*_?Xc2UHW4r8(+iWf%{9c z-)K#3!SYU`;m9vEeU$!a0%g%pb)Mu1t&bgP=BOH&Ia(HdioHj9>}OPe&EMr(B!Or< zY{VsW!uU2?jWe)kpRIY}CUz#}rE`x=cr?()Li9Q8RixfYT|&Sf0oplh8)&uzOZ!@m zrO(s8N)3r;L3`w+Bq1OK<|Baj#yiW*W8eCTN`9a4-!yYn_8gs0FXh)lV0Hp(KYUo- zOFJ~Q<@`eH@veRzj#Exj?>qa9Ivcyrt~K&YA;5tER;x#Y-S01z7vw-&+8_jkK%ap6 z3mG%zFlN$wj>^nYru(_|wO?L^OaQy7x8ldq0>x@-Xq(*LROi*v(~o>V5$oc|qe}95 zDEeD$AF?J(-^xWGIvq9Ice5LD3B~{5No=Vs&k-$`Jf*inz!w7e!}x@H886xOzk1J6 zIj!CoE3nZ{nHQX<$c2YmgH7tQc%0%s4T{rn%45 z_$oZI^H3XcTQI(=iH>>*7l{0`%U^#}cnPm0fui(5?JYUN9CC!)XueuipR;QI6(vup zFD6hlZ^K=#TnS(=yf34*H@5Dj%1Hp% zsYOPnJFC;W&28!_ zd=*DyCx5j=S}p{NBM_a8Em13uP8Dpg$@5qnw8PW3=mEU7xSmT*LO=)!J==2d0U5b387$cq4WzzMjH{7oyO z-IsFqH#Iu9wHf^q@5cJP`Xl8E0U^MT0N!4aeH8NcuMPZK_E6`i`_dvIFgpRfTpfuI zsUP7n)#%PFt?5}+Q|8aY_&ZfchRDro5r{Dm1q=o?U9@;|F!6U16Y~f;)Htzr<&ww_-nRKZ=d~T?hyPy9BTv z$Kp%sH)uy>iYdx}A14-Op|tr8FTR1pFg_y>z50sPPIt>2d21?Mu;7 z-|$a6q{X8M6g^K>>Pt(4FAr>|_P`i)s6U!IvyL+4L1Vs7?5ox{Y%4O&VcndvBAE z(NB3gB312Pb^g9d{zsp=x{bc~f_f0wsEhD^^=7=r?DLzgzP=90_xmie%d7tz;n!y& z{Ic46pILtTgLQ@F$?S|zd>c=h_BM#~r;SAOlV@Cmly6Aq!y{d~yb}WB2t;crrhgDl zh|a=gxD`)UsAyc2y-$)uD6H9*%yFf5m%nM6@?# zeuMS-f8#VpJ`)1d6NuJgSL}mBqGM?N-${5gj-*po55&IMfZef^%mkgDsQf|*l#f94 zM0b4BeR46mH4ftw{iGUFd%EYjo?1Trk{$~IA&@43)oOQIYj*@rQvX2vn0^7@RoCMl z^(dZGOTBy?J(W>hzuAlT*Ozkuv&Gfglqb+RN}W5$o5_|@?sjuTKbsV*6 zSJ?+9Eh~8^1cZPP5CTG=90cg}37x+(xh%Qn$D&HC4=IOMNiT(f5SW#~rK@OX!QIqe zIDpnC9v8g}AHqN5LYnDyF=ZJquY3ed4 zSv-pS)GhR_Z{qXOdH4X{iQ}UqaR{yW+koA$4r^v@oBU1)2mv7=1cZPP2$2A-Pp+C= zT!7~RrgERI`$y}zA5!Z>)S}S)B=d075*8l9BS}E?AxVSNx{$bCA|H`Yl7Q$#l7`r; z=!58kA)tOnXP@Sm7n7W0nr%y6jn!g*M*HF`cOL21$~z$-1cZPP5CTF#2nYco5E6mt z^R$aV@2SP)3gvw~i#7)yEqeO?Qb_tEeGvjeKnMr{As_^VfDjM@LcnJN>OH7Rj<{2mv7=1cZPP5CTF#2nd022;fg>AM}A7p}bG$2rcud;%RIMM}x}H z6Yld`rG05F6duAONkH@=Nki66i9U!vBngNAO|6E{xEWM_#ylV`bxH@Tp zW!684rZ zai)72>IVdX01yBIKmZ5;0U!VbfWW9Cpq9`Wc)}^<372r$^G4M>U>6Vo0zd!=00AHX z1b_e#00J``0lJeG>k;9s+7EEF>N}=Z&f z00e*l5C8(R4FNif7U&Tn;{5Va;Z(#qGC#YR?xp9?Hs3>ifdCKy0zd!=00AHX1b_e# zm}&&nVfn04@Pt#$6E3Bj)TUGI4JaQ700AHX1b_e#00KY&2mpcEg#hiAuPkFYLLBpv z;S}JoTs=(d%r5U6h1#ahGe!P#s{s$-VIu&4u+czV5BvfCU?Tv3u+c#O0Dl-Qe^4~V z+?bA|!=J`rOpVh|aeqwi{ZTFrDIfp@fB+Bx0zd!=00AH{+Yrz@5+aUpG~y^bykk9f zC!B3}AnFSQfB+Bx0zd!=00AHX1f~`NdPMO)>EP)`0)r*Y6COIX{(#bf01yBIKmZ5; z0U!VbfB+DfO$g9+Y9aO`9F_eTdDYQn>V31x|0cVxn3r2d9C!c^8v*!(jRxX;;1BQz z8v*!(jRyJ$_`^)|2l|#;G!pz6cuE~lxrF|0dl>FF8;!yH-?BEOfB+Bx0zd!=00AHX z1b_e#p#8G3(TJndGgqf#KfqCmqs$i374SzO00e*l5C8%|00;m9AOHk_z+@sogc!mR z;7AwJ&nC0gQ5FyY0zd!=00AHX1b_e#00KZjBXIZ}HAc&)VE?_+8mF_m{Dt&2_~K+C zfO)uOq=5(Uun~Yi*k}wsPaSvyPa6UFgN+96FZcue!A4*d{6XDLi@_645l^^?4jDCn zn926ob;EBU00e*l5C8%|00;m9AOHk30@*KUq2cIea~$7f$1!%6(w^`@AOHk_01yBI zKmZ5;0U!VbfWXuupgv7Yz!6R|N4T6`JhiQk(t!XF00KY&2mk>f00e*l5C8%ifzFOJ zJ}Ue18Lo1M+w)|2-eeiGnC-mD0;5QHe0zd!=00AHX1b_e#00JQd=ww=wkBAUQnB!o$ z%2x4M5o!lq2LeC<2mk>f00e*l5C8%|00>Mz0(7NXFliiFZ`X-g#z~H;MU&sEC=mz% z0U!VbfB+Bx0zd!=00AHng8+RkTU6%83`d6Jz_=_?|A}b>21Ulf+%7Z3mfKmZ5; z0U!VbfB+Bx0zL$2Gs6Y88aLO~+R7Z;EJ`oTF7e@w^FROy00AHX1b_e#00KY&2mpam zLO?B|WkcY|W@F5tuJZzQ<|wr;Yykp500;m9AOHk_01yBIKmZ6x2xK?XLOmihT;UAg z53oDL`YlvPOGx9|a1j`_d8@Fe+}^+gc-RQQA8a%brv!h1KiCMsA8a(xKfoU*#~;+0 z`G^oa;Xv_}i`Dxlr@c%mxIYXMI06A600e*l5C8%|00;m9AkZ8EdS5f z00e*l5C8%|00;m9ATSsLv=WV}6-6AOi6_LdyjH3U587}r>>=GI0+>fzMiY1d4;uma zgN+8_a^Mf}2O9zSgN+9I2l&Hm^#?kO*BdpQ7mod1GeU8dhtHX<{*u>w)cT8ZYaj&# zfB+Bx0zd!=00AHX1b{#+0(3^USVz^BIWeR?c&wnYSY(k82mk>f00e*l5C8%|00;m9 zATa6((C%s+9AStP>|GzDhey3NVIL3x0zd!=00AHX1b_e#00KZD1p!)5EvGRPCpX0X z`g$``r>*|QbbAV_$UAHVMs40I>?yZ6@Bkh*0`LbL4a6zIAK(u*0`LbL4fGH2hpF=i zbrbd^6gk3$^cVQ&)cL0_xr%?;8l->#5C8%|00;m9AOHk_01#-7K=utRsJ9*L0|Gz*2mk>f00e*l z5C8%|00<0*fLeo=Wy{JOnTac$tw&l!hYyA-q=$<@bsj0wWxRn0@URhpKiFv0=b-~1 z;A0~If3VTO{Q`f0KiCM&Y=5BnY9V;S37&8%y?bW2_w?`I?RyiyfdCKy0zd!=00AHX z1b_e#7z_bAK}Qnm5s~2pXKP#`jnOLx!xGX!00;m9AOHk_01yBIKmZ5;f$4<+?J&WQ z^>?bDmFaQHrdPYcS|9)ffB+Bx0zd!=00AHX1b{#r2yh(XGCc~Mw)+M4N3sLhzfK!C zLdUQWz&zeEvcLm)*a*NMY&53-ym+)H+S5h={$Qhl`y2iMf3Oi~+aJ`8vv^O!ar7^3 z`%~NcY~ROkAOHk_01yBIKmZ5;0U!Vb+D3rBL5pW$9N`!(X&XuC0|Gz*2mk>f00e*l z5C8%|00>Ms1k|oHKFeYUm#X`xTPwj{AOHk_01yBIKmZ5;0U!VbfWR;ip!I2mT3)O* zYStW`p~pq^k71w)ox?+*?RlusS8gld0X%F3;14z$h%10Uz#nV`;14z$=pWz@GtD39 zcC~0$>`Ay#U55L2y4}C!yA3HI00e*l5C8%|00;m9AOHl~Mu5)ActmJ8!5O+A;R5<` z+qgj=5C8%|00;m9AOHk_01yBIKw$bKK>O-hqcb>`aH0Cj^lv5900;m9AOHk_01yBI zKmZ5;0U$681ZWc;4K5#GKfm5+azy19(?^DZ8FUU0f$27{750{W0eAop8v*!(jRxWt z;1BQz8v*!(jRyJ$_`}ru1IH2ecA%JHJmE66JMORPc7K)c2Bd%h5C8%|00;m9AOHk_ z01y~90<=YTJ}sVMafB=AdBesH`hfrt00KY&2mk>f00e*l5C8%*0Ri>4nH5J!PnZd< zf;s>JAOHk_01yBIKmZ5;0U!VbCIbPr7ITD4^HHJU3a9IlR+$XMP{!~Om~Qi2VQ<+N zfCuof5r99~XdsRO{s4cl5r99~XrO<9Ka7Gu(52IUPr`9pu91X4jev({Vq-&KND2ETm8l5C8%|00;m9AOHk_01yBI zKwzdJK#%7=3CE`8TBA$ou9?;vs1FbT0zd!=00AHX1b_e#00KZ@auLX`qeXf|XyOM) zbbrCJeT(RZ$wdjJwU0pRyi(*Zw*l|~9yS8-2OAB<5x^hd4>khu2OACa5AX;0gN=ak z2X(|W;|LefMYf00e-*%tV0hofhkjE~L-RO#D$RAOHk_ z01yBIKmZ5;0U!VbfWXutKu7WFG81unCZ2Glj!WphQ-=>q9u5MdKCcz_mD?S701q1h z_=AlG;uzo$@CO?K_=AlG`Um*Kbnyq;oJWMqMs~f?G1^%p34fX{{*>DpYOiu_kOBfg z00;m9AOHk_01yBIKw#<+$gZSCqZl{X8K=2ZhYCst0zd!=00AHX1b_e#00KY&2+Rfq z)CYKVnf}T$BXnGUHeioB0s$ZZ1b_e#00KY&2mk>f00gEM0ks};giG?ZMn_{m!l}gr zr4Ivv&^%IHFQX1TfQO9${J};8aRl%O_=AlA{J};8{R8{~{$L}J<`499)gKW~F-N#u zEz4_>WnP5cG|KmZ5;0U!VbfB+Bx0zhE25TG}XKpf%H?7`7O1=auoAOHk_ z01yBIKmZ5;0U!VbW(NZ59=dM|`N2i%U$X;e)Dj2)0U!VbfB+Bx0zd!=00AH{dI-?n z`3@9Qun*zF?5fej0~WQ7z^Kn_g?;4~2Ohw~Mgaa`qk%XD_yhdGMgaa`qk;Yb{xH4$ zK|Pn(8|9tqCz&T)pw7>6ga1r#{|U8Uxpqha0U!VbfB+Bx0zd!=00AH{Y6#H$jCUn8 z9N{Fy6)w!aK57`i9v}b&fB+Bx0zd!=00AHX1c1QoM1T%aD<+L2T%-=2ohYN$KmZ5; z0U!VbfB+Bx0zd!=0D)0NfF7e3(~?Qpk8nx$_ED@qY-$&Q&^%IHFXIh7fQO9${J};8 zaXIh@_=AlA{J};8{R8{~{$L}}hCirZ(BdJ-5iVCR%dKw1-nQ!xeFXx8wojo6zkmP` z00KY&2mk>f00e*l5SSha^mZj2&u5MfBaSfJYf00e*l5C8(R z5dpeW@#-=|;R)B6jd-HYKmZ5;0U!VbfB+Bx0zd!=0DJLlXLtxN(r;snV1@HhKHUjVm8x6z}!5`ocHUjVm8x8ai@CW#VjX<0~&@sFx;nLPP z!ZBK8Y=|r0al(cgh35C8%|00;m9AOHk_01yBI zKmZ6#KLlvLUR}n-52o% zz#nWh&_BQ*W{N-1e>ILHTtGiGD8L^K68Uka<58|HQa}I*00AHX1b_e#00KY&2uxQ5 zXqC=g#4KTX&Cy_bAssVaYYe-A01yBIKmZ5;0U!VbfB+Bx0zkkZ&^d}$B=Llc>4QcV zM<4(MfB+Bx0zd!=00AHX1b_e#n9c~$<7pu+E^~w?uCR7oM!QVsy2AFh5SZ!nVo|@c zrvMM&VIu&4u+cyq5&QxEU?Tv3u+c#O0DqWH{y^ucv556XSJ1YmR%l;?M1GuYc$908 z6c7LcKmZ5;0U!VbfB+Bx0@E1*dKyO(F3~eb4JYV`Biv{@*ATV?0U!VbfB+Bx0zd!= z00AHX1b~1K0d)#37h(xl^2y;m5C8%|00;m9AOHk_01yBIKmZ6#cLZn?dRTERVP!wU z71`KyuNmxb2Z7l(FBbJJ`wj2_9yS8-2OAB<3Be!W4>khu2OACa5AcWS<`480wW1VD zxJ*4<)B){TV4;7_HayC;M+yi40U!VbfB+Bx0zd!=0D+l?06i|dpBDAw2p7{GGp&B8 z4-fzXKmZ5;0U!VbfB+Bx0zd!=_z_S?(Kz#j3)Qv$Jh%b`fWS;dAlsOZSI5)w8UHzt z>(9hmqfXNmf$a4jKh6F3py^sK*bM~Y5TI8&`)SjE9~!3xxq!gvBS8P6 zU^-f;)}wLy)pWERHV+?x*)~shcGZ`jDXc5i<;N;0!ld*?*>+1^B_v z7W7+({C~3d#+8p;Kp+Ky4)cQ!e>46Ieh~cV3`c+t7W}{40Y5my+drkZobGw)g6jK7 zDGh!Qz77N?2?5L-F88K06OVX)&{cmmmmbh>C(gew%?A96I=b*Ft|HE%9em>X!FI|k zS`hjQEc7qnF*SI^@`Grfsc9cmD)2W*r0|1H^xGCf-bMrcJN%*S4{fJtUD|@S zS8t(xvIF@W=f~;4=|i-?+LKsU2cD?uF1bP z#scI40`Umw_(8#6!4FQ-*TVP~L(X;)po0bf?<(+vxC3Sk0(tzP*WbYp&X|^Nw~vIj zQAL0@P`lEX_&d)XP7cZDmL8(()KBSH^?uq(Z9Km-sx_KfcE$69^?K7w18w@Jx(v<$ zfk6<6=LZKV1+qY3R1t{f2S?TZsj-VzQft%NQ~B3%#MG;`XbZK4@V7>rXwh0?SzFM$ zZI-5Wbyk%z55483@PlpJ4t+oX2(*U)W$IA6IvX#?`3Agja=R{Re9AifySa0JTngBAbpX5a^% z?Vr7@;d@B`i**g>^z%Qe_j~lJfZi9>WtyK+uM9k|_iV1)ElEcO>bH?w&YYzSM0xOo zPG2|Z;RpoUM<9J3Ev>?{K>rZnj@&V4{a&^tyNE7TpD*f_Zcibre?i{HBg_x_@uHuX z+R4>+ZwB6e{PK3db=x09{u1T~foBSyA-r*Y)ch&L5009>qhSxdEcmQ)J-^yK*B=ks zc&Jx-V((44JRY?3>pQ!7nqC>epf?_DuD|{~Q%42r$Kye%ycm8^k{=AOdVQczZZF_7 z^YO{=irD{Y6*_=!D}Rft#g+AYJM--);`#%zs#AM`(c=^w#%{-j?TSAak|0%3kIT_+@f!1O^N z#1BrNnoOppqr(r{wT|Hj?firNj^hUhE2AtO!w;4v%BdKBuq=TT5C8(BfB>zgzD(n# z{b6`y*iw3c@gS|NHl^*echI5gWZHHVkQyZ$s_}!>_L9~YRW}CP_mEMlD{Ptm2w?xg z>EDJ^TLWI_6y6)x;0h@aliPHglDRc-_el2Fh!d zAJp-KVtGU02gULt_(4$~{NRvS0{1c+f37 zdS|i!V3Rh{@q@MY=QH{}L3n<(dELnm2Fh>s`APlwL2rGBzz+uM7ZpF~wHN##k8|-4 z2#gj2^aORY#liJ^S=|$~_Ln#wRDbo9jqzZm|A^-Y>pn;O1lsq$4)zME;9W-%XHpP| z=Lb`?BJWH_fR6C=Q>RVGTF3H()3I*Dvw8IRL0#7resFMX()hu_%S+=2UFD_lgD!cT z0|G!`auJ}_)#b%EHuEi~7(UybkKd80vsy!<#e2X~It7y}r6#N1L?I3{t2iw7MHff;;JkNvErXvd2 zf3QibVgJGT3i}T>@f^qi0U!{Ez}z`>jpy5|D|YZs^#eLYy@|Hv9UwMP>(aVv1KL<^ zNzYfW<@G@ShfYx!(JhL{7d4$}~|bL zI9M5F>6G2S54MZv z%-2RQ&|j*|T#-`$HZ8}4<@mu;ef0Ad@q?8oT<+o7CUlrOj1KGYpFU>&qmaH_`%MjB zKG*lBBNFxPQNP#dRf2w9PV4c5VtGSYUsNm)`wu#OweBZ40|cfTftY!+QF{)qps%V8 z8qLG*s4Zx3bsF8Scsv+mPmUS=MEBUvKWjV~uP@g+GxNvugEO=K#a)K_6&Z52k3Gxp@cKe{klaf|`vg0@#0WR9j|L?V`gC zH&}Zfl*WUjYJb%(>_1pVWH@Q!@u2vAQyC97v4UQuM`&LEY?&X#{)0{02r@tb2*e;j zp9p+!>Z%85s~Amf=GkQqE%w@l@gRRGv;W2OgSHfY1A(bQAf6wb8v9V{s3D-v@bWy< zay%Hz4~|+pOgDQ%{9vbr@n8x+I5>W3{NUi_rSXHV@>2Lgmpsk^0U$702&mr_-?q#* zuFCOElVwX$4sBw8bF0{c@nC5*$ZL*0v-}OTf>(6>V1Yk=0fFHmfc*!DhwaSNOqY3| z2h(yqSdJg8_1B0WtUTeGdNoJ6SlnF==PtD{9;8<}^!Daj_0P8+KPZ-0nlIWEH(QR5 z63dH-9~9+}x68tBAOHl0kAQEUE${li(96T+Gc7LA*7@X(bJ|w=QeXrGYy{M!1%K&{2kqR`;Wzdl zoSasouLj<4THy!vmusv4s;8R7>KKNxO7 z>^~UB2swbjOhAD4)K6Mrf0-Mnrj~S$w#$6mtJsI}U}Zd5jUTMwh4{e=UQ^@KDAgc< z{Ra&aIL08**|W?gm-@G9$`1<9H|#&yT7R9#50?C;{Jzr)KPc$eTw$yHppK*w%Ns)c zpjci+{Gce0_`%k^J*F>3p4p5*Xr8rtkHEum&#C@UMlYnVt?TMq!7gh&*j9O_mLsLF zLr+z2qED(*>6hv+bgR0bV?7u1-vhjt<8^egI*X1|AE$S#*U;Pb+lBD=q^Yc*dLr$t z4q{u+qTi^i=z4V{U8^o<`+q=3tM}9PYCU6J`aI}C+DmQ7xShO=H4exo^6 z@YSxe=8HDNk5*I9=C(bY%Q=t!!1v0HY|qu|QaX?8|0(rWdfMDMIR?%24}u@9r*@!2 z)G2fk-v@W7hv^Y@H(jHCLWgr4((03Gk6NGJqE4hgPx$}4{r+TX%=UHEV`(ne;VtR_ zUYq(zI!1ksPEp@ttWTojc_#2d>K(MB+MHI=GVRod+sxm;mBxeZlxJ$2YCo-_o<;9q z48G5OzTshfMxzX$mF zcM0DQ`>IU}4K90&tNHz})}kHMr|5g?N`41>fEMuc>~HGVbS&?Qx>1aLRbMErHm29` z`_l2w_oesIOVv7shGCRnXs`Bvb5~Mxc`xx}n60^*vAB=>w)^Ox+}EANk!OOF2TdnWROP6a~PZ5s? z^Cy$x;wtGs)n(c{v-=#nS^wiZ_U5dA?5CG|>|tx^E=PV_e*e0K^xleoJO9m{U(_W+ zXR>2*yFK;~^wleSJnfgA&2JI9wsQMKUC$=QgRWZfTS6WG0-wEHGBM})&37;QHr*=v zm$rYra}6Cy&yCSp&ZBnm`p-V)oaI!u4(*>^Ov@|y&MW8gr#kD=UK!V{BHw$%E49}+ zT|-CEbNrfoSLlsif8Ez7lb@$eXn!6BJ|x^fU3CTRoxiWkx+1T3I@{4<+3#pc5Raa> z2K~Vr4@O!r@r9M1O^4C%)RJOuL8(28@*m31;y3s8Q}xeR^lN8-b#wOD`5j3ctwOKh zr`xqOF4X@KI+>m!X(?Y*FZ25UN6I1D(&^*axRmib+~=^%ns!z@{yKp?&GeMo;+2Pkio)9QZyYl=p}uzulkxBwX&`IcRS7ak@|~(%u${ zPd}eC|HODO-7+HjSm#1o7;K+M=*MawdTfId&h|YoGLdcLuLxhJe^u(cAiew)3G6?Z zf|ffk9Tm9WH*(9Fv-~Ac_Xj?I7vGfVaA$v@dkOD54XxLiN9SdW^ZT&kZ)^phzl`3V ze-AM>x{v(jYk#_smU``9K^M`ka=-7^>^e)2;x9$tq1%J~#|r+sdN^(C(jlKydwBZS znCpVT|&t}I(nzsh5PC-c|E1F|pB**xODTYNtDO7E9{FZI!o{T|3urek#Nw%tCfUd|j@ z|IN{(x8CkI(Ee%h9Q>WzUEi+yf^Cico1Vhoj30K&1^UEX3%y*OnU8#&I^6bLul7w_ z*_^*U*73=8d!L``3G_kr2O5vG>#u5i8<%Fk`CI1ysDF5E_0>T>-)KBYk5(UH4#bVW zue`}}VJ7p^4LA4Qm zUY*NdJ^CY9r%Vjq*DxoxfzKZMJiigd=u69UgHA!!I11eB&=Z+a?qP}EzGwY$8kLFN#3k3{HE0NNJqJYoOg@| z>7!yj8;l3lcSPJ8j0b5Q{>JmM8Ty_Whnb9?Tw1Ur-CY?JAeMg1(hK-maJYn`fT-^f}%ycpufn z>9%Bi{zgxit;nykKiat2c3t3-&z?jl*xYHwuF80j+qY?dQ!`!%!G?B>Gwz$(?I8cA-+Rk>kwdaWdu09S4~N{4 z$Aj2^(9t3)?c7xEKghEM>(8zHetTD?79H9-yCkXq7|Y&Q(a}R@_m#3e={j$@E_;^J zdtI91&e3bLtCQ@u`!7Fs2H6P>SCB=&F1DRS&Xr#bqArv3bU zwdWrF7?65R>SyS?V*5JZ9_w1{GaS)nw@bAik(k&Thxyw#&sM!S_YjSa=kFcbE}y)y zx!HA|kL)kkXHWP%j|Z#vg!sW~{%|^t9}Md2@O=#{iTwZRWFD|Vf#ZPJ;SvM|MvWv%qDfV!jWN&8m0Jhw(cFXH+ z{`Z$J@PpP$4lLhMJ8|sR@`f1QOH4Wy|J?Qn{NS~Ptp3IR|0wl&S}bB@jR$pk9ADUO z`!1q`<;C!WgXquiUxQ2>KiFP*aq)vvc`@;WK6!y3 z{Io$tAJuCEe&)N^_+3%|GvzZwF^^+goAi61u9b-?Bcdphml)j9%G_?qoAL0i?^7ZR!{9s+)Se_q;eZ5>- zk)kJ?F}inN@a2PW2t`S*Uz#5bmf!aRuYL9F{d?Rlk$ApnewRDTYrqfkOz&Q=;ndfq zBz~~-MEa#@AKO0A^#DJpdnB*^1b&cb1wG)w!>^yO)XM({J5B8)xHkX)ezZ?IHGND#HL(5m69>Rh9 z*M0vDg&(9B(LaNDy6y4#pN&@nKS*oxoQEDd1KV>#OiUF=(Ei?A-Umuvx4Hg;=J8L{ zp8fHlE!XC6>_0d-ntA-7aQ}Pe$(xe%_(8Ay@g|?^NMG^#&$mr}UVY?Zt-X=|k7sW( zdU~VEYrW-#^;_Qq3!Tz0j^~r({JG31dv2tsO68bqiupm0z6VVnzQ4EO6%0AnrxBh; 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 + * @since 04.05.2018 + * @copyright Copyright (C) Michael Gnehr 2018, All rights reserved + * @platform PHP + * @requirements PHP 7.0 or higher + */ + +require_once (dirname(__FILE__).'/interface.AuthHandler.php'); + +/** + * BasicAuth Handler + * handles Basic Authentification + * used on cron routes and routes without permission value + * @package Stura - Referat IT - ProtocolHelper + * @category framework + * @author michael gnehr + * @author Stura - Referat IT + * @since 04.05.2018 + * @copyright Copyright (C) Michael Gnehr 2018, All rights reserved + * @platform PHP + * @requirements PHP 7.0 or higher + */ +class AuthBasicHandler extends Singleton implements AuthHandler{ + + /** + * reference to own instance + * singelton instance of this class + * @var BasicAuthHandler + */ + private static $instance; + + /** + * user map + * @var array + */ + private static $BASICUSER; + + /** + * user array from config + * @var array + */ + private static $usermap; + + /** + * current user data + * keys + * eduPersonPrincipalName + * mail + * displayName + * groups + * @var array + */ + private $attributes; + + /** + * disable permissioncheck on require Auth/session creation + * used on routes without permission + * @var boolean + */ + private static $noPermCheck; + + /** + * class constructor + * private cause of singleton class + * @param bool $noPermCheck + */ + protected function __construct(){ + $noPermCheck = false; + //create session + session_start(); + self::$usermap = self::$BASICUSER; + self::$noPermCheck = $noPermCheck; + } + + /** + * return instance of this class + * singleton class + * return same instance on every call + * @param bool $noPermCheck + * @return BasicAuthHandler + */ + public static function getInstance(...$pars): AuthHandler{ + return parent::getInstance(...$pars); + } + + final static protected function static__set($name, $value){ + if (property_exists(get_class(), $name)) + self::$$name = $value; + else + die("$name ist keine Variable in " . get_class()); + } + private static $ADMINGROUP; + function isAdmin(){ + return $this->hasGroup(self::$ADMINGROUP); + } + + /** + * handle session and user login + */ + function requireAuth(){ + //check IP and user agent + if(isset($_SESSION['SILMPH']) && isset($_SESSION['SILMPH']['CLIENT_IP']) && isset($_SESSION['SILMPH']['CLIENT_AGENT'])){ + if ($_SESSION['SILMPH']['CLIENT_IP'] != $_SERVER['REMOTE_ADDR'] || $_SESSION['SILMPH']['CLIENT_AGENT'] != ((isset($_SERVER ['HTTP_USER_AGENT']))? $_SERVER['HTTP_USER_AGENT']: 'Unknown-IP:'.$_SERVER['REMOTE_ADDR'])){ + //die or reload page is IP isn't the same when session was created -> need new login + session_destroy(); + session_start(); + header("Refresh: 0"); + die(); + } + } else { + $_SESSION['SILMPH']['CLIENT_IP'] = $_SERVER['REMOTE_ADDR']; + $_SESSION['SILMPH']['CLIENT_AGENT'] = ((isset($_SERVER ['HTTP_USER_AGENT']))? $_SERVER['HTTP_USER_AGENT']: 'Unknown-IP:'.$_SERVER['REMOTE_ADDR']); + } + + if(!isset($_SESSION['SILMPH']['USER_ID'])){ + $_SESSION['SILMPH']['USER_ID'] = 0; + } + + if(!isset($_SESSION['SILMPH']['LAST_ACTION'])){ + $_SESSION['SILMPH']['LAST_ACTION'] = time(); + } + + if ( isset($_GET['logout']) && (strpos($_SERVER['REQUEST_URI'], '?logout=1') !== false || strpos($_SERVER['REQUEST_URI'], '&logout=1') !== false )){ + session_destroy(); + session_start(); + header('WWW-Authenticate: Basic realm="'.BASE_TITLE.' Please Login"'); + header('HTTP/1.0 401 Unauthorized'); + echo 'You have no permission to access this page.'; + die(); + } + + if(!isset($_SESSION['SILMPH']['MESSAGES'])){ + $_SESSION['SILMPH']['MESSAGES'] = array(); + } + if (!self::$noPermCheck && !isset($_SERVER['PHP_AUTH_USER'])){ + $_SESSION['SILMPH']['USER_ID'] = 0; + header('WWW-Authenticate: Basic realm="'.BASE_TITLE.' Please Login"'); + header('HTTP/1.0 401 Unauthorized'); + echo 'You are not allowd to access this page. Please Login.'; + die(); + } else { + if (!self::$noPermCheck) { + $_SESSION['SILMPH']['USER_ID'] = 0; + if (isset(self::$usermap[$_SERVER['PHP_AUTH_USER']]) && + self::$usermap[$_SERVER['PHP_AUTH_USER']]['password'] == $_SERVER['PHP_AUTH_PW']){ + $this->attributes = array_slice(self::$usermap[$_SERVER['PHP_AUTH_USER']], 1 ); + } else { + header('WWW-Authenticate: Basic realm="basic_'.BASE_TITLE.'_realm"'); + header('HTTP/1.0 401 Unauthorized'); + echo 'You are not allowd to access this page. Please Login.'; + die(); + } + } else { + $this->attributes = [ + 'displayName' => 'Anonymous', + 'mail' => '', + 'groups' => ['anonymous'], + 'eduPersonPrincipalName' => ['nologin'], + ]; + } + } + } + + /** + * check group permission - die on error + * return true if successfull + * @param string $groups String of groups + * @return bool true if the user has one or more groups from $group + */ + function requireGroup($group){ + $this->requireAuth(); + if (!$this->hasGroup($group)){ + header('WWW-Authenticate: Basic realm="'.BASE_TITLE.' Please Login"'); + header('HTTP/1.0 401 Unauthorized'); + echo 'You have no permission to access this page.'; + die(); + } + return true; + } + + /** + * check group permission - return result of check as boolean + * @param string $groups String of groups + * @param string $delimiter Delimiter of the groups in $group + * @return bool true if the user has one or more groups from $group + */ + function hasGroup($group, $delimiter = ","){ + $this->requireAuth(); + $attributes = $this->getAttributes(); + if (count(array_intersect(explode($delimiter, strtolower($group)), array_map("strtolower", $attributes["groups"]))) == 0){ + return false; + } + return true; + } + + /** + * return log out url + * @return string + */ + function getLogoutURL(){ + return BASE_URL.$_SERVER['REQUEST_URI'] . '?logout=1'; + } + + /** + * send html header to redirect to logout url + * @param string $param + */ + function logout(){ + header('Location: '. $this->getLogoutURL()); + die(); + } + + /** + * return current user attributes + * @return array + */ + function getAttributes(){ + return $this->attributes; + } + + /** + * return username or user mail address + * if not set return null + * @return string|NULL + */ + function getUsername(){ + $attributes = $this->getAttributes(); + if (isset($attributes["eduPersonPrincipalName"]) && isset($attributes["eduPersonPrincipalName"][0])) + return $attributes["eduPersonPrincipalName"][0]; + if (isset($attributes["mail"]) && isset($attributes["mail"])) + return $attributes["mail"]; + return null; + } + + /** + * return user displayname + * @return string + */ + function getUserFullName(){ + $this->requireAuth(); + return $this->getAttributes()["displayName"]; + } + + /** + * return user mail address + * @return string + */ + function getUserMail(){ + $this->requireAuth(); + return $this->getAttributes()["mail"]; + } +} diff --git a/lib/class.ErrorHandler.php b/lib/class.ErrorHandler.php new file mode 100644 index 0000000..06d79e0 --- /dev/null +++ b/lib/class.ErrorHandler.php @@ -0,0 +1,256 @@ + + * @since 07.05.2018 + * @copyright Copyright Referat IT (C) 2018 - All rights reserved + */ +class ErrorHandler +{ + /** + * contains default error messages + * @var array + */ + private static $default = [ + 403 => [ + 'headline' => 'Zugriff verweigert', + 'json' => 'Access denied.', + 'msg' => 'Sie sind nicht berechtigt auf diesen Inhalt zuzugreifen.', + 'additional' => 'Ihr momentaner Benutzerstatus verfügt nicht über die benötigten Berechtigungen, um auf diese Seite zuzugreifen. Melden Sie sich mit einem Nutzer an, der über entsprechende Zugriffe verfügt.' + ], + 404 => [ + 'headline' => 'Uuups...', + 'json' => 'Not found.', + 'msg' => 'Da ist wohl etwas schief gegangen. Die angeforderte Seite konnte nicht gefunden werden.', + 'additional' => 'Möglicherweise sind Sie über einen veralteten Link auf diese Seite gestoßen. Informieren Sie doch bitte den entsprechenden Seitenbetreiber, so dass dieser entfernt, oder korrigiert werden kann.' + ], + 418 => [ + 'headline' => 'Little Joke', + 'json' => "I'm a teapot", + 'msg' => "I'm a teapot. Please use a coffee pot.", + 'additional' => [ + 'headline' => 'More Information', + 'msg' => 'For additional information google "http error code 418" please..' + ] + ] + ]; + + // member variables -------------------------- + /** + * @var array $routeInfo current route information, if called in router + */ + private $routeInfo; + + /** + * class constructor + * @param array $routeInfo contains controller -> 'error' mostly, action -> error|http code, and optional msg + */ + function __construct($routeInfo) + { + $this->routeInfo = $routeInfo; + if ($this->routeInfo['controller'] != 'error'){ + $routeInfo = [ + 'method' => $_SERVER['REQUEST_METHOD'], + 'controller' => 'error', + 'action' => '404', + ]; + } + } + // ------------------------------------------- + + public static function _errorLog($msg, $caller = ''){ + error_log( date_create()->format('Y-m-d H:i:s')."\t". + ($caller?$caller."\t":''). + strip_tags(str_replace('
    ', "\n\t", $msg))); + } + + public static function _errorExit($msg){ + $re = '/(\.php$|\.phtml$|\/(.)+\/)/'; + //prepare message + $stack = debug_backtrace(null, 3); + $line0 = (isset($stack[0]['line'])?$stack[0]['line']:' ?? '); + $file0 = (isset($stack[0]['file'])? preg_replace($re, '', $stack[0]['file']):' ?? '); + $line1 = (isset($stack[1]['line'])?$stack[1]['line']:' ?? '); + $file1 = (isset($stack[1]['file'])? preg_replace($re, '', $stack[1]['file']):' ?? '); + //create message + if ($_SERVER['REQUEST_METHOD'] == 'POST'){ + $msg_log = "$file0 [$line0]: $msg\n". + "(Called in: $file1 [$line1])"; + if ($GLOBALS["DEV"]){ + $msg = "$file0 [$line0]: $msg\n". + "(Called in: $file1 [$line1])"; + } + } else { + $msg_log = "$file0 [$line0]:
    $msg
    \n". + "(Called in: $file1 [$line1])

    "; + if ($GLOBALS["DEV"]){ + $msg = "$file0 [$line0]:
    $msg
    \n". + "(Called in: $file1 [$line1])

    "; + } + } + // log message + self::_errorLog($msg_log); + // echo message + ErrorHandler::_renderError($msg); + exit(-1); + } + + /** + * render html error page | json error + * @param string $msg + * @param number $htmlCode + */ + public static function _renderError($msg, $htmlCode = 403){ + $info = ['code' => $htmlCode]; + if ($msg!==NULL){ + $info['msg']= $msg; + } + return self::_render($info); + } + + // ------------------------------------------- + + /** + * set html code response header + * @param integer|array $info error info, see _render function for more information + */ + private static function _setErrorCode($info){ + if (is_integer($info['code'])){ + $code = $info['code']; + http_response_code ($code); + } elseif (is_numeric($info)){ + http_response_code ($info); + } + } + + /** + * handle route information and incomplete error information and set defaults + * @param array $info + * @return array + */ + private static function _parseInfo($info){ + if (isset($info['controller']) && isset($info['action']) + && $info['controller'] == 'error' && is_numeric($info['action'])){ + $info['code'] = intval($info['action'], 10); + } + if (!isset($info['method'])) $info['method'] = $_SERVER['REQUEST_METHOD']; + if (!isset($info['code'])){ + $info['code'] = 404; + if (isset($info['controller']) && isset($info['action']) && + !isset($info['msg']) && $info['method'] != 'POST'){ + $info['msg'] = 'Route: '.$info['controller'].' . '.$info['action'].' konnte nicht gefunden werden.'; + } + } + + if (!isset($info['headline'])){ + $info['headline'] = isset(self::$default[$info['code']]['headline'])? self::$default[$info['code']]['headline']: ''; + } + if (!isset($info['json'])){ + $info['json'] = isset(self::$default[$info['code']]['json'])? self::$default[$info['code']]['json']: 'Unknown Error.'; + } + if (!isset($info['additional']) && isset($info['controller']) && $info['controller'] == 'error' && isset(self::$default[$info['code']]['additional'])){ + $info['additional'] = self::$default[$info['code']]['additional']; + } + if (!isset($info['image']) && isset(self::$default[$info['code']]['image'])){ + $info['image'] = self::$default[$info['code']]['image']; + } + //disable image with false + if (array_key_exists('image', $info)&&!$info['image']){ + unset($info['image']); + } + if (!isset($info['msg']) + && $info['method'] == 'POST' + && isset(self::$default[$info['code']]['json']) + ){ + $info['msg'] = self::$default[$info['code']]['json']; + } elseif (!isset($info['msg']) + && isset(self::$default[$info['code']]['msg']) + ){ + $info['msg'] = self::$default[$info['code']]['msg']; + } if (!isset($info['msg'])){ + $info['msg'] = 'Unknown Error.'; + } + return $info; + } + + // ----------------------------------------------------- + + /** + * detect request method and calls render function + * may set routeInfo + * dynamic version + * @param array $info contains controller -> 'error' mostly, action -> error|http code, and optional msg + */ + public function render($info = NULL){ + if ($info === NULL) $info = $this->routeInfo; + self::_render($info); + } + + /** + * detect request method and calls render function + * static version + * @param array $info + * could be routerInfo: [ + * 'controller' => 'error' mostly | 'route:controller', + * 'action' => error|http code|route:action, + * 'msg' => 'message' [optional] + * or parts of error information: [ + * 'code' => 418, + * 'headline' => 'little joke', + * 'json' => "I'm a teapot", + * 'msg' => "I'm a teapot. Please use a coffee pot.", + * 'additional' => [ + * 'headline' => 'More Information', + * 'msg' => 'For additional information google "http error code 418".' + * ] + * ] + * @param boolean $default + */ + public static function _render($info){ + $info = self::_parseInfo($info); + if (isset($info['method']) && $info['method'] == 'POST' || !isset($info['method']) && $_SERVER['REQUEST_METHOD'] == 'POST'){ + self::_renderJson($info); + } else { + self::_renderErrorPage($info); + } + } + + /** + * render json error page + * @param array $info error info, see _render function for more information + */ + public static function _renderJson($info){ + //create return message + $out = ['success' => false, 'msg' => $info['msg'], 'status' => $info['code'] ]; + //set html response code + self::_setErrorCode($info); + //echo resonse + require_once dirname(__FILE__) . '/class.JsonController.php'; + JsonController::print_json($out); + die(); + } + + /** + * render json error page + * @param array $param error info, see _render function for more information + */ + public static function _renderErrorPage($param){ + //UI globals + //global $URIBASE, $ADMINGROUP, $subtype; + $routeInfo = ['controller' => 'error']; + // set html response code + self::_setErrorCode($param); + + // include header + include SYSBASE."/template/html/header.phtml"; + //include error template + include SYSBASE."/template/html/error.phtml"; + // include footer + include SYSBASE."/template/html/footer.phtml"; + } +} + +?> \ No newline at end of file diff --git a/lib/class.Helper.php b/lib/class.Helper.php new file mode 100644 index 0000000..92042c1 --- /dev/null +++ b/lib/class.Helper.php @@ -0,0 +1,208 @@ + false, + 'code' => (-1), + 'data' => '', + ]; + // post to pdf builder =================================== + // use 'http' even if request is done to https://... + $options = array( + 'http' => array( + 'ignore_errors' => true, + 'header' => [ + "Content-type: application/x-www-form-urlencoded; charset=UTF-8", + ], + 'method' => 'POST', + 'content' => http_build_query($data), + ) + ); + if ($auth) { + $options['http']['header'][] = "Authorization: Basic ".(($auth_encode)?base64_encode($auth):$auth); + } + $context = stream_context_create($options); + //run post + $postresult = file_get_contents($url, false, $context); + + //handle result + $http_response_header = (isset($http_response_header))?$http_response_header:NULL; + if(is_array($http_response_header)) + { + $parts=explode(' ',$http_response_header[0]); + if(count($parts)>1) //HTTP/1.0 + $result['code'] = intval($parts[1]); //Get code + } + //error ? + if ($result['code'] === 200 && $postresult) { + $result['data'] = json_decode($postresult, true); + if ($result['data'] === NULL){ + $result['data'] = $postresult; + } + $result['success'] = true; + } elseif ($postresult){ + $result['data'] = strip_tags($postresult); + } + return $result; + } + + /** + * do post request + * uses curl + * @param string $url + * @param array $data + * @param string $auth + * @param boolean $auth_encode + */ + public static function do_post_request2($url, $data = NULL, $auth = NULL, $auth_encode = false){ + $result = [ + 'success' => false, + 'code' => (-1), + 'data' => '', + ]; + + //connection + $ch = curl_init(); + + $header = [ + "Content-type: application/x-www-form-urlencoded; charset=UTF-8" + ]; + if ($auth) { + curl_setopt($ch, CURLOPT_HTTPAUTH, CURLAUTH_BASIC); + curl_setopt($ch, CURLOPT_USERPWD, (($auth_encode)?$auth : base64_decode($auth))); + } + + //set curl options + curl_setopt($ch, CURLOPT_URL, $url); + curl_setopt($ch, CURLOPT_POST, true); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_HTTPHEADER, $header); + if ($data) { + $tmp_data = http_build_query($data); + curl_setopt($ch,CURLOPT_POSTFIELDS, $tmp_data); + } + + //run post + $postresult = curl_exec($ch); + + //handle result + $result['code'] = curl_getinfo($ch, CURLINFO_HTTP_CODE); + + //close connection + curl_close($ch); + + if ($result['code'] === 200 && $postresult) { + $result['data'] = json_decode($postresult, true); + if ($result['data'] === NULL){ + $result['data'] = $postresult; + } + $result['success'] = true; + } elseif ($postresult){ + $result['data'] = strip_tags($postresult); + } + + return $result; + } + + /** + * generates secure random hex string of length: 2*$length + * @param integer $length 0.5 string length + * @return NULL|string + */ + public static function generateRandomString($length) { + if (!is_int($length)){ + throw new Exception('Invalid argument type. Integer expected.'); + return null; + } + if (version_compare(PHP_VERSION, '7.0.0') >= 0){ + return bin2hex(random_bytes($length)); + } else { + return bin2hex(openssl_random_pseudo_bytes($length)); + } + } + + /** + * Print all Profiling Flags from prof_flag() + */ + private static $prof_timing = []; + private static $prof_names = []; + private static $prof_last_count = -1; + private static $prof_last_data = []; + /** + * Print all Profiling Flags from prof_flag() + */ + public static function prof_print($echo = true, $memory_usage = false){ + $sum = 0; + $size = count(self::$prof_timing); + if ($size != self::$prof_last_count){ + $out = ''; + for ($i = 0; $i < $size - 1; $i++){ + $out .= "".self::$prof_names[$i]."
    "; + $sum += self::$prof_timing[$i + 1] - self::$prof_timing[$i]; + $out .= sprintf("   %f
    ", self::$prof_timing[$i + 1] - self::$prof_timing[$i]); + } + $out .= "".self::$prof_names[$size-1]."
    "; + $out = '

    Ladezeit: ' . sprintf("%f", $sum) . '

    ' . $out; + if ($memory_usage){ + $out.= '
    Memory Usage

    '.self::formatFilesize(memory_get_usage()).'

    '; + } + $out .= "
    "; + + $prof_last_data = ['sum'=>$sum,'size'=>$size,'html'=>$out,'raw'=>['timing'=>self::$prof_timing,'names'=>self::$prof_names] ]; + $prof_last_count = $size; + } + if ($echo) echo $prof_last_data['html']; + return $prof_last_data; + } + + /** + * add profiling flag + */ + public static function prof_flag($str){ + self::$prof_timing[] = microtime(true); + self::$prof_names[] = $str; + } + + /** + * prettify filesize + * calculate short filesize from byte file size + * @param numeric $filesize in bytes + * @return string NaN| prettyfied filesize + */ + public static function formatFilesize($filesize){ + $unit = array('Byte','KB','MB','GB','TB','PB'); + $standard = 1024; + if(is_numeric($filesize)){ + $count = 0; + while(($filesize / $standard) >= 0.9){ + $filesize = $filesize / $standard; + $count++; + } + return round($filesize,2) .' '. $unit[$count]; + } else { + return 'NaN'; + } + } +} + +?> \ No newline at end of file diff --git a/lib/class.JsonController.php b/lib/class.JsonController.php new file mode 100644 index 0000000..7210d21 --- /dev/null +++ b/lib/class.JsonController.php @@ -0,0 +1,77 @@ + + * @since 17.02.2018 + * @copyright Copyright (C) 2018 - All rights reserved + * @platform PHP + * @requirements PHP 7.0 or higher + */ +class JsonController { + /** + * json result of the function + * @var array + */ + protected $json_result; + + // ================================================================================================ + + /** + * private class constructor + * implements singleton pattern + */ + function __construct(){ + } + + /** + * dummy function for inheritance + * so mother controller may implements a translator pattern + */ + function translate ($in){ + return $in; + } + + // ================================================================================================ + + /** + * returns 403 access denied in json format + */ + function json_access_denied($message = false){ + http_response_code (403); + $this->json_result = array('success' => false, 'eMsg' => $this->translate( ($message)? $message : 'Access Denied.')); + $this->print_json_result(); + } + + /** + * returns 404 not found in html format + * @param false|string $message (optional) error message + */ + function json_not_found($message = false){ + http_response_code (404); + $this->json_result = array('success' => false, 'eMsg' => $this->translate( ($message)? $message : 'Page not Found.')); + $this->print_json_result(); + } + + /** + * echo json result stored in $this->json_result + * @param boolean $jsonHeader, default: false + */ + protected function print_json_result($jsonHeader = false){ + self::print_json($this->json_result, $jsonHeader); + } + + /** + * + * @param array $json json data + * @param boolean $jsonHeader, default: true + */ + public static function print_json($json, $jsonHeader = true){ + if ($jsonHeader) header("Content-Type: application/json"); + echo json_encode($json, JSON_HEX_QUOT | JSON_HEX_TAG); + die(); + } +} \ No newline at end of file diff --git a/lib/class.Router.php b/lib/class.Router.php new file mode 100644 index 0000000..0d802cf --- /dev/null +++ b/lib/class.Router.php @@ -0,0 +1,217 @@ +requested_path = isset($_SERVER['PATH_INFO']) ? ($_SERVER['PATH_INFO'] != '/' ? $_SERVER['PATH_INFO'] : '/') : '/'; + if (is_string($not_found_route)){ + $this->not_found_route = $not_found_route; + }else{ + $this->not_found_route = "404"; + } + global $routing; + $this->routes = $routing; + if (isset($this->routes['not_found'])){ + $this->not_found_route = $this->routes['not_found']; + } + } + + /** + * @return the $requested_path + */ + public function getRequested_path(){ + return $this->requested_path; + } + + /** + * @return the $not_found_route + */ + public function getNot_found_route(){ + return $this->not_found_route; + } + + /** + * @param string $not_found_route + */ + public function setNot_found_route($not_found_route){ + if (is_string($not_found_route)){ + $this->not_found_route = $not_found_route; + } + } + + // public member functions -------------------------------- + + public function route($path = null){ + if ($path === null || !is_string($path)){ + $path = $this->requested_path; + } + //route request + $routeInfo = $this->_route($path, [$this->routes]); + + //check request method ------------------ + if ($routeInfo['controller'] != 'error' + && strpos(strtoupper($routeInfo['method']), strtoupper($_SERVER['REQUEST_METHOD'])) === false){ + $routeInfo = [ + 'method' => $_SERVER['REQUEST_METHOD'], + 'controller' => 'error', + 'action' => '403', + ]; + } + return $routeInfo; + } + + // private member functions ------------------------------- + + /** + * @param $path + * @param $routes + * + * @return array + * @throws Exception + */ + private function _route($path, $routes){ + $current = null; + $next = null; + if (mb_strpos($path, '/') !== false){ + $current = mb_substr($path, 0, mb_strpos($path, '/')); + $next = mb_substr($path, mb_strpos($path, '/') + 1); + }else{ + $current = $path; + $next = false; + } + + //remember current default route + $old_not_found = $this->not_found_route; + $ret = ['path' => $current]; + //handle current + $found = false; + //handle all matches on current level + foreach ($routes as $route){ + // throw error if path or type not set + if (!isset($route['path'])){ + echo '
    Falsch konfigurierte Route. Parameter "path" fehlt:
    '; + echo '
    ';
    +                var_export($route);
    +                echo '
    '; + throw new Exception("Router: Error on configuration. Parameter 'path' is missing."); + } + if (!isset($route['type'])){ + echo '
    Falsch konfigurierte Route. Parameter "type" fehlt:
    '; + echo '
    ';
    +                var_export($route);
    +                echo '
    '; + throw new Exception("Router: Error on configuration. Parameter 'type' is missing."); + } + //check if current path matches route path + if (($route['type'] === 'path' && $route['path'] === $current + || $route['type'] === 'pattern' && preg_match('/^' . $route['path'] . '$/', $current)) + && !isset($route['is_suffix'])){ + $found = true; + if (isset($route['controller'])) $ret['controller'] = $route['controller']; + if (isset($route['action'])) $ret['action'] = $route['action']; + if (isset($route['method'])) $ret['method'] = $route['method']; + if (isset($route['not_found'])) $this->not_found_route = $route['not_found']; + + foreach ($route as $k => $v){ + if (!preg_match('/^('.self::$reserved_keywords.')$/', $k)){ + $ret[$k] = $v; + } + } + //is pattern match + $matches = null; + if ($route['type'] === 'pattern' && preg_match('/^' . $route['path'] . '$/', $current, $matches)){ + $ret[$route['param']] = $matches[(isset($route['match'])) ? $route['match'] : 0]; + } + }else if (isset($route['is_suffix']) // suffix match - pattern only + && $route['type'] === 'pattern'){ + $tmpCurrent = $current . (($next) ? '/' . $next : ''); + $matches = null; + if (preg_match('/^' . $route['path'] . '$/', $tmpCurrent, $matches)){ + $found = true; + if (isset($route['controller'])) $ret['controller'] = $route['controller']; + if (isset($route['action'])) $ret['action'] = $route['action']; + if (isset($route['method'])) $ret['method'] = $route['method']; + if (isset($route['not_found'])) $this->not_found_route = $route['not_found']; + foreach ($route as $k => $v){ + if (!preg_match('/^('.self::$reserved_keywords.')$/', $k)){ + $ret[$k] = $v; + } + } + $ret[$route['param']] = $matches[(isset($route['match'])) ? $route['match'] : 0]; + } + } + if ($found && isset($route['value']) && isset($route['param'])){ + $ret[$route['param']] = $route['value']; + } + + //may handle children, if $routes contains children && path is not false or empty + if ($found && !isset($route['is_suffix']) && isset($route['children']) && $next != false){ + //handle children + $tmpRet = $this->_route($next, $route['children']); + //merge children if not null or false or not found flag set + if ($tmpRet && !isset($tmpRet['not_found'])){ + $ret['path'] .= '/' . $tmpRet['path']; + foreach ($tmpRet as $k => $v){ + if ($k != 'path'){ + $ret[$k] = $v; + } + } + }else if (isset($tmpRet['not_found'])){ + $ret['not_found'] = false; + $ret['controller'] = $tmpRet['controller']; + $ret['action'] = $tmpRet['action']; + }else{ + // else not found exception + $ret['not_found'] = true; + $ret['controller'] = 'error'; + $ret['action'] = $this->not_found_route; + } + } + + //reset not found variable + $this->not_found_route = $old_not_found; + //break if route matches + if ($found == true) break; + } + // path not found route + if (!$found && !isset($ret['not_found'])){ + $ret['not_found'] = true; + $ret['controller'] = 'error'; + $ret['action'] = $this->not_found_route; + } + return $ret; + } +} + +?> \ No newline at end of file diff --git a/lib/class.Singleton.php b/lib/class.Singleton.php new file mode 100644 index 0000000..c6bdf95 --- /dev/null +++ b/lib/class.Singleton.php @@ -0,0 +1,107 @@ + 0){ + die("Instance allready initialized, no Parameter Allowed after first init"); + } + return self::$instances[static::class]; + } + } + + /** + * @param $confArray array with variablename => variablevalue where variablename is a private static Variable in + * child Classes of Singleton + * + * @throws Exception + */ + final private static function setCredentials($confArray){ + + $visVars = get_class_vars(static::class); // gets all public and protected vars + foreach ($confArray as $varName => $value){ + if (property_exists(static::class, $varName)) + if (!in_array($varName, array_keys($visVars))) + static::static__set($varName, $value); + else + die("static \$$varName ist nicht private in Klasse " . static::class); + else + die("static \$$varName existiert nicht in Klasse " . static::class); + } + } + + abstract static protected function static__set($name, $value); + + /** + * @param array $conf Keys are the Names of Singleton-Childs and inside there will be a key-value pair with Name + * and Value of private static variable from Child. + */ + public static function configureAll($conf){ + if (!is_array($conf)){ + die("No array passed in Singelton::configureAll()"); + } + foreach ($conf as $className => $variables){ + if (!is_subclass_of($className, "Singleton")) + die("$className is not a Child of Singleton!"); + $className::setCredentials($variables); + self::$hasCredentialsSet[$className] = true; + } + } + + final public function __clone(){ + //No cloning possible + } + + final public function __debugInfo(){ + //No debug Info + } + + /* wanted Implementation in child to access (and set) PRIVATE static variables*/ + /* + final static protected function static__set($name, $value){ + if(property_exists(get_class(), $name)) + self::$$name = $value; + else + throw new Exception("$name ist keine Variable in ".get_class()); + } + */ +} + + +/* + * Example for Implementation: +class Test extends Singleton { + private static $test; + final static protected function static__set($name, $value){ + if(property_exists(get_class(), $name)) + self::$$name = $value; + else + throw new Exception("$name ist keine Variable in ".get_class()); + } +} + +ConfigHandler::configure("Test"); +*/ + + diff --git a/lib/class.TexBuilder.php b/lib/class.TexBuilder.php new file mode 100644 index 0000000..07790cb --- /dev/null +++ b/lib/class.TexBuilder.php @@ -0,0 +1,351 @@ + [ + 'projekt' => ['arraymap', + 'required' => true, + 'map' => [ + 'created' => [ 'date', + 'format' => 'Y-m-d H:i:s', + 'parse' => 'Y-m-d H:i:s', + 'error' => 'Ungültiges Projekt Datum.' + ], + 'name' => [ 'name', + 'maxlength' => '255', + 'error' => 'Ungültiger Projekt Name.' + ], + 'org' => [ 'name', + 'maxlength' => '255', + 'error' => 'Ungültiger Organisations Name.' + ], + 'id' => ['integer', + 'min' => '1', + 'error' => 'Ungültige Projekt ID.' + ], + ], + ], + 'auslage' => ['arraymap', + 'required' => true, + 'map' => [ + 'created' => [ 'date', + 'format' => 'Y-m-d H:i:s', + 'parse' => 'Y-m-d H:i:s', + 'error' => 'Ungültiges Auslagen Datum.' + ], + 'created_by' => [ 'name', + 'maxlength' => '255', + 'error' => 'Ungültiger Auslagen Name.' + ], + 'name' => [ 'name', + 'maxlength' => '255', + 'error' => 'Ungültiger Auslagen Name.' + ], + 'id' => ['integer', + 'min' => '1', + 'error' => 'Ungültige Auslagen ID.' + ], + 'zahlung' => ['arraymap', + 'required' => true, + 'map' => [ + 'name' => [ 'name', + 'maxlength' => '255', + 'error' => 'Ungültiger Zahlungs Name.' + ], + ], + ], + ], + ], + 'belege' => ['array', 'optional', + 'minlength' => 1, + 'key' => [ 'regex', + 'pattern' => '/^(\d+)$/' + ], + 'validator' => ['arraymap', + 'required' => true, + 'map' => [ + 'id' => ['integer', + 'min' => '1', + 'error' => 'Ungültige Beleg ID.' + ], + 'short' => ['integer', + 'min' => '1', + 'error' => 'Ungültige Beleg NR.' + ], + 'date' => [ 'date', + 'format' => 'Y-m-d H:i:s', + 'parse' => 'Y-m-d', + 'error' => 'Ungültiges Beleg Datum.' + ], + 'desc' => [ 'text', + 'strip', + 'trim', + ], + 'file_id' => ['integer', + 'min' => '1', + 'error' => 'Ungültige Beleg File ID.' + ], + 'file' => [ 'text', + 'strip', + 'trim', + ], + ] + ] + ], + ], + ]; + + /** + * + * @var Validator + */ + private $validator; + + /** + * is error and error message + * @var bool|string + */ + private $error = false; + + /** + * last valid key + * @var bool|string + */ + private $last_key = false; + + /** + * @var binary pdf file data + */ + private $binary_build; + + // getter / setter -------------------- + + /** + * @return the $error + */ + public function getError() + { + return $this->error; + } + + // constructor -------------------- + + /** + * class constructor + */ + function __construct() + { + $this->validator = new Validator(); + $this->error = false; + } + + /** + * check if pdf builder exists + * @return bool + */ + public function builderExist($key){ + return (isset(self::$valiMap[$key])); + } + + /** + * validate Post or $data variables by builderKey + * @param string $key + * @param array $data + * @return bool + */ + public function validate($key, $data = null){ + if (!$this->builderExist($key)){ + $this->error = 'Unknown builder.'; + $this->last_key = false; + return false; + } else { + $this->validator->validateMap(($data)?$data : $_POST, self::$valiMap[$key]); + if (!$this->validator->getIsError()){ + $this->error = false; + $this->last_key = $key; + return true; + } else { + $this->error = $this->validator->getLastErrorMsg(); + $this->last_key = false; + return false; + } + } + } + + public function getValidated(){ + return $this->validator->getFiltered(); + } + + /** + * validate Post or $data variables by builderKey + * alias of validate + * @param string $key + * @param array $data + * @return bool + */ + public function setData($key, $data = null){ + return $this->validate($key, $data); + } + + /** + * get binary pdf data + * @param bool $echo + * return binary|NULL + */ + public function getBinary($echo = false) { + if ($this->binary_build){ + if ($echo) echo $this->binary_build; + return $this->binary_build; + } + return NULL; + } + + /** + * get pdf as base64 string + * @param bool $echo + * return string + */ + public function getBase64($echo = false) { + if ($this->binary_build){ + $t = base64_encode($this->getBinary(false)); + if ($echo) echo $t; + return $t; + } + return NULL; + } + + // build pdf ------------------------------------ + + /** + * build pdf + */ + public function build() { + if (!$this->error && $this->last_key){ + //create images files + $files = []; + if(isset($this->validator->getFiltered()['belege'][0]['file'])){ + foreach ($this->validator->getFiltered()['belege'] as $b){ + if(($f = tempnam(sys_get_temp_dir(), 'tex-tmp-')) === false) { + $this->error = "Failed to create temporary file"; + foreach ($files as $v){ + if(file_exists($v)) unlink($v); + } + return; + } + if(file_exists($f)) unlink($f); + file_put_contents($f.'.pdf', base64_decode($b['file'])); + $files[str_pad($b['id'], 3, "0", STR_PAD_LEFT ).'-B'.$b['short']] = $f.'.pdf'; + } + } + + $validated = $this->validator->getFiltered(); + $validated['files'] = $files; + //get tex code + $tex = self::_renderTex($this->last_key, $validated); + //render twice + $this->_createPDF($tex); + //remove inline images + foreach ($files as $v){ + if(file_exists($v)) unlink($v); + } + return true; + } + } + + // private ---------------------------------------- + + /** + * render tex code + * @param string $key + * @param array $param + */ + private static function _renderTex($key, $param){ + $file = SYSBASE.'/template/tex/'.$key.'.phpTex'; + $tex = ''; + if(file_exists($file)) { + ob_start(); + include $file; + $tex = ob_get_clean(); + } + return $tex; + } + + /** + * create pdf file set binary code + */ + private function _createPDF($tex_code){ + //create temp file + if(($f = tempnam(sys_get_temp_dir(), 'tex-')) === false) { + $this->error = "Failed to create temporary file"; + return; + } + + $tex_f = $f . ".tex"; + $aux_f = $f . ".aux"; + $log_f = $f . ".log"; + $pdf_f = $f . ".pdf"; + + //write file to tmp file + file_put_contents($tex_f, $tex_code); + //switch to directory + chdir(sys_get_temp_dir()); + + //run command + $shellcmd = "pdflatex \"\\input{" . $tex_f . '}"'; + + $status = -100; + exec($shellcmd, $out, $status); + //render twice -> page numbers, etc. + exec($shellcmd, $out, $status); + + //unlink files + if(file_exists($tex_f)) unlink($tex_f); + if(file_exists($aux_f)) unlink($aux_f); + if(file_exists($log_f)) unlink($log_f); + + // Test here + if(!file_exists($pdf_f)) { + //unlink files + unlink($f); + $this->error = [ + 'msg' => "Output was not generated and latex returned: $status.", + 'code' => $status, + 'log' => $out, + 'tex' => $tex_code, + ]; + return; + } + + //load file to memory + $this->binary_build = file_get_contents ( $pdf_f ); + + //unlink files + unlink($pdf_f); + unlink($f); + } + + /** + * escape latex invalid letters + * @param string in + * @return string + */ + private static function texEscape($in){ + return str_replace( + ['\\', '~', '_', '%', '$', '&', '^', '"', '{', '}'], + ['\textbackslash', '\textasciitilde', '\_', '\%', '\$', '\&', '^', "''", '\{', '\}'], + $in + ); + } +} + +?> \ No newline at end of file diff --git a/lib/class.Validator.php b/lib/class.Validator.php new file mode 100644 index 0000000..4973f7e --- /dev/null +++ b/lib/class.Validator.php @@ -0,0 +1,1036 @@ + + * @since 17.02.2018 + * @copyright Copyright (C) 2018 - All rights reserved + * @platform PHP + * @requirements PHP 7.0 or higher + */ +class Validator { + + /** + * validator tracks if last test was successfull or not + * boolean + */ + protected $isError; + + /** + * last error message + * -> short error message + * -> on json retuned to sender + * string + */ + protected $lastErrorMsg; + + /** + * last stores last map key if map validation is used + */ + protected $lastMapKey = ''; + + /** + * last error description + * -> error description + * string + */ + protected $lastErrorDescription; + + /** + * last error message + * string + */ + protected $lastErrorCode; + + /** + * filter may sanitize inputs + * mixed + */ + protected $filtered; + + /** + * class constructor + */ + function __contstruct(){ + } + + // ========================================== + + /** + * set validation status + * + * @param boolean $isError error flag + * @param integer $code html code + * @param string $msg short message + * @param string $desc error description + */ + private function setError($isError, $code=0, $msg='', $desc=''){ + $this->isError = $isError; + $this->lastErrorCode = $code; + $this->lastErrorMsg = $msg; + $this->lastErrorDescription = ($desc == '')? $msg : $desc; + return $isError; + } + + /** + * @return the $isError + */ + public function getIsError() + { + return $this->isError; + } + + /** + * @return the $lastErrorMsg + */ + public function getLastErrorMsg() + { + return $this->lastErrorMsg; + } + + /** + * @return the $lastErrorDescription + */ + public function getLastErrorDescription() + { + return $this->lastErrorDescription; + } + + /** + * @return the $lastErrorCode + */ + public function getLastErrorCode() + { + return $this->lastErrorCode; + } + + /** + * @return the $lastMapKey + */ + public function getLastMapKey() + { + return $this->lastMapKey; + } + + /** + * filter may sanitize input values are stored here + * Post validators will create sanitized array + * @return the $filtered + */ + public function getFiltered($key = NULL) + { + if ($key === NULL) + return $this->filtered; + else + return $this->filtered[$key]; + } + + // ========================================== + + /** + * call selected validator function + * @param mixed $value + * @param string $validator + * @return boolean value is ok + */ + public function validate($value, $validator){ + $validatorName = (is_array($validator))? $validator[0] : $validator; + $validatorParams = (is_array($validator))? array_slice($validator, 1) : []; + if ( + method_exists($this, 'V_'.$validatorName) + && is_callable([$this, 'V_'.$validatorName]) ){ + return $this->{'V_'.$validatorName}($value, $validatorParams); + } else { + $this->setError(true, 403, 'Access Denied', "POST unknown validator"); + error_log("Validator: Unknown Validator: $validatorName"); + return !$this->isError; + } + } + + /** + * validate POST data with a validation list + * + * $map format: + * [ + * 'postkey' => 'validator', + * 'postkey2' => ['validator', 'validator_param' => 'validator_value', 'validator_param', ...], + * ] + * validator may contains parameter 'optional' -> so required can be disabled per parameter + * + * @param array $map + * @param boolean $required key is required + * @return boolean + */ + public function validateMap($source_unsafe, $map, $required = true){ + $out = []; + foreach($map as $key => $validator){ + $this->lastMapKey = $key; + if (!isset($source_unsafe[$key])){ + if ($required && !in_array('optional', $validator)){ + $this->setError(true, 403, 'Access Denied', "POST missing parameter: '$key'"); + return !$this->isError; + } else { + $this->setError(false); + } + } else { + $this->validate($source_unsafe[$key], $validator); + if ($this->isError) break; + $out[$key] = $this->filtered; + } + } + $this->filtered = $out; + return !$this->isError; + } + + /** + * validate POST data with a validation list + * add additional mfunction layer, so this will be required + * + * $map format: + * [ 'mfunction_name' => + * [ + * 'postkey' => 'validator', + * 'postkey2' => ['validator', 'validator_param' => 'validator_value', 'validator_param', ...], + * ] + * ] + * + * @param array $map + * @param string $groupKey + * @param boolean $required keys is required (groupKey key is always required) + * @return boolean + */ + public function validatePostGroup($map, $groupKey = 'mfunction', $required = true){ + if (!isset($_POST[$groupKey]) || !isset($map[$_POST[$groupKey]])){ + $this->setError(true, 403, 'Access Denied', "POST request don't match $groupKey."); + return !$this->isError; + } else { + $ret = $this->validateMap($_POST, $map[$_POST[$groupKey]], $required); + if ($ret) $this->filtered = [$_POST[$groupKey].'' => $this->filtered]; + return $ret; + } + } + + // ====== VALIDATORS ======================== + // functions must start with 'V_validatorname' + + /** + * dummy validator + * always return 'valid' + * @param $value + * @param $params + * @return boolean + */ + public function V_dummy($value = NULL, $params = NULL){ + $this->filtered = $value; + return true; + } + + /** + * integer validator + * + * params: + * KEY 1-> single value, 2-> key value pair + * min 2 + * max 2 + * even 1 + * odd 1 + * modulo 2 + * error 2 error message on error case + * + * @param $value + * @param $params + * @return boolean + */ + public function V_integer($value, $params = []){ + if (filter_var($value, FILTER_VALIDATE_INT) === false){ + $msg = (isset($params['error']))? $params['error'] : 'No Integer' ; + return !$this->setError(true, 200, $msg, 'No Integer'); + } else { + $v = filter_var($value, FILTER_VALIDATE_INT); + $this->filtered = $v; + if (in_array('even', $params, true) && $v%2 != 0){ + $msg = (isset($params['error']))? $params['error'] : 'Integer have to be even' ; + return !$this->setError(true, 200, $msg, 'integer not even'); + } + if (in_array('odd', $params, true) && $v%2 == 0){ + $msg = (isset($params['error']))? $params['error'] : 'Integer have to be odd' ; + return !$this->setError(true, 200, $msg, 'integer not odd'); + } + if (isset($params['min']) && $v < $params['min']){ + $msg = (isset($params['error']))? $params['error'] : "Integer out of range: smaller than {$params['min']}" ; + return !$this->setError(true, 200, $msg, 'integer to small'); + } + if (isset($params['max']) && $v > $params['max']){ + $msg = (isset($params['error']))? $params['error'] : "Integer out of range: larger than {$params['max']}" ; + return !$this->setError(true, 200, $msg, 'integer to big'); + } + if (isset($params['modulo']) && $v%$params['modulo'] != 0){ + $msg = (isset($params['error']))? $params['error'] : "Integer modulo failed" ; + return !$this->setError(true, 200, $msg, 'modulo failed'); + } + return !$this->setError(false); + } + } + + /** + * float validator + * + * params: + * KEY 1-> single value, 2-> key value pair + * decimal_seperator 2 [. or ,] default: . + * min 2 min value + * max 2 max value + * step 2 step - be carefull may produce errors (wrong deteced values) + * format 2 trim to x decimal places + * error 2 error message on error case + * + * @param $value + * @param $params + * @return boolean + */ + public function V_float($value, $params = []){ + $decimal = (isset($params['decimal_seperator']))? $params['decimal_seperator'] : '.' ; + if (filter_var($value, FILTER_VALIDATE_FLOAT, array('options' => array('decimal' => $decimal))) === false){ + $msg = (isset($params['error']))? $params['error'] : 'No Float' ; + return !$this->setError(true, 200, $msg, 'No Float'); + } else { + $v = filter_var($value, FILTER_VALIDATE_FLOAT, array('options' => array('decimal' => $decimal))); + if (isset($params['min']) && $v < $params['min']){ + $msg = (isset($params['error']))? $params['error'] : "Float out of range: smaller than {$params['min']}" ; + return !$this->setError(true, 200, $msg, 'float to small'); + } + if (isset($params['max']) && $v > $params['max']){ + $msg = (isset($params['error']))? $params['error'] : "Float out of range: larger than {$params['max']}" ; + return !$this->setError(true, 200, $msg, 'float to big'); + } + if (isset($params['step'])){ + $mod = $params['step']; + $cv = $v; + if (($p = strpos($mod , '.'))!== false){ + $ex = strlen(substr($params['step'], $p + 1)); + $ex = (pow(10, $ex)); + $mod = $mod * $ex; + $cv = $cv * $ex; + } + + if ((is_numeric( $cv ) && floor( $cv ).'' != $cv.'') || $cv % $mod != 0){ + $msg = (isset($params['error']))? $params['error'] : "float invalid step" ; + return !$this->setError(true, 200, $msg, 'float invalid step'); + } + } + if (isset($params['format'])){ + $this->filtered = number_format($v, $params['format'], $decimal, ''); + } else { + $this->filtered = $v; + } + return !$this->setError(false); + } + } + + /** + * check if integer and larger than 0 + * @param integer $value + */ + public function V_id ($value, $params = NULL){ + return $this->V_integer($value, ['min' => 1]); + } + + /** + * text validator + * + * params: + * KEY 1-> single value, 2-> key value pair + * strip 1 + * trim 1 + * + * @param $value + * @param $params + * @return boolean + */ + public function V_text($value, $params = []) { + if (!is_string($value)){ + return !$this->setError(true, 200, 'No Text', 'No Text'); + } else { + $s = ''.$value; + + if (in_array('strip', $params, true) ){ + $s = strip_tags($s); + } + if (in_array('trim', $params, true)){ + $s = trim($s); + } + $this->filtered = $s; + return !$this->setError(false); + } + } + + /** + * email validator + * + * $param + * empty 1 allow empty value + * + * @param $value + * @param $params + * @return boolean + */ + public function V_mail ($value, $params = []) { + $email = filter_var($value, FILTER_SANITIZE_EMAIL); + if (in_array('empty', $params, true) && $email === ''){ + $this->filtered = $email; + return !$this->setError(false); + } + $re = '/^([a-zA-Z0-9_.+-])+\@(([a-zA-Z0-9-])+\.)+([a-zA-Z0-9]{2,4})$/'; + if ($email !== '' && (filter_var($email, FILTER_VALIDATE_EMAIL) === false || !preg_match($re, $email) )){ + return !$this->setError(true, 200, "mail validation failed", 'mail validation failed'); + } else { + $this->filtered = $email; + return !$this->setError(false); + } + } + + /** + * phone validator + * + * @param $value + * @param $params + * @return boolean + */ + public function V_phone($value, $params = NULL) { + $phone = ''.trim(strip_tags(''.$value)); + $re = '/[^0-9+ ]/'; + $phone = trim(preg_replace($re, '' ,$phone)); + if ($phone == '' || $phone == false) $this->filtered = ''; + elseif (strlen($phone) > 40) { + return !$this->setError(true, 200, "phone validation failed", 'phone validation failed'); + } else { + $this->filtered = $phone; + } + return !$this->setError(false); + } + + /** + * name validator + * + * @param $value + * $param + * minlength 2 minimum string length + * maxlength 2 maximum string length - default 127, set -1 for unlimited value + * error 2 replace whole error message on error case + * empty 1 allow empty value + * multi 2 allow multiple names seperated with this seperator, length 1 + * multi_add_space 1 adds space after seperator to prettify list + * @return boolean + */ + public function V_name($value, $params = NULL) { + $name = trim(strip_tags(''.$value)); + if (in_array('empty', $params, true) && $name === ''){ + $this->filtered = ''; + return !$this->setError(false); + } + $re = ''; + $re_no_sep = '/^[a-zA-Z0-9äöüÄÖÜéèêóòôáàâíìîúùûÉÈÊÓÒÔÁÀÂÍÌÎÚÙÛß]+[a-zA-Z0-9\-_ .äöüÄÖÜéèêóòôáàâíìîúùûÉÈÊÓÒÔÁÀÂÍÌÎÚÙÛß]*[a-zA-Z0-9äöüÄÖÜéèêóòôáàâíìîúùûÉÈÊÓÒÔÁÀÂÍÌÎÚÙÛß]+$/'; + if (!isset($params['multi']) || strlen($params['multi']) != 1 ){ + $re = $re_no_sep; + $params['multi'] = NULL; + unset($params['multi']); + } else { + $sep = $params['multi']; + if (mb_strpos("/\\[]()-", $sep) !== false) $sep = "\\".$sep; + $re = '/^[a-zA-Z0-9äöüÄÖÜéèêóòôáàâíìîúùûÉÈÊÓÒÔÁÀÂÍÌÎÚÙÛß]+[a-zA-Z0-9\-_ '.$sep.'.äöüÄÖÜéèêóòôáàâíìîúùûÉÈÊÓÒÔÁÀÂÍÌÎÚÙÛß]*[a-zA-Z0-9äöüÄÖÜéèêóòôáàâíìîúùûÉÈÊÓÒÔÁÀÂÍÌÎÚÙÛß]+$/'; + } + if ( $name !== '' && (!preg_match($re, $name))){ + $msg = ((isset($params['error']) )?$params['error']:'name validation failed'); + return !$this->setError(true, 200, $msg, 'name validation failed'); + } + if (!isset($params['maxlength'])){ + $params['maxlength'] = 127; + } + if (isset($params['maxlength']) && $params['maxlength'] != -1 && strlen($name) > $params['maxlength']){ + $msg = "The name is too long (Maximum length: {$params['maxlength']})"; + if (isset($params['error'])) $msg = $params['error']; + return !$this->setError(true, 200, $msg, 'name validation failed - too long'); + } + if (isset($params['minlength']) && strlen($name) < $params['minlength']){ + $msg = "The name is too short (Minimum length: {$params['minlength']})"; + if (isset($params['error'])) $msg = $params['error']; + return !$this->setError(true, 200, $msg, 'name validation failed - too short'); + } + if (!isset($params['multi']) || !mb_strpos($name, $params['multi'])){ + $this->filtered=$name; + } elseif(mb_strpos($name, $params['multi'])) { + $tmp_list = explode($params['multi'], $name); + $tmp_names = []; + foreach ($tmp_list as $tmp_name){ + $tmp_name = trim($tmp_name); + if (preg_match($re_no_sep, $tmp_name) && $tmp_name){ + $tmp_names[] = $tmp_name; + } + } + $this->filtered=implode($params['multi'].((in_array('multi_add_space', $params, true))?' ':''), $tmp_names); + } + return !$this->setError(false); + } + + /** + * url validator + * + * @param $value + * @param $params + * @return boolean + */ + public function V_url($value, $params = NULL) { + $url = trim(strip_tags(''.$value)); + $re = '/^((http[s]?)((:|%3A)\/\/))(((\w)+((-|\.)(\w+))*)+(\w){0,6}?(:([0-5]?[0-9]{1,4}|6([0-4][0-9]{3}|5([0-4][0-9]{2}|5([0-2][0-9]|3[0-5])))))?\/)((\w)+((\.|-)(\w)+)*\/)*$/'; + if (!preg_match($re, $url) || strlen($url) >= 128){ + return !$this->setError(true, 200, "url validation failed", 'url validation failed'); + } else { + $this->filtered=$url; + } + return !$this->setError(false); + } + + /** + * ip validator + * check if string is a valid ip address (supports ipv4 and ipv6) + * @param $value + * @param $params + * @return boolean + */ + public function V_ip($value, $params = NULL){ + if (self::isValidIP($value)){ + $this->filtered = $value; + return !$this->setError(false); + } else { + return !$this->setError(true, 200, 'No ip address', 'No ip address'); + } + } + + /** + * check if string is a valid ip address (supports ipv4 and ipv6) + * helper function + * + * @param string $ipadr + * @param $recursive if true also allowes IP address with surrounding brackets [] + * @return boolean + */ + public static function isValidIP( $ipadr, $recursive = true) { + if ( preg_match('/^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$|^(([a-zA-Z]|[a-zA-Z][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z]|[A-Za-z][A-Za-z0-9\-]*[A-Za-z0-9])$|^\s*((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))(%.+)?\s*$/', $ipadr)) { + return true; + } else { + if ($recursive && strlen($ipadr) > 2 && $ipadr[0] == '[' && $ipadr[strlen($ipadr)] == ']'){ + return self::isValidIP(substr($ipadr, 1, -1), false); + } else { + return false; + } + } + } + + /** + * check if string is ends with other string + * @param string $haystack + * @param array|string $needle + * @param null|string $needleprefix + * @return boolean + */ + public static function endsWith($haystack, $needle, $needleprefix = null) + { + if (is_array($needle)){ + foreach ($needle as $sub){ + $n=(($needleprefix)?$needleprefix:'').$sub; + if (substr($haystack, -strlen($n))===$n) { + return true; + } + } + return false; + } else if (strlen($needle) == 0){ + return true; + } else { + return substr($haystack, -strlen($needle))===$needle; + } + } + + /** + * domain validator + * + * $param + * empty 1 allow empty value + * + * @param $value + * @param $params + * @return boolean + */ + public function V_domain($value, $params = NULL){ + $host = trim(strip_tags(''.$value)); + if (in_array('empty', $params, true) && $host === ''){ + $this->filtered = $host; + return !$this->setError(false); + } + if ($this->V_ip($host)){ + $this->filtered = $host; + return !$this->setError(false); + } else if ( preg_match("/^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-]*[A-Za-z0-9])$/", $host) && + ( (version_compare(PHP_VERSION, '7.0.0') >= 0) && filter_var($host, FILTER_VALIDATE_DOMAIN, FILTER_FLAG_HOSTNAME)!==false || + (version_compare(PHP_VERSION, '7.0.0') < 0) ) ) { + $this->filtered = $host; + return !$this->setError(false); + } else { + $value_idn = idn_to_ascii($host); + if ( preg_match("/^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-]*[A-Za-z0-9])$/", $value_idn) && + ( (version_compare(PHP_VERSION, '7.0.0') >= 0) && filter_var($value_idn, FILTER_VALIDATE_DOMAIN, FILTER_FLAG_HOSTNAME)!==false || + (version_compare(PHP_VERSION, '7.0.0') < 0) ) ) { + $this->filtered = $value_idn; + return !$this->setError(false); + } else { + return !$this->setError(true, 200, 'Kein gültiger Hostname angegeben' ); + } + } + } + + /** + * regex validator + * + * $param + * regex 2 match pattern + * errorkey 2 replace 'regex' with errorkey on error case + * error 2 replace whole error message on error case + * upper 1 string to uppercase + * lower 1 string to lower case + * replace 2 touple [search, replace] replace string + * minlength 2 minimum string length + * maxlength 2 maximum string length + * noTagStrip 1 disable tag strip before validation + * noTrim 1 disable trim whitespaces + * trimLeft 2 trim Text on left side, parameter trim characters + * trimRight 2 trim Text on right side, parameter trim characters + * empty 1 allow empty string if not in regex + * + * @param $value + * @param $params + * @return boolean + */ + public function V_regex($value, $params = ['pattern' => '/.*/']) { + $v = ''.$value; + if (!in_array('noTagStrip', $params, true)){ + $v = strip_tags($v); + } + if (!in_array('noTrim', $params, true)){ + $v = trim($v); + } + if (isset($params['trimLeft'])){ + $v = ltrim ( $v , $params['trimLeft'] ); + } + if (isset($params['trimRight'])){ + $v = rtrim ( $v , $params['trimRight'] ); + } + if (in_array('empty', $params, true) && $v === ''){ + $this->filtered = $v; + return !$this->setError(false); + } + if (isset($params['replace'])){ + $v = str_replace($params['replace'][0], $params['replace'][1], $v); + } + if (in_array('upper', $params, true)){ + $v = strtoupper($v); + } + if (in_array('lower', $params, true)){ + $v = strtolower($v); + } + if (isset($params['maxlength']) && strlen($v) >= $params['maxlength']){ + $msg = "String is too long (Maximum length: {$params['maxlength']})"; + return !$this->setError(true, 200, $msg); + } + if (isset($params['minlength']) && strlen($v) < $params['minlength']){ + $msg = "String is too short (Minimum length: {$params['minlength']})"; + return !$this->setError(true, 200, $msg); + } + $re = $params['pattern']; + if (!preg_match($re, $v) || (isset($params['maxlength']) && strlen($v) >= $params['maxlength'])) { + $msg = ((isset($params['errorkey']) )?$params['errorkey']:'regex').' validation failed'; + if (isset($params['error'])) $msg = $params['error']; + return !$this->setError(true, 200, $msg, $msg); + } else { + $this->filtered=$v; + } + return !$this->setError(false); + } + + /** + * password validator + * + * $param + * minlength 2 minimum string length + * maxlength 2 maximum string length + * encrypt 1 encrypt password + * empty 1 allow empty value + * + * @param $value + * @param $params + * @return boolean + */ + public function V_password($value, $params = []) { + $p = trim(strip_tags(''.$value)); + + if (in_array('empty', $params, true) && $p === ''){ + $this->filtered = $p; + return !$this->setError(false); + } + if (isset($params['maxlength']) && strlen($p) >= $params['maxlength']){ + $msg = "The password is too long (Maximum length: {$params['maxlength']})"; + return !$this->setError(true, 200, $msg); + } + if (isset($params['minlength']) && strlen($p) < $params['minlength']){ + $msg = "The password is too short (Minimum length: {$params['minlength']})"; + return !$this->setError(true, 200, $msg); + } + if (in_array('encrypt', $params, true)){ + $p = silmph_encrypt_key ($p, SILMPH_KEY_SECRET); + } + $this->filtered=$p; + return !$this->setError(false); + } + + /** + * name validator + * + * @param $value + * @param $params + * @return boolean + */ + public function V_path($value, $params = NULL) { + $path = trim(strip_tags(''.$value)); + $re = '/^((\w)+((\.|-)(\w)+)*)(\/(\w)+((\.|-)(\w)+)*)*$/'; + if (!preg_match($re, $path) || strlen($path) >= 128){ + return !$this->setError(true, 200, "path validation failed", 'path validation failed'); + } else { + $this->filtered=$path; + } + return !$this->setError(false); + } + + /** + * color validator + * + * @param $value + * @param $params + * @return boolean + */ + public function V_color($value, $params = NULL) { + $color = trim(strip_tags(''.$value)); + $re = '/^([a-fA-F0-9]){6}$/'; + if (!preg_match($re, $color) || strlen($color) != 6){ + return !$this->setError(true, 200, "color validation failed", 'color validation failed'); + } else { + $this->filtered=$color; + } + return !$this->setError(false); + } + + /** + * filename validator + * + * $param + * error 2 overwrite error message + * + * @param $value + * @param $params + * @return boolean + */ + public function V_filename($value, $params = NULL) { + $re = '/[^a-zA-Z0-9\-_(). äöüÄÖÜéèêóòôáàâíìîúùûÉÈÊÓÒÔÁÀÂÍÌÎÚÙÛß]/'; + $fname = trim(preg_replace($re, '' ,strip_tags(''.$value))); + $fname = str_replace('..', '.', $fname); + $fname = str_replace('..', '.', $fname); + if (( strlen($fname) >= 255) || ( $fname === '' )){ + $msg = (isset($params['error']))? $params['error'] : 'filename validation failed'; + return !$this->setError(true, 200, $msg, 'filename validation failed'); + } else { + $this->filtered=$fname; + } + return !$this->setError(false); + } + + /** + * time validator + * + * $param + * empty 1 allow empty value + * format 2 datetime-format + * error 2 overwrite error message + * parse 2 parse date to format after validation + * + * @param $value + * @param $params + * @return boolean + */ + public function V_time($value, $params = NULL) { + $time = trim(strip_tags(''.$value)); + $fmt = (isset($params['format']))? $params['format'] : 'H:i'; + if (in_array('empty', $params, true) && ($time === '0' || $time === 'false' || $time === ''||$time === false || $time == 0)){ + $this->filtered = false; + return !$this->setError(false); + } elseif (!in_array('empty', $params, true) && ($time === '0' || $time === 'false' || $time === ''||$time === false || $time == 0)){ + $msg = (isset($params['error']))? $params['error'] : 'time validation failed, format: "'.$fmt.'"'; + return !$this->setError(true, 200, $msg, 'time validation failed, format: "'.$fmt.'"'); + } else { + $d = DateTime::createFromFormat($fmt, $time); + if($d && $d->format($fmt) == $time){ + $this->filtered = $d->format((isset($params['parse']))?$params['parse']:$fmt); + return !$this->setError(false); + } else { + $msg = (isset($params['error']))? $params['error'] : 'time validation failed, format: "'.$fmt.'"'; + return !$this->setError(true, 200, $msg, 'time validation failed, format: "'.$fmt.'"'); + } + } + } + + /** + * array validator + * test if element is array + * + * + * $param + * key 2 validate array key to -> validatorelelemnt, requires param->validator to be set + * minlength 2 minimum string length + * maxlength 2 maximum string length + * empty 1 allow empty array + * false 1 allow false -> reset to empty array + * validator 2 run this validator on each array element + * error 2 overwrite error message + * + * @param array $a + * @param array $params + * @return boolean + */ + public function V_array($a, $params){ + if (!is_array($a)){ + if ($a === '0' && in_array('false', $params, true)){ + $a = []; + } else { + $msg = (isset($params['error']))? $params['error'] : 'Value is no array'; + return !$this->setError(true, 200, $msg, 'array validator failed'); + } + } + if ((!in_array('empty', $params, true) || count($a) > 0) && isset($params['minlength']) && count($a) < $params['minlength']){ + $msg = (isset($params['error']))? $params['error'] : 'Array to short: require minimal length of "'.$params['minlength'].'" elements'; + return !$this->setError(true, 200, $msg, 'array validator failed: array to short'); + } + if (isset($params['maxlength']) && count($a) > $params['maxlength']){ + $msg = (isset($params['error']))? $params['error'] : 'Array to long: maximal array length "'.$params['maxlength'].'"'; + return !$this->setError(true, 200, $msg, 'array validator failed: array to long'); + } + if (!in_array('empty', $params, true) && count($a) == 0){ + $msg = (isset($params['error']))? $params['error'] : 'Array to short: empty array is not permitted.'; + return !$this->setError(true, 200, $msg, 'array validator failed: array is empty'); + } + if (!isset($params['validator'])){ + $this->filtered=$a; + return !$this->setError(false); + } + $out = []; + $tmp_last_mapkey = $this->lastMapKey; + $tmp_last_key = ''; + foreach($a as $key => $entry){ + $tmp_last_key = $key; + $this->lastMapKey = ''; + //key + $keyFiltered = NULL; + if (isset($params['key'])){ + $this->validate($key, $params['key']); + if ($this->isError) break; + $keyFiltered = $this->filtered; + } + //value + $this->validate($entry, $params['validator']); + if ($this->isError) break; + if ($keyFiltered === NULL){ + $out[] = $this->filtered; + } else { + $out[$keyFiltered] = $this->filtered; + } + } + if ($this->isError) { + $curr = $this->_capsule_lastMapKey(); + $this->lastMapKey = "{$tmp_last_mapkey}[{$tmp_last_key}]{$curr}"; + } + $this->filtered = $out; + return !$this->isError; + } + + /** + * arraymap validator + * run validator on array and given map + * + * $param + * map 2 validation map + * reqired 2 boolean, default false + * + * @param array $a + * @param array $params + * @return boolean + */ + public function V_arraymap($a, $params){ + if (!isset($params['map'])){ + return !$this->setError(true, 200, 'invalid configuration on arraymap validation', 'arraymap validator failed: wrong configuration: missing parameter map'); + } + $tmp_last_mapkey = $this->lastMapKey; + $this->validateMap($a, $params['map'], (!isset($params['map'])? 'required': $params['required'] ) ); + if ($this->isError){ + $curr = $this->_capsule_lastMapKey(); + $this->lastMapKey = "{$tmp_last_mapkey}{$curr}"; + } + return !$this->isError; + } + + /** + * date validator + * + * $param + * format 2 datetime-format + * error 2 overwrite error message + * parse 2 parse date to format after validation + * empty 1 allow empty array + * + * @param $value + * @param $params + * @return boolean + */ + public function V_date($value, $params = NULL) { + $date = trim(strip_tags(''.$value)); + if (in_array('empty', $params, true) && $date === ''){ + $this->filtered = $date; + return !$this->setError(false); + } + $fmt = (isset($params['format']))? $params['format'] : 'Y-m-d'; + $d = DateTime::createFromFormat($fmt, $date); + if($d && $d->format($fmt) == $date){ + $this->filtered = $d->format((isset($params['parse']))?$params['parse']:$fmt); + } else { + $msg = (isset($params['error']))? $params['error'] : 'date validation failed, format: "'.$fmt.'"'; + return !$this->setError(true, 200, $msg, 'date validation failed, format: "'.$fmt.'"'); + } + return !$this->setError(false); + } + + /** + * array validator + * test if string is valid iban + * + * + * $param + * empty 1 allow empty array + * error 2 overwrite error message + * + * @param string $value + * @param array $params + * @return boolean + */ + public function V_iban($value, $params){ + $iban = trim(strip_tags(''.$value)); + $iban = strtoupper($iban); // to upper + $iban = str_replace(' ', '', $iban); //remove spaces + //empty + if (in_array('empty', $params, true) && $iban === ''){ + $this->filtered = $iban; + return !$this->setError(false); + } + //check iban + if (!self::_checkIBAN($iban)){ + $msg = (isset($params['error']))? $params['error'] : 'iban validation failed'; + return !$this->setError(true, 200, $msg, 'iban validation failed'); + } else { + $this->filtered=$iban; + } + return !$this->setError(false); + } + + /** + * check if string is valid iban, + * + * @param $iban iban srstring to check + * + * @return bool + * @see https://en.wikipedia.org/wiki/International_Bank_Account_Number#Validating_the_IBAN + */ + public static function _checkIBAN($iban){ + $iban = strtoupper(str_replace(' ', '', $iban)); + $Countries = ARRAY('AL' => 28, 'AD' => 24, 'AT' => 20, 'AZ' => 28, 'BH' => 22, 'BE' => 16, 'BA' => 20, 'BR' => 29, 'BG' => 22, 'CR' => 21, 'HR' => 21, 'CY' => 28, 'CZ' => 24, 'DK' => 18, 'DO' => 28, 'EE' => 20, 'FO' => 18, 'FI' => 18, 'FR' => 27, 'GE' => 22, 'DE' => 22, 'GI' => 23, 'GR' => 27, 'GL' => 18, 'GT' => 28, 'HU' => 28, 'IS' => 26, 'IE' => 22, 'IL' => 23, 'IT' => 27, 'JO' => 30, 'KZ' => 20, 'KW' => 30, 'LV' => 21, 'LB' => 28, 'LI' => 21, 'LT' => 20, 'LU' => 20, 'MK' => 19, 'MT' => 31, 'MR' => 27, 'MU' => 30, 'MC' => 27, 'MD' => 24, 'ME' => 22, 'NL' => 18, 'NO' => 15, 'PK' => 24, 'PS' => 29, 'PL' => 28, 'PT' => 25, 'QA' => 29, 'RO' => 24, 'SM' => 27, 'SA' => 24, 'RS' => 22, 'SK' => 24, 'SI' => 19, 'ES' => 24, 'SE' => 24, 'CH' => 21, 'TN' => 24, 'TR' => 26, 'AE' => 23, 'GB' => 22, 'VG' => 24); + + //1. check country code exists + iban has valid length + if( !array_key_exists(substr($iban,0,2), $Countries) + || strlen($iban) != $Countries[substr($iban,0,2)]){ + return false; + } + + //2. Rearrange countrycode and checksum + $rearranged = substr($iban, 4) . substr($iban, 0, 4); + + //3. convert to integer + $iban_letters = str_split($rearranged); + $iban_int_only = ''; + foreach ($iban_int_only as $char){ + if (is_int($char)) $iban_int_only .= $char; + else { + $ord = ord($char) - 55; // ascii representation - 55, so a => 10, b => 11, ... + if ($ord >= 10 && $ord <= 35){ + $iban_int_only .= $ord; + } else { + return false; + } + } + } + + //4. calculate mod 97 -> have to be 1 + if (self::_bcmod($iban_int_only, '97') == 1){ + return true; + }else{ + return false; + } + } + + /** + * _bcmod - get modulus (substitute for bcmod) + * be careful with big $modulus values + * + * @param string $left_operand

    The left operand, as a string.

    + * @param int $modulus

    The modulus, as a string.

    + * + * based on + * https://stackoverflow.com/questions/10626277/function-bcmod-is-not-available + * by Andrius Baranauskas and Laurynas Butkus :) Vilnius, Lithuania + **/ + public static function _bcmod($left_operand, $modulus){ + if (function_exists('bcmod')){ + return bcmod($left_operand, $modulus); + } else { + $take = 5; // how many numbers to take at once? + $mod = ''; + do + { + $a = (int)$mod.substr( $left_operand, 0, $take ); + $left_operand = substr( $left_operand, $take ); + $mod = $a % $modulus; + } + while ( strlen($left_operand) ); + + return (int)$mod; + } + } + + private function _capsule_lastMapKey(){ + $capsuled = $this->lastMapKey; + if ($capsuled != ''){ + if (substr($capsuled, 0, 1) != '[' && substr($capsuled, -1) != ']'){ + $capsuled = "[$capsuled]"; + } elseif (substr($capsuled, 0, 1) != '[' && substr($capsuled, -1) == ']'){ + $pos = strpos($capsuled, '['); + $capsuled = "[".substr($capsuled, 0, $pos).']'.substr($capsuled, $pos); + } + } + return $capsuled; + } +} \ No newline at end of file diff --git a/lib/inc.all.php b/lib/inc.all.php new file mode 100644 index 0000000..03ad203 --- /dev/null +++ b/lib/inc.all.php @@ -0,0 +1,17 @@ + + * @since 07.05.2018 + * @copyright Copyright (C) Michael Gnehr 2018, All rights reserved + * @platform PHP + * @requirements PHP 7.0 or higher + */ +interface AuthHandler +{ + /** + * return instance of this class + * singleton class + * return same instance on every call + * @param bool $noPermCheck + * @return BasicAuthHandler + */ + public static function getInstance(); + + /** + * handle session and user login + */ + function requireAuth(); + + /** + * check group permission - die on error + * return true if successfull + * @param string $groups String of groups + * @return bool true if the user has one or more groups from $group + */ + function requireGroup($group); + + /** + * check group permission - return result of check as boolean + * @param string $groups String of groups + * @param string $delimiter Delimiter of the groups in $group + * @return bool true if the user has one or more groups from $group + */ + function hasGroup($group, $delimiter = ","); + + /** + * return log out url + * @return string + */ + function getLogoutURL(); + + /** + * send html header to redirect to logout url + * @param string $param + */ + function logout(); + + /** + * return current user attributes + * @return array + */ + function getAttributes(); + + /** + * return username or user mail address + * if not set return null + * @return string|NULL + */ + function getUsername(); + + /** + * return user displayname + * @return string + */ + function getUserFullName(); + + /** + * return user mail address + * @return string + */ + function getUserMail(); +} + +?> \ No newline at end of file diff --git a/auszahlung.tex b/old/auszahlung.tex similarity index 100% rename from auszahlung.tex rename to old/auszahlung.tex diff --git a/bewilligung.tex b/old/bewilligung.tex similarity index 100% rename from bewilligung.tex rename to old/bewilligung.tex diff --git a/briefkopf.tex b/old/briefkopf.tex similarity index 100% rename from briefkopf.tex rename to old/briefkopf.tex diff --git a/hiddenAPIKEY.php.sample b/old/hiddenAPIKEY.php.sample similarity index 100% rename from hiddenAPIKEY.php.sample rename to old/hiddenAPIKEY.php.sample diff --git a/index.php b/old/index.php similarity index 99% rename from index.php rename to old/index.php index 998f353..071143c 100644 --- a/index.php +++ b/old/index.php @@ -263,7 +263,7 @@ private function generatePDF(){ }else{ $this->result["status"] = "Datei konnte nicht erstellt werden"; if (PDF_Factory::$reportLaTeXError){ - $this->result["pdflatexOutput"] = substr($ret, -200); + $this->result["pdflatexOutput"] = substr($ret, -1000); if (strpos($ret, 'no output PDF file produced') !== false && file_exists($this->filename . ".log")){ $this->result["pdflatexLog"] = substr(file_get_contents($this->filename . ".log"), -2000); } diff --git a/old/parameter.tex b/old/parameter.tex new file mode 100644 index 0000000..e69de29 diff --git a/zahlungsanweisung.tex b/old/zahlungsanweisung.tex similarity index 98% rename from zahlungsanweisung.tex rename to old/zahlungsanweisung.tex index 62e0d4d..5c34541 100644 --- a/zahlungsanweisung.tex +++ b/old/zahlungsanweisung.tex @@ -31,8 +31,8 @@ bindingoffset=0mm, top=20mm,bottom=20mm} -%\newcommand{\ID}{125} -%\newcommand{\IBAN}{DE08 1001 0010 0745 3161 07} +%\newcommand{\projektid}{\_\_} +%\newcommand{\iban}{DE08 1001 0010 0745 3161 07} %\newcommand{\empfaenger}{Michael Braun (jr)} %\newcommand{\hv}{Lukas Staab am 2017-10-20} %\newcommand{\kv}{Lukas Staab am 2017-10-20} diff --git a/zahlungsanweisung_user.tex b/old/zahlungsanweisung_user.tex similarity index 100% rename from zahlungsanweisung_user.tex rename to old/zahlungsanweisung_user.tex diff --git a/parameter.tex b/parameter.tex deleted file mode 100644 index 0b6e905..0000000 --- a/parameter.tex +++ /dev/null @@ -1,14 +0,0 @@ -\setkomavar{vereinName}{Bergfest e.V.} -\setkomavar{vereinPerson}{.} -\setkomavar{vereinAdresse}{.} -\setkomavar{vereinOrt}{.} -\setkomavar{datum}{19.03.2018} -\setkomavar{projId}{3051} -\setkomavar{projName}{Bergfest Zaubershow} -\setkomavar{projAbrechnungDatum}{hmm...} -\setkomavar{sturaBetrag}{1 600,00} -\setkomavar{sturaVorkasse}{0,00} -\setkomavar{sturaAbrechnung}{1 600,00} -\setkomavar{iban}{DE75 8405 1010 1010 0645 05} -\setkomavar{titel}{Auszahlungsbescheid} -\setkomavar{sturaRest}{1600} diff --git a/public/.htaccess b/public/.htaccess new file mode 100644 index 0000000..197199e --- /dev/null +++ b/public/.htaccess @@ -0,0 +1,4 @@ +RewriteEngine on +RewriteCond %{REQUEST_FILENAME} !-d +RewriteCond %{REQUEST_FILENAME} !-f +RewriteRule . index.php [L] diff --git a/public/css/style.css b/public/css/style.css new file mode 100644 index 0000000..4aa9df9 --- /dev/null +++ b/public/css/style.css @@ -0,0 +1,42 @@ +html, body { + height: 100; + width: 100; + padding: 5px; + margin: 5px; +} +body { + padding: 5px; +} +.content { + border: 1px solid #ddd; + padding: 5px; +} +/* Profiling ============================================== */ +.profiling-output { + position: fixed; + background-color: white; + top: calc(100% - 35px); + left: 10px; + z-index: 10; + padding: 4px; +} +.profiling-output h3 { + font-size: 18px; + padding: 5px; +} +.profiling-output:hover { + position: fixed; + bottom: 0; + top: inherit; + max-height: 35%; + overflow-y: auto; + z-index:300; + border: 1px solid #ddd; +} +.fa-angle-toggle::before { + content: "\f106"; +} + +.profiling-output:hover .fa-angle-toggle::before { + content: "\f107"; +} \ No newline at end of file diff --git a/public/index.php b/public/index.php new file mode 100644 index 0000000..3836517 --- /dev/null +++ b/public/index.php @@ -0,0 +1,88 @@ +route(); + +//ip and apikey +if($routeInfo['controller'] != 'error' && (!isset($routeInfo['allowall']) || !$routeInfo['allowall'] )){ + // check API KEY + if ($_SERVER['REQUEST_METHOD'] == 'GET' && (!isset($_GET['APIKEY'])||$_GET['APIKEY']!=API_KEY)){ + $routeInfo['action'] = '403'; + $routeInfo['controller'] = 'error'; + $routeInfo['method'] = 'POST'; + } elseif($_SERVER['REQUEST_METHOD'] == 'POST' && (!isset($_POST['APIKEY'])||$_POST['APIKEY']!=API_KEY)) { + $routeInfo['action'] = '403'; + $routeInfo['controller'] = 'error'; + $routeInfo['method'] = 'POST'; + } + // ip check + if ($_SERVER['REMOTE_ADDR'] && is_array($_SERVER['REMOTE_ADDR']) && !in_array($_SERVER['REMOTE_ADDR'], ALLOWED_IPS)){ + $routeInfo['action'] = '403'; + $routeInfo['controller'] = 'error'; + $routeInfo['method'] = 'POST'; + } +} +// auth --------------------------------- +if (isset($routeInfo['auth']) && ($routeInfo['auth'] == 'Basic' || $routeInfo['auth'] == 'basic')){ + define('AUTH_HANLER', 'AuthBasicHandler'); + AuthBasicHandler::getInstance()->requireAuth(); + AuthBasicHandler::getInstance()->requireGroup('basic'); + if (!isset($routeInfo['groups'])){ + $routeInfo['action'] = '403'; + $routeInfo['controller'] = 'error'; + $routeInfo['method'] = 'POST'; + } elseif (isset($routeInfo['groups']) && !AuthBasicHandler::getInstance()->hasGroup($routeInfo['groups'])){ + $routeInfo['action'] = '403'; + $routeInfo['controller'] = 'error'; + $routeInfo['method'] = 'POST'; + } +} else { + define('AUTH_HANLER', NULL); +} + +// handle route ------------------------- +switch ($routeInfo['controller']){ + case "old": + include SYSBASE.'/old/index.php'; + break; + case "pdfbuilder": + $builder = new TexBuilder(); + if (!AuthBasicHandler::getInstance()->hasGroup('pdfbuilder') + || !isset($_POST['action']) + || !$builder->builderExist($_POST['action'])) { + $routeInfo['action'] = '403'; + $routeInfo['controller'] = 'error'; + $routeInfo['method'] = 'POST'; + $errorHdl = new ErrorHandler($routeInfo); + $errorHdl->render(); + } elseif(!$builder->validate($_POST['action']) + || !$builder->build() + || $builder->getError()) { + JsonController::print_json([ + 'success' => false, + 'controller' => $routeInfo['controller'], + 'error' => $builder->getError(), + ], true); + } else { + JsonController::print_json([ + 'success' => true, + 'controller' => $routeInfo['controller'], + 'action' => $_POST['action'], + 'data' => $builder->getBase64(), + ], true); + } + break; + case 'error': + default: + $errorHdl = new ErrorHandler($routeInfo); + $errorHdl->render(); + break; +} + + diff --git a/template/html/error.phtml b/template/html/error.phtml new file mode 100644 index 0000000..3991f2c --- /dev/null +++ b/template/html/error.phtml @@ -0,0 +1,34 @@ + + * @since 17.02.2018 + * @copyright Copyright (C) 2018 - All rights reserved + */ +$param = $param; +?> +'; +} ?> +

    + +
    + +

    +

    + +
    Angeforderte Seite
    + > + +
    Sie sind von folgender Seite hier gelandet
    + > + + +
    diff --git a/template/html/footer.phtml b/template/html/footer.phtml new file mode 100644 index 0000000..4d4d6d3 --- /dev/null +++ b/template/html/footer.phtml @@ -0,0 +1,18 @@ + + * @since 17.02.2018 + * @copyright Copyright (C) 2018 - All rights reserved + */ +?> +=1){ + Helper::prof_flag('end'); + Helper::prof_print(true, true); +} ?> + + + \ No newline at end of file diff --git a/template/html/header.phtml b/template/html/header.phtml new file mode 100644 index 0000000..c766b28 --- /dev/null +++ b/template/html/header.phtml @@ -0,0 +1,28 @@ + + * @since 17.02.2018 + * @copyright Copyright (C) 2018 - All rights reserved + */ + ?> + + + + + + + + + + + + <?= BASE_TITLE ?> + + +
    + \ No newline at end of file diff --git a/template/tex/belegpdf.phpTex b/template/tex/belegpdf.phpTex new file mode 100644 index 0000000..ce9de42 --- /dev/null +++ b/template/tex/belegpdf.phpTex @@ -0,0 +1,217 @@ +\documentclass[a4paper,11pt]{article} + +\usepackage[T1]{fontenc} +\usepackage[utf8]{inputenc} +\usepackage{graphicx} +\usepackage{xcolor} +\usepackage{ngerman} +\usepackage[tx]{sfmath} +\usepackage{calc} +\usepackage{lastpage} +%\usepackage{ifthen} +\usepackage{xifthen} +\usepackage{multicol} +\renewcommand\familydefault{\sfdefault} +\usepackage{tgheros} +\usepackage{intcalc} + +\usepackage{amsmath,amssymb,amsthm,textcomp} +%\usepackage{enumerate} +\usepackage{enumitem} +\usepackage{multicol} +\usepackage{tikz} + +\usepackage{geometry} +\geometry{total={210mm,297mm}, +left=25mm,right=25mm,% +bindingoffset=0mm, top=20mm,bottom=20mm} + +% pdf version to min 1.6 +\pdfminorversion=6 + +\newcommand{\picpaths}{ $v){ + $sp = explode('/', $v); + $ff .= (($ff)?',':'').self::texEscape($k).'/'.self::texEscape($sp[2]); + } + echo $ff; +?>} +\newcommand{\footerstring}{} +\linespread{1.3} + +\newcommand{\linia}{\rule{\linewidth}{0.5pt}} + +\newcommand{\mysection}[1]{ +\begin{center} +{\large \textsc{#1}} +\vspace*{-0.5cm} +\\\linia\\ +\vspace*{-0.6cm} +\end{center} +} + +% custom theorems if needed +\newtheoremstyle{mytheor} + {1ex}{1ex}{\normalfont}{0pt}{\scshape}{.}{1ex} + {{\thmname{#1 }}{\thmnumber{#2}}{\thmnote{ (#3)}}} + +\theoremstyle{mytheor} +\newtheorem{defi}{Definition} + +% my own titles +\makeatletter +\renewcommand{\maketitle}{ +\begin{center} +\vspace*{-0.5cm} +{\huge \textsc{\@title}} +\linia +\end{center} +} +\makeatother +%%% + +% custom footers and headers +\usepackage{fancyhdr} +\pagestyle{fancy} +\lhead{} +\chead{} +\rhead{} +\lfoot{\footerstring} +\cfoot{} +\rfoot{Seite \thepage{} von \pageref{LastPage}} +\renewcommand{\headrulewidth}{0pt} +\renewcommand{\footrulewidth}{0pt} + +% +% all section titles centered and bolded +\usepackage{sectsty} +\allsectionsfont{\centering\bfseries\large} +% +% add section label +\renewcommand\thesection{} +% + +%%%----------%%%----------%%%----------%%%----------%%% + +\begin{document} + +\title{Belege Einreichen} +\author{StuRa der TU Ilmenau} + +\vspace*{-2.0cm} +\begin{figure}[h] +\centering +\includegraphics[width=3cm]{} +\end{figure} + +\maketitle + +\vspace*{-0.5cm} +{\setlength{\parindent}{0cm} +\textit{\textbf{Hinweis: }Die Belege für diese Auslagenerstattung müssen an dieses Dokument angehangen, alles zusammen getackert, und im Stura Büro bei Referat Fianzen eingereicht werden. Dazu legt man das Dokument in das Postfach des Referats Finanzen.} +} +\vspace*{-0.3cm} +\\\linia\\ +\vspace{-0.3cm} +\begin{figure}[h] +\centering +\parbox[b]{0.6\linewidth}{% size of the first signature box + \strut + \textbf{Eingegangen am} \hrule +} +\end{figure} +% +% +% +\vspace*{-0.1cm} +\mysection{Projekt Angaben} + +\begin{enumerate}[label=\Roman*] +\itemsep-2mm +\item \textbf{ID}\hfill +\item \textbf{Projekt}\hfill +\item \textbf{Organisation} \hfill +\item \textbf{Erstellt} \hfill +\end{enumerate} + +\vspace*{2mm} +\mysection{Abrechnung} +\begin{enumerate}[label=\Roman*,resume] +\itemsep-2mm +\item \textbf{ID}\hfill +\item \textbf{Name} \hfill +\item \textbf{Erstellt} \hfill +\item \textbf{Von} \hfill +\item \textbf{Zahlung An} \hfill +\end{enumerate} + +\vspace*{2mm} + +\begin{center} +\begin{tabular}{rrr} +\textbf{Beleg} & \textbf{Datum} & \textbf{Beschreibung} \\ +\\ +\end{tabular} +\end{center} + +\newpage +%Belege +\tikzset{ + font={\fontsize{29pt}{12}\selectfont}} +\foreach \nr/\picname in \picpaths +{ + %% bei mehrseitigen Belegen erst alle originale und dann alle Kopien + %% bei einseitigen Belegen, beides auf einer Seite + + \pdfximage{\picname} + %Switch case der Anzahl Seiten des Beleges + + + \begin{tikzpicture}[remember picture,overlay] + %rechteck + \draw [draw=black,line width=5mm,opacity=0.3] (16.5,-10) rectangle (-1,-1.25); + %belegnummer + \node [opacity=1,anchor=north east,xshift=\textwidth, yshift=1.85cm] {\raggedleft \nr}; + %text im Rechteck + \node [opacity=0.3,anchor=north west, yshift=-2.0cm] {Den hier unten abgebildeten }; + \node [opacity=0.3,anchor=north west, yshift=-3.6cm] {Beleg}; + \node [opacity=0.6,anchor=north west, xshift=3.0cm, yshift=-3.6cm] {\textbf{\nr{}}}; + \node [opacity=0.3,anchor=north west, yshift=-5.0cm] {antackern (Original).}; + \node [opacity=0.3,anchor=north west, yshift=-6.1cm] {Falls dieser Beleg ein A4 Beleg }; + \node [opacity=0.3,anchor=north west, yshift=-7.2cm] {ist, hefte das Original vor}; + \node [opacity=0.3,anchor=north west, yshift=-8.3cm] {dieser Seite ab.}; + \node [anchor =north east, inner sep=0pt,outer sep=0pt,yshift=-0.45\paperheight] at (current page.north east) {\fbox{\includegraphics[page=1,angle=90,width=0.925\paperwidth,height=0.45\paperheight]{\picname}}}; + \node [rotate=90, opacity=0.75,anchor=north west,xshift=-0.83\paperheight,yshift=2.92cm]{ Beleg \nr - Seite 1}; + \end{tikzpicture} + \newpage + \ifthenelse{\the\pdflastximagepages > 1}{ + \foreach \index in {2,...,\the\pdflastximagepages} + {%iteriere über die Seiten des Beleg pdfs + \ifthenelse{\not \isodd{\index}}{ + \begin{tikzpicture}[remember picture,overlay] + %belegnummer + \node [opacity=1,anchor=north east,xshift=\textwidth, yshift=1.85cm] {\raggedleft \nr}; + \node [anchor =north east, inner sep=0pt,outer sep=0pt,yshift=-2cm] at (current page.north east) {\fbox{\includegraphics[page=\index,angle=90,width=0.925\paperwidth,height=0.415\paperheight]{\picname}}}; + \node [rotate=90, opacity=0.75,anchor=north west,xshift=-0.375\paperheight,yshift=2.92cm] { Seite \index}; + \end{tikzpicture} + }{ %else unterer Teil des Blattes + \begin{tikzpicture}[remember picture,overlay] + \node [anchor =north east, inner sep=0pt,outer sep=0pt,yshift=-14.55cm] at (current page.north east) {\fbox{\includegraphics[page=\index,angle=90,width=0.925\paperwidth,height=0.415\paperheight]{\picname}}}; + \node [rotate=90, opacity=0.75,anchor=north west,xshift=-0.75\paperheight,yshift=2.92cm] { Seite \index}; + \end{tikzpicture} + \newpage + } %if end + + }{} %else end if page > 1 + \newpage + } +} + + +\end{document} \ No newline at end of file From 1f5f0b7bedf5de3cae873fe39e9b118b5da76407 Mon Sep 17 00:00:00 2001 From: cherrg Date: Tue, 17 Jul 2018 16:09:18 +0200 Subject: [PATCH 02/42] extend text validatur --- lib/class.Validator.php | 35 +++++++++++++++++++++++++++++++---- 1 file changed, 31 insertions(+), 4 deletions(-) diff --git a/lib/class.Validator.php b/lib/class.Validator.php index 4973f7e..a50f7c4 100644 --- a/lib/class.Validator.php +++ b/lib/class.Validator.php @@ -344,8 +344,14 @@ public function V_id ($value, $params = NULL){ * * params: * KEY 1-> single value, 2-> key value pair - * strip 1 - * trim 1 + * strip 1 + * trim 1 + * htmlspecialchars 1 + * htmlentities 1 + * minlength 2 minimum string length + * maxlength 2 maximum string length - default 127, set -1 for unlimited value + * error 2 replace whole error message on error case + * empty 1 allow empty value * * @param $value * @param $params @@ -353,16 +359,37 @@ public function V_id ($value, $params = NULL){ */ public function V_text($value, $params = []) { if (!is_string($value)){ - return !$this->setError(true, 200, 'No Text', 'No Text'); + $msg = "No Text"; + if (isset($params['error'])) $msg = $params['error']; + return !$this->setError(true, 200, $msg, 'No Text'); } else { + if (in_array('empty', $params, true) && $value === ''){ + $this->filtered = ''; + return !$this->setError(false); + } $s = ''.$value; - if (in_array('strip', $params, true) ){ $s = strip_tags($s); } + if (in_array('htmlspecialchars', $params, true)){ + $s = htmlspecialchars($s); + } + if (in_array('htmlentities', $params, true)){ + $s = htmlentities($s); + } if (in_array('trim', $params, true)){ $s = trim($s); } + if (isset($params['minlength']) && strlen($s) < $params['minlength']){ + $msg = "The text is too short (Minimum length: {$params['minlength']})"; + if (isset($params['error'])) $msg = $params['error']; + return !$this->setError(true, 200, $msg, 'text validation failed - too short'); + } + if (isset($params['maxlength']) && $params['maxlength'] != -1 && strlen($s) > $params['maxlength']){ + $msg = "The text is too long (Maximum length: {$params['maxlength']})"; + if (isset($params['error'])) $msg = $params['error']; + return !$this->setError(true, 200, $msg, 'text validation failed - too long'); + } $this->filtered = $s; return !$this->setError(false); } From 60488388be9bd792d125626ee03ba09d1f151703 Mon Sep 17 00:00:00 2001 From: cherrg Date: Tue, 17 Jul 2018 16:12:35 +0200 Subject: [PATCH 03/42] fix invalid validaor options + modify BelegPdf -> Projekt ID String --- lib/class.TexBuilder.php | 4 +++- template/tex/belegpdf.phpTex | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/class.TexBuilder.php b/lib/class.TexBuilder.php index 07790cb..1e95d70 100644 --- a/lib/class.TexBuilder.php +++ b/lib/class.TexBuilder.php @@ -25,8 +25,10 @@ class TexBuilder 'maxlength' => '255', 'error' => 'Ungültiger Projekt Name.' ], - 'org' => [ 'name', + 'org' => [ 'regex', + 'pattern' => '/^[a-zA-Z0-9\-_ ()äöüÄÖÜéèêóòôáàâíìîúùûÉÈÊÓÒÔÁÀÂÍÌÎÚÙÛß]*$/', 'maxlength' => '255', + 'empty', 'error' => 'Ungültiger Organisations Name.' ], 'id' => ['integer', diff --git a/template/tex/belegpdf.phpTex b/template/tex/belegpdf.phpTex index ce9de42..85bc5df 100644 --- a/template/tex/belegpdf.phpTex +++ b/template/tex/belegpdf.phpTex @@ -37,7 +37,7 @@ bindingoffset=0mm, top=20mm,bottom=20mm} } echo $ff; ?>} -\newcommand{\footerstring}{} +\newcommand{\footerstring}{} \linespread{1.3} \newcommand{\linia}{\rule{\linewidth}{0.5pt}} From a57cadb0454433c3850b672944db2a4477120d61 Mon Sep 17 00:00:00 2001 From: cherrg Date: Tue, 17 Jul 2018 16:13:37 +0200 Subject: [PATCH 04/42] fix .htaccess --- public/.htaccess | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/.htaccess b/public/.htaccess index 197199e..58cd039 100644 --- a/public/.htaccess +++ b/public/.htaccess @@ -1,4 +1,4 @@ RewriteEngine on RewriteCond %{REQUEST_FILENAME} !-d RewriteCond %{REQUEST_FILENAME} !-f -RewriteRule . index.php [L] +RewriteRule (.*)$ index.php/$1 [L] \ No newline at end of file From cc06555bb9359043c70efaaa3c27534278df71a0 Mon Sep 17 00:00:00 2001 From: cherrg Date: Wed, 18 Jul 2018 20:10:56 +0200 Subject: [PATCH 05/42] change footer line --- template/tex/belegpdf.phpTex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/template/tex/belegpdf.phpTex b/template/tex/belegpdf.phpTex index 85bc5df..f9dff2f 100644 --- a/template/tex/belegpdf.phpTex +++ b/template/tex/belegpdf.phpTex @@ -37,7 +37,7 @@ bindingoffset=0mm, top=20mm,bottom=20mm} } echo $ff; ?>} -\newcommand{\footerstring}{} +\newcommand{\footerstring}{} \linespread{1.3} \newcommand{\linia}{\rule{\linewidth}{0.5pt}} From c6f99a8adb6114c31565fb50263e30fa3079b2d7 Mon Sep 17 00:00:00 2001 From: cherrg Date: Wed, 25 Jul 2018 14:45:36 +0200 Subject: [PATCH 06/42] fix validator map --- lib/class.TexBuilder.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/class.TexBuilder.php b/lib/class.TexBuilder.php index 1e95d70..02d6618 100644 --- a/lib/class.TexBuilder.php +++ b/lib/class.TexBuilder.php @@ -21,12 +21,14 @@ class TexBuilder 'parse' => 'Y-m-d H:i:s', 'error' => 'Ungültiges Projekt Datum.' ], - 'name' => [ 'name', + 'name' => [ 'regex', + 'pattern' => '/^[a-zA-Z0-9\-_ \.!\?\/\\()äöüÄÖÜéèêóòôáàâíìîúùûÉÈÊÓÒÔÁÀÂÍÌÎÚÙÛß]*$/', 'maxlength' => '255', + 'empty', 'error' => 'Ungültiger Projekt Name.' ], 'org' => [ 'regex', - 'pattern' => '/^[a-zA-Z0-9\-_ ()äöüÄÖÜéèêóòôáàâíìîúùûÉÈÊÓÒÔÁÀÂÍÌÎÚÙÛß]*$/', + 'pattern' => '/^[a-zA-Z0-9\-_ \.!\?\/\\()äöüÄÖÜéèêóòôáàâíìîúùûÉÈÊÓÒÔÁÀÂÍÌÎÚÙÛß]*$/', 'maxlength' => '255', 'empty', 'error' => 'Ungültiger Organisations Name.' From 205e4352c3364778191966f473f535b0f0d24668 Mon Sep 17 00:00:00 2001 From: cherrg Date: Thu, 26 Jul 2018 23:23:25 +0200 Subject: [PATCH 07/42] =?UTF-8?q?update=20validator=20Adresse=20zu=20Ausla?= =?UTF-8?q?generstattung=20hinzugef=C3=BCgt?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore~ | 18 ------------------ lib/class.TexBuilder.php | 6 ++++++ lib/class.Validator.php | 9 +++++---- template/tex/belegpdf.phpTex | 5 +++-- 4 files changed, 14 insertions(+), 24 deletions(-) delete mode 100644 .gitignore~ diff --git a/.gitignore~ b/.gitignore~ deleted file mode 100644 index 0b63a7c..0000000 --- a/.gitignore~ +++ /dev/null @@ -1,18 +0,0 @@ -# https://git-scm.com/docs/gitignore -# https://help.github.com/articles/ignoring-files -# Example .gitignore files: https://github.com/github/gitignore -*.aux -*.log -*.out -*.pdf -*.synctex.gz -.project -.buildpath -*.settings/* -!/img/stura-try.pdf -/hiddenAPIKey.php -old/hiddenAPIKey.php -/parameter.tex -/.fuse* -*.idea - diff --git a/lib/class.TexBuilder.php b/lib/class.TexBuilder.php index 02d6618..d771641 100644 --- a/lib/class.TexBuilder.php +++ b/lib/class.TexBuilder.php @@ -68,6 +68,12 @@ class TexBuilder ], ], ], + 'address' => ['regex', + 'pattern' => '/^[a-zA-Z0-9\-_,:;\/\\\\()& \n\r\.\[\]%\'"#\*\+äöüÄÖÜéèêóòôáàâíìîúùûÉÈÊÓÒÔÁÀÂÍÌÎÚÙÛß]*$/', + 'empty', + 'maxlength' => '1023', + 'error' => 'Adressangabe fehlerhaft.', + ], ], ], 'belege' => ['array', 'optional', diff --git a/lib/class.Validator.php b/lib/class.Validator.php index a50f7c4..a93717c 100644 --- a/lib/class.Validator.php +++ b/lib/class.Validator.php @@ -308,16 +308,17 @@ public function V_float($value, $params = []){ return !$this->setError(true, 200, $msg, 'float to big'); } if (isset($params['step'])){ - $mod = $params['step']; + $mod = $params['step']; $cv = $v; + $ex = ''; if (($p = strpos($mod , '.'))!== false){ $ex = strlen(substr($params['step'], $p + 1)); $ex = (pow(10, $ex)); $mod = $mod * $ex; $cv = $cv * $ex; } - - if ((is_numeric( $cv ) && floor( $cv ).'' != $cv.'') || $cv % $mod != 0){ + $k = strlen($ex); + if ((is_numeric( $cv ) && mb_strpos($value, '.') + ($k) < mb_strlen($value)) || $cv % $mod != 0){ $msg = (isset($params['error']))? $params['error'] : "float invalid step" ; return !$this->setError(true, 200, $msg, 'float invalid step'); } @@ -959,7 +960,7 @@ public function V_date($value, $params = NULL) { public function V_iban($value, $params){ $iban = trim(strip_tags(''.$value)); $iban = strtoupper($iban); // to upper - $iban = str_replace(' ', '', $iban); //remove spaces + $iban = preg_replace('/(\s|\n|\r)/', '', $iban); //remove white spaces //empty if (in_array('empty', $params, true) && $iban === ''){ $this->filtered = $iban; diff --git a/template/tex/belegpdf.phpTex b/template/tex/belegpdf.phpTex index f9dff2f..7d10ade 100644 --- a/template/tex/belegpdf.phpTex +++ b/template/tex/belegpdf.phpTex @@ -47,7 +47,7 @@ bindingoffset=0mm, top=20mm,bottom=20mm} {\large \textsc{#1}} \vspace*{-0.5cm} \\\linia\\ -\vspace*{-0.6cm} +\vspace*{-0.5cm} \end{center} } @@ -142,8 +142,9 @@ bindingoffset=0mm, top=20mm,bottom=20mm} \item \textbf{ID}\hfill \item \textbf{Name} \hfill \item \textbf{Erstellt} \hfill -\item \textbf{Von} \hfill +\item \textbf{Eingereicht von} \hfill \item \textbf{Zahlung An} \hfill +\item \textbf{Addresse} \hfill \end{enumerate} \vspace*{2mm} From b2d3a258f8a47ee906e41773556917388773a8fe Mon Sep 17 00:00:00 2001 From: cherrg Date: Mon, 6 Aug 2018 14:46:46 +0200 Subject: [PATCH 08/42] validator update --- lib/class.TexBuilder.php | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/lib/class.TexBuilder.php b/lib/class.TexBuilder.php index d771641..752d2a6 100644 --- a/lib/class.TexBuilder.php +++ b/lib/class.TexBuilder.php @@ -22,13 +22,13 @@ class TexBuilder 'error' => 'Ungültiges Projekt Datum.' ], 'name' => [ 'regex', - 'pattern' => '/^[a-zA-Z0-9\-_ \.!\?\/\\()äöüÄÖÜéèêóòôáàâíìîúùûÉÈÊÓÒÔÁÀÂÍÌÎÚÙÛß]*$/', + 'pattern' => '/^[a-zA-Z0-9\-_ :,;%$§\&\+\*\.!\?\/\\\[\]\'"#~()äöüÄÖÜéèêóòôáàâíìîúùûÉÈÊÓÒÔÁÀÂÍÌÎÚÙÛß]*$/', 'maxlength' => '255', 'empty', 'error' => 'Ungültiger Projekt Name.' ], 'org' => [ 'regex', - 'pattern' => '/^[a-zA-Z0-9\-_ \.!\?\/\\()äöüÄÖÜéèêóòôáàâíìîúùûÉÈÊÓÒÔÁÀÂÍÌÎÚÙÛß]*$/', + 'pattern' => '/^[a-zA-Z0-9\-_ :,;%$§\&\+\*\.!\?\/\\\[\]\'"#~()äöüÄÖÜéèêóòôáàâíìîúùûÉÈÊÓÒÔÁÀÂÍÌÎÚÙÛß]*$/', 'maxlength' => '255', 'empty', 'error' => 'Ungültiger Organisations Name.' @@ -51,7 +51,9 @@ class TexBuilder 'maxlength' => '255', 'error' => 'Ungültiger Auslagen Name.' ], - 'name' => [ 'name', + 'name' => [ 'regex', + 'empty', + 'pattern' => '/^[a-zA-Z0-9\-_ :,;%$§\&\+\*\.!\?\/\\\[\]\'"#~()äöüÄÖÜéèêóòôáàâíìîúùûÉÈÊÓÒÔÁÀÂÍÌÎÚÙÛß]*$/', 'maxlength' => '255', 'error' => 'Ungültiger Auslagen Name.' ], @@ -62,8 +64,10 @@ class TexBuilder 'zahlung' => ['arraymap', 'required' => true, 'map' => [ - 'name' => [ 'name', - 'maxlength' => '255', + 'name' => [ 'regex', + 'pattern' => '/^[a-zA-Z0-9\-_ :,;%$§\&\+\*\.!\?\/\\\[\]\'"#~()äöüÄÖÜéèêóòôáàâíìîúùûÉÈÊÓÒÔÁÀÂÍÌÎÚÙÛß]*$/', + 'maxlength' => '127', + 'empty', 'error' => 'Ungültiger Zahlungs Name.' ], ], From b8eda1e6a4b65b293e0b30d9e8a5f6f63504624b Mon Sep 17 00:00:00 2001 From: cherrg Date: Mon, 6 Aug 2018 14:47:06 +0200 Subject: [PATCH 09/42] Tippfehler korrigiert --- template/tex/belegpdf.phpTex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/template/tex/belegpdf.phpTex b/template/tex/belegpdf.phpTex index 7d10ade..31495dd 100644 --- a/template/tex/belegpdf.phpTex +++ b/template/tex/belegpdf.phpTex @@ -109,7 +109,7 @@ bindingoffset=0mm, top=20mm,bottom=20mm} \vspace*{-0.5cm} {\setlength{\parindent}{0cm} -\textit{\textbf{Hinweis: }Die Belege für diese Auslagenerstattung müssen an dieses Dokument angehangen, alles zusammen getackert, und im Stura Büro bei Referat Fianzen eingereicht werden. Dazu legt man das Dokument in das Postfach des Referats Finanzen.} +\textit{\textbf{Hinweis: }Die Belege für diese Auslagenerstattung müssen an dieses Dokument angehangen, alles zusammen getackert, und im Stura Büro bei Referat Finanzen eingereicht werden. Dazu legt man das Dokument in das Postfach des Referats Finanzen.} } \vspace*{-0.3cm} \\\linia\\ From 7af8871e122aadaa74100eb53f219c55530c9a0e Mon Sep 17 00:00:00 2001 From: cherrg Date: Thu, 16 Aug 2018 20:35:34 +0200 Subject: [PATCH 10/42] update validator --- lib/class.Validator.php | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/lib/class.Validator.php b/lib/class.Validator.php index a93717c..bfe242f 100644 --- a/lib/class.Validator.php +++ b/lib/class.Validator.php @@ -229,6 +229,29 @@ public function V_dummy($value = NULL, $params = NULL){ return true; } + /** + * boolean validator + * + * params: + * error 2 error message on error case + * + * @param $value + * @param $params + * @return boolean + */ + public function V_boolean($value, $params = []){ + $v = trim($value); + if ($v === true || $v === 'true' || $v === '1' || $v === 1){ + $this->filtered = true; + return !$this->setError(false); + } else if ($v === false || $v === 'false' || $v === '0' || $v === 0 || $v === '') { + $this->filtered = false; + return !$this->setError(false); + } + $msg = (isset($params['error']))? $params['error'] : 'No Boolean' ; + return !$this->setError(true, 200, $msg, 'No Boolean'); + } + /** * integer validator * From 160f8952b3613536f61a00367db6778906833043 Mon Sep 17 00:00:00 2001 From: cherrg Date: Thu, 16 Aug 2018 20:36:03 +0200 Subject: [PATCH 11/42] gremienbescheinigung --- lib/class.TexBuilder.php | 64 +++++++ template/tex/gremienbescheinigung.pdfTex | 210 +++++++++++++++++++++++ 2 files changed, 274 insertions(+) create mode 100644 template/tex/gremienbescheinigung.pdfTex diff --git a/lib/class.TexBuilder.php b/lib/class.TexBuilder.php index 752d2a6..4213e8c 100644 --- a/lib/class.TexBuilder.php +++ b/lib/class.TexBuilder.php @@ -117,6 +117,70 @@ class TexBuilder ] ], ], + 'gremienbescheinigung' => [ + 'vorname' => [ 'regex', + 'pattern' => '/^[a-zA-Z0-9\-_ :,;%$§\&\+\*\.!\?\/\\\[\]\'"#~()äöüÄÖÜéèêóòôáàâíìîúùûÉÈÊÓÒÔÁÀÂÍÌÎÚÙÛß]*$/', + 'maxlength' => '255', + 'empty', + 'error' => 'Ungültiger Vorname.' + ], + 'name' => [ 'regex', + 'pattern' => '/^[a-zA-Z0-9\-_ :,;%$§\&\+\*\.!\?\/\\\[\]\'"#~()äöüÄÖÜéèêóòôáàâíìîúùûÉÈÊÓÒÔÁÀÂÍÌÎÚÙÛß]*$/', + 'maxlength' => '255', + 'empty', + 'error' => 'Ungültiger Name.' + ], + 'adresse' => [ 'regex', + 'pattern' => '/^[a-zA-Z0-9\-_ :,;%$§\&\+\*\.!\?\/\\\[\]\'"#~()äöüÄÖÜéèêóòôáàâíìîúùûÉÈÊÓÒÔÁÀÂÍÌÎÚÙÛß]*$/', + 'maxlength' => '255', + 'empty', + 'error' => 'Ungültige Adresse.' + ], + 'ort' => [ 'regex', + 'pattern' => '/^[a-zA-Z0-9\-_ :,;%$§\&\+\*\.!\?\/\\\[\]\'"#~()äöüÄÖÜéèêóòôáàâíìîúùûÉÈÊÓÒÔÁÀÂÍÌÎÚÙÛß]*$/', + 'maxlength' => '255', + 'empty', + 'error' => 'Ungültiger Ort.' + ], + 'male' => [ 'boolean', + 'error' => 'Wert für male.' + ], + 'geburtsdatum' => ['date', + 'format' => 'd.m.Y', + 'error' => 'Ungültiges Geburtsdatum.' + ], + 'date' => ['date', + 'format' => 'd.m.Y', + 'error' => 'Ungültiges Datum.' + ], + 'sum' => [ 'integer', + 'min' => 0, + 'error' => 'Ungültige Summe' + ], + 'smallest' => ['regex', + 'pattern' => '/^[a-zA-Z0-9\-_ :,;%$§\&\+\*\.!\?\/\\\[\]\'"#~()äöüÄÖÜéèêóòôáàâíìîúùûÉÈÊÓÒÔÁÀÂÍÌÎÚÙÛß]*$/', + 'maxlength' => '255', + 'error' => "Ungültiges Datum 'smallest'." + ], + 'biggest' => ['regex', + 'pattern' => '/^[a-zA-Z0-9\-_ :,;%$§\&\+\*\.!\?\/\\\[\]\'"#~()äöüÄÖÜéèêóòôáàâíìîúùûÉÈÊÓÒÔÁÀÂÍÌÎÚÙÛß]*$/', + 'maxlength' => '255', + 'error' => "Ungültiges Datum 'biggest'." + ], + 'konsul' => ['regex', + 'pattern' => '/^[a-zA-Z0-9\-_ :,;%$§\&\+\*\.!\?\/\\\[\]\'"#~()äöüÄÖÜéèêóòôáàâíìîúùûÉÈÊÓÒÔÁÀÂÍÌÎÚÙÛß]*$/', + 'maxlength' => '255', + 'error' => "Ungültiger Konsul name." + ], + 'arbeit' => ['array', + 'empty', + 'validator' => ['regex', + 'pattern' => '/^[a-zA-Z0-9\-_ :,;%$§\&\+\*\.!\?\/\\\[\]\'"#~()äöüÄÖÜéèêóòôáàâíìîúùûÉÈÊÓÒÔÁÀÂÍÌÎÚÙÛß]*$/', + 'maxlength' => '255', + 'error' => "Ungültiger Array Wert.", + ], + ], + ], ]; /** diff --git a/template/tex/gremienbescheinigung.pdfTex b/template/tex/gremienbescheinigung.pdfTex new file mode 100644 index 0000000..3dfd44f --- /dev/null +++ b/template/tex/gremienbescheinigung.pdfTex @@ -0,0 +1,210 @@ +%%% Quelle: https://meinnoteblog.wordpress.com/2010/11/12/latex-vorlagen-fur-briefe-und-rechnung/ +%--------------------------------------------------------------------------- +\documentclass%% +%--------------------------------------------------------------------------- +[fontsize=12pt,%% Schriftgroesse +%--------------------------------------------------------------------------- +% Satzspiegel +paper=a4,%% Papierformat +enlargefirstpage=on,%% Erste Seite anders +pagenumber=foot,%% Seitenzahl oben mittig +%--------------------------------------------------------------------------- +% Layout +headsepline=off,%% Linie unter der Seitenzahl +parskip=half,%% Abstand zwischen Absaetzen +firsthead=on,%% display header on first page +firstfoot=off,%% display footer on first page +%--------------------------------------------------------------------------- +% Was kommt in den Briefkopf und in die Anschrift +fromalign=right,%% Plazierung des Briefkopfs +fromphone=on,%% Telefonnummer im Absender +fromrule=off,%% Linie im Absender (aftername, afteraddress) +fromfax=on,%% Faxnummer +fromemail=on,%% Emailadresse +fromurl=on,%% Homepage +fromlogo=on,%% Firmenlogo +addrfield=on,%% Adressfeld fuer Fensterkuverts +backaddress=on,%% ...und Absender im Fenster +subject=untitled,%% Plazierung der Betreffzeile +locfield=narrow,%% zusaetzliches Feld fuer Absender +foldmarks=on,%% Faltmarken setzen +numericaldate=off,%% Datum numerisch ausgeben +refline=narrow,%% Geschaeftszeile im Satzspiegel +firstfoot=on,%% Footerbereich +%--------------------------------------------------------------------------- +% Formatierung +draft=off%% Entwurfsmodus +]{scrlttr2} +%--------------------------------------------------------------------------- +\usepackage[english, ngerman]{babel} +%\usepackage{scrpage2} +%\pagestyle{scrheadings} +%\clearscrheadfoot +\usepackage{ wasysym } +\usepackage{url} +\usepackage{lmodern} +\usepackage{enumitem} +\usepackage[utf8]{inputenc} +% symbols: (cell)phone, email +\RequirePackage{marvosym} +% for gray color in header +\RequirePackage{color} +\usepackage[T1]{fontenc} +\usepackage{graphicx} +%\usepackage[pdftitle={\id}]{hyperref} +\usepackage{ifthen} +\usepackage{lastpage} +\usepackage{array,multirow} +\usepackage{booktabs} +\usepackage{tabularx} +\usepackage{qrcode} +%--------------------------------------------------------------------------- +% Schriften werden hier definiert +\renewcommand*\familydefault{\sfdefault} % Latin Modern Sans +\setkomafont{fromname}{\sffamily\color{mygray}\LARGE} +%\setkomafont{pagenumber}{\sffamily} +\setkomafont{subject}{\mdseries} +\setkomafont{backaddress}{\mdseries} +\setkomafont{fromaddress}{\small\sffamily\mdseries\color{mygray}} +%--------------------------------------------------------------------------- +\begin{document} + %--------------------------------------------------------------------------- + % Briefstil und Position des Briefkopfs + \LoadLetterOption{DIN} %% oder: DINmtext, SN, SNleft, KOMAold. + \makeatletter + \@setplength{sigbeforevskip}{18mm} % Abstand der Signatur von dem closing + \@setplength{firstheadvpos}{17mm} % Abstand des Absenderfeldes vom Top + \@setplength{firstfootvpos}{265mm} % Abstand des Footers von oben + \@setplength{firstheadwidth}{\paperwidth} + \@setplength{locwidth}{70mm} % Breite des Locationfeldes + \@setplength{locvpos}{60mm} % Abstand des Locationfeldes von oben + \ifdim \useplength{toaddrhpos}>\z@ + \@addtoplength[-2]{firstheadwidth}{\useplength{toaddrhpos}} + \else + \@addtoplength[2]{firstheadwidth}{\useplength{toaddrhpos}} + \fi + \@setplength{foldmarkhpos}{6.5mm} + \makeatother + %--------------------------------------------------------------------------- + % Farben werden hier definiert + % define gray for header + \definecolor{mygray}{gray}{.55} + % define blue for address + \definecolor{myblue}{rgb}{0.25,0.45,0.75} + + %--------------------------------------------------------------------------- + % Absender Daten + \setkomavar{fromlogo}{\includegraphics[height=3.5cm]{img/stura-try.pdf}} + \setkomavar{fromname}{Studierendenrat der TU Ilmenau} + \setkomavar{fromaddress}{Max-Planck-Ring 7\\98693 Ilmenau} + \setkomavar{fromphone}[\phone~]{03677\,/\,69\,-\,1914} + \setkomavar{fromfax}[\FAX~]{03677\,/\,69\,-\,1193} + \setkomavar{fromemail}[\Letter~]{konsul@tu-ilmenau.de} + \setkomavar{fromurl}[\Mundus~]{https://stura.tu-ilmenau.de} + %\setkomafont{fromaddress}{\small\rmfamily\mdseries\slshape\color{myblue}} + + \setkomavar{backaddressseparator}{, } + %\setkomavar{backaddress}{StuRa der TU Ilmenau, Felderhof 112, 40880 Ratingen} % wenn erwünscht kann hier eine andere Backaddress eingetragen werden + \setkomavar{signature}{ + } + % signature same indention level as rest + \renewcommand*{\raggedsignature}{\raggedright} + \setkomavar{location}{\raggedleft + \vspace{1cm} + %Rechnungsnummer XZY hier gut platziert + }%%Zusätzliches Label Rechts von der Anschrift + + % Anlage neu definieren + \renewcommand{\enclname}{Anlagen} + \setkomavar{enclseparator}{: } + %--------------------------------------------------------------------------- + % Seitenstil + % pagenumber=footmiddle + \pagestyle{myheadings}%% keine Header in der Kopfzeile bzw. plain + \markboth{\usekomavar{fromname}}{\usekomavar{titel}} + \pagenumbering{arabic} + %--------------------------------------------------------------------------- + %--------------------------------------------------------------------------- + \setkomavar{firstfoot}{ + \centering{Seite \arabic{page} von \pageref{LastPage}}\\ + \footnotesize% + \rule[3pt]{\textwidth}{.4pt} \\ + \begin{tabular}[t]{l@{}}% + \usekomavar{fromname}\\ + \usekomavar{fromaddress}\\ + \end{tabular}% + \hfill + \begin{tabular}[t]{l@{}}% + %Verifizierung unter: \\ + %\qrcode[height=1cm]{https://www.ctan.org/tex-archive/macros/latex/contrib/qrcode?lang=en} + \end{tabular}% + \hfill + \begin{tabular}[t]{l@{}}% + Studentische/r Konsul/in \\ + \usekomavar[\phone~]{fromphone}\\ + \usekomavar[\Letter~]{fromemail}\\ + \end{tabular}% + + }% + \setkomavar{nextfoot}{ + \usekomavar{firstfoot} + } + %--------------------------------------------------------------------------- + %\setkomavar{yourref}{Test} + %\setkomavar{yourmail}{} + %\setkomavar{myref}{} + %\setkomavar{customer}{} + %\setkomavar{invoice}{} + %--------------------------------------------------------------------------- + % Datum und Ort werden hier eingetragen + \newkomavar{titel} + \newkomavar{datum} + \setkomavar{titel}{Gremienbescheinigung} + \setkomavar{date}{den } + \setkomavar{place}{Ilmenau} + %--------------------------------------------------------------------------- + \setkomavar{subject}{\Large \textbf{\usekomavar{titel}}} + % Briefkörper bündig am Briefkopf ausrichten + %\setlength{\oddsidemargin}{\useplength{toaddrhpos}} + %\addtolength{\oddsidemargin}{-1in} + %\setlength{\textwidth}{\useplength{firstheadwidth}} + % Hier beginnt der Brief, mit der Anschrift des Empfängers + \begin{letter} + { + \\ + \\ + + }% + %--------------------------------------------------------------------------- + % Der Betreff des Briefes + %--------------------------------------------------------------------------- + \opening{für , geboren am } + + \begin{tabularx}{\textwidth}{ccXllr} + \textbf{von}&\textbf{bis}&\textbf{Position}&&\textbf{Gremium/Organ}&\textbf{Arbeitsaufwand} \\ + \toprule + + \bottomrule + \end{tabularx} + + Insgesamt hat somit ca. Stunden Arbeit im Zeitraum von bis freiwillig erbracht. + + \closing{Für Engagement bedanken wir uns recht herzlich und + wünschen noch viel Erfolg auf dem weiteren Lebensweg.\\ + \includegraphics[angle=-3,height=1cm]{img/sign.png}\\ + i.A. \\ + Studentische/r Konsul/in der TU Ilmenau} + %--------------------------------------------------------------------------- + %--------------------------------------------------------------------------- + \end{letter} + %--------------------------------------------------------------------------- +\end{document} +%--------------------------------------------------------------------------- From d8d348bda1d9bf1291240d0367b77f80545b8752 Mon Sep 17 00:00:00 2001 From: cherrg Date: Thu, 16 Aug 2018 20:36:27 +0200 Subject: [PATCH 12/42] likas commit: some improvements - Versicherung --- template/tex/belegpdf.phpTex | 41 ++++++++++++++++++++++++------------ 1 file changed, 28 insertions(+), 13 deletions(-) diff --git a/template/tex/belegpdf.phpTex b/template/tex/belegpdf.phpTex index 31495dd..2ae9d5a 100644 --- a/template/tex/belegpdf.phpTex +++ b/template/tex/belegpdf.phpTex @@ -29,7 +29,7 @@ bindingoffset=0mm, top=20mm,bottom=20mm} % pdf version to min 1.6 \pdfminorversion=6 -\newcommand{\picpaths}{ $v){ $sp = explode('/', $v); @@ -103,13 +103,13 @@ bindingoffset=0mm, top=20mm,bottom=20mm} \begin{figure}[h] \centering \includegraphics[width=3cm]{} -\end{figure} +\end{figure} \maketitle \vspace*{-0.5cm} {\setlength{\parindent}{0cm} -\textit{\textbf{Hinweis: }Die Belege für diese Auslagenerstattung müssen an dieses Dokument angehangen, alles zusammen getackert, und im Stura Büro bei Referat Finanzen eingereicht werden. Dazu legt man das Dokument in das Postfach des Referats Finanzen.} +\textit{\textbf{Hinweis: }Die Belege für diese Auslagenerstattung müssen an dieses Dokument angehangen, alles zusammen getackert, und im Stura Büro bei Referat Finanzen eingereicht werden. Dazu legt man das Dokument in das Postfach des Referats Finanzen.} } \vspace*{-0.3cm} \\\linia\\ @@ -117,7 +117,7 @@ bindingoffset=0mm, top=20mm,bottom=20mm} \begin{figure}[h] \centering \parbox[b]{0.6\linewidth}{% size of the first signature box - \strut + \strut \textbf{Eingegangen am} \hrule } \end{figure} @@ -147,15 +147,30 @@ bindingoffset=0mm, top=20mm,bottom=20mm} \item \textbf{Addresse} \hfill \end{enumerate} +\mysection{Versicherung} +Ich versichere, dass alle Angaben von mir korrekt gemacht wurden. +Weiterhin habe und werde ich die Belege bei keiner anderen Stelle angeben bzw. abrechnen. +Außerdem habe ich alle Originalbelege angehangen, falls diese Belege nicht digital eingegangen sind. + +\vspace*{2mm} +\parbox[b]{0.4\linewidth}{% ...and the second one + \strut + \textbf{Antragsteller} \\[1.25cm]% This 2cm is the space for the signature under the names + \hrule + \vspace{0.25cm} + () +} + \vspace*{2mm} \begin{center} -\begin{tabular}{rrr} +\begin{tabular}{rrrr} \textbf{Beleg} & \textbf{Datum} & \textbf{Beschreibung} \\ \\ \end{tabular} @@ -167,17 +182,17 @@ bindingoffset=0mm, top=20mm,bottom=20mm} font={\fontsize{29pt}{12}\selectfont}} \foreach \nr/\picname in \picpaths { - %% bei mehrseitigen Belegen erst alle originale und dann alle Kopien + %% bei mehrseitigen Belegen erst alle originale und dann alle Kopien %% bei einseitigen Belegen, beides auf einer Seite - + \pdfximage{\picname} %Switch case der Anzahl Seiten des Beleges - - + + \begin{tikzpicture}[remember picture,overlay] %rechteck \draw [draw=black,line width=5mm,opacity=0.3] (16.5,-10) rectangle (-1,-1.25); - %belegnummer + %belegnummer \node [opacity=1,anchor=north east,xshift=\textwidth, yshift=1.85cm] {\raggedleft \nr}; %text im Rechteck \node [opacity=0.3,anchor=north west, yshift=-2.0cm] {Den hier unten abgebildeten }; @@ -196,19 +211,19 @@ bindingoffset=0mm, top=20mm,bottom=20mm} {%iteriere über die Seiten des Beleg pdfs \ifthenelse{\not \isodd{\index}}{ \begin{tikzpicture}[remember picture,overlay] - %belegnummer + %belegnummer \node [opacity=1,anchor=north east,xshift=\textwidth, yshift=1.85cm] {\raggedleft \nr}; \node [anchor =north east, inner sep=0pt,outer sep=0pt,yshift=-2cm] at (current page.north east) {\fbox{\includegraphics[page=\index,angle=90,width=0.925\paperwidth,height=0.415\paperheight]{\picname}}}; \node [rotate=90, opacity=0.75,anchor=north west,xshift=-0.375\paperheight,yshift=2.92cm] { Seite \index}; \end{tikzpicture} }{ %else unterer Teil des Blattes - \begin{tikzpicture}[remember picture,overlay] + \begin{tikzpicture}[remember picture,overlay] \node [anchor =north east, inner sep=0pt,outer sep=0pt,yshift=-14.55cm] at (current page.north east) {\fbox{\includegraphics[page=\index,angle=90,width=0.925\paperwidth,height=0.415\paperheight]{\picname}}}; \node [rotate=90, opacity=0.75,anchor=north west,xshift=-0.75\paperheight,yshift=2.92cm] { Seite \index}; \end{tikzpicture} \newpage } %if end - + }{} %else end if page > 1 \newpage } From 8243e55f69fcaddc5e6f5c0ad660eeaf3cbd0247 Mon Sep 17 00:00:00 2001 From: cherrg Date: Thu, 16 Aug 2018 20:59:06 +0200 Subject: [PATCH 13/42] lukas commit: fixed phperror --- lib/class.TexBuilder.php | 135 ++++++++++++++++++----------------- template/tex/belegpdf.phpTex | 2 +- 2 files changed, 72 insertions(+), 65 deletions(-) diff --git a/lib/class.TexBuilder.php b/lib/class.TexBuilder.php index 4213e8c..7b0e036 100644 --- a/lib/class.TexBuilder.php +++ b/lib/class.TexBuilder.php @@ -1,14 +1,15 @@ validator = new Validator(); + $this->error = false; + } + // getter / setter -------------------- /** * @return the $error */ - public function getError() - { + public function getError(){ return $this->error; } - // constructor -------------------- + // helper ----------------------------- /** - * class constructor + * escape latex invalid letters + * + * @param string in + * @return string */ - function __construct() - { - $this->validator = new Validator(); - $this->error = false; + private static function texEscape($in){ + return str_replace( + ['\\', '~', '_', '%', '$', '&', '^', '"', '{', '}'], + ['\textbackslash', '\textasciitilde', '\_', '\%', '\$', '\&', '^', "''", '\{', '\}'], + $in + ); } - + /** * check if pdf builder exists * @return bool @@ -237,6 +252,18 @@ public function builderExist($key){ /** * validate Post or $data variables by builderKey + * alias of validate + * @param string $key + * @param array $data + * @return bool + */ + public function setData($key, $data = null){ + return $this->validate($key, $data); + } + + /** + * validate Post or $data variables by builderKey + * * @param string $key * @param array $data * @return bool @@ -260,46 +287,12 @@ public function validate($key, $data = null){ } } - public function getValidated(){ - return $this->validator->getFiltered(); - } - - /** - * validate Post or $data variables by builderKey - * alias of validate - * @param string $key - * @param array $data - * @return bool - */ - public function setData($key, $data = null){ - return $this->validate($key, $data); - } - - /** - * get binary pdf data - * @param bool $echo - * return binary|NULL - */ - public function getBinary($echo = false) { - if ($this->binary_build){ - if ($echo) echo $this->binary_build; - return $this->binary_build; - } - return NULL; - } - /** - * get pdf as base64 string - * @param bool $echo - * return string + * return validated and filtered request data + * @return mixed */ - public function getBase64($echo = false) { - if ($this->binary_build){ - $t = base64_encode($this->getBinary(false)); - if ($echo) echo $t; - return $t; - } - return NULL; + public function getValidated(){ + return $this->validator->getFiltered(); } // build pdf ------------------------------------ @@ -325,7 +318,7 @@ public function build() { $files[str_pad($b['id'], 3, "0", STR_PAD_LEFT ).'-B'.$b['short']] = $f.'.pdf'; } } - + $validated = $this->validator->getFiltered(); $validated['files'] = $files; //get tex code @@ -339,6 +332,33 @@ public function build() { return true; } } + + /** + * get binary pdf data + * @param bool $echo + * return binary|NULL + */ + public function getBinary($echo = false) { + if ($this->binary_build){ + if ($echo) echo $this->binary_build; + return $this->binary_build; + } + return NULL; + } + + /** + * get pdf as base64 string + * @param bool $echo + * return string + */ + public function getBase64($echo = false) { + if ($this->binary_build){ + $t = base64_encode($this->getBinary(false)); + if ($echo) echo $t; + return $t; + } + return NULL; + } // private ---------------------------------------- @@ -410,20 +430,7 @@ private function _createPDF($tex_code){ //unlink files unlink($pdf_f); unlink($f); - } - - /** - * escape latex invalid letters - * @param string in - * @return string - */ - private static function texEscape($in){ - return str_replace( - ['\\', '~', '_', '%', '$', '&', '^', '"', '{', '}'], - ['\textbackslash', '\textasciitilde', '\_', '\%', '\$', '\&', '^', "''", '\{', '\}'], - $in - ); - } + } } ?> \ No newline at end of file diff --git a/template/tex/belegpdf.phpTex b/template/tex/belegpdf.phpTex index 2ae9d5a..a9ccb5e 100644 --- a/template/tex/belegpdf.phpTex +++ b/template/tex/belegpdf.phpTex @@ -170,7 +170,7 @@ Außerdem habe ich alle Originalbelege angehangen, falls diese Belege nicht digi echo '\\vspace{-3.8mm}\\\\\\hline\\parbox[t]{3cm}{\\raggedleft '.self::texEscape(str_pad($b['id'], 3, "0", STR_PAD_LEFT ).'-B'.$b['short']).'} & '; $d = explode(' ', $b['date']); echo '\\parbox[t]{4cm}{\\raggedleft '.self::texEscape($d[0]).'} & '; - echo '\\parbox[t]{5cm}{\\raggedleft \\ding{51}' . lesbare digitale Rechnung . '} & '; + echo '\\parbox[t]{5cm}{\\raggedleft \\ding{51} lesbare digitale Rechnung} & '; echo '\\parbox[t]{8cm}{\\raggedleft '.self::texEscape($b['desc']).'}'; ?>\\ \end{tabular} From 33a7141ceec15127d52a2282345e2b0075e2d5ee Mon Sep 17 00:00:00 2001 From: cherrg Date: Thu, 16 Aug 2018 21:04:20 +0200 Subject: [PATCH 14/42] lukas commit: some further improvements --- template/tex/belegpdf.phpTex | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/template/tex/belegpdf.phpTex b/template/tex/belegpdf.phpTex index a9ccb5e..d4cc62e 100644 --- a/template/tex/belegpdf.phpTex +++ b/template/tex/belegpdf.phpTex @@ -148,29 +148,28 @@ bindingoffset=0mm, top=20mm,bottom=20mm} \end{enumerate} \mysection{Versicherung} -Ich versichere, dass alle Angaben von mir korrekt gemacht wurden. -Weiterhin habe und werde ich die Belege bei keiner anderen Stelle angeben bzw. abrechnen. -Außerdem habe ich alle Originalbelege angehangen, falls diese Belege nicht digital eingegangen sind. +Ich versichere, dass alle Angaben von mir, zum jetzigen Zeitpunkt, korrekt eingereicht wurden.\\ +Weiterhin habe und werde ich die Belege bei keiner anderen Stelle angeben bzw. abrechnen.\\ +Außerdem habe ich alle Belege im Original angehangen, sofern diese nicht digital eingegangen sind. +Digitale Belege, die nicht in der verkleinerten Fassung lesbar sind habe ich zusätzlich auch nocheinmal in groß angehangen. \vspace*{2mm} \parbox[b]{0.4\linewidth}{% ...and the second one - \strut - \textbf{Antragsteller} \\[1.25cm]% This 2cm is the space for the signature under the names + \\[1.25cm]% This 2cm is the space for the signature under the names \hrule \vspace{0.25cm} - () + Unterschrift Antragssteller/in: } \vspace*{2mm} \begin{center} -\begin{tabular}{rrrr} +\begin{tabular}{rrr} \textbf{Beleg} & \textbf{Datum} & \textbf{Beschreibung} \\ \\ \end{tabular} From 8f7c955ea7d586dbe891a6ebdc25f79f07ed5659 Mon Sep 17 00:00:00 2001 From: cherrg Date: Thu, 16 Aug 2018 21:06:32 +0200 Subject: [PATCH 15/42] lukas commit: some fixes --- template/tex/belegpdf.phpTex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/template/tex/belegpdf.phpTex b/template/tex/belegpdf.phpTex index d4cc62e..ff6ed59 100644 --- a/template/tex/belegpdf.phpTex +++ b/template/tex/belegpdf.phpTex @@ -155,7 +155,7 @@ Digitale Belege, die nicht in der verkleinerten Fassung lesbar sind habe ich zus \vspace*{2mm} \parbox[b]{0.4\linewidth}{% ...and the second one - \\[1.25cm]% This 2cm is the space for the signature under the names + \vspace*{1.25cm}% This 2cm is the space for the signature under the names \hrule \vspace{0.25cm} Unterschrift Antragssteller/in: From 61d29e0e27a9309e1c2917a7336fcae1c89a58d7 Mon Sep 17 00:00:00 2001 From: cherrg Date: Thu, 16 Aug 2018 21:40:32 +0200 Subject: [PATCH 16/42] some improvements --- template/tex/belegpdf.phpTex | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/template/tex/belegpdf.phpTex b/template/tex/belegpdf.phpTex index ff6ed59..7b4d91f 100644 --- a/template/tex/belegpdf.phpTex +++ b/template/tex/belegpdf.phpTex @@ -14,6 +14,7 @@ \renewcommand\familydefault{\sfdefault} \usepackage{tgheros} \usepackage{intcalc} +\usepackage{setspace} \usepackage{amsmath,amssymb,amsthm,textcomp} %\usepackage{enumerate} @@ -108,9 +109,9 @@ bindingoffset=0mm, top=20mm,bottom=20mm} \maketitle \vspace*{-0.5cm} -{\setlength{\parindent}{0cm} +{\setlength{\parindent}{0cm}{\footnotesize \textit{\textbf{Hinweis: }Die Belege für diese Auslagenerstattung müssen an dieses Dokument angehangen, alles zusammen getackert, und im Stura Büro bei Referat Finanzen eingereicht werden. Dazu legt man das Dokument in das Postfach des Referats Finanzen.} -} +}} \vspace*{-0.3cm} \\\linia\\ \vspace{-0.3cm} @@ -135,7 +136,6 @@ bindingoffset=0mm, top=20mm,bottom=20mm} \item \textbf{Erstellt} \hfill \end{enumerate} -\vspace*{2mm} \mysection{Abrechnung} \begin{enumerate}[label=\Roman*,resume] \itemsep-2mm @@ -148,17 +148,19 @@ bindingoffset=0mm, top=20mm,bottom=20mm} \end{enumerate} \mysection{Versicherung} +{\footnotesize Ich versichere, dass alle Angaben von mir, zum jetzigen Zeitpunkt, korrekt eingereicht wurden.\\ Weiterhin habe und werde ich die Belege bei keiner anderen Stelle angeben bzw. abrechnen.\\ Außerdem habe ich alle Belege im Original angehangen, sofern diese nicht digital eingegangen sind. Digitale Belege, die nicht in der verkleinerten Fassung lesbar sind habe ich zusätzlich auch nocheinmal in groß angehangen. +} \vspace*{2mm} \parbox[b]{0.4\linewidth}{% ...and the second one \vspace*{1.25cm}% This 2cm is the space for the signature under the names \hrule \vspace{0.25cm} - Unterschrift Antragssteller/in: + Unterschrift } \vspace*{2mm} From aab01e90aaf55894d7d4abbd026ca17bd355583b Mon Sep 17 00:00:00 2001 From: cherrg Date: Mon, 24 Sep 2018 23:02:20 +0200 Subject: [PATCH 17/42] add gremienbescheinigung autoformat --- lib/class.TexBuilder.php | 908 ++++++++++++----------- template/tex/gremienbescheinigung.phpTex | 229 ++++++ 2 files changed, 709 insertions(+), 428 deletions(-) create mode 100644 template/tex/gremienbescheinigung.phpTex diff --git a/lib/class.TexBuilder.php b/lib/class.TexBuilder.php index 7b0e036..0d90ff9 100644 --- a/lib/class.TexBuilder.php +++ b/lib/class.TexBuilder.php @@ -4,433 +4,485 @@ * @author michael * */ -class TexBuilder -{ - // member variables ---------------- - /** - * vailidator map - * - * @var array - */ - private static $valiMap = [ - 'belegpdf' => [ - 'projekt' => ['arraymap', - 'required' => true, - 'map' => [ - 'created' => [ 'date', - 'format' => 'Y-m-d H:i:s', - 'parse' => 'Y-m-d H:i:s', - 'error' => 'Ungültiges Projekt Datum.' - ], - 'name' => [ 'regex', - 'pattern' => '/^[a-zA-Z0-9\-_ :,;%$§\&\+\*\.!\?\/\\\[\]\'"#~()äöüÄÖÜéèêóòôáàâíìîúùûÉÈÊÓÒÔÁÀÂÍÌÎÚÙÛß]*$/', - 'maxlength' => '255', - 'empty', - 'error' => 'Ungültiger Projekt Name.' - ], - 'org' => [ 'regex', - 'pattern' => '/^[a-zA-Z0-9\-_ :,;%$§\&\+\*\.!\?\/\\\[\]\'"#~()äöüÄÖÜéèêóòôáàâíìîúùûÉÈÊÓÒÔÁÀÂÍÌÎÚÙÛß]*$/', - 'maxlength' => '255', - 'empty', - 'error' => 'Ungültiger Organisations Name.' - ], - 'id' => ['integer', - 'min' => '1', - 'error' => 'Ungültige Projekt ID.' - ], - ], - ], - 'auslage' => ['arraymap', - 'required' => true, - 'map' => [ - 'created' => [ 'date', - 'format' => 'Y-m-d H:i:s', - 'parse' => 'Y-m-d H:i:s', - 'error' => 'Ungültiges Auslagen Datum.' - ], - 'created_by' => [ 'name', - 'maxlength' => '255', - 'error' => 'Ungültiger Auslagen Name.' - ], - 'name' => [ 'regex', - 'empty', - 'pattern' => '/^[a-zA-Z0-9\-_ :,;%$§\&\+\*\.!\?\/\\\[\]\'"#~()äöüÄÖÜéèêóòôáàâíìîúùûÉÈÊÓÒÔÁÀÂÍÌÎÚÙÛß]*$/', - 'maxlength' => '255', - 'error' => 'Ungültiger Auslagen Name.' - ], - 'id' => ['integer', - 'min' => '1', - 'error' => 'Ungültige Auslagen ID.' - ], - 'zahlung' => ['arraymap', - 'required' => true, - 'map' => [ - 'name' => [ 'regex', - 'pattern' => '/^[a-zA-Z0-9\-_ :,;%$§\&\+\*\.!\?\/\\\[\]\'"#~()äöüÄÖÜéèêóòôáàâíìîúùûÉÈÊÓÒÔÁÀÂÍÌÎÚÙÛß]*$/', - 'maxlength' => '127', - 'empty', - 'error' => 'Ungültiger Zahlungs Name.' - ], - ], - ], - 'address' => ['regex', - 'pattern' => '/^[a-zA-Z0-9\-_,:;\/\\\\()& \n\r\.\[\]%\'"#\*\+äöüÄÖÜéèêóòôáàâíìîúùûÉÈÊÓÒÔÁÀÂÍÌÎÚÙÛß]*$/', - 'empty', - 'maxlength' => '1023', - 'error' => 'Adressangabe fehlerhaft.', - ], - ], - ], - 'belege' => ['array', 'optional', - 'minlength' => 1, - 'key' => [ 'regex', - 'pattern' => '/^(\d+)$/' - ], - 'validator' => ['arraymap', - 'required' => true, - 'map' => [ - 'id' => ['integer', - 'min' => '1', - 'error' => 'Ungültige Beleg ID.' - ], - 'short' => ['integer', - 'min' => '1', - 'error' => 'Ungültige Beleg NR.' - ], - 'date' => [ 'date', - 'format' => 'Y-m-d H:i:s', - 'parse' => 'Y-m-d', - 'error' => 'Ungültiges Beleg Datum.' - ], - 'desc' => [ 'text', - 'strip', - 'trim', - ], - 'file_id' => ['integer', - 'min' => '1', - 'error' => 'Ungültige Beleg File ID.' - ], - 'file' => [ 'text', - 'strip', - 'trim', - ], - ] - ] - ], - ], - 'gremienbescheinigung' => [ - 'vorname' => [ 'regex', - 'pattern' => '/^[a-zA-Z0-9\-_ :,;%$§\&\+\*\.!\?\/\\\[\]\'"#~()äöüÄÖÜéèêóòôáàâíìîúùûÉÈÊÓÒÔÁÀÂÍÌÎÚÙÛß]*$/', - 'maxlength' => '255', - 'empty', - 'error' => 'Ungültiger Vorname.' - ], - 'name' => [ 'regex', - 'pattern' => '/^[a-zA-Z0-9\-_ :,;%$§\&\+\*\.!\?\/\\\[\]\'"#~()äöüÄÖÜéèêóòôáàâíìîúùûÉÈÊÓÒÔÁÀÂÍÌÎÚÙÛß]*$/', - 'maxlength' => '255', - 'empty', - 'error' => 'Ungültiger Name.' - ], - 'adresse' => [ 'regex', - 'pattern' => '/^[a-zA-Z0-9\-_ :,;%$§\&\+\*\.!\?\/\\\[\]\'"#~()äöüÄÖÜéèêóòôáàâíìîúùûÉÈÊÓÒÔÁÀÂÍÌÎÚÙÛß]*$/', - 'maxlength' => '255', - 'empty', - 'error' => 'Ungültige Adresse.' - ], - 'ort' => [ 'regex', - 'pattern' => '/^[a-zA-Z0-9\-_ :,;%$§\&\+\*\.!\?\/\\\[\]\'"#~()äöüÄÖÜéèêóòôáàâíìîúùûÉÈÊÓÒÔÁÀÂÍÌÎÚÙÛß]*$/', - 'maxlength' => '255', - 'empty', - 'error' => 'Ungültiger Ort.' - ], - 'male' => [ 'boolean', - 'error' => 'Wert für male.' - ], - 'geburtsdatum' => ['date', - 'format' => 'd.m.Y', - 'error' => 'Ungültiges Geburtsdatum.' - ], - 'date' => ['date', - 'format' => 'd.m.Y', - 'error' => 'Ungültiges Datum.' - ], - 'sum' => [ 'integer', - 'min' => 0, - 'error' => 'Ungültige Summe' - ], - 'smallest' => ['regex', - 'pattern' => '/^[a-zA-Z0-9\-_ :,;%$§\&\+\*\.!\?\/\\\[\]\'"#~()äöüÄÖÜéèêóòôáàâíìîúùûÉÈÊÓÒÔÁÀÂÍÌÎÚÙÛß]*$/', - 'maxlength' => '255', - 'error' => "Ungültiges Datum 'smallest'." - ], - 'biggest' => ['regex', - 'pattern' => '/^[a-zA-Z0-9\-_ :,;%$§\&\+\*\.!\?\/\\\[\]\'"#~()äöüÄÖÜéèêóòôáàâíìîúùûÉÈÊÓÒÔÁÀÂÍÌÎÚÙÛß]*$/', - 'maxlength' => '255', - 'error' => "Ungültiges Datum 'biggest'." - ], - 'konsul' => ['regex', - 'pattern' => '/^[a-zA-Z0-9\-_ :,;%$§\&\+\*\.!\?\/\\\[\]\'"#~()äöüÄÖÜéèêóòôáàâíìîúùûÉÈÊÓÒÔÁÀÂÍÌÎÚÙÛß]*$/', - 'maxlength' => '255', - 'error' => "Ungültiger Konsul name." - ], - 'arbeit' => ['array', - 'empty', - 'validator' => ['regex', - 'pattern' => '/^[a-zA-Z0-9\-_ :,;%$§\&\+\*\.!\?\/\\\[\]\'"#~()äöüÄÖÜéèêóòôáàâíìîúùûÉÈÊÓÒÔÁÀÂÍÌÎÚÙÛß]*$/', - 'maxlength' => '255', - 'error' => "Ungültiger Array Wert.", - ], - ], - ], - ]; - - /** - * - * @var Validator - */ - private $validator; - - /** - * is error and error message - * @var bool|string - */ - private $error = false; - - /** - * last valid key - * @var bool|string - */ - private $last_key = false; - - /** - * @var binary pdf file data - */ - private $binary_build; - - // constructor -------------------- - - /** - * class constructor - */ - function __construct(){ - $this->validator = new Validator(); - $this->error = false; - } - - // getter / setter -------------------- - - /** - * @return the $error - */ - public function getError(){ - return $this->error; - } - - // helper ----------------------------- - - /** - * escape latex invalid letters - * - * @param string in - * @return string - */ - private static function texEscape($in){ - return str_replace( - ['\\', '~', '_', '%', '$', '&', '^', '"', '{', '}'], - ['\textbackslash', '\textasciitilde', '\_', '\%', '\$', '\&', '^', "''", '\{', '\}'], - $in - ); - } - - /** - * check if pdf builder exists - * @return bool - */ - public function builderExist($key){ - return (isset(self::$valiMap[$key])); - } - - /** - * validate Post or $data variables by builderKey - * alias of validate - * @param string $key - * @param array $data - * @return bool - */ - public function setData($key, $data = null){ - return $this->validate($key, $data); - } - - /** - * validate Post or $data variables by builderKey - * - * @param string $key - * @param array $data - * @return bool - */ - public function validate($key, $data = null){ - if (!$this->builderExist($key)){ - $this->error = 'Unknown builder.'; - $this->last_key = false; - return false; - } else { - $this->validator->validateMap(($data)?$data : $_POST, self::$valiMap[$key]); - if (!$this->validator->getIsError()){ - $this->error = false; - $this->last_key = $key; - return true; - } else { - $this->error = $this->validator->getLastErrorMsg(); - $this->last_key = false; - return false; - } - } - } - - /** - * return validated and filtered request data - * @return mixed - */ - public function getValidated(){ - return $this->validator->getFiltered(); - } - - // build pdf ------------------------------------ - - /** - * build pdf - */ - public function build() { - if (!$this->error && $this->last_key){ - //create images files - $files = []; - if(isset($this->validator->getFiltered()['belege'][0]['file'])){ - foreach ($this->validator->getFiltered()['belege'] as $b){ - if(($f = tempnam(sys_get_temp_dir(), 'tex-tmp-')) === false) { - $this->error = "Failed to create temporary file"; - foreach ($files as $v){ - if(file_exists($v)) unlink($v); - } - return; - } - if(file_exists($f)) unlink($f); - file_put_contents($f.'.pdf', base64_decode($b['file'])); - $files[str_pad($b['id'], 3, "0", STR_PAD_LEFT ).'-B'.$b['short']] = $f.'.pdf'; - } - } - - $validated = $this->validator->getFiltered(); - $validated['files'] = $files; - //get tex code - $tex = self::_renderTex($this->last_key, $validated); - //render twice - $this->_createPDF($tex); - //remove inline images - foreach ($files as $v){ - if(file_exists($v)) unlink($v); - } - return true; - } - } - - /** - * get binary pdf data - * @param bool $echo - * return binary|NULL - */ - public function getBinary($echo = false) { - if ($this->binary_build){ - if ($echo) echo $this->binary_build; - return $this->binary_build; - } - return NULL; - } - - /** - * get pdf as base64 string - * @param bool $echo - * return string - */ - public function getBase64($echo = false) { - if ($this->binary_build){ - $t = base64_encode($this->getBinary(false)); - if ($echo) echo $t; - return $t; - } - return NULL; - } - - // private ---------------------------------------- - - /** - * render tex code - * @param string $key - * @param array $param - */ - private static function _renderTex($key, $param){ - $file = SYSBASE.'/template/tex/'.$key.'.phpTex'; - $tex = ''; - if(file_exists($file)) { - ob_start(); - include $file; - $tex = ob_get_clean(); - } - return $tex; - } - - /** - * create pdf file set binary code - */ - private function _createPDF($tex_code){ - //create temp file - if(($f = tempnam(sys_get_temp_dir(), 'tex-')) === false) { - $this->error = "Failed to create temporary file"; - return; - } - - $tex_f = $f . ".tex"; - $aux_f = $f . ".aux"; - $log_f = $f . ".log"; - $pdf_f = $f . ".pdf"; - - //write file to tmp file - file_put_contents($tex_f, $tex_code); - //switch to directory - chdir(sys_get_temp_dir()); - - //run command - $shellcmd = "pdflatex \"\\input{" . $tex_f . '}"'; - - $status = -100; - exec($shellcmd, $out, $status); - //render twice -> page numbers, etc. - exec($shellcmd, $out, $status); - - //unlink files - if(file_exists($tex_f)) unlink($tex_f); - if(file_exists($aux_f)) unlink($aux_f); - if(file_exists($log_f)) unlink($log_f); - - // Test here - if(!file_exists($pdf_f)) { - //unlink files - unlink($f); - $this->error = [ - 'msg' => "Output was not generated and latex returned: $status.", - 'code' => $status, - 'log' => $out, - 'tex' => $tex_code, - ]; - return; - } - - //load file to memory - $this->binary_build = file_get_contents ( $pdf_f ); - - //unlink files - unlink($pdf_f); - unlink($f); - } +class TexBuilder{ + // member variables ---------------- + /** + * vailidator map + * + * @var array + */ + private static $valiMap = [ + 'belegpdf' => [ + 'projekt' => ['arraymap', + 'required' => true, + 'map' => [ + 'created' => ['date', + 'format' => 'Y-m-d H:i:s', + 'parse' => 'Y-m-d H:i:s', + 'error' => 'Ungültiges Projekt Datum.' + ], + 'name' => ['regex', + 'pattern' => '/^[a-zA-Z0-9\-_ :,;%$§\&\+\*\.!\?\/\\\[\]\'"#~()äöüÄÖÜéèêóòôáàâíìîúùûÉÈÊÓÒÔÁÀÂÍÌÎÚÙÛß]*$/', + 'maxlength' => '255', + 'empty', + 'error' => 'Ungültiger Projekt Name.' + ], + 'org' => ['regex', + 'pattern' => '/^[a-zA-Z0-9\-_ :,;%$§\&\+\*\.!\?\/\\\[\]\'"#~()äöüÄÖÜéèêóòôáàâíìîúùûÉÈÊÓÒÔÁÀÂÍÌÎÚÙÛß]*$/', + 'maxlength' => '255', + 'empty', + 'error' => 'Ungültiger Organisations Name.' + ], + 'id' => ['integer', + 'min' => '1', + 'error' => 'Ungültige Projekt ID.' + ], + ], + ], + 'auslage' => ['arraymap', + 'required' => true, + 'map' => [ + 'created' => ['date', + 'format' => 'Y-m-d H:i:s', + 'parse' => 'Y-m-d H:i:s', + 'error' => 'Ungültiges Auslagen Datum.' + ], + 'created_by' => ['name', + 'maxlength' => '255', + 'error' => 'Ungültiger Auslagen Name.' + ], + 'name' => ['regex', + 'empty', + 'pattern' => '/^[a-zA-Z0-9\-_ :,;%$§\&\+\*\.!\?\/\\\[\]\'"#~()äöüÄÖÜéèêóòôáàâíìîúùûÉÈÊÓÒÔÁÀÂÍÌÎÚÙÛß]*$/', + 'maxlength' => '255', + 'error' => 'Ungültiger Auslagen Name.' + ], + 'id' => ['integer', + 'min' => '1', + 'error' => 'Ungültige Auslagen ID.' + ], + 'zahlung' => ['arraymap', + 'required' => true, + 'map' => [ + 'name' => ['regex', + 'pattern' => '/^[a-zA-Z0-9\-_ :,;%$§\&\+\*\.!\?\/\\\[\]\'"#~()äöüÄÖÜéèêóòôáàâíìîúùûÉÈÊÓÒÔÁÀÂÍÌÎÚÙÛß]*$/', + 'maxlength' => '127', + 'empty', + 'error' => 'Ungültiger Zahlungs Name.' + ], + ], + ], + 'address' => ['regex', + 'pattern' => '/^[a-zA-Z0-9\-_,:;\/\\\\()& \n\r\.\[\]%\'"#\*\+äöüÄÖÜéèêóòôáàâíìîúùûÉÈÊÓÒÔÁÀÂÍÌÎÚÙÛß]*$/', + 'empty', + 'maxlength' => '1023', + 'error' => 'Adressangabe fehlerhaft.', + ], + ], + ], + 'belege' => ['array', 'optional', + 'minlength' => 1, + 'key' => ['regex', + 'pattern' => '/^(\d+)$/' + ], + 'validator' => ['arraymap', + 'required' => true, + 'map' => [ + 'id' => ['integer', + 'min' => '1', + 'error' => 'Ungültige Beleg ID.' + ], + 'short' => ['integer', + 'min' => '1', + 'error' => 'Ungültige Beleg NR.' + ], + 'date' => ['date', + 'format' => 'Y-m-d H:i:s', + 'parse' => 'Y-m-d', + 'error' => 'Ungültiges Beleg Datum.' + ], + 'desc' => ['text', + 'strip', + 'trim', + ], + 'file_id' => ['integer', + 'min' => '1', + 'error' => 'Ungültige Beleg File ID.' + ], + 'file' => ['text', + 'strip', + 'trim', + ], + ] + ] + ], + ], + 'gremienbescheinigung' => [ + 'vorname' => ['regex', + 'pattern' => '/^[a-zA-Z0-9\-_ :,;%$§\&\+\*\.!\?\/\\\[\]\'"#~()äöüÄÖÜéèêóòôáàâíìîúùûÉÈÊÓÒÔÁÀÂÍÌÎÚÙÛß]*$/', + 'maxlength' => '255', + 'empty', + 'error' => 'Ungültiger Vorname.' + ], + 'name' => ['regex', + 'pattern' => '/^[a-zA-Z0-9\-_ :,;%$§\&\+\*\.!\?\/\\\[\]\'"#~()äöüÄÖÜéèêóòôáàâíìîúùûÉÈÊÓÒÔÁÀÂÍÌÎÚÙÛß]*$/', + 'maxlength' => '255', + 'empty', + 'error' => 'Ungültiger Name.' + ], + 'adresse' => ['regex', + 'pattern' => '/^[a-zA-Z0-9\-_ :,;%$§\&\+\*\.!\?\/\\\[\]\'"#~()äöüÄÖÜéèêóòôáàâíìîúùûÉÈÊÓÒÔÁÀÂÍÌÎÚÙÛß]*$/', + 'maxlength' => '255', + 'empty', + 'error' => 'Ungültige Adresse.' + ], + 'ort' => ['regex', + 'pattern' => '/^[a-zA-Z0-9\-_ :,;%$§\&\+\*\.!\?\/\\\[\]\'"#~()äöüÄÖÜéèêóòôáàâíìîúùûÉÈÊÓÒÔÁÀÂÍÌÎÚÙÛß]*$/', + 'maxlength' => '255', + 'empty', + 'error' => 'Ungültiger Ort.' + ], + 'male' => ['boolean', + 'error' => 'Wert für male.' + ], + 'geburtsdatum' => ['date', + 'format' => 'd.m.Y', + 'error' => 'Ungültiges Geburtsdatum.' + ], + 'date' => ['date', + 'format' => 'd.m.Y', + 'error' => 'Ungültiges Datum.' + ], + 'sum' => ['integer', + 'min' => 0, + 'error' => 'Ungültige Summe' + ], + 'smallest' => ['regex', + 'pattern' => '/^[a-zA-Z0-9\-_ :,;%$§\&\+\*\.!\?\/\\\[\]\'"#~()äöüÄÖÜéèêóòôáàâíìîúùûÉÈÊÓÒÔÁÀÂÍÌÎÚÙÛß]*$/', + 'maxlength' => '255', + 'error' => "Ungültiges Datum 'smallest'." + ], + 'biggest' => ['regex', + 'pattern' => '/^[a-zA-Z0-9\-_ :,;%$§\&\+\*\.!\?\/\\\[\]\'"#~()äöüÄÖÜéèêóòôáàâíìîúùûÉÈÊÓÒÔÁÀÂÍÌÎÚÙÛß]*$/', + 'maxlength' => '255', + 'error' => "Ungültiges Datum 'biggest'." + ], + 'konsul' => ['regex', + 'pattern' => '/^[a-zA-Z0-9\-_ :,;%$§\&\+\*\.!\?\/\\\[\]\'"#~()äöüÄÖÜéèêóòôáàâíìîúùûÉÈÊÓÒÔÁÀÂÍÌÎÚÙÛß]*$/', + 'maxlength' => '255', + 'error' => "Ungültiger Konsul name." + ], + 'skills' => [ 'array', + 'validator' => ['regex', + 'pattern' => '/^[a-zA-Z0-9\-_ :,;%$§\&\+\*\.!\?\/\\\[\]\'"#~()äöüÄÖÜéèêóòôáàâíìîúùûÉÈÊÓÒÔÁÀÂÍÌÎÚÙÛß]*$/', + 'maxlength' => '500', + 'error' => "Ungültiger Skill." + ], + ], + 'arbeit' => ['array', + 'empty', + 'validator' => ['arraymap', + 'required' => true, + 'map' => [ + 'checked' => ['integer', + 'min' => '1', + 'error' => 'Some checking error.' + ], + 'von' => ['date', + 'format' => 'Y-m-d', + 'error' => 'Ungültiges "von" Datum.' + ], + 'bis' => ['date', + 'empty', + 'format' => 'Y-m-d', + 'error' => 'Ungültiges "bis" Datum.' + ], + 'position' => ['text', + 'strip', + 'trim', + ], + 'gremium' => ['text', + 'strip', + 'trim', + ], + 'h' => ['integer', + 'min' => '0', + 'error' => 'Ungültige Stunden Zahl.' + ], + 'type' => ['regex', + 'pattern' => '#^h(/W|/S)?$#', + 'maxlength' => '4', + 'error' => "Ungültiger type name." + ], + ] + ] + ], + 'additional-text' => ['text', + 'empty', + 'strip', + 'trim', + ], + ], + ]; + + /** + * + * @var Validator + */ + private $validator; + + /** + * is error and error message + * + * @var bool|string + */ + private $error = false; + + /** + * last valid key + * + * @var bool|string + */ + private $last_key = false; + + /** + * @var binary pdf file data + */ + private $binary_build; + + // constructor -------------------- + + /** + * class constructor + */ + function __construct(){ + $this->validator = new Validator(); + $this->error = false; + } + + // getter / setter -------------------- + + /** + * escape latex invalid letters + * + * @param string in + * + * @return string + */ + private static function texEscape($in){ + return str_replace( + ['\\', '~', '_', '%', '$', '&', '^', '"', '{', '}'], + ['\textbackslash', '\textasciitilde', '\_', '\%', '\$', '\&', '^', "''", '\{', '\}'], + $in + ); + } + + // helper ----------------------------- + + /** + * @return the $error + */ + public function getError(){ + return $this->error; + } + + /** + * validate Post or $data variables by builderKey + * alias of validate + * + * @param string $key + * @param array $data + * + * @return bool + */ + public function setData($key, $data = null){ + return $this->validate($key, $data); + } + + /** + * validate Post or $data variables by builderKey + * + * @param string $key + * @param array $data + * + * @return bool + */ + public function validate($key, $data = null){ + if (!$this->builderExist($key)){ + $this->error = 'Unknown builder.'; + $this->last_key = false; + return false; + }else{ + $this->validator->validateMap(($data) ? $data : $_POST, self::$valiMap[$key]); + if (!$this->validator->getIsError()){ + $this->error = false; + $this->last_key = $key; + return true; + } else { + $this->error = $this->validator->getLastErrorMsg(); + $this->last_key = false; + return false; + } + } + } + + /** + * check if pdf builder exists + * + * @return bool + */ + public function builderExist($key){ + return (isset(self::$valiMap[$key])); + } + + /** + * return validated and filtered request data + * + * @return mixed + */ + public function getValidated(){ + return $this->validator->getFiltered(); + } + + // build pdf ------------------------------------ + + /** + * build pdf + */ + public function build(){ + if (!$this->error && $this->last_key){ + //create images files + $files = []; + if (isset($this->validator->getFiltered()['belege'][0]['file'])){ + foreach ($this->validator->getFiltered()['belege'] as $b){ + if (($f = tempnam(sys_get_temp_dir(), 'tex-tmp-')) === false){ + $this->error = "Failed to create temporary file"; + foreach ($files as $v){ + if (file_exists($v)) unlink($v); + } + return; + } + if (file_exists($f)) unlink($f); + file_put_contents($f . '.pdf', base64_decode($b['file'])); + $files[str_pad($b['id'], 3, "0", STR_PAD_LEFT) . '-B' . $b['short']] = $f . '.pdf'; + } + } + + $validated = $this->validator->getFiltered(); + $validated['files'] = $files; + //get tex code + $tex = self::_renderTex($this->last_key, $validated); + //render twice + $this->_createPDF($tex); + //remove inline images + foreach ($files as $v){ + if (file_exists($v)) unlink($v); + } + return true; + } + } + + /** + * render tex code + * + * @param string $key + * @param array $param + */ + private static function _renderTex($key, $param){ + $file = SYSBASE . '/template/tex/' . $key . '.phpTex'; + $tex = ''; + if (file_exists($file)){ + ob_start(); + include $file; + $tex = ob_get_clean(); + } + return $tex; + } + + /** + * create pdf file set binary code + */ + private function _createPDF($tex_code){ + //create temp file + if (($f = tempnam(sys_get_temp_dir(), 'tex-')) === false){ + $this->error = "Failed to create temporary file"; + return; + } + + $tex_f = $f . ".tex"; + $aux_f = $f . ".aux"; + $log_f = $f . ".log"; + $pdf_f = $f . ".pdf"; + + //write file to tmp file + file_put_contents($tex_f, $tex_code); + //switch to directory + chdir(sys_get_temp_dir()); + + //run command + $shellcmd = "pdflatex \"\\input{" . $tex_f . '}"'; + + $status = -100; + exec($shellcmd, $out, $status); + //render twice -> page numbers, etc. + exec($shellcmd, $out, $status); + + //unlink files + if (file_exists($tex_f)) unlink($tex_f); + if (file_exists($aux_f)) unlink($aux_f); + if (file_exists($log_f)) unlink($log_f); + + // Test here + if (!file_exists($pdf_f)){ + //unlink files + unlink($f); + $this->error = [ + 'msg' => "Output was not generated and latex returned: $status.", + 'code' => $status, + 'log' => $out, + 'tex' => $tex_code, + ]; + return; + } + + //load file to memory + $this->binary_build = file_get_contents($pdf_f); + + //unlink files + unlink($pdf_f); + unlink($f); + } + + // private ---------------------------------------- + + /** + * get pdf as base64 string + * + * @param bool $echo + * return string + */ + public function getBase64($echo = false){ + if ($this->binary_build){ + $t = base64_encode($this->getBinary(false)); + if ($echo) echo $t; + return $t; + } + return null; + } + + /** + * get binary pdf data + * + * @param bool $echo + * return binary|NULL + */ + public function getBinary($echo = false){ + if ($this->binary_build){ + if ($echo) echo $this->binary_build; + return $this->binary_build; + } + return null; + } } -?> \ No newline at end of file +?> diff --git a/template/tex/gremienbescheinigung.phpTex b/template/tex/gremienbescheinigung.phpTex new file mode 100644 index 0000000..1fb4a77 --- /dev/null +++ b/template/tex/gremienbescheinigung.phpTex @@ -0,0 +1,229 @@ +%%% Quelle: https://meinnoteblog.wordpress.com/2010/11/12/latex-vorlagen-fur-briefe-und-rechnung/ +%--------------------------------------------------------------------------- +\documentclass%% +%--------------------------------------------------------------------------- +[fontsize=12pt,%% Schriftgroesse +%--------------------------------------------------------------------------- +% Satzspiegel +paper=a4,%% Papierformat +enlargefirstpage=on,%% Erste Seite anders +pagenumber=foot,%% Seitenzahl oben mittig +%--------------------------------------------------------------------------- +% Layout +headsepline=off,%% Linie unter der Seitenzahl +parskip=half,%% Abstand zwischen Absaetzen +firsthead=on,%% display header on first page +firstfoot=off,%% display footer on first page +%--------------------------------------------------------------------------- +% Was kommt in den Briefkopf und in die Anschrift +fromalign=right,%% Plazierung des Briefkopfs +fromphone=on,%% Telefonnummer im Absender +fromrule=off,%% Linie im Absender (aftername, afteraddress) +fromfax=on,%% Faxnummer +fromemail=on,%% Emailadresse +fromurl=on,%% Homepage +fromlogo=on,%% Firmenlogo +addrfield=on,%% Adressfeld fuer Fensterkuverts +backaddress=on,%% ...und Absender im Fenster +subject=untitled,%% Plazierung der Betreffzeile +locfield=narrow,%% zusaetzliches Feld fuer Absender +foldmarks=on,%% Faltmarken setzen +numericaldate=off,%% Datum numerisch ausgeben +refline=narrow,%% Geschaeftszeile im Satzspiegel +firstfoot=on,%% Footerbereich +%--------------------------------------------------------------------------- +% Formatierung +draft=off%% Entwurfsmodus +]{scrlttr2} +%--------------------------------------------------------------------------- +\usepackage[english, ngerman]{babel} +%\usepackage{scrpage2} +%\pagestyle{scrheadings} +%\clearscrheadfoot +\usepackage{ wasysym } +\usepackage{url} +\usepackage{lmodern} +\usepackage{enumitem} +\usepackage[utf8]{inputenc} +% symbols: (cell)phone, email +\RequirePackage{marvosym} +% for gray color in header +\RequirePackage{color} +\usepackage[T1]{fontenc} +\usepackage{graphicx} +%\usepackage[pdftitle={\id}]{hyperref} +\usepackage{ifthen} +\usepackage{lastpage} +\usepackage{array,multirow} +\usepackage{booktabs} +\usepackage{longtable} +\usepackage{qrcode} +\usepackage{ngerman} +\RequirePackage[ngerman=ngerman-x-latest]{hyphsubst} +%--------------------------------------------------------------------------- +% Schriften werden hier definiert +\renewcommand*\familydefault{\sfdefault} % Latin Modern Sans +\setkomafont{fromname}{\sffamily\color{mygray}\LARGE} +%\setkomafont{pagenumber}{\sffamily} +\setkomafont{subject}{\mdseries} +\setkomafont{backaddress}{\mdseries} +\setkomafont{fromaddress}{\small\sffamily\mdseries\color{mygray}} +%--------------------------------------------------------------------------- +\begin{document} + %--------------------------------------------------------------------------- + % Briefstil und Position des Briefkopfs + \LoadLetterOption{DIN} %% oder: DINmtext, SN, SNleft, KOMAold. + \makeatletter + \@setplength{sigbeforevskip}{18mm} % Abstand der Signatur von dem closing + \@setplength{firstheadvpos}{17mm} % Abstand des Absenderfeldes vom Top + \@setplength{firstfootvpos}{265mm} % Abstand des Footers von oben + \@setplength{firstheadwidth}{\paperwidth} + \@setplength{locwidth}{70mm} % Breite des Locationfeldes + \@setplength{locvpos}{60mm} % Abstand des Locationfeldes von oben + \ifdim \useplength{toaddrhpos}>\z@ + \@addtoplength[-2]{firstheadwidth}{\useplength{toaddrhpos}} + \else + \@addtoplength[2]{firstheadwidth}{\useplength{toaddrhpos}} + \fi + \@setplength{foldmarkhpos}{6.5mm} + \makeatother + %--------------------------------------------------------------------------- + % Farben werden hier definiert + % define gray for header + \definecolor{mygray}{gray}{.55} + % define blue for address + \definecolor{myblue}{rgb}{0.25,0.45,0.75} + + %--------------------------------------------------------------------------- + % Absender Daten + \setkomavar{fromlogo}{\includegraphics[height=3.5cm]{} + } + \setkomavar{fromname}{Studierendenrat der TU Ilmenau} + \setkomavar{fromaddress}{Max-Planck-Ring 7\\98693 Ilmenau} + \setkomavar{fromphone}[\phone~]{03677\,/\,69\,-\,1914} + \setkomavar{fromfax}[\FAX~]{03677\,/\,69\,-\,1193} + \setkomavar{fromemail}[\Letter~]{konsul@tu-ilmenau.de} + \setkomavar{fromurl}[\Mundus~]{https://stura.tu-ilmenau.de} + %\setkomafont{fromaddress}{\small\rmfamily\mdseries\slshape\color{myblue}} + + \setkomavar{backaddressseparator}{, } + %\setkomavar{backaddress}{StuRa der TU Ilmenau, Felderhof 112, 40880 Ratingen} % wenn erwünscht kann hier eine andere Backaddress eingetragen werden + \setkomavar{signature}{ + } + % signature same indention level as rest + \renewcommand*{\raggedsignature}{\raggedright} + \setkomavar{location}{\raggedleft + \vspace{1cm} + %Rechnungsnummer XZY hier gut platziert + }%%Zusätzliches Label Rechts von der Anschrift + + % Anlage neu definieren + \renewcommand{\enclname}{Anlagen} + \setkomavar{enclseparator}{: } + %--------------------------------------------------------------------------- + % Seitenstil + % pagenumber=footmiddle + \pagestyle{myheadings}%% keine Header in der Kopfzeile bzw. plain + \markboth{\usekomavar{fromname}}{\usekomavar{titel}} + \pagenumbering{arabic} + %--------------------------------------------------------------------------- + %--------------------------------------------------------------------------- + \setkomavar{firstfoot}{ + \parbox[t]{\textwidth}{% + \centering{Seite \arabic{page} von \pageref{LastPage}}\\ + \footnotesize% + \rule[3pt]{\textwidth}{.4pt} \\ + \begin{tabular}[t]{l@{}}% + \usekomavar{fromname}~\\ + \usekomavar{fromaddress}~\\ + \end{tabular}% + \hfill + %\begin{tabular}[t]{l@{}}% + %Verifizierung unter: ~\\ + %\qrcode[height=1cm]{https://www.ctan.org/tex-archive/macros/latex/contrib/qrcode?lang=en} + %\end{tabular}% + %\hfill + \begin{tabular}[t]{l@{}}% + Studentische/r Konsul/in ~\\ + \usekomavar[\phone~]{fromphone}~\\ + \usekomavar[\Letter~]{fromemail}~\\ + \end{tabular}% + }}% + \setkomavar{nextfoot}{ + \usekomavar{firstfoot} + } + %--------------------------------------------------------------------------- + %\setkomavar{yourref}{Test} + %\setkomavar{yourmail}{} + %\setkomavar{myref}{} + %\setkomavar{customer}{} + %\setkomavar{invoice}{} + %--------------------------------------------------------------------------- + % Datum und Ort werden hier eingetragen + \newkomavar{titel} + \newkomavar{datum} + \setkomavar{titel}{Gremienbescheinigung} + \setkomavar{date}{den } + \setkomavar{place}{Ilmenau} + %--------------------------------------------------------------------------- + \setkomavar{subject}{\Large \textbf{\usekomavar{titel}}} + % Briefkörper bündig am Briefkopf ausrichten + %\setlength{\oddsidemargin}{\useplength{toaddrhpos}} + %\addtolength{\oddsidemargin}{-1in} + %\setlength{\textwidth}{\useplength{firstheadwidth}} + % Hier beginnt der Brief, mit der Anschrift des Empfängers + \begin{letter} + { + ~\\ + ~\\ + }% + %--------------------------------------------------------------------------- + % Der Betreff des Briefes + %--------------------------------------------------------------------------- + \opening{für , geboren am . war wie folgt ehrenamtlich aktiv:} + + \begin{longtable}{p{1.65cm}p{1.65cm}p{4.3cm}p{4.2cm}p{1.9cm}} + \textbf{von}&\textbf{bis}&\textbf{Position}&\textbf{Gremium/Organ}&\textbf{Umfang} \\ + \toprule \endhead + \bottomrule \multicolumn{5}{c}{\scriptsize \begin{tabular}[c]{ccc} \emph{h = Stunden}& \emph{h/W = Stunden pro Woche} & \emph{h/S = Stunden pro Semester} \end{tabular}} \\ + \bottomrule \endfoot + + + \end{longtable} + + Insgesamt hat somit rund Stunden Arbeit im Zeitraum von bis freiwillig erbracht. + + Im Rahmen dieser Tätigkeiten hat folgende Fähigkeiten erworben bzw. eingebracht: + \begin{itemize} + + \end{itemize} + + + \closing{Für Engagement bedanken wir uns recht herzlich und wünschen noch viel Erfolg auf dem weiteren Lebensweg.\\ + \vspace{2.5cm} + %\includegraphics[angle=-3,height=1cm]{} + i.A. \\ + Studentische/r Konsul/in der TU Ilmenau + \vspace{-2cm} + } + %--------------------------------------------------------------------------- + %--------------------------------------------------------------------------- + \end{letter} + %--------------------------------------------------------------------------- +\end{document} +%--------------------------------------------------------------------------- From 04ed3175bf61143020556d92bf20944f93a5d862 Mon Sep 17 00:00:00 2001 From: cherrg Date: Mon, 24 Sep 2018 23:02:45 +0200 Subject: [PATCH 18/42] auto format --- public/index.php | 117 ++++++++++++++++++++++++----------------------- 1 file changed, 59 insertions(+), 58 deletions(-) diff --git a/public/index.php b/public/index.php index 3836517..e029251 100644 --- a/public/index.php +++ b/public/index.php @@ -8,75 +8,76 @@ // routing ------------------------------ $router = new Router(); $routeInfo = $router->route(); - //ip and apikey -if($routeInfo['controller'] != 'error' && (!isset($routeInfo['allowall']) || !$routeInfo['allowall'] )){ - // check API KEY - if ($_SERVER['REQUEST_METHOD'] == 'GET' && (!isset($_GET['APIKEY'])||$_GET['APIKEY']!=API_KEY)){ - $routeInfo['action'] = '403'; - $routeInfo['controller'] = 'error'; - $routeInfo['method'] = 'POST'; - } elseif($_SERVER['REQUEST_METHOD'] == 'POST' && (!isset($_POST['APIKEY'])||$_POST['APIKEY']!=API_KEY)) { - $routeInfo['action'] = '403'; - $routeInfo['controller'] = 'error'; - $routeInfo['method'] = 'POST'; - } - // ip check - if ($_SERVER['REMOTE_ADDR'] && is_array($_SERVER['REMOTE_ADDR']) && !in_array($_SERVER['REMOTE_ADDR'], ALLOWED_IPS)){ - $routeInfo['action'] = '403'; - $routeInfo['controller'] = 'error'; - $routeInfo['method'] = 'POST'; - } +if ($routeInfo['controller'] != 'error' && (!isset($routeInfo['allowall']) || !$routeInfo['allowall'])){ + // check API KEY + if ($_SERVER['REQUEST_METHOD'] == 'GET' && (!isset($_GET['APIKEY']) || $_GET['APIKEY'] != API_KEY)){ + $routeInfo['action'] = '403'; + $routeInfo['controller'] = 'error'; + $routeInfo['method'] = 'POST'; + }else if ($_SERVER['REQUEST_METHOD'] == 'POST' && (!isset($_POST['APIKEY']) || $_POST['APIKEY'] != API_KEY)){ + $routeInfo['action'] = '403'; + $routeInfo['controller'] = 'error'; + $routeInfo['method'] = 'POST'; + } + + // ip check + if ($_SERVER['REMOTE_ADDR'] && is_array($_SERVER['REMOTE_ADDR']) && !in_array($_SERVER['REMOTE_ADDR'], ALLOWED_IPS)){ + $routeInfo['action'] = '403'; + $routeInfo['controller'] = 'error'; + $routeInfo['method'] = 'POST'; + } } + // auth --------------------------------- if (isset($routeInfo['auth']) && ($routeInfo['auth'] == 'Basic' || $routeInfo['auth'] == 'basic')){ - define('AUTH_HANLER', 'AuthBasicHandler'); - AuthBasicHandler::getInstance()->requireAuth(); - AuthBasicHandler::getInstance()->requireGroup('basic'); - if (!isset($routeInfo['groups'])){ - $routeInfo['action'] = '403'; - $routeInfo['controller'] = 'error'; - $routeInfo['method'] = 'POST'; - } elseif (isset($routeInfo['groups']) && !AuthBasicHandler::getInstance()->hasGroup($routeInfo['groups'])){ - $routeInfo['action'] = '403'; - $routeInfo['controller'] = 'error'; - $routeInfo['method'] = 'POST'; - } -} else { - define('AUTH_HANLER', NULL); + define('AUTH_HANLER', 'AuthBasicHandler'); + AuthBasicHandler::getInstance()->requireAuth(); + AuthBasicHandler::getInstance()->requireGroup('basic'); + if (!isset($routeInfo['groups'])){ + $routeInfo['action'] = '403'; + $routeInfo['controller'] = 'error'; + $routeInfo['method'] = 'POST'; + }else if (isset($routeInfo['groups']) && !AuthBasicHandler::getInstance()->hasGroup($routeInfo['groups'])){ + $routeInfo['action'] = '403'; + $routeInfo['controller'] = 'error'; + $routeInfo['method'] = 'POST'; + } +}else{ + define('AUTH_HANLER', null); } // handle route ------------------------- switch ($routeInfo['controller']){ case "old": - include SYSBASE.'/old/index.php'; + include SYSBASE . '/old/index.php'; break; case "pdfbuilder": - $builder = new TexBuilder(); - if (!AuthBasicHandler::getInstance()->hasGroup('pdfbuilder') - || !isset($_POST['action']) - || !$builder->builderExist($_POST['action'])) { - $routeInfo['action'] = '403'; - $routeInfo['controller'] = 'error'; - $routeInfo['method'] = 'POST'; - $errorHdl = new ErrorHandler($routeInfo); - $errorHdl->render(); - } elseif(!$builder->validate($_POST['action']) - || !$builder->build() - || $builder->getError()) { - JsonController::print_json([ - 'success' => false, - 'controller' => $routeInfo['controller'], - 'error' => $builder->getError(), - ], true); - } else { - JsonController::print_json([ - 'success' => true, - 'controller' => $routeInfo['controller'], - 'action' => $_POST['action'], - 'data' => $builder->getBase64(), - ], true); - } + $builder = new TexBuilder(); + if (!AuthBasicHandler::getInstance()->hasGroup('pdfbuilder') + || !isset($_POST['action']) + || !$builder->builderExist($_POST['action'])){ + $routeInfo['action'] = '403'; + $routeInfo['controller'] = 'error'; + $routeInfo['method'] = 'POST'; + $errorHdl = new ErrorHandler($routeInfo); + $errorHdl->render(); + }else if (!$builder->validate($_POST['action']) + || !$builder->build() + || $builder->getError()){ + JsonController::print_json([ + 'success' => false, + 'controller' => $routeInfo['controller'], + 'error' => $builder->getError(), + ], true); + }else{ + JsonController::print_json([ + 'success' => true, + 'controller' => $routeInfo['controller'], + 'action' => $_POST['action'], + 'data' => $builder->getBase64(), + ], true); + } break; case 'error': default: From 52685609f560356340da486af1c4e093025df764 Mon Sep 17 00:00:00 2001 From: cherrg Date: Mon, 24 Sep 2018 23:04:58 +0200 Subject: [PATCH 19/42] set error_log destination --- lib/inc.all.php | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/lib/inc.all.php b/lib/inc.all.php index 03ad203..9f374ea 100644 --- a/lib/inc.all.php +++ b/lib/inc.all.php @@ -1,5 +1,15 @@ Date: Mon, 24 Sep 2018 23:07:10 +0200 Subject: [PATCH 20/42] Eingegangen am Hinweis --- template/tex/belegpdf.phpTex | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/template/tex/belegpdf.phpTex b/template/tex/belegpdf.phpTex index 7b4d91f..18474f5 100644 --- a/template/tex/belegpdf.phpTex +++ b/template/tex/belegpdf.phpTex @@ -119,7 +119,8 @@ bindingoffset=0mm, top=20mm,bottom=20mm} \centering \parbox[b]{0.6\linewidth}{% size of the first signature box \strut - \textbf{Eingegangen am} \hrule + \textbf{Eingegangen am} \hrule~\\\vspace*{-7mm} + \textit{\tiny\\\hspace*{4.7cm}Auszufüllen durch Referat Finanzen} } \end{figure} % From af7cd3d9453b5ce09dbbe3d4587870846b5e79c6 Mon Sep 17 00:00:00 2001 From: cherrg Date: Mon, 24 Sep 2018 23:08:17 +0200 Subject: [PATCH 21/42] Deckblatt: extra leere Seite bei ungerader Deckblatt-Seitenzahl --- template/tex/belegpdf.phpTex | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/template/tex/belegpdf.phpTex b/template/tex/belegpdf.phpTex index 18474f5..80e5c1b 100644 --- a/template/tex/belegpdf.phpTex +++ b/template/tex/belegpdf.phpTex @@ -179,6 +179,13 @@ Digitale Belege, die nicht in der verkleinerten Fassung lesbar sind habe ich zus \end{center} \newpage +%empty page %%%%%%%%%%%%%%% +% +\ifthenelse{\isodd{\thepage}}{}{\null +\thispagestyle{empty}\addtocounter{page}{-1}\newpage} +% +%%%%%%%%%%%%%%%%%%%%%%%%%%% + %Belege \tikzset{ font={\fontsize{29pt}{12}\selectfont}} From 7168652e87324b5cca3c726220d48ff0c36509c8 Mon Sep 17 00:00:00 2001 From: Cherrg Date: Mon, 24 Sep 2018 23:57:30 +0200 Subject: [PATCH 22/42] renamed/replaced by gremienbescheinigung.phpTex --- template/tex/gremienbescheinigung.pdfTex | 210 ----------------------- 1 file changed, 210 deletions(-) delete mode 100644 template/tex/gremienbescheinigung.pdfTex diff --git a/template/tex/gremienbescheinigung.pdfTex b/template/tex/gremienbescheinigung.pdfTex deleted file mode 100644 index 3dfd44f..0000000 --- a/template/tex/gremienbescheinigung.pdfTex +++ /dev/null @@ -1,210 +0,0 @@ -%%% Quelle: https://meinnoteblog.wordpress.com/2010/11/12/latex-vorlagen-fur-briefe-und-rechnung/ -%--------------------------------------------------------------------------- -\documentclass%% -%--------------------------------------------------------------------------- -[fontsize=12pt,%% Schriftgroesse -%--------------------------------------------------------------------------- -% Satzspiegel -paper=a4,%% Papierformat -enlargefirstpage=on,%% Erste Seite anders -pagenumber=foot,%% Seitenzahl oben mittig -%--------------------------------------------------------------------------- -% Layout -headsepline=off,%% Linie unter der Seitenzahl -parskip=half,%% Abstand zwischen Absaetzen -firsthead=on,%% display header on first page -firstfoot=off,%% display footer on first page -%--------------------------------------------------------------------------- -% Was kommt in den Briefkopf und in die Anschrift -fromalign=right,%% Plazierung des Briefkopfs -fromphone=on,%% Telefonnummer im Absender -fromrule=off,%% Linie im Absender (aftername, afteraddress) -fromfax=on,%% Faxnummer -fromemail=on,%% Emailadresse -fromurl=on,%% Homepage -fromlogo=on,%% Firmenlogo -addrfield=on,%% Adressfeld fuer Fensterkuverts -backaddress=on,%% ...und Absender im Fenster -subject=untitled,%% Plazierung der Betreffzeile -locfield=narrow,%% zusaetzliches Feld fuer Absender -foldmarks=on,%% Faltmarken setzen -numericaldate=off,%% Datum numerisch ausgeben -refline=narrow,%% Geschaeftszeile im Satzspiegel -firstfoot=on,%% Footerbereich -%--------------------------------------------------------------------------- -% Formatierung -draft=off%% Entwurfsmodus -]{scrlttr2} -%--------------------------------------------------------------------------- -\usepackage[english, ngerman]{babel} -%\usepackage{scrpage2} -%\pagestyle{scrheadings} -%\clearscrheadfoot -\usepackage{ wasysym } -\usepackage{url} -\usepackage{lmodern} -\usepackage{enumitem} -\usepackage[utf8]{inputenc} -% symbols: (cell)phone, email -\RequirePackage{marvosym} -% for gray color in header -\RequirePackage{color} -\usepackage[T1]{fontenc} -\usepackage{graphicx} -%\usepackage[pdftitle={\id}]{hyperref} -\usepackage{ifthen} -\usepackage{lastpage} -\usepackage{array,multirow} -\usepackage{booktabs} -\usepackage{tabularx} -\usepackage{qrcode} -%--------------------------------------------------------------------------- -% Schriften werden hier definiert -\renewcommand*\familydefault{\sfdefault} % Latin Modern Sans -\setkomafont{fromname}{\sffamily\color{mygray}\LARGE} -%\setkomafont{pagenumber}{\sffamily} -\setkomafont{subject}{\mdseries} -\setkomafont{backaddress}{\mdseries} -\setkomafont{fromaddress}{\small\sffamily\mdseries\color{mygray}} -%--------------------------------------------------------------------------- -\begin{document} - %--------------------------------------------------------------------------- - % Briefstil und Position des Briefkopfs - \LoadLetterOption{DIN} %% oder: DINmtext, SN, SNleft, KOMAold. - \makeatletter - \@setplength{sigbeforevskip}{18mm} % Abstand der Signatur von dem closing - \@setplength{firstheadvpos}{17mm} % Abstand des Absenderfeldes vom Top - \@setplength{firstfootvpos}{265mm} % Abstand des Footers von oben - \@setplength{firstheadwidth}{\paperwidth} - \@setplength{locwidth}{70mm} % Breite des Locationfeldes - \@setplength{locvpos}{60mm} % Abstand des Locationfeldes von oben - \ifdim \useplength{toaddrhpos}>\z@ - \@addtoplength[-2]{firstheadwidth}{\useplength{toaddrhpos}} - \else - \@addtoplength[2]{firstheadwidth}{\useplength{toaddrhpos}} - \fi - \@setplength{foldmarkhpos}{6.5mm} - \makeatother - %--------------------------------------------------------------------------- - % Farben werden hier definiert - % define gray for header - \definecolor{mygray}{gray}{.55} - % define blue for address - \definecolor{myblue}{rgb}{0.25,0.45,0.75} - - %--------------------------------------------------------------------------- - % Absender Daten - \setkomavar{fromlogo}{\includegraphics[height=3.5cm]{img/stura-try.pdf}} - \setkomavar{fromname}{Studierendenrat der TU Ilmenau} - \setkomavar{fromaddress}{Max-Planck-Ring 7\\98693 Ilmenau} - \setkomavar{fromphone}[\phone~]{03677\,/\,69\,-\,1914} - \setkomavar{fromfax}[\FAX~]{03677\,/\,69\,-\,1193} - \setkomavar{fromemail}[\Letter~]{konsul@tu-ilmenau.de} - \setkomavar{fromurl}[\Mundus~]{https://stura.tu-ilmenau.de} - %\setkomafont{fromaddress}{\small\rmfamily\mdseries\slshape\color{myblue}} - - \setkomavar{backaddressseparator}{, } - %\setkomavar{backaddress}{StuRa der TU Ilmenau, Felderhof 112, 40880 Ratingen} % wenn erwünscht kann hier eine andere Backaddress eingetragen werden - \setkomavar{signature}{ - } - % signature same indention level as rest - \renewcommand*{\raggedsignature}{\raggedright} - \setkomavar{location}{\raggedleft - \vspace{1cm} - %Rechnungsnummer XZY hier gut platziert - }%%Zusätzliches Label Rechts von der Anschrift - - % Anlage neu definieren - \renewcommand{\enclname}{Anlagen} - \setkomavar{enclseparator}{: } - %--------------------------------------------------------------------------- - % Seitenstil - % pagenumber=footmiddle - \pagestyle{myheadings}%% keine Header in der Kopfzeile bzw. plain - \markboth{\usekomavar{fromname}}{\usekomavar{titel}} - \pagenumbering{arabic} - %--------------------------------------------------------------------------- - %--------------------------------------------------------------------------- - \setkomavar{firstfoot}{ - \centering{Seite \arabic{page} von \pageref{LastPage}}\\ - \footnotesize% - \rule[3pt]{\textwidth}{.4pt} \\ - \begin{tabular}[t]{l@{}}% - \usekomavar{fromname}\\ - \usekomavar{fromaddress}\\ - \end{tabular}% - \hfill - \begin{tabular}[t]{l@{}}% - %Verifizierung unter: \\ - %\qrcode[height=1cm]{https://www.ctan.org/tex-archive/macros/latex/contrib/qrcode?lang=en} - \end{tabular}% - \hfill - \begin{tabular}[t]{l@{}}% - Studentische/r Konsul/in \\ - \usekomavar[\phone~]{fromphone}\\ - \usekomavar[\Letter~]{fromemail}\\ - \end{tabular}% - - }% - \setkomavar{nextfoot}{ - \usekomavar{firstfoot} - } - %--------------------------------------------------------------------------- - %\setkomavar{yourref}{Test} - %\setkomavar{yourmail}{} - %\setkomavar{myref}{} - %\setkomavar{customer}{} - %\setkomavar{invoice}{} - %--------------------------------------------------------------------------- - % Datum und Ort werden hier eingetragen - \newkomavar{titel} - \newkomavar{datum} - \setkomavar{titel}{Gremienbescheinigung} - \setkomavar{date}{den } - \setkomavar{place}{Ilmenau} - %--------------------------------------------------------------------------- - \setkomavar{subject}{\Large \textbf{\usekomavar{titel}}} - % Briefkörper bündig am Briefkopf ausrichten - %\setlength{\oddsidemargin}{\useplength{toaddrhpos}} - %\addtolength{\oddsidemargin}{-1in} - %\setlength{\textwidth}{\useplength{firstheadwidth}} - % Hier beginnt der Brief, mit der Anschrift des Empfängers - \begin{letter} - { - \\ - \\ - - }% - %--------------------------------------------------------------------------- - % Der Betreff des Briefes - %--------------------------------------------------------------------------- - \opening{für , geboren am } - - \begin{tabularx}{\textwidth}{ccXllr} - \textbf{von}&\textbf{bis}&\textbf{Position}&&\textbf{Gremium/Organ}&\textbf{Arbeitsaufwand} \\ - \toprule - - \bottomrule - \end{tabularx} - - Insgesamt hat somit ca. Stunden Arbeit im Zeitraum von bis freiwillig erbracht. - - \closing{Für Engagement bedanken wir uns recht herzlich und - wünschen noch viel Erfolg auf dem weiteren Lebensweg.\\ - \includegraphics[angle=-3,height=1cm]{img/sign.png}\\ - i.A. \\ - Studentische/r Konsul/in der TU Ilmenau} - %--------------------------------------------------------------------------- - %--------------------------------------------------------------------------- - \end{letter} - %--------------------------------------------------------------------------- -\end{document} -%--------------------------------------------------------------------------- From 57862e8ba64d971b04a97360ec6e5dd1397e8e80 Mon Sep 17 00:00:00 2001 From: Cherrg Date: Mon, 22 Oct 2018 14:37:37 +0200 Subject: [PATCH 23/42] Update belegpdf.phpTex --- template/tex/belegpdf.phpTex | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/template/tex/belegpdf.phpTex b/template/tex/belegpdf.phpTex index 80e5c1b..263a080 100644 --- a/template/tex/belegpdf.phpTex +++ b/template/tex/belegpdf.phpTex @@ -38,7 +38,7 @@ bindingoffset=0mm, top=20mm,bottom=20mm} } echo $ff; ?>} -\newcommand{\footerstring}{} +\newcommand{\footerstring}{} \linespread{1.3} \newcommand{\linia}{\rule{\linewidth}{0.5pt}} @@ -131,7 +131,7 @@ bindingoffset=0mm, top=20mm,bottom=20mm} \begin{enumerate}[label=\Roman*] \itemsep-2mm -\item \textbf{ID}\hfill +\item \textbf{ID}\hfill \item \textbf{Projekt}\hfill \item \textbf{Organisation} \hfill \item \textbf{Erstellt} \hfill @@ -239,4 +239,4 @@ Digitale Belege, die nicht in der verkleinerten Fassung lesbar sind habe ich zus } -\end{document} \ No newline at end of file +\end{document} From 5a10488c18967aad72667be2db8f7f36ae86bc02 Mon Sep 17 00:00:00 2001 From: cherrg Date: Mon, 17 Dec 2018 14:37:31 +0100 Subject: [PATCH 24/42] add stura logos --- template/img/stura-padding-bot.pdf | Bin 0 -> 5059 bytes template/img/stura.pdf | Bin 0 -> 6570 bytes template/img/stura2.pdf | Bin 0 -> 13625 bytes 3 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 template/img/stura-padding-bot.pdf create mode 100644 template/img/stura.pdf create mode 100644 template/img/stura2.pdf diff --git a/template/img/stura-padding-bot.pdf b/template/img/stura-padding-bot.pdf new file mode 100644 index 0000000000000000000000000000000000000000..c75bd8d7100b6c57efe8ae98ac3f7b4d592bb6a7 GIT binary patch literal 5059 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 T&RAv~p`)t}Qc*FsJO=tNj?P3` literal 0 HcmV?d00001 diff --git a/template/img/stura.pdf b/template/img/stura.pdf new file mode 100644 index 0000000000000000000000000000000000000000..837f2d5a6badf74569d74b28a92a85282a3909f5 GIT binary patch literal 6570 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 Dy2}|n literal 0 HcmV?d00001 From d9c46b02f2a1bd692a7e896681120672eabdc4e3 Mon Sep 17 00:00:00 2001 From: cherrg Date: Thu, 17 Jan 2019 18:14:11 +0100 Subject: [PATCH 25/42] merge lukas changes + not commited yet + clean up * gitignore * config option: DEBUG_DO_NOT_DELETE__TEX_PDF * add 'Zahlungsanweisung' * latex shell command to config * shell command -> redirect error out * ver_dump with calling line and pre wrap * tex fix: belegpdf * additional Error description on 'access denied' message * validator update --- .gitignore | 14 +- lib/class.TexBuilder.php | 174 ++++++++++++++++------- lib/class.Validator.php | 197 +++++++++++++++----------- lib/inc.all.php | 13 ++ logs/.keep | 1 + old/img/stura-try.pdf | Bin 0 -> 5059 bytes public/index.php | 8 +- template/tex/zahlungsanweisung.phpTex | 181 +++++++++++++++++++++++ 8 files changed, 445 insertions(+), 143 deletions(-) create mode 100644 logs/.keep create mode 100644 old/img/stura-try.pdf create mode 100644 template/tex/zahlungsanweisung.phpTex diff --git a/.gitignore b/.gitignore index 443f149..ec31cc8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,19 +1,19 @@ # https://git-scm.com/docs/gitignore # https://help.github.com/articles/ignoring-files # Example .gitignore files: https://github.com/github/gitignore -*.aux -*.log -*.out -*.pdf +/old/*.aux +/old/*.log +/old/*.out +/old/*.pdf *.synctex.gz .project .buildpath config/config.php -*.settings/* -!/img/stura-try.pdf /hiddenAPIKey.php old/hiddenAPIKey.php /parameter.tex /.fuse* *.idea - +/logs/* +*.settings +*.directory diff --git a/lib/class.TexBuilder.php b/lib/class.TexBuilder.php index 0d90ff9..bc32dac 100644 --- a/lib/class.TexBuilder.php +++ b/lib/class.TexBuilder.php @@ -172,7 +172,7 @@ class TexBuilder{ 'maxlength' => '255', 'error' => "Ungültiger Konsul name." ], - 'skills' => [ 'array', + 'skills' => ['array', 'validator' => ['regex', 'pattern' => '/^[a-zA-Z0-9\-_ :,;%$§\&\+\*\.!\?\/\\\[\]\'"#~()äöüÄÖÜéèêóòôáàâíìîúùûÉÈÊÓÒÔÁÀÂÍÌÎÚÙÛß]*$/', 'maxlength' => '500', @@ -185,8 +185,8 @@ class TexBuilder{ 'required' => true, 'map' => [ 'checked' => ['integer', - 'min' => '1', - 'error' => 'Some checking error.' + 'min' => '0', + 'error' => 'Some id checking error.' ], 'von' => ['date', 'format' => 'Y-m-d', @@ -223,35 +223,100 @@ class TexBuilder{ 'trim', ], ], + 'zahlungsanweisung' => [ + 'projekt-id' => ['id'], + 'projekt-name' => ['text'], + 'projekt-org' => ['text'], + 'projekt-recht' => ['regex', + 'pattern' => '/^[a-zA-Z0-9\-_ :,;%$§\&\+\*\.!\?\/\\\[\]\'"#~()äöüÄÖÜéèêóòôáàâíìîúùûÉÈÊÓÒÔÁÀÂÍÌÎÚÙÛß]*$/', + 'maxlength' => '255', + 'error' => 'Ungültiges Recht.' + ], + 'projekt-create' => ['date', + 'format' => 'Y-m-d H:i:s', + 'parse' => 'y', + 'error' => 'Kein Erstellungsdatum des Projektes', + ], + 'auslage-id' => ['id'], + 'auslage-name' => ['text'], + 'zahlung-name' => ['regex', + 'pattern' => '/^[a-zA-Z0-9\-_ :,;%$§\&\+\*\.!\?\/\\\[\]\'"#~()äöüÄÖÜéèêóòôáàâíìîúùûÉÈÊÓÒÔÁÀÂÍÌÎÚÙÛß]*$/', + 'maxlength' => '255', + 'error' => 'Ungültiger Empfänger Name.' + ], + 'zahlung-iban' => ['iban', + ], + 'zahlung-value' => ['float', + 'format' => '2', + 'decimal' => '.', + 'parse' => 'money', + 'error' => 'Ungültiger Value' + ], + 'zahlung-adresse' => ['text'], + 'details' => ['array', + 'optional', + 'empty', + //'minlength' => 1, + 'key' => ['regex', + 'pattern' => '/^(\d+)$/' + ], + 'validator' => ['arraymap', + 'required' => true, + 'map' => [ + 'beleg-id' => ["text" + ], + 'titel' => ['text', 'trim', 'regex', + 'pattern' => '/^[0-9 ]*$/', + 'maxlength' => '255', + 'error' => 'Ungültiger Titel' + ], + 'einnahmen' => ['float', + 'min' => '0', + 'format' => '2', + 'decimal' => '.', + 'parse' => 'money', + 'error' => 'Ungültige Einnahme' + ], + 'ausgaben' => ['float', + 'min' => '0', + 'format' => '2', + 'decimal' => '.', + 'parse' => 'money', + 'error' => 'Ungültige Ausgabe' + ], + ] + ] + ] + ], ]; - + /** * * @var Validator */ private $validator; - + /** * is error and error message * * @var bool|string */ private $error = false; - + /** * last valid key * * @var bool|string */ private $last_key = false; - + /** * @var binary pdf file data */ private $binary_build; - + // constructor -------------------- - + /** * class constructor */ @@ -259,9 +324,9 @@ function __construct(){ $this->validator = new Validator(); $this->error = false; } - + // getter / setter -------------------- - + /** * escape latex invalid letters * @@ -269,23 +334,23 @@ function __construct(){ * * @return string */ - private static function texEscape($in){ + public static function texEscape($in){ return str_replace( ['\\', '~', '_', '%', '$', '&', '^', '"', '{', '}'], ['\textbackslash', '\textasciitilde', '\_', '\%', '\$', '\&', '^', "''", '\{', '\}'], $in ); } - + // helper ----------------------------- - + /** * @return the $error */ public function getError(){ return $this->error; } - + /** * validate Post or $data variables by builderKey * alias of validate @@ -298,7 +363,7 @@ public function getError(){ public function setData($key, $data = null){ return $this->validate($key, $data); } - + /** * validate Post or $data variables by builderKey * @@ -318,14 +383,19 @@ public function validate($key, $data = null){ $this->error = false; $this->last_key = $key; return true; - } else { - $this->error = $this->validator->getLastErrorMsg(); + }else{ + if ($this->validator->getLastErrorMsg() == 'Access Denied'){ + error_log("Error: Access Denied; Target: $key; Description: " . $this->validator->getLastErrorDescription()); + $this->error = $this->validator->getLastErrorMsg() . ' - ' . $this->validator->getLastErrorDescription(); + }else{ + $this->error = $this->validator->getLastErrorMsg(); + } $this->last_key = false; return false; } } } - + /** * check if pdf builder exists * @@ -334,7 +404,7 @@ public function validate($key, $data = null){ public function builderExist($key){ return (isset(self::$valiMap[$key])); } - + /** * return validated and filtered request data * @@ -343,9 +413,9 @@ public function builderExist($key){ public function getValidated(){ return $this->validator->getFiltered(); } - + // build pdf ------------------------------------ - + /** * build pdf */ @@ -358,16 +428,19 @@ public function build(){ if (($f = tempnam(sys_get_temp_dir(), 'tex-tmp-')) === false){ $this->error = "Failed to create temporary file"; foreach ($files as $v){ - if (file_exists($v)) unlink($v); + if (!DEBUG_DO_NOT_DELETE__TEX_PDF && file_exists($v)) + unlink($v); } - return; + return false; } + //remove empty file again if (file_exists($f)) unlink($f); + //set file with actual content file_put_contents($f . '.pdf', base64_decode($b['file'])); $files[str_pad($b['id'], 3, "0", STR_PAD_LEFT) . '-B' . $b['short']] = $f . '.pdf'; } } - + $validated = $this->validator->getFiltered(); $validated['files'] = $files; //get tex code @@ -375,13 +448,15 @@ public function build(){ //render twice $this->_createPDF($tex); //remove inline images - foreach ($files as $v){ - if (file_exists($v)) unlink($v); + if (!DEBUG_DO_NOT_DELETE__TEX_PDF){ + foreach ($files as $v){ + if (file_exists($v)) unlink($v); + } } return true; } } - + /** * render tex code * @@ -398,7 +473,7 @@ private static function _renderTex($key, $param){ } return $tex; } - + /** * create pdf file set binary code */ @@ -408,34 +483,35 @@ private function _createPDF($tex_code){ $this->error = "Failed to create temporary file"; return; } - + $tex_f = $f . ".tex"; $aux_f = $f . ".aux"; $log_f = $f . ".log"; $pdf_f = $f . ".pdf"; - + //write file to tmp file file_put_contents($tex_f, $tex_code); //switch to directory chdir(sys_get_temp_dir()); - + //run command - $shellcmd = "pdflatex \"\\input{" . $tex_f . '}"'; - + $shellcmd = SHELL_LATEX_COMMAND." \"\\input{" . $tex_f . '}" 2>&1'; + $status = -100; exec($shellcmd, $out, $status); - //render twice -> page numbers, etc. + //render twice -> for page numbers, etc. exec($shellcmd, $out, $status); - + //unlink files - if (file_exists($tex_f)) unlink($tex_f); - if (file_exists($aux_f)) unlink($aux_f); - if (file_exists($log_f)) unlink($log_f); - + if (!DEBUG_DO_NOT_DELETE__TEX_PDF && file_exists($tex_f)) unlink($tex_f); + if (!DEBUG_DO_NOT_DELETE__TEX_PDF && file_exists($aux_f)) unlink($aux_f); + if (!DEBUG_DO_NOT_DELETE__TEX_PDF && file_exists($log_f)) unlink($log_f); + // Test here if (!file_exists($pdf_f)){ //unlink files - unlink($f); + if (!DEBUG_DO_NOT_DELETE__TEX_PDF) + unlink($f); $this->error = [ 'msg' => "Output was not generated and latex returned: $status.", 'code' => $status, @@ -444,17 +520,19 @@ private function _createPDF($tex_code){ ]; return; } - + //load file to memory $this->binary_build = file_get_contents($pdf_f); - + //unlink files - unlink($pdf_f); - unlink($f); + if (!DEBUG_DO_NOT_DELETE__TEX_PDF){ + unlink($pdf_f); + unlink($f); + } } - + // private ---------------------------------------- - + /** * get pdf as base64 string * @@ -469,7 +547,7 @@ public function getBase64($echo = false){ } return null; } - + /** * get binary pdf data * diff --git a/lib/class.Validator.php b/lib/class.Validator.php index bfe242f..358cade 100644 --- a/lib/class.Validator.php +++ b/lib/class.Validator.php @@ -1,16 +1,17 @@ - * @since 17.02.2018 - * @copyright Copyright (C) 2018 - All rights reserved - * @platform PHP - * @requirements PHP 7.0 or higher + * @package Stura - Referat IT - ProtocolHelper + * @category framework + * @author michael g + * @author Stura - Referat IT + * @since 17.02.2018 + * @copyright Copyright (C) 2018 - All rights reserved + * @platform PHP + * @requirements PHP 7.0 or higher */ class Validator { @@ -19,7 +20,7 @@ class Validator { * boolean */ protected $isError; - + /** * last error message * -> short error message @@ -27,39 +28,39 @@ class Validator { * string */ protected $lastErrorMsg; - + /** * last stores last map key if map validation is used */ protected $lastMapKey = ''; - + /** * last error description * -> error description * string */ protected $lastErrorDescription; - + /** * last error message * string */ protected $lastErrorCode; - + /** * filter may sanitize inputs * mixed */ protected $filtered; - + /** * class constructor */ function __contstruct(){ } - + // ========================================== - + /** * set validation status * @@ -75,7 +76,7 @@ private function setError($isError, $code=0, $msg='', $desc=''){ $this->lastErrorDescription = ($desc == '')? $msg : $desc; return $isError; } - + /** * @return the $isError */ @@ -83,7 +84,7 @@ public function getIsError() { return $this->isError; } - + /** * @return the $lastErrorMsg */ @@ -91,7 +92,7 @@ public function getLastErrorMsg() { return $this->lastErrorMsg; } - + /** * @return the $lastErrorDescription */ @@ -99,7 +100,7 @@ public function getLastErrorDescription() { return $this->lastErrorDescription; } - + /** * @return the $lastErrorCode */ @@ -107,7 +108,7 @@ public function getLastErrorCode() { return $this->lastErrorCode; } - + /** * @return the $lastMapKey */ @@ -115,7 +116,7 @@ public function getLastMapKey() { return $this->lastMapKey; } - + /** * filter may sanitize input values are stored here * Post validators will create sanitized array @@ -128,9 +129,9 @@ public function getFiltered($key = NULL) else return $this->filtered[$key]; } - + // ========================================== - + /** * call selected validator function * @param mixed $value @@ -150,7 +151,7 @@ public function validate($value, $validator){ return !$this->isError; } } - + /** * validate POST data with a validation list * @@ -185,7 +186,7 @@ public function validateMap($source_unsafe, $map, $required = true){ $this->filtered = $out; return !$this->isError; } - + /** * validate POST data with a validation list * add additional mfunction layer, so this will be required @@ -213,10 +214,10 @@ public function validatePostGroup($map, $groupKey = 'mfunction', $required = tru return $ret; } } - + // ====== VALIDATORS ======================== // functions must start with 'V_validatorname' - + /** * dummy validator * always return 'valid' @@ -228,7 +229,7 @@ public function V_dummy($value = NULL, $params = NULL){ $this->filtered = $value; return true; } - + /** * boolean validator * @@ -251,7 +252,7 @@ public function V_boolean($value, $params = []){ $msg = (isset($params['error']))? $params['error'] : 'No Boolean' ; return !$this->setError(true, 200, $msg, 'No Boolean'); } - + /** * integer validator * @@ -298,18 +299,23 @@ public function V_integer($value, $params = []){ return !$this->setError(false); } } - + /** * float validator * - * params: - * KEY 1-> single value, 2-> key value pair - * decimal_seperator 2 [. or ,] default: . - * min 2 min value - * max 2 max value - * step 2 step - be carefull may produce errors (wrong deteced values) - * format 2 trim to x decimal places - * error 2 error message on error case + * params: + * KEY 1-> single value, 2-> key value pair + * decimal_seperator 2 [. or ,] default: . + * min 2 min value + * max 2 max value + * step 2 step - be carefull may produce errors (wrong deteced values) + * format 2 trim to x decimal places + * parse 2 has to be array with additional keys, parse after validation -> funs number format + * decimals 2 decimal count, default: 2 + * dec_point 2 dec point/seperator, default: ',' + * thousands 2 thousands seperator, default: '' + * append 2 append after float value -> return value is no float, string instead, appends X to float value, e.g. ' EUR', or ' $', default: disabled + * error 2 error message on error case * * @param $value * @param $params @@ -341,7 +347,7 @@ public function V_float($value, $params = []){ $cv = $cv * $ex; } $k = strlen($ex); - if ((is_numeric( $cv ) && mb_strpos($value, '.') + ($k) < mb_strlen($value)) || $cv % $mod != 0){ + if ((is_numeric( $cv ) && mb_strpos($value, '.') && mb_strpos($value, '.') + ($k) < mb_strlen($value)) || $cv % $mod != 0){ $msg = (isset($params['error']))? $params['error'] : "float invalid step" ; return !$this->setError(true, 200, $msg, 'float invalid step'); } @@ -351,18 +357,32 @@ public function V_float($value, $params = []){ } else { $this->filtered = $v; } + if (isset($params['parse']) && is_array($params['parse']){ + + $params['parse'] === 'money'){ + $this->filtered = number_format( + $v, + (isset($params['parse']['decimals']))? $params['parse']['decimals'] : 2, + (isset($params['parse']['dec_point']))? $params['parse']['dec_point'] : ',', + (isset($params['parse']['thousands']))? $params['parse']['thousands'] : '' + ); + if (isset($params['parse']['append'])){ + $this->filtered = $this->filtered . $params['parse']['append']; + } + } return !$this->setError(false); } } - + /** * check if integer and larger than 0 * @param integer $value + * @return boolean */ public function V_id ($value, $params = NULL){ return $this->V_integer($value, ['min' => 1]); } - + /** * text validator * @@ -418,12 +438,13 @@ public function V_text($value, $params = []) { return !$this->setError(false); } } - + /** * email validator * * $param - * empty 1 allow empty value + * empty 1 allow empty value + * maxlength 2 maximum string length * * @param $value * @param $params @@ -435,6 +456,10 @@ public function V_mail ($value, $params = []) { $this->filtered = $email; return !$this->setError(false); } + if (isset($params['maxlength']) && strlen($email) >= $params['maxlength']){ + $msg = "E-Mail is too long (Maximum length: {$params['maxlength']})"; + return !$this->setError(true, 200, $msg); + } $re = '/^([a-zA-Z0-9_.+-])+\@(([a-zA-Z0-9-])+\.)+([a-zA-Z0-9]{2,4})$/'; if ($email !== '' && (filter_var($email, FILTER_VALIDATE_EMAIL) === false || !preg_match($re, $email) )){ return !$this->setError(true, 200, "mail validation failed", 'mail validation failed'); @@ -443,7 +468,7 @@ public function V_mail ($value, $params = []) { return !$this->setError(false); } } - + /** * phone validator * @@ -463,12 +488,12 @@ public function V_phone($value, $params = NULL) { } return !$this->setError(false); } - + /** * name validator * * @param $value - * $param + * @param array $params * minlength 2 minimum string length * maxlength 2 maximum string length - default 127, set -1 for unlimited value * error 2 replace whole error message on error case @@ -483,7 +508,7 @@ public function V_name($value, $params = NULL) { $this->filtered = ''; return !$this->setError(false); } - $re = ''; + $re = NULL; $re_no_sep = '/^[a-zA-Z0-9äöüÄÖÜéèêóòôáàâíìîúùûÉÈÊÓÒÔÁÀÂÍÌÎÚÙÛß]+[a-zA-Z0-9\-_ .äöüÄÖÜéèêóòôáàâíìîúùûÉÈÊÓÒÔÁÀÂÍÌÎÚÙÛß]*[a-zA-Z0-9äöüÄÖÜéèêóòôáàâíìîúùûÉÈÊÓÒÔÁÀÂÍÌÎÚÙÛß]+$/'; if (!isset($params['multi']) || strlen($params['multi']) != 1 ){ $re = $re_no_sep; @@ -526,7 +551,7 @@ public function V_name($value, $params = NULL) { } return !$this->setError(false); } - + /** * url validator * @@ -544,7 +569,7 @@ public function V_url($value, $params = NULL) { } return !$this->setError(false); } - + /** * ip validator * check if string is a valid ip address (supports ipv4 and ipv6) @@ -560,7 +585,7 @@ public function V_ip($value, $params = NULL){ return !$this->setError(true, 200, 'No ip address', 'No ip address'); } } - + /** * check if string is a valid ip address (supports ipv4 and ipv6) * helper function @@ -580,7 +605,7 @@ public static function isValidIP( $ipadr, $recursive = true) { } } } - + /** * check if string is ends with other string * @param string $haystack @@ -604,7 +629,7 @@ public static function endsWith($haystack, $needle, $needleprefix = null) return substr($haystack, -strlen($needle))===$needle; } } - + /** * domain validator * @@ -641,7 +666,7 @@ public function V_domain($value, $params = NULL){ } } } - + /** * regex validator * @@ -709,7 +734,7 @@ public function V_regex($value, $params = ['pattern' => '/.*/']) { } return !$this->setError(false); } - + /** * password validator * @@ -725,7 +750,7 @@ public function V_regex($value, $params = ['pattern' => '/.*/']) { */ public function V_password($value, $params = []) { $p = trim(strip_tags(''.$value)); - + if (in_array('empty', $params, true) && $p === ''){ $this->filtered = $p; return !$this->setError(false); @@ -744,7 +769,7 @@ public function V_password($value, $params = []) { $this->filtered=$p; return !$this->setError(false); } - + /** * name validator * @@ -762,7 +787,7 @@ public function V_path($value, $params = NULL) { } return !$this->setError(false); } - + /** * color validator * @@ -780,7 +805,7 @@ public function V_color($value, $params = NULL) { } return !$this->setError(false); } - + /** * filename validator * @@ -804,7 +829,7 @@ public function V_filename($value, $params = NULL) { } return !$this->setError(false); } - + /** * time validator * @@ -838,7 +863,7 @@ public function V_time($value, $params = NULL) { } } } - + /** * array validator * test if element is array @@ -911,7 +936,7 @@ public function V_array($a, $params){ $this->filtered = $out; return !$this->isError; } - + /** * arraymap validator * run validator on array and given map @@ -936,7 +961,7 @@ public function V_arraymap($a, $params){ } return !$this->isError; } - + /** * date validator * @@ -966,7 +991,7 @@ public function V_date($value, $params = NULL) { } return !$this->setError(false); } - + /** * array validator * test if string is valid iban @@ -980,7 +1005,7 @@ public function V_date($value, $params = NULL) { * @param array $params * @return boolean */ - public function V_iban($value, $params){ + public function V_iban($value, $params = []){ $iban = trim(strip_tags(''.$value)); $iban = strtoupper($iban); // to upper $iban = preg_replace('/(\s|\n|\r)/', '', $iban); //remove white spaces @@ -998,33 +1023,34 @@ public function V_iban($value, $params){ } return !$this->setError(false); } - + /** - * check if string is valid iban, - * + * check if string is valid iban, + * * @param $iban iban srstring to check * * @return bool * @see https://en.wikipedia.org/wiki/International_Bank_Account_Number#Validating_the_IBAN */ public static function _checkIBAN($iban){ + if ($iban == '') return false; $iban = strtoupper(str_replace(' ', '', $iban)); - $Countries = ARRAY('AL' => 28, 'AD' => 24, 'AT' => 20, 'AZ' => 28, 'BH' => 22, 'BE' => 16, 'BA' => 20, 'BR' => 29, 'BG' => 22, 'CR' => 21, 'HR' => 21, 'CY' => 28, 'CZ' => 24, 'DK' => 18, 'DO' => 28, 'EE' => 20, 'FO' => 18, 'FI' => 18, 'FR' => 27, 'GE' => 22, 'DE' => 22, 'GI' => 23, 'GR' => 27, 'GL' => 18, 'GT' => 28, 'HU' => 28, 'IS' => 26, 'IE' => 22, 'IL' => 23, 'IT' => 27, 'JO' => 30, 'KZ' => 20, 'KW' => 30, 'LV' => 21, 'LB' => 28, 'LI' => 21, 'LT' => 20, 'LU' => 20, 'MK' => 19, 'MT' => 31, 'MR' => 27, 'MU' => 30, 'MC' => 27, 'MD' => 24, 'ME' => 22, 'NL' => 18, 'NO' => 15, 'PK' => 24, 'PS' => 29, 'PL' => 28, 'PT' => 25, 'QA' => 29, 'RO' => 24, 'SM' => 27, 'SA' => 24, 'RS' => 22, 'SK' => 24, 'SI' => 19, 'ES' => 24, 'SE' => 24, 'CH' => 21, 'TN' => 24, 'TR' => 26, 'AE' => 23, 'GB' => 22, 'VG' => 24); - + $countries = array('AL' => 28, 'AD' => 24, 'AT' => 20, 'AZ' => 28, 'BH' => 22, 'BE' => 16, 'BA' => 20, 'BR' => 29, 'BG' => 22, 'CR' => 21, 'HR' => 21, 'CY' => 28, 'CZ' => 24, 'DK' => 18, 'DO' => 28, 'EE' => 20, 'FO' => 18, 'FI' => 18, 'FR' => 27, 'GE' => 22, 'DE' => 22, 'GI' => 23, 'GR' => 27, 'GL' => 18, 'GT' => 28, 'HU' => 28, 'IS' => 26, 'IE' => 22, 'IL' => 23, 'IT' => 27, 'JO' => 30, 'KZ' => 20, 'KW' => 30, 'LV' => 21, 'LB' => 28, 'LI' => 21, 'LT' => 20, 'LU' => 20, 'MK' => 19, 'MT' => 31, 'MR' => 27, 'MU' => 30, 'MC' => 27, 'MD' => 24, 'ME' => 22, 'NL' => 18, 'NO' => 15, 'PK' => 24, 'PS' => 29, 'PL' => 28, 'PT' => 25, 'QA' => 29, 'RO' => 24, 'SM' => 27, 'SA' => 24, 'RS' => 22, 'SK' => 24, 'SI' => 19, 'ES' => 24, 'SE' => 24, 'CH' => 21, 'TN' => 24, 'TR' => 26, 'AE' => 23, 'GB' => 22, 'VG' => 24); + //1. check country code exists + iban has valid length - if( !array_key_exists(substr($iban,0,2), $Countries) - || strlen($iban) != $Countries[substr($iban,0,2)]){ + if( !array_key_exists(substr($iban,0,2), $countries) + || strlen($iban) != $countries[substr($iban,0,2)]){ return false; } - + //2. Rearrange countrycode and checksum $rearranged = substr($iban, 4) . substr($iban, 0, 4); - + //3. convert to integer $iban_letters = str_split($rearranged); $iban_int_only = ''; - foreach ($iban_int_only as $char){ - if (is_int($char)) $iban_int_only .= $char; + foreach ($iban_letters as $char){ + if (is_numeric($char)) $iban_int_only .= $char; else { $ord = ord($char) - 55; // ascii representation - 55, so a => 10, b => 11, ... if ($ord >= 10 && $ord <= 35){ @@ -1034,15 +1060,15 @@ public static function _checkIBAN($iban){ } } } - + //4. calculate mod 97 -> have to be 1 - if (self::_bcmod($iban_int_only, '97') == 1){ + if (self::_bcmod($iban_int_only, '97') === 1){ return true; }else{ return false; } } - + /** * _bcmod - get modulus (substitute for bcmod) * be careful with big $modulus values @@ -1056,7 +1082,7 @@ public static function _checkIBAN($iban){ **/ public static function _bcmod($left_operand, $modulus){ if (function_exists('bcmod')){ - return bcmod($left_operand, $modulus); + return (int)bcmod($left_operand, $modulus); } else { $take = 5; // how many numbers to take at once? $mod = ''; @@ -1067,11 +1093,16 @@ public static function _bcmod($left_operand, $modulus){ $mod = $a % $modulus; } while ( strlen($left_operand) ); - + return (int)$mod; } } - + + /** + * capsule function for array and arraymap validator + * adds [ and ] to $this->lastMapKey and return this string + * used on error messages in mentioned functions + **/ private function _capsule_lastMapKey(){ $capsuled = $this->lastMapKey; if ($capsuled != ''){ @@ -1084,4 +1115,4 @@ private function _capsule_lastMapKey(){ } return $capsuled; } -} \ No newline at end of file +} diff --git a/lib/inc.all.php b/lib/inc.all.php index 9f374ea..e9c9e4d 100644 --- a/lib/inc.all.php +++ b/lib/inc.all.php @@ -1,5 +1,8 @@ 0) { + function pvar_dump(...$vars){ + echo '
    ';
    +		$stack = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS,1);
    +		echo $stack[0]['file']."({$stack[0]['line']})\n";
    +		call_user_func_array('var_dump', $vars);
    +		echo '
    '; + } +} + require_once SYSBASE . '/lib/class.Helper.php'; require_once SYSBASE . '/lib/class.Singleton.php'; require_once SYSBASE . '/lib/class.JsonController.php'; diff --git a/logs/.keep b/logs/.keep new file mode 100644 index 0000000..8d1c8b6 --- /dev/null +++ b/logs/.keep @@ -0,0 +1 @@ + diff --git a/old/img/stura-try.pdf b/old/img/stura-try.pdf new file mode 100644 index 0000000000000000000000000000000000000000..c75bd8d7100b6c57efe8ae98ac3f7b4d592bb6a7 GIT binary patch literal 5059 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 T&RAv~p`)t}Qc*FsJO=tNj?P3` literal 0 HcmV?d00001 diff --git a/public/index.php b/public/index.php index e029251..dc9b116 100644 --- a/public/index.php +++ b/public/index.php @@ -20,7 +20,7 @@ $routeInfo['controller'] = 'error'; $routeInfo['method'] = 'POST'; } - + // ip check if ($_SERVER['REMOTE_ADDR'] && is_array($_SERVER['REMOTE_ADDR']) && !in_array($_SERVER['REMOTE_ADDR'], ALLOWED_IPS)){ $routeInfo['action'] = '403'; @@ -31,7 +31,7 @@ // auth --------------------------------- if (isset($routeInfo['auth']) && ($routeInfo['auth'] == 'Basic' || $routeInfo['auth'] == 'basic')){ - define('AUTH_HANLER', 'AuthBasicHandler'); + define('AUTH_HANDLER', 'AuthBasicHandler'); AuthBasicHandler::getInstance()->requireAuth(); AuthBasicHandler::getInstance()->requireGroup('basic'); if (!isset($routeInfo['groups'])){ @@ -44,7 +44,7 @@ $routeInfo['method'] = 'POST'; } }else{ - define('AUTH_HANLER', null); + define('AUTH_HANDLER', null); } // handle route ------------------------- @@ -85,5 +85,3 @@ $errorHdl->render(); break; } - - diff --git a/template/tex/zahlungsanweisung.phpTex b/template/tex/zahlungsanweisung.phpTex new file mode 100644 index 0000000..57dffa6 --- /dev/null +++ b/template/tex/zahlungsanweisung.phpTex @@ -0,0 +1,181 @@ +\documentclass[a4paper,11pt]{article} + +\usepackage[T1]{fontenc} +\usepackage[utf8]{inputenc} +\usepackage{graphicx} +\usepackage{xcolor} +\usepackage{ngerman} +\usepackage[tx]{sfmath} +\usepackage{calc} +\usepackage{lastpage} +%\usepackage{ifthen} +\usepackage{xifthen} +\usepackage{multicol} +\renewcommand\familydefault{\sfdefault} +\usepackage{tgheros} +\usepackage{intcalc} + +\usepackage{amsmath,amssymb,amsthm,textcomp} +%\usepackage{enumerate} +\usepackage{enumitem} +\usepackage{multicol} +\usepackage{tikz} + +\usepackage{geometry} +\geometry{total={210mm,297mm}, +left=25mm,right=25mm,% +bindingoffset=0mm, top=20mm,bottom=20mm} + +% pdf version to min 1.6 +\pdfminorversion=6 + +\newcommand{\footerstring}{} +\linespread{1.3} + +\newcommand{\linia}{\rule{\linewidth}{0.5pt}} + +\newcommand{\mysection}[1]{ +\begin{center} +{\large \textsc{#1}} +\vspace*{-0.5cm} +\\\linia\\ +\vspace*{-0.5cm} +\end{center} +} + +% custom theorems if needed +\newtheoremstyle{mytheor} +{1ex}{1ex}{\normalfont}{0pt}{\scshape}{.}{1ex} +{{\thmname{#1 }}{\thmnumber{#2}}{\thmnote{ (#3)}}} + +\theoremstyle{mytheor} +\newtheorem{defi}{Definition} + +% my own titles +\makeatletter +\renewcommand{\maketitle}{ +\begin{center} +\vspace*{-0.5cm} +{\huge \textsc{\@title}} +\linia +\end{center} +} +\makeatother +%%% + +% custom footers and headers +\usepackage{fancyhdr} +\pagestyle{fancy} +\lhead{} +\chead{} +\rhead{} +\lfoot{\footerstring} +\cfoot{} +\rfoot{Seite \thepage{} von \pageref{LastPage}} +\renewcommand{\headrulewidth}{0pt} +\renewcommand{\footrulewidth}{0pt} + +% +% all section titles centered and bolded +\usepackage{sectsty} +\allsectionsfont{\centering\bfseries\large} +% +% add section label +\renewcommand\thesection{} +% + +%%%----------%%%----------%%%----------%%%----------%%% + +\begin{document} + +\title{Zahlungs-/ Buchungsanweisung} +\author{StuRa der TU Ilmenau} + +\vspace*{-2.0cm} +\begin{figure}[h] +\centering +%\includegraphics[width=3cm]{stura2.pdf} +\end{figure} + +\maketitle + +\vspace*{-0.5cm} + +\mysection{Allgemeine Angaben} + +\begin{enumerate}[label=\Roman*] +\itemsep-2mm +\item \textbf{Projekt ID}\hfill IP-- +\item \textbf{Projekt}\hfill +\item \textbf{Organisation} \hfill +\item \textbf{Rechtsgrundlage} \hfill +\item \textbf{Abrechnungs ID}\hfill A +\item \textbf{Name Abrechnung} \hfill +\end{enumerate} + +\vspace*{2mm} +\mysection{Zahlungsanweisung/ Einnahmevermerk} +\begin{enumerate}[label=\Roman*,resume] +\itemsep-2mm +\item \textbf{Name} \hfill +\item \textbf{Adresse} \hfill +\item \textbf{IBAN} \hfill +\item \textbf{Betrag} \hfill +\vspace{0,5cm} +\item \textbf{Datum Zahlung/ Einnahme} \hfill \_\_\_\_.\_\_\_\_.\_\_\_\_\_\_\_\_ +\end{enumerate} + +\vspace{0,5cm} +\parbox[b]{0.4\linewidth}{% size of the first signature box +\strut +\textbf{Sachliche Richtigkeit} \\[1.25cm]% This 2cm is the space for the signature under the names +\hrule +\vspace{0.25cm} +%(\ifthenelse{\isempty \hv}{Haushaltsverantwortliche/r}{\hv}) +} +\hspace{0,5cm} % distance between the two signature blocks +\parbox[b]{0.4\linewidth}{% ...and the second one +\strut +\textbf{Rechnerische Richtigkeit} \\[1.25cm]% This 2cm is the space for the signature under the names +\hrule +\vspace{0.25cm} +%(\ifthenelse{\isempty \kv}{Kassenverantwortliche/r}{\kv}) +} +\par\vspace{0,5cm} + +\mysection{Buchungsanweisung} + +\begin{center} +\begin{tabular}{rrrrr} +\textbf{Beleg} & \textbf{Einnahme} & \textbf{Ausgabe} & \textbf{Titel} & \textbf{Buchungsnummer}\\ % Buchungsnummer per Hand + +\end{tabular} +\end{center} +\vspace{1cm} +\textbf{Angewiesen am:} \_\_\_\_.\_\_\_\_.\_\_\_\_\_\_\_\_\\\\ +\textbf{Gebucht am:} \_\_\_\_.\_\_\_\_.\_\_\_\_\_\_\_\_\\ + +\vspace{0,5cm} +\parbox[b]{0.4\linewidth}{% size of the first signature box +\strut +\textbf{Sachliche Richtigkeit} \\[1.25cm]% This 2cm is the space for the signature under the names +\hrule +\vspace{0.25cm} +%(\ifthenelse{\isempty \hv}{Haushaltsverantwortliche/r}{\hv}) +} +\hspace{1cm} % distance between the two signature blocks +\parbox[b]{0.4\linewidth}{% ...and the second one +\strut +\textbf{Rechnerische Richtigkeit} \\[1.25cm]% This 2cm is the space for the signature under the names +\hrule +\vspace{0.25cm} +%(\ifthenelse{\isempty \kv}{Kassenverantwortliche/r}{\kv}) +} + +\end{document} \ No newline at end of file From b3baaa726ac23114df184998b3ae9f6588a0b50b Mon Sep 17 00:00:00 2001 From: cherrg Date: Thu, 17 Jan 2019 18:24:21 +0100 Subject: [PATCH 26/42] update validator * php doc * ip validator (v4, v6, cidr) * array map - multiple errors --- lib/class.Validator.php | 592 +++++++++++++++++++++++++++++++++++----- 1 file changed, 526 insertions(+), 66 deletions(-) diff --git a/lib/class.Validator.php b/lib/class.Validator.php index 358cade..05e9978 100644 --- a/lib/class.Validator.php +++ b/lib/class.Validator.php @@ -68,6 +68,7 @@ function __contstruct(){ * @param integer $code html code * @param string $msg short message * @param string $desc error description + * @return bool */ private function setError($isError, $code=0, $msg='', $desc=''){ $this->isError = $isError; @@ -78,7 +79,7 @@ private function setError($isError, $code=0, $msg='', $desc=''){ } /** - * @return the $isError + * @return boolean $isError */ public function getIsError() { @@ -86,7 +87,7 @@ public function getIsError() } /** - * @return the $lastErrorMsg + * @return string $lastErrorMsg */ public function getLastErrorMsg() { @@ -94,7 +95,7 @@ public function getLastErrorMsg() } /** - * @return the $lastErrorDescription + * @return string $lastErrorDescription */ public function getLastErrorDescription() { @@ -102,7 +103,7 @@ public function getLastErrorDescription() } /** - * @return the $lastErrorCode + * @return integer $lastErrorCode */ public function getLastErrorCode() { @@ -110,7 +111,7 @@ public function getLastErrorCode() } /** - * @return the $lastMapKey + * @return string $lastMapKey */ public function getLastMapKey() { @@ -120,7 +121,8 @@ public function getLastMapKey() /** * filter may sanitize input values are stored here * Post validators will create sanitized array - * @return the $filtered + * @param string $key + * @return array|mixed $filtered */ public function getFiltered($key = NULL) { @@ -146,8 +148,12 @@ public function validate($value, $validator){ && is_callable([$this, 'V_'.$validatorName]) ){ return $this->{'V_'.$validatorName}($value, $validatorParams); } else { - $this->setError(true, 403, 'Access Denied', "POST unknown validator"); - error_log("Validator: Unknown Validator: $validatorName"); + $this->setError(true, 403, 'Access Denied', "POST unknown validator: $validatorName"); + if (class_exists('\intbf\ErrorHandler')){ + ErrorHandler::_errorLog("Validator: Unknown Validator: $validatorName", 'Validator: validate'); + } else { + error_log("Validator: validate: Unknown Validator: $validatorName", 'Validator: validate'); + } return !$this->isError; } } @@ -162,27 +168,61 @@ public function validate($value, $validator){ * ] * validator may contains parameter 'optional' -> so required can be disabled per parameter * + * @param array $source_unsafe * @param array $map - * @param boolean $required key is required + * @param boolean $required key is required, overwritable with validator parameter 'optional' + * @param boolean $errormap don't break on error, and sum up all errors messages, does not affect required error, so dont answer incomplete posts, return errors fields as arrays + * @param boolean $multi_validator allow multiple validators on same key, you have to set additional parameter 'multivalidator' and add additional array layer + * e.g. + * ['key' => ['multi', ['validator1'], ['validator2'], ...]] * @return boolean */ - public function validateMap($source_unsafe, $map, $required = true){ + public function validateMap(&$source_unsafe, $map, $required = true, $errormap = false, $multi_validator = false){ $out = []; + $errorMsgs = []; + $errorDesc = []; + $errorCode = []; + $hasError = false; foreach($map as $key => $validator){ $this->lastMapKey = $key; if (!isset($source_unsafe[$key])){ - if ($required && !in_array('optional', $validator)){ - $this->setError(true, 403, 'Access Denied', "POST missing parameter: '$key'"); + if ($required && !in_array('optional', $validator, true)){ + $this->setError(true, 403, 'Access Denied', "missing parameter: '$key'"); return !$this->isError; } else { $this->setError(false); } } else { - $this->validate($source_unsafe[$key], $validator); - if ($this->isError) break; - $out[$key] = $this->filtered; + $tmp_vali = []; + if ($multi_validator && (($pos = array_search('multivalidator' , $validator, true)) !== false)){ + $tmp_vali = $validator; + unset($tmp_vali[$pos]); + } else { + $tmp_vali[] = $validator; + } + foreach($tmp_vali as $vali){ + $this->validate($source_unsafe[$key], $vali); + if ($this->isError){ + if ($errormap===true){ + $hasError = true; + $errorMsgs[$key][] = $this->lastErrorMsg; + $errorDesc[$key][] = $this->lastErrorDescription; + $errorCode[$key][] = $this->lastErrorCode; + } else { + break 2; + } + } else { + $out[$key] = $this->filtered; + } + } } } + if ($hasError){ + $this->isError = true; + $this->lastErrorCode = $errorCode; + $this->lastErrorMsg = $errorMsgs; + $this->lastErrorDescription = $errorDesc; + } $this->filtered = $out; return !$this->isError; } @@ -557,32 +597,378 @@ public function V_name($value, $params = NULL) { * * @param $value * @param $params + * empty 1 allow empty value + * error 2 replace whole error message on error case + * forceprotocol 1 force http://|https:// in url + * forceslash 1 force trailingslash * @return boolean */ public function V_url($value, $params = NULL) { $url = trim(strip_tags(''.$value)); - $re = '/^((http[s]?)((:|%3A)\/\/))(((\w)+((-|\.)(\w+))*)+(\w){0,6}?(:([0-5]?[0-9]{1,4}|6([0-4][0-9]{3}|5([0-4][0-9]{2}|5([0-2][0-9]|3[0-5])))))?\/)((\w)+((\.|-)(\w)+)*\/)*$/'; + if (in_array('empty', $params, true) && $url === ''){ + $this->filtered = ''; + return !$this->setError(false); + } + $re = '/^((http[s]?)((:|%3A)\/\/))'.((in_array('forceprotocol', $params, true))?'':'?').'(((\w)+((-|\.)(\w+))*)+(\w){0,6}?(:([0-5]?[0-9]{1,4}|6([0-4][0-9]{3}|5([0-4][0-9]{2}|5([0-2][0-9]|3[0-5])))))?\/'.((in_array('forceslash', $params, true))?'':'?').')((\w)+((\.|-)(\w)+)*\/'.((in_array('forceslash', $params, true))?'':'?').')*$/'; if (!preg_match($re, $url) || strlen($url) >= 128){ - return !$this->setError(true, 200, "url validation failed", 'url validation failed'); + $msg = "url validation failed"; + if (isset($params['error'])) $msg = $params['error']; + return !$this->setError(true, 200, $msg, 'url validation failed'); } else { $this->filtered=$url; } return !$this->setError(false); } + /** + * client ip validator + * check if string is a valid ip on whitelist, or not in blacklist (supports ipv4 and ipv6) + * if value is not set function checks SERVER['REMOTE_ADDR'], so filtered value contains real client IP + * keep in mind, in array validation to set and override may given userinput to NULL, or set 'ignoreval' param + * this function does not check if the given value contains a valid ip address + * checks blacklist first + * + * v4subnet_black and v4subnet_white will only work with IPv4 IP addresses and clients + * + * @param mixed $value ignored + * @param array $params + * ignoreval 1 ignore value - use $_SERVER['REMOTE_ADDR'] + * whitelist 2 if set only allow client ips from this whitelist + * blacklist 2 if set block clients from this ips + * subnet 1 allow cidr subnets on black and whitelist format: '127.0.0.1/32' or '22AD:00DD:0000:1F33::/64' + * v4subnet_white 2 check if ip is in ip range, format: 127.0.0.1/32 + * error 2 replace whole error message on error case + * @return boolean + */ + public function V_clientIp($value = NULL, $params = NULL){ + //ignore value? + if ($value === NULL||!is_string||empty($value)||in_array('ignoreval', $params, true)) $value = $_SERVER['REMOTE_ADDR']; + //check blacklist + if (isset($params['blacklist']) && is_array($params['blacklist']) && in_array( $value, $params['blacklist'], true)){ + $msg = ((isset($params['error']) )?$params['error']:'Blocked ip address.'); + return !$this->setError(true, 200, $msg, 'Blocked ip address.'); + } + if (isset($params['blacklist']) && is_array($params['blacklist']) && in_array( 'subnet', $params, true)){ + foreach($params['blacklist'] as $b){ + if (strpos($b, '/') !== false){ + if ($this->V_ipCidr($value, ['onlyreturn', 'cidr' => $b])){ + $msg = ((isset($params['error']) )?$params['error']:'Blocked ip address.'); + return !$this->setError(true, 200, $msg, 'Blocked ip address.'); + } + } + } + } + //check whitelist + $found = false; + if (isset($params['whitelist']) && is_array($params['whitelist']) && in_array( 'subnet', $params, true)){ + $found = in_array( $value, $params['whitelist'], true); + if (!$found) foreach($params['whitelist'] as $w){ + if (strpos($w, '/') !== false){ + if ($this->V_ipCidr($value, ['onlyreturn', 'cidr' => $w])){ + $found = true; + break; + } + } + } + } + if (!$found && isset($params['whitelist']) && (!is_array($params['whitelist']) || !in_array( $value, $params['whitelist'], true))){ + $msg = ((isset($params['error']) )?$params['error']:'Blocked ip address.'); + return !$this->setError(true, 200, $msg, 'Blocked ip address.'); + } + + $this->filtered = $value; + return !$this->setError(false); + } + + /** + * ip v4 validator + * check if string is a valid ip v4 address + * @param $value + * no_priv_range 1 prevent private address range to be validated as true @see http://php.net/manual/de/filter.filters.flags.php + * no_res_range 1 prevent reservated address range to be validated as true @see http://php.net/manual/de/filter.filters.flags.php + * error 2 replace whole error message on error case + * onlyreturn 1 dont set $this->filtered or $this->error - required for other validators - return false or valid value + * @param array $params + * @return boolean + */ + public function V_ip4($value = NULL, $params = []){ + $flag = FILTER_FLAG_IPV4; + if (in_array('no_priv_range', $params, true)){ + $flag = $flag | FILTER_FLAG_NO_PRIV_RANGE; + } + if (in_array('no_res_range', $params, true)){ + $flag = $flag | FILTER_FLAG_NO_RES_RANGE; + } + $r = filter_var( + $value, + FILTER_VALIDATE_IP, + array('flags' => $flag) + ); + if (!$r && !in_array('onlyreturn', $params, true)){ + $msg = ((isset($params['error']) )?$params['error']:'Invalid IPv4.'); + return !$this->setError(true, 200, $msg, 'Invalid IPv4.'); + } elseif ($r && !in_array('onlyreturn', $params, true)) { + $this->filtered = $r; + return !$this->setError(false); + } elseif(!$r) { + return false; + } else { + return $r; + } + } + + /** + * ip v6 validator + * check if string is a valid ip v6 address + * @param $value + * no_priv_range 1 prevent private address range to be validated as true @see http://php.net/manual/de/filter.filters.flags.php + * no_res_range 1 prevent reservated address range to be validated as true @see http://php.net/manual/de/filter.filters.flags.php + * error 2 replace whole error message on error case + * onlyreturn 1 dont set $this->filtered or $this->error - required for other validators - return false or valid value + * @param array $params + * @return boolean + */ + public function V_ip6($value = NULL, $params = []){ + $flag = FILTER_FLAG_IPV6; + if (in_array('no_priv_range', $params, true)){ + $flag = $flag | FILTER_FLAG_NO_PRIV_RANGE; + } + if (in_array('no_res_range', $params, true)){ + $flag = $flag | FILTER_FLAG_NO_RES_RANGE; + } + $r = filter_var( + $value, + FILTER_VALIDATE_IP, + array('flags' => $flag) + ); + if (!$r && !in_array('onlyreturn', $params, true)){ + $msg = ((isset($params['error']) )?$params['error']:'Invalid IPv6.'); + return !$this->setError(true, 200, $msg, 'Invalid IPv6.'); + } elseif ($r && !in_array('onlyreturn', $params, true)) { + $this->filtered = $r; + return !$this->setError(false); + } elseif(!$r) { + return false; + } else { + return $r; + } + } + /** * ip validator * check if string is a valid ip address (supports ipv4 and ipv6) * @param $value - * @param $params + * getversion 1 do not only return valid value, do also return ip version -> return value: [$value, version] + * no_priv_range 1 prevent private address range to be validated as true @see http://php.net/manual/de/filter.filters.flags.php + * no_res_range 1 prevent reservated address range to be validated as true @see http://php.net/manual/de/filter.filters.flags.php + * error 2 replace whole error message on error case + * onlyreturn 1 dont set $this->filtered or $this->error - required for other validators - return false or valid value + * @param array $params * @return boolean */ - public function V_ip($value, $params = NULL){ + public function V_ip($value, $params = []){ + $tmp_p = ['onlyreturn']; + if (in_array('no_priv_range', $params, true)) $tmp_p[] = 'no_priv_range'; + if (in_array('no_res_range', $params, true)) $tmp_p[] = 'no_res_range'; + if ($v4 = $this->V_ip4( $value, $tmp_p )){ + if (!in_array('onlyreturn', $params, true)) $this->filtered = (in_array('getversion', $params, true))? [$v4, 4] : $v4; + return (in_array('onlyreturn', $params, true))? ($v4) : (!$this->setError(false)); + } elseif ($v6 = $this->V_ip6( $value, $tmp_p )){ + if (!in_array('onlyreturn', $params, true)) $this->filtered = (in_array('getversion', $params, true))? [$v6, 6] : $v6; + return (in_array('onlyreturn', $params, true))? ($v6) : (!$this->setError(false)); + } else { + $msg = ((isset($params['error']) )?$params['error']:'Invalid IP.'); + return (in_array('onlyreturn', $params, true))? (false) : !$this->setError(true, 200, $msg, 'Invalid IP.'); + } + } + + /** + * ipv4 cidr(subnet) validator + * check if string is in given ip range - given in cidr format + * + * partial based on, see in line comment + * @see https://stackoverflow.com/questions/594112/matching-an-ip-to-a-cidr-mask-in-php-5#14841828 + * + * @param $value + * cidr (required) 2 cidr e.g. 127.0.0.1/32 + * no_priv_range 1 prevent private address range to be validated as true @see http://php.net/manual/de/filter.filters.flags.php + * no_res_range 1 prevent reservated address range to be validated as true @see http://php.net/manual/de/filter.filters.flags.php + * error 2 replace whole error message on error case + * onlyreturn 1 dont set $this->filtered or $this->error - required for other validators - return false or valid value + * @param array $params + * @throws \Exception + * @return boolean + */ + public function V_ipv4Cidr($value, $params = []){ + $tmp_p = ['onlyreturn']; + if (in_array('no_priv_range', $params, true)) $tmp_p[] = 'no_priv_range'; + if (in_array('no_res_range', $params, true)) $tmp_p[] = 'no_res_range'; + // check cidr + if (!isset($params['cidr']) || !is_string($params['cidr']) || empty($params['cidr']) ) { + throw new \Exception('Validator[ipv4Cidr]: Missing cidr parameter.'); + } + $cidr = explode('/', $params['cidr']); + $subnet = isset($cidr[0]) ? $cidr[0] : NULL; + $mask = isset($cidr[1]) ? $cidr[1] : NULL; + if ($subnet === null || empty($subnet) || !filter_var($subnet, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) { + throw new \Exception('Validator[ipv4Cidr]: Invalid cidr parameter, missing or invalid subnet'); + } + if ($mask === null || empty($mask) || $mask < 0 || $mask > 32) { + throw new \Exception('Validator[ipv4Cidr]: Invalid cidr parameter, missing or invalid mask'); + } + // check is ip v4 + if ($v4 = $this->V_ip4( $value, $tmp_p )){ + // if condition -> based on: https://stackoverflow.com/questions/594112/matching-an-ip-to-a-cidr-mask-in-php-5#14841828 + if ((ip2long($v4) & ~((1 << (32 - $mask)) - 1) ) == ip2long($subnet)){ + if (!in_array('onlyreturn', $params, true)) $this->filtered = $v4; + return (in_array('onlyreturn', $params, true))? ($v4) : (!$this->setError(false)); + } else { + $msg = ((isset($params['error']) )?$params['error']:'No IPv4 cidr match.'); + return (in_array('onlyreturn', $params, true))? (false) : !$this->setError(true, 200, $msg, 'No IPv4 cidr match.'); + } + } else { + $msg = ((isset($params['error']) )?$params['error']:'Invalid IPv4.'); + return (in_array('onlyreturn', $params, true))? (false) : !$this->setError(true, 200, $msg, 'Invalid IPv4.'); + } + } + + /** + * ipv6 cidr(subnet) validator + * check if string is in given ip range - given in cidr format + * + * partial based on, see in line comment + * @see https://stackoverflow.com/questions/7951061/matching-ipv6-address-to-a-cidr-subnet#7952169 + * + * @param $value + * cidr (required) 2 cidr e.g. '22AD:00DD:0000:1F33::/64' + * no_priv_range 1 prevent private address range to be validated as true @see http://php.net/manual/de/filter.filters.flags.php + * no_res_range 1 prevent reservated address range to be validated as true @see http://php.net/manual/de/filter.filters.flags.php + * error 2 replace whole error message on error case + * onlyreturn 1 dont set $this->filtered or $this->error - required for other validators - return false or valid value + * @param array $params + * @throws \Exception + * @return boolean + */ + public function V_ipv6Cidr($value, $params = []){ + $tmp_p = ['onlyreturn']; + if (in_array('no_priv_range', $params, true)) $tmp_p[] = 'no_priv_range'; + if (in_array('no_res_range', $params, true)) $tmp_p[] = 'no_res_range'; + // check cidr + if (!isset($params['cidr']) || !is_string($params['cidr']) || empty($params['cidr']) ) { + throw new \Exception('Validator[ipv6Cidr]: Missing cidr parameter.'); + } + $cidr = explode('/', $params['cidr']); + $subnet = isset($cidr[0]) ? $cidr[0] : NULL; + $mask = isset($cidr[1]) ? $cidr[1] : NULL; + if ($subnet === null || empty($subnet)) { + throw new \Exception('Validator[ipv6Cidr]: Invalid cidr parameter, missing or invalid subnet'); + } + if ($mask === null || empty($mask) || $mask < 0 || $mask > 128) { + throw new \Exception('Validator[ipv6Cidr]: Invalid cidr parameter, missing or invalid mask'); + } + // check is ip v6 + if ($v6 = $this->V_ip6( $value, $tmp_p )){ + // until if condition -> based on: https://stackoverflow.com/questions/7951061/matching-ipv6-address-to-a-cidr-subnet#7952169 + $subnet = inet_pton($subnet); + $addr = inet_pton($v6); + // iPv6MaskToByteArray + $mask_tmp = $mask; + $addr_tmp = str_repeat("f", $mask_tmp / 4); + switch ($mask_tmp % 4) { + case 0: + break; + case 1: + $addr_tmp .= "8"; + break; + case 2: + $addr_tmp .= "c"; + break; + case 3: + $addr_tmp .= "e"; + break; + } + $addr_tmp = str_pad($addr_tmp, 32, '0'); + $addr_tmp = pack("H*" , $addr_tmp); + $mask_bin = $addr_tmp; + // --- + $match = (($addr & $mask_bin) == $subnet); + if ($match){ + if (!in_array('onlyreturn', $params, true)) $this->filtered = $v6; + return (in_array('onlyreturn', $params, true))? ($v6) : (!$this->setError(false)); + } else { + $msg = ((isset($params['error']) )?$params['error']:'No IPv6 cidr match.'); + return (in_array('onlyreturn', $params, true))? (false) : !$this->setError(true, 200, $msg, 'No IPv6 cidr match.'); + } + } else { + $msg = ((isset($params['error']) )?$params['error']:'Invalid IPv6.'); + return (in_array('onlyreturn', $params, true))? (false) : !$this->setError(true, 200, $msg, 'Invalid IPv6.'); + } + } + + /** + * ip cidr(subnet) validator + * check if string is in given ip range - given in cidr format + * + * @param $value + * cidr (required) 2 cidr e.g. '22AD:00DD:0000:1F33::/64' or '127.0.0.1/32' + * no_priv_range 1 prevent private address range to be validated as true @see http://php.net/manual/de/filter.filters.flags.php + * no_res_range 1 prevent reservated address range to be validated as true @see http://php.net/manual/de/filter.filters.flags.php + * error 2 replace whole error message on error case + * onlyreturn 1 dont set $this->filtered or $this->error - required for other validators - return false or valid value + * @param array $params + * @throws \Exception + * @return boolean + */ + public function V_ipCidr($value, $params = []){ + $tmp_p = ['onlyreturn']; + if (in_array('no_priv_range', $params, true)) $tmp_p[] = 'no_priv_range'; + if (in_array('no_res_range', $params, true)) $tmp_p[] = 'no_res_range'; + // check cidr + if (!isset($params['cidr']) || !is_string($params['cidr']) || empty($params['cidr']) ) { + throw new \Exception('Validator[ipCidr]: Missing cidr parameter.'); + } else { + $tmp_p['cidr'] = $params['cidr']; + } + $cidr = explode('/', $params['cidr']); + $subnet = isset($cidr[0]) ? $cidr[0] : NULL; + $mask = isset($cidr[1]) ? $cidr[1] : NULL; + + if ($subnet === null || empty($subnet)) { + throw new \Exception('Validator[ipCidr]: Invalid cidr parameter, missing or invalid subnet'); + } + if ($mask === null || empty($mask) || $mask < 0 || $mask > 128) { + throw new \Exception('Validator[ipCidr]: Invalid cidr parameter, missing or invalid mask'); + } + //cidr version + $cidr_version = (filter_var($subnet, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4))? 4 : 6; + if ($cidr_version == 4 && ($v4 = $this->V_ipv4cidr($value, $tmp_p))){ + if (!in_array('onlyreturn', $params, true)) $this->filtered = $v4; + return (in_array('onlyreturn', $params, true))? ($v4) : (!$this->setError(false)); + } elseif ($cidr_version == 6 && ($v6 = $this->V_ipv6cidr($value, $tmp_p))){ + if (!in_array('onlyreturn', $params, true)) $this->filtered = $v6; + return (in_array('onlyreturn', $params, true))? ($v6) : (!$this->setError(false)); + } else { + $msg = ((isset($params['error']) )?$params['error']:'No IP cidr match.'); + return (in_array('onlyreturn', $params, true))? (false) : !$this->setError(true, 200, $msg, 'No IP cidr match.'); + } + } + + /** + * ip validator (deprecated) + * old implementation, php now provides this function + * check if string is a valid ip address (supports ipv4 and ipv6) + * @param $value + * error 2 replace whole error message on error case + * @param array $params + * @return boolean + */ + public function V_ipOld($value, $params = NULL){ if (self::isValidIP($value)){ $this->filtered = $value; return !$this->setError(false); } else { - return !$this->setError(true, 200, 'No ip address', 'No ip address'); + $msg = ((isset($params['error']) )?$params['error']:'No ip address'); + return !$this->setError(true, 200, $msg, 'No ip address'); } } @@ -591,7 +977,7 @@ public function V_ip($value, $params = NULL){ * helper function * * @param string $ipadr - * @param $recursive if true also allowes IP address with surrounding brackets [] + * @param boolean $recursive if true also allowes IP address with surrounding brackets [] * @return boolean */ public static function isValidIP( $ipadr, $recursive = true) { @@ -649,41 +1035,45 @@ public function V_domain($value, $params = NULL){ if ($this->V_ip($host)){ $this->filtered = $host; return !$this->setError(false); - } else if ( preg_match("/^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-]*[A-Za-z0-9])$/", $host) && + } else if ( preg_match('/^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-]*[A-Za-z0-9])$/', $host) && ( (version_compare(PHP_VERSION, '7.0.0') >= 0) && filter_var($host, FILTER_VALIDATE_DOMAIN, FILTER_FLAG_HOSTNAME)!==false || (version_compare(PHP_VERSION, '7.0.0') < 0) ) ) { - $this->filtered = $host; - return !$this->setError(false); - } else { - $value_idn = idn_to_ascii($host); - if ( preg_match("/^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-]*[A-Za-z0-9])$/", $value_idn) && - ( (version_compare(PHP_VERSION, '7.0.0') >= 0) && filter_var($value_idn, FILTER_VALIDATE_DOMAIN, FILTER_FLAG_HOSTNAME)!==false || - (version_compare(PHP_VERSION, '7.0.0') < 0) ) ) { - $this->filtered = $value_idn; - return !$this->setError(false); - } else { - return !$this->setError(true, 200, 'Kein gültiger Hostname angegeben' ); - } - } + $this->filtered = $host; + return !$this->setError(false); + } else { + $value_idn = idn_to_ascii($host); + if ( preg_match('/^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-]*[A-Za-z0-9])$/', $value_idn) && + ( (version_compare(PHP_VERSION, '7.0.0') >= 0) && filter_var($value_idn, FILTER_VALIDATE_DOMAIN, FILTER_FLAG_HOSTNAME)!==false || + (version_compare(PHP_VERSION, '7.0.0') < 0) ) ) { + $this->filtered = $value_idn; + return !$this->setError(false); + } else { + return !$this->setError(true, 200, 'Kein gültiger Hostname angegeben' ); + } + } } /** * regex validator * * $param - * regex 2 match pattern - * errorkey 2 replace 'regex' with errorkey on error case - * error 2 replace whole error message on error case - * upper 1 string to uppercase - * lower 1 string to lower case - * replace 2 touple [search, replace] replace string - * minlength 2 minimum string length - * maxlength 2 maximum string length - * noTagStrip 1 disable tag strip before validation - * noTrim 1 disable trim whitespaces - * trimLeft 2 trim Text on left side, parameter trim characters - * trimRight 2 trim Text on right side, parameter trim characters - * empty 1 allow empty string if not in regex + * regex 2 match pattern + * strsplit 2 split string into parts and run regex on each part, here specify split length (integer), you may require this on large texts + * errorkey 2 replace 'regex' with errorkey on error case + * error 2 replace whole error message on error case + * upper 1 string to uppercase + * specialchars 1 add htmlspecialchar filter + * addslashes 1 add addslashes filter + * stripslashes 1 add stripslashes filter + * lower 1 string to lower case + * replace 2 touple [search, replace] replace string + * minlength 2 minimum string length + * maxlength 2 maximum string length + * noTagStrip 1 disable tag strip before validation + * noTrim 1 disable trim whitespaces + * trimLeft 2 trim Text on left side, parameter trim characters + * trimRight 2 trim Text on right side, parameter trim characters + * empty 1 allow empty string if not in regex * * @param $value * @param $params @@ -691,6 +1081,15 @@ public function V_domain($value, $params = NULL){ */ public function V_regex($value, $params = ['pattern' => '/.*/']) { $v = ''.$value; + if (in_array('specialchars', $params, true)){ + $v = htmlspecialchars($v); + } + if (in_array('addslashes', $params, true)){ + $v = addslashes($v); + } + if (in_array('stripslashes', $params, true)){ + $v = stripslashes($v); + } if (!in_array('noTagStrip', $params, true)){ $v = strip_tags($v); } @@ -716,20 +1115,30 @@ public function V_regex($value, $params = ['pattern' => '/.*/']) { if (in_array('lower', $params, true)){ $v = strtolower($v); } - if (isset($params['maxlength']) && strlen($v) >= $params['maxlength']){ + if (isset($params['maxlength']) && strlen($v) > $params['maxlength']){ $msg = "String is too long (Maximum length: {$params['maxlength']})"; - return !$this->setError(true, 200, $msg); + return !$this->setError(true, 200, (isset($params['error']))? $params['error']: $msg, $msg); } if (isset($params['minlength']) && strlen($v) < $params['minlength']){ $msg = "String is too short (Minimum length: {$params['minlength']})"; - return !$this->setError(true, 200, $msg); + return !$this->setError(true, 200, (isset($params['error']))? $params['error']: $msg, $msg); } $re = $params['pattern']; - if (!preg_match($re, $v) || (isset($params['maxlength']) && strlen($v) >= $params['maxlength'])) { - $msg = ((isset($params['errorkey']) )?$params['errorkey']:'regex').' validation failed'; - if (isset($params['error'])) $msg = $params['error']; - return !$this->setError(true, 200, $msg, $msg); + if (!isset($params['strsplit'])){ + if (!preg_match($re, $v)) { + $msg = ((isset($params['errorkey']) )?$params['errorkey']:'regex').' validation failed'; + return !$this->setError(true, 200, (isset($params['error']))? $params['error']: $msg, $msg); + } else { + $this->filtered=$v; + } } else { + $split = str_split($v, $params['strsplit']); + foreach($split as $part){ + if (!preg_match($re, $part)) { + $msg = ((isset($params['errorkey']) )?$params['errorkey']:'regex').' validation failed'; + return !$this->setError(true, 200, (isset($params['error']))? $params['error']: $msg, $msg); + } + } $this->filtered=$v; } return !$this->setError(false); @@ -741,30 +1150,57 @@ public function V_regex($value, $params = ['pattern' => '/.*/']) { * $param * minlength 2 minimum string length * maxlength 2 maximum string length - * encrypt 1 encrypt password * empty 1 allow empty value + * encrypt 1 encrypt password - only available if Crypto class is defined + * hash 1 hash password - only available if Crypto class is defined + * error 2 replace whole error message on error case * * @param $value * @param $params + * @throws \Exception * @return boolean */ public function V_password($value, $params = []) { $p = trim(strip_tags(''.$value)); - if (in_array('empty', $params, true) && $p === ''){ $this->filtered = $p; return !$this->setError(false); } if (isset($params['maxlength']) && strlen($p) >= $params['maxlength']){ $msg = "The password is too long (Maximum length: {$params['maxlength']})"; + if (isset($params['error'])) $msg = $params['error']; return !$this->setError(true, 200, $msg); } if (isset($params['minlength']) && strlen($p) < $params['minlength']){ $msg = "The password is too short (Minimum length: {$params['minlength']})"; + if (isset($params['error'])) $msg = $params['error']; return !$this->setError(true, 200, $msg); } - if (in_array('encrypt', $params, true)){ - $p = silmph_encrypt_key ($p, SILMPH_KEY_SECRET); + $emsg = NULL; + if (in_array('hash', $params, true)){ + if (!class_exists('\intbf\Crypto')){ + $emsg = 'Validator: Password: "hash" requires Crypto class to be loaded.'; + } elseif(!defined('AUTH_PW_PEPPER')){ + $emsg = 'Validator: Password: "hash": global constant AUTH_PW_PEPPER required.'; + } else { + $p = Crypto::hashPassword($p.AUTH_PW_PEPPER); + } + } elseif (in_array('encrypt', $params, true)){ + if (!class_exists('\intbf\Crypto')){ + $emsg = 'Validator: Password: "encrypt" requires Crypto class to be loaded.'; + } else { + $p = Crypto::pad_string($p); + $p = Crypto::encrypt_by_key_pw($p, Crypto::get_key_from_file(SYSBASE.'/secret.php'), CRYPTO_SECRET_KEY); + } + } + if (isset($emsg) && $emsg){ + if (class_exists('\intbf\ErrorHandler')){ + ErrorHandler::_errorTraceLog($emsg); + } else { + error_log($emsg); + } + if (isset($params['error'])) $emsg = $params['error']; + return !$this->setError(true, 200, $emsg); } $this->filtered=$p; return !$this->setError(false); @@ -775,13 +1211,27 @@ public function V_password($value, $params = []) { * * @param $value * @param $params + * empty 1 allow empty value + * maxlength 2 maximum string length + * error 2 replace whole error message on error case * @return boolean */ public function V_path($value, $params = NULL) { $path = trim(strip_tags(''.$value)); + if (in_array('empty', $params, true) && $path === ''){ + $this->filtered = ''; + return !$this->setError(false); + } + if (isset($params['maxlength']) && strlen($path) >= $params['maxlength']){ + $msg = "The path is too long (Maximum length: {$params['maxlength']})"; + if (isset($params['error'])) $msg = $params['error']; + return !$this->setError(true, 200, $msg); + } $re = '/^((\w)+((\.|-)(\w)+)*)(\/(\w)+((\.|-)(\w)+)*)*$/'; - if (!preg_match($re, $path) || strlen($path) >= 128){ - return !$this->setError(true, 200, "path validation failed", 'path validation failed'); + if (!preg_match($re, $path)){ + $msg = "path validation failed"; + if (isset($params['error'])) $msg = $params['error']; + return !$this->setError(true, 200, $msg, 'path validation failed'); } else { $this->filtered=$path; } @@ -793,13 +1243,21 @@ public function V_path($value, $params = NULL) { * * @param $value * @param $params + * empty 1 allow empty value + * error 2 replace whole error message on error case * @return boolean */ public function V_color($value, $params = NULL) { $color = trim(strip_tags(''.$value)); + if (in_array('empty', $params, true) && $color === ''){ + $this->filtered = ''; + return !$this->setError(false); + } $re = '/^([a-fA-F0-9]){6}$/'; if (!preg_match($re, $color) || strlen($color) != 6){ - return !$this->setError(true, 200, "color validation failed", 'color validation failed'); + $msg = "color validation failed"; + if (isset($params['error'])) $msg = $params['error']; + return !$this->setError(true, 200, $msg, 'color validation failed'); } else { $this->filtered=$color; } @@ -943,7 +1401,7 @@ public function V_array($a, $params){ * * $param * map 2 validation map - * reqired 2 boolean, default false + * required 2 boolean, default false * * @param array $a * @param array $params @@ -1027,7 +1485,7 @@ public function V_iban($value, $params = []){ /** * check if string is valid iban, * - * @param $iban iban srstring to check + * @param string $iban iban srstring to check * * @return bool * @see https://en.wikipedia.org/wiki/International_Bank_Account_Number#Validating_the_IBAN @@ -1070,15 +1528,17 @@ public static function _checkIBAN($iban){ } /** - * _bcmod - get modulus (substitute for bcmod) + * bcmod - get modulus (substitute for bcmod) * be careful with big $modulus values * * @param string $left_operand

    The left operand, as a string.

    * @param int $modulus

    The modulus, as a string.

    * * based on - * https://stackoverflow.com/questions/10626277/function-bcmod-is-not-available + * @see https://stackoverflow.com/questions/10626277/function-bcmod-is-not-available * by Andrius Baranauskas and Laurynas Butkus :) Vilnius, Lithuania + * + * @return integer **/ public static function _bcmod($left_operand, $modulus){ if (function_exists('bcmod')){ From f5febced206425cb9523eb70e68b77f2b01ca561 Mon Sep 17 00:00:00 2001 From: cherrg Date: Thu, 17 Jan 2019 19:11:49 +0100 Subject: [PATCH 27/42] bugfixes * tex bulder changed float parse flag * validateMap -> remove array reference --- lib/class.TexBuilder.php | 12 +++++++++--- lib/class.Validator.php | 10 ++++------ 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/lib/class.TexBuilder.php b/lib/class.TexBuilder.php index bc32dac..02d557f 100644 --- a/lib/class.TexBuilder.php +++ b/lib/class.TexBuilder.php @@ -249,7 +249,9 @@ class TexBuilder{ 'zahlung-value' => ['float', 'format' => '2', 'decimal' => '.', - 'parse' => 'money', + 'parse' => [ + 'append' => ' EUR' + ], 'error' => 'Ungültiger Value' ], 'zahlung-adresse' => ['text'], @@ -274,14 +276,18 @@ class TexBuilder{ 'min' => '0', 'format' => '2', 'decimal' => '.', - 'parse' => 'money', + 'parse' => [ + 'append' => ' EUR' + ], 'error' => 'Ungültige Einnahme' ], 'ausgaben' => ['float', 'min' => '0', 'format' => '2', 'decimal' => '.', - 'parse' => 'money', + 'parse' => [ + 'append' => ' EUR' + ], 'error' => 'Ungültige Ausgabe' ], ] diff --git a/lib/class.Validator.php b/lib/class.Validator.php index 05e9978..5b7a8ba 100644 --- a/lib/class.Validator.php +++ b/lib/class.Validator.php @@ -177,7 +177,7 @@ public function validate($value, $validator){ * ['key' => ['multi', ['validator1'], ['validator2'], ...]] * @return boolean */ - public function validateMap(&$source_unsafe, $map, $required = true, $errormap = false, $multi_validator = false){ + public function validateMap($source_unsafe, $map, $required = true, $errormap = false, $multi_validator = false){ $out = []; $errorMsgs = []; $errorDesc = []; @@ -354,7 +354,7 @@ public function V_integer($value, $params = []){ * decimals 2 decimal count, default: 2 * dec_point 2 dec point/seperator, default: ',' * thousands 2 thousands seperator, default: '' - * append 2 append after float value -> return value is no float, string instead, appends X to float value, e.g. ' EUR', or ' $', default: disabled + * append 2 appends XX to float value, e.g. ' EUR', or ' $', default: disabled * error 2 error message on error case * * @param $value @@ -397,16 +397,14 @@ public function V_float($value, $params = []){ } else { $this->filtered = $v; } - if (isset($params['parse']) && is_array($params['parse']){ - - $params['parse'] === 'money'){ + if (isset($params['parse']) && is_array($params['parse'])) { $this->filtered = number_format( $v, (isset($params['parse']['decimals']))? $params['parse']['decimals'] : 2, (isset($params['parse']['dec_point']))? $params['parse']['dec_point'] : ',', (isset($params['parse']['thousands']))? $params['parse']['thousands'] : '' ); - if (isset($params['parse']['append'])){ + if (isset($params['parse']['append'])) { $this->filtered = $this->filtered . $params['parse']['append']; } } From ac4cbeb51afa642fb056de968d06533bc01c82ca Mon Sep 17 00:00:00 2001 From: cherrg Date: Fri, 18 Jan 2019 04:52:05 +0100 Subject: [PATCH 28/42] add stura-member-list (anwesenheitsliste) * validator * tex file --- lib/class.TexBuilder.php | 143 ++++++++++++ template/tex/protocolmemberlist.phpTex | 290 +++++++++++++++++++++++++ 2 files changed, 433 insertions(+) create mode 100644 template/tex/protocolmemberlist.phpTex diff --git a/lib/class.TexBuilder.php b/lib/class.TexBuilder.php index 02d557f..06fac93 100644 --- a/lib/class.TexBuilder.php +++ b/lib/class.TexBuilder.php @@ -294,6 +294,148 @@ class TexBuilder{ ] ] ], + 'protocolmemberlist' => [ + 'date' => ['date', + 'format' => 'Y-m-d', + 'parse' => 'd.m.Y', + 'error' => 'Kein Datum angegeben', + ], + 'leitung' => ['regex', + 'pattern' => '/^[a-zA-Z0-9\-_ :,;%$§\&\+\*\.!\?\/\\\[\]\'"#~()äöüÄÖÜéèêóòôáàâíìîúùûÉÈÊÓÒÔÁÀÂÍÌÎÚÙÛß]*$/', + 'maxlength' => 255, + 'minlength' => 2, + 'empty', + 'error' => 'Ungültige Sitzungsleitung.' + ], + 'protocol' => ['regex', + 'pattern' => '/^[a-zA-Z0-9\-_ :,;%$§\&\+\*\.!\?\/\\\[\]\'"#~()äöüÄÖÜéèêóòôáàâíìîúùûÉÈÊÓÒÔÁÀÂÍÌÎÚÙÛß]*$/', + 'maxlength' => 255, + 'minlength' => 2, + 'empty', + 'error' => 'Ungültige Protokollleitung.' + ], + 'nth' => ['integer', + 'min' => 1, + 'empty', + 'error' => 'Ungültiger NTH Eintrag.' + ], + 'legislatur' => ['integer', + 'min' => 1, + 'error' => 'Ungültige Legislatur.' + ], + 'member_elected' => [ 'array', + 'empty', + 'error' => 'Missing key member_elected', + 'validator' => [ 'arraymap', + 'required' => true, + 'map' => [ + 'name' => [ 'regex', + 'pattern' => '/^[a-zA-Z0-9\-_ :,;%$§\&\+\*\.!\?\/\\\[\]\'"#~()äöüÄÖÜéèêóòôáàâíìîúùûÉÈÊÓÒÔÁÀÂÍÌÎÚÙÛß]*$/', + 'maxlength' => 255, + 'minlength' => 2, + 'error' => 'Ungültiger Name.' + ], + 'job' => [ 'regex', + 'pattern' => '/^[a-zA-Z0-9\-_ :,;%$§\&\+\*\.!\?\/\\\[\]\'"#~()äöüÄÖÜéèêóòôáàâíìîúùûÉÈÊÓÒÔÁÀÂÍÌÎÚÙÛß]*$/', + 'maxlength' => 255, + 'minlength' => 2, + 'empty', + 'error' => 'Ungültiger Job.' + ], + 'text' => [ 'regex', + 'pattern' => '/^[a-zA-Z0-9\-_ :,;%$§\&\+\*\.!\?\/\\\[\]\'"#~()äöüÄÖÜéèêóòôáàâíìîúùûÉÈÊÓÒÔÁÀÂÍÌÎÚÙÛß]*$/', + 'maxlength' => '255', + 'empty', + 'error' => 'Ungültiger text.' + ], + ] + ], + ], + 'member_active' => [ 'array', + 'empty', + 'error' => 'Missing key member_active', + 'validator' => [ 'arraymap', + 'required' => true, + 'map' => [ + 'name' => [ 'regex', + 'pattern' => '/^[a-zA-Z0-9\-_ :,;%$§\&\+\*\.!\?\/\\\[\]\'"#~()äöüÄÖÜéèêóòôáàâíìîúùûÉÈÊÓÒÔÁÀÂÍÌÎÚÙÛß]*$/', + 'maxlength' => 255, + 'minlength' => 2, + 'error' => 'Ungültiger Name.' + ], + 'job' => [ 'regex', + 'pattern' => '/^[a-zA-Z0-9\-_ :,;%$§\&\+\*\.!\?\/\\\[\]\'"#~()äöüÄÖÜéèêóòôáàâíìîúùûÉÈÊÓÒÔÁÀÂÍÌÎÚÙÛß]*$/', + 'maxlength' => 255, + 'minlength' => 2, + 'empty', + 'error' => 'Ungültiger Job.' + ], + 'text' => [ 'regex', + 'pattern' => '/^[a-zA-Z0-9\-_ :,;%$§\&\+\*\.!\?\/\\\[\]\'"#~()äöüÄÖÜéèêóòôáàâíìîúùûÉÈÊÓÒÔÁÀÂÍÌÎÚÙÛß]*$/', + 'maxlength' => '255', + 'empty', + 'error' => 'Ungültiger text.' + ], + ] + ], + ], + 'member_ref' => [ 'array', + 'empty', + 'error' => 'Missing key member_ref', + 'validator' => [ 'arraymap', + 'required' => true, + 'map' => [ + 'name' => [ 'regex', + 'pattern' => '/^[a-zA-Z0-9\-_ :,;%$§\&\+\*\.!\?\/\\\[\]\'"#~()äöüÄÖÜéèêóòôáàâíìîúùûÉÈÊÓÒÔÁÀÂÍÌÎÚÙÛß]*$/', + 'maxlength' => 255, + 'minlength' => 2, + 'error' => 'Ungültiger Name.' + ], + 'job' => [ 'regex', + 'pattern' => '/^[a-zA-Z0-9\-_ :,;%$§\&\+\*\.!\?\/\\\[\]\'"#~()äöüÄÖÜéèêóòôáàâíìîúùûÉÈÊÓÒÔÁÀÂÍÌÎÚÙÛß]*$/', + 'maxlength' => 255, + 'minlength' => 2, + 'empty', + 'error' => 'Ungültiger Job.' + ], + 'text' => [ 'regex', + 'pattern' => '/^[a-zA-Z0-9\-_ :,;%$§\&\+\*\.!\?\/\\\[\]\'"#~()äöüÄÖÜéèêóòôáàâíìîúùûÉÈÊÓÒÔÁÀÂÍÌÎÚÙÛß]*$/', + 'maxlength' => '255', + 'empty', + 'error' => 'Ungültiger text.' + ], + ] + ], + ], + 'member_stuff' => [ 'array', + 'empty', + 'error' => 'Missing key member_stuff', + 'validator' => [ 'arraymap', + 'required' => true, + 'map' => [ + 'name' => ['regex', + 'pattern' => '/^[a-zA-Z0-9\-_ :,;%$§\&\+\*\.!\?\/\\\[\]\'"#~()äöüÄÖÜéèêóòôáàâíìîúùûÉÈÊÓÒÔÁÀÂÍÌÎÚÙÛß]*$/', + 'maxlength' => 255, + 'minlength' => 2, + 'error' => 'Ungültiger Name.' + ], + 'job' => ['regex', + 'pattern' => '/^[a-zA-Z0-9\-_ :,;%$§\&\+\*\.!\?\/\\\[\]\'"#~()äöüÄÖÜéèêóòôáàâíìîúùûÉÈÊÓÒÔÁÀÂÍÌÎÚÙÛß]*$/', + 'maxlength' => 255, + 'minlength' => 2, + 'empty', + 'error' => 'Ungültiger Job.' + ], + 'text' => ['regex', + 'pattern' => '/^[a-zA-Z0-9\-_ :,;%$§\&\+\*\.!\?\/\\\[\]\'"#~()äöüÄÖÜéèêóòôáàâíìîúùûÉÈÊÓÒÔÁÀÂÍÌÎÚÙÛß]*$/', + 'maxlength' => '255', + 'empty', + 'error' => 'Ungültiger text.' + ], + ] + ], + ], + ], ]; /** @@ -472,6 +614,7 @@ public function build(){ private static function _renderTex($key, $param){ $file = SYSBASE . '/template/tex/' . $key . '.phpTex'; $tex = ''; + if (file_exists($file)){ ob_start(); include $file; diff --git a/template/tex/protocolmemberlist.phpTex b/template/tex/protocolmemberlist.phpTex new file mode 100644 index 0000000..fb60734 --- /dev/null +++ b/template/tex/protocolmemberlist.phpTex @@ -0,0 +1,290 @@ +\documentclass[a4paper,11pt]{article} + +\usepackage[T1]{fontenc} +\usepackage[utf8]{inputenc} +\usepackage{graphicx} +\usepackage{xcolor} +\usepackage{colortbl} +\usepackage{longtable} +\usepackage{ngerman} +\usepackage[tx]{sfmath} +\usepackage{calc} +\usepackage{lastpage} +\usepackage{ifthen} +\usepackage{xifthen} +\renewcommand\familydefault{\sfdefault} +\usepackage{tgheros} + +\usepackage{amsmath} +\usepackage{amssymb} +\usepackage{amsthm} +\usepackage{enumitem} +\usepackage{booktabs} +\usepackage{tabularx} + +\usepackage{geometry} +\geometry{total={210mm,297mm}, +left=25mm,right=25mm,% +bindingoffset=0mm, top=20mm,bottom=20mm} + +% pdf version to min 1.6 +\pdfminorversion=6 + + + +\newcommand{\footerstring}{StuRa - Attendance} +\linespread{1.3} + +\newcommand{\linia}{\rule{\linewidth}{0.5pt}} + +\newcommand{\mysection}[1]{ +\begin{center} +{\large \textsc{#1}} +\vspace*{-0.5cm} +\\\linia\\ +\vspace*{-0.5cm} +\end{center} +} + +% custom theorems if needed +\newtheoremstyle{mytheor} +{1ex}{1ex}{\normalfont}{0pt}{\scshape}{.}{1ex} +{{\thmname{#1 }}{\thmnumber{#2}}{\thmnote{ (#3)}}} + +\theoremstyle{mytheor} +\newtheorem{defi}{Definition} + +% my own titles +\makeatletter +\renewcommand{\maketitle}{ +\begin{center} +\vspace*{-0.5cm} +{\huge \textsc{\@title}} +\linia +\end{center} +} +\makeatother +%%% + +% custom footers and headers +\usepackage{fancyhdr} +\pagestyle{fancy} +\lhead{} +\chead{\maketitle} +\rhead{} +\lfoot{\footerstring} +\cfoot{} +\rfoot{Seite \thepage{} von \pageref{LastPage}} +\renewcommand{\headrulewidth}{0pt} +\renewcommand{\footrulewidth}{0pt} + +% +% all section titles centered and bolded +\usepackage{sectsty} +\allsectionsfont{\centering\bfseries\large} +% +% add section label +\renewcommand\thesection{} +% + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +\newcommand{\mytablehead}[1]{ +\bgroup +%\def\arraystretch{0.8} +\setlength\tabcolsep{1mm} +\definecolor{Gray}{gray}{0.25} +\begin{table}[!htbp] +\centering +%\caption{Add caption} +\begin{tabular}{|m{2em}|p{17em}|p{10em}|p{10em}|} +\toprule +\rowcolor{Gray} +\multicolumn{4}{c}{\textcolor{white}{\textbf{#1}}} \\ \toprule +\multicolumn{1}{|c|}{\textbf{\#}} & \multicolumn{1}{c|}{\textbf{Name}} & \multicolumn{1}{c|}{\textbf{Gremium}} & \multicolumn{1}{c|}{\textbf{Unterschrift}} \\ +\midrule + +} +%---------------------------------------------------------- +\newcommand{\myguesttablehead}[1]{ +\bgroup +%\def\arraystretch{0.8} +\setlength\tabcolsep{1mm} +\definecolor{Gray}{gray}{0.25} +\begin{table}[!htbp] +\centering +%\caption{Add caption} +\begin{tabular}{|m{2em}|p{17em}|p{20.48em}|} +\toprule +\rowcolor{Gray} +\multicolumn{3}{c}{\textcolor{white}{\textbf{#1}}} \\ \toprule +\multicolumn{1}{|c|}{\textbf{\#}} & \multicolumn{1}{c|}{\textbf{Name}} & \multicolumn{1}{c|}{\textbf{Gremium$\vert$Verein$\vert$Organisation$\vert$...}} \\ +\midrule + +} +%---------------------------------------------------------- +\newcommand{\mytablefoot}{ +\bottomrule +\end{tabular} +%\label{tab:addlabel}% +\end{table}% +\egroup +} +%---------------------------------------------------------- +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + + +%%%----------%%%----------%%%----------%%%----------%%% +\author{StuRa der TU Ilmenau} + +\begin{document} +% +%% - - - - - - - - - - - - - - - - - - - +% Page Header +%% - - - - - - - - - - - - - - - - - - - +\title{~\\\vspace*{3mm}\hspace*{-14.0cm}\includegraphics[width=2cm]{} \\\vspace*{-14mm} Anwesenheitsliste +\\\normalsize +\textbf{Datum:} format('d.m.y')?> +%\hspace{7mm}-\hspace{7mm}\textbf{Legislatur:} +%\hspace{7mm}-\hspace{7mm}\textbf{Sitzung:} + +} +~\\\vspace*{-13mm}\\ +% +%% - - - - - - - - - - - - - - - - - - - +% Leitung + Protokollkontrolle +%% - - - - - - - - - - - - - - - - - - - +\begin{figure}[!htbp] +\begin{tabularx}{\textwidth}{|X|X|} +\hline +\scriptsize{\textbf{Sitzungsleitung}} & \scriptsize{\textbf{Protokollkontrolle}} \\ +\multicolumn{1}{|c|}{} & \multicolumn{1}{c|}{} \\ \hline +\end{tabularx} +\end{figure} +\\\vspace*{-17mm}\\ +% +%% - - - - - - - - - - - - - - - - - - - +% Data: Sturaete, Angestellte, Referatsleitung, Aktiv +%% - - - - - - - - - - - - - - - - - - - + +% +%% - - - - - - - - - - - - - - - - - - - +% Table: Sturaete, Angestellte, Referatsleitung, Aktiv +%% - - - - - - - - - - - - - - - - - - - + +% +%% - - - - - - - - - - - - - - - - - - - +% Table: Gäste +%% - - - - - - - - - - - - - - - - - - - + $min_guests && $page_line_counter == 0) break; + + if ($page_line_counter == 0 || $guest_count == 1){ + + if ($global_counter != 1) $page_max_lines = $page_max_lines_default; + if ($table_open){ + echo "\n\\mytablefoot\n"; + $table_open = false; + $line_open = false; + } + $table_open = true; + //new page + if ($page_line_counter == 0 && ($global_counter != 1 || $skipfirst_head == true)){ + echo "\\newpage~\\vspace*{-3mm}\\\\"; + } else { + $skipfirst_head = true; + } + //table header + echo "\n\\myguesttablehead{".self::texEscape('Gäste')."}"; $page_line_counter++; $page_line_counter++; + } + + if ($line_open) { + echo " \\midrule"; + } + echo "\n $guest_count & & \\\\ "; + $line_open = true; +} +if ($table_open){ + echo "\n\\mytablefoot\n"; + $table_open = false; +} +$line_open = false; + +?> +\end{document} From c61acd3b0acf704dff2c695576b39f0cc6263123 Mon Sep 17 00:00:00 2001 From: cherrg Date: Fri, 18 Jan 2019 06:28:43 +0100 Subject: [PATCH 29/42] workaround for 'nonbug' bug in php: http_build_query * see https://bugs.php.net/bug.php?id=50407 modyfy max row count in Anwesenheitsliste --- lib/class.TexBuilder.php | 4 ++++ template/tex/protocolmemberlist.phpTex | 12 ++++++++---- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/lib/class.TexBuilder.php b/lib/class.TexBuilder.php index 06fac93..134e8b7 100644 --- a/lib/class.TexBuilder.php +++ b/lib/class.TexBuilder.php @@ -324,6 +324,7 @@ class TexBuilder{ 'error' => 'Ungültige Legislatur.' ], 'member_elected' => [ 'array', + 'optional', 'empty', 'error' => 'Missing key member_elected', 'validator' => [ 'arraymap', @@ -352,6 +353,7 @@ class TexBuilder{ ], ], 'member_active' => [ 'array', + 'optional', 'empty', 'error' => 'Missing key member_active', 'validator' => [ 'arraymap', @@ -380,6 +382,7 @@ class TexBuilder{ ], ], 'member_ref' => [ 'array', + 'optional', 'empty', 'error' => 'Missing key member_ref', 'validator' => [ 'arraymap', @@ -408,6 +411,7 @@ class TexBuilder{ ], ], 'member_stuff' => [ 'array', + 'optional', 'empty', 'error' => 'Missing key member_stuff', 'validator' => [ 'arraymap', diff --git a/template/tex/protocolmemberlist.phpTex b/template/tex/protocolmemberlist.phpTex index fb60734..db1e9da 100644 --- a/template/tex/protocolmemberlist.phpTex +++ b/template/tex/protocolmemberlist.phpTex @@ -170,18 +170,22 @@ bindingoffset=0mm, top=20mm,bottom=20mm} Date: Fri, 18 Jan 2019 20:08:14 +0100 Subject: [PATCH 30/42] modify table style --- template/tex/protocolmemberlist.phpTex | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/template/tex/protocolmemberlist.phpTex b/template/tex/protocolmemberlist.phpTex index db1e9da..8062f4a 100644 --- a/template/tex/protocolmemberlist.phpTex +++ b/template/tex/protocolmemberlist.phpTex @@ -91,7 +91,7 @@ bindingoffset=0mm, top=20mm,bottom=20mm} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \newcommand{\mytablehead}[1]{ \bgroup -%\def\arraystretch{0.8} +\def\arraystretch{1.2} \setlength\tabcolsep{1mm} \definecolor{Gray}{gray}{0.25} \begin{table}[!htbp] @@ -100,15 +100,14 @@ bindingoffset=0mm, top=20mm,bottom=20mm} \begin{tabular}{|m{2em}|p{17em}|p{10em}|p{10em}|} \toprule \rowcolor{Gray} -\multicolumn{4}{c}{\textcolor{white}{\textbf{#1}}} \\ \toprule +\multicolumn{4}{c}{\textcolor{white}{\textbf{#1}}} \\ \specialrule{.4pt}{0pt}{0pt} \multicolumn{1}{|c|}{\textbf{\#}} & \multicolumn{1}{c|}{\textbf{Name}} & \multicolumn{1}{c|}{\textbf{Gremium}} & \multicolumn{1}{c|}{\textbf{Unterschrift}} \\ -\midrule - +\specialrule{.4pt}{0pt}{0pt} } %---------------------------------------------------------- \newcommand{\myguesttablehead}[1]{ \bgroup -%\def\arraystretch{0.8} +\def\arraystretch{1.2} \setlength\tabcolsep{1mm} \definecolor{Gray}{gray}{0.25} \begin{table}[!htbp] @@ -117,14 +116,14 @@ bindingoffset=0mm, top=20mm,bottom=20mm} \begin{tabular}{|m{2em}|p{17em}|p{20.48em}|} \toprule \rowcolor{Gray} -\multicolumn{3}{c}{\textcolor{white}{\textbf{#1}}} \\ \toprule +\multicolumn{3}{c}{\textcolor{white}{\textbf{#1}}} \\ \specialrule{.4pt}{0pt}{0pt} \multicolumn{1}{|c|}{\textbf{\#}} & \multicolumn{1}{c|}{\textbf{Name}} & \multicolumn{1}{c|}{\textbf{Gremium$\vert$Verein$\vert$Organisation$\vert$...}} \\ -\midrule +\specialrule{.4pt}{0pt}{0pt} } %---------------------------------------------------------- \newcommand{\mytablefoot}{ -\bottomrule +\specialrule{.8pt}{0pt}{4pt} \end{tabular} %\label{tab:addlabel}% \end{table}% @@ -231,9 +230,9 @@ foreach($members as $m){ $last_head = $m['head']; } if ($line_open) { - echo " \\midrule"; + echo " \\specialrule{.4pt}{0pt}{0pt}"; } - echo "\n $global_counter & ".self::texEscape($m['name'])." & \\tiny{".self::texEscape($m['job'])."} & ".(($m['text'])?'\multicolumn{1}{c|}{'.self::texEscape($m['text']).'}':'')."\\\\ "; + echo "\n $global_counter & ".self::texEscape($m['name'])." & \\tiny{".self::texEscape($m['job'])."} & ".(($m['text'])?'\multicolumn{1}{p{10em}|}{\centering '.self::texEscape($m['text']).'}':'')."\\\\ "; $line_open = true; } if ($table_open){ @@ -279,7 +278,7 @@ while(true) { } if ($line_open) { - echo " \\midrule"; + echo " \\specialrule{.4pt}{0pt}{0pt}"; } echo "\n $guest_count & & \\\\ "; $line_open = true; From 2a74cc1af1d0a83c3a91d0966b2d28d1f2f193ba Mon Sep 17 00:00:00 2001 From: cherrg Date: Fri, 18 Jan 2019 21:47:57 +0100 Subject: [PATCH 31/42] modify query parameter - zahlungsanweisung -> empty iban --- lib/class.TexBuilder.php | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/class.TexBuilder.php b/lib/class.TexBuilder.php index 134e8b7..e898c50 100644 --- a/lib/class.TexBuilder.php +++ b/lib/class.TexBuilder.php @@ -245,6 +245,7 @@ class TexBuilder{ 'error' => 'Ungültiger Empfänger Name.' ], 'zahlung-iban' => ['iban', + 'empty', ], 'zahlung-value' => ['float', 'format' => '2', From 9ecd5a57f9770ea58b24708bbbf3a54cee8b64b6 Mon Sep 17 00:00:00 2001 From: cherrg Date: Tue, 29 Jan 2019 14:57:05 +0100 Subject: [PATCH 32/42] add inventory list update validator --- lib/class.TexBuilder.php | 64 +++++- lib/class.Validator.php | 20 +- template/tex/inventorylist.phpTex | 317 ++++++++++++++++++++++++++++++ 3 files changed, 393 insertions(+), 8 deletions(-) create mode 100644 template/tex/inventorylist.phpTex diff --git a/lib/class.TexBuilder.php b/lib/class.TexBuilder.php index e898c50..998c948 100644 --- a/lib/class.TexBuilder.php +++ b/lib/class.TexBuilder.php @@ -441,6 +441,61 @@ class TexBuilder{ ], ], ], + 'inventorylist' => [ + 'date' => ['date', + 'format' => 'Y-m-d', + 'parse' => 'd.m.Y', + 'error' => 'Kein Datum angegeben', + ], + 'header' => [ 'array', + 'empty', + 'validator' => [ 'arraymap', + 'required' => true, + 'map' => [ + 'title' => ['regex', + 'empty', + 'pattern' => '/^[a-zA-Z0-9\-_ :,;%$§\&\+\*\.!\?\/\\\[\]\'"#~()äöüÄÖÜéèêóòôáàâíìîúùûÉÈÊÓÒÔÁÀÂÍÌÎÚÙÛß]*$/', + 'maxlength' => 255, + 'minlength' => 0, + 'error' => 'Ungültiger Titel.' + ], + 'date' => ['regex', 'optional', + 'pattern' => '/^([0-9a-zA-Z_ :,\.\-\/])+$/', + 'maxlength' => 255, + 'minlength' => 1, + 'empty', + 'error' => 'Ungültiges Datumformat.' + ], + 'table_format' => ['regex', 'optional', + 'pattern' => '/^([a-zA-Z0-9\-_ :,;%$§\&\+\*\.!\?\/\\\[\]\'"#~()\{\}])*$/', + 'maxlength' => '255', + 'empty', + 'error' => 'Ungültiges Tabellen format.' + ], + 'center' => [ 'integer', 'optional', + 'min' => 1, + 'max' => 1, + 'error' => 'Ungültiges flag "center"', + ], + ], + ], + 'key' => ['regex', + 'pattern' => '/^[a-zA-Z0-9\-_ :,;%$§\&\+\*\.!\?\/\\\[\]\'"#~()äöüÄÖÜéèêóòôáàâíìîúùûÉÈÊÓÒÔÁÀÂÍÌÎÚÙÛß]*$/', + 'maxlength' => 255, + 'minlength' => 1, + 'error' => 'Ungültiger headerkey Eintrag.' + ] + ], + 'items' => [ 'array', + 'pre_json_decode', + 'empty', + 'false', + 'error' => 'Ungültiger items Eintrag.' + ], + 'modifier' => ['array', 'optional', + 'empty', + ], + ], ]; /** @@ -489,8 +544,8 @@ function __construct(){ */ public static function texEscape($in){ return str_replace( - ['\\', '~', '_', '%', '$', '&', '^', '"', '{', '}'], - ['\textbackslash', '\textasciitilde', '\_', '\%', '\$', '\&', '^', "''", '\{', '\}'], + ['\\', '~', '_', '%', '$', '&', '^', '"', '{', '}', '#', '€'], + ['\textbackslash', '\textasciitilde', '\_', '\%', '\$', '\&', '^', "''", '\{', '\}', '\#', '\EUR'], $in ); } @@ -615,6 +670,7 @@ public function build(){ * * @param string $key * @param array $param + * @return string */ private static function _renderTex($key, $param){ $file = SYSBASE . '/template/tex/' . $key . '.phpTex'; @@ -691,7 +747,7 @@ private function _createPDF($tex_code){ * get pdf as base64 string * * @param bool $echo - * return string + * @return string */ public function getBase64($echo = false){ if ($this->binary_build){ @@ -706,7 +762,7 @@ public function getBase64($echo = false){ * get binary pdf data * * @param bool $echo - * return binary|NULL + * @return binary|NULL */ public function getBinary($echo = false){ if ($this->binary_build){ diff --git a/lib/class.Validator.php b/lib/class.Validator.php index 5b7a8ba..6dbd404 100644 --- a/lib/class.Validator.php +++ b/lib/class.Validator.php @@ -1078,6 +1078,10 @@ public function V_domain($value, $params = NULL){ * @return boolean */ public function V_regex($value, $params = ['pattern' => '/.*/']) { + if (!is_numeric($value)&&!is_string($value)&&!is_bool($value)&&$value!=''){ + $msg = "Invalid 'value' type"; + return !$this->setError(true, 200, (isset($params['error']))? $params['error']: $msg, $msg); + } $v = ''.$value; if (in_array('specialchars', $params, true)){ $v = htmlspecialchars($v); @@ -1309,7 +1313,7 @@ public function V_time($value, $params = NULL) { $msg = (isset($params['error']))? $params['error'] : 'time validation failed, format: "'.$fmt.'"'; return !$this->setError(true, 200, $msg, 'time validation failed, format: "'.$fmt.'"'); } else { - $d = DateTime::createFromFormat($fmt, $time); + $d = \DateTime::createFromFormat($fmt, $time); if($d && $d->format($fmt) == $time){ $this->filtered = $d->format((isset($params['parse']))?$params['parse']:$fmt); return !$this->setError(false); @@ -1333,12 +1337,16 @@ public function V_time($value, $params = NULL) { * false 1 allow false -> reset to empty array * validator 2 run this validator on each array element * error 2 overwrite error message + * pre_json_decode 1 ron json decode on string -> workaround for limited php setting 'max_input_vars' * * @param array $a * @param array $params * @return boolean */ public function V_array($a, $params){ + if (in_array('pre_json_decode', $params, true)){ + $a = json_decode( $a, true); + } if (!is_array($a)){ if ($a === '0' && in_array('false', $params, true)){ $a = []; @@ -1398,14 +1406,18 @@ public function V_array($a, $params){ * run validator on array and given map * * $param - * map 2 validation map - * required 2 boolean, default false + * map 2 validation map + * required 2 boolean, default false + * pre_json_decode 1 ron json decode on string -> workaround for limited php setting 'max_input_vars' * * @param array $a * @param array $params * @return boolean */ public function V_arraymap($a, $params){ + if (in_array('pre_json_decode', $params, true)){ + $a = json_decode( $a, true); + } if (!isset($params['map'])){ return !$this->setError(true, 200, 'invalid configuration on arraymap validation', 'arraymap validator failed: wrong configuration: missing parameter map'); } @@ -1438,7 +1450,7 @@ public function V_date($value, $params = NULL) { return !$this->setError(false); } $fmt = (isset($params['format']))? $params['format'] : 'Y-m-d'; - $d = DateTime::createFromFormat($fmt, $date); + $d = \DateTime::createFromFormat($fmt, $date); if($d && $d->format($fmt) == $date){ $this->filtered = $d->format((isset($params['parse']))?$params['parse']:$fmt); } else { diff --git a/template/tex/inventorylist.phpTex b/template/tex/inventorylist.phpTex new file mode 100644 index 0000000..0edcdd5 --- /dev/null +++ b/template/tex/inventorylist.phpTex @@ -0,0 +1,317 @@ +\documentclass[a4paper,11pt,twoside]{article} + +\usepackage[T1]{fontenc} +\usepackage[utf8]{inputenc} +\usepackage{graphicx} +\usepackage{xcolor} +\usepackage{colortbl} +\usepackage{longtable} +\usepackage{ngerman} +\usepackage[tx]{sfmath} +\usepackage{calc} +\usepackage{lastpage} +\usepackage{ifthen} +\usepackage{xifthen} +\renewcommand\familydefault{\sfdefault} +\usepackage{tgheros} + +\usepackage{amsmath} +\usepackage{amssymb} +\usepackage{amsthm} +\usepackage{enumitem} +\usepackage{booktabs} +\usepackage{tabularx} +\usepackage{ marvosym } + +\usepackage[total={210mm,297mm},inner=20mm,outer=10mm,bindingoffset=0mm,top=20mm,bottom=20mm]{geometry} +%\usepackage{geometry} +%\geometry{total={210mm,297mm}, +%left=20mm,right=20mm,% +%bindingoffset=0mm, top=20mm,bottom=20mm} + +% pdf version to min 1.6 +\pdfminorversion=6 + + + +\newcommand{\footerstring}{StuRa - Inventar} +\linespread{1.3} + +\newcommand{\linia}{\rule{\linewidth}{0.5pt}} + +\newcommand{\mysection}[1]{ +\begin{center} +{\large \textsc{#1}} +\vspace*{-0.5cm} +\\\linia\\ +\vspace*{-0.5cm} +\end{center} +} + +% custom theorems if needed +\newtheoremstyle{mytheor} +{1ex}{1ex}{\normalfont}{0pt}{\scshape}{.}{1ex} +{{\thmname{#1 }}{\thmnumber{#2}}{\thmnote{ (#3)}}} + +\theoremstyle{mytheor} +\newtheorem{defi}{Definition} + +% my own titles +\makeatletter +\renewcommand{\maketitle}{ +\begin{center} +\vspace*{-0.5cm} +{\huge \textsc{\@title}} +\linia +\end{center} +} +\makeatother +%%% + +% custom footers and headers +\usepackage{fancyhdr} +\pagestyle{fancy} +\lhead{} +\chead{\maketitle} +\rhead{} +\lfoot{\footerstring} +\cfoot{} +\rfoot{Seite \thepage{} von \pageref{LastPage}} +\renewcommand{\headrulewidth}{0pt} +\renewcommand{\footrulewidth}{0pt} + +% +% all section titles centered and bolded +\usepackage{sectsty} +\allsectionsfont{\centering\bfseries\large} +% +% add section label +\renewcommand\thesection{} +% + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%---------------------------------------------------------- +\newcommand{\specialcell}[2][c]{% +\begin{tabular}[#1]{@{}c@{}}#2\end{tabular}} +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + + +%%%----------%%%----------%%%----------%%%----------%%% +\author{StuRa der TU Ilmenau} +\renewcommand{\baselinestretch}{1.0} +\begin{document} +% +%% - - - - - - - - - - - - - - - - - - - +% Page Header +%% - - - - - - - - - - - - - - - - - - - +\title{~\\\vspace*{3mm}\hspace*{-14.0cm}\includegraphics[width=2cm]{} \\\vspace*{-14mm} Inventar +\\\normalsize +% +%% - - - - - - - - - - - - - - - - - - - +% MODIFIER +%% - - - - - - - - - - - - - - - - - - - +\textbf{Stand:} format('d.m.Y')?> + $v) { + echo PHP_EOL . '\hspace{7mm}-\hspace{7mm}\textbf{' . self::texEscape($k) . (($v)?':} '. self::texEscape($v):'}'); + } + } + } +?> + +} +~\\\vspace*{-11mm}\\ +% +%% - - - - - - - - - - - - - - - - - - - +% TABLE Header +%% - - - - - - - - - - - - - - - - - - - +\definecolor{Gray}{gray}{0.25} +\bgroup +\def\baselinestretch{1.0} +\def\arraystretch{1.4} +\setlength\tabcolsep{1mm} +\begin{longtable}[!htbp]{ $head){ + $tf = ''; + if (isset($head['table_format']) && $head['table_format']){ + $tf = $head['table_format']; + } else { + $tf = 'l'; + } + echo $tf; + $param['header'][$k]['table_format'] = $tf; + } +?>} +\toprule +\rowcolor{Gray} + $head) { + if ($count) echo ' & '; + echo ((isset($head['center']))?'\multicolumn{1}{'.$head['table_format'].'}{\centering ':''); + echo '{\fontsize{10pt}{10pt}\selectfont '; + echo '\textcolor{white}{\textbf{'.str_replace('\textbackslashn', '\\newline ', TexBuilder::texEscape($head['title'])).'}}'; + echo '}'; + echo ((isset($head['center']))?'}':''); + $count++; + } + echo ' \\\\ \toprule'; +?> +\hline +\endfirsthead +\\ +\toprule +\rowcolor{Gray} + $head) { + if ($count) echo ' & '; + echo ((isset($head['center']))?'\multicolumn{1}{'.$head['table_format'].'}{\centering ':''); + echo '{\fontsize{10pt}{10pt}\selectfont '; + echo '\textcolor{white}{\textbf{'.str_replace('\textbackslashn', '\\newline ', TexBuilder::texEscape($head['title'])).'}}'; + echo '}'; + echo ((isset($head['center']))?'}':''); + $count++; +} +echo ' \\\\ \toprule'; +?> +\hline +\endhead +\endfoot +% +%% - - - - - - - - - - - - - - - - - - - +% TABLE footer +%% - - - - - - - - - - - - - - - - - - - + $i) { + $foot_count++; + echo PHP_EOL; + //echo PHP_EOL.'\multicolumn{'.count($param['header']).'}{c}{\centering{\fontsize{7pt}{7pt}\selectfont '; + switch ($m) { + case 'non_abgang': { + echo '\qquad\textbf{'.TexBuilder::texEscape($m).'} - Es werden keine deinventarisierten Posten angezeigt.\qquad'; + break; + } + case 'nur_abgang': { + echo '\qquad\textbf{'.TexBuilder::texEscape($m).'} - Es werden nur deinventarisierte Posten angezeigt.\qquad'; + break; + } + case 'state': { + echo '\qquad\textbf{'.TexBuilder::texEscape($m).'} - Beschränke Liste auf Posten mit diesem Status.\qquad'; + break; + } + case 'category': { + echo '\qquad\textbf{'.TexBuilder::texEscape($m).'} - Beschränke Liste auf Posten in dieser Kategorie.\qquad'; + break; + } + case 'place': { + echo '\qquad\textbf{'.TexBuilder::texEscape($m).'} - Beschränke Liste auf Posten an diesem Ort.\qquad\qquad\qquad'; + break; + } + case 'wert_ab': { + echo '\qquad\textbf{'.TexBuilder::texEscape($m).'} - Zeige nur Einträge ab einem Wert von X.\qquad\qquad'; + break; + } + case 'non_verbrauch': { + echo '\qquad\textbf{'.TexBuilder::texEscape($m).'} - Zeige keine eingetragenen Verbrauchsmaterialien.\qquad'; + break; + } + case 'verleihbar': { + echo '\qquad\textbf{'.TexBuilder::texEscape($m).'} - Zeige nur Verleihbare Artikel.\qquad'; + break; + } + default: + echo '\qquad\textbf{'.TexBuilder::texEscape($m).'}'; + break; + } + if ($foot_count%2 == 0){ + echo '\\\\'; + } + } + ?> + + +\endlastfoot +% +%% - - - - - - - - - - - - - - - - - - - +% Contend +%% - - - - - - - - - - - - - - - - - - - + $item) { + $count = 0; + echo PHP_EOL; + foreach ($param['header'] as $k => $head) { + if ($count) echo ' & '; + if (isset($item[$k]) && $item[$k] && !($k=='v' && $item[$k]=='0.00')){ + $val = $item[$k]; + if (isset($head['date'])){ + $val = date_create($val)->format($head['date']); + } + echo PHP_EOL; + echo ((isset($head['center']))?'\multicolumn{1}{'.$head['table_format'].'}{\centering ':''); + echo '{\fontsize{10pt}{10pt}\selectfont '; + if ($k=='v') { + echo ($item['ans'])?'ca. ':''; + } + echo TexBuilder::texEscape($val); + if ($k=='v') { + echo ($item['dm'])?' DM':TexBuilder::texEscape(' €'); + } + if ($k=='c' && isset($item['cc'])) { + echo '/'.TexBuilder::texEscape($item['cc']); + } + echo '}'; + echo ((isset($head['center']))?'}':''); + + } + $count++; + } + echo ' \\\\ \hline'; + } +?> +\end{longtable} +\egroup + +% +%% - - - - - - - - - - - - - - - - - - - +% DOCUMENT END +%% - - - - - - - - - - - - - - - - - - - + +\end{document} From e287541047de901eaa18cb68bdd640c46fc9ac36 Mon Sep 17 00:00:00 2001 From: cherrg Date: Wed, 6 Feb 2019 18:51:02 +0100 Subject: [PATCH 33/42] add Inventory -> Posten Details pdf --- lib/class.TexBuilder.php | 47 +++++ template/tex/inventorydetail.phpTex | 258 ++++++++++++++++++++++++++++ 2 files changed, 305 insertions(+) create mode 100644 template/tex/inventorydetail.phpTex diff --git a/lib/class.TexBuilder.php b/lib/class.TexBuilder.php index 998c948..81f37f9 100644 --- a/lib/class.TexBuilder.php +++ b/lib/class.TexBuilder.php @@ -496,6 +496,53 @@ class TexBuilder{ 'empty', ], ], + 'inventorydetail' => [ + 'date' => ['date', + 'format' => 'Y-m-d', + 'parse' => 'd.m.Y', + 'error' => 'Kein Datum angegeben', + ], + 'filedata' => [ 'array', 'optional', + 'empty', + 'validator' => [ 'arraymap', + 'required' => true, + 'map' => [ + 'data' => ['text', + 'strip', + 'trim', + ], + ], + ], + ], + 'data' => [ 'array', + 'empty', + 'required' => true, + ], + 'log' => [ 'array', 'optional', + 'empty', + 'validator' => [ 'arraymap', + 'required' => true, + 'map' => [ + 'id' => [ 'integer', + 'min' => '1', + 'error' => 'id' ], + 'date' => [ 'date', + 'format' => 'Y-m-d H:i:s', + 'error' => 'date' + ], + 'wer' => ['regex', + 'pattern' => '/^((^| )([.a-zA-ZäöüÄÖÜéèêóòôáàâíìîúùûÉÈÊÓÒÔÁÀÂÍÌÎÚÙÛß0-9#!?\-\.,:;_+\*\(\)\[\]\|\/\\\\]+))+$/', + 'empty', + 'maxlength' => 255, + 'error' => 'Invalid name' ], + 'was' => ['regex', + 'pattern' => '/( |\n|.)*/', + 'error' => 'Invalid note - Invalid letters?' + ], + ], + ], + ], + ], ]; /** diff --git a/template/tex/inventorydetail.phpTex b/template/tex/inventorydetail.phpTex new file mode 100644 index 0000000..7e23571 --- /dev/null +++ b/template/tex/inventorydetail.phpTex @@ -0,0 +1,258 @@ +\documentclass[a4paper,11pt,twoside]{article} + +\usepackage[T1]{fontenc} +\usepackage[utf8]{inputenc} +\usepackage{graphicx} +\usepackage{xcolor} +\usepackage{colortbl} +\usepackage{longtable} +\usepackage{ngerman} +\usepackage[tx]{sfmath} +\usepackage{calc} +\usepackage{lastpage} +\usepackage{ifthen} +\usepackage{xifthen} +\renewcommand\familydefault{\sfdefault} +\usepackage{tgheros} + +\usepackage{amsmath} +\usepackage{amssymb} +\usepackage{amsthm} +\usepackage{enumitem} +\usepackage{booktabs} +\usepackage{tabularx} +\usepackage{ marvosym } + +\usepackage[total={210mm,297mm},inner=20mm,outer=10mm,bindingoffset=0mm,top=20mm,bottom=20mm]{geometry} +%\usepackage{geometry} +%\geometry{total={210mm,297mm}, +%left=20mm,right=20mm,% +%bindingoffset=0mm, top=20mm,bottom=20mm} + +% pdf version to min 1.6 +\pdfminorversion=6 + + + +\newcommand{\footerstring}{StuRa - Inventar} +\linespread{1.3} + +\newcommand{\linia}{\rule{\linewidth}{0.5pt}} + +\newcommand{\mysection}[1]{ +\begin{center} +{\large \textsc{#1}} +\vspace*{-0.5cm} +\\\linia\\ +\vspace*{-0.5cm} +\end{center} +} + +% custom theorems if needed +\newtheoremstyle{mytheor} +{1ex}{1ex}{\normalfont}{0pt}{\scshape}{.}{1ex} +{{\thmname{#1 }}{\thmnumber{#2}}{\thmnote{ (#3)}}} + +\theoremstyle{mytheor} +\newtheorem{defi}{Definition} + +% my own titles +\makeatletter +\renewcommand{\maketitle}{ +\begin{center} +\vspace*{-0.5cm} +{\huge \textsc{\@title}} +\linia +\end{center} +} +\makeatother +%%% + +% custom footers and headers +\usepackage{fancyhdr} +\pagestyle{fancy} +\lhead{} +\chead{\maketitle} +\rhead{} +\lfoot{\footerstring} +\cfoot{} +\rfoot{Seite \thepage{} von \pageref{LastPage}} +\renewcommand{\headrulewidth}{0pt} +\renewcommand{\footrulewidth}{0pt} + +% +% all section titles centered and bolded +\usepackage{sectsty} +\allsectionsfont{\centering\bfseries\large} +% +% add section label +\renewcommand\thesection{} +% + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%---------------------------------------------------------- +\newcommand{\specialcell}[2][c]{% +\begin{tabular}[#1]{@{}c@{}}#2\end{tabular}} +\newcommand{\specialleftcell}[2][l]{% +\begin{tabular}[#1]{@{}l@{}}#2\end{tabular}} +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + + +%%%----------%%%----------%%%----------%%%----------%%% +\author{StuRa der TU Ilmenau} +\renewcommand{\baselinestretch}{1.0} +\begin{document} +% +%% - - - - - - - - - - - - - - - - - - - +% Page Header +%% - - - - - - - - - - - - - - - - - - - +\title{~\\\vspace*{3mm}\hspace*{-14.0cm}\includegraphics[width=2cm]{} \\\vspace*{-14mm} Inventar - Details +\\\normalsize +% +%% - - - - - - - - - - - - - - - - - - - +% MODIFIER +%% - - - - - - - - - - - - - - - - - - - +\textbf{Gedruckt:} format('d.m.Y')?> +} +~\\\vspace*{-11mm}\\ +% +%% - - - - - - - - - - - - - - - - - - - +% TABLE Header +%% - - - - - - - - - - - - - - - - - - - + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% +% ITEM +%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +\bgroup +\def\arraystretch{1.5} +\setlength\tabcolsep{1mm} +\definecolor{Gray}{gray}{0.25} +\begin{table}[!htbp] +\centering +%\caption{Add caption} +\begin{tabular}{p{9.7em}p{12.7em}p{7.2em}p{15.0em}} +\toprule +\rowcolor{Gray} +\multicolumn{4}{c}{\textcolor{white}{\textbf{}}} \\ \specialrule{.4pt}{0pt}{0pt} + +\multicolumn{4}{p{17em}}{ + +\vspace{-1mm}\hspace*{1cm}\begin{minipage}[m][6cm][c]{10cm} + 0) { + echo '\includegraphics[width=6cm,height=5cm,keepaspectratio]{'.explode('/', $param['filedata'][0]['file'])[2].'}'; + } +?> +\end{minipage} + + + +} \\ \specialrule{.4pt}{0pt}{0pt} + +{\textbf{ ID }} & & +{\textbf{ Status }} & \\ \specialrule{.4pt}{0pt}{0pt} + +{\textbf{ Clone }} & & &\\ \specialrule{.4pt}{0pt}{0pt} + +{\textbf{ Verbrauchsmaterial }} & & &\\ \specialrule{.4pt}{0pt}{0pt} + +{\textbf{ Verleihbar }} & & &\\ \specialrule{.4pt}{0pt}{0pt} + +{\textbf{ Standort }} & & +{\textbf{ Kategorie }} & \\ \specialrule{.4pt}{0pt}{0pt} + +{\textbf{ Wert }} & & +{\textbf{ Anschaffung }} & format((($param['data']['ans'])? 'm.Y' : 'd.m.Y')); ?> \\ \specialrule{.4pt}{0pt}{0pt} + + + + {\textbf{ Ausgetragen }} & format('d.m.Y'); ?> & + {\textbf{ Von }} & \\ \specialrule{.4pt}{0pt}{0pt} + + + +{\textbf{ Menge Kauf }} & & +{\textbf{ Menge Aktuell }} & \\ \specialrule{.4pt}{0pt}{0pt} + +{\textbf{ (Letzte Inventur) }} & format('d.m.Y'); ?> & +{\textbf{ (Erstellt) }} & format('d.m.Y'); ?> \\ \specialrule{.4pt}{0pt}{0pt} + +{\textbf{ Tags }} & \multicolumn{3}{l}{} \\ \specialrule{.4pt}{0pt}{0pt} + +{\textbf{ Beschreibung }} & \multicolumn{3}{l}{\small \def\arraystretch{1.1} {\hspace{-1.8mm} \specialleftcell{ + $s) { + echo TexBuilder::texEscape($s); + if ($k < (count($split) - 1)) echo ' \\\\'; + } + ?>}}} \\ \specialrule{.4pt}{0pt}{0pt} +{\textbf{ Bemerkung}} & \multicolumn{3}{l}{\small \def\arraystretch{1.1} {\hspace{-1.8mm} \specialleftcell{ + $s) { + echo TexBuilder::texEscape($s); + if ($k < (count($split) - 1)) echo ' \\\\'; + } + ?>}}} \\ \specialrule{.4pt}{0pt}{0pt} + + + + & + {\textbf{Status}} & \\ \specialrule{.4pt}{0pt}{0pt} +{\textbf{Bemerkung}} & \multicolumn{3}{l}{} \\ \specialrule{.4pt}{0pt}{0pt} +*/ ?> + +\specialrule{.8pt}{0pt}{4pt} +\end{tabular} +%\label{tab:addlabel}% +\end{table}% +\egroup + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% +% LOG +%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +\bgroup +\def\arraystretch{1.3} +\setlength\tabcolsep{1mm} +\definecolor{Gray}{gray}{0.25} +\begin{table}[!htbp] +\centering +%\caption{Add caption} +\begin{tabular}{p{2.7em}p{4.7em}p{7.2em}p{29.6em}} +\toprule +\rowcolor{Gray} +\multicolumn{4}{c}{\textcolor{white}{\textbf{}}} \\ \specialrule{.4pt}{0pt}{0pt} + + 0){ + foreach ($param['log'] as $l) { + echo "\n ".TexBuilder::texEscape($l['id']).' & \scriptsize{\specialcell{'.date_create($l['date'])->format('d.m.Y').'\\\\'.date_create($l['date'])->format('H:i').'}} & '.TexBuilder::texEscape($l['wer']).' & '.TexBuilder::texEscape($l['was']); + echo ' \\\\ \specialrule{.4pt}{0pt}{0pt}'; + } + } else { + echo '\multicolumn{4}{c}{Keine Einträge vorhanden}'; + } +?> + +\specialrule{.8pt}{0pt}{4pt} +\end{tabular} +%\label{tab:addlabel}% +\end{table}% +\egroup + + +% +%% - - - - - - - - - - - - - - - - - - - +% DOCUMENT END +%% - - - - - - - - - - - - - - - - - - - + +\end{document} From 7ba8873f6e8d828a47412c3c0ecb77f1c0061887 Mon Sep 17 00:00:00 2001 From: cherrg Date: Wed, 6 Feb 2019 18:52:54 +0100 Subject: [PATCH 34/42] add image general image for tex files --- lib/class.TexBuilder.php | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/lib/class.TexBuilder.php b/lib/class.TexBuilder.php index 81f37f9..ffc91c0 100644 --- a/lib/class.TexBuilder.php +++ b/lib/class.TexBuilder.php @@ -695,8 +695,28 @@ public function build(){ $files[str_pad($b['id'], 3, "0", STR_PAD_LEFT) . '-B' . $b['short']] = $f . '.pdf'; } } + $validated = $this->validator->getFiltered(); + if (isset($validated['filedata'])){ + foreach ($validated['filedata'] as $k => $fs){ + if (($f = tempnam(sys_get_temp_dir(), 'tex-tmp-')) === false){ + $this->error = "Failed to create temporary file"; + foreach ($files as $v){ + if (!DEBUG_DO_NOT_DELETE__TEX_PDF && file_exists($v)) + unlink($v); + } + return false; + } + //remove empty file again + if (file_exists($f)) unlink($f); + //set file with actual content + $f.=(isset($fs['type']) && $fs['type'])? '.'.$fs['type'] : '.png'; + file_put_contents($f , base64_decode($fs['data'])); + $files[$k] = $f; + $validated['filedata'][$k]['file'] = $f; + unset($validated['filedata'][$k]['data']); + } + } - $validated = $this->validator->getFiltered(); $validated['files'] = $files; //get tex code $tex = self::_renderTex($this->last_key, $validated); From 0b9b8e893fb3c68506f7e4275ab2b4e30c26a250 Mon Sep 17 00:00:00 2001 From: cherrg Date: Fri, 8 Feb 2019 01:52:19 +0100 Subject: [PATCH 35/42] bugfix for empty log --- template/tex/inventorydetail.phpTex | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/template/tex/inventorydetail.phpTex b/template/tex/inventorydetail.phpTex index 7e23571..7558271 100644 --- a/template/tex/inventorydetail.phpTex +++ b/template/tex/inventorydetail.phpTex @@ -119,14 +119,13 @@ ~\\\vspace*{-11mm}\\ % %% - - - - - - - - - - - - - - - - - - - -% TABLE Header +% ITEM INFO %% - - - - - - - - - - - - - - - - - - - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% -% ITEM -%% -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% +%% - - - - - - - - - - - - - - - - - - - +% TABLE Header +%% - - - - - - - - - - - - - - - - - - - \bgroup \def\arraystretch{1.5} @@ -209,17 +208,21 @@ {\textbf{Bemerkung}} & \multicolumn{3}{l}{} \\ \specialrule{.4pt}{0pt}{0pt} */ ?> +% +%% - - - - - - - - - - - - - - - - - - - +% TABLE Footer +%% - - - - - - - - - - - - - - - - - - - + \specialrule{.8pt}{0pt}{4pt} \end{tabular} %\label{tab:addlabel}% \end{table}% \egroup -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% +% +%% - - - - - - - - - - - - - - - - - - - % LOG -%% -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% - - - - - - - - - - - - - - - - - - - \bgroup \def\arraystretch{1.3} \setlength\tabcolsep{1mm} @@ -239,7 +242,7 @@ echo ' \\\\ \specialrule{.4pt}{0pt}{0pt}'; } } else { - echo '\multicolumn{4}{c}{Keine Einträge vorhanden}'; + echo '\multicolumn{4}{p{177.6mm}}{Keine Einträge vorhanden}\\\\'; } ?> From 5d6704d68c91e0302c683050b46ee5dc510dd954 Mon Sep 17 00:00:00 2001 From: Cherrg Date: Thu, 30 May 2019 02:35:44 +0200 Subject: [PATCH 36/42] Update class.TexBuilder.php formatting and local changes 1 --- lib/class.TexBuilder.php | 350 +++++++++++++++++++++++++++------------ 1 file changed, 243 insertions(+), 107 deletions(-) diff --git a/lib/class.TexBuilder.php b/lib/class.TexBuilder.php index ffc91c0..75c2e3f 100644 --- a/lib/class.TexBuilder.php +++ b/lib/class.TexBuilder.php @@ -13,58 +13,70 @@ class TexBuilder{ */ private static $valiMap = [ 'belegpdf' => [ - 'projekt' => ['arraymap', + 'projekt' => [ + 'arraymap', 'required' => true, 'map' => [ - 'created' => ['date', + 'created' => [ + 'date', 'format' => 'Y-m-d H:i:s', 'parse' => 'Y-m-d H:i:s', 'error' => 'Ungültiges Projekt Datum.' ], - 'name' => ['regex', + 'name' => [ + 'regex', 'pattern' => '/^[a-zA-Z0-9\-_ :,;%$§\&\+\*\.!\?\/\\\[\]\'"#~()äöüÄÖÜéèêóòôáàâíìîúùûÉÈÊÓÒÔÁÀÂÍÌÎÚÙÛß]*$/', 'maxlength' => '255', 'empty', 'error' => 'Ungültiger Projekt Name.' ], - 'org' => ['regex', + 'org' => [ + 'regex', 'pattern' => '/^[a-zA-Z0-9\-_ :,;%$§\&\+\*\.!\?\/\\\[\]\'"#~()äöüÄÖÜéèêóòôáàâíìîúùûÉÈÊÓÒÔÁÀÂÍÌÎÚÙÛß]*$/', 'maxlength' => '255', 'empty', 'error' => 'Ungültiger Organisations Name.' ], - 'id' => ['integer', + 'id' => [ + 'integer', 'min' => '1', 'error' => 'Ungültige Projekt ID.' ], ], ], - 'auslage' => ['arraymap', + 'auslage' => [ + 'arraymap', 'required' => true, 'map' => [ - 'created' => ['date', + 'created' => [ + 'date', 'format' => 'Y-m-d H:i:s', 'parse' => 'Y-m-d H:i:s', 'error' => 'Ungültiges Auslagen Datum.' ], - 'created_by' => ['name', + 'created_by' => [ + 'name', 'maxlength' => '255', 'error' => 'Ungültiger Auslagen Name.' ], - 'name' => ['regex', + 'name' => [ + 'regex', 'empty', 'pattern' => '/^[a-zA-Z0-9\-_ :,;%$§\&\+\*\.!\?\/\\\[\]\'"#~()äöüÄÖÜéèêóòôáàâíìîúùûÉÈÊÓÒÔÁÀÂÍÌÎÚÙÛß]*$/', 'maxlength' => '255', 'error' => 'Ungültiger Auslagen Name.' ], - 'id' => ['integer', + 'id' => [ + 'integer', 'min' => '1', 'error' => 'Ungültige Auslagen ID.' ], - 'zahlung' => ['arraymap', + 'zahlung' => [ + 'arraymap', 'required' => true, 'map' => [ - 'name' => ['regex', + 'name' => [ + 'regex', 'pattern' => '/^[a-zA-Z0-9\-_ :,;%$§\&\+\*\.!\?\/\\\[\]\'"#~()äöüÄÖÜéèêóòôáàâíìîúùûÉÈÊÓÒÔÁÀÂÍÌÎÚÙÛß]*$/', 'maxlength' => '127', 'empty', @@ -72,7 +84,8 @@ class TexBuilder{ ], ], ], - 'address' => ['regex', + 'address' => [ + 'regex', 'pattern' => '/^[a-zA-Z0-9\-_,:;\/\\\\()& \n\r\.\[\]%\'"#\*\+äöüÄÖÜéèêóòôáàâíìîúùûÉÈÊÓÒÔÁÀÂÍÌÎÚÙÛß]*$/', 'empty', 'maxlength' => '1023', @@ -80,36 +93,46 @@ class TexBuilder{ ], ], ], - 'belege' => ['array', 'optional', + 'belege' => [ + 'array', + 'optional', 'minlength' => 1, - 'key' => ['regex', + 'key' => [ + 'regex', 'pattern' => '/^(\d+)$/' ], - 'validator' => ['arraymap', + 'validator' => [ + 'arraymap', 'required' => true, 'map' => [ - 'id' => ['integer', + 'id' => [ + 'integer', 'min' => '1', 'error' => 'Ungültige Beleg ID.' ], - 'short' => ['integer', + 'short' => [ + 'integer', 'min' => '1', 'error' => 'Ungültige Beleg NR.' ], - 'date' => ['date', + 'date' => [ + 'date', 'format' => 'Y-m-d H:i:s', 'parse' => 'Y-m-d', 'error' => 'Ungültiges Beleg Datum.' ], - 'desc' => ['text', + 'desc' => [ + 'text', 'strip', 'trim', ], - 'file_id' => ['integer', + 'file_id' => [ + 'integer', 'min' => '1', 'error' => 'Ungültige Beleg File ID.' ], - 'file' => ['text', + 'file' => [ + 'text', 'strip', 'trim', ], @@ -118,98 +141,120 @@ class TexBuilder{ ], ], 'gremienbescheinigung' => [ - 'vorname' => ['regex', + 'vorname' => [ + 'regex', 'pattern' => '/^[a-zA-Z0-9\-_ :,;%$§\&\+\*\.!\?\/\\\[\]\'"#~()äöüÄÖÜéèêóòôáàâíìîúùûÉÈÊÓÒÔÁÀÂÍÌÎÚÙÛß]*$/', 'maxlength' => '255', 'empty', 'error' => 'Ungültiger Vorname.' ], - 'name' => ['regex', + 'name' => [ + 'regex', 'pattern' => '/^[a-zA-Z0-9\-_ :,;%$§\&\+\*\.!\?\/\\\[\]\'"#~()äöüÄÖÜéèêóòôáàâíìîúùûÉÈÊÓÒÔÁÀÂÍÌÎÚÙÛß]*$/', 'maxlength' => '255', 'empty', 'error' => 'Ungültiger Name.' ], - 'adresse' => ['regex', + 'adresse' => [ + 'regex', 'pattern' => '/^[a-zA-Z0-9\-_ :,;%$§\&\+\*\.!\?\/\\\[\]\'"#~()äöüÄÖÜéèêóòôáàâíìîúùûÉÈÊÓÒÔÁÀÂÍÌÎÚÙÛß]*$/', 'maxlength' => '255', 'empty', 'error' => 'Ungültige Adresse.' ], - 'ort' => ['regex', + 'ort' => [ + 'regex', 'pattern' => '/^[a-zA-Z0-9\-_ :,;%$§\&\+\*\.!\?\/\\\[\]\'"#~()äöüÄÖÜéèêóòôáàâíìîúùûÉÈÊÓÒÔÁÀÂÍÌÎÚÙÛß]*$/', 'maxlength' => '255', 'empty', 'error' => 'Ungültiger Ort.' ], - 'male' => ['boolean', + 'male' => [ + 'boolean', 'error' => 'Wert für male.' ], - 'geburtsdatum' => ['date', + 'geburtsdatum' => [ + 'date', 'format' => 'd.m.Y', 'error' => 'Ungültiges Geburtsdatum.' ], - 'date' => ['date', + 'date' => [ + 'date', 'format' => 'd.m.Y', 'error' => 'Ungültiges Datum.' ], - 'sum' => ['integer', + 'sum' => [ + 'integer', 'min' => 0, 'error' => 'Ungültige Summe' ], - 'smallest' => ['regex', + 'smallest' => [ + 'regex', 'pattern' => '/^[a-zA-Z0-9\-_ :,;%$§\&\+\*\.!\?\/\\\[\]\'"#~()äöüÄÖÜéèêóòôáàâíìîúùûÉÈÊÓÒÔÁÀÂÍÌÎÚÙÛß]*$/', 'maxlength' => '255', 'error' => "Ungültiges Datum 'smallest'." ], - 'biggest' => ['regex', + 'biggest' => [ + 'regex', 'pattern' => '/^[a-zA-Z0-9\-_ :,;%$§\&\+\*\.!\?\/\\\[\]\'"#~()äöüÄÖÜéèêóòôáàâíìîúùûÉÈÊÓÒÔÁÀÂÍÌÎÚÙÛß]*$/', 'maxlength' => '255', 'error' => "Ungültiges Datum 'biggest'." ], - 'konsul' => ['regex', + 'konsul' => [ + 'regex', 'pattern' => '/^[a-zA-Z0-9\-_ :,;%$§\&\+\*\.!\?\/\\\[\]\'"#~()äöüÄÖÜéèêóòôáàâíìîúùûÉÈÊÓÒÔÁÀÂÍÌÎÚÙÛß]*$/', 'maxlength' => '255', 'error' => "Ungültiger Konsul name." ], - 'skills' => ['array', - 'validator' => ['regex', + 'skills' => [ + 'array', + 'validator' => [ + 'regex', 'pattern' => '/^[a-zA-Z0-9\-_ :,;%$§\&\+\*\.!\?\/\\\[\]\'"#~()äöüÄÖÜéèêóòôáàâíìîúùûÉÈÊÓÒÔÁÀÂÍÌÎÚÙÛß]*$/', 'maxlength' => '500', 'error' => "Ungültiger Skill." ], ], - 'arbeit' => ['array', + 'arbeit' => [ + 'array', 'empty', - 'validator' => ['arraymap', + 'validator' => [ + 'arraymap', 'required' => true, 'map' => [ - 'checked' => ['integer', + 'checked' => [ + 'integer', 'min' => '0', 'error' => 'Some id checking error.' ], - 'von' => ['date', + 'von' => [ + 'date', 'format' => 'Y-m-d', 'error' => 'Ungültiges "von" Datum.' ], - 'bis' => ['date', + 'bis' => [ + 'date', 'empty', 'format' => 'Y-m-d', 'error' => 'Ungültiges "bis" Datum.' ], - 'position' => ['text', + 'position' => [ + 'text', 'strip', 'trim', ], - 'gremium' => ['text', + 'gremium' => [ + 'text', 'strip', 'trim', ], - 'h' => ['integer', + 'h' => [ + 'integer', 'min' => '0', 'error' => 'Ungültige Stunden Zahl.' ], - 'type' => ['regex', + 'type' => [ + 'regex', 'pattern' => '#^h(/W|/S)?$#', 'maxlength' => '4', 'error' => "Ungültiger type name." @@ -217,37 +262,55 @@ class TexBuilder{ ] ] ], - 'additional-text' => ['text', + 'additional-text' => [ + 'text', 'empty', 'strip', 'trim', ], ], 'zahlungsanweisung' => [ + 'short-type-projekt' => [ + 'regex', + 'pattern' => '/[A-Z]{2}/', + 'maxlength' => '2', + 'error' => 'Kein short-type-projekt', + ], 'projekt-id' => ['id'], 'projekt-name' => ['text'], 'projekt-org' => ['text'], - 'projekt-recht' => ['regex', + 'projekt-recht' => [ + 'regex', 'pattern' => '/^[a-zA-Z0-9\-_ :,;%$§\&\+\*\.!\?\/\\\[\]\'"#~()äöüÄÖÜéèêóòôáàâíìîúùûÉÈÊÓÒÔÁÀÂÍÌÎÚÙÛß]*$/', 'maxlength' => '255', 'error' => 'Ungültiges Recht.' ], - 'projekt-create' => ['date', + 'projekt-create' => [ + 'date', 'format' => 'Y-m-d H:i:s', 'parse' => 'y', 'error' => 'Kein Erstellungsdatum des Projektes', ], + 'short-type-auslage' => [ + 'regex', + 'pattern' => '/[A-Z]/', + 'maxlength' => '1', + 'error' => 'Kein short-type-auslage', + ], 'auslage-id' => ['id'], 'auslage-name' => ['text'], - 'zahlung-name' => ['regex', + 'zahlung-name' => [ + 'regex', 'pattern' => '/^[a-zA-Z0-9\-_ :,;%$§\&\+\*\.!\?\/\\\[\]\'"#~()äöüÄÖÜéèêóòôáàâíìîúùûÉÈÊÓÒÔÁÀÂÍÌÎÚÙÛß]*$/', 'maxlength' => '255', 'error' => 'Ungültiger Empfänger Name.' ], - 'zahlung-iban' => ['iban', + 'zahlung-iban' => [ + 'iban', 'empty', ], - 'zahlung-value' => ['float', + 'zahlung-value' => [ + 'float', 'format' => '2', 'decimal' => '.', 'parse' => [ @@ -256,24 +319,39 @@ class TexBuilder{ 'error' => 'Ungültiger Value' ], 'zahlung-adresse' => ['text'], - 'details' => ['array', + 'angewiesen-date' => [ + 'date', + 'optional', + 'format' => 'Y-m-d H:i:s', + 'parse' => 'd.m.Y', + 'error' => 'angewiesen-date ist falsch.', + ], + 'details' => [ + 'array', 'optional', 'empty', //'minlength' => 1, - 'key' => ['regex', + 'key' => [ + 'regex', 'pattern' => '/^(\d+)$/' ], - 'validator' => ['arraymap', + 'validator' => [ + 'arraymap', 'required' => true, 'map' => [ - 'beleg-id' => ["text" + 'beleg-id' => [ + 'text' ], - 'titel' => ['text', 'trim', 'regex', + 'titel' => [ + 'text', + 'trim', + 'regex', 'pattern' => '/^[0-9 ]*$/', 'maxlength' => '255', 'error' => 'Ungültiger Titel' ], - 'einnahmen' => ['float', + 'einnahmen' => [ + 'float', 'min' => '0', 'format' => '2', 'decimal' => '.', @@ -282,7 +360,8 @@ class TexBuilder{ ], 'error' => 'Ungültige Einnahme' ], - 'ausgaben' => ['float', + 'ausgaben' => [ + 'float', 'min' => '0', 'format' => '2', 'decimal' => '.', @@ -296,55 +375,65 @@ class TexBuilder{ ] ], 'protocolmemberlist' => [ - 'date' => ['date', + 'date' => [ + 'date', 'format' => 'Y-m-d', 'parse' => 'd.m.Y', 'error' => 'Kein Datum angegeben', ], - 'leitung' => ['regex', + 'leitung' => [ + 'regex', 'pattern' => '/^[a-zA-Z0-9\-_ :,;%$§\&\+\*\.!\?\/\\\[\]\'"#~()äöüÄÖÜéèêóòôáàâíìîúùûÉÈÊÓÒÔÁÀÂÍÌÎÚÙÛß]*$/', 'maxlength' => 255, 'minlength' => 2, 'empty', 'error' => 'Ungültige Sitzungsleitung.' ], - 'protocol' => ['regex', + 'protocol' => [ + 'regex', 'pattern' => '/^[a-zA-Z0-9\-_ :,;%$§\&\+\*\.!\?\/\\\[\]\'"#~()äöüÄÖÜéèêóòôáàâíìîúùûÉÈÊÓÒÔÁÀÂÍÌÎÚÙÛß]*$/', 'maxlength' => 255, 'minlength' => 2, 'empty', 'error' => 'Ungültige Protokollleitung.' ], - 'nth' => ['integer', + 'nth' => [ + 'integer', 'min' => 1, 'empty', 'error' => 'Ungültiger NTH Eintrag.' ], - 'legislatur' => ['integer', + 'legislatur' => [ + 'integer', 'min' => 1, 'error' => 'Ungültige Legislatur.' ], - 'member_elected' => [ 'array', + 'member_elected' => [ + 'array', 'optional', 'empty', 'error' => 'Missing key member_elected', - 'validator' => [ 'arraymap', + 'validator' => [ + 'arraymap', 'required' => true, 'map' => [ - 'name' => [ 'regex', + 'name' => [ + 'regex', 'pattern' => '/^[a-zA-Z0-9\-_ :,;%$§\&\+\*\.!\?\/\\\[\]\'"#~()äöüÄÖÜéèêóòôáàâíìîúùûÉÈÊÓÒÔÁÀÂÍÌÎÚÙÛß]*$/', 'maxlength' => 255, 'minlength' => 2, 'error' => 'Ungültiger Name.' ], - 'job' => [ 'regex', + 'job' => [ + 'regex', 'pattern' => '/^[a-zA-Z0-9\-_ :,;%$§\&\+\*\.!\?\/\\\[\]\'"#~()äöüÄÖÜéèêóòôáàâíìîúùûÉÈÊÓÒÔÁÀÂÍÌÎÚÙÛß]*$/', 'maxlength' => 255, 'minlength' => 2, 'empty', 'error' => 'Ungültiger Job.' ], - 'text' => [ 'regex', + 'text' => [ + 'regex', 'pattern' => '/^[a-zA-Z0-9\-_ :,;%$§\&\+\*\.!\?\/\\\[\]\'"#~()äöüÄÖÜéèêóòôáàâíìîúùûÉÈÊÓÒÔÁÀÂÍÌÎÚÙÛß]*$/', 'maxlength' => '255', 'empty', @@ -353,27 +442,32 @@ class TexBuilder{ ] ], ], - 'member_active' => [ 'array', + 'member_active' => [ + 'array', 'optional', 'empty', 'error' => 'Missing key member_active', - 'validator' => [ 'arraymap', + 'validator' => [ + 'arraymap', 'required' => true, 'map' => [ - 'name' => [ 'regex', + 'name' => [ + 'regex', 'pattern' => '/^[a-zA-Z0-9\-_ :,;%$§\&\+\*\.!\?\/\\\[\]\'"#~()äöüÄÖÜéèêóòôáàâíìîúùûÉÈÊÓÒÔÁÀÂÍÌÎÚÙÛß]*$/', 'maxlength' => 255, 'minlength' => 2, 'error' => 'Ungültiger Name.' ], - 'job' => [ 'regex', + 'job' => [ + 'regex', 'pattern' => '/^[a-zA-Z0-9\-_ :,;%$§\&\+\*\.!\?\/\\\[\]\'"#~()äöüÄÖÜéèêóòôáàâíìîúùûÉÈÊÓÒÔÁÀÂÍÌÎÚÙÛß]*$/', 'maxlength' => 255, 'minlength' => 2, 'empty', 'error' => 'Ungültiger Job.' ], - 'text' => [ 'regex', + 'text' => [ + 'regex', 'pattern' => '/^[a-zA-Z0-9\-_ :,;%$§\&\+\*\.!\?\/\\\[\]\'"#~()äöüÄÖÜéèêóòôáàâíìîúùûÉÈÊÓÒÔÁÀÂÍÌÎÚÙÛß]*$/', 'maxlength' => '255', 'empty', @@ -382,27 +476,32 @@ class TexBuilder{ ] ], ], - 'member_ref' => [ 'array', + 'member_ref' => [ + 'array', 'optional', 'empty', 'error' => 'Missing key member_ref', - 'validator' => [ 'arraymap', + 'validator' => [ + 'arraymap', 'required' => true, 'map' => [ - 'name' => [ 'regex', + 'name' => [ + 'regex', 'pattern' => '/^[a-zA-Z0-9\-_ :,;%$§\&\+\*\.!\?\/\\\[\]\'"#~()äöüÄÖÜéèêóòôáàâíìîúùûÉÈÊÓÒÔÁÀÂÍÌÎÚÙÛß]*$/', 'maxlength' => 255, 'minlength' => 2, 'error' => 'Ungültiger Name.' ], - 'job' => [ 'regex', + 'job' => [ + 'regex', 'pattern' => '/^[a-zA-Z0-9\-_ :,;%$§\&\+\*\.!\?\/\\\[\]\'"#~()äöüÄÖÜéèêóòôáàâíìîúùûÉÈÊÓÒÔÁÀÂÍÌÎÚÙÛß]*$/', 'maxlength' => 255, 'minlength' => 2, 'empty', 'error' => 'Ungültiger Job.' ], - 'text' => [ 'regex', + 'text' => [ + 'regex', 'pattern' => '/^[a-zA-Z0-9\-_ :,;%$§\&\+\*\.!\?\/\\\[\]\'"#~()äöüÄÖÜéèêóòôáàâíìîúùûÉÈÊÓÒÔÁÀÂÍÌÎÚÙÛß]*$/', 'maxlength' => '255', 'empty', @@ -411,27 +510,32 @@ class TexBuilder{ ] ], ], - 'member_stuff' => [ 'array', + 'member_stuff' => [ + 'array', 'optional', 'empty', 'error' => 'Missing key member_stuff', - 'validator' => [ 'arraymap', + 'validator' => [ + 'arraymap', 'required' => true, 'map' => [ - 'name' => ['regex', + 'name' => [ + 'regex', 'pattern' => '/^[a-zA-Z0-9\-_ :,;%$§\&\+\*\.!\?\/\\\[\]\'"#~()äöüÄÖÜéèêóòôáàâíìîúùûÉÈÊÓÒÔÁÀÂÍÌÎÚÙÛß]*$/', 'maxlength' => 255, 'minlength' => 2, 'error' => 'Ungültiger Name.' ], - 'job' => ['regex', + 'job' => [ + 'regex', 'pattern' => '/^[a-zA-Z0-9\-_ :,;%$§\&\+\*\.!\?\/\\\[\]\'"#~()äöüÄÖÜéèêóòôáàâíìîúùûÉÈÊÓÒÔÁÀÂÍÌÎÚÙÛß]*$/', 'maxlength' => 255, 'minlength' => 2, 'empty', 'error' => 'Ungültiger Job.' ], - 'text' => ['regex', + 'text' => [ + 'regex', 'pattern' => '/^[a-zA-Z0-9\-_ :,;%$§\&\+\*\.!\?\/\\\[\]\'"#~()äöüÄÖÜéèêóòôáàâíìîúùûÉÈÊÓÒÔÁÀÂÍÌÎÚÙÛß]*$/', 'maxlength' => '255', 'empty', @@ -442,100 +546,129 @@ class TexBuilder{ ], ], 'inventorylist' => [ - 'date' => ['date', + 'date' => [ + 'date', 'format' => 'Y-m-d', 'parse' => 'd.m.Y', 'error' => 'Kein Datum angegeben', ], - 'header' => [ 'array', + 'header' => [ + 'array', 'empty', - 'validator' => [ 'arraymap', + 'validator' => [ + 'arraymap', 'required' => true, 'map' => [ - 'title' => ['regex', + 'title' => [ + 'regex', 'empty', 'pattern' => '/^[a-zA-Z0-9\-_ :,;%$§\&\+\*\.!\?\/\\\[\]\'"#~()äöüÄÖÜéèêóòôáàâíìîúùûÉÈÊÓÒÔÁÀÂÍÌÎÚÙÛß]*$/', 'maxlength' => 255, 'minlength' => 0, 'error' => 'Ungültiger Titel.' ], - 'date' => ['regex', 'optional', + 'date' => [ + 'regex', + 'optional', 'pattern' => '/^([0-9a-zA-Z_ :,\.\-\/])+$/', 'maxlength' => 255, 'minlength' => 1, 'empty', 'error' => 'Ungültiges Datumformat.' ], - 'table_format' => ['regex', 'optional', + 'table_format' => [ + 'regex', + 'optional', 'pattern' => '/^([a-zA-Z0-9\-_ :,;%$§\&\+\*\.!\?\/\\\[\]\'"#~()\{\}])*$/', 'maxlength' => '255', 'empty', 'error' => 'Ungültiges Tabellen format.' ], - 'center' => [ 'integer', 'optional', + 'center' => [ + 'integer', + 'optional', 'min' => 1, 'max' => 1, 'error' => 'Ungültiges flag "center"', ], ], ], - 'key' => ['regex', + 'key' => [ + 'regex', 'pattern' => '/^[a-zA-Z0-9\-_ :,;%$§\&\+\*\.!\?\/\\\[\]\'"#~()äöüÄÖÜéèêóòôáàâíìîúùûÉÈÊÓÒÔÁÀÂÍÌÎÚÙÛß]*$/', 'maxlength' => 255, 'minlength' => 1, 'error' => 'Ungültiger headerkey Eintrag.' ] ], - 'items' => [ 'array', + 'items' => [ + 'array', 'pre_json_decode', 'empty', 'false', 'error' => 'Ungültiger items Eintrag.' ], - 'modifier' => ['array', 'optional', + 'modifier' => [ + 'array', + 'optional', 'empty', ], ], 'inventorydetail' => [ - 'date' => ['date', + 'date' => [ + 'date', 'format' => 'Y-m-d', 'parse' => 'd.m.Y', 'error' => 'Kein Datum angegeben', ], - 'filedata' => [ 'array', 'optional', + 'filedata' => [ + 'array', + 'optional', 'empty', - 'validator' => [ 'arraymap', + 'validator' => [ + 'arraymap', 'required' => true, 'map' => [ - 'data' => ['text', + 'data' => [ + 'text', 'strip', 'trim', ], ], ], ], - 'data' => [ 'array', + 'data' => [ + 'array', 'empty', 'required' => true, ], - 'log' => [ 'array', 'optional', + 'log' => [ + 'array', + 'optional', 'empty', - 'validator' => [ 'arraymap', + 'validator' => [ + 'arraymap', 'required' => true, 'map' => [ - 'id' => [ 'integer', + 'id' => [ + 'integer', 'min' => '1', - 'error' => 'id' ], - 'date' => [ 'date', + 'error' => 'id' + ], + 'date' => [ + 'date', 'format' => 'Y-m-d H:i:s', 'error' => 'date' ], - 'wer' => ['regex', + 'wer' => [ + 'regex', 'pattern' => '/^((^| )([.a-zA-ZäöüÄÖÜéèêóòôáàâíìîúùûÉÈÊÓÒÔÁÀÂÍÌÎÚÙÛß0-9#!?\-\.,:;_+\*\(\)\[\]\|\/\\\\]+))+$/', 'empty', 'maxlength' => 255, - 'error' => 'Invalid name' ], - 'was' => ['regex', + 'error' => 'Invalid name' + ], + 'was' => [ + 'regex', 'pattern' => '/( |\n|.)*/', 'error' => 'Invalid note - Invalid letters?' ], @@ -591,8 +724,8 @@ function __construct(){ */ public static function texEscape($in){ return str_replace( - ['\\', '~', '_', '%', '$', '&', '^', '"', '{', '}', '#', '€'], - ['\textbackslash', '\textasciitilde', '\_', '\%', '\$', '\&', '^', "''", '\{', '\}', '\#', '\EUR'], + ['\\', '~', '_', '%', '$', '&', '^', '"', '{', '}', '#', '€', 'ẞ'], + ['\textbackslash', '\textasciitilde', '\_', '\%', '\$', '\&', '^', "''", '\{', '\}', '\#', '\EUR', 'ß'], $in ); } @@ -737,6 +870,7 @@ public function build(){ * * @param string $key * @param array $param + * * @return string */ private static function _renderTex($key, $param){ @@ -814,6 +948,7 @@ private function _createPDF($tex_code){ * get pdf as base64 string * * @param bool $echo + * * @return string */ public function getBase64($echo = false){ @@ -829,6 +964,7 @@ public function getBase64($echo = false){ * get binary pdf data * * @param bool $echo + * * @return binary|NULL */ public function getBinary($echo = false){ From 33d9a7ad8bf533c8275963fe6df2e390dd202886 Mon Sep 17 00:00:00 2001 From: Cherrg Date: Thu, 30 May 2019 02:41:48 +0200 Subject: [PATCH 37/42] Update class.Validator.php formatting and local changes 2 --- lib/class.Validator.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/class.Validator.php b/lib/class.Validator.php index 6dbd404..31c625b 100644 --- a/lib/class.Validator.php +++ b/lib/class.Validator.php @@ -1168,7 +1168,7 @@ public function V_password($value, $params = []) { $this->filtered = $p; return !$this->setError(false); } - if (isset($params['maxlength']) && strlen($p) >= $params['maxlength']){ + if (isset($params['maxlength']) && strlen($p) > $params['maxlength']){ $msg = "The password is too long (Maximum length: {$params['maxlength']})"; if (isset($params['error'])) $msg = $params['error']; return !$this->setError(true, 200, $msg); From 153410e5f332d7d5dff93b0147839594f2b6c72e Mon Sep 17 00:00:00 2001 From: Cherrg Date: Thu, 30 May 2019 02:52:51 +0200 Subject: [PATCH 38/42] Update zahlungsanweisung.phpTex formatting and local changes 2 --- template/tex/zahlungsanweisung.phpTex | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/template/tex/zahlungsanweisung.phpTex b/template/tex/zahlungsanweisung.phpTex index 57dffa6..45ec8e6 100644 --- a/template/tex/zahlungsanweisung.phpTex +++ b/template/tex/zahlungsanweisung.phpTex @@ -105,11 +105,12 @@ bindingoffset=0mm, top=20mm,bottom=20mm} \begin{enumerate}[label=\Roman*] \itemsep-2mm -\item \textbf{Projekt ID}\hfill IP-- +\item \textbf{Projekt ID}\hfill -% +- \item \textbf{Projekt}\hfill \item \textbf{Organisation} \hfill \item \textbf{Rechtsgrundlage} \hfill -\item \textbf{Abrechnungs ID}\hfill A +\item \textbf{Abrechnungs ID}\hfill \item \textbf{Name Abrechnung} \hfill \end{enumerate} @@ -158,7 +159,9 @@ bindingoffset=0mm, top=20mm,bottom=20mm} \end{tabular} \end{center} \vspace{1cm} -\textbf{Angewiesen am:} \_\_\_\_.\_\_\_\_.\_\_\_\_\_\_\_\_\\\\ +\textbf{Angewiesen am:} \\\\ \textbf{Gebucht am:} \_\_\_\_.\_\_\_\_.\_\_\_\_\_\_\_\_\\ \vspace{0,5cm} @@ -178,4 +181,4 @@ bindingoffset=0mm, top=20mm,bottom=20mm} %(\ifthenelse{\isempty \kv}{Kassenverantwortliche/r}{\kv}) } -\end{document} \ No newline at end of file +\end{document} From a0b3acc2c53ae418441a63246dcc8b0896555ff7 Mon Sep 17 00:00:00 2001 From: Cherrg Date: Thu, 30 May 2019 03:00:00 +0200 Subject: [PATCH 39/42] Update class.Validator.php formatting and local changes 4 --- lib/class.Validator.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/class.Validator.php b/lib/class.Validator.php index 31c625b..d4b56d9 100644 --- a/lib/class.Validator.php +++ b/lib/class.Validator.php @@ -468,7 +468,7 @@ public function V_text($value, $params = []) { return !$this->setError(true, 200, $msg, 'text validation failed - too short'); } if (isset($params['maxlength']) && $params['maxlength'] != -1 && strlen($s) > $params['maxlength']){ - $msg = "The text is too long (Maximum length: {$params['maxlength']})"; + $msg = "The text is too long (Maximum length: {$params['maxlength']} - strlenght : " . strlen($s); if (isset($params['error'])) $msg = $params['error']; return !$this->setError(true, 200, $msg, 'text validation failed - too long'); } From 3b66a08030f4dda7c396d214c1118e3f1815d27c Mon Sep 17 00:00:00 2001 From: cherrg Date: Thu, 31 Oct 2019 00:13:48 +0100 Subject: [PATCH 40/42] minor bugfixes for template and example config file --- config/config.php.example | 43 ++++++++++++++++++++++++++++++++++++ lib/class.TexBuilder.php | 2 +- template/tex/belegpdf.phpTex | 1 + 3 files changed, 45 insertions(+), 1 deletion(-) create mode 100644 config/config.php.example diff --git a/config/config.php.example b/config/config.php.example new file mode 100644 index 0000000..3a8b006 --- /dev/null +++ b/config/config.php.example @@ -0,0 +1,43 @@ + [ + 'BASICUSER' => [ + 'cron' => [ + 'password' => 'ein_Passwort_FIXME', + 'displayName' => 'Ein Anzeigename', + 'mail' => 'mail@example.org_FIXME', + 'groups' => ['basic', 'pdfbuilder'], + 'eduPersonPrincipalName' => ['cronuser'], + ], + ] + ], +]; + +define('URIBASE', '/FIXME/public/'); +define('BASE_TITLE', 'FUI2PDF'); +define('BASE_URL', $_SERVER["SERVER_NAME"]); +define('API_KEY', "FIXME"); +define('ALLOWED_IPS', ["FIX.FIX.FIX.FIXME"]); + +/** + * set php error settings + */ +define('DEBUG', 1); +define('DEBUG_DO_NOT_DELETE__TEX_PDF', false); +ini_set('display_errors', (DEBUG)? 1:0); +ini_set('display_startup_errors', (DEBUG)? 1:0); +ini_set("log_errors", 1); +ini_set("error_log", dirname(__FILE__, 2 )."/logs/error.log"); +error_reporting(E_ALL); +define('SHELL_LATEX_COMMAND', '/usr/bin/pdflatex'); + +if (DEBUG){ + ini_set('xdebug.var_display_max_depth', 5); + ini_set('xdebug.var_display_max_children', 256); + ini_set('xdebug.var_display_max_data', 500); +} + diff --git a/lib/class.TexBuilder.php b/lib/class.TexBuilder.php index 75c2e3f..3d0a590 100644 --- a/lib/class.TexBuilder.php +++ b/lib/class.TexBuilder.php @@ -724,7 +724,7 @@ function __construct(){ */ public static function texEscape($in){ return str_replace( - ['\\', '~', '_', '%', '$', '&', '^', '"', '{', '}', '#', '€', 'ẞ'], + ["\\", '~', '_', '%', '$', '&', '^', '"', '{', '}', '#', '€', 'ẞ'], ['\textbackslash', '\textasciitilde', '\_', '\%', '\$', '\&', '^', "''", '\{', '\}', '\#', '\EUR', 'ß'], $in ); diff --git a/template/tex/belegpdf.phpTex b/template/tex/belegpdf.phpTex index 263a080..83146dd 100644 --- a/template/tex/belegpdf.phpTex +++ b/template/tex/belegpdf.phpTex @@ -15,6 +15,7 @@ \usepackage{tgheros} \usepackage{intcalc} \usepackage{setspace} +\usepackage[left]{eurosym} \usepackage{amsmath,amssymb,amsthm,textcomp} %\usepackage{enumerate} From 50a5f0e6c2a5b5b3a0282f96d16a16121b2e1839 Mon Sep 17 00:00:00 2001 From: cherrg Date: Thu, 2 Jul 2020 15:50:10 +0200 Subject: [PATCH 41/42] minor fixes --- template/tex/belegpdf.phpTex | 1 - template/tex/gremienbescheinigung.phpTex | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/template/tex/belegpdf.phpTex b/template/tex/belegpdf.phpTex index 83146dd..87ee8a3 100644 --- a/template/tex/belegpdf.phpTex +++ b/template/tex/belegpdf.phpTex @@ -146,7 +146,6 @@ bindingoffset=0mm, top=20mm,bottom=20mm} \item \textbf{Erstellt} \hfill \item \textbf{Eingereicht von} \hfill \item \textbf{Zahlung An} \hfill -\item \textbf{Addresse} \hfill \end{enumerate} \mysection{Versicherung} diff --git a/template/tex/gremienbescheinigung.phpTex b/template/tex/gremienbescheinigung.phpTex index 1fb4a77..ee3df91 100644 --- a/template/tex/gremienbescheinigung.phpTex +++ b/template/tex/gremienbescheinigung.phpTex @@ -174,13 +174,13 @@ draft=off%% Entwurfsmodus % Hier beginnt der Brief, mit der Anschrift des Empfängers \begin{letter} { - ~\\ + ~\\ ~\\ }% %--------------------------------------------------------------------------- % Der Betreff des Briefes %--------------------------------------------------------------------------- - \opening{für , geboren am . war wie folgt ehrenamtlich aktiv:} + \opening{für , geboren am . war wie folgt ehrenamtlich aktiv:} \begin{longtable}{p{1.65cm}p{1.65cm}p{4.3cm}p{4.2cm}p{1.9cm}} \textbf{von}&\textbf{bis}&\textbf{Position}&\textbf{Gremium/Organ}&\textbf{Umfang} \\ From e54cc9ea57a11b63becc5f95a8fed3e4619b2251 Mon Sep 17 00:00:00 2001 From: cherrg Date: Thu, 2 Jul 2020 15:55:06 +0200 Subject: [PATCH 42/42] new line for anrede --- template/tex/gremienbescheinigung.phpTex | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/template/tex/gremienbescheinigung.phpTex b/template/tex/gremienbescheinigung.phpTex index ee3df91..1cc6ce1 100644 --- a/template/tex/gremienbescheinigung.phpTex +++ b/template/tex/gremienbescheinigung.phpTex @@ -174,7 +174,8 @@ draft=off%% Entwurfsmodus % Hier beginnt der Brief, mit der Anschrift des Empfängers \begin{letter} { - ~\\ + ~\\ + ~\\ ~\\ }% %---------------------------------------------------------------------------