From 0a278c39d20a5d39e1bab1363d3d09ad8a1e15dc Mon Sep 17 00:00:00 2001 From: Julia Bichler Date: Sat, 27 Nov 2021 15:26:09 +0100 Subject: [PATCH] settings: Send email after deactivating user. This adds a feature where an admin can choose to send an email with custom content to an user after they deactivated them. Fixes #18943. --- static/images/help/deactivate-user-email.png | Bin 0 -> 46093 bytes static/js/dialog_widget.js | 12 +++++ static/js/settings_users.js | 17 +++++++ static/styles/modal.css | 19 ++++++++ .../confirm_deactivate_user.hbs | 27 +++++++++++ templates/zerver/api/changelog.md | 6 +++ .../zerver/emails/deactivate.source.html | 20 ++++++++ .../zerver/emails/deactivate.subject.txt | 1 + templates/zerver/emails/deactivate.txt | 9 ++++ templates/zerver/emails/email.css | 12 +++++ .../help/deactivate-or-reactivate-a-user.md | 23 ++++++++++ tools/linter_lib/custom_check.py | 2 +- version.py | 2 +- zerver/openapi/zulip.yaml | 15 ++++++ zerver/tests/test_users.py | 36 +++++++++++++++ zerver/views/users.py | 43 ++++++++++++++++-- 16 files changed, 238 insertions(+), 6 deletions(-) create mode 100644 static/images/help/deactivate-user-email.png create mode 100644 templates/zerver/emails/deactivate.source.html create mode 100644 templates/zerver/emails/deactivate.subject.txt create mode 100644 templates/zerver/emails/deactivate.txt diff --git a/static/images/help/deactivate-user-email.png b/static/images/help/deactivate-user-email.png new file mode 100644 index 0000000000000000000000000000000000000000..378b119b59ee58936f136dfa532c5f897c18f60b GIT binary patch literal 46093 zcmeFYXH-+&*Dq=>DDWsvKtK?r35Yc50s_)IkrF`Zp((uxCg zdkG~p=_Q0-1B4Ug`Hyiv+;Q)F?|9F>AMV*BV`M_+UTc=$oNMj5=Ki9ssZ37BKz8B6 z1#*>_FLWmTqFHQIlLu|^mWBeUPb=~>3V;|>hpyQ4=$*@kkj{0U7hfI z-#tIuvgxIP4ihvAb#iokD4L+bApTf9Rz_|>0U{wwI#a2J8XNVMbGq<$)`N4(!urP5i{~V-0@sam>J`1b+&T55{U5SR=cHk% z_CM!T@zYO1=hTOI*8lS%^$xT*MqV7&)U;wSiWtB5IlMlpp3kZvra+*+Lw}Y$;5&RS z#~10LVX%)!c$7d#r_`k3K(`}k1QJS)F$sjc;^29&tA~M2K5z5_awUsSG{*xKXte7f zmpGZU-;|VR{^N?*8>cB*=lOwAL?9+mywz2VZVu2AM$ABEG6)p_XbFcFovy4Ku{L!& zJsAEr)l(@IB)V{jmUKx(akuUq{)lourr>KQMxGQbcR3-=^y)7b4Z0n;7mfL~udE54 zDM}rus7%JN|E<5cD3Q--PUEhnCurhZ)=I;g{+`%O!!V@a1^w*)QkepvYqF{k7@Tg1 z6qlr?7pV62gNJ@)ZvrW|-UNy?gK(jfnEfR4B1Ab2wC@^mR%%1C@sq z`-{bJ7?CXokin+qBXfN}*s5u5qJaMA$c{t6Ssqv>mqAQ{2A1CZlSY+5gKfyZ zcAG1Yr33GnX;f#KBc|N^VSL%k2rqa;7ost#;Gj$g%T)T@vPRT!!X^jBwl5?`;50?l(v3b)o@fkr}V@ zKjIM%9!v69jUO$aVf@1pcwUJNE2g7O<;VRlER?vI|0d~|fxITM2WB!>!(+?r0eNKm z?NVWZg!5=4uVf-82CC;)O{eq0$7D~0W@On&#Pu*S1XQ`C_aNmiC0uii88mC&*MEBH z-6FAQs*EibmK5a&vWe3vb;%34HD+WNXA$~l$p!6-87vr+cN!B{lBodCh~ehFD3|Ly zrq0a%Bk1un{}H1T+lC@SML#Awi=cIo)*J+_xkvmyxzwk6G0HF&?jVBMa)PD*WZ^l^ z9?fu{Rxp^rD9W)asq|y4orH#B8DMW!REa7laC9pMg|PYk(g+8XCS-f6fi>+`sJ`RO z)8Y?(UpWoUztVeh7_=84Y%HCEF(_Jrh`=uy?+-wdI``OdGm||#N`ZPQaCA)pA%}dB z`#^}P+c*gqG=%buftwezY0zcfkr^GH3hy#qhqrX!XI)|1Q>l1b-`6yf=8Fu}J2XT- z8eZ|h*c^mOyl+`vO&jz<%XD?5?EnG@Q~EZm2rm&Su5Dj9)bP3TRhO;+fmqAHe&wGN zG)pEzIg>S@0n2Prrf$J5=|^s9^#Ni<19U=-gDse&^;*HMy zJCOl`O_8#Lp8CS~%0P;(1lU)IGL%y4^Nmi%pRdoGetNna9mL`hL$dqs zc)WKQzE*OoOtN9BQt!UUg5A%OeT>)2O?qo&ruBAN1O#1$h=(GL7fmgnWZ`FY~Z3ebd#Tg~9O@ zx4dxr+Gc0H^6}fEK&6BF5Q2zN$IV=0DSobYhLg+#BYtXO)e8O|&?cH#YEUar(9%6{ zI&D`QoTl%h`G>kN&o4Vp8xj7#7T&tIauaVRsUdd}zoTRG&)kUqM~&oGAvgVuoXTWT z`)tTZjrk;pQA()lb1ISDeQkObaFJELSx`s0Me6usJ{-Nk1)%`R%|84zVWbdD#l)`;~lWCfNi}X4e1qQzs&Df zO|TtuNwM8MT%mHypVS-nMA`ukMOXIUFdmLH^FFmo4oaUY_*z-O7wAD-thHXyu+?`8 zr7JHAye!@45GnslYYFt7XKNG2HuiOzuyrIP7LJuP{-{v{TER+7q|anBOisG?8z`KE!dO6xavY3iu?!kcb6#7B2ijB=>ve#;JLvm!zJ7cl|3h6wjadF z?e}YjY+rx`WyH@#-rCV#F>MHpbO(v{knPsw><9CXRPT#-}uhM z9u5R*XlQ7uMQEIs`^WFJ$MZmBiN-H{b(?(}>Stois&$)o7?6rq{-Vnb0+8VXeZe|c zj)onbsU}FiRl@gsY4w?P%g60n^%=x4Bmbl8fNJRd0bBWjBmJTz(#`lCXiJw_L#cS3 zNnZGJJg9Ir8lHrkwhm`;mi!ZzYUofETd!GGpWGC~i?k#l`4M1yy{1!vU|~)QMn6k3JxgY5C^W z)!4$PJQz)u-6>up>-_sZyeFHc$fJdaQ~RtvGF`J(v&5P*;^}Pg0>lj_s+wUjIXTC> z%&FO89oHb9bIh}>0r`~IVvAZ-B_Q<}^3Vn_lBg@Ed4Xv@T9G}SOUTLPDNY-g(=Xf* z8Bf30%0t_!dmRVgWZ+4YR)#wf!&C|bVcyARfeYJJlD-tK(nM@DQQh$9VQG3)Iq#ii zy^PMK9Mw~=oFb!PT;hrNvy*!0B&=?rp+Q%8{~@0Uk3XyuTIBD^Y1L1wb zusAH-GmlP7CE^?)wq1*;8w1jHVfD6$^-~E4=|-8|tIBT(NTl^n$bRe2$rrJVSPz%< zNsGNSm2=Nn>)y+jVf}fut~04`F8TWj){YQe<{a5WP)$Z9JvSgw9H|U4D_(K zBJ}9psbtCdp2&+%z{G+OLipQuae3Sm;`@A zm(SZZyIJ%d#RO^=g~96C(?)VxgwR~OpJmK5xY`saj)p}i2K+Yt4F4Pf?|axJ-XkXa z@_mh;%f1ND?`U|H@M>omPPNjpdNW=x?6jb$hxki}=Q#Z4Yqmz~$abq;x$5-{;ueUN89ZAsM!|0#QB*D*y9BNqqPgc2a)lOBwopX`QxG(fL@L5WaJ0%dB3%V z{ZOPS?fOx!_k9au4{vthzRJJX8KtbzbW|Awh}=son1iPJHKn&znc;nOqH2YXL+8eBU(~zEl>l#S+myww!*h+u32^WH3J9Kw%2H(UOtlw#? zU60c8|Fw#qW&`w0Vp&xr@~5cSp~*EnyU|wqBcUe{kvl^96k`Nj{7?cmkN(Ww;pTUo zZWzn+U#7Dkw~Y|l{e6i%`80JQuI8{1M;~|^->*=epj&FkkeEN%d5xaEk*^zF1ueUz z^r^!Rmkp`A=3v*@qTke(+1m#!yYw}v)GA|qxmwYmWv#qnt>84`yClol#Qnm#V8w?q zYzMUT)?|YE{m~6GKofiKXaK*<2-J*EPldLQ;#;5BE9cBIcp6M zlvu#*+V%{Wev(eJ5kRM$_9zkMU=)`!F43~>Ma!cTOr0~ov=cx>%jOs!uC|!9^_`)x6_~N~+|}*sC;x1qQdB!a<4vQ(#RYvKLT)Oqh2;wdn5Qt`_*uRjI0IpJ(i6?T6G z4D6`*K<(qR9U$U<00D7O)Xb~`O2`m44IVR6M(sqrrJpaj$m+Lilh#?0hRKqz%zQ@3 zEx^@o6Kisa0wxB`6}q{b-zu1D3R&p~Lfl|8MvDD6=^GV1p6;qxGcG0g>dM}aVK?!S zP0(pm_|9rsi(v0~ETV4k{=F$t03CfF$n!Ehe$%_0A6lQ~ zCXh-GB=G6?sk3xz|MJf@{I(hGE`R-(n{%u^2?!?8Z@F-XF#BJVw`|Zk>HRgYx7|&_ zMW@;*QLPtGulbltd$S#_MJH&(6YZtB(|S9J12h5i&BFDAyviP{Z(wo02AcqmHcih^ z#z0_xbKx-7B+@LrPr%k$Y@+0Yzb==escirOr%9OVHb0RZQ+vAdqd>&_+!LzR^f8 zn4PyC#@=Sgp{5Gf;iNqA7;}iV@?@cOIO(LpG7H4pAAv6k>Cx5OCcJx>L!g+-ftSYj ze9{v6bu#oZr!EY+TrF9`J%$ba;%5tTs^wsyMu?eBK?{|+cMCClz%F^QTG`(Hs72s}uUn7<#EAz@Hhcm29ks^UM%!XfCwOS38 z@TS|5qN^gRkwB5(l}QlO%Orn2zr#Jl>di4n?7vHHK5Tlcd-53J{|9Z1twZ@1MPRUq z00I_c;!M#d!QwB!siYFggmP>v8_kpe1LI5b+{X2}ZeqHWjEvNK+yo)G@0{gKNE7hz*h91V(hTp zj~-z8&WkFDC=%*}m~?fk${y$|QC+^D9b1M|)9kU^4C(?M&QK`w+rgCqHLWd0sfg6b zq0tvPR=BK-4mRApV#V9T(PoikMzqL@#uTXD;u@K|RE`sGV1K-8h3*^H;Ggehu5*CC z4b^7;vrF~`gUwWuU>*d1L-|e-XFQg z&tk2~j$CuTl>+uzwZ`vBCuVrkW(KaK*NvQYdqBYTKKh6ML`dvS4#2j}*7C8QuW~j< zcMYVg?Eg9Pb&j@#OMqn?>~?|<$>Xx^JSxymL0Q~ zcQ2|bO}SEAit6NGHu?Y@^zq;%kx?^nPO|2!q9%nE8^67Fj{|L?1lc0N>FH!_RRXQA zENdX$^7xmB5}s^k7u6zH^b#2}iUUE>mz?(PT81L9pg7a(`+zjGkOk9ODp*TqW8&s& zqkjB&89840$@^7=&H7L}#*%t&oelU+*J_ZSKJA!Bc!BEVU@C;HQxF6(?cgw$uKi+) zck zfT~O+z~h0{7ut~fiQ5I_*op#;NDXUsJW>!8w#@9CvNpZObc*Sd#p@&S_xKobvG2_8 zj8l$6`y?5u$J-UG9jQD-3FULI_9{+iw6hHbJiOw*Ddo&zm4iXw1B2F$vVho}c^$Et zrnNOjN zE9^5%*C616(P2M*ZOSLOMkU%Y=r1)7D5A16A0Uk$hD&-%!>-hsr zfDzanCUiibPc;$=gmR^1|^j@}yLYJE+Zd~}` zze*~ofY9myCp7om96Eu1l>7`QTyfbf-2c{l|D?zuvV)<}5TqSI8w?jW*zVbe2&u3>;!`JU{bmC+9P`UUbzxcmb-(!^Xn%%47u+&@hH_mM^uA#_4X3TcGRzP{aBZy-;C>f_C29ghe2|qxh4&vL+0?I*ugUq2)-APP z|En|?nIbJ2t4KS_M^mw0|ly*@m@y^_8uRv}jBf zIk~NCsnxsWak0u7Zm9oNdShLe9ZkuZcW++YTBq-7kj>EhyKFt&U*qE5)Sk3mjq%j{ zdK5Wu$m+0LvmasckY_}1t?^-L2BWl2y|V&MLV}4LHyPfv&~B=HoMYZHySB{c;>PP%6;TxfccE?!`>g)>0;j3(GUpSO`^;Cel(K*FQeva|RhMR?{#X*nf~Qwj#R{ zn>`GA!OLr5S7Ek-l4R+%hf^(*p#D9#Q3N-ibH>81(_@m9uKS=u;x?P>_tNxi zw=qthrN0Wnk~PpP_~K`I)+1dT287xD?dLqFO-3O@R?&jcyYG6ibFjs`Bfe6bw zylb&@WmH7r#N@C5V!~1FcL?9zSl#Dh&CMB4={6YI`?AspD|ufhrLix_t&*=0L^C*O zUT>BT>h+Jw26yrjN#_p-aF4g2v+r&#z4!~Sx5GwK)W?e%?mj&SuLO@MjcS&pnpS0V zYlISU8G-$Lrmo4|{>MQOPD)JjJ2S;n8Q`zdlH~k8Odb~2@%|In(M`ERJaGET~2j<%4J~%pbvPJ^<+D| zr)Vg7;3C6Bv+$&V@BDlF-*y89T~+_=t{y{I_$h{eHclH)ZP7VZFBOn^g69e;dJOs_ zH8_RBDF?wx0}}B*p9K16_o9{#;}e)Jf}|wWa%793V)e&5S+dHY)6hMMXW8-1R%ZIT zpUj*lH5{0~XLf~Ud+ZGFGPpaHK5}kjc5Df8@6)3q_J5!`qNdqS2}m;1GC`yI3YUb% z%kM!#16>Adrn-VEa{ZI{gM$g=dtJmr$O{Lv4hVzShJDurF-5cK7{X$>6R+I-VZ@CU zxHI_mXzpl;0x#=ntm6kD8RR|6MNmh_W;uIv=Tg^K`b-+hfWe=wMJw-JlO?VTV=_0= z3GQB16vTQHON5qQj~AOqjseY}6OJ-@^FWl`Vf?xD0)C+S*mm*^7atxcx_iz%O=~b7 z$45Imchn7vayzZF`;Yom-qrhx=GcGqH7rP=a0)UY%TnQ9Qt9z@6;M|kkfPY3{?*4^ z8p7!?2zwVYX(TGpjHMFh<8o8D%_x4ot3RpzwXc`Sc#&)pbq+pOq8)i?m4+`dyd#V3 z&HO-CeJxQWcuH4!{~H5Od0$7-H^TC^9z@JiX0VlIe7BoUZ?JPh?pm-Mg_+aN9!%${ zb*ktHIVRK#n?xuoFT2z%fN>D%3V->vxARZz zO2y1&j~zs2e`voe#l8G$rn}|Gu~WJITFdhkZN`;3d`4Px8*CojFxVQFQGd}jd{z{95^+6@{w735eYsL*vXTIk6IOwjTO zuihfB76mxrDtkww?F8SRoyz~Hd&Kt!F%*x;QM^``vonB|;!*!Xi{;6B%fIZ1oWwF2 zm{3@+#Z;uWJD$OaDn;oh=%7^+J05TGM2r@w_qOTd%c4W8GLEmEi6R+}hPQfnR!pYy zH$w87mV)UP+SgDM4AW-rCLM-1Hlh_O0PS#RCj0Ilx?E?U!cU2Jvq~=F%awXo%F{-y!ih=C8$I^{{s*Em3{Q%KlKpqe zz?Uu?NbnhF6ls!7N&fUw4|`gTsU{+igIlg4grndmqk;2#ndMqA3j*RwFAIT~#!knS ziZkyBr;k0=Dwrt9T2FSTPHr zPfz1tALrkZFOZLjvX`&BkF#T_#r!m9pnsY^wASxTS|#$Erl+dIB4>>WKiRg(Ct ze7lbhwa_Rfqn{01!H*VP3!ojpA_Ab#8bf_q*|GkhO7qUiYmin$KOb|E$3qL%Wol$# zcKkh^VNf9jt;#ek7VjV9Tz$0urUY?t8gu0`N-E$aZCo-2 zOwq$AuS~RS#JH+TP5P`zj{ZU5vzG>{wN`*xTD;hS{m{o79tTSz8lcJN-O~BXMX&kh z=c577yy3kY*rF86J;^TqL>ayTpZGm3Wa2F5ZmE!#gd5RJ9Su9Mg2n;YqB2YpaOTm>16Lh5c@b1NO$nSRYGnU{@SeJHC7-pweIrA2%$fcg?2 z($$r{DNfyP9av>z%02ixs`P@RiK)&fIBe|Y4ape)fvC6}PB#*dQzRF6?@$GoCD+x; zXI-!F@0eE*F0Mx!Uq^QB+feMKlh3~;I54%Agucdp*A7_`L~XV5Y74j4nOy}+AAdzW zd)CT2rh|QK^>k&UXY^4l4}Q)wgu$Ue#mrjLYdERpSNTlKGbYn|o%w5ff+_X*f*Oyz z=&4ek^Lsx)pQX(a0qb_z^7K3-1K@KzKzmq1|1=qL3O{VFDy({ zpvMpJsv)&*13)oCE> zp8!^bfJH8X&pM0Ng+(M~0RmTC^hkwYFnGpY!m+5CKfKd(a+xyJt{-uagPnt$)i=d# zty-HKXaiGVF3aJ#E-n4|`5m`V2g8>fXa;?xms(S&QIQ)s^*Um>Q`rW7Vo3yv z?G^a;m?EmBqwHV$o1;?L>JFQ%%IwQj$QUPpIH?6YO$(#E++3^s33d5T1K=^iblfwh zL11t<_Zu^ph;e{|OT=Dm1UpiON`HHJScJzxd4@X8Y(H5>#wp-Cxroint4@9;8MMl& zLc0^yFP$>P6vn!SeB5zQU&dFc_b!PHV;(vL$2<$^zA-fO&FMqh^=_TT4mXUo)#vOP za2z-;bKvzD^&QT(-shI)Zo}Ples*TBzTE}u#!=HjyMoOJL)($Kh({r5rv7qcHcqj) z115=`ZL&J`dw&Zfhla5I$EY=318b~+&U$X+s6t2F$%?;SxKz2WxnCq^S?s${ zsTvPw35MFu6!x}ezaW{6Wtc$5X2SYx1ZHji3CgbyzJ*S(36Wu0BJVOtB%tOD8qf*x z0V?K!A~L6pDff6n76` z7>f=(OOh2E5EQ%8a|YX0LwBFq7|H?EWg7~K|rJc&pPF)M>FM$a{~G{dx0Z0cCqEk@`#6 zO|mB@E`r>TU-{P`s{s_Q!|Ty0Fl#31a_KhL{lOups>yXf6_;z6m_*wT1b?7HS^X`Y z%1+0DYIOba0!MuEkg5mn0B$tEPx!Is(Y0bB(}R7mzA1^ae!=bE$N3y3B|SIh``PgK zuwGiJO}@C-P;qjL$15AgZ@H;``{vI5!!1Z_Psjq^G=DL60dDJ0X#mF~`Go?&eTJlm z|Fc*Q_yaFaG;SRp(3p$}T`GeOP!lkJ?&FwgKb6%Ky(*Bq*sWOVCw8{Vz6#rN1)h9K zPDswy05-Z_F%~W5HrH_0OMng3;t0@PT8_zx3cGMo4~hZQU@g{g2TxekEu6`ToS!|= z<4`_TUJFyAF?y0jD+s`~qlSq$EVIlS7@1$HX=wBuPw%h<78s*Ihx~KXiBm)Iv-vK8 zGeG_B0gc4PI^*%Ksu1dsz1{xSrgX3#Hr?fH2a~RFUp_44ik%5W)iT*dv=-m1P%Reyw;ghF0>+lH}%+eskN*vjgxIR@XkCJv?N>o z-4p*-`gxvnxKuk_l~FQ8$rN1MLHAnH)uE#ZYbcC=D$J5+*tNY+Z#fu-?I+ZvOBMId ze9UAYJ&4e)~;);R>CbZxBgr=w+Z_Fo~2M&+e zMA23GR2}t$@5XuKKQGjls;uelw;d%60LWo~hJi?e$8eq0BkD6jH`WaozKE8}(zAMe zhu8>Rnv1Spez|ouEp0E^Q$VZlA*P3JvwT9`dR`*$)Tz7LWDK|9*n!6y){YUAFa3Or z89`~wq$^7DtWRb3I*>U_xYz!ZAp^hKa32#IJQ|g@2~nAVZZ_B*>|f2$JHeOuxmp#}a^LS5?ywvZJ`EqdZ2BgUy)b^2 zLN0}dN`JiQOr{IeogPhfgYsX^o}TExCVGM54-NaNQ?doIsAHx>}RS%1s}ja{{tLB0WRBt-+>frArx1UO-)AGOkl@(;qnHHJ=K8IF+n@gs z>1fpT6x5dFtLeAE)u?|UtJWt&fF>N$vY)I)oB;;RCT z@sr~)hY*1~e~pnDU8==hA5XUZwO&Mgl+w7UKG->Ma)LBS2V_MLR9l&t3cGt_0n(dg zF&Z9On#vC(8+#xW;xxtwlr7j-evJ9@3m@|44xNIMJ!qt^xaVAv3q*1*csx+qa%-~U7@gUpe0u=Et?ByYB{*%R z?y1R%l$&D;uwOZN62UrF3&}HY$o$NAYuCRiQMdwn__;}#&#$9DLXuflL`<>MA2%QGA>VkFbr6PmN{^ z`#>iG131O=yR)1dcl?x$iA?5nB!>a;qeo)b4i?J6u`DM&qpRl+bWMM!K^QWeVip*A zTf4~OSN?$yj(oiyHB80f;t?6Ky6Pr79oUk%f||bx!@JATm4#SsVuh?>@^BxX`D}MM z-l*L(HGAK0Vvsc?Hh~Sp2oXa+Tap6~Px~~Yo`F6#5Ai|OByH^CU&ldu5Cvffmcb*- zMcy|<-iP!Y`(`fo2{}bp-}<)t+-MAOP9pDK>ghrk$kK;QJi&K=J#ttj%n}M37Bw2R zdP2VSkLLtz)q3lULbf=Wdyise6SFFU`E%#`7Nl^MfV7LT^R~Pyo7g?V6{iSblJwj2 zw+)^nNZLzC#_5P_i2=m}B&IUzp1IXBCmD^<&LL9sQ&O28S0!6@>XI9~AiJ$KS3U@8 zH-<{s?QXoi%C}sUI(vr&HUwB#FYCih#Ckk!J7!XrKE#@el!zndB`X%uH&5A_`x03Y z{ec3Im!Ukqz9TnK@7nIcyr;dK(km5ZGpaMMC_JothNxV5Hplg&S%*3OyKG#X8u|4+ zQZ_?J|o3=Etxk^nJ3&&&@>xPDFjV zX8TX81B@2u^9Ug~SL|}bNl#&ul>(6rZam~j|f&9mrd^fvx`X7qlLL(Ye9?oqKo_?2aNCYdbN(GMX@6C{&}tQ<{Z_T(0-tf6 zk!tmI&xvUE;G#*pJKCQwtvHMn-X91%1>cs@!u6r4r6z>^pRwx8GDRA0p^*+@?A(es z+hl6u9>pi7)f&rk*qW%6#xz>WZGErQ3WNo$InXw<^sZ1g#Ls>{E%*}5p0@&#v`u;U z65JoRF}7%Y9Iw+8wa&-6Z86|2=a;}gC@_fx*#Xr&G*!f{QascAPCQY*4(+Rxghk)N zicV&DeBB~6!_Vxi)cKjygIGoRpJdjm4(g}f$z^YgIKp|$1y3a5-_m|=U1enMXQJ5Q zWzb|2@igWXpZ7k@Ch29=(NE;0cXX?(z8RJPB2uQWut=G88_}E58+eB6{qj)R1IIx+ z7GvjU40QWF(ndDY>&x-kuPvnvu!YHiGyBWIdCEJ#KMV`^mlXV}s>Su}Sg6oRPn2di zpS~w`t@G0ANS<4Djm4+SwTsk3nlTt`y*1*y$M2izZm;36ZS_v9aqB*FJhB3Zn;jN1 zt3rI;{6c@4R=9MUuI<{arP2_HoF!*$7q_LqnU%~k15Gq@wxPYDs;hWaDkhW9Z)P`& z9T+t@th@rZH(=yoF)pC-m9q?Tz2-_+K0g5j9rs_5e!cm8qvq~ibxXSK!c*TMerr!f z;~+LK^XST+Mq20dQ?nl@ZEO#(aWl5@#Sd4?jxQ4sqiwVT)=hj$@2Os*~MQpKF@Til*jM& z*>5mp-V_*4)a!+VP;V2=4$Yq4OWoNixU*FpugMawzY}R!ch@)Pem){rWO_4Jf|?1+ zXBpPczfF!NFo^npVCHjFx`#HR5)x&oP6wP!a-!UudD3EnZ(ozd1lW`khTp6_`D^1J zY2FX`L`0;w!v+G6vp^Ua4eZAszN{wE2=C6`wd9E`qmseDr|G165L`1&f};{=@ zs_UqiVa4KY8+{Y__bOJTC$vd_*5P)~L3QC&l$P=;i}#<4idXh^R1#Ga_$9PFyW`9E z^U{6)e7wG9{BPX+Xozo8kW=BYwC%-2Jz6=OlvC7sFA7X|LH<^xvfl4Ksfv7Xjs*)J z#k*vxq@zemkjK8LF>>@<+CUg^$-!HtjEq)<@+;DQQQo&v`HGr?F`b5Qg@3R+B3+ z_(R*=yW;zed>`(L5*nECH0TAV-5t2W!PdXryq232VJ`DqC>+D~E;HZhssndZ60%1| z8wK;IaHQ2e5w;HEvRiA-2(%9vVRe%A)+Zer@+@ynJtdx0 zlDa`1jpALoa>6sT_!enjF{70h zn_GU5^jsxJ*vT{_m9MpzulY0xYeWsP=v9(I0D5eX?0vW1IyviN<#+!b!N)S! z!pt!j{QdN`3JuoIH&IsPB(1YAXt8_?<1&9b^q|_Piszj7f!L>@!1E}|PMFYE>WuO_L$7^V1kSGm$x-BiP zrurqhFu^|(^b0U;yx<%S&PG+)nxKZgu=}Oxyq3r3uKJ-WsJo%rR5&#FFim)$oHWNv za0JhldMGzha@YA)kQM2`;$r~pt#TWX^fC=#CuTy;m+!{|ZpGJShAY!IZw>ZMSV$C= zCtmAfe&AoL&-r!r*SWDQoNioQ*T&{q*1RgJhgsb^QSCq}83Dg=|3LhtF}8c9U~)*Y z&Tp&IKSkJ1otrn1Ir7m&862()g==->-98^9^8#s%XpKXdt3=>)(zeM>g@7BGa`p7$ zdj5sO%9b;}v?KK=P&K|?&9G`E=Laa90DlfpaH!~Ic*=UWI z?gUMSm*6)qG{Hj9L%O-jx0)X6yS!L}=FZqlgK&KMyjLCIFE%(2dhf#Lvo7hxa3w#` zc>nUr74;A7R)6-DFI~Bz68)TOI{-LrN*6m?w(o_kvr(5ppc2`>Wo5FffJq{9k3u zPklP5|MUX<|H)PA?k02(RKU4`)qf0!lh-yJ#M!Ci9g6AAYD zH~t0(leQ7fqxD7}qE-M0YC078p4j#yMYhO#J3n5kvIKmGF7+p%8~1YYRU;{sax1L< z#@2;NUg#TQa!&y{%UHpW1T0;sHKW(o3|g{!Pc5zbpjLCzixgwfh`&LnSOvtqcAbQ= zBdclxrlYACF(D4G`>?88YI|>YL*7L)kRlIs`EMxRaUAP5UMMQe+$l5Gbie0aB^D0i z;-Bg!Py9U~BJ(_M_8JK|n#rC{zz-z7vCU!geo5Q%KK>6zMB91xU8g$ckINjjQZOWQ zQ-ArJ5ZHPXN*S1a=7oNtbpCNZYN-YAdN<@kic%CwN#V`8d`WopugAuy*a^%(D1ntY zZ+(NtSO=a_!uN-Nr@MA$r`#L522=~gdC8FqHy4vc*H1m3>ks`#ZC7Cwl^)z@=9E5X zE1`U~x8UqKJSZ!!$a1e!Uslm_WeYi>a$+nAr-}d<4i%N&ISv4^GvbH8m0=r811s@iDy;3K0 zAlFD|jXPD}@N5E_ZdJxbz z>yun|KWD+^_bzc-d@|FgHr)ytp~Vz?xloJ4VIx5Q6~51GDqwesCuez7qRU^O*~XTJ z&unQn_0Jb{qxw?k^RKuOG$rv6%6o?-Roace{?4z)tgnefFoCjT9y38z;S+vQ(1hWs zDYsvs7?R9Z=S(F)1HU%jLZ?3e<2~6Sx;0YVQ0eugr0n0<<8yIFSOem7&T_wT%DKp^ z87BJ`OUwb}EVxPJd|LL9`CQJQ9O=UMwmpKwdIw+5xO5#P--&%A&7qqzW~yrGq@g%WAeBnP@rdhLsE4wzd&DD|wcgYHV*&(}emu6;W6*KLvX<9#*rFdq0$}p% zvuJQ<;2o2j&>!!4J>iEc^h53?O;U|rghZ^p|8rRHAAO`m(}O@PY&ZL61XLvYAMM?t zv3^XtZP55HKs7DrGw{+-(&5vworFgrBS)2&fD~~?bMpAlNKjLw!5ZQg9TU$Pzwf{N zH1W?5e&^cbb6#r&*CK(`RyM!yUe#Y8ZPDZP+_sTRn$_6$G<5w%D|2Q-eSYCHzc9dW zx~aoYDt6wHtr-T- zIWUF$2~YoG?tAa`LfPgdV3Adp#a-vNq}RIj!mK1zyq>->Y^1Ekr;&u3qApU}a#yuu zxl!(Mv)`WkLT`8_&Ha9)^h5rL=^QNw=y-La=kC$k*u!MlivRJcxWV91`I>v2=f$Oe zm>x)2JmW%DNKd~tNqnFJdibyNlb%zfjb9I*VeSw^aeH~{qyG;#n3&u|l=_JLsZ~sjYLpJA_ z!6ghv4(|ulzxI!=lc_nxfJt zw8oL2rE5@<5aW8^cYc>_&UQ>i-DmvE!o!QV@HfhHnsT;f7Hin~O4sk@Q|zB)Hz3Zi zbAy;bGL(C=BySeegghTh*8b!IRU#QJE#B!JQzq#MS!)m(W?>r)s`QPn!7-8?lqixk{AbvqTLx@Io=qF=xsh`!_9J9jr?}FI1-hs#IDK@^5&IV>pTfU#~SvxbxRFp@i5pH~DWhMG>-{yghb!+wNuqC|~5rUYy z{-#2aRx>#4O^jh}KC3mOIb}AKls+ig-p>$!6NQWc=1X;%Ybz1lD~rID4B+abT}%p5 zPby&TXD8w=im4vQmX^0fU+5Xy8=?qmw(|1QN8fMlfES7wQN-*2CHPPrJl|@3d9*My zZ7v)4>Lp@J=5QI|GvCNrNg{k-ftp0~57%GeVOINGhyd{HFfY%Na5Fn7?$y98$e(sM z!H9}cZhTQk=@LJG4a$xU8e1qB~iz(?`0Ks8$RVf z94wd}0nSz%vEA3EibmspBTapau_I%+EZT?x%XYROMfa9D%g^w@xyikvs9KO z{{7;otZY6$k>9ha*0WPP;RLMhj#Tks6~?2vGdViB@PNMov?Kghuck?KJ z5S}JTuJo9)8A;0?*u2QU=?tm4?)}CfV13DA!_H)})B-wPtL;kB)$=p{$lSq)AAuymRwn_OX|ZbAJ99P%2nNuW$m+QRb1 z9C(}4!ofZ+zByLdof<43eVOdQ)&nl)YEWMK_R~nmub0*8oo8uSr__4CtUNV)$_Jfk zEGMw;rH=2Nb%zmNuBax^%TCTmmoPT-rSdcsjfPi#dyK)I7hD>!M_?9$7TfB{xz3F~ z#!yb08{z13NNLcMlE9jjAO`+XA!o)0vfRi>6TIYmjjcq@0uVXt!_8OD{w(n&h$wqu zX!1{>xdKUs4*%YPWi6}6{kM52pa_qoJ!P|ghLWX`U;B)o!UobwT!0!{wIkk~c(S<0 zERCZeFnETRwwEv7;Eh@jIHZ*0--+4R(Qk-34N}8E6?#ed(fp*qTrnz#Sz9D%NYv#p zHHkO+7LDeT`D`mDG1fEZ!OR@A{d+e7B&Z^(S4rwqZ7~`71f2a5TG(y@ho$en{JFnf z7>@SxfIHO0f3H^f+8fX-A{^qIJJL;8zzK_m@7zE|TJ$hB#0KNSVujyXQw=y)(b5`@ z1mPO=772tip^5s!>Z4@ z4-v(wW_j-9!osR#QT8(B-i1cw(cWyX?;L*JH%4ZZeeP}37>So$$57Ofy8eB> z+Vo}7r#c*`r21X6SjSLE0>2A+6twkIs#r8<5bos?=NV}PA9ix3x9XDYl+ip@?{-Vy zG=0VVYT5z*utE6Nya6H+=zGjsudwp5Nnr5#W z&+fiLe<>25)67ZwK(hr?hOoYdOWa!w|#r1jnf z#^JtS1;X<=?XqF_i98OW^8G!UA_V^!c(ILMl`&Qs6$OY_jUNRc&xjrW*^L>i;>_SnEmyRi(rFN4e8Um? z2T^EY(01%0_+Z4IA8#69i3d{zT>2wXq#6EMt0CgNm=D@^FgJ8Rqhup0nW#2ph-yoG ziQ)3pX3?Kjl^L>{IU3bA0~*fgrORzPYq{->cs;GkVap}fTEO0d72GzU%+9vHOLqaJ zS@(>;Xa&oaE7$*o_$IYPlny-Oxxe+_f?2K#`>Ht;UoDNN2aEGKmdu+SiRbc?k(L&S!o^vz`i_ZP}1Ncs&M?K7X694zzImP}PSw!O4l_lxBE z^oCN}D52@vER5L5tIwg7*(2c3)#fs6-mA>)l>^~1R5k2-V#mPT5bMhoa3eZvoktxsrwg02VI&4{5b`q+j zKi@oFy}AwbmANyK1q?>y2AfdDyp7q7hfkv$6SVMoVdxh>WW3C6Y9pVqXRhI3F}cs9 ze%3B^iLb85@22467e;ph6Nsqr4GiTYU-Xg})9g{+R^eMwDk^qE%abGGO3WsIR-9fE zqQ!n=`&3Ff`i%hEGiT!yHc8_yL#99r*!CD*1d%c%P{zZ~k68;wh8f=0nE-C$@gm33 zY;wo3=Ca_1$FaNk@EcQc`(Dyb-yX9hsC5i_knUV|m0-zz`zj8A-8pk5^XVHPvz2aI z&gAXZCDZP9b0HEUsc%~209GTj4r(WS-yVEz{XO5?ISan2jL{V!X~aCHbH1A@os_O? zz0C}{=NL=w&G1LUJ9LQ8nPAeroh8dMH~aVHC6dXk6S|Syl&d=axf0KOlthU*)N|ImU&P%>8Ch5oHg*UH_}E1TNrqq`N)#~DerI;oM_b~GpDq%6?V7U zAR7_eUlI7wH;r9r?OUb*j`A5HBsIv0axN&@_q6n?Ye|9~dKEcUCYtP~QOIoe_Aca} zq!JY90e1u19Inrh({}Txq;5XDDlu$|!(J;g`Y_%}REe{hd$e!$cC_w;JeUn1T!;?@ zomR=8tc~P*Eq2^>)4KTUyO@GSbqrAm&)0^~())@pgK?qP!6SP2ViR)LihfO(#JwpC z+f~ATx|kq8%?clwob-miyp@S?2e7R>8r;^^|6%79X1D&xagv_Q+(sKMA`!p5f9jDs zmM_=*gT= zi|!2uuHLNCY92Q_C#WUP?Y{A0zU~%yu`3o%Rp=Ge=0-1qGgNC=^g0K&POx}U{=Q(U zUhb+iNktcX>W{*MjYQ`&dYswre7_RP;j6{`S9*e};I*`NoKU#s9lezy>@|R2Ylq4F z7E;3I9^~}VGr^B;!iC|8&90sZX4E5 zkLpqV+Cwn6Pa@?O8q6PjP;|#~FDOM$m`91hf4p2d=>Rl6s3ETi%QgsobnBB|3k(B# z$+?(XP7`_ksk6LZe#JfTOxO;EE&BXAcWmP{5zX|yVb*>AoQa)rBT+ZX>&uMf)YW#% zIa5nhVG1@TeSC)rJ=SiA6gNMGPtPhEQZCrT`B1|(Awfk$rysMvsJfIo_DIg9Jk?=< zLrYgrT3Zgo9F^k~f}C5><&m-Uk}BzZU{qC4Lqcw#FQLN6zk|H~J+$#+B3}0s@Q|24 z#<2V@B~NpUlii0UI%;2eZ^c7#vFkKOZUP&ogq|Z}cqfUGDC9;CoxR`r4YTz;eXw12 zncWw1H~zqFtQ&s#ki(}Y?@r@}P23Z1=J=ro_B%6OS2p(b*h zVyW97BWSO)W^@dcFY^#W9y(>H?J#o++VGVSH7&kLEUAS*{a2}U*(xB}-u58h2 zPV!w{OAB5_#zze&A4`?iR6-mDUM$Bk!W^UFP;`ij_HU>Z3nLEy-MoL7VSXUn@#nad z<;W-GvM8=K+rLX!!`$Y^I%}+TfPJDjR{R8f4N(L(bEH*XzZP3#pbN}$@`mG|(=3w1XVUIn{Ba#x4; z%O7S@zTCYdeWt1T633ao5wfXpe+-yA?{u4k?W6C%?)jq1ws}+9&k`c8u=MJ7ul~Lp zO#T%*d?elw*g3V&gd>BGk1y}9^Zemg@Syxdkm%E#>Tgq}m_M+(@$m7n6HeCFkj5(- z8>TcJ?dn9j)IW#j%gK`W?+oCc?DaAoW|n9!q+i5<%w!ZPU6KeZR#VgEdp~|PFu?xA zMB9-5e#K;MPsno@)=t^qmA;ofEO!n20NWZ~Pwduhw?!k zyC^j@Rfy4KSHhQ8y6Q<3$|`Od_I0i~1(rv(CTuz6#=o2YwSoW+Ah>}j>Gs_5d1-(H zs>hYm`b|Ol^=io23pa{Zyl{78^E(sF#L@=0RSg}7`VLQ*tCxC{w)D{vmdYMCb!gGg znMs#!F}!OKd@w#5 zQQR2xJ2tq|*_3wL;>fQg`3ceME{cGugwteG<~53@d6J9mB!O++%JY6Tg$;Ik=v^HS z2*b6#@;VVy-Lm3r$P7&p`bZeTYK2T~*lh^QbihoRVF0DlRq){o~f( zVu$0gss8yj$f$FcK@K+h#arMBN?nbS9z9LbRHotXp0*x*pSdFYlNh$&Xz8ulhJ$ZQ z%eTQj+l^sL@FZ@9(PiyLK0CtpG&EGS+cK2{G7A^>vg*yAtsAG=jWRodGaRbKF!Z)f~$T7&v2hrfBq_Lfa1Ig=`A>2m29V~Z+YFhpye~F z040fd0va*wKRnaqZStQu_3cfw^FokPtO(vyH8{KoQl{q%dBDX|{?3px8*bk&n!@lP z7h{RA$bWFmAC}4AI1bzjs+6Zs+x~UCbUYqVCGcwUsXxM9UEG)Su5%;$|y2 zVp`y^6l``x5%=-!DN=8{7n^{xo}?-92k0>7wcMKN`El9@sHR`H*E+@O4ZOmrPsES` z_zA;N(pSdrQd{_~aAJ3b5AoFqqf_nTwAO!U0g@tUP=?#;n49t4aOK_*35Q}bcn?N$ zu&#OyJgLIE&xV}Uw%EtVO{a@$-V!$3r;YgIsMHq6QsWYmj3m6FTb$sJe4|X>Xks_f zXJ;NdGcId8maMqC0E#FCFlJRqZp90`F#*T-WkQogdu;11YAiI_Zu|@+2LoblLdC@N zfId8tLicH1{VwY}3$-i*hJOCt2b%6Daau^bYT9q@_{H{9W#8I9bK{OICR8jwMvrfB zxD87Rt$BhmQ+W_F;O)5(f=E?%J$DsSvcMBGP2xHHK!YLr&4ASc`d)lO1=H(JtgNBN zEHf&*-zXI;#$SuP6X!xIO@|fiTGs{Yj>Xyq(CdLGl=Z>Q_%NXB3}(aLn8EE{B7`%qjT4+~ z&@-ps?BoiqclQ7YDBp_>WGvM_K0V>28g$Dd5zW}sepTOweH^r+ZhS)F(8*J4;?|95 zGhS!_mL~ENJ(t(-e60P{4{vXn4JfXOz#tIjuXT2h81?9LlT-B)l%VegQMbsg6vO5k zo;)uUVP%QJ0^V$p#<#HzM$eJAovGo8=)_O~JLMO$*@NSaBHD4O3f@2X^B-Wxi~Q`d zX|Ppa#c4e5l!u$UXUmk3Afq1)U0eG78O7NvNeRisQ(nv-tNB;TI|JBa2lC6ZYIx9wB6*3&1rfQRxil@oU{)8X1y#IlO&=Y~30$UTGZ z{f)Bc`Oa9<#ji#Y863$N+Dw!~WLwU;Gef-#F0SU~_gh6S3OKy&jY?&msU_zRE4T?F zf|ZIz?DB%2o~H;{V2f$J`#N!2I^FI^TXL9j&>p~NzpQX|HX?Mg82eXg#B8?I1l)dfVHO*K8tZXXG;ituh%46qb_xH~=VvQxl3s@q$1lnj)U z(A6AQp`Kya={qsnu4Dgz_y$8NLjEo%IA_unmo^`gQdc3#@Cg-k>nz;9G(PP@#-7%& z!4tPW--Ls>flQmfq`8ixt9@Wzi%w?Q^1yodOrJRmE=sZ^gqX@_PQ|X1>oc< z>+nkuuj5QV|GYK)hJ^;v1SAVV8dq1(P1MYCqmU1vr<34P5A&_aopHoqvhs_kb>hHv}L85!s5xo z7>NF{%22v*2>$PQ?j9K?#|540zH|Cl5ezAO5eu63Lf1NKa50I0^4RsrhddUao`%}A zS#yI%$fR``vy6WH<&K4Cy5`H@0jPtn>UDdkhV)v?V@vuL`UgImA6Rb+3-Dz9DQMaq zSRE%=%u@GLx^UU1tUZ$C^(gP6W6kFIQ6Vz^m6OShV2)VMY;&5gy7=ovXnmEX1D>|P zeyW%+kq@i@?;~mV^`dQLi!$OFKI}b`HpR6Y`wa1Q7C7Og1*RdGSp_B=eq207VJ*6$ zCwFdbUE^-9l|FDG)?vL-Y{{Znw=(_(d7|IEa@IHN@QV&`QnJ=mE#WDs;wSD4YY+Gw zzbq+`Dcf2_7Sf-(d5MlXR31{G9*|HyWAAA^JaC+tj< z^aaj8C0`eXC^i=l%vWQwqg%n;_sIy4L1Tp7@&R_nmxtgQp2Dc8WaCF#Ma#Y(4clYCyXTe{gXS+-` zDK#yrV}S%M-jFlr`wl&6hCZY*+Vk|0gQuv%JnbvLHO54W9dz4a@R1O-w|NVpEe4-4 zS8H$W%mLC5SG=@+;#9^|KntJ9v^#s&j6O*^8uycF#A1Vg%B$+F+n5pgBY-;Zi+>S? zC{e2Bi?wb})}DaBEL3}FY|GFw)G8hFuqrWAQ(;C9guA?XWZE>j+5p7+%T|?tqzA83 z;u9qlsL~qF7aD-F#L653-plr=pkV`v8HCgO@S|dB4W?**X)ZNn_<7PS9Jz86!?)%_ z^(ftu6XU`KQI@&Cb8_3&GCgG3Q3P^9^-HY}u$ut9rG3*>AyQrZr>1DvhcDqBOhcM> za>-TDPW&}f;_=A{fX%NR;l%HGAEgLNK>db221!Qu700lr&$CzD)FB&595P*i^tsPK z(V$V=$|H>&($;upruk|(Q8d0HtiHW>ns8cGj=+BF6_@4grXHsQvfmIWWSqA}>&`yn z$X9qfuKNZ+UdAg}hyR54x6FR{N}xf;oYDR@f@gY9kSL&k(SRbm} zCsnl%t5J8&`%)l_*N}JZkU4bZE^+=5<)67b@rY*g4{u!KA3`y(24v|3*b+B>$)`3w zb*`8AN;A-8Q?{s>)2(oZq%f8u)yNX$YKI$LL!TGLgE)M72b~rYNw2YurSvD#nw%oBe zTo@ztYaDhvwpdJQzi-!ciQ(t*pAF#*>}AR+f^}<-ChR1NqmiRfR~O~`7KL%BQF{PDiOavc~xl9pSHI}}-+&(&8}uMigS*BKMLoW-Y4^oQ(C#@$gC z2?JOqk9WIG8%YN~Mim}qd5U!lJGiL zwP_>G1qgg+r%$ebC9R^=-C_>d;;r{lYcDQL~YVSwWqF(6KNfbwf9G+XkWx{jv=;eWKG<_x;GO zx#}arjR%w|LAvF9dheH8C)`DM>3!XedAc)YK;6^^TH5VeGMmgt`VEQs5ZkvQETNjG zlS^kMB%J;)#1TOK^V?7%0~{ws>12G@uesJmf7~V%=(nfKeRcJlBA9RqI9C{GEIlpk z0BB3Ds08h)(n^j5D)Kjb4{LQTZ3>i`?TA0Tv;i#5zx26U`@xFGG27_=EA7; z&@T@wb(EkS9;{&b{?-dTRT*}>)xmxj=Io|)sf!B&G2V1Ka%m?Y&vVY>hkr5w6>css z1=NlJYIq6lGe-M)=ncib7n^&XhCmrRYR!B2>RFp9v2$O2Nh<&M#v1~0w#1M#qPcx} z1+aZfsB>6Jt|gf7XIJfo3uvRxYetP2?I&i+!#aZzB34(XZZzy#FNJi>IOkKrMKv_1 zYx|(hTPGdc-n_wg(MlfglMnFuhf&(bBCyZ|i7T>Fv*;*{0n&u3K=dlF@|wd>Vs^}4DF>Cbi%aFZyac)yU_ z*ppp3+EVme1SwQV!`yA{=nWGJV$EeWSh?{ugw7izb>X@?&yZ#IVcFf2mVV44Yco-F-U{-tQZr`)^mhBAsVf`W;bTZAx2tcs~P_XpT4j12rOV{{uBf zXQ+_54Fr{W77&JmKGG2>(7J3;?p#>1*O1J;_H^pPg~z!`;*M8%egCN1a8XDX;qAj( z^8FM=?dzE(=wUMv9i2}s?N_RGrZ*UG%ivdV?7(R-I|(?gs(quWz~XXFqIucfKeWcs z#B2XR3E>xP8JYETo9!Rs*&c2gL_E(e!JhMrSq}2_cd~Ihy71o^ZF2^jT-Bb#W<$Cl z&ZeF(2|CPJ=*2UyN2qL#M9)MGx?|#faHXEU!08Hz{_HNY-fY-I%;DP)(s5dsl6%?n z4`g!E9)MHhac0q!?Y11S9rdW`*s{XVd>wmshfzj;D&O{nYIg8vKlwx?u==&Cp1v+W zo!h+WXFX3EVzfZpJYfLQcagrTmS4|130{ecA|6$IRmpI>CgS>IAH`bEp5W|Wfq}b5 z+ZksAn}JCM@_enY!qo}cu4$4F6#%>$7b>?CNdqkh0Y_VXo#2lRR!;@^@8dDlT z24^Tmq77T)hUr{=5q_L^<$Sg4owor(>^(ps$RQVZ3zvzTkDGWD*FA!u%+Cl6@fQvK z|5L+JaQi(tjv;PFIuvB!%i9s*@yaji>h*{M-8)}`RgGnhNG9X^6?ZNma}?C`Dfh%0 zaZS@D@bFFx_WluRnQExqHAYSLSDjhJ#F8oNWfQ;Rt9M*}7x+bOKQ-<>qtF zSZl&^(`EC_g_T)`YQ<8m({MN=aaD%$a)$KY_jlb@vJV3L1{w30F1MIW2o$*ot*ekg z1nbhr@|(O6PqMmE^OU=vMky+2s;V`= zn3#6C1^-w{%#PZw*Zqq@&q0sXK!_aqQGX=Aw0>YFhzHi*b`?0OX+Rs09M~f^yOg6Q zQlMtDrno`>I;j3BgFCk4iwh`(>rty>E0g_Iw=wpB6QO!R$jc6-b+)jaNF&xthO#9! zvS|Z5>ji+R=m(%%URkDd$4Mk%+h)aScO3WQfOGnUjKP)E{gRD-TPDMvL+w@!)P1Xic;0-wf=~CK<2dek7iJ&| z(SNA6k|kD$mT8rB6h^L>AHhEQXF_(f+m+7}9ZKCHs>H%ywDlEN)g$BvOyq8ABKFcg zOhcPW>JmMG{e!Y}L_`Lf3oE z)!P!|;%>%e7G>#q6Ee``-AQUMJ+c7A9Nz z1kr4ZiTE?c3NbW7IKOzISZ{c5&|xfhgwd7>GpQ0~g~59bgqEz7kXsb^!e_kc!ankGzkGaHDLVhxWsAb<-%7GUEfUoTsKIm%&}FK<}vI1cdXVZ$x|a_Oxq9&9CgValzkq z!>o|&veqe!j6~UToVoLJl`u=zpF^21$OtX2=c6NgIw{;mj>&Gk+!W2U&=Q)c+HGGc z?`!*b-h*4yc+T5Kh$JYn2?V;|h$X4DIKIA{?bq+Jr$0_Be3f))BD0!Jd^^urO~DcH zTl{rJy3*BUmwUAtivW$Fv&uVsq#gcnlt@A?c@Djz3x!%ox7Wl0S^sI@$rv-lckn&3WD zH$Io^O8Tq9eF9UX%got-D5H8}gkfIJ;PgHg^zm99K9S2;yva*Lzq`IxB#K_JNsG8~ zVlK0p!i&rop%#4MgE(y*e)If9a2SyR>+Im=A!^Gz3pNNjSLCe&ChWarWG#~lyuSIy zwokikSrxJB#joPKqQeh7kaRXa#|&X5n#{taSRT5pKc?pWFL=K$=uoOr;l`Ou(oWCu zt+S1S+VQ`56hOl!orcSy)NN@gW4S%3KHDeg7iY3lzxa?PqKnBDq&=N@w7qu$6_u6v zS0HIoiw=Kl>j@!NyFhfTd^i(}eqY!3EAFH3T!PM4WxsWxMcAuw-QF`3V`B5;`_vAQ zPx6}@OvIAKa=GO7y%5BBS4e2K?T$RjqGJ2&n~DAfh()GVf94sMq1OiQ^XJQ(vE5?a zKOeLV8h4}1e41mhCstQam8UXCIenwj@1k};OCFI&JK0@wE&DddbfT+iaa}4wJN5eq z2h8<4i6eBXg2oN8X~lVb?9#y?>FlM+DMX-oWHT}R)3A;*d;|tK$58Ovh7=`*Dw&S) za{^~9ICA&Tv$?5XVSd2>Phm)&wCgJ{T{~OO&Mqfy-;)4#tTuB-n`;)SC+V-NTDG_> z^s}Y0Uc`H2*GMyv*F}RI7x>klJqgIt{F6-bHHuUkNnC-l;z+-%XgHhKljNTqgxB(a za1hS!lfOa_&3U`C9O(~{U3d+kkbOq|cfZD%(#iejS9ouoKRkEj978Og*s`u3$9v%u z`in32EIKPvqk`=0oX^evCCHi1RI_9h%g*ZjDgb7(cYesOsznn-6`Hc8oJk+x@a`|w z)+Z4G?EE%&(^GcJsknpRZ8PG{u$Ez7n)s)ErdJ}9Wn@o!@?Qv)8U4{<--%R_9RJWQ zLUE|2G()-BvNc?0-FB_2_-EsR7&^r?A3<3Lf`bst7>o(FeCAvPQDy+{-Y0k2$KHJ) zm>DKN^P6ivJNYpfbi58RmY~PLkb5R4jz?wuQbq4K^$&vmucYexs%5`4Jbq3GhDC?D zth<2nT-Yis^&NaZ>sj>_hLs0mBC+RCd%eJ$j)^gopg+6ke2PSCgOodR1=Me39~lz3 zY8({-bPjP`NfH`vGzV#y$zT=3L;KWztXYaUDV|| zW}?s=k8U@p7fXFll`xvqjzxc(jeD}X<%wx`*?@y3j6~mB^UR+hJAz?1!kW0jgiNjT z$(j4jolVpp2{Yz;QBsQ)+xC1fW^t0GP)J9Wxw7MN@;*fDaD-?u zpyWyUj*qj@&b#2V=cbZSAdEs{FK|>FvI?|@^yyv+(OdsQ;Oy5PJ)?zb@)9 zM8?>-5DR?|=e_iOti*auaWJTAMaZXJK7FK0$b3d%oD;0vW;k3giMhw7G9IR8pg-J6 zj7iGM+_K{%eZOZU=)z13QhP(LQ^s|Gjm0~j zn3$RPi&%ARaM3dO7;`LWz*ddedD9gulYawo;;CW zs^MQp--vhXr($pMAybnlVHcuLr#RfJHQH+XRroEHWrk0ZzsbD9r#hIUEmX;8-u4d# zc~fDv%@H2;2JmujPT>~5(UfJ$aU$XVgpjiGR4;CN44wSN@EwyV>yYMqIqQqu#pmvisH8l3Q)DD-_kZy_cKB{yXM za$diLphRzgPoY6*f+heOaF#x z8oQE2OVID>_a(u}ef`;_d^kOyUWYpHx~_=^4k+hfNvIdQYu?7w;|)FjDdf~oabXZ( zB;;w9Tw`Rg*%Zpv{7dWVxVwzDe^Bp%Bv{oy>;m)L<^t}%@7wq5u=H)CpyjPPz_KVI3|Y}@L;@d9${KrONs8tYEoA`nb8jtPQZ z7`HH=DCSnkZZHbUa$bd2fAyL!PwQO(Us~*Vz~glY9TF67ESEmE*FlOGJG zckaCoAOvl9Utu)l$=)z>=lSSf3lYrFQmO>7FY?GL$EbH_!Gq#jPdWoX9uk`|w9}KV zVNGZ&PG%HMW_{~VG4~Lxz?Wl`Ys*JXN#n}3YJY&Vd+ui4!(`97S5$ZKw=sI<7mY-@ zWY@|{-M+xj(q?s07jN)bX}Wq~s0=b2Ud+W` z#Uf^HV|g@9mOPgp@5FElO@CNfs$;13d&cE;PqqxhJ-%PZZ^D@eq-&Cm*kvnTw5)jo z$=TN+YNe#hjpk3`bR(N$VrBN5vSV;zKHt}?NvH>nW58elr;i--lHkEXg9E8wAA-?2 zqIau52G3s zEWDYt!CAuQn^VJvBD)hZ2cqW))f2f7?>{&DJ@vVFdyKp%_Mw{mu&6uTvMqt44kbkf>g3tL2tQgz4iJK%|xw@b%s7VE5WKgcUKTIoq= zt*f@}+XUWzQg3m%GyROcBZ(s(`t*R?`juu|dWT$Vy$?cN@{t6T4j9oHFRUI_aeZwI>{LK#xX(WuQ8S`V4;Q+?R| zY)&p9v4|O^fUGJp#hOx|;ZT6~BiFJsUs$L%AjAW~g6us8nJ<{CxMafxq0O8}!4h0t z)r@4>@0ppeZO5$U(Qxg5Cdq6rD4 z@NxQ5HaHH)+FCk#oXV8Dax*#mY|tlrh|;}}%%_w@B`ax$D9Kv`cjDS0F1_6V67i;bM2?ZFf@%0sfBf#D^>)E3Wq4 zR4FTIQ6HH?2RM5EE+vx0f;*+nVo`c+2+;nMXcxpIG9Wus&R9c`_(q1aXaRCo!E+P&KrvpIQkVHgTrtqHI9{((q3tOH~f6`c=zTW-#n z7#S@GVLXkzy0>S|v|j+06pN^kmWv5|-}rnWXk+JCbbCCbs3IcmLjhW#byp2@Z+7e` zU8~skzWf=#S4sg<`-80N#M~4lU~4Em@A{V!!c`Sj0me9X9>L{@DI;SMg%^?O#T6`t ziaa(X_;)A!RZ_AYGxi0AJFSW0?_E8Ayo9>32$uQSd1~`JWobT-P^`7D(aHF8a@*i{ z>pq>NVxL5#^0_@7x(+fO#%)!5B2MEOb-Ye%WX71*WwZ3h3KAcW^o#hf#}5sr$+mEH z4{|B{xoyTCt6jXLi?hoC&?& zsl4a0x~x)hP@{>Jc9%IQr4!r59I)|f+^e5^2xmrL^Zt8F-I}^@X8Q2=bubktrtF)) zCF*kXwvQF7=lSZN&bJIxGhDc~UrAJMJD?S}f(;>tl$BDVUfc!Smp#bbFVc09v9eOvA`G z*xJVR>o3!WKpm#TZ<}UPy&aCu6z)Cw4E3>bu{;9e6bB7{AIfC?Zdg`69nMqLU{Cp7 zOQ1P+6(|=Rpppyl8+}|t?PtA9z2N9Fy<_8{)a2|*EK{$3X!-mTV zF@ly-^e4n(R>4q`rl9HHlYz9qo-`IK0{(7t!hscPhjn>IH@kWTh5ep8nE0OmF z#f7}BTdxX^0;;9dasI;lzBd_~DJbGf5J_4669F+BZ|GdKli%qRY24%YRxh4y0@r?s z#a+(pE6khaN#+R9BFkYc%>MXmuf$>%@^Qb&@nB-V-TWxUZGsQ1Ui(2A{oef~JTrXA z@VbqBl@af^ivbv$eZA}YdCS|Z8M%x(TA26A$PL(#RZ zOs-^ghn_6bQFTY5TTg6oE=u#@jLhL(%UH)K*APK6GG`PJH^k01eJ8}pf>F&SX9yQ( zMAfPu2&7*{GM9b?=_xF0y|IUi>`Ji9W{zL z3m1CC#`o~-{FzOArzKs6*i;~1vI}{K&w4UYr=BxnR3u%_h%AdY9#w|XUo1=yK+ zZK1xB-O|nUbS|d0^#l1ovvx@P=&$6<=!>0n!GsKa*Wa~B7`9(I9jD8k?RN$gzlt!# z#-$*2kOS==9f45RU!V4gIUsDXDN-ZDK~b#D)0qq;219F<^pl}@GIzdYh)bj z;4v4iYiTycvuseuFF9I@i4qXUMEU8|bg&HnAK9e0ronNCEb%5bHaVK?Vb>BU+6YAM z@Q)LPKDGc@$Ty)Y+%!)}PX5%Emoi5gz2=6?UN8~UHBh!dcTp+7Bj$qQ=U&2=oSC3| zW%)nL5z`Jq${%%`=ts;K6E?-MS&E|8@&HtYo)kTiA#S>*CuH}tyK3x>J3b$&#S2m} zx^N2|R^Dkmp|yzp@Y0aIgc|SZAR)KkvV_L{&St`+KRjgphl@&c^OZEVW4Cnzeu0HIT9mt&4^`qu2|BGEJivqyo!sBm0JB>yvt{S#P9O( zvc@wQXZ45y{p2nNiOZ?UX(L~R_+nfQ3+$I{ctzAPNlEZ$6Syn1e&f?acd*2lIa$}* zU^GzJaxpGf^@L}ek$vBADALOgt~Y-^(iD{P{^> zW+on(&5W?c`_k)EMZ`d1|11gU318hz&cB62KH=fNc@=2kVyZ|T%Q!Xl;=4Gc?J16! zu=w@}>Cr^Y;751(A!8`{>l|~iK9CLZ?fIC;*%rsg3BZXUCEQ$(&c`BpvGTJN$qXQA zW#u1?@=e+_*VsdVRrj^|mV*0Ms?H!7natq{nX8unYMSrk)>O}oDoaYKzH7~1Wj}mqJR4yYd+_kbWPkSJx$RvQCZV*) z$@}ZAbVm3gq0wyRMe9?VmqdcUl6t~TTQTtQR=zW&+16?17G#2t5t>qG*9*5C0pQl3 z++{=CyJz$W!=ZY;*LtCmP-VJ0>KBFTsZ06{2?Z%8nQ#TRkG$w8!#)2iKAFF zQWng={7qF4iy@21X!6GsV&ap#E1_dg#BfZ$3w>;Qg5l4kmM09AO<<{b_NV#k5_caP zCCA5$h>KRd-S?ApR0I^{C&k1R5U-4Lk`6%()f*w9!DK_}-TA3Ek1eR0)Ba{rAHFaN z-_YJ3xjnsv_#uAIP{b33h`We5B;@JO($#M?NGL&7xm|N}Pc24*=jX{?1rc{)OZPZ$ zXb;g|v--P15ap|cDxY4`iHQ8B@Dd-*`cnh2iH&B{ZX@I3HWj(3n<@oKe_0Y7{2d^4 zZ{N$N+u68{HtS~CKdAHIZb0FE>zKf6U1yu(uU;pi_a4s*`gpo{B z7V6_#O;$_AA8-^sb<}L%=gOkm;8Xx#zTX46a}{EcA5j0!p|t*AUq0D2(haV3JdF~d z=PRr8lH4W)h(bj5n>WX7$kTGvV;cKd>k z2}7rTxfTegqw7fHUGUXYCs_0VB&@A5@-NRu9N6$_cevX&tE;14uLLoUijvZiMU7X} zkvWL_%c6+h@zgyTq)YCWkVFic`y*ErH~B$TZ8a|(iOk)|C|(8A$3(S`J+8~56 zRhmfobF@cr`3SNaV0j%pWypAuj_%+U+Wz0F`|hBozHV)$ccdy(q$<4%0jbiYi?qIu$xL!i&fYou ztiAT$>v^8+wFd`=S_<~vw@{SWsrX1m!f$wQ?N85V^dV$$V_L-53=XffYMOmECvh(n zvyIGmZ2El~Is!RC0zTvKl|6Xkzu(=dwttZ+Mv5FxkOrgw+`Tx7Jz>Ix5;1`P)+4Wg zMzgW*F79VG!AF{@P`3|hGjQjBtE&XKP>13ptRlaCy32d&P-ysAG#aF68Zu6a-k_dkjnU zD_C>hx~XgZ;2_X#i+ahWsnb|`ySz-ffFez(?`zlpam4A*^FdhmfDvsm+a0+>Ym z)!mM0#Nm+StHUGG4NT#zu#RYeH43Ea_>k;Tx-U5#elP(%*n+*Ps?G<4e-6fzqBN)8 zh;6;0E{0j1I2yJ}^_3VMl!Lply0FOz4vum=hC^tlR_j%8zG%EZ+$?fG9=ZF>XT}Hd z#6cnCCaK`d*my@YDK6kQI3{`gDG6@jyt+8a5mE_s>5?JDrWQ+^m`TSB0GtMaK&c$+ zdCgL%)t(#$g>Uw$6!b~JLJ%D?Bm`!LT5NDz(9 zYfNp6Nld8@QRWhej)=J9GArMoUP+TUTSJ(b26wre3ns=0XdR#Z6H?BUXcJ(1`>#s2 z(D@6@GYBEdo;1R^4(1i}{~+l6S99LK8OQ#Qs;n{j8m|m(BuZxQqPf{mD>%E|&}@cp zT-oW$lT~$;Uxz+khrZNh%TXmcOIuzSD3{D0`i7cy#F$mnjN+e%hJE_K6rcZ(+Vy|b zpsH`Sh*Xjb7-7!8v4$S$_h!X2EuyOA%eD|_;453IbrEY{C*@o^>zVoL^QEuyQ6~hh zYer5P{v8Z5lD401()n#V(?qjxwE1yogE&aPS?)BbbD^CAFADb(vFPuAv%9E!b-F`g z?PRjt^VQtA?=u2&(8U6ZlaU4mi>9al>6};hx3`NZ$Y%c_(MDQy@`g;&NVp;5lF76S z1!HbeS0B1uWH=opcY$RQ8ULa3)@7rErFCHnk$@9&@Ux zmDTB^yKcLikcU>tDO~g#+?cF^<-V(_0qSfrou<%v&$Igj*V!_HUp_y*wqcnM_qfKztZWk6K6a8e zu~PGzw`-!5HsBUsty4?y1>NS2<%zssCPRcG_!2$Y5eELV$qP5t%p`TI#JdC5gi&aIWr>5+|4&!0|D-^2)_ zx@-p+YFzRyyK1amvon7B_|&&m16j=udvALGoeShaW*JRJimGls)VctFu z5qN#Vc+N1>n6GV&S(Sabb;}DbSlMbJ)L_iTP)0hLWzyyi-KA9G%l-J;rZP#+b|9*J zm+Jwt(hHLOOuVyM)heBOBH7Q8en|4EgNbK^&)N{kMu-(Z1D%(&e^wERA9DwUq+y`fAX=Lt zLZRK-X$`4-k#!Woog~s)SS;kv(r)1Z0rpe-?;L^d);-J^Kk^!kqh*jPN-YntA3d?) z$<(D>&2kronCB?L8kquf)8(x>J40pR%Tw_Q+lUX_WM&;xl90pAWaDC3*2q5wEp z=xBF>^<(Uf1#6Ia0*V#^{zNyAS68^-`ko!@J>!@-vtD~M(MD2gyAiOq;KjC~;`o=B z!ljEO)epr;aQpi~=t4X6Fm}TZ^cBj`k+Nmjq2CBGu=1z3w7%E^E2*$hDhMUq${E#( z)*E8X(L7z3b?@mE9LRccULiHKqdh3QFJOf(;k?JZf8fk7&J~#&&u>4GEn0BmOhb%w zmr{WtzytsE34xM&{hzmLF@`^Xt~HqYsYtNxm916yrZv}cr3Vmxan=23ku@Uu?x&IrgZ2#}m=RWDPP>~xU!mXjjPib+Zu+EKo9_;JMMZXH zd3ONY<5R~$eTD6Sm#$jYl)+lAKiJ6iE6~TUs{ltK4jVo@NyfcYU_-UKHaLgJ0P8jU zsr<$ah}vQ_>oCbFkVx4`s3l^Ayu{9m<~3^2dc%kw!8b6QQ2OPPgApS_O<1z@BD#RD z#o!**Z`~M-q0oAiFrM?JYwMh=M%qyYk~3ZYC%zi*BB2SiM|0~GDnz5Zai&9?_hEiE z`|7tt=sbni7O*2a#fj4x3!mJn$HC0ENX{s^;6`hR>vp&>h{$|NbF}=nA^V)IRqja? z?5sp8d>K-=K50^IsX>@x;2J0~F&0aAV$RyJ$Fo`%cqrZwKJXJMe!^z1TnRo?{$3EMbDLypn82CdIL9S6aq0FI(EVm1Q(0cbzZ4gp! zg7jF&WWTY>XCU0np3fwb#f5wgO}c{B+Z=NnGi3FDGozG17L#R9~cqm8(JHPEu6mTA-+H&y%H7HuZiO)ivN|jdv-{LU z&;E}>!0OTLFK+XkN(X6x^M}`DyTHMX+1PW-01h5*n;-USj`FvInNtPOc$e6PsLzS+)zi znspd#z4q1b*ck{mz2JwKkLAopaU3G6*HT@AVVI~(c?!ej6y$!5+3Z5QC=-{R_0tA? zb5Z3?3cna9&DYdg&TFAP8X>138=Mk<@J@L1qCsPxIRjQ0moArz*;#sY59k!=#PMq- zgO(Rmm0YDj)hpQ&D#`kGNYgRIM*G{Z3%vhzR`casp3(rru20;yxqs za#*Q}r^Ypw%sES6FFoV^K9VZ;QEt0am7)qC;pm(;vlT6XzWESUU3&E}2WWM|>7r`| z>A6fbQLvlVI|2OSd}tiaW8+0o`zG%v?IvLbx4ge?#;|(Ixk8WOkIZPK*UgW_`X2+S z6B-}K?RNMJk1O_nk(plgdUm%N4y;&|zeUrc59gerqk$VkKZ=-BstA8D?*x$7dYp2T0*zDWr9GWrGe{7s-XkjGx=YdlMt1ju_wi6 zzBJXSzSsNXaF>y!7C?@CUU8IuJ~J0-Yt-{mVC1*Bi1UjT=4dl0HO#^l(|;a>y|*$y ztEX$LFIFnCcd!<-a3))X&R!PtTHZ4`M~XXAvwH*lo*J;6RgR zDD%?$^nVH428TjY=`5?~Yqy3Pe#2~~PQYor9CyYKg8i?S5Ct`Mr1)Q#jnyD!*5i+W zh(O*x85%g00TMjNWSZWXec$b4PiitwhzvW#tJbOhBmgs=#M~E4&cTRp|2(;tw$swFrOVbE(qyWz2qRewzH-|O{Rmvn6wMF03hv+6;j=H8=qbA3JEUZSeH~ry=>iJmx)7 zk-!%$rPCMw!Vl)27CR@&^}Ey1OgIOKM6#vUl8g#J$Zvisx6pXWE+= zKy_6bpO(r`dS#znKfpC3lc7n;rUGgzq?DiV-YBbWGfVS359_x8uFoitY0&gnz9E5S z+e!CkTb-?(e>&(SYU`7Vo^YF+1A@dQaA4e%(b%w907=m#$|F{%`t)HxQ0SuEima_k z`bro}+q_Q`JmV?AEkCH@HlHjjm8!m={8C?I!G9e|jhR;pqmh(GM%^6R8DT=D^40@8V`NLOW%k-?l1T8 z5#+sOc)fcgF)4wit82IvAKXUbimQw#*Q}C^)5{%|>l!V~e_Zaqn6k`IB8@n=x7mki zSC{%T($hLpg5Ht1xj}}lYWN+F^=gG0XH=qOU05v1ZZ?N?m%5E(f z9&0L4^s|e>y>~id)u{<;pY#xlEFzh%dcu`o%K#$~SO$??V(n#g#?}5rWkXjn&lxJ( zi2z|{(?2=LnPqZ+ti*IMa1o*Z`F7w^oGIv{jo`e}W8!{7Qj?dy)yJ9m^KP)y`G!TB4??%=^3mjBwG4E@K6b%$ z;3NT3Ro}>86JKJ5JQGgeOOxkMloKgV2aI-9=yK1?3fMY=cBZy|>vYSsWyK86Kbg(@ z^ffzEoj$|D{lt;!QNueRvW(@_IW^{YTJs+tuTy@K&(yjb!{qS9Bcq*Nl4yVS# zz*X&Y3}pdzh#qYIV3J&AuO1N8lHAMlS&nq<(Z^x4eU~{cmWy0y_fBBQQsn0H)T8^X zqSTt^ofhU~hqB>1D(szi@h1-#&WMWN{v2JK3+Sap>PK(*Sw^4`3$o*a!E@G+X4r>9){l81y5%+n}97lSD8VPeA6(XNNW+Taos8)_`dK~f|qLl%hifSyCd0_ zkfqipH9C(@Wtt)NwM35C`m=UH;9^S02wN5C_Z}<%S17h?ZuN}2H!D3ZKcTm*MCALr z;gv9*P&;ZSNC$4i_demAMvj3c`xHPXap}hSbewEpd_C}Y892vo2i+DjfxE28z<=UCLOAsRh05!{$DB@*j(3Sa%0GwgG&?vMTgnlqq= z+Enz6WMV+zfcb;NdrwTWx;pBH?NwA$wJ-IeuHDpiLd&iw{exB+(k(KED}K00>|9gw zw+Yq~yeQJ0^;~!yJB739p~a+N&bd64&p}-pR-o-gbZPH7-{4SihcH;j@-@YMf#Efz zg{!x}qxw*j_<%HUXjwJy8MXB}z6=;yFwCm@9I@hiVV8@gY zvACaI1b0?aPHtytENUrY?#{1d;%mLGjmCha`LLtwI+{$JH`7yB%0~gake=?ksZ-U3 zoy)>yga=Q+d|`Llep$c$k&3w7HYTcnW;ym9P^ivdXWo)s`I2{aCsVY9Mdf+<)1g*2 zNU(^tbPBPSQ-A1XY{OXz!Sbdx5b#{5! z=;f>O8MD~M!(kQOzG;5ub{#}SM3hw}qv@*onwVz{gOLNd9|Z2{5Z%n=gv?Ul?5X`l z(6*JMO!vrGHRzkNOGK&xbYUfCU;XK9Vui)4Q3*NDnE%UdzHn&$^T}Lt`oB2ee>kvR zq9g*Y;89W>s9{$Y`vpqy9tJ}4JgdNPL}m9h3e+KA$|7_QjVqc?Inpq6Vj6=?GsQmz zF+izEVr`M-2Arn%JrQjT^@U4P%Rmk+1h@G$K}M)p`TUb=eB3iDrA`9!TV+H4?|bZ17jEBHl

