From c7a2c647ecef61508676b054a006fad215b62971 Mon Sep 17 00:00:00 2001 From: Helen Hou-Sandi Date: Tue, 7 May 2019 16:27:46 -0400 Subject: [PATCH 01/73] Delete extraneous screenshot from root --- screenshot-1.png | Bin 76478 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 screenshot-1.png diff --git a/screenshot-1.png b/screenshot-1.png deleted file mode 100644 index ebd81d0be2e84c9a9b0764df91555ab4560dca71..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 76478 zcmc$^Wl-GTvM!7zKp?m~3=Tm<7@XivkU$_<(BLw-yEC}EyF+kycXxMpzmWev=j{E~ z`|VcU^I_Iht@%x_?$z=<-7^8-WJEuH!219J0r62>Oh_IA;$0O41hfPk)a%ZYzvcu4 zB(0CQ&{su!$ipQM1D!dd*0Y-ydPI7D6l(VjDGZ+sY7B}f77S_~i0_<=Qw4GqsNIP3 z!kLpW$z<@0{Ll=*);omdf$OFzP{i`!Y`my6FL94H2n6DCF9o^YXrjIzqva6*cztsR zzMV+|eqA8^-!>rD;a-=2-=_ z^XLK$G^ zzW?GjA#C}nDUN(j-?ThytiI$IhmaY1G~6SL{qt%we18b}Xr$QIpd#f?0`bYrz|E;L z#yR^>#UH2HIy!+QIVlES=SL7ys0`o7mj~zAoyZ>J$$iPl>c$E9H#gk+Y>4eP4f~dX zA9fNA|J2g!Xg6w({E4Xw1RkszF*fPnmEB-S+v4?!RvH&yUG3f6_f%r<6uQcHQ#}<{ zZq?1SxK7aMY#-Zys?)l-AK2N{?08gumHWdT7~vP(ZR20Qn`NW*5&Or1`sy22lix9qJhv@I$V z2?z6o0mSb~p%7kk8%6Z^x%YjLGrfRRlxF1^X>|?zT<%h4eoolXv)J(7ra;TW+|sgI z2_GxfRnLYPwO{c@)76xS>G4qEM(@13|C_pE~A0>+u=*>&ZO zBY(^v4}n&(Cp@Tc)q$TNo@$eox7mQunMP;*d}#dg!Lh@IGf4NwNbSsZC6!S0ysu)t zx8^nF-m>uvKFzth@eWAJ`*3k++y=KJHgfaDBqyDe&x}EaLG-wM_lzGsRz(9;A2q>@G?MTsvG@rGCz7k|;5 zDI8vn%EiQIk8m81Ll>1%c1w+9YQHRrM9IbI|&ZPo~n_R@1iE0yN#$h zFLG;?p4K1C@KoB596-l|;d|EOCdfP0p;M85m&Y3#6N7JQiz1rh{cuH(b%
QI?9 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%0AGS|k`7wTV7OB+* zHIHpBxK2Zfp=^|Se}OogfYB#=_Sx)5Wk->|k57Uh_lFxK6XAKs=(eA;C`wo)vcc?j z`+7kxL=JEPX4;8Hu|1W7q_Xc} zs)b{&!XXy&2}Vi>!A`a;9_)};y#OT`zn_ZyJSn^(H9Z<_*x&Ly1u7d&oXAfQJ{rHf zTrW6O{p$Iun`gR>tE_m$!;^li$gNpc@Rqo~Pq?ddzEfFklwIF0Wdg;L1${mTZlsOyqy78oY0y zsHX~6OnoNqc|1~A32UjihWUCT=M!@b$yhh$(J(>c8E?U~U5C5MHP*i=9*fEuG+(@# z;&N%NLH)3yNWtOvpn(Q;5F6Lt(Z>=2rFfsoGqP@Li-`%Wsdz7`O|m9BJrAs)BSd1yyC>edZFX6dEEK{PB0%HbNxz4)jbC zD(M_cC`K{cF5)Fxln^(u{>=GRqT1Upbq3xvobwkL!f8w=AC8* zm<^96rdwVp4h?a|6!JxuCm6bR(QFYUKVQPygB*P_T{G=T6;M}16u{?yDig-#wOcz= z7<@_v1>e<;DRgi<=DRWa`PbH4`>;!i`NavP!?aU$>V{wAt(LM4g82(pytKJ1g}#1B6fC^DtE3p*6?v(pw1a#tEl@e!ZL`Y1Gh_ z9s0w5Hw|-UcXIA1QAb_#Ncj%z86R)?Bu;zK3HIkg2wW=vMKuRWc_{`WTfBXht~+8# zIgDx>T4fwyis<;4^hqcQW3iR>EW>Z9S~^C5`9;oLDH2UO4wBK28VuLH9f|Ml$ z=VAra0XI=$!Hyha2@8M`Ws7z@SIm#7P1q#IMjxr4{&(ARabcsU=Y7}NCDrUal-qSi zGgWp9+iw|EFO>Ov+XlFWm{U9I_5I=|ly3VP3tWn$!r%JQL9uLyCS8YcDzN$VWf+GS z{-FJ1&Zgp;(d95vZ)AUrXSS*~pOw)>O_I9})VJ)?qxq4GjFFcq-p()unS9zQ2S$lq zu4&1xX~wTxE$bnS+lO&r@r(5op2f^OKldmvB(LPNzvA*hTAZ{p z3|fAk4n6PRH{$aNozQ_+G%|xR8|>1Cd|KyvK;AUG-GLe7@_A7L9;WvE6DvGB`Y}3@g1B*tLMv z27*uBq`Qm$RDG^Hu{&{UXRh0rm&mB{sd(9R9mtpY_YtNG6<%RNB@Q25WCdH)%k!Jx zJ1+9fi`NH*!D=z=$3Gos-^B5EqkR|C?II0K+lB0uFfnGx`4?RHXJo z08}*pe{;wW+1i6`F12pftYaq~^W{{>f7h;c@p1ZXMT5rz075>l@Wj{&@VK!t7t{3@@)aNW)Wb2PZnv-Fu1Sj`=&f-Gcu- z>I~oRjGSYyv)j+L<|uDsQ==Z2{s9em&>!0wG55@>ueRFx&eglv&XV{fO3x4LZZOtrp zPKn=FEHz49pCxiDymZ<~eyFaN=gl}g+1ViYTPOt3Zw!$Q?1oM2VaL}DHFxXE#+Aj? zE@{c1HjDEgcfQ;V8B7+^N|nhIrlMq?pKGJlynKRxYmWRGk!V$YlV20rxu}3s_GESM zv&<}zTj=3)Ydz!LyyJ|9(!5oswoZ@uHOnZO4NwUp!&@Qyu+>yit;r_N``m-*6_`aO}-)fQOc(w29_ zZ`0l#@zCN2Xp!fJrb9&Gr0>PMrJ0gvh83BPdM^;@CuCU~l)-t8cJ+v^GeU*Eg$QXJKfo9{Ck|3PV@|ikXzz*bE|NlVfRIGQ~!tIb*|(=GvZ8(0ckL} z=OjWt&z$J;EpgQsvBuFvFS6|rT`}C1Gyyf#__trOx|{~7;TRB0AgQa;pkZw*MXrBs ziX^*|9>v04v)XR44|({C8NucETPNv<64DQ5ul+Y7K>~?l=`3#UeLCr^IUW`a6Y8IK zsgW)k(4*4hz|vBjzmOu{nU$QPZVgeVzqC27O;o+r-<=j0W+z*q5OUb39y)&=wk|e) zk~Rw7HA7$^ZcAs@jj8QM zgMZ)Fs709|y-ynMF-v-Dm_JdB7%5bUFc$Cw3Y#NdY0lp-w(Gs2p{20l<5a@x)W@y& z)}AUdUd0#v+#lWhqalzt^|SP87!uZR5KXO^i{b&+1q)7u1P+kz@5nydz+f(-VNGjo zO(4B|V}V$v!FvqbeUxLC!%N!8$yHdSKAo8awI)5nu9xH0Ah1UxD*J3BQ-{Xc-$aRN z#*OIl^io43dy%btFzJOFWkV+-`dd%RbWv~TSq2oRV8AM)Ao1#M=@Y7ZW-5^ZHQCcT zaH?H{<7d@028)WKSQ4+++mAIj+e8q^j%Sg#2f>??Lxp7u7^J{CQ& z3m|J#nX8Q5M<5Y*f;;T-PDm=4yTPSd7#0r57q~IU)7kiyLk4X<=$B~e?Y9PiTOfJ0 zDHbG;#(W@ZSL$chUdbq;_R#l7$(}6yUl4DSt(bw`kj_D(LxXmOxqTbzBYe4lx~oRT z`(adMlt1sqlOX+dtCWWNT7L$L%P=J%ABi}$KSg%!a&{wIB4Kx|C-%QFX zL;$1Fk33+Pxz@j`e(?ML&vWigO{364i)D7xUbQXQgX)iXKr!rD8C1|>oS^Nf-Sj0g zR_quMT!}q_%L#U(ixX_+GbQU=@9A%`erUKjadX5rDCZXm<}J&2zVZ51+hpLwSaAG{ z|8tQ+dW77j8HZ88eCwN~oqn7hG03J_L@`Wu)6}6Ou{rboZt6zW0t>C?ncx4_+;v7Z z*=%ci?}!vb(NLrbC?%mtCm_=62ZY`cO@IJOhtP`xQUnAM1f_)Ddl6|u=t%D^R6%N# z8^3ezJ)U#_-Cy_DtTpfZ&YIclnP)%y-D}TqN~hB-3y7CskN3$7({?05_dgTNj;XcB zP?wek3YDP5VmK+?22CeduNH8I2dT@RKm5eC2r}yrp&CX(Pjs5 zs&OA(&35s$wn6<~5fDJS*N~t$1EnSRP$iCS*Ool5BJoXVZfWJILX;R&qXUGl zxjN{7qx_#R&{*#^l3Wue&)ZC)Oh^aNg0m{hN-K)^NaC#LytcwL(&7`UN5o13ZD|*i zx^+C0HowB<7-Z}4C?x2!7%TkJs91z|QUYsFw8SXxVFT2<_QJJ%sK^(>qZM=x9UI*B zaiKOU08JKe_Ey(-3**^&C0UYAQ0A4;4onz{C8{!J%4gyrlzW+Bvq%?0CuJft-b z-CdZy?oJBGj?hW6T?XHvPRuyW`&=Vz&WXX=N0`hQnR#ielUenueW6qDyu-`R9b%;s z%B0ig5Ef#|JRFetz0o6DQ>`TeIPErU7~3t*s&Tj-0aRpscAMDZbyWb+YgKRR2V{Ygo*3kM=Xkp?d^b$0Vd|jj zEN7BL%X`<|1*&v^h_~4jDfis2y5QP|*DBrb=fg8B^u@kdckZvrl>$aWqI|ipO%(V< zyh?Re4mo!6k(-+f_ZaV?le*vW=*hs^1dqAu8Xdx=q?+h50?~-oKzDPByJ#Bb_EyaP z=c{M%_dUlx8#PU8=ucwHT!XAM>4xozKvuF)N@?GKSIouUVYMZ#QF_Kdxm7)3a#yxWI;4lyKhy9A>)cn=CRR2E_5ds#cbD2KqRWoR8j^4}gl> zJ1iFjuoRtV@Z7CyeFPd+X`r}XT>6zJ0#o^)T@bC3!T{2Tf;&kTp1p9?l$|yJpR3pXEDoULLdXhC;W{|Df7w1IlOoBPaFO z)~?*WG0aPrU+Dy_HFXxuWhFP8@=RQZbY2vuQDhpyxSxn;s2MbPg)+qLukB@AYSy&K zZvdLVi{;Cb4rXM})@(j=jJ4_<&|WN|UOYW6Pz)+>a6n7Z+777q;Js6#xAMR(kPG*p{97XZ#mK5OjdF-Y^!RiqtwPteqCS}mfL54y%^+oJg^RAUa-073YEnE20B=M* z+5DmuCn@xgTBfFo_a+KmA4!YV8Ap({)02glO_7(?O&tb+&+}FWr`O(BW5++5l)3RC zS_G7g;~zS$1e{oc#g#DBs9MAifQy!qVS@OQg^ufwL~1|BSeo&uZBQFxsaHQRr271E zrj<%Rw?j)gQtXYGk%zNJljEinxGV=U-h@K%+hN-ELUp77N14dAVk8i^v-;O50rdZ5 zTU;}u7z~`#^YAs5vJZgbL(Bx0$?7KZ!UU zX#D(U>(`hQfd3gJjP|>{cMGc;C0Cz;UoaO zEVkR@NGrqHZ!QIB1VaM8UtWB%YQQnYA3PylZ*rjzPQpvb8if%lh&omu=z>=wfJ-k) zpyOysYcHxW69Rf*t^L8mv07NMp-t7+QTQ_P9)BGFSJC)q$0!zY!15v4hnv}JT_@Pg zC%>Tk2)Io{wduL(j2FB7as;!3qw#5M_z0a2^21+d{xy~5*q7n!E1k1|TvPVMGw zcup5vYd_w_gZNKMbosfh+WO?sFSXOd^4|wNqT+MUa8|KwN}?0XcrRO*q;SQc%>dRo z#E-DRR6dxKB-7dcR_sBv>AG_`@KeNUDe7OYGn$4B0QRHC!kuq`7~oy>SIEzO#W6Ix z@*ZoI`?5O)v{{VsE*ylvj6U#oIGA9 z0{EEgQHqK`93HRXb2IV8_FKu4dNsqHZc|l*!Dt(uk+E@#U+3KF&OtEbC zKr9(nKGJa~(y;t`WQ7vR+pbq>3Ha0y3suHeQ4(ej=AzTX%$%tyNs7-kKlWhUy2Oz8 z#&$GP0KJ1$Z?^uul`)7K29I4$8=dHNW+#!enJwgW+_v`ppd>Niut+cY+f zbNqhHBfy<)fTK7Mv<}-^ku2I7gtHX$PX|!tWI)Fw4<^92)J$YBP&nwFhf4gjR__Z^ zg8!hAj}*(2H*p2OK&nPxSmg#Z8VtXA+RFgQ8Aan|ZBg&jgT}|9 zD~OpiJofX^cjibU{(U?hz<}vg%@eoYjH+Daid50pK{NxpZ$?WVGPb`PqW72pM->Zz zMw6DNhHkg2SRN!Ham5S1^=tRQ_|FYZ5_U-wPT(aQnA9M=e?wZn$65wFBGY-x#s+GH z0lXUV&j)`hC%d{(cr14`*z}_8@~Y@X0vf)Wg@91G>pq`n)|Q#7Ts!&(d)8Mw+}0K()H>~$X`>J5F)N`E)m!%zFk8K$ZD zb(!AcVl#`|f@ZdE=j3=h7NoHtw1aURZyYQu1uO@t44t+82R+=y0Xmb9H(og`1~@Kv zQSabp^9y1c(MmblSX%4-;v4v_p0CC?vdrgIR1oxzhyt}GRk2C)@n>Q4Z}#1XVoU6j z5@dQLwoD=_uJ=}P5*~r=h6el}07&A@((G%9OdH#(q%S6TQmnwXuy`)#;1+|z#!1n&D$S6Fs`JPo~7 zOH!woC!D*izM*L~TW6%f^#FNeD6OjDWQ}9+E=%#&hfz{MNZc+NB-9Aj-N_U1znBmY zmhYc=;1JL-NSl=bzchG#zv`uOA7|5kv&G$~h6{a41`ILmSSA>eJ8FHyDx*rolXyzd z?Z6e=eq)Er_Sqy9wQ=(`{NYvq@_lVYgowW1mkz-Xogy29Rk2l?+Ch`|0WNVNRG}67 zQ@7SPDKDh9uVS+m;xvFF&@~63R8AWxQ%C-})r^q5#g9>8)uRORt#f9{If=;xXin-x^D>k}sY@Zg7qh3KzNv|d&l?g=6)$>W5aq_S49ohf7)QQm z)BuAWhD}GaC zRx)p?%RA-kTW8PEL=Sq#J02c9fzygKU>K1;&O{5jB9({)c6QFZ$9bRTguEwaUyDkU z#;rEWePP9nE`Fcrr6`E=c-&`UCgK^{6S)5g{Njx)=dD%L8?}_WGe04LWVW!lSN1ii z`Ucys#Jd&|v+hb=fSu@~zjm7(xm3h}TL}+Pcod>Mbo19Z-2%ArFUOwPo``u8p<^{c zW*tmjd*JQ(+}({YCFVd`&W7!Ibpa?;u`uZ)rZ$5adKQC8DJx-KoY2FkkEB7Dcp>Kl zxi%VppsN20V0t=LC*|qp-9-K4kwK_0)~8CFJ_gEkKd4ORB|mI!@v6p?iS`VC>g)2i z5MV`M9uF}WS%yU4rr}#dk)r8&`5}vHAbXiJgaWWUO|Q!7Ev4->;mY6{-zL z(2d|{6tDa_yOQK^JsUY=yvyGY`rL3UBfYv1Br?{fE<2m-uUM5|D^A2Mp3kLzHd-h$ zux$eOXrs;ZoIc0ICZ)9GSamA4>qu<`R;@nK8e#Yl5VhIitmPUszzp)h`$JL_K(dB_ zf32<=#ju?SKLDWXUtzNSQl4K*UM*>zZqJ@zZJ|2I%^QkT=%ADGnJhwsVBr3h^A0e3 z{0vbclj}y?BDdOTTPfq`&w89NBp%b}!glL``<>)zd8>y64z#4v6Ig!pvUVyTzZfa z2CswEwkGi#%B`Lv{-^FN)5O@1TkI-i%^=JQ`)j1>n)4i}L{OsMnD;|EF<^Pk5Rpjy zrnw-C?(ycUeTPgW9KB@-&e2zu&Kd)gbK(n2BqK9==TUCQ0@*0caW=mye9sZ$C>=w2%;7Mi2GK(}kwa;Zi4gv^Ub+PZwBdtpY)XG2A z=8y#8Nd(`YF1Gk~ihbez=x1MCw=#p5sC^t_+gE{!k8fE7YrtFbsW1Nr_QWVq-%9*^ z$;un~v0OHLSqARt`#$q?QAg|*6c>|+0soB2R}K@$42F7sq03tfy~)VlOI=teGVV?z z#6B~5FKKBE&+QmD@HwL!HC6Fxp4Ls&(5`}HAB1}r1k-$^maksvWs%I)uXaLC+wU~)v|^u|8*!HLcK6#L!}wl_4JOsBY73MR+gE^8NKxde9F zvZcsR72^ixuN-1FsovY(oKC*%YrXd+daPE~U90XI7YKmw{`Zd;9C3sIhxn7+PAxBE z?sccz?_Z>Kl(|B6p7Z428c{w&LY+#L<*eJ8ufTZaPtkRB;QKI4N{zMu5F?&?ljpYN0295m6^^QI#CWEK`*XN+58hLCccml019<_#csH{vXlUQdCui(_?c``^8!` zF&E0bJ+vr(NDugHY;KtI=0afaFHyG|6ik%jbaI-Qb7_oiGRLT^R)~;*6yMPN>Dli2 zTJCZ3-p=)GlariGm}qd9dt-g{*ZY$DK=&uJYOJ%hMqfUL@p^xsR~#xSGy^<7lx_2I zKfuZx%_UeW@8zU+$_14k=m?h;t!UI}$t`64&T|BV7=-7$y5HVu6&i?-c>qVcp|i*$ zM+b#v40=W@!z?;yn)aW4L=XRURV*;>CfIkCy!7?Gpd#tN?J;&>3i@v|432|06gs+VZ)a`9>YS$=9 zI?m2-vf_r$j|FxVK9f(is$ZPwkSIWo*wi?C{#|;D e8}I*TcN_lwH}eWmLwx7oYt~TJRzWFQhyE7_?ll4c From a7a6dd1b0b51d27de8712d72a0da52754d9d75f5 Mon Sep 17 00:00:00 2001 From: Jeffrey Paul Date: Mon, 20 May 2019 11:36:32 -0500 Subject: [PATCH 02/73] fix badge placement --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index fbeefb73..397a9ed7 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ # Simple Local Avatars > Adds an avatar upload field to user profiles. Generates requested sizes on demand just like Gravatar! + ![WordPress tested up to version](https://img.shields.io/badge/WordPress-v5.2%20tested-success.svg) ## Features From a67375d877e7418bee51523ea39ddc5123557e27 Mon Sep 17 00:00:00 2001 From: Jeffrey Paul Date: Thu, 30 May 2019 09:32:22 -0500 Subject: [PATCH 03/73] Update 3-help.md --- .github/ISSUE_TEMPLATE/3-help.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/3-help.md b/.github/ISSUE_TEMPLATE/3-help.md index e097139b..a91a682d 100644 --- a/.github/ISSUE_TEMPLATE/3-help.md +++ b/.github/ISSUE_TEMPLATE/3-help.md @@ -1,5 +1,5 @@ --- -name: ":interrobang: Need help with Simple Local Avatars?" +name: "❓Need help with Simple Local Avatars?" about: Ask us a question, we're here to help! title: '' labels: question From 9c199981aa2f1baab56871a16164f5083694811e Mon Sep 17 00:00:00 2001 From: Jeffrey Paul Date: Wed, 19 Jun 2019 21:28:26 -0500 Subject: [PATCH 04/73] add license badge --- README.md | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/README.md b/README.md index 397a9ed7..c9854bd8 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ > Adds an avatar upload field to user profiles. Generates requested sizes on demand just like Gravatar! -![WordPress tested up to version](https://img.shields.io/badge/WordPress-v5.2%20tested-success.svg) +![WordPress tested up to version](https://img.shields.io/badge/WordPress-v5.2%20tested-success.svg) [![GPLv2 License](https://img.shields.io/github/license/10up/simple-local-avatars.svg)](https://github.com/10up/simple-local-avatars/blob/develop/LICENSE.md) ## Features @@ -32,10 +32,6 @@ You can also use `get_simple_local_avatar()` (with the same arguments) to retrei Please read [CODE_OF_CONDUCT.md](https://github.com/10up/simple-local-avatars/blob/develop/CODE_OF_CONDUCT.md) for details on our code of conduct and [CONTRIBUTING.md](https://github.com/10up/simple-local-avatars/blob/develop/CONTRIBUTING.md) for details on the process for submitting pull requests to us. -## License - -Simple Local Avatars utilizes the [GNU General Public License v2.0 (or later)](https://github.com/10up/simple-local-avatars/blob/develop/LICENSE.md). - ## Like what you see?

