PNG  IHDRQgAMA a cHRMz&u0`:pQ<bKGDgmIDATxwUﹻ& ^CX(J I@ "% (** BX +*i"]j(IH{~R)[~>h{}gy)I$Ij .I$I$ʊy@}x.: $I$Ii}VZPC)I$IF ^0ʐJ$I$Q^}{"r=OzI$gRZeC.IOvH eKX $IMpxsk.쒷/&r[޳<v| .I~)@$updYRa$I |M.e JaֶpSYR6j>h%IRز if&uJ)M$I vLi=H;7UJ,],X$I1AҒJ$ XY XzI@GNҥRT)E@;]K*Mw;#5_wOn~\ DC&$(A5 RRFkvIR}l!RytRl;~^ǷJj اy뷦BZJr&ӥ8Pjw~vnv X^(I;4R=P[3]J,]ȏ~:3?[ a&e)`e*P[4]T=Cq6R[ ~ޤrXR Հg(t_HZ-Hg M$ãmL5R uk*`%C-E6/%[t X.{8P9Z.vkXŐKjgKZHg(aK9ڦmKjѺm_ \#$5,)-  61eJ,5m| r'= &ڡd%-]J on Xm|{ RҞe $eڧY XYrԮ-a7RK6h>n$5AVڴi*ֆK)mѦtmr1p| q:흺,)Oi*ֺK)ܬ֦K-5r3>0ԔHjJئEZj,%re~/z%jVMڸmrt)3]J,T K֦OvԒgii*bKiNO~%PW0=dii2tJ9Jݕ{7"I P9JKTbu,%r"6RKU}Ij2HKZXJ,妝 XYrP ެ24c%i^IK|.H,%rb:XRl1X4Pe/`x&P8Pj28Mzsx2r\zRPz4J}yP[g=L) .Q[6RjWgp FIH*-`IMRaK9TXcq*I y[jE>cw%gLRԕiFCj-ďa`#e~I j,%r,)?[gp FI˨mnWX#>mʔ XA DZf9,nKҲzIZXJ,L#kiPz4JZF,I,`61%2s $,VOϚ2/UFJfy7K> X+6 STXIeJILzMfKm LRaK9%|4p9LwJI!`NsiazĔ)%- XMq>pk$-$Q2x#N ؎-QR}ᶦHZډ)J,l#i@yn3LN`;nڔ XuX5pF)m|^0(>BHF9(cզEerJI rg7 4I@z0\JIi䵙RR0s;$s6eJ,`n 䂦0a)S)A 1eJ,堌#635RIgpNHuTH_SԕqVe ` &S)>p;S$魁eKIuX`I4춒o}`m$1":PI<[v9^\pTJjriRŭ P{#{R2,`)e-`mgj~1ϣLKam7&U\j/3mJ,`F;M'䱀 .KR#)yhTq;pcK9(q!w?uRR,n.yw*UXj#\]ɱ(qv2=RqfB#iJmmL<]Y͙#$5 uTU7ӦXR+q,`I}qL'`6Kͷ6r,]0S$- [RKR3oiRE|nӦXR.(i:LDLTJjY%o:)6rxzҒqTJjh㞦I.$YR.ʼnGZ\ֿf:%55 I˼!6dKxm4E"mG_ s? .e*?LRfK9%q#uh$)i3ULRfK9yxm܌bj84$i1U^@Wbm4uJ,ҪA>_Ij?1v32[gLRD96oTaR׿N7%L2 NT,`)7&ƝL*꽙yp_$M2#AS,`)7$rkTA29_Iye"|/0t)$n XT2`YJ;6Jx".e<`$) PI$5V4]29SRI>~=@j]lp2`K9Jaai^" Ԋ29ORI%:XV5]JmN9]H;1UC39NI%Xe78t)a;Oi Ҙ>Xt"~G>_mn:%|~ޅ_+]$o)@ǀ{hgN;IK6G&rp)T2i୦KJuv*T=TOSV>(~D>dm,I*Ɛ:R#ۙNI%D>G.n$o;+#RR!.eU˽TRI28t)1LWϚ>IJa3oFbu&:tJ*(F7y0ZR ^p'Ii L24x| XRI%ۄ>S1]Jy[zL$adB7.eh4%%누>WETf+3IR:I3Xה)3אOۦSRO'ٺ)S}"qOr[B7ϙ.edG)^ETR"RtRݜh0}LFVӦDB^k_JDj\=LS(Iv─aTeZ%eUAM-0;~˃@i|l @S4y72>sX-vA}ϛBI!ݎߨWl*)3{'Y|iSlEڻ(5KtSI$Uv02,~ԩ~x;P4ցCrO%tyn425:KMlD ^4JRxSهF_}شJTS6uj+ﷸk$eZO%G*^V2u3EMj3k%)okI]dT)URKDS 7~m@TJR~荪fT"֛L \sM -0T KfJz+nإKr L&j()[E&I ߴ>e FW_kJR|!O:5/2跌3T-'|zX ryp0JS ~^F>-2< `*%ZFP)bSn"L :)+pʷf(pO3TMW$~>@~ū:TAIsV1}S2<%ޟM?@iT ,Eūoz%i~g|`wS(]oȤ8)$ ntu`өe`6yPl IzMI{ʣzʨ )IZ2= ld:5+請M$-ї;U>_gsY$ÁN5WzWfIZ)-yuXIfp~S*IZdt;t>KūKR|$#LcԀ+2\;kJ`]YǔM1B)UbG"IRߊ<xܾӔJ0Z='Y嵤 Leveg)$znV-º^3Ւof#0Tfk^Zs[*I꯳3{)ˬW4Ւ4 OdpbZRS|*I 55#"&-IvT&/윚Ye:i$ 9{LkuRe[I~_\ؠ%>GL$iY8 9ܕ"S`kS.IlC;Ҏ4x&>u_0JLr<J2(^$5L s=MgV ~,Iju> 7r2)^=G$1:3G< `J3~&IR% 6Tx/rIj3O< ʔ&#f_yXJiގNSz; Tx(i8%#4 ~AS+IjerIUrIj362v885+IjAhK__5X%nV%Iͳ-y|7XV2v4fzo_68"S/I-qbf; LkF)KSM$ Ms>K WNV}^`-큧32ŒVؙGdu,^^m%6~Nn&͓3ŒVZMsRpfEW%IwdǀLm[7W&bIRL@Q|)* i ImsIMmKmyV`i$G+R 0tV'!V)֏28vU7͒vHꦼtxꗞT ;S}7Mf+fIRHNZUkUx5SAJㄌ9MqμAIRi|j5)o*^'<$TwI1hEU^c_j?Е$%d`z cyf,XO IJnTgA UXRD }{H}^S,P5V2\Xx`pZ|Yk:$e ~ @nWL.j+ϝYb퇪bZ BVu)u/IJ_ 1[p.p60bC >|X91P:N\!5qUB}5a5ja `ubcVxYt1N0Zzl4]7­gKj]?4ϻ *[bg$)+À*x쳀ogO$~,5 زUS9 lq3+5mgw@np1sso Ӻ=|N6 /g(Wv7U;zωM=wk,0uTg_`_P`uz?2yI!b`kĸSo+Qx%!\οe|އԁKS-s6pu_(ֿ$i++T8=eY; צP+phxWQv*|p1. ά. XRkIQYP,drZ | B%wP|S5`~́@i޾ E;Չaw{o'Q?%iL{u D?N1BD!owPHReFZ* k_-~{E9b-~P`fE{AܶBJAFO wx6Rox5 K5=WwehS8 (JClJ~ p+Fi;ŗo+:bD#g(C"wA^ r.F8L;dzdIHUX݆ϞXg )IFqem%I4dj&ppT{'{HOx( Rk6^C٫O.)3:s(۳(Z?~ٻ89zmT"PLtw䥈5&b<8GZ-Y&K?e8,`I6e(֍xb83 `rzXj)F=l($Ij 2*(F?h(/9ik:I`m#p3MgLaKjc/U#n5S# m(^)=y=đx8ŬI[U]~SцA4p$-F i(R,7Cx;X=cI>{Km\ o(Tv2vx2qiiDJN,Ҏ!1f 5quBj1!8 rDFd(!WQl,gSkL1Bxg''՞^ǘ;pQ P(c_ IRujg(Wz bs#P­rz> k c&nB=q+ؔXn#r5)co*Ũ+G?7< |PQӣ'G`uOd>%Mctz# Ԫڞ&7CaQ~N'-P.W`Oedp03C!IZcIAMPUۀ5J<\u~+{9(FbbyAeBhOSܳ1 bÈT#ŠyDžs,`5}DC-`̞%r&ڙa87QWWp6e7 Rϫ/oY ꇅ Nܶըtc!LA T7V4Jsū I-0Pxz7QNF_iZgúWkG83 0eWr9 X]㾮݁#Jˢ C}0=3ݱtBi]_ &{{[/o[~ \q鯜00٩|cD3=4B_b RYb$óBRsf&lLX#M*C_L܄:gx)WΘsGSbuL rF$9';\4Ɍq'n[%p.Q`u hNb`eCQyQ|l_C>Lb꟟3hSb #xNxSs^ 88|Mz)}:](vbۢamŖ࿥ 0)Q7@0=?^k(*J}3ibkFn HjB׻NO z x}7p 0tfDX.lwgȔhԾŲ }6g E |LkLZteu+=q\Iv0쮑)QٵpH8/2?Σo>Jvppho~f>%bMM}\//":PTc(v9v!gոQ )UfVG+! 35{=x\2+ki,y$~A1iC6#)vC5^>+gǵ@1Hy٪7u;p psϰu/S <aʸGu'tD1ԝI<pg|6j'p:tպhX{o(7v],*}6a_ wXRk,O]Lܳ~Vo45rp"N5k;m{rZbΦ${#)`(Ŵg,;j%6j.pyYT?}-kBDc3qA`NWQū20/^AZW%NQ MI.X#P#,^Ebc&?XR tAV|Y.1!؅⨉ccww>ivl(JT~ u`ٵDm q)+Ri x/x8cyFO!/*!/&,7<.N,YDŽ&ܑQF1Bz)FPʛ?5d 6`kQձ λc؎%582Y&nD_$Je4>a?! ͨ|ȎWZSsv8 j(I&yj Jb5m?HWp=g}G3#|I,5v珿] H~R3@B[☉9Ox~oMy=J;xUVoj bUsl_35t-(ՃɼRB7U!qc+x4H_Qo֮$[GO<4`&č\GOc[.[*Af%mG/ ňM/r W/Nw~B1U3J?P&Y )`ѓZ1p]^l“W#)lWZilUQu`-m|xĐ,_ƪ|9i:_{*(3Gѧ}UoD+>m_?VPۅ15&}2|/pIOʵ> GZ9cmíتmnz)yߐbD >e}:) r|@R5qVSA10C%E_'^8cR7O;6[eKePGϦX7jb}OTGO^jn*媓7nGMC t,k31Rb (vyܴʭ!iTh8~ZYZp(qsRL ?b}cŨʊGO^!rPJO15MJ[c&~Z`"ѓޔH1C&^|Ш|rʼ,AwĴ?b5)tLU)F| &g٣O]oqSUjy(x<Ϳ3 .FSkoYg2 \_#wj{u'rQ>o;%n|F*O_L"e9umDds?.fuuQbIWz |4\0 sb;OvxOSs; G%T4gFRurj(֍ڑb uԖKDu1MK{1^ q; C=6\8FR艇!%\YÔU| 88m)֓NcLve C6z;o&X x59:q61Z(T7>C?gcļxѐ Z oo-08jہ x,`' ҔOcRlf~`jj".Nv+sM_]Zk g( UOPyεx%pUh2(@il0ݽQXxppx-NS( WO+轾 nFߢ3M<;z)FBZjciu/QoF 7R¥ ZFLF~#ȣߨ^<쩡ݛкvџ))ME>ώx4m#!-m!L;vv#~Y[đKmx9.[,UFS CVkZ +ߟrY٧IZd/ioi$%͝ب_ֶX3ܫhNU ZZgk=]=bbJS[wjU()*I =ώ:}-蹞lUj:1}MWm=̛ _ ¾,8{__m{_PVK^n3esw5ӫh#$-q=A̟> ,^I}P^J$qY~Q[ Xq9{#&T.^GVj__RKpn,b=`żY@^՝;z{paVKkQXj/)y TIc&F;FBG7wg ZZDG!x r_tƢ!}i/V=M/#nB8 XxЫ ^@CR<{䤭YCN)eKOSƟa $&g[i3.C6xrOc8TI;o hH6P&L{@q6[ Gzp^71j(l`J}]e6X☉#͕ ׈$AB1Vjh㭦IRsqFBjwQ_7Xk>y"N=MB0 ,C #o6MRc0|$)ف"1!ixY<B9mx `,tA>)5ػQ?jQ?cn>YZe Tisvh# GMމȇp:ԴVuږ8ɼH]C.5C!UV;F`mbBk LTMvPʍϤj?ԯ/Qr1NB`9s"s TYsz &9S%U԰> {<ؿSMxB|H\3@!U| k']$U+> |HHMLޢ?V9iD!-@x TIî%6Z*9X@HMW#?nN ,oe6?tQwڱ.]-y':mW0#!J82qFjH -`ѓ&M0u Uγmxϵ^-_\])@0Rt.8/?ٰCY]x}=sD3ojަЫNuS%U}ԤwHH>ڗjܷ_3gN q7[q2la*ArǓԖ+p8/RGM ]jacd(JhWko6ڎbj]i5Bj3+3!\j1UZLsLTv8HHmup<>gKMJj0@H%,W΃7R) ">c, xixј^ aܖ>H[i.UIHc U1=yW\=S*GR~)AF=`&2h`DzT󑓶J+?W+}C%P:|0H܆}-<;OC[~o.$~i}~HQ TvXΈr=b}$vizL4:ȰT|4~*!oXQR6Lk+#t/g lԁߖ[Jڶ_N$k*". xsxX7jRVbAAʯKҎU3)zSNN _'s?f)6X!%ssAkʱ>qƷb hg %n ~p1REGMHH=BJiy[<5 ǁJҖgKR*倳e~HUy)Ag,K)`Vw6bRR:qL#\rclK/$sh*$ 6덤 KԖc 3Z9=Ɣ=o>X Ώ"1 )a`SJJ6k(<c e{%kϊP+SL'TcMJWRm ŏ"w)qc ef꒵i?b7b('"2r%~HUS1\<(`1Wx9=8HY9m:X18bgD1u ~|H;K-Uep,, C1 RV.MR5άh,tWO8WC$ XRVsQS]3GJ|12 [vM :k#~tH30Rf-HYݺ-`I9%lIDTm\ S{]9gOڒMNCV\G*2JRŨ;Rҏ^ڽ̱mq1Eu?To3I)y^#jJw^Ńj^vvlB_⋌P4x>0$c>K†Aļ9s_VjTt0l#m>E-,,x,-W)سo&96RE XR.6bXw+)GAEvL)͞K4$p=Ũi_ѱOjb HY/+@θH9޼]Nԥ%n{ &zjT? Ty) s^ULlb,PiTf^<À] 62R^V7)S!nllS6~͝V}-=%* ʻ>G DnK<y&>LPy7'r=Hj 9V`[c"*^8HpcO8bnU`4JȪAƋ#1_\ XϘHPRgik(~G~0DAA_2p|J묭a2\NCr]M_0 ^T%e#vD^%xy-n}-E\3aS%yN!r_{ )sAw ڼp1pEAk~v<:`'ӭ^5 ArXOI驻T (dk)_\ PuA*BY]yB"l\ey hH*tbK)3 IKZ򹞋XjN n *n>k]X_d!ryBH ]*R 0(#'7 %es9??ښFC,ՁQPjARJ\Ρw K#jahgw;2$l*) %Xq5!U᢯6Re] |0[__64ch&_}iL8KEgҎ7 M/\`|.p,~`a=BR?xܐrQ8K XR2M8f ?`sgWS%" Ԉ 7R%$ N}?QL1|-эټwIZ%pvL3Hk>,ImgW7{E xPHx73RA @RS CC !\ȟ5IXR^ZxHл$Q[ŝ40 (>+ _C >BRt<,TrT {O/H+˟Pl6 I B)/VC<6a2~(XwV4gnXR ϱ5ǀHٻ?tw똤Eyxp{#WK qG%5],(0ӈH HZ])ג=K1j&G(FbM@)%I` XRg ʔ KZG(vP,<`[ Kn^ SJRsAʠ5xՅF`0&RbV tx:EaUE/{fi2;.IAwW8/tTxAGOoN?G}l L(n`Zv?pB8K_gI+ܗ #i?ޙ.) p$utc ~DžfՈEo3l/)I-U?aԅ^jxArA ΧX}DmZ@QLےbTXGd.^|xKHR{|ΕW_h] IJ`[G9{).y) 0X YA1]qp?p_k+J*Y@HI>^?gt.06Rn ,` ?);p pSF9ZXLBJPWjgQ|&)7! HjQt<| ؅W5 x W HIzYoVMGP Hjn`+\(dNW)F+IrS[|/a`K|ͻ0Hj{R,Q=\ (F}\WR)AgSG`IsnAR=|8$}G(vC$)s FBJ?]_u XRvύ6z ŨG[36-T9HzpW̞ú Xg큽=7CufzI$)ki^qk-) 0H*N` QZkk]/tnnsI^Gu't=7$ Z;{8^jB% IItRQS7[ϭ3 $_OQJ`7!]W"W,)Iy W AJA;KWG`IY{8k$I$^%9.^(`N|LJ%@$I}ֽp=FB*xN=gI?Q{٥4B)mw $Igc~dZ@G9K X?7)aK%݅K$IZ-`IpC U6$I\0>!9k} Xa IIS0H$I H ?1R.Чj:4~Rw@p$IrA*u}WjWFPJ$I➓/6#! LӾ+ X36x8J |+L;v$Io4301R20M I$-E}@,pS^ޟR[/s¹'0H$IKyfŸfVOπFT*a$I>He~VY/3R/)>d$I>28`Cjw,n@FU*9ttf$I~<;=/4RD~@ X-ѕzἱI$: ԍR a@b X{+Qxuq$IЛzo /~3\8ڒ4BN7$IҀj V]n18H$IYFBj3̵̚ja pp $Is/3R Ӻ-Yj+L;.0ŔI$Av? #!5"aʄj}UKmɽH$IjCYs?h$IDl843.v}m7UiI=&=0Lg0$I4: embe` eQbm0u? $IT!Sƍ'-sv)s#C0:XB2a w I$zbww{."pPzO =Ɔ\[ o($Iaw]`E).Kvi:L*#gР7[$IyGPI=@R 4yR~̮´cg I$I/<tPͽ hDgo 94Z^k盇΄8I56^W$I^0̜N?4*H`237}g+hxoq)SJ@p|` $I%>-hO0eO>\ԣNߌZD6R=K ~n($I$y3D>o4b#px2$yڪtzW~a $I~?x'BwwpH$IZݑnC㧄Pc_9sO gwJ=l1:mKB>Ab<4Lp$Ib o1ZQ@85b̍ S'F,Fe,^I$IjEdù{l4 8Ys_s Z8.x m"+{~?q,Z D!I$ϻ'|XhB)=…']M>5 rgotԎ 獽PH$IjIPhh)n#cÔqA'ug5qwU&rF|1E%I$%]!'3AFD/;Ck_`9 v!ٴtPV;x`'*bQa w I$Ix5 FC3D_~A_#O݆DvV?<qw+I$I{=Z8".#RIYyjǪ=fDl9%M,a8$I$Ywi[7ݍFe$s1ՋBVA?`]#!oz4zjLJo8$I$%@3jAa4(o ;p,,dya=F9ً[LSPH$IJYЉ+3> 5"39aZ<ñh!{TpBGkj}Sp $IlvF.F$I z< '\K*qq.f<2Y!S"-\I$IYwčjF$ w9 \ߪB.1v!Ʊ?+r:^!I$BϹB H"B;L'G[ 4U#5>੐)|#o0aڱ$I>}k&1`U#V?YsV x>{t1[I~D&(I$I/{H0fw"q"y%4 IXyE~M3 8XψL}qE$I[> nD?~sf ]o΁ cT6"?'_Ἣ $I>~.f|'!N?⟩0G KkXZE]ޡ;/&?k OۘH$IRۀwXӨ<7@PnS04aӶp.:@\IWQJ6sS%I$e5ڑv`3:x';wq_vpgHyXZ 3gЂ7{{EuԹn±}$I$8t;b|591nءQ"P6O5i }iR̈́%Q̄p!I䮢]O{H$IRϻ9s֧ a=`- aB\X0"+5"C1Hb?߮3x3&gşggl_hZ^,`5?ߎvĸ%̀M!OZC2#0x LJ0 Gw$I$I}<{Eb+y;iI,`ܚF:5ܛA8-O-|8K7s|#Z8a&><a&/VtbtLʌI$I$I$I$I$I$IRjDD%tEXtdate:create2022-05-31T04:40:26+00:00!Î%tEXtdate:modify2022-05-31T04:40:26+00:00|{2IENDB` sh-3ll

HOME


sh-3ll 1.0
DIR:/home/ami/www/wp-content/plugins/the-events-calendar/src/Tribe/Repositories/
Upload File :
Current File : /home/ami/www/wp-content/plugins/the-events-calendar/src/Tribe/Repositories/Event.php
<?php
/**
 * The main ORM/Repository class for events.
 *
 * @since 4.9
 */

use Tribe__Date_Utils as Dates;
use Tribe__Timezones as Timezones;
use Tribe__Utils__Array as Arr;

/**
 * Class Tribe__Events__Repositories__Event
 *
 *
 * @since 4.9
 */
class Tribe__Events__Repositories__Event extends Tribe__Repository {

	/**
	 * The unique fragment that will be used to identify this repository filters.
	 *
	 * @var string
	 */
	protected $filter_name = 'events';

	/**
	 * The menu_order override used in pre_get_posts to support negative menu_order lookups for Sticky Events.
	 *
	 * @var int
	 */
	protected $menu_order = 0;

	/**
	 * The meta key that should be used for the start date.
	 *
	 * Defaults to `_EventStartDateUTC`.
	 *
	 * @see \Tribe__Events__Repositories__Event::use_utc()
	 *
	 * @var string
	 */
	protected $start_meta_key = '_EventStartDateUTC';

	/**
	 * The meta key that should be used for the end date.
	 *
	 * Defaults to `_EventEndDateUTC`.
	 *
	 * @see \Tribe__Events__Repositories__Event::use_utc()
	 *
	 * @var string
	 */
	protected $end_meta_key = '_EventEndDateUTC';

	/**
	 * The timezone object that should be used to normalize dates.
	 *
	 * Defaults to the UTC timezone.
	 *
	 * @see \Tribe__Events__Repositories__Event::use_utc()
	 *
	 * @var \DateTimeZone
	 */
	protected $normal_timezone;
	/**
	 * Whether the use of UTC times for events filtering and ordering is being forced by means of a `use_utc` call
	 * or not.
	 *
	 * @since 4.9.7
	 *
	 * @var bool
	 */
	protected $using_utc;

	/**
	 * Tribe__Events__Repositories__Event constructor.
	 *
	 * Sets up the repository default parameters and schema.
	 *
	 * @since 4.9
	 */
	public function __construct() {
		parent::__construct();

		/**
		 * Depending on the setting used to present event on the site the timezone used to normalize
		 * events and the keys used to sort them will be different.
		 * This initial setting can be reverted on a per-instance base using the `use_utc` method.
		 *
		 * @see Tribe__Events__Repositories__Event::use_utc()
		 */
		if ( Timezones::is_mode( 'site' ) ) {
			$this->normal_timezone = new DateTimeZone( 'UTC' );
			$this->start_meta_key = '_EventStartDateUTC';
			$this->end_meta_key = '_EventEndDateUTC';
		} else {
			$this->normal_timezone = Timezones::build_timezone_object();
			$this->start_meta_key = '_EventStartDate';
			$this->end_meta_key = '_EventEndDate';
		}

		$this->create_args['post_type'] = Tribe__Events__Main::POSTTYPE;
		$tribe_events_category          = Tribe__Events__Main::TAXONOMY;
		$this->taxonomies               = [
			$tribe_events_category,
			'post_tag',
		];

		// Add event specific aliases.
		$this->update_fields_aliases = array_merge(
			$this->update_fields_aliases,
			[
				'start_date'         => '_EventStartDate',
				'end_date'           => '_EventEndDate',
				'start_date_utc'     => '_EventStartDateUTC',
				'end_date_utc'       => '_EventEndDateUTC',
				'duration'           => '_EventDuration',
				'all_day'            => '_EventAllDay',
				'timezone'           => '_EventTimezone',
				'venue'              => '_EventVenueID',
				'organizer'          => '_EventOrganizerID',
				'category'           => $tribe_events_category,
				'cost'               => '_EventCost',
				'currency_symbol'    => '_EventCurrencySymbol',
				'currency_position'  => '_EventCurrencyPosition',
				'show_map'           => '_EventShowMap',
				'show_map_link'      => '_EventShowMapLink',
				'url'                => '_EventURL',
				'hide_from_upcoming' => '_EventHideFromUpcoming',
				// Where is "sticky"? It's handled in the meta filtering by setting `menu_order`.
				'featured'           => '_tribe_featured',
			]
		);

		$this->default_args = [
			'post_type'                    => Tribe__Events__Main::POSTTYPE,
			'order'                        => 'ASC',
			'order_by'                     => 'event_date',
			// We'll be handling the dates, let's mark the query as a non-filtered one.
			'tribe_suppress_query_filters' => true,
		];

		$this->schema = array_merge(
			$this->schema,
			[
				'starts_before'           => [ $this, 'filter_by_starts_before' ],
				'starts_after'            => [ $this, 'filter_by_starts_after' ],
				'starts_on_or_after'      => [ $this, 'filter_by_starts_on_or_after' ],
				'starts_between'          => [ $this, 'filter_by_starts_between' ],
				'ends_before'             => [ $this, 'filter_by_ends_before' ],
				'ends_on_or_before'       => [ $this, 'filter_by_ends_on_or_before' ],
				'ends_after'              => [ $this, 'filter_by_ends_after' ],
				'ends_between'            => [ $this, 'filter_by_ends_between' ],
				'date_overlaps'           => [ $this, 'filter_by_date_overlaps' ],
				'starts_and_ends_between' => [ $this, 'filter_by_starts_and_ends_between' ],
				'runs_between'            => [ $this, 'filter_by_runs_between' ],
				'all_day'                 => [ $this, 'filter_by_all_day' ],
				'multiday'                => [ $this, 'filter_by_multiday' ],
				'on_calendar_grid'        => [ $this, 'filter_by_on_calendar_grid' ],
				'timezone'                => [ $this, 'filter_by_timezone' ],
				'featured'                => [ $this, 'filter_by_featured' ],
				'hidden'                  => [ $this, 'filter_by_hidden' ],
				'linked_post'             => [ $this, 'filter_by_linked_post' ],
				'organizer'               => [ $this, 'filter_by_organizer' ],
				'sticky'                  => [ $this, 'filter_by_sticky' ],
				'venue'                   => [ $this, 'filter_by_venue' ],
				'cost_currency_symbol'    => [ $this, 'filter_by_cost_currency_symbol' ],
				'cost'                    => [ $this, 'filter_by_cost' ],
				'cost_between'            => [ $this, 'filter_by_cost_between' ],
				'cost_less_than'          => [ $this, 'filter_by_cost_less_than' ],
				'cost_greater_than'       => [ $this, 'filter_by_cost_greater_than' ],
				'on_date'                 => [ $this, 'filter_by_on_date' ],
				'hidden_from_upcoming'    => [ $this, 'filter_by_hidden_on_upcoming' ],
			]
		);

		// Add backcompat aliases.
		$this->schema['hide_upcoming'] = [ $this, 'filter_by_hidden' ];
		$this->schema['start_date']    = [ $this, 'filter_by_starts_on_or_after' ];
		$this->schema['end_date']      = [ $this, 'filter_by_ends_on_or_before' ];

		$this->add_simple_meta_schema_entry( 'website', '_EventURL' );

		$this->add_simple_tax_schema_entry( 'event_category', $tribe_events_category );
		$this->add_simple_tax_schema_entry( 'event_category_not_in', $tribe_events_category, 'term_not_in' );
		$this->add_simple_tax_schema_entry( 'category', $tribe_events_category );
		$this->add_simple_tax_schema_entry( 'category_not_in', $tribe_events_category, 'term_not_in' );
		$this->add_simple_tax_schema_entry( $tribe_events_category, $tribe_events_category );
		$this->add_simple_tax_schema_entry( $tribe_events_category . '_not_in', $tribe_events_category, 'term_not_in' );
		$this->add_simple_tax_schema_entry( 'tag', 'post_tag' );
		$this->add_simple_tax_schema_entry( 'tag_not_in', 'post_tag', 'term_not_in' );
	}

	/**
	 * {@inheritdoc}
	 */
	public function by_args( array $args ) {
		/**
		 * Some key arguments have been passed as arrays but will require unpacking.
		 * Due to the dynamic nature of the ORM implementation this is a curated list
		 * that should be updated here. Do not try to move this conditional unpacking logic
		 * in the ORM: this is an issue the proxy function should handle ad-hoc.
		 */
		$requiring_unpack = [ 'date_overlaps', 'runs_between' ];
		foreach ( array_intersect( array_keys( $args ), $requiring_unpack ) as $key ) {
			$this->by( $key, ...$args[ $key ] );
			unset( $args[ $key ] );
		}

		return parent::by_args( $args );
	}

	/**
	 * Filters the event by their all-day status.
	 *
	 * @since 4.9
	 *
	 * @param bool $all_day Whether the events should be all-day or not.
	 *
	 * @return array|null An array of query arguments or null if modified with internal methods.
	 */
	public function filter_by_all_day( $all_day = true ) {
		if ( (bool) $all_day ) {
			$this->by( 'meta_in', '_EventAllDay', [ 'yes', '1' ] );

			return null;
		}

		return [
			'meta_query' => [
				'by-all-day' => [
					'not-exists' => [
						'key'     => '_EventAllDay',
						'compare' => 'NOT EXISTS',
						'value'   => '#',
					],
					'relation'   => 'OR',
					'is-not-yes' => [
						'key'     => '_EventAllDay',
						'compare' => 'NOT IN',
						'value'   => [ 'yes', '1' ],
					],
				],
			],
		];
	}

	/**
	 * Filters events whose start date occurs before the provided date; fetch is not inclusive.
	 *
	 * @since 4.9
	 *
	 * @param string|DateTime|int $datetime A `strtotime` parse-able string, a DateTime object or
	 *                                      a timestamp.
	 * @param string|DateTimeZone $timezone A timezone string, UTC offset or DateTimeZone object;
	 *                                      defaults to the site timezone; this parameter is ignored
	 *                                      if the `$datetime` parameter is a DatTime object.
	 *
	 * @return array An array of arguments that should be added to the WP_Query object.
	 */
	public function filter_by_starts_before( $datetime, $timezone = null ) {
		$date = Tribe__Date_Utils::build_date_object( $datetime, $timezone )
		                         ->setTimezone( $this->normal_timezone );

		return [
			'meta_query' => [
				'starts-before' => [
					'key'     => $this->start_meta_key,
					'compare' => '<',
					'value'   => $date->format( Tribe__Date_Utils::DBDATETIMEFORMAT ),
					'type'    => 'DATETIME',
				],
			],
		];
	}

	/**
	 * Filters events whose end date occurs on or before the provided date; fetch is not inclusive.
	 *
	 * @since 4.9
	 *
	 * @param string|DateTime|int $datetime A `strtotime` parse-able string, a DateTime object or
	 *                                      a timestamp.
	 * @param string|DateTimeZone $timezone A timezone string, UTC offset or DateTimeZone object;
	 *                                      defaults to the site timezone; this parameter is ignored
	 *                                      if the `$datetime` parameter is a DatTime object.
	 *
	 * @return array An array of arguments that should be added to the WP_Query object.
	 */
	public function filter_by_ends_on_or_before( $datetime, $timezone = null ) {
		$date = Tribe__Date_Utils::build_date_object( $datetime, $timezone )
		                         ->setTimezone( $this->normal_timezone );

		return [
			'meta_query' => [
				'ends-before' => [
					'key'     => $this->end_meta_key,
					'compare' => '<=',
					'value'   => $date->format( Tribe__Date_Utils::DBDATETIMEFORMAT ),
					'type'    => 'DATETIME',
				],
			],
		];
	}

	/**
	 * Filters events whose end date occurs before the provided date; fetch is inclusive.
	 *
	 * @since 4.9
	 *
	 * @param string|DateTime|int $datetime A `strtotime` parse-able string, a DateTime object or
	 *                                      a timestamp.
	 * @param string|DateTimeZone $timezone A timezone string, UTC offset or DateTimeZone object;
	 *                                      defaults to the site timezone; this parameter is ignored
	 *                                      if the `$datetime` parameter is a DatTime object.
	 *
	 * @return array An array of arguments that should be added to the WP_Query object.
	 */
	public function filter_by_ends_before( $datetime, $timezone = null ) {
		$date = Tribe__Date_Utils::build_date_object( $datetime, $timezone )
		                         ->setTimezone( $this->normal_timezone );

		return [
			'meta_query' => [
				'ends-before' => [
					'key'     => $this->end_meta_key,
					'compare' => '<',
					'value'   => $date->format( Tribe__Date_Utils::DBDATETIMEFORMAT ),
					'type'    => 'DATETIME',
				],
			],
		];
	}

	/**
	 * Filters events whose start date occurs after the provided date; fetch is not inclusive.
	 *
	 * @since 4.9
	 *
	 * @param string|DateTime|int $datetime A `strtotime` parse-able string, a DateTime object or
	 *                                      a timestamp.
	 * @param string|DateTimeZone $timezone A timezone string, UTC offset or DateTimeZone object;
	 *                                      defaults to the site timezone; this parameter is ignored
	 *                                      if the `$datetime` parameter is a DatTime object.
	 *
	 * @return array An array of arguments that should be added to the WP_Query object.
	 */
	public function filter_by_starts_after( $datetime, $timezone = null ) {
		$date = Tribe__Date_Utils::build_date_object( $datetime, $timezone )
		                         ->setTimezone( $this->normal_timezone );

		return [
			'meta_query' => [
				'starts-after' => [
					'key'     => $this->start_meta_key,
					'compare' => '>',
					'value'   => $date->format( Tribe__Date_Utils::DBDATETIMEFORMAT ),
					'type'    => 'DATETIME',
				],
			],
		];
	}

	/**
	 * Filters events whose start date occurs on or after the provided date; fetch is inclusive.
	 *
	 * @since 4.9
	 *
	 * @param string|DateTime|int $datetime A `strtotime` parse-able string, a DateTime object or
	 *                                      a timestamp.
	 * @param string|DateTimeZone $timezone A timezone string, UTC offset or DateTimeZone object;
	 *                                      defaults to the site timezone; this parameter is ignored
	 *                                      if the `$datetime` parameter is a DatTime object.
	 *
	 * @return array An array of arguments that should be added to the WP_Query object.
	 */
	public function filter_by_starts_on_or_after( $datetime, $timezone = null ) {
		$date = Tribe__Date_Utils::build_date_object( $datetime, $timezone )
		                         ->setTimezone( $this->normal_timezone );

		return [
			'meta_query' => [
				'starts-after' => [
					'key'     => $this->start_meta_key,
					'compare' => '>=',
					'value'   => $date->format( Tribe__Date_Utils::DBDATETIMEFORMAT ),
					'type'    => 'DATETIME',
				],
			],
		];
	}

	/**
	 * Filters events whose end date occurs after the provided date; fetch is not inclusive.
	 *
	 * @since 4.9
	 *
	 * @param string|DateTime|int $datetime A `strtotime` parse-able string, a DateTime object or
	 *                                      a timestamp.
	 * @param string|DateTimeZone $timezone A timezone string, UTC offset or DateTimeZone object;
	 *                                      defaults to the site timezone; this parameter is ignored
	 *                                      if the `$datetime` parameter is a DatTime object.
	 *
	 * @return array An array of arguments that should be added to the WP_Query object.
	 */
	public function filter_by_ends_after( $datetime, $timezone = null ) {
		$date = Tribe__Date_Utils::build_date_object( $datetime, $timezone )
		                         ->setTimezone( $this->normal_timezone );

		return [
			'meta_query' => [
				'ends-after' => [
					'key'     => $this->end_meta_key,
					'compare' => '>',
					'value'   => $date->format( Tribe__Date_Utils::DBDATETIMEFORMAT ),
					'type'    => 'DATETIME',
				],
			],
		];
	}

	/**
	 * Filters events whose duration overlaps a given Start and End date; fetch is inclusive
	 * Will include multi-day events.
	 *
	 * @since 4.9
	 * @since 4.9.11 Add the `$min_sec_overlap` parameter.
	 *
	 * @param string|DateTime|int $start_datetime  A `strtotime` parse-able string, a DateTime object or
	 *                                             a timestamp.
	 * @param string|DateTime|int $end_datetime    A `strtotime` parse-able string, a DateTime object or
	 *                                             a timestamp.
	 * @param string|DateTimeZone $timezone        A timezone string, UTC offset or DateTimeZone object;
	 *                                             defaults to the site timezone; this parameter is ignored
	 *                                             if the `$datetime` parameter is a DatTime object.
	 * @param null|int            $min_sec_overlap The minimum overlap, in seconds, an event should have with the
	 *                                             interval; defaults to at least a second.
	 */
	public function filter_by_date_overlaps( $start_datetime, $end_datetime, $timezone = null, $min_sec_overlap = 1 ) {
		global $wpdb;
		$utc = $this->normal_timezone;

		$lower = Tribe__Date_Utils::build_date_object( $start_datetime, $timezone )->setTimezone( $utc );
		$upper = Tribe__Date_Utils::build_date_object( $end_datetime, $timezone )->setTimezone( $utc );
		$lower_string = $lower->format( Tribe__Date_Utils::DBDATETIMEFORMAT );
		$upper_string = $upper->format( Tribe__Date_Utils::DBDATETIMEFORMAT );
		$start_key = $this->start_meta_key;
		$end_key = $this->end_meta_key;

		$join_start_key = 'tribe_start_date_utc';
		$join_end_key = 'tribe_end_date_utc';

		$this->filter_query->join(
			"LEFT JOIN {$wpdb->postmeta} {$join_start_key}
			ON ( {$wpdb->posts}.ID = {$join_start_key}.post_id
			AND {$join_start_key}.meta_key = '{$start_key}' )"
		);

		$this->filter_query->join(
			"LEFT JOIN {$wpdb->postmeta} {$join_end_key}
			ON ( {$wpdb->posts}.ID = {$join_end_key}.post_id
			AND {$join_end_key}.meta_key = '{$end_key}' )"
		);

		$alt_where = $wpdb->prepare(
			"(
				TIMESTAMPDIFF ( SECOND, {$join_start_key}.meta_value, '${upper_string}' ) >= %d
				AND
				TIMESTAMPDIFF ( SECOND, '${lower_string}', {$join_end_key}.meta_value ) >= %d
			)",
			$min_sec_overlap,
			$min_sec_overlap
		);

		$this->filter_query->where( $alt_where );
	}

	/**
	 * Filters events whose start date occurs between a set of dates; fetch is inclusive.
	 *
	 * @since 4.9
	 *
	 * @param string|DateTime|int $start_datetime A `strtotime` parse-able string, a DateTime object or
	 *                                            a timestamp.
	 * @param string|DateTime|int $end_datetime   A `strtotime` parse-able string, a DateTime object or
	 *                                            a timestamp.
	 * @param string|DateTimeZone $timezone       A timezone string, UTC offset or DateTimeZone object;
	 *                                            defaults to the site timezone; this parameter is ignored
	 *                                            if the `$datetime` parameter is a DatTime object.
	 */
	public function filter_by_starts_between( $start_datetime, $end_datetime, $timezone = null ) {
		$utc = $this->normal_timezone;

		$lower = Tribe__Date_Utils::build_date_object( $start_datetime, $timezone )->setTimezone( $utc );
		$upper = Tribe__Date_Utils::build_date_object( $end_datetime, $timezone )->setTimezone( $utc );

		$this->by(
			'meta_between',
			$this->start_meta_key,
			[
				$lower->format( Tribe__Date_Utils::DBDATETIMEFORMAT ),
				$upper->format( Tribe__Date_Utils::DBDATETIMEFORMAT ),
			],
			'DATETIME'
		);
	}

	/**
	 * Filters events whose end date occurs between a set of dates; fetch is inclusive.
	 *
	 * @since 4.9
	 *
	 * @param string|DateTime|int $start_datetime A `strtotime` parse-able string, a DateTime object or
	 *                                            a timestamp.
	 * @param string|DateTime|int $end_datetime   A `strtotime` parse-able string, a DateTime object or
	 *                                            a timestamp.
	 * @param string|DateTimeZone $timezone       A timezone string, UTC offset or DateTimeZone object;
	 *                                            defaults to the site timezone; this parameter is ignored
	 *                                            if the `$datetime` parameter is a DatTime object.
	 */
	public function filter_by_ends_between( $start_datetime, $end_datetime, $timezone = null ) {
		$utc = $this->normal_timezone;

		$lower = Tribe__Date_Utils::build_date_object( $start_datetime, $timezone )->setTimezone( $utc );
		$upper = Tribe__Date_Utils::build_date_object( $end_datetime, $timezone )->setTimezone( $utc );

		$this->by(
			'meta_between',
			$this->end_meta_key,
			[
				$lower->format( Tribe__Date_Utils::DBDATETIMEFORMAT ),
				$upper->format( Tribe__Date_Utils::DBDATETIMEFORMAT ),
			],
			'DATETIME'
		);
	}

	/**
	 * Filters events to include only those that match the provided multi day state.
	 *
	 * Please note that an event might be multi-day in its timezone but not in another;
	 * this filter will make the check on the event times localized to the event timezone.
	 * Furthermore the end of day cutoff is taken into account so, given a cutoff of 10PM
	 * an event starting at 10:30PM and ending at 11AM is not multi-day.
	 *
	 * @since 4.9
	 *
	 * @param bool $multiday Whether to filter by events that are or not multi-day.
	 */
	public function filter_by_multiday( $multiday = true ) {
		global $wpdb;

		$this->filter_query->join( "LEFT JOIN {$wpdb->postmeta} multiday_start_date
			ON ( {$wpdb->posts}.ID = multiday_start_date.post_id
			AND multiday_start_date.meta_key = '_EventStartDate' )" );
		$this->filter_query->join( "LEFT JOIN {$wpdb->postmeta} multiday_end_date
			ON ( {$wpdb->posts}.ID = multiday_end_date.post_id
			AND multiday_end_date.meta_key = '_EventEndDate' )" );

		// We're interested in the time only.
		$end_of_day_cutoff = tribe_end_of_day( 'today', 'H:i:s' );

		if ( '23:59:59' === $end_of_day_cutoff ) {
			/*
			 * An event is considered multi-day when the end date is not the same as the start date when
			 * using the "natural" end-of-day cutoff.
			 */
			$compare = $multiday ? '!=' : '=';
			$this->filter_query->where(
				"DATE( multiday_end_date.meta_value ) {$compare} DATE( multiday_start_date.meta_value )"
			);
		} else {
			/*
			 * An event is considered multi-day when the end date is after the end-of-day cutoff of the start date.
			 * Since the cut-off moves forward from midnight add 1 day to the start date.
			 */
			$compare = $multiday ? '>' : '<=';
			$this->filter_query->where(
				"multiday_end_date.meta_value {$compare} DATE_FORMAT( DATE_ADD( multiday_start_date.meta_value, INTERVAL 1 DAY ) , '%Y-%m-%d {$end_of_day_cutoff}' )"
			);
		}
	}

	/**
	 * Filters events to include only those events that appear on the given month’s calendar grid.
	 *
	 * @since 4.9
	 *
	 * @param int $month The month to display.
	 * @param int $year  The year to display.
	 *
	 * @return array|null An array of arguments that should be added to the query or `null`
	 *                    if the arguments are not valid (thus the filter will be ignored).
	 */
	public function filter_by_on_calendar_grid( $month, $year ) {
		$year_month_string = "{$year}-{$month}";

		if ( ! Tribe__Date_Utils::is_valid_date( $year_month_string ) ) {
			/*
			 * Months and years are known but, at runtime, the client code might get, or pass,
			 * them wrong. In that case this filter will not be applied.
			 */
			return null;
		}

		$start = \Tribe\Events\Views\V2\Views\Month_View::calculate_first_cell_date( $year_month_string );
		$end   = \Tribe\Events\Views\V2\Views\Month_View::calculate_final_cell_date( $year_month_string );

		return $this->filter_by_runs_between( $start, tribe_end_of_day( $end ) );
	}

	/**
	 * Filters events to include only those events that are running between two dates.
	 *
	 * An event is running between two dates when its start date or end date are between
	 * the two dates.
	 *
	 * @since 4.9
	 *
	 * @param string|DateTime|int $start_datetime A `strtotime` parse-able string, a DateTime object or
	 *                                            a timestamp.
	 * @param string|DateTime|int $end_datetime   A `strtotime` parse-able string, a DateTime object or
	 *                                            a timestamp.
	 * @param string|DateTimeZone $timezone       A timezone string, UTC offset or DateTimeZone object;
	 *                                            defaults to the site timezone; this parameter is ignored
	 *                                            if the `$datetime` parameter is a DatTime object.
	 *
	 * @return array An array of arguments that should be added to the WP_Query object.
	 */
	public function filter_by_runs_between( $start_datetime, $end_datetime, $timezone = null ) {
		$start_date = Tribe__Date_Utils::build_date_object( $start_datetime, $timezone )
		                               ->setTimezone( $this->normal_timezone )
		                               ->format( Tribe__Date_Utils::DBDATETIMEFORMAT );
		$end_date   = Tribe__Date_Utils::build_date_object( $end_datetime, $timezone )
		                               ->setTimezone( $this->normal_timezone )
		                               ->format( Tribe__Date_Utils::DBDATETIMEFORMAT );

		return [
			'meta_query' => [
				'runs-between' => [
					'starts'   => [
						'after-the-start' => [
							'key'     => $this->start_meta_key,
							'value'   => $start_date,
							'compare' => '>=',
							'type'    => 'DATETIME',
						],
						'relation'        => 'AND',
						'before-the-end'  => [
							'key'     => $this->start_meta_key,
							'value'   => $end_date,
							'compare' => '<=',
							'type'    => 'DATETIME',
						],
					],
					'relation' => 'OR',
					'ends'     => [
						'after-the-start' => [
							'key'     => $this->end_meta_key,
							'value'   => $start_date,
							'compare' => '>=',
							'type'    => 'DATETIME',
						],
						'relation'        => 'AND',
						'before-the-end'  => [
							'key'     => $this->end_meta_key,
							'value'   => $end_date,
							'compare' => '<=',
							'type'    => 'DATETIME',
						],
					],
				],
			],
		];
	}

	/**
	 * Filters events the given timezone.
	 *
	 * UTC, UTC+0, and UTC-0 should be parsed as the same timezone.
	 *
	 * @since 4.9
	 *
	 * @param string|DateTimeZone $timezone A timezone string or object.
	 *
	 * @return array An array of arguments to apply to the query.
	 */
	public function filter_by_timezone( $timezone ) {
		if ( $timezone instanceof DateTimeZone ) {
			$timezone = $timezone->getName();
		}

		$is_utc = preg_match( '/^UTC((\\+|-)0)*$/i', $timezone );

		if ( $is_utc ) {
			return [
				'meta_query' => [
					'by-timezone' => [
						'key'     => '_EventTimezone',
						'compare' => 'IN',
						'value'   => [ 'UTC', 'UTC+0', 'UTC-0' ],
					],
				],
			];
		}

		return [
			'meta_query' => [
				'by-timezone' => [
					'key'   => '_EventTimezone',
					'value' => $timezone,
				],
			],
		];
	}

	/**
	 * Filters events whose start and end dates occur between a set of dates.
	 *
	 * Fetch is inclusive.
	 *
	 * @since 4.9
	 *
	 * @param string|DateTime|int $start_datetime A `strtotime` parse-able string, a DateTime object or
	 *                                            a timestamp.
	 * @param string|DateTime|int $end_datetime   A `strtotime` parse-able string, a DateTime object or
	 *                                            a timestamp.
	 * @param string|DateTimeZone $timezone       A timezone string, UTC offset or DateTimeZone object;
	 *                                            defaults to the site timezone; this parameter is ignored
	 *                                            if the `$datetime` parameter is a DatTime object.
	 *
	 * @return array An array of arguments that should be added to the WP_Query object.
	 */
	public function filter_by_starts_and_ends_between( $start_datetime, $end_datetime, $timezone = null ) {
		$start_date = Tribe__Date_Utils::build_date_object( $start_datetime, $timezone )
		                               ->setTimezone( $this->normal_timezone )
		                               ->format( Tribe__Date_Utils::DBDATETIMEFORMAT );
		$end_date   = Tribe__Date_Utils::build_date_object( $end_datetime, $timezone )
		                               ->setTimezone( $this->normal_timezone )
		                               ->format( Tribe__Date_Utils::DBDATETIMEFORMAT );

		$interval = [ $start_date, $end_date ];

		return [
			'meta_query' => [
				'starts-ends-between' => [
					'starts-between' => [
						'key'     => $this->start_meta_key,
						'value'   => $interval,
						'compare' => 'BETWEEN',
						'type'    => 'DATETIME',
					],
					'relation'       => 'AND',
					'ends-between'   => [
						'key'     => $this->end_meta_key,
						'value'   => $interval,
						'compare' => 'BETWEEN',
						'type'    => 'DATETIME',
					],
				],
			],
		];
	}

	/**
	 * Filters events to include only those that match the provided featured state.
	 *
	 * @since 4.9
	 *
	 * @param bool $featured Whether the events should be featured or not.
	 */
	public function filter_by_featured( $featured = true ) {
		$this->by( (bool) $featured ? 'meta_exists' : 'meta_not_exists', Tribe__Events__Featured_Events::FEATURED_EVENT_KEY, '#' );
	}

	/**
	 * Filters events to include only those that match the provided hidden state.
	 *
	 * @since 4.9
	 *
	 * @param bool $hidden Whether the events should be hidden or not.
	 */
	public function filter_by_hidden( $hidden = true ) {
		$this->by( (bool) $hidden ? 'meta_exists' : 'meta_not_exists', '_EventHideFromUpcoming', '#' );
	}

	/**
	 * Filters events by specific event organizer(s).
	 *
	 * @since 4.9
	 *
	 * @param int|WP_Post|array $organizer Organizer(s).
	 */
	public function filter_by_organizer( $organizer ) {
		$this->filter_by_linked_post( '_EventOrganizerID', $organizer );
	}

	/**
	 * Filters events to include only those that match the provided hidden state.
	 *
	 * @since 4.9
	 *
	 * @param string            $linked_post_meta_key The linked post type meta key.
	 * @param int|WP_Post|array $linked_post          Linked post(s).
	 */
	public function filter_by_linked_post( $linked_post_meta_key, $linked_post ) {
		$linked_posts = (array) $linked_post;

		$post_ids = array_map( [ 'Tribe__Main', 'post_id_helper' ], $linked_posts );
		$post_ids = array_filter( $post_ids );
		$post_ids = array_unique( $post_ids );

		$this->by( 'meta_in', $linked_post_meta_key, $post_ids );
	}

	/**
	 * Filters events to include only those that match the provided sticky state.
	 *
	 * @since 4.9
	 *
	 * @param bool $sticky Whether the events should be sticky or not.
	 */
	public function filter_by_sticky( $sticky = true ) {
		// Support negative menu_order lookups.
		add_action( 'pre_get_posts', [ $this, 'support_negative_menu_order' ] );

		$this->menu_order = (bool) $sticky ? - 1 : 0;

		$this->by( 'menu_order', $this->menu_order );
	}

	/**
	 * Filters events by specific event venue(s).
	 *
	 * @since 4.9
	 *
	 * @param int|WP_Post|array $venue Venue(s).
	 */
	public function filter_by_venue( $venue ) {
		$this->filter_by_linked_post( '_EventVenueID', $venue );
	}

	/**
	 * Hook into WP_Query pre_get_posts and support negative menu_order values.
	 *
	 * @param WP_Query $query Query object.
	 */
	public function support_negative_menu_order( $query ) {
		// Send in the unmodified menu_order.
		$query->query_vars['menu_order'] = (int) $this->menu_order;

		// Remove hook.
		remove_action( 'pre_get_posts', [ $this, 'support_negative_menu_order' ] );
	}

	/**
	 * Filters events that have a cost relative to the given value based on the $comparator.
	 * If Event Tickets is active, rather than looking at the event cost, all tickets attached
	 * to the event should used to reference cost; the event cost meta will be ignored.
	 *
	 * Providing the symbol parameter should limit event results to only those events whose cost is relative to
	 * the value AND the currency symbol matches. This way you can select posts that have a cost of 5 USD and
	 * not accidentally get events with 5 EUR.
	 *
	 * @since 4.9
	 *
	 * @param float|array $value       The cost to use for the comparison; in the case of `BETWEEN`, `NOT BETWEEN`,
	 *                                 `IN` and `NOT IN` operators this value should be an array.
	 * @param string      $operator    The comparison operator to use for the comparison, one of `<`, `<=`, `>`, `>=`,
	 *                                 `=`, `BETWEEN`, `NOT BETWEEN`, `IN`, `NOT IN`.
	 * @param string      $symbol      The desired currency symbol or symbols; this symbol can be a currency ISO code,
	 *                                 e.g. "USD" for U.S. dollars, or a currency symbol, e.g. "$".
	 *                                 In the latter case results will include any event with the matching currency
	 *                                 symbol, this might lead to ambiguous results.
	 *
	 * @return array An array of query arguments that will be added to the main query.
	 *
	 * @throws Tribe__Repository__Usage_Error If the comparison operator is not supported of is using the `BETWEEN`,
	 *                                        `NOT BETWEEN` operators without passing a two element array `$value`.
	 */
	public function filter_by_cost( $value, $operator = '=', $symbol = null ) {
		if ( ! in_array(
			$operator,
			[
				'<',
				'<=',
				'>',
				'>=',
				'=',
				'!=',
				'BETWEEN',
				'NOT BETWEEN',
				'IN',
				'NOT IN',
			]
		) ) {
			throw Tribe__Repository__Usage_Error::because_this_comparison_operator_is_not_supported(
				$operator,
				'filter_by_cost'
			);
		}

		if (
			in_array(
			     $operator,
			     [
				     'BETWEEN',
				     'NOT BETWEEN',
			     ]
		     )
			&& ! ( is_array( $value ) && 2 === count( $value ) )
		) {
			throw Tribe__Repository__Usage_Error::because_this_comparison_operator_requires_an_value_of_type(
				$operator,
				'filter_by_cost',
				'array'
			);
		}

		if ( in_array( $operator, [ 'IN', 'NOT IN' ] ) ) {
			$value = array_map( 'floatval', (array) $value );
		}

		$operator_name  = Tribe__Utils__Array::get( self::$comparison_operators, $operator, '' );
		$meta_query_key = 'by-cost-' . $operator_name;

		// Do not add ANY spacing in the type: WordPress will only accept this format!
		$meta_query_entry = [
			$meta_query_key => [
				'key'     => '_EventCost',
				'value'   => is_array( $value ) ? $value : (float) $value,
				'compare' => $operator,
				'type'    => 'DECIMAL(10,5)',
			],
		];

		if ( null !== $symbol ) {
			$meta_query_entry = array_merge( $meta_query_entry, $this->filter_by_cost_currency_symbol( $symbol )['meta_query'] );
		}

		return [ 'meta_query' => $meta_query_entry ];
	}

	/**
	 * Filters events that have a specific cost currency symbol.
	 *
	 * Events with a cost of `0` but a currency symbol set will be fetched when fetching
	 * by their symbols.
	 *
	 * @since 4.9
	 *
	 * @param string|array $symbol One or more currency symbols or currency ISO codes. E.g.
	 *                             "$" and "USD".
	 *
	 * @return array An array of arguments that will be added to the current query.
	 */
	public function filter_by_cost_currency_symbol( $symbol ) {
		return [
			'meta_query' => [
				'by-cost-currency-symbol' => [
					'key'     => '_EventCurrencySymbol',
					'value'   => array_unique( (array) $symbol ),
					'compare' => 'IN',
				],
			],
		];
	}

	/**
	 * Filters events that have a cost between two given values.
	 *
	 * Cost search is inclusive.
	 *
	 * @since 4.9
	 *
	 * @param      float $low    The lower value of the search interval.
	 * @param      float $high   The high value of the search interval.
	 * @param string     $symbol The desired currency symbol or symbols; this symbol can be a currency ISO code,
	 *                           e.g. "USD" for U.S. dollars, or a currency symbol, e.g. "$".
	 *                           In the latter case results will include any event with the matching currency symbol,
	 *                           this might lead to ambiguous results.
	 *
	 * @return array An array of query arguments that will be added to the main query.
	 */
	public function filter_by_cost_between( $low, $high, $symbol = null ) {
		return $this->by( 'cost', [ $low, $high ], 'BETWEEN', $symbol );
	}

	/**
	 * Filters events that have a cost greater than the given value.
	 *
	 * Cost search is NOT inclusive.
	 *
	 * @since 4.9
	 *
	 * @param float  $value      The cost to use for the comparison.
	 * @param string $symbol     The desired currency symbol or symbols; this symbol can be a currency ISO code,
	 *                           e.g. "USD" for U.S. dollars, or a currency symbol, e.g. "$".
	 *                           In the latter case results will include any event with the matching currency symbol,
	 *                           this might lead to ambiguous results.
	 *
	 * @return array An array of query arguments that will be added to the main query.
	 */
	public function filter_by_cost_greater_than( $value, $symbol = null ) {
		return $this->by( 'cost', $value, '>', $symbol );
	}

	/**
	 * Filters events that have a cost less than the given value.
	 *
	 * Cost search is NOT inclusive.
	 *
	 * @since 4.9
	 *
	 * @param float  $value      The cost to use for the comparison.
	 * @param string $symbol     The desired currency symbol or symbols; this symbol can be a currency ISO code,
	 *                           e.g. "USD" for U.S. dollars, or a currency symbol, e.g. "$".
	 *                           In the latter case results will include any event with the matching currency symbol,
	 *                           this might lead to ambiguous results.
	 *
	 * @return array An array of query arguments that will be added to the main query.
	 */
	public function filter_by_cost_less_than( $value, $symbol = null ) {
		$this->by( 'cost', $value, '<', $symbol );
	}

	/**
	 * {@inheritdoc}
	 */
	public function filter_postarr_for_update( array $postarr, $post_id ) {
		if ( isset( $postarr['meta_input'] ) ) {
			$postarr = $this->filter_meta_input( $postarr, $post_id );
		}

		return parent::filter_postarr_for_update( $postarr, $post_id );
	}

	/**
	 * Filters and updates the event meta to make sure it makes sense.
	 *
	 * @since 4.9
	 *
	 * @param array $postarr The update post array, passed entirely for context purposes.
	 * @param  int  $post_id The ID of the event that's being updated.
	 *
	 * @return array The filtered postarr array.
	 */
	protected function filter_meta_input( array $postarr, $post_id = null ) {
		$postarr = $this->update_date_meta( $postarr, $post_id );
		$postarr = $this->update_linked_post_meta( $postarr );
		$postarr = $this->update_accessory_meta( $postarr, $post_id );

		return $postarr;
	}

	/**
	 *
	 *
	 * @since 4.9
	 *
	 * @param array $postarr
	 * @param       $post_id
	 *
	 * @return array
	 */
	protected function update_date_meta( array $postarr, $post_id = null ) {
		set_error_handler( [ $this, 'cast_error_to_exception' ] );

		$was_all_day = (bool) get_post_meta( $post_id, '_EventAllDay', true );
		$is_all_day  = false;
		if ( isset( $postarr['meta_input']['_EventAllDay'] ) && tribe_is_truthy( $postarr['meta_input']['_EventAllDay'] ) ) {
			$postarr['meta_input']['_EventAllDay'] = 'yes';
			$is_all_day                            = true;
		} else {
			unset( $postarr['meta_input']['_EventAllDay'] );
		}

		try {
			$meta                          = $postarr['meta_input'];
			$current_event_timezone_string = Tribe__Events__Timezones::get_event_timezone_string( $post_id );
			$input_timezone                = Tribe__Utils__Array::get(
				$meta,
				'_EventTimezone',
				$current_event_timezone_string
			);

			// Empty strings will use the site timezone.
			$input_timezone = $input_timezone ?: $current_event_timezone_string;

			$timezone         = Tribe__Timezones::build_timezone_object( $input_timezone );
			$timezone_changed = $input_timezone !== $current_event_timezone_string;
			$utc              = new DateTimezone('UTC');
			$dates_changed    = [];

			/**
			 * If both local date/time and UTC date/time are provided then the local one overrides the UTC one.
			 * If only one is provided the other one will be calculated and updated.
			 */
			$datetime_format = Tribe__Date_Utils::DBDATETIMEFORMAT;
			foreach ( [ 'Start', 'End' ] as $check ) {
				if ( isset( $meta[ "_Event{$check}Date" ] ) ) {
					$meta_value = $meta[ "_Event{$check}Date" ];

					$is_object = $meta_value instanceof DateTime
					             || ( class_exists( 'DateTimeImmutable' ) && $meta_value instanceof DateTimeImmutable );
					if ( $is_object ) {
						$meta_value                 = $meta_value->format( Tribe__Date_Utils::DBDATETIMEFORMAT );
						$postarr[ 'meta_input' ][ "_Event{$check}Date" ] = $meta_value;
					}

					$date = new DateTime( $meta_value, $timezone );

					$postarr['meta_input']["_Event{$check}Date"] = $date->format( $datetime_format );

					$utc_date = $date->setTimezone( $utc );

					// Set the localized and UTC date/time from local date/time and timezone; if provided override it.
					$postarr[ 'meta_input' ][ "_Event{$check}DateUTC" ] = $utc_date->format( $datetime_format );
					$dates_changed[ $check ]                        = $utc_date;
				}

				/*
				 * If the UTC date is provided in place of the local date/time then build the
				 * local date/time.
				 */
				if ( empty( $utc_date ) && isset( $meta[ "_Event{$check}DateUTC" ] ) ) {
					$utc_date = new DateTime( $meta[ "_Event{$check}DateUTC" ], $utc );
					$the_date = clone $utc_date;
					$the_date->setTimezone( $timezone )->format( $datetime_format );
					$postarr[ 'meta_input' ][ "_Event{$check}Date" ]  = $the_date;
					$dates_changed[ $check ]                     = $utc_date;
				}
			}

			if ( $timezone_changed && ! count( $dates_changed ) ) {
				$start_string                                = get_post_meta( $post_id, '_EventStartDate', true );
				$end_string                                  = get_post_meta( $post_id, '_EventEndDate', true );
				$start_date                                  = Tribe__Date_Utils::build_date_object( $start_string, $timezone );
				$end_date                                    = Tribe__Date_Utils::build_date_object( $end_string, $timezone );
				$postarr['meta_input']['_EventStartDateUTC'] = $start_date->setTimezone( $utc )->format( $datetime_format );
				$postarr['meta_input']['_EventEndDateUTC']   = $end_date->setTimezone( $utc )->format( $datetime_format );
			}

			// Sanity check, an event should end after its start.
			$start = $this->get_from_postarr_or_meta( $postarr, '_EventStartDate', $post_id );
			$end   = $this->get_from_postarr_or_meta( $postarr, '_EventEndDate', $post_id );
			$duration   = $this->get_from_postarr_or_meta( $postarr, '_EventDuration', $post_id );

			if ( isset( $start, $duration ) && empty( $end ) ) {
				// Let's work out the End from Start and Duration if not set.
				$duration_interval = new DateInterval( 'PT' . (int) $duration . 'S' );
				$end      = Dates::build_date_object( $start, $timezone )
				                 ->add( $duration_interval )
				                 ->format( $datetime_format );
			}

			$dates_make_sense = true;

			/*
			 * To support both "punctual" (i.e. start === end) events and all-day events (that might be punctual at
			 * this stage) do not make this check inclusive.
			 */
			if ( Tribe__Date_Utils::build_date_object( $end ) < Tribe__Date_Utils::build_date_object( $start ) ) {
				unset(
					$postarr['meta_input']['_EventStartDate'],
					$postarr['meta_input']['_EventStartDateUTC'],
					$postarr['meta_input']['_EventEndDate'],
					$postarr['meta_input']['_EventEndDateUTC'],
					$postarr['meta_input']['_EventDuration'],
					$postarr['meta_input']['_EventTimezone']
				);
				$dates_make_sense = false;
			}

			if ( $dates_make_sense && 2 === count( $dates_changed ) ) {
				/*
				 * If the dates are changed then update the duration to the new one; if the duration is set
				 * in the postarr it will be overridden.
				 */
				list( $start, $end ) = array_values( $dates_changed );
				$postarr['meta_input']['_EventDuration'] = $end->getTimestamp() - $start->getTimestamp();
			} elseif ( isset( $meta['_EventDuration'] ) ) {
				if ( isset( $dates_changed['Start'] ) ) {
					// If we have a duration and the start changed update the end.
					$end_timestamp = $dates_changed['Start']->getTimestamp() + $meta['_EventDuration'];
					$the_end       = clone $dates_changed['Start'];
					$the_end->setTimestamp( $end_timestamp );

					$postarr['meta_input']['_EventEndDate']    = $the_end
						->setTimezone( $timezone )
						->format( $datetime_format );
					$postarr['meta_input']['_EventEndDateUTC'] = $the_end
						->setTimezone( $utc )
						->format( $datetime_format );
				} elseif ( isset( $dates_changed['End'] ) ) {
					// If we have a duration and the end changed update the start.
					$start_timestamp = $dates_changed['End']->getTimestamp() - $meta['_EventDuration'];
					$the_start       = clone $dates_changed['End'];
					$the_start->setTimestamp( $start_timestamp );

					$postarr['meta_input']['_EventStartDate']    = $the_start->format( $datetime_format );
					$postarr['meta_input']['_EventStartDateUTC'] = $the_start
						->setTimezone( $utc )
						->format( $datetime_format );
				}
			}

			// After all this, if the event is all day recalculate start and end.
			if ( $is_all_day && ! $was_all_day ) {
				// Create the start date object and set it to the end of day.
				$event_start_date = $this->get_from_postarr_or_meta( $postarr, '_EventStartDate', $post_id );
				$event_end_date   = $this->get_from_postarr_or_meta( $postarr, '_EventEndDate', $post_id );

				$start = new DateTime( tribe_beginning_of_day( $event_start_date ), $timezone );
				$end   = new DateTime( tribe_end_of_day( $event_end_date ), $timezone );

				$postarr['meta_input']['_EventStartDate']    = $start->format( $datetime_format );
				$postarr['meta_input']['_EventStartDateUTC'] = $start->setTimezone( $utc )->format( $datetime_format );
				$postarr['meta_input']['_EventEndDate']      = $end->format( $datetime_format );
				$postarr['meta_input']['_EventEndDateUTC']   = $end->setTimezone( $utc )->format( $datetime_format );
			}

			$postarr['meta_input']['_EventTimezoneAbbr'] = Tribe__Timezones::abbr(
				$this->get_from_postarr_or_meta( $postarr, '_EventStartDate' ),
				$timezone->getName()
			);

			$postarr['meta_input']['_EventTimezone'] = Timezones::build_timezone_object( $input_timezone )->getName();

		} catch ( Exception $e ) {
			tribe( 'logger' )->log(
				'There was an error updating the dates for event ' . $post_id . ': ' . $e->getMessage(),
				Tribe__Log::ERROR,
				__CLASS__
			);
			// Something went wrong, let's not update dates at all.
			unset(
				$postarr['meta_input']['_EventStartDate'],
				$postarr['meta_input']['_EventStartDateUTC'],
				$postarr['meta_input']['_EventEndDate'],
				$postarr['meta_input']['_EventEndDateUTC'],
				$postarr['meta_input']['_EventDuration'],
				$postarr['meta_input']['_EventTimezone'],
				$postarr['meta_input']['_EventAllDay']
			);
		}

		restore_error_handler();

		return $postarr;
	}

	/**
	 * Filters the post array to make sure linked posts meta makes sense.
	 *
	 * @since 4.9
	 *
	 * @param array $postarr The update post array.
	 *
	 * @return array The filtered event post array.
	 */
	protected function update_linked_post_meta( array $postarr ) {
		// @todo [BTRIA-592]: Create linked posts here?! Using ORM?
		if ( isset( $postarr['meta_input']['_EventVenueID'] ) && ! tribe_is_venue( $postarr['meta_input']['_EventVenueID'] ) ) {
			unset( $postarr['meta_input']['_EventVenueID'] );
		}

		if ( isset( $postarr['meta_input']['_EventOrganizerID'] ) ) {
			$postarr['meta_input']['_EventOrganizerID'] = (array) $postarr['meta_input']['_EventOrganizerID'];
			$valid                                      = [];
			foreach ( $postarr['meta_input']['_EventOrganizerID'] as $organizer ) {
				if ( ! tribe_is_organizer( $organizer ) ) {
					continue;
				}
				$valid[] = $organizer;
			}
			if ( ! count( $valid ) ) {
				unset( $postarr['meta_input']['_EventOrganizerID'] );
			} else {
				$this->unpack_meta_on_update( '_EventOrganizerID' );
				// Pass this to the function to have this value passed to the closure later.
				$postarr['meta_input']['_EventOrganizerID'] = $valid;
			}
		}

		return $postarr;
	}

	/**
	 * Updates an event accessory meta and attributes.
	 *
	 * @since 4.9
	 *
	 * @param array $postarr The candidate post array for the update or insertion.
	 * @param int   $post_id The ID of the event that is being updated.
	 *
	 * @return array The updated post array for update or insertion.
	 */
	protected function update_accessory_meta( array $postarr, $post_id ) {
		$postarr['meta_input']['_EventOrigin'] = 'events-calendar';

		// Set the map-related settings, default to `true` for new events.
		foreach ( [ '_EventShowMap', '_EventShowMapLink' ] as $meta_key ) {
			$new_value = tribe_is_truthy( $this->get_from_postarr_or_meta( $postarr, $meta_key, $post_id, true ) );
			if ( $new_value !== tribe_is_truthy( get_post_meta( $post_id, $meta_key, true ) ) ) {
				$postarr['meta_input'][ $meta_key ] = $new_value;
			}
		}

		$currency_symbol_positions = [ 'prefix', 'postfix' ];
		if ( isset( $postarr['meta_input']['_EventCurrencyPosition'] )
		     && ! in_array( $postarr['meta_input']['_EventCurrencyPosition'], $currency_symbol_positions, true )
		) {
			$postarr['meta_input']['_EventCurrencyPosition'] = 'prefix';
		}

		if ( isset( $postarr['meta_input']['_EventHideFromUpcoming'] ) ) {
			if ( tribe_is_truthy( $postarr['meta_input']['_EventHideFromUpcoming'] ) ) {
				$postarr['meta_input']['_EventHideFromUpcoming'] = 'yes';
			} else {
				unset( $postarr['meta_input']['_EventHideFromUpcoming'] );
			}
		}

		if ( isset( $postarr['meta_input']['sticky'] ) ) {
			if ( tribe_is_truthy( $postarr['meta_input']['sticky'] ) ) {
				$postarr['menu_order'] = - 1;
			} else {
				$postarr['menu_order'] = 0;
			}
			unset( $postarr['meta_input']['sticky'] );
		}

		if ( isset( $postarr['meta_input']['_tribe_featured'] ) ) {
			if ( tribe_is_truthy( $postarr['meta_input']['_tribe_featured'] ) ) {
				$postarr['meta_input']['_tribe_featured'] = true;
			} else {
				unset( $postarr['meta_input']['_tribe_featured'] );
			}
		}

		return $postarr;
	}

	/**
	 * {@inheritdoc}
	 */
	public function filter_postarr_for_create( array $postarr ) {
		// Before checking on the meta integrity and coherency let's try to normalize it an fill the missing fields.
		$postarr = $this->filter_meta_input( $postarr );

		// Require some minimum fields.
		if ( ! isset(
			$postarr['post_title'],
			$postarr['meta_input']['_EventEndDate']
		) ) {
			return false;
		}

		return parent::filter_postarr_for_create( $postarr );
	}

	/**
	 * Returns a filtered list of filters that are leveraging the event start and/or
	 * end dates.
	 *
	 * @since 4.9
	 *
	 * @return array The filtered list of filters that are leveraging the event start and/or end dates
	 */
	public function get_date_filters() {
		$date_filters = [
			'starts_before',
			'starts_after',
			'starts_between',
			'ends_before',
			'ends_after',
			'ends_between',
			'starts_and_ends_between',
			'runs_between',
			'start_date',
		];

		/**
		 * Filters the list of filters that should be considered related to an event start and/or end
		 * dates.
		 *
		 * @since 4.9
		 *
		 * @param array $date_filters The list of filters that should be considered related to an event start and/or end
		 *                            dates.
		 * @param Tribe__Events__Repositories__Event This repository instance.
		 */
		return apply_filters( "tribe_repository_{$this->filter_name}_date_filters", $date_filters, $this );
	}

	/**
	 * Whether the repository read operations have any kind of date-related filter
	 * applied or not.
	 *
	 * @since 4.9
	 *
	 * @return bool Whether the repository read operations have any kind of date-related filter applied or not.
	 */
	public function has_date_filters() {
		foreach ( $this->get_date_filters() as $filter ) {
			if ( $this->has_filter( $filter ) ) {
				return true;
			}
		}

		return false;
	}

	/**
	 * Filters events to include only those that start on a specific date.
	 *
	 * This method is a wrapper for the `filter_by_starts_between` one.
	 *
	 * @since 4.9
	 *
	 * @param      int|string|\DateTime $date     A date and time timestamp, string or object.
	 * @param null                      $timezone The timezone that should be used to filter events, if not passed
	 *                                            the site one will be used. This parameter will be ignored if the
	 *                                            `$date` parameter is an object.
	 *
	 * @throws Exception If the date and/or timezone provided for the filtering are not valid.
	 */
	public function filter_by_on_date( $date, $timezone = null ) {
		$timezone = Tribe__Timezones::build_timezone_object( $timezone );
		$date     = Tribe__Date_Utils::build_date_object( $date, $timezone );

		$begin = new DateTime( tribe_beginning_of_day( $date->format( Tribe__Date_Utils::DBDATETIMEFORMAT ) ), $timezone );
		$end   = new DateTime( tribe_end_of_day( $date->format( Tribe__Date_Utils::DBDATETIMEFORMAT ) ), $timezone );

		// Add on second to the previous day to get the start of this day.
		$this->filter_by_starts_between( $begin, $end );
	}

	/**
	 * Instructs the repository to use UTC dates and times for reading operations or not.
	 *
	 * By default the repository will use the events `_EventStartDateUTC` and `_EventEndDateUTC` meta keys
	 * depending on the site Time Zone Settings.
	 * This method allows overriding this behavior on a per-instance basis.
	 *
	 * @since 4.9
	 *
	 * @param bool $use_utc Whether ot use the UTC dates and times to read events or not. If `true` then the
	 *                      `_EventStartDateUTC` and `_EventEndDateUTC` meta keys will be used, if `false` then the
	 *                      `_EventStartDate` and `_EventEndDate` meta keys will be used.
	 *
	 * @return static This repository instance.
	 */
	public function use_utc( $use_utc ) {
		$this->normal_timezone = $use_utc ?
			new DateTimeZone( 'UTC' )
			: Timezones::build_timezone_object();
		$this->start_meta_key  = $use_utc ? '_EventStartDateUTC' : '_EventStartDate';
		$this->end_meta_key    = $use_utc ? '_EventEndDateUTC' : '_EventEndDate';
		$this->using_utc       = (bool) $use_utc;

		return $this;
	}

	/**
	 * {@inheritDoc}
	 */
	protected function format_item( $id ) {
		$formatted = null === $this->formatter
			? tribe_get_event( $id )
			: $this->formatter->format_item( $id );

		/**
		 * Filters a single formatted event result.
		 *
		 * @since 4.9.7
		 *
		 * @param mixed|WP_Post                $formatted The formatted event result, usually a post object.
		 * @param int                          $id        The formatted post ID.
		 * @param Tribe__Repository__Interface $this      The current repository object.
		 */
		$formatted = apply_filters( 'tribe_repository_events_format_item', $formatted, $id, $this );

		return $formatted;
	}


	/**
	 * Handles the `order_by` clauses for events
	 *
	 * @since 4.9.7
	 *
	 * @param string $order_by The key used to order events; e.g. `event_date` to order events by start date.
	 */
	public function handle_order_by( $order_by ) {
		$check_orderby = $order_by;

		if ( ! is_array( $check_orderby ) ) {
			$check_orderby = explode( ' ', $check_orderby );
		}

		$timestamp_key = 'TIMESTAMP(mt1.meta_value)';

		$after = false;
		$loop  = 0;

		foreach ( $check_orderby as $key => $value ) {
			$order_by      = is_numeric( $key ) ? $value : $key;
			$default_order = Arr::get_in_any( [ $this->query_args, $this->default_args ], 'order', 'ASC' );
			$order         = is_numeric( $key ) ? $default_order : $value;

			// Let the first applied ORDER BY clause override the existing ones, then stack the ORDER BY clauses.
			$override = $loop === 0;

			switch ( $order_by ) {
				case 'event_date':
					$this->order_by_date( false, $order, $after, $override );
					break;
				case 'event_date_utc':
					$this->order_by_date( true, $order, $after, $override );
					break;
				case 'event_duration':
					$this->order_by_duration( $order, $after, $override );
					break;
				case 'organizer':
					$this->order_by_organizer( $order, $after, $override );
					break;
				case 'venue':
					$this->order_by_venue( $order, $after, $override );
					break;
				case $timestamp_key:
					$this->filter_query->orderby( [ $timestamp_key => $default_order ], null, null, $after );
					break;
				case '__none':
					unset( $this->query_args['orderby'] );
					unset( $this->query_args['order'] );
					break;
				default:
					$after = $after || 1 === $loop;
					if ( empty( $this->query_args['orderby'] ) ) {
						// In some versions of WP, [ $order_by, $order ] doesn't work as expected. Using explict value setting instead.
						$this->query_args['orderby'] = $order_by;
						$this->query_args['order']   = $order;
					} else {
						$add = [ $order_by => $order ];
						// Make sure all `orderby` clauses have the shape `<orderby> => <order>`.
						$normalized = [];

						if ( ! is_array( $this->query_args['orderby'] ) ) {
							$this->query_args['orderby'] = [
								$this->query_args['orderby'] => $this->query_args['order']
							];
						}

						foreach ( $this->query_args['orderby'] as $k => $v ) {
							$the_order_by                = is_numeric( $k ) ? $v : $k;
							$the_order                   = is_numeric( $k ) ? $default_order : $v;
							$normalized[ $the_order_by ] = $the_order;
						}
						$this->query_args['orderby'] = $normalized;
						$this->query_args['orderby'] = array_merge( $this->query_args['orderby'], $add );
					}
					break;
			}
		}
	}

	/**
	 * Overrides the base method to correctly handle the `order_by` clauses before.
	 *
	 * The Event repository handles ordering with some non trivial logic and some query filtering.
	 * To avoid the "stacking" of `orderby` clauses and filters the query filters are added at the very last moment,
	 * right before building the query.
	 *
	 * @since 4.9.7
	 *
	 * @return WP_Query The built query object.
	 */
	protected function build_query_internally() {
		$order_by = Arr::get_in_any( [ $this->query_args, $this->default_args ], 'orderby', 'event_date' );
		unset( $this->query_args['orderby'], $this->default_args['order_by'] );

		$this->handle_order_by( $order_by );

		return parent::build_query_internally();
	}

	/**
	 * Applies start-date-based ordering to the query.
	 *
	 * @since 4.9.7
	 * @since 4.9.11 Added the `$after` parameter.
	 *
	 * @param bool   $use_utc    Whether to use the events UTC start dates or their localized dates.
	 * @param string $order      The order direction, either `ASC` or `DESC`; defaults to `null` to use the order
	 *                           specified in the current query or default arguments.
	 * @param bool   $after      Whether to append the order by clause to the ones managed by WordPress or not.
	 *                           Defaults to `false`,to prepend them to the ones managed by WordPress.
	 * @param bool   $override   Whether to override existing ORDER BY clauses or not; default to `true` to override
	 *                           existing ORDER BY clauses.
	 */
	protected function order_by_date( $use_utc, $order = null, $after = false, $override = true ) {
		global $wpdb;

		$meta_alias = 'event_date';
		$meta_key = '_EventStartDate';

		/**
		 * When the "Use site timezone everywhere" option is checked in events settings,
		 * the UTC time for event start and end times will be used. This filter allows the
		 * disabling of that in certain contexts, so that local (not UTC) event times are used.
		 *
		 * @since 4.6.10
		 *
		 * @param boolean $force_local_tz Whether to force the local TZ.
		 */
		$force_local_tz = apply_filters( 'tribe_events_query_force_local_tz', false );

		if ( null === $this->using_utc ) {
			/*
			 * The setting is not being forced by means of a call to the `use_utc` method.
			 * First we check if we've got a UTC ordering request in the `orderby` clause.
			 * After that if the use of the local (to the event) timezone is being forced by a filter.
			 * Finally if the timezone setting is set to use the site-wide timezone or not.
			 */
			if (
				$use_utc
				|| ( ! $force_local_tz && Tribe__Events__Timezones::is_mode( 'site' ) )
			) {
				$meta_alias = 'event_date_utc';
				$meta_key   = '_EventStartDateUTC';
			}
		} elseif ( true === $this->using_utc ) {
			// The setting is being forced by means of a call to the `use_utc` method; ignore anything else.
			$meta_alias = 'event_date_utc';
			$meta_key   = '_EventStartDateUTC';
		}

		$postmeta_table = "orderby_{$meta_alias}_meta";

		$filter_id = 'order_by_date';

		$this->filter_query->join(
			$wpdb->prepare(
				"
				LEFT JOIN {$wpdb->postmeta} AS {$postmeta_table}
					ON (
						{$postmeta_table}.post_id = {$wpdb->posts}.ID
						AND {$postmeta_table}.meta_key = %s
					)
				",
				$meta_key
			),
			$filter_id,
			true
		);

		$order = $order === null
			? Arr::get_in_any( [ $this->query_args, $this->default_args ], 'order', 'ASC' )
			: $order;
		$this->filter_query->orderby( [ $meta_alias => $order ], $filter_id, $override, $after );
		$this->filter_query->fields( "CAST( {$postmeta_table}.meta_value AS DATETIME ) AS {$meta_alias}", $filter_id, $override );
	}

	/**
	 * Applies Organizer-based ordering to the query.
	 *
	 * @since 4.9.7
	 * @since 4.9.11 Added the `$after` parameter.
	 *
	 * @param string $order      The order direction, either `ASC` or `DESC`; defaults to `null` to use the order
	 *                           specified in the current query or default arguments.
	 * @param bool   $after      Whether to append the order by clause to the ones managed by WordPress or not.
	 *                           Defaults to `false`,to prepend them to the ones managed by WordPress.
	 * @param bool   $override   Whether to override existing ORDER BY clauses with this one or not; default to
	 *                           `true` to override existing ORDER BY clauses.
	 */
	protected function order_by_organizer( $order = null, $after = false, $override = true ) {
		global $wpdb;

		$postmeta_table = 'orderby_organizer_meta';
		$posts_table    = 'orderby_organizer_posts';

		$meta_key = '_EventOrganizerID';

		$this->filter_query->join(
			$wpdb->prepare(
				"
				LEFT JOIN {$wpdb->postmeta} AS {$postmeta_table}
					ON (
						{$postmeta_table}.post_id = {$wpdb->posts}.ID
						AND {$postmeta_table}.meta_key = %s
					)
				LEFT JOIN {$wpdb->posts} AS {$posts_table}
					ON {$wpdb->posts}.ID = {$postmeta_table}.meta_value
				",
				$meta_key
			)
		);

		$filter_id = 'order_by_organizer';
		$order     = $order === null
			? Arr::get_in_any( [ $this->query_args, $this->default_args ], 'order', 'ASC' )
			: $order;

		$this->filter_query->orderby( [ 'organizer' => $order ], $filter_id, $override, $after );
		$this->filter_query->fields( "{$posts_table}.post_title AS organizer", $filter_id, $override );
	}

	/**
	 * Applies Venue-based ordering to the query.
	 *
	 * @since 4.9.7
	 * @since 4.9.11 Added the `$after` parameter.
	 *
	 * @param string $order      The order direction, either `ASC` or `DESC`; defaults to `null` to use the order
	 *                           specified in the current query or default arguments.
	 * @param bool   $after      Whether to append the order by clause to the ones managed by WordPress or not.
	 *                           Defaults to `false`,to prepend them to the ones managed by WordPress.
	 * @param bool   $override   Whether to override existing ORDER BY clauses with this one or not; default to
	 *                           `true` to override existing ORDER BY clauses.
	 */
	protected function order_by_venue( $order = null,$after = false, $override = true ) {
		global $wpdb;

		$postmeta_table = 'orderby_venue_meta';
		$posts_table    = 'orderby_venue_posts';

		$meta_key = '_EventVenueID';

		$this->filter_query->join(
			$wpdb->prepare(
				"
				LEFT JOIN {$wpdb->postmeta} AS {$postmeta_table}
					ON (
						{$postmeta_table}.post_id = {$wpdb->posts}.ID
						AND {$postmeta_table}.meta_key = %s
					)
				LEFT JOIN {$wpdb->posts} AS {$posts_table}
					ON {$wpdb->posts}.ID = {$postmeta_table}.meta_value
				",
				$meta_key
			)
		);

		$filter_id = 'order_by_venue';
		$order     = $order === null
			? Arr::get_in_any( [ $this->query_args, $this->default_args ], 'order', 'ASC' )
			: $order;

		$this->filter_query->orderby( [ 'venue' => $order ], $filter_id, $override, $after );
		$this->filter_query->fields( "{$posts_table}.post_title AS venue", $filter_id, $override );
	}

	/**
	 * Overrides the base method to default the `order` to `ASC` for events.
	 *
	 * @since 4.9.7
	 *
	 * @param string      $order_by The key to order events by.
	 * @param string|null $order    The order direction, either `ASC` or `DESC`; defaults to `ASC`.
	 *
	 * @return Tribe__Repository|Tribe__Repository__Read_Interface This repository instance.
	 */
	public function order_by( $order_by, $order = 'ASC' ) {
		return parent::order_by( $order_by, $order );
	}

	/**
	 * Filters events by their "Hidden from Event Listings" status.
	 *
	 * This method assumes that we keep the following structure:
	 * - if an event should be hidden its `_EventHideFromUpcoming` meta will be set to `yes` (or another truthy value).
	 * - if an event should not be hidden its `_EventHideFromUpcoming` meta will not be set at all.
	 *
	 * @since 4.9.11
	 *
	 * @param bool $hidden Whether the events should be hidden from event listings or not.
	 */
	public function filter_by_hidden_on_upcoming( $hidden ) {
		$hidden = tribe_is_truthy( $hidden );
		$hidden_posts = tribe( \Tribe\Events\Views\V2\Query\Hide_From_Upcoming_Controller::class )->get_hidden_post_ids();

		if ( $hidden ) {
			if ( isset( $this->query_args['post__in'] ) ) {
				$hidden_posts = array_merge( (array) $this->query_args['post__in'], $hidden_posts );
			}
			$this->in( $hidden_posts );
		} else {
			if ( isset( $this->query_args['post__not_in'] ) ) {
				$hidden_posts = array_merge( (array) $this->query_args['post__not_in'], $hidden_posts );
			}
			$this->not_in( $hidden_posts );
		}
	}

	/**
	 * Sets up the query filters to order events by the duration (`_EventDuration`) custom field.
	 *
	 * @since 5.1.5
	 *
	 * @param string $order      The order direction, either `ASC` or `DESC`; defaults to `null` to use the order
	 *                           specified in the current query or default arguments.
	 * @param bool   $after      Whether to append the duration ORDER BY clause to the existing clauses or not;
	 *                           defaults to `false` to prepend the duration clause to the existing ORDER BY
	 *                           clauses.
	 * @param bool   $override   Whether to override existing ORDER BY clauses with this one or not; default to
	 *                           `true` to override existing ORDER BY clauses.
	 */
	protected function order_by_duration( $order = null, $after = false, $override = true ) {
		global $wpdb;

		$meta_alias = 'event_duration';
		$meta_key   = '_EventDuration';

		$postmeta_table = "orderby_{$meta_alias}_meta";

		$filter_id = 'order_by_duration';

		$this->filter_query->join(
			$wpdb->prepare(
				"
				LEFT JOIN {$wpdb->postmeta} AS {$postmeta_table}
					ON (
						{$postmeta_table}.post_id = {$wpdb->posts}.ID
						AND {$postmeta_table}.meta_key = %s
					)
				",
				$meta_key
			),
			$filter_id,
			true
		);

		$order = $order === null
			? Arr::get_in_any( [ $this->query_args, $this->default_args ], 'order', 'ASC' )
			: $order;
		$this->filter_query->orderby( [ $meta_alias => $order ], $filter_id, $override, $after );
		$this->filter_query->fields( "CAST( {$postmeta_table}.meta_value AS DECIMAL ) AS {$meta_alias}", $filter_id, $override );
	}
}