zsMP#iptt%h40wqo!3D7CP_gLLCW0Ufy_Ahr!;fa}Qly)C;R!5?L&d;1vUr+J;pj;4 z3VcPD*>rzO0wJr=M@sJ19R@h>vWh)BtnoVYyzaJF$?y2NLYUS%1h5##wbsuSSvhg6 z5_r_=3BVWpJkUqaAxzJ|UcCLyfj?r8 zhGk2vc$_ZUu;zX|t_xt{9&ZkLvPf&%`5kPb2}KfTj;`~{Ku4plECQoaa>SEHO>(W* z%A7EhJmwh`%;)4=+So0$SM!~&Bu(}l3>Ab+pi`bqH%V)Y#yg;fp6t%eZU&m(-TZjz z`Q7E}YbIKpxPS7|!fCqfm~FX1gfaZ{LC$iGtxR<{t(#|-Wp4FmQcKJ<`9L1+*K36C zfYDg=m6i*r*;KrmXo5{7Pu#0$kG<{$D02R0yYzh=cZFMcYO!%bk zNocWfXx5UGq)II#l+2Is!vPB>%D2eYFln{|2l*5sh9odhHuK zLkNR}s@&+pamW^6XM~Hin5uVcp27gExj7o(vn`x`4Pa2yD#uE;lOBc;Q##zB0;f?2 zm_1~?siUO;88v3}=6{`}NobwgS>sxQ;im7Eo~&>(iYaU0)9Ay-J04dw_QB(6a`HS&fuW9l_si^dO(Wn+xz#mzI!$(>{>9@*Lq)ssHwkXnBPkO% zy?K{ys8ZmbE9+-$qd*$xieD-&P~V7*pH=4*zZzWik;Rdz{E@$wOEEOkLtb78T4eTU zd2s>Rv~zP$>fEou&r~xcR%jp)$(UX?m5>vx8%n?dBw6WKD)QW_T7J#({DfWsv{R!* zpGy1sD&`jVk4GYtW^)R|(lT`TywmR+aPlDVpX7$(^ z93{f4SehyP_?DU0mWViag1GyM$=e;zZax*Yp2iU2ETNiKHxGw zDMEyD+mX@rqHBwOwIE#Y6N-JEmTCKJA0q07XVzW=4(^F5Z?i_am05LXmF<8*sKEtO zhc>C48k(CPdm9+%gK<2{XFBdGG}Yyg`jeTSjFpuYy)O7fWcb`xNi?Y|SLEdIJ+xf6 zJcU!mqH7?Qso=E-?sAuf&>HHd?)k9%Ux)O!=d2esX6$3Jx%l1#6kP7*KFkTp`K(3$ zXV*<@d3vwsPv2%jbqtc8LFb@oj4=s#SObOf6q~ zQO=A6_Qf&;9jmooPjE>su1m?7{epJLf|7$J-DIYmYuA&!ohxGmUyOPTGbgG{W?m5p z7%6p^hbTKAxFycb?iU1hH=)_u9u1Rb%WaEXy0mJkwurB`$m(iKq(daHvhU-|yzJ-E z;;_LttGXRiF<{YiW*uEBvnHYcT9DmbwLTj33Y>q5l*PXMIdW*cl~al)8MK8J_W;AT+ww$xK_d&sjDXmOHYwNGp&GER>YMo+1r+912i zRNxt-&}jEHYSnq!Fmb#aK?$%ZBF6hJw&v$22|+zZMxjh z#dl%1C+{g!;RrXqn$!Gy fn}Xo$|GaIB$oY>!RMxV$7v51-(o!sww+#CaGLkQP literal 0 HcmV?d00001 diff --git a/static/js/dialog_widget.js b/static/js/dialog_widget.js index 750e3fae6a..85d153138e 100644 --- a/static/js/dialog_widget.js +++ b/static/js/dialog_widget.js @@ -118,6 +118,10 @@ export function launch(conf) { } const $submit_button = $dialog.find(".dialog_submit_button"); + const $send_email_checkbox = $dialog.find(".send_email"); + const $email_field = $dialog.find(".email_field"); + + $email_field.hide(); // This is used to link the submit button with the form, if present, in the modal. // This makes it so that submitting this form by pressing Enter on an input element @@ -140,6 +144,14 @@ export function launch(conf) { conf.on_click(e); }); + $($send_email_checkbox).on("change", () => { + if ($($send_email_checkbox).is(":checked")) { + $email_field.show(); + } else { + $email_field.hide(); + } + }); + overlays.open_modal("dialog_widget_modal", { autoremove: true, on_show: () => { diff --git a/static/js/settings_users.js b/static/js/settings_users.js index 81bb930905..11c46470ea 100644 --- a/static/js/settings_users.js +++ b/static/js/settings_users.js @@ -21,6 +21,7 @@ import * as settings_bots from "./settings_bots"; import * as settings_config from "./settings_config"; import * as settings_data from "./settings_data"; import * as settings_panel_menu from "./settings_panel_menu"; +import * as settings_ui from "./settings_ui"; import * as timerender from "./timerender"; import * as ui from "./ui"; import * as user_pill from "./user_pill"; @@ -442,11 +443,16 @@ export function confirm_deactivation(user_id, handle_confirm, loading_spinner) { const bots_owned_by_user = bot_data.get_all_bots_owned_by_user(user_id); const user = people.get_by_user_id(user_id); + const realm_uri = page_params.realm_uri; + const realm_name = page_params.realm_name; const opts = { username: user.full_name, email: settings_data.email_for_user_settings(user), bots_owned_by_user, number_of_invites_by_user, + admin_email: people.my_current_email(), + realm_uri, + realm_name, }; const html_body = render_settings_deactivation_user_modal(opts); @@ -479,6 +485,17 @@ function handle_deactivation($tbody) { function handle_confirm() { const url = "/json/users/" + encodeURIComponent(user_id); dialog_widget.submit_api_request(channel.del, url); + + let data = {}; + if ($(".send_email").is(":checked")) { + data = { + deactivation_notification_comment: $(".email_field_textarea").val(), + }; + } + + const $status_field = $("#admin-user-list .alert-notification"); + + settings_ui.do_settings_change(channel.del, url, data, $status_field); } confirm_deactivation(user_id, handle_confirm, true); diff --git a/static/styles/modal.css b/static/styles/modal.css index 2db3145164..4d64b6af12 100644 --- a/static/styles/modal.css +++ b/static/styles/modal.css @@ -124,6 +124,25 @@ margin-bottom: 10px; } +.email_field { + margin-top: 10px; + + .email_field_textarea { + width: 97%; + resize: vertical; + } + + .border-top { + border-top: 1px solid hsla(300, 2%, 11%, 0.3); + padding-top: 10px; + } + + .email-body { + margin-left: 20px; + margin-top: 20px; + } +} + @keyframes mmfadeIn { from { opacity: 0; diff --git a/static/templates/confirm_dialog/confirm_deactivate_user.hbs b/static/templates/confirm_dialog/confirm_deactivate_user.hbs index c019fb35b6..7b0930892a 100644 --- a/static/templates/confirm_dialog/confirm_deactivate_user.hbs +++ b/static/templates/confirm_dialog/confirm_deactivate_user.hbs @@ -18,3 +18,30 @@ {{/if}}