From 760bf93dae09191337f3a035c1f347d45dfea2ae Mon Sep 17 00:00:00 2001 From: Jeffrey Paul Date: Wed, 19 Jun 2019 21:30:36 -0500 Subject: [PATCH 05/73] add support level section and badge --- README.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index c9854bd8..f149a54b 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ > Adds an avatar upload field to user profiles. Generates requested sizes on demand just like Gravatar! -![WordPress tested up to version](https://img.shields.io/badge/WordPress-v5.2%20tested-success.svg) [![GPLv2 License](https://img.shields.io/github/license/10up/simple-local-avatars.svg)](https://github.com/10up/simple-local-avatars/blob/develop/LICENSE.md) +[![Support Level](https://img.shields.io/badge/support-active-green.svg)](#support-level) ![WordPress tested up to version](https://img.shields.io/badge/WordPress-v5.2%20tested-success.svg) [![GPLv2 License](https://img.shields.io/github/license/10up/simple-local-avatars.svg)](https://github.com/10up/simple-local-avatars/blob/develop/LICENSE.md) ## Features @@ -28,6 +28,10 @@ Use avatars in your theme using WordPress' built in `get_avatar()` function: [ht You can also use `get_simple_local_avatar()` (with the same arguments) to retreive local avatars a bit faster, but this will make your theme dependent on this plug-in. +## Support Level + +**Active:** 10up is actively working on this, and we expect to continue work for the foreseeable future including keeping tested up to the most recent version of WordPress. Bug reports, feature requests, questions, and pull requests are welcome. + ## Contributing Please read [CODE_OF_CONDUCT.md](https://github.com/10up/simple-local-avatars/blob/develop/CODE_OF_CONDUCT.md) for details on our code of conduct and [CONTRIBUTING.md](https://github.com/10up/simple-local-avatars/blob/develop/CONTRIBUTING.md) for details on the process for submitting pull requests to us. From 93bfa23cc8bc2ee6f0b650adc400f9ad2d153257 Mon Sep 17 00:00:00 2001 From: Jeffrey Paul Date: Wed, 19 Jun 2019 21:31:32 -0500 Subject: [PATCH 06/73] add version badge --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f149a54b..7e4d728d 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ > Adds an avatar upload field to user profiles. Generates requested sizes on demand just like Gravatar! -[![Support Level](https://img.shields.io/badge/support-active-green.svg)](#support-level) ![WordPress tested up to version](https://img.shields.io/badge/WordPress-v5.2%20tested-success.svg) [![GPLv2 License](https://img.shields.io/github/license/10up/simple-local-avatars.svg)](https://github.com/10up/simple-local-avatars/blob/develop/LICENSE.md) +[![Support Level](https://img.shields.io/badge/support-active-green.svg)](#support-level) [![Release Version](https://img.shields.io/github/release/10up/simple-local-avatars.svg)](https://github.com/10up/simple-local-avatars/releases/latest) ![WordPress tested up to version](https://img.shields.io/badge/WordPress-v5.2%20tested-success.svg) [![GPLv2 License](https://img.shields.io/github/license/10up/simple-local-avatars.svg)](https://github.com/10up/simple-local-avatars/blob/develop/LICENSE.md) ## Features From ac3e61dddeda6a494b074d02587cc2ad1da2aefa Mon Sep 17 00:00:00 2001 From: Paul de Wouters Date: Fri, 11 Oct 2019 12:27:13 +0100 Subject: [PATCH 07/73] Explicitly declare global --- simple-local-avatars.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/simple-local-avatars.php b/simple-local-avatars.php index cc1420d6..64577e54 100644 --- a/simple-local-avatars.php +++ b/simple-local-avatars.php @@ -564,7 +564,7 @@ public function set_avatar_rest( $input, $user ) { } -$simple_local_avatars = new Simple_Local_Avatars; +$GLOBALS['simple_local_avatars'] = new Simple_Local_Avatars(); /** * more efficient to call simple local avatar directly in theme and avoid gravatar setup From 8cce98152935f3b15606cef34d711b0fc677c500 Mon Sep 17 00:00:00 2001 From: Paul de Wouters Date: Mon, 21 Oct 2019 09:18:41 +0100 Subject: [PATCH 08/73] Update simple-local-avatars.php Co-Authored-By: Adam Silverstein --- simple-local-avatars.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/simple-local-avatars.php b/simple-local-avatars.php index 64577e54..a1724912 100644 --- a/simple-local-avatars.php +++ b/simple-local-avatars.php @@ -564,7 +564,8 @@ public function set_avatar_rest( $input, $user ) { } -$GLOBALS['simple_local_avatars'] = new Simple_Local_Avatars(); +global $simple_local_avatars; +$simple_local_avatars = new Simple_Local_Avatars(); /** * more efficient to call simple local avatar directly in theme and avoid gravatar setup From b6193dd2da8db0ca9069d23f9f505df89798ae0d Mon Sep 17 00:00:00 2001 From: Ledwing Hernandez Date: Mon, 9 Dec 2019 10:38:46 -0500 Subject: [PATCH 09/73] Installed wordpress on new local project instance to test simle local avatars on wp 5.3 Installed plugin Plugin loads properly when activated Profile Image uploads properly Image is properly saved in uploads with full and five other image sizes Image maturity rating saves and updates properly No console errors Avatar properly obeys max maturity rating set in Settings>Discussion Avatar takes over profile image when image is appropriate according to maturity levels Gravatar properly takes over if you have one and no local avatar is set Only allow local avatars checkbox works properly Checkbox to only allow users with file upload capabilities to upload local avatars is working properly Avatar area resizes properly Updated tested up to in readme.txt to 5.3 --- readme.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.txt b/readme.txt index 64e13669..93d9c044 100644 --- a/readme.txt +++ b/readme.txt @@ -3,7 +3,7 @@ Contributors: jakemgold, 10up, thinkoomph Donate link: https://10up.com/plugins/simple-local-avatars-wordpress/ Tags: avatar, gravatar, user photos, users, profile Requires at least: 4.6 -Tested up to: 5.2 +Tested up to: 5.3 Stable tag: 2.1.1 Text Domain: simple-local-avatars From 35faafb3b30094a8a5cc66524e4ee0baad95a0ad Mon Sep 17 00:00:00 2001 From: Ledwing Hernandez Date: Wed, 11 Dec 2019 11:52:45 -0500 Subject: [PATCH 10/73] Create main.yml Updating "Tested up to" to WP 5.3 --- .github/workflows/main.yml | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 .github/workflows/main.yml diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 00000000..051eb905 --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,13 @@ +name: Update Plugin / Deploy to Wordpress + +on: [push] + +jobs: +build: + +runs-on: ubuntu-latest + +steps: + +name: WordPress Plugin Deploy +uses: 10up/action-wordpress-plugin-deploy@1.4.0 From bd8c3008e9f8ecf9a2be0a712f6afb45ad7bc131 Mon Sep 17 00:00:00 2001 From: Ledwing Hernandez Date: Wed, 11 Dec 2019 12:00:35 -0500 Subject: [PATCH 11/73] Update main.yml --- .github/workflows/main.yml | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 051eb905..b8041d4f 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -1,13 +1,12 @@ -name: Update Plugin / Deploy to Wordpress - -on: [push] - +name: Plugin asset/readme update +on: + push: + branches: + - develop jobs: -build: - -runs-on: ubuntu-latest - -steps: - -name: WordPress Plugin Deploy -uses: 10up/action-wordpress-plugin-deploy@1.4.0 + master: + name: Update plugin readme/tested up to + runs-on: ubuntu-latest + steps: + - name: WordPress Plugin Readme/Assets Update + uses: 10up/action-wordpress-plugin-asset-update@1.4.0 From 98c5a6142afc65bcad51cc107f43e4d6c702b3ea Mon Sep 17 00:00:00 2001 From: Ledwing Hernandez Date: Mon, 16 Dec 2019 15:32:33 -0500 Subject: [PATCH 12/73] Delete main.yml --- .github/workflows/main.yml | 12 ------------ 1 file changed, 12 deletions(-) delete mode 100644 .github/workflows/main.yml diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml deleted file mode 100644 index b8041d4f..00000000 --- a/.github/workflows/main.yml +++ /dev/null @@ -1,12 +0,0 @@ -name: Plugin asset/readme update -on: - push: - branches: - - develop -jobs: - master: - name: Update plugin readme/tested up to - runs-on: ubuntu-latest - steps: - - name: WordPress Plugin Readme/Assets Update - uses: 10up/action-wordpress-plugin-asset-update@1.4.0 From 533d97fc6b4e5461d937e91cf53a629a52f9f0ce Mon Sep 17 00:00:00 2001 From: Jeffrey Paul Date: Wed, 18 Dec 2019 21:29:10 -0600 Subject: [PATCH 13/73] update wp deploy action to YML format --- .github/main.workflow | 16 ---------------- .github/push-deploy.yml | 16 ++++++++++++++++ 2 files changed, 16 insertions(+), 16 deletions(-) delete mode 100644 .github/main.workflow create mode 100644 .github/push-deploy.yml diff --git a/.github/main.workflow b/.github/main.workflow deleted file mode 100644 index c33fc217..00000000 --- a/.github/main.workflow +++ /dev/null @@ -1,16 +0,0 @@ -workflow "Deploy" { - resolves = ["WordPress Plugin Deploy"] - on = "push" -} - -# Filter for tag -action "tag" { - uses = "actions/bin/filter@master" - args = "tag" -} - -action "WordPress Plugin Deploy" { - needs = ["tag"] - uses = "10up/actions-wordpress/dotorg-plugin-deploy@master" - secrets = ["SVN_PASSWORD", "SVN_USERNAME", "GITHUB_TOKEN"] -} diff --git a/.github/push-deploy.yml b/.github/push-deploy.yml new file mode 100644 index 00000000..d4c496ed --- /dev/null +++ b/.github/push-deploy.yml @@ -0,0 +1,16 @@ +name: Deploy to WordPress.org +on: + push: + tags: + - "*" +jobs: + tag: + name: New tag + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@master + - name: WordPress Plugin Deploy + uses: 10up/action-wordpress-plugin-deploy@master + env: + SVN_PASSWORD: ${{ secrets.SVN_PASSWORD }} + SVN_USERNAME: ${{ secrets.SVN_USERNAME }} From 0010352725345f731f3ca8c4e851297cfbce2a6b Mon Sep 17 00:00:00 2001 From: Jeffrey Paul Date: Wed, 18 Dec 2019 21:33:56 -0600 Subject: [PATCH 14/73] Create push-asset-readme-update.yml --- .github/workflows/push-asset-readme-update.yml | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 .github/workflows/push-asset-readme-update.yml diff --git a/.github/workflows/push-asset-readme-update.yml b/.github/workflows/push-asset-readme-update.yml new file mode 100644 index 00000000..7efb6913 --- /dev/null +++ b/.github/workflows/push-asset-readme-update.yml @@ -0,0 +1,16 @@ +name: Plugin asset/readme update +on: + push: + branches: + - master +jobs: + master: + name: Push to master + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@master + - name: WordPress.org plugin asset/readme update + uses: 10up/action-wordpress-plugin-asset-update@master + env: + SVN_PASSWORD: ${{ secrets.SVN_PASSWORD }} + SVN_USERNAME: ${{ secrets.SVN_USERNAME }} From 9c3d4212fca48ca8ab0c530f37725d5a3f7cc746 Mon Sep 17 00:00:00 2001 From: Jeffrey Paul Date: Wed, 18 Dec 2019 21:36:22 -0600 Subject: [PATCH 15/73] Rename .github/push-deploy.yml to .github/workflows/push-deploy.yml --- .github/{ => workflows}/push-deploy.yml | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename .github/{ => workflows}/push-deploy.yml (100%) diff --git a/.github/push-deploy.yml b/.github/workflows/push-deploy.yml similarity index 100% rename from .github/push-deploy.yml rename to .github/workflows/push-deploy.yml From 6662cc1db46ff32014fa8b00af857347caf09d66 Mon Sep 17 00:00:00 2001 From: Tung Du Date: Mon, 20 Jan 2020 08:30:55 +0700 Subject: [PATCH 16/73] Switch to `pre_get_avatar_data` filter. In fact, we only need to filter the url of the avatar, not the avatar img element. So instead of hook to `get_avatar` filter and override the `get_avatar` function, we hook to `pre_get_avatar_data` filter to manipulate the avatar url and let the core handle the reset. This reduce duplication code and effort to follow core changes. --- simple-local-avatars.php | 1312 +++++++++++++++++++------------------- 1 file changed, 650 insertions(+), 662 deletions(-) diff --git a/simple-local-avatars.php b/simple-local-avatars.php index a1724912..d581d39d 100644 --- a/simple-local-avatars.php +++ b/simple-local-avatars.php @@ -1,662 +1,650 @@ -options = (array) get_option( 'simple_local_avatars' ); - $this->avatar_ratings = array( - 'G' => __('G — Suitable for all audiences'), - 'PG' => __('PG — Possibly offensive, usually for audiences 13 and above'), - 'R' => __('R — Intended for adult audiences above 17'), - 'X' => __('X — Even more mature than above') - ); - - // supplement remote avatars, but not if inside "local only" mode - if ( empty( $this->options['only'] ) ) - add_filter( 'get_avatar', array( $this, 'get_avatar' ), 10, 5 ); - - add_action( 'admin_init', array( $this, 'admin_init' ) ); - - add_action( 'admin_enqueue_scripts', array( $this, 'admin_enqueue_scripts' ) ); - add_action( 'show_user_profile', array( $this, 'edit_user_profile' ) ); - add_action( 'edit_user_profile', array( $this, 'edit_user_profile' ) ); - - add_action( 'personal_options_update', array( $this, 'edit_user_profile_update' ) ); - add_action( 'edit_user_profile_update', array( $this, 'edit_user_profile_update' ) ); - add_action( 'admin_action_remove-simple-local-avatar', array( $this, 'action_remove_simple_local_avatar' ) ); - add_action( 'wp_ajax_assign_simple_local_avatar_media', array( $this, 'ajax_assign_simple_local_avatar_media' ) ); - add_action( 'wp_ajax_remove_simple_local_avatar', array( $this, 'action_remove_simple_local_avatar' ) ); - add_action( 'user_edit_form_tag', array( $this, 'user_edit_form_tag' ) ); - - add_filter( 'avatar_defaults', array( $this, 'avatar_defaults' ) ); - - add_action( 'rest_api_init', array( $this, 'register_rest_fields' ) ); - } - - /** - * Retrieve the local avatar for a user who provided a user ID or email address. - * - * @param string $avatar Avatar return by original function - * @param int|string|object $id_or_email A user ID, email address, or comment object - * @param int $size Size of the avatar image - * @param string $default URL to a default image to use if no avatar is available - * @param string $alt Alternative text to use in image tag. Defaults to blank - * @return string tag for the user's avatar - */ - public function get_avatar( $avatar = '', $id_or_email = '', $size = 96, $default = '', $alt = '' ) { - if ( is_numeric( $id_or_email ) ) - $user_id = (int) $id_or_email; - elseif ( is_string( $id_or_email ) && ( $user = get_user_by( 'email', $id_or_email ) ) ) - $user_id = $user->ID; - elseif ( is_object( $id_or_email ) && ! empty( $id_or_email->user_id ) ) - $user_id = (int) $id_or_email->user_id; - - if ( empty( $user_id ) ) - return $avatar; - - // fetch local avatar from meta and make sure it's properly ste - $local_avatars = get_user_meta( $user_id, 'simple_local_avatar', true ); - if ( empty( $local_avatars['full'] ) ) - return $avatar; - - // check rating - $avatar_rating = get_user_meta( $user_id, 'simple_local_avatar_rating', true ); - if ( ! empty( $avatar_rating ) && 'G' != $avatar_rating && ( $site_rating = get_option( 'avatar_rating' ) ) ) { - $ratings = array_keys( $this->avatar_ratings ); - $site_rating_weight = array_search( $site_rating, $ratings ); - $avatar_rating_weight = array_search( $avatar_rating, $ratings ); - if ( false !== $avatar_rating_weight && $avatar_rating_weight > $site_rating_weight ) - return $avatar; - } - - // handle "real" media - if ( ! empty( $local_avatars['media_id'] ) ) { - // has the media been deleted? - if ( ! $avatar_full_path = get_attached_file( $local_avatars['media_id'] ) ) { - return $avatar; - } - } - - $size = (int) $size; - - if ( empty( $alt ) ) - $alt = get_the_author_meta( 'display_name', $user_id ); - - // generate a new size - if ( ! array_key_exists( $size, $local_avatars ) ) { - $local_avatars[$size] = $local_avatars['full']; // just in case of failure elsewhere - - // allow automatic rescaling to be turned off - if ( $allow_dynamic_resizing = apply_filters( 'simple_local_avatars_dynamic_resize', true ) ) : - - $upload_path = wp_upload_dir(); - - // get path for image by converting URL, unless its already been set, thanks to using media library approach - if ( ! isset( $avatar_full_path ) ) - $avatar_full_path = str_replace( $upload_path['baseurl'], $upload_path['basedir'], $local_avatars['full'] ); - - // generate the new size - $editor = wp_get_image_editor( $avatar_full_path ); - if ( ! is_wp_error( $editor ) ) { - $resized = $editor->resize( $size, $size, true ); - if ( ! is_wp_error( $resized ) ) { - $dest_file = $editor->generate_filename(); - $saved = $editor->save( $dest_file ); - if ( ! is_wp_error( $saved ) ) - $local_avatars[$size] = str_replace( $upload_path['basedir'], $upload_path['baseurl'], $dest_file ); - } - } - - // save updated avatar sizes - update_user_meta( $user_id, 'simple_local_avatar', $local_avatars ); - - endif; - } - - if ( 'http' != substr( $local_avatars[$size], 0, 4 ) ) - $local_avatars[$size] = home_url( $local_avatars[$size] ); - - $author_class = is_author( $user_id ) ? ' current-author' : '' ; - $avatar = "" . esc_attr( $alt ) . ""; - - return apply_filters( 'simple_local_avatar', $avatar ); - } - - public function admin_init() { - // upgrade pre 2.0 option - if ( $old_ops = get_option( 'simple_local_avatars_caps' ) ) { - if ( ! empty( $old_ops['simple_local_avatars_caps'] ) ) - update_option( 'simple_local_avatars', array( 'caps' => 1 ) ); - - delete_option( 'simple_local_avatar_caps' ); - } - - register_setting( 'discussion', 'simple_local_avatars', array( $this, 'sanitize_options' ) ); - add_settings_field( - 'simple-local-avatars-only', - __('Local Avatars Only','simple-local-avatars'), - array( $this, 'avatar_settings_field' ), - 'discussion', - 'avatars', - array( - 'key' => 'only', - 'desc' => __( 'Only allow local avatars (still uses Gravatar for default avatars)', 'simple-local-avatars' ) - ) - ); - add_settings_field( - 'simple-local-avatars-caps', - __('Local Upload Permissions','simple-local-avatars'), - array( $this, 'avatar_settings_field' ), - 'discussion', - 'avatars', - array( - 'key' => 'caps', - 'desc' => __( 'Only allow users with file upload capabilities to upload local avatars (Authors and above)', 'simple-local-avatars' ) - ) - ); - } - - /** - * Add scripts to the profile editing page - * - * @param string $hook_suffix Page hook - */ - public function admin_enqueue_scripts( $hook_suffix ) { - if ( 'profile.php' != $hook_suffix && 'user-edit.php' != $hook_suffix ) - return; - - if ( current_user_can( 'upload_files' ) ) - wp_enqueue_media(); - - $user_id = ( 'profile.php' == $hook_suffix ) ? get_current_user_id() : (int) $_GET['user_id']; - - $this->remove_nonce = wp_create_nonce( 'remove_simple_local_avatar_nonce' ); - - $script_name_append = ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) ? '.dev' : ''; - wp_enqueue_script( 'simple-local-avatars', plugins_url( '', __FILE__ ) . '/simple-local-avatars' . $script_name_append . '.js', array('jquery'), false, true ); - wp_localize_script( 'simple-local-avatars', 'i10n_SimpleLocalAvatars', array( - 'user_id' => $user_id, - 'insertMediaTitle' => __('Choose an Avatar','simple-local-avatars'), - 'insertIntoPost' => __('Set as avatar','simple-local-avatars'), - 'deleteNonce' => $this->remove_nonce, - 'mediaNonce' => wp_create_nonce( 'assign_simple_local_avatar_nonce' ), - ) ); - } - - /** - * Sanitize new settings field before saving - * - * @param array|string $input Passed input values to sanitize - * @return array|string Sanitized input fields - */ - public function sanitize_options( $input ) { - $new_input['caps'] = empty( $input['caps'] ) ? 0 : 1; - $new_input['only'] = empty( $input['only'] ) ? 0 : 1; - return $new_input; - } - - /** - * Settings field for avatar upload capabilities - * - * @param array $args Field arguments - */ - public function avatar_settings_field( $args ) { - $args = wp_parse_args( $args, array( - 'key' => '', - 'desc' => '', - ) ); - - if ( empty( $this->options[$args['key']] ) ) - $this->options[$args['key']] = 0; - - echo ' - - '; - } - - /** - * Output new Avatar fields to user editing / profile screen - * - * @param object $profileuser User object - */ - public function edit_user_profile( $profileuser ) { - ?> -

- - - - - - - - - - - -
- ID ); - remove_filter( 'pre_option_avatar_rating', '__return_null' ); - ?> - - options['caps'] ); - - if ( $upload_rights ) { - do_action( 'simple_local_avatar_notices' ); - wp_nonce_field( 'simple_local_avatar_nonce', '_simple_local_avatar_nonce', false ); - $remove_url = add_query_arg(array( - 'action' => 'remove-simple-local-avatar', - 'user_id' => $profileuser->ID, - '_wpnonce' => $this->remove_nonce, - ) ); - ?> - -

-
- - -

- -

-   - simple_local_avatar ) ) echo ' style="display:none;"'; ?>> -

