7zXZִF!t/ ]8Kvl Nϡ%x͢!k{E,?]z9?_۸ V #?CZ9 6#_-ڰhL+&+>yE(K#O.nй`2UQeQ%a=wŦOR|`iӃ7aT2` 7<UwRw('~1\h1.W \Z;7р<̞ =J_ez![𵎞ISGoB%\e_6Rմ${Sj,ѕb79'F\Ԥ\*+r4n{uU_#X%۬1⛘jAJ*U^ȍwǒ=?p>? ?kd8ӣG(B, +[Az1?h=nZ=;mq6Os<_Ģ "GHQzX;OvӚ[W;}ytcBw[Sxz#OwBe:{9t֦ZXW] \%LcFwȐ駚/2iN6t{q0NB6q@  uj_tgoNW@t;ެ8*Zz*ovcY8-dijSSj6[sKQ|sj+[ +^8@. S59%Lp V&B\.aV5"&j|EAHŷ,'D<ޏTqD+Hk^0kQn3DV‱h`Y~0Nƌ[ka gf߾OG1_VȱY{)ڀRu@_z#j t UJ|N|J:zkAbYariqfy>zp+hfsi½ToTslEkE=W(佇|42d"WjE£N˼>'D0>~iUs;&64 ޸<|_+) ب#c`׸"AVT\CT?P#T5Q7Xe醱J;T\''NO1+ :@qgmcה A'pI-!wq8UNjd[s$&C8iCd4g<19Mg#0 cek.Nۍ>slֈ_3Ŵd󎆘_qI6"ݦi׸/+tAKZ݃ Xiҷ$}@R<4}lU|.aOtr'4ħu_&#f;19$X,r%uG[tnIzdlGRtgP4fw h7j qFYU2=rh|@2Kfa(7/~,tLvfj20/S&=IA /.peagҼR(絫ҼOmIL98SӀ 6_BBI4"kO9#??XOj/wXXMHШ!/ Z,Ou0q\&ŀ|K_ m?<ǥ+V;ŅQl Z׬$:&*7yQ*0,'Σj2Fp"Q1p7nI{3)lq.{DaGqWW1'$ws t~rH0~RuY%v Fu22̙'`N&VN8u%1P4\Lt~ּqu#;t#$3a4d49/G4`Udq.8cΊ#R.hh$2=ّ vZjr}KK*J9#( 3.ide P5A0US R"lǦ?iDKjW|ƵG"fLfeX۞yB.?pz 7usZw;O"oj.0+'Ĝ OJt&V5;1"$ls`t?os19)N=+\ (!'nXs,DMDVۤL,Ob{91ưP=z̩}kJ;]#4<}`=.ȵʦ^,Tdo׿D5+ NF mĩ$0P_I˅BhΔ+\9A!9aF,˝eCD@Ut(SpY DUf/rɩ(ˢc%-v 4>QG!y*uk<-Ch3&vr."X~7['Z.1&}C( %⮁Es -0p.:%#= gfeEdhz: rёj\Id\鉣 G!sU5R!Gaz/x_f@%Pc{yJ'3C:<y;r U*\:2{Pp:Dee<oxr{IrzC)8:{TR@U=tE@ RBqZAk$qbSp?_"n4*̀Dͭ 9I*67&qyfb C4eƔ1lQq HW_@{ qלdXd*s~.xzl`yО5@nˑ&Z \^U&dk 50g\}O`Tw9R0S5X(4IpSʪdX ҳM*O=KNdž/f kUQ 3Tttc>A-4N{Hv As"?vټ0k9H!IK"l]wS7ý?ZJW"r*XCsK-|9ɘ0:a3DG^ /ݘ??Hx:1ݴk8Twk9vtV@D^pm,^s\{hq[= P\X$rt2s 1W7)J*W`7,pU_RC(I![pQhl*R&^@C-[_x|PtZ/ǔq_:' RL-E`Zy}B݌MvNf*Df*溋a~⋺ x su}qG;&1|q8WNS 驱q>A^O <:HΒAV 55>^PF#$5@-NH[sw%ߐ6=߭O Ԛ%fx\شHBsg}/DX~q \z>(ߝC0B^(8Tk7ܠ]5ƧVZz94dXransGeϮVI@T(zYK?2uK/gId Gt$`Fn1ވ=QU u/n+2j밈A&cv -@0+\}#A*͡ݘ8;xz%Vr f;yjR݌usj]F\HCN"p+[ܚy:}w#}p ?VePɒicF:q]qfK d>y4+2[^w5CT9WGN?%p8=KBxwN 91abj] =<`{rsM# YW(xa~Be_pBO5^}YcYf\sĚ]ȹ1WGn +:Hc7zϩ5v `-!29K LmopPX .׽R2FC:t}jnl^yɁG׏X1 >` q>;B[t]ӝ{C{צԶ!G3nyډ}$:R@gAňncNF@Etvbz&vT)7Ge(hXh_z~MY j[Kv) joH abG6UeZViڼI8m."j3hLϠ-!&DJWŬS ̘:2?7)fmLyMʔn)55!i`С&1cNMujڻO}haܨCڮZw2JC=h$mw1yS ycdًYT2p L߃iF ׳h8Cu-'|hl_ΚRD*;,_/ Utl* CwQp-rlT+R(:ZZ $dgR zWUf:HzLa,B?# 2`.CR8chsiuǐ"-*?)NJdJ`{Ϝ?un 7=2-Bӹ4 {Mx329\6$S<GT;c(W[ތ)4Oz"8&p=]<;I<C@R WO`lp#oLj= "OAwܵN|֝}'\r)6N>=8&AB5 r٨4G Z<Q[h7Cٹ\ub[N1Ipbo,s O"9+²/'PSx;#dMHJTq%`)U//#ͻ|=%w%#xl)%O]ghܝUku n:Ԋ-8مۍ$pU okMG׸dƟ/E3̓ΫUAZZ#h:R|G"')ѻ  ZP4T1>4au{g,.M79(c ' c)W񷴮fp0O#q1{]1*ҩ#9&>]dXFsjT'7a M0v4N'bC@yȜLkg %fMbg7οᝌe!nK :5=0T4uF2&CsCל (Ǭ..ꬋt9}7_CT^W7@!GQAqL%aip$JKZ+\ƛ+ 5=:mlș9]CkR5I)5A0Ќ'&_BOC tcX}wB x˒ܸsޒpAKmyğz941%GWn7۟Qkx&gO%:S$c™Ʈw$!a]#gi-.W'_xy4Y.aoO]> }:6qӣ>62Tz#z+t"| ~^ddh$}n/rr-W:~*%16ߚykO.u1U=IZWcI ǢP|3Y%c 6MH lX0Вf*^oLQW5wvԥLTNF0%w*P̛N+P$~Y=1j|4(>NuH)A.au6-TyLK_!BNbwƢ'_kv` q ʼn7ovX&ĩ_27I?ʙe۪@Xc =b;i;ؙ~r]_QxWg̻׀W`\\~DDz + jYF<|9š``dv%ޔswSi,^=m^trdf9%ҖXl"e$|Ƭm;̓`U6mcS<{"#pF'7DɾdDdo0h:5֘9w4ZaTp 0Gh9yD %`b&L$PnJ͝Bv> :d.í^\0aK.6W$6n>?J@ZL~Kdאvz{x1t ׋.;Z NJeKd @jR=8ҦK ^6?5u?M ac}je`-~ÈWz ߛ^Z5_7B/,^.<&Jwd(]kZqIo0Z@v!r cY.G2𠊜%,k>TH N_b(yH@@OjmGj{1DtS%NOkʕ<"$@KR["I>Lc/L"L-( 2cƳLBE!gA[ev[ң%;`wߞW-9Oͨ5AJY4F]7d `^E`އCzN*z`L5klP ( B奀Z2u4LGzsb"$4=UcđZC}8 J TȡP3HD)۽{OBN8er1 FR }[u9>Xw ih90 r{+<AΎcIgB_4:浠VJFV֗.]EUz l3\ʚfY[XVQz[UjR,gZ4Jʵ{ Pdq3|H{Ed>g액(ZG|~gϬNL?nHV&9|BsNt ajL~ƛԇmk@M5Pҋ/R/ d$˶KڧhpL/uI ypJw/9ް!mQ'43ccɳǖ*#Ȗ{brZ, Srw ~խK51Jh$: 9+^uwF"4GCLl-ȍ?v^XQzo;mBUEWڀǓtAp DFv*pp'y}>vM"Lg.Mq}K,^3k[i(UWc ?;ǛkUGtAl Y}sֆaOkk #~]e~sQvdTŨ-_4?Vv-]^\X.rh jB7%w{ G xmJyVqox$Kta@/CA?t)O1Zl(D7k>y&&^N5 OF,%g= 8 ,!DCȲByf8wٮ8,.. 3p%ݽ-|=c*P<Evpе) womv& 2țy: D Ǣ:unJcGU]RQr!PwI<{1װAsW@g+tI^U)\J^7r iT"fLѿTt5{.=ٛzpS{n$oUD]Cȵ3Hm% [SU䂱PsmOiï[g"-NS},̀MH wH87C1N!”狻JH}q.!@e[!S& >ͱ[Q!Us \QQ 3/QfoI{Nn9jV7ZpE9֞н-O되۶k.Ôu҈ m%'6$NrQ%E dw} ?SI,R,xaIjTo$T|aE9=gi uC`n/sz8*i&kr:?jYF{2v^F9մGeI_c"*yMnW̄6WYxO#ɣj5oP{#h C2hn bVo)4)g? Ym؝LI/J@*Ў/1=T 4ȫA?Y,:RCVjSHo3ЪJ`*FJ7 ?J_<d\rEeGC0:Kfs m18 MR3S?}bN?IMRdMh>!]EJQ)J5y{I n Ξ)\Ijge梁8J-F5%a@6&?q*.uJtk#|A:@ !f"Magidp9>UJH/d1_sZW#`76 >{*k,6])Cs;XxC.-IVG/d+Կ9`o{W~Pڜ7zЪ*a_km2\-Ci-SY|f@n0BϛQXë`S 8*9vvts1HG_.ȧ㠹2EjiZg8" :qn{E]:3=EJIš'7kwt<p=\U)1)t6t&I$o$E6 dO2nIc7EE+u7;NaXdy -RTLFPk6Z<~{294U;_Pso9SoR Wt'JHȻ;h&mhc4 oq{I▃+C`J9vG\I;-R<+sɈX͙BjEXGN U9r=6 2;RPJ<,LFz *e8Fc7d=]W\IغC(b!UBtfB@"q_؟mE$dML ekVQE"3QU1oDFhԚ QӬ#E|7 U>0_P8S:;/f ֵbaa~~@.0SLueC1'ޯD> U_Y9!=~9׳.b[a1Vz3ȱBCV#J-`oxEt_#R[/9eC1s/fa[FZTT7Və&~B,G@wMo3apT׫GIO02Ɋa.imA>,IʂS4dB^#kέtj,3`ӂ= M=oH8E&|Of`_f dhCNL51fOJb|^'8a,4\sӬ >i>C<|oc7I?4pn]~q|x(j;~[C ⸇#,ZYYaIvc@G+ vʨm$gnd&W&"6NtM \CyH\@ՙC eaU4Sbؑ*@~Od7Zl۴D[d\yI[玞-od #gz ~(?(s).2T(PPc,}X}ORc?t5/1ɋ zTmFX c"NK+ڝuH YrҔESOfEj:k`\NBrזU'3V{2qzVvEy){[wB5>Z7jxxҜ.M.zO<[ ώ3ȱ>",$ #cv# g,Bt׳ΙM Yu|hVh?I$F?\`ǁ!7$N VFȟgpj[WqzOzW,e3>2i|q*<gTۈr&\6YB`l+X%EЈM GꪰTaIi @g8ý/" lhW{@G>qhU1ڤc^2:݈#g; PE="^Mgt^cw흏JW!Ξ T2l2LvGCa}ޠz xTige1D#MFFSi)aRL|$Q*㦝e%el{*[.0U9PQh{K Cʽ |J5 Vݛp=T229MBy|td$4xTƲ,BjRB.ޮ저g ,H؜ۀ\SksfH^2\='\ B*6P)FW NkTz<\3 _E uΐp-nrb7b:Z:pĴ7o>?Rt;4Ö[nyLRfU'š_"Z^r\mk@YaO#F)vm^ViiU'`-OXv}z{яE?@=W#=(E"=S-2Za hz*tpY]=gYDHRo=gu;gTeCn^xUZ,x۰kSHE8jrEr~MԣAҷ#VOΥh A:r@U xkl9 MW"AM9t1l>x`%7Swo`N;Vێ'5-8U$WULP&t~1RQb/0R" u}WXL#mMcF 3P`{2.!;٨"EK rg,Re)ꧏ&4bJN> .CϩJ7GPY+[-Xhn R>l\my:gtił0i]܍MP#SI@/"J8q;H“]Yц][𐌭o$'pΚxaqI04!Z(?|{ adu([n0Q6#c:EfARMfM#f`ouxmGuy++$ !BD6NBkl!:c[) +s{A4jMj߄>]@OdLMu䝃nbYyLH5~lK 'bR]@.SdV\e*RAptrT(Z8&F2dގ"ǩҴ –5L^ImoZY:5/NkQ@jL\͉s55,֒۰"+`˻G1RVxlukՃ:r.DBGlYzGPqz=R?"պ_EŎ,1<xBY<=AZ rLb O4 ߏ#9ztZi qE3٢Ú{h# '`WMVX뷳lŧB$<|N.h]KqTHM `.[z]m1%h2FbS,y4,E$5ovUSg3) ֕xc-MΞr \>Wiܣ Ogl2gBSy\HU?1]S#Ŗ5x؊GNbJhvq0ap #OK PD}f|mTܶ҉ː&v+*v_QjQiEcP諰۾\H2t;Q֠RͲUhvVD{@C=P)ǟLw KH\5XYhAnɊYYEqIڤ Qכٷ6bڲM䢳 Q)oEh{d)I'nXtKuW254☊k҉44w厸֍!N2}|)J;luS M;Q3Ӫ' x1 <$ `>~#Ua=hkML|o(б ,Z簳x3 LV#d- aoz3|nGAe㝍GT9u(7z[L&nh&^ ;VS/iE>FT8!%wC#-:(i´Zpi-u8/G 1{U󣊁;C"muw4F jӸӢf& X6J|'_\ߪ-K IDq9EX`>7&S҇b #t.`Bߏ"M2; [+=^\ Ӛ&FAڛF{soIK"aڴnXveDrg(#!5O`3/!"~=57 op_&G0, QOոfYF8?s{\;CĆ'XMNc2;pz~qD#t DSȿ󫤍krJMr16i3zø(|=!LISfKx8R|1OOJl{ikT|C]+((|;̺ThV*%-G%wQJj2\xq*d.򮄘~Ī|@>rEG'ҠPiz[7A19bMw(_Ōrءfzk)ѽ610W/>ʮRI?"q%p^]Er<9t1zMg[e4H;ŝ&x λ|,ikj XjeJ_{r 35C0iu߽gU9(=\2xkoz5맖$⧽aQ}' 0=1{(;]`pmCf,҇T]h 9%ٷl6ZIv;ޯ`û:9 FU\gWy'֚l 5L001Ȱ-O̶#| ÖrOIa% f xWm>>b}Edl~*6 !~꯸:pr o?H2&jy&Ε+IKe UU76VZ"(4g4C9wәrQ-d $! 7b/ҸzQ|Uȕ<9=hw8UPy/QjŕOϜ?Ț!x@4hҬ'vtzIݍYtwuVK(f +wNUQټ 伜yFZË3]jCGfJ^Q@%btC/x) \3T4^BLID o>9J< h~\lkZNy hpx+,v)#w=wTր{|G…j2S~b4h3 c/ aS(vI:32/fQ̬k]ɱyӁʭHRa6ss3rkY&t&Pܫʢ!ɮXgi1辛 7)/ FH)+dFdkMv>!- {8Hj; ~\]5e-s)l, 9,*Vw,̭Dr;aaZPNԽ99s>$|!ߨl]Yhj 拜@ﯿu7#%3nhB`YPXNTsPM)<-6ȸxӼ#JV j!-q5m=5P}hMg\k7Mg ᕞ \##^˷Nv_\aD7bګ295SIynmB}j;xlE@Êܾk-b)dg {*(&,}ܖs!=R4 )B厎Ʃ(P#t7';)30۬UFf&)bq%nm/,m}Gwe5Ȝ}ݨB밢UkWsn(Ƈ8߫Ƹf209'YvFґtkY T=>(s6a]=s@z/4hCj;U`LWcI8wPPFG:YF3Q MKN7QAK ƅ J섷R![:*(fӧ)x/Gȃ|tls$XY/@/35¾4 DwԉM ڙe5͙;Ϲ+&s-5!ʼnTnSfDdԆ'c;%bEob]8&k GE1ZhHbcu5\:|RpW{d ]s?HY^]X[zwO"DDnہD&#xOFaSخҀ7u8Ah$~:&"VjLW2v]ǯ3vuWeU5Qsؓܵ+>A<8KXL;/ #D;"MA88Oaa@,f7Krt@Wȇ}& z(+^p%ȼ20ʉYvV@~WYMt+?zeEDYcX߫DmWb%"Hsx`7~1ўOJWrFoh^~Aqn.RXMmN4gPiYrwH:u e3\q RJOS} _EqN_v*0`PS mRvs]h昜șm%EyYik hY@以 ㌀k@D]oX\u[#[Wy{6y^QGG"mFHD8LzMAvk̜EAtxxO#fݛpc^4ze6%ˉOȽl@=.q?MPKxAujxߓrCIHL7aՖCY,m D0UAF,ݬ|tpUm\0$!bQ-H&wa">(](/]dl|iyH}XOspꃁBX5~ߧܳ%|$]x w(. -? E …Ό] W) csAV-, j҂|*+yז._8g:nֹK5w{={j+2vZwǩ ]<#,J;tIj)D/5EؠfyXR%c0 2$C&TX"Fx0Y>d7NJ;EM) 3 s39 A| .ev.TM8]cf-׹J9ebWEUйkv=oƕo]|AuL!/Q}5k|-w]!wf q;᳒ID]-*1C9Btj4g -i{qu:snInJqsdSkt^_>WDq=pSEd]͔AMJ“|FX#+,BvF ŠMnMa%ct=f%VO)p?xݭ d@%M,D^< (cJP *<.sS [/!Ӏq1뀰eD9`$9c-`u5ŐK_RE.%w 8IDY!x7NXn!( ջF+*6gM-d v DTk䘝!ϝ`ض:e%b}{el @ bBl-/!NuF N?8᭄^}ACE˒٠$8DZ&=$;>B~\B!P\cӬikal9T>RFMgFRYD-YPW;p-:^mHaQهB")2ceo:N)zfF.{G\՚Z \T1/o-)%ਲ਼O^7ۯoұt$&8-x}{2g9ŝh9:[pvƂ<͋xY52ne $Iu\fzRJ @ ']W!?elz}u.>Aޏt $?VUa<:+aK5SԡpE#z 3,pd.M#ɣ H~g 2PKORMH[~Gj.^xj9Ԁz0W 4@D &^]+uΫU:K:$uneQGQA4%|[ǢGTv,ɂ y^dA ܁UP7\QfZ:swOu9Uţ_+дr[8_VEO=y) i4Ob{1bڠ!!Kmwv/[/d V 'c7p&T!=Cݠ4Sc@c )9ów2v&GץJ+Hl/i;J`bDieno9Żt D/X)T>A/qc ՓbMsI,a_B >)&l >OpK~KgLtv9JUpsdz_C6P>;X9`j'a؛aC'o5t~ڨ+28<{Y8 >*ҎD;'ٚ֠FU{z̠g9 fp\t}?䋦,B亐!?VIv")%ئlC75ƜxUE\0bu.uF3/ 3/%j$өHa|I|owd^hdzq|rـѹ'xYd0r8t,[GIA X;\ipFJb1u҂c5<!,4׵J)@W4Fc _]VII]]3`nwݔ9`ḫRj "GRQR7#E1%_t Y;<,OAQ20$yfoH )yOy$N&ePvc"q?w>xR}*$$чlMEhyul1^aAWarK.횴{gPo R)eaEzkahЍNIMM@0z7 O{.Iv~eb$!3&p1 7#$hb<:6rܘsE-:WS;qq.Ѹm9esR>mLjr893]ѥDan$a5Fuse,GkT 2?}~Rnx/[ΰ/Zo WXrh zn&evtxȪ:րpcL9> 3\۔xۆxA*{Mmxf,z!E1LUWwX$`01Jw1]D. xaA m?H~:=QYekY]xLKjIAeH AJ3tUVڊ N/OG7yJ$*EG )f<\ װnQ}o =Ja:*Y^J݉_R\8#N&tTsht5LdK"lK(i=2K7pH@y]1\HeQ Є~GY=KXg@:q;IZ T00ycƨ )GSMqXiȶeJkejJ[ ;gt*xfP|Wi "KG雰e~`۾T[k : ae-{-4|G=SɅS_t|Sq=qd) ^(*lbwnL}߄1 |]S4dYM$!Pr{I{L_^ 9l1*].e] J\Lw>H0zeO>4#{lV//B DC@:}ʯ"Cw7U1(j(d]Hw3ߜāf$zlq+݉=K4S0GK+>p]`~:ߍ->T*-iQ4 =IX@  Vw*aIKHlVʁg´ U*08X|`rT` t8dRRl|uQE& 8[c|t $r7VrvLHD34S?t ͛8[)гM&0st r%}"^lTqr>-=[~ȗj^:[T-gM )[0c 'cm[#'~5"t ]rRAD7X";: N܏ 8#DqVOa|wI'CWTMG/yn^!ɹ(}(}|n^/>FGwT=o49QKX+QqO1(6Ȕ.: xg`?ʾrCm9)(򶊳(WAQɼGV!)t|W||A$]BT s-bjAG)wfgQAgGFx2(Z^-r!Vs@AlVG9U(HKSFj'ۏ'4uklWg^zP=g0_]< nLsJiIwX .8T| ~@ͮ[~0O5s[8kWpKCg~~'+#NFZŴڒ*xsP|T^]oxd;ZMZhQ=B\66;`F]^f脀}KȘ:aƯ޼lSqgѩ`F䬜 g&LWY^Q)Ӎ:Mq ceZG3[Q9Yq@i[P6` ǭc)iyJ]2Q/?hp(nzͧ]$nm>8#*Q$FRxQyd$KPqiNG4f@Q IC)FCЕV$bE(A?~hV)2Yݑll'P햽'{/)UINH3K2m 4b֞^wY29j+. C.U(T8[ХNXܧFAKХ#2\a[FſA:\WjbK-oj6MZ'e[\ ng):FTA&罦!?D˱'4H}gG@ض"{2IxeB!GBf0 T# RbTr5YXQRU::̪@"rXÀ8u֫#/ G[`y/\i/>,䛞›}%P i Kٸ+Y' gWk8Ё$KlQ"f,G :)=x?/]ݢ ?FbAR'i' v$@0I%,~4,dPzղ;XHz:w69m@ruu\sPmu0/$ДI&!˿?k0v^Ydzti[ ~T 6m|@?6 a/D#F}/(a5LN vq8o}O!@|eH?vwҚψ_ǎEEi`+J17։ء3v R\ޫqW]'Su"T1[rs*]z>h3QYHW|ru0ի2 #ڶh2| 3v4:RԴqCd㯽EEʍJ{'* n\&"b/^Ž]V?I=蒚T/]<-UV񞩽V  z(m˸cApktIIzZ^0 Mmv-Deӟk󲌆n=:_ 33}baZ]\Tu4WuҡktF2].ǦWL}akRfwE[hh%2l:H'QY59zಶ+"V 1Q9cJdf&A4 BsŶd[,v٥g_c9uD^𚣟%qyX.ݻ`7 )Z%O4 5RӟJ* JDŽ+t@KJ\2r vV0 䭲h9q1$uAu.כm6tu|z⍒LPeunVI0¶]eU\]_阵U̒g.ć,YG[CR;u`ȕs"RJ Wu|_U[swGf]t`9^4Wrl^7E-,UQVg uh}h)S-{H΂I SݳR~:\m2j/YH46 Yseސgk=F81t~ۯ5D{Igc# y|$M/~~cI-5l)й"BNp>񓑅%׮.e.Ͱ[wum6)E| E ̕8X+جz@!S3oD/Y~"߃)oբ_E&xU5YoQ#O6x\SG%#/-م7<96c[?a&gʒl/J5 F?9h-3E 7mwLLmUChdO0^FXaRX'`)paTU9eܴ- g32,Ϡg'þD'Ol^H;PG"Ŭ#~6UmPܪ`'S\6X_˅#zB .oa`Q~#Jeא5lµ{zShhYi"ky㦩\I?5XeH #;Cށ.QDAu!B+%b1A%[ՍWk&`;:cK K̎a@u˺8Ni_JR ~v2:`F(1f?{X,"Z_BV޿ח7tZh<'=+T1Ʋ~F]"FI>dDW3Y]V+K9зfCtS}#醇R:*B&3q.d%GUb#PFiW&cѬAY=@W֡XH~`Vڤ?P|u}{RLjr⯻u ֊q5wz]n⥨3[*+e^Q@:>Om5Vw d3/+0@*$>3 O!1{ }WYgޑ,9fpMt OYޙ B +✞g XQ6J4dWɲvo$ sZi^zOP8le|Y+w4H5i>/R35[k$b[kp\=2۞W!CPn,wҽ.NO ). pI?K86#F$hUR e%?vp]NE7k7nz4"[^m*_TSa@<O.(euC ga8ȑύz˜d##ত=@? mraJttFYJ?%7O}X}ԓj'rs^ 0 02$yHƽVmG-^c(xʡޔ_޶8*,1`d)2\{Ugsd;g݌]|Y?Y+XLo[MS9u~W* %^C;`t0Lw=a@LM+jZ3xvrmꃗ|*j.[<٥"F;<)mxZu;Ő" _GV"D@^ޚ+ FDPE6Y%my=R~ydh;rǥbT5R"9yS63!e+5r"uMW4v劚c)=2]a ]%7U=X`S$Id`E95S皰EvȤú‚<cO\>L&!bxLWFݺpm4Gr˜B@A#|B5 _O7̸"DqggMb>m |tSe veAc"z7PE!vx$!ʫU T DZ .t&2$[v̓ZuSeXxik~I|@ t=7kc JEXܦc}T`D[~H=WzȂG+w_E6'JQl[׼۬rHb5 Wub%e'j{jy Bq'/f<ZQf"V?0; ť\m_j`Q]Z|۪>p;ږMckMP @@q"Ea=O/ȥEWԭ2mvm.7> :tUCoXX_"m5\6?LȶׯrYcku;%V_;җW/B:@o}_mo+Һ`/H-{Erw;96ƕ.IZZN|\~U]2; ?wƇ"5;&֯.[ 8I=i?9VK9N\2*ެL!Aqm?SMRBɠKL垡 }qCRp׷ >^P:f%X·nq/ 4^ÁE(Jﳥ$ҷ(MS Liv/R.1T׼N2 I|YfBi;1oKX/*TW[ U`%/>7"磅l~mUsWФM=g _ N87ς:(%c[kpѱ]?Վ5^>W:B&@L0C0hq:3>Y*^LSUs_:qۗd&SLk7w_'b_z% Fm,KZe)!+KhIICM@{9Q쥋OO{MQfZRKWXؐeLH:UO\PxBh{b+uc[ 0TZMO}Ŵ0df= qUmj[ /Rڒ(%02S$tVx|U~v[оL4e*?Wxg}@|T9{63H Ιx e&j}:H ^wE`q[ 3^\zbB=40n䶪X=b*S5= Y^pHR%&FG5s h$B dS)1xh^'[O -< ǭiw`ˡgEE|Lo5ԴPOj;{s:x\]B 0Ai JeaiS_7JR+ŽdhkQbI9/jc#~wV4F~If%黅 ]:(5[(!'ԆNNl,LB+Sd;1?flȄIswPk GW@֏g 4ފQOHSψv(@/:1pt UھlT*XBW>}uP FМgǴQās]YJu^V:ۆoJF$&wiھE.=I͗TЊȊ4 ce]#gkhܙfFue>ݯ0QiɢRP!h '1*3azOfZ!mk|KqmBwTEG0~xgV=3kǚJ&Ke'#+m96WǾ#6 cW9cUfN%|Eruv:v(iqÿސP+} F x}潟Oѧ8fbg5\r#-˕ҡ|a2vL!&6v4*:Ϧ\ rA/ ݩ*XEP-[Ry[+ c::R/x$RBf'oŗQ(5ɱwST*jfz -(cF (DgҴ$n3I B(3r:uPW  ھ̯~w nĚ.)_XѩqAƀB$2K$ lYxXBOy}bۚ&e3e9T8?]0Ě nuiQuANp"_M?U<lGSvZuz#a"U*o6` Vt5Bs~'M ϒidEɚd]h'K=-x EW^ljYt;[y+x :Qk!b4' I='\4 _Pp>8 2Qi~-pߥsP쐚75)=aZ- Yd'Nm;VFW$u;%tɊ|'7W,U kZ?_8N b ĝdNx 1)p^!hp}PƊA{6),Q.'N !yyQP z敉qΥK RRlx¹77d~7gޒcqY-TSh 3W mWBvdyK>ފrX`J21,3)qÑubyQRDs7L G_*`#ԯz sxsR3iv~)s=s#ֻ̛~_.n vOi\= Wqe4[|n@X6>=zd9gԃ y:͌ $#r ȅ*Y,[aю%X1[gf髪 mϙv;;ݭ?rkBS|+#9Ͳ4KM5Q4\ѧHe̙V(pʤAV_.REw4`ʻxAj.f`V{1v6}-3?s~%~ت '}lUI+YP7tlM '][Pu'[-}$>#Ieл?Tvd=^)=PsƏdb&17lcv X 51(aOXKI!YgzS g:\d.SI vg9"[(*JihIz_#N+<˩14o)85&y:YiF8JfB-k5pYGP#|+VV7Nq㣉 b^dөFʢfe(x} ŗa)|y?Ӟ!;?*ԦӵAɢA';oeuB|=>M|2R\mk{rY31Cћue@Z4p g;!YAsRŹpf7M'PK5heK*r/2JvJK6L<|YD'jT0DaCfN-wm'BDBsj e޾qn.[HY&rQvrB>pId&?ǜwFp݋2-V'h՗׭9T2:$9yNⲬfňZa߉ݰz6vVK?iѢkJC:^y۾)X`HK<3YBF_)o\GKNMdf62x}/]zfRK[dl]J N,JG ~##"2AluwK}u@'` /d](@ 9>v!G(^˞ 78u0 }c"$J0@i?E)&R+ϱu&X$,mT%Ig qяU}w$Ep[9nnk&1ϠT\7y 0ih%?a sz;ZF1ͷ̵{1ſSHSs6Y&94mSVn1Cr.?^zZ!diJHz)) mD2 ًePf;Q w +d^E^5&8lH^$9C < Mr-oAlRqaS{K3gt'īt18o}d{VȐo xSy^4ʹPO:?%G*<-k&KQ&3QemMR.T_{`XgK[],4*:@PⲸµX:%݈+uNbDOX G(az,ލ0|ΞCi' U 7[lm [I?P% z Jd # &=8Aa&zJ\Yzn,g]{0a/Zzǹ\޲!\Ss='gil/f;HY:D I;|!zHt 2p&MfVrܼujji#:!p$T#O]?kw" Ze~3U\cjÆ T^ j)'";z|M*ݘ}e@$O{D }AΑYy1I{pUpo{"XBPfGS^vκ@\ړGqhV"E/tL@6ZOop\hp8RR4YCnfuR"΋ EEAW3wn:VwQQ[꺻pOgWH[P:p2-{S qK+-^(h3oD Fyt`}q,:`Oxg@/\_tӇNZd]Gta2g:`'#QTN6s0M㉍x {gboڴxfk矽*G5`o4f g±Ǝ<&fRndĘ`qN< tջL3[F3\w CVb}:`SDsW(Z qF@N|vTnGr].刼 z5l@f>NY³i;f r,\Vc*衭m*H\d1%gQYɳdMmE;6,_aNG*ҟyo0QJGmeÿ́CHstGFpl <ku+_|jz"KDv~ uUi3v-<|.Rj H2=tB@C>K$-)śXk0񝌠QE HݪD.3O>XdPҩ8uVQ:2Ų0u  YjSOPr ;$ 2`K䪔s^bLd1ӽuMh:腘̸% VL;5Q(( p#U ,KkR9{ˬ]s& )_`?)xIӥ蟍TZ1-1fWڄx - Wut2-\ӳR=3y^%|$B݊%-³^R{:TRV&13H{~p+b}9}y_~i8yTtCHė_ K~Nu,nVɖr˅20`Sl}of?TUYs]UQ4Fo~ʎ/إᗙD발a!(?y7]J$ - zR!ln®hԍT>$A80 [Wfca)$\q?BwLl !0^&e}wd0RuX#&SPXHji$]vG7Y^3 a"*\D=;mRmоs8mC0^^uC i{ԆQ5]. HCM`F(剳] CP>~Bk`{)䲤jwKx|(V}ULɳsT]-0F%b#N[>eou11rLTZLϊq_LA[Ppp8/ Ʌm_]}LyD4|ȿ5x"T$Hs1xχ&ퟭũU9xqe WGBC܅؃t- +{`LEc+dKhlNP/sWר"R &qB"S,'{x-`J#r-9h{ٜ )AXŌHؠ7T^#TJNt n/xC!~. 㭳/2$%UHBܺw0t^Z5{>*w ƆƒQNf׮R [~[Jy * ޲LS{U Yq ^6it1sY{mgJ7noY} 'MIaY'ˀƗBZ {6Lv^ Z4FgufF]Mrm2'Xc{iOuJՃ\7w gkh/o"O<1>H,.`I$A9Ä<9` BSO13xܰ/m)Fس\ n8\I 5x y$M\Sb>)/i#;k־v9-6OQ-F5%_~}w"UyG:)^j# ^:_ԓ镏;X8B !kɹ#q)O϶ p"z_Q3Z4C%3{H@rG4 WQ@$v3FS?&a|EKٺΠ_?iOX/qVL]kfU۷PP~88x!">j9o $ d~<.*CNؚiOBebjv){(0 + %&Vb?DV2y*Ϗnco%PVo+]~5rr}Ńvh A4L~9؍]پة[rރU".!*+ ̺̈4VD='Hfae]v74 9Bݔa3CTo$yr#,TቿF}[1ZAK=>}Wj-y-;sҧ_ q̓Yn7 D3tv9^I"P hgiO @CmWͽD}4iLAgp%)I&B}%K3:l6)dQJt${y5bk~U (*&/4sGJ% @Qj&"XU.r#E2^~4dz$IYeԞfJA2X7p=e~ve..YtvEr01͓<$X隁&69qBe;*]APsBM 8twQ&J0e=>=/j``l[0*nK:pIhgcwFg" y|Eڑt>,æk箛ApC#ʢ q.\aҡx2$zp5ъ1.v}?I'79(}vlFdt܂S=pNm'9u7u>%(+} yDʭD&o1sI=iS_忞ChlNYK:& U Y蝚xXOIԛq%=%UU%k@G ȊdDO;RyS<}JA!)sR7'l)gԊbPdTPqM&.*Hmq;'mt ؿ B,]$ CQ@A\TfvҨVĉnKO&-l}e awz':}9j[BD PETD/ƪ~\DЅ@g SAMQ=zNoq}8w͸+QOt7ʦA~-{Eo Wt-_HTm*P :sBG{  6k3FZM&k)TosF{tr5Pot0vLٚ-}7NSzV0q{:6 SG)A. jiw+IUPjm7-]껣`"85jh j;cSM_L%ς~ WrBUr[tX U輤^:-}YWL7w?Etv@T~:-C0Ĝq*ibCl8h {I9xy:p6@曒h5nznᨠ?Fi * 8hOxA:ʞ)<,) >?}XH_WV8DF"xm\za2 8~7{Mfo}x77:Q6&3$u+:|~q~\Ń:n3yE؟7mdͪ5RD9af ~r!p}/qZ-aV q2y-y2ɒjZqKNlβ>˜>9z)D- T4#s9{lSGiX,t1x2/ ̗2 = % YOoaY  GB8(o3CG&"2PݣnzGTF4 +2zέ&K :_sʺ5K<8. Xdq[v#k7DBSBFyсhPw w.:;6%E;L=jJxHyD29S> :HRE̛wkCB J"N&>ĺyyR,ݗ e;Q夶z[CkWEڄM2PFA>|BP#)gjbQ"WwGR  {u0ƶ\{$4=5_B 5gy ;Ȩ,fThVqO"O\bYW~T2"SdCw6 ʙIxINHcn  !Ɂɨ 2sOl2.횇" j~y=x˜c&Yd嬨drOׄO2Kg;۹=7# :Tszl_ŻՌ,M6ʚ?yѰTYcC;2őrsOҼot6\֥uB~Ȑ~, fN"d798 [\M:P9Jpua 2m{.duBʯ2 m},q鵲(HRdz PD&v @w'HR[Diw{Kt.5> yFgX#}̞J|@3nΘjBv[ty- ̵p%.i呓8HtN@D8$N}-A9kds1>-PQ_3'cu-Mal`~شm4/(ug"M[tc_\`;eNhYluƄvZm/wl5Ň Ҕ)esJΛb^%u Wѫq+C׫SS菇  sR_YS!a^8gXUXZj!o1& "t^W4[Lڻ`2ե,r3+, ,m )455t+ƱL7~e;qЃ fB c+ (J5L&N"7$ J "/*:_5e̮%Gm:6ӄix~7q9rZX>aSƲ)އo`1I;6RTz$"&}͛${>ݼ3H>5$g+?Ne6T$Dl/P<f1̴Q>_/[0%7N:RKB ]sa̟ͰTV|Hb,$'^2k&W^ʇC‹N=mgvk } pWARQ"='nu-O$ԢcNhBTjI|w TrUl$}3fe;Ѯw,kr}t*v³%p /wH~5cg4nWǴZ]Zrvr{SW8bӌiX{%VbogΉF|89?gdC1tkU0Tꖒk_W9y3#JSն4J}cU)ohJ)LwVwM:rF\ `6k8kfc fɔSIp+[ݤ;בjUNv+"98Vm{##%=>*e >9%&*lI@kܙY:EȚ׍,cCh8{cŊ) _1Πh_Α}!HQ?+&Mäa׾:6"2⤨=7W˷/BD˖voa?NDik Q%X!:͟W _0g$vQhӃ̅r,aj҅Ekp(Bߣ 煿\đ+mmO$.l$GdBRvdWM"4m|$|2Ud cO:|F\YIzqÒ.G*p( d@OpqfΥ&O>\ݑq\P_@_4l0aST*bQZD@ϥv>Qk"tW@mږpA$Ewlg سLcl^Dt܎'cjL j}żDe_MwڏP uW(67),Ob8ݠo 'gP)x(KC^xlCYR%䠕9aIE  Er&*"y} w,^ ff޻z(AL26Cmig8'$@$BA6+Hq3Ui{^mÌ@#:q?!ic:+o]b>kB3d6 f9&G6VA*TZ 6q3>$̬JVm"vjFe)s)[n &}.f_R (Ӟ,cl' r4N r6KH\Ng4q]a0R.^<%H ;)#?YO4cs3="'tMA ŤE.De̽s4}G1 jݤBoU0o#va6ȒrG J;qFNhUZL-^$Tw"ȣ]EAe"ps̥Qp"?7G1_:7rcN@"æFƍvy'e.Q6Q9*6SKa4]lTF*bm*/h3X5KUfggbZ1ő 5s T=ҟ1ALHveȱ4lÔ/Ym6<iyvKv/fL~Sw#oWPj=i F• .eGGWLH g}5Pl0Unf JY@^|w[zMDc|\&2BQKIYU')O[lK:.kgA}9âxξ2j#(Ћ7JC'6PАW !>\c0du㎦ɛFi;r筆:FIA(!FV̢7Ypb(:PV]VHDi۷-ӑ.1tˍfBKٳ #hxS0d]c[($$QŸt +ٓj .[X,`oewPloE=Yv=aYǝ2ʀo^@*%m CzfB,q9-٦o=ƹ)Fҹ!HQ$Uz9n[E~ttlB2Y\FG;y:]JB[ESL͊JhRTz ?%1'$Y[;m#/EKj p4jħ1~7P9K>rˮ{ Ia?WJ^Ohn_z.m@ (!0bM1S zlLF``jl3ڰGqWDWozg&K]; N;u${qࠢd7S, !m0LpTIh26 D5C/T\}vĘ$ԅ7>4l>?40#E+5ݼ.Dz|򠅡P>O?~6ADd5R-1UIL%}z-iJl>hi%U1n|}w: $x*!$Yԛ&+)!A JSȯfzZLS\!ܧGdni} *r?COgGteV$=NTCz_Q~ːG@d8?'&xE-K'-$%%P!}.=τ-e'2|3;\ǿS#$Uޔ ! poQSeGhO'i!ǭe>ZEe砱U>t⩛BwKoidAvrrz4Cwd|y݁cYA}DVBR{x~bD@TtIn})[ gmՁ5Ԛ)b@-iycůR5wZ|ű(sBaXX4\[R湁u"e1.^`b՞:N3?V`E,|NAk o(ԁxMRR!V"Nf8՜\^Xu3Zf<уAu{~>6:lHQ8 Fx kYGn(Fvn*p'6"8,o ,毶~CӘ[]&~8?f0?RdPϒRӛLeZ^gXmǵ94YMPMȧ6nU3[t6:;<88hz*w)[]4Wgeo22VCY^y^hL4)Z~cM\\"uxGaqK3 \.ABJr k(s-6]>#8'xlCX\'C:Xom/ s<5ڂB,JE29'M]sM Dg؋rEoO <]I+K&CIld]1d5HrJ#D8yx\}L[|@"a20RUvog~y_w&$cq]r쒟[Ű7LOMo>/ _l18m/$jKXtTvN6;bI(޶}K+`zwP+#P+u{ѤWBep,yYEzd#Q7>0 P؟`&ԉn13e~Q;)US%Ǔgb桥,6zfN.d^]ؕ ;ENt{-̓~O!䠇A8L>sH~eۘs E=&-NJ+ a[8eRL3c"U/K^SրEJKՁICI@߁.GʊMCUo"Fu w|LdK~b@9vd/Kl`Gjp#)}\'Ÿ R+gጾGu i˽և)t tDΒ@;܎3k% ٵ:8hXF: |jSNw Cra%<m=F#j{bB/suvFB lѳSѠ oA/ޜfCJsU0g OucRsӎ Kp(U?!w3꧙aQVY-xlUj7x`mZ[>!Gɤa; #].{~;Et*2F\aEaxRz/O@!8IWW3m{}ݯ-n_,#gIYs?݁ɄL:r8 f(H5߱qZ狈lJr[n'>p ϋ&!> TqfWr9lxr|O^FLmz4JEWHWƴӚ[:\m\PgtWwrk'ĭZ/ ٠s269l=~p"QOl)n!lՒ pNhs+;Ra"bU^vĹ{e+:4N1v /8 ǒ7Ig T jpc@)$H! 95{2MN &- :כ@uF<:.!8M|XvR.?M%0#Uz*X!8&&y-3 `V5:= ZO3E<>\2hkEo0gɽ5,UF)AIQKd?K[} 1S<%ޤ9P\eHg i]?Z1HK%tN9H‰ }c iq/(2ѕq:I ކD w{r%6D/r#/ tU^O3$F)7K)T- :1C7G=7j|9I] 4,Pb4).4bne{Q#a<Yˆ짢L[Qͨ[8l }A'&i ccix/4NK|V _Q%9K6u)z}@VfW|B H MV0TM?WfiC6_2=]i]B^k7UR^ې,wӯ xX\>e-)O+cy!ZzS EW2$(e%Jsi6 %j[ТD =$!&T p?4bڨ[V:迁B{HGm$S:vȅLbmhTzC뒿ER>t}+Y5j)I?Vd@.>ژ4F I .KӁH9pILL@$#OP躽aܚˤ4=jl]7EpDӢ?.wg0Ƞ/BDzvtoNK#ׅDtH3qfk2=!Md:OO n"zs"|iY L!9?aRtA> !N'#OiLWm>d QêY ,:o^X-^ir·ǠDo!'>1556 1557 1558 1559 1560 1561 1562 1563 1564 1565 1566 1567 1568 1569 1570 1571 1572 1573 1574 1575 1576 1577 1578 1579 1580 1581 1582 1583 1584 1585 1586 1587 1588 1589 1590 1591 1592 1593 1594 1595 1596 1597 1598 1599 1600 1601 1602 1603 1604 1605 1606 1607 1608 1609 1610 1611 1612 1613 1614 1615 1616 1617 1618 1619 1620 1621 1622 1623 1624 1625 1626 1627 1628 1629 1630 1631 1632 1633 1634 1635 1636 1637 1638
<?php
/**
*
* @package phpBB3
* @version $Id$
* @copyright (c) 2005 phpBB Group
* @license http://opensource.org/licenses/gpl-license.php GNU Public License
*
*/

