}
zzo6Jf+hk6PpXOf3qeF2uF2n`gy5{=MwGWOyYD=3mK4xg`8EJVa6>m!_Zxi8FHXct)
zfB3R?2+4HKK3gR|A$Jxzx(4{_bqGljWOJ*MI@{O3Lx@|~raZ@ZZhnK~c)0s<77H
z2Vh-1M6H~-h5K*P_7IWyU$${I*q%K7(4LDrsMMV#eIr!
zd8N}zobO1doNfRme56bwhrhY-|2L9lN!v(T7l5+n^cs=RM)Ew7M?klA2rPcZ5
z*R)T%N?HZ(e=mM-LM<1BkaEtc$<%^Ix_3vkhfpdF!RLprHryS#3v!Po?-^x}psv&0;k2o18+F8sQ=*v&w!F#TGtqFm#tT@9=FWBmFAGf^fRx2}nl9YMG@_a@5DBi@;o|GmB=
zu%EO8|4I9lvdxIs!Rq9HP1q&;T>lBv8XC!}n@V$VF*kpu=TxV_M8X{iH>FY|!bd36
zllu+f?c{eM{Ui6k*G94mj*Sp+jxsy_73hD~AJ=kmO*T709%l~J2;svSS
zkb5&_i*ipO?V$PRALfU&6J+YrZ+WJWIi1E&68_Sr?knHdczx@H*sfA*x#L-OOupF?;)Wumyl2=}n%
z&-u#5g}ON?m6Mdp-07)18h;_JE9S#ba4UCJZe96t9j*Mx{aOi>(N&In1oy9`zrg^~
zE7&?j`MYWkC-QcXUWafA?gR<=n}^2)GIAGIAu`9?O8m
zdpW#9vStHg2Y4Hk(x+Q|qYzKV_>&=?+rc$^bn6~FaFCaxv0c1fdkh#j$e)|8w6|{G
zq5msq`|6CIEqOD?_UYKESFCqX_gHVUUW0n{roOjcZ0}C71LDio_C&<*E9XfVzpsKP
z;r6x_J#WYSr#su59PlJ6lKemYapL!dcnZg#eCf#$pE{!_P5gqFp19)wqiar+9z3RT
l?2sO@LkIq^9y;-hGkUUa5B|dw=l%b5(Yt+Ma6pjv{{Xn=p!5I$
delta 26751
zcmZA91#}hH!iM2F2@b&mgb*abLVy6lg9dkZhu~fuiVW_4aEC%E4#mAli#l#+oj%)V&$e@twD;Mg1fL!xaBrrJGuz<`i0e4%adZyH`8l5BJgKcz
zu5ON#96VSDQ(`%6hoSfbhT#c}z{i*cQ+IcqTo{ev*dFuZ6wHSEG174|JNF2vK#Crw
zKs2g>&Zq{a%$O1AszAb?<_S?qO{W>EzyurLZu9S11HLg07r};-{E
z1;~`|aogRbC3(UpL#1CRZyo*WkiAx}Wz(KflRjp!Y4B%MxreBRU!jKf3#P(^LmVd~X2c9w64kM0m=E321cnmWj%uI)ksvII
z={=6q0JUg(4>Qiel*IR7T0DcH_yFTzd`8p{lVEO4hNZ9sYK;s=l^cyAJl~l>K+Aj;
zYKV@ZM&uC&<2zK3Qw}#3i(^jWF{lQ+qB_zS^}s2Z9QR-fJcUW{hV>Duy|eLhN?di}Nvp
z=R4PJLcCFC+f>1dqz}Q#cm$)d_GriHf)lYEzD7MTdW_ixy-{zr3^j!(Fg@PE{P+d6
zE274l5p08Q1`;L{$c1Y$5-(vLj5E%pH^hp>N1{gJ1Zr_59q%~BFbnFd7);x5dDSI0B{3cR-kf+v^)`Ou&AI+zt3pvrxVYIp%g;C|F<{|gi1zZi(0
zP*aqAqIse8*pYZuEP_9x+Iuw7HFJ|_k{ROks0TE}FdT%Pa3QMUILyZ)Op9t@J66Jr
z*cCHOahxhR0X2fZV-#ka$_~Ir$f|c{p%(XMmq0cGCs7SPN6lICX=brSVsYZ-a0$AY
z*u#FFuD!!N&M*&Hi+b>NREM(9GzU))Y)O0$X2jQ+2UB?+r!KnX2$WF)Oo~@9CH{rl
zMjtQ`6VEbpmi^Y^0!#)I5
za4e?5<*0(YP(8kk&G9itVC8vc`}D>j;)^j9w_`HAWYZsD7UEwpJ7%75)>aj3Hw@Pt
zP9va#JFVAHYv8Lj{Q`5sMPn%Gby4|!Fc_y>*J4`YM{WKMoBjbc6{!}Q^4T#b@fdXL
z6X;5yApV5?@U@M1UBtOhd>N{Nz{O^SvY{Sa0$FcPL)5N$h~Hu2C1%JsVF>Y~sPeZ_
zBm4n1aw(QF{wx9~VyP)u3iSp}u>|%(Eym3@{fPBCs)5%w9Q*g`r5w-t=zBg0uRwSUsG#Ja^Jj{rHU@m-*S}fUCn}%y+X5!ys
zHe8G~@d&B|DSt3?T^v=f0ct9SV+NdQ-Hf5y|GyH@n><7HG|n1hDC$j1U>2;0dhh^L
zhZmq4+=(i85i{Wv)QBcoYdVq{wJl4a%2h`lbZs%M_J3~zsyNA);5h3r9r2^6k+@~k
zzn~Uf&^nWz7u8@*)El-(Uk5Ra_yX%b)C=51?E=5`=Hoj(X5slxYnw0{Lx~?p&Hdk)
z1CwqrBUA)^Z-DA>TP%#DFckNoI&clu@h_+~kYS_waLb44SQp%blhM@!Ds3_k_y+Zc
zV^BR_jH+yoK>sfh=20
z!E&evwnbk@P;btYm`Otj6+c`#NXo(nYs{ZQpMV+;HhV=?!3)(1{QUfy|)
z+CAU9JIqj@#PrvHKJ*eleVq}pfd7ehU`0cxcB
z*z}R8AzzGXaUU|(u5*!qR_$ABy8UMVmqazx#Ks3;C~+^UquVegp0(+BQ62I>V9I5|
zVB+ObYo#UT#h#c7m*O@}>NWyusPjSd;3237%)^Yh6^G$@)atMElld&@h%v-hVF7%A
zTIJ~unFr^@v>rNuY#gV>VIGPBN9Yjg8*rsd!hxfvV%wk1{_ca}q))_1{1Mfm+n5bM
zVhD!)VurFLW+L7Y)#3iA?K>M)Z#RbG1x$~xZ9L^M#$Q8JfPfmTfoiBTDt&^De}|f)
zBd8I0VEu%8X{L#nV{g6!VV>iOj&OI0U<%;rPXeScIV-^{Y8@_fmK{>0j{$F8_`3
zuTQ{x&is7;5N8oDbKWeLOPGRqyx+~ilomBI#ZW_AAM;~RY>g|h0{UMtBUS}9%f0&M>
zLvs#I2}(eGJp$
z71U6_Ky@(bb+eYDP%jp1ordbbKJ2uq
z5wBqx%zVrIHmn^sBEA>vVc>03z9VXFY{3Hf7JWbe=elFRDq#^ahT}loW#dtI&DZkI
z7)|;uYw$g@JL+L5=|fSAYzfxJBN&3I{xmO`4>f{SQAcwux`hbzBG4RHqvkZueY0i$#fl
zhkCK=s1bgR{m>0~VScGJ3bpv2qUI|5zovn5n2vZi)DVutQ2Y+V@Tg6HfI4{mUYa*f
zi#nJhP}{FKcECR_panws{Q
z99-I$j6Rh#}VYDAKLV6^ncnF(aalBoUJ4%MNFs5e-Rnu_01
z4L?AY^ZRHzmIEV+SHw{4i+aFpo4*DN5kH2SqR&_!lYV0U^~Uu+nf=}#^(G5Y=?5`A
zp2HA)YU7DM+cki}q*t=G!8F7N+x(fBiujK<{}`qte#gc?eP;YM)M>t$Im?4Bi8sbj
z+==<|3UhWI1u1yVU4U&o_S9dC#kus1fx>8OtUN##&e~)vB
zKrT#%|DbyM8Ovcv9FG%*txz2pf$G>2)D-PPy~#d>|d3*;%ck2SwoE<|=&2`jvd4+m#qWIl7FP$%gNv{xoa2xoa29H)k6=E0jXH9(Co)4=3pFya*a=-!haO@$
zX5{y^MX@3-S3YWL!jgELK%Vc!5YUjW(N^~TdsL%#|~;4#!tRtYfWx}nNVK)vZw
z)VAA&s(0G@6hnz8PwMfV4^gP8sDthT4<7^sa$|CSEZIXw$rK*v6TZVx(qE_aI9rLQ
zNks$1Pon1VQEHFxw_lypc${X$-(VZ88|3l*G`tNpa?h|hhVkQ&My3U7WV~rTuJ4Wa
zlF*ujE2t5O4)*wdfM|$XrBkpUE<-KKH0eCPBe^M-P&yXI8(0ieg?OA8tc5zdr(ZoD>I92L4SgTfE|`Go=t9&RZ?y5-
zn4kDd)CrqCqv>c3Y)O2yOF$Lxp&ELFS{w-&XYGP;)Dc_))j%g3pN2(?sK+OVk=l
z&U~r8Jg7BM2G#MVs3W_#bsp*k+yex3WM06e_!xB#d_cWvqA)WeA*jVw1eIP5wVj%y
zI@}+%IA^1_*Df2sfwhS{nLWPWh}6M?L^mPJ#dZE9pwHuvSvs`sE^%|sF7HYzJtkn9~+bIgq!Wy1ee9dJpUN_;Tt%@gG`t2qKS#C0$R2cSB(%f>%oHR8o{nRdrv
zwD$i#0y==+pjK-Pdr@cgSS*R#P(%AKs-vkRjU{j#@wV6;A7ewToX51Y6o(MMkJ=@%
zdF^KhmL>ic-Sz~Exi)TJ;fy
z&Cs{OOvH!V_%hr<{2*#=^yi0vb>K(zjUejeOI_6CG{NYit{EyX37X@ps2=)9o1x8z
z1&HrLZKEfsZ5pSTnd>yD#a03}WleE8jz^71&f*^5A0B(6I&>LpVPFZ5vmM*I1auSz
zm-P7l)KUi3k!@HMZ{j)(D&=t=;}O(u*;Lw0$r~I*ymE{=84qJK;_=Fusf@*@DvvcW
zQCYKno8U6yZWjVtG>OZZp2i}lq;nEmlK!B)SzHw=n2IYO|~}n#z&BboSo@0y^0?px*2dYL2g?9{d!wSp2J+5ov)c
zw-43eWz-sYj_P>QYG#pTKrPNP7=~?Z`Y6;nu^K~ozH^3vdi1v~;Hhp3WJ3*MHPn9X
zh#H}_sDtSOYIh{8VHRmL>cgiy>YP}LW$+i&$Rwy~Mlctu!`0CZCD4U{Mqm=^jh3Q5
zM0Q~`oG-_=|zwl-$LX{fo`
zidu}HQ6EliI(U5lmdixcHykf&b?>tAd)5RU&5<5yZD1XWYJU}m;t34X{(nS3t2ap}
zQy>>=_0~d-M0eCkOhFCtCR9V`ZTcHj{j{BpMNs>{A*$R^)NWahIxnuH&W|Kr*#FuV
zWe7CJ+BgB%U~??cmCtcMzCNSA^BwALK6K7wIpSG+m^bN!YG(rKGhrU8{3g_*{RwrF
zUPVpGQ`GsAxF`ExM`^~Mra&~RKuuHwT~Q4TvGLh9{ynO}gQy2yK(+JO=EwWS#51At
zi&^Vn0^*%eYpB;Zu1Q!*LOc?FKrNC@s0Z)HBzPVZ<1I{tk5L_bgDMxlmoXI8k!VzY
zeVg7Mbz%;~!ngs|u|Hh``n39pnv)#8O+zhE75bwptU@(>9rNK^EQ&e$nDjQN^dYG9
z4X97W2dI6Xvaeax^-*hM395bfX9B|ryuvxyr=OY2ME%X12BY3QD{5r&q2{_624EG8
zkBx9PwnCMEZS@Q=KNSaHY0~?n@^=_r=P&^^{3~i`ub}4k9%@y;L+ys3fo2;PLM@(_
z*cN-C>R&{C7~R739!3bY`cr*tEQ+ev95rPrsp5
z7gV{ks3Y|PYFj=>ZOfE{&4?62)hmbk@M(dn*WJc@quLvZX|?|+5zrb~iF)uk)De0Y
zwfJ6HJwwdUCq*@!0X0&Qs1Yb@t&Un;4N(VFchm_x4>j}~QB$`EeZT)ZPC$$19%=~F
z4>dzo5Y>@Ns2(@LvKWgIxXQ-QVt(Q&hnWwd3OJegWYh}gj=%p=X$@y@6b
z+lYG3k&$-)Un4<7_$O+Jo}z~S18OMakMcO7m=Sd(m$tS-<-0b24*C<{iTaG#izG@D>OgI#hY`2Ip#0yP4^VhMEb5ok>ybe#DV9Dy3LpHK%y{PE`VKM!gyTch@WFVulF
z9M#}L)LPk$db7ir56_?%zo5haW=`-peTjP~`r3D$1e45z^P+mx12y;SupS;qjZFH<
zW*_InO2q49VO)e7^50Ph(PfN_w@`0>AAM7bTC^W99J5Z*nq&V}C!itghkCQgsG(hm
zYH$Urp|v)DD{9IPqZ&Sq`l@yXH9{Gunvuzi>PS`8$kevRqDH7M>fe%fTmp@8vdy@U
z+TYJn4|1lN5lDn;Fe9pCSyAP3qdHOoHByyP9d3@=4ee1U=}y#Qyo6fZZ_w4j;6L4*
zP{F8-jHn|r7wV)dk6K)$0+k<*8tLd6?0*F+
zlc0i)Y=QQ+zyQ=#jJ5G8s5e-IRd63_PUFoq2TVRxd?9wkpHcNAy=H_8qMjFnW3iT(
z{jWE>M8Z6LiW>59v&>x1Kvh_Q8saUe5!#O`cM>({7f>&7AEWRkYKpSWHtCVp;;8yn
zY`mdMU?K_aPz^pq_4vKjnPYxs8XvXYN}}enF}B75sKs;zHRtzG^`D?R^b&P~dght~
zsUVgo-ULUWyM#b70-^KFhfp(AL*r3zFax#d=A%YnDHg3)H9IXH7t2~@}KU^(soPXyF~n1yDpTcRp!C|NlN2A)ShKYH;(~^LW&@QMUby16MBI=97d{l!=P$%75)ZA`F
z)!S#|=TPUtE!0Rnv+4ee&7w!;Mkpd)fGS)W|MJO~HE92p(AGnlBcoNzkhQglZ`6a?{h4
zs3{0Xbu>3BzpTx#Wo?Ig;2@hn9W}>`ZT@B({~5KLuArvsrb|F?{uDLD@m81zrAEbb
zp$Zm54P8~#2(?0Ws1K^#1nWFhxz(r+?8Clz7PWTDe`gkTWh_P9twkU&fvKnp2e2-l
z!$z2OrTGQo5L`w4GHUw_U1f%P9u^_K1+(GbsNIw7d()A;7)iW5>SXMV`EVtMY5!j&
z(3XU^*aw@fHXS)@y^E^&8fRem52jcz<;e7sx+wdbf_WEiCQ$JP;*@!HFa&RgHh*z7uC*68{diQz;RT2H&F-N
zQ>APFzagL=C0S<*hN0f12x_rZM17g8j@nMWa19Pd--pb4^WaF-14^MfToW~-v8dI5
z3G?F}EQF~xu>W%qs7gSqybtQZ12KT_YU6Mv@!1XwFBp#cw!0YBp`TD|<~P&`Jw}b>r!DM%y?LS^O^-97_HO}H#p>%@hEn3cbIP?kvlz3FA}!lG|aw>
zk7I^<2kOYZx7+-9o_>$Vc|d$CR>1{(J-&Zi=1-hJJpVqkR`#J*{~gp~eTW*duc(nt
zy5D>Yb~6%aKte?1J3?xVi>JVL$D8}!5YXH30B
zt_`F@Juo9`kwl`7$ik?K<*f}+5A1+y_!|txA*k|;t?RG`@!hC)J5a9Y#&TMbuP3z%2OXD*IpiKJy=DXiA_8*0J$e
z)SC{l>0?kMG6(g>OHsd|*o4}ir%)YwfT8#fH5I|vOuJ#Ia`{mmt8tC}pP4{w60|r*
zp&l^b7FdBgXttxK=r%^<1JoObUN`$a9Q7h~Q0ZM!9qEmFq0u%z8#UsqQ6sv~wSiNp
zIsd~Jc!1jHA8mfz8|IBup(J5&ddVT}d^Jl1`a&DQUHV`%R?NRytP#qnM8tUn&4$iart5NOk!s>Vuvt!V0
z`}@CA1bhvmdfpH9xjo6IFGWq!CR9hyqaJh{ci=F7$-_lBLQ#rqu9f%tb#
z$AVE)lmmVL{eNBprAa7l3%ItxRMcvnjrvg8fciT93#xpvd**!j*18Ba5(iLIa~!o>
zE}}-{HtGeRS>yi6+S2|HA)xJ05zAp0)SGTZ4e>ry17}cEbRDbW8!Uxo?wf{(qZa2J
zRC~XnhWZs|!;lB&WGsUk!B*&MXgU&TfrC)1{W9u%yx(8u2+o9ah}S?(&7Y`U@(#5&
zJP*wfr$W7PcGS>EVSlWQ8p(sG=UqmX`}-mLUvK(~1U2aQx2c%eni(}@(Wq@x3pEw(
zaGHl7LNN>j9-H3_&&_YYf?v4ikIM^R
zm_MsO!Ms!`_pcecKA4aA0@TQyLXFHD)SCppG~b@Hq2hxv7fwM9@owybhfs?$_bYQG
zx5p^r%UlAga31yGFIWikyfy{fVL{?cQ60H}Di`;SNiT%j$6ZisWtw#vYB6p>Eylg5
z1ME0z=zm4+0{1Eb-*!SRmWMW;?5#OKf>1|nX;ep>p$?jHsCw5>4c$YnjYp^lf5A{p
z@XpkaK*cLyZtQ@JnCo~6Xc4VKHMk44CVoQo{8!YOea-p?^#&>4n-eoV`V-HAItOy2
z-ZTm|B1KSZtFle6gW64vFi>ArIuOw6{1&ynX4?3AtU&xI*2P30%vs+86SJ7cp+1hi
zAI&$Qvsi+7{!eBt^+eVC1=W$@&t|G}qNcJI7S{eBPC#e)PSi+TLY;_zTi;_1;t9W)
z?brxw6CZ<`noFqsf3Y3Ld^H_ig=+UK=EtWv3d8uKr>RK{-KJcSzBFQ|^D@i*qjA;fF?`?O{w8)R)cE
zs407dI)Xo-wq?OYe!l-QstIb|dGiN!`cgCaM;7@Fh8Izd~
z4aV}s*W+4zfm%C@lKc7o)Uq4Zk+2kgzOU^uxQuuYyp73H`uTRtbF4@_cc7nsUT~d0
z1avgsMXlO;(v&a9mvY(P9ShHC#0B%p0FAGOG~Sbs;Ih_6u{_>7v%#33dJv)Eq~n9vp*OD^*b=G9Oj$AM~|{t`T3i=Ui|Q&a#2eTe2W2ta@ekC4GiCMj{WLrf)xj&650hu}^Zg6VWv~?S
zei(_{P$T5twFxP*n}pJ+Bew_U$7MGDyESgOX{ZR6B)>nZ+%{CbzfcELa1N7SA2p&g
zu^KMJe)t6GDF6O%ggF=%ptjc~)SEv-Fph^E_eRyr6!4zd5Q-P0=v;4$dO=ODk+Pri4@sGg6)5{1_bTpvqz$xf)g=7i
z6^}3<&Aw}*P48gMgBJK7pX`q
zPuUuTog>`Zns-U;OQkp5<+w8t&p{#WmKKDUla`tIY#hp+fG}UaojBTlTr<2k>Ib=p
zNxe;O9#XPUYa;gsO7YdoIYPRwFN6zVe#*`ytV?_MdtZnJN}Yd6k3wD3ts#`x)fWel
z#&-iJ5#dXur6kPfg1!RqYYiT>n2f!I4|1>ZHQ=4vAYGdllv`~3p`xS6(hF>|X$SGS
zGVG-@Aq90Z+Wfir3r+Xq)+G}2kLHxoRot7fVHS6Sjr~f_OKN;ajt=zuguf+kKk4g;
zm&8e=ox(r4Pm=bJtxM2JP1;}NcOh*L_P~QS|1$B48SNzo5Rx+aq|gm`^g&uo&MEu`fmzY%v@
z+j1;v9f#a~?fXw%?-DOTSl3wUH0FM5
z<4Su$TX89`E5O>Ha2?XO;9L9fel{;M4ky|cB5`OW+BT%$BJ}n~wGZ?ruOG)PO_k47
zeZr#-66Uka_kV74k6OEJjZx$`CVZ5%1KiChSBr8*3G13_@cr`wX^*(OQ2w|7NguAV
z)X_D8`uxVpX`(OaLr54!2H#JdrDP=J)^&mk*Sy`Eb`$oC%%@@L+Vu{UsnbE$vd=Zkb9cc
zpNTcHPw7lJy?w$JN?s;?BWVk1<-d#HV*0L|q&2W*-`afr;Pu$H)ta>9#JeNkRGg!>
zUV7q%x%sYD(ebd9@gnOARCm-?0#C7eUTnQY5FG%|aixaO*
znTy=IUXZ41q4fvKZ6mLUiC1)vk#LbnXA)y6xXw1NO5goYIFRrF()ca0@A^ob-xNFd
zxUYDxHV;oxfRt~E>C5L;TD;{=(IPP5cS7HglZTvo-l!IV?i5n~;%WRu?YQK`5j416k0)rx>%jUgD?q=6`)Xn(y=#b8>#r2`xnBxs*v7|dop)Q%4M(*&4o3&t9ip(
z238qqQ~P5KwMNsz|G#SgPr^7_({&bqq|OxX^Mt#4hqerfXvVYekXw&d_HwVat)Ib$
zw&pU@zx5t!naMqAd-@MG-jf?asZ`{4wY92{f1LPooWb3K^l0wBgnLka0`+bazGGWn
zN&LU-Hs#)t$1hKvGTcqM8`D+^(wAU)KE7JD%24bPWpte*O+uS
zy@y%{xs`0nW1Pv&Z%!N!cTeu^#QS4+td2*hl?8v~enZ-R;$u)(bNigtJo7r?UEDuW
zt|bm4|A8%cinKzuY*~FiRrfY&Qy@=$lBZE@1l6};Mq4~5@ePFk#{aHQ{@y)pGRK`n
z@@DUyHi7Na5o$;Qmv%p32^!Un+~>A&HQ188T*&WHoDzh0lGohk
ze;}28#FNu{j4l0)8a>Ic!5!B=bu6~^9*PYt;~}*@
zCAx5rqo%H;_=NNZn25V9VO@XHVo~nXgiDfMkybi;Q@73HejzrKdn`HpNy7I>(xTK_
zPg*E<4$=~FKPS9~^yO-f>lXJ#()QEpeDa^!CtV_~6!(7@zh7})+js+;xWJa}px?{i
zC-FB5ZQ@Qy!I8vA)8K#CzXX!o3QCKhOn1r-q)cz(4GHVwk9fYTD*tRonWo+p?Lyq$
z+~ehdZh5KwtZemwx<^r{C$k)YryrO(-&e`x#aAdl6mJZI%0^-z9yY
zO1j_M)IyXPL~0!#w2Ir0J3e_2xGND}L@ix&h<_lQfX@$I&AHofM{*A*?-^x}qOM=L
zbI_))E!1g2_`JeHl!alaz1CGMnLdv9=uOifNHWLr=TT?2{#gfToR
zC-)8Rk)$2O0sqsw%C_W=M^D?3_mnbMxwDYA!#krxpqrXlZu+%CMY%>&y8@Q;#rS~-
z(^4h_x31xo{g!YQ?#-0-5^qb(|6SV&>?Q5c|D;W&Yy;v|u_E~^2@fOu#GggFmPYuE
zwi8UHS-6;+zr{HfC@`9EYr^%ZRGaWmlD#$=rK8>&!c8bQ
zgFBG;BkJxWEtvQ;!hZg&m_`J$c-M3cPvMZPs}A>2icRp|=@=9phy1GCrOEBdor|1Z
z+`9COy2^xekv;<3`cnDMPrR)+s#8e&5JLXkWoV(X%`HU9uf(TvSEpO=xOF9<+!h=D
zKztARhe-Q}dkOKY~hkY(`qJcO#0v4H@Ra;`|k?%Ms*H!3z8DwuKivA(?TkWWhMT*Ek2EMH+a{(
z+-JGtQfD0M3b8%dZR?lAGA7IS&tk-LQ@62@
zB=HpC`8ItI;U7qg;!a2SE9qAVe{b`VuxUECfSxUJ8^0t#+jc{Sri^HED{w9!?
zJ0I~yWDc{H_*XuhLWDXO71<^H7MJ4N~a+81gSB&hC4N-_i*bvOZpOTK(~-=VT4wzE-R%ewJzBb
zHSnQ*=J~BPy6x|_HS^Vw(sR3{^<3V$AlS1deCL8Pp7+CdE<5b;k5Y2Fxwl?=X3Qlo
T+0MhSJY7O}ev{hoYo`ALakfYC
diff --git a/languages/site-reviews-en_US.po b/languages/site-reviews-en_US.po
index 50ccd5a49..07c469812 100644
--- a/languages/site-reviews-en_US.po
+++ b/languages/site-reviews-en_US.po
@@ -2085,11 +2085,21 @@ msgctxt "admin-text"
msgid "Terms Accepted"
msgstr "Terms Accepted"
-#: config/integrations/woocommerce.php:6
+#: config/integrations/ultimatemember.php:6, config/integrations/woocommerce.php:6
msgctxt "admin-text"
msgid "Enable Integration?"
msgstr "Enable Integration?"
+#: config/integrations/ultimatemember.php:8
+msgctxt "admin-text"
+msgid "This will enable the Ultimate Member integration with Site Reviews."
+msgstr "This will enable the Ultimate Member integration with Site Reviews."
+
+#: config/integrations/ultimatemember.php:9, config/integrations/woocommerce.php:9, views/integrations/woocommerce/tools/import-product-reviews.php:34, views/pages/tools/general/import-reviews.php:6, views/pages/tools/general/import-reviews.php:113
+msgctxt "admin-text"
+msgid "Import Reviews"
+msgstr "Import Reviews"
+
#: config/integrations/woocommerce.php:8
msgctxt "admin-text"
msgid ""
@@ -2103,11 +2113,6 @@ msgstr ""
"need to first export them to a CSV file, and then import them using the %s "
"tool."
-#: config/integrations/woocommerce.php:9, views/integrations/woocommerce/tools/import-product-reviews.php:34, views/pages/tools/general/import-reviews.php:6, views/pages/tools/general/import-reviews.php:113
-msgctxt "admin-text"
-msgid "Import Reviews"
-msgstr "Import Reviews"
-
#: config/integrations/woocommerce.php:19
msgctxt "admin-text"
msgid "Rating Style"
@@ -2982,7 +2987,7 @@ msgctxt "admin-text"
msgid "Tools"
msgstr "Tools"
-#: plugin/Controllers/MenuController.php:53, plugin/Controllers/MenuController.php:115, plugin/Controllers/MenuController.php:139
+#: plugin/Controllers/MenuController.php:53, plugin/Controllers/MenuController.php:115, plugin/Controllers/MenuController.php:140
msgctxt "admin-text"
msgid "Addons"
msgstr "Addons"
@@ -3017,7 +3022,7 @@ msgctxt "admin-text"
msgid "API"
msgstr "API"
-#: plugin/Controllers/MenuController.php:134, plugin/Controllers/MenuController.php:160
+#: plugin/Controllers/MenuController.php:134, plugin/Controllers/MenuController.php:161
msgctxt "admin-text"
msgid "General"
msgstr "General"
@@ -3037,27 +3042,32 @@ msgctxt "admin-text"
msgid "Schema"
msgstr "Schema"
-#: plugin/Controllers/MenuController.php:140
+#: plugin/Controllers/MenuController.php:139
+msgctxt "admin-text"
+msgid "Integrations"
+msgstr "Integrations"
+
+#: plugin/Controllers/MenuController.php:141
msgctxt "admin-text"
msgid "Licenses"
msgstr "Licenses"
-#: plugin/Controllers/MenuController.php:161
+#: plugin/Controllers/MenuController.php:162
msgctxt "admin-text"
msgid "Scheduled Actions"
msgstr "Scheduled Actions"
-#: plugin/Controllers/MenuController.php:162, views/pages/tools/sync.php:58
+#: plugin/Controllers/MenuController.php:163, views/pages/tools/sync.php:58
msgctxt "admin-text"
msgid "Sync Reviews"
msgstr "Sync Reviews"
-#: plugin/Controllers/MenuController.php:163
+#: plugin/Controllers/MenuController.php:164
msgctxt "admin-text"
msgid "Console"
msgstr "Console"
-#: plugin/Controllers/MenuController.php:164
+#: plugin/Controllers/MenuController.php:165
msgctxt "admin-text"
msgid "System Info"
msgstr "System Info"
@@ -4643,6 +4653,21 @@ msgctxt "admin-text"
msgid "Unapproved Review (Site Reviews)"
msgstr "Unapproved Review (Site Reviews)"
+#: plugin/Integrations/UltimateMember/DirectoryController.php:38, plugin/Integrations/UltimateMember/ProfileController.php:38
+msgctxt "admin-text"
+msgid "Site Reviews: Display User Rating"
+msgstr "Site Reviews: Display User Rating"
+
+#: plugin/Integrations/UltimateMember/DirectoryController.php:49, plugin/Integrations/UltimateMember/ProfileController.php:49
+msgctxt "admin-text"
+msgid "Site Reviews: Highest rated first"
+msgstr "Site Reviews: Highest rated first"
+
+#: plugin/Integrations/UltimateMember/DirectoryController.php:50, plugin/Integrations/UltimateMember/ProfileController.php:50
+msgctxt "admin-text"
+msgid "Site Reviews: Lowest rated first"
+msgstr "Site Reviews: Lowest rated first"
+
#: plugin/Modules/Html/TemplateTags.php:144
msgctxt "admin-text"
msgid "View the review in WordPress →"
diff --git a/languages/site-reviews.pot b/languages/site-reviews.pot
index 79ead1507..f6387dcc8 100644
--- a/languages/site-reviews.pot
+++ b/languages/site-reviews.pot
@@ -1567,21 +1567,26 @@ msgstr ""
msgid "This review is based on my own experience and is my genuine opinion."
msgstr ""
-#: config/integrations/woocommerce.php:6
+#: config/integrations/ultimatemember.php:6, config/integrations/woocommerce.php:6
msgctxt "admin-text"
msgid "Enable Integration?"
msgstr ""
-#: config/integrations/woocommerce.php:8
+#: config/integrations/ultimatemember.php:8
msgctxt "admin-text"
-msgid "This will completely replace the default WooCommerce review system with Site Reviews. If you have existing WooCommerce comment reviews, you may need to first export them to a CSV file, and then import them using the %s tool."
+msgid "This will enable the Ultimate Member integration with Site Reviews."
msgstr ""
-#: config/integrations/woocommerce.php:9, views/integrations/woocommerce/tools/import-product-reviews.php:34, views/pages/tools/general/import-reviews.php:6, views/pages/tools/general/import-reviews.php:113
+#: config/integrations/ultimatemember.php:9, config/integrations/woocommerce.php:9, views/integrations/woocommerce/tools/import-product-reviews.php:34, views/pages/tools/general/import-reviews.php:6, views/pages/tools/general/import-reviews.php:113
msgctxt "admin-text"
msgid "Import Reviews"
msgstr ""
+#: config/integrations/woocommerce.php:8
+msgctxt "admin-text"
+msgid "This will completely replace the default WooCommerce review system with Site Reviews. If you have existing WooCommerce comment reviews, you may need to first export them to a CSV file, and then import them using the %s tool."
+msgstr ""
+
#: config/integrations/woocommerce.php:19
msgctxt "admin-text"
msgid "Rating Style"
@@ -2428,7 +2433,7 @@ msgctxt "admin-text"
msgid "Tools"
msgstr ""
-#: plugin/Controllers/MenuController.php:53, plugin/Controllers/MenuController.php:115, plugin/Controllers/MenuController.php:139
+#: plugin/Controllers/MenuController.php:53, plugin/Controllers/MenuController.php:115, plugin/Controllers/MenuController.php:140
msgctxt "admin-text"
msgid "Addons"
msgstr ""
@@ -2463,7 +2468,7 @@ msgctxt "admin-text"
msgid "API"
msgstr ""
-#: plugin/Controllers/MenuController.php:134, plugin/Controllers/MenuController.php:160
+#: plugin/Controllers/MenuController.php:134, plugin/Controllers/MenuController.php:161
msgctxt "admin-text"
msgid "General"
msgstr ""
@@ -2483,27 +2488,32 @@ msgctxt "admin-text"
msgid "Schema"
msgstr ""
-#: plugin/Controllers/MenuController.php:140
+#: plugin/Controllers/MenuController.php:139
+msgctxt "admin-text"
+msgid "Integrations"
+msgstr ""
+
+#: plugin/Controllers/MenuController.php:141
msgctxt "admin-text"
msgid "Licenses"
msgstr ""
-#: plugin/Controllers/MenuController.php:161
+#: plugin/Controllers/MenuController.php:162
msgctxt "admin-text"
msgid "Scheduled Actions"
msgstr ""
-#: plugin/Controllers/MenuController.php:162, views/pages/tools/sync.php:58
+#: plugin/Controllers/MenuController.php:163, views/pages/tools/sync.php:58
msgctxt "admin-text"
msgid "Sync Reviews"
msgstr ""
-#: plugin/Controllers/MenuController.php:163
+#: plugin/Controllers/MenuController.php:164
msgctxt "admin-text"
msgid "Console"
msgstr ""
-#: plugin/Controllers/MenuController.php:164
+#: plugin/Controllers/MenuController.php:165
msgctxt "admin-text"
msgid "System Info"
msgstr ""
@@ -4738,6 +4748,21 @@ msgctxt "admin-text"
msgid "Unapproved Review (Site Reviews)"
msgstr ""
+#: plugin/Integrations/UltimateMember/DirectoryController.php:38, plugin/Integrations/UltimateMember/ProfileController.php:38
+msgctxt "admin-text"
+msgid "Site Reviews: Display User Rating"
+msgstr ""
+
+#: plugin/Integrations/UltimateMember/DirectoryController.php:49, plugin/Integrations/UltimateMember/ProfileController.php:49
+msgctxt "admin-text"
+msgid "Site Reviews: Highest rated first"
+msgstr ""
+
+#: plugin/Integrations/UltimateMember/DirectoryController.php:50, plugin/Integrations/UltimateMember/ProfileController.php:50
+msgctxt "admin-text"
+msgid "Site Reviews: Lowest rated first"
+msgstr ""
+
#: plugin/Modules/Html/Form.php:24
msgid "Submit Form"
msgstr ""
diff --git a/plugin/Database/CountManager.php b/plugin/Database/CountManager.php
index b514d269d..ef5745d6c 100644
--- a/plugin/Database/CountManager.php
+++ b/plugin/Database/CountManager.php
@@ -3,6 +3,7 @@
namespace GeminiLabs\SiteReviews\Database;
use GeminiLabs\SiteReviews\Database;
+use GeminiLabs\SiteReviews\Helpers\Cast;
use GeminiLabs\SiteReviews\Helpers\Str;
use GeminiLabs\SiteReviews\Modules\Rating;
@@ -21,6 +22,21 @@ public function posts(int $postId): void
glsr()->action('ratings/count/post', $postId, $counts);
}
+ public function postsAverage(int $postId): float
+ {
+ return Cast::toFloat(get_post_meta($postId, static::META_AVERAGE, true));
+ }
+
+ public function postsRanking(int $postId): float
+ {
+ return Cast::toFloat(get_post_meta($postId, static::META_RANKING, true));
+ }
+
+ public function postsReviews(int $postId): int
+ {
+ return Cast::toInt(get_post_meta($postId, static::META_REVIEWS, true));
+ }
+
public function recalculate(): void
{
$this->recalculateFor('post');
@@ -53,6 +69,21 @@ public function terms(int $termId): void
glsr()->action('ratings/count/term', $termId, $counts);
}
+ public function termsAverage(int $termId): float
+ {
+ return Cast::toFloat(get_term_meta($termId, static::META_AVERAGE, true));
+ }
+
+ public function termsRanking(int $termId): float
+ {
+ return Cast::toFloat(get_term_meta($termId, static::META_RANKING, true));
+ }
+
+ public function termsReviews(int $termId): int
+ {
+ return Cast::toInt(get_term_meta($termId, static::META_REVIEWS, true));
+ }
+
public function users(int $userId): void
{
$counts = glsr_get_ratings(['assigned_users' => $userId]);
@@ -62,6 +93,21 @@ public function users(int $userId): void
glsr()->action('ratings/count/user', $userId, $counts);
}
+ public function usersAverage(int $userId): float
+ {
+ return Cast::toFloat(get_user_meta($userId, static::META_AVERAGE, true));
+ }
+
+ public function usersRanking(int $userId): float
+ {
+ return Cast::toFloat(get_user_meta($userId, static::META_RANKING, true));
+ }
+
+ public function usersReviews(int $userId): int
+ {
+ return Cast::toInt(get_user_meta($userId, static::META_REVIEWS, true));
+ }
+
protected function metaId(string $metaGroup): string
{
return "{$metaGroup}_id";
diff --git a/plugin/Integrations/UltimateMember/Controller.php b/plugin/Integrations/UltimateMember/Controller.php
deleted file mode 100644
index 97c7f40a1..000000000
--- a/plugin/Integrations/UltimateMember/Controller.php
+++ /dev/null
@@ -1,36 +0,0 @@
-author_id) {
- return $defaultUrl;
- }
- if ('none' !== $type && $url === $defaultUrl) {
- return '';
- }
- return $url;
- }
-
- /**
- * @filter site-reviews/assigned_users/profile_id
- */
- public function filterProfileId(int $profileId): int
- {
- if (empty($profileId)) {
- return (int) um_get_requested_user();
- }
- return $profileId;
- }
-}
diff --git a/plugin/Integrations/UltimateMember/Controllers/Controller.php b/plugin/Integrations/UltimateMember/Controllers/Controller.php
new file mode 100644
index 000000000..68d2f769e
--- /dev/null
+++ b/plugin/Integrations/UltimateMember/Controllers/Controller.php
@@ -0,0 +1,137 @@
+author_id) {
+ return $defaultUrl;
+ }
+ if ('none' !== $type && $url === $defaultUrl) {
+ return '';
+ }
+ return $url;
+ }
+
+ /**
+ * @filter site-reviews/enqueue/public/inline-styles
+ */
+ public function filterInlineStyles(string $css): string
+ {
+ if (glsr_get_option('integrations.ultimatemember.display_directory_ratings', false, 'bool')) {
+ $css .= '.um-directory .um-members-grid .um-member-rating:has(.glsr-star-rating) {display:flex; justify-content:center;}';
+ $css .= '.um-directory .um-members-list .um-member-rating:has(.glsr-star-rating) {display:flex;}';
+ $css .= '.um-directory .glsr-star-rating {margin:3px 0;}';
+ }
+ if (glsr_get_option('integrations.ultimatemember.display_reviews_tab', false, 'bool')) {
+ $css .= '.um-reviews-summary .glsr-summary-text {--glsr-summary-text: var(--glsr-text-base);}';
+ $css .= '.um-reviews-summary {padding-top:var(--glsr-gap-lg); padding-bottom:var(--glsr-gap-xl);}';
+ $css .= '.um-reviews-form:has(div) {border-top: 3px solid #eee; padding:var(--glsr-gap-xl) 0;}';
+ $css .= '.um-reviews-reviews:has(.glsr-reviews) {border-top: 3px solid #eee; padding-top:var(--glsr-gap-xl);}';
+ }
+ return $css;
+ }
+
+ /**
+ * @filter site-reviews/assigned_users/profile_id
+ */
+ public function filterProfileId(int $profileId): int
+ {
+ if (empty($profileId)) {
+ return (int) um_get_requested_user();
+ }
+ return $profileId;
+ }
+
+ /**
+ * @filter site-reviews/settings
+ */
+ public function filterSettings(array $settings): array
+ {
+ return array_merge(glsr()->config('integrations/ultimatemember'), $settings);
+ }
+
+ /**
+ * @filter site-reviews/settings/sanitize
+ */
+ public function filterSettingsCallback(array $settings, array $input): array
+ {
+ $enabled = Arr::get($input, 'settings.integrations.ultimatemember.enabled');
+ if ('yes' === $enabled && !$this->gatekeeper()->allows()) { // this renders any error notices
+ $settings = Arr::set($settings, 'settings.integrations.ultimatemember.enabled', 'no');
+ }
+ $shortcodes = [
+ 'form' => 'site_reviews_form',
+ 'reviews' => 'site_reviews',
+ 'summary' => 'site_reviews_summary',
+ ];
+ foreach ($shortcodes as $key => $shortcode) {
+ $path = "settings.integrations.ultimatemember.{$key}";
+ $value = Arr::get($input, $path);
+ if (1 !== preg_match("/^\[{$shortcode}(\s[^\]]*\]|\])$/", $value)) {
+ continue;
+ }
+ if (!str_contains($value, 'assigned_users')) {
+ $value = str_replace($shortcode, sprintf('%s assigned_users="profile_id"', $shortcode), $value);
+ $settings = Arr::set($settings, $path, $value);
+ }
+ }
+ return $settings;
+ }
+
+ /**
+ * @filter site-reviews/integration/subsubsub
+ */
+ public function filterSubsubsub(array $subsubsub): array
+ {
+ $subsubsub['ultimatemember'] = 'Ultimate Member';
+ return $subsubsub;
+ }
+
+ /**
+ * @action admin_init
+ */
+ public function renderNotice(): void
+ {
+ if (glsr_get_option('integrations.ultimatemember.enabled', false, 'bool')) {
+ $this->gatekeeper()->allows(); // this renders any error notices
+ }
+ }
+
+ /**
+ * @action site-reviews/settings/ultimatemember
+ */
+ public function renderSettings(string $rows): void
+ {
+ glsr(Template::class)->render('integrations/ultimatemember/settings', [
+ 'context' => [
+ 'rows' => $rows,
+ ],
+ ]);
+ }
+
+ protected function gatekeeper(): Gatekeeper
+ {
+ return new Gatekeeper([
+ 'ultimate-member/ultimate-member.php' => [
+ 'minimum_version' => '2.5',
+ 'name' => 'Ultimate Member',
+ 'plugin_uri' => 'https://wordpress.org/plugins/ultimate-member/',
+ 'untested_version' => '3.0',
+ ],
+ ]);
+ }
+}
diff --git a/plugin/Integrations/UltimateMember/Controllers/DirectoryController.php b/plugin/Integrations/UltimateMember/Controllers/DirectoryController.php
new file mode 100644
index 000000000..1db401f9b
--- /dev/null
+++ b/plugin/Integrations/UltimateMember/Controllers/DirectoryController.php
@@ -0,0 +1,136 @@
+usersReviews($userId);
+ if (0 < $total || glsr_get_option('integrations.ultimatemember.display_empty', false, 'bool')) {
+ $rating = glsr(CountManager::class)->usersAverage($userId);
+ $html = glsr(Builder::class)->div([
+ 'class' => 'um-member-rating',
+ 'text' => glsr_star_rating($rating, $total),
+ ]);
+ }
+ $data['glsr_rating_html'] = $html;
+ return $data;
+ }
+
+ /**
+ * @filter um_admin_extend_directory_options_profile
+ */
+ public function filterDirectoryProfileOptions(array $fields): array
+ {
+ $fields[] = [
+ 'id' => '_um_glsr_display_user_rating',
+ 'type' => 'checkbox',
+ 'label' => _x('Site Reviews: Display User Rating', 'admin-text', 'site-reviews'),
+ 'value' => UM()->query()->get_meta_value('_um_glsr_display_user_rating', null, 'na'),
+ ];
+ return $fields;
+ }
+
+ /**
+ * @filter um_members_directory_sort_fields
+ */
+ public function filterDirectoryProfileSortOptions(array $options): array
+ {
+ $options['glsr_highest_rated'] = _x('Site Reviews: Highest rated first', 'admin-text', 'site-reviews');
+ $options['glsr_lowest_rated'] = _x('Site Reviews: Lowest rated first', 'admin-text', 'site-reviews');
+ return $options;
+ }
+
+ /**
+ * Used when the UM "Custom usermeta table" setting is disabled.
+ *
+ * @param string $sortby
+ *
+ * @filter um_modify_sortby_parameter
+ */
+ public function filterDirectorySortBy(array $queryArgs, $sortby): array
+ {
+ if (!in_array($sortby, ['glsr_highest_rated', 'glsr_lowest_rated'])) {
+ return $queryArgs;
+ }
+ $order = 'glsr_highest_rated' === $sortby ? 'DESC' : 'ASC';
+ $sortKey = 'bayesian' === glsr_get_option('integrations.ultimatemember.sorting')
+ ? CountManager::META_RANKING
+ : CountManager::META_AVERAGE;
+ $queryArgs['meta_query'] ??= [];
+ $queryArgs['meta_query'][] = [
+ 'relation' => 'OR',
+ [
+ 'compare' => 'NOT EXISTS',
+ 'key' => $sortKey,
+ 'type' => 'NUMERIC',
+ ],
+ '_rating_key' => [
+ 'compare' => 'EXISTS',
+ 'key' => $sortKey,
+ 'type' => 'NUMERIC',
+ ],
+ ];
+ $queryArgs['orderby'] = [
+ '_rating_key' => $order,
+ 'user_registered' => 'DESC',
+ ];
+ unset($queryArgs['order']);
+ return $queryArgs;
+ }
+
+ /**
+ * Used when the UM "Custom usermeta table" setting is enabled.
+ *
+ * @param \um\core\Member_Directory_Meta $query
+ * @param array $directoryData
+ * @param string $sortby
+ *
+ * @action um_pre_users_query
+ */
+ public function modifyQuerySortby($query, $directoryData, $sortby): void
+ {
+ if (!in_array($sortby, ['glsr_highest_rated', 'glsr_lowest_rated'])) {
+ return;
+ }
+ $order = esc_sql('glsr_highest_rated' === $sortby ? 'DESC' : 'ASC');
+ $sortKey = 'bayesian' === glsr_get_option('integrations.ultimatemember.sorting')
+ ? CountManager::META_RANKING
+ : CountManager::META_AVERAGE;
+ $query->joins[] = glsr(Query::class)->sql(
+ "LEFT JOIN table|usermeta AS glsr_user_meta ON (glsr_user_meta.user_id = u.ID AND glsr_user_meta.meta_key = %s)",
+ $sortKey
+ );
+ $query->sql_order = " ORDER BY CAST(glsr_user_meta.meta_value AS SIGNED) {$order}, u.user_registered DESC";
+ }
+
+ /**
+ * @param array $args
+ *
+ * @action um_members_just_after_name_tmpl
+ * @action um_members_list_after_user_name_tmpl
+ */
+ public function modifyTmpl($args): void
+ {
+ $displayRating = Arr::get($args, 'glsr_display_user_rating');
+ if (empty($displayRating)) {
+ return;
+ }
+ echo "<# if ('undefined' !== typeof user.glsr_rating_html) { #>{{{user.glsr_rating_html}}}<# } #>";
+ }
+}
diff --git a/plugin/Integrations/UltimateMember/Controllers/ProfileController.php b/plugin/Integrations/UltimateMember/Controllers/ProfileController.php
new file mode 100644
index 000000000..f14c3e9cd
--- /dev/null
+++ b/plugin/Integrations/UltimateMember/Controllers/ProfileController.php
@@ -0,0 +1,137 @@
+ 'um-faicon-star',
+ 'name' => __('Reviews', 'site-reviews'),
+ ];
+ return $tabs;
+ }
+
+ /**
+ * @filter site-reviews/reviews/fallback
+ */
+ public function filterReviewsFallback(string $fallback, array $args): string
+ {
+ if ($args['fallback'] !== __('There are no reviews yet. Be the first one to write one.', 'site-reviews')) {
+ return $fallback;
+ }
+ $value = get_current_user_id() === um_get_requested_user()
+ ? __('You have not received any reviews.', 'site-reviews')
+ : __('This person has not received any reviews.', 'site-reviews');
+ return glsr(Builder::class)->p([
+ 'class' => 'glsr-no-margins',
+ 'text' => $value,
+ ]);
+ }
+
+ /**
+ * @filter site-reviews/summary/value/percentages
+ */
+ public function filterSummaryPercentagesValue(string $value, TagContract $tag): string
+ {
+ if (!empty(array_sum($tag->ratings))) {
+ return $value;
+ }
+ return '';
+ }
+
+ /**
+ * @filter site-reviews/summary/value/text
+ */
+ public function filterSummaryRatingValue(string $value, TagContract $tag): string
+ {
+ $value = get_current_user_id() === um_get_requested_user()
+ ? __('Your rating', 'site-reviews')
+ : __('User rating', 'site-reviews');
+ return glsr(Builder::class)->h4($value);
+ }
+
+ /**
+ * @filter site-reviews/summary/value/rating
+ */
+ public function filterSummaryTextValue(string $value, TagContract $tag): string
+ {
+ if (!empty(array_sum($tag->ratings))) {
+ return $value;
+ }
+ if (get_current_user_id() === um_get_requested_user()) {
+ return __('No one has reviewed you yet.', 'site-reviews');
+ }
+ return __('No one has reviewed this person yet.', 'site-reviews');
+ }
+
+ /**
+ * @action um_profile_content_user_reviews
+ */
+ public function renderReviewsTab(): void
+ {
+ if (!glsr_get_option('integrations.ultimatemember.display_reviews_tab', false, 'bool')) {
+ return;
+ }
+ glsr(Template::class)->render('templates/ultimatemember/reviews', [
+ 'context' => [
+ 'form' => $this->shortcodeForm(),
+ 'reviews' => $this->shortcodeReviews(),
+ 'summary' => $this->shortcodeSummary(),
+ ],
+ ]);
+ }
+
+ protected function shortcodeForm(): string
+ {
+ if (!is_user_logged_in()) {
+ $text = sprintf(__('You must be %s to review this person.', 'site-reviews'), glsr(SiteReviewsFormShortcode::class)->loginLink());
+ return glsr(Template::class)->build('templates/login-register', [
+ 'context' => compact('text'),
+ ]);
+ } else {
+ $ratings = glsr_get_ratings([
+ 'assigned_users' => um_get_requested_user(),
+ 'status' => 'all',
+ 'user__in' => get_current_user_id(),
+ ]);
+ if (0 < $ratings->reviews) {
+ return '';
+ }
+ }
+ return do_shortcode(glsr_get_option('integrations.ultimatemember.form'));
+ }
+
+ protected function shortcodeReviews(): string
+ {
+ add_filter('site-reviews/reviews/fallback', [$this, 'filterReviewsFallback'], 20, 2);
+ $shortcode = do_shortcode(glsr_get_option('integrations.ultimatemember.reviews'));
+ remove_filter('site-reviews/reviews/fallback', [$this, 'filterReviewsFallback'], 20);
+ return $shortcode;
+ }
+
+ protected function shortcodeSummary(): string
+ {
+ add_filter('site-reviews/summary/value/percentages', [$this, 'filterSummaryPercentagesValue'], 20, 2);
+ add_filter('site-reviews/summary/value/rating', [$this, 'filterSummaryRatingValue'], 20, 2);
+ add_filter('site-reviews/summary/value/text', [$this, 'filterSummaryTextValue'], 20, 2);
+ $shortcode = do_shortcode(glsr_get_option('integrations.ultimatemember.summary'));
+ remove_filter('site-reviews/summary/value/percentages', [$this, 'filterSummaryPercentagesValue'], 20);
+ remove_filter('site-reviews/summary/value/rating', [$this, 'filterSummaryRatingValue'], 20);
+ remove_filter('site-reviews/summary/value/text', [$this, 'filterSummaryTextValue'], 20);
+ return $shortcode;
+ }
+}
diff --git a/plugin/Integrations/UltimateMember/Hooks.php b/plugin/Integrations/UltimateMember/Hooks.php
index 56577ee74..cb067e22b 100644
--- a/plugin/Integrations/UltimateMember/Hooks.php
+++ b/plugin/Integrations/UltimateMember/Hooks.php
@@ -3,23 +3,57 @@
namespace GeminiLabs\SiteReviews\Integrations\UltimateMember;
use GeminiLabs\SiteReviews\Hooks\AbstractHooks;
+use GeminiLabs\SiteReviews\Integrations\UltimateMember\Controllers\Controller;
+use GeminiLabs\SiteReviews\Integrations\UltimateMember\Controllers\DirectoryController;
+use GeminiLabs\SiteReviews\Integrations\UltimateMember\Controllers\ProfileController;
class Hooks extends AbstractHooks
{
public function run(): void
{
- if (!$this->isInstalled()) {
- return;
- }
$this->hook(Controller::class, [
- ['filterAvatarUrl', 'site-reviews/avatar/generate', 10, 2],
- ['filterProfileId', 'site-reviews/assigned_users/profile_id', 5],
+ ['filterSettings', 'site-reviews/settings'],
+ ['filterSettingsCallback', 'site-reviews/settings/sanitize', 10, 2],
+ ['filterSubsubsub', 'site-reviews/integration/subsubsub'],
+ ['renderNotice', 'admin_init'],
+ ['renderSettings', 'site-reviews/settings/ultimatemember'],
]);
+ if ($this->isInstalled()) {
+ $this->hook(Controller::class, [
+ ['filterAvatarUrl', 'site-reviews/avatar/generate', 10, 2],
+ ['filterProfileId', 'site-reviews/assigned_users/profile_id', 5],
+ ]);
+ }
+ if ($this->isEnabled()) {
+ $this->hook(Controller::class, [
+ ['filterInlineStyles', 'site-reviews/enqueue/public/inline-styles'],
+ ]);
+ $this->hook(DirectoryController::class, [
+ ['filterAjaxMembersData', 'um_ajax_get_members_data', 50, 2],
+ ['filterDirectoryProfileOptions', 'um_admin_extend_directory_options_profile', 15],
+ ['filterDirectoryProfileSortOptions', 'um_members_directory_sort_fields'],
+ ['filterDirectorySortBy', 'um_modify_sortby_parameter', 100, 2],
+ ['modifyQuerySortby', 'um_pre_users_query', 10, 3],
+ ['modifyTmpl', 'um_members_just_after_name_tmpl', 1],
+ ['modifyTmpl', 'um_members_list_after_user_name_tmpl', 1],
+ ]);
+ $this->hook(ProfileController::class, [
+ ['filterProfileTabs', 'um_user_profile_tabs', 100],
+ ['renderReviewsTab', 'um_profile_content_user_reviews'],
+ ]);
+ }
+ }
+
+ protected function isEnabled(): bool
+ {
+ return 'yes' === $this->option('integrations.ultimatemember.enabled')
+ && $this->isInstalled();
}
protected function isInstalled(): bool
{
- return function_exists('um_get_default_avatar_uri')
+ return function_exists('UM')
+ && function_exists('um_get_default_avatar_uri')
&& function_exists('um_get_requested_user');
}
}
diff --git a/plugin/Modules/Html/Tags/SummaryTag.php b/plugin/Modules/Html/Tags/SummaryTag.php
index 88cb6bb30..1b0af3b31 100644
--- a/plugin/Modules/Html/Tags/SummaryTag.php
+++ b/plugin/Modules/Html/Tags/SummaryTag.php
@@ -7,8 +7,7 @@
class SummaryTag extends Tag
{
- /** @var array */
- protected $ratings;
+ public array $ratings = [];
protected function hideOption(): string
{
diff --git a/templates/ultimatemember/reviews.php b/templates/ultimatemember/reviews.php
new file mode 100644
index 000000000..7e8515b8c
--- /dev/null
+++ b/templates/ultimatemember/reviews.php
@@ -0,0 +1,11 @@
+
+
+
+ {{ summary }}
+
+
+ {{ form }}
+
+
+ {{ reviews }}
+
diff --git a/tests/phpstan/stubs/ultimate-member.php b/tests/phpstan/stubs/ultimate-member.php
index 1fb6c0270..fab7551dc 100644
--- a/tests/phpstan/stubs/ultimate-member.php
+++ b/tests/phpstan/stubs/ultimate-member.php
@@ -1,6 +1,135 @@
+
+Ultimate Member
+
+