- simple_local_avatar ) ) - echo '' . __('No local avatar is set. Set up your avatar at Gravatar.com.','simple-local-avatars') . ''; - else - echo '' . __('You do not have media management permissions. To change your local avatar, contact the blog administrator.','simple-local-avatars') . ''; - } - ?> -
-
simple_local_avatar ) ); ?>> - - simple_local_avatar_rating ) || ! array_key_exists( $profileuser->simple_local_avatar_rating, $this->avatar_ratings ) ) - $profileuser->simple_local_avatar_rating = 'G'; - - foreach ( $this->avatar_ratings as $key => $rating ) : - echo "\n\t
"; - endforeach; - ?> -

-
- avatar_delete( $user_id ); // delete old images if successful - - $meta_value = array(); - - // set the new avatar - if ( is_int( $url_or_media_id ) ) { - $meta_value['media_id'] = $url_or_media_id; - $url_or_media_id = wp_get_attachment_url( $url_or_media_id ); - } - - $meta_value['full'] = $url_or_media_id; - - update_user_meta( $user_id, 'simple_local_avatar', $meta_value ); // save user information (overwriting old) - } - - /** - * Save any changes to the user profile - * - * @param int $user_id ID of user being updated - */ - public function edit_user_profile_update( $user_id ) { - // check nonces - if( empty( $_POST['_simple_local_avatar_nonce'] ) || ! wp_verify_nonce( $_POST['_simple_local_avatar_nonce'], 'simple_local_avatar_nonce' ) ) - return; - - // check for uploaded files - if ( ! empty( $_FILES['simple-local-avatar']['name'] ) ) : - - // need to be more secure since low privelege users can upload - if ( false !== strpos( $_FILES['simple-local-avatar']['name'], '.php' ) ) { - $this->avatar_upload_error = __('For security reasons, the extension ".php" cannot be in your file name.','simple-local-avatars'); - add_action( 'user_profile_update_errors', array( $this, 'user_profile_update_errors' ) ); - return; - } - - // front end (theme my profile etc) support - if ( ! function_exists( 'media_handle_upload' ) ) - require_once( ABSPATH . 'wp-admin/includes/media.php' ); - - // allow developers to override file size upload limit for avatars - add_filter( 'upload_size_limit', array( $this, 'upload_size_limit' ) ); - - $this->user_id_being_edited = $user_id; // make user_id known to unique_filename_callback function - $avatar_id = media_handle_upload( 'simple-local-avatar', 0, array(), array( - 'mimes' => array( - 'jpg|jpeg|jpe' => 'image/jpeg', - 'gif' => 'image/gif', - 'png' => 'image/png', - ), - 'test_form' => false, - 'unique_filename_callback' => array( $this, 'unique_filename_callback' ) - ) ); - - remove_filter( 'upload_size_limit', array( $this, 'upload_size_limit' ) ); - - if ( is_wp_error( $avatar_id ) ) { // handle failures. - $this->avatar_upload_error = '' . __( 'There was an error uploading the avatar:', 'simple-local-avatars' ) . ' ' . esc_html( $avatar_id->get_error_message() ); - add_action( 'user_profile_update_errors', array( $this, 'user_profile_update_errors' ) ); - return; - } - - $this->assign_new_user_avatar( $avatar_id, $user_id ); - - endif; - - // handle rating - if ( isset( $avatar_id ) || $avatar = get_user_meta( $user_id, 'simple_local_avatar', true ) ) { - if ( empty( $_POST['simple_local_avatar_rating'] ) || ! array_key_exists( $_POST['simple_local_avatar_rating'], $this->avatar_ratings ) ) - $_POST['simple_local_avatar_rating'] = key( $this->avatar_ratings ); - - update_user_meta( $user_id, 'simple_local_avatar_rating', $_POST['simple_local_avatar_rating'] ); - } - } - - /** - * Allow developers to override the maximum allowable file size for avatar uploads - * - * @param int $bytes WordPress default byte size check - * @return int Maximum byte size - */ - public function upload_size_limit( $bytes ) { - return apply_filters( 'simple_local_avatars_upload_limit', $bytes ); - } - - /** - * Runs when a user clicks the Remove button for the avatar - */ - public function action_remove_simple_local_avatar() { - if ( ! empty( $_GET['user_id'] ) && ! empty( $_GET['_wpnonce'] ) && wp_verify_nonce( $_GET['_wpnonce'], 'remove_simple_local_avatar_nonce' ) ) { - $user_id = (int) $_GET['user_id']; - - if ( ! current_user_can('edit_user', $user_id) ) - wp_die( __('You do not have permission to edit this user.') ); - - $this->avatar_delete( $user_id ); // delete old images if successful - - if ( defined( 'DOING_AJAX' ) && DOING_AJAX ) - echo get_simple_local_avatar( $user_id ); - } - - if ( defined( 'DOING_AJAX' ) && DOING_AJAX ) - die; - } - - /** - * AJAX callback for assigning media ID fetched from media library to user - */ - public function ajax_assign_simple_local_avatar_media() { - // check required information and permissions - if ( empty( $_POST['user_id'] ) || empty( $_POST['media_id'] ) || ! current_user_can( 'upload_files' ) || ! current_user_can( 'edit_user', $_POST['user_id'] ) || empty( $_POST['_wpnonce'] ) || ! wp_verify_nonce( $_POST['_wpnonce'], 'assign_simple_local_avatar_nonce' ) ) - die; - - $media_id = (int) $_POST['media_id']; - $user_id = (int) $_POST['user_id']; - - // ensure the media is real is an image - if ( wp_attachment_is_image( $media_id ) ) - $this->assign_new_user_avatar( $media_id, $user_id ); - - echo get_simple_local_avatar( $user_id ); - - die; - } - - /** - * remove the custom get_avatar hook for the default avatar list output on options-discussion.php - */ - public function avatar_defaults( $avatar_defaults ) { - remove_action( 'get_avatar', array( $this, 'get_avatar' ) ); - return $avatar_defaults; - } - - /** - * Delete avatars based on a user_id - * - * @param int $user_id - */ - public function avatar_delete( $user_id ) { - $old_avatars = (array) get_user_meta( $user_id, 'simple_local_avatar', true ); - - if ( empty( $old_avatars ) ) - return; - - // if it was uploaded media, don't erase the full size or try to erase an the ID - if ( array_key_exists( 'media_id', $old_avatars ) ) - unset( $old_avatars['media_id'], $old_avatars['full'] ); - - if ( ! empty( $old_avatars ) ) { - $upload_path = wp_upload_dir(); - - foreach ($old_avatars as $old_avatar ) { - // derive the path for the file based on the upload directory - $old_avatar_path = str_replace( $upload_path['baseurl'], $upload_path['basedir'], $old_avatar ); - if ( file_exists( $old_avatar_path ) ) - unlink( $old_avatar_path ); - } - } - - delete_user_meta( $user_id, 'simple_local_avatar' ); - delete_user_meta( $user_id, 'simple_local_avatar_rating' ); - } - - /** - * Creates a unique, meaningful file name for uploaded avatars. - * - * @param string $dir Path for file - * @param string $name Filename - * @param string $ext File extension (e.g. ".jpg") - * @return string Final filename - */ - public function unique_filename_callback( $dir, $name, $ext ) { - $user = get_user_by( 'id', (int) $this->user_id_being_edited ); - $name = $base_name = sanitize_file_name( $user->display_name . '_avatar_' . time() ); - - // ensure no conflicts with existing file names - $number = 1; - while ( file_exists( $dir . "/$name$ext" ) ) { - $name = $base_name . '_' . $number; - $number++; - } - - return $name . $ext; - } - - /** - * Adds errors based on avatar upload problems. - * - * @param WP_Error $errors Error messages for user profile screen. - */ - public function user_profile_update_errors( WP_Error $errors ) { - $errors->add( 'avatar_error', $this->avatar_upload_error ); - } - - /** - * Registers the simple_local_avatar field in the REST API. - */ - public function register_rest_fields() { - register_rest_field( 'user', 'simple_local_avatar', array( - 'get_callback' => array( $this, 'get_avatar_rest' ), - 'update_callback' => array( $this, 'set_avatar_rest' ), - 'schema' => array( - 'description' => 'The users simple local avatar', - 'type' => 'object', - ) - )); - } - - /** - * Returns the simple_local_avatar meta key for the given user. - * - * @param object $user User object - */ - public function get_avatar_rest( $user ) { - $local_avatar = get_user_meta( $user['id'], 'simple_local_avatar', true ); - if ( empty( $local_avatar ) ) { - return; - } - return $local_avatar; - } - - /** - * Updates the simple local avatar from a REST request. - * - * Since we are just adding a field to the existing user endpoint - * we don't need to worry about ensuring the calling user has proper permissions. - * Only the user or an administrator would be able to change the avatar. - * - * @param array $input Input submitted via REST request. - * @param object $user The user making the request. - */ - public function set_avatar_rest( $input, $user ) { - $this->assign_new_user_avatar($input['media_id'], $user->ID); - } - -} - -global $simple_local_avatars; -$simple_local_avatars = new Simple_Local_Avatars(); - -/** - * more efficient to call simple local avatar directly in theme and avoid gravatar setup - * - * @param int|string|object $id_or_email A user ID, email address, or comment object - * @param int $size Size of the avatar image - * @param string $default URL to a default image to use if no avatar is available - * @param string $alt Alternate text to use in image tag. Defaults to blank - * @return string tag for the user's avatar - */ -function get_simple_local_avatar( $id_or_email, $size = 96, $default = '', $alt = '' ) { - global $simple_local_avatars; - $avatar = $simple_local_avatars->get_avatar( '', $id_or_email, $size, $default, $alt ); - - if ( empty ( $avatar ) ) { - remove_action( 'get_avatar', array( $simple_local_avatars, 'get_avatar' ) ); - $avatar = get_avatar( $id_or_email, $size, $default, $alt ); - add_action( 'get_avatar', array( $simple_local_avatars, 'get_avatar' ), 10, 5 ); - } - - return $avatar; -} - -if ( ! function_exists( 'get_avatar' ) && ( $simple_local_avatars_options = get_option('simple_local_avatars') ) && ! empty( $simple_local_avatars_options['only'] ) ) : - - /** - * Retrieve the avatar for a user who provided a user ID or email address. - * - * @param int|string|object $id_or_email A user ID, email address, or comment object - * @param int $size Size of the avatar image - * @param string $default URL to a default image to use if no avatar is available - * @param string $alt Alternative text to use in image tag. Defaults to blank - * @return string tag for the user's avatar - */ - function get_avatar( $id_or_email, $size = 96, $default = '', $alt = '' ) { - global $simple_local_avatars; - - if ( ! get_option('show_avatars') ) - return false; - - $safe_alt = empty( $alt ) ? '' : esc_attr( $alt ); - - if ( !is_numeric($size) ) - $size = 96; - - if ( ! $avatar = $simple_local_avatars->get_avatar( '', $id_or_email, $size, $default, $alt ) ) : - - if ( empty($default) ) { - $avatar_default = get_option('avatar_default'); - if ( empty($avatar_default) ) - $default = 'mystery'; - else - $default = $avatar_default; - } - - $host = is_ssl() ? 'https://secure.gravatar.com' : 'http://0.gravatar.com'; - - if ( 'mystery' == $default ) - $default = "$host/avatar/ad516503a11cd5ca435acc9bb6523536?s={$size}"; // ad516503a11cd5ca435acc9bb6523536 == md5('unknown@gravatar.com') - elseif ( 'blank' == $default ) - $default = includes_url( 'images/blank.gif' ); - elseif ( 'gravatar_default' == $default ) - $default = "$host/avatar/?s={$size}"; - else - $default = "$host/avatar/?d=$default&s={$size}"; - - $avatar = "{$safe_alt}"; - - endif; - - return apply_filters('get_avatar', $avatar, $id_or_email, $size, $default, $alt); - } - -endif; - -/** - * on uninstallation, remove the custom field from the users and delete the local avatars - */ - -register_uninstall_hook( __FILE__, 'simple_local_avatars_uninstall' ); - -function simple_local_avatars_uninstall() { - $simple_local_avatars = new Simple_Local_Avatars; - $users = get_users(array( - 'meta_key' => 'simple_local_avatar', - 'fields' => 'ids', - )); - - foreach ( $users as $user_id ): - $simple_local_avatars->avatar_delete( $user_id ); - endforeach; - - delete_option('simple_local_avatars'); -} +options = (array) get_option( 'simple_local_avatars' ); + $this->avatar_ratings = array( + 'G' => __('G — Suitable for all audiences'), + 'PG' => __('PG — Possibly offensive, usually for audiences 13 and above'), + 'R' => __('R — Intended for adult audiences above 17'), + 'X' => __('X — Even more mature than above') + ); + + add_filter( 'pre_get_avatar_data', array( $this, 'get_avatar_data' ), 10, 2 ); + + add_action( 'admin_init', array( $this, 'admin_init' ) ); + + add_action( 'admin_enqueue_scripts', array( $this, 'admin_enqueue_scripts' ) ); + add_action( 'show_user_profile', array( $this, 'edit_user_profile' ) ); + add_action( 'edit_user_profile', array( $this, 'edit_user_profile' ) ); + + add_action( 'personal_options_update', array( $this, 'edit_user_profile_update' ) ); + add_action( 'edit_user_profile_update', array( $this, 'edit_user_profile_update' ) ); + add_action( 'admin_action_remove-simple-local-avatar', array( $this, 'action_remove_simple_local_avatar' ) ); + add_action( 'wp_ajax_assign_simple_local_avatar_media', array( $this, 'ajax_assign_simple_local_avatar_media' ) ); + add_action( 'wp_ajax_remove_simple_local_avatar', array( $this, 'action_remove_simple_local_avatar' ) ); + add_action( 'user_edit_form_tag', array( $this, 'user_edit_form_tag' ) ); + + add_filter( 'avatar_defaults', array( $this, 'avatar_defaults' ) ); + + add_action( 'rest_api_init', array( $this, 'register_rest_fields' ) ); + } + + /** + * Filter avatar data early to add avatar url if needed. This filter hooks + * before Gravatar setup to prevent wasted requests. + * + * @since 2.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. + */ + public function get_avatar_data( $args, $id_or_email ) { + $simple_local_avatar_url = $this->get_simple_local_avatar_url( $id_or_email, $args['size'] ); + if( $simple_local_avatar_url ) { + $args['url'] = $simple_local_avatar_url; + } + + // Local only mode + if( ! $simple_local_avatar_url && $this->options['only'] ) { + $args['url'] = $this->get_default_avatar_url( $args['size'] ); + } + + return $args; + } + + /** + * Get local avatar url. + * + * @since 2.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 int $size Requested avatar size. + */ + public function get_simple_local_avatar_url( $id_or_email, $size ) { + if ( is_numeric( $id_or_email ) ) + $user_id = (int) $id_or_email; + elseif ( is_string( $id_or_email ) && ( $user = get_user_by( 'email', $id_or_email ) ) ) + $user_id = $user->ID; + elseif ( is_object( $id_or_email ) && ! empty( $id_or_email->user_id ) ) + $user_id = (int) $id_or_email->user_id; + + if ( empty( $user_id ) ) + return ''; + + // Fetch local avatar from meta and make sure it's properly set. + $local_avatars = get_user_meta( $user_id, 'simple_local_avatar', true ); + if ( empty( $local_avatars['full'] ) ) + return ''; + + // Check rating. + $avatar_rating = get_user_meta( $user_id, 'simple_local_avatar_rating', true ); + if ( ! empty( $avatar_rating ) && 'G' != $avatar_rating && ( $site_rating = get_option( 'avatar_rating' ) ) ) { + $ratings = array_keys( $this->avatar_ratings ); + $site_rating_weight = array_search( $site_rating, $ratings ); + $avatar_rating_weight = array_search( $avatar_rating, $ratings ); + if ( false !== $avatar_rating_weight && $avatar_rating_weight > $site_rating_weight ) + return ''; + } + + // Handle "real" media. + if ( ! empty( $local_avatars['media_id'] ) ) { + // has the media been deleted? + if ( ! $avatar_full_path = get_attached_file( $local_avatars['media_id'] ) ) { + return ''; + } + } + + $size = (int) $size; + + // Generate a new size. + if ( ! array_key_exists( $size, $local_avatars ) ) { + $local_avatars[$size] = $local_avatars['full']; // just in case of failure elsewhere + + // Allow automatic rescaling to be turned off. + if ( apply_filters( 'simple_local_avatars_dynamic_resize', true ) ) : + + $upload_path = wp_upload_dir(); + + // Get path for image by converting URL, unless its already been set, thanks to using media library approach. + if ( ! isset( $avatar_full_path ) ) + $avatar_full_path = str_replace( $upload_path['baseurl'], $upload_path['basedir'], $local_avatars['full'] ); + + // generate the new size + $editor = wp_get_image_editor( $avatar_full_path ); + if ( ! is_wp_error( $editor ) ) { + $resized = $editor->resize( $size, $size, true ); + if ( ! is_wp_error( $resized ) ) { + $dest_file = $editor->generate_filename(); + $saved = $editor->save( $dest_file ); + if ( ! is_wp_error( $saved ) ) + $local_avatars[$size] = str_replace( $upload_path['basedir'], $upload_path['baseurl'], $dest_file ); + } + } + + // Save updated avatar sizes. + update_user_meta( $user_id, 'simple_local_avatar', $local_avatars ); + + endif; + } + + if ( 'http' != substr( $local_avatars[$size], 0, 4 ) ) + $local_avatars[$size] = home_url( $local_avatars[$size] ); + + return esc_url( $local_avatars[$size] ); + } + + /** + * Get default avatar url + * + * @since 2.2.0 + * + * @param int $size Requested avatar size. + */ + public function get_default_avatar_url( $size ) { + if ( empty( $default ) ) { + $avatar_default = get_option( 'avatar_default' ); + if ( empty( $avatar_default ) ) + $default = 'mystery'; + else + $default = $avatar_default; + } + + $host = is_ssl() ? 'https://secure.gravatar.com' : 'http://0.gravatar.com'; + + if ( 'mystery' == $default ) + $default = "$host/avatar/ad516503a11cd5ca435acc9bb6523536?s={$size}"; // ad516503a11cd5ca435acc9bb6523536 == md5('unknown@gravatar.com') + elseif ( 'blank' == $default ) + $default = includes_url( 'images/blank.gif' ); + elseif ( 'gravatar_default' == $default ) + $default = "$host/avatar/?s={$size}"; + else + $default = "$host/avatar/?d=$default&s={$size}"; + + return $default; + } + + public function admin_init() { + // upgrade pre 2.0 option + if ( $old_ops = get_option( 'simple_local_avatars_caps' ) ) { + if ( ! empty( $old_ops['simple_local_avatars_caps'] ) ) + update_option( 'simple_local_avatars', array( 'caps' => 1 ) ); + + delete_option( 'simple_local_avatar_caps' ); + } + + register_setting( 'discussion', 'simple_local_avatars', array( $this, 'sanitize_options' ) ); + add_settings_field( + 'simple-local-avatars-only', + __('Local Avatars Only','simple-local-avatars'), + array( $this, 'avatar_settings_field' ), + 'discussion', + 'avatars', + array( + 'key' => 'only', + 'desc' => __( 'Only allow local avatars (still uses Gravatar for default avatars)', 'simple-local-avatars' ) + ) + ); + add_settings_field( + 'simple-local-avatars-caps', + __('Local Upload Permissions','simple-local-avatars'), + array( $this, 'avatar_settings_field' ), + 'discussion', + 'avatars', + array( + 'key' => 'caps', + 'desc' => __( 'Only allow users with file upload capabilities to upload local avatars (Authors and above)', 'simple-local-avatars' ) + ) + ); + } + + /** + * Add scripts to the profile editing page + * + * @param string $hook_suffix Page hook + */ + public function admin_enqueue_scripts( $hook_suffix ) { + if ( 'profile.php' != $hook_suffix && 'user-edit.php' != $hook_suffix ) + return; + + if ( current_user_can( 'upload_files' ) ) + wp_enqueue_media(); + + $user_id = ( 'profile.php' == $hook_suffix ) ? get_current_user_id() : (int) $_GET['user_id']; + + $this->remove_nonce = wp_create_nonce( 'remove_simple_local_avatar_nonce' ); + + $script_name_append = ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) ? '.dev' : ''; + wp_enqueue_script( 'simple-local-avatars', plugins_url( '', __FILE__ ) . '/simple-local-avatars' . $script_name_append . '.js', array('jquery'), false, true ); + wp_localize_script( 'simple-local-avatars', 'i10n_SimpleLocalAvatars', array( + 'user_id' => $user_id, + 'insertMediaTitle' => __('Choose an Avatar','simple-local-avatars'), + 'insertIntoPost' => __('Set as avatar','simple-local-avatars'), + 'deleteNonce' => $this->remove_nonce, + 'mediaNonce' => wp_create_nonce( 'assign_simple_local_avatar_nonce' ), + ) ); + } + + /** + * Sanitize new settings field before saving + * + * @param array|string $input Passed input values to sanitize + * @return array|string Sanitized input fields + */ + public function sanitize_options( $input ) { + $new_input['caps'] = empty( $input['caps'] ) ? 0 : 1; + $new_input['only'] = empty( $input['only'] ) ? 0 : 1; + return $new_input; + } + + /** + * Settings field for avatar upload capabilities + * + * @param array $args Field arguments + */ + public function avatar_settings_field( $args ) { + $args = wp_parse_args( $args, array( + 'key' => '', + 'desc' => '', + ) ); + + if ( empty( $this->options[$args['key']] ) ) + $this->options[$args['key']] = 0; + + echo ' + + '; + } + + /** + * Output new Avatar fields to user editing / profile screen + * + * @param object $profileuser User object + */ + public function edit_user_profile( $profileuser ) { + ?> +