/**
* @ignore
*/
if (!defined('IN_PHPBB'))
{
	exit;
}

if (!class_exists('bbcode'))
{
	include(PHPBB_ROOT_PATH . 'includes/bbcode.' . PHP_EXT);
}

/**
* BBCODE FIRSTPASS
* BBCODE first pass class (functions for parsing messages for db storage)
* @package phpBB3
*/
class bbcode_firstpass extends bbcode
{
	var $message = '';
	var $warn_msg = array();
	var $parsed_items = array();

	/**
	* Parse BBCode
	*/
	function parse_bbcode()
	{
		if (!$this->bbcodes)
		{
			$this->bbcode_init();
		}

		global $user;

		$this->bbcode_bitfield = '';
		$bitfield = new bitfield();

		foreach ($this->bbcodes as $bbcode_name => $bbcode_data)
		{
			if (isset($bbcode_data['disabled']) && $bbcode_data['disabled'])
			{
				foreach ($bbcode_data['regexp'] as $regexp => $replacement)
				{
					if (preg_match($regexp, $this->message))
					{
						$this->warn_msg[] = sprintf($user->lang['UNAUTHORISED_BBCODE'] , '[' . $bbcode_name . ']');
						continue;
					}
				}
			}
			else
			{
				foreach ($bbcode_data['regexp'] as $regexp => $replacement)
				{
					// The pattern gets compiled and cached by the PCRE extension,
					// it should not demand recompilation
					if (preg_match($regexp, $this->message))
					{
						$this->message = preg_replace($regexp, $replacement, $this->message);
						$bitfield->set($bbcode_data['bbcode_id']);
					}
				}
			}
		}

		$this->bbcode_bitfield = $bitfield->get_base64();
	}

