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`Mini Shell

HOME


Mini Shell 1.0
DIR:/home/theoceans.in/www/wp-content/plugins/unyson/framework/helpers/
Upload File :
Current File : //home/theoceans.in/www/wp-content/plugins/unyson/framework/helpers/general.php
<?php if ( ! defined( 'FW' ) ) {
	die( 'Forbidden' );
}
// Useful functions

/**
 * Convert to Unix style directory separators
 */
function fw_fix_path( $path ) {
	$windows_network_path = isset( $_SERVER['windir'] ) && in_array( substr( $path, 0, 2 ),
			array( '//', '\\\\' ),
			true );
	$fixed_path           = untrailingslashit( str_replace( array( '//', '\\' ), array( '/', '/' ), $path ) );

	if ( empty( $fixed_path ) && ! empty( $path ) ) {
		$fixed_path = '/';
	}

	if ( $windows_network_path ) {
		$fixed_path = '//' . ltrim( $fixed_path, '/' );
	}

	return $fixed_path;
}

/**
 * Relative path of the framework customizations directory
 *
 * @param string $append
 *
 * @return string
 */
function fw_get_framework_customizations_dir_rel_path( $append = '' ) {
	try {
		$dir = FW_Cache::get( $cache_key = 'fw_customizations_dir_rel_path' );
	} catch ( FW_Cache_Not_Found_Exception $e ) {
		FW_Cache::set(
			$cache_key,
			$dir = apply_filters( 'fw_framework_customizations_dir_rel_path', '/framework-customizations' )
		);
	}

	return $dir . $append;
}

/** Child theme related functions */
{
	/**
	 * Full path to the child-theme framework customizations directory
	 *
	 * @param string $rel_path
	 *
	 * @return null|string
	 */
	function fw_get_stylesheet_customizations_directory( $rel_path = '' ) {
		if ( is_child_theme() ) {
			return get_stylesheet_directory() . fw_get_framework_customizations_dir_rel_path( $rel_path );
		} else {
			// check is_child_theme() before using this function
			return null;
		}
	}

	/**
	 * URI to the child-theme framework customizations directory
	 *
	 * @param string $rel_path
	 *
	 * @return null|string
	 */
	function fw_get_stylesheet_customizations_directory_uri( $rel_path = '' ) {
		if ( is_child_theme() ) {
			return get_stylesheet_directory_uri() . fw_get_framework_customizations_dir_rel_path( $rel_path );
		} else {
			// check is_child_theme() before using this function
			return null;
		}
	}
}

/** Parent theme related functions */
{
	/**
	 * Full path to the parent-theme framework customizations directory
	 *
	 * @param string $rel_path
	 *
	 * @return string
	 */
	function fw_get_template_customizations_directory( $rel_path = '' ) {
		try {
			$dir = FW_Cache::get( $cache_key = 'fw_template_customizations_dir' );
		} catch ( FW_Cache_Not_Found_Exception $e ) {
			FW_Cache::set(
				$cache_key,
				$dir = get_template_directory() . fw_get_framework_customizations_dir_rel_path()
			);
		}

		return $dir . $rel_path;
	}

	/**
	 * URI to the parent-theme framework customizations directory
	 *
	 * @param string $rel_path
	 *
	 * @return string
	 */
	function fw_get_template_customizations_directory_uri( $rel_path = '' ) {
		try {
			$dir = FW_Cache::get( $cache_key = 'fw_template_customizations_dir_uri' );
		} catch ( FW_Cache_Not_Found_Exception $e ) {
			FW_Cache::set(
				$cache_key,
				$dir = get_template_directory_uri() . fw_get_framework_customizations_dir_rel_path()
			);
		}

		return $dir . $rel_path;
	}
}

/** Framework related functions */
{
	/**
	 * Full path to the parent-theme/framework directory
	 *
	 * @param string $rel_path
	 *
	 * @return string
	 */
	function fw_get_framework_directory( $rel_path = '' ) {
		try {
			$dir = FW_Cache::get( $cache_key = 'fw_framework_dir' );
		} catch ( FW_Cache_Not_Found_Exception $e ) {
			FW_Cache::set(
				$cache_key,
				$dir = apply_filters(
					'fw_framework_directory',
					fw_fix_path( dirname( dirname( __FILE__ ) ) ) // double dirname() to remove '/helpers', use parent dir
				)
			);
		}

		return $dir . $rel_path;
	}

	/**
	 * URI to the parent-theme/framework directory
	 *
	 * @param string $rel_path
	 *
	 * @return string
	 */
	function fw_get_framework_directory_uri( $rel_path = '' ) {
		try {
			$uri = FW_Cache::get( $cache_key = 'fw_framework_dir_uri' );
		} catch ( FW_Cache_Not_Found_Exception $e ) {
			FW_Cache::set(
				$cache_key,
				$uri = apply_filters(
					'fw_framework_directory_uri',
					( $uri = fw_get_path_url( fw_get_framework_directory() ) )
						? $uri
						: get_template_directory_uri() . '/framework'
				)
			);
		}

		return $uri . $rel_path;
	}
}

/**
 * Recursively find a key's value in array
 *
 * @param string $keys 'a/b/c'
 * @param array|object $array_or_object
 * @param null|mixed $default_value
 * @param string $keys_delimiter
 *
 * @return null|mixed
 */
function fw_akg( $keys, $array_or_object, $default_value = null, $keys_delimiter = '/' ) {
	if ( ! is_array( $keys ) ) {
		$keys = explode( $keys_delimiter, (string) $keys );
	}

	$array_or_object = fw_call( $array_or_object );

	$key_or_property = array_shift( $keys );
	if ( $key_or_property === null ) {
		return fw_call( $default_value );
	}

	$is_object = is_object( $array_or_object );

	if ( $is_object ) {
		if ( ! property_exists( $array_or_object, $key_or_property ) ) {
			return fw_call( $default_value );
		}
	} else {
		if ( ! is_array( $array_or_object ) || ! array_key_exists( $key_or_property, $array_or_object ) ) {
			return fw_call( $default_value );
		}
	}

	if ( isset( $keys[0] ) ) { // not used count() for performance reasons
		if ( $is_object ) {
			return fw_akg( $keys, $array_or_object->{$key_or_property}, $default_value );
		} else {
			return fw_akg( $keys, $array_or_object[ $key_or_property ], $default_value );
		}
	} else {
		if ( $is_object ) {
			return $array_or_object->{$key_or_property};
		} else {
			return $array_or_object[ $key_or_property ];
		}
	}
}

/**
 * Set (or create if not exists) value for specified key in some array level
 *
 * @param string $keys 'a/b/c', or 'a/b/c/' equivalent to: $arr['a']['b']['c'][] = $val;
 * @param mixed $value
 * @param array|object $array_or_object
 * @param string $keys_delimiter
 *
 * @return array|object
 */
function fw_aks( $keys, $value, &$array_or_object, $keys_delimiter = '/' ) {
	if ( ! is_array( $keys ) ) {
		$keys = explode( $keys_delimiter, (string) $keys );
	}

	$key_or_property = array_shift( $keys );
	if ( $key_or_property === null ) {
		return $array_or_object;
	}

	$is_object = is_object( $array_or_object );

	if ( $is_object ) {
		if ( ! property_exists( $array_or_object, $key_or_property )
		     || ! ( is_array( $array_or_object->{$key_or_property} ) || is_object( $array_or_object->{$key_or_property} ) )
		) {
			if ( $key_or_property === '' ) {
				// this happens when use 'empty keys' like: abc/d/e////i/j//foo/
				trigger_error( 'Cannot push value to object like in array ($arr[] = $val)', E_USER_WARNING );
			} else {
				$array_or_object->{$key_or_property} = array();
			}
		}
	} else {
		if ( ! is_array( $array_or_object ) ) {
			$array_or_object = array();
		}

		if ( ! array_key_exists( $key_or_property,
				$array_or_object ) || ! is_array( $array_or_object[ $key_or_property ] )
		) {
			if ( $key_or_property === '' ) {
				// this happens when use 'empty keys' like: abc.d.e....i.j..foo.
				$array_or_object[] = array();

				// get auto created key (last)
				end( $array_or_object );
				$key_or_property = key( $array_or_object );
			} else {
				$array_or_object[ $key_or_property ] = array();
			}
		}
	}

	if ( isset( $keys[0] ) ) { // not used count() for performance reasons
		if ( $is_object ) {
			fw_aks( $keys, $value, $array_or_object->{$key_or_property} );
		} else {
			fw_aks( $keys, $value, $array_or_object[ $key_or_property ] );
		}
	} else {
		if ( $is_object ) {
			$array_or_object->{$key_or_property} = $value;
		} else {
			$array_or_object[ $key_or_property ] = $value;
		}
	}

	return $array_or_object;
}

/**
 * Unset specified key in some array level
 *
 * @param string $keys 'a/b/c' -> unset($arr['a']['b']['c']);
 * @param array|object $array_or_object
 * @param string $keys_delimiter
 *
 * @return array|object
 */
function fw_aku( $keys, &$array_or_object, $keys_delimiter = '/' ) {
	if ( ! is_array( $keys ) ) {
		$keys = explode( $keys_delimiter, (string) $keys );
	}

	$key_or_property = array_shift( $keys );
	if ( $key_or_property === null || $key_or_property === '' ) {
		return $array_or_object;
	}

	$is_object = is_object( $array_or_object );

	if ( $is_object ) {
		if ( ! property_exists( $array_or_object, $key_or_property ) ) {
			return $array_or_object;
		}
	} else {
		if ( ! is_array( $array_or_object ) || ! array_key_exists( $key_or_property, $array_or_object ) ) {
			return $array_or_object;
		}
	}

	if ( isset( $keys[0] ) ) { // not used count() for performance reasons
		if ( $is_object ) {
			fw_aku( $keys, $array_or_object->{$key_or_property} );
		} else {
			fw_aku( $keys, $array_or_object[ $key_or_property ] );
		}
	} else {
		if ( $is_object ) {
			unset( $array_or_object->{$key_or_property} );
		} else {
			unset( $array_or_object[ $key_or_property ] );
		}
	}

	return $array_or_object;
}

/**
 * Generate random unique md5
 */
function fw_rand_md5() {
	return md5( time() . '-' . uniqid( rand(), true ) . '-' . mt_rand( 1, 1000 ) );
}

function fw_unique_increment() {
	static $i = 0;

	return ++ $i;
}

/**
 * print_r() alternative
 *
 * @param mixed $value Value to debug
 */
function fw_print( $value ) {
	static $first_time = true;

	if ( $first_time ) {
		ob_start();
		echo '<style type="text/css">
		div.fw_print_r {
			max-height: 500px;
			overflow-y: scroll;
			background: #23282d;
			margin: 10px 30px;
			padding: 0;
			border: 1px solid #F5F5F5;
			border-radius: 3px;
			position: relative;
			z-index: 11111;
		}

		div.fw_print_r pre {
			color: #78FF5B;
			background: #23282d;
			text-shadow: 1px 1px 0 #000;
			font-family: Consolas, monospace;
			font-size: 12px;
			margin: 0;
			padding: 5px;
			display: block;
			line-height: 16px;
			text-align: left;
		}

		div.fw_print_r_group {
			background: #f1f1f1;
			margin: 10px 30px;
			padding: 1px;
			border-radius: 5px;
			position: relative;
			z-index: 11110;
		}
		div.fw_print_r_group div.fw_print_r {
			margin: 9px;
			border-width: 0;
		}
		</style>';
		echo str_replace( array( '  ', "\n" ), '', ob_get_clean() );

		$first_time = false;
	}

	if ( func_num_args() == 1 ) {
		echo '<div class="fw_print_r"><pre>';
		echo fw_htmlspecialchars( FW_Dumper::dump( $value ) );
		echo '</pre></div>';
	} else {
		echo '<div class="fw_print_r_group">';
		foreach ( func_get_args() as $param ) {
			fw_print( $param );
		}
		echo '</div>';
	}
}

/**
 * Alias for fw_print
 *
 * @see fw_print()
 */
if ( ! function_exists( 'debug' ) ) {
	function debug() {
		call_user_func_array( 'fw_print', func_get_args() );
	}
}

/**
 * Generate html tag
 *
 * @param string $tag Tag name
 * @param array $attr Tag attributes
 * @param bool|string $end Append closing tag. Also accepts body content
 *
 * @return string The tag's html
 */
function fw_html_tag( $tag, $attr = array(), $end = false ) {
	$html = '<' . $tag . ' ' . fw_attr_to_html( $attr );

	if ( $end === true ) {
		# <script></script>
		$html .= '></' . $tag . '>';
	} else if ( $end === false ) {
		# <br/>
		$html .= '/>';
	} else {
		# <div>content</div>
		$html .= '>' . $end . '</' . $tag . '>';
	}

	return $html;
}

/**
 * Generate attributes string for html tag
 *
 * @param array $attr_array array('href' => '/', 'title' => 'Test')
 *
 * @return string 'href="/" title="Test"'
 */
function fw_attr_to_html( array $attr_array ) {
	$html_attr = '';

	foreach ( $attr_array as $attr_name => $attr_val ) {
		if ( $attr_val === false ) {
			continue;
		}

		$html_attr .= $attr_name . '="' . fw_htmlspecialchars( $attr_val ) . '" ';
	}

	return $html_attr;
}

/**
 * Strip slashes from values, and from keys if magic_quotes_gpc = On
 */
function fw_stripslashes_deep_keys( $value ) {
	static $magic_quotes = null;
	if ( $magic_quotes === null ) {
		$magic_quotes = false; //https://www.php.net/manual/en/function.get-magic-quotes-gpc.php - always returns FALSE as of PHP 5.4.0. false fixes https://github.com/ThemeFuse/Unyson/issues/3915
	}

	if ( is_array( $value ) ) {
		if ( $magic_quotes ) {
			$new_value = array();
			foreach ( $value as $key => $val ) {
				$new_value[ is_string( $key ) ? stripslashes( $key ) : $key ] = fw_stripslashes_deep_keys( $val );
			}
			$value = $new_value;
			unset( $new_value );
		} else {
			$value = array_map( 'fw_stripslashes_deep_keys', $value );
		}
	} elseif ( is_object( $value ) ) {
		$vars = get_object_vars( $value );
		foreach ( $vars as $key => $data ) {
			$value->{$key} = fw_stripslashes_deep_keys( $data );
		}
	} elseif ( is_string( $value ) ) {
		$value = stripslashes( $value );
	}

	return $value;
}

/**
 * Add slashes to values, and to keys if magic_quotes_gpc = On
 */
function fw_addslashes_deep_keys( $value ) {
	static $magic_quotes = null;
	if ( $magic_quotes === null ) {
		$magic_quotes = get_magic_quotes_gpc();
	}

	if ( is_array( $value ) ) {
		if ( $magic_quotes ) {
			$new_value = array();
			foreach ( $value as $key => $value ) {
				$new_value[ is_string( $key ) ? addslashes( $key ) : $key ] = fw_addslashes_deep_keys( $value );
			}
			$value = $new_value;
			unset( $new_value );
		} else {
			$value = array_map( 'fw_addslashes_deep_keys', $value );
		}
	} elseif ( is_object( $value ) ) {
		$vars = get_object_vars( $value );
		foreach ( $vars as $key => $data ) {
			$value->{$key} = fw_addslashes_deep_keys( $data );
		}
	} elseif ( is_string( $value ) ) {
		$value = addslashes( $value );
	}

	return $value;
}

/**
 * Check if current screen pass/match give rules
 *
 * @param array $rules Rules for current screen
 *
 * @return bool
 */
function fw_current_screen_match( array $rules ) {
	$available_options = array(
		'action'      => true,
		'base'        => true,
		'id'          => true,
		'is_network'  => true,
		'is_user'     => true,
		'parent_base' => true,
		'parent_file' => true,
		'post_type'   => true,
		'taxonomy'    => true,
	);

	if ( empty( $rules ) ) {
		return true;
	}

	$rules = array_merge(
		array(
			'exclude' => array(), // array of arrays or array with keys from $available_options
			'only'    => array(), // same as in 'exclude'
		),
		$rules
	);

	if ( empty( $rules['exclude'] ) && empty( $rules['only'] ) ) {
		return true;
	}

	global $current_screen;

	if ( gettype( $current_screen ) != 'object' ) {
		return false;
	}

	// check if current screen passes the "only" rules
	do {
		$only = $rules['only'];

		if ( empty( $only ) ) {
			break;
		}

		if ( ! isset( $only[0] ) ) { // if not array of arrays
			$only = array( $only );
		}

		$found_one = false;
		$counter   = 0;
		foreach ( $only as $rule ) {
			if ( ! count( $rule ) ) {
				continue;
			}

			$match = true;

			foreach ( $rule as $r_key => $r_val ) {
				if ( ! isset( $available_options[ $r_key ] ) ) {
					continue;
				}

				if ( gettype( $r_val ) != 'array' ) {
					$r_val = array( $r_val );
				}

				$counter ++;

				if ( ! in_array( $current_screen->{$r_key}, $r_val ) ) {
					$match = false;
					break;
				}
			}

			if ( $match ) {
				$found_one = true;
				break;
			}
		}

		if ( ! $found_one && $counter ) {
			return false;
		}
	} while ( false );

	// check if current screen passes the "exclude" rules
	do {
		$exclude = $rules['exclude'];

		if ( empty( $exclude ) ) {
			break;
		}

		if ( ! isset( $exclude[0] ) ) { // if not array of arrays
			$exclude = array( $exclude );
		}

		foreach ( $exclude as $rule ) {
			if ( ! count( $rule ) ) {
				continue;
			}

			$match   = true;
			$counter = 0;

			foreach ( $rule as $r_key => $r_val ) {
				if ( ! isset( $available_options[ $r_key ] ) ) {
					continue;
				}

				if ( gettype( $r_val ) != 'array' ) {
					$r_val = array( $r_val );
				}

				$counter ++;

				if ( ! in_array( $current_screen->{$r_key}, $r_val ) ) {
					$match = false;
					break;
				}
			}

			if ( $match && $counter ) {
				return false;
			}
		}
	} while ( false );

	return true;
}

/**
 * Search relative path in child then in parent theme directory and return URI
 *
 * @param  string $rel_path '/some/path_to_dir' or '/some/path_to_file.php'
 *
 * @return string URI
 */
function fw_locate_theme_path_uri( $rel_path ) {
	if ( is_child_theme() && file_exists( get_stylesheet_directory() . $rel_path ) ) {
		return get_stylesheet_directory_uri() . $rel_path;
	} elseif ( file_exists( get_template_directory() . $rel_path ) ) {
		return get_template_directory_uri() . $rel_path;
	} else {
		return 'about:blank#theme-file-not-found:' . $rel_path;
	}
}

/**
 * Search relative path in child then in parent theme directory and return full path
 *
 * @param  string $rel_path '/some/path_to_dir' or '/some/path_to_file.php'
 *
 * @return string URI
 */
function fw_locate_theme_path( $rel_path ) {
	if ( is_child_theme() && file_exists( get_stylesheet_directory() . $rel_path ) ) {
		return get_stylesheet_directory() . $rel_path;
	} elseif ( file_exists( get_template_directory() . $rel_path ) ) {
		return get_template_directory() . $rel_path;
	} else {
		return false;
	}
}

/**
 * There is a theme which does: if (!defined('FW')): function fw_render_view() { ... } endif;
 * It works fine, except in this case
 * https://github.com/ThemeFuse/Unyson/commit/07be8b1f4b50eaf0f1f7e85ea1c6912a0415d241#diff-cf866bf08b8f747e3120221a6b1b07cfR48
 * it throws fatal error because this function here is defined after that
 */
if ( ! function_exists( 'fw_render_view' ) ):
	/**
	 * Safe render a view and return html
	 * In view will be accessible only passed variables
	 * Use this function to not include files directly and to not give access to current context variables (like $this)
	 *
	 * @param string $file_path
	 * @param array $view_variables
	 * @param bool $return In some cases, for memory saving reasons, you can disable the use of output buffering
	 *
	 * @return string HTML
	 */
	function fw_render_view( $file_path, $view_variables = array(), $return = true ) {

		if ( ! is_file( $file_path ) ) {
			return '';
		}

		extract( $view_variables, EXTR_REFS );
		unset( $view_variables );

		if ( $return ) {
			ob_start();
			require $file_path;

			return ob_get_clean();
		} else {
			require $file_path;
		}

		return '';
	}
endif;

/**
 * Safe load variables from an file
 * Use this function to not include files directly and to not give access to current context variables (like $this)
 *
 * @param string $file_path
 * @param array $_extract_variables Extract these from file array('variable_name' => 'default_value')
 * @param array $_set_variables Set these to be available in file (like variables in view)
 *
 * @return array
 */
function fw_get_variables_from_file( $file_path, array $_extract_variables, array $_set_variables = array() ) {
	extract( $_set_variables, EXTR_REFS );
	unset( $_set_variables );

	require $file_path;

	foreach ( $_extract_variables as $variable_name => $default_value ) {
		if ( isset( $$variable_name ) ) {
			$_extract_variables[ $variable_name ] = $$variable_name;
		}
	}

	return $_extract_variables;
}

/**
 * Use this function to not include files directly and to not give access to current context variables (like $this)
 *
 * @param string $file_path
 * @param bool $once
 *
 * @return bool If was included or not
 */
function fw_include_file_isolated( $file_path, $once = false ) {
	if ( file_exists( $file_path ) ) {
		if ( (bool) $once ) {
			include_once $file_path;
		} else {
			include $file_path;
		}

		return true;
	} else {
		return false;
	}
}

/**
 * Extract only input options (without containers)
 *
 * @param array $options
 *
 * @return array {option_id => option}
 */
function fw_extract_only_options( array $options ) {
	$collected = array();

	fw_collect_options( $collected, $options );

	return $collected;
}

/**
 * Collect correct options from the first level of the array and group them
 *
 * @param array $collected Will be filled with found correct options
 * @param array $options
 *
 * @deprecated
 * It is deprecated since 2.4 because container types were added and there can be any type of containers
 * but this function is hardcoded only for tab,box,group.
 * Use fw_collect_options()
 */
function fw_collect_first_level_options( &$collected, &$options ) {
	if ( empty( $options ) ) {
		return;
	}

	if ( empty( $collected ) ) {
		$collected['tabs'] =
		$collected['boxes'] =
		$collected['groups'] =
		$collected['options'] =
		$collected['groups_and_options'] =
		$collected['all'] = array();
	}

	foreach ( $options as $option_id => &$option ) {
		if ( isset( $option['options'] ) ) {
			// this is container for other options

			switch ( $option['type'] ) {
				case 'tab':
					$collected['tabs'][ $option_id ] =& $option;
					break;
				case 'box':
					$collected['boxes'][ $option_id ] =& $option;
					break;
				case 'group':
					$collected['groups'][ $option_id ]             =& $option;
					$collected['groups_and_options'][ $option_id ] =& $option;
					break;
				default:
					trigger_error( 'Invalid option container type: ' . $option['type'], E_USER_WARNING );
					continue 2;
			}

			$collected['all'][ $option['type'] . ':~:' . $option_id ] = array(
				'type'   => $option['type'],
				'id'     => $option_id,
				'option' => &$option,
			);
		} elseif (
			is_int( $option_id )
			&&
			is_array( $option )
			&&
			/**
			 * make sure the array key was generated automatically
			 * and it's not an associative array with numeric keys created like this: $options[1] = array();
			 */
			isset( $options[0] )
		) {
			/**
			 * Array "without key" containing options.
			 *
			 * This happens when options are returned into array from a function:
			 * $options = array(
			 *  'foo' => array('type' => 'text'),
			 *  'bar' => array('type' => 'textarea'),
			 *
			 *  // this is our case
			 *  // go inside this array and extract the options as they are on the same array level
			 *  array(
			 *      'hello' => array('type' => 'text'),
			 *  ),
			 *
			 *  // there can be any nested arrays
			 *  array(
			 *      array(
			 *          array(
			 *              'h1' => array('type' => 'text'),
			 *          ),
			 *      ),
			 *  ),
			 * )
			 */
			fw_collect_first_level_options( $collected, $option );
		} elseif ( isset( $option['type'] ) ) {
			// simple option, last possible level in options array
			$collected['options'][ $option_id ]            =& $option;
			$collected['groups_and_options'][ $option_id ] =& $option;

			$collected['all'][ 'option' . ':~:' . $option_id ] = array(
				'type'   => 'option',
				'id'     => $option_id,
				'option' => &$option,
			);
		} else {
			trigger_error( 'Invalid option: ' . $option_id, E_USER_WARNING );
		}
	}
	unset( $option );
}

/**
 * @param array $result
 * @param array $options
 * @param array $settings
 * @param array $_recursion_data (private) for internal use
 */
function fw_collect_options( &$result, &$options, $settings = array(), $_recursion_data = array() ) {
	static $default_settings = array(
		/**
		 * @type bool Wrap the result/collected options in arrays will useful info
		 *
		 * If true:
		 * $result = array(
		 *   '(container|option):{id}' => array(
		 *      'id' => '{id}',
		 *      'level' => int, // from which nested level this option is
		 *      'group' => 'container|option',
		 *      'option' => array(...),
		 *   )
		 * )
		 *
		 * If false:
		 * $result = array(
		 *   '{id}' => array(...),
		 *   // Warning: There can be options and containers with the same id (array key will be replaced)
		 * )
		 */
		'info_wrapper'          => false,
		/**
		 * @type int Nested options level limit. For e.g. use 1 to collect only first level. 0 is for unlimited.
		 */
		'limit_level'           => 0,
		/**
		 * @type false|array('option-type', ...) Empty array will skip all types
		 */
		'limit_option_types'    => false,
		/**
		 * @type false|array('container-type', ...) Empty array will skip all types
		 */
		'limit_container_types' => array(),
		/**
		 * @type int Limit the number of options that will be collected
		 */
		'limit'                 => 0,
		/**
		 * @type callable Executed on each collected option
		 * @since 2.6.0
		 */
		'callback'              => null,
	);

	static $access_key = null;

	if ( empty( $options ) ) {
		return;
	}

	if ( empty( $_recursion_data ) ) {
		if ( is_null( $access_key ) ) {
			$access_key = new FW_Access_Key( 'fw_collect_options' );
		}

		$settings = array_merge( $default_settings, $settings );

		$_recursion_data = array(
			'level'      => 1,
			'access_key' => $access_key,
			// todo: maybe add 'parent' => array('id' => '{id}', 'type' => 'container|option') ?
		);
	} elseif ( ! (
		isset( $_recursion_data['access_key'] )
		&&
		( $_recursion_data['access_key'] instanceof FW_Access_Key )
		&&
		( $_recursion_data['access_key']->get_key() === 'fw_collect_options' )
	)
	) {
		trigger_error( 'Call not allowed', E_USER_ERROR );
	}

	if (
		$settings['limit_level']
		&&
		$_recursion_data['level'] > $settings['limit_level']
	) {
		return;
	}

	foreach ( $options as $option_id => &$option ) {
		if ( isset( $option['options'] ) ) { // this is a container
			do {
				if (
					is_array( $settings['limit_container_types'] )
					&&
					(
						// Customizer options can contain options with not existing or empty $option['type']
						empty( $option['type'] )
						||
						! in_array( $option['type'], $settings['limit_container_types'] )
					)
				) {
					break;
				}

				if (
					$settings['limit']
					&&
					count( $result ) >= $settings['limit']
				) {
					return;
				}

				if ( $settings['info_wrapper'] ) {
					$result[ 'container:' . $option_id ] = array(
						'group'  => 'container',
						'id'     => $option_id,
						'option' => &$option,
						'level'  => $_recursion_data['level'],
					);
				} else {
					$result[ $option_id ] = &$option;
				}

				if ( $settings['callback'] ) {
					call_user_func_array( $settings['callback'],
						array(
							array(
								'group'  => 'container',
								'id'     => $option_id,
								'option' => &$option,
							)
						) );
				}
			} while ( false );

			fw_collect_options(
				$result,
				$option['options'],
				$settings,
				array_merge( $_recursion_data, array( 'level' => $_recursion_data['level'] + 1 ) )
			);
		} elseif (
			is_int( $option_id )
			&&
			is_array( $option )
			&&
			/**
			 * make sure the array key was generated automatically
			 * and it's not an associative array with numeric keys created like this: $options[1] = array();
			 */
			isset( $options[0] )
		) {
			/**
			 * Array "without key" containing options.
			 *
			 * This happens when options are returned into array from a function:
			 * $options = array(
			 *  'foo' => array('type' => 'text'),
			 *  'bar' => array('type' => 'textarea'),
			 *
			 *  // this is our case
			 *  // go inside this array and extract the options as they are on the same array level
			 *  array(
			 *      'hello' => array('type' => 'text'),
			 *  ),
			 *
			 *  // there can be any nested arrays
			 *  array(
			 *      array(
			 *          array(
			 *              'h1' => array('type' => 'text'),
			 *          ),
			 *      ),
			 *  ),
			 * )
			 */
			fw_collect_options( $result, $option, $settings, $_recursion_data );
		} elseif ( isset( $option['type'] ) ) { // option
			if (
				is_array( $settings['limit_option_types'] )
				&&
				! in_array( $option['type'], $settings['limit_option_types'] )
			) {
				continue;
			}

			if (
				$settings['limit']
				&&
				count( $result ) >= $settings['limit']
			) {
				return;
			}

			if ( $settings['info_wrapper'] ) {
				$result[ 'option:' . $option_id ] = array(
					'group'  => 'option',
					'id'     => $option_id,
					'option' => &$option,
					'level'  => $_recursion_data['level'],
				);
			} else {
				$result[ $option_id ] = &$option;
			}

			if ( $settings['callback'] ) {
				call_user_func_array( $settings['callback'],
					array(
						array(
							'group'  => 'option',
							'id'     => $option_id,
							'option' => &$option,
						)
					) );
			}
		} else {
			trigger_error( 'Invalid option: ' . $option_id, E_USER_WARNING );
		}
	}
}

/**
 * Get correct values from input (POST) for given options
 * This values can be saved in db then replaced with $option['value'] for each option
 *
 * @param array $options
 * @param array $input_array
 *
 * @return array Values
 */
function fw_get_options_values_from_input( array $options, $input_array = null ) {
	if ( ! is_array( $input_array ) ) {
		$input_array = FW_Request::POST( fw()->backend->get_options_name_attr_prefix() );
	}

	$values = array();

	$maybe_new_values = apply_filters(
		'fw:get_options_values_from_input:before',
		null,
		$options, $input_array
	);

	if ($maybe_new_values) {
		return $maybe_new_values;
	}

	foreach ( fw_extract_only_options( $options ) as $id => $option ) {
		$values[ $id ] = fw()->backend->option_type( $option['type'] )->get_value_from_input(
			$option,
			isset( $input_array[ $id ] ) ? $input_array[ $id ] : null
		);

		if ( is_null( $values[ $id ] ) ) {
			// do not save null values
			unset( $values[ $id ] );
		}
	}

	return $values;
}

/**
 * @param $attr_name
 * @param bool $set_mode
 *
 * @return mixed
 */
function fw_html_attr_name_to_array_multi_key( $attr_name, $set_mode = false ) {
	if ( $set_mode ) {
		/**
		 * The key will be used to set value in array
		 * 'hello[world][]' -> 'hello/world/'
		 * $array['hello']['world'][] = $value;
		 */
		$attr_name = str_replace( '[]', '/', $attr_name );
	} else {
		/**
		 * The key will be used to get value from array
		 * 'hello[world][]' -> 'hello/world'
		 * $value = $array['hello']['world'];
		 */
		$attr_name = str_replace( '[]', '', $attr_name );
	}

	$attr_name = str_replace( '][', '/', $attr_name );
	$attr_name = str_replace( '[', '/', $attr_name );
	$attr_name = str_replace( ']', '', $attr_name );

	return $attr_name;
}

/**
 * Used when getting some option value from serialized array saved in a custom place
 * and that option is unreachable for standard WordPress filters by other plugins
 * For e.g. that option cannot be translated by plugins, so we pass its value through this function and do the fixes
 *
 * @param $value
 *
 * @return array
 */
function fw_prepare_option_value( $value ) {
	if ( empty( $value ) ) {
		return $value;
	}

	if ( function_exists( 'qtrans_useCurrentLanguageIfNotFoundUseDefaultLanguage' ) ) {
		$value = qtrans_useCurrentLanguageIfNotFoundUseDefaultLanguage( $value );
	}

	return $value;
}

/**
 * This function is used in 'save_post' action
 *
 * Used to check if current post save is a regular "Save" button press
 * not a revision, auto-save or something else
 *
 * @param $post_id
 *
 * @return bool
 *
 * @deprecated
 * save_post action happens also happens on Preview, Revision, Auto-save Restore, ...
 * the verifications in this function simplifies too much the save process,
 * the developers should study and understand better how it works
 * and handle different save cases in their scripts using wp functions
 */
function fw_is_real_post_save( $post_id ) {
	return ! (
		wp_is_post_revision( $post_id )
		|| wp_is_post_autosave( $post_id )
		|| ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE )
		|| ( defined( 'DOING_AJAX' ) && DOING_AJAX )
		|| empty( $_POST )
		|| empty( $_POST['post_ID'] )
		|| $_POST['post_ID'] != $post_id
	);
}

/**
 * @return Array with Google fonts
 */
function fw_get_google_fonts() {
	$cache_key = 'fw_google_fonts';

	try {
		return FW_Cache::get( $cache_key );
	} catch ( FW_Cache_Not_Found_Exception $e ) {
		$g_fonts   = json_decode( fw_get_google_fonts_v2(), true );
		$old_fonts = include( dirname( __FILE__ ) . '/fw-google-fonts.json.php' );
		$fonts     = array();

		foreach ( $g_fonts['items'] as $font ) {
			$fonts[ $font['family'] ] = array(
				'family'   => $font['family'],
				'variants' => $font['variants'],
				'position' => isset( $old_fonts[ $font['family'] ] )
					? $old_fonts[ $font['family'] ]['position']
					: 99999
			);
		}

		$fonts = apply_filters( 'fw_google_fonts', $fonts );

		FW_Cache::set( $cache_key, $fonts );

		return $fonts;
	}
}

/**
 * @return string JSON encoded array with Google fonts
 */
function fw_get_google_fonts_v2() {
	$saved_data = get_option( 'fw_google_fonts', false );
	$ttl        = 7 * DAY_IN_SECONDS;

	if (
		false === $saved_data
		||
		( $saved_data['last_update'] + $ttl < time() )
	) {
		$response = wp_remote_get( apply_filters( 'fw_googleapis_webfonts_url',
			'https://google-webfonts-cache.unyson.io/v1/webfonts' ) );
		$body     = wp_remote_retrieve_body( $response );

		if (
			200 === wp_remote_retrieve_response_code( $response )
			&&
			! is_wp_error( $body ) && ! empty( $body )
		) {
			update_option( 'fw_google_fonts',
				array(
					'last_update' => time(),
					'fonts'       => $body
				),
				false );

			return $body;
		} else {
			if ( empty( $saved_data['fonts'] ) ) {
				$saved_data['fonts'] = json_encode( array( 'items' => array() ) );
			}

			update_option( 'fw_google_fonts',
				array(
					'last_update' => time() - $ttl + MINUTE_IN_SECONDS,
					'fonts'       => $saved_data['fonts']
				),
				false );
		}
	}

	return $saved_data['fonts'];
}

/**
 * @return string Current url
 */
function fw_current_url() {
	static $url = null;
	if ( $url === null ) {
		if ( is_multisite() && ! ( defined( 'SUBDOMAIN_INSTALL' ) && SUBDOMAIN_INSTALL ) ) {
			switch_to_blog( 1 );
			$url = get_option( 'home' );
			restore_current_blog();
		} else {
			$url = get_option( 'home' );
		}

		//Remove the "//" before the domain name
		$url = ltrim( fw_get_url_without_scheme( $url ), '/' );

		//Remove the ulr subdirectory in case it has one
		$split = explode( '/', $url );

		//Remove end slash
		$url = rtrim( $split[0], '/' );

		$url .= '/' . ltrim( fw_akg( 'REQUEST_URI', $_SERVER, '' ), '/' );
		$url = set_url_scheme( '//' . $url ); // https fix
	}

	return $url;
}

function fw_is_valid_domain_name( $domain_name ) {
	return ( preg_match( "/^([a-z\d](-*[a-z\d])*)(\.([a-z\d](-*[a-z\d])*))*$/i", $domain_name ) // valid chars check
	         && preg_match( "/^.{1,253}$/", $domain_name ) // overall length check
	         && preg_match( "/^[^\.]{1,63}(\.[^\.]{1,63})*$/", $domain_name ) ); // length of each label
}

/**
 * Use this id do not want to enter every time same last two parameters
 * Info: Cannot use default parameters because in php 5.2 encoding is not UTF-8 by default
 *
 * @param string $string
 *
 * @return string
 */
function fw_htmlspecialchars( $string ) {
	return htmlspecialchars( $string, ENT_QUOTES, 'UTF-8' );
}

/**
 * Check if current user has one capability from the given list
 *
 * @param array $capabilities list of capabilities to check
 * @param mixed $default_value
 *
 * @return string|bool|mixed
 *  Return first capability that user can.
 *  Else, return default value if it is not null, else return first capability from list.
 *  Use default value false to check if user can some of the capabilities
 */
function fw_current_user_can( $capabilities, $default_value = null ) {
	if ( is_user_logged_in() ) {
		foreach ( $capabilities as $capability ) {
			if ( current_user_can( $capability ) ) {
				return $capability;
			}
		}
	}

	return ( $default_value !== null ? fw_call( $default_value ) : array_shift( $capabilities ) );
}

/**
 * Convert number of seconds to 'X {units}'
 *
 * E.g. 123 => '2 minutes'
 * then you can use this string how you want, for e.g. append ' ago' => '2 minutes ago'
 *
 * @param int $seconds
 *
 * @return string
 */
function fw_human_time( $seconds ) {
	static $translations = null;
	if ( $translations === null ) {
		$translations = array(
			'year'  => __( 'year', 'fw' ),
			'years' => __( 'years', 'fw' ),

			'month'  => __( 'month', 'fw' ),
			'months' => __( 'months', 'fw' ),

			'week'  => __( 'week', 'fw' ),
			'weeks' => __( 'weeks', 'fw' ),

			'day'  => __( 'day', 'fw' ),
			'days' => __( 'days', 'fw' ),

			'hour'  => __( 'hour', 'fw' ),
			'hours' => __( 'hours', 'fw' ),

			'minute'  => __( 'minute', 'fw' ),
			'minutes' => __( 'minutes', 'fw' ),

			'second'  => __( 'second', 'fw' ),
			'seconds' => __( 'seconds', 'fw' ),
		);
	}

	$tokens = array(
		31536000 => 'year',
		2592000  => 'month',
		604800   => 'week',
		86400    => 'day',
		3600     => 'hour',
		60       => 'minute',
		1        => 'second'
	);

	foreach ( $tokens as $unit => $translation_key ) {
		if ( $seconds < $unit ) {
			continue;
		}

		$number_of_units = floor( $seconds / $unit );

		return $number_of_units . ' ' . $translations[ $translation_key . ( $number_of_units != 1 ? 's' : '' ) ];
	}
}

/**
 * Convert bytes to human readable format
 *
 * @param integer $bytes Size in bytes to convert
 * @param integer $precision
 *
 * @return string
 * @since 2.4.17
 */
function fw_human_bytes( $bytes, $precision = 2 ) {
	$kilobyte = 1024;
	$megabyte = $kilobyte * 1024;
	$gigabyte = $megabyte * 1024;
	$terabyte = $gigabyte * 1024;

	if ( ( $bytes >= 0 ) && ( $bytes < $kilobyte ) ) {
		return $bytes . ' B';

	} elseif ( ( $bytes >= $kilobyte ) && ( $bytes < $megabyte ) ) {
		return round( $bytes / $kilobyte, $precision ) . ' KB';

	} elseif ( ( $bytes >= $megabyte ) && ( $bytes < $gigabyte ) ) {
		return round( $bytes / $megabyte, $precision ) . ' MB';

	} elseif ( ( $bytes >= $gigabyte ) && ( $bytes < $terabyte ) ) {
		return round( $bytes / $gigabyte, $precision ) . ' GB';

	} elseif ( $bytes >= $terabyte ) {
		return round( $bytes / $terabyte, $precision ) . ' TB';
	} else {
		return $bytes . ' B';
	}
}

function fw_strlen( $string ) {
	if ( function_exists( 'mb_strlen' ) ) {
		return mb_strlen( $string, 'UTF-8' );
	} else {
		return strlen( $string );
	}
}

/**
 * If currently is a Post Edit page display/submit
 * @return bool
 */
function fw_is_post_edit() {
	static $result = null;

	if ( $result === null ) {
		$result = false;

		if ( is_admin() ) {
			if (
				empty( $_POST )
				&&
				isset( $_GET['action'] )
				&&
				$_GET['action'] === 'edit'
				&&
				isset( $_GET['post'] )
			) {
				// Display Edit Post page
				$result = true;
			} elseif (
				isset( $_POST['action'] )
				&&
				$_POST['action'] === 'editpost'
				&&
				isset( $_POST['post_type'] )
				&&
				isset( $_POST['post_ID'] )
				&&
				strpos( wp_get_referer(), 'action=edit' ) !== false
			) {
				// Submit Edit Post page
				$result = true;
			}
		}
	}

	return $result;
}

/**
 * @param string $dirname 'foo-bar'
 *
 * @return string 'Foo_Bar'
 */
function fw_dirname_to_classname( $dirname ) {
	$class_name = explode( '-', $dirname );
	$class_name = array_map( 'ucfirst', $class_name );
	$class_name = implode( '_', $class_name );

	return $class_name;
}

/**
 * This function is a wrapper function that set correct width and height for iframes from wp_oembed_get() function
 *
 * @param $url
 * @param array $args
 *
 * @return bool|string
 */
function fw_oembed_get( $url, $args = array() ) {
	$html = wp_oembed_get( $url, $args );

	if ( ! empty( $args['width'] ) and ! empty( $args['height'] ) and class_exists( 'DOMDocument' ) and ! empty( $html ) ) {
		$dom_element = new DOMDocument();
		@$dom_element->loadHTML( $html );

		if ( $obj = $dom_element->getElementsByTagName( 'iframe' )->item( 0 ) ) {
			$obj->setAttribute( 'width', $args['width'] );
			$obj->setAttribute( 'height', $args['height'] );
			//saveXml instead of SaveHTML for php version compatibility
			$html = $dom_element->saveXML( $obj, LIBXML_NOEMPTYTAG );
		}
	}

	return $html;
}

/**
 * @var $length
 * @return string
 *
 * Reference
 *
 * Strong cryptography in PHP
 * http://www.zimuel.it/en/strong-cryptography-in-php/
 * > Don't use rand() or mt_rand()
 */
function fw_secure_rand( $length ) {
	if ( function_exists( 'openssl_random_pseudo_bytes' ) ) {
		$rnd = openssl_random_pseudo_bytes( $length, $strong );
		if ( $strong ) {
			return $rnd;
		}
	}

	$sha = '';
	$rnd = '';

	if ( file_exists( '/dev/urandom' ) ) {
		$fp = fopen( '/dev/urandom', 'rb' );
		if ( $fp ) {
			if ( function_exists( 'stream_set_read_buffer' ) ) {
				stream_set_read_buffer( $fp, 0 );
			}
			$sha = fread( $fp, $length );
			fclose( $fp );
		}
	}

	for ( $i = 0; $i < $length; $i ++ ) {
		$sha  = hash( 'sha256', $sha . mt_rand() );
		$char = mt_rand( 0, 62 );
		$rnd .= chr( hexdec( $sha[ $char ] . $sha[ $char + 1 ] ) );
	}

	return $rnd;
}

/**
 * Try to make user friendly title from an id
 *
 * @param string $id 'hello-world'
 *
 * @return string 'Hello world'
 */
function fw_id_to_title( $id ) {
	// mb_ucfirst()
	if ( function_exists( 'mb_strtoupper' ) && function_exists( 'mb_substr' ) && function_exists( 'mb_strlen' ) ) {
		$id = mb_strtoupper( mb_substr( $id, 0, 1, 'UTF-8' ), 'UTF-8' ) . mb_substr( $id,
				1,
				mb_strlen( $id, 'UTF-8' ),
				'UTF-8' );
	} else {
		$id = strtoupper( substr( $id, 0, 1 ) ) . substr( $id, 1, strlen( $id ) );
	}

	return str_replace( array( '_', '-' ), ' ', $id );
}

/**
 * Alias
 *
 * @param string $extension_name
 *
 * @return FW_Extension|null
 */
function fw_ext( $extension_name ) {
	return fw()->extensions->get( $extension_name );
}

/*
 * Return URI without scheme
 */
function fw_get_url_without_scheme( $url ) {
	return preg_replace( '/^[^:]+:\/\//', '//', $url );
}

/**
 * Try to find file path by its uri and read the file contents
 *
 * @param string $file_uri
 *
 * @return bool|string false or string - the file contents
 */
function fw_read_file_by_uri( $file_uri ) {
	static $base = null;

	if ( $base === null ) {
		$base                     = array();
		$base['dir']              = WP_CONTENT_DIR;
		$base['uri']              = ltrim( content_url(), '/' );
		$base['uri_prefix_regex'] = '/^' . preg_quote( $base['uri'], '/' ) . '/';
	}

	$file_rel_path = preg_replace( $base['uri_prefix_regex'], '', $file_uri );

	if ( $base['uri'] === $file_rel_path ) {
		// the file is not inside base dir
		return false;
	}

	$file_path = $base['dir'] . '/' . $file_rel_path;

	if ( ! file_exists( $file_path ) ) {
		return false;
	}

	return file_get_contents( $file_path );
}

/**
 * Make stylesheet contents (portable) independent of directory location
 * For e.g. replace relative paths 'url(img/bg.png)' with full paths 'url(http://site.com/assets/img/bg.png)'
 *
 * @param string $href 'http://.../style.css'
 * @param null|string $contents If not specified, will try to read from $href
 *
 * @return bool|string false - on failure; string - stylesheet contents
 */
function fw_make_stylesheet_portable( $href, $contents = null ) {
	if ( is_null( $contents ) ) {
		$contents = fw_read_file_by_uri( $href );

		if ( $contents === false ) {
			return false;
		}
	}

	$dir_uri = dirname( $href );

	/**
	 * Replace relative 'url(img/bg.png)'
	 * with full 'url(http://site.com/assets/img/bg.png)'
	 *
	 * Do not touch if url starts with:
	 * - 'https://'
	 * - 'http://'
	 * - '/' (also matches '//')
	 * - '#' (for css property: "behavior: url(#behaveBinObject)")
	 * - 'data:'
	 */
	$contents = preg_replace(
		'/url\s*\((?!\s*[\'"]?(?:\/|data\:|\#|(?:https?:)?\/\/))\s*([\'"])?/',
		'url($1' . $dir_uri . '/',
		$contents
	);

	return $contents;
}

/**
 * Return all images sizes register by add_image_size() merged with
 * WordPress default image sizes.
 * @link https://codex.wordpress.org/Function_Reference/get_intermediate_image_sizes
 *
 * @param string $size
 *
 * @return array|bool
 */
function fw_get_image_sizes( $size = '' ) {
	global $_wp_additional_image_sizes;

	$sizes                        = array();
	$get_intermediate_image_sizes = get_intermediate_image_sizes();

	// Create the full array with sizes and crop info
	foreach ( $get_intermediate_image_sizes as $_size ) {
		if ( in_array( $_size, array( 'thumbnail', 'medium', 'large' ) ) ) {
			$sizes[ $_size ]['width']  = get_option( $_size . '_size_w' );
			$sizes[ $_size ]['height'] = get_option( $_size . '_size_h' );
			$sizes[ $_size ]['crop']   = (bool) get_option( $_size . '_crop' );
		} elseif ( isset( $_wp_additional_image_sizes[ $_size ] ) ) {
			$sizes[ $_size ] = array(
				'width'  => $_wp_additional_image_sizes[ $_size ]['width'],
				'height' => $_wp_additional_image_sizes[ $_size ]['height'],
				'crop'   => $_wp_additional_image_sizes[ $_size ]['crop']
			);
		}
	}

	// Get only 1 size if found
	if ( $size ) {
		if ( isset( $sizes[ $size ] ) ) {
			return $sizes[ $size ];
		} else {
			return false;
		}
	}

	return $sizes;
}

/**
 * @param string $icon A string that is meant to be an icon (an image, a font icon class, or something else)
 * @param array Additional attributes
 *
 * @return string
 */
function fw_string_to_icon_html( $icon, array $attributes = array() ) {
	if (
		preg_match( '/\.(png|jpg|jpeg|gif|svg|webp)$/', $icon )
		||
		preg_match( '/^data:image\//', $icon )
	) {
		// http://.../image.png
		$tag  = 'img';
		$attr = array(
			'src' => $icon,
			'alt' => 'icon',
		);
	} elseif ( preg_match( '/^[a-zA-Z0-9\-_ ]+$/', $icon ) ) {
		// 'font-icon font-icon-class'
		$tag  = 'span';
		$attr = array(
			'class' => trim( $icon ),
		);
	} else {
		// can't detect. maybe it's raw html '<span ...'
		return $icon;
	}

	foreach ( $attributes as $attr_name => $attr_val ) {
		if ( isset( $attr[ $attr_name ] ) ) {
			if ( $attr_name === 'class' ) {
				$attr[ $attr_name ] .= ' ' . $attr_val;
			} else {
				// ignore. do not overwrite already set attributes
			}
		} else {
			$attr[ $attr_name ] = (string) $attr_val;
		}
	}

	return fw_html_tag( $tag, $attr );
}

/**
 * @return string|null
 * @since 2.4.10
 */
function fw_get_json_last_error_message() {
	switch ( function_exists( 'json_last_error' ) ? json_last_error() : - 1 ) {
		case JSON_ERROR_NONE:
			return null; // __('No errors', 'fw');
			break;
		case JSON_ERROR_DEPTH:
			return __( 'Maximum stack depth exceeded', 'fw' );
			break;
		case JSON_ERROR_STATE_MISMATCH:
			return __( 'Underflow or the modes mismatch', 'fw' );
			break;
		case JSON_ERROR_CTRL_CHAR:
			return __( 'Unexpected control character found', 'fw' );
			break;
		case JSON_ERROR_SYNTAX:
			return __( 'Syntax error, malformed JSON', 'fw' );
			break;
		case JSON_ERROR_UTF8:
			return __( 'Malformed UTF-8 characters, possibly incorrectly encoded', 'fw' );
			break;
		default:
			return __( 'Unknown error', 'fw' );
			break;
	}
}

/**
 * Return mime_types by file extension ex : input : array( 'png', 'jpg', 'jpeg' ) => output : array( 'image/jpeg' ).
 *
 * @param array $type
 *
 * @return array
 */
function fw_get_mime_type_by_ext( $type = array() ) {
	$result = array();

	foreach ( wp_get_mime_types() as $key => $mime_type ) {
		$types = explode( '|', $key );
		foreach ( $type as $item ) {
			if ( in_array( $item, $types ) && ! in_array( $mime_type, $result ) ) {
				$result[] = $mime_type;
			}
		}
	}

	return $result;
}

/**
 * Return types from file extensions ex : input array( 'png', 'jpg', 'zip' ) => output : array( 'image', 'archive' ).
 *
 * @see wp_ext2type() function.
 *
 * @param array $ext_array
 *
 * @return array
 */
function fw_multi_ext2type( $ext_array = array() ) {
	$result = array();

	foreach ( $ext_array as $ext ) {
		if ( ! in_array( $type = wp_ext2type( $ext ), $result ) ) {
			$result[] = $type;
		}
	}

	return $result;
}

if ( ! function_exists( 'fw_resize' ) ) {
	function fw_resize( $url, $width = false, $height = false, $crop = false ) {
		$fw_resize = FW_Resize::getInstance();
		$response  = $fw_resize->process( $url, $width, $height, $crop );

		return ( ! is_wp_error( $response ) && ! empty( $response['src'] ) ) ? $response['src'] : $url;
	}
}

/**
 * fw_get_path_url( dirname(__FILE__) .'/test.css' ) --> http://site.url/path/to/test.css
 *
 * @param string $path
 *
 * @return string|null
 * @since 2.6.11
 */
function fw_get_path_url( $path ) {
	try {
		$paths_to_urls = FW_Cache::get( $cache_key = 'fw:paths_to_urls' );
	} catch ( FW_Cache_Not_Found_Exception $e ) {
		$wp_upload_dir = wp_upload_dir();

		$paths_to_urls = array(
			fw_fix_path( WP_PLUGIN_DIR )             => plugins_url(),
			fw_fix_path( get_theme_root() )          => get_theme_root_uri(),
			fw_fix_path( $wp_upload_dir['basedir'] ) => $wp_upload_dir['baseurl'],
		);

		if ( is_multisite() && WPMU_PLUGIN_DIR ) {
			$paths_to_urls[ fw_fix_path( WPMU_PLUGIN_DIR ) ] = WPMU_PLUGIN_URL;
		}

		FW_Cache::set( $cache_key, $paths_to_urls );
	}

	$path = fw_fix_path( $path );

	foreach ( $paths_to_urls as $_path => $_url ) {
		if ( preg_match( $regex = '/^' . preg_quote( $_path, '/' ) . '($|\/)/', $path ) ) {
			return $_url . '/' . preg_replace( $regex, '', $path );
		}
	}

	return null;
}

/**
 * @param string|array $callback Callback function
 * @param array $args Callback arguments
 * @param bool $cache Whenever you want to cache the function value after it's first call or not
 * Recommend when the function call may require many resources or time (database requests) , or the value is small
 * Not recommended using on very large values
 *
 * @return FW_Callback
 *
 * @since 2.6.14
 */
function fw_callback( $callback, array $args = array(), $cache = true ) {
	return new FW_Callback( $callback, $args, $cache );
}

/**
 * In the value is instance of FW_Callback class then it is executed and returns the callback value
 * In other case function returns the provided value
 *
 * @param mixed|FW_Callback $value
 *
 * @return mixed
 *
 * @since 2.6.14
 */
function fw_call( $value ) {
	if ( ! fw_is_callback( $value ) ) {
		return $value;
	}

	return ( is_object( $value ) && get_class( $value ) == 'Closure' )
		? $value()
		: $value->execute();
}

/**
 * Check is the current value is instance of FW_Callback class
 *
 * @param mixed $value
 *
 * @return bool
 */
function fw_is_callback( $value ) {
	return $value instanceof FW_Callback || ( is_object( $value ) && get_class( $value ) == 'Closure' );
}

/**
 * Check for command line interface
 *
 * @return bool
 * @since 2.6.16
 */
function fw_is_cli() {
	return ( php_sapi_name() === 'cli' ) && defined( 'WP_CLI' );
}