+ + + + + + + + + + + +
+ ID ); + remove_filter( 'pre_option_avatar_rating', '__return_null' ); + ?> + + options['caps'] ); + + if ( $upload_rights ) { + do_action( 'simple_local_avatar_notices' ); + wp_nonce_field( 'simple_local_avatar_nonce', '_simple_local_avatar_nonce', false ); + $remove_url = add_query_arg(array( + 'action' => 'remove-simple-local-avatar', + 'user_id' => $profileuser->ID, + '_wpnonce' => $this->remove_nonce, + ) ); + ?> + +

+
+ + +

+ +

+   + simple_local_avatar ) ) echo ' style="display:none;"'; ?>> +

+ simple_local_avatar ) ) + echo '' . __('No local avatar is set. Set up your avatar at Gravatar.com.','simple-local-avatars') . ''; + else + echo '' . __('You do not have media management permissions. To change your local avatar, contact the blog administrator.','simple-local-avatars') . ''; + } + ?> +
+
simple_local_avatar ) ); ?>> + + simple_local_avatar_rating ) || ! array_key_exists( $profileuser->simple_local_avatar_rating, $this->avatar_ratings ) ) + $profileuser->simple_local_avatar_rating = 'G'; + + foreach ( $this->avatar_ratings as $key => $rating ) : + echo "\n\t
"; + endforeach; + ?> +

+
+ avatar_delete( $user_id ); // delete old images if successful + + $meta_value = array(); + + // set the new avatar + if ( is_int( $url_or_media_id ) ) { + $meta_value['media_id'] = $url_or_media_id; + $url_or_media_id = wp_get_attachment_url( $url_or_media_id ); + } + + $meta_value['full'] = $url_or_media_id; + + update_user_meta( $user_id, 'simple_local_avatar', $meta_value ); // save user information (overwriting old) + } + + /** + * Save any changes to the user profile + * + * @param int $user_id ID of user being updated + */ + public function edit_user_profile_update( $user_id ) { + // check nonces + if( empty( $_POST['_simple_local_avatar_nonce'] ) || ! wp_verify_nonce( $_POST['_simple_local_avatar_nonce'], 'simple_local_avatar_nonce' ) ) + return; + + // check for uploaded files + if ( ! empty( $_FILES['simple-local-avatar']['name'] ) ) : + + // need to be more secure since low privelege users can upload + if ( false !== strpos( $_FILES['simple-local-avatar']['name'], '.php' ) ) { + $this->avatar_upload_error = __('For security reasons, the extension ".php" cannot be in your file name.','simple-local-avatars'); + add_action( 'user_profile_update_errors', array( $this, 'user_profile_update_errors' ) ); + return; + } + + // front end (theme my profile etc) support + if ( ! function_exists( 'media_handle_upload' ) ) + require_once( ABSPATH . 'wp-admin/includes/media.php' ); + + // allow developers to override file size upload limit for avatars + add_filter( 'upload_size_limit', array( $this, 'upload_size_limit' ) ); + + $this->user_id_being_edited = $user_id; // make user_id known to unique_filename_callback function + $avatar_id = media_handle_upload( 'simple-local-avatar', 0, array(), array( + 'mimes' => array( + 'jpg|jpeg|jpe' => 'image/jpeg', + 'gif' => 'image/gif', + 'png' => 'image/png', + ), + 'test_form' => false, + 'unique_filename_callback' => array( $this, 'unique_filename_callback' ) + ) ); + + remove_filter( 'upload_size_limit', array( $this, 'upload_size_limit' ) ); + + if ( is_wp_error( $avatar_id ) ) { // handle failures. + $this->avatar_upload_error = '' . __( 'There was an error uploading the avatar:', 'simple-local-avatars' ) . ' ' . esc_html( $avatar_id->get_error_message() ); + add_action( 'user_profile_update_errors', array( $this, 'user_profile_update_errors' ) ); + return; + } + + $this->assign_new_user_avatar( $avatar_id, $user_id ); + + endif; + + // handle rating + if ( isset( $avatar_id ) || $avatar = get_user_meta( $user_id, 'simple_local_avatar', true ) ) { + if ( empty( $_POST['simple_local_avatar_rating'] ) || ! array_key_exists( $_POST['simple_local_avatar_rating'], $this->avatar_ratings ) ) + $_POST['simple_local_avatar_rating'] = key( $this->avatar_ratings ); + + update_user_meta( $user_id, 'simple_local_avatar_rating', $_POST['simple_local_avatar_rating'] ); + } + } + + /** + * Allow developers to override the maximum allowable file size for avatar uploads + * + * @param int $bytes WordPress default byte size check + * @return int Maximum byte size + */ + public function upload_size_limit( $bytes ) { + return apply_filters( 'simple_local_avatars_upload_limit', $bytes ); + } + + /** + * Runs when a user clicks the Remove button for the avatar + */ + public function action_remove_simple_local_avatar() { + if ( ! empty( $_GET['user_id'] ) && ! empty( $_GET['_wpnonce'] ) && wp_verify_nonce( $_GET['_wpnonce'], 'remove_simple_local_avatar_nonce' ) ) { + $user_id = (int) $_GET['user_id']; + + if ( ! current_user_can('edit_user', $user_id) ) + wp_die( __('You do not have permission to edit this user.') ); + + $this->avatar_delete( $user_id ); // delete old images if successful + + if ( defined( 'DOING_AJAX' ) && DOING_AJAX ) + echo get_simple_local_avatar( $user_id ); + } + + if ( defined( 'DOING_AJAX' ) && DOING_AJAX ) + die; + } + + /** + * AJAX callback for assigning media ID fetched from media library to user + */ + public function ajax_assign_simple_local_avatar_media() { + // check required information and permissions + if ( empty( $_POST['user_id'] ) || empty( $_POST['media_id'] ) || ! current_user_can( 'upload_files' ) || ! current_user_can( 'edit_user', $_POST['user_id'] ) || empty( $_POST['_wpnonce'] ) || ! wp_verify_nonce( $_POST['_wpnonce'], 'assign_simple_local_avatar_nonce' ) ) + die; + + $media_id = (int) $_POST['media_id']; + $user_id = (int) $_POST['user_id']; + + // ensure the media is real is an image + if ( wp_attachment_is_image( $media_id ) ) + $this->assign_new_user_avatar( $media_id, $user_id ); + + echo get_simple_local_avatar( $user_id ); + + die; + } + + /** + * remove the custom get_avatar hook for the default avatar list output on options-discussion.php + */ + public function avatar_defaults( $avatar_defaults ) { + remove_action( 'pre_get_avatar_data', array( $this, 'get_avatar_data' ) ); + return $avatar_defaults; + } + + /** + * Delete avatars based on a user_id + * + * @param int $user_id + */ + public function avatar_delete( $user_id ) { + $old_avatars = (array) get_user_meta( $user_id, 'simple_local_avatar', true ); + + if ( empty( $old_avatars ) ) + return; + + // if it was uploaded media, don't erase the full size or try to erase an the ID + if ( array_key_exists( 'media_id', $old_avatars ) ) + unset( $old_avatars['media_id'], $old_avatars['full'] ); + + if ( ! empty( $old_avatars ) ) { + $upload_path = wp_upload_dir(); + + foreach ($old_avatars as $old_avatar ) { + // derive the path for the file based on the upload directory + $old_avatar_path = str_replace( $upload_path['baseurl'], $upload_path['basedir'], $old_avatar ); + if ( file_exists( $old_avatar_path ) ) + unlink( $old_avatar_path ); + } + } + + delete_user_meta( $user_id, 'simple_local_avatar' ); + delete_user_meta( $user_id, 'simple_local_avatar_rating' ); + } + + /** + * Creates a unique, meaningful file name for uploaded avatars. + * + * @param string $dir Path for file + * @param string $name Filename + * @param string $ext File extension (e.g. ".jpg") + * @return string Final filename + */ + public function unique_filename_callback( $dir, $name, $ext ) { + $user = get_user_by( 'id', (int) $this->user_id_being_edited ); + $name = $base_name = sanitize_file_name( $user->display_name . '_avatar_' . time() ); + + // ensure no conflicts with existing file names + $number = 1; + while ( file_exists( $dir . "/$name$ext" ) ) { + $name = $base_name . '_' . $number; + $number++; + } + + return $name . $ext; + } + + /** + * Adds errors based on avatar upload problems. + * + * @param WP_Error $errors Error messages for user profile screen. + */ + public function user_profile_update_errors( WP_Error $errors ) { + $errors->add( 'avatar_error', $this->avatar_upload_error ); + } + + /** + * Registers the simple_local_avatar field in the REST API. + */ + public function register_rest_fields() { + register_rest_field( 'user', 'simple_local_avatar', array( + 'get_callback' => array( $this, 'get_avatar_rest' ), + 'update_callback' => array( $this, 'set_avatar_rest' ), + 'schema' => array( + 'description' => 'The users simple local avatar', + 'type' => 'object', + ) + )); + } + + /** + * Returns the simple_local_avatar meta key for the given user. + * + * @param object $user User object + */ + public function get_avatar_rest( $user ) { + $local_avatar = get_user_meta( $user['id'], 'simple_local_avatar', true ); + if ( empty( $local_avatar ) ) { + return; + } + return $local_avatar; + } + + /** + * Updates the simple local avatar from a REST request. + * + * Since we are just adding a field to the existing user endpoint + * we don't need to worry about ensuring the calling user has proper permissions. + * Only the user or an administrator would be able to change the avatar. + * + * @param array $input Input submitted via REST request. + * @param object $user The user making the request. + */ + public function set_avatar_rest( $input, $user ) { + $this->assign_new_user_avatar($input['media_id'], $user->ID); + } + +} + +global $simple_local_avatars; +$simple_local_avatars = new Simple_Local_Avatars(); + +/** + * more efficient to call simple local avatar directly in theme and avoid + * gravatar setup. Since 2.2, This function is only a proxy for get_avatar + * due to internal changes. + * + * @param int|string|object $id_or_email A user ID, email address, or comment object + * @param int $size Size of the avatar image + * @param string $default URL to a default image to use if no avatar is available + * @param string $alt Alternate text to use in image tag. Defaults to blank + * @param array $args Support new args parameter. + * + * @return string tag for the user's avatar + */ +function get_simple_local_avatar( $id_or_email, $size = 96, $default = '', $alt = '', $args = array() ) { + return apply_filters( 'simple_local_avatar', get_avatar( $id_or_email, $size, $default, $alt, $args ) ); +} + +/** + * on uninstallation, remove the custom field from the users and delete the local avatars + */ + +register_uninstall_hook( __FILE__, 'simple_local_avatars_uninstall' ); + +function simple_local_avatars_uninstall() { + $simple_local_avatars = new Simple_Local_Avatars; + $users = get_users(array( + 'meta_key' => 'simple_local_avatar', + 'fields' => 'ids', + )); + + foreach ( $users as $user_id ): + $simple_local_avatars->avatar_delete( $user_id ); + endforeach; + + delete_option('simple_local_avatars'); +} From 83fe85dc2f90645fde1803e4839e2a18e6f1a408 Mon Sep 17 00:00:00 2001 From: Tung Du Date: Wed, 22 Jan 2020 22:17:37 +0700 Subject: [PATCH 17/73] Fix whitespace changes noise --- simple-local-avatars.php | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/simple-local-avatars.php b/simple-local-avatars.php index d581d39d..57c620fd 100644 --- a/simple-local-avatars.php +++ b/simple-local-avatars.php @@ -99,7 +99,7 @@ public function get_simple_local_avatar_url( $id_or_email, $size ) { if ( empty( $local_avatars['full'] ) ) return ''; - // Check rating. + // check rating $avatar_rating = get_user_meta( $user_id, 'simple_local_avatar_rating', true ); if ( ! empty( $avatar_rating ) && 'G' != $avatar_rating && ( $site_rating = get_option( 'avatar_rating' ) ) ) { $ratings = array_keys( $this->avatar_ratings ); @@ -109,7 +109,7 @@ public function get_simple_local_avatar_url( $id_or_email, $size ) { return ''; } - // Handle "real" media. + // handle "real" media if ( ! empty( $local_avatars['media_id'] ) ) { // has the media been deleted? if ( ! $avatar_full_path = get_attached_file( $local_avatars['media_id'] ) ) { @@ -123,12 +123,12 @@ public function get_simple_local_avatar_url( $id_or_email, $size ) { if ( ! array_key_exists( $size, $local_avatars ) ) { $local_avatars[$size] = $local_avatars['full']; // just in case of failure elsewhere - // Allow automatic rescaling to be turned off. + // allow automatic rescaling to be turned off if ( apply_filters( 'simple_local_avatars_dynamic_resize', true ) ) : $upload_path = wp_upload_dir(); - // Get path for image by converting URL, unless its already been set, thanks to using media library approach. + // get path for image by converting URL, unless its already been set, thanks to using media library approach if ( ! isset( $avatar_full_path ) ) $avatar_full_path = str_replace( $upload_path['baseurl'], $upload_path['basedir'], $local_avatars['full'] ); @@ -144,7 +144,7 @@ public function get_simple_local_avatar_url( $id_or_email, $size ) { } } - // Save updated avatar sizes. + // save updated avatar sizes update_user_meta( $user_id, 'simple_local_avatar', $local_avatars ); endif; @@ -501,7 +501,7 @@ public function ajax_assign_simple_local_avatar_media() { * remove the custom get_avatar hook for the default avatar list output on options-discussion.php */ public function avatar_defaults( $avatar_defaults ) { - remove_action( 'pre_get_avatar_data', array( $this, 'get_avatar_data' ) ); + remove_action( 'get_avatar', array( $this, 'get_avatar' ) ); return $avatar_defaults; } @@ -648,3 +648,4 @@ function simple_local_avatars_uninstall() { delete_option('simple_local_avatars'); } + From ed2fb9e591f94e738b88e1a5dbf7362ff0159162 Mon Sep 17 00:00:00 2001 From: Tung Du Date: Wed, 22 Jan 2020 22:23:46 +0700 Subject: [PATCH 18/73] fix: line ending mode --- simple-local-avatars.php | 1302 +++++++++++++++++++------------------- 1 file changed, 651 insertions(+), 651 deletions(-) diff --git a/simple-local-avatars.php b/simple-local-avatars.php index 57c620fd..793b6c51 100644 --- a/simple-local-avatars.php +++ b/simple-local-avatars.php @@ -1,651 +1,651 @@ -options = (array) get_option( 'simple_local_avatars' ); - $this->avatar_ratings = array( - 'G' => __('G — Suitable for all audiences'), - 'PG' => __('PG — Possibly offensive, usually for audiences 13 and above'), - 'R' => __('R — Intended for adult audiences above 17'), - 'X' => __('X — Even more mature than above') - ); - - add_filter( 'pre_get_avatar_data', array( $this, 'get_avatar_data' ), 10, 2 ); - - add_action( 'admin_init', array( $this, 'admin_init' ) ); - - add_action( 'admin_enqueue_scripts', array( $this, 'admin_enqueue_scripts' ) ); - add_action( 'show_user_profile', array( $this, 'edit_user_profile' ) ); - add_action( 'edit_user_profile', array( $this, 'edit_user_profile' ) ); - - add_action( 'personal_options_update', array( $this, 'edit_user_profile_update' ) ); - add_action( 'edit_user_profile_update', array( $this, 'edit_user_profile_update' ) ); - add_action( 'admin_action_remove-simple-local-avatar', array( $this, 'action_remove_simple_local_avatar' ) ); - add_action( 'wp_ajax_assign_simple_local_avatar_media', array( $this, 'ajax_assign_simple_local_avatar_media' ) ); - add_action( 'wp_ajax_remove_simple_local_avatar', array( $this, 'action_remove_simple_local_avatar' ) ); - add_action( 'user_edit_form_tag', array( $this, 'user_edit_form_tag' ) ); - - add_filter( 'avatar_defaults', array( $this, 'avatar_defaults' ) ); - - add_action( 'rest_api_init', array( $this, 'register_rest_fields' ) ); - } - - /** - * Filter avatar data early to add avatar url if needed. This filter hooks - * before Gravatar setup to prevent wasted requests. - * - * @since 2.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. - */ - public function get_avatar_data( $args, $id_or_email ) { - $simple_local_avatar_url = $this->get_simple_local_avatar_url( $id_or_email, $args['size'] ); - if( $simple_local_avatar_url ) { - $args['url'] = $simple_local_avatar_url; - } - - // Local only mode - if( ! $simple_local_avatar_url && $this->options['only'] ) { - $args['url'] = $this->get_default_avatar_url( $args['size'] ); - } - - return $args; - } - - /** - * Get local avatar url. - * - * @since 2.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 int $size Requested avatar size. - */ - public function get_simple_local_avatar_url( $id_or_email, $size ) { - if ( is_numeric( $id_or_email ) ) - $user_id = (int) $id_or_email; - elseif ( is_string( $id_or_email ) && ( $user = get_user_by( 'email', $id_or_email ) ) ) - $user_id = $user->ID; - elseif ( is_object( $id_or_email ) && ! empty( $id_or_email->user_id ) ) - $user_id = (int) $id_or_email->user_id; - - if ( empty( $user_id ) ) - return ''; - - // Fetch local avatar from meta and make sure it's properly set. - $local_avatars = get_user_meta( $user_id, 'simple_local_avatar', true ); - if ( empty( $local_avatars['full'] ) ) - return ''; - - // check rating - $avatar_rating = get_user_meta( $user_id, 'simple_local_avatar_rating', true ); - if ( ! empty( $avatar_rating ) && 'G' != $avatar_rating && ( $site_rating = get_option( 'avatar_rating' ) ) ) { - $ratings = array_keys( $this->avatar_ratings ); - $site_rating_weight = array_search( $site_rating, $ratings ); - $avatar_rating_weight = array_search( $avatar_rating, $ratings ); - if ( false !== $avatar_rating_weight && $avatar_rating_weight > $site_rating_weight ) - return ''; - } - - // handle "real" media - if ( ! empty( $local_avatars['media_id'] ) ) { - // has the media been deleted? - if ( ! $avatar_full_path = get_attached_file( $local_avatars['media_id'] ) ) { - return ''; - } - } - - $size = (int) $size; - - // Generate a new size. - if ( ! array_key_exists( $size, $local_avatars ) ) { - $local_avatars[$size] = $local_avatars['full']; // just in case of failure elsewhere - - // allow automatic rescaling to be turned off - if ( apply_filters( 'simple_local_avatars_dynamic_resize', true ) ) : - - $upload_path = wp_upload_dir(); - - // get path for image by converting URL, unless its already been set, thanks to using media library approach - if ( ! isset( $avatar_full_path ) ) - $avatar_full_path = str_replace( $upload_path['baseurl'], $upload_path['basedir'], $local_avatars['full'] ); - - // generate the new size - $editor = wp_get_image_editor( $avatar_full_path ); - if ( ! is_wp_error( $editor ) ) { - $resized = $editor->resize( $size, $size, true ); - if ( ! is_wp_error( $resized ) ) { - $dest_file = $editor->generate_filename(); - $saved = $editor->save( $dest_file ); - if ( ! is_wp_error( $saved ) ) - $local_avatars[$size] = str_replace( $upload_path['basedir'], $upload_path['baseurl'], $dest_file ); - } - } - - // save updated avatar sizes - update_user_meta( $user_id, 'simple_local_avatar', $local_avatars ); - - endif; - } - - if ( 'http' != substr( $local_avatars[$size], 0, 4 ) ) - $local_avatars[$size] = home_url( $local_avatars[$size] ); - - return esc_url( $local_avatars[$size] ); - } - - /** - * Get default avatar url - * - * @since 2.2.0 - * - * @param int $size Requested avatar size. - */ - public function get_default_avatar_url( $size ) { - if ( empty( $default ) ) { - $avatar_default = get_option( 'avatar_default' ); - if ( empty( $avatar_default ) ) - $default = 'mystery'; - else - $default = $avatar_default; - } - - $host = is_ssl() ? 'https://secure.gravatar.com' : 'http://0.gravatar.com'; - - if ( 'mystery' == $default ) - $default = "$host/avatar/ad516503a11cd5ca435acc9bb6523536?s={$size}"; // ad516503a11cd5ca435acc9bb6523536 == md5('unknown@gravatar.com') - elseif ( 'blank' == $default ) - $default = includes_url( 'images/blank.gif' ); - elseif ( 'gravatar_default' == $default ) - $default = "$host/avatar/?s={$size}"; - else - $default = "$host/avatar/?d=$default&s={$size}"; - - return $default; - } - - public function admin_init() { - // upgrade pre 2.0 option - if ( $old_ops = get_option( 'simple_local_avatars_caps' ) ) { - if ( ! empty( $old_ops['simple_local_avatars_caps'] ) ) - update_option( 'simple_local_avatars', array( 'caps' => 1 ) ); - - delete_option( 'simple_local_avatar_caps' ); - } - - register_setting( 'discussion', 'simple_local_avatars', array( $this, 'sanitize_options' ) ); - add_settings_field( - 'simple-local-avatars-only', - __('Local Avatars Only','simple-local-avatars'), - array( $this, 'avatar_settings_field' ), - 'discussion', - 'avatars', - array( - 'key' => 'only', - 'desc' => __( 'Only allow local avatars (still uses Gravatar for default avatars)', 'simple-local-avatars' ) - ) - ); - add_settings_field( - 'simple-local-avatars-caps', - __('Local Upload Permissions','simple-local-avatars'), - array( $this, 'avatar_settings_field' ), - 'discussion', - 'avatars', - array( - 'key' => 'caps', - 'desc' => __( 'Only allow users with file upload capabilities to upload local avatars (Authors and above)', 'simple-local-avatars' ) - ) - ); - } - - /** - * Add scripts to the profile editing page - * - * @param string $hook_suffix Page hook - */ - public function admin_enqueue_scripts( $hook_suffix ) { - if ( 'profile.php' != $hook_suffix && 'user-edit.php' != $hook_suffix ) - return; - - if ( current_user_can( 'upload_files' ) ) - wp_enqueue_media(); - - $user_id = ( 'profile.php' == $hook_suffix ) ? get_current_user_id() : (int) $_GET['user_id']; - - $this->remove_nonce = wp_create_nonce( 'remove_simple_local_avatar_nonce' ); - - $script_name_append = ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) ? '.dev' : ''; - wp_enqueue_script( 'simple-local-avatars', plugins_url( '', __FILE__ ) . '/simple-local-avatars' . $script_name_append . '.js', array('jquery'), false, true ); - wp_localize_script( 'simple-local-avatars', 'i10n_SimpleLocalAvatars', array( - 'user_id' => $user_id, - 'insertMediaTitle' => __('Choose an Avatar','simple-local-avatars'), - 'insertIntoPost' => __('Set as avatar','simple-local-avatars'), - 'deleteNonce' => $this->remove_nonce, - 'mediaNonce' => wp_create_nonce( 'assign_simple_local_avatar_nonce' ), - ) ); - } - - /** - * Sanitize new settings field before saving - * - * @param array|string $input Passed input values to sanitize - * @return array|string Sanitized input fields - */ - public function sanitize_options( $input ) { - $new_input['caps'] = empty( $input['caps'] ) ? 0 : 1; - $new_input['only'] = empty( $input['only'] ) ? 0 : 1; - return $new_input; - } - - /** - * Settings field for avatar upload capabilities - * - * @param array $args Field arguments - */ - public function avatar_settings_field( $args ) { - $args = wp_parse_args( $args, array( - 'key' => '', - 'desc' => '', - ) ); - - if ( empty( $this->options[$args['key']] ) ) - $this->options[$args['key']] = 0; - - echo ' - - '; - } - - /** - * Output new Avatar fields to user editing / profile screen - * - * @param object $profileuser User object - */ - public function edit_user_profile( $profileuser ) { - ?> -