	/**
	* Prepare some bbcodes for better parsing
	*/
	function prepare_bbcodes()
	{
		// Ok, seems like users instead want the no-parsing of urls, smilies, etc. after and before and within quote tags being tagged as "not a bug".
		// Fine by me ;) Will ease our live... but do not come back and cry at us, we won't hear you.

		/* Add newline at the end and in front of each quote block to prevent parsing errors (urls, smilies, etc.)
		if (strpos($this->message, '[quote') !== false && strpos($this->message, '[/quote]') !== false)
		{
			$this->message = str_replace("\r\n", "\n", $this->message);

			// We strip newlines and spaces after and before quotes in quotes (trimming) and then add exactly one newline
			$this->message = preg_replace('#\[quote(=&quot;.*?&quot;)?\]\s*(.*?)\s*\[/quote\]#siu', '[quote\1]' . "\n" . '\2' ."\n[/quote]", $this->message);
		}
		*/

		// Add other checks which needs to be placed before actually parsing anything (be it bbcodes, smilies, urls...)
	}

	/**
	* Init bbcode data for later parsing
	*/
	function bbcode_init()
	{
		static $rowset;

		// This array holds all bbcode data. BBCodes will be processed in this
		// order, so it is important to keep [code] in first position and
		// [quote] in second position.
		$this->bbcodes = array(
			'code'			=> array('bbcode_id' => 8,	'regexp' => array('#\[code(?:=([a-z]+))?\](.+\[/code\])#ise' => "\$this->bbcode_code('\$1', '\$2')")),
			'quote'			=> array('bbcode_id' => 0,	'regexp' => array('#\[quote(?:=&quot;(.*?)&quot;)?\](.+)\[/quote\]#ise' => "\$this->bbcode_quote('\$0')")),
			'attachment'	=> array('bbcode_id' => 12,	'regexp' => array('#\[attachment=([0-9]+)\](.*?)\[/attachment\]#ise' => "\$this->bbcode_attachment('\$1', '\$2')")),
			'b'				=> array('bbcode_id' => 1,	'regexp' => array('#\[b\](.*?)\[/b\]#ise' => "\$this->bbcode_strong('\$1')")),
			'i'				=> array('bbcode_id' => 2,	'regexp' => array('#\[i\](.*?)\[/i\]#ise' => "\$this->bbcode_italic('\$1')")),
			'url'			=> array('bbcode_id' => 3,	'regexp' => array('#\[url(=(.*))?\](.*)\[/url\]#iUe' => "\$this->validate_url('\$2', '\$3')")),
			'img'			=> array('bbcode_id' => 4,	'regexp' => array('#\[img\](.*)\[/img\]#iUe' => "\$this->bbcode_img('\$1')")),
			'size'			=> array('bbcode_id' => 5,	'regexp' => array('#\[size=([\-\+]?\d+)\](.*?)\[/size\]#ise' => "\$this->bbcode_size('\$1', '\$2')")),
			'color'			=> array('bbcode_id' => 6,	'regexp' => array('!\[color=(#[0-9a-f]{6}|[a-z\-]+)\](.*?)\[/color\]!ise' => "\$this->bbcode_color('\$1', '\$2')")),
			'u'				=> array('bbcode_id' => 7,	'regexp' => array('#\[u\](.*?)\[/u\]#ise' => "\$this->bbcode_underline('\$1')")),
			'list'			=> array('bbcode_id' => 9,	'regexp' => array('#\[list(?:=(?:[a-z0-9]|disc|circle|square))?].*\[/list]#ise' => "\$this->bbcode_parse_list('\$0')")),
			'email'			=> array('bbcode_id' => 10,	'regexp' => array('#\[email=?(.*?)?\](.*?)\[/email\]#ise' => "\$this->validate_email('\$1', '\$2')")),
			'flash'			=> array('bbcode_id' => 11,	'regexp' => array('#\[flash=([0-9]+),([0-9]+)\](.*?)\[/flash\]#ie' => "\$this->bbcode_flash('\$1', '\$2', '\$3')"))
		);

		// Zero the parsed items array
		$this->parsed_items = array();

		foreach ($this->bbcodes as $tag => $bbcode_data)
		{
			$this->parsed_items[$tag] = 0;
		}

		if (!is_array($rowset))
		{
			global $db;
			$rowset = array();

			$sql = 'SELECT *
				FROM ' . BBCODES_TABLE;
			$result = $db->sql_query($sql);

			while ($row = $db->sql_fetchrow($result))
			{
				$rowset[] = $row;
			}
			$db->sql_freeresult($result);
		}

		foreach ($rowset as $row)
		{
			$this->bbcodes[$row['bbcode_tag']] = array(
				'bbcode_id'	=> (int) $row['bbcode_id'],
				'regexp'	=> array($row['first_pass_match'] => str_replace('$uid', $this->bbcode_uid, $row['first_pass_replace']))
			);
		}
	}

