From 73e45df3fcc84e554a6625090528799f34e3b644 Mon Sep 17 00:00:00 2001 From: user Date: Sat, 24 Nov 2018 12:34:30 +0100 Subject: [PATCH] Lot of stuff and fixed Edge not accepting #RRGGBBAA notation issue latest --- .vs/ProjectSettings.json | 3 + .vs/VSWorkspaceState.json | 14 + .vs/block_gradelevel/v15/.suo | Bin 0 -> 77824 bytes .vs/slnx.sqlite | Bin 0 -> 147456 bytes LICENSE | 12 +- amd/build/renderbadge.min.js | 2 +- amd/src/renderbadge.js | 35 +- block_gradelevel.php | 176 ++-- cfg_globallevels.php | 68 +- cfg_skilllevels.php | 96 +- cfg_skills.php | 70 +- classes/levelset.php | 982 ++++++++++----------- classes/skillmgmtservice.php | 944 ++++++++++---------- db/access.php | 96 +- db/install.xml | 86 +- db/services.php | 186 ++-- db/upgrade.php | 138 +-- doc_USER_GRADE.txt | 368 ++++---- edit_form.php | 162 ++-- lang/en/block_gradelevel.php | 108 +-- lang/nl/block_gradelevel.php | 100 +-- lib.php | 8 +- package-lock.json | 6 +- pix/undefinedskill.svg | 1544 ++++++++++++++++----------------- settings.php | 78 +- styles.css | 288 +++--- version.php | 6 +- view-icon.php | 98 +-- view-results.php | 180 ++-- 29 files changed, 2948 insertions(+), 2906 deletions(-) create mode 100644 .vs/ProjectSettings.json create mode 100644 .vs/VSWorkspaceState.json create mode 100644 .vs/block_gradelevel/v15/.suo create mode 100644 .vs/slnx.sqlite diff --git a/.vs/ProjectSettings.json b/.vs/ProjectSettings.json new file mode 100644 index 0000000..866f1e1 --- /dev/null +++ b/.vs/ProjectSettings.json @@ -0,0 +1,3 @@ +{ + "CurrentProjectSetting": null +} \ No newline at end of file diff --git a/.vs/VSWorkspaceState.json b/.vs/VSWorkspaceState.json new file mode 100644 index 0000000..5c3679e --- /dev/null +++ b/.vs/VSWorkspaceState.json @@ -0,0 +1,14 @@ +{ + "ExpandedNodes": [ + "", + "\\amd", + "\\classes", + "\\db", + "\\lang", + "\\lang\\en", + "\\lang\\nl", + "\\pix" + ], + "SelectedNode": "\\db\\access.php", + "PreviewInSolutionExplorer": false +} \ No newline at end of file diff --git a/.vs/block_gradelevel/v15/.suo b/.vs/block_gradelevel/v15/.suo new file mode 100644 index 0000000000000000000000000000000000000000..f88cf9ccce17dd09233021f5f8898473617e2a06 GIT binary patch literal 77824 zcmeHQ34k0$x$cAn$Uy`WARNMi370$T%+BmFkwkWPb_qx%$tD4GU^6?@yP0HWXPKEz zva(ST1OtMA2kJw-&}p^9=OP7VM( z``mNS@gC1#1i)o8>?1X>UdiFFsI(~k${3D$Ws;v#IvlCYo&Mf)UEccMvAsb0 zD-rL`0X_0sV9Y67m9v3;1ZVxqPM1{5A0LSpX|C$>r}kHO;WnDD7A{b-N=8|$B$TAG z4Zov`s+^~cqbAbG&6rX!;#E5=Q)bVq75`DQ6y8TT0Pe4M`la3SC#(|tZ0GOjn{m;mq_^S%fpxE=*; z0i*%j0DQ)8#$(PnvTgEOgd(oD8~1kL_-fPhOL0C0c#U!I^~P~e_W$wsKicg7sP~!F z|3%xGi+r*FnfpDTv5&I2blZ6=+mPAGYy7? zh{vP3|MS`{H2Z)3{r?KoJ!t{IC4n?cob$LF*H;6s0lW!tJ>WXPn*n@&1CBQW-U4_l z;B5eo2@e9^0eC0iU4VZB+zfa(;1(3a+|HAQe#`za;{GxIGWgNd^oD2M4Gp_G8jwqTIzG+;4+c@5b<9Cho z@8S4>asGWAe*pMzz(ar^0)7JcG2meU@%$9Wp8*o&x+6@HF860RI9!19%qj z96$jda-K2)E+d0+W=w-o6Xy}bojl_?1o9uE|FXgyqE0`0BhHpC(l2|#@#HYVWib-w zag^nIL}JGQKZ>C&0LbFM3UN<064(H|1>ijwWi6m41~KZA2P=XfWX%+ec-0OlzlL`< zBFN&u5^+f*8OZmM%gdww6Zj4T$VUd(1@NRgXRIP8MWqk9B7d%GzolHQK>0~mj|Ir$ zA4J@TNSqOx{a>eh&Hmr)|If$%??nGeo4R!}QT12QKhhqCsu4{O$hTc%TyFrq8waf; z1uPgem^3blJ2Aj^C8PwE7n|NLKRq4vw;i{1>~i=y7K>EYKziG#w1DpCKzB*;b-BOA zM2SUQyW`0o^ba>7pg!PD3eu@8ZJR@#~D$&Yr-+`+g#vM2O?e6ujegET;o-F>)BChCv>|?`d_oJHT zD?iPTjA<<~rCqZxR?~=e8tqRjykz-T1HWj0R{Z3Dh@1U!0yKfRor0J4cPJA4^C5bw z)$vb)Cmq(Pf(!mxl#7o6$kxA^{#fy^L3y*lN6cLoSJ89z=uEw^4d6~YI4YSw8so$jA}84 zH5rBSiGH80lGJYsqtXh*DwkR0Qcj?~r|@n;>+NyO1zdujPkvxhX#+m;rIZdS`veec ziVo_L`=P+H1X#|g0}EyBVWc*W5{x4)3SgA$yw+Prs~hqW zFF;Cjp>e0)xNk*#Z9cbEO%@CB;Y=>MZD=%~NU0fhyPAn7vZ;6>pN!|#v6Px0PNYWF zTKEr2%0;DDYais$wgBIm9{8X4-*1tLBwylM!OM^xT@M!rF&vs3P zLaiO)seweMpwgnjk<&~K#}mn!2g%tg|$Z)wBc>6e?( zDnzYQS1wk6e$UxgJ@MdU+ZNn9{D58Fr9M?*TRM};j%JGmHNQQbRQbvB-t%7?kf`u> z7s5q(zIo5d(FH#)jwM^~eES3M9C^&HJiJ#mT!trt|1azCrM?~xwT8O%2F9DarozG2 zNQX_+7D(<~cJ9e@ddF^~m}!+eotkzwQ7EeU-`^|V??f*RciJGblV{O~>S#Ynv~-G^ z1+;e#yk@nAiosaa_tVS&@w*@1(|XCj{{HAk-gVLLm$S%blFM_g<}8#`yDU{{q`S4- zpg$(+R`2)?HAn#521_u35|D)u`Tk<;H^+7y)EB?-gyA)(_FhKofr@-Xz;eLJfKvd}Yn%pP**F8(3J3r= z`q1WqnptW`+X3VfIR`{-F*P>iNvV@1U%CSDQotF2m4H=%mjTWM^ZQNCC(< zi~vRfTL5XmR=_qu29O1e0dfFp5ibGc0n~#Q0TY1jfE|FHfJwls0i^d+fL(ys09cmS z;n+7#1UCVfiubBnnjo@G@G}2P+#)IK{KxsI5!&8tspx6qjq+oQ2l{g^l3-h9u;RC; ze|lWI-%}y~u+6cZqgd1BvWqM3or9Cr#)TC>=iHs<+e8}TNcoHXm3>GS|JjJkzRloH zf7pYn2jXX6XMdH&{~g4=#z@i)f4kPpjmS0!FY@30Nau1Rtxf1T8_=7m?dNRYPRu+~ ze5Y{iHuuORPD1%vZqg^&@)I{{EyKEL?99gmu+kdByc(emZIpc?p}ZV9V}CXWjK8z^ zEsp@m;(smU8i}m+&cAXBNH^Nx;4HX~bM86&$PcXeMC7v9-}2-GAK69PsAY_q6T!gE zLmm9Hm47SAFIkY!EUJv0UFlPb9De(X-47kO{wH5t{j0~X`0^D`-ob2$oQWgvNrg?` z>n{mHKZ=2%lZaYPZb$_DTFH8ql3{|mX# zKPU$va~zwQF=o^)d>s)u9x=G#Z+F)}l~s(Ms`KwB@L2RCH~b-Y`;(gSe*^eAUNgAk z*W*?DrTr~w$ufX!{eKH_Mfo-PfuzpdhmZd3N27mu^1i?Rb6x+TPu;)!Gej<0ojAs| zDd7CzGaG-qV0KAgc~_H7v<9H79KtK)9gn>GwS{=N&uGdEnv4o*MeW zaZ4Vx!>&!!*4uxzp@IVy1j5z3IN}6EAE#{9okhE6UD$2=-bTlM=8b zz&7=Ns0(Kqe=q3&C|EdZublONF?4)dx>K^HeYsg4UW;e6$rQ8hf@WzA$M^|?{!m6F zKP^jtC>I?>!41I)|3;&83;sucqx`oG_~+x6AWP*>?XS%8ht=*=mwrZ2>QU4REn!6I z?bCw&l_vYLII`tmi?|;$k~4LUqalQj6~h=Q@7w01b*UX zaEdFQ-G-A1O`m~k;VU2#HAj9!3}@69`$Ey{hlMt(GfH&2Vy3S%#8)9W~buIIjI?}e9pK#tow|dWGOy)`p z`hpPFpVYBzu)#Lsz3OVoz*BAE2Y1gubL4XmtzU9_;gwsFn=-PiH9hv&(j=A;26UZ% z#wPDnkd(2sC0;)$=CSo#&H2Cmb^cF`-PB250U&L<5=Zi9Zv^ZHTn!NX9phhz>o)_g z2iyR-5%3nkp#VN-8gDbsdEE>%w+1}x4~!z!p})}V&)zXBR%PrNMbxPdaPa2Ptvm1dQ_dDK zWnPZkb&o%GgR6}G$oT$N{a>vsK$_nljc#v#f7*{U^}n??TP}&(FZ920%&!DtMbU)< zQUAm_C9W=})^8H`$i2r+bChxDss@d_oEvJxyB+xDI+-r*SGP>&PPd z&K2cb05j3l*PCS(^NQ;h*nV*Bc_;K5#K;Cpnq2^lAZIPe0jc){sC))jgSba*TphG0 zN>o7I&}3}?C`}R87W%iLW@umgHf$M1d<(IDaNqnU)>7<5+mGzCEzDXP`u53crTN}| z(8Bb|Qc*@lp*_pK-fPmZpd-B{P5Z%qP#)c*)i#U=^jxs-(rVGl1a+f+oXp4m@s+k? zTxV~}qfA^K&2l}oAGAJ}E3ffUoIT{+DoeI6(mu|AW>A6=l&CbuitGKUOnOQ8kMBg0 z(#(OQ@fjfsZht`g>jTB#C29X^wh9m_>Bkvh|38a*$0Gq$NtEZ#{%Rp^&oLgEOXku3 zzf3D+kFosjEL<`V3~uwUGWP#0_vuJWw)~eNuCTwGMJJF5G+-hhJ%RC3_kT@Wd0O_- zcHYh-h%Ww$dYq%YbyjRe5Pz5Uv=8)mMfyX5-mXYE5DLYDfu858z`#IX zc-Pb!Ytn_}K$_Uk0GRow+E&9dhgcU7>5Rkz(NHK9=!^6W0Bct)8tvJ2y-Tk2((7r6 z-+^>SEv%Yyw5k!VH257~V7$n4k={n*e02~V&iogczH&Y@zr%9|n;Z=)%N9J-^Wtmo zTK&m~KYah<51xXW_mTb?A@9D#alTPGljn z$c64)WA*c|0wev8}LimfJ!3GeE z&w6mi2HXSa!+Mh-etsRa$?FnN z|8n5ANS1bZi&48rE7!PCN_+b4PASXn52r2-_eO*5q4scKpu49l5Q&C50?~nBC=l)p z4Mc<8!S3!@aMz6u%8c!*Ysoxa*PuLU6=Ys)E}5U*e&ok$ey{8w_CIB$l|9uh ztVRUdQ!rTZd+`6K+5a7mv`E&qL|uN!FZ$^JujcamZU1w$QFht#FGBncqK@3xKOg$W z-8X#tvL}-R@B3)qyb})N+hs-8(0aARY0m#)*xifsf3?{EcZY;kPzp`Jh8eBoU^MR* zB@RZ`(}=*sRj^WcES!=oo{ng1m*GbvDT^wl4o{4Zsv1Woi6J3C9;hsRWw>uOFb$5n>k^b&j zPhV(P19M}h_L6mTLxbu@v~cqfN2NnbS2~QSy8bAoH}7O5F%k-;x>C_-VppR(on?Tx zQ-#=cx<=>9!rn;c%1tNfRniupZ)+i$%T!kxmDQ7*{#j!EmnMJJvi_(M>%S<3Fj$5< z+sj6*|2h$vWbxmLxIV7`Dig+R3v9V*UjJ3g`lBlTpSAC=Rla2X--i6qX9+h*))N=XFG@I_-!1uZV9iTmO9V zI3tM*Fz>_#^XF>z+FWz1``-#cciaJsls;&hd$gnRg5|*d4{F74*1u5SMSF1F#$5L< zE{mI4q5mJ^=LC&x`MLCOo3Z}Sz5O|_z~uTf;$2Y#{vRRkrvVInm%(lQ2iI%93Bd87 zx&A}1v;E;WrH&-v<#ec+9Uth4wzn140^C~`i|MgZyXF4)`sKkq=UM(l@`@9ttVl6& zN+;yXUuF$X!Y^dDtuL3!KtzV8*FxJ_>X@2OC);9a?M5O$xw-vSuQDPI7W2q*YfBcq zPd<|#Zf)75dx%{bX$$hNwWSZ`oXD#y$JB{pK9Ol{SvxVDNhe>fPHxC;Q^!^gcXcH? zk{z9)XgHzUi}fU& z+t8+1-|N#n$7r=y7tPkIwL0;O>f^Zx_1v>s|2Wy-3w)dh{wlz!tQ;d}x@G>pY5i^e zV)E^;-fyB?$=uRlg~HZLCe-|7TVgDoO%&BYVM`*F+fjq^tkDXOEs3#I2IEByS!$HZ zlanPwBe{H5Q~ygUgc_w>6w~E)p08n!A~luYsJv;4Pj$@R+q7if-q@gaUnovOpWK!# z&}gTpBN7aSBJF{JNHi9Rgk!ydXs{PHJRM!VJ>f{SzoVni)lSW;PAHX24s9IlUq5u# z`kq*STXAO*IqL2Wcemp&(Agerr-fc9(B0joPgo3ecXf0{W9_>d)#s8UqeEJI(mmhT z(##vBg`Lil?(AQ(@m$-W#sdwt;b6X|>|EWT>4-j8AnTI~$XKH$-o&_Fwz-HT zSzBm+Ddbp{c4%sU!-vYfOAs4W>bG#-f@5!BaKnla4bs)_;b7-TM|+?%1v^7bS+xfe ziLMle)MPL+5{(AKk?^iqqsBWiE8}5OZGq{XpyQXYK~nPz(%+#I#w@7dvQSASPQ>#z zK*KB61M|MX^p0FT)$@YS6)Tn-#AdqGwzQhqw^FJtH|A}hKgI&IaL#4KX40gtF4WI2 z%>%zguEMA1jcbkXbEVQ@F(>q5GjslaQTen#RXu~>A%ZxAp6iTNzIdz z?U1s!oNLExR39obY^uq1#(AuvO1NrrVo{0s{4cQBwe@= z(C|^rn?dR#?X^AOEy-#ADW`cprwvHc|I^;^W|bdKq()WDd&qGylbSJ zf72bA%=GWn^#4|`#Zx0B9&2xE`hT0r(wx0dyXU9>H{IRJ0u#^R${l%(E6Yha7$ML` zT7*{Pq+xUDc3LS}n>SmPmh;TG9lF}z@VSy#p_2ayXqf&`SP*C;Z+0|`wFmj_>7-hV zvC$H{mz}kMPH4Tq=|kmTBxYrlr@pcs=iH9IVUn`!gpGsyaKM$L9N+GwPF4g@W_(ko|~iv`^pXNJze z#_L=`Vaw?{vrtY8H@v^;1M_vPi3gxhOo|w>nX^P>erZ+7DURCi+T+PqUKk$7s+fLm zHdWp(+`OrBci{X8K*Q{6r&q)1hTye`VvB9w zb+)hd;|19YcF-)$GgUCVy*8!XsM)URGS{@eXHofU&T1OzxDEBTYp2tYf<)6NHq4g>HjU4w5_Oeqi$JIVCF3qnCUA?8of5lw7N(3BQ{FpEwda( zX7=hDdnY~l-p-qo7%7{#nIqgaiY8QwFQ^;S`hN>y^Rn0*L#ggBiju-#GWTAHnh?A4RqT3sBi8%6@!G0=9)Xk7#>&={(Hp}rp2bJHWgsl-@ zrV?Kxur%iTSNkg5-kM)+NZE+pdHR+0I6Di!J=n3MA8oA&zllZc+{OJh{n#l-Y?)NK znUjAwtTnhr^EsK)+G1Yovs>}&A3moI!u`WyE!9@#5>N$Fgh_m-gtnJ=7RMs)1yDYg zn%{PZMlqWHUn(SES>|DeNwmI+gg&qYuv}>Rf01L|oBm%aJb1-TzFcHGW7GeOS&<{; zHeG|S7HiSrH~qiVvVW=kwjm~8E@;$Jn<_N@zsPu2@SwbD`hO7>>nFck$%z&qLF6>~ z&#Qb|T|Tj5E6I35mMCq%>L0hwUA4InLIK=#0n%*=EAJG+Q|nvM`iIe4PWr0XcW2B) zwk9u6`QMxLzfG~*ze`L5RCBpFzkR{ zj1;cXIP_g+^u5-rYlt~D(ILHOg~A0R=KV7k(z)o)HR)tNSICVN+cu>O*b-u}IFU-{ z+Scj2Lu|<9GKIDc>G1*#3Yz}E>U52}Py7GcFp(Z#n@Da;jABC*WBZYnA^G;V$-H62 zR&*x$X)`Ah@}(axUf6Tt5!cTC&chF^>HXth*(t2s?eN}o@Jh#m;gOD!F79L45ln;= zbJ?9%R_Q$(us4pW>zJypZEzyTRYS2vF~Jm0JcDrujd@W%S+#9ws2BB&O=p8LClvx>~!SY}b$4KBCjy zm!d31>YM6FU2h@@G%B6{EKyBGITC4m-F2MvlC{WH`hN8rY)UfXz5UmcffK)Z;Lh;n zk#ORap}9XBJLPGVy|O>p&#Nj5-DzfQ@=hL9c8+^glj13Fa(-KdOu#u@ZV`1jV5&;$ z?z6v@-=VhoE9QVsRW|#YgG#-HqFFMYQwNp|c#Pp`9!62v?~8S%$GN@5b9ASZ^N`*M zQgk%G5RATQ1xvYI8%!NNxr+4JLZj*bu?|oDVYC7fn*JX%mf1`v&yl{i;(f&bqo(A^ zV|a}#G=8wEYk#`xm^YmHv)8?O@g2Wzo%+H@xI^ZVg z#2?2W$3PZ10IEwmoP@PS5&V~mzmkpCS(exXHgWZFA&xZj;N!9wC8!w+;F_BANu;ma zl<68?Md2#7cpc*gwRyVC%iNm3g@hMt)5zh`XQXr|Yx;jIYI}Hl@fqnuCBFsDOoXQY zKi92u_ErCXSk%79DP0z{mNYm3`HfNpEeOT@;8(6&z9ahO$JcE6{O-RzxAb0a6<;yn zh-S%S0k&=#O^OlPL4Eg^dZbPOynZi?4o-EZ??7QEoytmg)n;K7(bzsLQ5 zHT{2pY(h^>ef;y%|A(Q;b+h=;eNNQF3COMkQWU5(eV8?{&P|i!)E9l#qjk0NGbJv= z*{(xTjsf<1FnjeS=B)qMcPW;oTuuKk*g&~fe=*XPyEpv5$js1K+dO9)#{jfDEb*b{ zxw)gz)=xKh(T$oui|ea62e}5ny41&2fr59XPlNh3de$DVTVg88p-8=U&REQGQJW_$ zsm$j><*6ui9y44s=Uqy_at_tLR=H*D#dY8r$2a3TO#RCR@a^<B1t{rBt+qjm@*dfOhiDADrrKNEyQT&bnS$yV-}-Q~_wrl2n*DAnYk{ma*% z@~L|dW(G`_G&X|ov_GJx|F0OfkH!YlJ^ro;ny%m(;Qy;)^{Xtm;^0ip+{T;!pW5p* zFKn#;C(HD>*+GZw(#!qA|Hu3~M%_yOt;7pgTHM>uE4xX@m3C70wiJ|{a9|FBp~ zRjd7EBXd$|P}DeVr9Lj&Qfa==Kde?=NC~Sxhw48)wSHfJ*MBzG{}rJH5!MHdYbUaL zV*herrwi~eb)B}^Yux<9Vy`FhGkswTXVvjE0>?~T|7BXl(p>-3T>k@F*j)cp=~tn- z{-?SAhnlSYZ~YG@QG`R$ru{!=#GY^V|4sW}deAY?W5;oo!WxMpJf4ndvO(6gYOiVk zTPuG+eW1I)q^A9E9`$5xiB{R)fBWB(6@o6&m3yTr=`ox3U!)QA^`nV0u6VNgHEP=bfc291ALDhRv1$Kfvj5SXU)d`r z5Bu4dufJ?}eA(Oo{;7?#f3fl>1Jng!^C0`=a zidy6v@G%WWvD*UIq|;k}Kh{Zxluo`;HBcU*rK9swAzB=bW^%)cOv$E;NgjB{*hD6y zS->fZ^Nxb$`{vdD^2E5d(HK2ZSG>#{9>d@>8-KfC_V4e#bJ@8k&*>f08i>cFo}aJL Q`CYUBdvSrdU-bX~2iw&15#4_HesRCT>5I$utlC^xrgau=)+4+2EeS32}HP53~|y zbqC?eRCh=l#dpO5A&(Dp=iC0l2AOA>A|`K&2-4)V1pdF3fK9Q{3{1?GImg}}^Tf)5 z;h?s;Ys-*7G`LLb8yFrK8wd^eqI0;vd0C+->d4npV5l>{J|U zWx?(4Xiw%|>n>aqpXl16iRlvOXoO5Qrp{!$Z%9j>HTfs977O{K86s?J-Hjb9R*-VG zcQiJ1H5DB_P2?ONkNNnnF`s`>ODuNcy%fnxcf-;ZO)1JP3xz}`?qsjF;98JvcT-DK zr+Zar+scN{eca2N_Hj3OyF1$2A=;Hq?cI9$cEy22y_~L{^?7nMDcuX6LiYJjo#N>{XlXulJVkZ$f#d)O`;fheeN2*(AXw%gB z)|nbZgX2TP$$YX8&l=>g8#*Ic6=bHi@AU?}hkkljsep>(wUF%V_vJQEehBOqJ(l8a8jjPVh*dfVIf@UvlCMz!IwUeY`#`f(}Mm&j#|A*#tlCi1{f^?Y3n zPbQjy!~n#QC#GACJ%U6QnrG}0BzoB*IgfN!61@!}e|Cld0>st2uJ!>4#sPpN`f6Jh9cw1r7d*SeRsCHF^G_`+iy{;)Q>QG;iA zto4X=TcC5%+~8f&?GB8OQnMS=o0>@5)7;V7)Yj5Ydp=P6r!6yyQtdO*uZeV!J4{|!p56lglgc6T3y6zs_Id+%3^aYT4dgbnp*fk z=<{g3VePEmOxqpX$kzV zlE606YBlY8{sz(oZHvudcHz`WxHR(f5KOql{Cs7>y4spbkH50A)>8&I9)GZkvIpxb z{MA}zMZi;5Q&(511!_Ij{<0u4`2rO_5+^WK)zIzR~O>L>iR}lz$wCbudt+cMHrdk8CzoI-)U0dTRuPqCBDg)I)kFVTU=BWsRT>O2$ z@?d4qQ{}HJFRdftYl2mtO0BHaQ(IMA?kV$C25Lb7wLz`QQ(fotmDZKjc>Fbfe?$Z| zT6rW-fm*)?@~HOJ*3?wh^?CYgDyt!;T9AEBZLO!Sy2=l^tga|8E%(&afRI4^o?2gB zX+$kPUnPmjS65X_lo9aM__Sbou&T~eUJk;pECU7keRZ0rvahVBwyds9tAPC0_$xr6 z!3s}pMRmESvb-kff$Ua#YAVb7%C++Hs%p?iONABji=7vS6k<=3wX4;An2~N24X6&sPA@>JCYK?Go}9?(;%r%$V?)U)HJCL}que7up z6jiQOgR+92YON-s7GFhmT|~kD3SU{N<^ia#tObo~ez1bdO2|%a5Y*CFR#sgXs0!9p zmsNQDRUjoWFHd!_3`EdZ3cB-``oJDhCAzD(lM1OMM^$4dj4TDgauJt9OCw z^4e-Dry3vRzAjJ=^$z4$?$`PtZ^2rBUyUc=@2jk>ssugwH88pW$e|Xj-V-P*5B615 z1vRa%&J(N%RBF{waHvv|&nZ@p%k7vP#z=)(fMksDm>+VRkhXrK7U_n zO^qk$uPv-P!;>C zs!E|@swx8}P4iV~An{n9{80Jg^VAoB8dX*Ya)v}I{gnY|#44*iWz|)|a(|$&y05y- zqm`EWtILTnqcPR_LDg|FX{9w0L(8C4EB}#gB8$l`#oA;U9IMa zw!+_6N17fDj1`(JsCT8M<)x*8I$w3Qr?0#s=&$laeGh`Yp*_`<>d8eK`@Wj0Ky8h` z6hvPI)x577s$w;?nSo$mu(Y}s%&5E^+OD8hT?S2?9~1-)Mmf}$vI3L2!C?)%}mWLZ;*N}q!;b4Xs9G=|29$I2HltjSZ+II8)^v|z`mcO*z0rFA9c@V~UI)?HS+ zOJ(IQm8BDQi`CRPu@*wF*8*dWq2a!P{_#=z2BQn+cLoC5!DJR@ZiHd!_`qN=jw?KO z7x$XAbYf>NygeHo8y|`Al6e}VZzIkI#WO+ZVR(Zi3y01$v!(@(E_k;w0ep`X$hnykd{DN0%-}PC6JatS^{Yaq$QA+Kw1K63H-+- zFvH4t%yDaBvw&iWqrd{K)eABirpOGl0CdqRuEXMHun?Fo-7u_TESO=@sfNWhwW68H zF-I05SOqALRXxt6-lbll9;ZI1e(@hGU^?Qo1kw^nOCT+Qv;@);NJ}6sfwTnD5=cuR zErGNI{?|(&huPIU#i~1N7E@wQT7|&QfU$kz8U$tz(@3TUrdn-anZ_*O;}!wP;@zey z{%e!^SM_!EHuW@h488+ct`@11@}2UY@}zQ&a+Y$a;!|3b#fl_WI9}xA_M{fepR=qrRXvsBP2+iw_zY zvA?|F@ca)?T9;o9JmwTUBkM-0>@Uw>`1(tp$1scmTGp(mdgHzF=rmVsf)fY^FuPyv{cq9E2?nlKhL&KdAY~`}?(| z63y_~Ob(EPpDfL_=;fTf*k?dhczkdyTnyri`Y*y*{iC4`+Azu?ys=-= z{j(R^O!<(jXsYYOGj+cOg*H?Dl*KbJg4IkTa`4Wrp-RsISfFQWAP^dkO*s0?Z4%|oq?gI;0FBP~edC^nWUFFz&A$Z)LK`E#eX zyga@98F^@V+HlnJvfY@o@eyj#q+FcNo-@a0DgY^v@8w}VH7!xpGkZ1$92kbZI0grc zHxCU){amwbraTA;>+fM@cU+wLxtLE{$f5qBu}I{E$repG2NOm?YGZMpv$D~IjdjH! z&RH(>Hr5lPw-ddMb;RK9a$wH&`eE?OoM|&n2XhUN2BO9~J0s47*m^?aq5HcsL7K$CaH|kfVBO1^0a*gC@YohumR=kPhjpLqX&@%z zgP--sS)4<)ndX2W?25H)WmEA`P!E&=n{G`v*=-{On|0D0*=8yM-VtA5gRfunkUT=u z6B^bTvLu@+2N+hgH8!<(HATrHk_QL;#tdf)AQVVp&^O$#v*z$vfk>;=OLoLF25{TK zydnBvDA4QeYU=E5>4dGFipMsO>A|OQV7L%v(63XnY$R>FEimW{hqb6ka;>0Vh$>na z%}6TV7|n|yA7ZMK)|ixM)bEvL z>X*vf>Z|hC(i(YJIaj?;zEHhhy+l1%?vU;3k?IJ{0Ng5lqg2Yh(hJIM(*0_`a=CPk zGN`UmJESqSPAyaOl~bhClzcT)IaE4W=~5n18Tn255oNmavhuZjqC8)o|L=R;rDIA< zAT5Ek1kw^nOCT+Qv;@);NJ}6sf&Z=qoJ^HDX&eI6O|>aKV9Z!;V=~OcgH zFwCgTR+v0yYkhs# z!7r=P zMARH;-mFB{gp@c@z)q!PhU6cn1S~dYIuG*zg1i*Q|2C6yoBEwHOZ`H5N_|OwP_juo zq}SBDWlgGxyQ%OCH zW44G#1@$nF+#(+3GuR??bo>?}md$0GfcRr;I!d$HMdsMRHxjUfQWM7Ro??-8K#}OG z4Nn&9F(r@RX}E*|ys{{R%{Lo|@G}?AX6vWQ7`%BWqxflNGK{C<-8hiRE{G%+9mhM_ z3R7(4W*ErR#0(?(nFUm4$zyqCUMdf=wjzH@-Q~@tx`TBTc{Clvn4WWIvIU?`Z0Cw#<}j;Louc zK=*SrCVl8Xoh>q05RUz+y`>!dr>^Kwof%pdGW_SDP-Og1_W$FQBTVX(>gno!YJu{( zay#6ne`yJ%C6JatS^{Yaq$QA+Kw1K638W>EmOxqpX$kyWB~Zn&ChvlsIVYm5s<^he zthyMELa42%vXNjrbACvCFe?eRGl#0g2ZMtEybH+r5kGaXO?)soCjc_J6X%!22eZ)B z;7kMZBPS9hUI>Z&;(}Ge4*%YQojJY)f|VAR*VL6)7MGI!|2SpBq@JP9SH4j0fUp0b zg4^^jErGNI(h^8ZAT5Ek1kw^nOCT+Qv;@);NJ}6sfq$^Uogtb2Qk`9{<~D zu%A8tx6)ugd;HI0CV%eupTX2{h@Uw0r_%uuulVu5rIPg)@IQC_Pu~9*$jeOX=Wzbt zlj`ko3c#uAVd@5Twc4bXt8-uiEk z^zs{blid#qsXRh@VB78)u=^n)l|@K*P8-R9O%DmFG(s9(aCZi5bx24h5mNgP?HRDc zAt4nLRv_;^1lD_Upc?dfZYoTsgRPsZhFL- znE^W%64HDUFz>*FfB&aLGGLcNLMotKU)_6G)jb)oGa(_(rCeXkx`zKe19l`Nq}dz^ z*nUvKn++MTAt52TDOdYJ`3G&xfb9qgX->oup1#E&5Dq0bDJ_?142T|rKGR+ zxa{%AGGOCDLdqee1&3bv`i&oDz>b52~urI z5B}OT4K@HY^JbeJH;4SN?gaAwzd+45sh_A{s2`|1)Nj>SU?$*?>Qm|?>V4{+>J946 z>ZPy?!1?N#>dES%>hbDUb(4A^%n)d5uey)grLI(4)CP4|wMs34a|81KC)Lb!UeXds zOCT+Qv;@);NJ}6sfwTnD5=cuRErGNI(h}Hd30N7kxu$?ztLKqxWj?u9%q7?IJaR2_ zlWXZ5axIxnuEn#+b#X4aF3cgDu;j`zZ1M3sk ztE^kCKI`t*9IKiAn7xxdiS@Ddtc&@Cd5pP|8E3i}k7d&GuH|0KuPpmo3eA5vKWe_h ze2{syc`;0P?9^}GpkOlR<(chfi`mw%jrG8}E~`S}n2UFqdloF*09*?eM7f#jlLz`IIzuLs^)vyc~dm*}DgV?+=*R%8_p07pSVlw)aVa~D0r2Dqbc;9M@d*Zp{0 zEm_6=z_DnNA+Aw4f2Y$I92kd#Cm_1mEyUO?m-Yd_#}h3{*Z9atXmm^q!r74{Q0lmQ z2vOxO)qY-x%KXyc&kM0Qt27WFqA@fyG7g!dvV&a3-9xVC*SY-=LYFehH<7ZAz)>LQ z_<+OZvQy1=YW+={NAT(}XU{7>06cZE(XBSxeM4GzXvIKyEa4U+a~90&1%BO3#)m<& zjpSsHmTksMfR*Y(47W9j3oK=dS)Kp3mfkdjpSNX-45RDqmRZ_Kkv) z;%*?m59EbC2Ht>gWNdsi!W(x3ysyiPy}(;kg!vm8g(Fgi$Chb*J~)ag{tmcbl%3tc zot1^$n|!0gqDLy%+H421ta{F&uyc zk`RI8!94dW14l4~k?05<59~6x$5V;MSWp%ycY;#CwSU zIJux1LgeQ|o?zx2vp}}YHtG_0dzsi&YG z{B@}r3TJTgj@pu(-GI&ILg5}p`ZvXf)p=$UEs$~&gbs{+6Ld=&4VzF~JqaHDT@L&)HC zaB^KGaMjdcIs1GAgT12~c}Vwan*-Vi(MU=n?_6J0SMf`O@(ao#sBUn+V6d0&$~79= zWOVb+{+X^aV06IIc*Nkep^>pI^ujv>GiuNkEgYchsu4v9$ zl!Cmx(Xr4F4Oq!2`Qx2?&d(?U4^_oz=FQ`st+O1323`#sIr7IlTgoyPB=9cF&Y2G+ zy@rIx{X+v|c;%gq**OJMdxA|HQi+wgq0W(S@Q9VT!8L0xc;w_@RzVwZY~i{H@Xnf? zS$RL^&ChZhyyM#!-dQ%+F~^W6LFT9l>oIY0sKj;@xzp!tk)*FQ9T-3p{{F1CW0`Nfl$dmB! zSfDIA@_c#b9NxeHU0IAHf;?Z4S#1XfXqYjT5iKy#HxTHhEh)6#q2X{c2~xH@mvRuU zp@AnxV@cfmzc;!C-_$<5mCLt59MI3Fxrd%iFC3=XH_#t*@l01{ffbnGA&5)^L9G{R zI*hv3#hg5|S7rfgU?UQMxW=HbYH;$5C(FYC8w^D-wy~kXUZU>-Ka79E>*8;DW&sRp z^CzxX0NfMTEdj`#xVjl&&crFB09g~;rUT?moZJU6d*WomIcs8DD}Z$pF7qaC$OM?N z!)Z}pG%@P!X1JRR-vGR+-VUq$cUMYzH?Ju7DwoK2F!wXRVJ=`!U^X*-OgEFw*e!pz zd}vv1X=ZcfWs)FmmG-y&)Ap1qDht%l)Cv2?7DYUWTPW6wg77-?ndKkR9SniwbpsoudTOOPhme*f{G$O&Frw;Z8_aCOF31kmY-18C|3C*`6%gD zrrGkcrB>WmTw%3{lj6tXeX#H)pFNvBfo)`~#c!k!*f(LVLI?kmZ3BB3yH2=4`kQpR zFpEElKbYUa?_QZ?{FV;7jdi2FR8y(pM%vA`D(VjD_dlF!m>!7CtYsMkjJFw?cXX# zOOKkrR=!f6Q*PwGv%bnb!}+-r#9#3ja~qW(lr74TY!aT6_v12c z=h`O3b=DQIc49waUtu@L*}7Gj2`x2H2jmiVr?=`D%Lu|!&M)0;9g%Q6<~ z0#h6AH@%@Sb1c!h2N-05XK>WFiFEx;uaR)1Vd+`9!t^TfA`kRVEih(!C4(ujKnJ0b z9M{`578)_VoJ4ua#Z*}WqZ%C5y8;f7^^LZ`;lu+_5lk!I-<};&S(FQeK`(qCA~Mc`AwWWGpKQC3qq(y~mS6K9)pzG>P&^66N6} z%0o$%2a_ldOqt93?&VypID4Hr;PEYNQ}U1x{(x6AGMf%M{*V%2#@$+5Y-G8*a-XX_G_l! z5~+}pG#$m6enaFCHw-h~Kq`?QG7{?g6twF|(6~tr)3r015_o(j40TP{5FLevmxTi3 zIDtqd(li`2T|GrEuQEvL*HiFZX(;;@*-TAx3M)e!HPhu&1i#FX#HEI)E-~ujV&lz4 zB&HO~zA&PiPHoVj>g%U!2%FBEf`0ClqC1Dg4-d__X$8~SQw!p( zlmX71BH=S=1u@P^nNG*J8{vTtGeeQ0c8&Q)$4sY1o8VK4rG$o8`C!lEF`WG|okD1% z8fjXg8qn^H>15>Fdtew&G4IsE0|#lQZP8dxnmVap5vK6E&?eJ~@v`X-jR)3Yc20;! zbo|s29T$!0SSM3qu%4Bn@vycmv}xFM%oG|wI-=Y7DtZ)23+w}^-63odz!@Y@nyo~d z#A_h1K^t8*FcdZ&8HpQ79nkSX`oz?+96`=bN;bg5$stQ|ksn6RaEdePlL+kT#NorXnn$_VGwA zBOE{(OQMAFI4on1MiUY`Fh151a^w{x5?A*^5n^2HJDi-uq4>B4lk&4cVrG%Ovgthg z`b6;!7=71~5{Op}kKpD>Ak z04ZN=w!W7Hh29aJJK8@5|Jq1_D8qh6?lmd6_a#OaeUj`Gcek2|GVa;1H=)E$YxfX^ z#Lr!O$+=-_QUgSofSzZ;#*J0#qCvo*bmu{tzx`G@whs|f) z1lV$ttN1SDp5*Maq^DY2GGi-ISYnghLJZh2zuHXhk%_kbO%VgvXH1t7LB&0h8;Ri= zCqS2yQ_><`+J;Ew-b0tz?xd7@H(*IZ}yl-S}3E+QVFqtS&{Mm5hXBDZ9gr;y$*AZ5dOtNAf!fgbBTJ=T1p zrpT1jT!TB$pgy-AHM~rRlx8QEa~3%lFEk7<*1*$jc) z=u;a!F_IK>7WUapL(-uIv1tm9v{+F4A~O&+VmfqWXEJ4zhcq6f&`S?lprJye_3L3U zXrI9>vH&_se+_!+y`3#fTfQNqM2E54-*OpYGOOPb9~Z3s{}9&xzYJsD$JGbayI|%2 z_3D-CMe5n=DX{MUNcCWKOdV4DVBP;}buV>KwNc#-R{a;N3skr2QZrRiWtBH zpOyEN*J0OyrpHYZe&C3I#?yJFWbegV4K(_YzVdbA{WYea<)8OmSn5+FXZg*%0tgzJPWgbRhUgp-BigbCpwAuJ3Eny|mHx3EfR z6P60Q3YCIKm@mu`oI-{m2n_#E{#*WY{$u`K{x$x2{we-p{vQ4|{cAK?A`8orxf$v5-6^R;{#zmU)8bNMV@;W=0*^1b~l`(NxI+TXIjY=6%F zxcve9UG|&p*W0hOUt~Yqev18g`;qp8?PK;Kd!N16zS_Q*eNTI%eK%NFQfyyfciUa| zOuJ}jxk>Kt+!x%Rx%ar&xfi&nxktErx!bwlao2E{ap!Yqa3^ucaEEi7xdXZNT!7n; z^K$K63%3VX$CY!7xOvZX34s+YYepW9zgnw=IL!EY}P{ z^?mCb))%eMSRb|CXT8IEqxD*Lrj0RM94%WAHY038*oZKWFoqCD7)3Y`VFV$BFpMyS zFo>`LVLieC!a9V0ggyifA&3w_@FVyT4nXKd*dJjn!hQ&A5cWmb2VphB-UvMiUW9Ih zE`&~my%1I*bRe`NtVCFWupD7egf@g$gcgKmgeHV#2#p9!5gHKoK-e8&2|_)>ZV0;~ z?1E5-P>WE50H+@l-BuyM$AILn0s%hyBX?y8@NplxgAe)$z(;&$%fkAL5H3Wx0O5Ru z^AOHOI0xZugtHLNL^uQCbcE9g6fU_L;Ur+<N8t(`uMo--kq64Rh;hHO#H2)iAf7R>RzS zS`BmSX*JBPr`0gGo>s%$dRh&0>uEL2t*6y6x1LtR+4^@2g2iS{5TLLRg4UgiwgE0AW5t0m3|le1y3O zc?fQVIS8{6W+CJv$OcVnmGXfyDs2?s6$^wsKFpnO`v)6@H{(U7-Ay$~rMJO`isW~6zrs1- zD~%Jz>4#m-1A|)E7Wfu!s8j3HU~xh~OF>c_JDVE1o7~+EOII|x*QDTBD+`6P;D%o+ z-=^WQfw3)Z!L{zT_U@*ZrcU>&&bE~eo%^_#H|^tY@OF2!wL=mso7%hG?H%yvU9n=Z zI}#s!pt_N)BR2-uncSUC%}t$6?Tt-c?ln3irna^)N`R0>7#Yc6N4tAj(~2f2PGdt? zW5cqh#gT-TX(QTj&^J6r%J-A0#e@(|FOfvjjg%siuy@rmBF9L=JCUb2t;FRCL=;#0 z#KfWeiKKsbepdO$)~$7SH?2mExf?rHtRTf}?|=eE#fsmKuXVTgG;}t$Hgp!2S5*~J z6;Ba>EYH<8kNu1g(9huC*Di(%|oKMp>0U?GFtRbGzpQ4 zs#kPl=>JHIfWk>I+(`SBATzxYX&xUO+_KlWZxFtI(}JlYNL@>ogZ>RBtT%#@5;xFK zatHhagV2sn&6Z$Y(bhJla1vXG|4f6iA{6kEum3tjo2JgU&IF$$jSmec^O4$|Cvc7~ zs3;w@qR`9-e8a=qXe0Szw>PmgZNWqqF}}hidZ&mXT5%G2rx4XvFHft@F>7PbRzn^W zV-e!3O`^A<+9Y}z4Sy%9js7I1(a?u|@&oz*#zf+tV?X;5_g|~c2~UZHD%L$Pq=m