- - - - - - - - - - - -
- ID ); - remove_filter( 'pre_option_avatar_rating', '__return_null' ); - ?> - - options['caps'] ); - - if ( $upload_rights ) { - do_action( 'simple_local_avatar_notices' ); - wp_nonce_field( 'simple_local_avatar_nonce', '_simple_local_avatar_nonce', false ); - $remove_url = add_query_arg(array( - 'action' => 'remove-simple-local-avatar', - 'user_id' => $profileuser->ID, - '_wpnonce' => $this->remove_nonce, - ) ); - ?> - -

-
- - -

- -

-   - simple_local_avatar ) ) echo ' style="display:none;"'; ?>> -

- simple_local_avatar ) ) - echo '' . __('No local avatar is set. Set up your avatar at Gravatar.com.','simple-local-avatars') . ''; - else - echo '' . __('You do not have media management permissions. To change your local avatar, contact the blog administrator.','simple-local-avatars') . ''; - } - ?> -
-
simple_local_avatar ) ); ?>> - - simple_local_avatar_rating ) || ! array_key_exists( $profileuser->simple_local_avatar_rating, $this->avatar_ratings ) ) - $profileuser->simple_local_avatar_rating = 'G'; - - foreach ( $this->avatar_ratings as $key => $rating ) : - echo "\n\t
"; - endforeach; - ?> -

