LxmMTGgng"OpPXVTCHR fE : w@\-r a]q,\yZm7>RE=jR:iK`ݒ(:ӨT@ 6 @]Jk]D G6<3BU 4㫑>#C+NEUyx{C%Ų?()UƫlP]R+Y.j-f<@?}ނ8Lh{z# 5ʟr lqhHI>_yE E1WrRxoJUJ5?l}egGphU=JA("UB1*hW13"iO!B+.a!XPPm:]QyݾD7+g4jSF%\fߥƆ%3ZvZUᇛ byC$g4f]E nW8#e` D!F۱?b}]k8OKbsH)rxsXD/[>B3y )4ɍ\RsO)Fai:eD n{_huS^ǹ/Y$Lza&lg z[~%3R9Eae=$/g #B.ſ4J&<3V@hFar5O8cȇ~*2,sVЩ* =9-M?ϰY )WDŽn(ofނ|5׹[@L1/Տٯa+AkPiK%CA?svB@>[}Dmoa-]ɮO/:ʙDANa 3L_w|&(?HM>a\*DeaXk*C"y9!ޜ;PmmK]K &ta6H﫭!K(MX]fnWiTeuC݋k"w럶V HLК`2- &2ʖLfn.<Ń3_7QEt;? ڌimZ:/ΒNhgSC&RF?6rFFE%JsdhxqRv`5z7aږ_zWW_6?:JLimJhu9 s22J?Y]׺D }PHp-j' x2|XA%(q}fˍDEP J=ic)0#"8Y gë޺K}mQGmduIzLXƛ~Z-D.KxLP7Vt <5YGIw(_?&BwP;yc\ SŪBΞ à9.6׉lYO&%GZ:]oɀRP_cּi,c;]:(&6kG~982dPpSk Urۢ|L_O 0{L\oI57u@̨FGs(-t'w|{?4H@+*5 zX݄n؃YkxYYŞx4Md90x+B$qF\y9՛{Y942n~"+ ŧ_o`Eb/ ~-vxuUD7 DCDbÑI!8w}C5re8SpxKV*Qc`,1҄Ji>p8;ݹ _IP%XNuecݧLM,8HD+Z[ajs=t wf? DZzC`Q)/ll[ۥl^<4>)ξ H45vXxZrGK룾#oˠk~9e);-qչmQ]$lix[j]pVsڨE@j5arZH_הެ5ͫҙeǡR~:X*nּJ}7u<ūKl? BNKaJ@PO=iaNy6lr;T يC57Ӿ.+q;"g;n#/ L}/?$'j$_{Ls杕45U3C!뺙@ ҏAl9/q8:LS>((%@Az6* ?`[^ gbe04xeMV֛̋fE*)!R)~yQ0R2::O1&։::&R[5xDU xˆMUX)a(XVٺm_6^Kq&7udqr}ӗC~ ӫ70'lv˧ Bh 0} _.#B(qU|eԔ (jN|}oqO{w4C&W1+Ox8: Vz\\l/ ?8"ylT`(s>T/W\O&sW"NiS}{"| U*AǨL7nЋلwjHAKrsY\ؒMӜfR=`^%@ݾpaG<&ޏbƶ?]wgTu\١x*0Zf%՝/uHhspWk2,m>J~r(#yAIww?Zit,]QU1j@"DՅ>B RQ+xz>l€^yIi=u EFr{r BO6MѲG=f/PPi(.uXVEwSØiW+|,r cLe_$'Aٴ.'o M5Ze-T{ɻ6.y*kP\ӑ%F>U>H;uMΆg97#Ucx_Y4~ ԁX*AGd ,_e%tʹ[r 'q@"9[EsǓ%v|3BoЫ}pdXNlgTMN\r&bն3Y*ZUѕ.}s}@D:}"ӓKqELb iߨV?i4/y?tBIyh , ]GhHGsՇN#򆀂ƃ6h3'ZgR (cI"4 Q9e7 sRg zT D5Qh퐅A\;cv@3onB .@)oJ]OD2/дCN }xzp%$Ia/hZJ_i7Huh n w;vF& [kU- PQHQ5#]#lv dW:VY2&W^:Fcm4x cұ.,ʜ7l4blEq2hnA6焳"WU:Jd%SOcs }#g=s@9܊7|$ȩ8x[sڋ 9vEܢ9nYԨKT0?}(&a<!lƊBยC8w}ܣX1D-Pb"Q׊61Fy__^*LdW\'BF/IdFZ_;UW8M_HfKFjX+6M /)vq>zӉOXӕe S?OY3DA ,zXkN`!/}V# `Ov]6rz@X2+86w^5%X s)t!wՁq>U]*!13)<7癿`) $B$&DkʇEw74iIi).*kФS7qALyM%T/OS:\ʝ9e-].wE${8 I(hT,c6#6V&p}d>AL@&1ہ %Ά]uc`+#C:]T] ^}\b)MA#B˗ "]LQNeOЪTQ,޵Ń ΂Rye\8Maq=~~}̽^m Ý_n.Z5{C2:Ax?}ki̟rS44D%YBF{!O`$5/30DT˅-E>ԡ,"!䪮Nb›$[_rţkt|Eڽ* VQaؗy}xv!;Aŭ5ZtVo5Q7_k /.1aTj'O6r*5畆?D@yn2׫1:g~.^f^=LUCY.LMdUUUJ=ǟm(I[^ef*藚58W)>XxKx4pVw 1=8[l X,QQ WkGO 3SWj$zi̓DlGb4װBs(!+@w|}2<ۏiM&tFxǏnO~zFЏG(qkx%JAYZc T~>&10u CȆYlL2|}›"4(+U`)*nx"8++!i,$ ߛR  JHhӽJlH!967;.G"8ou~۾~C._&_ϱWNkzN$K5蝧EYRq2z{=m%Ap!Veu5UhַoV%=0S#kTQZO#ODC^v#l#؟*"Q&KOpU#^M0Z~sR&nE\O# h!aKZq RWnLEe} ,"jyOaԛnS[z8puhk>4a睘oLBg@$7҈Kw=|!mxuwC$5M{5&-{+m:K^8NaiCz4$~㒔*ȉ C(]6DsZj.TI{ ;z#ˇ@2uP:՚Ր[\{&U{oD,5)1SRan!cz9%?m/h OMTLsrsHy(%`,#%B v,c炶`{NF;^̜i#dbth# TF HdA4{k6܁OЄR QvZbs*0^_Jqۄ КwF.#XoERvnKT>Je0I_{e浭T0=">>X\:qv(qۙ@J4;ƱI+BD9?,ȩTy?wIV!)K?Yk M "+ 7vwUNGp@Am#B98cy49{(KuZLfc7!fR&>;p]+ꉶ_f%rIkb Fq't>y>X$>Iy{d,4geGu`6MA XSJBy?Cee_/Vd^t7lKCTBIb]͡hce^XG枻bj &W3#?9C>v++ KcUV X@ ,*-e[ /|1fQb 1[;S+H|^Gvw$W`j-хNg^ګ54VaOYa5 eY$Ĉrps$|;UNtkP҄kUY^|dInr>6hF歉sP@9|I4SQyӞрf_1wK=,5ZZvP`# Ay([wOk8k6ፂ([UlI0D{m`h鿸8_ǡNAlP=.Ҽj5[j F9%r!\`uE8P^+9䶑b{wAMɴi[\~v[r_ G @q.K S*n']dt+^H^S Be3AQnw_@.Bl[Rc4;øPܦ⑵%'$p]4M*07h޸hSZ@FoyƆY]ocu'޲`ɓjbݱ#; lb[zg5}PI=dHp\K-6tksҍ^4 db`}ϝdf \CGר28yz4E@R+_{b< nr@gXzZT3n$0`. Jndzs/YjV>EX§ouHmUr109Qm8^ՠ f${Wykn2H%1\/W Q@xYN_GEy$Y v mޛz$"!˔%P&s!dl10P1߸ʓtTj'coJlY7C;4TPSz.@Ab s-Njfi/wizC"Ԩس~^'F{,ZV>0x>"{b_>AwLݝ/S< A+dJ6vjDvV#EqQYa} )/QF^zєX#(( Q8`ܙGVXww .|u鐀% 1z/ߍ'u[rIA Ix[~} g51;yq@ZCՎ9@3xn' y'*S+GV{w4'7SQϮ>M2H]-Um\*c$'o88DmQ}Co hA4ZT/tA(,KS|^v䢕Fgu N>0_0 y||j7{f3EIq?D&۸e_9I(@wn!ze|<㚾af4U*4T}K%'͜sOW(_,>*T\ᶂL$Ȯu&bx+I<=DZTp]ւaOӔiIpȄckqj+ƚfpS8Kә!1%8ِޚ{nd?F3^.Iw~|*?['y{1w1,Hh%gi:}*C,ςO:/;nم󓶜ܖLkxjTPދ}}vȏ z[!EWlhUsNj;0]~[y`U Ԡs(xs⠝d=o`wqJieKKCL+ӵ\;AE X ^1?2Q5^q&2/QAH:GmT T;cm³Q<5$ 蕧!-xVjI#,  :lF<nc\:>vUm+oLaZo3SˣAύ0{ǽ?̮K}6@8Mbդ1ss]+HMJ>x 7`t蕪&lr7\ڍQx> ̭pԙH:r+#P>N}` b,vW'=}wՕ :Y f/13Ҕ]"IkyV)q2^_XM0gIK>oE䍰@ X5[U؆&REAMl:.JCT&al$&p|bGfL䑞iGoLf1*" 6l'Ĝ]I` fבXH;_'qQ_q(Rަh٨WE)u@4`xZdnR@ WN`a~43" d` eHLqGM>7 EoIc9B2:d&Q:&6,k,}& ,/jOp-gLgh%XIwCDByOhZ|&m-ih?2PsK ѽ3':lT:ȈWn9ހ{(̥2U`T&Ev٫> .&9 dܒϫ I~>h-^AVOei8q V$*7]x=|ɢb.աҬ$Odi32$ސSyiB7~QdqרP).z;RZ'v !X4%ᴗ8p=kZ F@Dp1dF-L5ð&T}]jf$`R,'X'UnC cʅsg$[vLt~d9 =NSٌGh8F+B[''~}_l;48Q`4dM=PJǵ{?&sn':( 1jJoH\]&8̗ Q(Y o&;06r <- ,r:f3P>|Vc84*wZ41?8GUeAh~I%-!JLRrh֪ÉNf @B?# OQczlnq W?t@cg 2%,xᦣO+x2UaziS1 1m۟NN14]_vg.~-܎F=fL.SPug#"to?E#4 `Ql۬|K yO}{i`r W{ZX!H"_60MmkEe#Oa bieyV]_B9+K;2Uӽ)cg^bv?>{jgaGgN)w$B$۠3XG:#I6~AȐ2}J- KuPGnN&TYuSqzrD<$W>cԷ pfgBVd71tYR geh ]qP!8A!^XYts3 B0tiД ,$D^D5EB%Mg:SX%PLtoLҮh_ZV bY!d|qh!Dl;҄m.!%C17]$aU#5A{kOgT#'n~'&cXY\O4 U2lE܏>vk/qJŽ $-bG.Hgk4SPSPzBwKMj=cn@H4җn ر(A• KpB4?``s ȳE-_wE,88#w>S7D6Io/4J;&(mWcf|ae/z6 /Hơk-'tK>T ec$4b`̀"8YkSzoOgX/"!ҌJVli#eHRAtC ΍ɱ؞[DPhN=*d1+՚CD_#[<{KM_1{q}7X]lB\oDںō^ld5mCtcv5lYml9.쀆j2@թ5%39Q*:g8!|%&Ȓ_}hOtv1Vl3#ҙЧdxauo@Q7ǾNs.k*8e<;`:%G $en?޴:IfЂ#w+^]Oo6LJ-et[-rix6̓ ?Dz ^hI$.3Ghɯ"e;0 wv¤5VyRJ3 rɃEɬE :7#k.A^Wѯԡ~EE=M2ӇjfnH>vqzv m  ڳ,i(.LV )[1!d8.B* uK\DCnbŀ/)ǘ*z E6y)񇱸?\V^:>'_3SN1&ھmDr|ӫu)M9z9ʶЏvIXBKx/ZY_g =8j L-ZƱ(l#SZmIW+6b ۇYTKd;!烐S4jߩC3c` lxzE.HH[b i 筒J~K~oN& t]FG̓@#M}Gx OQVw؞G0o1L'Dz]<]IoDJ7S*$ӳWrfD =1/=Huolù*>NX=[@{l+y SH.Weh־HU[Xd)\ru#Fs=Q#1@(e-]iycCȱ}niUat஛K!R8;>&UNoGG>;BeU$TN#36*g?;.t[!O;,S#h8L8}2282=׏@2喱)[D λOkZC+yjBԅ)'Ol}d(-ƽG(dG#Co}U\L] l $8Q}D@0І=/;|%ۡG72VqWS"O 5F_@ZEQlWq¥xOZK]r#l8G㫉 |W[eiKd8$iNuOr-YJ!~"ط7bt 8%04Vy-}sRgN4uC\4tX墑 z{K<9V%'IWwS>x&K'p?Tmft"(a&窘ri!ߕϭtާTBHI\WW׬;mV1ojV3@q6BS |3KrX.nr;KZ##,^h)Ilpo _ |[kiWc**oX r;9d+Q`D0JA`jw7Bʋ"*!²n>qrWA1^ARKIՠ>EkwxbR4VB;DQ4O2?HRnze^UxyokpX`jК4K /& +Y[>hscf d(O-"(h 4MR;*099?n3j'%>59:M""/7jƣ+"rFԂ~p!!^hJGVkQBLUt _B(ZOuJի"7ɂ f7;=(ĖzX^Wdϸ299mMnsrn&2ětʔ>Umⶳ-͉]^h(I¶taW$ҠH\k.$P?{kh"߷Ğ~p_Fr+O@luшi̟XZ׏1RĊ{ 5GfotpF}%X oM|k7)Dȟ]kwx2+l&NR_;4ф R@,"veovUBh/QΛRl8*l5+B'Wm0&ZI0qb`\k 1^`L$3ӱE7\76> *5Ep x3RV4e"M  gi3Z-^m^u#?t)\x>"6[g іf݈4}X-<xX?n]]C đ,%w!IQs61U+b@=V{H=*ͱJ1Tu7 -ʇ^TMq)Xxּ&*qz'zɶdN^hh3?Ѭ3ķ+#G0хlhhƞ[ D&SI@tn1kHtUU !R6 J4%9`Iz6PB4=DM̹<0[.Tw}LhyCuYGzMrKs i,;Q*- Q@+/"uJ*,OAỏ=^6F.ddoM=9-x2'&HdQ i-}pGjORdGR(}z颡-=hD4iG#.m+X"vg4徆SB\+_Ap.,d q,7jٳHndZܒog`Jܘl0WAUZ;c5q+E205Dcbx`@= <^ 1ZLN禞 j+Xe:IO:qgxLzu즨t{Y6t+EqB`jYp_QqJnN"s/"wjGu1Fq)It `ɘO=Q함}* %ǧ>ʿ'*t4 WxB2԰̄4㾃t찎z}(1Ԡl6X71g†G~tO 5j :e0Ue4E1dETnvpXf c]QfGv׊\A{-a t#T$[.ޫ&aI6a-72Ia:Q?5b{9Jxd+v ZۇG]vA762T1{s>#uFN[2 ܞ Xt!ogUZ~akX?K4Tќ5t4F0-S'"(ofk@zZ)Ŋ)8$Ey!pz7P_w$HNG]n$2,wtj FOALSnm-7?Xy bj]sK "p*_t߅ = d:ŻJ@ Ch"T!m(NƌtRD^"pAg%wt0/IPY+1'Lsσ\CK'u۳4_P0:8;g H`aQoZ[[{d|#@  Xh-s31ބ?@ ;^R;2dUkH$H(s^ǯŠ&`(xl P~ ggG4*ld+[ٹ -yGm55 >y|L]ZC@A#<G`C7j>Oh-aߓٮ*ui]21-ڔB\qt~J榦S\WZv]?>U;oCհ`!c۩0\}$֌ok6]j% xuo"Ӻbc!̒GmS:TjɝWұ<tA9Oۯ=i_o VY/RLhW-3'ɠs،3ߔ~&}eUg)c%*o%il/0e.銱(,i a=f5SH`U:6ZK9}"֎fs e5IF,zZ!>!0fO|b` @[{]J'2(S YZ28+;-T=_uio^E#WwezZBK5\[%r'-^nofr]VHh}NΚJs 2k`!k׏AM7fs ˹ueKJKYQ95L?#er׺t j`g>NJ@Pfs̽g@$!EϮ N'&iR$n+N?#B J䔀;B2dC-0x>Tꏦ?SBᗥ6cpx.s_6Piyگ\ @~U(SR8Q{YvlHЂ?ñ;[Nzlv ,(t>b4rf\tL͇q-󜇛eTAՁn,xo #MLsVxYc:ړhL$uczb}ʊӐ }ZR;3 h&{mR]1~x#n-x a}t <5"EtA\Ȉ<F2l:bUC8$ dTN!w1W-k +b&45-~#,qj6ٟSH:lSƅ ?ȯ YLneY3%\nb2!5 yX>}~ɛ̉—a}qˏ Fg}'33x!]z_[e=@S֙_SҫCq(9J5o@j>.}w#׈Zա֎c mkMw94M&[|ڼrI{ x!߻ VKXS:y>F't{a weSX'῾s$6bz-٭+0…}7Y_c&?2T"Xv\6 + `0'b  $7I^ cل:GreϏ|lΥ<645b<2-gF8OɥaLx(4LJ "3TPy"Ϋ]ai|GtG Bav"TշpwO 1+$4{qMᆼV"zD %S'R@ 6@J_S#~^u¼l"ҪW٥j:$BG QR.1j= `g]?),#<JwNyKS e~P+TRiv}-Е_g8c>={6+%RW|uTm cP:VxK>qF[12^j↴G囬$C/0|}F){!+q ˆ'a!):J MZdh]ڀo <3꾂 붊s  E4zkR  E:oߍAK -r{KG0ÀvsD4R$c']~3& _];D&.܋͎qJDse'-E1AT9tѲWzq,?Bac=Չ+U9^$-׾㹣DI]ԝId,tD\|aɌ£ȗO] SY| V[gOvyYs>q;¹TȦMF_Bu2|ңXV.tեO5~IKgAp*Z "R5rr3!ew1j]J鋡6tT6q*nC.=0/"U.M vfD"ݮj?^2f E .\Z~ؤ[~V1CwRt{)֗E~Ie/L0ʂUX_9`?rH4 s:M74}K35 w:nR-dmW: }zJe%[+*^20)bG.ҷs]{>@'`IXIG3Bl<=#{C+J""&։2oŃ'.*FtB11Q>ܛ*,$X5xeR!S '|XW9r~id;%Kvv yRfW>FlXL%`q=[ս6m^2wum1B[8?tbpz7<0@RcQ [,GjJAOt%lj^5=c'2c 5بl!I 6eIxOY~2nސmLŖ< ~A 9ͩۍ o}ѼPW4kw:Uyso 7rPiIumMyO 6Կl̜=me;kFӚZf*֍ѐ% #r0o9៶>џ}e gHLUB6${6k͘ l֧\  x_d?8Z* 0tU[Vגe|RVv4/)pBqэBMyh0*Bz!aZ<%*雦`8ts 9:ȍ) 0^Q8]apT̽j=)DD,ߎxY~l'i5UdRFJP (b"{J|!rƸ"` U/{[kDu\p=bD@*} G &4ƭgb`ElCMsYYhDb93/@:vOHTy'e@ӊ `X՗"^୥N.ߔ/Mh+ Jw]. pCL2,}E:Dg` DţpXls{vx~ƣP/ SJFә VMc |i`M_zڦH 9t_l/#@_*q$:$l1^@F Nw{kD{3yw=nrg`/[;$(|Y0BO.HB݄Kv:X@RҢDAej)u?I'U8ɧuΟb @\)=Oq¯(?֞w.}&]) 2g(ɚ $ o@$^TEQ)ŰsU]&1n}Y6 e%jۉ?Gdo˶y}m Yes뇇N<:W6zQCq;w1Qe[2Xc H?$xJL@´ }(@?qW, [6ɵ$;ؚ`h70cYJH:}0p4%.!HOٟyyeDZ:oE}RG NO81dGk380 γ[b 4 ݁ ?~3;FSD$cNvHhq/5B1W+K$m3 [%D;k]qm>?^`2A)3H<|Ꞔ85-eAM~GOc+'#5j)R:ЂM[Z&qAl,(+ӭ{ 3Oߴ:X L}Z.ޱc#Ũg~eȏ FZpJei,7*R Q(G ߳!T#LB4,/w5yo+8Y[sFL#ёwP&- P=ޚ4v] ,>TwIND~ȭ4%Bt&[|UN {SG!;fl%mz'. ./<,$ R2r0Ebk .Z} n }yy)>V-) R%\V)xx*KAз:{% E }(0?XW9|ק oշT 1ZiMĄ*!HG s D̦>[mI sejy^sA&Ykxcmn®ӉJ!|H;l m]%|>5x1<PY>A~G<1 K,e.@SN H+ҕ{XPCpۦ&0#sn|L h J7kδŒK)bkэk%/3-mRW7|;F7ԍo.uqr()A#$ϲ`v9Ww5;Bq. S¤r\ ,]}5z"1 ayϾlk&"U;?œ&RB^TCGφt],ZMWp+=8G/mdڔJsXڇ?}޴l Ze@3C`"a; FUBmHSP.Aރ쪄sHTCMzV\Za0*´_WzCon sk#'mRyJ,Ǵ PܰPi5M(GDUj֙~3opVox֣=qTeG36!?eCAw̐ 9/EJg"k#e c')Ѡ.Y9M$?j=8/dO+҃-.Qv{/QfҬqKjdrG]YߨONf =Qv[,m̛8bF O?OS"aO>#kjL,I+1P Sڡ=<ϱKZA$\6^ [@5e]lmĩof@'VyI|.Oq $GO$6Y w/pqZlwd K.F]4b5<@cR*PgQOX I+knO"K̻*5""+剅;@rg9H*g""eMV/[bѧt4xo3/ c %)`6%vAF%G0U_$JRWEz'b<)7?bDyhf٫GemcdGot@Ѭ6 VU$Q%Oy+5dݱPy&}["e PJuhf"? @9MyƌB{xGkն\7wp `_%L^~鑱0ye+o))Ld'0?6y $nF*$Hk8yߔd@bhQjdJX.L*6zhRC+s0e=}Wapػf6X o1tyQn*Fiol!CLlAQ%EL2h.l;QJ80QM,4! *:Z χi6r>Ro mdPl?m`ϑ87L hxlcȇ Z%XS ~>WY%qCWzF 0hEȟd+$ܘG=ߏnx祝 _c/Hۚ#Η~sP:HWt_n!|!,4 h(mv7]p3: ;WJl]+*[~r{ә>^6Cw=[NKOEN?/1\FݙmLdhC-t vOt&BV[;R)L#&963c,7IPSA+y=7,J)*Q蝎ePY\"֭t6?ٻa Uɴl6[EwHUW[tX}CuYM wmKۥBmO{3UYR4ҿ~GD&+H4_K`}=Ʀ aer.qkg~[:qɥ -]:KpP*^l px SAޠ5@@#ofNM:M )bU/hl&_lRCXf8>"X0DSYwInK  !#J؄s{BH)TE0@E۝sFg"UQc#+KgJz L50z*Gp(i-)|fX U2y3I)fJk&1&>8rD [52+bT!zR*}sGq0z̯dz[TcK}G mQ?G6H/]" ([|;d_ zOՖn#mN88}T-<+ҵ]/-/.ʣf}F~)/3[M뙢d(Ϛm*o>u sK(J[r^ 5uX$V٠=ҽFo[/'r۲)J;?FdiJ&*mYէͱx,B$loΎ,7TSp@~U Bx#6MNtx(TPR ]q4)M*=S7%DpX._oTXW.H'ZCx?>|\"St5r}; 9MmƔ2c)K:JIwSF*.+DP}H#;M lA)VK9ީZəyya/./E:mu 7җD ʹ}3gr_LpuD듬WW,lKW{&Hm+rV~q[0hȞL(xQtb( 08gf!-Ru=_ Y"ԟXeNhrndV#޻-U΍#;"md(8! F Mp, fQ''EG p :%н­:~GFˁw;tw =wdݬ3VLyY`q!f^m?4E(m+ d"%3"5B!'C K3j$oIO hZMa}P=ߣG7URfB-yp;W4z}{iAdklN Id ]2]8LtCN/p~D_*'iO2NjXAE7l90vSܲ %vԗآjU3Mi G*4+Az^^lԝ #]Q fZXbTN9~.a/Q8]z6 rfZypSҞUGv/u3R&6=d:"[7ZrڂWZDC>q7mrpZ1a9p}B\dn+|*w:Ɲ6O'ڊF3 Q"?!B)ZZ-Z4 2`rH+skyZ1rg &LB̩+IQ^IyvDsopO-,<2of'>C]HĔdoTrCzMrJOp, ] (,?؝Ab,fS[ӳz1hUBKIB7E I j*C#PJm1ZRmfܣ,9;]=U@O *@n5߹n*S vѾg^89!A!G=i^@l^&dtrjL6D/(!n YyDX՚1x?[m IJ}'cYI w'6Rq?"3lր.+Nv1[E(!e1Rq3 ȝ(l^${SѺ2߳pu,?ZÏtQ؝F/cuX4F,ԟ@b/'3y}ĢvMlS9 1>ǃ4Z'ʗ59SXٔ`%[psFR T6SPj1؋4=XN Z0!IԽ8Q|ަM[GY@!|wqsU! Yc󉱭$69iݧ;cYJ1WL!*Ml(H/Rw6 ?5h / Ҝ^w*:Nr?lP KaͻET$&[K?&-]@+5Z/0ywoajof|dh1s'nB"B,1^Nm8U;Hor UE9~(N> V6] GtH #Vf lֽb3dgi.!3Γ%>In[ -3C73Նg ,>%2Hdr+ 0A`Y O_ao2TCUcʢhDоYN̈́=?Fz g>niuN2H`S8ɶмUPG-b[fv1JlˮZ,O'YbUI\"aKfʳ?[Y*+/'e5~/%%4IYLSiciVaxζ>I ~HʒkI/ٛ4q홺nd,+U\) ѴԶfM"nAOD'6HM4`woQR''# .!yĩ`B&$8N>&v Y悤y'|mc"5Hz)>׬sgndzA_r>aye˵^"Q#Z?Ce Tks!@Nc%%+mB V.yQ{ߠfVkw $ќ&,5v2>cG;7y̮-31GarT6wmlrHڜU@O4 ؞TJiPh#Reۼz0+U}?⋡#-os}x3#^68WSc|Gymy QmiFyoW5o3Z$x?Ufq.֍smTJLhTҢ#G-iC$PhKlq Wm=@>O#HvK}Rx\4=<@)$(,6&k8UFlPNhV  s8f.,7E 6hJR"UT r(葰z1;>- aڬB=tíV:i:IOPqD$<'՞x=0I!٭Z~e\ ^ U>93Ӌ]ݧ ͋PW'p$DDv1| }h%gt~6.} ғ,q6&)آpvI {qhW'`NKo6#\z<ׁͷ$ęDѬ ! 7,?haڟt\hŪ53d9\fu| 5PǢTO؄ν-$JV"6bd:+Fg* @EY; ab76HKZ;qxÌ\ZT4[mz }"!%&gnvwg--ʹͲOf5#C-s^1D5Q)9 5m*[!, Ft ]lx!Q9wղ7~wI@ bbX_OaomxsoδA46+$ueYѐ5#_ͩ)?3_YT]50oWm L) S'btЪk|MUb_"o.({3 AL>"L}Z*D8tw^&ʋ5!A5Mz cI' :ŲXUm*́2|IE :9һZۮ̷f4.bJbLH?|Us7eEffxhm?l2&߲9͜G?>W_4=~If*\crӤwJSAm~ @OR SPG;ܩ;-nдiG|Q3RoIktBƞueRX࠳4Q%QND<{>u(bW⠞aM[6zD̒3$Y3a0%CfvU'y/Sz~kM7] p[hd-Q'gK֡QCT.-B"';;zj$IP p p#QS>8^C g(t|[-B(8hvVc9jPZ\Tl%Fy@{A<_ы [ƽZ9S~Jث2BL!Jz8ɵFbaSCmE5 L} $'⽜SDuqbbREQem; dkR=l%P BS=^D>{W(5~=v|m 6n?dW+RR隰I;8`0#UaC(d^FvuS24'pwϝc?=ƺY.>)ʡ [x{s:ENy¦Z.^htj tzDIVߤǘ&wV#H%FACq@ w =ݲџ JtTN?y L Zh2uM_RPd6<"Quѣlrߡԍ:q8[_]aeNʅpvojބw\0΂&^l)|_̜_0zG'Ԕh$/MDZwqxW:`i w_Q:綂)8[XgeU>RsRd^9F7JQRTȢl7:Pڔ](W(symHLC*Kg]Mk\Axnnr|߄X5>>OV¡o=z jvlieK1bǪ~.*MGNenH}QʅA,/e@Gber', 'who', 'role', 'role__in', 'role__not_in' ) ); $fields = array( 'ID', 'user_login' ); $show = ! empty( $r['show'] ) ? $r['show'] : 'display_name'; if ( 'display_name_with_login' === $show ) { $fields[] = 'display_name'; } else { $fields[] = $show; } $query_args['fields'] = $fields; $show_option_all = $r['show_option_all']; $show_option_none = $r['show_option_none']; $option_none_value = $r['option_none_value']; /** * Filters the query arguments for the list of users in the dropdown. * * @since 4.4.0 * * @param array $query_args The query arguments for get_users(). * @param array $r The arguments passed to wp_dropdown_users() combined with the defaults. */ $query_args = apply_filters( 'wp_dropdown_users_args', $query_args, $r ); $users = get_users( $query_args ); $output = ''; if ( ! empty( $users ) && ( empty( $r['hide_if_only_one_author'] ) || count( $users ) > 1 ) ) { $name = esc_attr( $r['name'] ); if ( $r['multi'] && ! $r['id'] ) { $id = ''; } else { $id = $r['id'] ? " id='" . esc_attr( $r['id'] ) . "'" : " id='$name'"; } $output = "'; } /** * Filters the wp_dropdown_users() HTML output. * * @since 2.3.0 * * @param string $output HTML output generated by wp_dropdown_users(). */ $html = apply_filters( 'wp_dropdown_users', $output ); if ( $r['echo'] ) { echo $html; } return $html; } /** * Sanitize user field based on context. * * Possible context values are: 'raw', 'edit', 'db', 'display', 'attribute' and 'js'. The * 'display' context is used by default. 'attribute' and 'js' contexts are treated like 'display' * when calling filters. * * @since 2.3.0 * * @param string $field The user Object field name. * @param mixed $value The user Object value. * @param int $user_id User ID. * @param string $context How to sanitize user fields. Looks for 'raw', 'edit', 'db', 'display', * 'attribute' and 'js'. * @return mixed Sanitized value. */ function sanitize_user_field( $field, $value, $user_id, $context ) { $int_fields = array( 'ID' ); if ( in_array( $field, $int_fields ) ) { $value = (int) $value; } if ( 'raw' == $context ) { return $value; } if ( ! is_string( $value ) && ! is_numeric( $value ) ) { return $value; } $prefixed = false !== strpos( $field, 'user_' ); if ( 'edit' == $context ) { if ( $prefixed ) { /** This filter is documented in wp-includes/post.php */ $value = apply_filters( "edit_{$field}", $value, $user_id ); } else { /** * Filters a user field value in the 'edit' context. * * The dynamic portion of the hook name, `$field`, refers to the prefixed user * field being filtered, such as 'user_login', 'user_email', 'first_name', etc. * * @since 2.9.0 * * @param mixed $value Value of the prefixed user field. * @param int $user_id User ID. */ $value = apply_filters( "edit_user_{$field}", $value, $user_id ); } if ( 'description' == $field ) { $value = esc_html( $value ); // textarea_escaped? } else { $value = esc_attr( $value ); } } elseif ( 'db' == $context ) { if ( $prefixed ) { /** This filter is documented in wp-includes/post.php */ $value = apply_filters( "pre_{$field}", $value ); } else { /** * Filters the value of a user field in the 'db' context. * * The dynamic portion of the hook name, `$field`, refers to the prefixed user * field being filtered, such as 'user_login', 'user_email', 'first_name', etc. * * @since 2.9.0 * * @param mixed $value Value of the prefixed user field. */ $value = apply_filters( "pre_user_{$field}", $value ); } } else { // Use display filters by default. if ( $prefixed ) { /** This filter is documented in wp-includes/post.php */ $value = apply_filters( "{$field}", $value, $user_id, $context ); } else { /** * Filters the value of a user field in a standard context. * * The dynamic portion of the hook name, `$field`, refers to the prefixed user * field being filtered, such as 'user_login', 'user_email', 'first_name', etc. * * @since 2.9.0 * * @param mixed $value The user object value to sanitize. * @param int $user_id User ID. * @param string $context The context to filter within. */ $value = apply_filters( "user_{$field}", $value, $user_id, $context ); } } if ( 'user_url' == $field ) { $value = esc_url( $value ); } if ( 'attribute' == $context ) { $value = esc_attr( $value ); } elseif ( 'js' == $context ) { $value = esc_js( $value ); } return $value; } /** * Update all user caches * * @since 3.0.0 * * @param WP_User $user User object to be cached * @return bool|null Returns false on failure. */ function update_user_caches( $user ) { if ( $user instanceof WP_User ) { if ( ! $user->exists() ) { return false; } $user = $user->data; } wp_cache_add( $user->ID, $user, 'users' ); wp_cache_add( $user->user_login, $user->ID, 'userlogins' ); wp_cache_add( $user->user_email, $user->ID, 'useremail' ); wp_cache_add( $user->user_nicename, $user->ID, 'userslugs' ); } /** * Clean all user caches * * @since 3.0.0 * @since 4.4.0 'clean_user_cache' action was added. * * @param WP_User|int $user User object or ID to be cleaned from the cache */ function clean_user_cache( $user ) { if ( is_numeric( $user ) ) { $user = new WP_User( $user ); } if ( ! $user->exists() ) { return; } wp_cache_delete( $user->ID, 'users' ); wp_cache_delete( $user->user_login, 'userlogins' ); wp_cache_delete( $user->user_email, 'useremail' ); wp_cache_delete( $user->user_nicename, 'userslugs' ); /** * Fires immediately after the given user's cache is cleaned. * * @since 4.4.0 * * @param int $user_id User ID. * @param WP_User $user User object. */ do_action( 'clean_user_cache', $user->ID, $user ); } /** * Determines whether the given username exists. * * For more information on this and similar theme functions, check out * the {@link https://developer.wordpress.org/themes/basics/conditional-tags/ * Conditional Tags} article in the Theme Developer Handbook. * * @since 2.0.0 * * @param string $username Username. * @return int|false The user's ID on success, and false on failure. */ function username_exists( $username ) { $user = get_user_by( 'login', $username ); if ( $user ) { $user_id = $user->ID; } else { $user_id = false; } /** * Filters whether the given username exists or not. * * @since 4.9.0 * * @param int|false $user_id The user's ID on success, and false on failure. * @param string $username Username to check. */ return apply_filters( 'username_exists', $user_id, $username ); } /** * Determines whether the given email exists. * * For more information on this and similar theme functions, check out * the {@link https://developer.wordpress.org/themes/basics/conditional-tags/ * Conditional Tags} article in the Theme Developer Handbook. * * @since 2.1.0 * * @param string $email Email. * @return int|false The user's ID on success, and false on failure. */ function email_exists( $email ) { $user = get_user_by( 'email', $email ); if ( $user ) { return $user->ID; } return false; } /** * Checks whether a username is valid. * * @since 2.0.1 * @since 4.4.0 Empty sanitized usernames are now considered invalid * * @param string $username Username. * @return bool Whether username given is valid */ function validate_username( $username ) { $sanitized = sanitize_user( $username, true ); $valid = ( $sanitized == $username && ! empty( $sanitized ) ); /** * Filters whether the provided username is valid or not. * * @since 2.0.1 * * @param bool $valid Whether given username is valid. * @param string $username Username to check. */ return apply_filters( 'validate_username', $valid, $username ); } /** * Insert a user into the database. * * Most of the `$userdata` array fields have filters associated with the values. Exceptions are * 'ID', 'rich_editing', 'syntax_highlighting', 'comment_shortcuts', 'admin_color', 'use_ssl', * 'user_registered', and 'role'. The filters have the prefix 'pre_user_' followed by the field * name. An example using 'description' would have the filter called, 'pre_user_description' that * can be hooked into. * * @since 2.0.0 * @since 3.6.0 The `aim`, `jabber`, and `yim` fields were removed as default user contact * methods for new installations. See wp_get_user_contact_methods(). * @since 4.7.0 The user's locale can be passed to `$userdata`. * * @global wpdb $wpdb WordPress database abstraction object. * * @param array|object|WP_User $userdata { * An array, object, or WP_User object of user data arguments. * * @type int $ID User ID. If supplied, the user will be updated. * @type string $user_pass The plain-text user password. * @type string $user_login The user's login username. * @type string $user_nicename The URL-friendly user name. * @type string $user_url The user URL. * @type string $user_email The user email address. * @type string $display_name The user's display name. * Default is the user's username. * @type string $nickname The user's nickname. * Default is the user's username. * @type string $first_name The user's first name. For new users, will be used * to build the first part of the user's display name * if `$display_name` is not specified. * @type string $last_name The user's last name. For new users, will be used * to build the second part of the user's display name * if `$display_name` is not specified. * @type string $description The user's biographical description. * @type string|bool $rich_editing Whether to enable the rich-editor for the user. * False if not empty. * @type string|bool $syntax_highlighting Whether to enable the rich code editor for the user. * False if not empty. * @type string|bool $comment_shortcuts Whether to enable comment moderation keyboard * shortcuts for the user. Default false. * @type string $admin_color Admin color scheme for the user. Default 'fresh'. * @type bool $use_ssl Whether the user should always access the admin over * https. Default false. * @type string $user_registered Date the user registered. Format is 'Y-m-d H:i:s'. * @type string|bool $show_admin_bar_front Whether to display the Admin Bar for the user on the * site's front end. Default true. * @type string $role User's role. * @type string $locale User's locale. Default empty. * } * @return int|WP_Error The newly created user's ID or a WP_Error object if the user could not * be created. */ function wp_insert_user( $userdata ) { global $wpdb; if ( $userdata instanceof stdClass ) { $userdata = get_object_vars( $userdata ); } elseif ( $userdata instanceof WP_User ) { $userdata = $userdata->to_array(); } // Are we updating or creating? if ( ! empty( $userdata['ID'] ) ) { $ID = (int) $userdata['ID']; $update = true; $old_user_data = get_userdata( $ID ); if ( ! $old_user_data ) { return new WP_Error( 'invalid_user_id', __( 'Invalid user ID.' ) ); } // hashed in wp_update_user(), plaintext if called directly $user_pass = ! empty( $userdata['user_pass'] ) ? $userdata['user_pass'] : $old_user_data->user_pass; } else { $update = false; // Hash the password $user_pass = wp_hash_password( $userdata['user_pass'] ); } $sanitized_user_login = sanitize_user( $userdata['user_login'], true ); /** * Filters a username after it has been sanitized. * * This filter is called before the user is created or updated. * * @since 2.0.3 * * @param string $sanitized_user_login Username after it has been sanitized. */ $pre_user_login = apply_filters( 'pre_user_login', $sanitized_user_login ); //Remove any non-printable chars from the login string to see if we have ended up with an empty username $user_login = trim( $pre_user_login ); // user_login must be between 0 and 60 characters. if ( empty( $user_login ) ) { return new WP_Error( 'empty_user_login', __( 'Cannot create a user with an empty login name.' ) ); } elseif ( mb_strlen( $user_login ) > 60 ) { return new WP_Error( 'user_login_too_long', __( 'Username may not be longer than 60 characters.' ) ); } if ( ! $update && username_exists( $user_login ) ) { return new WP_Error( 'existing_user_login', __( 'Sorry, that username already exists!' ) ); } /** * Filters the list of blacklisted usernames. * * @since 4.4.0 * * @param array $usernames Array of blacklisted usernames. */ $illegal_logins = (array) apply_filters( 'illegal_user_logins', array() ); if ( in_array( strtolower( $user_login ), array_map( 'strtolower', $illegal_logins ) ) ) { return new WP_Error( 'invalid_username', __( 'Sorry, that username is not allowed.' ) ); } /* * If a nicename is provided, remove unsafe user characters before using it. * Otherwise build a nicename from the user_login. */ if ( ! empty( $userdata['user_nicename'] ) ) { $user_nicename = sanitize_user( $userdata['user_nicename'], true ); if ( mb_strlen( $user_nicename ) > 50 ) { return new WP_Error( 'user_nicename_too_long', __( 'Nicename may not be longer than 50 characters.' ) ); } } else { $user_nicename = mb_substr( $user_login, 0, 50 ); } $user_nicename = sanitize_title( $user_nicename ); // Store values to save in user meta. $meta = array(); /** * Filters a user's nicename before the user is created or updated. * * @since 2.0.3 * * @param string $user_nicename The user's nicename. */ $user_nicename = apply_filters( 'pre_user_nicename', $user_nicename ); $raw_user_url = empty( $userdata['user_url'] ) ? '' : $userdata['user_url']; /** * Filters a user's URL before the user is created or updated. * * @since 2.0.3 * * @param string $raw_user_url The user's URL. */ $user_url = apply_filters( 'pre_user_url', $raw_user_url ); $raw_user_email = empty( $userdata['user_email'] ) ? '' : $userdata['user_email']; /** * Filters a user's email before the user is created or updated. * * @since 2.0.3 * * @param string $raw_user_email The user's email. */ $user_email = apply_filters( 'pre_user_email', $raw_user_email ); /* * If there is no update, just check for `email_exists`. If there is an update, * check if current email and new email are the same, or not, and check `email_exists` * accordingly. */ if ( ( ! $update || ( ! empty( $old_user_data ) && 0 !== strcasecmp( $user_email, $old_user_data->user_email ) ) ) && ! defined( 'WP_IMPORTING' ) && email_exists( $user_email ) ) { return new WP_Error( 'existing_user_email', __( 'Sorry, that email address is already used!' ) ); } $nickname = empty( $userdata['nickname'] ) ? $user_login : $userdata['nickname']; /** * Filters a user's nickname before the user is created or updated. * * @since 2.0.3 * * @param string $nickname The user's nickname. */ $meta['nickname'] = apply_filters( 'pre_user_nickname', $nickname ); $first_name = empty( $userdata['first_name'] ) ? '' : $userdata['first_name']; /** * Filters a user's first name before the user is created or updated. * * @since 2.0.3 * * @param string $first_name The user's first name. */ $meta['first_name'] = apply_filters( 'pre_user_first_name', $first_name ); $last_name = empty( $userdata['last_name'] ) ? '' : $userdata['last_name']; /** * Filters a user's last name before the user is created or updated. * * @since 2.0.3 * * @param string $last_name The user's last name. */ $meta['last_name'] = apply_filters( 'pre_user_last_name', $last_name ); if ( empty( $userdata['display_name'] ) ) { if ( $update ) { $display_name = $user_login; } elseif ( $meta['first_name'] && $meta['last_name'] ) { /* translators: 1: first name, 2: last name */ $display_name = sprintf( _x( '%1$s %2$s', 'Display name based on first name and last name' ), $meta['first_name'], $meta['last_name'] ); } elseif ( $meta['first_name'] ) { $display_name = $meta['first_name']; } elseif ( $meta['last_name'] ) { $display_name = $meta['last_name']; } else { $display_name = $user_login; } } else { $display_name = $userdata['display_name']; } /** * Filters a user's display name before the user is created or updated. * * @since 2.0.3 * * @param string $display_name The user's display name. */ $display_name = apply_filters( 'pre_user_display_name', $display_name ); $description = empty( $userdata['description'] ) ? '' : $userdata['description']; /** * Filters a user's description before the user is created or updated. * * @since 2.0.3 * * @param string $description The user's description. */ $meta['description'] = apply_filters( 'pre_user_description', $description ); $meta['rich_editing'] = empty( $userdata['rich_editing'] ) ? 'true' : $userdata['rich_editing']; $meta['syntax_highlighting'] = empty( $userdata['syntax_highlighting'] ) ? 'true' : $userdata['syntax_highlighting']; $meta['comment_shortcuts'] = empty( $userdata['comment_shortcuts'] ) || 'false' === $userdata['comment_shortcuts'] ? 'false' : 'true'; $admin_color = empty( $userdata['admin_color'] ) ? 'fresh' : $userdata['admin_color']; $meta['admin_color'] = preg_replace( '|[^a-z0-9 _.\-@]|i', '', $admin_color ); $meta['use_ssl'] = empty( $userdata['use_ssl'] ) ? 0 : $userdata['use_ssl']; $user_registered = empty( $userdata['user_registered'] ) ? gmdate( 'Y-m-d H:i:s' ) : $userdata['user_registered']; $meta['show_admin_bar_front'] = empty( $userdata['show_admin_bar_front'] ) ? 'true' : $userdata['show_admin_bar_front']; $meta['locale'] = isset( $userdata['locale'] ) ? $userdata['locale'] : ''; $user_nicename_check = $wpdb->get_var( $wpdb->prepare( "SELECT ID FROM $wpdb->users WHERE user_nicename = %s AND user_login != %s LIMIT 1", $user_nicename, $user_login ) ); if ( $user_nicename_check ) { $suffix = 2; while ( $user_nicename_check ) { // user_nicename allows 50 chars. Subtract one for a hyphen, plus the length of the suffix. $base_length = 49 - mb_strlen( $suffix ); $alt_user_nicename = mb_substr( $user_nicename, 0, $base_length ) . "-$suffix"; $user_nicename_check = $wpdb->get_var( $wpdb->prepare( "SELECT ID FROM $wpdb->users WHERE user_nicename = %s AND user_login != %s LIMIT 1", $alt_user_nicename, $user_login ) ); $suffix++; } $user_nicename = $alt_user_nicename; } $compacted = compact( 'user_pass', 'user_email', 'user_url', 'user_nicename', 'display_name', 'user_registered' ); $data = wp_unslash( $compacted ); if ( ! $update ) { $data = $data + compact( 'user_login' ); } /** * Filters user data before the record is created or updated. * * It only includes data in the wp_users table wp_user, not any user metadata. * * @since 4.9.0 * * @param array $data { * Values and keys for the user. * * @type string $user_login The user's login. Only included if $update == false * @type string $user_pass The user's password. * @type string $user_email The user's email. * @type string $user_url The user's url. * @type string $user_nicename The user's nice name. Defaults to a URL-safe version of user's login * @type string $display_name The user's display name. * @type string $user_registered MySQL timestamp describing the moment when the user registered. Defaults to * the current UTC timestamp. * } * @param bool $update Whether the user is being updated rather than created. * @param int|null $id ID of the user to be updated, or NULL if the user is being created. */ $data = apply_filters( 'wp_pre_insert_user_data', $data, $update, $update ? (int) $ID : null ); if ( $update ) { if ( $user_email !== $old_user_data->user_email ) { $data['user_activation_key'] = ''; } $wpdb->update( $wpdb->users, $data, compact( 'ID' ) ); $user_id = (int) $ID; } else { $wpdb->insert( $wpdb->users, $data ); $user_id = (int) $wpdb->insert_id; } $user = new WP_User( $user_id ); /** * Filters a user's meta values and keys immediately after the user is created or updated * and before any user meta is inserted or updated. * * Does not include contact methods. These are added using `wp_get_user_contact_methods( $user )`. * * @since 4.4.0 * * @param array $meta { * Default meta values and keys for the user. * * @type string $nickname The user's nickname. Default is the user's username. * @type string $first_name The user's first name. * @type string $last_name The user's last name. * @type string $description The user's description. * @type bool $rich_editing Whether to enable the rich-editor for the user. False if not empty. * @type bool $syntax_highlighting Whether to enable the rich code editor for the user. False if not empty. * @type bool $comment_shortcuts Whether to enable keyboard shortcuts for the user. Default false. * @type string $admin_color The color scheme for a user's admin screen. Default 'fresh'. * @type int|bool $use_ssl Whether to force SSL on the user's admin area. 0|false if SSL is * not forced. * @type bool $show_admin_bar_front Whether to show the admin bar on the front end for the user. * Default true. * } * @param WP_User $user User object. * @param bool $update Whether the user is being updated rather than created. */ $meta = apply_filters( 'insert_user_meta', $meta, $user, $update ); // Update user meta. foreach ( $meta as $key => $value ) { update_user_meta( $user_id, $key, $value ); } foreach ( wp_get_user_contact_methods( $user ) as $key => $value ) { if ( isset( $userdata[ $key ] ) ) { update_user_meta( $user_id, $key, $userdata[ $key ] ); } } if ( isset( $userdata['role'] ) ) { $user->set_role( $userdata['role'] ); } elseif ( ! $update ) { $user->set_role( get_option( 'default_role' ) ); } wp_cache_delete( $user_id, 'users' ); wp_cache_delete( $user_login, 'userlogins' ); if ( $update ) { /** * Fires immediately after an existing user is updated. * * @since 2.0.0 * * @param int $user_id User ID. * @param WP_User $old_user_data Object containing user's data prior to update. */ do_action( 'profile_update', $user_id, $old_user_data ); } else { /** * Fires immediately after a new user is registered. * * @since 1.5.0 * * @param int $user_id User ID. */ do_action( 'user_register', $user_id ); } return $user_id; } /** * Update a user in the database. * * It is possible to update a user's password by specifying the 'user_pass' * value in the $userdata parameter array. * * If current user's password is being updated, then the cookies will be * cleared. * * @since 2.0.0 * * @see wp_insert_user() For what fields can be set in $userdata. * * @param array|object|WP_User $userdata An array of user data or a user object of type stdClass or WP_User. * @return int|WP_Error The updated user's ID or a WP_Error object if the user could not be updated. */ function wp_update_user( $userdata ) { if ( $userdata instanceof stdClass ) { $userdata = get_object_vars( $userdata ); } elseif ( $userdata instanceof WP_User ) { $userdata = $userdata->to_array(); } $ID = isset( $userdata['ID'] ) ? (int) $userdata['ID'] : 0; if ( ! $ID ) { return new WP_Error( 'invalid_user_id', __( 'Invalid user ID.' ) ); } // First, get all of the original fields $user_obj = get_userdata( $ID ); if ( ! $user_obj ) { return new WP_Error( 'invalid_user_id', __( 'Invalid user ID.' ) ); } $user = $user_obj->to_array(); // Add additional custom fields foreach ( _get_additional_user_keys( $user_obj ) as $key ) { $user[ $key ] = get_user_meta( $ID, $key, true ); } // Escape data pulled from DB. $user = add_magic_quotes( $user ); if ( ! empty( $userdata['user_pass'] ) && $userdata['user_pass'] !== $user_obj->user_pass ) { // If password is changing, hash it now $plaintext_pass = $userdata['user_pass']; $userdata['user_pass'] = wp_hash_password( $userdata['user_pass'] ); /** * Filters whether to send the password change email. * * @since 4.3.0 * * @see wp_insert_user() For `$user` and `$userdata` fields. * * @param bool $send Whether to send the email. * @param array $user The original user array. * @param array $userdata The updated user array. */ $send_password_change_email = apply_filters( 'send_password_change_email', true, $user, $userdata ); } if ( isset( $userdata['user_email'] ) && $user['user_email'] !== $userdata['user_email'] ) { /** * Filters whether to send the email change email. * * @since 4.3.0 * * @see wp_insert_user() For `$user` and `$userdata` fields. * * @param bool $send Whether to send the email. * @param array $user The original user array. * @param array $userdata The updated user array. */ $send_email_change_email = apply_filters( 'send_email_change_email', true, $user, $userdata ); } wp_cache_delete( $user['user_email'], 'useremail' ); wp_cache_delete( $user['user_nicename'], 'userslugs' ); // Merge old and new fields with new fields overwriting old ones. $userdata = array_merge( $user, $userdata ); $user_id = wp_insert_user( $userdata ); if ( ! is_wp_error( $user_id ) ) { $blog_name = wp_specialchars_decode( get_option( 'blogname' ), ENT_QUOTES ); $switched_locale = false; if ( ! empty( $send_password_change_email ) || ! empty( $send_email_change_email ) ) { $switched_locale = switch_to_locale( get_user_locale( $user_id ) ); } if ( ! empty( $send_password_change_email ) ) { /* translators: Do not translate USERNAME, ADMIN_EMAIL, EMAIL, SITENAME, SITEURL: those are placeholders. */ $pass_change_text = __( 'Hi ###USERNAME###, This notice confirms that your password was changed on ###SITENAME###. If you did not change your password, please contact the Site Administrator at ###ADMIN_EMAIL### This email has been sent to ###EMAIL### Regards, All at ###SITENAME### ###SITEURL###' ); $pass_change_email = array( 'to' => $user['user_email'], /* translators: Password change notification email subject. %s: Site name */ 'subject' => __( '[%s] Password Changed' ), 'message' => $pass_change_text, 'headers' => '', ); /** * Filters the contents of the email sent when the user's password is changed. * * @since 4.3.0 * * @param array $pass_change_email { * Used to build wp_mail(). * @type string $to The intended recipients. Add emails in a comma separated string. * @type string $subject The subject of the email. * @type string $message The content of the email. * The following strings have a special meaning and will get replaced dynamically: * - ###USERNAME### The current user's username. * - ###ADMIN_EMAIL### The admin email in case this was unexpected. * - ###EMAIL### The user's email address. * - ###SITENAME### The name of the site. * - ###SITEURL### The URL to the site. * @type string $headers Headers. Add headers in a newline (\r\n) separated string. * } * @param array $user The original user array. * @param array $userdata The updated user array. */ $pass_change_email = apply_filters( 'password_change_email', $pass_change_email, $user, $userdata ); $pass_change_email['message'] = str_replace( '###USERNAME###', $user['user_login'], $pass_change_email['message'] ); $pass_change_email['message'] = str_replace( '###ADMIN_EMAIL###', get_option( 'admin_email' ), $pass_change_email['message'] ); $pass_change_email['message'] = str_replace( '###EMAIL###', $user['user_email'], $pass_change_email['message'] ); $pass_change_email['message'] = str_replace( '###SITENAME###', $blog_name, $pass_change_email['message'] ); $pass_change_email['message'] = str_replace( '###SITEURL###', home_url(), $pass_change_email['message'] ); wp_mail( $pass_change_email['to'], sprintf( $pass_change_email['subject'], $blog_name ), $pass_change_email['message'], $pass_change_email['headers'] ); } if ( ! empty( $send_email_change_email ) ) { /* translators: Do not translate USERNAME, ADMIN_EMAIL, NEW_EMAIL, EMAIL, SITENAME, SITEURL: those are placeholders. */ $email_change_text = __( 'Hi ###USERNAME###, This notice confirms that your email address on ###SITENAME### was changed to ###NEW_EMAIL###. If you did not change your email, please contact the Site Administrator at ###ADMIN_EMAIL### This email has been sent to ###EMAIL### Regards, All at ###SITENAME### ###SITEURL###' ); $email_change_email = array( 'to' => $user['user_email'], /* translators: Email change notification email subject. %s: Site name */ 'subject' => __( '[%s] Email Changed' ), 'message' => $email_change_text, 'headers' => '', ); /** * Filters the contents of the email sent when the user's email is changed. * * @since 4.3.0 * * @param array $email_change_email { * Used to build wp_mail(). * @type string $to The intended recipients. * @type string $subject The subject of the email. * @type string $message The content of the email. * The following strings have a special meaning and will get replaced dynamically: * - ###USERNAME### The current user's username. * - ###ADMIN_EMAIL### The admin email in case this was unexpected. * - ###NEW_EMAIL### The new email address. * - ###EMAIL### The old email address. * - ###SITENAME### The name of the site. * - ###SITEURL### The URL to the site. * @type string $headers Headers. * } * @param array $user The original user array. * @param array $userdata The updated user array. */ $email_change_email = apply_filters( 'email_change_email', $email_change_email, $user, $userdata ); $email_change_email['message'] = str_replace( '###USERNAME###', $user['user_login'], $email_change_email['message'] ); $email_change_email['message'] = str_replace( '###ADMIN_EMAIL###', get_option( 'admin_email' ), $email_change_email['message'] ); $email_change_email['message'] = str_replace( '###NEW_EMAIL###', $userdata['user_email'], $email_change_email['message'] ); $email_change_email['message'] = str_replace( '###EMAIL###', $user['user_email'], $email_change_email['message'] ); $email_change_email['message'] = str_replace( '###SITENAME###', $blog_name, $email_change_email['message'] ); $email_change_email['message'] = str_replace( '###SITEURL###', home_url(), $email_change_email['message'] ); wp_mail( $email_change_email['to'], sprintf( $email_change_email['subject'], $blog_name ), $email_change_email['message'], $email_change_email['headers'] ); } if ( $switched_locale ) { restore_previous_locale(); } } // Update the cookies if the password changed. $current_user = wp_get_current_user(); if ( $current_user->ID == $ID ) { if ( isset( $plaintext_pass ) ) { wp_clear_auth_cookie(); // Here we calculate the expiration length of the current auth cookie and compare it to the default expiration. // If it's greater than this, then we know the user checked 'Remember Me' when they logged in. $logged_in_cookie = wp_parse_auth_cookie( '', 'logged_in' ); /** This filter is documented in wp-includes/pluggable.php */ $default_cookie_life = apply_filters( 'auth_cookie_expiration', ( 2 * DAY_IN_SECONDS ), $ID, false ); $remember = ( ( $logged_in_cookie['expiration'] - time() ) > $default_cookie_life ); wp_set_auth_cookie( $ID, $remember ); } } return $user_id; } /** * A simpler way of inserting a user into the database. * * Creates a new user with just the username, password, and email. For more * complex user creation use wp_insert_user() to specify more information. * * @since 2.0.0 * @see wp_insert_user() More complete way to create a new user * * @param string $username The user's username. * @param string $password The user's password. * @param string $email Optional. The user's email. Default empty. * @return int|WP_Error The newly created user's ID or a WP_Error object if the user could not * be created. */ function wp_create_user( $username, $password, $email = '' ) { $user_login = wp_slash( $username ); $user_email = wp_slash( $email ); $user_pass = $password; $userdata = compact( 'user_login', 'user_email', 'user_pass' ); return wp_insert_user( $userdata ); } /** * Returns a list of meta keys to be (maybe) populated in wp_update_user(). * * The list of keys returned via this function are dependent on the presence * of those keys in the user meta data to be set. * * @since 3.3.0 * @access private * * @param WP_User $user WP_User instance. * @return array List of user keys to be populated in wp_update_user(). */ function _get_additional_user_keys( $user ) { $keys = array( 'first_name', 'last_name', 'nickname', 'description', 'rich_editing', 'syntax_highlighting', 'comment_shortcuts', 'admin_color', 'use_ssl', 'show_admin_bar_front', 'locale' ); return array_merge( $keys, array_keys( wp_get_user_contact_methods( $user ) ) ); } /** * Set up the user contact methods. * * Default contact methods were removed in 3.6. A filter dictates contact methods. * * @since 3.7.0 * * @param WP_User $user Optional. WP_User object. * @return array Array of contact methods and their labels. */ function wp_get_user_contact_methods( $user = null ) { $methods = array(); if ( get_site_option( 'initial_db_version' ) < 23588 ) { $methods = array( 'aim' => __( 'AIM' ), 'yim' => __( 'Yahoo IM' ), 'jabber' => __( 'Jabber / Google Talk' ), ); } /** * Filters the user contact methods. * * @since 2.9.0 * * @param array $methods Array of contact methods and their labels. * @param WP_User $user WP_User object. */ return apply_filters( 'user_contactmethods', $methods, $user ); } /** * The old private function for setting up user contact methods. * * Use wp_get_user_contact_methods() instead. * * @since 2.9.0 * @access private * * @param WP_User $user Optional. WP_User object. Default null. * @return array Array of contact methods and their labels. */ function _wp_get_user_contactmethods( $user = null ) { return wp_get_user_contact_methods( $user ); } /** * Gets the text suggesting how to create strong passwords. * * @since 4.1.0 * * @return string The password hint text. */ function wp_get_password_hint() { $hint = __( 'Hint: The password should be at least twelve characters long. To make it stronger, use upper and lower case letters, numbers, and symbols like ! " ? $ % ^ & ).' ); /** * Filters the text describing the site's password complexity policy. * * @since 4.1.0 * * @param string $hint The password hint text. */ return apply_filters( 'password_hint', $hint ); } /** * Creates, stores, then returns a password reset key for user. * * @since 4.4.0 * * @global wpdb $wpdb WordPress database abstraction object. * @global PasswordHash $wp_hasher Portable PHP password hashing framework. * * @param WP_User $user User to retrieve password reset key for. * * @return string|WP_Error Password reset key on success. WP_Error on error. */ function get_password_reset_key( $user ) { global $wpdb, $wp_hasher; if ( ! ( $user instanceof WP_User ) ) { return new WP_Error( 'invalidcombo', __( 'ERROR: There is no account with that username or email address.' ) ); } /** * Fires before a new password is retrieved. * * Use the {@see 'retrieve_password'} hook instead. * * @since 1.5.0 * @deprecated 1.5.1 Misspelled. Use 'retrieve_password' hook instead. * * @param string $user_login The user login name. */ do_action( 'retreive_password', $user->user_login ); /** * Fires before a new password is retrieved. * * @since 1.5.1 * * @param string $user_login The user login name. */ do_action( 'retrieve_password', $user->user_login ); $allow = true; if ( is_multisite() && is_user_spammy( $user ) ) { $allow = false; } /** * Filters whether to allow a password to be reset. * * @since 2.7.0 * * @param bool $allow Whether to allow the password to be reset. Default true. * @param int $user_data->ID The ID of the user attempting to reset a password. */ $allow = apply_filters( 'allow_password_reset', $allow, $user->ID ); if ( ! $allow ) { return new WP_Error( 'no_password_reset', __( 'Password reset is not allowed for this user' ) ); } elseif ( is_wp_error( $allow ) ) { return $allow; } // Generate something random for a password reset key. $key = wp_generate_password( 20, false ); /** * Fires when a password reset key is generated. * * @since 2.5.0 * * @param string $user_login The username for the user. * @param string $key The generated password reset key. */ do_action( 'retrieve_password_key', $user->user_login, $key ); // Now insert the key, hashed, into the DB. if ( empty( $wp_hasher ) ) { require_once ABSPATH . WPINC . '/class-phpass.php'; $wp_hasher = new PasswordHash( 8, true ); } $hashed = time() . ':' . $wp_hasher->HashPassword( $key ); $key_saved = $wpdb->update( $wpdb->users, array( 'user_activation_key' => $hashed ), array( 'user_login' => $user->user_login ) ); if ( false === $key_saved ) { return new WP_Error( 'no_password_key_update', __( 'Could not save password reset key to database.' ) ); } return $key; } /** * Retrieves a user row based on password reset key and login * * A key is considered 'expired' if it exactly matches the value of the * user_activation_key field, rather than being matched after going through the * hashing process. This field is now hashed; old values are no longer accepted * but have a different WP_Error code so good user feedback can be provided. * * @since 3.1.0 * * @global wpdb $wpdb WordPress database object for queries. * @global PasswordHash $wp_hasher Portable PHP password hashing framework instance. * * @param string $key Hash to validate sending user's password. * @param string $login The user login. * @return WP_User|WP_Error WP_User object on success, WP_Error object for invalid or expired keys. */ function check_password_reset_key( $key, $login ) { global $wpdb, $wp_hasher; $key = preg_replace( '/[^a-z0-9]/i', '', $key ); if ( empty( $key ) || ! is_string( $key ) ) { return new WP_Error( 'invalid_key', __( 'Invalid key.' ) ); } if ( empty( $login ) || ! is_string( $login ) ) { return new WP_Error( 'invalid_key', __( 'Invalid key.' ) ); } $row = $wpdb->get_row( $wpdb->prepare( "SELECT ID, user_activation_key FROM $wpdb->users WHERE user_login = %s", $login ) ); if ( ! $row ) { return new WP_Error( 'invalid_key', __( 'Invalid key.' ) ); } if ( empty( $wp_hasher ) ) { require_once ABSPATH . WPINC . '/class-phpass.php'; $wp_hasher = new PasswordHash( 8, true ); } /** * Filters the expiration time of password reset keys. * * @since 4.3.0 * * @param int $expiration The expiration time in seconds. */ $expiration_duration = apply_filters( 'password_reset_expiration', DAY_IN_SECONDS ); if ( false !== strpos( $row->user_activation_key, ':' ) ) { list( $pass_request_time, $pass_key ) = explode( ':', $row->user_activation_key, 2 ); $expiration_time = $pass_request_time + $expiration_duration; } else { $pass_key = $row->user_activation_key; $expiration_time = false; } if ( ! $pass_key ) { return new WP_Error( 'invalid_key', __( 'Invalid key.' ) ); } $hash_is_correct = $wp_hasher->CheckPassword( $key, $pass_key ); if ( $hash_is_correct && $expiration_time && time() < $expiration_time ) { return get_userdata( $row->ID ); } elseif ( $hash_is_correct && $expiration_time ) { // Key has an expiration time that's passed return new WP_Error( 'expired_key', __( 'Invalid key.' ) ); } if ( hash_equals( $row->user_activation_key, $key ) || ( $hash_is_correct && ! $expiration_time ) ) { $return = new WP_Error( 'expired_key', __( 'Invalid key.' ) ); $user_id = $row->ID; /** * Filters the return value of check_password_reset_key() when an * old-style key is used. * * @since 3.7.0 Previously plain-text keys were stored in the database. * @since 4.3.0 Previously key hashes were stored without an expiration time. * * @param WP_Error $return A WP_Error object denoting an expired key. * Return a WP_User object to validate the key. * @param int $user_id The matched user ID. */ return apply_filters( 'password_reset_key_expired', $return, $user_id ); } return new WP_Error( 'invalid_key', __( 'Invalid key.' ) ); } /** * Handles resetting the user's password. * * @since 2.5.0 * * @param WP_User $user The user * @param string $new_pass New password for the user in plaintext */ function reset_password( $user, $new_pass ) { /** * Fires before the user's password is reset. * * @since 1.5.0 * * @param object $user The user. * @param string $new_pass New user password. */ do_action( 'password_reset', $user, $new_pass ); wp_set_password( $new_pass, $user->ID ); update_user_option( $user->ID, 'default_password_nag', false, true ); /** * Fires after the user's password is reset. * * @since 4.4.0 * * @param WP_User $user The user. * @param string $new_pass New user password. */ do_action( 'after_password_reset', $user, $new_pass ); } /** * Handles registering a new user. * * @since 2.5.0 * * @param string $user_login User's username for logging in * @param string $user_email User's email address to send password and add * @return int|WP_Error Either user's ID or error on failure. */ function register_new_user( $user_login, $user_email ) { $errors = new WP_Error(); $sanitized_user_login = sanitize_user( $user_login ); /** * Filters the email address of a user being registered. * * @since 2.1.0 * * @param string $user_email The email address of the new user. */ $user_email = apply_filters( 'user_registration_email', $user_email ); // Check the username if ( $sanitized_user_login == '' ) { $errors->add( 'empty_username', __( 'ERROR: Please enter a username.' ) ); } elseif ( ! validate_username( $user_login ) ) { $errors->add( 'invalid_username', __( 'ERROR: This username is invalid because it uses illegal characters. Please enter a valid username.' ) ); $sanitized_user_login = ''; } elseif ( username_exists( $sanitized_user_login ) ) { $errors->add( 'username_exists', __( 'ERROR: This username is already registered. Please choose another one.' ) ); } else { /** This filter is documented in wp-includes/user.php */ $illegal_user_logins = array_map( 'strtolower', (array) apply_filters( 'illegal_user_logins', array() ) ); if ( in_array( strtolower( $sanitized_user_login ), $illegal_user_logins ) ) { $errors->add( 'invalid_username', __( 'ERROR: Sorry, that username is not allowed.' ) ); } } // Check the email address if ( $user_email == '' ) { $errors->add( 'empty_email', __( 'ERROR: Please type your email address.' ) ); } elseif ( ! is_email( $user_email ) ) { $errors->add( 'invalid_email', __( 'ERROR: The email address isn’t correct.' ) ); $user_email = ''; } elseif ( email_exists( $user_email ) ) { $errors->add( 'email_exists', __( 'ERROR: This email is already registered, please choose another one.' ) ); } /** * Fires when submitting registration form data, before the user is created. * * @since 2.1.0 * * @param string $sanitized_user_login The submitted username after being sanitized. * @param string $user_email The submitted email. * @param WP_Error $errors Contains any errors with submitted username and email, * e.g., an empty field, an invalid username or email, * or an existing username or email. */ do_action( 'register_post', $sanitized_user_login, $user_email, $errors ); /** * Filters the errors encountered when a new user is being registered. * * The filtered WP_Error object may, for example, contain errors for an invalid * or existing username or email address. A WP_Error object should always returned, * but may or may not contain errors. * * If any errors are present in $errors, this will abort the user's registration. * * @since 2.1.0 * * @param WP_Error $errors A WP_Error object containing any errors encountered * during registration. * @param string $sanitized_user_login User's username after it has been sanitized. * @param string $user_email User's email. */ $errors = apply_filters( 'registration_errors', $errors, $sanitized_user_login, $user_email ); if ( $errors->has_errors() ) { return $errors; } $user_pass = wp_generate_password( 12, false ); $user_id = wp_create_user( $sanitized_user_login, $user_pass, $user_email ); if ( ! $user_id || is_wp_error( $user_id ) ) { $errors->add( 'registerfail', sprintf( __( 'ERROR: Couldn’t register you… please contact the webmaster !' ), get_option( 'admin_email' ) ) ); return $errors; } update_user_option( $user_id, 'default_password_nag', true, true ); //Set up the Password change nag. /** * Fires after a new user registration has been recorded. * * @since 4.4.0 * * @param int $user_id ID of the newly registered user. */ do_action( 'register_new_user', $user_id ); return $user_id; } /** * Initiates email notifications related to the creation of new users. * * Notifications are sent both to the site admin and to the newly created user. * * @since 4.4.0 * @since 4.6.0 Converted the `$notify` parameter to accept 'user' for sending * notifications only to the user created. * * @param int $user_id ID of the newly created user. * @param string $notify Optional. Type of notification that should happen. Accepts 'admin' * or an empty string (admin only), 'user', or 'both' (admin and user). * Default 'both'. */ function wp_send_new_user_notifications( $user_id, $notify = 'both' ) { wp_new_user_notification( $user_id, null, $notify ); } /** * Retrieve the current session token from the logged_in cookie. * * @since 4.0.0 * * @return string Token. */ function wp_get_session_token() { $cookie = wp_parse_auth_cookie( '', 'logged_in' ); return ! empty( $cookie['token'] ) ? $cookie['token'] : ''; } /** * Retrieve a list of sessions for the current user. * * @since 4.0.0 * @return array Array of sessions. */ function wp_get_all_sessions() { $manager = WP_Session_Tokens::get_instance( get_current_user_id() ); return $manager->get_all(); } /** * Remove the current session token from the database. * * @since 4.0.0 */ function wp_destroy_current_session() { $token = wp_get_session_token(); if ( $token ) { $manager = WP_Session_Tokens::get_instance( get_current_user_id() ); $manager->destroy( $token ); } } /** * Remove all but the current session token for the current user for the database. * * @since 4.0.0 */ function wp_destroy_other_sessions() { $token = wp_get_session_token(); if ( $token ) { $manager = WP_Session_Tokens::get_instance( get_current_user_id() ); $manager->destroy_others( $token ); } } /** * Remove all session tokens for the current user from the database. * * @since 4.0.0 */ function wp_destroy_all_sessions() { $manager = WP_Session_Tokens::get_instance( get_current_user_id() ); $manager->destroy_all(); } /** * Get the user IDs of all users with no role on this site. * * @since 4.4.0 * @since 4.9.0 The `$site_id` parameter was added to support multisite. * * @param int|null $site_id Optional. The site ID to get users with no role for. Defaults to the current site. * @return array Array of user IDs. */ function wp_get_users_with_no_role( $site_id = null ) { global $wpdb; if ( ! $site_id ) { $site_id = get_current_blog_id(); } $prefix = $wpdb->get_blog_prefix( $site_id ); if ( is_multisite() && $site_id != get_current_blog_id() ) { switch_to_blog( $site_id ); $role_names = wp_roles()->get_names(); restore_current_blog(); } else { $role_names = wp_roles()->get_names(); } $regex = implode( '|', array_keys( $role_names ) ); $regex = preg_replace( '/[^a-zA-Z_\|-]/', '', $regex ); $users = $wpdb->get_col( $wpdb->prepare( " SELECT user_id FROM $wpdb->usermeta WHERE meta_key = '{$prefix}capabilities' AND meta_value NOT REGEXP %s ", $regex ) ); return $users; } /** * Retrieves the current user object. * * Will set the current user, if the current user is not set. The current user * will be set to the logged-in person. If no user is logged-in, then it will * set the current user to 0, which is invalid and won't have any permissions. * * This function is used by the pluggable functions wp_get_current_user() and * get_currentuserinfo(), the latter of which is deprecated but used for backward * compatibility. * * @since 4.5.0 * @access private * * @see wp_get_current_user() * @global WP_User $current_user Checks if the current user is set. * * @return WP_User Current WP_User instance. */ function _wp_get_current_user() { global $current_user; if ( ! empty( $current_user ) ) { if ( $current_user instanceof WP_User ) { return $current_user; } // Upgrade stdClass to WP_User if ( is_object( $current_user ) && isset( $current_user->ID ) ) { $cur_id = $current_user->ID; $current_user = null; wp_set_current_user( $cur_id ); return $current_user; } // $current_user has a junk value. Force to WP_User with ID 0. $current_user = null; wp_set_current_user( 0 ); return $current_user; } if ( defined( 'XMLRPC_REQUEST' ) && XMLRPC_REQUEST ) { wp_set_current_user( 0 ); return $current_user; } /** * Filters the current user. * * The default filters use this to determine the current user from the * request's cookies, if available. * * Returning a value of false will effectively short-circuit setting * the current user. * * @since 3.9.0 * * @param int|bool $user_id User ID if one has been determined, false otherwise. */ $user_id = apply_filters( 'determine_current_user', false ); if ( ! $user_id ) { wp_set_current_user( 0 ); return $current_user; } wp_set_current_user( $user_id ); return $current_user; } /** * Send a confirmation request email when a change of user email address is attempted. * * @since 3.0.0 * @since 4.9.0 This function was moved from wp-admin/includes/ms.php so it's no longer Multisite specific. * * @global WP_Error $errors WP_Error object. */ function send_confirmation_on_profile_email() { global $errors; $current_user = wp_get_current_user(); if ( ! is_object( $errors ) ) { $errors = new WP_Error(); } if ( $current_user->ID != $_POST['user_id'] ) { return false; } if ( $current_user->user_email != $_POST['email'] ) { if ( ! is_email( $_POST['email'] ) ) { $errors->add( 'user_email', __( 'ERROR: The email address isn’t correct.' ), array( 'form-field' => 'email', ) ); return; } if ( email_exists( $_POST['email'] ) ) { $errors->add( 'user_email', __( 'ERROR: The email address is already used.' ), array( 'form-field' => 'email', ) ); delete_user_meta( $current_user->ID, '_new_email' ); return; } $hash = md5( $_POST['email'] . time() . wp_rand() ); $new_user_email = array( 'hash' => $hash, 'newemail' => $_POST['email'], ); update_user_meta( $current_user->ID, '_new_email', $new_user_email ); $sitename = wp_specialchars_decode( get_option( 'blogname' ), ENT_QUOTES ); /* translators: Do not translate USERNAME, ADMIN_URL, EMAIL, SITENAME, SITEURL: those are placeholders. */ $email_text = __( 'Howdy ###USERNAME###, You recently requested to have the email address on your account changed. If this is correct, please click on the following link to change it: ###ADMIN_URL### You can safely ignore and delete this email if you do not want to take this action. This email has been sent to ###EMAIL### Regards, All at ###SITENAME### ###SITEURL###' ); /** * Filters the text of the email sent when a change of user email address is attempted. * * The following strings have a special meaning and will get replaced dynamically: * ###USERNAME### The current user's username. * ###ADMIN_URL### The link to click on to confirm the email change. * ###EMAIL### The new email. * ###SITENAME### The name of the site. * ###SITEURL### The URL to the site. * * @since MU (3.0.0) * @since 4.9.0 This filter is no longer Multisite specific. * * @param string $email_text Text in the email. * @param array $new_user_email { * Data relating to the new user email address. * * @type string $hash The secure hash used in the confirmation link URL. * @type string $newemail The proposed new email address. * } */ $content = apply_filters( 'new_user_email_content', $email_text, $new_user_email ); $content = str_replace( '###USERNAME###', $current_user->user_login, $content ); $content = str_replace( '###ADMIN_URL###', esc_url( admin_url( 'profile.php?newuseremail=' . $hash ) ), $content ); $content = str_replace( '###EMAIL###', $_POST['email'], $content ); $content = str_replace( '###SITENAME###', $sitename, $content ); $content = str_replace( '###SITEURL###', home_url(), $content ); /* translators: New email address notification email subject. %s: Site name */ wp_mail( $_POST['email'], sprintf( __( '[%s] Email Change Request' ), $sitename ), $content ); $_POST['email'] = $current_user->user_email; } } /** * Adds an admin notice alerting the user to check for confirmation request email * after email address change. * * @since 3.0.0 * @since 4.9.0 This function was moved from wp-admin/includes/ms.php so it's no longer Multisite specific. * * @global string $pagenow */ function new_user_email_admin_notice() { global $pagenow; if ( 'profile.php' === $pagenow && isset( $_GET['updated'] ) ) { $email = get_user_meta( get_current_user_id(), '_new_email', true ); if ( $email ) { /* translators: %s: New email address */ echo '

' . sprintf( __( 'Your email address has not been updated yet. Please check your inbox at %s for a confirmation email.' ), '' . esc_html( $email['newemail'] ) . '' ) . '

'; } } } /** * Get all user privacy request types. * * @since 4.9.6 * @access private * * @return array List of core privacy action types. */ function _wp_privacy_action_request_types() { return array( 'export_personal_data', 'remove_personal_data', ); } /** * Registers the personal data exporter for users. * * @since 4.9.6 * * @param array $exporters An array of personal data exporters. * @return array An array of personal data exporters. */ function wp_register_user_personal_data_exporter( $exporters ) { $exporters['wordpress-user'] = array( 'exporter_friendly_name' => __( 'WordPress User' ), 'callback' => 'wp_user_personal_data_exporter', ); return $exporters; } /** * Finds and exports personal data associated with an email address from the user and user_meta table. * * @since 4.9.6 * * @param string $email_address The users email address. * @return array An array of personal data. */ function wp_user_personal_data_exporter( $email_address ) { $email_address = trim( $email_address ); $data_to_export = array(); $user = get_user_by( 'email', $email_address ); if ( ! $user ) { return array( 'data' => array(), 'done' => true, ); } $user_meta = get_user_meta( $user->ID ); $user_prop_to_export = array( 'ID' => __( 'User ID' ), 'user_login' => __( 'User Login Name' ), 'user_nicename' => __( 'User Nice Name' ), 'user_email' => __( 'User Email' ), 'user_url' => __( 'User URL' ), 'user_registered' => __( 'User Registration Date' ), 'display_name' => __( 'User Display Name' ), 'nickname' => __( 'User Nickname' ), 'first_name' => __( 'User First Name' ), 'last_name' => __( 'User Last Name' ), 'description' => __( 'User Description' ), ); $user_data_to_export = array(); foreach ( $user_prop_to_export as $key => $name ) { $value = ''; switch ( $key ) { case 'ID': case 'user_login': case 'user_nicename': case 'user_email': case 'user_url': case 'user_registered': case 'display_name': $value = $user->data->$key; break; case 'nickname': case 'first_name': case 'last_name': case 'description': $value = $user_meta[ $key ][0]; break; } if ( ! empty( $value ) ) { $user_data_to_export[] = array( 'name' => $name, 'value' => $value, ); } } $data_to_export[] = array( 'group_id' => 'user', 'group_label' => __( 'User' ), 'item_id' => "user-{$user->ID}", 'data' => $user_data_to_export, ); return array( 'data' => $data_to_export, 'done' => true, ); } /** * Update log when privacy request is confirmed. * * @since 4.9.6 * @access private * * @param int $request_id ID of the request. */ function _wp_privacy_account_request_confirmed( $request_id ) { $request = wp_get_user_request_data( $request_id ); if ( ! $request ) { return; } if ( ! in_array( $request->status, array( 'request-pending', 'request-failed' ), true ) ) { return; } update_post_meta( $request_id, '_wp_user_request_confirmed_timestamp', time() ); wp_update_post( array( 'ID' => $request_id, 'post_status' => 'request-confirmed', ) ); } /** * Notify the site administrator via email when a request is confirmed. * * Without this, the admin would have to manually check the site to see if any * action was needed on their part yet. * * @since 4.9.6 * * @param int $request_id The ID of the request. */ function _wp_privacy_send_request_confirmation_notification( $request_id ) { $request = wp_get_user_request_data( $request_id ); if ( ! is_a( $request, 'WP_User_Request' ) || 'request-confirmed' !== $request->status ) { return; } $already_notified = (bool) get_post_meta( $request_id, '_wp_admin_notified', true ); if ( $already_notified ) { return; } $manage_url = add_query_arg( 'page', $request->action_name, admin_url( 'tools.php' ) ); $action_description = wp_user_request_action_description( $request->action_name ); /** * Filters the recipient of the data request confirmation notification. * * In a Multisite environment, this will default to the email address of the * network admin because, by default, single site admins do not have the * capabilities required to process requests. Some networks may wish to * delegate those capabilities to a single-site admin, or a dedicated person * responsible for managing privacy requests. * * @since 4.9.6 * * @param string $admin_email The email address of the notification recipient. * @param WP_User_Request $request The request that is initiating the notification. */ $admin_email = apply_filters( 'user_request_confirmed_email_to', get_site_option( 'admin_email' ), $request ); $email_data = array( 'request' => $request, 'user_email' => $request->email, 'description' => $action_description, 'manage_url' => $manage_url, 'sitename' => wp_specialchars_decode( get_option( 'blogname' ), ENT_QUOTES ), 'siteurl' => home_url(), 'admin_email' => $admin_email, ); /* translators: Do not translate SITENAME, USER_EMAIL, DESCRIPTION, MANAGE_URL, SITEURL; those are placeholders. */ $email_text = __( 'Howdy, A user data privacy request has been confirmed on ###SITENAME###: User: ###USER_EMAIL### Request: ###DESCRIPTION### You can view and manage these data privacy requests here: ###MANAGE_URL### Regards, All at ###SITENAME### ###SITEURL###' ); /** * Filters the body of the user request confirmation email. * * The email is sent to an administrator when an user request is confirmed. * The following strings have a special meaning and will get replaced dynamically: * * ###SITENAME### The name of the site. * ###USER_EMAIL### The user email for the request. * ###DESCRIPTION### Description of the action being performed so the user knows what the email is for. * ###MANAGE_URL### The URL to manage requests. * ###SITEURL### The URL to the site. * * @since 4.9.6 * * @param string $email_text Text in the email. * @param array $email_data { * Data relating to the account action email. * * @type WP_User_Request $request User request object. * @type string $user_email The email address confirming a request * @type string $description Description of the action being performed so the user knows what the email is for. * @type string $manage_url The link to click manage privacy requests of this type. * @type string $sitename The site name sending the mail. * @type string $siteurl The site URL sending the mail. * @type string $admin_email The administrator email receiving the mail. * } */ $content = apply_filters( 'user_confirmed_action_email_content', $email_text, $email_data ); $content = str_replace( '###SITENAME###', $email_data['sitename'], $content ); $content = str_replace( '###USER_EMAIL###', $email_data['user_email'], $content ); $content = str_replace( '###DESCRIPTION###', $email_data['description'], $content ); $content = str_replace( '###MANAGE_URL###', esc_url_raw( $email_data['manage_url'] ), $content ); $content = str_replace( '###SITEURL###', esc_url_raw( $email_data['siteurl'] ), $content ); $subject = sprintf( /* translators: Privacy data request confirmed notification email subject. 1: Site title, 2: Name of the confirmed action. */ __( '[%1$s] Action Confirmed: %2$s' ), $email_data['sitename'], $action_description ); /** * Filters the subject of the user request confirmation email. * * @since 4.9.8 * * @param string $subject The email subject. * @param string $sitename The name of the site. * @param array $email_data { * Data relating to the account action email. * * @type WP_User_Request $request User request object. * @type string $user_email The email address confirming a request * @type string $description Description of the action being performed so the user knows what the email is for. * @type string $manage_url The link to click manage privacy requests of this type. * @type string $sitename The site name sending the mail. * @type string $siteurl The site URL sending the mail. * @type string $admin_email The administrator email receiving the mail. * } */ $subject = apply_filters( 'user_request_confirmed_email_subject', $subject, $email_data['sitename'], $email_data ); $email_sent = wp_mail( $email_data['admin_email'], $subject, $content ); if ( $email_sent ) { update_post_meta( $request_id, '_wp_admin_notified', true ); } } /** * Notify the user when their erasure request is fulfilled. * * Without this, the user would never know if their data was actually erased. * * @since 4.9.6 * * @param int $request_id The privacy request post ID associated with this request. */ function _wp_privacy_send_erasure_fulfillment_notification( $request_id ) { $request = wp_get_user_request_data( $request_id ); if ( ! is_a( $request, 'WP_User_Request' ) || 'request-completed' !== $request->status ) { return; } $already_notified = (bool) get_post_meta( $request_id, '_wp_user_notified', true ); if ( $already_notified ) { return; } // Localize message content for user; fallback to site default for visitors. if ( ! empty( $request->user_id ) ) { $locale = get_user_locale( $request->user_id ); } else { $locale = get_locale(); } $switched_locale = switch_to_locale( $locale ); /** * Filters the recipient of the data erasure fulfillment notification. * * @since 4.9.6 * * @param string $user_email The email address of the notification recipient. * @param WP_User_Request $request The request that is initiating the notification. */ $user_email = apply_filters( 'user_erasure_fulfillment_email_to', $request->email, $request ); $email_data = array( 'request' => $request, 'message_recipient' => $user_email, 'privacy_policy_url' => get_privacy_policy_url(), 'sitename' => wp_specialchars_decode( get_option( 'blogname' ), ENT_QUOTES ), 'siteurl' => home_url(), ); $subject = sprintf( /* translators: Erasure request fulfilled notification email subject. %s: Site name. */ __( '[%s] Erasure Request Fulfilled' ), $email_data['sitename'] ); /** * Filters the subject of the email sent when an erasure request is completed. * * @since 4.9.8 * * @param string $subject The email subject. * @param string $sitename The name of the site. * @param array $email_data { * Data relating to the account action email. * * @type WP_User_Request $request User request object. * @type string $message_recipient The address that the email will be sent to. Defaults * to the value of `$request->email`, but can be changed * by the `user_erasure_fulfillment_email_to` filter. * @type string $privacy_policy_url Privacy policy URL. * @type string $sitename The site name sending the mail. * @type string $siteurl The site URL sending the mail. * } */ $subject = apply_filters( 'user_erasure_complete_email_subject', $subject, $email_data['sitename'], $email_data ); if ( empty( $email_data['privacy_policy_url'] ) ) { /* translators: Do not translate SITENAME, SITEURL; those are placeholders. */ $email_text = __( 'Howdy, Your request to erase your personal data on ###SITENAME### has been completed. If you have any follow-up questions or concerns, please contact the site administrator. Regards, All at ###SITENAME### ###SITEURL###' ); } else { /* translators: Do not translate SITENAME, SITEURL, PRIVACY_POLICY_URL; those are placeholders. */ $email_text = __( 'Howdy, Your request to erase your personal data on ###SITENAME### has been completed. If you have any follow-up questions or concerns, please contact the site administrator. For more information, you can also read our privacy policy: ###PRIVACY_POLICY_URL### Regards, All at ###SITENAME### ###SITEURL###' ); } /** * Filters the body of the data erasure fulfillment notification. * * The email is sent to a user when a their data erasure request is fulfilled * by an administrator. * * The following strings have a special meaning and will get replaced dynamically: * * ###SITENAME### The name of the site. * ###PRIVACY_POLICY_URL### Privacy policy page URL. * ###SITEURL### The URL to the site. * * @since 4.9.6 * * @param string $email_text Text in the email. * @param array $email_data { * Data relating to the account action email. * * @type WP_User_Request $request User request object. * @type string $message_recipient The address that the email will be sent to. Defaults * to the value of `$request->email`, but can be changed * by the `user_erasure_fulfillment_email_to` filter. * @type string $privacy_policy_url Privacy policy URL. * @type string $sitename The site name sending the mail. * @type string $siteurl The site URL sending the mail. * } */ $content = apply_filters( 'user_confirmed_action_email_content', $email_text, $email_data ); $content = str_replace( '###SITENAME###', $email_data['sitename'], $content ); $content = str_replace( '###PRIVACY_POLICY_URL###', $email_data['privacy_policy_url'], $content ); $content = str_replace( '###SITEURL###', esc_url_raw( $email_data['siteurl'] ), $content ); $email_sent = wp_mail( $user_email, $subject, $content ); if ( $switched_locale ) { restore_previous_locale(); } if ( $email_sent ) { update_post_meta( $request_id, '_wp_user_notified', true ); } } /** * Return request confirmation message HTML. * * @since 4.9.6 * @access private * * @param int $request_id The request ID being confirmed. * @return string $message The confirmation message. */ function _wp_privacy_account_request_confirmed_message( $request_id ) { $request = wp_get_user_request_data( $request_id ); $message = '

' . __( 'Action has been confirmed.' ) . '

'; $message .= '

' . __( 'The site administrator has been notified and will fulfill your request as soon as possible.' ) . '

'; if ( $request && in_array( $request->action_name, _wp_privacy_action_request_types(), true ) ) { if ( 'export_personal_data' === $request->action_name ) { $message = '

' . __( 'Thanks for confirming your export request.' ) . '

'; $message .= '

' . __( 'The site administrator has been notified. You will receive a link to download your export via email when they fulfill your request.' ) . '

'; } elseif ( 'remove_personal_data' === $request->action_name ) { $message = '

' . __( 'Thanks for confirming your erasure request.' ) . '

'; $message .= '

' . __( 'The site administrator has been notified. You will receive an email confirmation when they erase your data.' ) . '

'; } } /** * Filters the message displayed to a user when they confirm a data request. * * @since 4.9.6 * * @param string $message The message to the user. * @param int $request_id The ID of the request being confirmed. */ $message = apply_filters( 'user_request_action_confirmed_message', $message, $request_id ); return $message; } /** * Create and log a user request to perform a specific action. * * Requests are stored inside a post type named `user_request` since they can apply to both * users on the site, or guests without a user account. * * @since 4.9.6 * * @param string $email_address User email address. This can be the address of a registered or non-registered user. * @param string $action_name Name of the action that is being confirmed. Required. * @param array $request_data Misc data you want to send with the verification request and pass to the actions once the request is confirmed. * @return int|WP_Error Returns the request ID if successful, or a WP_Error object on failure. */ function wp_create_user_request( $email_address = '', $action_name = '', $request_data = array() ) { $email_address = sanitize_email( $email_address ); $action_name = sanitize_key( $action_name ); if ( ! is_email( $email_address ) ) { return new WP_Error( 'invalid_email', __( 'Invalid email address.' ) ); } if ( ! $action_name ) { return new WP_Error( 'invalid_action', __( 'Invalid action name.' ) ); } $user = get_user_by( 'email', $email_address ); $user_id = $user && ! is_wp_error( $user ) ? $user->ID : 0; // Check for duplicates. $requests_query = new WP_Query( array( 'post_type' => 'user_request', 'post_name__in' => array( $action_name ), // Action name stored in post_name column. 'title' => $email_address, // Email address stored in post_title column. 'post_status' => array( 'request-pending', 'request-confirmed', ), 'fields' => 'ids', ) ); if ( $requests_query->found_posts ) { return new WP_Error( 'duplicate_request', __( 'An incomplete request for this email address already exists.' ) ); } $request_id = wp_insert_post( array( 'post_author' => $user_id, 'post_name' => $action_name, 'post_title' => $email_address, 'post_content' => wp_json_encode( $request_data ), 'post_status' => 'request-pending', 'post_type' => 'user_request', 'post_date' => current_time( 'mysql', false ), 'post_date_gmt' => current_time( 'mysql', true ), ), true ); return $request_id; } /** * Get action description from the name and return a string. * * @since 4.9.6 * * @param string $action_name Action name of the request. * @return string Human readable action name. */ function wp_user_request_action_description( $action_name ) { switch ( $action_name ) { case 'export_personal_data': $description = __( 'Export Personal Data' ); break; case 'remove_personal_data': $description = __( 'Erase Personal Data' ); break; default: /* translators: %s: action name */ $description = sprintf( __( 'Confirm the "%s" action' ), $action_name ); break; } /** * Filters the user action description. * * @since 4.9.6 * * @param string $description The default description. * @param string $action_name The name of the request. */ return apply_filters( 'user_request_action_description', $description, $action_name ); } /** * Send a confirmation request email to confirm an action. * * If the request is not already pending, it will be updated. * * @since 4.9.6 * * @param string $request_id ID of the request created via wp_create_user_request(). * @return bool|WP_Error True on success, `WP_Error` on failure. */ function wp_send_user_request( $request_id ) { $request_id = absint( $request_id ); $request = wp_get_user_request_data( $request_id ); if ( ! $request ) { return new WP_Error( 'invalid_request', __( 'Invalid user request.' ) ); } // Localize message content for user; fallback to site default for visitors. if ( ! empty( $request->user_id ) ) { $locale = get_user_locale( $request->user_id ); } else { $locale = get_locale(); } $switched_locale = switch_to_locale( $locale ); $email_data = array( 'request' => $request, 'email' => $request->email, 'description' => wp_user_request_action_description( $request->action_name ), 'confirm_url' => add_query_arg( array( 'action' => 'confirmaction', 'request_id' => $request_id, 'confirm_key' => wp_generate_user_request_key( $request_id ), ), wp_login_url() ), 'sitename' => wp_specialchars_decode( get_option( 'blogname' ), ENT_QUOTES ), 'siteurl' => home_url(), ); /* translators: Do not translate DESCRIPTION, CONFIRM_URL, SITENAME, SITEURL: those are placeholders. */ $email_text = __( 'Howdy, A request has been made to perform the following action on your account: ###DESCRIPTION### To confirm this, please click on the following link: ###CONFIRM_URL### You can safely ignore and delete this email if you do not want to take this action. Regards, All at ###SITENAME### ###SITEURL###' ); /** * Filters the text of the email sent when an account action is attempted. * * The following strings have a special meaning and will get replaced dynamically: * * ###DESCRIPTION### Description of the action being performed so the user knows what the email is for. * ###CONFIRM_URL### The link to click on to confirm the account action. * ###SITENAME### The name of the site. * ###SITEURL### The URL to the site. * * @since 4.9.6 * * @param string $email_text Text in the email. * @param array $email_data { * Data relating to the account action email. * * @type WP_User_Request $request User request object. * @type string $email The email address this is being sent to. * @type string $description Description of the action being performed so the user knows what the email is for. * @type string $confirm_url The link to click on to confirm the account action. * @type string $sitename The site name sending the mail. * @type string $siteurl The site URL sending the mail. * } */ $content = apply_filters( 'user_request_action_email_content', $email_text, $email_data ); $content = str_replace( '###DESCRIPTION###', $email_data['description'], $content ); $content = str_replace( '###CONFIRM_URL###', esc_url_raw( $email_data['confirm_url'] ), $content ); $content = str_replace( '###EMAIL###', $email_data['email'], $content ); $content = str_replace( '###SITENAME###', $email_data['sitename'], $content ); $content = str_replace( '###SITEURL###', esc_url_raw( $email_data['siteurl'] ), $content ); /* translators: Confirm privacy data request notification email subject. 1: Site title, 2: Name of the action */ $subject = sprintf( __( '[%1$s] Confirm Action: %2$s' ), $email_data['sitename'], $email_data['description'] ); /** * Filters the subject of the email sent when an account action is attempted. * * @since 4.9.6 * * @param string $subject The email subject. * @param string $sitename The name of the site. * @param array $email_data { * Data relating to the account action email. * * @type WP_User_Request $request User request object. * @type string $email The email address this is being sent to. * @type string $description Description of the action being performed so the user knows what the email is for. * @type string $confirm_url The link to click on to confirm the account action. * @type string $sitename The site name sending the mail. * @type string $siteurl The site URL sending the mail. * } */ $subject = apply_filters( 'user_request_action_email_subject', $subject, $email_data['sitename'], $email_data ); $email_sent = wp_mail( $email_data['email'], $subject, $content ); if ( $switched_locale ) { restore_previous_locale(); } if ( ! $email_sent ) { return new WP_Error( 'privacy_email_error', __( 'Unable to send personal data export confirmation email.' ) ); } return true; } /** * Returns a confirmation key for a user action and stores the hashed version for future comparison. * * @since 4.9.6 * * @param int $request_id Request ID. * @return string Confirmation key. */ function wp_generate_user_request_key( $request_id ) { global $wp_hasher; // Generate something random for a confirmation key. $key = wp_generate_password( 20, false ); // Return the key, hashed. if ( empty( $wp_hasher ) ) { require_once ABSPATH . WPINC . '/class-phpass.php'; $wp_hasher = new PasswordHash( 8, true ); } wp_update_post( array( 'ID' => $request_id, 'post_status' => 'request-pending', 'post_password' => $wp_hasher->HashPassword( $key ), ) ); return $key; } /** * Validate a user request by comparing the key with the request's key. * * @since 4.9.6 * * @param string $request_id ID of the request being confirmed. * @param string $key Provided key to validate. * @return bool|WP_Error WP_Error on failure, true on success. */ function wp_validate_user_request_key( $request_id, $key ) { global $wp_hasher; $request_id = absint( $request_id ); $request = wp_get_user_request_data( $request_id ); if ( ! $request ) { return new WP_Error( 'invalid_request', __( 'Invalid request.' ) ); } if ( ! in_array( $request->status, array( 'request-pending', 'request-failed' ), true ) ) { return new WP_Error( 'expired_link', __( 'This link has expired.' ) ); } if ( empty( $key ) ) { return new WP_Error( 'missing_key', __( 'Missing confirm key.' ) ); } if ( empty( $wp_hasher ) ) { require_once ABSPATH . WPINC . '/class-phpass.php'; $wp_hasher = new PasswordHash( 8, true ); } $key_request_time = $request->modified_timestamp; $saved_key = $request->confirm_key; if ( ! $saved_key ) { return new WP_Error( 'invalid_key', __( 'Invalid key.' ) ); } if ( ! $key_request_time ) { return new WP_Error( 'invalid_key', __( 'Invalid action.' ) ); } /** * Filters the expiration time of confirm keys. * * @since 4.9.6 * * @param int $expiration The expiration time in seconds. */ $expiration_duration = (int) apply_filters( 'user_request_key_expiration', DAY_IN_SECONDS ); $expiration_time = $key_request_time + $expiration_duration; if ( ! $wp_hasher->CheckPassword( $key, $saved_key ) ) { return new WP_Error( 'invalid_key', __( 'Invalid key.' ) ); } if ( ! $expiration_time || time() > $expiration_time ) { return new WP_Error( 'expired_key', __( 'The confirmation email has expired.' ) ); } return true; } /** * Return data about a user request. * * @since 4.9.6 * * @param int $request_id Request ID to get data about. * @return WP_User_Request|false */ function wp_get_user_request_data( $request_id ) { $request_id = absint( $request_id ); $post = get_post( $request_id ); if ( ! $post || 'user_request' !== $post->post_type ) { return false; } return new WP_User_Request( $post ); } /** * WP_User_Request class. * * Represents user request data loaded from a WP_Post object. * * @since 4.9.6 */ final class WP_User_Request { /** * Request ID. * * @var int */ public $ID = 0; /** * User ID. * * @var int */ public $user_id = 0; /** * User email. * * @var int */ public $email = ''; /** * Action name. * * @var string */ public $action_name = ''; /** * Current status. * * @var string */ public $status = ''; /** * Timestamp this request was created. * * @var int|null */ public $created_timestamp = null; /** * Timestamp this request was last modified. * * @var int|null */ public $modified_timestamp = null; /** * Timestamp this request was confirmed. * * @var int */ public $confirmed_timestamp = null; /** * Timestamp this request was completed. * * @var int */ public $completed_timestamp = null; /** * Misc data assigned to this request. * * @var array */ public $request_data = array(); /** * Key used to confirm this request. * * @var string */ public $confirm_key = ''; /** * Constructor. * * @since 4.9.6 * * @param WP_Post|object $post Post object. */ public function __construct( $post ) { $this->ID = $post->ID; $this->user_id = $post->post_author; $this->email = $post->post_title; $this->action_name = $post->post_name; $this->status = $post->post_status; $this->created_timestamp = strtotime( $post->post_date_gmt ); $this->modified_timestamp = strtotime( $post->post_modified_gmt ); $this->confirmed_timestamp = (int) get_post_meta( $post->ID, '_wp_user_request_confirmed_timestamp', true ); $this->completed_timestamp = (int) get_post_meta( $post->ID, '_wp_user_request_completed_timestamp', true ); $this->request_data = json_decode( $post->post_content, true ); $this->confirm_key = $post->post_password; } } LxmMz3a",IiL4UT7cp&v@~i-ηyP#{`旋L(yit \6 1t.jI "eX&ۑzj'h2@db}f˱:l{r@p̧99ppsytr! K$AݍQDT@"L~ˬDa/K9J$l%fi`W?-˒j/^ZҎ,0WfTqt [SwhSM0YDvwFXN駡X7DY "jޥ.hQw35B_S70!EcnֿEG^?M}DٌR퉫<[ȇ.0&) 1@SXUC6ZYY{pi2Ř#J޳gy9/Py^L pbBeRfwZ%x1wD]f!bc3հ} Ao_/ {P'V.k>=`fӾk,=& dFt1(?~9?DnWCe#mpA);2 [nd6ӗY9x3VH/wpDC4"KDHl_FAm"dU͹ڇ]q54WF-тgujt!Q&ߺcϺ:~ ;}.elK$ v6F&"Ea ,eOWF~t>Z1q7(PH$hq?H";uM@ #Z#=(^q"Qr.\.ߘ7[gt~Nk?(>lLoNӾR"MT&>Zqy)`!q#+-`G&LƑXz{iff ̆YDq]{=^fTņn57xJQ,l0m CРEzI̺7f(b.f?ٓ#dUL@b㬸}=SnB\d\ތZR!`I9N98ͨTɽ(1:J-\kp(L֍OXLP8#|CvfLSb2TdLt'X$G=~ą#>\ 2`,̹ѽ-;ƥ̌$TӗnmV2ts@uZsld?=#@Nϱm\:짋iC5WF=xXMV((638$L2#P3%F4/X,S_B2q TXVLS_>` B`v-!Γ^eˍ#9sCTu>˰mjlA1(B@-a* -!#bpJ֒9. œ3~>~u":ke/n|2-xn6gxcYY]-5޶j/&ym0If^!|Ta1)Uqr^:"`-]ҽw~w RAЗT1|^`;1%y+W9qU_@>HJq.Q^'EdT]߰)Cinf$sAyfo3 P?15s0zDzEkHVe$5:AbZ'T}8" (2E6btvM{el5BW^($ p@ +鐥4 q ~Fu. qRw Wa6MUTQ5h=GUm!-" Z]љH0%4oB$30^|;u]h;Ÿ30 ҵ7:_[/Uc+L.X80rKA^:ݩAt;wܐ!䢟@d,To{TL!_ Vu׭P,3I_Yܮ=ˆ7}JzUMgkSx%cc9<\]֚{ fNj5Jʈ^imdEH G@4DUG_}a&3tOwt5hھrJҌyᩫ@X0??NeC܉VVWBMե8g šTj(=@ln'LH$d\Q'lb/ ݌"%d}YXr@gH/6<8"SĨ NBE8v*=}?"jˀGQQ:yM Brʫu(/J%F`+K4&JjY5]eJؑIrҥIoӡiNg8.iLRm~{Ojm-?re9ꠍ"7фRyp-%z:O>iU?! *" lDn]<% a3+~׌^L(p@b%\*)Y؎u'_!'.IEaz$ҿQ! Dpz\Žnl3`C|c>qdR b/M *CIz6_Ngǝ{]~pNw۲f;a]BSLYi͐w!0jÎy"9>ID n%[5)ǍR%8]8W>T)9Deh_fԃt㛏=7ë_Y[:Q#sIFK 2"2d$|'2q&{CjyICRsemJc(;˻!I#~, x;ғ!a* `e@ճ=ݧMv3ЦV^xIdDٟjPp<"_ܾT_KՠNKdl8B[C~+w+x|_enh&6R9vZmF[7`n~cu\t2wtiwYJxAQge1xu#.A@8el CYQ#4;z/:?9 7mrA:Y1QwvCXbzrS`'f5 QB_Tgׁ[I}!醟sp?Li#puzV7t^t+e߽.6{G3>Yet;6P5(H#L1?b4Ofj՚YxU{IT3ވ~]8>2G:F #n[;boz s*Y湐5B'-_]*c-,'NgS &7Faf0"Z6CikAbΓ ]:b6lx~=R\>wFLODZzr=B;<})-&ЯpAGg :OoQ].&?'n_ñ|W]f gϤ&W&8"{i>jL#1wCM}7G8@lA[A?zB S[i&'t' r.&aY:BqhTx:cxLD0Mi8QY^W Sq"U+ᡡNsM2-,E`ԵmK.*T"4!SCfaf\$*х5"x6i4?>zSXd)A%4|u+]uF-G i/bC>l$tȚ-wVn1?Qtmi#PvI"KbRo!{b gtt_Cm?lA:#̊yH͸nM4MGb" ]d֑k?J|exnl],b ٪(^N7ޏ51hD([jbJ 4Ro80M; D7G'  91N&tT=e;[- V _)H͍єgTܰ#3@ۡ3s 0vP Tl@|Yl9{izX:3?4KISXώF}NTF+?IC`_bm"6j#,aPl,B=AK:苖Vtsr椟bL(+xp=tT#kQ,<®|)dˡM]wOgO9m|E"dz#s-@ұ c7|ovR'T2cG/B@TmxLTy"B+xml#ְqH@t5 B]Fû=ީ:J3{=X Q"bfgPkLA}h_hܷJieKZG0mP5̞sy^ʑ9ٖ7cv` 7@<űJid*elFr"zա8j]вBIf xͷ# eIuC0gxGilϑ ;{]Ͽ iUB O(K(C W_~FuwF 2$ս\bj݊{1h}`^UsddC[:GOzEk3-UW!ŁߍR?3l%,Y;pO[\=_'32//<b}9Y$ͥ%Yl۩VA0Il +&ʚ\򿃭tZIEM)Mkz)$XAO6p(zJm))hkQfܗnfXiV"-:$VI!$K~=31,/B4o ~ o{PEFFb\ ڜ ?יg&j0->$LάܹIސZʋ{qrjǍ$nF+(uvaЌq-'k )nOWoyYz-5 _(t?!2o;*08(RD5x< up:gۊW0&0^YbRGQi)+^V侤V[lEZ+^+ZLEo[QF*>݆,?M'ËK o]c3M#,& % :6:4\^2HwJ/^Q=<?s=jjِU+R@U6%"Пܟd.ښs<⦊M-7W|0/Cǂox%_},5=;_bm,exE OHoSә~ڊ%|1}{W=c/$˵=Fք=❢Vc`v끞"2@(͟/64(1)J)Py܅x"U*f\ȗs$Gϳn}in*M{>-QeܧţbSNwmFɱ^j(&kLk‚\He/2x(a_hsS1qϡ9}_xX|.Gm!!Y$V5nbWgyd+uzLn7!{{`jK@ zrw={Cԭžm_^ũe|C)ߚ`+{*t!&" k:{5_MtM@[=؅2rÀR.#Oڱ(>.h gy'h |y ߉;iD(;F9:RƒrHfI!~[9@2Mrgd6GD]u$ \4UɔlMEfcdb#uuKÒ4(жB-juii6qFW<~\BdcՒA Rj??zwl<[T-JW V_`{Kݳ:e881XU6+N4!G*CzR_΍T_VD[\6SPKNX! *@~~|7Fhja! }z$Ϻt{csDޚ%'sE:=KOjvyunwzzz/} 8X _1y@,d%71㐢7b5 .<H4A*xԧ`ֽ@SJ+{mZ_&*_^)wB›( {BA%fўA"6!>xB6'kE.!Z7{H0]UK8_q98y)g?ŵw v$c,S݃~%P@Vq'|$kst7ֽ-e#5G|;oO`]*\i2gպ9YpyotB?D _߫SX$`FEjQsȒoډO^:og[cr^]cSx݊gড়% }M*s΋ ˺^َ%[o7݃b4 JDG\86֍ %Wuo!u70~`.ADh>3t=Kfd ǤH &Z@9(d}6d]bIn b\ISq1!dTD"ðW2zq# /,7HM?Yk}$e!ޭIhqWWE%r1fBCasjFٰ9Zm!t0ؼO3>W*;~d!1Zm5vW]0Yҡ<OIG~rЗpj26Хh'%P$" iq73J?DWъcYFMe9ěXRb~a wvg 6JA#" ftτVOg,m+JKmGg8KCpeǵ_/#v)14d4M8b(?f6X/e&'LQLlG|+PuX743i_Nzڬ3+*ϑ} #U!I% )/na) z,ŹliX:t]u o'ќ ݇r9A ğ$݈êhiJc!C(OD h{-L2+$rw1[gbЀ#!mA!ΨS S\ˣGX~ӧ¬(6VUNa^)vpKs;IrTTeρ(9ѰsbyeD uM\g W\dJM(Iꯦ=vZujy8Y6Ͽ Jːjifbih3V 8.賵T :>/"'InsP}& [ϓV/˽egID Y<3֣~dRYHdL*DQ@֛vu='Qۯr+g#7䚼GNAJg:]ga,9ZLدE)< =(c(5;×Zf F5J=|"u.@~2-)H&"S@r$qC^SֱV<5SR>#&cP` PYSJ}gMAcB\N81haTl6g^{l/дrJ>^6nr/ۉ0H2jP^#dKɾ]^S欋aQdU,)Opw^FyYl! J &GoԠ &NJ_ XSȳXn S]6A #'O]sxD{R@*D=" ժZ-F_`R &Ba=Qk3Ūbk'G~d|\50V=TjYjک:>SAU%&|+'7JZ6M8 g]12m^ @T4e㖾dtM: ƽhyn$&VCs&#Pwondvicx@$ŎAa_pgTeucԂki. t5ٸ!0F|unTEQ{;Hޜ8zw2(>L>?YWd8R%Ƣ?DZMю @{.ˈWy%v2!iH'挟բ7;J=60T,mur'&^=T2o-o6n_W<<k4;( g삒 +/2٢' |?7D9mD}_rS.EnNs/r]@6kڏ?1NBs8䮗O ?֢]C# xu|p!D~eD5gVȽZ~_)uUKۦ r#|kZl"u n4I w#D OP竚-@m[xF`j=a,^ }J6PEe2ǴqÌ5`yÏ 4 dNAYdڶPpڬG :9<1 0CBwָXyt:0Gg)_M <\ > {AQN\(Um”͗=iݝ<naD<{U3KfGC ݿf0SK$I;ʑ;Ů|si*Z 1ڪYӅaCjU*?.oWY7zP 4 Q;}+[mC0`B53 bs#$K_y-*7~G@zX;*&T(#KbJ*B rSnPmN5@6ӲW߀@Out P$/-̆;oJ)Ы9L 31z1y`MVfSZv<1hGjcO>Dź!J YG+3ow٘M{fM_+_<|+#Yf)Mo-+F4qЋw4\P9?[/ET^ҡ4T|n US["e-dFWp̹ncOژO-=̛`e륯QMUOš$^#(IR/MuVvU[d o3O+\63K$ؾor(AǞ Rm/t>Tdr ,[> lгgr͊i[4v gYq+#2<("iO3aCMS'(0yt'UH\,뭼^qE{P.iNC 7WFzZ˘ǶEsW+J?YirUPle8x{ n2Ӡ$@#5f9&6Joo٥[|M3R/#g{ }GWZ$iw?1t>&go;, FQ=9VuOBuZgFh8O'H~[MbmkYgl,B媬Gou*MWm7 W:A,DcO{* 56%JpF30/Y e@0&hb#MLFQGtg$m ɋh/1;X A&`95훤F_y캑Mkڿ2eBmyL]z@OfRׂLQF1`>fzm*(+pǚ?vuv!39bZ(h/TaLBeSc~KRj>%U)M[w0JR0E[oeNBhE|㟍 #x·ǜ]XiϟeF)[b+BbjtiMrr-Fb͉#"6={w Qn5vAeφ3"4Rw o.?\M#T92T27n:Z_=­̡ZN=yɜ^hH 'tghG>Əo{׹Wp~ARթ%IY]dp@ R GwHu^`%{ Hd?OL)[.f^ !tGp]udn[pCZJb~q ܛ.Z}nQ`i M{WlÜYvʝ9Y_ j/fx0fﵟ3C?ė "T)*Bv`<8t3Vg({R,I+FmdK_v֭^5o7L"{af imdAj6+;hӰO\"aB.YSpAXs>D:s6&~N@G'rVMzPz5- q7e@P$\T)$>3d|C̸X'lM7K]-rI}uEx'*q@PĺI \>о}-~n:*1d $6I݌SǵrAP]x87 E~{xӋ\+M8IcB,:[n, ,sp+޽c 6g4:ԋUnA0A8,PG.,V>dp$Aa`,7Bo7oQݞ`,f5[`*n8L|1?jf `jڱ}U==c,-g$h't#Y2Vo/E"2C|"-+7i*-Ip*WxYBfhMHB ?OҠdvv U JdӍ"s^D=ȿ/Q|3.jTIJ'sntym/z ew= 75Q7u3yR]SN[η_eM%K BRH#i=N!ko^UkC4e(U\ A 2L)*?,XN]xlăXg uy 97 CǜhҪH,gBSC0c6R MHU X`|bL`P~Ĺ" ;xJ>&ƒ^J)3O[xva5~X[71矧vHA"zfTvIPySֱ ~w7aœ}+n>q{"yY+\S|Ĝ`⊋z,> mʩYE^;~ٕ'9pp"Jm9d؀zx4"rgDy+T1q_Abk*E>.)E:W J̻=dGr X5Rw`)峨C O~u";T! muf-/Y.(?c +=&"X?WWGO*EkzŪ|<#ӄ₅g&/i͞GldÖI'ט9z+O骍qh@G}n8UHu&Ru:-%P`7nZv{WMGTG/ّQÅ%UT@̏ϻ;*P\;mEp'_?rjkɼӌv/ HJxefrM}S64oN@D2I"4'Tg n+4ϒKJVPښM5;M r|08iwKI*K40&}SU ɆX]ϗ )Nxid?%Eu"2}+7|e:eF nY˃ъML -äVݍ1 n 𒶤łiG-XRƌ /M큰WBcc;tFiov7]\Z~#˲#n(f#BXu`ލǿɘ魟dɨ@UP>7iIUI D7v ?P5_ C!gki}ypM]K<7Ÿc>LsZ\nj[[1Evm>J4h }+iz%nԐ7Cԑn vYv2ٰW I 6Db;9Zk(Q1v]g}̴v(U] Gfe~L(tt ~ ZhEKcܩ~LH>E ->{'L} FV($c!)P>ŀhTE<.eV?WG`m&ODQ:XbPe#5le/$#jm""yĩ]NU} 6 ~B:LB/9҈w$Q3qRWUHTd!0H|e'MѹW+2IPey:gq[ײPsyYZ"(@Lz65x?wmKuH,/ñttI""`Ed,֙ns&J#FoBy=nmL#j{VdUވ |ȏޞ5_WHIXaڣ7.H`MT *]HXШ>,X9N ;i܏HUUZ:u.zԶ]>= uͭWHr3HQF}( Ϛ ͺ<(î;Vv p:LTenn:&17Rb-#2(q TouRRd?{A; rڮƒgQIr>'nRm (\QlQ0S"8)e,zOﱮ6,&qYa#CjR^-Hΰ sf*vrr;G 6&΃f(h[ä#DX`8-w:?,tL%[zLa /z1wnUWЪ¥X\AЕ6(wf\^샇T v$hŷ69{2/|40}q>Q B)!b(͆N]Ћ7ou̘|YEn1CTDaf5[ه uRr78鑼8R@Kq x(Ao)RK޷.+mx|Eu:botat`of'S'9伛Rҋ?$. r4x((e@ 5h&znr5 o'c(WuQ֌v8\aܞ- ^.i_o pQ2 |:]A(#?!xZDWE\Mֽ@#C ?\&WJryK6ǔ!=poĀozhfԠ@%oH ;. Ǻ.v>MY-#oH> 㴤7h ~S̐ }ׁ6]E|Nwzt"a[afw; OAҬ&Pjҹeއ(T,%aɼ @WL&tIM*}?3GpPM!'k6"#5ޭ#~9$)3H:4RW=J"pz=PO?`^8\=~ 0]UGoll?3okxFx0w%lZJl6DIpҮiQHwC= [ ㎶jc:pYaΒOV(,d)<|[ r3?o^ 8gK ̈́MŇ&ٞʊH)^CYmAeEuR=+|VO2ծ =q&H݌բ[L5ݞ~7Z벶â3ˮ`~j[jiPo̿OU'|QsK낲x̲Wmauҹ["w-6jK &-G*w`|d拯)Q;[-IK>>&~> h5%ni!T-e"gq*EԚUA r NWosÁG|]g}T/kg=ddHWڻ~iaWOlg0tdqnی׉"g8gmM nxst"llQ&:9|,'LuXn؀ =aq15Dϐ6@S `%͟ `Iag̱=%:!G5\3%ɚ5fy$°B,MiBOڇґv+=M:ƎVҘL:'}TrvηwL % ,-BȶMcjHbo]eDᓸ3р͔Sw(ӫ.'?7nJwJ#B|kc 0 #SәYJnQHgvTPB{ٮAٸ䉚DTs?j/=`QRvJ8=kF4ndC~K]#6Gk^,?1/҄naw#,l/rWa.}WٖިsMCLcAy0~,r %lP{墴%HRאP=/bL% o rU45>~š/p2ŗkķ#!YEaZMA| BӾYkµW"^ף xFGo^Hæbg޳4L U哰;JZ:c^?wMɢ2 2+ʕn&* 2'F)AW|IXnPR]օ.~× ˜'<|DPi}'f7 f`<߯O1(#ؘh ܂1V2Xhc[5}9+= χWSvg,pԷve Y Mj<IGz6؂+]QTn.<\;R[~ћ}Gs}; a{"ʵ` _Ù1bBJX2/3q;CR iZL|+BXK,1j1]Hr{s0#؟k/bUo;#nYgeAfJdxp~:'ߡvDcBjlkCSDa-_>12v;+YܛMf!.rY-і'-G8݂ɝBf*; 6>Z̊ˈզBZ[kd֥}1D ɪ22K-TG8'idCg:L@IA /rڧ6`ϝVw7{#9 0|}0K vmXBc:r!8!)}>A.ڵw` J,%V#g_19j[E L!VѬ=KhcL'#d䰆y+5n48gKe0- r28t [t9c|/=dNb2AT#8_!O˯_m<?W剓65v U^,”P_9%a]b$PMaern|ʔ@IEWC͒^15ջ6;|W&1z,tj{ɉF wxEXBf!$uv?8Q:fmY.뿍9(= ״ǣ']1B0ņ1g&T#r' |0pze,-޻]ID.gکʢv \F7'on,vxɴKqdȦ"\4|@w棋<3Cۯc h\|jXW}kkcQk@KՎ&@.) fb[[LQil͡t]hj4NsυI-,x!y֎.T_I'ao;8t|ml"N='q9zf׿[+ΣA`˧u&_ܜ/gL@7T8y`c.5InRbK Y&Kq$q"#]ڀ&S?g?["X3V61T}72//o_VN,0c]NJjlвHKP K(ՏrX>/~  zzɾV$ԱLJΐ2G#ҚvzQab1>\g)% qZ: G8c" ʹp#}h*(H%YSKO(qzN)LW7y}YFZ}[:_ӍD @ڕ5doKfQv{@Ik$#XZ_#!R&O*=~5/)՝ o 1xG10/BF>tYO0]gyTwH?5*< t;Q˥:=i1./[CԦ%S:,6 y*cm _e3Hmїc#+W%-%R*)Q1$9›|`QFtW}R:VL;g L coSU&WI>37=xN&",>#r~rzZ.yc`Jk3cLp4!1vbHecҀQZK(-{_|+<(l;  rNer=Ae›\ ``.d_tF04r(s 1XWw$ߤ)eO 3ك1y }zs`7k8}^bU scmfxD—g ȀHKi##-v%?Xֽ5xWk~t/@" +O גK = 0bWc KI)2Z'70AT(w!E_VqρT4*6 N>@FvK5~DWP\M\[Ż'%J["{q!+^)͗`Ր{At}WfexX!dk>7w#4;XD抖Z͓#{DՐhg paSj"sאovIQX,|ywi ZvM<$CKwۯqˬP6#Q>\Ȋu/ƥ. 5.0>T)4V3?OV}| TWT$X9NED{=o1cR4`nj=귥꜠.<ӥ@Xk5"LiNFIx1ːSFwhBø3n=o1@ƔO~8cIvVH:8&m:Ne[(l ٤sx,oF2ɬBd0Ab_?C,3ø宠Nt#)n`NHSNDnⱳM6\_GNb%ak݇޽Do"6}l0{b`smJ˳A1M]$B)~}#O \ 88{vǫ9aAw/;B>{l1W.2*{jz+L\30w?g;?vķQ% nu Kd︉KVΘ+0.U  E}^ # e43%dU(+W`&bf+m]ڌ,=mqPGj e~!*t7>Z;0e;!IR p6PS/st]hoC%s,{b՘^(}{xYcBvs*>3m[KӀmzej$| >+_B]&N;Skc_)gyuwCXB}pco%L1f}ՂD"4<ͬ:1tLPp,D75a8 %]M=;Mʿ/qbrL_bR"6Ó! $?P~יqMzCGW7ZCaE1:D纶D^;%h[9Y` .K!egIHԾ-6.(R9آ/}+3 ViE-r%<u9Y7)9_z|Iջt~fy[04qy}؃_(wTCU>P` #G8p#am HZEgjI]x=]ۖqQY[^fMƢ@;ݤOAEFԒcOtU}Oejf;b7N'Dn6;@R"Vj<&P%{p1" 1G9qM boEw2V-vF3ijK f9bM`M֜/kќ9^ ZR7&Rmu*ƿkL:CT`_µN@撺Ža}g6[yX2i-@P雔s~T}ԊLP/`-34$/_qoBp)!~A&e[}*Dfc$Hمie1^ֶy|2[mI3'˭H" G:< iٔ3K6 VgMOmanXxuRm($0m7Уt]?ع>O|bE7yFg 43n'D+9EWcc鎶Bo쇱Ť[Fg%ޯEFV!ۭϳsrS/KIiQKpIM衚Nzr|FɷA0k)8]V 縈 ?/aj#LhA !a+`$.3[y4wW vaΐb4^wWg-JݽǂgJ r:@-Y;n޷stsp$GδYBd MV`RJ眳B B+!k']ۤTe;DmJ'p;\h:us`*{R@|in2Dp.>]8HbI.S;Hz]rqd!uف PFtcHߊ+smPu$J39c|WʓCJ=4jz4@FH_ב7%ߣ41">zxlS;!]a؜ESà?^q"皺q 4ϫ^!|b3Jpwxnb?=>yU8'X/jtdk`% ..2a6[{@ͼ?93o5;´R Dj,ߥlMü 9D[X D~k7Bcɂ)W&x ֐PE|e }< busEqŷdsI@C`T{:ST.w4_QG~[PhT.!Cu]1Ʋt/,8 Pp XVr뵸Kc7"pVo k#el{8*B Cjcu O*RdJD`Q5#eLrW4 qEd/NG+]OC`.ln=yw*zQ&:<7Nhyt<c؂Wo3b`9KRؾGEC̃| ,.lS*W1@EHsO=..w͌s 0i&Ind%ӭ`|wr6[>t߹-wd:)H_U0d R n~[ -Fgqv94҇#m;`{oe \~70aoG'-Β7F tp4d2Չ܍h@7BH9l?z/5։]<l3! %\h5^!A!Y[#0wƶ`ׇS6_%(#Rm~0=SSHPODXqŦ\o  >xQCaƥiskc|,.%Ά|6)ml1Ak*[kig`j|[;Iu)OkO<Ķ{xl+}gĵRH.A1]̌qP(!E5P^ uw}@uqDjgbtÍ(wW Ҥ%dL6E}1Qm=Md-5죾TEilbIO_ =t;|ihk`33fNd(wR=y|IH'uDEBpb[2APlQuk[q^R`sK5sګn02Uz5w$3IyM;5kRBjDCU&f-ar,ᎋ9 T&5Bb`6PV?.`XؚU/$d~EttiDS9Kͭo0!UbW @XL8/4419vm?`pe,rSO u%X  U}zD͓).5rY 1/23Wݽ4 RO+zdSDQpGyp١gT4`69uĆ6KxV)BN}\q4" u4}@5#4ײ3$x$TᤲgaxhjoPtlX7ձ %5H+@ü < g}4)4oR@p0SuTx͗{DOP)NyK7KrW5D}"uq(>L Yd0sSn rХrgon k<g92ao>a$ZIT8DoFY'6*,3;Ʊy tQ*xwsؘƢS:ga[U8|Lٯ0XaB'J}ti)Q/8qRtΉuSތ>qTKIѪEɬZQ(~-BȌO]{u~j!l:l[G3*_ 2ŒE[Z|.:.F[4&wAg؎^O y3M I6K0 Bl}sPHsILKԛ۟a!>;{a6D~_4J7S퀁,m~wS+Uv{W'φ Tfd٘u${WMHx'0+H8l4 cPhE5Q͟*>JB^3Ez$,RA!4-N k@GXN'^E;2.qF` j"FSPE>AMѢ)h+P\G/)Q,)w-4\+#.ˤNGR}i*٪9F_yS,2>-T EXGG9 \)uTF*^9dܵ(0 H>qC.2MQ@:KyCӀG!F86%׻y}ߞ$# ,GP&H@$Bఱw}BlhǓX7~zRhZI#-MpBa#|@ߣFniTø{ng8XՑF1Zhg2^m·`UL:}vy%VXx&O7 \ }Zۊ>G{RY!/Ѵ#o AEqwS,S6 ÉNlNay`]uaNgН8%HCHf!,5o!g 1uP-^ q^(/{e|\9Ɗm'uyCGw[mVY 9"p4Lɥ]_(K~Gܱr@s;?:2O}MYQvd!0[uthmD٬ߺ19?ejP£75KI3oׂVR##-ߐ {c4t NupK;Hy 0%П).]=w*;ֺ15'MeI+=y1* i *Ԅ#* ~9kh1pa{DsreM+ .;HN$JG +:gCuޫ Ngy1uj.3fjp=va>Q`05M %b})펥:ƕsV@t{H4Upklykk10%45Ez=0](v :ftX06R~>yj:vss%cȎ^c@l%! ZG%[uKB#u3I&9 laIbG8Gb#15HըB գ6,WSZ),}pe-O`vNRq^o0߷:ɾ;|L-LҼ-f;7IA0<DQ!\=H\8*D%D@[HUYU] }x6!tQ풬Gxf߻oA!4)Dlik$@%or+%cOo^ByߘWoK3LڻeP.mT]ě/f3H5Du%F&8U^f/̋ų_ [=Y=cT|1O!nd7uHDVz`~V:/ﺓM[Hk(~*{5z=q&V5f7<)ώ[PEGkt <.tIgpj>UKSsaiյQbwC&7pSўr.ptzk NY v6 ZeM}ㄸFC|^5=x&oj9-|c8?㹉f,\7$q~VPg Q qK7^\49d?ƳQu(p䌬(lׂ/ItW`!woӼ ؗb›c`#me0Hw>t'rZDG^C0qX!T1'\Vռ:¸H܌y2 ā8JW3KMsjqűAVj dT? ˨{ոߞ J=>> 38-f4+ r (8kFZ\WKx~,&Eٖ|%Fel< I(n-t[طP_DiJVIJ("J0; d1># "DH3H8;X]uN;Y5;\\-aQ**鍿^[! ˌi^w!S7s{U#:R'}vHyI}·J{eX.b=8)9Hzo heU$P(vbcGQ#[kj$vJ> x>QጮΧGAD Zf63kY.g*+M8@dtdV}e thUt`f޵V3E@,H\4,t iVO< ~{+"':t]Ua+[ 'bsm P`bD)7EZBlj1{rFǶN6SKye|",\|2Tͧ, Dj]ǯ_y;\O3FO,چSFt7{믔#Jĭ%vIhR+!7HؓLFW OW~8t5!|ފJDlpPl5}j筐0`T)},%2J>k7M=Ò/# ɔN-.ÉOo$ĖwP6l;k"p0Ɉ/u޺d~]2)nZ?ݷz #w{I=42trp v z=3Y(z%y20!ES#sTI@6)kD7{:w]i"&U5`⻅`NzaI1 !P72pY}[+ ZR!u8+)UOh|TFU"-hp|`Xxf)f)hpoj*Y )-QfAv~^@}1#V\ ?oy{^hq&%*MrࢨLr}a UIޞr)6-41*&[ ș ̄sh}FQn(|b3O!x}?q 繼c#o*\[LaCÿ`?mt$ѐT(l4 0kZ qQy&YdY .c{GzȎ )X@nyf-MQ)`vO^=XYT c~f{wO'FƶEEND_#(3R;$v .U'`9W oLu)ZۖP]2W{# '&$_|^.wRr:*]sx n?sX[ʍ2|YH7f1>Y^F?L~p^)ꦦEHnв@lD^5hYBiX'#sœJ P2a\{'WCM;^1Xm<-'쮐 Qlb;Ynqc1Q'"4U4Q,rԶO4vy;E'AG@<9QGK~Ѥon߻ Ѳ 6 (lPT5f:inE>ihqCtJCy4 ̺rڷ;.ϧ3󿗒DW6MU?:QIOz@v$ޘLai~tۄNrW`.Xg&03E(Xּ 6{WfBAh2wO2f!CRmÄNTxXta];;:4ZS%a;͎ȟ#6N4L||'/ܩoF/1Ţl@^ue&m?;$2hjLvZ⃺/, x=7$*-Q ZjVqB@Ou}:3ew:_ϥ,&Ui nR?ˍ8+mtyĺMս'vL}:f裄Ls {z!;QQ(wwuTZD-+UOP6;j;eTDx j-?kcOcgf߯{q00IHXwLⴁ_ܐ6xX :lHՙS1_@- =&QZh$pcʏ}Tssg:.O01un~͚H# bݎ,ӂM=o]lD6 q`!Oq){ iU8uI$#zYpf\qo2Nc3'.1Hcݷ,ж τF:]+TIRQ_I4OHXسmP.+&:-n5Xrit]d@| Ґ'V֑COgf݉^vhde䗹_Z+Ą|I/漻fTv}^cVeVq!<]KqԪffDZN)Kqu@zdCėxn02{|?&.Nk 9&:E(/ڣSVGKT|5h򝶝8TWcK/Ifkk;ۜID1UJʾ1gui4?aW5<^72{cljY5!1fe|x;[ƁY]5ɨѾO Y׊Fޣ )FD1tڋkzB$eck9 QEF|1fguڶAZH̖F'6 ++/+5~J5~4 o.2 ]>N4c.%%Vh, ܓ~ĪrQmYlI.݂*gOU*>ǎOD9:Yaf1p:@[?#ꆴHN*4)N8 ^KafC{c`;~wԳCupp?JAx(L[\ ٮeg&ݪi(ۋG\:KӶ`"#an9'"!U*FAh́=Q@Hz!^<(2Fq{7K?h xd9Q'ԃUxڦSݤy `12$vV:Ww}hWA{1͍ L`t)s ׍e/B @2V'hQό= m: 1Hi@c0E`&q2 8|+ '.zxq4;HF$ [{Pf;NM-Sn29v^ Z( (]nn1nysSu)QV2ԛx#n#`(`rVʅn4w Ho-%DWYXn. Wxkp@ F+f j+[X%7'9 3Rg<)d) /#洵X7j:*bԝNF$ >2%;/ȥ>£! (|}Jk;x;8XU]u0x53_˥0R4LC}b^`:8Fﴳy]{dGCo{йrL {2R؂>X=%v!7KRC/)guwkzHʞLKA7V.R ja*{FM!^F5JH1I*zp!>,^!R^ӂNgHq,@%[ /n@fp^X?EVT([dJR6? +M,q@fMΛ8Z̓c.5.Bm1BuyF8? 1N%b]fCq_u}jZ,'ן*"Wj8{V=5z <&{9IFͻG<S\ 𑅤Aud<$EKC8j=箈[W1P ?<#q)_">c_-I"KFTb}ҴH=Tݣȴ|Ֆ 8$PV_\BkZ)6'kE[֯Za*V&^{sX9NA m5rϬ|rۜ5#ͬ' AԊCK+UsHECv^1)we(,.6z ^!z9ݿ m[I̥?d}#+UӞߠ O`b!Zz:G8riu+h}8pwf;[ 5•'"goܓh'-zdGd|$uɭJ|\)wI1(zXXP!/m 8r<^,Y/3cPBFig{(xJk>CSjJr#fW-@|'e1݌/"90롫2<)5OY֕3[axR| _.pK` 3ߖW.4E(` Q !9n]2[ɗ.sG (.ղyhnlY$].htMQ&y }/wԬx|KܛF-F둥/%!Ȟ~`J PlIkhOqZ'l)*0x uVq+UC͡u߃~dWP_~*x $)Xm+M)>Kn<7^X;eAM63OZ$00byFΣ˽Ϝ04ޢ7؞&}W[00˚τ rK:==㾩SU1"-ȵ49ę Н/h feJtҌ"nd0vz%p|y| Kcˌ3dyFC'}ge}X+|$>TNLXs~2yr^Cϳ6v?bV&PN?&sFըR8U*hEu9HXϨx\Y}o Hu: ^4CR6#ӧVi<-:k;{d8Ug&PR=7U }os[3ap ou[zS8q U2F5L4!ܼ8pq%sx`e}qxw%)-۵[mEwkZД^%-OlXR!>j:D0u|#ֈ˻5ƈ[&c+<*M-^fx-vP##pU h}wB UWhTOϐU5P^z& *;:v~J_@d_ux'ѡtc.0i%]Vv MCny4UeT:1}4Gfs_@YjV~ɸ{M zi|NaL2)MN c@͟=t"%u9b|&Z5$0dF[UP)7x[?1I-,y(HukrboS1(#sD=AOY"qs C~[R8-)]i V5Qm.nDD WhԌ;$6zVǯOTe54^H~{G$jο"?f5գ?Qg Lvk㞺B1Tld׹\;)ӽ;-e'M&TH=-*LpEan|Yz"^d!^PMk펜拏+[Ҩٮ yLW!KxB&zactIC 8Bn=2N:yf7 ^l.Z,k`5^[\/Za d4*b |r{jX 2Nq~MSt{$Lr'?DV=LG]V"!q^ҭѹ|aF Ss[4yA23ul馧qM‚sbƳh==r/gĺGVѮCBbDA %ܨfWetAfDdD[%3,!Pd) ԞdHςC zUEMƅo$y|9gKʰhܗǍ5(#ouLJIɯ-7 5j2R _lS#Bd})y$l]UF0P< > 6\ * @return string|void The delete post link URL for the given post. */ function get_delete_post_link( $id = 0, $deprecated = '', $force_delete = false ) { if ( ! empty( $deprecated ) ) { _deprecated_argument( __FUNCTION__, '3.0.0' ); } if ( ! $post = get_post( $id ) ) { return; } $post_type_object = get_post_type_object( $post->post_type ); if ( ! $post_type_object ) { return; } if ( ! current_user_can( 'delete_post', $post->ID ) ) { return; } $action = ( $force_delete || ! EMPTY_TRASH_DAYS ) ? 'delete' : 'trash'; $delete_link = add_query_arg( 'action', $action, admin_url( sprintf( $post_type_object->_edit_link, $post->ID ) ) ); /** * Filters the post delete link. * * @since 2.9.0 * * @param string $link The delete link. * @param int $post_id Post ID. * @param bool $force_delete Whether to bypass the trash and force deletion. Default false. */ return apply_filters( 'get_delete_post_link', wp_nonce_url( $delete_link, "$action-post_{$post->ID}" ), $post->ID, $force_delete ); } /** * Retrieves the edit comment link. * * @since 2.3.0 * * @param int|WP_Comment $comment_id Optional. Comment ID or WP_Comment object. * @return string|void The edit comment link URL for the given comment. */ function get_edit_comment_link( $comment_id = 0 ) { $comment = get_comment( $comment_id ); if ( ! current_user_can( 'edit_comment', $comment->comment_ID ) ) { return; } $location = admin_url( 'comment.php?action=editcomment&c=' ) . $comment->comment_ID; /** * Filters the comment edit link. * * @since 2.3.0 * * @param string $location The edit link. */ return apply_filters( 'get_edit_comment_link', $location ); } /** * Displays the edit comment link with formatting. * * @since 1.0.0 * * @param string $text Optional. Anchor text. If null, default is 'Edit This'. Default null. * @param string $before Optional. Display before edit link. Default empty. * @param string $after Optional. Display after edit link. Default empty. */ function edit_comment_link( $text = null, $before = '', $after = '' ) { $comment = get_comment(); if ( ! current_user_can( 'edit_comment', $comment->comment_ID ) ) { return; } if ( null === $text ) { $text = __( 'Edit This' ); } $link = '' . $text . ''; /** * Filters the comment edit link anchor tag. * * @since 2.3.0 * * @param string $link Anchor tag for the edit link. * @param int $comment_id Comment ID. * @param string $text Anchor text. */ echo $before . apply_filters( 'edit_comment_link', $link, $comment->comment_ID, $text ) . $after; } /** * Displays the edit bookmark link. * * @since 2.7.0 * * @param int|stdClass $link Optional. Bookmark ID. Default is the id of the current bookmark. * @return string|void The edit bookmark link URL. */ function get_edit_bookmark_link( $link = 0 ) { $link = get_bookmark( $link ); if ( ! current_user_can( 'manage_links' ) ) { return; } $location = admin_url( 'link.php?action=edit&link_id=' ) . $link->link_id; /** * Filters the bookmark edit link. * * @since 2.7.0 * * @param string $location The edit link. * @param int $link_id Bookmark ID. */ return apply_filters( 'get_edit_bookmark_link', $location, $link->link_id ); } /** * Displays the edit bookmark link anchor content. * * @since 2.7.0 * * @param string $link Optional. Anchor text. Default empty. * @param string $before Optional. Display before edit link. Default empty. * @param string $after Optional. Display after edit link. Default empty. * @param int $bookmark Optional. Bookmark ID. Default is the current bookmark. */ function edit_bookmark_link( $link = '', $before = '', $after = '', $bookmark = null ) { $bookmark = get_bookmark( $bookmark ); if ( ! current_user_can( 'manage_links' ) ) { return; } if ( empty( $link ) ) { $link = __( 'Edit This' ); } $link = '' . $link . ''; /** * Filters the bookmark edit link anchor tag. * * @since 2.7.0 * * @param string $link Anchor tag for the edit link. * @param int $link_id Bookmark ID. */ echo $before . apply_filters( 'edit_bookmark_link', $link, $bookmark->link_id ) . $after; } /** * Retrieves the edit user link. * * @since 3.5.0 * * @param int $user_id Optional. User ID. Defaults to the current user. * @return string URL to edit user page or empty string. */ function get_edit_user_link( $user_id = null ) { if ( ! $user_id ) { $user_id = get_current_user_id(); } if ( empty( $user_id ) || ! current_user_can( 'edit_user', $user_id ) ) { return ''; } $user = get_userdata( $user_id ); if ( ! $user ) { return ''; } if ( get_current_user_id() == $user->ID ) { $link = get_edit_profile_url( $user->ID ); } else { $link = add_query_arg( 'user_id', $user->ID, self_admin_url( 'user-edit.php' ) ); } /** * Filters the user edit link. * * @since 3.5.0 * * @param string $link The edit link. * @param int $user_id User ID. */ return apply_filters( 'get_edit_user_link', $link, $user->ID ); } // Navigation links /** * Retrieves the previous post that is adjacent to the current post. * * @since 1.5.0 * * @param bool $in_same_term Optional. Whether post should be in a same taxonomy term. Default false. * @param array|string $excluded_terms Optional. Array or comma-separated list of excluded term IDs. Default empty. * @param string $taxonomy Optional. Taxonomy, if $in_same_term is true. Default 'category'. * @return null|string|WP_Post Post object if successful. Null if global $post is not set. Empty string if no * corresponding post exists. */ function get_previous_post( $in_same_term = false, $excluded_terms = '', $taxonomy = 'category' ) { return get_adjacent_post( $in_same_term, $excluded_terms, true, $taxonomy ); } /** * Retrieves the next post that is adjacent to the current post. * * @since 1.5.0 * * @param bool $in_same_term Optional. Whether post should be in a same taxonomy term. Default false. * @param array|string $excluded_terms Optional. Array or comma-separated list of excluded term IDs. Default empty. * @param string $taxonomy Optional. Taxonomy, if $in_same_term is true. Default 'category'. * @return null|string|WP_Post Post object if successful. Null if global $post is not set. Empty string if no * corresponding post exists. */ function get_next_post( $in_same_term = false, $excluded_terms = '', $taxonomy = 'category' ) { return get_adjacent_post( $in_same_term, $excluded_terms, false, $taxonomy ); } /** * Retrieves the adjacent post. * * Can either be next or previous post. * * @since 2.5.0 * * @global wpdb $wpdb WordPress database abstraction object. * * @param bool $in_same_term Optional. Whether post should be in a same taxonomy term. Default false. * @param array|string $excluded_terms Optional. Array or comma-separated list of excluded term IDs. Default empty. * @param bool $previous Optional. Whether to retrieve previous post. Default true * @param string $taxonomy Optional. Taxonomy, if $in_same_term is true. Default 'category'. * @return null|string|WP_Post Post object if successful. Null if global $post is not set. Empty string if no * corresponding post exists. */ function get_adjacent_post( $in_same_term = false, $excluded_terms = '', $previous = true, $taxonomy = 'category' ) { global $wpdb; if ( ( ! $post = get_post() ) || ! taxonomy_exists( $taxonomy ) ) { return null; } $current_post_date = $post->post_date; $join = ''; $where = ''; $adjacent = $previous ? 'previous' : 'next'; if ( ! empty( $excluded_terms ) && ! is_array( $excluded_terms ) ) { // Back-compat, $excluded_terms used to be $excluded_categories with IDs separated by " and ". if ( false !== strpos( $excluded_terms, ' and ' ) ) { _deprecated_argument( __FUNCTION__, '3.3.0', sprintf( __( 'Use commas instead of %s to separate excluded terms.' ), "'and'" ) ); $excluded_terms = explode( ' and ', $excluded_terms ); } else { $excluded_terms = explode( ',', $excluded_terms ); } $excluded_terms = array_map( 'intval', $excluded_terms ); } /** * Filters the IDs of terms excluded from adjacent post queries. * * The dynamic portion of the hook name, `$adjacent`, refers to the type * of adjacency, 'next' or 'previous'. * * @since 4.4.0 * * @param array $excluded_terms Array of excluded term IDs. */ $excluded_terms = apply_filters( "get_{$adjacent}_post_excluded_terms", $excluded_terms ); if ( $in_same_term || ! empty( $excluded_terms ) ) { if ( $in_same_term ) { $join .= " INNER JOIN $wpdb->term_relationships AS tr ON p.ID = tr.object_id INNER JOIN $wpdb->term_taxonomy tt ON tr.term_taxonomy_id = tt.term_taxonomy_id"; $where .= $wpdb->prepare( 'AND tt.taxonomy = %s', $taxonomy ); if ( ! is_object_in_taxonomy( $post->post_type, $taxonomy ) ) { return ''; } $term_array = wp_get_object_terms( $post->ID, $taxonomy, array( 'fields' => 'ids' ) ); // Remove any exclusions from the term array to include. $term_array = array_diff( $term_array, (array) $excluded_terms ); $term_array = array_map( 'intval', $term_array ); if ( ! $term_array || is_wp_error( $term_array ) ) { return ''; } $where .= ' AND tt.term_id IN (' . implode( ',', $term_array ) . ')'; } if ( ! empty( $excluded_terms ) ) { $where .= " AND p.ID NOT IN ( SELECT tr.object_id FROM $wpdb->term_relationships tr LEFT JOIN $wpdb->term_taxonomy tt ON (tr.term_taxonomy_id = tt.term_taxonomy_id) WHERE tt.term_id IN (" . implode( ',', array_map( 'intval', $excluded_terms ) ) . ') )'; } } // 'post_status' clause depends on the current user. if ( is_user_logged_in() ) { $user_id = get_current_user_id(); $post_type_object = get_post_type_object( $post->post_type ); if ( empty( $post_type_object ) ) { $post_type_cap = $post->post_type; $read_private_cap = 'read_private_' . $post_type_cap . 's'; } else { $read_private_cap = $post_type_object->cap->read_private_posts; } /* * Results should include private posts belonging to the current user, or private posts where the * current user has the 'read_private_posts' cap. */ $private_states = get_post_stati( array( 'private' => true ) ); $where .= " AND ( p.post_status = 'publish'"; foreach ( (array) $private_states as $state ) { if ( current_user_can( $read_private_cap ) ) { $where .= $wpdb->prepare( ' OR p.post_status = %s', $state ); } else { $where .= $wpdb->prepare( ' OR (p.post_author = %d AND p.post_status = %s)', $user_id, $state ); } } $where .= ' )'; } else { $where .= " AND p.post_status = 'publish'"; } $op = $previous ? '<' : '>'; $order = $previous ? 'DESC' : 'ASC'; /** * Filters the JOIN clause in the SQL for an adjacent post query. * * The dynamic portion of the hook name, `$adjacent`, refers to the type * of adjacency, 'next' or 'previous'. * * @since 2.5.0 * @since 4.4.0 Added the `$taxonomy` and `$post` parameters. * * @param string $join The JOIN clause in the SQL. * @param bool $in_same_term Whether post should be in a same taxonomy term. * @param array $excluded_terms Array of excluded term IDs. * @param string $taxonomy Taxonomy. Used to identify the term used when `$in_same_term` is true. * @param WP_Post $post WP_Post object. */ $join = apply_filters( "get_{$adjacent}_post_join", $join, $in_same_term, $excluded_terms, $taxonomy, $post ); /** * Filters the WHERE clause in the SQL for an adjacent post query. * * The dynamic portion of the hook name, `$adjacent`, refers to the type * of adjacency, 'next' or 'previous'. * * @since 2.5.0 * @since 4.4.0 Added the `$taxonomy` and `$post` parameters. * * @param string $where The `WHERE` clause in the SQL. * @param bool $in_same_term Whether post should be in a same taxonomy term. * @param array $excluded_terms Array of excluded term IDs. * @param string $taxonomy Taxonomy. Used to identify the term used when `$in_same_term` is true. * @param WP_Post $post WP_Post object. */ $where = apply_filters( "get_{$adjacent}_post_where", $wpdb->prepare( "WHERE p.post_date $op %s AND p.post_type = %s $where", $current_post_date, $post->post_type ), $in_same_term, $excluded_terms, $taxonomy, $post ); /** * Filters the ORDER BY clause in the SQL for an adjacent post query. * * The dynamic portion of the hook name, `$adjacent`, refers to the type * of adjacency, 'next' or 'previous'. * * @since 2.5.0 * @since 4.4.0 Added the `$post` parameter. * @since 4.9.0 Added the `$order` parameter. * * @param string $order_by The `ORDER BY` clause in the SQL. * @param WP_Post $post WP_Post object. * @param string $order Sort order. 'DESC' for previous post, 'ASC' for next. */ $sort = apply_filters( "get_{$adjacent}_post_sort", "ORDER BY p.post_date $order LIMIT 1", $post, $order ); $query = "SELECT p.ID FROM $wpdb->posts AS p $join $where $sort"; $query_key = 'adjacent_post_' . md5( $query ); $result = wp_cache_get( $query_key, 'counts' ); if ( false !== $result ) { if ( $result ) { $result = get_post( $result ); } return $result; } $result = $wpdb->get_var( $query ); if ( null === $result ) { $result = ''; } wp_cache_set( $query_key, $result, 'counts' ); if ( $result ) { $result = get_post( $result ); } return $result; } /** * Retrieves the adjacent post relational link. * * Can either be next or previous post relational link. * * @since 2.8.0 * * @param string $title Optional. Link title format. Default '%title'. * @param bool $in_same_term Optional. Whether link should be in a same taxonomy term. Default false. * @param array|string $excluded_terms Optional. Array or comma-separated list of excluded term IDs. Default empty. * @param bool $previous Optional. Whether to display link to previous or next post. Default true. * @param string $taxonomy Optional. Taxonomy, if $in_same_term is true. Default 'category'. * @return string|void The adjacent post relational link URL. */ function get_adjacent_post_rel_link( $title = '%title', $in_same_term = false, $excluded_terms = '', $previous = true, $taxonomy = 'category' ) { if ( $previous && is_attachment() && $post = get_post() ) { $post = get_post( $post->post_parent ); } else { $post = get_adjacent_post( $in_same_term, $excluded_terms, $previous, $taxonomy ); } if ( empty( $post ) ) { return; } $post_title = the_title_attribute( array( 'echo' => false, 'post' => $post, ) ); if ( empty( $post_title ) ) { $post_title = $previous ? __( 'Previous Post' ) : __( 'Next Post' ); } $date = mysql2date( get_option( 'date_format' ), $post->post_date ); $title = str_replace( '%title', $post_title, $title ); $title = str_replace( '%date', $date, $title ); $link = $previous ? "\n"; $adjacent = $previous ? 'previous' : 'next'; /** * Filters the adjacent post relational link. * * The dynamic portion of the hook name, `$adjacent`, refers to the type * of adjacency, 'next' or 'previous'. * * @since 2.8.0 * * @param string $link The relational link. */ return apply_filters( "{$adjacent}_post_rel_link", $link ); } /** * Displays the relational links for the posts adjacent to the current post. * * @since 2.8.0 * * @param string $title Optional. Link title format. Default '%title'. * @param bool $in_same_term Optional. Whether link should be in a same taxonomy term. Default false. * @param array|string $excluded_terms Optional. Array or comma-separated list of excluded term IDs. Default empty. * @param string $taxonomy Optional. Taxonomy, if $in_same_term is true. Default 'category'. */ function adjacent_posts_rel_link( $title = '%title', $in_same_term = false, $excluded_terms = '', $taxonomy = 'category' ) { echo get_adjacent_post_rel_link( $title, $in_same_term, $excluded_terms, true, $taxonomy ); echo get_adjacent_post_rel_link( $title, $in_same_term, $excluded_terms, false, $taxonomy ); } /** * Displays relational links for the posts adjacent to the current post for single post pages. * * This is meant to be attached to actions like 'wp_head'. Do not call this directly in plugins * or theme templates. * * @since 3.0.0 * * @see adjacent_posts_rel_link() */ function adjacent_posts_rel_link_wp_head() { if ( ! is_single() || is_attachment() ) { return; } adjacent_posts_rel_link(); } /** * Displays the relational link for the next post adjacent to the current post. * * @since 2.8.0 * * @see get_adjacent_post_rel_link() * * @param string $title Optional. Link title format. Default '%title'. * @param bool $in_same_term Optional. Whether link should be in a same taxonomy term. Default false. * @param array|string $excluded_terms Optional. Array or comma-separated list of excluded term IDs. Default empty. * @param string $taxonomy Optional. Taxonomy, if $in_same_term is true. Default 'category'. */ function next_post_rel_link( $title = '%title', $in_same_term = false, $excluded_terms = '', $taxonomy = 'category' ) { echo get_adjacent_post_rel_link( $title, $in_same_term, $excluded_terms, false, $taxonomy ); } /** * Displays the relational link for the previous post adjacent to the current post. * * @since 2.8.0 * * @see get_adjacent_post_rel_link() * * @param string $title Optional. Link title format. Default '%title'. * @param bool $in_same_term Optional. Whether link should be in a same taxonomy term. Default false. * @param array|string $excluded_terms Optional. Array or comma-separated list of excluded term IDs. Default true. * @param string $taxonomy Optional. Taxonomy, if $in_same_term is true. Default 'category'. */ function prev_post_rel_link( $title = '%title', $in_same_term = false, $excluded_terms = '', $taxonomy = 'category' ) { echo get_adjacent_post_rel_link( $title, $in_same_term, $excluded_terms, true, $taxonomy ); } /** * Retrieves the boundary post. * * Boundary being either the first or last post by publish date within the constraints specified * by $in_same_term or $excluded_terms. * * @since 2.8.0 * * @param bool $in_same_term Optional. Whether returned post should be in a same taxonomy term. * Default false. * @param array|string $excluded_terms Optional. Array or comma-separated list of excluded term IDs. * Default empty. * @param bool $start Optional. Whether to retrieve first or last post. Default true * @param string $taxonomy Optional. Taxonomy, if $in_same_term is true. Default 'category'. * @return null|array Array containing the boundary post object if successful, null otherwise. */ function get_boundary_post( $in_same_term = false, $excluded_terms = '', $start = true, $taxonomy = 'category' ) { $post = get_post(); if ( ! $post || ! is_single() || is_attachment() || ! taxonomy_exists( $taxonomy ) ) { return null; } $query_args = array( 'posts_per_page' => 1, 'order' => $start ? 'ASC' : 'DESC', 'update_post_term_cache' => false, 'update_post_meta_cache' => false, ); $term_array = array(); if ( ! is_array( $excluded_terms ) ) { if ( ! empty( $excluded_terms ) ) { $excluded_terms = explode( ',', $excluded_terms ); } else { $excluded_terms = array(); } } if ( $in_same_term || ! empty( $excluded_terms ) ) { if ( $in_same_term ) { $term_array = wp_get_object_terms( $post->ID, $taxonomy, array( 'fields' => 'ids' ) ); } if ( ! empty( $excluded_terms ) ) { $excluded_terms = array_map( 'intval', $excluded_terms ); $excluded_terms = array_diff( $excluded_terms, $term_array ); $inverse_terms = array(); foreach ( $excluded_terms as $excluded_term ) { $inverse_terms[] = $excluded_term * -1; } $excluded_terms = $inverse_terms; } $query_args['tax_query'] = array( array( 'taxonomy' => $taxonomy, 'terms' => array_merge( $term_array, $excluded_terms ), ), ); } return get_posts( $query_args ); } /** * Retrieves the previous post link that is adjacent to the current post. * * @since 3.7.0 * * @param string $format Optional. Link anchor format. Default '« %link'. * @param string $link Optional. Link permalink format. Default '%title'. * @param bool $in_same_term Optional. Whether link should be in a same taxonomy term. Default false. * @param array|string $excluded_terms Optional. Array or comma-separated list of excluded term IDs. Default empty. * @param string $taxonomy Optional. Taxonomy, if $in_same_term is true. Default 'category'. * @return string The link URL of the previous post in relation to the current post. */ function get_previous_post_link( $format = '« %link', $link = '%title', $in_same_term = false, $excluded_terms = '', $taxonomy = 'category' ) { return get_adjacent_post_link( $format, $link, $in_same_term, $excluded_terms, true, $taxonomy ); } /** * Displays the previous post link that is adjacent to the current post. * * @since 1.5.0 * * @see get_previous_post_link() * * @param string $format Optional. Link anchor format. Default '« %link'. * @param string $link Optional. Link permalink format. Default '%title'. * @param bool $in_same_term Optional. Whether link should be in a same taxonomy term. Default false. * @param array|string $excluded_terms Optional. Array or comma-separated list of excluded term IDs. Default empty. * @param string $taxonomy Optional. Taxonomy, if $in_same_term is true. Default 'category'. */ function previous_post_link( $format = '« %link', $link = '%title', $in_same_term = false, $excluded_terms = '', $taxonomy = 'category' ) { echo get_previous_post_link( $format, $link, $in_same_term, $excluded_terms, $taxonomy ); } /** * Retrieves the next post link that is adjacent to the current post. * * @since 3.7.0 * * @param string $format Optional. Link anchor format. Default '« %link'. * @param string $link Optional. Link permalink format. Default '%title'. * @param bool $in_same_term Optional. Whether link should be in a same taxonomy term. Default false. * @param array|string $excluded_terms Optional. Array or comma-separated list of excluded term IDs. Default empty. * @param string $taxonomy Optional. Taxonomy, if $in_same_term is true. Default 'category'. * @return string The link URL of the next post in relation to the current post. */ function get_next_post_link( $format = '%link »', $link = '%title', $in_same_term = false, $excluded_terms = '', $taxonomy = 'category' ) { return get_adjacent_post_link( $format, $link, $in_same_term, $excluded_terms, false, $taxonomy ); } /** * Displays the next post link that is adjacent to the current post. * * @since 1.5.0 * @see get_next_post_link() * * @param string $format Optional. Link anchor format. Default '« %link'. * @param string $link Optional. Link permalink format. Default '%title' * @param bool $in_same_term Optional. Whether link should be in a same taxonomy term. Default false. * @param array|string $excluded_terms Optional. Array or comma-separated list of excluded term IDs. Default empty. * @param string $taxonomy Optional. Taxonomy, if $in_same_term is true. Default 'category'. */ function next_post_link( $format = '%link »', $link = '%title', $in_same_term = false, $excluded_terms = '', $taxonomy = 'category' ) { echo get_next_post_link( $format, $link, $in_same_term, $excluded_terms, $taxonomy ); } /** * Retrieves the adjacent post link. * * Can be either next post link or previous. * * @since 3.7.0 * * @param string $format Link anchor format. * @param string $link Link permalink format. * @param bool $in_same_term Optional. Whether link should be in a same taxonomy term. Default false. * @param array|string $excluded_terms Optional. Array or comma-separated list of excluded terms IDs. Default empty. * @param bool $previous Optional. Whether to display link to previous or next post. Default true. * @param string $taxonomy Optional. Taxonomy, if $in_same_term is true. Default 'category'. * @return string The link URL of the previous or next post in relation to the current post. */ function get_adjacent_post_link( $format, $link, $in_same_term = false, $excluded_terms = '', $previous = true, $taxonomy = 'category' ) { if ( $previous && is_attachment() ) { $post = get_post( get_post()->post_parent ); } else { $post = get_adjacent_post( $in_same_term, $excluded_terms, $previous, $taxonomy ); } if ( ! $post ) { $output = ''; } else { $title = $post->post_title; if ( empty( $post->post_title ) ) { $title = $previous ? __( 'Previous Post' ) : __( 'Next Post' ); } /** This filter is documented in wp-includes/post-template.php */ $title = apply_filters( 'the_title', $title, $post->ID ); $date = mysql2date( get_option( 'date_format' ), $post->post_date ); $rel = $previous ? 'prev' : 'next'; $string = ''; $inlink = str_replace( '%title', $title, $link ); $inlink = str_replace( '%date', $date, $inlink ); $inlink = $string . $inlink . ''; $output = str_replace( '%link', $inlink, $format ); } $adjacent = $previous ? 'previous' : 'next'; /** * Filters the adjacent post link. * * The dynamic portion of the hook name, `$adjacent`, refers to the type * of adjacency, 'next' or 'previous'. * * @since 2.6.0 * @since 4.2.0 Added the `$adjacent` parameter. * * @param string $output The adjacent post link. * @param string $format Link anchor format. * @param string $link Link permalink format. * @param WP_Post $post The adjacent post. * @param string $adjacent Whether the post is previous or next. */ return apply_filters( "{$adjacent}_post_link", $output, $format, $link, $post, $adjacent ); } /** * Displays the adjacent post link. * * Can be either next post link or previous. * * @since 2.5.0 * * @param string $format Link anchor format. * @param string $link Link permalink format. * @param bool $in_same_term Optional. Whether link should be in a same taxonomy term. Default false. * @param array|string $excluded_terms Optional. Array or comma-separated list of excluded category IDs. Default empty. * @param bool $previous Optional. Whether to display link to previous or next post. Default true. * @param string $taxonomy Optional. Taxonomy, if $in_same_term is true. Default 'category'. */ function adjacent_post_link( $format, $link, $in_same_term = false, $excluded_terms = '', $previous = true, $taxonomy = 'category' ) { echo get_adjacent_post_link( $format, $link, $in_same_term, $excluded_terms, $previous, $taxonomy ); } /** * Retrieves the link for a page number. * * @since 1.5.0 * * @global WP_Rewrite $wp_rewrite * * @param int $pagenum Optional. Page number. Default 1. * @param bool $escape Optional. Whether to escape the URL for display, with esc_url(). Defaults to true. * Otherwise, prepares the URL with esc_url_raw(). * @return string The link URL for the given page number. */ function get_pagenum_link( $pagenum = 1, $escape = true ) { global $wp_rewrite; $pagenum = (int) $pagenum; $request = remove_query_arg( 'paged' ); $home_root = parse_url( home_url() ); $home_root = ( isset( $home_root['path'] ) ) ? $home_root['path'] : ''; $home_root = preg_quote( $home_root, '|' ); $request = preg_replace( '|^' . $home_root . '|i', '', $request ); $request = preg_replace( '|^/+|', '', $request ); if ( ! $wp_rewrite->using_permalinks() || is_admin() ) { $base = trailingslashit( get_bloginfo( 'url' ) ); if ( $pagenum > 1 ) { $result = add_query_arg( 'paged', $pagenum, $base . $request ); } else { $result = $base . $request; } } else { $qs_regex = '|\?.*?$|'; preg_match( $qs_regex, $request, $qs_match ); if ( ! empty( $qs_match[0] ) ) { $query_string = $qs_match[0]; $request = preg_replace( $qs_regex, '', $request ); } else { $query_string = ''; } $request = preg_replace( "|$wp_rewrite->pagination_base/\d+/?$|", '', $request ); $request = preg_replace( '|^' . preg_quote( $wp_rewrite->index, '|' ) . '|i', '', $request ); $request = ltrim( $request, '/' ); $base = trailingslashit( get_bloginfo( 'url' ) ); if ( $wp_rewrite->using_index_permalinks() && ( $pagenum > 1 || '' != $request ) ) { $base .= $wp_rewrite->index . '/'; } if ( $pagenum > 1 ) { $request = ( ( ! empty( $request ) ) ? trailingslashit( $request ) : $request ) . user_trailingslashit( $wp_rewrite->pagination_base . '/' . $pagenum, 'paged' ); } $result = $base . $request . $query_string; } /** * Filters the page number link for the current request. * * @since 2.5.0 * @since 5.2.0 Added the `$pagenum` argument. * * @param string $result The page number link. * @param int $pagenum The page number. */ $result = apply_filters( 'get_pagenum_link', $result, $pagenum ); if ( $escape ) { return esc_url( $result ); } else { return esc_url_raw( $result ); } } /** * Retrieves the next posts page link. * * Backported from 2.1.3 to 2.0.10. * * @since 2.0.10 * * @global int $paged * * @param int $max_page Optional. Max pages. Default 0. * @return string|void The link URL for next posts page. */ function get_next_posts_page_link( $max_page = 0 ) { global $paged; if ( ! is_single() ) { if ( ! $paged ) { $paged = 1; } $nextpage = intval( $paged ) + 1; if ( ! $max_page || $max_page >= $nextpage ) { return get_pagenum_link( $nextpage ); } } } /** * Displays or retrieves the next posts page link. * * @since 0.71 * * @param int $max_page Optional. Max pages. Default 0. * @param bool $echo Optional. Whether to echo the link. Default true. * @return string|void The link URL for next posts page if `$echo = false`. */ function next_posts( $max_page = 0, $echo = true ) { $output = esc_url( get_next_posts_page_link( $max_page ) ); if ( $echo ) { echo $output; } else { return $output; } } /** * Retrieves the next posts page link. * * @since 2.7.0 * * @global int $paged * @global WP_Query $wp_query * * @param string $label Content for link text. * @param int $max_page Optional. Max pages. Default 0. * @return string|void HTML-formatted next posts page link. */ function get_next_posts_link( $label = null, $max_page = 0 ) { global $paged, $wp_query; if ( ! $max_page ) { $max_page = $wp_query->max_num_pages; } if ( ! $paged ) { $paged = 1; } $nextpage = intval( $paged ) + 1; if ( null === $label ) { $label = __( 'Next Page »' ); } if ( ! is_single() && ( $nextpage <= $max_page ) ) { /** * Filters the anchor tag attributes for the next posts page link. * * @since 2.7.0 * * @param string $attributes Attributes for the anchor tag. */ $attr = apply_filters( 'next_posts_link_attributes', '' ); return '" . preg_replace( '/&([^#])(?![a-z]{1,8};)/i', '&$1', $label ) . ''; } } /** * Displays the next posts page link. * * @since 0.71 * * @param string $label Content for link text. * @param int $max_page Optional. Max pages. Default 0. */ function next_posts_link( $label = null, $max_page = 0 ) { echo get_next_posts_link( $label, $max_page ); } /** * Retrieves the previous posts page link. * * Will only return string, if not on a single page or post. * * Backported to 2.0.10 from 2.1.3. * * @since 2.0.10 * * @global int $paged * * @return string|void The link for the previous posts page. */ function get_previous_posts_page_link() { global $paged; if ( ! is_single() ) { $nextpage = intval( $paged ) - 1; if ( $nextpage < 1 ) { $nextpage = 1; } return get_pagenum_link( $nextpage ); } } /** * Displays or retrieves the previous posts page link. * * @since 0.71 * * @param bool $echo Optional. Whether to echo the link. Default true. * @return string|void The previous posts page link if `$echo = false`. */ function previous_posts( $echo = true ) { $output = esc_url( get_previous_posts_page_link() ); if ( $echo ) { echo $output; } else { return $output; } } /** * Retrieves the previous posts page link. * * @since 2.7.0 * * @global int $paged * * @param string $label Optional. Previous page link text. * @return string|void HTML-formatted previous page link. */ function get_previous_posts_link( $label = null ) { global $paged; if ( null === $label ) { $label = __( '« Previous Page' ); } if ( ! is_single() && $paged > 1 ) { /** * Filters the anchor tag attributes for the previous posts page link. * * @since 2.7.0 * * @param string $attributes Attributes for the anchor tag. */ $attr = apply_filters( 'previous_posts_link_attributes', '' ); return '" . preg_replace( '/&([^#])(?![a-z]{1,8};)/i', '&$1', $label ) . ''; } } /** * Displays the previous posts page link. * * @since 0.71 * * @param string $label Optional. Previous page link text. */ function previous_posts_link( $label = null ) { echo get_previous_posts_link( $label ); } /** * Retrieves the post pages link navigation for previous and next pages. * * @since 2.8.0 * * @global WP_Query $wp_query * * @param string|array $args { * Optional. Arguments to build the post pages link navigation. * * @type string $sep Separator character. Default '—'. * @type string $prelabel Link text to display for the previous page link. * Default '« Previous Page'. * @type string $nxtlabel Link text to display for the next page link. * Default 'Next Page »'. * } * @return string The posts link navigation. */ function get_posts_nav_link( $args = array() ) { global $wp_query; $return = ''; if ( ! is_singular() ) { $defaults = array( 'sep' => ' — ', 'prelabel' => __( '« Previous Page' ), 'nxtlabel' => __( 'Next Page »' ), ); $args = wp_parse_args( $args, $defaults ); $max_num_pages = $wp_query->max_num_pages; $paged = get_query_var( 'paged' ); //only have sep if there's both prev and next results if ( $paged < 2 || $paged >= $max_num_pages ) { $args['sep'] = ''; } if ( $max_num_pages > 1 ) { $return = get_previous_posts_link( $args['prelabel'] ); $return .= preg_replace( '/&([^#])(?![a-z]{1,8};)/i', '&$1', $args['sep'] ); $return .= get_next_posts_link( $args['nxtlabel'] ); } } return $return; } /** * Displays the post pages link navigation for previous and next pages. * * @since 0.71 * * @param string $sep Optional. Separator for posts navigation links. Default empty. * @param string $prelabel Optional. Label for previous pages. Default empty. * @param string $nxtlabel Optional Label for next pages. Default empty. */ function posts_nav_link( $sep = '', $prelabel = '', $nxtlabel = '' ) { $args = array_filter( compact( 'sep', 'prelabel', 'nxtlabel' ) ); echo get_posts_nav_link( $args ); } /** * Retrieves the navigation to next/previous post, when applicable. * * @since 4.1.0 * @since 4.4.0 Introduced the `in_same_term`, `excluded_terms`, and `taxonomy` arguments. * * @param array $args { * Optional. Default post navigation arguments. Default empty array. * * @type string $prev_text Anchor text to display in the previous post link. Default '%title'. * @type string $next_text Anchor text to display in the next post link. Default '%title'. * @type bool $in_same_term Whether link should be in a same taxonomy term. Default false. * @type array|string $excluded_terms Array or comma-separated list of excluded term IDs. Default empty. * @type string $taxonomy Taxonomy, if `$in_same_term` is true. Default 'category'. * @type string $screen_reader_text Screen reader text for nav element. Default 'Post navigation'. * } * @return string Markup for post links. */ function get_the_post_navigation( $args = array() ) { $args = wp_parse_args( $args, array( 'prev_text' => '%title', 'next_text' => '%title', 'in_same_term' => false, 'excluded_terms' => '', 'taxonomy' => 'category', 'screen_reader_text' => __( 'Post navigation' ), ) ); $navigation = ''; $previous = get_previous_post_link( '', $args['prev_text'], $args['in_same_term'], $args['excluded_terms'], $args['taxonomy'] ); $next = get_next_post_link( '', $args['next_text'], $args['in_same_term'], $args['excluded_terms'], $args['taxonomy'] ); // Only add markup if there's somewhere to navigate to. if ( $previous || $next ) { $navigation = _navigation_markup( $previous . $next, 'post-navigation', $args['screen_reader_text'] ); } return $navigation; } /** * Displays the navigation to next/previous post, when applicable. * * @since 4.1.0 * * @param array $args Optional. See get_the_post_navigation() for available arguments. * Default empty array. */ function the_post_navigation( $args = array() ) { echo get_the_post_navigation( $args ); } /** * Returns the navigation to next/previous set of posts, when applicable. * * @since 4.1.0 * * @global WP_Query $wp_query WordPress Query object. * * @param array $args { * Optional. Default posts navigation arguments. Default empty array. * * @type string $prev_text Anchor text to display in the previous posts link. * Default 'Older posts'. * @type string $next_text Anchor text to display in the next posts link. * Default 'Newer posts'. * @type string $screen_reader_text Screen reader text for nav element. * Default 'Posts navigation'. * } * @return string Markup for posts links. */ function get_the_posts_navigation( $args = array() ) { $navigation = ''; // Don't print empty markup if there's only one page. if ( $GLOBALS['wp_query']->max_num_pages > 1 ) { $args = wp_parse_args( $args, array( 'prev_text' => __( 'Older posts' ), 'next_text' => __( 'Newer posts' ), 'screen_reader_text' => __( 'Posts navigation' ), ) ); $next_link = get_previous_posts_link( $args['next_text'] ); $prev_link = get_next_posts_link( $args['prev_text'] ); if ( $prev_link ) { $navigation .= ''; } if ( $next_link ) { $navigation .= ''; } $navigation = _navigation_markup( $navigation, 'posts-navigation', $args['screen_reader_text'] ); } return $navigation; } /** * Displays the navigation to next/previous set of posts, when applicable. * * @since 4.1.0 * * @param array $args Optional. See get_the_posts_navigation() for available arguments. * Default empty array. */ function the_posts_navigation( $args = array() ) { echo get_the_posts_navigation( $args ); } /** * Retrieves a paginated navigation to next/previous set of posts, when applicable. * * @since 4.1.0 * * @param array $args { * Optional. Default pagination arguments, see paginate_links(). * * @type string $screen_reader_text Screen reader text for navigation element. * Default 'Posts navigation'. * } * @return string Markup for pagination links. */ function get_the_posts_pagination( $args = array() ) { $navigation = ''; // Don't print empty markup if there's only one page. if ( $GLOBALS['wp_query']->max_num_pages > 1 ) { $args = wp_parse_args( $args, array( 'mid_size' => 1, 'prev_text' => _x( 'Previous', 'previous set of posts' ), 'next_text' => _x( 'Next', 'next set of posts' ), 'screen_reader_text' => __( 'Posts navigation' ), ) ); // Make sure we get a string back. Plain is the next best thing. if ( isset( $args['type'] ) && 'array' == $args['type'] ) { $args['type'] = 'plain'; } // Set up paginated links. $links = paginate_links( $args ); if ( $links ) { $navigation = _navigation_markup( $links, 'pagination', $args['screen_reader_text'] ); } } return $navigation; } /** * Displays a paginated navigation to next/previous set of posts, when applicable. * * @since 4.1.0 * * @param array $args Optional. See get_the_posts_pagination() for available arguments. * Default empty array. */ function the_posts_pagination( $args = array() ) { echo get_the_posts_pagination( $args ); } /** * Wraps passed links in navigational markup. * * @since 4.1.0 * @access private * * @param string $links Navigational links. * @param string $class Optional. Custom class for nav element. Default: 'posts-navigation'. * @param string $screen_reader_text Optional. Screen reader text for nav element. Default: 'Posts navigation'. * @return string Navigation template tag. */ function _navigation_markup( $links, $class = 'posts-navigation', $screen_reader_text = '' ) { if ( empty( $screen_reader_text ) ) { $screen_reader_text = __( 'Posts navigation' ); } $template = ' '; /** * Filters the navigation markup template. * * Note: The filtered template HTML must contain specifiers for the navigation * class (%1$s), the screen-reader-text value (%2$s), and placement of the * navigation links (%3$s): * * * * @since 4.4.0 * * @param string $template The default template. * @param string $class The class passed by the calling function. * @return string Navigation template. */ $template = apply_filters( 'navigation_markup_template', $template, $class ); return sprintf( $template, sanitize_html_class( $class ), esc_html( $screen_reader_text ), $links ); } /** * Retrieves the comments page number link. * * @since 2.7.0 * * @global WP_Rewrite $wp_rewrite * * @param int $pagenum Optional. Page number. Default 1. * @param int $max_page Optional. The maximum number of comment pages. Default 0. * @return string The comments page number link URL. */ function get_comments_pagenum_link( $pagenum = 1, $max_page = 0 ) { global $wp_rewrite; $pagenum = (int) $pagenum; $result = get_permalink(); if ( 'newest' == get_option( 'default_comments_page' ) ) { if ( $pagenum != $max_page ) { if ( $wp_rewrite->using_permalinks() ) { $result = user_trailingslashit( trailingslashit( $result ) . $wp_rewrite->comments_pagination_base . '-' . $pagenum, 'commentpaged' ); } else { $result = add_query_arg( 'cpage', $pagenum, $result ); } } } elseif ( $pagenum > 1 ) { if ( $wp_rewrite->using_permalinks() ) { $result = user_trailingslashit( trailingslashit( $result ) . $wp_rewrite->comments_pagination_base . '-' . $pagenum, 'commentpaged' ); } else { $result = add_query_arg( 'cpage', $pagenum, $result ); } } $result .= '#comments'; /** * Filters the comments page number link for the current request. * * @since 2.7.0 * * @param string $result The comments page number link. */ return apply_filters( 'get_comments_pagenum_link', $result ); } /** * Retrieves the link to the next comments page. * * @since 2.7.1 * * @global WP_Query $wp_query * * @param string $label Optional. Label for link text. Default empty. * @param int $max_page Optional. Max page. Default 0. * @return string|void HTML-formatted link for the next page of comments. */ function get_next_comments_link( $label = '', $max_page = 0 ) { global $wp_query; if ( ! is_singular() ) { return; } $page = get_query_var( 'cpage' ); if ( ! $page ) { $page = 1; } $nextpage = intval( $page ) + 1; if ( empty( $max_page ) ) { $max_page = $wp_query->max_num_comment_pages; } if ( empty( $max_page ) ) { $max_page = get_comment_pages_count(); } if ( $nextpage > $max_page ) { return; } if ( empty( $label ) ) { $label = __( 'Newer Comments »' ); } /** * Filters the anchor tag attributes for the next comments page link. * * @since 2.7.0 * * @param string $attributes Attributes for the anchor tag. */ return '' . preg_replace( '/&([^#])(?![a-z]{1,8};)/i', '&$1', $label ) . ''; } /** * Displays the link to the next comments page. * * @since 2.7.0 * * @param string $label Optional. Label for link text. Default empty. * @param int $max_page Optional. Max page. Default 0. */ function next_comments_link( $label = '', $max_page = 0 ) { echo get_next_comments_link( $label, $max_page ); } /** * Retrieves the link to the previous comments page. * * @since 2.7.1 * * @param string $label Optional. Label for comments link text. Default empty. * @return string|void HTML-formatted link for the previous page of comments. */ function get_previous_comments_link( $label = '' ) { if ( ! is_singular() ) { return; } $page = get_query_var( 'cpage' ); if ( intval( $page ) <= 1 ) { return; } $prevpage = intval( $page ) - 1; if ( empty( $label ) ) { $label = __( '« Older Comments' ); } /** * Filters the anchor tag attributes for the previous comments page link. * * @since 2.7.0 * * @param string $attributes Attributes for the anchor tag. */ return '' . preg_replace( '/&([^#])(?![a-z]{1,8};)/i', '&$1', $label ) . ''; } /** * Displays the link to the previous comments page. * * @since 2.7.0 * * @param string $label Optional. Label for comments link text. Default empty. */ function previous_comments_link( $label = '' ) { echo get_previous_comments_link( $label ); } /** * Displays or retrieves pagination links for the comments on the current post. * * @see paginate_links() * @since 2.7.0 * * @global WP_Rewrite $wp_rewrite * * @param string|array $args Optional args. See paginate_links(). Default empty array. * @return string|array|void Markup for comment page links or array of comment page links. */ function paginate_comments_links( $args = array() ) { global $wp_rewrite; if ( ! is_singular() ) { return; } $page = get_query_var( 'cpage' ); if ( ! $page ) { $page = 1; } $max_page = get_comment_pages_count(); $defaults = array( 'base' => add_query_arg( 'cpage', '%#%' ), 'format' => '', 'total' => $max_page, 'current' => $page, 'echo' => true, 'type' => 'plain', 'add_fragment' => '#comments', ); if ( $wp_rewrite->using_permalinks() ) { $defaults['base'] = user_trailingslashit( trailingslashit( get_permalink() ) . $wp_rewrite->comments_pagination_base . '-%#%', 'commentpaged' ); } $args = wp_parse_args( $args, $defaults ); $page_links = paginate_links( $args ); if ( $args['echo'] && 'array' !== $args['type'] ) { echo $page_links; } else { return $page_links; } } /** * Retrieves navigation to next/previous set of comments, when applicable. * * @since 4.4.0 * * @param array $args { * Optional. Default comments navigation arguments. * * @type string $prev_text Anchor text to display in the previous comments link. * Default 'Older comments'. * @type string $next_text Anchor text to display in the next comments link. * Default 'Newer comments'. * @type string $screen_reader_text Screen reader text for nav element. Default 'Comments navigation'. * } * @return string Markup for comments links. */ function get_the_comments_navigation( $args = array() ) { $navigation = ''; // Are there comments to navigate through? if ( get_comment_pages_count() > 1 ) { $args = wp_parse_args( $args, array( 'prev_text' => __( 'Older comments' ), 'next_text' => __( 'Newer comments' ), 'screen_reader_text' => __( 'Comments navigation' ), ) ); $prev_link = get_previous_comments_link( $args['prev_text'] ); $next_link = get_next_comments_link( $args['next_text'] ); if ( $prev_link ) { $navigation .= ''; } if ( $next_link ) { $navigation .= ''; } $navigation = _navigation_markup( $navigation, 'comment-navigation', $args['screen_reader_text'] ); } return $navigation; } /** * Displays navigation to next/previous set of comments, when applicable. * * @since 4.4.0 * * @param array $args See get_the_comments_navigation() for available arguments. Default empty array. */ function the_comments_navigation( $args = array() ) { echo get_the_comments_navigation( $args ); } /** * Retrieves a paginated navigation to next/previous set of comments, when applicable. * * @since 4.4.0 * * @see paginate_comments_links() * * @param array $args { * Optional. Default pagination arguments. * * @type string $screen_reader_text Screen reader text for nav element. Default 'Comments navigation'. * } * @return string Markup for pagination links. */ function get_the_comments_pagination( $args = array() ) { $navigation = ''; $args = wp_parse_args( $args, array( 'screen_reader_text' => __( 'Comments navigation' ), ) ); $args['echo'] = false; // Make sure we get a string back. Plain is the next best thing. if ( isset( $args['type'] ) && 'array' == $args['type'] ) { $args['type'] = 'plain'; } $links = paginate_comments_links( $args ); if ( $links ) { $navigation = _navigation_markup( $links, 'comments-pagination', $args['screen_reader_text'] ); } return $navigation; } /** * Displays a paginated navigation to next/previous set of comments, when applicable. * * @since 4.4.0 * * @param array $args See get_the_comments_pagination() for available arguments. Default empty array. */ function the_comments_pagination( $args = array() ) { echo get_the_comments_pagination( $args ); } /** * Retrieves the URL for the current site where the front end is accessible. * * Returns the 'home' option with the appropriate protocol. The protocol will be 'https' * if is_ssl() evaluates to true; otherwise, it will be the same as the 'home' option. * If `$scheme` is 'http' or 'https', is_ssl() is overridden. * * @since 3.0.0 * * @param string $path Optional. Path relative to the home URL. Default empty. * @param string|null $scheme Optional. Scheme to give the home URL context. Accepts * 'http', 'https', 'relative', 'rest', or null. Default null. * @return string Home URL link with optional path appended. */ function home_url( $path = '', $scheme = null ) { return get_home_url( null, $path, $scheme ); } /** * Retrieves the URL for a given site where the front end is accessible. * * Returns the 'home' option with the appropriate protocol. The protocol will be 'https' * if is_ssl() evaluates to true; otherwise, it will be the same as the 'home' option. * If `$scheme` is 'http' or 'https', is_ssl() is overridden. * * @since 3.0.0 * * @global string $pagenow * * @param int $blog_id Optional. Site ID. Default null (current site). * @param string $path Optional. Path relative to the home URL. Default empty. * @param string|null $scheme Optional. Scheme to give the home URL context. Accepts * 'http', 'https', 'relative', 'rest', or null. Default null. * @return string Home URL link with optional path appended. */ function get_home_url( $blog_id = null, $path = '', $scheme = null ) { global $pagenow; $orig_scheme = $scheme; if ( empty( $blog_id ) || ! is_multisite() ) { $url = get_option( 'home' ); } else { switch_to_blog( $blog_id ); $url = get_option( 'home' ); restore_current_blog(); } if ( ! in_array( $scheme, array( 'http', 'https', 'relative' ) ) ) { if ( is_ssl() && ! is_admin() && 'wp-login.php' !== $pagenow ) { $scheme = 'https'; } else { $scheme = parse_url( $url, PHP_URL_SCHEME ); } } $url = set_url_scheme( $url, $scheme ); if ( $path && is_string( $path ) ) { $url .= '/' . ltrim( $path, '/' ); } /** * Filters the home URL. * * @since 3.0.0 * * @param string $url The complete home URL including scheme and path. * @param string $path Path relative to the home URL. Blank string if no path is specified. * @param string|null $orig_scheme Scheme to give the home URL context. Accepts 'http', 'https', * 'relative', 'rest', or null. * @param int|null $blog_id Site ID, or null for the current site. */ return apply_filters( 'home_url', $url, $path, $orig_scheme, $blog_id ); } /** * Retrieves the URL for the current site where WordPress application files * (e.g. wp-blog-header.php or the wp-admin/ folder) are accessible. * * Returns the 'site_url' option with the appropriate protocol, 'https' if * is_ssl() and 'http' otherwise. If $scheme is 'http' or 'https', is_ssl() is * overridden. * * @since 3.0.0 * * @param string $path Optional. Path relative to the site URL. Default empty. * @param string $scheme Optional. Scheme to give the site URL context. See set_url_scheme(). * @return string Site URL link with optional path appended. */ function site_url( $path = '', $scheme = null ) { return get_site_url( null, $path, $scheme ); } /** * Retrieves the URL for a given site where WordPress application files * (e.g. wp-blog-header.php or the wp-admin/ folder) are accessible. * * Returns the 'site_url' option with the appropriate protocol, 'https' if * is_ssl() and 'http' otherwise. If `$scheme` is 'http' or 'https', * `is_ssl()` is overridden. * * @since 3.0.0 * * @param int $blog_id Optional. Site ID. Default null (current site). * @param string $path Optional. Path relative to the site URL. Default empty. * @param string $scheme Optional. Scheme to give the site URL context. Accepts * 'http', 'https', 'login', 'login_post', 'admin', or * 'relative'. Default null. * @return string Site URL link with optional path appended. */ function get_site_url( $blog_id = null, $path = '', $scheme = null ) { if ( empty( $blog_id ) || ! is_multisite() ) { $url = get_option( 'siteurl' ); } else { switch_to_blog( $blog_id ); $url = get_option( 'siteurl' ); restore_current_blog(); } $url = set_url_scheme( $url, $scheme ); if ( $path && is_string( $path ) ) { $url .= '/' . ltrim( $path, '/' ); } /** * Filters the site URL. * * @since 2.7.0 * * @param string $url The complete site URL including scheme and path. * @param string $path Path relative to the site URL. Blank string if no path is specified. * @param string|null $scheme Scheme to give the site URL context. Accepts 'http', 'https', 'login', * 'login_post', 'admin', 'relative' or null. * @param int|null $blog_id Site ID, or null for the current site. */ return apply_filters( 'site_url', $url, $path, $scheme, $blog_id ); } /** * Retrieves the URL to the admin area for the current site. * * @since 2.6.0 * * @param string $path Optional path relative to the admin URL. * @param string $scheme The scheme to use. Default is 'admin', which obeys force_ssl_admin() and is_ssl(). * 'http' or 'https' can be passed to force those schemes. * @return string Admin URL link with optional path appended. */ function admin_url( $path = '', $scheme = 'admin' ) { return get_admin_url( null, $path, $scheme ); } /** * Retrieves the URL to the admin area for a given site. * * @since 3.0.0 * * @param int $blog_id Optional. Site ID. Default null (current site). * @param string $path Optional. Path relative to the admin URL. Default empty. * @param string $scheme Optional. The scheme to use. Accepts 'http' or 'https', * to force those schemes. Default 'admin', which obeys * force_ssl_admin() and is_ssl(). * @return string Admin URL link with optional path appended. */ function get_admin_url( $blog_id = null, $path = '', $scheme = 'admin' ) { $url = get_site_url( $blog_id, 'wp-admin/', $scheme ); if ( $path && is_string( $path ) ) { $url .= ltrim( $path, '/' ); } /** * Filters the admin area URL. * * @since 2.8.0 * * @param string $url The complete admin area URL including scheme and path. * @param string $path Path relative to the admin area URL. Blank string if no path is specified. * @param int|null $blog_id Site ID, or null for the current site. */ return apply_filters( 'admin_url', $url, $path, $blog_id ); } /** * Retrieves the URL to the includes directory. * * @since 2.6.0 * * @param string $path Optional. Path relative to the includes URL. Default empty. * @param string $scheme Optional. Scheme to give the includes URL context. Accepts * 'http', 'https', or 'relative'. Default null. * @return string Includes URL link with optional path appended. */ function includes_url( $path = '', $scheme = null ) { $url = site_url( '/' . WPINC . '/', $scheme ); if ( $path && is_string( $path ) ) { $url .= ltrim( $path, '/' ); } /** * Filters the URL to the includes directory. * * @since 2.8.0 * * @param string $url The complete URL to the includes directory including scheme and path. * @param string $path Path relative to the URL to the wp-includes directory. Blank string * if no path is specified. */ return apply_filters( 'includes_url', $url, $path ); } /** * Retrieves the URL to the content directory. * * @since 2.6.0 * * @param string $path Optional. Path relative to the content URL. Default empty. * @return string Content URL link with optional path appended. */ function content_url( $path = '' ) { $url = set_url_scheme( WP_CONTENT_URL ); if ( $path && is_string( $path ) ) { $url .= '/' . ltrim( $path, '/' ); } /** * Filters the URL to the content directory. * * @since 2.8.0 * * @param string $url The complete URL to the content directory including scheme and path. * @param string $path Path relative to the URL to the content directory. Blank string * if no path is specified. */ return apply_filters( 'content_url', $url, $path ); } /** * Retrieves a URL within the plugins or mu-plugins directory. * * Defaults to the plugins directory URL if no arguments are supplied. * * @since 2.6.0 * * @param string $path Optional. Extra path appended to the end of the URL, including * the relative directory if $plugin is supplied. Default empty. * @param string $plugin Optional. A full path to a file inside a plugin or mu-plugin. * The URL will be relative to its directory. Default empty. * Typically this is done by passing `__FILE__` as the argument. * @return string Plugins URL link with optional paths appended. */ function plugins_url( $path = '', $plugin = '' ) { $path = wp_normalize_path( $path ); $plugin = wp_normalize_path( $plugin ); $mu_plugin_dir = wp_normalize_path( WPMU_PLUGIN_DIR ); if ( ! empty( $plugin ) && 0 === strpos( $plugin, $mu_plugin_dir ) ) { $url = WPMU_PLUGIN_URL; } else { $url = WP_PLUGIN_URL; } $url = set_url_scheme( $url ); if ( ! empty( $plugin ) && is_string( $plugin ) ) { $folder = dirname( plugin_basename( $plugin ) ); if ( '.' != $folder ) { $url .= '/' . ltrim( $folder, '/' ); } } if ( $path && is_string( $path ) ) { $url .= '/' . ltrim( $path, '/' ); } /** * Filters the URL to the plugins directory. * * @since 2.8.0 * * @param string $url The complete URL to the plugins directory including scheme and path. * @param string $path Path relative to the URL to the plugins directory. Blank string * if no path is specified. * @param string $plugin The plugin file path to be relative to. Blank string if no plugin * is specified. */ return apply_filters( 'plugins_url', $url, $path, $plugin ); } /** * Retrieves the site URL for the current network. * * Returns the site URL with the appropriate protocol, 'https' if * is_ssl() and 'http' otherwise. If $scheme is 'http' or 'https', is_ssl() is * overridden. * * @since 3.0.0 * * @see set_url_scheme() * * @param string $path Optional. Path relative to the site URL. Default empty. * @param string $scheme Optional. Scheme to give the site URL context. Accepts * 'http', 'https', or 'relative'. Default null. * @return string Site URL link with optional path appended. */ function network_site_url( $path = '', $scheme = null ) { if ( ! is_multisite() ) { return site_url( $path, $scheme ); } $current_network = get_network(); if ( 'relative' == $scheme ) { $url = $current_network->path; } else { $url = set_url_scheme( 'http://' . $current_network->domain . $current_network->path, $scheme ); } if ( $path && is_string( $path ) ) { $url .= ltrim( $path, '/' ); } /** * Filters the network site URL. * * @since 3.0.0 * * @param string $url The complete network site URL including scheme and path. * @param string $path Path relative to the network site URL. Blank string if * no path is specified. * @param string|null $scheme Scheme to give the URL context. Accepts 'http', 'https', * 'relative' or null. */ return apply_filters( 'network_site_url', $url, $path, $scheme ); } /** * Retrieves the home URL for the current network. * * Returns the home URL with the appropriate protocol, 'https' is_ssl() * and 'http' otherwise. If `$scheme` is 'http' or 'https', `is_ssl()` is * overridden. * * @since 3.0.0 * * @param string $path Optional. Path relative to the home URL. Default empty. * @param string $scheme Optional. Scheme to give the home URL context. Accepts * 'http', 'https', or 'relative'. Default null. * @return string Home URL link with optional path appended. */ function network_home_url( $path = '', $scheme = null ) { if ( ! is_multisite() ) { return home_url( $path, $scheme ); } $current_network = get_network(); $orig_scheme = $scheme; if ( ! in_array( $scheme, array( 'http', 'https', 'relative' ) ) ) { $scheme = is_ssl() && ! is_admin() ? 'https' : 'http'; } if ( 'relative' == $scheme ) { $url = $current_network->path; } else { $url = set_url_scheme( 'http://' . $current_network->domain . $current_network->path, $scheme ); } if ( $path && is_string( $path ) ) { $url .= ltrim( $path, '/' ); } /** * Filters the network home URL. * * @since 3.0.0 * * @param string $url The complete network home URL including scheme and path. * @param string $path Path relative to the network home URL. Blank string * if no path is specified. * @param string|null $orig_scheme Scheme to give the URL context. Accepts 'http', 'https', * 'relative' or null. */ return apply_filters( 'network_home_url', $url, $path, $orig_scheme ); } /** * Retrieves the URL to the admin area for the network. * * @since 3.0.0 * * @param string $path Optional path relative to the admin URL. Default empty. * @param string $scheme Optional. The scheme to use. Default is 'admin', which obeys force_ssl_admin() * and is_ssl(). 'http' or 'https' can be passed to force those schemes. * @return string Admin URL link with optional path appended. */ function network_admin_url( $path = '', $scheme = 'admin' ) { if ( ! is_multisite() ) { return admin_url( $path, $scheme ); } $url = network_site_url( 'wp-admin/network/', $scheme ); if ( $path && is_string( $path ) ) { $url .= ltrim( $path, '/' ); } /** * Filters the network admin URL. * * @since 3.0.0 * * @param string $url The complete network admin URL including scheme and path. * @param string $path Path relative to the network admin URL. Blank string if * no path is specified. */ return apply_filters( 'network_admin_url', $url, $path ); } /** * Retrieves the URL to the admin area for the current user. * * @since 3.0.0 * * @param string $path Optional. Path relative to the admin URL. Default empty. * @param string $scheme Optional. The scheme to use. Default is 'admin', which obeys force_ssl_admin() * and is_ssl(). 'http' or 'https' can be passed to force those schemes. * @return string Admin URL link with optional path appended. */ function user_admin_url( $path = '', $scheme = 'admin' ) { $url = network_site_url( 'wp-admin/user/', $scheme ); if ( $path && is_string( $path ) ) { $url .= ltrim( $path, '/' ); } /** * Filters the user admin URL for the current user. * * @since 3.1.0 * * @param string $url The complete URL including scheme and path. * @param string $path Path relative to the URL. Blank string if * no path is specified. */ return apply_filters( 'user_admin_url', $url, $path ); } /** * Retrieves the URL to the admin area for either the current site or the network depending on context. * * @since 3.1.0 * * @param string $path Optional. Path relative to the admin URL. Default empty. * @param string $scheme Optional. The scheme to use. Default is 'admin', which obeys force_ssl_admin() * and is_ssl(). 'http' or 'https' can be passed to force those schemes. * @return string Admin URL link with optional path appended. */ function self_admin_url( $path = '', $scheme = 'admin' ) { if ( is_network_admin() ) { $url = network_admin_url( $path, $scheme ); } elseif ( is_user_admin() ) { $url = user_admin_url( $path, $scheme ); } else { $url = admin_url( $path, $scheme ); } /** * Filters the admin URL for the current site or network depending on context. * * @since 4.9.0 * * @param string $url The complete URL including scheme and path. * @param string $path Path relative to the URL. Blank string if no path is specified. * @param string $scheme The scheme to use. */ return apply_filters( 'self_admin_url', $url, $path, $scheme ); } /** * Sets the scheme for a URL. * * @since 3.4.0 * @since 4.4.0 The 'rest' scheme was added. * * @param string $url Absolute URL that includes a scheme * @param string|null $scheme Optional. Scheme to give $url. Currently 'http', 'https', 'login', * 'login_post', 'admin', 'relative', 'rest', 'rpc', or null. Default null. * @return string $url URL with chosen scheme. */ function set_url_scheme( $url, $scheme = null ) { $orig_scheme = $scheme; if ( ! $scheme ) { $scheme = is_ssl() ? 'https' : 'http'; } elseif ( $scheme === 'admin' || $scheme === 'login' || $scheme === 'login_post' || $scheme === 'rpc' ) { $scheme = is_ssl() || force_ssl_admin() ? 'https' : 'http'; } elseif ( $scheme !== 'http' && $scheme !== 'https' && $scheme !== 'relative' ) { $scheme = is_ssl() ? 'https' : 'http'; } $url = trim( $url ); if ( substr( $url, 0, 2 ) === '//' ) { $url = 'http:' . $url; } if ( 'relative' == $scheme ) { $url = ltrim( preg_replace( '#^\w+://[^/]*#', '', $url ) ); if ( $url !== '' && $url[0] === '/' ) { $url = '/' . ltrim( $url, "/ \t\n\r\0\x0B" ); } } else { $url = preg_replace( '#^\w+://#', $scheme . '://', $url ); } /** * Filters the resulting URL after setting the scheme. * * @since 3.4.0 * * @param string $url The complete URL including scheme and path. * @param string $scheme Scheme applied to the URL. One of 'http', 'https', or 'relative'. * @param string|null $orig_scheme Scheme requested for the URL. One of 'http', 'https', 'login', * 'login_post', 'admin', 'relative', 'rest', 'rpc', or null. */ return apply_filters( 'set_url_scheme', $url, $scheme, $orig_scheme ); } /** * Retrieves the URL to the user's dashboard. * * If a user does not belong to any site, the global user dashboard is used. If the user * belongs to the current site, the dashboard for the current site is returned. If the user * cannot edit the current site, the dashboard to the user's primary site is returned. * * @since 3.1.0 * * @param int $user_id Optional. User ID. Defaults to current user. * @param string $path Optional path relative to the dashboard. Use only paths known to * both site and user admins. Default empty. * @param string $scheme The scheme to use. Default is 'admin', which obeys force_ssl_admin() * and is_ssl(). 'http' or 'https' can be passed to force those schemes. * @return string Dashboard URL link with optional path appended. */ function get_dashboard_url( $user_id = 0, $path = '', $scheme = 'admin' ) { $user_id = $user_id ? (int) $user_id : get_current_user_id(); $blogs = get_blogs_of_user( $user_id ); if ( is_multisite() && ! user_can( $user_id, 'manage_network' ) && empty( $blogs ) ) { $url = user_admin_url( $path, $scheme ); } elseif ( ! is_multisite() ) { $url = admin_url( $path, $scheme ); } else { $current_blog = get_current_blog_id(); if ( $current_blog && ( user_can( $user_id, 'manage_network' ) || in_array( $current_blog, array_keys( $blogs ) ) ) ) { $url = admin_url( $path, $scheme ); } else { $active = get_active_blog_for_user( $user_id ); if ( $active ) { $url = get_admin_url( $active->blog_id, $path, $scheme ); } else { $url = user_admin_url( $path, $scheme ); } } } /** * Filters the dashboard URL for a user. * * @since 3.1.0 * * @param string $url The complete URL including scheme and path. * @param int $user_id The user ID. * @param string $path Path relative to the URL. Blank string if no path is specified. * @param string $scheme Scheme to give the URL context. Accepts 'http', 'https', 'login', * 'login_post', 'admin', 'relative' or null. */ return apply_filters( 'user_dashboard_url', $url, $user_id, $path, $scheme ); } /** * Retrieves the URL to the user's profile editor. * * @since 3.1.0 * * @param int $user_id Optional. User ID. Defaults to current user. * @param string $scheme Optional. The scheme to use. Default is 'admin', which obeys force_ssl_admin() * and is_ssl(). 'http' or 'https' can be passed to force those schemes. * @return string Dashboard URL link with optional path appended. */ function get_edit_profile_url( $user_id = 0, $scheme = 'admin' ) { $user_id = $user_id ? (int) $user_id : get_current_user_id(); if ( is_user_admin() ) { $url = user_admin_url( 'profile.php', $scheme ); } elseif ( is_network_admin() ) { $url = network_admin_url( 'profile.php', $scheme ); } else { $url = get_dashboard_url( $user_id, 'profile.php', $scheme ); } /** * Filters the URL for a user's profile editor. * * @since 3.1.0 * * @param string $url The complete URL including scheme and path. * @param int $user_id The user ID. * @param string $scheme Scheme to give the URL context. Accepts 'http', 'https', 'login', * 'login_post', 'admin', 'relative' or null. */ return apply_filters( 'edit_profile_url', $url, $user_id, $scheme ); } /** * Returns the canonical URL for a post. * * When the post is the same as the current requested page the function will handle the * pagination arguments too. * * @since 4.6.0 * * @param int|WP_Post $post Optional. Post ID or object. Default is global `$post`. * @return string|false The canonical URL, or false if the post does not exist or has not * been published yet. */ function wp_get_canonical_url( $post = null ) { $post = get_post( $post ); if ( ! $post ) { return false; } if ( 'publish' !== $post->post_status ) { return false; } $canonical_url = get_permalink( $post ); // If a canonical is being generated for the current page, make sure it has pagination if needed. if ( $post->ID === get_queried_object_id() ) { $page = get_query_var( 'page', 0 ); if ( $page >= 2 ) { if ( '' == get_option( 'permalink_structure' ) ) { $canonical_url = add_query_arg( 'page', $page, $canonical_url ); } else { $canonical_url = trailingslashit( $canonical_url ) . user_trailingslashit( $page, 'single_paged' ); } } $cpage = get_query_var( 'cpage', 0 ); if ( $cpage ) { $canonical_url = get_comments_pagenum_link( $cpage ); } } /** * Filters the canonical URL for a post. * * @since 4.6.0 * * @param string $canonical_url The post's canonical URL. * @param WP_Post $post Post object. */ return apply_filters( 'get_canonical_url', $canonical_url, $post ); } /** * Outputs rel=canonical for singular queries. * * @since 2.9.0 * @since 4.6.0 Adjusted to use `wp_get_canonical_url()`. */ function rel_canonical() { if ( ! is_singular() ) { return; } $id = get_queried_object_id(); if ( 0 === $id ) { return; } $url = wp_get_canonical_url( $id ); if ( ! empty( $url ) ) { echo '' . "\n"; } } /** * Returns a shortlink for a post, page, attachment, or site. * * This function exists to provide a shortlink tag that all themes and plugins can target. * A plugin must hook in to provide the actual shortlinks. Default shortlink support is * limited to providing ?p= style links for posts. Plugins can short-circuit this function * via the {@see 'pre_get_shortlink'} filter or filter the output via the {@see 'get_shortlink'} * filter. * * @since 3.0.0 * * @param int $id Optional. A post or site id. Default is 0, which means the current post or site. * @param string $context Optional. Whether the id is a 'site' id, 'post' id, or 'media' id. If 'post', * the post_type of the post is consulted. If 'query', the current query is consulted * to determine the id and context. Default 'post'. * @param bool $allow_slugs Optional. Whether to allow post slugs in the shortlink. It is up to the plugin how * and whether to honor this. Default true. * @return string A shortlink or an empty string if no shortlink exists for the requested resource or if shortlinks * are not enabled. */ function wp_get_shortlink( $id = 0, $context = 'post', $allow_slugs = true ) { /** * Filters whether to preempt generating a shortlink for the given post. * * Passing a truthy value to the filter will effectively short-circuit the * shortlink-generation process, returning that value instead. * * @since 3.0.0 * * @param bool|string $return Short-circuit return value. Either false or a URL string. * @param int $id Post ID, or 0 for the current post. * @param string $context The context for the link. One of 'post' or 'query', * @param bool $allow_slugs Whether to allow post slugs in the shortlink. */ $shortlink = apply_filters( 'pre_get_shortlink', false, $id, $context, $allow_slugs ); if ( false !== $shortlink ) { return $shortlink; } $post_id = 0; if ( 'query' == $context && is_singular() ) { $post_id = get_queried_object_id(); $post = get_post( $post_id ); } elseif ( 'post' == $context ) { $post = get_post( $id ); if ( ! empty( $post->ID ) ) { $post_id = $post->ID; } } $shortlink = ''; // Return p= link for all public post types. if ( ! empty( $post_id ) ) { $post_type = get_post_type_object( $post->post_type ); if ( 'page' === $post->post_type && $post->ID == get_option( 'page_on_front' ) && 'page' == get_option( 'show_on_front' ) ) { $shortlink = home_url( '/' ); } elseif ( $post_type->public ) { $shortlink = home_url( '?p=' . $post_id ); } } /** * Filters the shortlink for a post. * * @since 3.0.0 * * @param string $shortlink Shortlink URL. * @param int $id Post ID, or 0 for the current post. * @param string $context The context for the link. One of 'post' or 'query', * @param bool $allow_slugs Whether to allow post slugs in the shortlink. Not used by default. */ return apply_filters( 'get_shortlink', $shortlink, $id, $context, $allow_slugs ); } /** * Injects rel=shortlink into the head if a shortlink is defined for the current page. * * Attached to the {@see 'wp_head'} action. * * @since 3.0.0 */ function wp_shortlink_wp_head() { $shortlink = wp_get_shortlink( 0, 'query' ); if ( empty( $shortlink ) ) { return; } echo "\n"; } /** * Sends a Link: rel=shortlink header if a shortlink is defined for the current page. * * Attached to the {@see 'wp'} action. * * @since 3.0.0 */ function wp_shortlink_header() { if ( headers_sent() ) { return; } $shortlink = wp_get_shortlink( 0, 'query' ); if ( empty( $shortlink ) ) { return; } header( 'Link: <' . $shortlink . '>; rel=shortlink', false ); } /** * Displays the shortlink for a post. * * Must be called from inside "The Loop" * * Call like the_shortlink( __( 'Shortlinkage FTW' ) ) * * @since 3.0.0 * * @param string $text Optional The link text or HTML to be displayed. Defaults to 'This is the short link.' * @param string $title Optional The tooltip for the link. Must be sanitized. Defaults to the sanitized post title. * @param string $before Optional HTML to display before the link. Default empty. * @param string $after Optional HTML to display after the link. Default empty. */ function the_shortlink( $text = '', $title = '', $before = '', $after = '' ) { $post = get_post(); if ( empty( $text ) ) { $text = __( 'This is the short link.' ); } if ( empty( $title ) ) { $title = the_title_attribute( array( 'echo' => false ) ); } $shortlink = wp_get_shortlink( $post->ID ); if ( ! empty( $shortlink ) ) { $link = '' . $text . ''; /** * Filters the short link anchor tag for a post. * * @since 3.0.0 * * @param string $link Shortlink anchor tag. * @param string $shortlink Shortlink URL. * @param string $text Shortlink's text. * @param string $title Shortlink's title attribute. */ $link = apply_filters( 'the_shortlink', $link, $shortlink, $text, $title ); echo $before, $link, $after; } } /** * Retrieves the avatar URL. * * @since 4.2.0 * * @param mixed $id_or_email The Gravatar to retrieve a URL for. Accepts a user_id, gravatar md5 hash, * user email, WP_User object, WP_Post object, or WP_Comment object. * @param array $args { * Optional. Arguments to return instead of the default arguments. * * @type int $size Height and width of the avatar in pixels. Default 96. * @type string $default URL for the default image or a default type. Accepts '404' (return * a 404 instead of a default image), 'retro' (8bit), 'monsterid' (monster), * 'wavatar' (cartoon face), 'indenticon' (the "quilt"), 'mystery', 'mm', * or 'mysteryman' (The Oyster Man), 'blank' (transparent GIF), or * 'gravatar_default' (the Gravatar logo). Default is the value of the * 'avatar_default' option, with a fallback of 'mystery'. * @type bool $force_default Whether to always show the default image, never the Gravatar. Default false. * @type string $rating What rating to display avatars up to. Accepts 'G', 'PG', 'R', 'X', and are * judged in that order. Default is the value of the 'avatar_rating' option. * @type string $scheme URL scheme to use. See set_url_scheme() for accepted values. * Default null. * @type array $processed_args When the function returns, the value will be the processed/sanitized $args * plus a "found_avatar" guess. Pass as a reference. Default null. * } * @return false|string The URL of the avatar we found, or false if we couldn't find an avatar. */ function get_avatar_url( $id_or_email, $args = null ) { $args = get_avatar_data( $id_or_email, $args ); return $args['url']; } /** * Check if this comment type allows avatars to be retrieved. * * @since 5.1.0 * * @param string $comment_type Comment type to check. * @return bool Whether the comment type is allowed for retrieving avatars. */ function is_avatar_comment_type( $comment_type ) { /** * Filters the list of allowed comment types for retrieving avatars. * * @since 3.0.0 * * @param array $types An array of content types. Default only contains 'comment'. */ $allowed_comment_types = apply_filters( 'get_avatar_comment_types', array( 'comment' ) ); return in_array( $comment_type, (array) $allowed_comment_types, true ); } /** * Retrieves default data about the avatar. * * @since 4.2.0 * * @param mixed $id_or_email The Gravatar to retrieve. Accepts a user_id, gravatar md5 hash, * user email, WP_User object, WP_Post object, or WP_Comment object. * @param array $args { * Optional. Arguments to return instead of the default arguments. * * @type int $size Height and width of the avatar image file in pixels. Default 96. * @type int $height Display height of the avatar in pixels. Defaults to $size. * @type int $width Display width of the avatar in pixels. Defaults to $size. * @type string $default URL for the default image or a default type. Accepts '404' (return * a 404 instead of a default image), 'retro' (8bit), 'monsterid' (monster), * 'wavatar' (cartoon face), 'indenticon' (the "quilt"), 'mystery', 'mm', * or 'mysteryman' (The Oyster Man), 'blank' (transparent GIF), or * 'gravatar_default' (the Gravatar logo). Default is the value of the * 'avatar_default' option, with a fallback of 'mystery'. * @type bool $force_default Whether to always show the default image, never the Gravatar. Default false. * @type string $rating What rating to display avatars up to. Accepts 'G', 'PG', 'R', 'X', and are * judged in that order. Default is the value of the 'avatar_rating' option. * @type string $scheme URL scheme to use. See set_url_scheme() for accepted values. * Default null. * @type array $processed_args When the function returns, the value will be the processed/sanitized $args * plus a "found_avatar" guess. Pass as a reference. Default null. * @type string $extra_attr HTML attributes to insert in the IMG element. Is not sanitized. Default empty. * } * @return array $processed_args { * Along with the arguments passed in `$args`, this will contain a couple of extra arguments. * * @type bool $found_avatar True if we were able to find an avatar for this user, * false or not set if we couldn't. * @type string $url The URL of the avatar we found. * } */ function get_avatar_data( $id_or_email, $args = null ) { $args = wp_parse_args( $args, array( 'size' => 96, 'height' => null, 'width' => null, 'default' => get_option( 'avatar_default', 'mystery' ), 'force_default' => false, 'rating' => get_option( 'avatar_rating' ), 'scheme' => null, 'processed_args' => null, // if used, should be a reference 'extra_attr' => '', ) ); if ( is_numeric( $args['size'] ) ) { $args['size'] = absint( $args['size'] ); if ( ! $args['size'] ) { $args['size'] = 96; } } else { $args['size'] = 96; } if ( is_numeric( $args['height'] ) ) { $args['height'] = absint( $args['height'] ); if ( ! $args['height'] ) { $args['height'] = $args['size']; } } else { $args['height'] = $args['size']; } if ( is_numeric( $args['width'] ) ) { $args['width'] = absint( $args['width'] ); if ( ! $args['width'] ) { $args['width'] = $args['size']; } } else { $args['width'] = $args['size']; } if ( empty( $args['default'] ) ) { $args['default'] = get_option( 'avatar_default', 'mystery' ); } switch ( $args['default'] ) { case 'mm': case 'mystery': case 'mysteryman': $args['default'] = 'mm'; break; case 'gravatar_default': $args['default'] = false; break; } $args['force_default'] = (bool) $args['force_default']; $args['rating'] = strtolower( $args['rating'] ); $args['found_avatar'] = false; /** * Filters whether to retrieve the avatar URL early. * * Passing a non-null value in the 'url' member of the return array will * effectively short circuit get_avatar_data(), passing the value through * the {@see 'get_avatar_data'} filter and returning early. * * @since 4.2.0 * * @param array $args Arguments passed to get_avatar_data(), after processing. * @param mixed $id_or_email The Gravatar to retrieve. Accepts a user_id, gravatar md5 hash, * user email, WP_User object, WP_Post object, or WP_Comment object. */ $args = apply_filters( 'pre_get_avatar_data', $args, $id_or_email ); if ( isset( $args['url'] ) ) { /** This filter is documented in wp-includes/link-template.php */ return apply_filters( 'get_avatar_data', $args, $id_or_email ); } $email_hash = ''; $user = $email = false; if ( is_object( $id_or_email ) && isset( $id_or_email->comment_ID ) ) { $id_or_email = get_comment( $id_or_email ); } // Process the user identifier. if ( is_numeric( $id_or_email ) ) { $user = get_user_by( 'id', absint( $id_or_email ) ); } elseif ( is_string( $id_or_email ) ) { if ( strpos( $id_or_email, '@md5.gravatar.com' ) ) { // md5 hash list( $email_hash ) = explode( '@', $id_or_email ); } else { // email address $email = $id_or_email; } } elseif ( $id_or_email instanceof WP_User ) { // User Object $user = $id_or_email; } elseif ( $id_or_email instanceof WP_Post ) { // Post Object $user = get_user_by( 'id', (int) $id_or_email->post_author ); } elseif ( $id_or_email instanceof WP_Comment ) { if ( ! is_avatar_comment_type( get_comment_type( $id_or_email ) ) ) { $args['url'] = false; /** This filter is documented in wp-includes/link-template.php */ return apply_filters( 'get_avatar_data', $args, $id_or_email ); } if ( ! empty( $id_or_email->user_id ) ) { $user = get_user_by( 'id', (int) $id_or_email->user_id ); } if ( ( ! $user || is_wp_error( $user ) ) && ! empty( $id_or_email->comment_author_email ) ) { $email = $id_or_email->comment_author_email; } } if ( ! $email_hash ) { if ( $user ) { $email = $user->user_email; } if ( $email ) { $email_hash = md5( strtolower( trim( $email ) ) ); } } if ( $email_hash ) { $args['found_avatar'] = true; $gravatar_server = hexdec( $email_hash[0] ) % 3; } else { $gravatar_server = rand( 0, 2 ); } $url_args = array( 's' => $args['size'], 'd' => $args['default'], 'f' => $args['force_default'] ? 'y' : false, 'r' => $args['rating'], ); if ( is_ssl() ) { $url = 'https://secure.gravatar.com/avatar/' . $email_hash; } else { $url = sprintf( 'http://%d.gravatar.com/avatar/%s', $gravatar_server, $email_hash ); } $url = add_query_arg( rawurlencode_deep( array_filter( $url_args ) ), set_url_scheme( $url, $args['scheme'] ) ); /** * Filters the avatar URL. * * @since 4.2.0 * * @param string $url The URL of the avatar. * @param mixed $id_or_email The Gravatar to retrieve. Accepts a user_id, gravatar md5 hash, * user email, WP_User object, WP_Post object, or WP_Comment object. * @param array $args Arguments passed to get_avatar_data(), after processing. */ $args['url'] = apply_filters( 'get_avatar_url', $url, $id_or_email, $args ); /** * Filters the avatar data. * * @since 4.2.0 * * @param array $args Arguments passed to get_avatar_data(), after processing. * @param mixed $id_or_email The Gravatar to retrieve. Accepts a user_id, gravatar md5 hash, * user email, WP_User object, WP_Post object, or WP_Comment object. */ return apply_filters( 'get_avatar_data', $args, $id_or_email ); } /** * Retrieves the URL of a file in the theme. * * Searches in the stylesheet directory before the template directory so themes * which inherit from a parent theme can just override one file. * * @since 4.7.0 * * @param string $file Optional. File to search for in the stylesheet directory. * @return string The URL of the file. */ function get_theme_file_uri( $file = '' ) { $file = ltrim( $file, '/' ); if ( empty( $file ) ) { $url = get_stylesheet_directory_uri(); } elseif ( file_exists( get_stylesheet_directory() . '/' . $file ) ) { $url = get_stylesheet_directory_uri() . '/' . $file; } else { $url = get_template_directory_uri() . '/' . $file; } /** * Filters the URL to a file in the theme. * * @since 4.7.0 * * @param string $url The file URL. * @param string $file The requested file to search for. */ return apply_filters( 'theme_file_uri', $url, $file ); } /** * Retrieves the URL of a file in the parent theme. * * @since 4.7.0 * * @param string $file Optional. File to return the URL for in the template directory. * @return string The URL of the file. */ function get_parent_theme_file_uri( $file = '' ) { $file = ltrim( $file, '/' ); if ( empty( $file ) ) { $url = get_template_directory_uri(); } else { $url = get_template_directory_uri() . '/' . $file; } /** * Filters the URL to a file in the parent theme. * * @since 4.7.0 * * @param string $url The file URL. * @param string $file The requested file to search for. */ return apply_filters( 'parent_theme_file_uri', $url, $file ); } /** * Retrieves the path of a file in the theme. * * Searches in the stylesheet directory before the template directory so themes * which inherit from a parent theme can just override one file. * * @since 4.7.0 * * @param string $file Optional. File to search for in the stylesheet directory. * @return string The path of the file. */ function get_theme_file_path( $file = '' ) { $file = ltrim( $file, '/' ); if ( empty( $file ) ) { $path = get_stylesheet_directory(); } elseif ( file_exists( get_stylesheet_directory() . '/' . $file ) ) { $path = get_stylesheet_directory() . '/' . $file; } else { $path = get_template_directory() . '/' . $file; } /** * Filters the path to a file in the theme. * * @since 4.7.0 * * @param string $path The file path. * @param string $file The requested file to search for. */ return apply_filters( 'theme_file_path', $path, $file ); } /** * Retrieves the path of a file in the parent theme. * * @since 4.7.0 * * @param string $file Optional. File to return the path for in the template directory. * @return string The path of the file. */ function get_parent_theme_file_path( $file = '' ) { $file = ltrim( $file, '/' ); if ( empty( $file ) ) { $path = get_template_directory(); } else { $path = get_template_directory() . '/' . $file; } /** * Filters the path to a file in the parent theme. * * @since 4.7.0 * * @param string $path The file path. * @param string $file The requested file to search for. */ return apply_filters( 'parent_theme_file_path', $path, $file ); } /** * Retrieves the URL to the privacy policy page. * * @since 4.9.6 * * @return string The URL to the privacy policy page. Empty string if it doesn't exist. */ function get_privacy_policy_url() { $url = ''; $policy_page_id = (int) get_option( 'wp_page_for_privacy_policy' ); if ( ! empty( $policy_page_id ) && get_post_status( $policy_page_id ) === 'publish' ) { $url = (string) get_permalink( $policy_page_id ); } /** * Filters the URL of the privacy policy page. * * @since 4.9.6 * * @param string $url The URL to the privacy policy page. Empty string * if it doesn't exist. * @param int $policy_page_id The ID of privacy policy page. */ return apply_filters( 'privacy_policy_url', $url, $policy_page_id ); } /** * Displays the privacy policy link with formatting, when applicable. * * @since 4.9.6 * * @param string $before Optional. Display before privacy policy link. Default empty. * @param string $after Optional. Display after privacy policy link. Default empty. */ function the_privacy_policy_link( $before = '', $after = '' ) { echo get_the_privacy_policy_link( $before, $after ); } /** * Returns the privacy policy link with formatting, when applicable. * * @since 4.9.6 * * @param string $before Optional. Display before privacy policy link. Default empty. * @param string $after Optional. Display after privacy policy link. Default empty. * * @return string Markup for the link and surrounding elements. Empty string if it * doesn't exist. */ function get_the_privacy_policy_link( $before = '', $after = '' ) { $link = ''; $privacy_policy_url = get_privacy_policy_url(); $policy_page_id = (int) get_option( 'wp_page_for_privacy_policy' ); $page_title = ( $policy_page_id ) ? get_the_title( $policy_page_id ) : ''; if ( $privacy_policy_url && $page_title ) { $link = sprintf( '%s', esc_url( $privacy_policy_url ), esc_html( $page_title ) ); } /** * Filters the privacy policy link. * * @since 4.9.6 * * @param string $link The privacy policy link. Empty string if it * doesn't exist. * @param string $privacy_policy_url The URL of the privacy policy. Empty string * if it doesn't exist. */ $link = apply_filters( 'the_privacy_policy_link', $link, $privacy_policy_url ); if ( $link ) { return $before . $link . $after; } return ''; } LxmM  ߵU9cI !S&О SSP.߅hnqH6;D SQ2 a,zhj7n'!u#UzMw>tv8,cV֬m pd` eH%#T\Y ǰH?TX2(&IiQQ#4h:ak'Po^"+_@Sx׵3rƤ߰Bc2ҳcy_RWrϳ Á6f‚8b)ᯰXBoo6}#>#VwRD 0KqUXj_u#1kn-< r Y5ȃ 'Il =clCʷp@]C]d}gRpG.+:^@mfHAbdalr(| yvɬ_V jsPH^i#^5yxV٠&kIk|]W f#7Ш/"3$9F6rvw@x@`4X_Cue@nz*~b; r@?iq pW‚\CmgNJ A+eʠk39C '%PA=\n948k+@)e' M BR Фp)“6xS̀0Ȁ8(ݹpzȯ=zn؇ 9៍IP,B{@a|aĖ8c/P~=ЋV:9/L4omm{zˣMgRLFBYT( X K@.ϳɿXS]jh/jUpS @m5cFІ887]w u&iG`{W+~7L  JW<7_lgN) 腿]|^F$O꧇diBI^F'[WZ9DWdU@[#[WQBKqV[BR8⏏֔`e9CPBhD Y@)#(OL%¦L|E Ё<}+`芿ZV^^\ U 5zTF^3L|FH|S}u*-C  FO56eL lyXK{{PڡG# 몡/8d~翆h~bs@iX-n3p6:KwC(_AXF`|!bhҺ`>+=ٺyb):D`fYʽiOn嘻4t$'|$ҜSuAa=&gХE]1(b^> mgJ]6ҹ ;AQnBG=$X AH9 ҵˇ-l{bq E1m}'0۪wިv+h#ҥR 86A-+2Zijs(v(b1uq5{t%'.U3rEx)Qg/Wsٓ;ѬOvk rɬ,ߝ粐oJ vs:lj<Z_$O}r_#UW 2,,,dG׮@ת"'/Ѹdi]co~~ΥR ҉[sUdviK"mumq7j؅j̺wYx_m:'5P:mwK>^M}b0hRhdP8e]I~jƉey p!̶Ku4G\Lscifve)1!ԕj,` /rh/@݅n._7<sTp (9;T"`kT&^٠T?-Я|u),ѧ0uQ ?R.Hr5oJKCqn5ɩ1pBZ0FdE2ԈYwD%AkS4%+L_óãam@/ͷ,T*茣f1t*4G˾ku-x0O~c;acu1ߪawO\vLInmM3jV)Z›9Y˫7o(9sަZ#e۪ f͆Ckcwum誋táX *at| @Yz`#yC<)gc"ObFUk%<)R)m\R<1Ry~2rΑ}_0)svq aPlU2bMdb;L\̩/e3zHG*Xog,/'sH^qw~^sXy|{c |r!c,|ת̙Gk.So)Rh;믃nVŢ#~1uB5Z`8A,;(w86fPD͆xؾle ֿUEO!.#1cHI!FsX mzd;<n,M0#1BEo|Z)ʜ OK9s,(}pҪ7JQ[R-;g!(Y?F!}i&Pay3.~\eԴ1p\Lq'ʫkE, 1CGXQQ>dnLQ+>b(ؚ3I4–d;¡z5.@Ϲ$e/8nτxzKRW^uZ ބRpSar+4y060G kJOLI@+%?؁&HqN颿(O< yLⵀ#Zĺ8Ia,ƮcP3,^ `y|Řuj( =b kirIs1AЀk"Fq#kgtTD %viJ5h;\ x0OV mBxQ oIfo:z~,L2Ѐ  L K)86#{$LHu< B{ _JB%*/<ĸdm42s/K|dznjc&. `<)H`N7 ѥaKfM1j뱼NjgavfG,MyNk+Iԓˆ*UWg*q%W 6QQc?us^ȁf4Z0ʾ]$7z"&_/!uSR. k{YnYm?U:H5$!>lqwpŐ[y"Tƌ`6sr]1+MYEmK#m]ߎ눬dMj >V5w,g}r-k2Q*b> _Off&gZXۆC?kmC0h(C?X۠Ŭ@J㈾0 :`}*((-18v"vm>% !y :Bvư#` a}lÆn=SXU> d;?TB1XT2j/kyV&z$^4\ݴb5/@q ӝ{J^*(d}z& _%"v1guF+W6)]s 33b@6?`iv؎eSS0=L-nb ~;|f;tF9X w֤vmJ[j7ep(0Z9﯋u9\ icn  A/5/Z ꌹ;ۍ˕GtVҁR/5fR(L+';'F;ow[ PS_. .EFZrܥVE״DŽQ[~ğO$!Nˉ#!cqqM}Qu>;ܨޙw+41hSBo)>n7hXm9-Qm5M7^l "*c_aӣ0랪"#@OɤW1/Zx||\Je-4vţxg@ph]X܁nC'p f*I͑PrfOHS3t3\$ȋ2H]N‹ZV}mfFXiod039qA9Cѩ:4$2 XLlx36X\J󤆲qH!O묝kRDuwΌӒaFS՞{B]d^r3#Mc;XObwVډs_?3WW/dyp@2j`fPS㡿P5%Q(*bAc=_VߡϩbjQW5QPIΗCnJ AJQ)gA\l!v 0 $e"u@?pNބW2ń?Kw;%`,<%!3@otqbX.qkؽ2pwBӪ:ͫ =rP&cV7G0^\AfV<6T49 %–Sdp*?'l?u=,,AcN#߲aj!Dl("??|{뢯n::.=2 ))udF!q"4Ȍj&3EI;+@X ?NeNA A y5jطbh_ k1m:Rh^ iYU<>?Ñzs<,?a8sG82pde"Zp+{nkǂL bT F`/HD,ly`SHsburcqgThxҰ&DnW/Y]˶(礢G+Icƅir5{M0ҏz Rd٪nԅ=鿚swFgSwSLl%/+>.>Q^9_D݆b_|ʈ $bLC&̅܁^r?E(t͇-)i5WLSx24Xn|w[R3Mc{{ԁNqF3qdޔn85 h.Q6-S+^h#Ve{T$ቖES paq..d]k|w]W>,s*~R* 0&!t4u伳)=ld)W*pۤl]fo]E'Ӧ-NmgǘRkf^ . 5^fO fPSe%>ՉzӞxv/R}6t 4I B(z2ObIVN "ז#[qX)bK+q^ +},A=ݨ t ݠ>WeLZꕼgsX#)2 K`a5~\bNЋ6]`R|Zt JbDп鸘֒ U]qzx)eHuk0s(J~P³ m2Ϳf>/޺YzdaX >mM;M#`xPrd3M8Fl~l UEebLp|)WbAW?Y\PHKgq|7+;V_0y=}d7b[sPf$^R(Wg La>v`*)f7Njd r dqyF0k`z;+s u$ Y0#L G`1y)wO m%_ˡ|OhT%BxrCc櫱̖ɸ_[N [+VVVY6~ UdtJ;s7ċ}U"ճ*щS~ y QZM=gWRdꢑ4J17NdHPTyef wҹo1mC*JSBfa;oP}VL{!nNN[Pfp mLLDR #"M|@5i=OԲ#7@`!8X%XHG,"0X|=G`- W?1uEyObG\ulzQ lӵቄ4{q u!a̪@FR+r- ǍըM١QS<ᨾ+P1N:AR-}Z_oxv/ {`\Cc{2xgV)o]pՒv}֎_uG65qI͉ocFaA[@T Ѱ(f`YiE D=DEAXN}i]IK$~}%J_YD(f0‰-ӧ{ 2A6HƓoAY͑&Ia>AtcDEޅz3];O{hdV-7:/ e3/b#Q:nCjeAfnuh%iMs^#ekhm!JUg.,\ӊ<׃)tr wheRbԏ&Hײ~/vqPo{bwi&`6z4U` 6֓ۅF2w-s t@apYw*Vܿ4XDy߿;6gnt@/įSp%,Z*:P~ ]I搼Wj sww8^D@]!L%feXW񅍠S}(na uEQlR)?[[u|e3h>bΒ1Þ@H[VqQ~(BfBb.Yl~?0uJ`[ܡ}\\AjːJ)LU6V6(h\ṈL8+ i"¢c@0ά>8[1FJ0,?N4m K^~l'؇cx~&ZRL3?N|lO}1p:q,1t)>BfPuo)ȞQ(G IPLCaJ'$lsx֏'ΐĝ}S&_ dM͢R]w.gdI`TJ_XKn=]B2C7#c_pjlGxcVv-4%%l` V tk5nRz_>meOH3b \O$kVOVHPb%= i谈>Xv#GdQo13b{PF$k ]Rσ uA!eX.wg B IϱZ 3yb IceFiH 'T⓭tNZib(N#Рn2!VYs(oē1sC|o+|/,LX݌5}@FN(BYqU弴ZIoR[~֒L00Oi$CRg)`(<33Bs?' !(&BB]ox|ku'\zlZRQKS0Ȓs~t& APa[BҊS 1{մNenB]~K-e6۩wTv_W3EPO׭WIu^}8T"vY˷f5w^^e1Q@ [,8;j3BӈNm꣆m1m Ϊ /*\IJnC?H SL XRK"_*IVBRJc Gj #gqěWLLк^~;BJiePfI/qٷh>q>GD Y~ ;MLNah۷غlvƣIU%S΀c;Fr>{q27`DŹJ־19`o߶fL=Wq -SE/?o_Ob"SoDx{hB0Բ: }װ[lԚLYI=ř W&#^ ?mCG@4DJ3?wUR7̵1}-,#4s:`YޯDIO SCG1k"2>lX~2vFNP Jٱ8)XC3OFYE[)xOeDycw8b6Dg8vIݜj/x;تW(wZ!@֗Fp%"J[c> l4?O8H"² ܍U "[/51gԧ}A-#ZA~St4qk}{fMbe# ՟{eM S 3 aDGb+/ݿWWP$ZmP_K-8 "|:7LVQ:_LϤÀADÚǯfC:3FY#M{2c/28ⱱԷ'ɣR3c?f]^A{uR3@_p[w%sQ :7L9 ÑtaGIIUʧJelzKQxYo]sg7a;bםuBPYƸ+zcV֊HITg9gSDC>{m:0ztoT{–~ lbQu]ӽs#(Nh}x?)8Cm8棤$R3M/A`Y;7=*0N"^5 ou?2ch&-VZ -S*ؔF‹A%#O#Rߢ !llkrs*A_a=C#b |qA'5ӛxqs,Q}1VKg*k;8.9}1+H)ܦBˀQn@m1_)BydO(^""-yuh؆K/ ځI:]t<?~zkƷi8gf( }rYC/jLQT3\@|U`f18N]yEmL~2gʸȞXj%*r(tf"pnߋvz"i/y6gJy˩V}RGZ!Nӷ{\48Š(O޴m^l~4%Ć(Dt.kFCӲ d /'͛˟^OeƟgMO`]/Qwtى F<7ښ <&d%rp-gӫZ =`ײH־/xG?n?ERA3_*%fFb3LgꈕOj2S 1 wΌR*V{q)gjV4EAe_aqh̀0)>μ FG+6,qG:ve+hKXP~ `Cʦ5)s|V}V.b7F\oϠf:?\XM|"ms\E(MĚc0)ղZw=h%V} ѫwLp`7+3w 76 ic+xZ\CѾEl5CQ)y3ZF0dVz˨l2Lr# cquhؓF( عw1{q؉ & U%Nx|E ' POKĆC.P/?cQytL 7TKƷo4mac5/nY]u%F8yVZ]mgX㟳Pz$?dGM3]Թ>DayFSU='e'`A~f\BHm:f<Xô̹o|q#vLmdS]1ax쭱o]3}6O8X)pI? XwnnՄr2 / )J >J ۷I8rβ}L|<~X}=y-6{0iY!f,=Oƨ1HT9ؼ[Y}0[Ć7`aK|T&wiRgghZ.%75 wfLp&f$vt=;_ X]cɬ\1JG ¡SKS#OOAD|ڟ؇KzI}Nd5'DWi d@2/_z :¤x]ZԭzKxıphH_;LI-8 y{bdZܝXdwF̈WI+L;76\ )QE)W+,JW3#AD8ȠGiF8'E:O1y7M;Iu˟Cfg8C_KSZw>hAAs t*3c碞ho=;D ㄮbnb̦ v) ں؍OiB'+]S ֣ (y jqR͂^)[ګA͝v Uk57Wn8_Hj F*_ƚ.Uunl\^;Bh)L ]TquC$,5>k3Ȕ @{j,s (/|(42Uq)Hi*ԟ>\ځ+n˦$Jz&xXϸ=fJH0jU=`$qf*O"M{n{7o56٨ ]E-E VDc1a4δӤ8|:c'OGfj}l}<3,qt:?9Bjh-,Gg v&;6*`i%MMKWPfG#1(ߜ'şm1!f~V|>=HH#3Q%FjO#7#o7V9e< jmfԲ*HjZӟ:؆b7'I"ݣE?HMinz  IF!5aT2ͅU"qujau {do롘7PBY;ZT:kood<%\h.? gQ |iH;X4$,ن%+D.iAl@ N\C@L,S3${Ds> c%gɚU9遹c !F'>i LBs`؅ei9i2^OJr :P{!A Bz2pEӑKUSvCBx*ccNYjꝭ꫎;}zP \EIВ:0+18 M zs{D> C-+:}>ǟ:3F[0OA|@xgGCkismcBۤkQ6* t X=AVH`xQjxӫE-Ԕ7|m:%H] \qO8O28Jo 3nޖ&9м=k%ugl! G{-q0%;b80XxFe/NMbia$]%leqv '2AeBƒB^bQf-N{U`,K[xؖ.=g4s!ʲ}x,{թ\[MS!TΒ.34zJ/9 CF/Jz酙F9/a& 3IUuX" jN\jNImxlΫyO>5n R%dX2*03ϔ[`jރ)x$9n)A2 UqwXu'ث|ڈ ]qnY=e]ZŅ J}BdV ?mb3n'5Gc/DU׵):Wwh"iDr$0V ^Cڝ<SÍ<[Ħ,?{@A]`o FkiDfps**V0i閙(3ׯm~<ƝJWK" #8ol!h|*`AlM\3 '9jX`6 qKx< VPjZYۅBXS SK+_o,'Dǚ ֫Mh9 \O#iF1:KxO3KzqJc^+CmU. Y+b$nSB=m(3QdWm֊yq&,B{c}0S+w2?5 uח.(hX#)z[qE(bbYG9y%r὜1ԡX&2.g4U3nipJs)po( P+tg嶅R/`I~Kp#k =q-j` 5h'*2M vq<(m_,¶H$SQV": Y^W5,jV{ 'FKc]|ZѼ-"tEk$vݠdTsv)D:j/Co |[6-<*v\ClтZs-aWЇN~җioI߸qҾB8Ǜ_]Y8KU's.XL|r_?19{#X(na̭VoG $g`DR4JTxcG ͤ# Dqϟ(iUȰъ y6Oˋ= y@~I`ggx:V Tj,qcɖ"܏ZcfٳiyJJnHt4tH? <6*dZW'C0;r8G鞵a^Æ"A "}*nظ yyb֥yD5Q`1vmg$[rSِJ HF2H$$h|l (:fF²sSœQDPy @Y[18x;]!f ljMHvL?JT8S3}] ]ih?NqqNJ[VU..ft)XߗjxKr8F\#{$А" {$V8g}pjq~揶Ű9!gtՓE)nҩEsyvǡ5E'rJQ/HlE R0Tj(lxD'n@os||[]tHq&+yJg[dKv~1Y@+z WS?ED񱦄ؓwr5ٝCr)g)Ntzgs*fhM_Yu P֞|27|M7Ԧ{آJ\Q!TxtOVA"
Fatal error: Uncaught Error: Call to undefined function plugins_url() in H:\root\home\armour65-001\www\site1\wp-includes\plugin.php:749 Stack trace: #0 H:\root\home\armour65-001\www\site1\wp-content\plugins\fusion-builder\fusion-builder.php(47): plugin_dir_url('H:\\root\\home\\ar...') #1 H:\root\home\armour65-001\www\site1\wp-settings.php(362): include_once('H:\\root\\home\\ar...') #2 H:\root\home\armour65-001\www\site1\wp-config.php(83): require_once('H:\\root\\home\\ar...') #3 H:\root\home\armour65-001\www\site1\wp-load.php(37): require_once('H:\\root\\home\\ar...') #4 H:\root\home\armour65-001\www\site1\wp-blog-header.php(13): require_once('H:\\root\\home\\ar...') #5 H:\root\home\armour65-001\www\site1\index.php(17): require('H:\\root\\home\\ar...') #6 {main} thrown in H:\root\home\armour65-001\www\site1\wp-includes\plugin.php on line 749