+ + diff --git a/templates/zerver/api/changelog.md b/templates/zerver/api/changelog.md index 5e901ed895..399aa396d1 100644 --- a/templates/zerver/api/changelog.md +++ b/templates/zerver/api/changelog.md @@ -20,6 +20,12 @@ format used by the Zulip server that they are interacting with. ## Changes in Zulip 6.0 +**Feature level 135** + +* [`DELETE /user/{user_id}`](/api/deactivate-user): Added + `deactivation_notification_comment` field controlling whether the + user receives a notification email about their deactivation. + **Feature level 134** * [`GET /events`](/api/get-events): Added `user_topic` event type diff --git a/templates/zerver/emails/deactivate.source.html b/templates/zerver/emails/deactivate.source.html new file mode 100644 index 0000000000..87f29813ee --- /dev/null +++ b/templates/zerver/emails/deactivate.source.html @@ -0,0 +1,20 @@ +{% extends "zerver/emails/compiled/email_base_default.html" %} + +{% block illustration %} + +{% endblock %} + +{% block content %} +{% trans %} +Your Zulip account on {{ realm_uri }} has been deactivated, and you will no longer be able to log in. +{% endtrans %} + +

+ +{% if deactivation_notification_comment %} +{{ _("The administrators provided the following comment:") }} + +
{{ deactivation_notification_comment }}
+{% endif %} + +{% endblock %} diff --git a/templates/zerver/emails/deactivate.subject.txt b/templates/zerver/emails/deactivate.subject.txt new file mode 100644 index 0000000000..6f4e6dc440 --- /dev/null +++ b/templates/zerver/emails/deactivate.subject.txt @@ -0,0 +1 @@ +{% trans %}Notification of account deactivation on {{ realm_name }}{% endtrans %} diff --git a/templates/zerver/emails/deactivate.txt b/templates/zerver/emails/deactivate.txt new file mode 100644 index 0000000000..6c96153460 --- /dev/null +++ b/templates/zerver/emails/deactivate.txt @@ -0,0 +1,9 @@ +{% trans %} +Your Zulip account on {{ realm_uri }} has been deactivated, and you will no longer be able to log in. +{% endtrans %} + +{% if deactivation_notification_comment %} +{{ _("The administrators provided the following comment:") }} + +{{ deactivation_notification_comment }} +{% endif %} diff --git a/templates/zerver/emails/email.css b/templates/zerver/emails/email.css index 3cd0ed6e40..ef78febd8e 100644 --- a/templates/zerver/emails/email.css +++ b/templates/zerver/emails/email.css @@ -21,6 +21,11 @@ body { -webkit-text-size-adjust: 100%; } +pre { + font-family: sans-serif; + font-size: 14px; +} + table { border-collapse: separate; mso-table-lspace: 0; @@ -323,6 +328,13 @@ a.button:hover { color: #15c; } +.deactivated-user-text { + padding: 0 0 0 15px; + margin: 0 0 20px; + border-left: 5px solid #eee; + white-space: pre-line; +} + @media only screen and (max-width: 620px) { table[class="body"] h1 { font-size: 28px !important; diff --git a/templates/zerver/help/deactivate-or-reactivate-a-user.md b/templates/zerver/help/deactivate-or-reactivate-a-user.md index a2591fdd78..7533028fad 100644 --- a/templates/zerver/help/deactivate-or-reactivate-a-user.md +++ b/templates/zerver/help/deactivate-or-reactivate-a-user.md @@ -43,6 +43,29 @@ user's bots will also be deactivated. Lastly, the user will be unable to create a new Zulip account in your organization using their deactivated email address. +### Notify users of their deactivation + +Zulip can optionally send the user an email notification that their account was deactivated. + +{start_tabs} + +{settings_tab|user-list-admin} + + 2. Click the **Deactivate** button to the right of the user account that you +want to deactivate. + + 3. Check the checkbox labeled **"Notify this user by email?"**. + + 4. Optional: Enter a custom message for the user in the provided textbox. + + 3. Approve by clicking **Confirm**. + +{end_tabs} + +Here is a sample notification email: + +view-of-admin + ## Reactivate a user Organization administrators can reactivate a deactivated user. The reactivated diff --git a/tools/linter_lib/custom_check.py b/tools/linter_lib/custom_check.py index 110845d0db..a66f95ddaa 100644 --- a/tools/linter_lib/custom_check.py +++ b/tools/linter_lib/custom_check.py @@ -230,7 +230,7 @@ python_rules = RuleList( rules=[ { "pattern": "subject|SUBJECT", - "exclude_pattern": "subject to the|email|outbox", + "exclude_pattern": "subject to the|email|outbox|account deactivation", "description": "avoid subject as a var", "good_lines": ["topic_name"], "bad_lines": ['subject="foo"', " MAX_SUBJECT_LEN"], diff --git a/version.py b/version.py index e9e42ba49c..21965485ce 100644 --- a/version.py +++ b/version.py @@ -33,7 +33,7 @@ DESKTOP_WARNING_VERSION = "5.4.3" # Changes should be accompanied by documentation explaining what the # new level means in templates/zerver/api/changelog.md, as well as # "**Changes**" entries in the endpoint's documentation in `zulip.yaml`. -API_FEATURE_LEVEL = 134 +API_FEATURE_LEVEL = 135 # Bump the minor PROVISION_VERSION to indicate that folks should provision # only when going from an old version of the code to a newer version. Bump diff --git a/zerver/openapi/zulip.yaml b/zerver/openapi/zulip.yaml index 1cf2651fea..fdf69ad226 100644 --- a/zerver/openapi/zulip.yaml +++ b/zerver/openapi/zulip.yaml @@ -8801,6 +8801,21 @@ paths: given their user ID. parameters: - $ref: "#/components/parameters/UserId" + - name: deactivation_notification_comment + in: query + description: | + If not `null`, requests that the deactivated user receive + a notification email about their account deactivation. + + If not `""`, encodes custom text written by the administrator + to be included in the notification email. + + **Changes**: New in Zulip 5.0 (feature level 135). + schema: + type: string + example: | + Farewell! + required: false responses: "200": $ref: "#/components/responses/SimpleSuccess" diff --git a/zerver/tests/test_users.py b/zerver/tests/test_users.py index 0943271718..8849321d31 100644 --- a/zerver/tests/test_users.py +++ b/zerver/tests/test_users.py @@ -1399,6 +1399,42 @@ class ActivateTest(ZulipTestCase): user = self.example_user("hamlet") self.assertTrue(user.is_active) + def test_email_sent(self) -> None: + self.login("iago") + user = self.example_user("hamlet") + + # Verify no email sent by default. + result = self.client_delete(f"/json/users/{user.id}", dict()) + self.assert_json_success(result) + from django.core.mail import outbox + + self.assert_length(outbox, 0) + user.refresh_from_db() + self.assertFalse(user.is_active) + + # Reactivate user + do_reactivate_user(user, acting_user=None) + user.refresh_from_db() + self.assertTrue(user.is_active) + + # Verify no email sent by default. + result = self.client_delete( + f"/json/users/{user.id}", + dict( + deactivation_notification_comment="Dear Hamlet,\nyou just got deactivated.", + ), + ) + self.assert_json_success(result) + user.refresh_from_db() + self.assertFalse(user.is_active) + + self.assert_length(outbox, 1) + msg = outbox[0] + self.assertEqual(msg.subject, "Notification of account deactivation on Zulip Dev") + self.assert_length(msg.reply_to, 1) + self.assertEqual(msg.reply_to[0], "noreply@testserver") + self.assertIn("Dear Hamlet,", msg.body) + def test_api_with_nonexistent_user(self) -> None: self.login("iago") diff --git a/zerver/views/users.py b/zerver/views/users.py index 4e7beb396c..4d07b49173 100644 --- a/zerver/views/users.py +++ b/zerver/views/users.py @@ -48,6 +48,7 @@ from zerver.lib.integrations import EMBEDDED_BOTS from zerver.lib.rate_limiter import rate_limit_spectator_attachment_access_by_file from zerver.lib.request import REQ, has_request_variables from zerver.lib.response import json_success +from zerver.lib.send_email import FromAddress, send_email from zerver.lib.streams import access_stream_by_id, access_stream_by_name, subscribed_to_stream from zerver.lib.types import ProfileDataElementUpdateDict, ProfileDataElementValue, Validator from zerver.lib.upload import upload_avatar_image @@ -71,6 +72,7 @@ from zerver.lib.users import ( from zerver.lib.utils import generate_api_key from zerver.lib.validator import ( check_bool, + check_capped_string, check_dict, check_dict_only, check_int, @@ -104,15 +106,28 @@ def check_last_owner(user_profile: UserProfile) -> bool: return user_profile.is_realm_owner and not user_profile.is_bot and len(owners) == 1 +@has_request_variables def deactivate_user_backend( - request: HttpRequest, user_profile: UserProfile, user_id: int + request: HttpRequest, + user_profile: UserProfile, + user_id: int, + deactivation_notification_comment: Optional[str] = REQ( + str_validator=check_capped_string(max_length=2000), default=None + ), ) -> HttpResponse: target = access_user_by_id(user_profile, user_id, for_admin=True) if target.is_realm_owner and not user_profile.is_realm_owner: raise OrganizationOwnerRequired() if check_last_owner(target): raise JsonableError(_("Cannot deactivate the only organization owner")) - return _deactivate_user_profile_backend(request, user_profile, target) + if deactivation_notification_comment is not None: + deactivation_notification_comment = deactivation_notification_comment.strip() + return _deactivate_user_profile_backend( + request, + user_profile, + target, + deactivation_notification_comment=deactivation_notification_comment, + ) def deactivate_user_own_backend(request: HttpRequest, user_profile: UserProfile) -> HttpResponse: @@ -129,13 +144,33 @@ def deactivate_bot_backend( request: HttpRequest, user_profile: UserProfile, bot_id: int ) -> HttpResponse: target = access_bot_by_id(user_profile, bot_id) - return _deactivate_user_profile_backend(request, user_profile, target) + return _deactivate_user_profile_backend( + request, user_profile, target, deactivation_notification_comment=None + ) def _deactivate_user_profile_backend( - request: HttpRequest, user_profile: UserProfile, target: UserProfile + request: HttpRequest, + user_profile: UserProfile, + target: UserProfile, + *, + deactivation_notification_comment: Optional[str], ) -> HttpResponse: do_deactivate_user(target, acting_user=user_profile) + + # It's important that we check for None explicitly here, since "" + # encodes sending an email without a custom administrator comment. + if deactivation_notification_comment is not None: + send_email( + "zerver/emails/deactivate", + to_user_ids=[target.id], + from_address=FromAddress.NOREPLY, + context={ + "deactivation_notification_comment": deactivation_notification_comment, + "realm_uri": target.realm.uri, + "realm_name": target.realm.name, + }, + ) return json_success(request)