-
- avatar_delete( $user_id ); // delete old images if successful - - $meta_value = array(); - - // set the new avatar - if ( is_int( $url_or_media_id ) ) { - $meta_value['media_id'] = $url_or_media_id; - $url_or_media_id = wp_get_attachment_url( $url_or_media_id ); - } - - $meta_value['full'] = $url_or_media_id; - - update_user_meta( $user_id, 'simple_local_avatar', $meta_value ); // save user information (overwriting old) - } - - /** - * Save any changes to the user profile - * - * @param int $user_id ID of user being updated - */ - public function edit_user_profile_update( $user_id ) { - // check nonces - if( empty( $_POST['_simple_local_avatar_nonce'] ) || ! wp_verify_nonce( $_POST['_simple_local_avatar_nonce'], 'simple_local_avatar_nonce' ) ) - return; - - // check for uploaded files - if ( ! empty( $_FILES['simple-local-avatar']['name'] ) ) : - - // need to be more secure since low privelege users can upload - if ( false !== strpos( $_FILES['simple-local-avatar']['name'], '.php' ) ) { - $this->avatar_upload_error = __('For security reasons, the extension ".php" cannot be in your file name.','simple-local-avatars'); - add_action( 'user_profile_update_errors', array( $this, 'user_profile_update_errors' ) ); - return; - } - - // front end (theme my profile etc) support - if ( ! function_exists( 'media_handle_upload' ) ) - require_once( ABSPATH . 'wp-admin/includes/media.php' ); - - // allow developers to override file size upload limit for avatars - add_filter( 'upload_size_limit', array( $this, 'upload_size_limit' ) ); - - $this->user_id_being_edited = $user_id; // make user_id known to unique_filename_callback function - $avatar_id = media_handle_upload( 'simple-local-avatar', 0, array(), array( - 'mimes' => array( - 'jpg|jpeg|jpe' => 'image/jpeg', - 'gif' => 'image/gif', - 'png' => 'image/png', - ), - 'test_form' => false, - 'unique_filename_callback' => array( $this, 'unique_filename_callback' ) - ) ); - - remove_filter( 'upload_size_limit', array( $this, 'upload_size_limit' ) ); - - if ( is_wp_error( $avatar_id ) ) { // handle failures. - $this->avatar_upload_error = '' . __( 'There was an error uploading the avatar:', 'simple-local-avatars' ) . ' ' . esc_html( $avatar_id->get_error_message() ); - add_action( 'user_profile_update_errors', array( $this, 'user_profile_update_errors' ) ); - return; - } - - $this->assign_new_user_avatar( $avatar_id, $user_id ); - - endif; - - // handle rating - if ( isset( $avatar_id ) || $avatar = get_user_meta( $user_id, 'simple_local_avatar', true ) ) { - if ( empty( $_POST['simple_local_avatar_rating'] ) || ! array_key_exists( $_POST['simple_local_avatar_rating'], $this->avatar_ratings ) ) - $_POST['simple_local_avatar_rating'] = key( $this->avatar_ratings ); - - update_user_meta( $user_id, 'simple_local_avatar_rating', $_POST['simple_local_avatar_rating'] ); - } - } - - /** - * Allow developers to override the maximum allowable file size for avatar uploads - * - * @param int $bytes WordPress default byte size check - * @return int Maximum byte size - */ - public function upload_size_limit( $bytes ) { - return apply_filters( 'simple_local_avatars_upload_limit', $bytes ); - } - - /** - * Runs when a user clicks the Remove button for the avatar - */ - public function action_remove_simple_local_avatar() { - if ( ! empty( $_GET['user_id'] ) && ! empty( $_GET['_wpnonce'] ) && wp_verify_nonce( $_GET['_wpnonce'], 'remove_simple_local_avatar_nonce' ) ) { - $user_id = (int) $_GET['user_id']; - - if ( ! current_user_can('edit_user', $user_id) ) - wp_die( __('You do not have permission to edit this user.') ); - - $this->avatar_delete( $user_id ); // delete old images if successful - - if ( defined( 'DOING_AJAX' ) && DOING_AJAX ) - echo get_simple_local_avatar( $user_id ); - } - - if ( defined( 'DOING_AJAX' ) && DOING_AJAX ) - die; - } - - /** - * AJAX callback for assigning media ID fetched from media library to user - */ - public function ajax_assign_simple_local_avatar_media() { - // check required information and permissions - if ( empty( $_POST['user_id'] ) || empty( $_POST['media_id'] ) || ! current_user_can( 'upload_files' ) || ! current_user_can( 'edit_user', $_POST['user_id'] ) || empty( $_POST['_wpnonce'] ) || ! wp_verify_nonce( $_POST['_wpnonce'], 'assign_simple_local_avatar_nonce' ) ) - die; - - $media_id = (int) $_POST['media_id']; - $user_id = (int) $_POST['user_id']; - - // ensure the media is real is an image - if ( wp_attachment_is_image( $media_id ) ) - $this->assign_new_user_avatar( $media_id, $user_id ); - - echo get_simple_local_avatar( $user_id ); - - die; - } - - /** - * remove the custom get_avatar hook for the default avatar list output on options-discussion.php - */ - public function avatar_defaults( $avatar_defaults ) { - remove_action( 'get_avatar', array( $this, 'get_avatar' ) ); - return $avatar_defaults; - } - - /** - * Delete avatars based on a user_id - * - * @param int $user_id - */ - public function avatar_delete( $user_id ) { - $old_avatars = (array) get_user_meta( $user_id, 'simple_local_avatar', true ); - - if ( empty( $old_avatars ) ) - return; - - // if it was uploaded media, don't erase the full size or try to erase an the ID - if ( array_key_exists( 'media_id', $old_avatars ) ) - unset( $old_avatars['media_id'], $old_avatars['full'] ); - - if ( ! empty( $old_avatars ) ) { - $upload_path = wp_upload_dir(); - - foreach ($old_avatars as $old_avatar ) { - // derive the path for the file based on the upload directory - $old_avatar_path = str_replace( $upload_path['baseurl'], $upload_path['basedir'], $old_avatar ); - if ( file_exists( $old_avatar_path ) ) - unlink( $old_avatar_path ); - } - } - - delete_user_meta( $user_id, 'simple_local_avatar' ); - delete_user_meta( $user_id, 'simple_local_avatar_rating' ); - } - - /** - * Creates a unique, meaningful file name for uploaded avatars. - * - * @param string $dir Path for file - * @param string $name Filename - * @param string $ext File extension (e.g. ".jpg") - * @return string Final filename - */ - public function unique_filename_callback( $dir, $name, $ext ) { - $user = get_user_by( 'id', (int) $this->user_id_being_edited ); - $name = $base_name = sanitize_file_name( $user->display_name . '_avatar_' . time() ); - - // ensure no conflicts with existing file names - $number = 1; - while ( file_exists( $dir . "/$name$ext" ) ) { - $name = $base_name . '_' . $number; - $number++; - } - - return $name . $ext; - } - - /** - * Adds errors based on avatar upload problems. - * - * @param WP_Error $errors Error messages for user profile screen. - */ - public function user_profile_update_errors( WP_Error $errors ) { - $errors->add( 'avatar_error', $this->avatar_upload_error ); - } - - /** - * Registers the simple_local_avatar field in the REST API. - */ - public function register_rest_fields() { - register_rest_field( 'user', 'simple_local_avatar', array( - 'get_callback' => array( $this, 'get_avatar_rest' ), - 'update_callback' => array( $this, 'set_avatar_rest' ), - 'schema' => array( - 'description' => 'The users simple local avatar', - 'type' => 'object', - ) - )); - } - - /** - * Returns the simple_local_avatar meta key for the given user. - * - * @param object $user User object - */ - public function get_avatar_rest( $user ) { - $local_avatar = get_user_meta( $user['id'], 'simple_local_avatar', true ); - if ( empty( $local_avatar ) ) { - return; - } - return $local_avatar; - } - - /** - * Updates the simple local avatar from a REST request. - * - * Since we are just adding a field to the existing user endpoint - * we don't need to worry about ensuring the calling user has proper permissions. - * Only the user or an administrator would be able to change the avatar. - * - * @param array $input Input submitted via REST request. - * @param object $user The user making the request. - */ - public function set_avatar_rest( $input, $user ) { - $this->assign_new_user_avatar($input['media_id'], $user->ID); - } - -} - -global $simple_local_avatars; -$simple_local_avatars = new Simple_Local_Avatars(); - -/** - * more efficient to call simple local avatar directly in theme and avoid - * gravatar setup. Since 2.2, This function is only a proxy for get_avatar - * due to internal changes. - * - * @param int|string|object $id_or_email A user ID, email address, or comment object - * @param int $size Size of the avatar image - * @param string $default URL to a default image to use if no avatar is available - * @param string $alt Alternate text to use in image tag. Defaults to blank - * @param array $args Support new args parameter. - * - * @return string tag for the user's avatar - */ -function get_simple_local_avatar( $id_or_email, $size = 96, $default = '', $alt = '', $args = array() ) { - return apply_filters( 'simple_local_avatar', get_avatar( $id_or_email, $size, $default, $alt, $args ) ); -} - -/** - * on uninstallation, remove the custom field from the users and delete the local avatars - */ - -register_uninstall_hook( __FILE__, 'simple_local_avatars_uninstall' ); - -function simple_local_avatars_uninstall() { - $simple_local_avatars = new Simple_Local_Avatars; - $users = get_users(array( - 'meta_key' => 'simple_local_avatar', - 'fields' => 'ids', - )); - - foreach ( $users as $user_id ): - $simple_local_avatars->avatar_delete( $user_id ); - endforeach; - - delete_option('simple_local_avatars'); -} - +options = (array) get_option( 'simple_local_avatars' ); + $this->avatar_ratings = array( + 'G' => __('G — Suitable for all audiences'), + 'PG' => __('PG — Possibly offensive, usually for audiences 13 and above'), + 'R' => __('R — Intended for adult audiences above 17'), + 'X' => __('X — Even more mature than above') + ); + + add_filter( 'pre_get_avatar_data', array( $this, 'get_avatar_data' ), 10, 2 ); + + add_action( 'admin_init', array( $this, 'admin_init' ) ); + + add_action( 'admin_enqueue_scripts', array( $this, 'admin_enqueue_scripts' ) ); + add_action( 'show_user_profile', array( $this, 'edit_user_profile' ) ); + add_action( 'edit_user_profile', array( $this, 'edit_user_profile' ) ); + + add_action( 'personal_options_update', array( $this, 'edit_user_profile_update' ) ); + add_action( 'edit_user_profile_update', array( $this, 'edit_user_profile_update' ) ); + add_action( 'admin_action_remove-simple-local-avatar', array( $this, 'action_remove_simple_local_avatar' ) ); + add_action( 'wp_ajax_assign_simple_local_avatar_media', array( $this, 'ajax_assign_simple_local_avatar_media' ) ); + add_action( 'wp_ajax_remove_simple_local_avatar', array( $this, 'action_remove_simple_local_avatar' ) ); + add_action( 'user_edit_form_tag', array( $this, 'user_edit_form_tag' ) ); + + add_filter( 'avatar_defaults', array( $this, 'avatar_defaults' ) ); + + add_action( 'rest_api_init', array( $this, 'register_rest_fields' ) ); + } + + /** + * Filter avatar data early to add avatar url if needed. This filter hooks + * before Gravatar setup to prevent wasted requests. + * + * @since 2.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. + */ + public function get_avatar_data( $args, $id_or_email ) { + $simple_local_avatar_url = $this->get_simple_local_avatar_url( $id_or_email, $args['size'] ); + if( $simple_local_avatar_url ) { + $args['url'] = $simple_local_avatar_url; + } + + // Local only mode + if( ! $simple_local_avatar_url && $this->options['only'] ) { + $args['url'] = $this->get_default_avatar_url( $args['size'] ); + } + + return $args; + } + + /** + * Get local avatar url. + * + * @since 2.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 int $size Requested avatar size. + */ + public function get_simple_local_avatar_url( $id_or_email, $size ) { + if ( is_numeric( $id_or_email ) ) + $user_id = (int) $id_or_email; + elseif ( is_string( $id_or_email ) && ( $user = get_user_by( 'email', $id_or_email ) ) ) + $user_id = $user->ID; + elseif ( is_object( $id_or_email ) && ! empty( $id_or_email->user_id ) ) + $user_id = (int) $id_or_email->user_id; + + if ( empty( $user_id ) ) + return ''; + + // Fetch local avatar from meta and make sure it's properly set. + $local_avatars = get_user_meta( $user_id, 'simple_local_avatar', true ); + if ( empty( $local_avatars['full'] ) ) + return ''; + + // check rating + $avatar_rating = get_user_meta( $user_id, 'simple_local_avatar_rating', true ); + if ( ! empty( $avatar_rating ) && 'G' != $avatar_rating && ( $site_rating = get_option( 'avatar_rating' ) ) ) { + $ratings = array_keys( $this->avatar_ratings ); + $site_rating_weight = array_search( $site_rating, $ratings ); + $avatar_rating_weight = array_search( $avatar_rating, $ratings ); + if ( false !== $avatar_rating_weight && $avatar_rating_weight > $site_rating_weight ) + return ''; + } + + // handle "real" media + if ( ! empty( $local_avatars['media_id'] ) ) { + // has the media been deleted? + if ( ! $avatar_full_path = get_attached_file( $local_avatars['media_id'] ) ) { + return ''; + } + } + + $size = (int) $size; + + // Generate a new size. + if ( ! array_key_exists( $size, $local_avatars ) ) { + $local_avatars[$size] = $local_avatars['full']; // just in case of failure elsewhere + + // allow automatic rescaling to be turned off + if ( apply_filters( 'simple_local_avatars_dynamic_resize', true ) ) : + + $upload_path = wp_upload_dir(); + + // get path for image by converting URL, unless its already been set, thanks to using media library approach + if ( ! isset( $avatar_full_path ) ) + $avatar_full_path = str_replace( $upload_path['baseurl'], $upload_path['basedir'], $local_avatars['full'] ); + + // generate the new size + $editor = wp_get_image_editor( $avatar_full_path ); + if ( ! is_wp_error( $editor ) ) { + $resized = $editor->resize( $size, $size, true ); + if ( ! is_wp_error( $resized ) ) { + $dest_file = $editor->generate_filename(); + $saved = $editor->save( $dest_file ); + if ( ! is_wp_error( $saved ) ) + $local_avatars[$size] = str_replace( $upload_path['basedir'], $upload_path['baseurl'], $dest_file ); + } + } + + // save updated avatar sizes + update_user_meta( $user_id, 'simple_local_avatar', $local_avatars ); + + endif; + } + + if ( 'http' != substr( $local_avatars[$size], 0, 4 ) ) + $local_avatars[$size] = home_url( $local_avatars[$size] ); + + return esc_url( $local_avatars[$size] ); + } + + /** + * Get default avatar url + * + * @since 2.2.0 + * + * @param int $size Requested avatar size. + */ + public function get_default_avatar_url( $size ) { + if ( empty( $default ) ) { + $avatar_default = get_option( 'avatar_default' ); + if ( empty( $avatar_default ) ) + $default = 'mystery'; + else + $default = $avatar_default; + } + + $host = is_ssl() ? 'https://secure.gravatar.com' : 'http://0.gravatar.com'; + + if ( 'mystery' == $default ) + $default = "$host/avatar/ad516503a11cd5ca435acc9bb6523536?s={$size}"; // ad516503a11cd5ca435acc9bb6523536 == md5('unknown@gravatar.com') + elseif ( 'blank' == $default ) + $default = includes_url( 'images/blank.gif' ); + elseif ( 'gravatar_default' == $default ) + $default = "$host/avatar/?s={$size}"; + else + $default = "$host/avatar/?d=$default&s={$size}"; + + return $default; + } + + public function admin_init() { + // upgrade pre 2.0 option + if ( $old_ops = get_option( 'simple_local_avatars_caps' ) ) { + if ( ! empty( $old_ops['simple_local_avatars_caps'] ) ) + update_option( 'simple_local_avatars', array( 'caps' => 1 ) ); + + delete_option( 'simple_local_avatar_caps' ); + } + + register_setting( 'discussion', 'simple_local_avatars', array( $this, 'sanitize_options' ) ); + add_settings_field( + 'simple-local-avatars-only', + __('Local Avatars Only','simple-local-avatars'), + array( $this, 'avatar_settings_field' ), + 'discussion', + 'avatars', + array( + 'key' => 'only', + 'desc' => __( 'Only allow local avatars (still uses Gravatar for default avatars)', 'simple-local-avatars' ) + ) + ); + add_settings_field( + 'simple-local-avatars-caps', + __('Local Upload Permissions','simple-local-avatars'), + array( $this, 'avatar_settings_field' ), + 'discussion', + 'avatars', + array( + 'key' => 'caps', + 'desc' => __( 'Only allow users with file upload capabilities to upload local avatars (Authors and above)', 'simple-local-avatars' ) + ) + ); + } + + /** + * Add scripts to the profile editing page + * + * @param string $hook_suffix Page hook + */ + public function admin_enqueue_scripts( $hook_suffix ) { + if ( 'profile.php' != $hook_suffix && 'user-edit.php' != $hook_suffix ) + return; + + if ( current_user_can( 'upload_files' ) ) + wp_enqueue_media(); + + $user_id = ( 'profile.php' == $hook_suffix ) ? get_current_user_id() : (int) $_GET['user_id']; + + $this->remove_nonce = wp_create_nonce( 'remove_simple_local_avatar_nonce' ); + + $script_name_append = ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) ? '.dev' : ''; + wp_enqueue_script( 'simple-local-avatars', plugins_url( '', __FILE__ ) . '/simple-local-avatars' . $script_name_append . '.js', array('jquery'), false, true ); + wp_localize_script( 'simple-local-avatars', 'i10n_SimpleLocalAvatars', array( + 'user_id' => $user_id, + 'insertMediaTitle' => __('Choose an Avatar','simple-local-avatars'), + 'insertIntoPost' => __('Set as avatar','simple-local-avatars'), + 'deleteNonce' => $this->remove_nonce, + 'mediaNonce' => wp_create_nonce( 'assign_simple_local_avatar_nonce' ), + ) ); + } + + /** + * Sanitize new settings field before saving + * + * @param array|string $input Passed input values to sanitize + * @return array|string Sanitized input fields + */ + public function sanitize_options( $input ) { + $new_input['caps'] = empty( $input['caps'] ) ? 0 : 1; + $new_input['only'] = empty( $input['only'] ) ? 0 : 1; + return $new_input; + } + + /** + * Settings field for avatar upload capabilities + * + * @param array $args Field arguments + */ + public function avatar_settings_field( $args ) { + $args = wp_parse_args( $args, array( + 'key' => '', + 'desc' => '', + ) ); + + if ( empty( $this->options[$args['key']] ) ) + $this->options[$args['key']] = 0; + + echo ' + + '; + } + + /** + * Output new Avatar fields to user editing / profile screen + * + * @param object $profileuser User object + */ + public function edit_user_profile( $profileuser ) { + ?> +

+ + + + + + + + + + + +
+ ID ); + remove_filter( 'pre_option_avatar_rating', '__return_null' ); + ?> + + options['caps'] ); + + if ( $upload_rights ) { + do_action( 'simple_local_avatar_notices' ); + wp_nonce_field( 'simple_local_avatar_nonce', '_simple_local_avatar_nonce', false ); + $remove_url = add_query_arg(array( + 'action' => 'remove-simple-local-avatar', + 'user_id' => $profileuser->ID, + '_wpnonce' => $this->remove_nonce, + ) ); + ?> + +

+
+ + +

+ +

+   + simple_local_avatar ) ) echo ' style="display:none;"'; ?>> +

+ simple_local_avatar ) ) + echo '' . __('No local avatar is set. Set up your avatar at Gravatar.com.','simple-local-avatars') . ''; + else + echo '' . __('You do not have media management permissions. To change your local avatar, contact the blog administrator.','simple-local-avatars') . ''; + } + ?> +
+
simple_local_avatar ) ); ?>> + + simple_local_avatar_rating ) || ! array_key_exists( $profileuser->simple_local_avatar_rating, $this->avatar_ratings ) ) + $profileuser->simple_local_avatar_rating = 'G'; + + foreach ( $this->avatar_ratings as $key => $rating ) : + echo "\n\t
"; + endforeach; + ?> +