	/**
	* Making some pre-checks for bbcodes as well as increasing the number of parsed items
	*/
	function check_bbcode($bbcode, &$in)
	{
		// when using the /e modifier, preg_replace slashes double-quotes but does not
		// seem to slash anything else
		$in = str_replace("\r\n", "\n", str_replace('\"', '"', $in));

		// Trimming here to make sure no empty bbcodes are parsed accidently
		if (trim($in) == '')
		{
			return false;
		}

		$this->parsed_items[$bbcode]++;

		return true;
	}

	/**
	* Transform some characters in valid bbcodes
	*/
	function bbcode_specialchars($text)
	{
		$str_from = array('<', '>', '[', ']', '.', ':');
		$str_to = array('&lt;', '&gt;', '&#91;', '&#93;', '&#46;', '&#58;');

		return str_replace($str_from, $str_to, $text);
	}

	/**
	* Parse size tag
	*/
	function bbcode_size($stx, $in)
	{
		global $user, $config;

		if (!$this->check_bbcode('size', $in))
		{
			return $in;
		}

		if ($config['max_' . $this->mode . '_font_size'] && $config['max_' . $this->mode . '_font_size'] < $stx)
		{
			$this->warn_msg[] = sprintf($user->lang['MAX_FONT_SIZE_EXCEEDED'], $config['max_' . $this->mode . '_font_size']);

			return '[size=' . $stx . ']' . $in . '[/size]';
		}

		// Do not allow size=0
		if ($stx <= 0)
		{
			return '[size=' . $stx . ']' . $in . '[/size]';
		}

		return '[size=' . $stx . ':' . $this->bbcode_uid . ']' . $in . '[/size:' . $this->bbcode_uid . ']';
	}

