PNG  IHDRX cHRMz&u0`:pQ<bKGD pHYsodtIME MeqIDATxw]Wug^Qd˶ 6`!N:!@xI~)%7%@Bh&`lnjVF29gΨ4E$|>cɚ{gk= %,a KX%,a KX%,a KX%,a KX%,a KX%,a KX%, b` ǟzeאfp]<!SJmɤY޲ڿ,%c ~ع9VH.!Ͳz&QynֺTkRR.BLHi٪:l;@(!MԴ=žI,:o&N'Kù\vRmJ雵֫AWic H@" !: Cé||]k-Ha oݜ:y F())u]aG7*JV@J415p=sZH!=!DRʯvɱh~V\}v/GKY$n]"X"}t@ xS76^[bw4dsce)2dU0 CkMa-U5tvLƀ~mlMwfGE/-]7XAƟ`׮g ewxwC4\[~7@O-Q( a*XGƒ{ ՟}$_y3tĐƤatgvێi|K=uVyrŲlLӪuܿzwk$m87k( `múcE)"@rK( z4$D; 2kW=Xb$V[Ru819קR~qloѱDyįݎ*mxw]y5e4K@ЃI0A D@"BDk_)N\8͜9dz"fK0zɿvM /.:2O{ Nb=M=7>??Zuo32 DLD@D| &+֎C #B8ַ`bOb $D#ͮҪtx]%`ES`Ru[=¾!@Od37LJ0!OIR4m]GZRJu$‡c=%~s@6SKy?CeIh:[vR@Lh | (BhAMy=݃  G"'wzn޺~8ԽSh ~T*A:xR[ܹ?X[uKL_=fDȊ؂p0}7=D$Ekq!/t.*2ʼnDbŞ}DijYaȲ(""6HA;:LzxQ‘(SQQ}*PL*fc\s `/d'QXW, e`#kPGZuŞuO{{wm[&NBTiiI0bukcA9<4@SӊH*؎4U/'2U5.(9JuDfrޱtycU%j(:RUbArLֺN)udA':uGQN"-"Is.*+k@ `Ojs@yU/ H:l;@yyTn}_yw!VkRJ4P)~y#)r,D =ě"Q]ci'%HI4ZL0"MJy 8A{ aN<8D"1#IJi >XjX֔#@>-{vN!8tRݻ^)N_╗FJEk]CT՟ YP:_|H1@ CBk]yKYp|og?*dGvzنzӴzjֺNkC~AbZƷ`.H)=!QͷVTT(| u78y֮}|[8-Vjp%2JPk[}ԉaH8Wpqhwr:vWª<}l77_~{s۴V+RCģ%WRZ\AqHifɤL36: #F:p]Bq/z{0CU6ݳEv_^k7'>sq*+kH%a`0ԣisqにtү04gVgW΂iJiS'3w.w}l6MC2uԯ|>JF5`fV5m`Y**Db1FKNttu]4ccsQNnex/87+}xaUW9y>ͯ骵G{䩓Գ3+vU}~jJ.NFRD7<aJDB1#ҳgSb,+CS?/ VG J?|?,2#M9}B)MiE+G`-wo߫V`fio(}S^4e~V4bHOYb"b#E)dda:'?}׮4繏`{7Z"uny-?ǹ;0MKx{:_pÚmFמ:F " .LFQLG)Q8qN q¯¯3wOvxDb\. BKD9_NN &L:4D{mm o^tֽ:q!ƥ}K+<"m78N< ywsard5+вz~mnG)=}lYݧNj'QJS{S :UYS-952?&O-:W}(!6Mk4+>A>j+i|<<|;ر^߉=HE|V#F)Emm#}/"y GII웻Jі94+v뾧xu~5C95~ūH>c@덉pʃ1/4-A2G%7>m;–Y,cyyaln" ?ƻ!ʪ<{~h~i y.zZB̃/,雋SiC/JFMmBH&&FAbϓO^tubbb_hZ{_QZ-sύodFgO(6]TJA˯#`۶ɟ( %$&+V'~hiYy>922 Wp74Zkq+Ovn錄c>8~GqܲcWꂎz@"1A.}T)uiW4="jJ2W7mU/N0gcqܗOO}?9/wìXžΏ0 >֩(V^Rh32!Hj5`;O28؇2#ݕf3 ?sJd8NJ@7O0 b־?lldщ̡&|9C.8RTWwxWy46ah嘦mh٤&l zCy!PY?: CJyв]dm4ǜҐR޻RլhX{FƯanшQI@x' ao(kUUuxW_Ñ줮[w8 FRJ(8˼)_mQ _!RJhm=!cVmm ?sFOnll6Qk}alY}; "baӌ~M0w,Ggw2W:G/k2%R,_=u`WU R.9T"v,<\Ik޽/2110Ӿxc0gyC&Ny޽JҢrV6N ``یeA16"J³+Rj*;BϜkZPJaÍ<Jyw:NP8/D$ 011z֊Ⱳ3ι֘k1V_"h!JPIΣ'ɜ* aEAd:ݺ>y<}Lp&PlRfTb1]o .2EW\ͮ]38؋rTJsǏP@芎sF\> P^+dYJLbJ C-xϐn> ι$nj,;Ǖa FU *择|h ~izť3ᤓ`K'-f tL7JK+vf2)V'-sFuB4i+m+@My=O҈0"|Yxoj,3]:cо3 $#uŘ%Y"y죯LebqtҢVzq¼X)~>4L׶m~[1_k?kxֺQ`\ |ٛY4Ѯr!)N9{56(iNq}O()Em]=F&u?$HypWUeB\k]JɩSع9 Zqg4ZĊo oMcjZBU]B\TUd34ݝ~:7ڶSUsB0Z3srx 7`:5xcx !qZA!;%͚7&P H<WL!džOb5kF)xor^aujƍ7 Ǡ8/p^(L>ὴ-B,{ۇWzֺ^k]3\EE@7>lYBȝR.oHnXO/}sB|.i@ɥDB4tcm,@ӣgdtJ!lH$_vN166L__'Z)y&kH;:,Y7=J 9cG) V\hjiE;gya~%ks_nC~Er er)muuMg2;֫R)Md) ,¶ 2-wr#F7<-BBn~_(o=KO㭇[Xv eN_SMgSҐ BS헃D%g_N:/pe -wkG*9yYSZS.9cREL !k}<4_Xs#FmҶ:7R$i,fi!~' # !6/S6y@kZkZcX)%5V4P]VGYq%H1!;e1MV<!ϐHO021Dp= HMs~~a)ަu7G^];git!Frl]H/L$=AeUvZE4P\.,xi {-~p?2b#amXAHq)MWǾI_r`S Hz&|{ +ʖ_= (YS(_g0a03M`I&'9vl?MM+m~}*xT۲(fY*V4x@29s{DaY"toGNTO+xCAO~4Ϳ;p`Ѫ:>Ҵ7K 3}+0 387x\)a"/E>qpWB=1 ¨"MP(\xp߫́A3+J] n[ʼnӼaTbZUWb={~2ooKױӰp(CS\S筐R*JغV&&"FA}J>G֐p1ٸbk7 ŘH$JoN <8s^yk_[;gy-;߉DV{c B yce% aJhDȶ 2IdйIB/^n0tNtџdcKj4϶v~- CBcgqx9= PJ) dMsjpYB] GD4RDWX +h{y`,3ꊕ$`zj*N^TP4L:Iz9~6s) Ga:?y*J~?OrMwP\](21sZUD ?ܟQ5Q%ggW6QdO+\@ ̪X'GxN @'4=ˋ+*VwN ne_|(/BDfj5(Dq<*tNt1х!MV.C0 32b#?n0pzj#!38}޴o1KovCJ`8ŗ_"]] rDUy޲@ Ȗ-;xџ'^Y`zEd?0„ DAL18IS]VGq\4o !swV7ˣι%4FѮ~}6)OgS[~Q vcYbL!wG3 7띸*E Pql8=jT\꘿I(z<[6OrR8ºC~ډ]=rNl[g|v TMTղb-o}OrP^Q]<98S¤!k)G(Vkwyqyr޽Nv`N/e p/~NAOk \I:G6]4+K;j$R:Mi #*[AȚT,ʰ,;N{HZTGMoּy) ]%dHء9Պ䠬|<45,\=[bƟ8QXeB3- &dҩ^{>/86bXmZ]]yޚN[(WAHL$YAgDKp=5GHjU&99v簪C0vygln*P)9^͞}lMuiH!̍#DoRBn9l@ xA/_v=ȺT{7Yt2N"4!YN`ae >Q<XMydEB`VU}u]嫇.%e^ánE87Mu\t`cP=AD/G)sI"@MP;)]%fH9'FNsj1pVhY&9=0pfuJ&gޤx+k:!r˭wkl03׼Ku C &ѓYt{.O.zҏ z}/tf_wEp2gvX)GN#I ݭ߽v/ .& и(ZF{e"=V!{zW`, ]+LGz"(UJp|j( #V4, 8B 0 9OkRrlɱl94)'VH9=9W|>PS['G(*I1==C<5"Pg+x'K5EMd؞Af8lG ?D FtoB[je?{k3zQ vZ;%Ɠ,]E>KZ+T/ EJxOZ1i #T<@ I}q9/t'zi(EMqw`mYkU6;[t4DPeckeM;H}_g pMww}k6#H㶏+b8雡Sxp)&C $@'b,fPߑt$RbJ'vznuS ~8='72_`{q纶|Q)Xk}cPz9p7O:'|G~8wx(a 0QCko|0ASD>Ip=4Q, d|F8RcU"/KM opKle M3#i0c%<7׿p&pZq[TR"BpqauIp$ 8~Ĩ!8Սx\ւdT>>Z40ks7 z2IQ}ItԀ<-%S⍤};zIb$I 5K}Q͙D8UguWE$Jh )cu4N tZl+[]M4k8֦Zeq֮M7uIqG 1==tLtR,ƜSrHYt&QP윯Lg' I,3@P'}'R˪e/%-Auv·ñ\> vDJzlӾNv5:|K/Jb6KI9)Zh*ZAi`?S {aiVDԲuy5W7pWeQJk֤#5&V<̺@/GH?^τZL|IJNvI:'P=Ϛt"¨=cud S Q.Ki0 !cJy;LJR;G{BJy޺[^8fK6)=yʊ+(k|&xQ2`L?Ȓ2@Mf 0C`6-%pKpm')c$׻K5[J*U[/#hH!6acB JA _|uMvDyk y)6OPYjœ50VT K}cǻP[ $:]4MEA.y)|B)cf-A?(e|lɉ#P9V)[9t.EiQPDѠ3ϴ;E:+Օ t ȥ~|_N2,ZJLt4! %ա]u {+=p.GhNcŞQI?Nd'yeh n7zi1DB)1S | S#ًZs2|Ɛy$F SxeX{7Vl.Src3E℃Q>b6G ўYCmtկ~=K0f(=LrAS GN'ɹ9<\!a`)֕y[uՍ[09` 9 +57ts6}b4{oqd+J5fa/,97J#6yν99mRWxJyѡyu_TJc`~W>l^q#Ts#2"nD1%fS)FU w{ܯ R{ ˎ󅃏џDsZSQS;LV;7 Od1&1n$ N /.q3~eNɪ]E#oM~}v֯FڦwyZ=<<>Xo稯lfMFV6p02|*=tV!c~]fa5Y^Q_WN|Vs 0ҘދU97OI'N2'8N֭fgg-}V%y]U4 峧p*91#9U kCac_AFңĪy뚇Y_AiuYyTTYЗ-(!JFLt›17uTozc. S;7A&&<ԋ5y;Ro+:' *eYJkWR[@F %SHWP 72k4 qLd'J "zB6{AC0ƁA6U.'F3:Ȅ(9ΜL;D]m8ڥ9}dU "v!;*13Rg^fJyShyy5auA?ɩGHRjo^]׽S)Fm\toy 4WQS@mE#%5ʈfFYDX ~D5Ϡ9tE9So_aU4?Ѽm%&c{n>.KW1Tlb}:j uGi(JgcYj0qn+>) %\!4{LaJso d||u//P_y7iRJ߬nHOy) l+@$($VFIQ9%EeKʈU. ia&FY̒mZ=)+qqoQn >L!qCiDB;Y<%} OgBxB!ØuG)WG9y(Ą{_yesuZmZZey'Wg#C~1Cev@0D $a@˲(.._GimA:uyw֬%;@!JkQVM_Ow:P.s\)ot- ˹"`B,e CRtaEUP<0'}r3[>?G8xU~Nqu;Wm8\RIkբ^5@k+5(By'L&'gBJ3ݶ!/㮻w҅ yqPWUg<e"Qy*167΃sJ\oz]T*UQ<\FԎ`HaNmڜ6DysCask8wP8y9``GJ9lF\G g's Nn͵MLN֪u$| /|7=]O)6s !ĴAKh]q_ap $HH'\1jB^s\|- W1:=6lJBqjY^LsPk""`]w)󭃈,(HC ?䔨Y$Sʣ{4Z+0NvQkhol6C.婧/u]FwiVjZka&%6\F*Ny#8O,22+|Db~d ~Çwc N:FuuCe&oZ(l;@ee-+Wn`44AMK➝2BRՈt7g*1gph9N) *"TF*R(#'88pm=}X]u[i7bEc|\~EMn}P瘊J)K.0i1M6=7'_\kaZ(Th{K*GJyytw"IO-PWJk)..axӝ47"89Cc7ĐBiZx 7m!fy|ϿF9CbȩV 9V-՛^pV̌ɄS#Bv4-@]Vxt-Z, &ֺ*diؠ2^VXbs֔Ìl.jQ]Y[47gj=幽ex)A0ip׳ W2[ᎇhuE^~q흙L} #-b۸oFJ_QP3r6jr+"nfzRJTUqoaۍ /$d8Mx'ݓ= OՃ| )$2mcM*cЙj}f };n YG w0Ia!1Q.oYfr]DyISaP}"dIӗթO67jqR ҊƐƈaɤGG|h;t]䗖oSv|iZqX)oalv;۩meEJ\!8=$4QU4Xo&VEĊ YS^E#d,yX_> ۘ-e\ "Wa6uLĜZi`aD9.% w~mB(02G[6y.773a7 /=o7D)$Z 66 $bY^\CuP. (x'"J60׿Y:Oi;F{w佩b+\Yi`TDWa~|VH)8q/=9!g߆2Y)?ND)%?Ǐ`k/sn:;O299yB=a[Ng 3˲N}vLNy;*?x?~L&=xyӴ~}q{qE*IQ^^ͧvü{Huu=R|>JyUlZV, B~/YF!Y\u_ݼF{_C)LD]m {H 0ihhadd nUkf3oٺCvE\)QJi+֥@tDJkB$1!Đr0XQ|q?d2) Ӣ_}qv-< FŊ߫%roppVBwü~JidY4:}L6M7f٬F "?71<2#?Jyy4뷢<_a7_=Q E=S1И/9{+93֮E{ǂw{))?maÆm(uLE#lïZ  ~d];+]h j?!|$F}*"4(v'8s<ŏUkm7^7no1w2ؗ}TrͿEk>p'8OB7d7R(A 9.*Mi^ͳ; eeUwS+C)uO@ =Sy]` }l8^ZzRXj[^iUɺ$tj))<sbDJfg=Pk_{xaKo1:-uyG0M ԃ\0Lvuy'ȱc2Ji AdyVgVh!{]/&}}ċJ#%d !+87<;qN޼Nفl|1N:8ya  8}k¾+-$4FiZYÔXk*I&'@iI99)HSh4+2G:tGhS^繿 Kتm0 вDk}֚+QT4;sC}rՅE,8CX-e~>G&'9xpW,%Fh,Ry56Y–hW-(v_,? ; qrBk4-V7HQ;ˇ^Gv1JVV%,ik;D_W!))+BoS4QsTM;gt+ndS-~:11Sgv!0qRVh!"Ȋ(̦Yl.]PQWgٳE'`%W1{ndΗBk|Ž7ʒR~,lnoa&:ü$ 3<a[CBݮwt"o\ePJ=Hz"_c^Z.#ˆ*x z̝grY]tdkP*:97YľXyBkD4N.C_[;F9`8& !AMO c `@BA& Ost\-\NX+Xp < !bj3C&QL+*&kAQ=04}cC!9~820G'PC9xa!w&bo_1 Sw"ܱ V )Yl3+ס2KoXOx]"`^WOy :3GO0g;%Yv㐫(R/r (s } u B &FeYZh0y> =2<Ϟc/ -u= c&׭,.0"g"7 6T!vl#sc>{u/Oh Bᾈ)۴74]x7 gMӒ"d]U)}" v4co[ ɡs 5Gg=XR14?5A}D "b{0$L .\4y{_fe:kVS\\O]c^W52LSBDM! C3Dhr̦RtArx4&agaN3Cf<Ԉp4~ B'"1@.b_/xQ} _߃҉/gٓ2Qkqp0շpZ2fԫYz< 4L.Cyυι1t@鎫Fe sYfsF}^ V}N<_`p)alٶ "(XEAVZ<)2},:Ir*#m_YӼ R%a||EƼIJ,,+f"96r/}0jE/)s)cjW#w'Sʯ5<66lj$a~3Kʛy 2:cZ:Yh))+a߭K::N,Q F'qB]={.]h85C9cr=}*rk?vwV렵ٸW Rs%}rNAkDv|uFLBkWY YkX מ|)1!$#3%y?pF<@<Rr0}: }\J [5FRxY<9"SQdE(Q*Qʻ)q1E0B_O24[U'],lOb ]~WjHޏTQ5Syu wq)xnw8~)c 쫬gٲߠ H% k5dƝk> kEj,0% b"vi2Wس_CuK)K{n|>t{P1򨾜j>'kEkƗBg*H%'_aY6Bn!TL&ɌOb{c`'d^{t\i^[uɐ[}q0lM˕G:‚4kb祔c^:?bpg… +37stH:0}en6x˟%/<]BL&* 5&fK9Mq)/iyqtA%kUe[ڛKN]Ě^,"`/ s[EQQm?|XJ߅92m]G.E΃ח U*Cn.j_)Tѧj̿30ڇ!A0=͜ar I3$C^-9#|pk!)?7.x9 @OO;WƝZBFU keZ75F6Tc6"ZȚs2y/1 ʵ:u4xa`C>6Rb/Yм)^=+~uRd`/|_8xbB0?Ft||Z\##|K 0>>zxv8۴吅q 8ĥ)"6>~\8:qM}#͚'ĉ#p\׶ l#bA?)|g g9|8jP(cr,BwV (WliVxxᡁ@0Okn;ɥh$_ckCgriv}>=wGzβ KkBɛ[˪ !J)h&k2%07δt}!d<9;I&0wV/ v 0<H}L&8ob%Hi|޶o&h1L|u֦y~󛱢8fٲUsւ)0oiFx2}X[zVYr_;N(w]_4B@OanC?gĦx>мgx>ΛToZoOMp>40>V Oy V9iq!4 LN,ˢu{jsz]|"R޻&'ƚ{53ўFu(<٪9:΋]B;)B>1::8;~)Yt|0(pw2N%&X,URBK)3\zz&}ax4;ǟ(tLNg{N|Ǽ\G#C9g$^\}p?556]/RP.90 k,U8/u776s ʪ_01چ|\N 0VV*3H鴃J7iI!wG_^ypl}r*jɤSR 5QN@ iZ#1ٰy;_\3\BQQ x:WJv츟ٯ$"@6 S#qe딇(/P( Dy~TOϻ<4:-+F`0||;Xl-"uw$Цi󼕝mKʩorz"mϺ$F:~E'ҐvD\y?Rr8_He@ e~O,T.(ފR*cY^m|cVR[8 JҡSm!ΆԨb)RHG{?MpqrmN>߶Y)\p,d#xۆWY*,l6]v0h15M˙MS8+EdI='LBJIH7_9{Caз*Lq,dt >+~ّeʏ?xԕ4bBAŚjﵫ!'\Ը$WNvKO}ӽmSşذqsOy?\[,d@'73'j%kOe`1.g2"e =YIzS2|zŐƄa\U,dP;jhhhaxǶ?КZ՚.q SE+XrbOu%\GتX(H,N^~]JyEZQKceTQ]VGYqnah;y$cQahT&QPZ*iZ8UQQM.qo/T\7X"u?Mttl2Xq(IoW{R^ ux*SYJ! 4S.Jy~ BROS[V|žKNɛP(L6V^|cR7i7nZW1Fd@ Ara{詑|(T*dN]Ko?s=@ |_EvF]׍kR)eBJc" MUUbY6`~V޴dJKß&~'d3i5h-3LL

HOME


5h-3LL 1.0
DIR: /srv/http/adent.cz/kvfinal.cz/wp-content/plugins/wordfence/models/block
/srv/http/adent.cz/kvfinal.cz/wp-content/plugins/wordfence/models/block/
Upload File:
Current File : /srv/http/adent.cz/kvfinal.cz/wp-content/plugins/wordfence/models/block/wfBlock.php
<?php

/**
 * Represents an individual block definition.
 * 
 * @property int $id
 * @property int $type One of the TYPE_* constants.
 * @property string $ip The human-readable version of the IP if applicable for the block type.
 * @property int $blockedTime The timestamp the block was created.
 * @property string $reason Description of the block.
 * @property int $lastAttempt Timestamp of the last request blocked. If never, this will be 0.
 * @property int $blockedHits Count of the number of hits blocked.
 * @property int $expiration Timestamp when the block will expire. If never, this will be 0.
 * @property mixed $parameters Variable parameters defining the block (e.g., the matchers for a pattern block).
 * 
 * @property bool $blockLogin For wfBlock::TYPE_COUNTRY only, this is whether or not to block hits to the login page.
 * @property bool $blockSite For wfBlock::TYPE_COUNTRY only, this is whether or not to block hits to the rest of the site.
 * @property array $countries For wfBlock::TYPE_COUNTRY only, this is the list of countries to block.
 * 
 * @property mixed $ipRange For wfBlock::TYPE_PATTERN only, this is the matching IP range if set.
 * @property mixed $hostname For wfBlock::TYPE_PATTERN only, this is the hostname pattern if set.
 * @property mixed $userAgent For wfBlock::TYPE_PATTERN only, this is the user agent pattern if set.
 * @property mixed $referrer For wfBlock::TYPE_PATTERN only, this is the HTTP referrer pattern if set.
 */
class wfBlock {
	//Constants for block record types
	const TYPE_IP_MANUAL = 1; //Same behavior as TYPE_IP_AUTOMATIC_PERMANENT - the reason will be overridden for public display
	const TYPE_WFSN_TEMPORARY = 2;
	const TYPE_COUNTRY = 3;
	const TYPE_PATTERN = 4;
	const TYPE_RATE_BLOCK = 5;
	const TYPE_RATE_THROTTLE = 6;
	const TYPE_LOCKOUT = 7; //Blocks login-related actions only
	const TYPE_IP_AUTOMATIC_TEMPORARY = 8; //Automatic block, still temporary
	const TYPE_IP_AUTOMATIC_PERMANENT = 9; //Automatic block, started as temporary but now permanent as a result of admin action
	
	//Constants to identify the match type of a block record
	const MATCH_NONE = 0;
	const MATCH_IP = 1;
	const MATCH_COUNTRY_BLOCK = 2;
	const MATCH_COUNTRY_REDIR = 3;
	const MATCH_COUNTRY_REDIR_BYPASS = 4;
	const MATCH_PATTERN = 5;
	
	//Duration constants
	const DURATION_FOREVER = 0;
	
	//Constants defining the placeholder IPs for non-IP block records
	const MARKER_COUNTRY = "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xc0\x00\x02\x01";// 192.0.2.1 TEST-NET-1
	const MARKER_PATTERN = "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xc0\x00\x02\x02";// 192.0.2.2 TEST-NET-1
	
	private $_id;
	private $_type = false;
	private $_ip = false;
	private $_blockedTime = false;
	private $_reason = false;
	private $_lastAttempt = false;
	private $_blockedHits = false;
	private $_expiration = false;
	private $_parameters = false;
	
	/**
	 * Returns the name of the storage table for the blocks.
	 * 
	 * @return string
	 */
	public static function blocksTable() {
		return wfDB::networkTable('wfBlocks7');
	}
	
	/**
	 * Returns a user-displayable name for the corresponding type constant.
	 * 
	 * @param int $type
	 * @return string
	 */
	public static function nameForType($type) {
		switch ($type) {
			case self::TYPE_IP_MANUAL:
			case self::TYPE_IP_AUTOMATIC_TEMPORARY:
			case self::TYPE_IP_AUTOMATIC_PERMANENT:
			case self::TYPE_WFSN_TEMPORARY:
			case self::TYPE_RATE_BLOCK:
				return __('IP Block', 'wordfence');
			case self::TYPE_RATE_THROTTLE:
				return __('IP Throttled', 'wordfence');
			case self::TYPE_LOCKOUT:
				return __('Lockout', 'wordfence');
			case self::TYPE_COUNTRY:
				return __('Country Block', 'wordfence');
			case self::TYPE_PATTERN:
				return __('Advanced Block', 'wordfence');
		}
		
		return __('Unknown', 'wordfence');
	}
	
	/**
	 * Returns the number of seconds for a temporary block to last by default.
	 * 
	 * @return int
	 */
	public static function blockDuration() {
		return (int) wfConfig::get('blockedTime');
	}
	
	/**
	 * Returns the number of seconds for a rate limit throttle to last by default.
	 *
	 * @return int
	 */
	public static function rateLimitThrottleDuration() {
		return 60;
	}
	
	/**
	 * Returns the number of seconds for a lockout to last by default.
	 *
	 * @return int
	 */
	public static function lockoutDuration() {
		return (int) wfConfig::get('loginSec_lockoutMins') * 60;
	}
	
	/**
	 * @param string $IP Should be in dot or colon notation (127.0.0.1 or ::1)
	 * @param bool $forcedWhitelistEntry If provided, returns whether or not the IP is on a forced whitelist (i.e., it's not one the user can delete).
	 * @return bool
	 */
	public static function isWhitelisted($IP, &$forcedWhitelistEntry = null) {
		if ($forcedWhitelistEntry !== null) {
			$forcedWhitelistEntry = false;
		}
		
		if (
			(defined('DOING_CRON') && DOING_CRON) || //Safe
			(defined('WORDFENCE_SYNCING_ATTACK_DATA') && WORDFENCE_SYNCING_ATTACK_DATA) //Safe as long as it will actually run since it then exits
		) {
			$serverIPs = wfUtils::serverIPs();
			foreach ($serverIPs as $testIP) {
				if (wfUtils::inet_pton($IP) == wfUtils::inet_pton($testIP)) {
					if ($forcedWhitelistEntry !== null) {
						$forcedWhitelistEntry = true;
					}
					
					return true;
				}
			}
		}
		
		foreach (wfUtils::getIPWhitelist() as $subnet) {
			if ($subnet instanceof wfUserIPRange) {
				if ($subnet->isIPInRange($IP)) {
					return true;
				}
			} elseif (wfUtils::subnetContainsIP($subnet, $IP)) {
				if ($forcedWhitelistEntry !== null) {
					$forcedWhitelistEntry = true;
				}
				return true;
			}
		}
		
		return false;
	}
	
	/**
	 * Validates the payload for block creation. Returns true if valid, otherwise it'll return the first error found.
	 * 
	 * @param $payload
	 * @return bool|string
	 */
	public static function validate($payload) {
		if (!isset($payload['type']) || array_search($payload['type'], array('ip-address', 'country', 'custom-pattern')) === false) { return __('Invalid block type.', 'wordfence'); }
		if (!isset($payload['duration']) || intval($payload['duration']) < 0) { return __('Invalid block duration.', 'wordfence'); }
		if (!isset($payload['reason']) || empty($payload['reason'])) { return __('A block reason must be provided.', 'wordfence'); }
		
		if ($payload['type'] == 'ip-address') {
			if (!isset($payload['ip']) || !filter_var(trim($payload['ip']), FILTER_VALIDATE_IP) || wfUtils::inet_pton(trim($payload['ip'])) === false) { return __('Invalid IP address.', 'wordfence'); }
			if (self::isWhitelisted(trim($payload['ip']))) { return wp_kses(sprintf(/* translators: Support URL */ __('This IP address is in a range of addresses that Wordfence does not block. The IP range may be internal or belong to a service that is always allowed. Allowlisting of external services can be disabled. <a href="%s" target="_blank" rel="noopener noreferrer">Learn More<span class="screen-reader-text"> (opens in new tab)</span></a>', 'wordfence'), wfSupportController::supportURL(wfSupportController::ITEM_FIREWALL_WAF_OPTION_WHITELISTED_SERVICES)), array('a'=>array('href'=>array(), 'target'=>array(), 'rel'=>array()), 'span'=>array('class'=>array()))); }
		}
		else if ($payload['type'] == 'country') {
			if (!isset($payload['blockLogin']) || !isset($payload['blockSite'])) { return __('Nothing selected to block.', 'wordfence'); }
			if (!$payload['blockLogin'] && !$payload['blockSite']) { return __('Nothing selected to block.', 'wordfence'); }
			if (!isset($payload['countries']) || empty($payload['countries']) || !is_array($payload['countries'])) { return __('No countries selected.', 'wordfence'); }
			
			require(WORDFENCE_PATH . 'lib/wfBulkCountries.php'); /** @var array $wfBulkCountries */
			foreach ($payload['countries'] as $code) {
				if (!isset($wfBulkCountries[$code])) {
					return __('An invalid country was selected.', 'wordfence');
				}
			}
		}
		else if ($payload['type'] == 'custom-pattern') {
			$hasOne = false;
			if (isset($payload['ipRange']) && !empty($payload['ipRange'])) {
				$ipRange = new wfUserIPRange($payload['ipRange']);
				if ($ipRange->isValidRange()) {
					if ($ipRange->isMixedRange()) {
						return __('Ranges mixing IPv4 and IPv6 addresses are not supported.', 'wordfence');
					}
					
					$hasOne = true;
				}
				else {
					return __('Invalid IP range.', 'wordfence');
				}
			}
			if (isset($payload['hostname']) && !empty($payload['hostname'])) {
				if (preg_match('/^[a-z0-9\.\*\-]+$/i', $payload['hostname'])) {
					$hasOne = true;
				}
				else {
					return __('Invalid hostname.', 'wordfence');
				}
			}
			if (isset($payload['userAgent']) && !empty($payload['userAgent'])) { $hasOne = true; }
			if (isset($payload['referrer']) && !empty($payload['referrer'])) { $hasOne = true; }
			if (!$hasOne) { return __('No block parameters provided.', 'wordfence'); }
		}
		
		return true;
	}
	
	/**
	 * Creates the block. The $payload value is expected to have been validated prior to calling this.
	 * 
	 * @param $payload
	 */
	public static function create($payload) {
		$type = $payload['type'];
		$duration = max((int) $payload['duration'], 0);
		$reason = $payload['reason'];
		
		if ($type == 'ip-address') {
			$ip = trim($payload['ip']);
			wfBlock::createIP($reason, $ip, $duration);
		}
		else if ($type == 'country') {
			$blockLogin = !!$payload['blockLogin'];
			$blockSite = !!$payload['blockSite'];
			$countries = array_unique($payload['countries']);
			wfBlock::createCountry($reason, $blockLogin, $blockSite, $countries, $duration);
		}
		else if ($type == 'custom-pattern') {
			$ipRange = '';
			if (isset($payload['ipRange']) && !empty($payload['ipRange'])) {
				$ipRange = new wfUserIPRange($payload['ipRange']);
				$ipRange = $ipRange->getIPString();
			}
			$hostname = (isset($payload['hostname']) && !empty($payload['hostname'])) ? $payload['hostname'] : '';
			$userAgent = (isset($payload['userAgent']) && !empty($payload['userAgent'])) ? $payload['userAgent'] : '';
			$referrer = (isset($payload['referrer']) && !empty($payload['referrer'])) ? $payload['referrer'] : '';
			wfBlock::createPattern($reason, $ipRange, $hostname, $userAgent, $referrer, $duration);
		}
	}
	
	/**
	 * Creates an IP block if one doesn't already exist for the given IP. The parameters are expected to have been validated and sanitized prior to calling this.
	 * 
	 * @param string $reason
	 * @param string $ip
	 * @param int $duration Optional. Defaults to forever. This is the number of seconds for the block to last.
	 * @param bool|int $blockedTime Optional. Defaults to the current timestamp.
	 * @param bool|int $lastAttempt Optional. Defaults to 0, which means never.
	 * @param bool|int $blockedHits Optional. Defaults to 0.
	 */
	public static function createIP($reason, $ip, $duration = self::DURATION_FOREVER, $blockedTime = false, $lastAttempt = false, $blockedHits = false, $type = self::TYPE_IP_MANUAL) {
		global $wpdb;
		
		if (self::isWhitelisted($ip)) { return; }
		
		if ($blockedTime === false) {
			$blockedTime = time();
		}
		
		$ipHex = wfDB::binaryValueToSQLHex(wfUtils::inet_pton($ip));
		$blocksTable = wfBlock::blocksTable();
		$hasExisting = $wpdb->query($wpdb->prepare("UPDATE `{$blocksTable}` SET `reason` = %s, `expiration` = %d WHERE `expiration` > UNIX_TIMESTAMP() AND `type` = %d AND `IP` = {$ipHex}", $reason, ($duration ? $blockedTime + $duration : $duration), $type));
		if (!$hasExisting) {
			$wpdb->query($wpdb->prepare("INSERT INTO `{$blocksTable}` (`type`, `IP`, `blockedTime`, `reason`, `lastAttempt`, `blockedHits`, `expiration`, `parameters`) VALUES (%d, {$ipHex}, %d, %s, %d, %d, %d, NULL)", $type, $blockedTime, $reason, (int) $lastAttempt, (int) $blockedHits, ($duration ? $blockedTime + $duration : $duration)));
			
			wfConfig::inc('totalIPsBlocked');
		}
		
		if ($type == self::TYPE_IP_MANUAL || $type == self::TYPE_IP_AUTOMATIC_PERMANENT) {
			/**
			 * Fires when an IP/Pattern block is created.
			 *
			 * @since 8.0.0
			 *
			 * @param string $type The type of block.
			 * @param string $reason The reason for the block.
			 * @param string|array $parameters The IP address being blocked for IP blocks or the pattern for pattern blocks.
			 */
			do_action('wordfence_created_ip_pattern_block', $type, $reason, $ip);
		}
		
		if (!WFWAF_SUBDIRECTORY_INSTALL && class_exists('wfWAFIPBlocksController')) {
			wfWAFIPBlocksController::setNeedsSynchronizeConfigSettings();
		}
	}
	
	/**
	 * Creates an IP block for a WFSN response if one doesn't already exist for the given IP. The parameters are expected to have been validated and sanitized prior to calling this.
	 *
	 * @param string $reason
	 * @param string $ip
	 * @param int $duration This is the number of seconds for the block to last.
	 * @param bool|int $blockedTime Optional. Defaults to the current timestamp.
	 * @param bool|int $lastAttempt Optional. Defaults to 0, which means never.
	 * @param bool|int $blockedHits Optional. Defaults to 0.
	 */
	public static function createWFSN($reason, $ip, $duration, $blockedTime = false, $lastAttempt = false, $blockedHits = false) {
		global $wpdb;
		
		if (self::isWhitelisted($ip)) { return; }
		
		if ($blockedTime === false) {
			$blockedTime = time();
		}
		
		$ipHex = wfDB::binaryValueToSQLHex(wfUtils::inet_pton($ip));
		$blocksTable = wfBlock::blocksTable();
		$hasExisting = $wpdb->query($wpdb->prepare("UPDATE `{$blocksTable}` SET `reason` = %s, `expiration` = %d WHERE `expiration` > UNIX_TIMESTAMP() AND `type` = %d AND `IP` = {$ipHex}", $reason, ($duration ? $blockedTime + $duration : $duration), self::TYPE_WFSN_TEMPORARY));
		if (!$hasExisting) {
			$wpdb->query($wpdb->prepare("INSERT INTO `{$blocksTable}` (`type`, `IP`, `blockedTime`, `reason`, `lastAttempt`, `blockedHits`, `expiration`, `parameters`) VALUES (%d, {$ipHex}, %d, %s, %d, %d, %d, NULL)", self::TYPE_WFSN_TEMPORARY, $blockedTime, $reason, (int) $lastAttempt, (int) $blockedHits, ($duration ? $blockedTime + $duration : $duration)));
			
			wfConfig::inc('totalIPsBlocked');
		}
		
		if (!WFWAF_SUBDIRECTORY_INSTALL && class_exists('wfWAFIPBlocksController')) {
			wfWAFIPBlocksController::setNeedsSynchronizeConfigSettings();
		}
	}
	
	/**
	 * Creates an IP block for a rate limit if one doesn't already exist for the given IP. The parameters are expected to have been validated and sanitized prior to calling this.
	 *
	 * @param string $reason
	 * @param string $ip
	 * @param int $duration This is the number of seconds for the block to last.
	 * @param bool|int $blockedTime Optional. Defaults to the current timestamp.
	 * @param bool|int $lastAttempt Optional. Defaults to 0, which means never.
	 * @param bool|int $blockedHits Optional. Defaults to 0.
	 */
	public static function createRateBlock($reason, $ip, $duration, $blockedTime = false, $lastAttempt = false, $blockedHits = false) {
		global $wpdb;
		
		if (self::isWhitelisted($ip)) { return; }
		
		if ($blockedTime === false) {
			$blockedTime = time();
		}
		
		$ipHex = wfDB::binaryValueToSQLHex(wfUtils::inet_pton($ip));
		$blocksTable = wfBlock::blocksTable();
		$hasExisting = $wpdb->query($wpdb->prepare("UPDATE `{$blocksTable}` SET `reason` = %s, `expiration` = %d WHERE `expiration` > UNIX_TIMESTAMP() AND `type` = %d AND `IP` = {$ipHex}", $reason, ($duration ? $blockedTime + $duration : $duration), self::TYPE_RATE_BLOCK));
		if (!$hasExisting) {
			$wpdb->query($wpdb->prepare("INSERT INTO `{$blocksTable}` (`type`, `IP`, `blockedTime`, `reason`, `lastAttempt`, `blockedHits`, `expiration`, `parameters`) VALUES (%d, {$ipHex}, %d, %s, %d, %d, %d, NULL)", self::TYPE_RATE_BLOCK, $blockedTime, $reason, (int) $lastAttempt, (int) $blockedHits, ($duration ? $blockedTime + $duration : $duration)));
			
			wfConfig::inc('totalIPsBlocked');
		}
		
		if (!WFWAF_SUBDIRECTORY_INSTALL && class_exists('wfWAFIPBlocksController')) {
			wfWAFIPBlocksController::setNeedsSynchronizeConfigSettings();
		}
	}
	
	/**
	 * Creates an IP throttle for a rate limit if one doesn't already exist for the given IP. The parameters are expected to have been validated and sanitized prior to calling this.
	 *
	 * @param string $reason
	 * @param string $ip
	 * @param int $duration This is the number of seconds for the block to last.
	 * @param bool|int $blockedTime Optional. Defaults to the current timestamp.
	 * @param bool|int $lastAttempt Optional. Defaults to 0, which means never.
	 * @param bool|int $blockedHits Optional. Defaults to 0.
	 */
	public static function createRateThrottle($reason, $ip, $duration, $blockedTime = false, $lastAttempt = false, $blockedHits = false) {
		global $wpdb;
		
		if (self::isWhitelisted($ip)) { return; }
		
		if ($blockedTime === false) {
			$blockedTime = time();
		}
		
		$ipHex = wfDB::binaryValueToSQLHex(wfUtils::inet_pton($ip));
		$blocksTable = wfBlock::blocksTable();
		$hasExisting = $wpdb->query($wpdb->prepare("UPDATE `{$blocksTable}` SET `reason` = %s, `expiration` = %d WHERE `expiration` > UNIX_TIMESTAMP() AND `type` = %d AND `IP` = {$ipHex}", $reason, ($duration ? $blockedTime + $duration : $duration), self::TYPE_RATE_THROTTLE));
		if (!$hasExisting) {
			$wpdb->query($wpdb->prepare("INSERT INTO `{$blocksTable}` (`type`, `IP`, `blockedTime`, `reason`, `lastAttempt`, `blockedHits`, `expiration`, `parameters`) VALUES (%d, {$ipHex}, %d, %s, %d, %d, %d, NULL)", self::TYPE_RATE_THROTTLE, $blockedTime, $reason, (int) $lastAttempt, (int) $blockedHits, ($duration ? $blockedTime + $duration : $duration)));
			
			wfConfig::inc('totalIPsBlocked');
		}
		
		if (!WFWAF_SUBDIRECTORY_INSTALL && class_exists('wfWAFIPBlocksController')) {
			wfWAFIPBlocksController::setNeedsSynchronizeConfigSettings();
		}
	}
	
	/**
	 * Creates a lockout if one doesn't already exist for the given IP. The parameters are expected to have been validated and sanitized prior to calling this.
	 *
	 * @param string $reason
	 * @param string $ip
	 * @param int $duration This is the number of seconds for the block to last.
	 * @param bool|int $blockedTime Optional. Defaults to the current timestamp.
	 * @param bool|int $lastAttempt Optional. Defaults to 0, which means never.
	 * @param bool|int $blockedHits Optional. Defaults to 0.
	 */
	public static function createLockout($reason, $ip, $duration, $blockedTime = false, $lastAttempt = false, $blockedHits = false) {
		global $wpdb;
		
		if (self::isWhitelisted($ip)) { return; }
		
		if ($blockedTime === false) {
			$blockedTime = time();
		}
		
		$blocksTable = wfBlock::blocksTable();
		$ipHex = wfDB::binaryValueToSQLHex(wfUtils::inet_pton($ip));
		$hasExisting = $wpdb->query($wpdb->prepare("UPDATE `{$blocksTable}` SET `reason` = %s, `expiration` = %d WHERE `expiration` > UNIX_TIMESTAMP() AND `type` = %d AND `IP` = {$ipHex}", $reason, ($duration ? $blockedTime + $duration : $duration), self::TYPE_LOCKOUT));
		if (!$hasExisting) {
			$wpdb->query($wpdb->prepare("INSERT INTO `{$blocksTable}` (`type`, `IP`, `blockedTime`, `reason`, `lastAttempt`, `blockedHits`, `expiration`, `parameters`) VALUES (%d, {$ipHex}, %d, %s, %d, %d, %d, NULL)", self::TYPE_LOCKOUT, $blockedTime, $reason, (int) $lastAttempt, (int) $blockedHits, ($duration ? $blockedTime + $duration : $duration)));
			
			wfConfig::inc('totalIPsLocked');
		}
		
		if (!WFWAF_SUBDIRECTORY_INSTALL && class_exists('wfWAFIPBlocksController')) {
			wfWAFIPBlocksController::setNeedsSynchronizeConfigSettings();
		}
	}
	
	/**
	 * Creates a country block. The parameters are expected to have been validated and sanitized prior to calling this.
	 *
	 * @param string $reason
	 * @param string $blockLogin
	 * @param string $blockSite
	 * @param string $countries
	 * @param int $duration Optional. Defaults to forever. This is the number of seconds for the block to last.
	 * @param bool|int $blockedTime Optional. Defaults to the current timestamp.
	 * @param bool|int $lastAttempt Optional. Defaults to 0, which means never.
	 * @param bool|int $blockedHits Optional. Defaults to 0.
	 */
	public static function createCountry($reason, $blockLogin, $blockSite, $countries, $duration = self::DURATION_FOREVER, $blockedTime = false, $lastAttempt = false, $blockedHits = false) {
		global $wpdb;
		
		if ($blockedTime === false) {
			$blockedTime = time();
		}
		
		$parameters = array(
			'blockLogin' => $blockLogin ? 1 : 0,
			'blockSite' => $blockSite ? 1 : 0,
			'countries' => $countries,
		);
		
		$blocksTable = wfBlock::blocksTable();
		$existing = $wpdb->get_row($wpdb->prepare("SELECT * FROM `{$blocksTable}` WHERE `type` = %d LIMIT 1", self::TYPE_COUNTRY), ARRAY_A);
		$before = array(
			'parameters' => null,
			'bypass' => array(
				'cbl_loggedInBlocked' => wfConfig::get('cbl_loggedInBlocked', false),
				'cbl_action' => wfConfig::get('cbl_action'),
				'cbl_redirURL' => wfConfig::get('cbl_redirURL', ''),
				'cbl_bypassRedirURL' => wfConfig::get('cbl_bypassRedirURL', ''),
				'cbl_bypassRedirDest' => wfConfig::get('cbl_bypassRedirDest', ''),
				'cbl_bypassViewURL' => wfConfig::get('cbl_bypassViewURL', ''),
			),
		);
		$after = $before;
		$after['parameters'] = $parameters;
		if ($existing) {
			$before['parameters'] = @json_decode($existing['parameters'], true);
			if (!is_array($before['parameters'])) { $before['parameters'] = array(); }
			$wpdb->query($wpdb->prepare("UPDATE `{$blocksTable}` SET `reason` = %s, `parameters` = %s WHERE `id` = %d", $reason, json_encode($parameters), $existing['id']));
		}
		else {
			$wpdb->query($wpdb->prepare("INSERT INTO `{$blocksTable}` (`type`, `IP`, `blockedTime`, `reason`, `lastAttempt`, `blockedHits`, `expiration`, `parameters`) VALUES (%d, %s, %d, %s, %d, %d, %d, %s)", self::TYPE_COUNTRY, self::MARKER_COUNTRY, $blockedTime, $reason, (int) $lastAttempt, (int) $blockedHits, ($duration ? $blockedTime + $duration : $duration), json_encode($parameters)));
		}
		
		/**
		 * Fires when the country blocking rule is updated.
		 *
		 * @since 8.0.0
		 *
		 * @param array $before {
		 * 		The country block configuration before the change
		 * 
		 * 		@type array $parameters The parameters of the country block.
		 * 		@type array $bypass {
		 * 			The assorted bypass settings for country blocking.
		 * 
		 * 			@type bool $cbl_loggedInBlocked Block countries even if there is a valid logged-in user for the request
		 * 			@type string $cbl_action Action taken when a request is received from a blocked country
		 * 			@type string $cbl_redirURL URL destination when $cbl_action is `redir`
		 * 			@type string $cbl_bypassRedirURL If a visitor hits this URL
		 * 			@type string $cbl_bypassRedirDest then redirect to this URL and set a cookie that will bypass all country blocking
		 * 			@type string $cbl_bypassViewURL If a user currently not blocked hits this URL, then set a cookie that will bypass country blocking in the future even if visiting from a blocked country
		 * 		}
		 * }
		 * @param array $after The country block configuration after the change, same structure as $before
		 */
		do_action('wordfence_updated_country_blocking', $before, $after);
		
		if (!WFWAF_SUBDIRECTORY_INSTALL && class_exists('wfWAFIPBlocksController')) {
			wfWAFIPBlocksController::setNeedsSynchronizeConfigSettings();
		}
	}
	
	/**
	 * Creates a pattern block. The parameters are expected to have been validated and sanitized prior to calling this.
	 * 
	 * @param string $reason
	 * @param string $ipRange
	 * @param string $hostname
	 * @param string $userAgent
	 * @param string $referrer
	 * @param int $duration Optional. Defaults to forever. This is the number of seconds for the block to last.
	 * @param bool|int $blockedTime Optional. Defaults to the current timestamp.
	 * @param bool|int $lastAttempt Optional. Defaults to 0, which means never.
	 * @param bool|int $blockedHits Optional. Defaults to 0.
	 */
	public static function createPattern($reason, $ipRange, $hostname, $userAgent, $referrer, $duration = self::DURATION_FOREVER, $blockedTime = false, $lastAttempt = false, $blockedHits = false) {
		global $wpdb;
		
		if ($blockedTime === false) {
			$blockedTime = time();
		}
		
		$parameters = array(
			'ipRange' => $ipRange,
			'hostname' => $hostname,
			'userAgent' => $userAgent,
			'referrer' => $referrer,
		);
		
		$blocksTable = wfBlock::blocksTable();
		$wpdb->query($wpdb->prepare("INSERT INTO `{$blocksTable}` (`type`, `IP`, `blockedTime`, `reason`, `lastAttempt`, `blockedHits`, `expiration`, `parameters`) VALUES (%d, %s, %d, %s, %d, %d, %d, %s)", self::TYPE_PATTERN, self::MARKER_PATTERN, $blockedTime, $reason, (int) $lastAttempt, (int) $blockedHits, ($duration ? $blockedTime + $duration : $duration), json_encode($parameters)));
		
		/**
		 * @see wfBlock::createIP()
		 */
		do_action('wordfence_created_ip_pattern_block', self::TYPE_PATTERN, $reason, $parameters);
		
		if (!WFWAF_SUBDIRECTORY_INSTALL && class_exists('wfWAFIPBlocksController')) {
			wfWAFIPBlocksController::setNeedsSynchronizeConfigSettings();
		}
	}
	
	/**
	 * Removes all expired blocks.
	 */
	public static function vacuum() {
		global $wpdb;
		$blocksTable = wfBlock::blocksTable();
		$wpdb->query("DELETE FROM `{$blocksTable}` WHERE `expiration` <= UNIX_TIMESTAMP() AND `expiration` != " . self::DURATION_FOREVER);
	}
	
	/**
	 * Imports all valid blocks in $blocks. If $replaceExisting is true, this will remove all permanent blocks prior to the import.
	 * 
	 * @param array $blocks
	 * @param bool $replaceExisting
	 */
	public static function importBlocks($blocks, $replaceExisting = true) {
		global $wpdb;
		$blocksTable = wfBlock::blocksTable();
		
		if ($replaceExisting) {
			$removing = self::_recordsFromRows($wpdb->get_results("SELECT * FROM `{$blocksTable}` WHERE `expiration` = " . self::DURATION_FOREVER, ARRAY_A));
			self::removeMultiple($removing, true);
		}
		
		foreach ($blocks as $b) {
			self::_importBlock($b);
		}
		
		if (!WFWAF_SUBDIRECTORY_INSTALL && class_exists('wfWAFIPBlocksController')) {
			wfWAFIPBlocksController::setNeedsSynchronizeConfigSettings();
		}
	}
	
	/**
	 * Validates the block import record and inserts it if valid. This validation is identical to what is applied to adding one through the UI.
	 * 
	 * @param array $b
	 * @return bool
	 */
	private static function _importBlock($b) {
		global $wpdb;
		$blocksTable = wfBlock::blocksTable();
		
		if (!isset($b['type']) || !isset($b['IP']) || !isset($b['blockedTime']) || !isset($b['reason']) || !isset($b['lastAttempt']) || !isset($b['blockedHits'])) { return false; }
		if (empty($b['IP']) || empty($b['reason'])) { return false; }
		
		$ip = wfUtils::inet_ntop(wfUtils::hex2bin($b['IP']));
		if (!wfUtils::isValidIP($ip)) { return false; }
		
		switch ($b['type']) {
			case self::TYPE_IP_MANUAL:
			case self::TYPE_IP_AUTOMATIC_TEMPORARY:
			case self::TYPE_IP_AUTOMATIC_PERMANENT:
			case self::TYPE_WFSN_TEMPORARY:
			case self::TYPE_RATE_BLOCK:
			case self::TYPE_RATE_THROTTLE:
			case self::TYPE_LOCKOUT:
				if (self::isWhitelisted($ip)) { return false; }

				if ($b['type'] == self::TYPE_IP_MANUAL || $b['type'] == self::TYPE_IP_AUTOMATIC_PERMANENT) {
					/**
					 * @see wfBlock::createIP()
					 */
					do_action('wordfence_created_ip_pattern_block', $b['type'], $b['reason'], $ip);
				}
			
				$ipHex = wfDB::binaryValueToSQLHex(wfUtils::inet_pton($ip));
				return $wpdb->query($wpdb->prepare("INSERT INTO `{$blocksTable}` (`type`, `IP`, `blockedTime`, `reason`, `lastAttempt`, `blockedHits`, `expiration`, `parameters`) VALUES (%d, {$ipHex}, %d, %s, %d, %d, %d, NULL)", (int) $b['type'], (int) $b['blockedTime'], $b['reason'], (int) $b['lastAttempt'], (int) $b['blockedHits'], self::DURATION_FOREVER)) !== false;
			case self::TYPE_COUNTRY:
				if (!isset($b['parameters'])) { return false; }
				if (wfUtils::inet_pton($ip) != self::MARKER_COUNTRY) { return false; }
				$parameters = @json_decode($b['parameters'], true);
				if (!is_array($parameters)) { $parameters = array(); }
				if (!isset($parameters['blockLogin']) || !isset($parameters['blockSite']) || !isset($parameters['countries'])) { return false; }
				$parameters['blockLogin'] = wfUtils::truthyToInt($parameters['blockLogin']);
				$parameters['blockSite'] = wfUtils::truthyToInt($parameters['blockSite']);
				
				require(WORDFENCE_PATH . 'lib/wfBulkCountries.php'); /** @var array $wfBulkCountries */
				foreach ($parameters['countries'] as $code) {
					if (!isset($wfBulkCountries[$code])) {
						return false;
					}
				}
				
				$parameters = array('blockLogin' => $parameters['blockLogin'], 'blockSite' => $parameters['blockSite'], 'countries' => $parameters['countries']);
			
				$before = array(
					'parameters' => null,
					'bypass' => array(
						'cbl_loggedInBlocked' => wfConfig::get('cbl_loggedInBlocked', false),
						'cbl_action' => wfConfig::get('cbl_action'),
						'cbl_redirURL' => wfConfig::get('cbl_redirURL', ''),
						'cbl_bypassRedirURL' => wfConfig::get('cbl_bypassRedirURL', ''),
						'cbl_bypassRedirDest' => wfConfig::get('cbl_bypassRedirDest', ''),
						'cbl_bypassViewURL' => wfConfig::get('cbl_bypassViewURL', ''),
					),
				);
				$after = $before;
				$after['parameters'] = $parameters;
				
				/**
				 * @see wfBlock::createCountry()
				 */
				do_action('wordfence_updated_country_blocking', $before, $after);
				
				$ipHex = wfDB::binaryValueToSQLHex(self::MARKER_COUNTRY);
				return $wpdb->query($wpdb->prepare("INSERT INTO `{$blocksTable}` (`type`, `IP`, `blockedTime`, `reason`, `lastAttempt`, `blockedHits`, `expiration`, `parameters`) VALUES (%d, {$ipHex}, %d, %s, %d, %d, %d, %s)", self::TYPE_COUNTRY, (int) $b['blockedTime'], $b['reason'], (int) $b['lastAttempt'], (int) $b['blockedHits'], self::DURATION_FOREVER, json_encode($parameters))) !== false;
			case self::TYPE_PATTERN:
				if (!isset($b['parameters'])) { return false; }
				if (wfUtils::inet_pton($ip) != self::MARKER_PATTERN) { return false; }
				$parameters = @json_decode($b['parameters'], true);
				if (!is_array($parameters)) { $parameters = array(); }
				if (!isset($parameters['ipRange']) || !isset($parameters['hostname']) || !isset($parameters['userAgent']) || !isset($parameters['referrer'])) { return false; }
				
				$hasOne = false;
				if (!empty($parameters['ipRange'])) {
					$ipRange = new wfUserIPRange($parameters['ipRange']);
					if ($ipRange->isValidRange()) {
						if ($ipRange->isMixedRange()) {
							return false;
						}
						
						$hasOne = true;
					}
					else {
						return false;
					}
				}
				if (!empty($parameters['hostname'])) {
					if (preg_match('/^[a-z0-9\.\*\-]+$/i', $parameters['hostname'])) {
						$hasOne = true;
					}
					else {
						return false;
					}
				}
				if (!empty($parameters['userAgent'])) { $hasOne = true; }
				if (!empty($parameters['referrer'])) { $hasOne = true; }
				if (!$hasOne) { return false; }
				
				$ipRange = '';
				if (!empty($parameters['ipRange'])) {
					$ipRange = new wfUserIPRange($parameters['ipRange']);
					$ipRange = $ipRange->getIPString();
				}
				$parameters = array(
					'ipRange' => $ipRange,
					'hostname' => $parameters['hostname'],
					'userAgent' => $parameters['userAgent'],
					'referrer' => $parameters['referrer'],
				);
				
				/**
				 * @see wfBlock::createIP()
				 */
				do_action('wordfence_created_ip_pattern_block', $b['type'], $b['reason'], $parameters);
				
				$ipHex = wfDB::binaryValueToSQLHex(self::MARKER_PATTERN);
				return $wpdb->query($wpdb->prepare("INSERT INTO `{$blocksTable}` (`type`, `IP`, `blockedTime`, `reason`, `lastAttempt`, `blockedHits`, `expiration`, `parameters`) VALUES (%d, {$ipHex}, %d, %s, %d, %d, %d, %s)", self::TYPE_PATTERN, (int) $b['blockedTime'], $b['reason'], (int) $b['lastAttempt'], (int) $b['blockedHits'], self::DURATION_FOREVER, json_encode($parameters))) !== false;
		}
		
		return false;
	}
	
	/**
	 * Returns an array suitable for JSON output of all permanent blocks.
	 * 
	 * @return array
	 */
	public static function exportBlocks() {
		global $wpdb;
		$blocksTable = wfBlock::blocksTable();
		$query = "SELECT `type`, HEX(`IP`) AS `IP`, `blockedTime`, `reason`, `lastAttempt`, `blockedHits`, `parameters` FROM `{$blocksTable}` WHERE `expiration` = " . self::DURATION_FOREVER;
		$rows = $wpdb->get_results($query, ARRAY_A);
		return $rows;
	}
	
	/**
	 * Returns all unexpired blocks (including lockouts by default), optionally only of the specified types. These are sorted descending by the time created.
	 * 
	 * @param bool $prefetch If true, the full data for the block is fetched rather than using lazy loading.
	 * @param array $ofTypes An optional array of block types to restrict the returned array of blocks to.
	 * @param int $offset The offset to start the result fetch at.
	 * @param int $limit The maximum number of results to return. -1 for all.
	 * @param string $sortColumn The column to sort by.
	 * @param string $sortDirection The direction to sort.
	 * @param string $filter An optional value to filter by.
	 * @return wfBlock[]
	 */
	public static function allBlocks($prefetch = false, $ofTypes = array(), $offset = 0, $limit = -1, $sortColumn = 'type', $sortDirection = 'ascending', $filter = '') {
		global $wpdb;
		$blocksTable = wfBlock::blocksTable();
		$columns = '`id`';
		if ($prefetch) {
			$columns = '*';
		}
		
		$sort = 'typeSort';
		switch ($sortColumn) { //Match the display table column to the corresponding schema column
			case 'type':
				//Use default;
				break;
			case 'detail':
				$sort = 'detailSort';
				break;
			case 'ruleAdded':
				$sort = 'blockedTime';
				break;
			case 'reason':
				$sort = 'reason';
				break;
			case 'expiration':
				$sort = 'expiration';
				break;
			case 'blockCount':
				$sort = 'blockedHits';
				break;
			case 'lastAttempt':
				$sort = 'lastAttempt';
				break;
		}
		
		$order = 'ASC';
		if ($sortDirection == 'descending') {
			$order = 'DESC';
		}
		
		$query = "SELECT {$columns}, CASE 
WHEN `type` = " . self::TYPE_COUNTRY . " THEN 0
WHEN `type` = " . self::TYPE_PATTERN . " THEN 1
WHEN `type` = " . self::TYPE_LOCKOUT . " THEN 2
WHEN `type` = " . self::TYPE_RATE_THROTTLE . " THEN 3
WHEN `type` = " . self::TYPE_RATE_BLOCK . " THEN 4
WHEN `type` = " . self::TYPE_IP_AUTOMATIC_PERMANENT . " THEN 5
WHEN `type` = " . self::TYPE_IP_AUTOMATIC_TEMPORARY . " THEN 6
WHEN `type` = " . self::TYPE_WFSN_TEMPORARY . " THEN 7
WHEN `type` = " . self::TYPE_IP_MANUAL . " THEN 8
ELSE 9999
END AS `typeSort`, CASE 
WHEN `type` = " . self::TYPE_COUNTRY . " THEN `parameters`
WHEN `type` = " . self::TYPE_PATTERN . " THEN `parameters`
WHEN `type` = " . self::TYPE_IP_MANUAL . " THEN `IP`
WHEN `type` = " . self::TYPE_IP_AUTOMATIC_PERMANENT . " THEN `IP`
WHEN `type` = " . self::TYPE_RATE_BLOCK . " THEN `IP`
WHEN `type` = " . self::TYPE_RATE_THROTTLE . " THEN `IP`
WHEN `type` = " . self::TYPE_LOCKOUT . " THEN `IP`
WHEN `type` = " . self::TYPE_WFSN_TEMPORARY . " THEN `IP`
WHEN `type` = " . self::TYPE_IP_AUTOMATIC_TEMPORARY . " THEN `IP`
ELSE 9999
END AS `detailSort`
 FROM `{$blocksTable}` WHERE ";
		if (!empty($ofTypes)) {
			$sanitizedTypes = array_map('intval', $ofTypes);
			$query .= "`type` IN (" . implode(', ', $sanitizedTypes) . ') AND ';
		}
		$query .= '(`expiration` = ' . self::DURATION_FOREVER . " OR `expiration` > UNIX_TIMESTAMP()) ORDER BY `{$sort}` {$order}, `id` DESC";
		
		if ($limit > -1) {
			$offset = (int) $offset;
			$limit = (int) $limit;
			$query .= " LIMIT {$offset},{$limit}";
		}
		
		$rows = $wpdb->get_results($query, ARRAY_A);
		$result = array();
		foreach ($rows as $r) {
			if ($prefetch) {
				if ($r['type'] == self::TYPE_COUNTRY || $r['type'] == self::TYPE_PATTERN) {
					$ip = null;
				}
				else {
					$ip = wfUtils::inet_ntop($r['IP']);
				}
				
				$parameters = null;
				if ($r['type'] == self::TYPE_PATTERN || $r['type'] == self::TYPE_COUNTRY) {
					$parameters = @json_decode($r['parameters'], true);
					if (!is_array($parameters)) { $parameters = array(); }
				}
				
				$result[] = new wfBlock($r['id'], $r['type'], $ip, $r['blockedTime'], $r['reason'], $r['lastAttempt'], $r['blockedHits'], $r['expiration'], $parameters);
			}
			else {
				$result[] = new wfBlock($r['id']);
			}
		}
		
		return $result;
	}
	
	/**
	 * Functions identically to wfBlock::allBlocks except that it filters the result. The filtering is done within PHP rather than MySQL, so this will impose a performance penalty and should only
	 * be used when filtering is actually wanted.
	 * 
	 * @param bool $prefetch
	 * @param array $ofTypes
	 * @param int $offset
	 * @param int $limit
	 * @param string $sortColumn
	 * @param string $sortDirection
	 * @param string $filter
	 * @return wfBlock[]
	 */
	public static function filteredBlocks($prefetch = false, $ofTypes = array(), $offset = 0, $limit = -1, $sortColumn = 'type', $sortDirection = 'ascending', $filter = '') {
		$filter = trim($filter);
		$matchType = '';
		$matchValue = '';
		if (empty($filter)) {
			return self::allBlocks($prefetch, $ofTypes, $offset, $limit, $sortColumn, $sortDirection);
		}
		else if (wfUtils::isValidIP($filter)) { //e.g., 4.5.6.7, ffe0::, ::0
			$matchType = 'ip';
			$matchValue = wfUtils::inet_ntop(wfUtils::inet_pton($filter));
		}
		
		if (empty($matchType) && preg_match('/^(?:[0-9]+|\*)\.(?:(?:[0-9]+|\*)\.(?!$))*(?:(?:[0-9]+|\*))?$/', trim($filter, '.'))) { //e.g., possible wildcard IPv4 like 4.5.*
			$components = explode('.', trim($filter, '.'));
			if (count($components) <= 4) {
				$components = array_pad($components, 4, '*');
				$matchType = 'ipregex';
				$matchValue = '^';
				foreach ($components as $c) {
					if (empty($c) || $c == '*') {
						$matchValue .= '\d+';
					}
					else {
						$matchValue .= (int) $c;
					}
					
					$matchValue .= '\.';
				}
				$matchValue = substr($matchValue, 0, -2);
				$matchValue .= '$';
			}
		}
		
		if (empty($matchType) && preg_match('/^(?:[0-9a-f]+\:)(?:[0-9a-f]+\:|\*){1,2}(?:[0-9a-f]+|\*)?$/i', $filter)) { //e.g., possible wildcard IPv6 like ffe0:*
			$components = explode(':', $filter);
			$matchType = 'ipregex';
			$matchValue = '^';
			for ($i = 0; $i < 4; $i++) {
				if (isset($components[$i])) {
					$matchValue .= strtoupper(str_pad(dechex($components[$i]), 4, '0', STR_PAD_LEFT));
				}
				else {
					$matchValue .= '[0-9a-f]{4}';
				}
				$matchValue .= ':';
			}
			$matchValue = substr($matchValue, 0, -1);
			$matchValue .= '$';
		}
		
		if (empty($matchType)) {
			$matchType = 'literal';
			$matchValue = $filter;
		}
		
		$offsetProcessed = 0;
		$limitProcessed = 0;
		
		$returnBlocks = array();
		for ($i = 0; true; $i += WORDFENCE_BLOCKED_IPS_PER_PAGE) {
			$blocks = wfBlock::allBlocks(true, $ofTypes, $i, WORDFENCE_BLOCKED_IPS_PER_PAGE, $sortColumn, $sortDirection);
			if (empty($blocks)) {
				break;
			}
			
			foreach ($blocks as $b) {
				$include = false;
				
				if (stripos($b->reason, $filter) !== false) {
					$include = true;
				}
				
				if (!$include && $b->type == self::TYPE_PATTERN) {
					if (stripos($b->hostname, $filter) !== false) { $include = true; }
					else if (stripos($b->userAgent, $filter) !== false) { $include = true; }
					else if (stripos($b->referrer, $filter) !== false) { $include = true; }
					else if (stripos($b->ipRange, $filter) !== false) { $include = true; }
				}
				
				if (!$include && stripos(self::nameForType($b->type), $filter) !== false) {
					$include = true;
				}
				
				if (!$include) {
					switch ($matchType) {
						case 'ip':
							if ($b->matchRequest($matchValue, '', '') != self::MATCH_NONE) {
								$include = true;
							}
							else if ($b->type == self::TYPE_LOCKOUT && wfUtils::inet_pton($matchValue) == wfUtils::inet_pton($b->ip)) {
								$include = true;
							}
							break;
						case 'ipregex':
							if (preg_match('/' . $matchValue . '/i', $b->ip)) {
								$include = true;
							}
							break;
						case 'literal':
							//Already checked above
							break;
					}
				}
				
				if ($include) {
					if ($offsetProcessed < $offset) { //Still searching for the start offset
						$offsetProcessed++;
						continue;
					}
					
					$returnBlocks[] = $b;
					$limitProcessed++;
				}
				
				if ($limit != -1 && $limitProcessed >= $limit) {
					return $returnBlocks;
				}
			}
		}
		
		return $returnBlocks;
	}
	
	/**
	 * Returns all unexpired blocks of types wfBlock::TYPE_IP_MANUAL, wfBlock::TYPE_IP_AUTOMATIC_TEMPORARY, wfBlock::TYPE_IP_AUTOMATIC_PERMANENT, wfBlock::TYPE_WFSN_TEMPORARY, wfBlock::TYPE_RATE_BLOCK, and wfBlock::TYPE_RATE_THROTTLE.
	 *
	 * @param bool $prefetch If true, the full data for the block is fetched rather than using lazy loading.
	 * @return wfBlock[]
	 */
	public static function ipBlocks($prefetch = false) {
		return self::allBlocks($prefetch, array(self::TYPE_IP_MANUAL, self::TYPE_IP_AUTOMATIC_TEMPORARY, self::TYPE_IP_AUTOMATIC_PERMANENT, self::TYPE_WFSN_TEMPORARY, self::TYPE_RATE_BLOCK, self::TYPE_RATE_THROTTLE));
	}
	
	/**
	 * Finds an IP block matching the given IP, returning it if found. Returns false if none are found.
	 * 
	 * @param string $ip
	 * @return bool|wfBlock
	 */
	public static function findIPBlock($ip) {
		global $wpdb;
		$blocksTable = wfBlock::blocksTable();
		
		$ipHex = wfDB::binaryValueToSQLHex(wfUtils::inet_pton($ip));
		
		$query = "SELECT * FROM `{$blocksTable}` WHERE ";
		
		$ofTypes = array(self::TYPE_IP_MANUAL, self::TYPE_IP_AUTOMATIC_TEMPORARY, self::TYPE_IP_AUTOMATIC_PERMANENT, self::TYPE_WFSN_TEMPORARY, self::TYPE_RATE_BLOCK, self::TYPE_RATE_THROTTLE);
		$query .= "`type` IN (" . implode(', ', $ofTypes) . ') AND ';
		$query .= "`IP` = {$ipHex} AND ";
		$query .= '(`expiration` = ' . self::DURATION_FOREVER . ' OR `expiration` > UNIX_TIMESTAMP()) ORDER BY `blockedTime` DESC LIMIT 1';
		
		$r = $wpdb->get_row($query, ARRAY_A);
		if (is_array($r)) {
			$ip = wfUtils::inet_ntop($r['IP']);
			return self::_recordFromRow($r);
		}
		return false;
	}
	
	/**
	 * Returns all unexpired blocks of type wfBlock::TYPE_COUNTRY.
	 *
	 * @param bool $prefetch If true, the full data for the block is fetched rather than using lazy loading.
	 * @return wfBlock[]
	 */
	public static function countryBlocks($prefetch = false) {
		return self::allBlocks($prefetch, array(self::TYPE_COUNTRY));
	}
	
	/**
	 * Returns whether or not there is a country block rule.
	 * 
	 * @return bool
	 */
	public static function hasCountryBlock() {
		$countryBlocks = self::countryBlocks();
		return !empty($countryBlocks);
	}
	
	/**
	 * Returns the value for the country blocking bypass cookie.
	 *
	 * @return string
	 */
	public static function countryBlockingBypassCookieValue() {
		$val = wfConfig::get('cbl_cookieVal', false);
		if (!$val) {
			$val = uniqid();
			wfConfig::set('cbl_cookieVal', $val);
		}
		return $val;
	}
	
	/**
	 * Returns all unexpired blocks of type wfBlock::TYPE_PATTERN.
	 * 
	 * @param bool $prefetch If true, the full data for the block is fetched rather than using lazy loading.
	 * @return wfBlock[]
	 */
	public static function patternBlocks($prefetch = false) {
		return self::allBlocks($prefetch, array(self::TYPE_PATTERN));
	}
	
	/**
	 * Returns all unexpired lockouts (type wfBlock::TYPE_LOCKOUT).
	 *
	 * @param bool $prefetch If true, the full data for the block is fetched rather than using lazy loading.
	 * @return wfBlock[]
	 */
	public static function lockouts($prefetch = false) {
		return self::allBlocks($prefetch, array(self::TYPE_LOCKOUT));
	}
	
	/**
	 * Returns the lockout record for the given IP if it exists.
	 * 
	 * @param string $ip
	 * @return bool|wfBlock
	 */
	public static function lockoutForIP($ip) {
		global $wpdb;
		$blocksTable = wfBlock::blocksTable();
		$ipHex = wfDB::binaryValueToSQLHex(wfUtils::inet_pton($ip));
		
		$row = $wpdb->get_row($wpdb->prepare("SELECT * FROM `{$blocksTable}` WHERE `IP` = {$ipHex} AND `type` = %d AND (`expiration` = %d OR `expiration` > UNIX_TIMESTAMP())",  self::TYPE_LOCKOUT, self::DURATION_FOREVER), ARRAY_A);
		if ($row) {
			return self::_recordFromRow($row);
		}
		
		return false;
	}
	
	/**
	 * Removes all blocks whose ID is in the given array.
	 * 
	 * @param array $blockIDs
	 * @param bool $retrieve if true, fetch and return the deleted rows
	 * @param bool $notify Whether or not to broadcast the deletion action (should only do when this is called in response to a manual action)
	 * @return bool|array true(or an array of blocks, if $retrieve is specified) or false on failure
	 */
	public static function removeBlockIDs($blockIDs, $retrieve = false, $notify = true) {
		$blockIDs = array_map('intval', $blockIDs);
		$blocks = self::_recordsFromRows($blockIDs);
		$result = self::removeMultiple($blocks, $notify);
		if ($retrieve && $result) {
			return $result;
		}
		
		return !!$result;
	}
	
	/**
	 * Removes all IP blocks (i.e., manual, wfsn, or rate limited)
	 *
	 * @param bool $notify Whether or not to broadcast the deletion action (should only do when this is called in response to a manual action)
	 */
	public static function removeAllIPBlocks($notify = true) {
		global $wpdb;
		$blocksTable = wfBlock::blocksTable();
		$rows = $wpdb->get_results("SELECT * FROM `{$blocksTable}` WHERE `type` IN (" . implode(', ', array(self::TYPE_IP_MANUAL, self::TYPE_IP_AUTOMATIC_TEMPORARY, self::TYPE_IP_AUTOMATIC_PERMANENT, self::TYPE_WFSN_TEMPORARY, self::TYPE_RATE_BLOCK, self::TYPE_RATE_THROTTLE, self::TYPE_LOCKOUT)) . ")", ARRAY_A);
		$blocks = self::_recordsFromRows($rows);
		self::removeMultiple($blocks, $notify);
	}
	
	/**
	 * Removes all country blocks
	 *
	 * @param bool $notify Whether or not to broadcast the deletion action (should only do when this is called in response to a manual action)
	 */
	public static function removeAllCountryBlocks($notify = true) {
		global $wpdb;
		$blocksTable = wfBlock::blocksTable();
		$rows = $wpdb->get_results("SELECT * FROM `{$blocksTable}` WHERE `type` IN (" . implode(', ', array(self::TYPE_COUNTRY)) . ")", ARRAY_A);
		$blocks = self::_recordsFromRows($rows);
		self::removeMultiple($blocks, $notify);
	}
	
	/**
	 * Removes all blocks that were created by WFSN responses.
	 * 
	 * Note: if this ever becomes called by a manual user action, it should be refactored to call 
	 * self::removeMultiple() in order to dispatch the relevant event.
	 */
	public static function removeTemporaryWFSNBlocks() {
		global $wpdb;
		$blocksTable = wfBlock::blocksTable();
		$wpdb->query($wpdb->prepare("DELETE FROM `{$blocksTable}` WHERE `type` = %d", self::TYPE_WFSN_TEMPORARY));
	}
	
	/**
	 * Converts all blocks to non-expiring whose ID is in the given array.
	 * 
	 * @param array $blockIDs
	 */
	public static function makePermanentBlockIDs($blockIDs) {
		global $wpdb;
		$blocksTable = wfBlock::blocksTable();
		
		//TODO: revise this if we support user-customizable durations
		$supportedTypes = array(
			self::TYPE_WFSN_TEMPORARY,
			self::TYPE_RATE_BLOCK,
			self::TYPE_RATE_THROTTLE,
			self::TYPE_LOCKOUT,
			self::TYPE_IP_AUTOMATIC_TEMPORARY,
		);
		
		$blockIDs = array_map('intval', $blockIDs);
		$query = $wpdb->prepare("UPDATE `{$blocksTable}` SET `expiration` = %d, `type` = %d WHERE `id` IN (" . implode(', ', $blockIDs) . ") AND `type` IN (" . implode(', ', $supportedTypes) . ") AND (`expiration` > UNIX_TIMESTAMP())", self::DURATION_FOREVER, self::TYPE_IP_AUTOMATIC_PERMANENT);
		$wpdb->query($query);
		
		$supportedTypes = array(
			self::TYPE_IP_MANUAL,
		);
		
		$blockIDs = array_map('intval', $blockIDs);
		$query = $wpdb->prepare("UPDATE `{$blocksTable}` SET `expiration` = %d, `type` = %d WHERE `id` IN (" . implode(', ', $blockIDs) . ") AND `type` IN (" . implode(', ', $supportedTypes) . ") AND (`expiration` > UNIX_TIMESTAMP())", self::DURATION_FOREVER, self::TYPE_IP_MANUAL);
		$wpdb->query($query);
	}
	
	/**
	 * Removes all specific IP blocks and lockouts that can result in the given IP being blocked.
	 * 
	 * @param string $ip
	 * @param bool $notify Whether or not to broadcast the deletion action (should only do when this is called in response to a manual action)
	 */
	public static function unblockIP($ip, $notify = true) {
		global $wpdb;
		$blocksTable = wfBlock::blocksTable();
		$ipHex = wfDB::binaryValueToSQLHex(wfUtils::inet_pton($ip));
		$rows = $wpdb->get_results("SELECT * FROM `{$blocksTable}` WHERE `IP` = {$ipHex}", ARRAY_A);
		$blocks = self::_recordsFromRows($rows);
		self::removeMultiple($blocks, $notify);
	}
	
	/**
	 * Removes all lockouts that can result in the given IP being blocked.
	 *
	 * @param string $ip
	 * @param bool $notify Whether or not to broadcast the deletion action (should only do when this is called in response to a manual action)
	 */
	public static function unlockOutIP($ip, $notify = true) {
		global $wpdb;
		$blocksTable = wfBlock::blocksTable();
		$ipHex = wfDB::binaryValueToSQLHex(wfUtils::inet_pton($ip));
		$rows = $wpdb->get_results($wpdb->prepare("SELECT * FROM `{$blocksTable}` WHERE `IP` = {$ipHex} AND `type` = %d", self::TYPE_LOCKOUT), ARRAY_A);
		$blocks = self::_recordsFromRows($rows);
		self::removeMultiple($blocks, $notify);
	}
	
	/**
	 * Internal function to convert a raw query row result into a populated wfBlock instance. $row is expected to be an
	 * associative array.
	 * 
	 * @param array $row
	 * @return mixed
	 */
	private static function _recordFromRow($row) {
		$records = self::_recordsFromRows(array($row));
		return $records[0];
	}
	
	/**
	 * Internal function to convert an array of raw query row results to an array of populated wfBlock instances. $rows
	 * is expected to be an array of integer IDs or an array of associative arrays.
	 * 
	 * @param array[]|int[] $rows
	 * @return array
	 */
	private static function _recordsFromRows($rows) {
		$records = array();
		foreach ($rows as $r) {
			if ($r instanceof stdClass) {
				$r = (array) $r;
			}
			
			if (is_array($r)) {
				$b = new wfBlock($r['id']);
				$b->_populateData($r);
			}
			else {
				$b = new wfBlock($r);
			}
			$records[] = $b;
		}
		return $records;
	}
	
	/**
	 * Constructs a wfBlock instance. This _does not_ create a new record in the table, only fetches or updates an existing one.
	 * 
	 * @param $id
	 * @param bool $type
	 * @param bool $ip
	 * @param bool $blockedTime
	 * @param bool $reason
	 * @param bool $lastAttempt
	 * @param bool $blockedHits
	 * @param bool $expiration
	 * @param bool $parameters
	 */
	public function __construct($id, $type = false, $ip = false, $blockedTime = false, $reason = false, $lastAttempt = false, $blockedHits = false, $expiration = false, $parameters = false) {
		$this->_id = $id;
		$this->_type = $type;
		$this->_ip = $ip;
		$this->_blockedTime = $blockedTime;
		$this->_reason = $reason;
		$this->_lastAttempt = $lastAttempt;
		$this->_blockedHits = $blockedHits;
		$this->_expiration = $expiration;
		$this->_parameters = $parameters;
	}
	
	public function __get($key) {
		switch ($key) {
			case 'id':
				return $this->_id;
			case 'type':
				if ($this->_type === false) { $this->_fetch(); }
				return $this->_type;
			case 'IP':
			case 'ip':
				if ($this->_type === false) { $this->_fetch(); }
				return $this->_ip;
			case 'blockedTime':
				if ($this->_type === false) { $this->_fetch(); }
				return $this->_blockedTime;
			case 'reason':
				if ($this->_type === false) { $this->_fetch(); }
				return $this->_reason;
			case 'lastAttempt':
				if ($this->_type === false) { $this->_fetch(); }
				return $this->_lastAttempt;
			case 'blockedHits':
				if ($this->_type === false) { $this->_fetch(); }
				return $this->_blockedHits;
			case 'expiration':
				if ($this->_type === false) { $this->_fetch(); }
				return $this->_expiration;
			case 'parameters':
				if ($this->_type === false) { $this->_fetch(); }
				return $this->_parameters;
				
			//Country
			case 'blockLogin':
				if ($this->type != self::TYPE_COUNTRY) { throw new OutOfBoundsException("{$key} is not a valid property for this block type"); }
				return $this->parameters['blockLogin'];
			case 'blockSite':
				if ($this->type != self::TYPE_COUNTRY) { throw new OutOfBoundsException("{$key} is not a valid property for this block type"); }
				return $this->parameters['blockSite'];
			case 'countries':
				if ($this->type != self::TYPE_COUNTRY) { throw new OutOfBoundsException("{$key} is not a valid property for this block type"); }
				return $this->parameters['countries'];
				
			//Pattern
			case 'ipRange':
				if ($this->type != self::TYPE_PATTERN) { throw new OutOfBoundsException("{$key} is not a valid property for this block type"); }
				return $this->parameters['ipRange'];
			case 'hostname':
				if ($this->type != self::TYPE_PATTERN) { throw new OutOfBoundsException("{$key} is not a valid property for this block type"); }
				return $this->parameters['hostname'];
			case 'userAgent':
				if ($this->type != self::TYPE_PATTERN) { throw new OutOfBoundsException("{$key} is not a valid property for this block type"); }
				return $this->parameters['userAgent'];
			case 'referrer':
				if ($this->type != self::TYPE_PATTERN) { throw new OutOfBoundsException("{$key} is not a valid property for this block type"); }
				return $this->parameters['referrer'];
		}
		
		throw new OutOfBoundsException("{$key} is not a valid property");
	}
	
	public function __isset($key) {
		switch ($key) {
			case 'id':
			case 'type':
			case 'ip':
			case 'blockedTime':
			case 'reason':
			case 'lastAttempt':
			case 'blockedHits':
			case 'expiration':
				return true;
			case 'parameters':
				if ($this->_type === false) { $this->_fetch(); }
				return !empty($this->_parameters);
			
			//Country
			case 'blockLogin':
				if ($this->type != self::TYPE_COUNTRY) { return false; }
				return !empty($this->parameters['blockLogin']);
			case 'blockSite':
				if ($this->type != self::TYPE_COUNTRY) { return false; }
				return !empty($this->parameters['blockSite']);
			case 'countries':
				if ($this->type != self::TYPE_COUNTRY) { return false; }
				return !empty($this->parameters['countries']);
			
			//Pattern
			case 'ipRange':
				if ($this->type != self::TYPE_PATTERN) { return false; }
				return !empty($this->parameters['ipRange']);
			case 'hostname':
				if ($this->type != self::TYPE_PATTERN) { return false; }
				return !empty($this->parameters['hostname']);
			case 'userAgent':
				if ($this->type != self::TYPE_PATTERN) { return false; }
				return !empty($this->parameters['userAgent']);
			case 'referrer':
				if ($this->type != self::TYPE_PATTERN) { return false; }
				return !empty($this->parameters['referrer']);
		}
		
		return false;
	}
	
	/**
	 * Fetches the record for the block from the database and populates the instance variables.
	 */
	private function _fetch() {
		global $wpdb;
		$blocksTable = wfBlock::blocksTable();
		$row = $wpdb->get_row($wpdb->prepare("SELECT * FROM `{$blocksTable}` WHERE `id` = %d", $this->id), ARRAY_A);
		if ($row !== null) {
			$this->_populateData($row);
		}
	}
	
	/**
	 * Populates the instance data from the given $row.
	 * 
	 * @param array $row
	 */
	private function _populateData($row) {
		$this->_type = $row['type'];
		
		$ip = $row['IP'];
		if ($ip == self::MARKER_COUNTRY || $ip == self::MARKER_PATTERN) {
			$this->_ip = null;
		}
		else {
			$this->_ip = wfUtils::inet_ntop($ip);
		}
		
		$this->_blockedTime = $row['blockedTime'];
		$this->_reason = $row['reason'];
		$this->_lastAttempt = $row['lastAttempt'];
		$this->_blockedHits = $row['blockedHits'];
		$this->_expiration = $row['expiration'];
		
		$parameters = $row['parameters'];
		if ($parameters === null) {
			$this->_parameters = null;
		}
		else {
			$this->_parameters = @json_decode($parameters, true);
			if (!is_array($this->_parameters)) { $this->_parameters = array(); }
		}
	}
	
	/**
	 * Tests the block parameters against the given request. If matched, this will return the corresponding wfBlock::MATCH_
	 * constant. If not, it will return wfBlock::MATCH_NONE.
	 * 
	 * @param $ip
	 * @param $userAgent
	 * @param $referrer
	 * @return int
	 */
	public function matchRequest($ip, $userAgent, $referrer) {
		switch ($this->type) {
			case self::TYPE_IP_MANUAL:
			case self::TYPE_IP_AUTOMATIC_TEMPORARY:
			case self::TYPE_IP_AUTOMATIC_PERMANENT:
			case self::TYPE_WFSN_TEMPORARY:
			case self::TYPE_RATE_BLOCK:
			case self::TYPE_RATE_THROTTLE:
				if (wfUtils::inet_pton($ip) == wfUtils::inet_pton($this->ip))
				{
					return self::MATCH_IP;
				}
				break;
			case self::TYPE_PATTERN:
				$match = (!empty($this->ipRange) || !empty($this->hostname) || !empty($this->userAgent) || !empty($this->referrer));
				if (!empty($this->ipRange)) {
					$range = new wfUserIPRange($this->ipRange);
					$match = $match && $range->isIPInRange($ip);
				}
				if (!empty($this->hostname)) {
					$hostname = wfUtils::reverseLookup($ip);
					$match = $match && preg_match(wfUtils::patternToRegex($this->hostname), $hostname);
				}
				if (!empty($this->userAgent)) {
					$match = $match && fnmatch($this->userAgent, $userAgent, FNM_CASEFOLD);
				}
				if (!empty($this->referrer)) {
					$match = $match && fnmatch($this->referrer, $referrer, FNM_CASEFOLD);
				}
				
				if ($match) {
					return self::MATCH_PATTERN;
				}
				
				break;
			case self::TYPE_COUNTRY:
				if (!wfConfig::get('isPaid')) {
					return self::MATCH_NONE;
				}
				
				//Bypass Redirect URL Hit
				$bareRequestURI = wfUtils::extractBareURI($_SERVER['REQUEST_URI']);
				$bareBypassRedirURI = wfUtils::extractBareURI(wfConfig::get('cbl_bypassRedirURL', ''));
				if ($bareBypassRedirURI && $bareRequestURI == $bareBypassRedirURI) {
					$bypassRedirDest = wfConfig::get('cbl_bypassRedirDest', '');
					if ($bypassRedirDest) {
						wfUtils::setcookie('wfCBLBypass', wfBlock::countryBlockingBypassCookieValue(), time() + (86400 * 365), '/', null, wfUtils::isFullSSL(), true);
						return self::MATCH_COUNTRY_REDIR_BYPASS;
					}
				}
				
				//Bypass View URL Hit
				$bareBypassViewURI = wfUtils::extractBareURI(wfConfig::get('cbl_bypassViewURL', ''));
				if ($bareBypassViewURI && $bareBypassViewURI == $bareRequestURI) {
					wfUtils::setcookie('wfCBLBypass', wfBlock::countryBlockingBypassCookieValue(), time() + (86400 * 365), '/', null, wfUtils::isFullSSL(), true);
					return self::MATCH_NONE;
				}
				
				//Early exit checks
				if ($this->_shouldBypassCountryBlocking()) { //Has valid bypass cookie
					return self::MATCH_NONE;
				}
				
				if ($this->blockLogin) {
					add_filter('authenticate', array($this, '_checkForBlockedCountryFilter'), 1, 1);
				}
				
				if (!$this->blockLogin && $this->_isAuthRequest()) { //Not blocking login and this is a login request
					return self::MATCH_NONE;
				}
				else if (!$this->blockSite && !$this->_isAuthRequest()) { //Not blocking site and this may be a site request
					return self::MATCH_NONE;
				}
				else if (is_user_logged_in() && !wfConfig::get('cbl_loggedInBlocked', false)) { //Not blocking logged in users and a login session exists
					return self::MATCH_NONE;
				}
				
				//Block everything
				if ($this->blockSite && $this->blockLogin) {
					return $this->_checkForBlockedCountry();
				}
				
				//Block the login form itself and any attempt to authenticate
				if ($this->blockLogin && $this->_isAuthRequest()) {
					return $this->_checkForBlockedCountry();
				}
				
				//Block requests that aren't to the login page, xmlrpc.php, or a user already logged in
				if ($this->blockSite && !$this->_isAuthRequest() && !defined('XMLRPC_REQUEST')) {
					return $this->_checkForBlockedCountry();
				}
				
				//XMLRPC is inaccesible when public portion of the site and auth is disabled
				if ($this->blockLogin && $this->blockSite && defined('XMLRPC_REQUEST')) {
					return $this->_checkForBlockedCountry();
				}
				
				break;
		}
		
		return self::MATCH_NONE;
	}
	
	/**
	 * Returns whether or not the current request should be treated as an auth request.
	 * 
	 * @return bool
	 */
	private function _isAuthRequest() {
		if ((strpos($_SERVER['REQUEST_URI'], '/wp-login.php') !== false)) {
			return true;
		}
		return false;
	}
	
	/**
	 * Tests whether or not the country blocking bypass cookie is set and valid.
	 * 
	 * @return bool
	 */
	private function _shouldBypassCountryBlocking() {
		if (isset($_COOKIE['wfCBLBypass']) && $_COOKIE['wfCBLBypass'] == wfBlock::countryBlockingBypassCookieValue()) {
			return true;
		}
		return false;
	}
	
	/**
	 * Checks the country block against the requesting IP, returning the action to take.
	 * 
	 * @return int
	 */
	private function _checkForBlockedCountry() {
		$blockedCountries = $this->countries;
		$bareRequestURI = untrailingslashit(wfUtils::extractBareURI($_SERVER['REQUEST_URI']));
		$IP = wfUtils::getIP();
		if ($country = wfUtils::IP2Country($IP)) {
			foreach ($blockedCountries as $blocked) {
				if (strtoupper($blocked) == strtoupper($country)) { //At this point we know the user has been blocked
					if (wfConfig::get('cbl_action') == 'redir') {
						$redirURL = wfConfig::get('cbl_redirURL');
						$eRedirHost = wfUtils::extractHostname($redirURL);
						$isExternalRedir = false;
						if ($eRedirHost && $eRedirHost != wfUtils::extractHostname(home_url())) { //It's an external redirect...
							$isExternalRedir = true;
						}
						
						if ((!$isExternalRedir) && untrailingslashit(wfUtils::extractBareURI($redirURL)) == $bareRequestURI) { //Is this the URI we want to redirect to, then don't block it
							return self::MATCH_NONE;
						}
						else {
							return self::MATCH_COUNTRY_REDIR;
						}
					}
					else {
						return self::MATCH_COUNTRY_BLOCK;
					}
				}
			}
		}
		
		return self::MATCH_NONE;
	}
	
	/**
	 * Filter hook for the country blocking check. Does nothing if not blocked, otherwise presents the block page and exits.
	 * 
	 * Note: Must remain `public` for callback to work.
	 */
	public function _checkForBlockedCountryFilter($user) {
		$block = $this->_checkForBlockedCountry();
		if ($block == self::MATCH_NONE) { 
			return $user;
		}
		
		$log = wfLog::shared();
		$log->getCurrentRequest()->actionDescription = __('blocked access via country blocking', 'wordfence');
		wfConfig::inc('totalCountryBlocked');
		wfActivityReport::logBlockedIP(wfUtils::getIP(), null, 'country');
		$log->do503(3600, __('Access from your area has been temporarily limited for security reasons', 'wordfence')); //exits
	}
	
	/**
	 * Adds $quantity to the blocked count and sets the timestamp for lastAttempt.
	 * 
	 * @param int $quantity
	 * @param bool|int $timestamp
	 */
	public function recordBlock($quantity = 1, $timestamp = false) {
		if ($timestamp === false) {
			$timestamp = time();
		}
		
		global $wpdb;
		$blocksTable = wfBlock::blocksTable();
		$wpdb->query($wpdb->prepare("UPDATE `{$blocksTable}` SET `blockedHits` = `blockedHits` + %d, `lastAttempt` = GREATEST(`lastAttempt`, %d) WHERE `id` = %d", $quantity, $timestamp, $this->id));
		$this->_type = false; //Trigger a re-fetch next access 
	}
	
	/**
	 * Returns an array suitable for JSON of the values needed to edit the block.
	 * 
	 * @return array
	 */
	public function editValues() {
		switch ($this->type) {
			case self::TYPE_COUNTRY:
				return array(
					'blockLogin' => wfUtils::truthyToInt($this->blockLogin),
					'blockSite' => wfUtils::truthyToInt($this->blockSite),
					'countries' => $this->countries,
					'reason' => $this->reason,
					'expiration' => $this->expiration,
				);
		}
		
		return array();
	}
	
	/**
	 * Removes this block record. May trigger an additional query to fetch the notification data if $notify is true and
	 * the record is ID-only.
	 * 
	 * @param bool $notify If true, will dispatch the `wordfence_deleted_block` action.
	 * @return null|wfBlock null if a failure occurs, otherwise the block
	 */
	public function remove($notify = false) {
		$result = self::removeMultiple(array($this), $notify);
		if (is_array($result)) {
			return $result[0];
		}
		return null;
	}
	
	/**
	 * Deletes the given block, optionally dispatching the `wordfence_deleted_block` action for each block. May trigger 
	 * an additional query to fetch the notification data if $notify is true and any record is ID-only.
	 * 
	 * @param wfBlock[] $blocks
	 * @param bool $notify If true, will dispatch the `wordfence_deleted_block` action.
	 * @return null|wfBlock[] null if a failure occurs, otherwise the blocks
	 */
	public static function removeMultiple($blocks, $notify = false) {
		if (empty($blocks)) { return array(); }
		
		global $wpdb;
		$blocksTable = wfBlock::blocksTable();
		
		$blockIDs = array_map(function($b) { return intval($b->id); }, $blocks);
		$inClause = implode(', ', $blockIDs);
		
		if ($notify) {
			$blockIDsToPopulate = array_filter(array_map(function($b) { return ($b->_type === false ? intval($b->id) : null); }, $blocks));
			if (!empty($blockIDsToPopulate)) {
				$populateInClause = implode(', ', $blockIDsToPopulate);
				$data = wfUtils::array_kmap(function($r) { return array($r['id'] => $r); }, $wpdb->get_results("SELECT * FROM `{$blocksTable}` WHERE `id` IN ({$populateInClause})", ARRAY_A));
				foreach ($blocks as $b) { /** @var wfBlock $b */
					if (isset($data[$b->id])) {
						$b->_populateData($data[$b->id]);
					}
				}
			}
		}
		
		$query = "DELETE FROM `{$blocksTable}` WHERE `id` IN (" . $inClause . ")";
		if ($wpdb->query($query) !== false) {
			$payload = array();
			if ($notify) {
				foreach ($blocks as $b) {
					$type = $b->type;
					$reason = $b->reason;
					$parameters = (($type != self::TYPE_COUNTRY && $type != self::TYPE_PATTERN) ? $b->ip : $b->parameters);
					
					/**
					 * Fires when a blocking rule is deleted by manual action.
					 *
					 * @since 8.0.0
					 *
					 * @param string $type The type of block.
					 * @param string $reason The reason of the block.
					 * @param array|null $parameters The parameters of the block if needed for disambiguation (e.g., the country block returns null because there is only one rule at most)
					 */
					do_action('wordfence_deleted_block', $type, $reason, $parameters);
				}
			}
			
			return $blocks;
		}
		
		return null;
	}
}