+
+ avatar_delete( $user_id ); // delete old images if successful + + $meta_value = array(); + + // set the new avatar + if ( is_int( $url_or_media_id ) ) { + $meta_value['media_id'] = $url_or_media_id; + $url_or_media_id = wp_get_attachment_url( $url_or_media_id ); + } + + $meta_value['full'] = $url_or_media_id; + + update_user_meta( $user_id, 'simple_local_avatar', $meta_value ); // save user information (overwriting old) + } + + /** + * Save any changes to the user profile + * + * @param int $user_id ID of user being updated + */ + public function edit_user_profile_update( $user_id ) { + // check nonces + if( empty( $_POST['_simple_local_avatar_nonce'] ) || ! wp_verify_nonce( $_POST['_simple_local_avatar_nonce'], 'simple_local_avatar_nonce' ) ) + return; + + // check for uploaded files + if ( ! empty( $_FILES['simple-local-avatar']['name'] ) ) : + + // need to be more secure since low privelege users can upload + if ( false !== strpos( $_FILES['simple-local-avatar']['name'], '.php' ) ) { + $this->avatar_upload_error = __('For security reasons, the extension ".php" cannot be in your file name.','simple-local-avatars'); + add_action( 'user_profile_update_errors', array( $this, 'user_profile_update_errors' ) ); + return; + } + + // front end (theme my profile etc) support + if ( ! function_exists( 'media_handle_upload' ) ) + require_once( ABSPATH . 'wp-admin/includes/media.php' ); + + // allow developers to override file size upload limit for avatars + add_filter( 'upload_size_limit', array( $this, 'upload_size_limit' ) ); + + $this->user_id_being_edited = $user_id; // make user_id known to unique_filename_callback function + $avatar_id = media_handle_upload( 'simple-local-avatar', 0, array(), array( + 'mimes' => array( + 'jpg|jpeg|jpe' => 'image/jpeg', + 'gif' => 'image/gif', + 'png' => 'image/png', + ), + 'test_form' => false, + 'unique_filename_callback' => array( $this, 'unique_filename_callback' ) + ) ); + + remove_filter( 'upload_size_limit', array( $this, 'upload_size_limit' ) ); + + if ( is_wp_error( $avatar_id ) ) { // handle failures. + $this->avatar_upload_error = '' . __( 'There was an error uploading the avatar:', 'simple-local-avatars' ) . ' ' . esc_html( $avatar_id->get_error_message() ); + add_action( 'user_profile_update_errors', array( $this, 'user_profile_update_errors' ) ); + return; + } + + $this->assign_new_user_avatar( $avatar_id, $user_id ); + + endif; + + // handle rating + if ( isset( $avatar_id ) || $avatar = get_user_meta( $user_id, 'simple_local_avatar', true ) ) { + if ( empty( $_POST['simple_local_avatar_rating'] ) || ! array_key_exists( $_POST['simple_local_avatar_rating'], $this->avatar_ratings ) ) + $_POST['simple_local_avatar_rating'] = key( $this->avatar_ratings ); + + update_user_meta( $user_id, 'simple_local_avatar_rating', $_POST['simple_local_avatar_rating'] ); + } + } + + /** + * Allow developers to override the maximum allowable file size for avatar uploads + * + * @param int $bytes WordPress default byte size check + * @return int Maximum byte size + */ + public function upload_size_limit( $bytes ) { + return apply_filters( 'simple_local_avatars_upload_limit', $bytes ); + } + + /** + * Runs when a user clicks the Remove button for the avatar + */ + public function action_remove_simple_local_avatar() { + if ( ! empty( $_GET['user_id'] ) && ! empty( $_GET['_wpnonce'] ) && wp_verify_nonce( $_GET['_wpnonce'], 'remove_simple_local_avatar_nonce' ) ) { + $user_id = (int) $_GET['user_id']; + + if ( ! current_user_can('edit_user', $user_id) ) + wp_die( __('You do not have permission to edit this user.') ); + + $this->avatar_delete( $user_id ); // delete old images if successful + + if ( defined( 'DOING_AJAX' ) && DOING_AJAX ) + echo get_simple_local_avatar( $user_id ); + } + + if ( defined( 'DOING_AJAX' ) && DOING_AJAX ) + die; + } + + /** + * AJAX callback for assigning media ID fetched from media library to user + */ + public function ajax_assign_simple_local_avatar_media() { + // check required information and permissions + if ( empty( $_POST['user_id'] ) || empty( $_POST['media_id'] ) || ! current_user_can( 'upload_files' ) || ! current_user_can( 'edit_user', $_POST['user_id'] ) || empty( $_POST['_wpnonce'] ) || ! wp_verify_nonce( $_POST['_wpnonce'], 'assign_simple_local_avatar_nonce' ) ) + die; + + $media_id = (int) $_POST['media_id']; + $user_id = (int) $_POST['user_id']; + + // ensure the media is real is an image + if ( wp_attachment_is_image( $media_id ) ) + $this->assign_new_user_avatar( $media_id, $user_id ); + + echo get_simple_local_avatar( $user_id ); + + die; + } + + /** + * remove the custom get_avatar hook for the default avatar list output on options-discussion.php + */ + public function avatar_defaults( $avatar_defaults ) { + remove_action( 'get_avatar', array( $this, 'get_avatar' ) ); + return $avatar_defaults; + } + + /** + * Delete avatars based on a user_id + * + * @param int $user_id + */ + public function avatar_delete( $user_id ) { + $old_avatars = (array) get_user_meta( $user_id, 'simple_local_avatar', true ); + + if ( empty( $old_avatars ) ) + return; + + // if it was uploaded media, don't erase the full size or try to erase an the ID + if ( array_key_exists( 'media_id', $old_avatars ) ) + unset( $old_avatars['media_id'], $old_avatars['full'] ); + + if ( ! empty( $old_avatars ) ) { + $upload_path = wp_upload_dir(); + + foreach ($old_avatars as $old_avatar ) { + // derive the path for the file based on the upload directory + $old_avatar_path = str_replace( $upload_path['baseurl'], $upload_path['basedir'], $old_avatar ); + if ( file_exists( $old_avatar_path ) ) + unlink( $old_avatar_path ); + } + } + + delete_user_meta( $user_id, 'simple_local_avatar' ); + delete_user_meta( $user_id, 'simple_local_avatar_rating' ); + } + + /** + * Creates a unique, meaningful file name for uploaded avatars. + * + * @param string $dir Path for file + * @param string $name Filename + * @param string $ext File extension (e.g. ".jpg") + * @return string Final filename + */ + public function unique_filename_callback( $dir, $name, $ext ) { + $user = get_user_by( 'id', (int) $this->user_id_being_edited ); + $name = $base_name = sanitize_file_name( $user->display_name . '_avatar_' . time() ); + + // ensure no conflicts with existing file names + $number = 1; + while ( file_exists( $dir . "/$name$ext" ) ) { + $name = $base_name . '_' . $number; + $number++; + } + + return $name . $ext; + } + + /** + * Adds errors based on avatar upload problems. + * + * @param WP_Error $errors Error messages for user profile screen. + */ + public function user_profile_update_errors( WP_Error $errors ) { + $errors->add( 'avatar_error', $this->avatar_upload_error ); + } + + /** + * Registers the simple_local_avatar field in the REST API. + */ + public function register_rest_fields() { + register_rest_field( 'user', 'simple_local_avatar', array( + 'get_callback' => array( $this, 'get_avatar_rest' ), + 'update_callback' => array( $this, 'set_avatar_rest' ), + 'schema' => array( + 'description' => 'The users simple local avatar', + 'type' => 'object', + ) + )); + } + + /** + * Returns the simple_local_avatar meta key for the given user. + * + * @param object $user User object + */ + public function get_avatar_rest( $user ) { + $local_avatar = get_user_meta( $user['id'], 'simple_local_avatar', true ); + if ( empty( $local_avatar ) ) { + return; + } + return $local_avatar; + } + + /** + * Updates the simple local avatar from a REST request. + * + * Since we are just adding a field to the existing user endpoint + * we don't need to worry about ensuring the calling user has proper permissions. + * Only the user or an administrator would be able to change the avatar. + * + * @param array $input Input submitted via REST request. + * @param object $user The user making the request. + */ + public function set_avatar_rest( $input, $user ) { + $this->assign_new_user_avatar($input['media_id'], $user->ID); + } + +} + +global $simple_local_avatars; +$simple_local_avatars = new Simple_Local_Avatars(); + +/** + * more efficient to call simple local avatar directly in theme and avoid + * gravatar setup. Since 2.2, This function is only a proxy for get_avatar + * due to internal changes. + * + * @param int|string|object $id_or_email A user ID, email address, or comment object + * @param int $size Size of the avatar image + * @param string $default URL to a default image to use if no avatar is available + * @param string $alt Alternate text to use in image tag. Defaults to blank + * @param array $args Support new args parameter. + * + * @return string tag for the user's avatar + */ +function get_simple_local_avatar( $id_or_email, $size = 96, $default = '', $alt = '', $args = array() ) { + return apply_filters( 'simple_local_avatar', get_avatar( $id_or_email, $size, $default, $alt, $args ) ); +} + +/** + * on uninstallation, remove the custom field from the users and delete the local avatars + */ + +register_uninstall_hook( __FILE__, 'simple_local_avatars_uninstall' ); + +function simple_local_avatars_uninstall() { + $simple_local_avatars = new Simple_Local_Avatars; + $users = get_users(array( + 'meta_key' => 'simple_local_avatar', + 'fields' => 'ids', + )); + + foreach ( $users as $user_id ): + $simple_local_avatars->avatar_delete( $user_id ); + endforeach; + + delete_option('simple_local_avatars'); +} + From 0c8394505fd09317ab7c92a3867492a1b2a54d8c Mon Sep 17 00:00:00 2001 From: Jeffrey Paul Date: Mon, 10 Feb 2020 12:48:52 -0600 Subject: [PATCH 19/73] bump WP tested-up-to 5.3 in README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 7e4d728d..ec6c5de2 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ > Adds an avatar upload field to user profiles. Generates requested sizes on demand just like Gravatar! -[![Support Level](https://img.shields.io/badge/support-active-green.svg)](#support-level) [![Release Version](https://img.shields.io/github/release/10up/simple-local-avatars.svg)](https://github.com/10up/simple-local-avatars/releases/latest) ![WordPress tested up to version](https://img.shields.io/badge/WordPress-v5.2%20tested-success.svg) [![GPLv2 License](https://img.shields.io/github/license/10up/simple-local-avatars.svg)](https://github.com/10up/simple-local-avatars/blob/develop/LICENSE.md) +[![Support Level](https://img.shields.io/badge/support-active-green.svg)](#support-level) [![Release Version](https://img.shields.io/github/release/10up/simple-local-avatars.svg)](https://github.com/10up/simple-local-avatars/releases/latest) ![WordPress tested up to version](https://img.shields.io/badge/WordPress-v5.3%20tested-success.svg) [![GPLv2 License](https://img.shields.io/github/license/10up/simple-local-avatars.svg)](https://github.com/10up/simple-local-avatars/blob/develop/LICENSE.md) ## Features From 00a83620a6d1f1bc43d54a6d921ba4d3fa90d037 Mon Sep 17 00:00:00 2001 From: Tung Du Date: Thu, 13 Feb 2020 09:24:34 +0700 Subject: [PATCH 20/73] fix: Undefined index: only --- simple-local-avatars.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/simple-local-avatars.php b/simple-local-avatars.php index 793b6c51..9ee87984 100644 --- a/simple-local-avatars.php +++ b/simple-local-avatars.php @@ -67,7 +67,7 @@ public function get_avatar_data( $args, $id_or_email ) { } // Local only mode - if( ! $simple_local_avatar_url && $this->options['only'] ) { + if( ! $simple_local_avatar_url && ! empty( $this->options['only'] ) ) { $args['url'] = $this->get_default_avatar_url( $args['size'] ); } From 554b7515277dc8715d58483dcb569911bc4c7664 Mon Sep 17 00:00:00 2001 From: Tim Moore Date: Sat, 4 Apr 2020 13:52:30 -0400 Subject: [PATCH 21/73] Bumping tested up to version. --- readme.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/readme.txt b/readme.txt index 93d9c044..16cdc026 100644 --- a/readme.txt +++ b/readme.txt @@ -3,7 +3,7 @@ Contributors: jakemgold, 10up, thinkoomph Donate link: https://10up.com/plugins/simple-local-avatars-wordpress/ Tags: avatar, gravatar, user photos, users, profile Requires at least: 4.6 -Tested up to: 5.3 +Tested up to: 5.4 Stable tag: 2.1.1 Text Domain: simple-local-avatars @@ -72,7 +72,7 @@ You can also use `get_simple_local_avatar()` (with the same arguments) to retrei * Optimization for WordPress 3.2 / 3.3 (substitutes deprecated function) = 1.3 = -* Avatar file name saved as "user-display-name_avatar" (or other image extension) +* Avatar file name saved as "user-display-name_avatar" (or other image extension) * Russian localization added * Assorted minor code optimizations From 3b96f76ab56422afe8c4b398305d3dfb4f7eb29f Mon Sep 17 00:00:00 2001 From: Jeffrey Paul Date: Mon, 6 Apr 2020 23:14:13 -0500 Subject: [PATCH 22/73] update tested-up-to badge in README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index ec6c5de2..e258c8b1 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ > Adds an avatar upload field to user profiles. Generates requested sizes on demand just like Gravatar! -[![Support Level](https://img.shields.io/badge/support-active-green.svg)](#support-level) [![Release Version](https://img.shields.io/github/release/10up/simple-local-avatars.svg)](https://github.com/10up/simple-local-avatars/releases/latest) ![WordPress tested up to version](https://img.shields.io/badge/WordPress-v5.3%20tested-success.svg) [![GPLv2 License](https://img.shields.io/github/license/10up/simple-local-avatars.svg)](https://github.com/10up/simple-local-avatars/blob/develop/LICENSE.md) +[![Support Level](https://img.shields.io/badge/support-active-green.svg)](#support-level) [![Release Version](https://img.shields.io/github/release/10up/simple-local-avatars.svg)](https://github.com/10up/simple-local-avatars/releases/latest) ![WordPress tested up to version](https://img.shields.io/badge/WordPress-v5.4%20tested-success.svg) [![GPLv2 License](https://img.shields.io/github/license/10up/simple-local-avatars.svg)](https://github.com/10up/simple-local-avatars/blob/develop/LICENSE.md) ## Features From 07491b91b8c713ee16b37e10d8af52664c1fdd3c Mon Sep 17 00:00:00 2001 From: Jeffrey Paul Date: Wed, 6 May 2020 20:54:40 -0500 Subject: [PATCH 23/73] add version links to CHANGELOG.md --- CHANGELOG.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 62911a22..77af279e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -89,3 +89,20 @@ All notable changes to this project will be documented in this file, per [the Ke ## [1.0] - 2011-01-18 - Initial release + +[Unreleased]: https://github.com/10up/simple-local-avatars/compare/master...develop +[2.1.1]: https://github.com/10up/simple-local-avatars/compare/2.1...2.1.1 +[2.1]: https://github.com/10up/simple-local-avatars/compare/2.0...2.1 +[2.0]: https://github.com/10up/simple-local-avatars/compare/1.3.1...2.0 +[1.3.1]: https://github.com/10up/simple-local-avatars/compare/1.3...1.3.1 +[1.3]: https://github.com/10up/simple-local-avatars/compare/1.2.4...1.3 +[1.2.4]: https://github.com/10up/simple-local-avatars/compare/1.2.3...1.2.4 +[1.2.3]: https://github.com/10up/simple-local-avatars/compare/1.2.2...1.2.3 +[1.2.2]: https://github.com/10up/simple-local-avatars/compare/1.2.1...1.2.2 +[1.2.1]: https://github.com/10up/simple-local-avatars/compare/1.2...1.2.1 +[1.2]: https://github.com/10up/simple-local-avatars/compare/1.1.3...1.2 +[1.1.3]: https://github.com/10up/simple-local-avatars/compare/1.1.2...1.1.3 +[1.1.2]: https://github.com/10up/simple-local-avatars/compare/1.1.1...1.1.2 +[1.1.1]: https://github.com/10up/simple-local-avatars/compare/1.1...1.1.1 +[1.1]: https://github.com/10up/simple-local-avatars/compare/1.0...1.1 +[1.0]: https://github.com/10up/simple-local-avatars/releases/tag/1.0 From 35e6f47f3852ee0114c100b599c2a6740c598095 Mon Sep 17 00:00:00 2001 From: Oscar Sanchez Date: Fri, 29 May 2020 21:44:08 -0500 Subject: [PATCH 24/73] Adds ability to retrieve avatar with WP_Post object --- simple-local-avatars.php | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/simple-local-avatars.php b/simple-local-avatars.php index a1724912..565b58c5 100644 --- a/simple-local-avatars.php +++ b/simple-local-avatars.php @@ -53,10 +53,10 @@ public function __construct() { } /** - * Retrieve the local avatar for a user who provided a user ID or email address. + * Retrieve the local avatar for a user who provided a user ID, email address or post/comment object. * * @param string $avatar Avatar return by original function - * @param int|string|object $id_or_email A user ID, email address, or comment object + * @param int|string|object $id_or_email A user ID, email address, or post/comment object * @param int $size Size of the avatar image * @param string $default URL to a default image to use if no avatar is available * @param string $alt Alternative text to use in image tag. Defaults to blank @@ -69,7 +69,9 @@ public function get_avatar( $avatar = '', $id_or_email = '', $size = 96, $defaul $user_id = $user->ID; elseif ( is_object( $id_or_email ) && ! empty( $id_or_email->user_id ) ) $user_id = (int) $id_or_email->user_id; - + elseif ( $id_or_email instanceof WP_Post && ! empty( $id_or_email->post_author ) ) + $user_id = (int) $id_or_email->post_author; + if ( empty( $user_id ) ) return $avatar; @@ -592,9 +594,9 @@ function get_simple_local_avatar( $id_or_email, $size = 96, $default = '', $alt if ( ! function_exists( 'get_avatar' ) && ( $simple_local_avatars_options = get_option('simple_local_avatars') ) && ! empty( $simple_local_avatars_options['only'] ) ) : /** - * Retrieve the avatar for a user who provided a user ID or email address. + * Retrieve the avatar for a user who provided a user ID, post/comment object or email address. * - * @param int|string|object $id_or_email A user ID, email address, or comment object + * @param int|string|object $id_or_email A user ID, email address, or post/comment object * @param int $size Size of the avatar image * @param string $default URL to a default image to use if no avatar is available * @param string $alt Alternative text to use in image tag. Defaults to blank From b6f8b82b1cdddd9cc97aae27ab19c257dc89d067 Mon Sep 17 00:00:00 2001 From: Tung Du Date: Wed, 3 Jun 2020 15:52:44 +0700 Subject: [PATCH 25/73] fix: support passing WP_Post to get_avatar --- simple-local-avatars.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/simple-local-avatars.php b/simple-local-avatars.php index 9ee87984..59d200e4 100644 --- a/simple-local-avatars.php +++ b/simple-local-avatars.php @@ -90,6 +90,8 @@ public function get_simple_local_avatar_url( $id_or_email, $size ) { $user_id = $user->ID; elseif ( is_object( $id_or_email ) && ! empty( $id_or_email->user_id ) ) $user_id = (int) $id_or_email->user_id; + elseif ( $id_or_email instanceof WP_Post && ! empty( $id_or_email->post_author ) ) + $user_id = (int) $id_or_email->post_author; if ( empty( $user_id ) ) return ''; From cb789246799d84adeac3fbadf4300305c78b6e43 Mon Sep 17 00:00:00 2001 From: Myles McNamara Date: Wed, 3 Jun 2020 10:32:31 -0400 Subject: [PATCH 26/73] Change assign_new_user_avatar to public This allows 3rd party plugins or other user code to call method to assign new user avatar --- simple-local-avatars.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/simple-local-avatars.php b/simple-local-avatars.php index a1724912..296eb29b 100644 --- a/simple-local-avatars.php +++ b/simple-local-avatars.php @@ -326,7 +326,7 @@ public function user_edit_form_tag() { * @param int|string $url_or_media_id Local URL for avatar or ID of attachment * @param int $user_id ID of user to assign image to */ - private function assign_new_user_avatar( $url_or_media_id, $user_id ) { + public function assign_new_user_avatar( $url_or_media_id, $user_id ) { // delete the old avatar $this->avatar_delete( $user_id ); // delete old images if successful From 67903fa798e23e3ee7518a8b3ca38a089caa68b3 Mon Sep 17 00:00:00 2001 From: Jeffrey Paul Date: Thu, 11 Jun 2020 16:15:47 -0500 Subject: [PATCH 27/73] remove issue and PR templates --- .github/.DS_Store | Bin 0 -> 6148 bytes .github/ISSUE_TEMPLATE/1-bug-report.md | 37 -------------- .github/ISSUE_TEMPLATE/2-feature-request.md | 25 ---------- .github/ISSUE_TEMPLATE/3-help.md | 13 ----- .github/PULL_REQUEST_TEMPLATE.md | 52 -------------------- 5 files changed, 127 deletions(-) create mode 100644 .github/.DS_Store delete mode 100644 .github/ISSUE_TEMPLATE/1-bug-report.md delete mode 100644 .github/ISSUE_TEMPLATE/2-feature-request.md delete mode 100644 .github/ISSUE_TEMPLATE/3-help.md delete mode 100644 .github/PULL_REQUEST_TEMPLATE.md diff --git a/.github/.DS_Store b/.github/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..5008ddfcf53c02e82d7eee2e57c38e5672ef89f6 GIT binary patch literal 6148 zcmeH~Jr2S!425mzP>H1@V-^m;4Wg<&0T*E43hX&L&p$$qDprKhvt+--jT7}7np#A3 zem<@ulZcFPQ@L2!n>{z**++&mCkOWA81W14cNZlEfg7;MkzE(HCqgga^y>{tEnwC%0;vJ&^%eQ zLs35+`xjp>T0 - -**Describe the bug** - - -**Steps to Reproduce** - -1. Go to '...' -2. Click on '....' -3. Scroll down to '....' -4. See error - -**Expected behavior** - - -**Screenshots** - - -**Environment information** - - Device: - - OS: - - Browser and version: - - Restricted Site Access version: - - Theme and version: - - Other installed plugin(s) and version(s): - -**Additional context** - diff --git a/.github/ISSUE_TEMPLATE/2-feature-request.md b/.github/ISSUE_TEMPLATE/2-feature-request.md deleted file mode 100644 index 828741f3..00000000 --- a/.github/ISSUE_TEMPLATE/2-feature-request.md +++ /dev/null @@ -1,25 +0,0 @@ ---- -name: "\U0001F680 Feature request" -about: Suggest an idea for this project -title: '' -labels: enhancement -assignees: '' - ---- - - - -**Is your enhancement related to a problem? Please describe.** - - -**Describe the solution you'd like** - - -**Designs** - - -**Describe alternatives you've considered** - - -**Additional context** - diff --git a/.github/ISSUE_TEMPLATE/3-help.md b/.github/ISSUE_TEMPLATE/3-help.md deleted file mode 100644 index a91a682d..00000000 --- a/.github/ISSUE_TEMPLATE/3-help.md +++ /dev/null @@ -1,13 +0,0 @@ ---- -name: "❓Need help with Simple Local Avatars?" -about: Ask us a question, we're here to help! -title: '' -labels: question -assignees: '' - ---- - - - -**Describe your question** - diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md deleted file mode 100644 index fed84274..00000000 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ /dev/null @@ -1,52 +0,0 @@ - - -### Description of the Change - - - -### Alternate Designs - - - -### Benefits - - - -### Possible Drawbacks - - - -### Verification Process - - - -### Checklist: - - - -- [ ] I have read the [**CONTRIBUTING**](https://github.com/10up/simple-local-avatars/blob/develop/CONTRIBUTING.md) document. -- [ ] My code follows the code style of this project. -- [ ] My change requires a change to the documentation. -- [ ] I have updated the documentation accordingly. -- [ ] I have added tests to cover my change. -- [ ] All new and existing tests passed. - - - -### Applicable Issues - - From 4b0261565b2679758a20a4b10172fc696fba10b3 Mon Sep 17 00:00:00 2001 From: Tung Du Date: Sat, 13 Jun 2020 08:49:47 +0700 Subject: [PATCH 28/73] fix: remove ds store and add gitignore file --- .github/.DS_Store | Bin 6148 -> 0 bytes .gitignore | 6 ++++++ 2 files changed, 6 insertions(+) delete mode 100644 .github/.DS_Store create mode 100644 .gitignore diff --git a/.github/.DS_Store b/.github/.DS_Store deleted file mode 100644 index 5008ddfcf53c02e82d7eee2e57c38e5672ef89f6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6148 zcmeH~Jr2S!425mzP>H1@V-^m;4Wg<&0T*E43hX&L&p$$qDprKhvt+--jT7}7np#A3 zem<@ulZcFPQ@L2!n>{z**++&mCkOWA81W14cNZlEfg7;MkzE(HCqgga^y>{tEnwC%0;vJ&^%eQ zLs35+`xjp>T0 Date: Mon, 29 Jun 2020 22:29:34 -0500 Subject: [PATCH 29/73] update release instructions in CONTRIBUTING.md --- CONTRIBUTING.md | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 0964f46b..db2dc030 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -28,11 +28,17 @@ The `develop` branch is the development branch which means it contains the next ## Release instructions -1. Version bump: Bump the version number in `simple-local-avatars.php`. -2. Changelog: Add/update the changelog in both `readme.txt` and `README.md` -3. Readme updates: Make any other readme changes as necessary. `README.md` is geared toward GitHub and `readme.txt` contains WordPress.org-specific content. The two are slightly different. -4. Merge: Make a non-fast-forward merge from `develop` to `master`. -5. SVN update: Copy files over to the `trunk` folder of an SVN checkout of the plugin. If the plugin banner, icon, or screenshots have changed, copy those to the top-level `assets` folder. Commit those changes. -6. SVN tag: Make a folder inside `tags` with the current version number, copy the contents of `trunk` into it, and commit with the message `Tagging X.Y.Z`. There is also an SVN command for tagging; however, note that it runs on the remote and requires care because the entire WordPress.org plugins repo is actually single SVN repo. -7. Check WordPress.org: Ensure that the changes are live on https://wordpress.org/plugins/simple-local-avatars/. This may take a few minutes. -8. Git tag: Tag the release in Git and push the tag to GitHub. It should now appear under [releases](https://github.com/10up/simple-local-avatars/releases) there as well. +1. Branch: Starting from `develop`, cut a release branch named `release/X.Y.Z` for your changes. +1. Version bump: Bump the version number in `simple-local-avatars.php` and `readme.txt` if it does not already reflect the version being released. +1. Changelog: Add/update the changelog in both `CHANGELOG.md` and `readme.txt`. +1. Props: update `CREDITS.md` with any new contributors, confirm maintainers are accurate. +1. New files: Check to be sure any new files/paths that are unnecessary in the production version are included in `.gitattributes`. +1. Readme updates: Make any other readme changes as necessary. `README.md` is geared toward GitHub and `readme.txt` contains WordPress.org-specific content. The two are slightly different. +1. Merge: Make a non-fast-forward merge from your release branch to `develop` (or merge the pull request), then do the same for `develop` into `trunk` (`git checkout trunk && git merge --no-ff develop`). `trunk` contains the latest stable release. +1. Test: Run through common tasks while on `trunk` to be sure it functions correctly. +1. Push: Push your `trunk` branch to GitHub (e.g. `git push origin trunk`). +1. Release: Create a [new release](https://github.com/10up/simple-local-avatars/releases/new), naming the tag and the release with the new version number, and targeting the `trunk` branch. Paste the changelog from `CHANGELOG.md` into the body of the release and include a link to the [closed issues on the milestone](https://github.com/10up/simple-local-avatars/milestone/#?closed=1). +1. SVN: Wait for the [GitHub Action](https://github.com/10up/simple-local-avatars/actions) to finish deploying to the WordPress.org repository. If all goes well, users with SVN commit access for that plugin will receive an emailed diff of changes. +1. Check WordPress.org: Ensure that the changes are live on https://wordpress.org/plugins/simple-local-avatars/. This may take a few minutes. +1. Close the milestone: Edit the [milestone](https://github.com/10up/simple-local-avatars/milestone/#) with release date (in the `Due date (optional)` field) and link to GitHub release (in the `Description` field), then close the milestone. +1. Punt incomplete items: If any open issues or PRs which were milestoned for `X.Y.Z` do not make it into the release, update their milestone to `X.Y.Z+1`, `X.Y+1.0`, `X+1.0.0` or `Future Release`. From 5afe0cca89c9b1d6f6d8904a96e007ef90fee8c8 Mon Sep 17 00:00:00 2001 From: Jeffrey Paul Date: Mon, 29 Jun 2020 22:32:12 -0500 Subject: [PATCH 30/73] remove version from composer.json per spec recommendation --- composer.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/composer.json b/composer.json index 3b863ad4..9d863796 100644 --- a/composer.json +++ b/composer.json @@ -1,7 +1,6 @@ { "name": "10up/simple-local-avatars", "description": "Adds an avatar upload field to user profiles. Generates requested sizes on demand just like Gravatar!", - "version": "2.1.1", "type": "wordpress-plugin", "keywords": [ "wordpress", @@ -22,4 +21,4 @@ "require": { "php": ">=5.3" } -} \ No newline at end of file +} From 59234336494029100e93e612d63484def1291814 Mon Sep 17 00:00:00 2001 From: Jeffrey Paul Date: Mon, 29 Jun 2020 22:33:16 -0500 Subject: [PATCH 31/73] update license in composer.json to match simple-local-avatars.php --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 9d863796..15f9e588 100644 --- a/composer.json +++ b/composer.json @@ -7,7 +7,7 @@ "10up" ], "homepage": "https://github.com/10up/simple-local-avatars", - "license": ["GPL-2.0-only"], + "license": ["GPL-2.0-or-later"], "authors": [ { "name": "10up", From 1f3dc4eba65645df709f0e3b9ea29311669289b7 Mon Sep 17 00:00:00 2001 From: Jeffrey Paul Date: Mon, 29 Jun 2020 22:49:20 -0500 Subject: [PATCH 32/73] Create CREDITS.md --- CREDITS.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 CREDITS.md diff --git a/CREDITS.md b/CREDITS.md new file mode 100644 index 00000000..0869c2a8 --- /dev/null +++ b/CREDITS.md @@ -0,0 +1,19 @@ +The following acknowledges the Maintainers for this repository, those who have Contributed to this repository (via bug reports, code, design, ideas, project management, translation, testing, etc.), and any Libraries utilized. + +## Maintainers + +The following individuals are responsible for curating the list of issues, responding to pull requests, and ensuring regular releases happen. + +[Helen Hou-Sandi (@helen)](https://github.com/helen), [Jeffrey Paul](https://github.com/jeffpaul) + +## Contributors + +Thank you to all the people who have already contributed to this repository via bug reports, code, design, ideas, project management, translation, testing, etc. + +[Jake Goldman (@jakemgold)](https://github.com/jakemgold), [Steve Grunwell (@stevegrunwell)](https://github.com/stevegrunwell), [Ravi Chandra (@ravichdev)](https://github.com/ravichdev), [Eduard Florea (@eflorea)](https://github.com/eflorea), [Helen Hou-Sandi (@helen)](https://github.com/helen), [@kniebremser](https://github.com/kniebremser), [Robbie Trencheny (@robbiet480)](https://github.com/robbiet480), [Jeffrey Paul (@jeffpaul)](https://github.com/jeffpaul), [Adam Silverstein (@adamsilverstein)](https://github.com/adamsilverstein), [Paul de Wouters (@pdewouters)](https://github.com/pdewouters), [Ledwing Hernandez (@Waka867)](https://github.com/Waka867), [Tim Moore (@tmoorewp)](https://github.com/tmoorewp), [Oscar Sanchez S. (@oscarssanchez)](https://github.com/oscarssanchez), [Tung Du (@dinhtungdu)](https://github.com/dinhtungdu). + +## Libraries + +The following software libraries are utilized in this repository. + +n/a. From 51b453fd04563e4e887f290e1becdd7414f15d56 Mon Sep 17 00:00:00 2001 From: Jeffrey Paul Date: Mon, 29 Jun 2020 23:05:10 -0500 Subject: [PATCH 33/73] add 2.2.0 items to CHANGELOG.md --- CHANGELOG.md | 72 ++++++++++++++++++++++++++++++++++------------------ 1 file changed, 47 insertions(+), 25 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 77af279e..0301a8f9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,44 +2,63 @@ All notable changes to this project will be documented in this file, per [the Keep a Changelog standard](http://keepachangelog.com/). +## [Unreleased] - TBD + +## [2.2.0] - TBD +### Added +- Ability to retrieve avatar with `WP_Post` object (props [@oscarssanchez](https://github.com/oscarssanchez), [@blobaugh](https://github.com/blobaugh) via [#47](https://github.com/10up/simple-local-avatars/pull/47)) + +### Changed +- Bumped WordPress version support to 5.4 (props [@Waka867](https://github.com/Waka867), [@tmoorewp](https://github.com/tmoorewp), [@jeffpaul](https://github.com/jeffpaul) via [#36](https://github.com/10up/simple-local-avatars/pull/36), [#43](https://github.com/10up/simple-local-avatars/pull/43)) +- GitHub Actions from HCL to YAML workflow syntax (props [@jeffpaul](https://github.com/jeffpaul) via [#37](https://github.com/10up/simple-local-avatars/pull/37)) +- Documentation updates (props [@jeffpaul](https://github.com/jeffpaul) via [#29](https://github.com/10up/simple-local-avatars/pull/29), [#30](https://github.com/10up/simple-local-avatars/pull/30), [#33](https://github.com/10up/simple-local-avatars/pull/33), [#45](https://github.com/10up/simple-local-avatars/pull/45), [#50](https://github.com/10up/simple-local-avatars/pull/50)) + +### Fixed +- Initialize `Simple_Local_Avatars` on the `$simple_local_avatars` global, enabling bundling plugin with composer (props [@pdewouters](https://github.com/pdewouters), [@adamsilverstein](https://github.com/adamsilverstein) via [#34](https://github.com/10up/simple-local-avatars/pull/34)) + ## [2.1.1] - 2019-05-07 -* Fixed: Do not delete avatars just because they don't exist on the local filesystem. This was occasionally dumping avatars when WordPress uploads were stored elsewhere, e.g. a cloud service. +### Fixed +- Do not delete avatars just because they don't exist on the local filesystem. This was occasionally dumping avatars when WordPress uploads were stored elsewhere, e.g. a cloud service. ## [2.1] - 2018-10-24 ### Added -* All avatar uploads now go into the media library. Don't worry - users without the ability to upload files cannot otherwise see the contents of your media library. This allows local avatars to respect other functionality your site may have around uploaded images, such as external hosting. -* REST API support for getting and updating. -* Use .org language packs rather than bundling translations. +- All avatar uploads now go into the media library. Don't worry - users without the ability to upload files cannot otherwise see the contents of your media library. This allows local avatars to respect other functionality your site may have around uploaded images, such as external hosting. +- REST API support for getting and updating. +- Use .org language packs rather than bundling translations. + ### Fixed -* Avoid an `ArgumentCountError`. -* A couple of internationalization issues. +- Avoid an `ArgumentCountError`. +- A couple of internationalization issues. ## [2.0] - 2013-06-02 ### Added -* Choose or upload an avatar from the media library (for users with appropriate capabilities)! -* Local avatars are rated for appropriateness, just like Gravatar -* A new setting under Discussion enables administrators to turn off Gravatar (only use local avatars) -* Delete the local avatar with a single button click (like everywhere else in WordPress) -* Uploaded avatar file names are appended with the timestamp, addressing browser image caching issues -* New developer filter for preventing automatic rescaling: simple_local_avatars_dynamic_resize -* New developer filter for limiting upload size: simple_local_avatars_upload_limit -* Upgraded functions deprecated since WordPress 3.5 -* Hungarian translation added (needs further updating again with new version) +- Choose or upload an avatar from the media library (for users with appropriate capabilities)! +- Local avatars are rated for appropriateness, just like Gravatar +- A new setting under Discussion enables administrators to turn off Gravatar (only use local avatars) +- Delete the local avatar with a single button click (like everywhere else in WordPress) +- Uploaded avatar file names are appended with the timestamp, addressing browser image caching issues +- New developer filter for preventing automatic rescaling: simple_local_avatars_dynamic_resize +- New developer filter for limiting upload size: simple_local_avatars_upload_limit +- Upgraded functions deprecated since WordPress 3.5 +- Hungarian translation added (needs further updating again with new version) + ### Fixed -* Fixed translations not working on front end (although translations are now a bit out of date...) -* Assorted refactoring / improvements under the hood +- Fixed translations not working on front end (although translations are now a bit out of date...) +- Assorted refactoring / improvements under the hood ## [1.3.1] - 2011-12-29 ### Added -* Brazilian Portuguese and Belarusian translations +- Brazilian Portuguese and Belarusian translations + ### Fixed -* Bug fixes (most notably correct naming of image files based on user display name) -* Optimization for WordPress 3.2 / 3.3 (substitutes deprecated function) +- Bug fixes (most notably correct naming of image files based on user display name) +- Optimization for WordPress 3.2 / 3.3 (substitutes deprecated function) ## [1.3] - 2011-09-22 ### Added -- Avatar file name saved as "user-display-name_avatar" (or other image extension) +- Avatar file name saved as "user-display-name_avatar" (or other image extension) - Russian localization added + ### Fixed - Assorted minor code optimizations @@ -58,17 +77,19 @@ All notable changes to this project will be documented in this file, per [the Ke ## [1.2.1] - 2011-01-26 ### Added - French localization + ### Fixed - Simplify uninstall code ## [1.2] - 2011-01-26 -### Fixed -- Fix path issues on some IIS servers (resulting in missing avatar images) -- Fix rare uninstall issues related to deleted avatars ### Added - Spanish localization - Other minor under the hood optimizations +### Fixed +- Fix path issues on some IIS servers (resulting in missing avatar images) +- Fix rare uninstall issues related to deleted avatars + ## [1.1.3] - 2011-01-20 ### Fixed - Properly deletes old avatars upon changing avatar @@ -90,7 +111,8 @@ All notable changes to this project will be documented in this file, per [the Ke ## [1.0] - 2011-01-18 - Initial release -[Unreleased]: https://github.com/10up/simple-local-avatars/compare/master...develop +[Unreleased]: https://github.com/10up/simple-local-avatars/compare/trunk...develop +[2.2.0]: https://github.com/10up/simple-local-avatars/compare/2.1.1...2.2.0 [2.1.1]: https://github.com/10up/simple-local-avatars/compare/2.1...2.1.1 [2.1]: https://github.com/10up/simple-local-avatars/compare/2.0...2.1 [2.0]: https://github.com/10up/simple-local-avatars/compare/1.3.1...2.0 From 106dacc16c1f43b90220d81b92121742718aa48b Mon Sep 17 00:00:00 2001 From: Jeffrey Paul Date: Mon, 29 Jun 2020 23:05:41 -0500 Subject: [PATCH 34/73] Update CREDITS.md --- CREDITS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CREDITS.md b/CREDITS.md index 0869c2a8..05e9b342 100644 --- a/CREDITS.md +++ b/CREDITS.md @@ -10,7 +10,7 @@ The following individuals are responsible for curating the list of issues, respo Thank you to all the people who have already contributed to this repository via bug reports, code, design, ideas, project management, translation, testing, etc. -[Jake Goldman (@jakemgold)](https://github.com/jakemgold), [Steve Grunwell (@stevegrunwell)](https://github.com/stevegrunwell), [Ravi Chandra (@ravichdev)](https://github.com/ravichdev), [Eduard Florea (@eflorea)](https://github.com/eflorea), [Helen Hou-Sandi (@helen)](https://github.com/helen), [@kniebremser](https://github.com/kniebremser), [Robbie Trencheny (@robbiet480)](https://github.com/robbiet480), [Jeffrey Paul (@jeffpaul)](https://github.com/jeffpaul), [Adam Silverstein (@adamsilverstein)](https://github.com/adamsilverstein), [Paul de Wouters (@pdewouters)](https://github.com/pdewouters), [Ledwing Hernandez (@Waka867)](https://github.com/Waka867), [Tim Moore (@tmoorewp)](https://github.com/tmoorewp), [Oscar Sanchez S. (@oscarssanchez)](https://github.com/oscarssanchez), [Tung Du (@dinhtungdu)](https://github.com/dinhtungdu). +[Jake Goldman (@jakemgold)](https://github.com/jakemgold), [Steve Grunwell (@stevegrunwell)](https://github.com/stevegrunwell), [Ravi Chandra (@ravichdev)](https://github.com/ravichdev), [Eduard Florea (@eflorea)](https://github.com/eflorea), [Helen Hou-Sandi (@helen)](https://github.com/helen), [@kniebremser](https://github.com/kniebremser), [Robbie Trencheny (@robbiet480)](https://github.com/robbiet480), [Jeffrey Paul (@jeffpaul)](https://github.com/jeffpaul), [Adam Silverstein (@adamsilverstein)](https://github.com/adamsilverstein), [Paul de Wouters (@pdewouters)](https://github.com/pdewouters), [Ledwing Hernandez (@Waka867)](https://github.com/Waka867), [Tim Moore (@tmoorewp)](https://github.com/tmoorewp), [Oscar Sanchez S. (@oscarssanchez)](https://github.com/oscarssanchez), [Tung Du (@dinhtungdu)](https://github.com/dinhtungdu), [Ben Lobaugh (@blobaugh)](https://github.com/blobaugh). ## Libraries From 36054e89764eb33b762988b7f80e31b303540353 Mon Sep 17 00:00:00 2001 From: Jeffrey Paul Date: Mon, 29 Jun 2020 23:07:03 -0500 Subject: [PATCH 35/73] update banner in README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e258c8b1..274f2898 100644 --- a/README.md +++ b/README.md @@ -39,5 +39,5 @@ Please read [CODE_OF_CONDUCT.md](https://github.com/10up/simple-local-avatars/bl ## Like what you see?