	/**
	* Parse color tag
	*/
	function bbcode_color($stx, $in)
	{
		if (!$this->check_bbcode('color', $in))
		{
			return $in;
		}

		return '[color=' . $stx . ':' . $this->bbcode_uid . ']' . $in . '[/color:' . $this->bbcode_uid . ']';
	}

	/**
	* Parse u tag
	*/
	function bbcode_underline($in)
	{
		if (!$this->check_bbcode('u', $in))
		{
			return $in;
		}

		return '[u:' . $this->bbcode_uid . ']' . $in . '[/u:' . $this->bbcode_uid . ']';
	}

	/**
	* Parse b tag
	*/
	function bbcode_strong($in)
	{
		if (!$this->check_bbcode('b', $in))
		{
			return $in;
		}

		return '[b:' . $this->bbcode_uid . ']' . $in . '[/b:' . $this->bbcode_uid . ']';
	}

	/**
	* Parse i tag
	*/
	function bbcode_italic($in)
	{
		if (!$this->check_bbcode('i', $in))
		{
			return $in;
		}

		return '[i:' . $this->bbcode_uid . ']' . $in . '[/i:' . $this->bbcode_uid . ']';
	}

	/**
	* Parse img tag
	*/
	function bbcode_img($in)
	{
		global $user, $config;

		if (!$this->check_bbcode('img', $in))
		{
			return $in;
		}

		$in = trim($in);
		$error = false;

		$in = str_replace(' ', '%20', $in);

		// Checking urls
		if (!preg_match('#^' . get_preg_expression('url') . '$#i', $in) && !preg_match('#^' . get_preg_expression('www_url') . '$#i', $in))
		{
			return '[img]' . $in . '[/img]';
		}

		// Try to cope with a common user error... not specifying a protocol but only a subdomain
		if (!preg_match('#^[a-z0-9]+://#i', $in))
		{
			$in = 'http://' . $in;
		}

		if ($config['max_' . $this->mode . '_img_height'] || $config['max_' . $this->mode . '_img_width'])
		{
			$stats = @getimagesize($in);

			if ($stats === false)
			{
				$error = true;
				$this->warn_msg[] = $user->lang['UNABLE_GET_IMAGE_SIZE'];
			}
			else
			{
				if ($config['max_' . $this->mode . '_img_height'] && $config['max_' . $this->mode . '_img_height'] < $stats[1])
				{
					$error = true;
					$this->warn_msg[] = sprintf($user->lang['MAX_IMG_HEIGHT_EXCEEDED'], $config['max_' . $this->mode . '_img_height']);
				}

				if ($config['max_' . $this->mode . '_img_width'] && $config['max_' . $this->mode . '_img_width'] < $stats[0])
				{
					$error = true;
					$this->warn_msg[] = sprintf($user->lang['MAX_IMG_WIDTH_EXCEEDED'], $config['max_' . $this->mode . '_img_width']);
				}
			}
		}

		if ($error || $this->path_in_domain($in))
		{
			return '[img]' . $in . '[/img]';
		}

		return '[img:' . $this->bbcode_uid . ']' . $this->bbcode_specialchars($in) . '[/img:' . $this->bbcode_uid . ']';
	}

	/**
	* Parse flash tag
	*/
	function bbcode_flash($width, $height, $in)
	{
		global $user, $config;

		if (!$this->check_bbcode('flash', $in))
		{
			return $in;
		}

		$in = trim($in);
		$error = false;

		// Apply the same size checks on flash files as on images
		if ($config['max_' . $this->mode . '_img_height'] || $config['max_' . $this->mode . '_img_width'])
		{
			if ($config['max_' . $this->mode . '_img_height'] && $config['max_' . $this->mode . '_img_height'] < $height)
			{
				$error = true;
				$this->warn_msg[] = sprintf($user->lang['MAX_FLASH_HEIGHT_EXCEEDED'], $config['max_' . $this->mode . '_img_height']);
			}

			if ($config['max_' . $this->mode . '_img_width'] && $config['max_' . $this->mode . '_img_width'] < $width)
			{
				$error = true;
				$this->warn_msg[] = sprintf($user->lang['MAX_FLASH_WIDTH_EXCEEDED'], $config['max_' . $this->mode . '_img_width']);
			}
		}

		if ($error || $this->path_in_domain($in))
		{
			return '[flash=' . $width . ',' . $height . ']' . $in . '[/flash]';
		}

		return '[flash=' . $width . ',' . $height . ':' . $this->bbcode_uid . ']' . $this->bbcode_specialchars($in) . '[/flash:' . $this->bbcode_uid . ']';
	}

	/**
	* Parse inline attachments [ia]
	*/
	function bbcode_attachment($stx, $in)
	{
		if (!$this->check_bbcode('attachment', $in))
		{
			return $in;
		}

		return '[attachment=' . $stx . ':' . $this->bbcode_uid . ']<!-- ia' . $stx . ' -->' . trim($in) . '<!-- ia' . $stx . ' -->[/attachment:' . $this->bbcode_uid . ']';
	}

	/**
	* Parse code text from code tag
	* @private
	*/
	function bbcode_parse_code($stx, &$code)
	{
		switch (strtolower($stx))
		{
			case 'php':

				$remove_tags = false;
				$code = str_replace(array('&lt;', '&gt;'), array('<', '>'), $code);

				if (!preg_match('/\<\?.*?\?\>/is', $code))
				{
					$remove_tags = true;
					$code = "<?php $code ?>";
				}

				$conf = array('highlight.bg', 'highlight.comment', 'highlight.default', 'highlight.html', 'highlight.keyword', 'highlight.string');
				foreach ($conf as $ini_var)
				{
					@ini_set($ini_var, str_replace('highlight.', 'syntax', $ini_var));
				}

				// Because highlight_string is specialcharing the text (but we already did this before), we have to reverse this in order to get correct results
				$code = htmlspecialchars_decode($code);
				$code = highlight_string($code, true);

				$str_from = array('<span style="color: ', '<font color="syntax', '</font>', '<code>', '</code>','[', ']', '.', ':');
				$str_to = array('<span class="', '<span class="syntax', '</span>', '', '', '&#91;', '&#93;', '&#46;', '&#58;');

				if ($remove_tags)
				{
					$str_from[] = '<span class="syntaxdefault">&lt;?php </span>';
					$str_to[] = '';
					$str_from[] = '<span class="syntaxdefault">&lt;?php&nbsp;';
					$str_to[] = '<span class="syntaxdefault">';
				}

				$code = str_replace($str_from, $str_to, $code);
				$code = preg_replace('#^(<span class="[a-z_]+">)\n?(.*?)\n?(</span>)$#is', '$1$2$3', $code);

				if ($remove_tags)
				{
					$code = preg_replace('#(<span class="[a-z]+">)?\?&gt;(</span>)#', '$1&nbsp;$2', $code);
				}

				$code = preg_replace('#^<span class="[a-z]+"><span class="([a-z]+)">(.*)</span></span>#s', '<span class="$1">$2</span>', $code);
				$code = preg_replace('#(?:\s++|&nbsp;)*+</span>$#u', '</span>', $code);

				// remove newline at the end
				if (!empty($code) && substr($code, -1) == "\n")
				{
					$code = substr($code, 0, -1);
				}

				return "[code=$stx:" . $this->bbcode_uid . ']' . $code . '[/code:' . $this->bbcode_uid . ']';
			break;

			default:
				return '[code:' . $this->bbcode_uid . ']' . $this->bbcode_specialchars($code) . '[/code:' . $this->bbcode_uid . ']';
			break;
		}
	}