ktfL_JLf1VG9M{Y+tV{l;_D?Boq$s1BWm@wx9#O^f;vba9Xd}m zP}I0|pGY?X9uhhQGn~*~M16a-(J)l3q{jP5Gsih#kB_bO)C%>;F#7mAd%Ok4dm8Ld z_xOc&rw7OK#G2Dm=mz|FYpREgmUU+Wlh&fv#yOfiy2cU>zZXC5=_So`q92!1eTiJg z7^0d?WFimzRL|GN&{w5j7bOM&`9hDtYU~jtvd}zZk08;@7Rhp{W(c55JiC(|Z#8N$If6BI|QbFvoNroYjCvaPsMz&PwUGCbBhrZ+W_$eTMlo7!61>0k(I z|5W)hjA4w!ow@o@Tu0}-f9A%=65<(MbtU>6lWHH8nt4A#_z6gJ-cB2)uk)aoK$ zQ&o?mRTi5AzO3Jenp*fk=<{g3VePi1#@Sv1jV<{YqX!;$?o z&PWC#V2O)kjh?&Mh&Dp`@0n(gZ*UxJE!NoSBPP&B`u%^>+(w#^mOxqpX$hnykd{DN z0%-}PC6JatS^{Yaq$QA+!2fd+Ame|V^(vFPUU@`(gI{OA3vSZCv;@);NJ}6sf&VEI zIA&S~m*w%;PuzPWY%oSv1(4M;^d;PY7EZj6%s}_FHSO(A^mF^dvaokscPlLSY4mn> zwe>V5^E7ltmS2&DMqRMHsj=Ig_+p?f=x*q8FZG2ZE57iJU|Vppd#YCsgufAAS|Cd- z2zlWW=RLO0-Q3x+GAY&A%VBsS+R+}n#azbjNzPXlr^sGBOLJH8&(CgB6g2yX9O7QE z-vT$ls^Vxdp&Z6ikFAS^#quSnl5nqz@wBzK!}7L0VL@AxBnW477py;vaYJEJXsOk}BH1`B#qtaKZapR*!Zkhq9aFsLR8d6VJH@k;*H1BuF5xz)+NhWEKe(0=>UMfK$g__$9|h=4A{bb(<*kA$9>eLB*j`_Vz z2y`+%_thB4$WPtC`Ve6aM$XoR_$i$pLmW0Zf$=!PxNGFtOkfQ71_R@RaKIoQTAXkT z0rn;Vbjd=1K{&R#*Ecw*Bd{!Wv1Zf9k<&j(ob<%P-eGN1%mr-o3ETbj9BNt|a^?pd zk~NAc2jP88G6jM;Ap@r46kq*Jb$T)j*kLa@Iw|gkUznTAx0tSt9jhBX_k3Zl3_ho% zh-F~7FBFZ1-0%ytNGxEZ`r*8U8u1~q>}fK?C$se2;%LB4qwwKb{2jkgnFZXrv4gZJ z6TN_+Ja-23%7s=L7cid0=WcP;ZQmEd!KvjmGk7LY#Gdt#DT#dUpsRlS zJ`i@-T`_D>gHtHsL%j{;)bJ7CC@c_w6XC*BkojDHZq4@95FU<{r)iVJkrD%^qVc(_ zbL+S7ZAf{11nO^c%2SZ}+?9}Wk0E7Jg%eXwMdNdqcS-I z5kQ@xQ_YchS3`UELn5&lh@a}jNDkgIeF4-Llc=Wy^6w zVZ$G7{@*Z*;6FCCzv&zB?@oyHl}aG)9wX8R&C4zzM$4~1eO3W6D}nR*CCj%qgVnXQ z(e~A{d1x@`?@d%m@AwFPoc_2k@3shvsFa()yl6`fPnKZy_Vp0G-eyMnGFYF<>ER*r z(KYGb7)gAt`Bqo?_5lc(&?`bNQibD!5ds9d2m-B34n(UfT_+L=6eB>OBVC2t`yo(4 z0n}XRq8OV*qZ?R9TywYg0V_O0C~LFhH3-M`&_>2vm(G9G-LwrF)e9>kt>Hwxn z*Zl1PV9Fh3-)W3-ZBpC~FhA(3 z-F^Tt(+5F`id6Eba=sHMme?xjkk?G-5(IDu>kltED zdQmCr;gSdt=nPO-CjSe+(wULGnRcH9c~`nU8B1Y<6U@e~(~kH4h-F4~;yo)~HT9iMX;;eLpY z)+jd!x(yyG5#VzMAyE^K=BPi6!H9nZ_?&*?4?SPl1V9f@)&)U$0(_32_y=PC5RiC8 zfX`V={2NW?-FKftGidwTSdvC`j~tF`*uDYe4G(a5#+of**hXPc@3vq|)eAk&p{eL# zALjeBWrj5^x3GeImAnDYr^}SSk^UfEA)O*^mJWc^=1L_&{7k%Gyx#Jm9l-Cw&xF(2ZnB?aABL0H^0B2KWq1}CE(ZQIZ0 zvDvM!S#Pm!wRTz;S|#=q_IK==?EY*%%QA1k$$J+u$1r`&u8bm1h^xh7VY~38aEAH^ z<39`gfBVn$J^a&u3yx|B;Xx&$y>#PgqDD9G=#|-X&7TlW+9_w`n9&HpL0;hfB`ULw)ED+d)G%u#V)TkW&(QhxLl`g{P2N06M0L1dbk-Oaeu3fcdg{ zm8Xz_ZCP0~B|L9AdR{t&NT5KlOG|1`fiYj!BpEzGH;xuC5DvqCzHpBX8Ium6(*dE` zY@0U^XlpYjXC~507ffaWO&29}5n)L%j08iQ;IQ?W8$#!2P0lc+1hoPF^My+?vL>e+ z=z0!zTbMDKnULCUxmlAL23{x^{O1dI&2UUk1G+vSBd3#+KfbV3bWExSpOMkf2>Ig+ zi`|0L#C>k!uOgjR*e(BU)MJnAm0qx?ZU0SkySeOUJCZ?|!JK8gM6v|Z-+lZk<_l#x+pDS?(7N;Qbj=Zcn z6WfTEVcLxb)X(MBk$1S|?1_^O2NskAZwr#m9bv>yuDoOByor+z1K<376u994UxU*j z%f*uwzy_lbTIYvDqeHNJ-$9WYk#Nfwnm%zCPnQ3j03Q_2oh*X@(5Is*kVhL-^C4f< z0kOWHyLhq`nCL77v>wE7?{)Av=o;|_NQn%0!2Vv=+{u!pI0r+c7$-P@>0OAkc<1AM zCu{zs2iU3oMY7P**GDQni8bPcc;AM27w>$$Z$VxcCFHe@Jef(X)B$n6nN>Ww5SVsa z4X?R#CyO8eRX*evb_^xme0qiWp3j~=SqR?nyr)7R2sez5`nE&>7#_}9IJp2A^yYdX zylM~@rg!+)Yk{%28(@CWQ9L>S=j85znR6!#Ai$66{LYz+C+7jvPU`$dh%-Ma&IFyG zHFN&tTwwZ1B_203Z!%Ah4XS!n8Bqscc=*f(lWySATTJ8A!RY1-56W~*&H+xn1&Fog z`=}+8v%!bX>_a-yN#4j4C-I&y+&i;ma@NlZ(J^kK8KY*Akl+ny;VJ+;x%&}b}fa>p;sp9S28Nl(f| zFW?6;l}~IVk9`=0(Yls&(tHwucbLwW7f);>9RuC^#)E%-wlnv5u(F^u;GR;T;R%?B%PbowpbwMPm*sl^&VVKch)57z$EhLlx0nl z-b*4qYgWc2>9ZKiI3uTMl5|gYri{}w^Clxx=Cl+C`$M5Y%{P2#)WH{K%*dG}of2IR z>-QShMz`RVFO;P0Nz(T~--J#;p-{Jj47q5B9<ACv?+;v z0bf+NgG?;_qOg4S4l=itGE)UGBDYAfQAW!2Qxf@X(;Y=7lNlxx^&v`1zLZ1~n1{d* zM%r_DW|G+vIy@Z>Z8Exn_12lddUoVJ2YI{>jf`!f7x27s26!G6>0yFrcsu|*^O1KH zWM-ik_w7qTgP^}DgrG2)NIR!LGbqVD8+pb+&|nBUd+-ObUbZKU5@F0oA3xwl1VH|X zR{?lE8}o|3_{lb%GY7nGj`b0s{&t2!WOzaV!k(Q245yjQw2ncv;L{89f-r86xq$7g zGGN;fYyY5Y)8*T!Egc^l3k`R}BP5AH0+G0@O=f5X$xv!1TfuXR1$nFa4(2SA@x*%d zIrVY%0rf8RX7zgYO7$Z3Z1oiNc=bs2V0BC#Qv1|ib+x*ex~JNx?xt3$#p(jpt-91q zRa9AJQu({`h4N?RJ>_-f1?6ew5#?UxcI9`2oAp@h5!Nl%QR@b4(7M*zW9_iES{tmpSSzfHtp(QER)=+(mA6{h9qc#kU)hh? zci30iKeA7<53#>zZ)JbOUd3L*p39!bp2!}>9?EWHL+m=%$L`B^u`Adnb_rXVy%u8^}!DGz*Fbi=La~*R9b0Kq<_>TCh_($;-TV3F(j@Ned4}im$*V~5|@ZIVyRdp&J}aS8KNxOgdc?O zgfE3pg%5-`g_ne9g~x>Zg*%0tgzJPWgbRhUgp-BigbCpwAuJ3Eny|mHx3EfR6P60Q z3YCIKm@mu`oI-{m2n_#E{#*WY{$u`K{x$x2{we-p{vQ4|{ARAZ@jj3+~U{d)({X3*6J(Biy~*?cDFUYq-m}^SLv)lelAG1<7XaKyE!3;P&IZTszmo z?ZMS?<=i4}9yg1d$*G*3GuyV?zP5d4`;+Z$+bgy|*q*RGXuBKci*B&}+IF$+9NVe3 z6Kq>;huFq#!?u3g0k(Z?ownt+Wwv@-wXMWfXv?!@+oszjo7MU+>p!f2vwmWI-};92 zMe8%xN3Hi+@37uzy;d5PHb_Bft<)oRz-(57w2M?BEtU$T*^)z=Ch?L*+#!A={#E=) z?v$6qDwle>8fL)?qWQsybMl$naE*cG$9UH)4BO#YMnw)~3x z2l)y4LHTYuTb?dUvQ_$*^bhH8(kIgU(i^a1<{9Zx=|1TW=|<^V>2m1;=}c*xbgXoQ zwB^Tg4Lfqsvr@MpY)06Gun}P#VGJRRFp6*>!U#eLVHjZuVGv;h!g_=Ogmno02z>|| zLJ%Q<;79Nw9DvY^us_0Dg#8fKAnc2<55j7My%Bm4ya?S0T?m~Bdm*es=s;*kSc$L# zVL8H{2yFDdA~YcEfv`Km5`=n$-4J#~*ae{up%$SAp&FqIp%S42 zp&X$Mp%kG6p_qWn`&)$HAl!g(J;HSe*CJela5chJ2){dY{!o3LhAp9QT zZiKrK?nJl);dX@E5N<`d1>t6dn-Fe9_#MJ;5q^Vk1H$zP*CAYsa1FxM2v;He8sSQW zD-bS6xD4S^gi8=EMz{#!LWBzt&PO;8;ar4s5Y9$83*k(JGZ0QkI1S-cgi{bsM%adM z62h+#PDD5X;dq4O5ROGS2H|LgqY$l3s2pU2VA%NgV@F5(4(2KA? z!dis=5Y`~m08hEv9|d^W)&2;;J+AhL z0d8}(KLqewS9?T%ACg#H-VYGoM|cn6U4(ZK-bQ!};Z1}$5MD=k4dGRUR}fxCcnRS} zgclHK3+D3D7R=?PEttzoTQHZGwqP!Aqy_Vk0FIW$2#XLFA`~GMA}m0dk5GUx4JPY8KJCXBAXu^h%f!QaeZ!k@%%<^y~?jGRmPc}%9| zbo+MuyY@$gmG)Zu;r2m$Cv%#8HjI9Ea9?qsaPM-jaL?M>VeI>R?q=a3v6ef?Qpr5c z+{Rqb?8dF;cC&PG^SKP$r0r7}+dgEw-F6*}X-~HuYztX@)+SrPwl~`fa}KL)h0=RA z(fYadMa$*Z-&xPK9%&u1?r+^oY_OJEbFD1<8T%s4aa_Tk$DYC-!ydwp!pz40Y`1N; zC`h|9%}hB{$k;9aVhh>XFt;JFCgv;VL*`}XamK}{jMcKk@}=ct%UhNgEKgYOv)pRA z&T^^cY|BZOt(Gm8kR@o@$I@x)*;n$7HPnR2nr>KR##C&|0pLQ4`F z)_Puux}G8##vz-za_G4#O1n5#+@3SoYN%;*EPkzjV3=&6 z($k;86j)$p6b>c}jdqQNMtb^^D4L6@ilpD^3l8`OTi~;R0d07!Cm2m{zo^S=NN-P_ z>b-`jAUNuSjpBv_EmR@VyL03Gj-_FMv*p{U>+7iN{iy3*8YXNEh;J;gt{Due zah(rVYPE8y=`EY5ppQ&JAD)7~VLDS3O&8N_8BakQv&9PU{gcGxkU;dHLy2e?O&6Ez zw7if+xn?F)65)xxd9~>X0o7S~_KcDGHdT=JK8?Ij^TyC=ZrxL06 zjyX(2ax7$#YHuGbkg7jI&^7yDQg-hWOHFp7^axAOFwJ4~p*j??;~tgNDAE*ppNYDj zj=G+T3*~(v{`Ow6l-5Vm7pRY9A^I+}r%;cj^Ehc9WCqgE6P;mb@4bJ~`MiIKx}J-= zp4DBgp{PrXy4r{WA!b;!7<&%19FUT_e@f~!@@&8u=<0$N#mQ7e2yrXqT4qe;VWv+k zN+gyIyCA!L^q1D_{@cM`hn30S5I4_X|-IOM7b!5a#j-M%p}Sg zNtDx*D5oS*PEMk1OQM{VMEO+`<-{b)38eEtv`Cll>mL);_mcVLtG~_f+6x((Wcj+P0Tf?XB>?B^ z^0=8sOD`!K7PW{waoMN&DGLI=`zzVR}$^%V)`?#$?h` z^01xPc>)5AvdP_H2nQoqgsH4Lj3&TD@VAb(!_#Q?eU+Ajjgn%90V zfHJTB8~|=!`{^_QdEo)+kvAO#f(U*Dn6x59n5H7og|G@?CBkxqRtihbMmP%r<}4xH z5}2MOaExGi)AW`3A5L*tJzafI{iFJTJxgT8e#tMbk?x1F`fJJ@Xg&QsRST3`iqx2{%lrm+$dYLjqVdd}S&n(^YyV3xRK%bTGk#7=?fo~B`ln;^z zV1}Rt-p3ZmGh_zds{TcKM--(Oq{rZG>Q&N((&^Ii(&5s$a-=e>-e9@ea)kVxS_*G= z8S&5J%i_c0O)yVzqD39w4bg6`wJuV3u}+7z z4A5wx8XL-IXn1vsf$hEsXeIU>;&_Wv*gQXAWmJFg|8)W+l_e)Y*4o zau@|>jJ~ma3?`=kNcV#-QittwN8m$k*hJj>L@MIr+^>kGXgwXI7liBmdRCD3m#oe4 z-V=4*PF!T2ruTvvr}u2~K!DXPq(8LULE2)tX$!8-q9aJH=SCVHme%yZYXOqXwFq>M zfyCBxxgKv1oo3K_E{O)YDC%_{hB_yfa?e?|jkXDsNR#VsBH3u|>gk0QaV=jNZ@x6% zJa4>t*m!e4dA5>OI*~6=yZ=vnZvr1xb@q?XK67W4B_uN;?12#W%*dvRvQ9ui$S@-= zfB^yoLV`(H+`WpsB++V173v0vPNGyT?$)~RTCLTpZLQtZSF82yYqhra|9#H6=iYPg zOi1v3-~Z?T`~QR_&pFTYoaZd}EYEq)=BBvU*RQEd{z-Q+GXTS#qT@2UtA=J9=6#6C z{dsWx^5FW=bOK>#k#3nBW%4n5322nGwe{% zmBl~J;y;?y4e+P7Y^JSlb!({7Bq!v>j?ar7M=2%=t!F|pc*W!!L(^}fHJ~(`^#d!( zzjuTGtQ-8Vy1}d6;MaD8U(^l0n(716Nt?N9uF4tlS$g^*b~UAJ_eR>b647NvFoiZSJX74bwcc`c+#;KmF zgL!aw=D~I5!R@8bNgrD~ms8bcMcl#@pq;U)%9_153oqKrrrN3>o4xxCFD&0xcbUDn zVxME7gW2EGA8MTY z7L{Q-dtGKY<(noW^ETmrp!72$ZM3O3{bZhi>3uX`l1~$Idyb4r{@cov{3Ztzpw2k& zKJN4<-I6N*qkGav!G~;Ev!on9WGs=X?qU;uhC4&Y ztq`ti9amki<5q@t+uU_r`WWNeWd=xYhvBZeU2uyd zSrPYJ5%*aUhpdQut%!T9h=bvyL=jKW8%SoH?o9b~C+!lWQhrR7P6k^y2r-3alv%OU z9~thH-*DHkH6*UtSI-F_XLB*Jv_U`fQ!Ztij;Xm&cUHGXT((HS=BZ;-(Ynkt6geBz zpVnPJldE`UtDd|nonxwwJ6gvvTO3KMxm@6CcId9vx~o=qor1LRv>q>T)thve9-{hW zfvdVvcXjZ9Gb4Ysg{TImzXzA-hRm0GEgMRqh$+M73K;H49n8E!O@NJ{Yz)i1Z1F#D z@ju6-JUdU5&zxoIBr<2}1)fRjk#NWDS(Y7!~7_bHKjo%`6{r$mbFeM+R*-ls&0 z?R`q5*xsi^>Lt$kA_p&U@H_|T14!nx96ZCp(**ebf5aO6%=Q{$1$Y6m0z8>Wy$0|I zVg)#aSOM-rtN;fPE5OZ&72x_r>P3L>B~mW{T$M;Y4{&iJ^&G%?h!tR4BJ~Ww=0xgg zfcix0DS%ap)RO?GCQ?rTEKQ^y2Ur+S{Ve#IV@^Ev7(i7#^;3W;@zkRL6XU5z07l1C z4+9K~ryc?*j;9_3=pRo#01%3&?g#M1Q}?K$KvS|03L{^=L6gwPtOC$#?#dRd*kUOz@B(| zF2MKW={W%H@$_tf%i`%-0BSrv6X2|PdImsCJbgSsLp*&PKwUgt1+XHXt^_zKo<0^} zaXfttOLYdeStLFK11f>5Il$mSzIF~^RVCj}4q!PW-<2F(!NKJmT*kqr99+V|#T>A$ zUGWUt+7-{RtzGd9+u9Y+T)2jr zIcVXanS(Pp*vvr_2e3<1fK430JWalh9BkmAo`dxqtm9xU2hi5+j*84Y4q%=pUy_5l z9L(WhHV3mffH9spXK-*l2gh+x#X%(p$8x~FSVd+!_d%DZ;8Qu6!og$?j^-f2L7amM z4kmFhk%I{wjOSn+2V*%H!$CO*qd6$!U=#-YYYzUw!B-r7$-&<__=1D~;^1!_ z{3i#WbMRLVKI7n14*o)5Li!U9J|-|e{U02B#KDIge89ng|0tcn#~Srsd~ zvMN?|WmT-`%Bon=l~u8#E30BfS60P}uB?g`U0D??y0R)(bY)en=*p^C(Us9+80CQ# zU0D??y0R)(bY)fFNMI0>TU;zgwa4?@hBJ(;2uW|5m4qoNp6%JnJ z;3W=T4xZ)U84g%0PGp|qz9%_&f`i97_!$R}aqv?P9_8Q>4j$&>BS(ITtwRIIEm%-E)24V1@f# z-)p|_`>w*N@a?$UZ;kI1-(sATf5dr@^J3@O&NCeU<=N}L!F{#+Qg@yERQD41V7JY6 zuj>xiF4t<;5}XnL*!wl8=hx94`ElqDV{d$l3(vhduHO4_dt*0{-^s>_q*=r+=uM{aD*bCIDQm)CUS4& zR@_R^7P&aGJ+d*f!g0UjBKKY1dvNaH9q%t(;mBy$K%7VTDE#yAW8uBwE5jE$hK1LL zmxPZEkHRkaD;z0zJnRoUaFYA;&_|*7LvM#(3q6l>+z*ECcFb^0b4+rKc75r3+V!aG z7VNvf%yoh5)X>GD?V;tNnW0gkFm6eF8|S+B2Cs0Q;+`Jd6g)XN$MIvwIXGF~FK7>Z z8h9u0bH~%pDb9(`(avF5f$#4OIX#Yl`<%WP9Dl)n{8t@YaF60T-#p(;-!Z;~Z=7$W zZ?Lb(*T?7gJsGHUZ3;}nZ36uQF8^n^LEu^39dL*L27jAh_1x)S??27I$bY=Q%zu>f zrSeDI3h=n&O|1Oi;B0rF?QU}Z)A>hbvf~Dn<)w~u9W9P}#|lTvF~KoJEdB#xHH;R- za4{7#8=bhVv93A!NG|G2&&_12YRpXXI{P@98Z%=xSgyfQ8XQIzDY~dFgN{1lXvz$v zMG);iqzwAvh@&~vkAuD(gb8fUC3_07>8~kr zy3;TH6(O3_Uy{|!xVR?$caHv?gHL%{*EXg9%n|Q%@H-CP`FxNpnJ$F$ce&=sGE4lBG z+y`{-y}FBSl3=o~=eYXT^bd8{E?N-OZW1;gWmqP(7lKWJh<0Re1 zc8;*{i#QH#3*#5H6}I+oZo3cVw%x8=)B)bQ7Syj>-edd%Fl7DYq4_MP_vE2M?Kua1<9Lb&o0@sBTqf5 zS(^`8mk+rrAM#W*Ed20~51o+ez(t}`)M!bU}!bWjNJHPZo7>Y}gV;A#$_(-IOoErB);c5(onmXOeC2|%YMaH;rKDZL}8xmMt6 z+I81<-G%%1Y2aAfRKu21nTwroVy=^wh;0P97TvBaw3RW}0k{S+*KKh1=lN|W^Bhu; z{)3QLy_^=*>Ngn+dwvTd`421NE(jdOI_gDGy3n^TdIgw*`b94TgzFc*1mLY-^rF{#F~d?pq$esXRYYXzMHYvz6x=O+R%d0v*q(smtL`aQC8H5voXY-vW0~ zbKhhh+ESy;{;Dgf4K>%{h(e1vM9`9p8bVD$+ftbgEiODT2-oHP4Ww~_XD%{ zQEI}RS>NdUA9--g^5Bj!L}6P~@-JrZpUvKf`KO~)B|CY!O5<70CBZ2+wK+8l;H>7< zOn{c=R24u&bE+(Op6%4;)G&ah&8fiv3!4|c3oy5N(K`UgH7|M_U~2QCUja;NUi20~ zdGn$-0fsj(`X#`?=0$G+6f`gT1wgoY(dz)-=0$9bB?TxonyNp!s@f^5=*+fayY%e3 zwY6IsT9bpQSHsR9{etGIn{qI_b1*;5!CXhNZ%$34*f*!fQ%p^bCt>$U)3%zr7TR=p z636jP+uE_glc~8iHEwFcqAEBWlYb0dV+(Q}Y0;y!J*la2cGK!Dw8496)0WliQ?0el zt;rPaLNF-We!GRZlrY)QJ&A30&j~fBaZU0Jb4;2%qSVs42qVlwO6Y7#=!ra@Sxt?t zH~^`I;Hf*cds4c4(h{oaO^vG?png|7Wsf7=*EXc7F?AYX5#T9+M**G$=mV_bnoG_1 zG&Qw7+b>+kVL2VP9r1#|@5e zOY>UEIZ|yu3pXK(yILhhKyoUDo=0QD)b=yMDK1ym55MUSa6J+$SKGIPOS<6Mm^zzu zad6)sOQ`MJzp&BEmik)B z3C`ukWokP;Zp8L%~L)3P9t%Y6b&hx-F=+gC=w9g#jUx|W; z9%nfW&&$#AYCFBcBJK*O{Ii0f^Q&80EM7=`nnKXSYP}Be(tL3?Zb@?#CLOh7}MzgG*3n=%ENvzp!0gzw|(MgSU7Tc=ZP) z@Ev8k>;`X)c+NX4-@lJHRF(`exp$ybo2CK%A*gJ z2i-Q^i?oCXgO_fZ?gejX@N~7EUg{I~_wqJm*`=QsiF$lT1^P64uTS)8#K<}|I-g+j zfcwA*^l2x9`^fq<_iYnUw#yLCT(-Tbv2L{-DWtdgL<;#g(2kFrJV@d8(R0;ydWmn= zEPiwNVU7+Sh6G6su@P!Jz04=B>ZKOQi^gz)ZyoW`8+?Lq3q3@@PU#pf@YP1~O;g|# z-&DR7LUx1q^cY%T4R}Qh)Xptw0336PV~NB8RbVB^8UV)x8m$(CLzJ88@e-2<+@+PM z!6$-S)L?vi)`JQNo`P{GTY53)zg@Qe0Z9EqDfRU&%pRoy#AO?RacluN#5mU4gsk!7 z0g_WGbp4$;PcU&-&sioobtgDKAAmg02dBtmYm;E(5Ii4J%H~Na(@qg+035#?29D~( zalAkr$-{9xF${HPE;vM;A@Mv7h6`~$M0uM7K2ZwN9%ac1&ikl7%$DM`))?2_gTXON z;;_~j*Iwe7DRJcF9Zx~1?K66lo;FI)@sg~Z^jtOs9LGr<6dSlSfIOWw03&7njkq* zL&oAQpmur*N2Jsk6ATylMo~?oCvc3E9;SAVj1E-W=^Y$)%RlrWUMcxsNGqjS>EW9m zVjWrlotfUg5i^9A^>~R3zmPFx0LCGD=BDRy$W=_Kr#EkUmHJo|v5t~rHRdI*=zy_m zJH2<)yIbj9{f44Vm4aWiDQya^0q9(BlO{`V-E`lZJD(^;YnLCm(XTww9gy^30VEBR zB+(bN=-fAvPE3#6i1cX6u%Q$0B%PR^wh`$Oy0CzVX$R?m^r(#}ds(-Y-QZORftQ}S z=>_ivgP>p012<;<%B-h$qr0A87V=zfJJpr3MV`c~eDAp)_iXf3dq#W0?*DZE#{Go* zfP0(!H1`~Lxo5AZJ#sr%+m}ZY5hu?0KZ5)Gufz?1r-o;S$AtbF`d#Q*oD8@obZ%%> zXd&zY0o;QAPVmX#UAT$w4BR0yCs-bg1U|2LC< z{3SSf|2p;$wBe4sjPes@7jCp)gcJ83->-ZR`*vavLZxpY?%uo4yW6|bJJTEWe2zQ( z>Hn+FyI`kS>6~J}@5ukdWKgQ!+JXXP4c~6>qwb^kL8LcGV%x_?)qV6VNMRv=;6#4E zVq0mUy3Y=xc&*5INup&wNH&Q?M@Q&aiM5pWQ}><^61_Wz7aToJjZKY8Y(sG$b?_J-Nl&s;xo21u7$EXKL=|soZt4B#o)B~FS zDP9$|yh;iNP5tcVSyC`)+NY2}S1&-3CqVC#f`ODzbY(&UQ7~)a49okXLm*x9zx6U{ zk-Cp&|6&9(8iZ}%e9&mNFK9-KuPPce=px(Y10bnYk|ee9BHP9N3)Q_VKxJvu z-u~e&c|-|X@^X+^()5;GQi%}M!bAuoHv{{PQ}@!eof(i2g4H%-&w|CP#G(Tri)QLV z79EbGUnN$AMA zf(Q@z$nSr}LV>~RUYZB9@edgi_kRkg#DaZd>fV$t!Sb?F%J1G-4(??>I=q)!ViKuH!xR@y)qJ^FA8O zwi?Abq;H{mfCeJun+4FjBs@?*UOhm=(Gk2*KSAA3FNKMB^Q^ZSTK!7-xWOgretIyB zX+8L+)+gk~g~qA-17HxfSeL;ohsCRuj~-O4?)M`=-bz$HIy6SzuYkd20U zn|(_8u#%{{-v@SmOs3c6v}fdog+{CU=`%CsRhC6>sOqA~qm&m9EL8W?#}+kAe`mG4 znB>ss&5=XROqattsq+3Lhd!~8qc_F!{**WRm^pc47gp+mm`^DWQQl;IgW&}c50&mG zeS;UHr6rYn2dVq|f-00%)IvI=ssVJR*B6ga_mRO;OeCnDwy=Y%h6{W@oK&IiBcr8w zh!2BA&ks@kuo#^mG@~iozCQ@5q8i#ms;(OhKB|^I@LfA( zpt_H0Co3hSgR8*&FLHOypuXxps*J3!0Uhrf$7~%#kV2}1B85grYipkj8dW$!({+lN z?aHH3>QvuEsiQdQ{$mY5@+HOP>OQJuVmf8o2xszu`@*79b)VL)Fzpa!zh(1EwoOlR zDzUT2LBo{moRQ$OO8-yLWc5oG=ISmbRzD0w>NzMZ^N?Cc%JJDi%j3i9q2N6$hj&#R zyk}Z?Gh;E9YzLBkLUap8t2R<}Z08m=aq$5iyd^VNTuBBE(3_ zhMc;ZiHMQ3irg5N3_^+Zh{|S5sBJWhIkJT$`zJ8`wIvZw^q?56Ov_dOdy5fACpNAHxZF9iH-gE~Oj92&p0cN1K*&v813964CPIe=v zKNKLxv!ooGZn89a!2RnGxM%j3`>iOx!VGXrUqQ1@iW|!G^40GwLoA12Ol~9NKCAx7 z7`Vh$bCMIBKN*I{D9OmrIvCEWS!(GrFEC3!@ZXDtk+v%EOC!5>MV%%XTl>KG<6+2W zCHSOzph*IQA(nF=B<^E-%iTq3CgZ<7nL`Fmex%|o3NytV#&kOw!kGLBvw7mqn$6^Z1}J*pz@`q0qL8VE<;4wnfxf9MEfO3-nCZh2X55j5sVG1gS~ig4 znI+#>hqRgeNZZ(HD5Wt7BT9+VPg`aL1kadIvD#J+o@kW!a(pK*|66DuG6ID-8e~z3 ztIgM*Tf7iBs1y~b3?W1VFjmM77x+r(6ONJ+C+@uz5IhBg5c|j@iM?Mbq?Srj@zL2j zy?~I~mtr0v@rW1&1dkFz%)^f)W@l)a+BOU!#7d@q#hdQHi{bw)0@u)WF5{!d!5Ro7&K8Ht<7ih4Z^wu(qF=gN?QzEqKb+u^aKRYvqQlXm3Y=@50nT9 zo@b^bwxXV5dlEaZ+6pB}A{GH5>7f$jX#jXcp6K>M`UUMaN^Qa6!|yGathV(BPdv_d zr(pUB3!dp__B<#^zXh8#J!Jh8Uk3FNC-&$TR?INZH2c86YkY;;MkW9;apyZsjBDjv zau5jh1N#1C7U*Rx?Gw=Pka<9KJUsD6ELK0_Xrtkhj00l03-dKLui!RM%Du{8oDhnuZDi773M7K&p18`zD23%ik!><7=N2-wV6fUo%WSN(K)`0aR?vcbb_G-lhrnpIla|}^8qEr^w!ZT`P=^oa1upHk zmYjtll0wT$-L5w5B&&r`ROBTy*^QM6T5Z@#rVJs;vf40J-AOi$Z#AbwSTcNko3Pkj zyi>Q2pikFL3`MpJrh!HV64C$ZmI=_Jh3ZbSh=`AhFUObLike3DQFmIbB1N092(eSP zis)10qJ~M}w^&7r*2hZKon#de)g5Q_>RS0`eaWd5ts4wRvXS)2s2~MoF%c;?a#}YH zB-v(SWV9|?pzb7_iN(%Rl$r|ySx2mdnpqG=_7M?}Q8G)YWGohvq7$iPWDAK=GK;8W z$U-7nREkcB6{|bRJ|fz8%a&D}>Rb6=rD#5p$wtx*S)u*^bDgtok;fxfL{5tg!kPKI z!`pGQ+aR2de>!wWs3EjCGz4q;kKwfYTI}t&;RU`nynH{%|0n;Qc)@W= z*X{PZE_9vd8shvL-mKg0-0Ym~EOLD8c-C>1<8(*D;j_PMzu*3Sd$WDAy|3+KB==kY z`)&{RcYCnW6r|p{yYe`)J=oZ*Lz+r2XwzNZ z6yha)r5AF2ur0Qe?jpvLeEW#<;jo9vI~whc^c$h)>qi}Q2e{mH=7Fm^Tr1AA|H@7~ z(MZ|R=P_6=X|2n@3*7Oy5O!;k#v_(uP!{wakp`4P$H%n$l$0L9K`IDNb!z||?~x4B zZ3KsPf2!k`qrgFGOt&0wjKY3-(q<$Bo8^rInr@q4JwV*QD8V@yQf7o+4K9=M#_C6y zS4Khy)wLUL(!ilvjFI>Y`pd{q8|gRQruI4Hr;Rk5ZaJPE1%02i8!25qoZoRggS@nn zmLrAfDKC#vUP$+0Ll(~q;z3^6{#?gTX_ij4Dw0-?L5t2dRSHa-4Yr(!LfP zBJH{TQ!WO1-d>7UUWaf}E3c6sMnNc(A7Pqs8nA7RIZRJIL*rniZ?%*@F}<6uUto}n zL0lUP5N_3xhg;hRwZ0bNzD2EHO+DY~QvUc{_Hfn_$Eql5-b#e|RyD7dVq9@#F`iC~ zK+6$EeDU1!%f+BXSJJ}NX$U9!9FsB8gGA_)$<9dDFEQ^l zc9uKljznL1GH9Z&Y{Em!xG1BQ8x+kx$eKlc>oSSoyr5(Df&aJy@Sh~{n{PQV`@mmC zY0czM>(p|TLK-yV>*mRDccpMN_Sd&9l_VRx`W+Q<)YBAbx%JezM#1Q?3>;ECmQq|J z$(2fXB&B%Cw@vXdO7UVzvXtV%ao9pn1WnjNjnwIvF62QWy;1647a^pmvAI1`E(YPk zw1l$|;iNta{ipFx5<;2$2;&JM{|gXCd@;_`yVI2pJ6YdPKxolMD0FwHEA3wmLB8gj z^VOT>*e@xK^S)&o?VrtJ=l@+*c!DtWP3V`Q1GwXEerROK7JM;yE$-%D983iJ2EGWq z8rT)s5SSf^`M>eM?|&NS`5XN+{r#2Cl~-^5#jw}vkZuXOz@Jm2-c>m}FSuJ60fa#gt;c=PUl=k?BWoG0VX!r{)a z<3AnG<8Axxj@5Y4KJF;8|I7X&P6OOxzsSDcKF?lm`!BVFoA~UL)IGG!!&D$x3tQIX z0i&4>xVQ%maw|;gIS3_MUQ(>?p>-cN{xTCI-SfjQKlVb9w`i!NryoPBW7{@9hXhgM*EVmu04srS$_jL4d9V2egZ zAgTR8l9L(!BqACRp zq@oB0^AeC!G!^^aI!I5+O~sUQq?%L`In@=hQECV29n40980FOi*{u{!RQjnMq;i<{ z7Wif+r=Awg!W^q3 zZhMO&e*yY~S-J#EZ&BnOqIQIZ0JJ8dwF{^edS{2wT0`Iwtu^1WUOpVc9*>z&2iYRO zMcCu|B-D-o!U~NocU3SKgE)^V7^!xUosu=bADh5unQVLG#c)a8U=Ll>v z?;x9`*n>iLz1*|+-r<;Oc92z4q*+*L=GHgXne8?P2J*v0A)hRjJ;)DX6I}<{C`F35 zY{oEJH@Cj7VU6U3#2{=<9b~H%TIAtuP2K>k03EX3QoCYI1Gt6fld<5C?Up$lAB;te zWVh^1jGjN@W{VE8Rtjsfyf`kq5%+KD1T0x8d%^qmXz-G{)(hUZM&s-;*)GL+VXUNk zUZZo#WVOsgdzoltoy9xEi+Gb{hRf+smELMay zT$2ZB-3ps%EGhr;$V(;0IkM% zki}8(8`>kS#&?iWQRb)B_ztodig7-7HNJz4g58j*6v+N3Vlzsi8ha8t$hap+Mk!Qd z??ngM@B~Sps}&vB4|*w?>4aXYX{?6gS{(9Dvz5CiIlaZ6FFn8VJnFgCbD5{bvkZ6d zd)&XqO8q^Up`Yts<(}l<G%omg z@R{K5;I`n3;H==F|K0!2OMCinf9V8s{Srr1S zerElaKI(p5Sr#&O;Xpnq%S;^wlATVju-Af@6k}FL1g|AN)6sPBlX7h04$bS>dbAOEhB3F{En?ZUHB&p02 zj`M(gLE_h&OTw`Zq=OPkZ)6F_Jdmyf$@CQy_V<7$RYXLtH?V}g10?DndRuz-tq4#d z$AOnW+Mned!!r=F?Ms^=$)VsgxokvA#P0G#jWiBN(S< ziRnm;Y*{ikvLe99rdJ+jWXlfEOClp%mduGFl-`P?WMs>dDN!1CqGV*tk`Ylxl94S- zrb99MN|rUr$&GATGAEiDbDA_WvSrD{*b^fe*|M@}Q7=noWXl#?l1fIltZY;?N-0Q1 zK}JPU6ngi^>~C4Bq`0#F#u6~h zlo(L)h(QBN@w&cXpz171u-sTut{$K<4vn4NwStZU1BsR(lLt4hJkZyFv28lIF>Per z@|Df98@#vm^FDDY@7(8?0Z`8Jz6LQ!2Oa$&5Nkjbjmv0~ z-VZ^VB9V+H>3snt>Z#1_&RY#M8E9m!!aECT{R~La){Gp{tK0bveI~0DhHty+kcNqgMAlI zwj7G77+L`42JchEOO^yE8H|?^xY0mzg7XUEq`?KDYpLB@x00gK9T?O;CoUS0 z#Bj-vRcXVe$ph~9`qKJ;+Ffgl{72;F$ODnkROn#ny3l!{)uBb9qeFv2&frJES8(h9jlmtkrr@$*Ww0#RC-Bd}9|MmDZpZ!q z+XANrjti6rA~-AXfPa_&T>mQnLjQQb7Z!x4aAIH!?)Mv~4DfyJd(ZcjPsOQ$S-xRD zyZ1fskG+?BmwP98?VfjFk=W>&onPGm0=z~)9#!%%SH(brqYKa(Qs4nDO&V%_G*!(@S-Hl;d0b6(T zGmOYSkVSvbM_we3W;&v5blp9n)PqFJ8-gTH+Dg^ea8dM)QWzt8*AnDn6G-M;H>6g?boG(E>*{RL)oec<~+o;VQRE2tw6yYNrF3Z$| zq$!J8j66ijZVU@g^&P1mB#T5ZhRgBl!4cp*g5h$AdXV(N!w#1t)PrQI=yAAAs0V4H zcf`Xb!jWl14%e&Ua+rFMtRFoLm*wg~GJW)NxCA%ZKLodF`f2j8xhTFOO)bP%l;@wa zo6SX0iewcLr6^|F^=k|l_~=H_gE8>+c79%>9wg(5G`qIU&%r<@6v3cP%r!vsb1;x0 zMKEAIvdquHK&BMIfS{HeMWgCLGNho()oldEb&SWW2m6Eg-@0zJQ0<~wj%cq&_YxQl zk~}9gx|aaV>Rn5uP)0A}@9d{`Ee6Tl%Kg`ts9j{8M^1fqv~m=M#TGMM?OFr|(Yxm# zyZsv{sa<5zI8u$JYXJg@OiIaxHqmv0L^ASE+CkEvo_H+8zfQG`Lv0ySzzd`Hqk}%gx+WqT{EN<8Hpur zqU(5&2qe(B%@k-UqIcXIkUO$kv7pqGiEt@(k8lS zhAirvp+guhu1$2&?AR=lw23a73Cki$o9LoBu7wxcL>J9(WnR)Ix@c}IKCMwDNt@`R z8Le3iX%k&Edo_u{aNb4p)I4IKO?1&b)Wi>MqHChWZ~h_%_@qtiLP=GuZ$E&uETEp6C+zAvm#S`WuZ)Heq>l=Joe>(5&m8H#qfjS19%B= zM|f*^eRvUj2ViozJlxNBO4u9vI`k=C1b7`5gZn})p;f_Qp=qHq-{-#H`Le$ALVbe& z2>w2FQ|NNv^T9)~9$XeYFW4H~5L^(9`xXV?^gSB*doU7oD)$Ed5O_L}4O|`A<_iZ> zzMJu$;H1FtKtaIgI|J_u{=)yDKkMJ^-w{|5EDpZv-|Ro#zt~^p9|{Y@7s@BfACz~L z*Ocd!$9!{q$70XIExuj8cI6u7GUYtHK)69UUCAg5u#aJ~(pPc95c1u>?@Hji68NqJ zzAJ(M$r70UYqI2tGjudo<56CEm3?L1_SJQp>0Yb6NL+(``{?PvChOj}3#^p=n(TjU zQ3(6T<#Q9V8}aNP5c{=m6$Goat{Ai3(tql^WEB*9-+O(^C1$&^Z0fsYHT*U~mHzMQ zRz)O9d+*{0m9clps>o&>l&J=sS7U#Wjq9TQ-zDoJQ+|vv*UsqsE?EHK@c ziryt#B2ztpWa<^YjGZrgnYr{Jam%XJHT9d*Z!5cPKH{R+ieZ$?lnTcP#)T)cH`b*e z8{nMafGxnhk16vKJ$$Uq<}y#xyUXAa*C%Hl7n1H2jx4=rPAYTeb}RE$k$((!D;1|&1`wb z{PzI>R z)@%r}oMoIL;*pM&WsakSG{zsQJHw614jPRPvm7z-I<9#hZ`sY+9sdJ@E zdy~I4`F>;a#ZA6REMe?cpyc_IyGe3yU{P8-?wSsb1^fP!XG!i&lDnQ-5t2nF=4C~i zhm66?258A($0m}Wn0%$wiy)N#0D1BcIhfxIOw;xh`gqi$**Tbs{?0K>j`VnU^4Iy0 z@8&~J)Dp#()GIHHY-0UUrVn+RNL|&nwn(Gq&K$)u&44^96v;t9CA5tE%Ew=ZuDSMTRm6ggIrBt!|{@{Dk_ak3}Z@O=U&*%Nv`>OX|?=J5- z-nqER?=#Qao+mwbdUkm(^wfHe_mq43yZ`0>llxWogYH}1SGl*iSGZ@mOWl6gr>@sr z_qeWcZE;O=4RraOAK{(8hn;(!S2~+2CC+v6He`w!fZ^Ekq$Js~P>5V)nDcE<^NA0B5eqr~36=S0Bu>!S|R`Z2H4t96^ zfN*=E+9|KI>!zBCzETLHb#_hA|F(S3mMusRjBS zTC8@G-mM#-wzuMT1dM6;3-ZVkwUab!VV$pUnbF)_d!`1IiJnJBtDU4z3+p^y;%%s{ zZ#;3;2D}q*@__kTtkaWzEvLY91(2jU3nUea2S;?Wq!SAyWx)M3kfad{Bo&I|CLl=@ z7D&p!{R|*=Js4)jmKGpSzy<YBYA8!u>GUD({4>o(y! zs2RBL)Np~#gAHM{(k`mx>XsJ#C=+de0D;!oa|myMKr8JzgnRm{Sz2x91ngMTL~TDx z%^FMXIP;6-ts04Wm_F%b$fC7&o>eYuB(_S>GJ9TY z;>4+HmKNIC%uL}2wt6|On@|G5v>KmR@C585p%r;iw{Y^dc2xs32?mt$juF_>vOy0G zW3l`a-FQb3!fAzGUy?Ci1siYwK0da*POl#n9dE}8{p>n~Gfc7C_TTZgJB!rpT3sYA zYHo%`!yn@sKMm2<>8Y`pT*voQvui+PrRu;rg=Qsc`>j0;^y(b6?Q@`4<)R-0x;7Vm zL0>g{x{hXF46>I2yD|q$=~$s-wRHT(_9@WIbJ5>}jMH?qCiyo_z@D0eJpo_q6kyq> zL=O2Bk^eDi-Aq&V5@*#nu8rQ zP|ea(F;6e9eqm#{p5B*7RH#{6FJ?=#7;tiKg2_Q3UC);y6fGqmSt#6qk)>5;F$33^ zKclv%@C;d&mYVa>p2XeySz2YzLwjN2AT0zyDLuI9_mk|Mx%JXkpRU(he>gHCrQ=&XaL!qc`n+jjIp24;XFGNA99)*T41 z_Cb$!ypWH5#b){E^})b!oDff&n>F$0_Q5GSN*9w3f-Y2de78NDZAAaSX?r$?F@13y zbwXc({O%MB>l0BV`f@tEd+UGb6w7zV0?`q%{RAjsOqYd8S8^)R{|k-ED$ff!%?NaBtv=fkYtS z|HA(cZVt@)JN#Sx)BPp5JMazVe%Ku@Q8wcKz+;plzQ6lk_C4U+>$}|7=u6=wK*;;0 z_gCIWy*GL{c#rnlJb&~&@43s<>Y3*me`H)I^E z=#niBBr>!bNZHbWnRYyM6p*r|fsjGUx@>7+$gM!imIj7Ab&}demIi$L#!gYt8WM{}F`M&{)tPOcc?}J1(hdiVwK_bgT9ug`< zr)+<~j;6SO+c>q8>|JKZS5IW(bRj5bk{l!Z-IM{BnF9GHHmfLz$vq#JdWD z$+D3ncq{~yRpYR!K5CTON#+Z-u8dOa_OM~x^C4Ktn9&Q?pG{Fa$*jSaiXk=M8N_@z zCGNqw#7;7Kyu95^B$@e5ds+34Z@;&9~ ziH`+I^u*#mDfeX{9Fva#EvIw3B+SUL8`r&bP6J8KfZGB7sS>}DVb{mtpJL&6-G?q> zGLWK+Fx^#F;kp6hj+Vq3*>s%)QbHmb*>tT0DK3%p22kPr0;@(9Ac;APFd(1kATgBiWv(8~ansZI}m~{@d^5Pz|&LKdG24>VxI}I;`t^Anf zb`FyG^^1HeY+s=&mVhLxqNxL{u)PCu10``-&8x6Ii?3I#hp}{l6^JV)#iiGpiT)8N zkSIu^q_FUgA7!HQIbtc2Vxi?a");e("canvas",t).remove(),l.append(i),a.render(i[0],n),l.data("badge-props",n)}},render:function(e,t){let i=e.getContext("2d"),l={base:t.color,light:a.shadeBlendConvert(.6,t.color),dark:a.shadeBlendConvert(.3,t.color),lightPoint:a.shadeBlendConvert(.8,t.color),reflection:{lightest:"#ffffff51",darkest:"#ffffff20"},radialGradient:{x0:.75,y0:.25,r0:.05,x1:.6,y1:.4,r1:.4},levelText:"white"},n={size:Math.min(t.height,t.width),borderWidth:.05,reflection:{angle:-20,offset:.125},levelText:{x:.5,y:.9,size:.2,font:"Open Sans, Arial, helvetica, sans-serif"},icon:{x:.5,y:.47,scale:.7}};r.info(" Config",n),r.info(" Colors",l),r.info(" Props",t);let s=i.createRadialGradient(n.size*l.radialGradient.x0,n.size*l.radialGradient.y0,n.size*l.radialGradient.r0,n.size*l.radialGradient.x1,n.size*l.radialGradient.y1,n.size*l.radialGradient.r1);s.addColorStop(0,l.lightPoint),s.addColorStop(1,l.base),i.beginPath(),i.fillStyle=s,i.arc(.5*n.size,.5*n.size,n.size/2,0,2*Math.PI),i.fill();let o=Math.asin(n.reflection.offset),d=n.reflection.angle/360*2*Math.PI,h=i.createLinearGradient((.5-n.reflection.offset*Math.sin(d))/2*n.size,(.5+n.reflection.offset*Math.cos(d))/2*n.size,Math.sin(d)/2*n.size,Math.cos(d)/2*n.size);h.addColorStop(0,l.reflection.lightest),h.addColorStop(1,l.reflection.darkest),i.beginPath(),i.fillStyle=h,i.arc(.5*n.size,.5*n.size,n.size/2,0+o+d,Math.PI-o+d),i.fill();let f=n.size*n.borderWidth;if(i.beginPath(),i.strokeStyle=l.light,i.lineWidth=f,i.arc(.5*n.size,.5*n.size,n.size/2-f/2,0,2*Math.PI),i.stroke(),i.beginPath(),i.strokeStyle=l.dark,i.lineWidth=f,i.arc(.5*n.size,.5*n.size,n.size/2-f/2,0-Math.PI/2,2*t.progress*Math.PI-Math.PI/2),i.stroke(),t.level&&(i.font=n.size*n.levelText.size+"px "+n.levelText.font,i.fillStyle=l.levelText,i.textAlign="center",i.fillText(""+t.level,n.size*n.levelText.x,n.size*n.levelText.y)),t.image){let e=new Image;e.onload=function(){let e={x:0,y:0,w:n.size*n.icon.scale,h:n.size*n.icon.scale};this.width>this.height?e.h*=this.height/this.width:e.w*=this.width/this.height,e.x=n.size*n.icon.x-e.w/2,e.y=n.size*n.icon.y-e.h/2,i.drawImage(this,e.x,e.y,e.w,e.h)},e.src=t.image}},fetchProperties:function(t){let i=e(t),l=i.find("img"),r=null;return l.length>0&&(r=l.attr("src")),{progress:i.attr("data-progress"),width:i.attr("data-width"),height:i.attr("data-height"),color:"#"+i.attr("data-color"),level:i.attr("data-level"),image:r}},shadeBlendConvert:function(e,t,i){if("number"!=typeof e||e<-1||e>1||"string"!=typeof t||"r"!=t[0]&&"#"!=t[0]||i&&"string"!=typeof i)return null;this.sbcRip||(this.sbcRip=(e=>{let t=e.length,i={};if(t>9){if((e=e.split(",")).length<3||e.length>4)return null;i[0]=l(e[0].split("(")[1]),i[1]=l(e[1]),i[2]=l(e[2]),i[3]=e[3]?parseFloat(e[3]):-1}else{if(8==t||6==t||t<4)return null;t<6&&(e="#"+e[1]+e[1]+e[2]+e[2]+e[3]+e[3]+(t>4?e[4]+""+e[4]:"")),e=l(e.slice(1),16),i[0]=e>>16&255,i[1]=e>>8&255,i[2]=255&e,i[3]=-1,9!=t&&5!=t||(i[3]=r(i[2]/255*1e4)/1e4,i[2]=i[1],i[1]=i[0],i[0]=e>>24&255)}return i}));var l=parseInt,r=Math.round,a=t.length>9,n=(a="string"==typeof i?i.length>9||"c"==i&&!a:a,e<0),s=(e=n?-1*e:e,i=i&&"c"!=i?i:n?"#000000":"#FFFFFF",this.sbcRip(t)),o=this.sbcRip(i);return s&&o?a?"rgb"+(s[3]>-1||o[3]>-1?"a(":"(")+r((o[0]-s[0])*e+s[0])+","+r((o[1]-s[1])*e+s[1])+","+r((o[2]-s[2])*e+s[2])+(s[3]<0&&o[3]<0?")":","+(s[3]>-1&&o[3]>-1?r(1e4*((o[3]-s[3])*e+s[3]))/1e4:o[3]<0?s[3]:o[3])+")"):"#"+(4294967296+16777216*r((o[0]-s[0])*e+s[0])+65536*r((o[1]-s[1])*e+s[1])+256*r((o[2]-s[2])*e+s[2])+(s[3]>-1&&o[3]>-1?r(255*((o[3]-s[3])*e+s[3])):o[3]>-1?r(255*o[3]):s[3]>-1?r(255*s[3]):255)).toString(16).slice(1,s[3]>-1||o[3]>-1?void 0:-2):null}};return a}); \ No newline at end of file +define(["jquery","core/str","core/ajax","block_gradelevel/debugger"],function(e,t,i,r){let l=r("renderbadge");function n(e){if(/^#([A-Fa-f0-9]{3})$/.test(e)){let t=e.substring(1).split(""),i="0x"+[t[0],t[0],t[1],t[1],t[2],t[2]].join("");return"rgb("+[i>>16&255,i>>8&255,255&i].join(",")+")"}if(/^#([A-Fa-f0-9]{6})$/.test(e)){let t="0x"+e.substring(1).split("").join("");return"rgb("+[t>>16&255,t>>8&255,255&t].join(",")+")"}if(/^#([A-Fa-f0-9]{8})$/.test(e)){let t=e.substring(1).split(""),i="0x"+[t[0],t[1],t[2],t[3],t[4],t[5]].join(""),r="0x"+[t[6],t[7]].join("");return"rgba("+[i>>16&255,i>>8&255,255&i].join(",")+","+r/255+")"}throw Error("Invalid hex code: "+e)}l.enable();let a={init:function(){l.info("Setting up badge renderers"),e("figure.levelbadge").each(function(){a.setup_badge(this,!0)})},setup_badge:function(t,i){let r=e(t),n=e(t).data("badge-props");if(l.info("Setting up skill badge on ",t,n),n&&i)l.info(" skill badge was already configured. Skipping process...");else{n=a.fetchProperties(t);let i=e("");e("canvas",t).remove(),r.append(i),a.render(i[0],n),r.data("badge-props",n)}},render:function(e,t){let i=e.getContext("2d"),r={base:t.color,light:a.shadeBlendConvert(.6,t.color),dark:a.shadeBlendConvert(.3,t.color),lightPoint:a.shadeBlendConvert(.8,t.color),reflection:{lightest:"#ffffff51",darkest:"#ffffff20"},radialGradient:{x0:.75,y0:.25,r0:.05,x1:.6,y1:.4,r1:.4},levelText:"white"},s={size:Math.min(t.height,t.width),borderWidth:.05,reflection:{angle:-20,offset:.125},levelText:{x:.5,y:.9,size:.2,font:"Open Sans, Arial, helvetica, sans-serif"},icon:{x:.5,y:.47,scale:.7}};l.info(" Config",s),l.info(" Colors",r),l.info(" Props",t);let o=i.createRadialGradient(s.size*r.radialGradient.x0,s.size*r.radialGradient.y0,s.size*r.radialGradient.r0,s.size*r.radialGradient.x1,s.size*r.radialGradient.y1,s.size*r.radialGradient.r1);o.addColorStop(0,n(r.lightPoint)),o.addColorStop(1,n(r.base)),i.beginPath(),i.fillStyle=o,i.arc(.5*s.size,.5*s.size,s.size/2,0,2*Math.PI),i.fill();let d=Math.asin(s.reflection.offset),h=s.reflection.angle/360*2*Math.PI;l.info("rflAngleRad:",h);let f=i.createLinearGradient((.5-s.reflection.offset*Math.sin(h))/2*s.size,(.5+s.reflection.offset*Math.cos(h))/2*s.size,Math.sin(h)/2*s.size,Math.cos(h)/2*s.size);l.info("rflGradient",f),f.addColorStop(0,n(r.reflection.lightest)),f.addColorStop(1,n(r.reflection.darkest)),i.beginPath(),i.fillStyle=f,i.arc(.5*s.size,.5*s.size,s.size/2,0+d+h,Math.PI-d+h),i.fill(),l.info("Starting with border");let g=s.size*s.borderWidth;if(i.beginPath(),i.strokeStyle=r.light,i.lineWidth=g,i.arc(.5*s.size,.5*s.size,s.size/2-g/2,0,2*Math.PI),i.stroke(),i.beginPath(),i.strokeStyle=r.dark,i.lineWidth=g,i.arc(.5*s.size,.5*s.size,s.size/2-g/2,0-Math.PI/2,2*t.progress*Math.PI-Math.PI/2),i.stroke(),t.level&&(i.font=s.size*s.levelText.size+"px "+s.levelText.font,i.fillStyle=r.levelText,i.textAlign="center",i.fillText(""+t.level,s.size*s.levelText.x,s.size*s.levelText.y)),t.image){let e=new Image;e.onload=function(){let e={x:0,y:0,w:s.size*s.icon.scale,h:s.size*s.icon.scale};this.width>this.height?e.h*=this.height/this.width:e.w*=this.width/this.height,e.x=s.size*s.icon.x-e.w/2,e.y=s.size*s.icon.y-e.h/2,i.drawImage(this,e.x,e.y,e.w,e.h)},e.src=t.image}},fetchProperties:function(t){let i=e(t),r=i.find("img"),l=null;return r.length>0&&(l=r.attr("src")),{progress:i.attr("data-progress"),width:i.attr("data-width"),height:i.attr("data-height"),color:"#"+i.attr("data-color"),level:i.attr("data-level"),image:l}},shadeBlendConvert:function(e,t,i){if("number"!=typeof e||e<-1||e>1||"string"!=typeof t||"r"!=t[0]&&"#"!=t[0]||i&&"string"!=typeof i)return null;this.sbcRip||(this.sbcRip=(e=>{let t=e.length,i={};if(t>9){if((e=e.split(",")).length<3||e.length>4)return null;i[0]=r(e[0].split("(")[1]),i[1]=r(e[1]),i[2]=r(e[2]),i[3]=e[3]?parseFloat(e[3]):-1}else{if(8==t||6==t||t<4)return null;t<6&&(e="#"+e[1]+e[1]+e[2]+e[2]+e[3]+e[3]+(t>4?e[4]+""+e[4]:"")),e=r(e.slice(1),16),i[0]=e>>16&255,i[1]=e>>8&255,i[2]=255&e,i[3]=-1,9!=t&&5!=t||(i[3]=l(i[2]/255*1e4)/1e4,i[2]=i[1],i[1]=i[0],i[0]=e>>24&255)}return i}));var r=parseInt,l=Math.round,n=t.length>9,a=(n="string"==typeof i?i.length>9||"c"==i&&!n:n,e<0),s=(e=a?-1*e:e,i=i&&"c"!=i?i:a?"#000000":"#FFFFFF",this.sbcRip(t)),o=this.sbcRip(i);return s&&o?n?"rgb"+(s[3]>-1||o[3]>-1?"a(":"(")+l((o[0]-s[0])*e+s[0])+","+l((o[1]-s[1])*e+s[1])+","+l((o[2]-s[2])*e+s[2])+(s[3]<0&&o[3]<0?")":","+(s[3]>-1&&o[3]>-1?l(1e4*((o[3]-s[3])*e+s[3]))/1e4:o[3]<0?s[3]:o[3])+")"):"#"+(4294967296+16777216*l((o[0]-s[0])*e+s[0])+65536*l((o[1]-s[1])*e+s[1])+256*l((o[2]-s[2])*e+s[2])+(s[3]>-1&&o[3]>-1?l(255*((o[3]-s[3])*e+s[3])):o[3]>-1?l(255*o[3]):s[3]>-1?l(255*s[3]):255)).toString(16).slice(1,s[3]>-1||o[3]>-1?void 0:-2):null}};return a}); \ No newline at end of file diff --git a/amd/src/renderbadge.js b/amd/src/renderbadge.js index 15e573d..0f30388 100644 --- a/amd/src/renderbadge.js +++ b/amd/src/renderbadge.js @@ -7,6 +7,29 @@ define(['jquery', 'core/str', 'core/ajax','block_gradelevel/debugger' ], functio let debug = Debugger("renderbadge"); debug.enable(); + // function is used to overcome Edge's non-support of CSS4 hex rgba notation (@nov 2018) + /* eslint-disable no-bitwise*/ + function hexToRgbA(hex){ + /*eslint no-bitwise: "off"*/ + if(/^#([A-Fa-f0-9]{3})$/.test(hex)){ + let h= hex.substring(1).split(''); + let c= '0x'+[h[0], h[0], h[1], h[1], h[2], h[2]].join(''); + return 'rgb('+[(c>>16)&255, (c>>8)&255, c&255].join(',')+')'; + } + if(/^#([A-Fa-f0-9]{6})$/.test(hex)){ + let h= hex.substring(1).split(''); + let c= '0x'+h.join(''); + return 'rgb('+[(c>>16)&255, (c>>8)&255, c&255].join(',')+')'; + } + if(/^#([A-Fa-f0-9]{8})$/.test(hex)){ + let h= hex.substring(1).split(''); + let c = '0x' + [h[0],h[1],h[2],h[3],h[4],h[5],].join(''); + let a= '0x' + [h[6],h[7],].join(''); + return 'rgba('+[(c>>16)&255, (c>>8)&255, c&255].join(',')+','+a/255.0+')'; + } + throw Error('Invalid hex code: ' + hex); + } + /* eslint-enable no-bitwise */ let self = { init: function init() { debug.info("Setting up badge renderers"); @@ -90,8 +113,8 @@ define(['jquery', 'core/str', 'core/ajax','block_gradelevel/debugger' ], functio config.size * colors.radialGradient.y1, (config.size ) * colors.radialGradient.r1 ); - baseGradient.addColorStop(0, colors.lightPoint); - baseGradient.addColorStop(1, colors.base); + baseGradient.addColorStop(0, hexToRgbA(colors.lightPoint)); + baseGradient.addColorStop(1, hexToRgbA(colors.base)); ctx.beginPath(); ctx.fillStyle = baseGradient; ctx.arc(0.5 * config.size, 0.5 * config.size, config.size / 2, 0, 2 * Math.PI); @@ -102,21 +125,23 @@ define(['jquery', 'core/str', 'core/ajax','block_gradelevel/debugger' ], functio let rflOffset = Math.asin(config.reflection.offset); let rflAngleRad = (config.reflection.angle / 360.0) * 2 * Math.PI; + debug.info("rflAngleRad:",rflAngleRad); let rflGradient = ctx.createLinearGradient( (0.5 - config.reflection.offset * Math.sin(rflAngleRad))/2 * config.size, (0.5 + config.reflection.offset * Math.cos(rflAngleRad))/2 * config.size, Math.sin(rflAngleRad)/2 * config.size, Math.cos(rflAngleRad)/2 * config.size ); - rflGradient.addColorStop(0, colors.reflection.lightest); - rflGradient.addColorStop(1, colors.reflection.darkest); + debug.info("rflGradient",rflGradient); + rflGradient.addColorStop(0, hexToRgbA(colors.reflection.lightest)); + rflGradient.addColorStop(1, hexToRgbA(colors.reflection.darkest)); ctx.beginPath(); ctx.fillStyle = rflGradient; ctx.arc(0.5 * config.size, 0.5 * config.size, config.size / 2, 0 + rflOffset + rflAngleRad, Math.PI - rflOffset + rflAngleRad); ctx.fill(); - + debug.info("Starting with border"); // draw empty border let strokeWidth = config.size * config.borderWidth; ctx.beginPath(); diff --git a/block_gradelevel.php b/block_gradelevel.php index ef735f3..aab1f73 100644 --- a/block_gradelevel.php +++ b/block_gradelevel.php @@ -1,89 +1,89 @@ -libdir.'/gradelib.php'); -require_once($CFG->dirroot.'/grade/querylib.php'); -require_once($CFG->dirroot.'/blocks/gradelevel/lib.php'); - -use block_gradelevel; - -class block_gradelevel extends block_base { - - public $levelset; - - public function init() { - global $PAGE; - global $COURSE; - - $this->title = get_config('gradelevel', 'blocktitle'); - if(empty($this->title)) - { - $this->title = get_string('title', 'block_gradelevel'); - } - - // include javascript and run badge renderer when page loading is complete - $PAGE->requires->js_call_amd('block_gradelevel/renderbadge', 'init'); - - // find or create the levelset for this course - $this->levelset = block_gradelevel_levelset::find_by_course($COURSE->id); - } - - // The PHP tag and the curly bracket for the class definition - // will only be closed after there is another function added in the next section. - - public function html_attributes() { - $attributes = parent::html_attributes(); // Get default values - $attributes['class'] .= ' block_'. $this->name(); // Append our class to class attribute - return $attributes; - } - - public function get_content() { - global $CFG; - global $USER; - global $COURSE; - - if ($this->content !== null) { - return $this->content; - } - - $this->content = new stdClass; - - if(empty($this->levelset)) - { - $this->content->text = "

"; - $this->content->footer = get_string("unattached_course",'block_gradelevel'); - } - else { - // below can be a single call to $this->levelset->get_user_leveldata() once the need for debugging (fixed point total) is gone - $pointstotal = $this->levelset->get_levelset_grade($USER->id); - $level_info = $this->levelset->calculate_level($pointstotal); - - $this->content->text = $this->levelset->render_badge($pointstotal); - - if($level_info->levelup_total > 0) - { - $this->content->footer = "
".get_string('levelup_at','block_gradelevel')." {$level_info->points_in_level}/{$level_info->levelup_total}
"; - } - else - { - $this->content->footer = "
".get_string('levelup_done','block_gradelevel')."
"; - } - - $coursecontext = context_course::instance($COURSE->id); - - if(has_capability('block/gradelevel:viewresults', $coursecontext)) - { - $this->content->footer .= "\n"; - } - } - return $this->content; - } - - - public function hide_header() { return !get_config('gradelevel', 'showtitle'); } - - public function has_config() { return true; } - - - - - +libdir.'/gradelib.php'); +require_once($CFG->dirroot.'/grade/querylib.php'); +require_once($CFG->dirroot.'/blocks/gradelevel/lib.php'); + +use block_gradelevel; + +class block_gradelevel extends block_base { + + public $levelset; + + public function init() { + global $PAGE; + global $COURSE; + + $this->title = get_config('gradelevel', 'blocktitle'); + if(empty($this->title)) + { + $this->title = get_string('title', 'block_gradelevel'); + } + + // include javascript and run badge renderer when page loading is complete + $PAGE->requires->js_call_amd('block_gradelevel/renderbadge', 'init'); + + // find or create the levelset for this course + $this->levelset = block_gradelevel_levelset::find_by_course($COURSE->id); + } + + // The PHP tag and the curly bracket for the class definition + // will only be closed after there is another function added in the next section. + + public function html_attributes() { + $attributes = parent::html_attributes(); // Get default values + $attributes['class'] .= ' block_'. $this->name(); // Append our class to class attribute + return $attributes; + } + + public function get_content() { + global $CFG; + global $USER; + global $COURSE; + + if ($this->content !== null) { + return $this->content; + } + + $this->content = new stdClass; + + if(empty($this->levelset)) + { + $this->content->text = "
"; + $this->content->footer = get_string("unattached_course",'block_gradelevel'); + } + else { + // below can be a single call to $this->levelset->get_user_leveldata() once the need for debugging (fixed point total) is gone + $pointstotal = $this->levelset->get_levelset_grade($USER->id); + $level_info = $this->levelset->calculate_level($pointstotal); + + $this->content->text = $this->levelset->render_badge($pointstotal); + + if($level_info->levelup_total > 0) + { + $this->content->footer = "
".get_string('levelup_at','block_gradelevel')." {$level_info->points_in_level}/{$level_info->levelup_total}
"; + } + else + { + $this->content->footer = "
".get_string('levelup_done','block_gradelevel')."
"; + } + + $coursecontext = context_course::instance($COURSE->id); + + if(has_capability('block/gradelevel:viewresults', $coursecontext)) + { + $this->content->footer .= "\n"; + } + } + return $this->content; + } + + + public function hide_header() { return !get_config('gradelevel', 'showtitle'); } + + public function has_config() { return true; } + + + + + } \ No newline at end of file diff --git a/cfg_globallevels.php b/cfg_globallevels.php index 8a72895..391ff25 100644 --- a/cfg_globallevels.php +++ b/cfg_globallevels.php @@ -1,34 +1,34 @@ -libdir.'/adminlib.php'); - - -admin_externalpage_setup("block_gradelevel_default_levels"); - -$systemcontext = context_system::instance(); -// Check if user has capability to manage skills -require_capability('block/gradelevel:skillmanager', $systemcontext); - - -$PAGE->requires->js_call_amd('block_gradelevel/leveleditor', 'init'); - -print $OUTPUT->header(); -print $OUTPUT->heading(get_string('cfgpage_globallevels', 'block_gradelevel')); - -// render page for skill level 0 (global) -print block_gradelevel_skillmgmtservice::render_leveltable(0); -print $OUTPUT->footer(); +libdir.'/adminlib.php'); + + +admin_externalpage_setup("block_gradelevel_default_levels"); + +$systemcontext = context_system::instance(); +// Check if user has capability to manage skills +require_capability('block/gradelevel:skillmanager', $systemcontext); + + +$PAGE->requires->js_call_amd('block_gradelevel/leveleditor', 'init'); + +print $OUTPUT->header(); +print $OUTPUT->heading(get_string('cfgpage_globallevels', 'block_gradelevel')); + +// render page for skill level 0 (global) +print block_gradelevel_skillmgmtservice::render_leveltable(0); +print $OUTPUT->footer(); diff --git a/cfg_skilllevels.php b/cfg_skilllevels.php index 7404ad7..0ce716c 100644 --- a/cfg_skilllevels.php +++ b/cfg_skilllevels.php @@ -1,48 +1,48 @@ -libdir.'/adminlib.php'); - - -admin_externalpage_setup("block_gradelevel_config_skills"); - - -$skill_id = required_param('skill_id', PARAM_INT); - -$systemcontext = context_system::instance(); -// Check if user has capability to manage skills -require_capability('block/gradelevel:skillmanager', $systemcontext); - -$skill = block_gradelevel_levelset::find_by_id($skill_id); - -$PAGE->requires->js_call_amd('block_gradelevel/renderbadge', 'init'); -$PAGE->requires->js_call_amd('block_gradelevel/skilleditor', 'init'); -$PAGE->requires->js_call_amd('block_gradelevel/leveleditor', 'init'); - -print $OUTPUT->header(); - -// render skill editor -print $OUTPUT->heading(get_string('cfgpage_editskill','block_gradelevel')." ".$skill->getName()); -print block_gradelevel_skillmgmtservice::render_skill_editor($skill_id); - -// render level editor -print $OUTPUT->heading(get_string('cfgpage_skilllevels','block_gradelevel')); -print block_gradelevel_skillmgmtservice::render_leveltable($skill_id); - -// add back button to return to skill management page -$cfg_skills_url = $CFG->wwwroot."/blocks/gradelevel/cfg_skills.php"; -print ""; -print $OUTPUT->footer(); +libdir.'/adminlib.php'); + + +admin_externalpage_setup("block_gradelevel_config_skills"); + + +$skill_id = required_param('skill_id', PARAM_INT); + +$systemcontext = context_system::instance(); +// Check if user has capability to manage skills +require_capability('block/gradelevel:skillmanager', $systemcontext); + +$skill = block_gradelevel_levelset::find_by_id($skill_id); + +$PAGE->requires->js_call_amd('block_gradelevel/renderbadge', 'init'); +$PAGE->requires->js_call_amd('block_gradelevel/skilleditor', 'init'); +$PAGE->requires->js_call_amd('block_gradelevel/leveleditor', 'init'); + +print $OUTPUT->header(); + +// render skill editor +print $OUTPUT->heading(get_string('cfgpage_editskill','block_gradelevel')." ".$skill->getName()); +print block_gradelevel_skillmgmtservice::render_skill_editor($skill_id); + +// render level editor +print $OUTPUT->heading(get_string('cfgpage_skilllevels','block_gradelevel')); +print block_gradelevel_skillmgmtservice::render_leveltable($skill_id); + +// add back button to return to skill management page +$cfg_skills_url = $CFG->wwwroot."/blocks/gradelevel/cfg_skills.php"; +print ""; +print $OUTPUT->footer(); diff --git a/cfg_skills.php b/cfg_skills.php index 3d9139b..52798cf 100644 --- a/cfg_skills.php +++ b/cfg_skills.php @@ -1,35 +1,35 @@ -libdir.'/adminlib.php'); - - -admin_externalpage_setup("block_gradelevel_config_skills"); - -$systemcontext = context_system::instance(); -// Check if user has capability to manage skills -require_capability('block/gradelevel:skillmanager', $systemcontext); - - -$PAGE->requires->js_call_amd('block_gradelevel/skilleditor', 'init'); -$PAGE->requires->js_call_amd('block_gradelevel/renderbadge', 'init'); - -print $OUTPUT->header(); -print $OUTPUT->heading(get_string('cfgpage_skills', 'block_gradelevel')); - -// render page for skill level 0 (global) -print block_gradelevel_skillmgmtservice::render_skill_list(0); -print $OUTPUT->footer(); +libdir.'/adminlib.php'); + + +admin_externalpage_setup("block_gradelevel_config_skills"); + +$systemcontext = context_system::instance(); +// Check if user has capability to manage skills +require_capability('block/gradelevel:skillmanager', $systemcontext); + + +$PAGE->requires->js_call_amd('block_gradelevel/skilleditor', 'init'); +$PAGE->requires->js_call_amd('block_gradelevel/renderbadge', 'init'); + +print $OUTPUT->header(); +print $OUTPUT->heading(get_string('cfgpage_skills', 'block_gradelevel')); + +// render page for skill level 0 (global) +print block_gradelevel_skillmgmtservice::render_skill_list(0); +print $OUTPUT->footer(); diff --git a/classes/levelset.php b/classes/levelset.php index 47f331e..d42b049 100644 --- a/classes/levelset.php +++ b/classes/levelset.php @@ -1,492 +1,492 @@ -libdir.'/gradelib.php'); -require_once($CFG->dirroot.'/grade/querylib.php'); - -//namespace block_gradelevel; - -class block_gradelevel_levelset { - const UNDEFINED_BADGE_COLOR="#3F3F3F"; - const NULL_BADGE_COLOR = "#320000"; - const DEFAULT_ICON = "/blocks/gradelevel/pix/undefinedskill.svg"; - - const GLOBAL_DEFAULTS = array( - 0 => "#320000", - 250 => "#2ad4ff", // + 250 - 750 => "#cd7f32", // + 500 - 1750 => "#92A1A6", // + 1000 - 2750 => "#f6ae00", // + 2000 - ); - - private $id; - private $data; - private $levels = null; - private $global_levels = null; - - /** - * Construct a levelset object for an existing database item - * - */ - private function __construct($id = null, $dataObject = null) - { - global $DB; - - $this->id = $id; - if($id != null) - { - if(isset($dataObject) && isset($dataObject->id) && $dataObject->id == $id) // slight sanity check - { - $this->data = $dataObject; - } - else { - // database validity check went south, retrieve again - $this->data = $DB->get_record('block_gradelevel_levelset', array('id' => $this->id)); - } - - //retrieve levels for this levelset - $this->levels = $DB->get_records('block_gradelevel_levels', array('levelset_id' => $this->id)); - - usort( $this->levels, function( $a, $b) { - return ( $a->points < $b->points ) ? -1 : 1; - } ); - - } - // retrieve global levels - $this->global_levels = $DB->get_records('block_gradelevel_levels', array('levelset_id' => 0)); - - usort( $this->global_levels, function( $a, $b) { - return ( $a->points < $b->points ) ? -1 : 1; - } ); - - // if no global levels are defined, insert default global levels - if(count($this->global_levels) == 0) - { - foreach(static::GLOBAL_DEFAULTS as $points => $color) - { - // setup default - $row = new stdClass; - $row->levelset_id = 0; - $row->points = $points; - $row->badgecolor = $color; - // insert into db - if(!$DB->insert_record('block_gradelevel_levels',$row)){ - print_error('inserterror', 'block_gradelevel'); - } - - } - - // and reload global levels; - $this->global_levels = $DB->get_records('block_gradelevel_levels', array('levelset_id' => 0)); - - usort( $this->global_levels, function( $a, $b) { - return ( $a->points < $b->points ) ? -1 : 1; - } ); - - } - - } - - public function getId() : string - { - return $this->id; - } - - public function setName(string $name) - { - $this->data->name = $name; - } - - public function getName() : string - { - return $this->data->name; - } - - public function setIcon(string $iconname) - { - $this->data->icon = $iconname; - } - - public function getIcon() : string - { - $icon = $this->data->icon; - if(empty($icon)) - { - $icon = static::DEFAULT_ICON; - } - return $icon; - } - - - - /** - * Find a levelset for a given course - * - * @params int $course_id The id of the course to find a levelset for - * @return levelset The levelset for this course or null if none found; - */ - static public function find_by_course($course_id) - { - global $DB; - - try { - // FIXME: Make this more efficient by joining it into one sql statement. - $records = $DB->get_records('block_gradelevel_course_link', array('course_id' => $course_id)); - if(count($records) > 0) - { - $levelset = $DB->get_record('block_gradelevel_levelset', array('id' => array_values($records)[0]->levelset_id)); - if($levelset) - { - return new static($levelset->id,$levelset); - } - } - } - catch(Exception $x){} // catch error if table does not (yet exist) - return null; // return null if no current levelset linked - } - - static public function find_by_id(int $id) - { - global $DB; - - $levelset = $DB->get_record('block_gradelevel_levelset', array('id' => $id)); - if($levelset) - { - return new static($levelset->id,$levelset); - } - else - { - return null; - } - - } - - /** - * List attached courses for this levelset - * - * @return array An array with the id's of attached courses - */ - public function list_courses() - { - global $DB; - $list = array(); - $links = $DB->get_records('block_gradelevel_course_link', array('levelset_id' => $this->id)); - foreach($links as $link) - { - $list[] = $link->course_id; - } - - return $list; - } - - /** - * Attach a course to this levelset. The course will be detached from any other levelsets. - * - * @params int $course_id The id of the course to attach - */ - public function attach_course($course_id) - { - global $DB; - // check if course attachement is already done - if(!in_array($course_id,$this->list_courses)) - { - // no, now find an existing attachment for this course - $rows = $DB->get_records('block_gradelevel_course_link', array('course_id' => $course_id)); - if(empty($rows)) - { - // create new attachment if existing link was not found - $row = new stdClass; - $row->levelset_id = $this->id; - $row->course_id = $course_id; - // insert new row - if(!$DB->insert_record('block_gradelevel_course_link',$row)){ - print_error('inserterror', 'block_gradelevel'); - } - } - else - { - // update existing link (automatically detaches course from its previous levelset) - $row = array_values($rows)[0]; - - $row->course_id = $course_id; - // update existing row - if(!$DB->update_record('block_gradelevel_course_link',$row)){ - print_error('updateerror', 'block_gradelevel'); - } - } - } - - } - - /** - * Detache a course from this levelset. - * - * @params int $course_id The id of the course to detach - */ - public function detach_course($course_id) - { - global $DB; - $rows = $DB->get_records('block_gradelevel_course_link', array('course_id' => $course_id, 'levelset_id' => $this->id)); - if(!empty($rows)) - { - if(!$DB->delete_records('block_gradelevel_course_link', array('id' => array_values($rows)[0]->id))) - { - print_error('deleteerror','block_gradelevel'); - } - } - } - - - /** - * Store changes made to the levelset data parameter containing levelset data - * - */ - public function save_data() - { - global $DB; - if($this->data->id == $this->id) // sanity check so we don't kill everything - { - if(!$DB->update_record('block_gradelevel_levelset',$this->data)){ - print_error('updateerror', 'block_gradelevel'); - } - } - else - { - print_error('datarowchanged_error', 'block_gradelevel'); - } - - } - - /** - * Retrieve point total for all attached courses for a given user - * - * @param int $user_id The id - * @return int Total points for this user in this levelset - */ - public function get_levelset_grade($user_id) - { - // loop through all attached courses and add up the grade points gathered - $points = 0; - foreach($this->list_courses() as $course_id) - { - $result = grade_get_course_grade($user_id,$course_id); - $points += $result->grade; - } - return $points; - } - - /** - * Return the levelup data for a given userid in this levelset - * - * @param int $user_id The id - * @return stdClass A stdClass containing the level data for the specified number of point - */ - public function get_user_leveldata($user_id) - { - $points = $this->get_levelset_grade($user_id); - return $this->calculate_level($points); - } - - - - /** - * Create a new levelset - * - * @params string $name Optional name of the new levelset - * @return levelset The new levelset - */ - static public function create_new($name="New levelset") - { - global $DB; - // create a new levelset - - $row = new stdClass; - $row->name = $name; - - if(!$id = $DB->insert_record('block_gradelevel_levelset',$row, true)){ - print_error('inserterror', 'block_gradelevel'); - } - else - { - $rows = $DB->get_records('block_gradelevel_levelset', array('id' => $id)); - if(count($rows) > 0) - { - return new static($id,array_values($rows)[0]); - } - } - - throw new RuntimeException("Could not create new levelset"); - } - - /** - * List all levelsets - * - * @return array Array of levelset - */ - static public function list_all() - { - global $DB; - $list = array(); - $levelsets = $DB->get_records('block_gradelevel_levelset'); - - foreach($levelsets as $lset) - { - $list[] = new static($lset->id,$lset); - } - - return $list; - - } - - /** - * Calculate the levelup data, given a specified set of points - * - * @params int points The amount of points to calculate for - * @return stdClass A stdClass containing the level data for the specified number of point - */ - public function calculate_level($points){ - - $levels = $this->badgelevels(); - - $level = 0; - $badge_color = static::NULL_BADGE_COLOR; - $current_at = 0; - $next_at = 0; - foreach($levels as $threshold => $badgeColor) - { - if($points >= $threshold){ - $level++; - $badge_color = $badgeColor; - $current_at = $threshold; - } - else - { - $next_at = $threshold; - break; - } - } - - $levelup_points = $next_at - $current_at; - $points_in_level = $points - $current_at; - if($levelup_points == 0){ // at max level - $progress = 0; - $points_in_level = 0; - } - else - { - $progress = $points_in_level / $levelup_points; - } - - $result = new stdClass; - $result->level = $level; - $result->badge_color = $badge_color; - $result->progress = $progress; - $result->next_at = $next_at; - $result->levelup_total = $levelup_points; - $result->points_in_level = $points_in_level; - return $result; - } - - /** - * Simplified list of levels and associated badge colors for this levelset - * Takes data from global levelset if more specialized data is not set - * - * @return array An array of points (keys) and badge color (values), sorted by level - */ - public function badgelevels() - { - - $level_info = array(); - - // If we have levels defined, use those, otherwise use the global levels - if(!empty($this->levels)) - { - if(array_values($this->levels)[0]->points > 0) - { - // insert level 0 - $level_info[0] = static::NULL_BADGE_COLOR; - } - - $i = 0; - foreach($this->levels as $lvl) - { - // Check if color is properly set or needs to be retrieved from global config - if(!empty($lvl->badgecolor)) { - $color = $lvl->badgecolor; - } - else - { - $color = static::UNDEFINED_BADGE_COLOR; - } - - $level_info[$lvl->points] = $color; - $i++; - } - - } - else - { - if(empty($this->global_levels) || array_values($this->global_levels)[0]->points > 0) - { - // insert level 1 if levels don't start at 0 points, - // or if no global levels are defined. - At least start somewhere... - $level_info[0] = static::NULL_BADGE_COLOR; - } - - // use global levels if levelset is not defined. - foreach($this->global_levels as $lvl) - { - // Check if color is properly set - if(!empty($lvl->badgecolor)) { - $color = $lvl->badgecolor; - } - else - { - $color = static::UNDEFINED_BADGE_COLOR; - } - $level_info[$lvl->points] = $color; - } - } - - return $level_info; - - } - - public function render_badge(int $points,int $size=150){ - global $CFG; - - $info = $this->calculate_level($points); - - $image = $this->getIcon(); - if(strncmp($image,"data:",5) == 0) - { - $image_url = $CFG->wwwroot."/blocks/gradelevel/view-icon.php?skillid=".$this->id; - } - else - { - $image_url = $image; - } - - $html = "
badgelevels(); - $maxpoints = array_pop(array_keys($levels)); - - return $this->render_badge($maxpoints, $size); - - } - +libdir.'/gradelib.php'); +require_once($CFG->dirroot.'/grade/querylib.php'); + +//namespace block_gradelevel; + +class block_gradelevel_levelset { + const UNDEFINED_BADGE_COLOR="#3F3F3F"; + const NULL_BADGE_COLOR = "#320000"; + const DEFAULT_ICON = "/blocks/gradelevel/pix/undefinedskill.svg"; + + const GLOBAL_DEFAULTS = array( + 0 => "#320000", + 250 => "#2ad4ff", // + 250 + 750 => "#cd7f32", // + 500 + 1750 => "#92A1A6", // + 1000 + 2750 => "#f6ae00", // + 2000 + ); + + private $id; + private $data; + private $levels = null; + private $global_levels = null; + + /** + * Construct a levelset object for an existing database item + * + */ + private function __construct($id = null, $dataObject = null) + { + global $DB; + + $this->id = $id; + if($id != null) + { + if(isset($dataObject) && isset($dataObject->id) && $dataObject->id == $id) // slight sanity check + { + $this->data = $dataObject; + } + else { + // database validity check went south, retrieve again + $this->data = $DB->get_record('block_gradelevel_levelset', array('id' => $this->id)); + } + + //retrieve levels for this levelset + $this->levels = $DB->get_records('block_gradelevel_levels', array('levelset_id' => $this->id)); + + usort( $this->levels, function( $a, $b) { + return ( $a->points < $b->points ) ? -1 : 1; + } ); + + } + // retrieve global levels + $this->global_levels = $DB->get_records('block_gradelevel_levels', array('levelset_id' => 0)); + + usort( $this->global_levels, function( $a, $b) { + return ( $a->points < $b->points ) ? -1 : 1; + } ); + + // if no global levels are defined, insert default global levels + if(count($this->global_levels) == 0) + { + foreach(static::GLOBAL_DEFAULTS as $points => $color) + { + // setup default + $row = new stdClass; + $row->levelset_id = 0; + $row->points = $points; + $row->badgecolor = $color; + // insert into db + if(!$DB->insert_record('block_gradelevel_levels',$row)){ + print_error('inserterror', 'block_gradelevel'); + } + + } + + // and reload global levels; + $this->global_levels = $DB->get_records('block_gradelevel_levels', array('levelset_id' => 0)); + + usort( $this->global_levels, function( $a, $b) { + return ( $a->points < $b->points ) ? -1 : 1; + } ); + + } + + } + + public function getId() : string + { + return $this->id; + } + + public function setName(string $name) + { + $this->data->name = $name; + } + + public function getName() : string + { + return $this->data->name; + } + + public function setIcon(string $iconname) + { + $this->data->icon = $iconname; + } + + public function getIcon() : string + { + $icon = $this->data->icon; + if(empty($icon)) + { + $icon = static::DEFAULT_ICON; + } + return $icon; + } + + + + /** + * Find a levelset for a given course + * + * @params int $course_id The id of the course to find a levelset for + * @return levelset The levelset for this course or null if none found; + */ + static public function find_by_course($course_id) + { + global $DB; + + try { + // FIXME: Make this more efficient by joining it into one sql statement. + $records = $DB->get_records('block_gradelevel_course_link', array('course_id' => $course_id)); + if(count($records) > 0) + { + $levelset = $DB->get_record('block_gradelevel_levelset', array('id' => array_values($records)[0]->levelset_id)); + if($levelset) + { + return new static($levelset->id,$levelset); + } + } + } + catch(Exception $x){} // catch error if table does not (yet exist) + return null; // return null if no current levelset linked + } + + static public function find_by_id(int $id) + { + global $DB; + + $levelset = $DB->get_record('block_gradelevel_levelset', array('id' => $id)); + if($levelset) + { + return new static($levelset->id,$levelset); + } + else + { + return null; + } + + } + + /** + * List attached courses for this levelset + * + * @return array An array with the id's of attached courses + */ + public function list_courses() + { + global $DB; + $list = array(); + $links = $DB->get_records('block_gradelevel_course_link', array('levelset_id' => $this->id)); + foreach($links as $link) + { + $list[] = $link->course_id; + } + + return $list; + } + + /** + * Attach a course to this levelset. The course will be detached from any other levelsets. + * + * @params int $course_id The id of the course to attach + */ + public function attach_course($course_id) + { + global $DB; + // check if course attachement is already done + if(!in_array($course_id,$this->list_courses)) + { + // no, now find an existing attachment for this course + $rows = $DB->get_records('block_gradelevel_course_link', array('course_id' => $course_id)); + if(empty($rows)) + { + // create new attachment if existing link was not found + $row = new stdClass; + $row->levelset_id = $this->id; + $row->course_id = $course_id; + // insert new row + if(!$DB->insert_record('block_gradelevel_course_link',$row)){ + print_error('inserterror', 'block_gradelevel'); + } + } + else + { + // update existing link (automatically detaches course from its previous levelset) + $row = array_values($rows)[0]; + + $row->course_id = $course_id; + // update existing row + if(!$DB->update_record('block_gradelevel_course_link',$row)){ + print_error('updateerror', 'block_gradelevel'); + } + } + } + + } + + /** + * Detache a course from this levelset. + * + * @params int $course_id The id of the course to detach + */ + public function detach_course($course_id) + { + global $DB; + $rows = $DB->get_records('block_gradelevel_course_link', array('course_id' => $course_id, 'levelset_id' => $this->id)); + if(!empty($rows)) + { + if(!$DB->delete_records('block_gradelevel_course_link', array('id' => array_values($rows)[0]->id))) + { + print_error('deleteerror','block_gradelevel'); + } + } + } + + + /** + * Store changes made to the levelset data parameter containing levelset data + * + */ + public function save_data() + { + global $DB; + if($this->data->id == $this->id) // sanity check so we don't kill everything + { + if(!$DB->update_record('block_gradelevel_levelset',$this->data)){ + print_error('updateerror', 'block_gradelevel'); + } + } + else + { + print_error('datarowchanged_error', 'block_gradelevel'); + } + + } + + /** + * Retrieve point total for all attached courses for a given user + * + * @param int $user_id The id + * @return int Total points for this user in this levelset + */ + public function get_levelset_grade($user_id) + { + // loop through all attached courses and add up the grade points gathered + $points = 0; + foreach($this->list_courses() as $course_id) + { + $result = grade_get_course_grade($user_id,$course_id); + $points += $result->grade; + } + return $points; + } + + /** + * Return the levelup data for a given userid in this levelset + * + * @param int $user_id The id + * @return stdClass A stdClass containing the level data for the specified number of point + */ + public function get_user_leveldata($user_id) + { + $points = $this->get_levelset_grade($user_id); + return $this->calculate_level($points); + } + + + + /** + * Create a new levelset + * + * @params string $name Optional name of the new levelset + * @return levelset The new levelset + */ + static public function create_new($name="New levelset") + { + global $DB; + // create a new levelset + + $row = new stdClass; + $row->name = $name; + + if(!$id = $DB->insert_record('block_gradelevel_levelset',$row, true)){ + print_error('inserterror', 'block_gradelevel'); + } + else + { + $rows = $DB->get_records('block_gradelevel_levelset', array('id' => $id)); + if(count($rows) > 0) + { + return new static($id,array_values($rows)[0]); + } + } + + throw new RuntimeException("Could not create new levelset"); + } + + /** + * List all levelsets + * + * @return array Array of levelset + */ + static public function list_all() + { + global $DB; + $list = array(); + $levelsets = $DB->get_records('block_gradelevel_levelset'); + + foreach($levelsets as $lset) + { + $list[] = new static($lset->id,$lset); + } + + return $list; + + } + + /** + * Calculate the levelup data, given a specified set of points + * + * @params int points The amount of points to calculate for + * @return stdClass A stdClass containing the level data for the specified number of point + */ + public function calculate_level($points){ + + $levels = $this->badgelevels(); + + $level = 0; + $badge_color = static::NULL_BADGE_COLOR; + $current_at = 0; + $next_at = 0; + foreach($levels as $threshold => $badgeColor) + { + if($points >= $threshold){ + $level++; + $badge_color = $badgeColor; + $current_at = $threshold; + } + else + { + $next_at = $threshold; + break; + } + } + + $levelup_points = $next_at - $current_at; + $points_in_level = $points - $current_at; + if($levelup_points == 0){ // at max level + $progress = 0; + $points_in_level = 0; + } + else + { + $progress = $points_in_level / $levelup_points; + } + + $result = new stdClass; + $result->level = $level; + $result->badge_color = $badge_color; + $result->progress = $progress; + $result->next_at = $next_at; + $result->levelup_total = $levelup_points; + $result->points_in_level = $points_in_level; + return $result; + } + + /** + * Simplified list of levels and associated badge colors for this levelset + * Takes data from global levelset if more specialized data is not set + * + * @return array An array of points (keys) and badge color (values), sorted by level + */ + public function badgelevels() + { + + $level_info = array(); + + // If we have levels defined, use those, otherwise use the global levels + if(!empty($this->levels)) + { + if(array_values($this->levels)[0]->points > 0) + { + // insert level 0 + $level_info[0] = static::NULL_BADGE_COLOR; + } + + $i = 0; + foreach($this->levels as $lvl) + { + // Check if color is properly set or needs to be retrieved from global config + if(!empty($lvl->badgecolor)) { + $color = $lvl->badgecolor; + } + else + { + $color = static::UNDEFINED_BADGE_COLOR; + } + + $level_info[$lvl->points] = $color; + $i++; + } + + } + else + { + if(empty($this->global_levels) || array_values($this->global_levels)[0]->points > 0) + { + // insert level 1 if levels don't start at 0 points, + // or if no global levels are defined. - At least start somewhere... + $level_info[0] = static::NULL_BADGE_COLOR; + } + + // use global levels if levelset is not defined. + foreach($this->global_levels as $lvl) + { + // Check if color is properly set + if(!empty($lvl->badgecolor)) { + $color = $lvl->badgecolor; + } + else + { + $color = static::UNDEFINED_BADGE_COLOR; + } + $level_info[$lvl->points] = $color; + } + } + + return $level_info; + + } + + public function render_badge(int $points,int $size=150){ + global $CFG; + + $info = $this->calculate_level($points); + + $image = $this->getIcon(); + if(strncmp($image,"data:",5) == 0) + { + $image_url = $CFG->wwwroot."/blocks/gradelevel/view-icon.php?skillid=".$this->id; + } + else + { + $image_url = $image; + } + + $html = "
badgelevels(); + $maxpoints = array_pop(array_keys($levels)); + + return $this->render_badge($maxpoints, $size); + + } + } \ No newline at end of file diff --git a/classes/skillmgmtservice.php b/classes/skillmgmtservice.php index 7ef35c0..401b221 100644 --- a/classes/skillmgmtservice.php +++ b/classes/skillmgmtservice.php @@ -1,473 +1,473 @@ -libdir.'/gradelib.php'); -require_once($CFG->libdir.'/externallib.php'); -require_once($CFG->dirroot.'/grade/querylib.php'); - -//namespace block_gradelevel; - - -class block_gradelevel_skillmgmtservice extends external_api -{ - const DEBUG = false; // enable debug logging - const DEMOBADGE_SIZE = 150; // size of demo badge - - private static function log($message) - { - if(self::DEBUG) - { - error_log($message."\n",3,"/tmp/block_gradelevel.log"); - } - } - - private static function list_courses($skill_id) - { - global $DB; - $list = array(); - $links = $DB->get_records('block_gradelevel_course_link', array('levelset_id' => $skill_id)); - foreach($links as $link) - { - $list[] = $link->course_id; - } - - return $list; - } - - // Input parameter config - public static function submit_levels_parameters() - { - return new external_function_parameters( - array( - 'skill_id' => new external_value(PARAM_INT, 'id of skill'), - 'levels' => new external_multiple_structure( - new external_single_structure( - array( - 'id' => new external_value(PARAM_INT, 'id of level'), - 'points' => new external_value(PARAM_INT, 'number of points for this level'), - 'badgecolor' => new external_value(PARAM_TEXT, 'color of level badge'), - ) - ) - ) - ) - ); - } - - public static function list_levels_parameters() - { - return new external_function_parameters( - array( - 'skill_id' => new external_value(PARAM_INT, 'id of skill'), - ) - ); - } - - public static function list_skills_parameters() - { - return new external_function_parameters( array() ); - } - - public static function get_skill_parameters() - { - return new external_function_parameters( - array( - 'id' => new external_value(PARAM_INT, 'id of skill'), - ) - ); - } - - public static function update_skill_parameters() - { - return new external_function_parameters( - array( - 'id' => new external_value(PARAM_INT, 'id of skill'), - 'name' => new external_value(PARAM_TEXT, 'Name of skill'), - 'icon' => new external_value(PARAM_RAW, 'Icon for skill'), - ) - ); - } - - public static function add_skill_parameters() - { - return new external_function_parameters( - array( - 'name' => new external_value(PARAM_TEXT, 'Name of skill'), - 'icon' => new external_value(PARAM_RAW, 'Icon for skill'), - ) - ); - } - - public static function delete_skill_parameters() - { - return new external_function_parameters( - array( - 'id' => new external_value(PARAM_INT, 'Id of skill'), - ) - ); - } - - // Output parameter config - public static function submit_levels_returns() - { - return new external_multiple_structure( - new external_single_structure( - array( - 'id' => new external_value(PARAM_INT, 'id of level'), - 'points' => new external_value(PARAM_INT, 'number of points for this level'), - 'badgecolor' => new external_value(PARAM_TEXT, 'color of level badge'), - ) - ) - ); - } - - public static function list_levels_returns() - { - return new external_multiple_structure( - new external_single_structure( - array( - 'id' => new external_value(PARAM_INT, 'id of level'), - 'points' => new external_value(PARAM_INT, 'number of points for this level'), - 'badgecolor' => new external_value(PARAM_TEXT, 'color of level badge'), - ) - ) - ); - } - - public static function list_skills_returns() - { - return new external_multiple_structure( - new external_single_structure( - array( - 'id' => new external_value(PARAM_INT, 'Id of skill'), - 'name' => new external_value(PARAM_TEXT, 'Name of skill'), - 'icon' => new external_value(PARAM_RAW, 'Icon for skill'), - 'html' => new external_value(PARAM_RAW, 'Demo badge'), - ) - ) - ); - } - - public static function get_skill_returns() - { - return new external_single_structure( - array( - 'name' => new external_value(PARAM_TEXT, 'Name of skill'), - 'icon' => new external_value(PARAM_RAW, 'Icon for skill'), - 'html' => new external_value(PARAM_RAW, 'Demo badge'), - ) - ); - } - - public static function update_skill_returns() - { - return new external_single_structure( - array( - 'id' => new external_value(PARAM_INT, 'Id of skill'), - 'name' => new external_value(PARAM_TEXT, 'Name of skill'), - 'icon' => new external_value(PARAM_RAW, 'Icon for skill'), - 'html' => new external_value(PARAM_RAW, 'Demo badge'), - ) - ); - } - - public static function add_skill_returns() - { - return new external_single_structure( - array( - 'id' => new external_value(PARAM_INT, 'Id of skill'), - 'name' => new external_value(PARAM_TEXT, 'Name of skill'), - 'icon' => new external_value(PARAM_RAW, 'Icon for skill'), - 'html' => new external_value(PARAM_RAW, 'Demo badge'), - ) - ); - } - - public static function delete_skill_returns() - { - return new external_single_structure( - array( - 'id' => new external_value(PARAM_INT, 'Id of skill'), - 'deleted' => new external_value(PARAM_BOOL, 'Deletion succesful'), - ) - ); - } - - // Actual functions - public static function submit_levels(int $skill_id, array $levels) - { - global $CFG, $DB; - self::log("submit_levels called, skill_id: {$skill_id}, levels: \n".print_r($levels,true)); - - $systemcontext = context_system::instance(); - self::validate_context($systemcontext); - - - foreach($levels as $lvl_raw) - { - // convert level array to stdObj - $lvl = (object) $lvl_raw; - $lvl->levelset_id = $skill_id; - - self::log("Processing level: ".print_r($lvl,true)); - - if($lvl->points >= 0) - { - if($lvl->id >= 0) - { - // Update record - self::log("updating row {$lvl->id}: {$lvl->points}, {$lvl->badgecolor} @ {$lvl->levelset_id}"); - $DB->update_record('block_gradelevel_levels',$lvl); - } - else - { - // unset invalid id before insert - unset($lvl->id); - self::log("inserting new row {$lvl->points}, {$lvl->badgecolor} @ {$lvl->levelset_id}"); - // Insert record - $DB->insert_record('block_gradelevel_levels',$lvl); - } - } - else - { - // points is empty: delete record if we have a valid id. - if($lvl->id >= 0) - { - self::log("deleting empty row {$lvl->id}"); - $DB->delete_records('block_gradelevel_levels', array('id' => $lvl->id)); - } - else - { - self::log("ignoring empty row"); - } - - }/**/ - - } - - return static::list_levels($skill_id); - } - - public static function list_levels(int $skill_id) - { - global $CFG, $DB; - - $levels = $DB->get_records('block_gradelevel_levels', array('levelset_id' => $skill_id)); - - if($skill_id == 0 || count($levels) > 0) - { - // If global level, or skills are defined, return those - - // Sort by points - usort( $levels, function( $a, $b) { - return ( $a->points < $b->points ) ? -1 : 1; - } ); - - - return $levels; - } - else - { - // Else, return a nameless clone of the default levels). - $levels = $DB->get_records('block_gradelevel_levels', array('levelset_id' => 0)); - - // Sort by points - usort( $levels, function( $a, $b) { - return ( $a->points < $b->points ) ? -1 : 1; - } ); - - foreach($levels as $lvl) - { - $lvl->id = -255; // replace level id with -255, which is code for "new level" when returned - } - - return $levels; - } - - } - - public static function list_skills() - { - global $CFG, $DB; - - $skills = $DB->get_records('block_gradelevel_levelset'); - - // Sort by points - usort( $skills, function( $a, $b) { - return ( $a->name < $b->name) ? -1 : 1; - } ); - - foreach($skills as $skill ) - { - $skill->html = block_gradelevel_levelset::find_by_id($skill->id)->render_demo_badge(static::DEMOBADGE_SIZE); - } - - - return $skills; - - - } - - public static function get_skill(int $id) - { - global $CFG, $DB; - - $skill = $DB->get_record('block_gradelevel_levels', array('id' => $id)); - $skill->html = block_gradelevel_levelset::find_by_id($skill->id)->render_demo_badge(static::DEMOBADGE_SIZE); - - return $skill; - - } - - public static function update_skill(int $id, $name, $icon ) - { - global $CFG, $DB; - - $skill = $DB->get_record('block_gradelevel_levelset', array('id' => $id)); - - if($name != null){ - $skill->name = $name; - } - - if($icon != null){ - $skill->icon = $icon; - } - - $DB->update_record('block_gradelevel_levelset',$skill); - - - $skill->html = block_gradelevel_levelset::find_by_id($skill->id)->render_demo_badge(static::DEMOBADGE_SIZE); - - return $skill; - } - - public static function add_skill($name, $icon) - { - global $CFG, $DB; - - $skill = new stdClass; - - if(empty($name)){ - $skill->name = get_string('defaults_name','block_gradelevel'); - } - else { - $skill->name = $name; - } - if(empty($icon)){ - $skill->icon = "/blocks/gradelevel/pix/undefinedskill.svg"; - } - else { - $skill->icon = $icon; - } - - $id = $DB->insert_record('block_gradelevel_levelset',$skill, true); - - $skill->id = $id; - $skill->html = static::single_skill_editor_item(block_gradelevel_levelset::find_by_id($skill->id)); - - return $skill; - } - - public static function delete_skill(int $id) - { - global $CFG, $DB; - - $skill = block_gradelevel_levelset::find_by_id($id); - if(count($skill->list_courses()) > 0) - { - throw new Exception("Cannot delete skills that have courses attached"); - } - - $result = array('id' => $id); - $result['deleted'] = ($DB->delete_records('block_gradelevel_levelset',array('id' => $id)))?true:false; - return $result; - } - - - // Other public functions - - public static function render_leveltable($skill_id) - { - $levels = static::list_levels($skill_id); - - $s = "