- +

From fdd86b9e021d401e55904d08262281ed4c3c5341 Mon Sep 17 00:00:00 2001 From: Jeffrey Paul Date: Mon, 29 Jun 2020 23:13:27 -0500 Subject: [PATCH 36/73] add 2.2.0 items to changelog in readme.txt --- readme.txt | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/readme.txt b/readme.txt index 16cdc026..0f7090db 100644 --- a/readme.txt +++ b/readme.txt @@ -9,7 +9,6 @@ Text Domain: simple-local-avatars Adds an avatar upload field to user profiles. Generates requested sizes on demand just like Gravatar! - == Description == Adds an avatar upload field to user profiles if the current user has media permissions. Generates requested sizes on demand just like Gravatar! Simple and lightweight. @@ -23,7 +22,6 @@ Just edit a user profile, and scroll down to the new "Avatar" field. The plug-in 1. Let's you decide whether lower privilege users (subscribers, contributors) can upload their own avatar 1. Enables rating of local avatars, just like Gravatar - == Installation == 1. Install easily with the WordPress plugin control panel or manually download the plugin and upload the extracted folder to the `/wp-content/plugins/` directory @@ -35,14 +33,19 @@ Use avatars in your theme using WordPress' built in `get_avatar()` function: [ht You can also use `get_simple_local_avatar()` (with the same arguments) to retreive local avatars a bit faster, but this will make your theme dependent on this plug-in. - == Screenshots == 1. Avatar upload field on a user profile page - == Changelog == += 2.2.0 = +* **Added:** Ability to retrieve avatar with `WP_Post` object (props [@oscarssanchez](https://profiles.wordpress.org/oscarssanchez), [@blobaugh](https://profiles.wordpress.org/blobaugh)) +* **Changed:** Bumped WordPress version support to 5.4 (props [@Waka867](https://github.com/Waka867), [@tmoorewp](https://profiles.wordpress.org/tmoorewp), [@jeffpaul](https://profiles.wordpress.org/jeffpaul)) +* **Changed:** GitHub Actions from HCL to YAML workflow syntax (props [@jeffpaul](https://profiles.wordpress.org/jeffpaul)) +* **Changed:** Documentation updates (props [@jeffpaul](https://profiles.wordpress.org/jeffpaul)) +* **Fixed:** Initialize `Simple_Local_Avatars` on the `$simple_local_avatars` global, enabling bundling plugin with composer (props [@pauldewouters](https://profiles.wordpress.org/pauldewouters/), [@adamsilverstein](https://profiles.wordpress.org/adamsilverstein)) + = 2.1.1 = * Fixed: Do not delete avatars just because they don't exist on the local filesystem. This was occasionally dumping avatars when WordPress uploads were stored elsewhere, e.g. a cloud service. @@ -109,7 +112,6 @@ You can also use `get_simple_local_avatar()` (with the same arguments) to retrei * All users (regardless of capabilities) can upload avatars by default. To limit avatar uploading to users with upload files capabilities (Authors and above), check the applicable option under Settings > Discussion. This was the default behavior in 1.0. * Localization support; German included - == Upgrade Notice == = 2.1 = From 0575e46c7f32c791eec090997102dd31b94ebe7e Mon Sep 17 00:00:00 2001 From: Jeffrey Paul Date: Mon, 29 Jun 2020 23:14:38 -0500 Subject: [PATCH 37/73] Update .gitattributes --- .gitattributes | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/.gitattributes b/.gitattributes index a234753c..182a845e 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,10 +1,15 @@ -/.wordpress-org export-ignore +# Directories +/.git export-ignore /.github export-ignore +/.wordpress-org export-ignore +# Files /.gitattributes export-ignore +/.gitignore export-ignore /CHANGELOG.md export-ignore /CODE_OF_CONDUCT.md export-ignore /composer.json export-ignore /CONTRIBUTING.md export-ignore +/CREDITS.md export-ignore /LICENSE.md export-ignore -/README.md export-ignore \ No newline at end of file +/README.md export-ignore From 543ebe966ddfe781c8564d34f51ee64e515abac5 Mon Sep 17 00:00:00 2001 From: Jeffrey Paul Date: Mon, 29 Jun 2020 23:17:50 -0500 Subject: [PATCH 38/73] update plugin headers in simple-local-avatars.php --- simple-local-avatars.php | 67 +++++++++++++++++++++------------------- 1 file changed, 35 insertions(+), 32 deletions(-) diff --git a/simple-local-avatars.php b/simple-local-avatars.php index 565b58c5..c9ecbb6d 100644 --- a/simple-local-avatars.php +++ b/simple-local-avatars.php @@ -1,19 +1,22 @@ options['only'] ) ) add_filter( 'get_avatar', array( $this, 'get_avatar' ), 10, 5 ); - + add_action( 'admin_init', array( $this, 'admin_init' ) ); add_action( 'admin_enqueue_scripts', array( $this, 'admin_enqueue_scripts' ) ); add_action( 'show_user_profile', array( $this, 'edit_user_profile' ) ); add_action( 'edit_user_profile', array( $this, 'edit_user_profile' ) ); - + add_action( 'personal_options_update', array( $this, 'edit_user_profile_update' ) ); add_action( 'edit_user_profile_update', array( $this, 'edit_user_profile_update' ) ); add_action( 'admin_action_remove-simple-local-avatar', array( $this, 'action_remove_simple_local_avatar' ) ); add_action( 'wp_ajax_assign_simple_local_avatar_media', array( $this, 'ajax_assign_simple_local_avatar_media' ) ); add_action( 'wp_ajax_remove_simple_local_avatar', array( $this, 'action_remove_simple_local_avatar' ) ); add_action( 'user_edit_form_tag', array( $this, 'user_edit_form_tag' ) ); - + add_filter( 'avatar_defaults', array( $this, 'avatar_defaults' ) ); add_action( 'rest_api_init', array( $this, 'register_rest_fields' ) ); @@ -99,10 +102,10 @@ public function get_avatar( $avatar = '', $id_or_email = '', $size = 96, $defaul } $size = (int) $size; - + if ( empty( $alt ) ) $alt = get_the_author_meta( 'display_name', $user_id ); - + // generate a new size if ( ! array_key_exists( $size, $local_avatars ) ) { $local_avatars[$size] = $local_avatars['full']; // just in case of failure elsewhere @@ -136,13 +139,13 @@ public function get_avatar( $avatar = '', $id_or_email = '', $size = 96, $defaul if ( 'http' != substr( $local_avatars[$size], 0, 4 ) ) $local_avatars[$size] = home_url( $local_avatars[$size] ); - + $author_class = is_author( $user_id ) ? ' current-author' : '' ; $avatar = "" . esc_attr( $alt ) . ""; - + return apply_filters( 'simple_local_avatar', $avatar ); } - + public function admin_init() { // upgrade pre 2.0 option if ( $old_ops = get_option( 'simple_local_avatars_caps' ) ) { @@ -229,7 +232,7 @@ public function avatar_settings_field( $args ) { if ( empty( $this->options[$args['key']] ) ) $this->options[$args['key']] = 0; - + echo '