	/**
	* Parse code tag
	* Expects the argument to start right after the opening [code] tag and to end with [/code]
	*/
	function bbcode_code($stx, $in)
	{
		if (!$this->check_bbcode('code', $in))
		{
			return $in;
		}

		// We remove the hardcoded elements from the code block here because it is not used in code blocks
		// Having it here saves us one preg_replace per message containing [code] blocks
		// Additionally, magic url parsing should go after parsing bbcodes, but for safety those are stripped out too...
		$htm_match = get_preg_expression('bbcode_htm');
		unset($htm_match[4], $htm_match[5]);
		$htm_replace = array('\1', '\1', '\2', '\1');

		$out = $code_block = '';
		$open = 1;

		while ($in)
		{
			// Determine position and tag length of next code block
			preg_match('#(.*?)(\[code(?:=([a-z]+))?\])(.+)#is', $in, $buffer);
			$pos = (isset($buffer[1])) ? strlen($buffer[1]) : false;
			$tag_length = (isset($buffer[2])) ? strlen($buffer[2]) : false;

			// Determine position of ending code tag
			$pos2 = stripos($in, '[/code]');

			// Which is the next block, ending code or code block
			if ($pos !== false && $pos < $pos2)
			{
				// Open new block
				if (!$open)
				{
					$out .= substr($in, 0, $pos);
					$in = substr($in, $pos);
					$stx = (isset($buffer[3])) ? $buffer[3] : '';
					$code_block = '';
				}
				else
				{
					// Already opened block, just append to the current block
					$code_block .= substr($in, 0, $pos) . ((isset($buffer[2])) ? $buffer[2] : '');
					$in = substr($in, $pos);
				}

				$in = substr($in, $tag_length);
				$open++;
			}
			else
			{
				// Close the block
				if ($open == 1)
				{
					$code_block .= substr($in, 0, $pos2);
					$code_block = preg_replace($htm_match, $htm_replace, $code_block);

					// Parse this code block
					$out .= $this->bbcode_parse_code($stx, $code_block);
					$code_block = '';
					$open--;
				}
				else if ($open)
				{
					// Close one open tag... add to the current code block
					$code_block .= substr($in, 0, $pos2 + 7);
					$open--;
				}
				else
				{
					// end code without opening code... will be always outside code block
					$out .= substr($in, 0, $pos2 + 7);
				}

				$in = substr($in, $pos2 + 7);
			}
		}

		// if now $code_block has contents we need to parse the remaining code while removing the last closing tag to match up.
		if ($code_block)
		{
			$code_block = substr($code_block, 0, -7);
			$code_block = preg_replace($htm_match, $htm_replace, $code_block);

			$out .= $this->bbcode_parse_code($stx, $code_block);
		}

		return $out;
	}

	/**
	* Parse list bbcode
	* Expects the argument to start with a tag
	*/
	function bbcode_parse_list($in)
	{
		if (!$this->check_bbcode('list', $in))
		{
			return $in;
		}

		// $tok holds characters to stop at. Since the string starts with a '[' we'll get everything up to the first ']' which should be the opening [list] tag
		$tok = ']';
		$out = '[';

		// First character is [
		$in = substr($in, 1);
		$list_end_tags = $item_end_tags = array();

		do
		{
			$pos = strlen($in);

			for ($i = 0, $tok_len = strlen($tok); $i < $tok_len; ++$i)
			{
				$tmp_pos = strpos($in, $tok[$i]);

				if ($tmp_pos !== false && $tmp_pos < $pos)
				{
					$pos = $tmp_pos;
				}
			}

			$buffer = substr($in, 0, $pos);
			$tok = $in[$pos];

			$in = substr($in, $pos + 1);

			if ($tok == ']')
			{
				// if $tok is ']' the buffer holds a tag
				if (strtolower($buffer) == '/list' && sizeof($list_end_tags))
				{
					// valid [/list] tag, check nesting so that we don't hit false positives
					if (sizeof($item_end_tags) && sizeof($item_end_tags) >= sizeof($list_end_tags))
					{
						// current li tag has not been closed
						$out = preg_replace('/\n?\[$/', '[', $out) . array_pop($item_end_tags) . '][';
					}

					$out .= array_pop($list_end_tags) . ']';
					$tok = '[';
				}
				else if (preg_match('#^list(=[0-9a-z])?$#i', $buffer, $m))
				{
					// sub-list, add a closing tag
					if (empty($m[1]) || preg_match('/^(?:disc|square|circle)$/i', $m[1]))
					{
						array_push($list_end_tags, '/list:u:' . $this->bbcode_uid);
					}
					else
					{
						array_push($list_end_tags, '/list:o:' . $this->bbcode_uid);
					}
					$out .= 'list' . substr($buffer, 4) . ':' . $this->bbcode_uid . ']';
					$tok = '[';
				}
				else
				{
					if (($buffer == '*' || substr($buffer, -2) == '[*') && sizeof($list_end_tags))
					{
						// the buffer holds a bullet tag and we have a [list] tag open
						if (sizeof($item_end_tags) >= sizeof($list_end_tags))
						{
							if (substr($buffer, -2) == '[*')
							{
								$out .= substr($buffer, 0, -2) . '[';
							}
							// current li tag has not been closed
							if (preg_match('/\n\[$/', $out, $m))
							{
								$out = preg_replace('/\n\[$/', '[', $out);
								$buffer = array_pop($item_end_tags) . "]\n[*:" . $this->bbcode_uid;
							}
							else
							{
								$buffer = array_pop($item_end_tags) . '][*:' . $this->bbcode_uid;
							}
						}
						else
						{
							$buffer = '*:' . $this->bbcode_uid;
						}

						$item_end_tags[] = '/*:m:' . $this->bbcode_uid;
					}
					else if ($buffer == '/*')
					{
						array_pop($item_end_tags);
						$buffer = '/*:' . $this->bbcode_uid;
					}

					$out .= $buffer . $tok;
					$tok = '[]';
				}
			}
			else
			{
				// Not within a tag, just add buffer to the return string
				$out .= $buffer . $tok;
				$tok = ($tok == '[') ? ']' : '[]';
			}
		}
		while ($in);

		// do we have some tags open? close them now
		if (sizeof($item_end_tags))
		{
			$out .= '[' . implode('][', $item_end_tags) . ']';
		}
		if (sizeof($list_end_tags))
		{
			$out .= '[' . implode('][', $list_end_tags) . ']';
		}

		return $out;
	}

	/**
	* Parse quote bbcode
	* Expects the argument to start with a tag
	*/
	function bbcode_quote($in)
	{
		global $config, $user;

		/**
		* If you change this code, make sure the cases described within the following reports are still working:
		* #3572 - [quote="[test]test"]test [ test[/quote] - (correct: parsed)
		* #14667 - [quote]test[/quote] test ] and [ test [quote]test[/quote] (correct: parsed)
		* #14770 - [quote="["]test[/quote] (correct: parsed)
		* [quote="[i]test[/i]"]test[/quote] (correct: parsed)
		* [quote="[quote]test[/quote]"]test[/quote] (correct: parsed - Username displayed as [quote]test[/quote])
		* #20735 - [quote]test[/[/b]quote] test [/quote][/quote] test - (correct: quoted: "test[/[/b]quote] test" / non-quoted: "[/quote] test" - also failed if layout distorted)
		*/

		$in = str_replace("\r\n", "\n", str_replace('\"', '"', trim($in)));

		if (!$in)
		{
			return '';
		}

		// To let the parser not catch tokens within quote_username quotes we encode them before we start this...
		$in = preg_replace('#quote=&quot;(.*?)&quot;\]#ie', "'quote=&quot;' . str_replace(array('[', ']'), array('&#91;', '&#93;'), '\$1') . '&quot;]'", $in);

		$tok = ']';
		$out = '[';

		$in = substr($in, 1);
		$close_tags = $error_ary = array();
		$buffer = '';

		do
		{
			$pos = strlen($in);
			for ($i = 0, $tok_len = strlen($tok); $i < $tok_len; ++$i)
			{
				$tmp_pos = strpos($in, $tok[$i]);
				if ($tmp_pos !== false && $tmp_pos < $pos)
				{
					$pos = $tmp_pos;
				}
			}

			$buffer .= substr($in, 0, $pos);
			$tok = $in[$pos];
			$in = substr($in, $pos + 1);

			if ($tok == ']')
			{
				if (strtolower($buffer) == '/quote' && sizeof($close_tags) && substr($out, -1, 1) == '[')
				{
					// we have found a closing tag
					$out .= array_pop($close_tags) . ']';
					$tok = '[';
					$buffer = '';

					/* Add space at the end of the closing tag if not happened before to allow following urls/smilies to be parsed correctly
					* Do not try to think for the user. :/ Do not parse urls/smilies if there is no space - is the same as with other bbcodes too.
					* Also, we won't have any spaces within $in anyway, only adding up spaces -> #10982
					if (!$in || $in[0] !== ' ')
					{
						$out .= ' ';
					}*/
				}
				else if (preg_match('#^quote(?:=&quot;(.*?)&quot;)?$#is', $buffer, $m) && substr($out, -1, 1) == '[')
				{
					$this->parsed_items['quote']++;

					// the buffer holds a valid opening tag
					if ($config['max_quote_depth'] && sizeof($close_tags) >= $config['max_quote_depth'])
					{
						// there are too many nested quotes
						$error_ary['quote_depth'] = sprintf($user->lang['QUOTE_DEPTH_EXCEEDED'], $config['max_quote_depth']);

						$out .= $buffer . $tok;
						$tok = '[]';
						$buffer = '';

						continue;
					}

					array_push($close_tags, '/quote:' . $this->bbcode_uid);

					if (isset($m[1]) && $m[1])
					{
						$username = str_replace(array('&#91;', '&#93;'), array('[', ']'), $m[1]);
						$username = preg_replace('#\[(?!b|i|u|color|url|email|/b|/i|/u|/color|/url|/email)#iU', '&#91;$1', $username);

						$end_tags = array();
						$error = false;

						preg_match_all('#\[((?:/)?(?:[a-z]+))#i', $username, $tags);
						foreach ($tags[1] as $tag)
						{
							if ($tag[0] != '/')
							{
								$end_tags[] = '/' . $tag;
							}
							else
							{
								$end_tag = array_pop($end_tags);
								$error = ($end_tag != $tag) ? true : false;
							}
						}

						if ($error)
						{
							$username = $m[1];
						}

						$out .= 'quote=&quot;' . $username . '&quot;:' . $this->bbcode_uid . ']';
					}
					else
					{
						$out .= 'quote:' . $this->bbcode_uid . ']';
					}

					$tok = '[';
					$buffer = '';
				}
				else if (preg_match('#^quote=&quot;(.*?)#is', $buffer, $m))
				{
					// the buffer holds an invalid opening tag
					$buffer .= ']';
				}
				else
				{
					$out .= $buffer . $tok;
					$tok = '[]';
					$buffer = '';
				}
			}
			else
			{
/**
*				Old quote code working fine, but having errors listed in bug #3572
*
*				$out .= $buffer . $tok;
*				$tok = ($tok == '[') ? ']' : '[]';
*				$buffer = '';
*/

				$out .= $buffer . $tok;

				if ($tok == '[')
				{
					// Search the text for the next tok... if an ending quote comes first, then change tok to []
					$pos1 = stripos($in, '[/quote');
					// If the token ] comes first, we change it to ]
					$pos2 = strpos($in, ']');
					// If the token [ comes first, we change it to [
					$pos3 = strpos($in, '[');

					if ($pos1 !== false && ($pos2 === false || $pos1 < $pos2) && ($pos3 === false || $pos1 < $pos3))
					{
						$tok = '[]';
					}
					else if ($pos3 !== false && ($pos2 === false || $pos3 < $pos2))
					{
						$tok = '[';
					}
					else
					{
						$tok = ']';
					}
				}
				else
				{
					$tok = '[]';
				}
				$buffer = '';
			}
		}
		while ($in);

		if (sizeof($close_tags))
		{
			$out .= '[' . implode('][', $close_tags) . ']';
		}

		foreach ($error_ary as $error_msg)
		{
			$this->warn_msg[] = $error_msg;
		}

		return $out;
	}

	/**
	* Validate email
	*/
	function validate_email($var1, $var2)
	{
		$var1 = str_replace("\r\n", "\n", str_replace('\"', '"', trim($var1)));
		$var2 = str_replace("\r\n", "\n", str_replace('\"', '"', trim($var2)));

		$txt = $var2;
		$email = ($var1) ? $var1 : $var2;

		$validated = true;

		if (!preg_match('/^' . get_preg_expression('email') . '$/i', $email))
		{
			$validated = false;
		}

		if (!$validated)
		{
			return '[email' . (($var1) ? "=$var1" : '') . ']' . $var2 . '[/email]';
		}

		$this->parsed_items['email']++;

		if ($var1)
		{
			$retval = '[email=' . $this->bbcode_specialchars($email) . ':' . $this->bbcode_uid . ']' . $txt . '[/email:' . $this->bbcode_uid . ']';
		}
		else
		{
			$retval = '[email:' . $this->bbcode_uid . ']' . $this->bbcode_specialchars($email) . '[/email:' . $this->bbcode_uid . ']';
		}

		return $retval;
	}