".get_string('levelcfg_description','block_gradelevel')."

"; - $s .= ""; - $s .= ""; - $s .= ""; - foreach($levels as $lvl) - { - $color = ltrim($lvl->badgecolor,"#"); // trim any leading hashes - $s .= ""; - - } - $s .= ""; - $s .= ""; - $s .= "
".get_string('levelcfg_head_points','block_gradelevel')."".get_string('levelcfg_head_color','block_gradelevel')."
".get_string("levelcfg_addlevel",'block_gradelevel')."
"; - $s .= "

"; - if($skill_id > 0) - { - $s .= " "; - } - $s .= "

"; - - return $s; - - } - - public static function render_skill_list() - { - global $CFG; - $skills = block_gradelevel_levelset::list_all(); - - // Sort by points - usort( $skills, function( $a, $b) { - return ( $a->getName() < $b->getName()) ? -1 : 1; - } ); - - $s = "
    "; - foreach($skills as $skill) - { - $s .= static::single_skill_editor_item($skill); - } - - $s .= "
"; - $s .= "".get_string('cfg_addskill','block_gradelevel').""; - $s .= "
"; - - return $s; - } - - public static function render_skill_editor(int $skill_id) - { - $skill = block_gradelevel_levelset::find_by_id($skill_id); - - $s = "
    "; - $s .= static::single_skill_editor_item($skill,true); - $s .= "
"; - - return $s; - } - - private static function single_skill_editor_item($skill,$single=false) - { - global $OUTPUT; - - $skill_id = $skill->getId(); - $s = "
  • "; - if(!$single){ - $s .= ""; - } - $s .= $skill->render_demo_badge(static::DEMOBADGE_SIZE).""; - - if(!$single){ - $s .= ""; - if(count($skill->list_courses()) == 0){ - $s .= ""; - } - } - - $s .= ""; - $s .= "
    ".$skill->getName().""; - $s .= ""; - $s .= "
  • "; - return $s; - } - +libdir.'/gradelib.php'); +require_once($CFG->libdir.'/externallib.php'); +require_once($CFG->dirroot.'/grade/querylib.php'); + +//namespace block_gradelevel; + + +class block_gradelevel_skillmgmtservice extends external_api +{ + const DEBUG = false; // enable debug logging + const DEMOBADGE_SIZE = 150; // size of demo badge + + private static function log($message) + { + if(self::DEBUG) + { + error_log($message."\n",3,"/tmp/block_gradelevel.log"); + } + } + + private static function list_courses($skill_id) + { + global $DB; + $list = array(); + $links = $DB->get_records('block_gradelevel_course_link', array('levelset_id' => $skill_id)); + foreach($links as $link) + { + $list[] = $link->course_id; + } + + return $list; + } + + // Input parameter config + public static function submit_levels_parameters() + { + return new external_function_parameters( + array( + 'skill_id' => new external_value(PARAM_INT, 'id of skill'), + 'levels' => new external_multiple_structure( + new external_single_structure( + array( + 'id' => new external_value(PARAM_INT, 'id of level'), + 'points' => new external_value(PARAM_INT, 'number of points for this level'), + 'badgecolor' => new external_value(PARAM_TEXT, 'color of level badge'), + ) + ) + ) + ) + ); + } + + public static function list_levels_parameters() + { + return new external_function_parameters( + array( + 'skill_id' => new external_value(PARAM_INT, 'id of skill'), + ) + ); + } + + public static function list_skills_parameters() + { + return new external_function_parameters( array() ); + } + + public static function get_skill_parameters() + { + return new external_function_parameters( + array( + 'id' => new external_value(PARAM_INT, 'id of skill'), + ) + ); + } + + public static function update_skill_parameters() + { + return new external_function_parameters( + array( + 'id' => new external_value(PARAM_INT, 'id of skill'), + 'name' => new external_value(PARAM_TEXT, 'Name of skill'), + 'icon' => new external_value(PARAM_RAW, 'Icon for skill'), + ) + ); + } + + public static function add_skill_parameters() + { + return new external_function_parameters( + array( + 'name' => new external_value(PARAM_TEXT, 'Name of skill'), + 'icon' => new external_value(PARAM_RAW, 'Icon for skill'), + ) + ); + } + + public static function delete_skill_parameters() + { + return new external_function_parameters( + array( + 'id' => new external_value(PARAM_INT, 'Id of skill'), + ) + ); + } + + // Output parameter config + public static function submit_levels_returns() + { + return new external_multiple_structure( + new external_single_structure( + array( + 'id' => new external_value(PARAM_INT, 'id of level'), + 'points' => new external_value(PARAM_INT, 'number of points for this level'), + 'badgecolor' => new external_value(PARAM_TEXT, 'color of level badge'), + ) + ) + ); + } + + public static function list_levels_returns() + { + return new external_multiple_structure( + new external_single_structure( + array( + 'id' => new external_value(PARAM_INT, 'id of level'), + 'points' => new external_value(PARAM_INT, 'number of points for this level'), + 'badgecolor' => new external_value(PARAM_TEXT, 'color of level badge'), + ) + ) + ); + } + + public static function list_skills_returns() + { + return new external_multiple_structure( + new external_single_structure( + array( + 'id' => new external_value(PARAM_INT, 'Id of skill'), + 'name' => new external_value(PARAM_TEXT, 'Name of skill'), + 'icon' => new external_value(PARAM_RAW, 'Icon for skill'), + 'html' => new external_value(PARAM_RAW, 'Demo badge'), + ) + ) + ); + } + + public static function get_skill_returns() + { + return new external_single_structure( + array( + 'name' => new external_value(PARAM_TEXT, 'Name of skill'), + 'icon' => new external_value(PARAM_RAW, 'Icon for skill'), + 'html' => new external_value(PARAM_RAW, 'Demo badge'), + ) + ); + } + + public static function update_skill_returns() + { + return new external_single_structure( + array( + 'id' => new external_value(PARAM_INT, 'Id of skill'), + 'name' => new external_value(PARAM_TEXT, 'Name of skill'), + 'icon' => new external_value(PARAM_RAW, 'Icon for skill'), + 'html' => new external_value(PARAM_RAW, 'Demo badge'), + ) + ); + } + + public static function add_skill_returns() + { + return new external_single_structure( + array( + 'id' => new external_value(PARAM_INT, 'Id of skill'), + 'name' => new external_value(PARAM_TEXT, 'Name of skill'), + 'icon' => new external_value(PARAM_RAW, 'Icon for skill'), + 'html' => new external_value(PARAM_RAW, 'Demo badge'), + ) + ); + } + + public static function delete_skill_returns() + { + return new external_single_structure( + array( + 'id' => new external_value(PARAM_INT, 'Id of skill'), + 'deleted' => new external_value(PARAM_BOOL, 'Deletion succesful'), + ) + ); + } + + // Actual functions + public static function submit_levels(int $skill_id, array $levels) + { + global $CFG, $DB; + self::log("submit_levels called, skill_id: {$skill_id}, levels: \n".print_r($levels,true)); + + $systemcontext = context_system::instance(); + self::validate_context($systemcontext); + + + foreach($levels as $lvl_raw) + { + // convert level array to stdObj + $lvl = (object) $lvl_raw; + $lvl->levelset_id = $skill_id; + + self::log("Processing level: ".print_r($lvl,true)); + + if($lvl->points >= 0) + { + if($lvl->id >= 0) + { + // Update record + self::log("updating row {$lvl->id}: {$lvl->points}, {$lvl->badgecolor} @ {$lvl->levelset_id}"); + $DB->update_record('block_gradelevel_levels',$lvl); + } + else + { + // unset invalid id before insert + unset($lvl->id); + self::log("inserting new row {$lvl->points}, {$lvl->badgecolor} @ {$lvl->levelset_id}"); + // Insert record + $DB->insert_record('block_gradelevel_levels',$lvl); + } + } + else + { + // points is empty: delete record if we have a valid id. + if($lvl->id >= 0) + { + self::log("deleting empty row {$lvl->id}"); + $DB->delete_records('block_gradelevel_levels', array('id' => $lvl->id)); + } + else + { + self::log("ignoring empty row"); + } + + }/**/ + + } + + return static::list_levels($skill_id); + } + + public static function list_levels(int $skill_id) + { + global $CFG, $DB; + + $levels = $DB->get_records('block_gradelevel_levels', array('levelset_id' => $skill_id)); + + if($skill_id == 0 || count($levels) > 0) + { + // If global level, or skills are defined, return those + + // Sort by points + usort( $levels, function( $a, $b) { + return ( $a->points < $b->points ) ? -1 : 1; + } ); + + + return $levels; + } + else + { + // Else, return a nameless clone of the default levels). + $levels = $DB->get_records('block_gradelevel_levels', array('levelset_id' => 0)); + + // Sort by points + usort( $levels, function( $a, $b) { + return ( $a->points < $b->points ) ? -1 : 1; + } ); + + foreach($levels as $lvl) + { + $lvl->id = -255; // replace level id with -255, which is code for "new level" when returned + } + + return $levels; + } + + } + + public static function list_skills() + { + global $CFG, $DB; + + $skills = $DB->get_records('block_gradelevel_levelset'); + + // Sort by points + usort( $skills, function( $a, $b) { + return ( $a->name < $b->name) ? -1 : 1; + } ); + + foreach($skills as $skill ) + { + $skill->html = block_gradelevel_levelset::find_by_id($skill->id)->render_demo_badge(static::DEMOBADGE_SIZE); + } + + + return $skills; + + + } + + public static function get_skill(int $id) + { + global $CFG, $DB; + + $skill = $DB->get_record('block_gradelevel_levels', array('id' => $id)); + $skill->html = block_gradelevel_levelset::find_by_id($skill->id)->render_demo_badge(static::DEMOBADGE_SIZE); + + return $skill; + + } + + public static function update_skill(int $id, $name, $icon ) + { + global $CFG, $DB; + + $skill = $DB->get_record('block_gradelevel_levelset', array('id' => $id)); + + if($name != null){ + $skill->name = $name; + } + + if($icon != null){ + $skill->icon = $icon; + } + + $DB->update_record('block_gradelevel_levelset',$skill); + + + $skill->html = block_gradelevel_levelset::find_by_id($skill->id)->render_demo_badge(static::DEMOBADGE_SIZE); + + return $skill; + } + + public static function add_skill($name, $icon) + { + global $CFG, $DB; + + $skill = new stdClass; + + if(empty($name)){ + $skill->name = get_string('defaults_name','block_gradelevel'); + } + else { + $skill->name = $name; + } + if(empty($icon)){ + $skill->icon = "/blocks/gradelevel/pix/undefinedskill.svg"; + } + else { + $skill->icon = $icon; + } + + $id = $DB->insert_record('block_gradelevel_levelset',$skill, true); + + $skill->id = $id; + $skill->html = static::single_skill_editor_item(block_gradelevel_levelset::find_by_id($skill->id)); + + return $skill; + } + + public static function delete_skill(int $id) + { + global $CFG, $DB; + + $skill = block_gradelevel_levelset::find_by_id($id); + if(count($skill->list_courses()) > 0) + { + throw new Exception("Cannot delete skills that have courses attached"); + } + + $result = array('id' => $id); + $result['deleted'] = ($DB->delete_records('block_gradelevel_levelset',array('id' => $id)))?true:false; + return $result; + } + + + // Other public functions + + public static function render_leveltable($skill_id) + { + $levels = static::list_levels($skill_id); + + $s = "

    ".get_string('levelcfg_description','block_gradelevel')."

    "; + $s .= ""; + $s .= ""; + $s .= ""; + foreach($levels as $lvl) + { + $color = ltrim($lvl->badgecolor,"#"); // trim any leading hashes + $s .= ""; + + } + $s .= ""; + $s .= ""; + $s .= "
    ".get_string('levelcfg_head_points','block_gradelevel')."".get_string('levelcfg_head_color','block_gradelevel')."
    ".get_string("levelcfg_addlevel",'block_gradelevel')."
    "; + $s .= "

    "; + if($skill_id > 0) + { + $s .= " "; + } + $s .= "

    "; + + return $s; + + } + + public static function render_skill_list() + { + global $CFG; + $skills = block_gradelevel_levelset::list_all(); + + // Sort by points + usort( $skills, function( $a, $b) { + return ( $a->getName() < $b->getName()) ? -1 : 1; + } ); + + $s = "
      "; + foreach($skills as $skill) + { + $s .= static::single_skill_editor_item($skill); + } + + $s .= "
    "; + $s .= "".get_string('cfg_addskill','block_gradelevel').""; + $s .= "
    "; + + return $s; + } + + public static function render_skill_editor(int $skill_id) + { + $skill = block_gradelevel_levelset::find_by_id($skill_id); + + $s = "
      "; + $s .= static::single_skill_editor_item($skill,true); + $s .= "
    "; + + return $s; + } + + private static function single_skill_editor_item($skill,$single=false) + { + global $OUTPUT; + + $skill_id = $skill->getId(); + $s = "
  • "; + if(!$single){ + $s .= ""; + } + $s .= $skill->render_demo_badge(static::DEMOBADGE_SIZE).""; + + if(!$single){ + $s .= ""; + if(count($skill->list_courses()) == 0){ + $s .= ""; + } + } + + $s .= ""; + $s .= "
    ".$skill->getName().""; + $s .= ""; + $s .= "
  • "; + return $s; + } + } \ No newline at end of file diff --git a/db/access.php b/db/access.php index f93e569..4b61ef2 100644 --- a/db/access.php +++ b/db/access.php @@ -1,49 +1,49 @@ - array( - 'captype' => 'write', - 'contextlevel' => CONTEXT_SYSTEM, - 'archetypes' => array( - 'user' => CAP_ALLOW - ), - - 'clonepermissionsfrom' => 'moodle/my:manageblocks' - ), - - 'block/gradelevel:addinstance' => array( - 'riskbitmask' => RISK_SPAM | RISK_XSS, - - 'captype' => 'write', - 'contextlevel' => CONTEXT_BLOCK, - 'archetypes' => array( - 'editingteacher' => CAP_ALLOW, - 'manager' => CAP_ALLOW - ), - - 'clonepermissionsfrom' => 'moodle/site:manageblocks' - ), - - 'block/gradelevel:skillmanager' => array( - 'riskbitmask' => RISK_CONFIG | RISK_DATALOSS | RISK_XSS, - - 'captype' => 'write', - 'contextlevel' => CONTEXT_SYSTEM, - 'archetypes' => array( - 'manager' => CAP_ALLOW - ), - ), - - 'block/gradelevel:viewresults' => array( - 'riskbitmask' => RISK_DATALOSS | RISK_XSS, - - 'captype' => 'write', - 'contextlevel' => CONTEXT_BLOCK, - 'archetypes' => array( - 'editingteacher' => CAP_ALLOW, - 'manager' => CAP_ALLOW - ), - - ), - + array( + 'captype' => 'write', + 'contextlevel' => CONTEXT_SYSTEM, + 'archetypes' => array( + 'user' => CAP_ALLOW + ), + + 'clonepermissionsfrom' => 'moodle/my:manageblocks' + ), + + 'block/gradelevel:addinstance' => array( + 'riskbitmask' => RISK_SPAM | RISK_XSS, + + 'captype' => 'write', + 'contextlevel' => CONTEXT_BLOCK, + 'archetypes' => array( + 'editingteacher' => CAP_ALLOW, + 'manager' => CAP_ALLOW + ), + + 'clonepermissionsfrom' => 'moodle/site:manageblocks' + ), + + 'block/gradelevel:skillmanager' => array( + 'riskbitmask' => RISK_CONFIG | RISK_DATALOSS | RISK_XSS, + + 'captype' => 'write', + 'contextlevel' => CONTEXT_SYSTEM, + 'archetypes' => array( + 'manager' => CAP_ALLOW + ), + ), + + 'block/gradelevel:viewresults' => array( + 'riskbitmask' => RISK_DATALOSS | RISK_XSS, + + 'captype' => 'write', + 'contextlevel' => CONTEXT_BLOCK, + 'archetypes' => array( + 'editingteacher' => CAP_ALLOW, + 'manager' => CAP_ALLOW + ), + + ), + ); \ No newline at end of file diff --git a/db/install.xml b/db/install.xml index 8924ab4..4164451 100644 --- a/db/install.xml +++ b/db/install.xml @@ -1,44 +1,44 @@ - - - - - - - - - - - - - - -
    - - - - - - - - - - - -
    - - - - - - - - - - - -
    -
    + + + + + + + + + + + + + + +
    + + + + + + + + + + + +
    + + + + + + + + + + + +
    +
    \ No newline at end of file diff --git a/db/services.php b/db/services.php index b9cdef4..ecf3fb4 100644 --- a/db/services.php +++ b/db/services.php @@ -1,93 +1,93 @@ - array( - 'functions' => array('block_gradelevel_submit_levels', 'block_gradelevel_list_levels'), - 'requiredcapability' => 'block/gradelevel:changelevels', - 'shortname'=> 'block_gradelevel_levelmgmt', - 'restrictedusers' => 0, - 'enabled' => 0, - 'ajax' => true, - ), -); - -$functions = array( - 'block_gradelevel_submit_levels' => array( //web service function name - 'classname' => 'block_gradelevel_skillmgmtservice', //class containing the external function - 'methodname' => 'submit_levels', //external function name - 'classpath' => 'blocks/gradelevel/skillmgmtservice.php', //file containing the class/external function - 'description' => 'Update level settings for a given skill', //human readable description of the web service function - 'type' => 'write', //database rights of the web service function (read, write) - 'ajax' => true, - 'capabilities' => 'block/gradelevel:skillmanager', - 'loginrequired' => true, - 'services' => array('block_gradelevel_levelmgmt'), - ), - 'block_gradelevel_list_levels' => array( //web service function name - 'classname' => 'block_gradelevel_skillmgmtservice', //class containing the external function - 'methodname' => 'list_levels', //external function name - 'classpath' => 'blocks/gradelevel/skillmgmtservice.php', //file containing the class/external function - 'description' => 'List level settings for a given skill', //human readable description of the web service function - 'type' => 'read', //database rights of the web service function (read, write) - 'ajax' => true, - 'capabilities' => 'block/gradelevel:skillmanager', - 'loginrequired' => true, - 'services' => array('block_gradelevel_levelmgmt'), - ), - 'block_gradelevel_list_skills' => array( //web service function name - 'classname' => 'block_gradelevel_skillmgmtservice', //class containing the external function - 'methodname' => 'list_skills', //external function name - 'classpath' => 'blocks/gradelevel/skillmgmtservice.php', //file containing the class/external function - 'description' => 'List skills', //human readable description of the web service function - 'type' => 'read', //database rights of the web service function (read, write) - 'ajax' => true, - 'capabilities' => 'block/gradelevel:skillmanager', - 'loginrequired' => true, - 'services' => array('block_gradelevel_levelmgmt'), - ), - 'block_gradelevel_get_skill' => array( //web service function name - 'classname' => 'block_gradelevel_skillmgmtservice', //class containing the external function - 'methodname' => 'get_skill', //external function name - 'classpath' => 'blocks/gradelevel/skillmgmtservice.php', //file containing the class/external function - 'description' => 'Retrieve skill information', //human readable description of the web service function - 'type' => 'read', //database rights of the web service function (read, write) - 'ajax' => true, - 'capabilities' => 'block/gradelevel:skillmanager', - 'loginrequired' => true, - 'services' => array('block_gradelevel_levelmgmt'), - ), - 'block_gradelevel_update_skill' => array( //web service function name - 'classname' => 'block_gradelevel_skillmgmtservice', //class containing the external function - 'methodname' => 'update_skill', //external function name - 'classpath' => 'blocks/gradelevel/skillmgmtservice.php', //file containing the class/external function - 'description' => 'Update a skill', //human readable description of the web service function - 'type' => 'read', //database rights of the web service function (read, write) - 'ajax' => true, - 'capabilities' => 'block/gradelevel:skillmanager', - 'loginrequired' => true, - 'services' => array('block_gradelevel_levelmgmt'), - ), - 'block_gradelevel_add_skill' => array( //web service function name - 'classname' => 'block_gradelevel_skillmgmtservice', //class containing the external function - 'methodname' => 'add_skill', //external function name - 'classpath' => 'blocks/gradelevel/skillmgmtservice.php', //file containing the class/external function - 'description' => 'Add a new skill', //human readable description of the web service function - 'type' => 'read', //database rights of the web service function (read, write) - 'ajax' => true, - 'capabilities' => 'block/gradelevel:skillmanager', - 'loginrequired' => true, - 'services' => array('block_gradelevel_levelmgmt'), - ), - 'block_gradelevel_delete_skill' => array( //web service function name - 'classname' => 'block_gradelevel_skillmgmtservice', //class containing the external function - 'methodname' => 'delete_skill', //external function name - 'classpath' => 'blocks/gradelevel/skillmgmtservice.php', //file containing the class/external function - 'description' => 'Delete a skill', //human readable description of the web service function - 'type' => 'read', //database rights of the web service function (read, write) - 'ajax' => true, - 'capabilities' => 'block/gradelevel:skillmanager', - 'loginrequired' => true, - 'services' => array('block_gradelevel_levelmgmt'), - ), - -); + array( + 'functions' => array('block_gradelevel_submit_levels', 'block_gradelevel_list_levels'), + 'requiredcapability' => 'block/gradelevel:changelevels', + 'shortname'=> 'block_gradelevel_levelmgmt', + 'restrictedusers' => 0, + 'enabled' => 0, + 'ajax' => true, + ), +); + +$functions = array( + 'block_gradelevel_submit_levels' => array( //web service function name + 'classname' => 'block_gradelevel_skillmgmtservice', //class containing the external function + 'methodname' => 'submit_levels', //external function name + 'classpath' => 'blocks/gradelevel/skillmgmtservice.php', //file containing the class/external function + 'description' => 'Update level settings for a given skill', //human readable description of the web service function + 'type' => 'write', //database rights of the web service function (read, write) + 'ajax' => true, + 'capabilities' => 'block/gradelevel:skillmanager', + 'loginrequired' => true, + 'services' => array('block_gradelevel_levelmgmt'), + ), + 'block_gradelevel_list_levels' => array( //web service function name + 'classname' => 'block_gradelevel_skillmgmtservice', //class containing the external function + 'methodname' => 'list_levels', //external function name + 'classpath' => 'blocks/gradelevel/skillmgmtservice.php', //file containing the class/external function + 'description' => 'List level settings for a given skill', //human readable description of the web service function + 'type' => 'read', //database rights of the web service function (read, write) + 'ajax' => true, + 'capabilities' => 'block/gradelevel:skillmanager', + 'loginrequired' => true, + 'services' => array('block_gradelevel_levelmgmt'), + ), + 'block_gradelevel_list_skills' => array( //web service function name + 'classname' => 'block_gradelevel_skillmgmtservice', //class containing the external function + 'methodname' => 'list_skills', //external function name + 'classpath' => 'blocks/gradelevel/skillmgmtservice.php', //file containing the class/external function + 'description' => 'List skills', //human readable description of the web service function + 'type' => 'read', //database rights of the web service function (read, write) + 'ajax' => true, + 'capabilities' => 'block/gradelevel:skillmanager', + 'loginrequired' => true, + 'services' => array('block_gradelevel_levelmgmt'), + ), + 'block_gradelevel_get_skill' => array( //web service function name + 'classname' => 'block_gradelevel_skillmgmtservice', //class containing the external function + 'methodname' => 'get_skill', //external function name + 'classpath' => 'blocks/gradelevel/skillmgmtservice.php', //file containing the class/external function + 'description' => 'Retrieve skill information', //human readable description of the web service function + 'type' => 'read', //database rights of the web service function (read, write) + 'ajax' => true, + 'capabilities' => 'block/gradelevel:skillmanager', + 'loginrequired' => true, + 'services' => array('block_gradelevel_levelmgmt'), + ), + 'block_gradelevel_update_skill' => array( //web service function name + 'classname' => 'block_gradelevel_skillmgmtservice', //class containing the external function + 'methodname' => 'update_skill', //external function name + 'classpath' => 'blocks/gradelevel/skillmgmtservice.php', //file containing the class/external function + 'description' => 'Update a skill', //human readable description of the web service function + 'type' => 'read', //database rights of the web service function (read, write) + 'ajax' => true, + 'capabilities' => 'block/gradelevel:skillmanager', + 'loginrequired' => true, + 'services' => array('block_gradelevel_levelmgmt'), + ), + 'block_gradelevel_add_skill' => array( //web service function name + 'classname' => 'block_gradelevel_skillmgmtservice', //class containing the external function + 'methodname' => 'add_skill', //external function name + 'classpath' => 'blocks/gradelevel/skillmgmtservice.php', //file containing the class/external function + 'description' => 'Add a new skill', //human readable description of the web service function + 'type' => 'read', //database rights of the web service function (read, write) + 'ajax' => true, + 'capabilities' => 'block/gradelevel:skillmanager', + 'loginrequired' => true, + 'services' => array('block_gradelevel_levelmgmt'), + ), + 'block_gradelevel_delete_skill' => array( //web service function name + 'classname' => 'block_gradelevel_skillmgmtservice', //class containing the external function + 'methodname' => 'delete_skill', //external function name + 'classpath' => 'blocks/gradelevel/skillmgmtservice.php', //file containing the class/external function + 'description' => 'Delete a skill', //human readable description of the web service function + 'type' => 'read', //database rights of the web service function (read, write) + 'ajax' => true, + 'capabilities' => 'block/gradelevel:skillmanager', + 'loginrequired' => true, + 'services' => array('block_gradelevel_levelmgmt'), + ), + +); diff --git a/db/upgrade.php b/db/upgrade.php index e043fec..e5069f2 100644 --- a/db/upgrade.php +++ b/db/upgrade.php @@ -1,70 +1,70 @@ -get_manager(); - - /// Add a new column newcol to the mdl_myqtype_options - if ($oldversion < 2018091600) { - - // Define table block_gradelevel_levelset to be created. - $table = new xmldb_table('block_gradelevel_levelset'); - - // Adding fields to table block_gradelevel_levelset. - $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null); - $table->add_field('name', XMLDB_TYPE_CHAR, '128', null, null, null, null); - $table->add_field('parent_id', XMLDB_TYPE_INTEGER, '10', null, null, null, null); - $table->add_field('icon', XMLDB_TYPE_TEXT, null, null, null, null, null); - - - // Adding keys to table block_gradelevel_levelset. - $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id')); - $table->add_key('parent_id_idx', XMLDB_KEY_FOREIGN, array('parent_id'), 'block_gradelevel_levelset', array('id')); - - // Conditionally launch create table for block_gradelevel_levelset. - if (!$dbman->table_exists($table)) { - $dbman->create_table($table); - } - - // Define table block_gradelevel_levels to be created. - $table = new xmldb_table('block_gradelevel_levels'); - - // Adding fields to table block_gradelevel_levels. - $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null); - $table->add_field('levelset_id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null); - $table->add_field('points', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0'); - $table->add_field('badgecolor', XMLDB_TYPE_CHAR, '10', null, null, null, null); - - // Adding keys to table block_gradelevel_levels. - $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id')); - $table->add_key('levelset_id_idx', XMLDB_KEY_FOREIGN, array('levelset_id'), 'block_gradelevel_levelset', array('id')); - - // Conditionally launch create table for block_gradelevel_levels. - if (!$dbman->table_exists($table)) { - $dbman->create_table($table); - } - - // Define table block_gradelevel_course_link to be created. - $table = new xmldb_table('block_gradelevel_course_link'); - - // Adding fields to table block_gradelevel_course_link. - $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null); - $table->add_field('course_id', XMLDB_TYPE_INTEGER, '20', null, XMLDB_NOTNULL, null, null); - $table->add_field('levelset_id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null); - - // Adding keys to table block_gradelevel_course_link. - $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id')); - $table->add_key('course_id_idx', XMLDB_KEY_FOREIGN_UNIQUE, array('course_id'), 'mdl_course', array('id')); - $table->add_key('levelset_id_idx', XMLDB_KEY_FOREIGN, array('levelset_id'), 'block_gradelevel_levelset', array('id')); - - // Conditionally launch create table for block_gradelevel_course_link. - if (!$dbman->table_exists($table)) { - $dbman->create_table($table); - } - - // Gradelevel savepoint reached. - upgrade_block_savepoint(true, 2018091600, 'gradelevel'); - - } - - return true; +get_manager(); + + /// Add a new column newcol to the mdl_myqtype_options + if ($oldversion < 2018091600) { + + // Define table block_gradelevel_levelset to be created. + $table = new xmldb_table('block_gradelevel_levelset'); + + // Adding fields to table block_gradelevel_levelset. + $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null); + $table->add_field('name', XMLDB_TYPE_CHAR, '128', null, null, null, null); + $table->add_field('parent_id', XMLDB_TYPE_INTEGER, '10', null, null, null, null); + $table->add_field('icon', XMLDB_TYPE_TEXT, null, null, null, null, null); + + + // Adding keys to table block_gradelevel_levelset. + $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id')); + $table->add_key('parent_id_idx', XMLDB_KEY_FOREIGN, array('parent_id'), 'block_gradelevel_levelset', array('id')); + + // Conditionally launch create table for block_gradelevel_levelset. + if (!$dbman->table_exists($table)) { + $dbman->create_table($table); + } + + // Define table block_gradelevel_levels to be created. + $table = new xmldb_table('block_gradelevel_levels'); + + // Adding fields to table block_gradelevel_levels. + $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null); + $table->add_field('levelset_id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null); + $table->add_field('points', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0'); + $table->add_field('badgecolor', XMLDB_TYPE_CHAR, '10', null, null, null, null); + + // Adding keys to table block_gradelevel_levels. + $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id')); + $table->add_key('levelset_id_idx', XMLDB_KEY_FOREIGN, array('levelset_id'), 'block_gradelevel_levelset', array('id')); + + // Conditionally launch create table for block_gradelevel_levels. + if (!$dbman->table_exists($table)) { + $dbman->create_table($table); + } + + // Define table block_gradelevel_course_link to be created. + $table = new xmldb_table('block_gradelevel_course_link'); + + // Adding fields to table block_gradelevel_course_link. + $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null); + $table->add_field('course_id', XMLDB_TYPE_INTEGER, '20', null, XMLDB_NOTNULL, null, null); + $table->add_field('levelset_id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null); + + // Adding keys to table block_gradelevel_course_link. + $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id')); + $table->add_key('course_id_idx', XMLDB_KEY_FOREIGN_UNIQUE, array('course_id'), 'mdl_course', array('id')); + $table->add_key('levelset_id_idx', XMLDB_KEY_FOREIGN, array('levelset_id'), 'block_gradelevel_levelset', array('id')); + + // Conditionally launch create table for block_gradelevel_course_link. + if (!$dbman->table_exists($table)) { + $dbman->create_table($table); + } + + // Gradelevel savepoint reached. + upgrade_block_savepoint(true, 2018091600, 'gradelevel'); + + } + + return true; } \ No newline at end of file diff --git a/doc_USER_GRADE.txt b/doc_USER_GRADE.txt index 1877541..a6b6084 100644 --- a/doc_USER_GRADE.txt +++ b/doc_USER_GRADE.txt @@ -1,184 +1,184 @@ - USER: stdClass Object -( - [id] => 2 - [auth] => manual - [confirmed] => 1 - [policyagreed] => 0 - [deleted] => 0 - [suspended] => 0 - [mnethostid] => 1 - [username] => admin - [idnumber] => - [firstname] => Admin - [lastname] => User - [email] => pm@miqra.nl - [emailstop] => 0 - [icq] => - [skype] => - [yahoo] => - [aim] => - [msn] => - [phone1] => - [phone2] => - [institution] => - [department] => - [address] => - [city] => - [country] => - [lang] => en - [calendartype] => gregorian - [theme] => - [timezone] => 99 - [firstaccess] => 1530717879 - [lastaccess] => 1536959033 - [lastlogin] => 1530732921 - [currentlogin] => 1536953353 - [lastip] => 2001:985:a081:1:5dfc:c799:21e1:4fdc - [secret] => - [picture] => 0 - [url] => - [descriptionformat] => 1 - [mailformat] => 1 - [maildigest] => 0 - [maildisplay] => 1 - [autosubscribe] => 1 - [trackforums] => 0 - [timecreated] => 0 - [timemodified] => 1530717931 - [trustbitmask] => 0 - [imagealt] => - [lastnamephonetic] => - [firstnamephonetic] => - [middlename] => - [alternatename] => - [lastcourseaccess] => Array - ( - [2] => 1530738819 - ) - - [currentcourseaccess] => Array - ( - [2] => 1536959033 - ) - - [groupmember] => Array - ( - ) - - [profile] => Array - ( - ) - - [sesskey] => ejGT6VqIBv - [ajax_updatable_user_prefs] => Array - ( - [drawer-open-nav] => alpha - [filepicker_recentrepository] => int - [filepicker_recentlicense] => safedir - [filepicker_recentviewmode] => int - [filemanager_recentviewmode] => int - ) - - [editing] => 1 - [preference] => Array - ( - [core_message_migrate_data] => 1 - [auth_manual_passwordupdatetime] => 1530717931 - [email_bounce_count] => 1 - [email_send_count] => 1 - [filepicker_recentrepository] => 4 - [filepicker_recentlicense] => allrightsreserved - [login_failed_count_since_success] => 6 - [ifirst] => - [ilast] => - [_lastloaded] => 1536959033 - ) - - [enrol] => Array - ( - [enrolled] => Array - ( - [2] => 2147483647 - ) - - [tempguest] => Array - ( - ) - - ) - - [grade_last_report] => Array - ( - [2] => grader - ) - - [gradeediting] => Array - ( - [2] => 0 - ) - - [access] => Array - ( - [ra] => Array - ( - [/1] => Array - ( - [7] => 7 - ) - - [/1/2] => Array - ( - [8] => 8 - ) - - [/1/40/23] => Array - ( - [3] => 3 - [5] => 5 - ) - - ) - - [time] => 1536958657 - [rsw] => Array - ( - ) - - ) - -) - -COURSE: stdClass Object -( - [id] => 2 - [category] => 2 - [sortorder] => 20001 - [fullname] => PIC Microcontrollers programmeren - [shortname] => PIC-MCU3 - [idnumber] => - [summary] => Programmeren van PIC18 microcontrollers van microchip. - [summaryformat] => 1 - [format] => topics - [showgrades] => 1 - [newsitems] => 0 - [startdate] => 1526594400 - [enddate] => 0 - [marker] => 0 - [maxbytes] => 0 - [legacyfiles] => 0 - [showreports] => 0 - [visible] => 1 - [visibleold] => 1 - [groupmode] => 0 - [groupmodeforce] => 0 - [defaultgroupingid] => 0 - [lang] => - [calendartype] => - [theme] => - [timecreated] => 1526555879 - [timemodified] => 1536956992 - [requested] => 0 - [enablecompletion] => 1 - [completionnotify] => 0 - [cacherev] => 1536956992 -) + USER: stdClass Object +( + [id] => 2 + [auth] => manual + [confirmed] => 1 + [policyagreed] => 0 + [deleted] => 0 + [suspended] => 0 + [mnethostid] => 1 + [username] => admin + [idnumber] => + [firstname] => Admin + [lastname] => User + [email] => pm@miqra.nl + [emailstop] => 0 + [icq] => + [skype] => + [yahoo] => + [aim] => + [msn] => + [phone1] => + [phone2] => + [institution] => + [department] => + [address] => + [city] => + [country] => + [lang] => en + [calendartype] => gregorian + [theme] => + [timezone] => 99 + [firstaccess] => 1530717879 + [lastaccess] => 1536959033 + [lastlogin] => 1530732921 + [currentlogin] => 1536953353 + [lastip] => 2001:985:a081:1:5dfc:c799:21e1:4fdc + [secret] => + [picture] => 0 + [url] => + [descriptionformat] => 1 + [mailformat] => 1 + [maildigest] => 0 + [maildisplay] => 1 + [autosubscribe] => 1 + [trackforums] => 0 + [timecreated] => 0 + [timemodified] => 1530717931 + [trustbitmask] => 0 + [imagealt] => + [lastnamephonetic] => + [firstnamephonetic] => + [middlename] => + [alternatename] => + [lastcourseaccess] => Array + ( + [2] => 1530738819 + ) + + [currentcourseaccess] => Array + ( + [2] => 1536959033 + ) + + [groupmember] => Array + ( + ) + + [profile] => Array + ( + ) + + [sesskey] => ejGT6VqIBv + [ajax_updatable_user_prefs] => Array + ( + [drawer-open-nav] => alpha + [filepicker_recentrepository] => int + [filepicker_recentlicense] => safedir + [filepicker_recentviewmode] => int + [filemanager_recentviewmode] => int + ) + + [editing] => 1 + [preference] => Array + ( + [core_message_migrate_data] => 1 + [auth_manual_passwordupdatetime] => 1530717931 + [email_bounce_count] => 1 + [email_send_count] => 1 + [filepicker_recentrepository] => 4 + [filepicker_recentlicense] => allrightsreserved + [login_failed_count_since_success] => 6 + [ifirst] => + [ilast] => + [_lastloaded] => 1536959033 + ) + + [enrol] => Array + ( + [enrolled] => Array + ( + [2] => 2147483647 + ) + + [tempguest] => Array + ( + ) + + ) + + [grade_last_report] => Array + ( + [2] => grader + ) + + [gradeediting] => Array + ( + [2] => 0 + ) + + [access] => Array + ( + [ra] => Array + ( + [/1] => Array + ( + [7] => 7 + ) + + [/1/2] => Array + ( + [8] => 8 + ) + + [/1/40/23] => Array + ( + [3] => 3 + [5] => 5 + ) + + ) + + [time] => 1536958657 + [rsw] => Array + ( + ) + + ) + +) + +COURSE: stdClass Object +( + [id] => 2 + [category] => 2 + [sortorder] => 20001 + [fullname] => PIC Microcontrollers programmeren + [shortname] => PIC-MCU3 + [idnumber] => + [summary] => Programmeren van PIC18 microcontrollers van microchip. + [summaryformat] => 1 + [format] => topics + [showgrades] => 1 + [newsitems] => 0 + [startdate] => 1526594400 + [enddate] => 0 + [marker] => 0 + [maxbytes] => 0 + [legacyfiles] => 0 + [showreports] => 0 + [visible] => 1 + [visibleold] => 1 + [groupmode] => 0 + [groupmodeforce] => 0 + [defaultgroupingid] => 0 + [lang] => + [calendartype] => + [theme] => + [timecreated] => 1526555879 + [timemodified] => 1536956992 + [requested] => 0 + [enablecompletion] => 1 + [completionnotify] => 0 + [cacherev] => 1536956992 +) diff --git a/edit_form.php b/edit_form.php index 18a06b0..9391192 100644 --- a/edit_form.php +++ b/edit_form.php @@ -1,82 +1,82 @@ -dirroot.'/blocks/gradelevel/lib.php'); - -class block_gradelevel_edit_form extends block_edit_form { - - - protected function specific_definition($mform) { - global $CFG; - // retrieve levelset - $levelset = $this->block->levelset; - - // Section header title according to language file. - $mform->addElement('header', 'config_header', get_string('blocksettings', 'block')); - - $skills = block_gradelevel_levelset::list_all(); - // sort by name - usort( $skills, function( $a, $b) { - return ( $a->getName() < $b->getName()) ? -1 : 1; - } ); - - $options = array( '-1' => get_string('edit_noneskill', 'block_gradelevel')); - foreach($skills as $skill) - { - $options[$skill->getId()] = $skill->getName(); - } - - // A sample string variable with a default value. - $mform->addElement('select', 'attached_skill', get_string('edit_pickskill', 'block_gradelevel'), $options); - - - } - - public function set_data($defaults) - { - global $COURSE; - - $defaults->attached_skill = -1; - // retrieve levelset - $skill = block_gradelevel_levelset::find_by_course($COURSE->id); - if(isset($skill)){ - $defaults->attached_skill = $skill->getId(); - } - - - parent::set_data($defaults); - } - - - public function get_data() - { - $data = parent::get_data(); - - if(isset($data)) - { - $this->save_data($data); - } - - return $data; - } - - protected function save_data($data) - { - global $COURSE; - // retrieve levelset - - if(isset($data->attached_skill)) - { - $currentskill = block_gradelevel_levelset::find_by_course($COURSE->id); - if(isset($currentskill)) - { - $currentskill->detach_course($COURSE->id); - } - - $skill = block_gradelevel_levelset::find_by_id($data->attached_skill); - if(isset($skill)){ - $skill->attach_course($COURSE->id); - } - } - } - +dirroot.'/blocks/gradelevel/lib.php'); + +class block_gradelevel_edit_form extends block_edit_form { + + + protected function specific_definition($mform) { + global $CFG; + // retrieve levelset + $levelset = $this->block->levelset; + + // Section header title according to language file. + $mform->addElement('header', 'config_header', get_string('blocksettings', 'block')); + + $skills = block_gradelevel_levelset::list_all(); + // sort by name + usort( $skills, function( $a, $b) { + return ( $a->getName() < $b->getName()) ? -1 : 1; + } ); + + $options = array( '-1' => get_string('edit_noneskill', 'block_gradelevel')); + foreach($skills as $skill) + { + $options[$skill->getId()] = $skill->getName(); + } + + // A sample string variable with a default value. + $mform->addElement('select', 'attached_skill', get_string('edit_pickskill', 'block_gradelevel'), $options); + + + } + + public function set_data($defaults) + { + global $COURSE; + + $defaults->attached_skill = -1; + // retrieve levelset + $skill = block_gradelevel_levelset::find_by_course($COURSE->id); + if(isset($skill)){ + $defaults->attached_skill = $skill->getId(); + } + + + parent::set_data($defaults); + } + + + public function get_data() + { + $data = parent::get_data(); + + if(isset($data)) + { + $this->save_data($data); + } + + return $data; + } + + protected function save_data($data) + { + global $COURSE; + // retrieve levelset + + if(isset($data->attached_skill)) + { + $currentskill = block_gradelevel_levelset::find_by_course($COURSE->id); + if(isset($currentskill)) + { + $currentskill->detach_course($COURSE->id); + } + + $skill = block_gradelevel_levelset::find_by_id($data->attached_skill); + if(isset($skill)){ + $skill->attach_course($COURSE->id); + } + } + } + } \ No newline at end of file diff --git a/lang/en/block_gradelevel.php b/lang/en/block_gradelevel.php index 79122b9..fef09e4 100644 --- a/lang/en/block_gradelevel.php +++ b/lang/en/block_gradelevel.php @@ -1,54 +1,54 @@ -libdir.'/gradelib.php'); -require_once($CFG->dirroot.'/grade/querylib.php'); - +libdir.'/gradelib.php'); +require_once($CFG->dirroot.'/grade/querylib.php'); + diff --git a/package-lock.json b/package-lock.json index 48e341a..5a05955 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,3 +1,3 @@ -{ - "lockfileVersion": 1 -} +{ + "lockfileVersion": 1 +} diff --git a/pix/undefinedskill.svg b/pix/undefinedskill.svg index ae44c4e..01dd950 100644 --- a/pix/undefinedskill.svg +++ b/pix/undefinedskill.svg @@ -1,772 +1,772 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - M - - - - - FZ - - - - - M - - - - - FZ - - S - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + M + + + + + FZ + + + + + M + + + + + FZ + + S + + + diff --git a/settings.php b/settings.php index 17b7ee7..d4f01a2 100644 --- a/settings.php +++ b/settings.php @@ -1,40 +1,40 @@ -add('blocksettings', new admin_category('block_gradelevel', get_string('cfgpage_generic', 'block_gradelevel'))); - - -$settings->add(new admin_setting_configcheckbox( - 'gradelevel/showtitle', - get_string('labelshowtitle', 'block_gradelevel'), - get_string('descshowtitle', 'block_gradelevel'), - true - )); - -$settings->add(new admin_setting_configtext( - 'gradelevel/blocktitle', - get_string('labeltitle', 'block_gradelevel'), - get_string('desctitle', 'block_gradelevel'), - get_string('title', 'block_gradelevel') - )); - - -$ADMIN->add("block_gradelevel", $settings); - -// Add the default levels page -$ADMIN->add("block_gradelevel", new admin_externalpage( - 'block_gradelevel_default_levels', - get_string('cfgpage_globallevels', 'block_gradelevel'), - $CFG->wwwroot . '/blocks/gradelevel/cfg_globallevels.php' - ) - ); - -// Add the skill configpage -$ADMIN->add("block_gradelevel", new admin_externalpage( - 'block_gradelevel_config_skills', - get_string('cfgpage_skills', 'block_gradelevel'), - $CFG->wwwroot . '/blocks/gradelevel/cfg_skills.php' - ) - ); - - +add('blocksettings', new admin_category('block_gradelevel', get_string('cfgpage_generic', 'block_gradelevel'))); + + +$settings->add(new admin_setting_configcheckbox( + 'gradelevel/showtitle', + get_string('labelshowtitle', 'block_gradelevel'), + get_string('descshowtitle', 'block_gradelevel'), + true + )); + +$settings->add(new admin_setting_configtext( + 'gradelevel/blocktitle', + get_string('labeltitle', 'block_gradelevel'), + get_string('desctitle', 'block_gradelevel'), + get_string('title', 'block_gradelevel') + )); + + +$ADMIN->add("block_gradelevel", $settings); + +// Add the default levels page +$ADMIN->add("block_gradelevel", new admin_externalpage( + 'block_gradelevel_default_levels', + get_string('cfgpage_globallevels', 'block_gradelevel'), + $CFG->wwwroot . '/blocks/gradelevel/cfg_globallevels.php' + ) + ); + +// Add the skill configpage +$ADMIN->add("block_gradelevel", new admin_externalpage( + 'block_gradelevel_config_skills', + get_string('cfgpage_skills', 'block_gradelevel'), + $CFG->wwwroot . '/blocks/gradelevel/cfg_skills.php' + ) + ); + + $settings = null; \ No newline at end of file diff --git a/styles.css b/styles.css index 732ce46..490c2df 100644 --- a/styles.css +++ b/styles.css @@ -1,145 +1,145 @@ -figure.levelbadge, figure.dummybadge { - text-align: center; -} - -div.pointinfo { - text-align: center; - font-family: "Open Sans", Arial, helvetica, sans-serif; -} - -div.pointinfo.complete { - font-weight: bold; -} - -div.pointinfo span.currentpoints { - font-weight: bold; -} - -div.pointinfo span.leveluppoints { - font-weight: bold; -} - -div.teachermode { - text-align: center; -} - -td.block_gradelevel_addlevel { - text-align: right; - -} -input.jscolor[type=text] { - width: 5em; -} - - -div.skill_set ul { - padding-left: 0px; - margin-left: 0px; -} - -div.skill_set li.skill_info { - position: relative; /* needed to have absolute positions be relative to this li */ - list-style: none; - display: inline-block; - width: 220px; - height: 220px; - border-color: #ccc; - border-width: 1px; - border-style: solid; - margin: 5px; - padding: 5px; - padding-top: 10px; -} - - -div.skill_set li.skill_info figcaption { - text-align: center; -} - -div.skill_set li.skill_info input[type=text] { - width: 150px; -} - -div.skill_set li.skill_info a.editicon { - position: absolute; - top: 5px; - right: 10px; -} - -div.skill_set li.skill_info a.deleteskill { - position: absolute; - top: 5px; - left: 7px; -} -div.skill_set li.skill_info a.deleteskill i.icon { - color: red; -} - -div.skill_set li.skill_info a.deleteskill i.icon.disabled { - color: #ccc; -} - -div.skill_set li.skill_info a.editname { - margin-left: 5px; -} - -table.level_config tr[data-rowid='-255'] { -} - -table.level_config tr[data-rowid='-255'] td { - -} - -table.level_config tr[data-rowid='-255'] input[data-name='points'] { - background-color: #F0F0F0; -} - -div.block_gradelevel_results ul { - padding-left: 0px; - margin-left: 0px; -} - -div .block_gradelevel_results li.result_badge { - position: relative; /* needed to have absolute positions be relative to this li */ - list-style: none; - display: inline-block; - width: 190px; - height: 210px; - border-color: #ccc; - border-width: 1px; - border-style: solid; - margin: 5px; - padding: 5px; - padding-top: 10px; -} - - -div.block_gradelevel_results li.result_badge figcaption { - text-align: center; - height: 3em; - margin-bottom: 0.5rem; -} - -div.block_gradelevel_results li.result_badge div.pointinfo { - font-size: 85%; -} - -div.block_gradelevel_results li.result_badge figcaption span.namepart { - break-inside: avoid; - display: inline-block; -} - -div.block_gradelevel_results figure.levelset_icon { - margin-bottom: 0.5rem; -} - -figure.levelset_icon { - background-color: #ccc; - background-image: -moz-linear-gradient(45deg, #aaa 25%, transparent 25%), -moz-linear-gradient(-45deg, #aaa 25%, transparent 25%), -moz-linear-gradient(45deg, transparent 75%, #aaa 75%), -moz-linear-gradient(-45deg, transparent 75%, #aaa 75%); - background-image: -webkit-gradient(linear, 0 100%, 100% 0, color-stop(.25, #aaa), color-stop(.25, transparent)), -webkit-gradient(linear, 0 0, 100% 100%, color-stop(.25, #aaa), color-stop(.25, transparent)), -webkit-gradient(linear, 0 100%, 100% 0, color-stop(.75, transparent), color-stop(.75, #aaa)), -webkit-gradient(linear, 0 0, 100% 100%, color-stop(.75, transparent), color-stop(.75, #aaa)); - -moz-background-size: 32px 32px; - background-size: 32px 33px; - -webkit-background-size: 32px 33px; /* override value for shitty webkit */ - background-position: 0 0, 16px 0, 16px -16px, 0px 16px; - display: inline-block; +figure.levelbadge, figure.dummybadge { + text-align: center; +} + +div.pointinfo { + text-align: center; + font-family: "Open Sans", Arial, helvetica, sans-serif; +} + +div.pointinfo.complete { + font-weight: bold; +} + +div.pointinfo span.currentpoints { + font-weight: bold; +} + +div.pointinfo span.leveluppoints { + font-weight: bold; +} + +div.teachermode { + text-align: center; +} + +td.block_gradelevel_addlevel { + text-align: right; + +} +input.jscolor[type=text] { + width: 5em; +} + + +div.skill_set ul { + padding-left: 0px; + margin-left: 0px; +} + +div.skill_set li.skill_info { + position: relative; /* needed to have absolute positions be relative to this li */ + list-style: none; + display: inline-block; + width: 220px; + height: 220px; + border-color: #ccc; + border-width: 1px; + border-style: solid; + margin: 5px; + padding: 5px; + padding-top: 10px; +} + + +div.skill_set li.skill_info figcaption { + text-align: center; +} + +div.skill_set li.skill_info input[type=text] { + width: 150px; +} + +div.skill_set li.skill_info a.editicon { + position: absolute; + top: 5px; + right: 10px; +} + +div.skill_set li.skill_info a.deleteskill { + position: absolute; + top: 5px; + left: 7px; +} +div.skill_set li.skill_info a.deleteskill i.icon { + color: red; +} + +div.skill_set li.skill_info a.deleteskill i.icon.disabled { + color: #ccc; +} + +div.skill_set li.skill_info a.editname { + margin-left: 5px; +} + +table.level_config tr[data-rowid='-255'] { +} + +table.level_config tr[data-rowid='-255'] td { + +} + +table.level_config tr[data-rowid='-255'] input[data-name='points'] { + background-color: #F0F0F0; +} + +div.block_gradelevel_results ul { + padding-left: 0px; + margin-left: 0px; +} + +div .block_gradelevel_results li.result_badge { + position: relative; /* needed to have absolute positions be relative to this li */ + list-style: none; + display: inline-block; + width: 190px; + height: 210px; + border-color: #ccc; + border-width: 1px; + border-style: solid; + margin: 5px; + padding: 5px; + padding-top: 10px; +} + + +div.block_gradelevel_results li.result_badge figcaption { + text-align: center; + height: 3em; + margin-bottom: 0.5rem; +} + +div.block_gradelevel_results li.result_badge div.pointinfo { + font-size: 85%; +} + +div.block_gradelevel_results li.result_badge figcaption span.namepart { + break-inside: avoid; + display: inline-block; +} + +div.block_gradelevel_results figure.levelset_icon { + margin-bottom: 0.5rem; +} + +figure.levelset_icon { + background-color: #ccc; + background-image: -moz-linear-gradient(45deg, #aaa 25%, transparent 25%), -moz-linear-gradient(-45deg, #aaa 25%, transparent 25%), -moz-linear-gradient(45deg, transparent 75%, #aaa 75%), -moz-linear-gradient(-45deg, transparent 75%, #aaa 75%); + background-image: -webkit-gradient(linear, 0 100%, 100% 0, color-stop(.25, #aaa), color-stop(.25, transparent)), -webkit-gradient(linear, 0 0, 100% 100%, color-stop(.25, #aaa), color-stop(.25, transparent)), -webkit-gradient(linear, 0 100%, 100% 0, color-stop(.75, transparent), color-stop(.75, #aaa)), -webkit-gradient(linear, 0 0, 100% 100%, color-stop(.75, transparent), color-stop(.75, #aaa)); + -moz-background-size: 32px 32px; + background-size: 32px 33px; + -webkit-background-size: 32px 33px; /* override value for shitty webkit */ + background-position: 0 0, 16px 0, 16px -16px, 0px 16px; + display: inline-block; } \ No newline at end of file diff --git a/version.php b/version.php index 760c91c..5c3fab6 100644 --- a/version.php +++ b/version.php @@ -1,4 +1,4 @@ -component = 'block_gradelevel'; // Recommended since 2.0.2 (MDL-26035). Required since 3.0 (MDL-48494) -$plugin->version = 2018092402; // YYYYMMDDHH (year, month, day, 24-hr time) +component = 'block_gradelevel'; // Recommended since 2.0.2 (MDL-26035). Required since 3.0 (MDL-48494) +$plugin->version = 2018112400; // YYYYMMDDHH (year, month, day, iteration) $plugin->requires = 2018050800; // YYYYMMDDHH (This is the release version for Moodle 3.5) \ No newline at end of file diff --git a/view-icon.php b/view-icon.php index c09b0ec..24cf0a2 100644 --- a/view-icon.php +++ b/view-icon.php @@ -1,49 +1,49 @@ -get_record('block_gradelevel_levelset', array('id' => $skillid)); - -if(is_object($skill)) -{ - $image = $skill->icon; - - if(strncmp($image,"data:",5) == 0) - { - $parts = explode(",",ltrim(substr($image,5)),2); - $typeinfo = explode(";",$parts[0]); - if(count($typeinfo) > 1 && $typeinfo[1] == "base64") - { - $binary = base64_decode($parts[1]); - } - else - { - $binary = $parts[0]; - } - $mime = $typeinfo[0]; - - // set header - header("Content-type: {$mime}"); - header("Pragma:"); - // Allow cache for 5 seconds - header("Cache-Control: public, max-age=5"); - // print data - print $binary; - } - else - { - // redirect to specified url - header("Location: {$image}",true,302); - } - -} -else -{ - http_response_code(404); - print "Image not found"; -} +get_record('block_gradelevel_levelset', array('id' => $skillid)); + +if(is_object($skill)) +{ + $image = $skill->icon; + + if(strncmp($image,"data:",5) == 0) + { + $parts = explode(",",ltrim(substr($image,5)),2); + $typeinfo = explode(";",$parts[0]); + if(count($typeinfo) > 1 && $typeinfo[1] == "base64") + { + $binary = base64_decode($parts[1]); + } + else + { + $binary = $parts[0]; + } + $mime = $typeinfo[0]; + + // set header + header("Content-type: {$mime}"); + header("Pragma:"); + // Allow cache for 5 seconds + header("Cache-Control: public, max-age=5"); + // print data + print $binary; + } + else + { + // redirect to specified url + header("Location: {$image}",true,302); + } + +} +else +{ + http_response_code(404); + print "Image not found"; +} diff --git a/view-results.php b/view-results.php index a371918..f2044ce 100644 --- a/view-results.php +++ b/view-results.php @@ -1,90 +1,90 @@ -set_url('/blocks/gradelevel/view-results.php', array('courseid' => $courseid)); -$PAGE->set_pagelayout('standard'); - -$PAGE->requires->js_call_amd('block_gradelevel/renderbadge', 'init'); - -print $OUTPUT->header(); -print $OUTPUT->heading(get_string('results_heading', 'block_gradelevel') . " " . $skill->getName()); -print "

    ".get_string('course', 'core')." " . $COURSE->fullname."

    "; - -//retrieve groups -$course_groups = groups_get_all_groups($COURSE->id); -// retrieve users -$remaining_users = get_enrolled_users($coursecontext); - -print "
    "; -if(count($course_groups > 0)) -{ - // loop through all groups first - foreach($course_groups as $grp) - { - // Get members - $members = get_enrolled_users($coursecontext,'mod/assignment:submit',$grp->id); - $count_members = count($members); - - // show group name - print $OUTPUT->heading($grp->name." ({$count_members})",4,array('groupname')); - - // Sort on last name - usort( $members, function( $a, $b) { - return ( $a->lastname < $b->lastname ) ? -1 : 1; - } ); - - print "
      "; - foreach($members as $usr) - { - $pointstotal = $skill->get_levelset_grade($usr->id); - $level_info = $skill->calculate_level($pointstotal); - - print "
    • "; - print $skill->render_badge($pointstotal,BADGE_SIZE); - print "
      {$usr->firstname} {$usr->lastname}
      "; - print "
      ".get_string('levelup_at','block_gradelevel')." {$level_info->points_in_level}/{$level_info->levelup_total}
      "; - print "
    • "; - unset($remaining_users[$usr->id]); - } - print "
    "; - } - - $count_members = count($remaining_users); - print $OUTPUT->heading(get_string('results_ungrouped','block_gradelevel')." ({$count_members})" ,4,array('groupname')); - -} -usort( $remaining_users, function( $a, $b) { - return ( $a->lastname < $b->lastname ) ? -1 : 1; -} ); -print "
      "; -foreach($remaining_users as $usr) -{ - $pointstotal = $skill->get_levelset_grade($usr->id); - $level_info = $skill->calculate_level($pointstotal); - print "
    • "; - print $skill->render_badge($pointstotal,BADGE_SIZE); - print "
      {$usr->firstname} {$usr->lastname}
      "; - print "
      ".get_string('levelup_at','block_gradelevel')." {$level_info->points_in_level}/{$level_info->levelup_total}
      "; - print "
    • "; - -} -print "
    "; - -print "
    "; - -print $OUTPUT->footer(); +set_url('/blocks/gradelevel/view-results.php', array('courseid' => $courseid)); +$PAGE->set_pagelayout('standard'); + +$PAGE->requires->js_call_amd('block_gradelevel/renderbadge', 'init'); + +print $OUTPUT->header(); +print $OUTPUT->heading(get_string('results_heading', 'block_gradelevel') . " " . $skill->getName()); +print "

    ".get_string('course', 'core')." " . $COURSE->fullname."

    "; + +//retrieve groups +$course_groups = groups_get_all_groups($COURSE->id); +// retrieve users +$remaining_users = get_enrolled_users($coursecontext); + +print "
    "; +if(count($course_groups > 0)) +{ + // loop through all groups first + foreach($course_groups as $grp) + { + // Get members + $members = get_enrolled_users($coursecontext,'mod/assignment:submit',$grp->id); + $count_members = count($members); + + // show group name + print $OUTPUT->heading($grp->name." ({$count_members})",4,array('groupname')); + + // Sort on last name + usort( $members, function( $a, $b) { + return ( $a->lastname < $b->lastname ) ? -1 : 1; + } ); + + print "
      "; + foreach($members as $usr) + { + $pointstotal = $skill->get_levelset_grade($usr->id); + $level_info = $skill->calculate_level($pointstotal); + + print "
    • "; + print $skill->render_badge($pointstotal,BADGE_SIZE); + print "
      {$usr->firstname} {$usr->lastname}
      "; + print "
      ".get_string('levelup_at','block_gradelevel')." {$level_info->points_in_level}/{$level_info->levelup_total}
      "; + print "
    • "; + unset($remaining_users[$usr->id]); + } + print "
    "; + } + + $count_members = count($remaining_users); + print $OUTPUT->heading(get_string('results_ungrouped','block_gradelevel')." ({$count_members})" ,4,array('groupname')); + +} +usort( $remaining_users, function( $a, $b) { + return ( $a->lastname < $b->lastname ) ? -1 : 1; +} ); +print "
      "; +foreach($remaining_users as $usr) +{ + $pointstotal = $skill->get_levelset_grade($usr->id); + $level_info = $skill->calculate_level($pointstotal); + print "
    • "; + print $skill->render_badge($pointstotal,BADGE_SIZE); + print "
      {$usr->firstname} {$usr->lastname}
      "; + print "
      ".get_string('levelup_at','block_gradelevel')." {$level_info->points_in_level}/{$level_info->levelup_total}
      "; + print "
    • "; + +} +print "
    "; + +print "
    "; + +print $OUTPUT->footer();