	/**
	* Validate url
	*
	* @param string $var1 optional url parameter for url bbcode: [url(=$var1)]$var2[/url]
	* @param string $var2 url bbcode content: [url(=$var1)]$var2[/url]
	*/
	function validate_url($var1, $var2)
	{
		global $config;

		$var1 = str_replace("\r\n", "\n", str_replace('\"', '"', trim($var1)));
		$var2 = str_replace("\r\n", "\n", str_replace('\"', '"', trim($var2)));

		$url = ($var1) ? $var1 : $var2;

		if ($var1 && !$var2)
		{
			$var2 = $var1;
		}

		if (!$url)
		{
			return '[url' . (($var1) ? '=' . $var1 : '') . ']' . $var2 . '[/url]';
		}

		$valid = false;

		$url = str_replace(' ', '%20', $url);

		// Checking urls
		if (preg_match('#^' . get_preg_expression('url') . '$#i', $url) ||
			preg_match('#^' . get_preg_expression('www_url') . '$#i', $url) ||
			preg_match('#^' . preg_quote(generate_board_url(), '#') . get_preg_expression('relative_url') . '$#i', $url))
		{
			$valid = true;
		}

		if ($valid)
		{
			$this->parsed_items['url']++;

			// if there is no scheme, then add http schema
			if (!preg_match('#^[a-z][a-z\d+\-.]*:/{2}#i', $url))
			{
				$url = 'http://' . $url;
			}

			// Is this a link to somewhere inside this board? If so then remove the session id from the url
			if (strpos($url, generate_board_url()) !== false && strpos($url, 'sid=') !== false)
			{
				$url = preg_replace('/(&amp;|\?)sid=[0-9a-f]{32}&amp;/', '\1', $url);
				$url = preg_replace('/(&amp;|\?)sid=[0-9a-f]{32}$/', '', $url);
				$url = append_sid($url);
			}

			return ($var1) ? '[url=' . $this->bbcode_specialchars($url) . ':' . $this->bbcode_uid . ']' . $var2 . '[/url:' . $this->bbcode_uid . ']' : '[url:' . $this->bbcode_uid . ']' . $this->bbcode_specialchars($url) . '[/url:' . $this->bbcode_uid . ']';
		}

		return '[url' . (($var1) ? '=' . $var1 : '') . ']' . $var2 . '[/url]';
	}

	/**
	* Check if url is pointing to this domain/script_path/php-file
	*
	* @param string $url the url to check
	* @return true if the url is pointing to this domain/script_path/php-file, false if not
	*
	* @access private
	*/
	function path_in_domain($url)
	{
		global $config, $user;

		if ($config['force_server_vars'])
		{
			$check_path = $config['script_path'];
		}
		else
		{
			$check_path = ($user->page['root_script_path'] != '/') ? substr($user->page['root_script_path'], 0, -1) : '/';
		}

		// Is the user trying to link to a php file in this domain and script path?
		if (strpos($url, '.' . PHP_EXT) !== false && strpos($url, $check_path) !== false)
		{
			$server_name = $user->host;

			// Forcing server vars is the only way to specify/override the protocol
			if ($config['force_server_vars'] || !$server_name)
			{
				$server_name = $config['server_name'];
			}

			// Check again in correct order...
			$pos_ext = strpos($url, '.' . PHP_EXT);
			$pos_path = strpos($url, $check_path);
			$pos_domain = strpos($url, $server_name);

			if ($pos_domain !== false && $pos_path >= $pos_domain && $pos_ext >= $pos_path)
			{
				// Ok, actually we allow linking to some files (this may be able to be extended in some way later...)
				if (strpos($url, '/' . $check_path . '/download/file.' . PHP_EXT) !== 0)
				{
					return false;
				}

				return true;
			}
		}

		return false;
	}
}

/**
* Main message parser for posting, pm, etc. takes raw message
* and parses it for attachments, bbcode and smilies
* @package phpBB3
*/
class parse_message extends bbcode_firstpass
{
	var $attachment_data = array();
	var $filename_data = array();

	// Helps ironing out user error
	var $message_status = '';

	var $allow_img_bbcode = true;
	var $allow_flash_bbcode = true;
	var $allow_quote_bbcode = true;
	var $allow_url_bbcode = true;

	var $mode;

	/**
	* Init - give message here or manually
	*/
	function parse_message($message = '')
	{
		// Init BBCode UID
		$this->bbcode_uid = substr(base_convert(unique_id(), 16, 36), 0, BBCODE_UID_LEN);

		if ($message)
		{
			$this->message = $message;
		}
	}

	/**
	* Parse Message
	*/
	function parse($allow_bbcode, $allow_magic_url, $allow_smilies, $allow_img_bbcode = true, $allow_flash_bbcode = true, $allow_quote_bbcode = true, $allow_url_bbcode = true, $update_this_message = true, $mode = 'post')
	{
		global $config, $db, $user;

		$mode = ($mode != 'post') ? 'sig' : 'post';

		$this->mode = $mode;

		$this->allow_img_bbcode = $allow_img_bbcode;
		$this->allow_flash_bbcode = $allow_flash_bbcode;
		$this->allow_quote_bbcode = $allow_quote_bbcode;
		$this->allow_url_bbcode = $allow_url_bbcode;

		// If false, then $this->message won't be altered, the text will be returned instead.
		if (!$update_this_message)
		{
			$tmp_message = $this->message;
			$return_message = &$this->message;
		}

		if ($this->message_status == 'display')
		{
			$this->decode_message();
		}

		// Do some general 'cleanup' first before processing message,
		// e.g. remove excessive newlines(?), smilies(?)
		$match = array('#(script|about|applet|activex|chrome):#i');
		$replace = array("\\1&#058;");
		$this->message = preg_replace($match, $replace, trim($this->message));

		// Message length check. 0 disables this check completely.
		if ($config['max_' . $mode . '_chars'] > 0)
		{
			$msg_len = ($mode == 'post') ? utf8_strlen($this->message) : utf8_strlen(preg_replace('#\[\/?[a-z\*\+\-]+(=[\S]+)?\]#ius', ' ', $this->message));

			if ((!$msg_len && $mode !== 'sig') || $config['max_' . $mode . '_chars'] && $msg_len > $config['max_' . $mode . '_chars'])
			{
				$this->warn_msg[] = (!$msg_len) ? $user->lang['TOO_FEW_CHARS'] : sprintf($user->lang['TOO_MANY_CHARS_' . strtoupper($mode)], $msg_len, $config['max_' . $mode . '_chars']);
				return (!$update_this_message) ? $return_message : $this->warn_msg;
			}
		}

		// Check for "empty" message
		if ($mode !== 'sig' && utf8_clean_string($this->message) === '')
		{
			$this->warn_msg[] = $user->lang['TOO_FEW_CHARS'];
			return (!$update_this_message) ? $return_message : $this->warn_msg;
		}

		// Prepare BBcode (just prepares some tags for better parsing)
		if ($allow_bbcode && strpos($this->message, '[') !== false)
		{
			$this->bbcode_init();
			$disallow = array('img', 'flash', 'quote', 'url');
			foreach ($disallow as $bool)
			{
				if (!${'allow_' . $bool . '_bbcode'})
				{
					$this->bbcodes[$bool]['disabled'] = true;
				}
			}

			$this->prepare_bbcodes();
		}

		// Parse smilies
		if ($allow_smilies)
		{
			$this->smilies($config['max_' . $mode . '_smilies']);
		}

		$num_urls = 0;

		// Parse BBCode
		if ($allow_bbcode && strpos($this->message, '[') !== false)
		{
			$this->parse_bbcode();
			$num_urls += $this->parsed_items['url'];
		}

		// Parse URL's
		if ($allow_magic_url)
		{
			$this->magic_url(generate_board_url());

			if ($config['max_' . $mode . '_urls'])
			{
				$num_urls += preg_match_all('#\<!-- ([lmwe]) --\>.*?\<!-- \1 --\>#', $this->message, $matches);
			}
		}

		// Check number of links
		if ($config['max_' . $mode . '_urls'] && $num_urls > $config['max_' . $mode . '_urls'])
		{
			$this->warn_msg[] = sprintf($user->lang['TOO_MANY_URLS'], $config['max_' . $mode . '_urls']);
			return (!$update_this_message) ? $return_message : $this->warn_msg;
		}

		if (!$update_this_message)
		{
			unset($this->message);
			$this->message = $tmp_message;
			return $return_message;
		}

		$this->message_status = 'parsed';
		return false;
	}

	/**
	* Formatting text for display
	*/
	function format_display($allow_bbcode, $allow_magic_url, $allow_smilies, $update_this_message = true)
	{
		// If false, then the parsed message get returned but internal message not processed.
		if (!$update_this_message)
		{
			$tmp_message = $this->message;
			$return_message = &$this->message;
		}

		if ($this->message_status == 'plain')
		{
			// Force updating message - of course.
			$this->parse($allow_bbcode, $allow_magic_url, $allow_smilies, $this->allow_img_bbcode, $this->allow_flash_bbcode, $this->allow_quote_bbcode, $this->allow_url_bbcode, true);
		}

		// Replace naughty words such as farty pants
		$this->message = censor_text($this->message);

		// Parse BBcode
		if ($allow_bbcode)
		{
			$this->bbcode_cache_init();

			// We are giving those parameters to be able to use the bbcode class on its own
			$this->bbcode_second_pass($this->message, $this->bbcode_uid);
		}

		$this->message = bbcode_nl2br($this->message);
		$this->message = smiley_text($this->message, !$allow_smilies);

		if (!$update_this_message)
		{
			unset($this->message);
			$this->message = $tmp_message;
			return $return_message;
		}

		$this->message_status = 'display';
		return false;
	}

	/**
	* Decode message to be placed back into form box
	*/
	function decode_message($custom_bbcode_uid = '', $update_this_message = true)
	{
		// If false, then the parsed message get returned but internal message not processed.
		if (!$update_this_message)
		{
			$tmp_message = $this->message;
			$return_message = &$this->message;
		}

		($custom_bbcode_uid) ? decode_message($this->message, $custom_bbcode_uid) : decode_message($this->message, $this->bbcode_uid);

		if (!$update_this_message)
		{
			unset($this->message);
			$this->message = $tmp_message;
			return $return_message;
		}

		$this->message_status = 'plain';
		return false;
	}

	/**
	* Replace magic urls of form http://xxx.xxx., www.xxx. and xxx@xxx.xxx.
	* Cuts down displayed size of link if over 50 chars, turns absolute links
	* into relative versions when the server/script path matches the link
	*/
	function magic_url($server_url)
	{
		// We use the global make_clickable function
		$this->message = make_clickable($this->message, $server_url);
	}

	/**
	* Parse Smilies
	*/
	function smilies($max_smilies = 0)
	{
		global $db, $user;
		static $match;
		static $replace;

		// See if the static arrays have already been filled on an earlier invocation
		if (!is_array($match))
		{
			$match = $replace = array();

			// NOTE: obtain_* function? chaching the table contents?

			// For now setting the ttl to 10 minutes
			$sql = 'SELECT *
				FROM ' . SMILIES_TABLE . '
				ORDER BY ' . $db->sql_function('length_varchar', 'code') . ' DESC';
			$result = $db->sql_query($sql, 600);

			while ($row = $db->sql_fetchrow($result))
			{
				if (empty($row['code']))
				{
					continue;
				}

				// (assertion)
				$match[] = '(?<=^|[\n .])' . preg_quote($row['code'], '#') . '(?![^<>]*>)';
				$replace[] = '<!-- s' . $row['code'] . ' --><img src="{SMILIES_PATH}/' . $row['smiley_url'] . '" alt="' . $row['code'] . '" title="' . $row['emotion'] . '" /><!-- s' . $row['code'] . ' -->';
			}
			$db->sql_freeresult($result);
		}

		if (sizeof($match))
		{
			if ($max_smilies)
			{
				$num_matches = preg_match_all('#' . implode('|', $match) . '#', $this->message, $matches);
				unset($matches);

				if ($num_matches !== false && $num_matches > $max_smilies)
				{
					$this->warn_msg[] = sprintf($user->lang['TOO_MANY_SMILIES'], $max_smilies);
					return;
				}
			}

			// Make sure the delimiter # is added in front and at the end of every element within $match
			$this->message = trim(preg_replace(explode(chr(0), '#' . implode('#' . chr(0) . '#', $match) . '#'), $replace, $this->message));
		}
	}

	/**
	* Parse Attachments
	*/
	function parse_attachments($form_name, $mode, $forum_id, $submit, $preview, $refresh, $is_message = false)
	{
		global $config, $auth, $user, $db;

		$error = array();

		$num_attachments = sizeof($this->attachment_data);
		$this->filename_data['filecomment'] = utf8_normalize_nfc(request_var('filecomment', '', true));
		$upload_file = (isset($_FILES[$form_name]) && $_FILES[$form_name]['name'] != 'none' && trim($_FILES[$form_name]['name'])) ? true : false;

		$add_file		= (isset($_POST['add_file'])) ? true : false;
		$delete_file	= (isset($_POST['delete_file'])) ? true : false;

		// First of all adjust comments if changed
		$actual_comment_list = utf8_normalize_nfc(request_var('comment_list', array(''), true));

		foreach ($actual_comment_list as $comment_key => $comment)
		{
			if (!isset($this->attachment_data[$comment_key]))
			{
				continue;
			}

			if ($this->attachment_data[$comment_key]['attach_comment'] != $actual_comment_list[$comment_key])
			{
				$this->attachment_data[$comment_key]['attach_comment'] = $actual_comment_list[$comment_key];
			}
		}

		$cfg = array();
		$cfg['max_attachments'] = ($is_message) ? $config['max_attachments_pm'] : $config['max_attachments'];
		$forum_id = ($is_message) ? 0 : $forum_id;

		if ($submit && in_array($mode, array('post', 'reply', 'quote', 'edit')) && $upload_file)
		{
			if ($num_attachments < $cfg['max_attachments'] || $auth->acl_get('a_') || $auth->acl_get('m_', $forum_id))
			{
				$filedata = upload_attachment($form_name, $forum_id, false, '', $is_message);
				$error = $filedata['error'];

				if ($filedata['post_attach'] && !sizeof($error))
				{
					$sql_ary = array(
						'physical_filename'	=> $filedata['physical_filename'],
						'attach_comment'	=> $this->filename_data['filecomment'],
						'real_filename'		=> $filedata['real_filename'],
						'extension'			=> $filedata['extension'],
						'mimetype'			=> $filedata['mimetype'],
						'filesize'			=> $filedata['filesize'],
						'filetime'			=> $filedata['filetime'],
						'thumbnail'			=> $filedata['thumbnail'],
						'is_orphan'			=> 1,
						'in_message'		=> ($is_message) ? 1 : 0,
						'poster_id'			=> $user->data['user_id'],
					);

					$db->sql_query('INSERT INTO ' . ATTACHMENTS_TABLE . ' ' . $db->sql_build_array('INSERT', $sql_ary));

					$new_entry = array(
						'attach_id'		=> $db->sql_nextid(),
						'is_orphan'		=> 1,
						'real_filename'	=> $filedata['real_filename'],
						'attach_comment'=> $this->filename_data['filecomment'],
					);

					$this->attachment_data = array_merge(array(0 => $new_entry), $this->attachment_data);
					$this->message = preg_replace('#\[attachment=([0-9]+)\](.*?)\[\/attachment\]#e', "'[attachment='.(\\1 + 1).']\\2[/attachment]'", $this->message);

					$this->filename_data['filecomment'] = '';

					// This Variable is set to false here, because Attachments are entered into the
					// Database in two modes, one if the id_list is 0 and the second one if post_attach is true
					// Since post_attach is automatically switched to true if an Attachment got added to the filesystem,
					// but we are assigning an id of 0 here, we have to reset the post_attach variable to false.
					//
					// This is very relevant, because it could happen that the post got not submitted, but we do not
					// know this circumstance here. We could be at the posting page or we could be redirected to the entered
					// post. :)
					$filedata['post_attach'] = false;
				}
			}
			else
			{
				$error[] = sprintf($user->lang['TOO_MANY_ATTACHMENTS'], $cfg['max_attachments']);
			}
		}

		if ($preview || $refresh || sizeof($error))
		{
			// Perform actions on temporary attachments
			if ($delete_file)
			{
				include_once(PHPBB_ROOT_PATH . 'includes/functions_admin.' . PHP_EXT);

				$index = array_keys(request_var('delete_file', array(0 => 0)));
				$index = (!empty($index)) ? $index[0] : false;

				if ($index !== false && !empty($this->attachment_data[$index]))
				{
					// delete selected attachment
					if ($this->attachment_data[$index]['is_orphan'])
					{
						$sql = 'SELECT attach_id, physical_filename, thumbnail
							FROM ' . ATTACHMENTS_TABLE . '
							WHERE attach_id = ' . (int) $this->attachment_data[$index]['attach_id'] . '
								AND is_orphan = 1
								AND poster_id = ' . $user->data['user_id'];
						$result = $db->sql_query($sql);
						$row = $db->sql_fetchrow($result);
						$db->sql_freeresult($result);

						if ($row)
						{
							phpbb_unlink($row['physical_filename'], 'file');

							if ($row['thumbnail'])
							{
								phpbb_unlink($row['physical_filename'], 'thumbnail');
							}

							$db->sql_query('DELETE FROM ' . ATTACHMENTS_TABLE . ' WHERE attach_id = ' . (int) $this->attachment_data[$index]['attach_id']);
						}
					}
					else
					{
						delete_attachments('attach', array(intval($this->attachment_data[$index]['attach_id'])));
					}

					unset($this->attachment_data[$index]);
					$this->message = preg_replace('#\[attachment=([0-9]+)\](.*?)\[\/attachment\]#e', "(\\1 == \$index) ? '' : ((\\1 > \$index) ? '[attachment=' . (\\1 - 1) . ']\\2[/attachment]' : '\\0')", $this->message);

					// Reindex Array
					$this->attachment_data = array_values($this->attachment_data);
				}
			}
			else if (($add_file || $preview) && $upload_file)
			{
				if ($num_attachments < $cfg['max_attachments'] || $auth->acl_gets('m_', 'a_', $forum_id))
				{
					$filedata = upload_attachment($form_name, $forum_id, false, '', $is_message);
					$error = array_merge($error, $filedata['error']);

					if (!sizeof($error))
					{
						$sql_ary = array(
							'physical_filename'	=> $filedata['physical_filename'],
							'attach_comment'	=> $this->filename_data['filecomment'],
							'real_filename'		=> $filedata['real_filename'],
							'extension'			=> $filedata['extension'],
							'mimetype'			=> $filedata['mimetype'],
							'filesize'			=> $filedata['filesize'],
							'filetime'			=> $filedata['filetime'],
							'thumbnail'			=> $filedata['thumbnail'],
							'is_orphan'			=> 1,
							'in_message'		=> ($is_message) ? 1 : 0,
							'poster_id'			=> $user->data['user_id'],
						);

						$db->sql_query('INSERT INTO ' . ATTACHMENTS_TABLE . ' ' . $db->sql_build_array('INSERT', $sql_ary));

						$new_entry = array(
							'attach_id'		=> $db->sql_nextid(),
							'is_orphan'		=> 1,
							'real_filename'	=> $filedata['real_filename'],
							'attach_comment'=> $this->filename_data['filecomment'],
						);

						$this->attachment_data = array_merge(array(0 => $new_entry), $this->attachment_data);
						$this->message = preg_replace('#\[attachment=([0-9]+)\](.*?)\[\/attachment\]#e', "'[attachment='.(\\1 + 1).']\\2[/attachment]'", $this->message);
						$this->filename_data['filecomment'] = '';
					}
				}
				else
				{
					$error[] = sprintf($user->lang['TOO_MANY_ATTACHMENTS'], $cfg['max_attachments']);
				}
			}
		}

		foreach ($error as $error_msg)
		{
			$this->warn_msg[] = $error_msg;
		}
	}

	/**
	* Get Attachment Data
	*/
	function get_submitted_attachment_data($check_user_id = false)
	{
		global $user, $db, $config;

		$this->filename_data['filecomment'] = utf8_normalize_nfc(request_var('filecomment', '', true));
		$attachment_data = (isset($_POST['attachment_data'])) ? $_POST['attachment_data'] : array();
		$this->attachment_data = array();

		$check_user_id = ($check_user_id === false) ? $user->data['user_id'] : $check_user_id;

		if (!sizeof($attachment_data))
		{
			return;
		}

		$not_orphan = $orphan = array();

		foreach ($attachment_data as $pos => $var_ary)
		{
			if ($var_ary['is_orphan'])
			{
				$orphan[(int) $var_ary['attach_id']] = $pos;
			}
			else
			{
				$not_orphan[(int) $var_ary['attach_id']] = $pos;
			}
		}

		// Regenerate already posted attachments
		if (sizeof($not_orphan))
		{
			// Get the attachment data, based on the poster id...
			$sql = 'SELECT attach_id, is_orphan, real_filename, attach_comment
				FROM ' . ATTACHMENTS_TABLE . '
				WHERE ' . $db->sql_in_set('attach_id', array_keys($not_orphan)) . '
					AND poster_id = ' . $check_user_id;
			$result = $db->sql_query($sql);

			while ($row = $db->sql_fetchrow($result))
			{
				$pos = $not_orphan[$row['attach_id']];
				$this->attachment_data[$pos] = $row;
				set_var($this->attachment_data[$pos]['attach_comment'], $_POST['attachment_data'][$pos]['attach_comment'], 'string', true);

				unset($not_orphan[$row['attach_id']]);
			}
			$db->sql_freeresult($result);
		}

		if (sizeof($not_orphan))
		{
			trigger_error('NO_ACCESS_ATTACHMENT', E_USER_ERROR);
		}

		// Regenerate newly uploaded attachments
		if (sizeof($orphan))
		{
			$sql = 'SELECT attach_id, is_orphan, real_filename, attach_comment
				FROM ' . ATTACHMENTS_TABLE . '
				WHERE ' . $db->sql_in_set('attach_id', array_keys($orphan)) . '
					AND poster_id = ' . $user->data['user_id'] . '
					AND is_orphan = 1';
			$result = $db->sql_query($sql);

			while ($row = $db->sql_fetchrow($result))
			{
				$pos = $orphan[$row['attach_id']];
				$this->attachment_data[$pos] = $row;
				set_var($this->attachment_data[$pos]['attach_comment'], $_POST['attachment_data'][$pos]['attach_comment'], 'string', true);

				unset($orphan[$row['attach_id']]);
			}
			$db->sql_freeresult($result);
		}

		if (sizeof($orphan))
		{
			trigger_error('NO_ACCESS_ATTACHMENT', E_USER_ERROR);
		}

		ksort($this->attachment_data);
	}

	/**
	* Parse Poll
	*/
	function parse_poll(&$poll)
	{
		global $auth, $user, $config;

		$poll_max_options = $poll['poll_max_options'];

		// Parse Poll Option text ;)
		$tmp_message = $this->message;
		$this->message = $poll['poll_option_text'];
		$bbcode_bitfield = $this->bbcode_bitfield;

		$poll['poll_option_text'] = $this->parse($poll['enable_bbcode'], ($config['allow_post_links']) ? $poll['enable_urls'] : false, $poll['enable_smilies'], $poll['img_status'], false, false, $config['allow_post_links'], false);

		$bbcode_bitfield = base64_encode(base64_decode($bbcode_bitfield) | base64_decode($this->bbcode_bitfield));
		$this->message = $tmp_message;

		// Parse Poll Title
		$tmp_message = $this->message;
		$this->message = $poll['poll_title'];
		$this->bbcode_bitfield = $bbcode_bitfield;

		$poll['poll_options'] = explode("\n", trim($poll['poll_option_text']));
		$poll['poll_options_size'] = sizeof($poll['poll_options']);

		if (!$poll['poll_title'] && $poll['poll_options_size'])
		{
			$this->warn_msg[] = $user->lang['NO_POLL_TITLE'];
		}
		else
		{
			if (utf8_strlen(preg_replace('#\[\/?[a-z\*\+\-]+(=[\S]+)?\]#ius', ' ', $this->message)) > 100)
			{
				$this->warn_msg[] = $user->lang['POLL_TITLE_TOO_LONG'];
			}
			$poll['poll_title'] = $this->parse($poll['enable_bbcode'], ($config['allow_post_links']) ? $poll['enable_urls'] : false, $poll['enable_smilies'], $poll['img_status'], false, false, $config['allow_post_links'], false);
			if (strlen($poll['poll_title']) > 255)
			{
				$this->warn_msg[] = $user->lang['POLL_TITLE_COMP_TOO_LONG'];
			}
		}

		$this->bbcode_bitfield = base64_encode(base64_decode($bbcode_bitfield) | base64_decode($this->bbcode_bitfield));
		$this->message = $tmp_message;
		unset($tmp_message);

		if (sizeof($poll['poll_options']) == 1)
		{
			$this->warn_msg[] = $user->lang['TOO_FEW_POLL_OPTIONS'];
		}
		else if ($poll['poll_options_size'] > (int) $config['max_poll_options'])
		{
			$this->warn_msg[] = $user->lang['TOO_MANY_POLL_OPTIONS'];
		}
		else if ($poll_max_options > $poll['poll_options_size'])
		{
			$this->warn_msg[] = $user->lang['TOO_MANY_USER_OPTIONS'];
		}

		$poll['poll_max_options'] = ($poll['poll_max_options'] < 1) ? 1 : (($poll['poll_max_options'] > $config['max_poll_options']) ? $config['max_poll_options'] : $poll['poll_max_options']);
	}
}

?>