* @license GNU General Public License, version 2 (GPL-2.0) * * For full copyright and license information, please see * the docs/CREDITS.txt file. * */ namespace phpbb\search; /** * @ignore */ define('SEARCH_RESULT_NOT_IN_CACHE', 0); define('SEARCH_RESULT_IN_CACHE', 1); define('SEARCH_RESULT_INCOMPLETE', 2); /** * optional base class for search plugins providing simple caching based on ACM * and functions to retrieve ignore_words and synonyms */ class base { var $ignore_words = array(); var $match_synonym = array(); var $replace_synonym = array(); function search_backend(&$error) { // This class cannot be used as a search plugin $error = true; } /** * Retrieves cached search results * * @param string $search_key an md5 string generated from all the passed search options to identify the results * @param int &$result_count will contain the number of all results for the search (not only for the current page) * @param array &$id_ary is filled with the ids belonging to the requested page that are stored in the cache * @param int &$start indicates the first index of the page * @param int $per_page number of ids each page is supposed to contain * @param string $sort_dir is either a or d representing ASC and DESC * * @return int SEARCH_RESULT_NOT_IN_CACHE or SEARCH_RESULT_IN_CACHE or SEARCH_RESULT_INCOMPLETE */ function obtain_ids($search_key, &$result_count, &$id_ary, &$start, $per_page, $sort_dir) { global $cache; if (!($stored_ids = $cache->get('_search_results_' . $search_key))) { // no search results cached for this search_key return SEARCH_RESULT_NOT_IN_CACHE; } else { $result_count = $stored_ids[-1]; $reverse_ids = ($stored_ids[-2] != $sort_dir) ? true : false; $complete = true; // Change start parameter in case out of bounds if ($result_count) { if ($start < 0) { $start = 0; } else if ($start >= $result_count) { $start = floor(($result_count - 1) / $per_page) * $per_page; } } // change the start to the actual end of the current request if the sort direction differs // from the dirction in the cache and reverse the ids later if ($reverse_ids) { $start = $result_count - $start - $per_page; // the user requested a page past the last index if ($start < 0) { return SEARCH_RESULT_NOT_IN_CACHE; } } for ($i = $start, $n = $start + $per_page; ($i < $n) && ($i < $result_count); $i++) { if (!isset($stored_ids[$i])) { $complete = false; } else { $id_ary[] = $stored_ids[$i]; } } unset($stored_ids); if ($reverse_ids) { $id_ary = array_reverse($id_ary); } if (!$complete) { return SEARCH_RESULT_INCOMPLETE; } return SEARCH_RESULT_IN_CACHE; } } /** * Caches post/topic ids * * @param string $search_key an md5 string generated from all the passed search options to identify the results * @param string $keywords contains the keywords as entered by the user * @param array $author_ary an array of author ids, if the author should be ignored during the search the array is empty * @param int $result_count contains the number of all results for the search (not only for the current page) * @param array &$id_ary contains a list of post or topic ids that shall be cached, the first element * must have the absolute index $start in the result set. * @param int $start indicates the first index of the page * @param string $sort_dir is either a or d representing ASC and DESC * * @return null */ function save_ids($search_key, $keywords, $author_ary, $result_count, &$id_ary, $start, $sort_dir) { global $cache, $config, $db, $user; $length = min(count($id_ary), $config['search_block_size']); // nothing to cache so exit if (!$length) { return; } $store_ids = array_slice($id_ary, 0, $length); // create a new resultset if there is none for this search_key yet // or add the ids to the existing resultset if (!($store = $cache->get('_search_results_' . $search_key))) { // add the current keywords to the recent searches in the cache which are listed on the search page if (!empty($keywords) || count($author_ary)) { $sql = 'SELECT search_time FROM ' . SEARCH_RESULTS_TABLE . ' WHERE search_key = \'' . $db->sql_escape($search_key) . '\''; $result = $db->sql_query($sql); if (!$db->sql_fetchrow($result)) { $sql_ary = array( 'search_key' => $search_key, 'search_time' => time(), 'search_keywords' => $keywords, 'search_authors' => ' ' . implode(' ', $author_ary) . ' ' ); $sql = 'INSERT INTO ' . SEARCH_RESULTS_TABLE . ' ' . $db->sql_build_array('INSERT', $sql_ary); $db->sql_query($sql); } $db->sql_freeresult($result); } $sql = 'UPDATE ' . USERS_TABLE . ' SET user_last_search = ' . time() . ' WHERE user_id = ' . $user->data['user_id']; $db->sql_query($sql); $store = array(-1 => $result_count, -2 => $sort_dir); $id_range = range($start, $start + $length - 1); } else { // we use one set of results for both sort directions so we have to calculate the indizes // for the reversed array and we also have to reverse the ids themselves if ($store[-2] != $sort_dir) { $store_ids = array_reverse($store_ids); $id_range = range($store[-1] - $start - $length, $store[-1] - $start - 1); } else { $id_range = range($start, $start + $length - 1); } } $store_ids = array_combine($id_range, $store_ids); // append the ids if (is_array($store_ids)) { $store += $store_ids; // if the cache is too big if (count($store) - 2 > 20 * $config['search_block_size']) { // remove everything in front of two blocks in front of the current start index for ($i = 0, $n = $id_range[0] - 2 * $config['search_block_size']; $i < $n; $i++) { if (isset($store[$i])) { unset($store[$i]); } } // remove everything after two blocks after the current stop index end($id_range); for ($i = $store[-1] - 1, $n = current($id_range) + 2 * $config['search_block_size']; $i > $n; $i--) { if (isset($store[$i])) { unset($store[$i]); } } } $cache->put('_search_results_' . $search_key, $store, $config['search_store_results']); $sql = 'UPDATE ' . SEARCH_RESULTS_TABLE . ' SET search_time = ' . time() . ' WHERE search_key = \'' . $db->sql_escape($search_key) . '\''; $db->sql_query($sql); } unset($store); unset($store_ids); unset($id_range); } /** * Removes old entries from the search results table and removes searches with keywords that contain a word in $words. */ function destroy_cache($words, $authors = false) { global $db, $cache, $config; // clear all searches that searched for the specified words if (count($words)) { $sql_where = ''; foreach ($words as $word) { $sql_where .= " OR search_keywords " . $db->sql_like_expression($db->get_any_char() . $word . $db->get_any_char()); } $sql = 'SELECT search_key FROM ' . SEARCH_RESULTS_TABLE . " WHERE search_keywords LIKE '%*%' $sql_where"; $result = $db->sql_query($sql); while ($row = $db->sql_fetchrow($result)) { $cache->destroy('_search_results_' . $row['search_key']); } $db->sql_freeresult($result); } // clear all searches that searched for the specified authors if (is_array($authors) && count($authors)) { $sql_where = ''; foreach ($authors as $author) { $sql_where .= (($sql_where) ? ' OR ' : '') . 'search_authors ' . $db->sql_like_expression($db->get_any_char() . ' ' . (int) $author . ' ' . $db->get_any_char()); } $sql = 'SELECT search_key FROM ' . SEARCH_RESULTS_TABLE . " WHERE $sql_where"; $result = $db->sql_query($sql); while ($row = $db->sql_fetchrow($result)) { $cache->destroy('_search_results_' . $row['search_key']); } $db->sql_freeresult($result); } $sql = 'DELETE FROM ' . SEARCH_RESULTS_TABLE . ' WHERE search_time < ' . (time() - (int) $config['search_store_results']); $db->sql_query($sql); } } oAz* h,ag(4pM+?sQXhicU35J;9[C"^j0Щ Gaw/lu`rBt6^.*07Ᏹڤ8翍Q˺s{洞 ;Rw"%|ﶙj0n75`|*gt/ùTpGm'3$?w$_LXVV3:%tFr'c3~tglN{7v?7@ݪ cTK=P,/]\ qx(YI<*ӋŠ2w 7l.;69oc=کHu2-s4 ~f/+*ZÖzhgVa < ])K7g %r\MXoJ10_SJ+lvDBR,Uwt._`ͼmd20D ؐgۋ0/!MNE \*zP$ b0#:ئg֔lpb?b;@%tJM7pø>(f H@28bG6Ex? +g<^/(ޚEc^?x7xVF똌 =lM,Ȥ2bYOTB&W6RTL) Q~(z088n)[d ľИKI l٬(+:30>6BRXU`* gL=TqTNԔ1o@];ʼJ5EG~V=#qR-edA%WXBkI_'bD)AiG+U46r-:.}jPɾ (7_I~N*Ev]Xxhwўc9̚K>rp 0qS)(9xȊI{@[sL_)#AKo@/֌t@%fv6o6| 9pTFT4S VW  k!Mi0 K^F!˚'4lwmPp*ƅ(Mi:`V%a/><48r=>B 1.g sT@rM3j2EKamUty)i|cxeHrcLj=$Ӑ1 (o*e q_oNV _`j,>';[m*@\T^;l!ѠW[ZbÚ^TitOGb:Λ ; 6YuY@B t+ͱy+ EMqH6ِ4ƪw<:K2N|ve8\ػopEFApaBLpm1 ;|KM K]j\RǗ?'BIVܽP_B m|}3;BuSbҗw5n[lvJ<OIgz.Dt 3 m5xc`P'3픊sS2㵪hrxPo XE7eHo JZޘdjr n+2E*GQǧ(kMeǭ6wy3\8%k9u{~ RT cHb{6ULXY4{,n҅†F?x$ rg`dΏ]K" zIAG#N{2/d"uqf0ȝ3q(}F!Q_5[)k82y!W̜y,rsj nA.2-s!\DC-H?I96X3ZcE:k{?soM/"ô\X:7lWEe[}6w7`і@b{zջ]Dȑ4;XCao.Li &USt;(:"ETxQn &][1?:zK;k NۊHQR2j`" Ѳ^bpj\>4[Ȍ,T:?˛'5]mm{$O f[ 7Z̴q:uJ᧷̨ 7)pusV\v4 <C?Oef<19M5v}.JTA8 d*%U vLo vڬ_HyoTtT-`?IJӜEÚ?U=ǣS$Eۏj_sd l٢P @s O5kD;B n{|g)GF7Ҭ-k\^0Mpd[?U$d&xh!(2Jh=QQQOW?s3Z0n^'m:\8&ЎACF,*M-jRJ5OK65$5Pg>tLPͩ A108;G?C\%w?JLS#KG69)6tWW9.d@ҺƯBSII H8;NT/v N)gΟ#niHlYy| ۋҫ^}ź6rG&D!(f9Z(-zh~ۣsrzqtr/"h^u]FP_qjew).6u9CA4mz*?({8*E>5[@~"N4l#UL): 3I_8E-r,F!ԕTYMk)iE^n;ƴ|NMi3ު4?ىCdANّ݉6G˼o$m5F,qID|n@Ț  7[֗qlU2"U~0?pI-Ǐ*@]ZgPM8]G-z˱Ǿ7qbI* rQ*6d5yCH> H,4kYHEݑ69z%!ʪ {O!yA);O"yXWx! !', {E{*Ndv:(Q_A[6Q%y쎑XndEILU/" GkD!gnb"LҪ59K= ]kr/<-6MFD$k//t9_U4⿍0-M̴\UA1]H,ؕ)*c1`s655-0ZIf2ND׊^ 3Vty ?fWdg; ٚ; 8md[,;2@ٟ!֚q+K0f4>Dq"R~vcf"hh)wY:jT#qHf/@^A@)\'yӭeU+Ehj ct!M+tY!Vޛt{Og{kҹZd vYсOŸܐ IWԯ3',fH;,Nݝ.`St=%A9Ko".9Ziŋf`~p:0pp|'7Ic׉JΏJMt!D"d"if_!rK58)BA`%3@YFNiO`ã:WhHG7k|Z7V&lݮ\nN%\-`DQ.Q UD6t8ѿ-d;벭$b; Β}M4RZPB䵶8CFR+XDŅt)M8: N>X!ծ:3v~6b' 0 KK&1oAbhW^4CD W"‘zEÕ|(_K~L_4S~uV*a>v~PU O L y>JЯIHo OJR02vTsG72f Xz0FǠL2MşAu=Ӫ(Z$i ѝJw~-/AEoxE_29&C5F|Cvb+?-^p}GJaѡH.p'V֬A;~pllOrTʥ9\=S pTcDs7~-I(e Xhhg Vϸtȼ<7 ,Hb#J&$`|ESCFrW%:Sj7X.+|q б;SovS緬忋mg-R` (_QqMcs;G#tCyw$,z-\5\{ѦE?Y<]ޜm%m>)2񚷂:W%A9-C>xg4a =Y)28^ P~CR|M!e `ԌqԅUc D駯yI i=4{ 'n\&z*BLĜM:LO4:]<=Ԅʫ(QxFlQt./F`bqZQ!o/at.KrY/=U$uvft 9? ߴiWo|,`E. |8 ) uaշ(C? Z|[jndzZ:_{+u&|g! y,ڻBr GǫQ *Z2KΗ.Vezu' }[=`>D>d2ܤN̆؜0Pp0@YxJgh]f~GA\1dSQr`R+Vf]KpQ@+C33VҸVˉ*NLOpa'WաR?_&Vtud:}_(;$TM<LTL> 1ɏLTW_|=$ΆOM顔UŦHTr4-tpjh.Ww0Rb]z5yע\%Os JV~DZ~4P6ծ?5)i=zp i?ћ`A5u/jhOY+dZIOg f"5QPݠ͌I$~JϣN*s{8vPe!Q3RnTrbrbxNjZgHdN(Q ̻<8{ƈjN(zjV~R<.xb7xAx}i[<$&Ϭ&ǚ鄌Glm#!h9 K|k '5a2r/U7!znsQ?Wݣ3Ĝ>qw$8zel9ҩE,*6lBS+٭Y.LsY\]Too1 Bq*+Ǭ0jKyv|`̠ܝfCQ୙a-`dz⌮t)SL Gg6|g^|sAA0a['?H7VJ#t#}sVƬɢ+L*o+vE}s[M-u2D0Gs4R( xj ̈BdAJSd#]Ky9 sًνѶ{5պ.Ym$^IUq:sj,&P0 5l?|!LYZ6E*2+;,K>]ŏnw;X:0dqI$ ZWvgBfF/۬2u޺R~?u6T5q෍c[%LEbf\ οC \@!).WP0e}m \mzUH1SgVx+qDqWmZ]4oH`rg z {rrRB(TyFՖB<,xd*kYk/Mb(quKA q}%o bI"C7uѿt]?_ߋ7irn&IEv\/T~~Ƣro& Mg{ K $dRYԦ~_T V3lȭ>;"?OyV&Z:HB;S_^û:}Wrma|] cz( ",JAY .4gy4h!i^ǔxƘU)L ;j9%ہ|O]P!$'_#lEi7YF-q$@ee m.di't I{xE^(Qh;T]D(6 p:|ԶB  6xl S!-w .s ri=p0ʧ֞:5t4Fw $_l)#a,S Ǘ˰3/-6W.4DGFVrg,,I>uY3x=2GnyN;wxLsvK-"ަRdJM޲Tx%%Bc {qWT&1X 8V-kۘ!leQC d$Auwi7JP\"((\QCj<- 0u1j5))5AHyTHQԧKGVfHO!ˣr#H p5F~ ڗ3 IOgC Ϲ CbeScЁE;A 20XXQ:F-vjUc JrurJҥ ^!<Я[#X3eD2)Qyz}h-ii$?lz,b{mBQAFd3Erc8Q/aZY9 אVJ4-0d'$. ߝ4^XkKv'/l:`MzCie3?ŕV fJ[k?&n5nӔ~!#3V:]v503ʩc}/݅V(ϦdOKۇ :!LUht fPŊj;ɩ0E~{FB o?jl=`Rkzhᾤj8VJ85U1SP^tػ@P)JŔS4IKQHo j`Ǥnz'88ԩ[=Z{IfGr#=JOWt6l$aK-~#2@ye (OK[d9xx([)"H=?MF+meVᘐ$*3km6MxsyLgm(,>mkMcF朤>ĦyXQTT:c|Պf@nW; 7=v6$_#ix$ͳJV֍׵ѮFk}+l6o~AK=mp<ӣ`1ʐSX>.6bqY0vѳ(E4ĆO}o{E;w?X ;_7%X2 3!_5Zzx~/(_g>EC cpiJ4ńr|eUNa #SxDE9%/_pG~8AU  x-ө,VTV8iuۮmI)?`t3ITc*&l=hfv'X5 -jq\l@Z5yktƽ\M ˊ୆#BFVKKfߊ轭auJ/۹}OP$/ &v  ȭ'`X{WlK,artP:]íRdf!q#I}py\O[8U&Ḧ3dvN7AH"Д^4@no *ӝq Nj֌cTEo?t^ qd& !q]g/n0Z3㠷*5&sMV4"Du E} -_ʎ: [+kbYbz8K|w!Ec=)5 @WU S6CnJK:ƔCb8.Z%yʥfsĚ{KAl JOtQOfaM ,(XY IH{Ǜ o%b~C+ɐ8|[ 89VΐH_MBd&|QۢQ -:e)wxSOl,\DSD`ǐժyVȐn'VCBFs0OoH{(Tm+`ah Xc2.*jz:h.c([̉-? J {ʟ_KYK'R1wُY9le Wo҉=Lm`"]JWfea߽"d5g|.vBF4KN"C^sLU=j&g2*Su+7셂Pe~;sE2&{ČzPåIs[T83Kum%lGʐ2&Fe!jh5D- ZTQ`rd>S u3@$LVj);1IxKD5t5$AB[]{nqӱg5y;061^$<drmgrHza"MՅ'f?4Xj3*}P׷Ĭo!,2MU7d14T&PG]$_1 >QVMR7#CU.~e9I:P7mlboߑ_ s7ߞ#)n/+6W mQqQ!V7*XM!IaD"Գ{SJlY~_{\LM[Ԓ{Pm?HaAj#&L> JKOAR&;bA搶$D9e= DEC wWFeH<:j+H]5?zD;g/V1hW;f[Y yR=4qHkx@%n~eeka\O{-Cvw ICXlRqX~xsI- IAuM nJ%V\״d;?Ox,lgyC6}V OyN*ŬnHKⴖϾODy[$iӍw9*ėld=x%$j] Q`thY\W>by|E`R$ZAHg>;4N3z(fsS}e-ߦ `Z[66Q6k&𕦣0\zo* d&Z<: e{7y柼օ%yb*!/nD(I=9Wb V=sXNNd)WH߱h Q췮ku#rz4ƸDFK#!@љMgܦUz-oWUtVUKxt9S217dk_]$8jojK@)!ݫomDi? pYtgS|ënj$%S2@F2 Zy!Xv0Jח8hZ 2P'>,f&Ŀ:zO?3kzEM2k?T^8O%Jtd2F4ѪkV|4o ;)>k4 {օ6piO4 ~Dd %Xb%j*ԟvySrLby+D-(d_%4U(]HwXbQ7eqM{ʮ֝jnkµHm rƿvz„b J@ָ F^(!}13!ry(j ^Q2#p)$ۏ//mz})Ʃ-pz.?Cyxp>ÐH0% \F`WjCa%Kze6VzL cp),ڎj!ڻܱm Iہ:3JJ37&pLU$E'׌m{t$zmG:|/[G3{&Qڶ9~r+]l?(0*rj'lS6/Z~3gLLm|.gxg |wOטY.*W"Llj֧*Z0{(hޡ I/*S AlRtPa?C@9B@wx n?3Ec:c2ƉFwqffTs"6x_YkT2^CL ]Y3l2>d5i3g;_MQy4B >f4ԇ7ХY#"eUuSڎ^U+RެlD[ ]:kؔbbfo;i~f$O;āe>9䎁_>2^NPg\z'v[>oL:8'@tsא6FuϬՎ/Аq#H Wcz]Q5 w衸;bz>eEJrj !kdx.WCఉ|"nk!v _:;<؊QVdq11jtZ2veS70}D;lc: [KB>_Τ3(Ɣ[VCjtdp،  yaI zkmX7mʩcksQv!z5AE|;B{U;FȒ>e0)opAj KȋO983ݲY2rN _i-Xl 8SoY2' "8.,5;CuuEI>T,W5a^SNWͯ>5A[~;-=9@ Dl1'n-{ϨS??P% O67 N6xOBe㩊.i(\5 Wy>#EPbb=@eh7pI:Jw^갢\$ީ9,S@NkCU\z4jf|D h+D08)ciK46 {&jJ|IojIW~, +y&\gB|:CZ4Ku %@ /\,TF/l64_s῕BYyDO<\zJ2@Ff0\3$đ·O]~|w .+E' Z sW3]Xe5d*i\-uXn;0Hʖm&Y5YLۇh̟t !"„8)jR|J.E3ҁWY\=5(]w-N] o,Va񛫨۵ !US 0ߐC\X/\Y[jj~ F@|}mZtHL1{ߘ#.m\"իC*ymy#qHIx@V:TtMj={M6̍1sL Otb |5N!JS3{ "oD"Ժz ~}4Z^ w?ͼw,87_>_:myd_ S mHgfM@kKIhb 'E̔C>9A:Ua7lqX4ȇALOjZ1TX4D&h6@L1L-/MYHaPTNGɕs6 Bt*6UOs^!/ w.oRuN86^~5!$Z$C )(-$`Y*IF O~Rb|,@00#Qſc##{[A/p{6hbռ Dw_1'مwWZ>( |? 7Qe^pFƳCRcdy ,G@xMkdN0bvQdž =ϺzGLPTM}bjآhԏ eoS&a`,IU''ҋӴZVyI}06 s%o:H|.ȏ"Ś9vXXc|{ldXQ'"_8Z(4)scBF:C%d徰 x#),L@D< YwK-P9ueOڋ-bU%Yt*}nz5[)? q*҂9iN#qJ>#w]Yܷqm_?7|T X5b/JCY`~dK0\CZq{yܕE}d+7vZF\ٔyBppxfU%{{mv"2X"$ֈgEL,M&LHiiUh<۲6T!Ӟf.}6͏ua L-3pY Hx&mK:nvHK#<7 C-gT3d|PBf"~z5 S3s" d>3i̤>tm5oWC>|h_BѼᮨєJ4=YA`@XPz sS^gW;&C|OPz#:q`ZDSaЯTOCSdC֓Vo ?Cٲ@Ih)kqC5k;EOr5K[m|anv{= hIaA }O+$b 1Y5}hj@Be`OD} gl~x 2:"W^m Ϯm.:Z(1lPIQhuéO0 Ld@L3(ndGm6uֈ).ZMHbAӫr2}c[#صЋ[)3 WUO?iJE'Ϫq=S dzɐ5!K;vq }~`n f4d 8|^Jzzz%Dh謕.@Uxijw鍍X=AhC+K *"p,]Y:N(Ԋ*4gl3 /K:Fjč:ӳc9SŃZX%wB*C y ?ȐK>?P`$kԌh RK))N'#N]p wE#dN=E+#zbXQ U yON|5|<7PkUoFXk:b>>3OﺸyٵFPzd(k` QB8+gO*Ft=Hڈ~,5vf=ur+❣41i55!ݕB*^w'5V"0yqa6K9 yENinM(JOm>;wv[:N{M `W`9lOl ى0\+;Uz$XaP[Q{G֡,0ASZYnC,uV4"dL"QlգXc;[(:?>yvu[2cCrQt jvPTwA}ʔ\(#f4vc w~"‰dv9"0f{︥ٯoQ|h ~=>d;sIIOz9ˠJt@"~ƹ$U47[f,mK*d޼7z|)W}wo/?ͭurڥ^9U%fLBLaR=%WZmA+pub1Oa$ 7 qZmT3vؤbZ _c VUtL"84^ZNv䌖 [ 0fN[C`v/f^Q}I7~?E`*= t0VObWi"T/MlDcs =3aC}kZ˘TYMjt[8"irPKM̫-s߷2;2a,)H ijʥXt NM%G"s8Uxm1>P }Y>qM{3_qZmQT3PZ,Y4N7u떇+$=xÄ0IԏXo݈]@*97^—V: 7bBJ4uȞ0?ä鍯GBX@qh/Y&׮m?qlKMxk/#NDz7_IX_nZAG.Azjqku |+_شP '[4S4hL]#'|o/QeJܶ .#kp_=0b`N>- |EȨ,WNbRQF@)k|L;UL‰YWUó\4K!wf;Eo-z/:GyJ6.Mj W(nRxt( eS6.8*{8lC'Ya^{C 2LV*włAoӄsaJ+Uÿё޶|@Lqf8\ +p,9JZ[ `9<}2T2uu7;\:*':w\g2|=> c(aԳ8CA2ї>XJAn~"oN7ȐY[iڿl03G4s:&L gdNͶZ2\y<,؆U8|tAH㩮b1:K @l,ofsJW rdMVeʳLꊹ‘(X4] ޮH B.ڄX]UP8Hۧ&\ ֌J &9{i$/ڙS<"M\tG +{Oh n𫒉A_S[A(O{m)YFϴ| _VȞ:\ 1qJXW{↡^аs_/LG=^bFqQSybbjv&U{#mz/RomVByyfӪEmڹB}!nИņHf|8'L\] )4NO=yb7 `;#|:lqb*6[EU{7[}iأ{Cx`,GZ=o4:!YRz\2c42ˈk`(h|"M^*2"jw$?=\qy*:ыY,*Uϣ AP.'וnJG!*o4S[z8B܂M#^jT[dmD4JN~}r֏~(+l5l/FwPj@%Kr! ڞJSuOZīS;Ɏ+i q[~/,gug]zC*$Azso2]+P Eo Цc yb(DZ Wyޔk= 0 ;pXB`'dz)Y[Vue'O[WRJ~W,eθ9"(3 *dž@ +5GUNS7 b5u26_NZ˾+Ĝppqp>(JGqMá]949au/Uj7g-v8ng7(C3p3жhT90-MЍ07 .tI :"2=ZAu Ғh$}=箞1$!cʖ>^xb:}topO (]RɌ&Y~>_ǧ $wq(4wa|dW zyՠaLAS(!eMp ǝ/9u\LJ͸R#xkO%*ǎ?S=TB\zn SmJA{Ѧsl{]]GsAMp2S0S~9a\%(8עCuu#X+TSngX6kUo#(ߏyrpS[Ө%|o&i=?*ڬ܅lg0^]uZ[.X"b.!Uz Vw%XLMO!_@"Qۆ/[ORt,J,RZy]Xf,1ƪ x$2]k劈@©3S j Cڬ;-\&fVI $ PKTրyӎ1 xIivp?~d,r&5Ư `㩅 7fXuXnm}D$ɪwMZ<^M) *Cdn2. ﯭFFR%7ͫUnZڭQsa\<ƣ׌,W?ݗY?<[ {nm8n.$?3qM4Q.F.r15WrDdH[bPV\st͟oP3l¼Y!@LtJ3{dK70>{E.Iһ&y3xlӺy?ցA4Lǽ~~׵);v:0C$gkTcM SK,zA0F#1F 1ĉ"<;kTǐeA͔2k&tjK^d6:{qȨQzKrEb7,p O.\ndݒuraDWWf05X+i7rj+<[uؑrsUqUHv@-p}9ɂwXBLf;MRyBSm\ᢇ3{ǥ=-3&\eyȞ_`VoJIĹy`;P(}_L˦*6R-$kX(#JNXL(-5Q- AP]S"!R/)*wa:58 (/º~|DdlC>Mb7iG Dq$J~Bk3Cn|3κx(Ñ#Fr˜I@CZJ ,N6d|CkJwݒw=м\Gi7zB>!{uiq( #v08EBKN f,B&9l2}¹GO6-tt#9Z$T0z(gGCSYƳb#Z'v\ {7N7+y |)sI>"U=͵*ÉrK_=p^,cU**Oh8jז7 R\;i7mlbl%Iarv-Zgs)o!8ÌS8C{(;y`?{N e` .Hfހ9ZFY\ZHhpz ?xb|Wu%cldyQOHوITow0K)L̟GPG]HJ[`ۙZ{ԪfX5&o<\޶ŀ!.[Foʑ[nmi 7ʗB8!ih6"Пi "dSczC[C7CC7~ NTBI-ǃ)L$JY^&o\@3+?8&نsjXZzOb^O̟j͖(q8ɸUL D, eGId O[J%V[wF"$v ARWCj@PQͫ@hD N`-3Ǿv&B8mܛU!^eSfp쇁$͐!}umR1(և|l^X(]f/h'W-V]*dȠ#WCa>/*}'-T81RO_!77y$6@eɒndaX$Z[d|HGU+@\\7R5P&i:ռ(#5Naw£̕eO4Ru[#YG,U~5# r}D49MU c0 w;荺Qqo:@ (ʶ5JhUFo(sEJ*DRGoݍ?xkT*QJMm[A:9YB+)eΎ?Ӷ5/q߭R+ "ή ][͞HO|oW*ܭn _ܨޗED\ ^K ӈVFa_'!|1$0{z\ɗr=TӓBVbeӱ%{eOH!D\U9Л}IH/A ~ 8,MEPg?`ͤ@t؅򿮌*@@P Q)O5+A'~YAL+Bu$AmL& {_/.fbغ ͵ =]΂Gӌc} 2/jM`tˊ*#KS'hx 3=No/EV\ѭtߣ5%wϷS;8㸁6O ȵgBwjP[^32o8823B?,Ƹ*[wdQVW_ :(qEC)} L>p\eEJXBw,RZ_n[?[MvrTppHrDәz4]1n̠vGюI0iCbDorL?OayO㐲}Fiɓn4H0= ,4$Z]x$or0w#gQl=qMB'7X.J574wЕ tbpsRE+3"ͷ+ go:O 5+䒂Wg2~ڪ؆Ċ:v*r{v W&R 3جz:F %=QsN\ -@A'`^zyA|GLBEDk 02Iڲ9vvXY2݈O?LWρgt>%7sڨ4}2c{9Nz{?GG40B5 fƾ/ تUc\T^0=;s> 0۬wE #s|~(iFYfH gvk7fb ͒JuX⥼0]qG*ԴVȪDݥE?5j@5_˱>J| `ǧ=*تqv Z|Ռ(QgR _ȁ\& 96cF41b`zBnPgK9=5f+ IRkR.>ZRD-Sڠrc'䤕 }r߸S<vYRQYX7YIi }̄^"Q.lH 5 TE-BT~E 7xp/ly˯9%dH!࿠Qlj#N-.HU+2 fvKC?s茸l**ҮCpV a˝1Fxo`xG=WYt~.pȕ~?(4G%`&cu#P>5.XⳀ@Ϩ_Kۆ[qzQ~Jx{Gvv ^k_WʑT ,fǰi6N? r^@\ؔ ̉KpA\΍ٟ A({7@آ|"k)Ӭjaf>5qud:fpY%2wɴLlIXцKWbmou+ v51~FFY+9Ej~Z3T@ ek|$2:aYL{,iēUH4LI KG|xiهyʛ:*Ɣ.(Ùt-İP6Ry'KEZuk#yW#x9ݞWߗqDЕw̧v2eA7Qf`%#\{i3`g8Jc Fj_(LEd eO{` 8w"U7%%C@-m!K+-Cinb6.=~*ҭPcs6 n{zO gиc@|.y B;dBJ=<Q$\7g\4h|7٨4l;=rV5QIc{ |_ Y7;9;]S|4sKM > \W||Ӎۼ^UYfn-+?Յz xUlS~,OgcMڻ&;ջuo}RVHUY0|SD*˜V@Xu#DuZ qu{r$69Î0D5m}C OڱjJzRh5`]RzCJbM_TcH4o@췃 NQ/DZ:rR(?UGFP[]WP_,9}iAl*%Qa &&ʛI5”#1>ĺkYe5L 6|0K%7=wHmk;~ kx: ^km?pI(V$:⌲~%%|B"юdp4oZ&&<5NsWRsrlkN<)I{VT_W p^V$sO(GnSJ[&F/">@78Q'YKzI79``@-iE&<@1lE@CZ2s{:!8|<1&ITu(5g"2X ?b}jD]e8M7:4v3+hy ^ >1<.acQT ]apƃ2y,[ p-Dp|lnIqBSp8`?3: Uw.@1璌A- \[NVe>9HdLAhH<^b)B 2dqcEB!Kf6+0,'X - vWvҍ4=^`M'Y~Kp dKN|K|¬sD`ݏQt>8)n 9#k< OIV4eś5p/Z6~gUIa4DBx52۹:7\|S4bT&{O{san[kI-h C;Lzp3ǞHDy=ljǪϚ;̝ITUM?J|W$"!< ucN~o#ߺ9_6Kھ&x$''ا=vt% YDR(lN?ֿ |0UE풵.RX.ʯ1AsfZ9R3wY8v"Joԅ/sgTzfR6*PREV[*K&s2/i|ˈ,EDeF@͜q_aM"bVB5]. W4) CqĽ'XU(A36RfVGaEå"{l{}BƑ+pI6'L% mŬvQuQMWRI$W(C_0tR*^.DkE?R4I#ҩڌ:R{;nk;-8p]%l$^} 1H~%19;vx?A O^J=_O4|eO>@NxlKu^BP?-+:G+YOa=D@XEieuL#ΦX m/GgEb2RX%d3$I+&QWUFEo ꄅ,`eUnOkUdPyWo">B7WËxoóz~mIrJ - WO ua@_#BԜ.K""ںcVuSp㼖l K Ȥ".ɿ}O{b3QQBtIzijjk~zL!X6UlIgn}T{fy@|yEP5DMepq<,BNTkOc⦥6]$ǚ,Cj[0 M,])9qk(3臄b5 2>1@^,ƄO+Bۥ<돸%O }pm,XҒ逅XmTj nrhnc VqAkοz)F]BC)_2ےjcTvíWa͇즷zK_iwvѡu; Ld~jn;%Y(~cqїp Y5T;5QSWmF'|q|5O^)ui*Rx:g.V ,45^AiY<{M-2y|{jjh.{E>Vf6ǏzIu}Lx f̑_S"^ؑL;`N:xFPA`fC[yfWkowpS!n1U?4- }GY fՖIl"-֝;=9"FJ+Vb9z6(O;3I&E\аX*wp.WO^Jٔ' (<1e#:5G; c_~t_C{>DgڣJB- ['/񸹳Zxr{6܂X zB#m7hZJ;ZyҲqiVA2E=y+d&4>&;Kkn¼~*S8j3kJYn_Wl}jzs< {7:7CEjDLO4~:ax̛iy'X po31yCUݮ ;XX@3#?6B)H͍x0..%X7kÀvIp9Zht1p/\jAWlFMf[wl)R.+k%vO}4 w W^>ܥtI#"5vwE>CHG<у @uCVUKސx4ڱ'`K7_ηZ. jgRj @|ͧr<TA#Uy6$]l_( q5@n2  2,M:nc!F /9 b'&T%Bkvg@Ct舴vӳk$ٞ6 +1iwwa8qU?zkn{}A&Y )(- .vn\ϺqEs^O b4 .U.>1=_qWUl)kF;y* K%0}3ſv[[Ud6%&H*d2-\q 8l"po 2|ةaboqKNYS[%0QKF`P&Z1b{K.2&&KpQS߷NK6A,`_VTR˯› Fɟ_!l3-DyzYIRb_*CSt?pU8lýTj(J-ؤ: YE96<}i)bדZ 'M'bWRhEK('HO?^qSQni2Dd,otP%hx8'}RSzkXh(' (Z);^'릥yϟtv0VϟNL-l@fJ%vxw Dag;lo{Y-&J'8Q7p*[(,c ^[!< &6dXxΆ4WFDTV-0]_LV_C[Am4.W}ύ<\_/<+VÉ%@ve[pKOvl$qH{8/+{)BZiq@"H1IhiSy3{f,6("055IM .9BR7 1 02+Cz2Xd5 49]#!=lYEy8ݻSڴ)Ƈ=RwfdޗDhrY ga#lZ[SdR[Q^QfTm Gp5N8.:b׍1Iƶ=jf^!y\`rH &8 ~Gh^Y= !d `|SP[F* H6 O#GLshw4 [lŒD,l7y MVV6hmu"GRWkT(wedH(v͍WD%fİF֭G81n\{c("L8|R! ,jf̪,RSy~U .C+nQWoZ׈+t m"񬰿8IwCY#yκ0MV!5Ìwwޗm~Sj0Ϊųw,co ѐw?⡨10 )e ?tO5,uNۑC: ?N^ yIE\t+5mu=1^h2jɈY]AĎ H֠qvḫhUŃBwfJ|6nK-avSWol')<ٿׅ@;(7㧡)ﰒWʟV5}è-EsP 8ىi5x&,.O:4:誁 }.'du'$2bR7mQG рgfZY1 QaeGYv _@!~'@5L dK3:V̦cl4C@.|A: IؘSB2P/`~ %rOV N^0UU+9CiR\1ZKв^ 6B"BlGWR{*eˬj2pZu(6<ЁliFҠ36ybre n/zR&1Nܸ/=K"m.JŤ3JF ΘK l+ aV-UVbֽ3tAV \ژL^+5`OaR@m'o8O3$֨\d$K`f64U@Ji]Fx=ѦيQk@R\m`j׭9@ڰ}d-i"gF&B=;[GOrBn +GAӆ\EU7dCa 銈~tP1*>XD|<r~芷eUa}er p,T@IdR,G8tHdkY^=Jy09Uur') ,-QNRk=Mc [{]ղ Vl%L1 | Jt2k#.'> X@x+hdTq`e| Oj;!V,Yk*E3ѿג7s@F&CS:yU6|/BXS=J{5rF1_6N5WK`zj?}f]I ]<@ӻFva㇃ϓ eU\tbZp3^@)!:AIH{[1ZĨ3{<*] ͗}[<_;iXЏy6>X&1Gc ,?x-ݟN`gڠ6DTbƼlƙU TA2,mת,zZ8ulM۟##t :=v[F`?^L ]y]-9ƁFU_V5OGW߉mdn1uu޼-~77mZIe\hq Y *xa]Xy^7q/)ٻ2Q5Q,V%sȤp0 xֈk`7Vpux&6]i!,`crV4)> 8A7uBo5bBj)D#KłQ/vSK֤«f7?n>JQsϴpd1nڍ›'d7>z,D:N<+n2;B +?w n)a]53p"lHWp`]M͵wx;Yk3Uax_vTu'r@w?$9 Ehd-p1pIm/b-2Ջ ^͟ƴp¶iuJ5&Bu6(,6a&8#\GvhŰ[JuBcNUHW`= cnSƝmM8(&V=j)KDiy/cĸKwN٫_$Ew\OKZꀿ  *1N=N=dXB)/ 1Pަ<M`OQXLz=Y&8Rx78N.szK)T!uLF4LJ*ݚ!63Ubz앬 3rۂL;LϗK#HdcL IXny5?<#.Srtb6N79M[ŻzmoHt'C+t'tz?s^0*ŚMA d٠QUu #bgg5 %b<[If!6ڊ̈'JY' F<*{sZ+ ?F}H:&E[ଲguu_FuniO\'HV+9Y]t*m?-Pϐ;|;=Bj S*bB};֗o%5?nW.(}:^^ÊK"PYxKoÚN$E }D@3UX덥}PuK *n TEF B ~Hι8CJdD. CBu[f sbb>}͎b%+%uzpzoB=RZ)YiF6+! A^0QYŗxȿXyH4jy].Êv ~ߦi49Dtl_(tD4 * "T1D`ϮM0I=Ԝ/wҵvv6g{nc2T̸UzEP3e݌7}z}MBi=KsU]m[ {҇&*Te25/zɗN3U`0zl/4:WdTڕU\>vj1{tQޓe3sJ]z=D !(Di%ORx+)l.]PU*"fJߴ럼uϚLYΣrhGy؊CjG?PEudGRٕp#(L{$r ƾn*!hUz)@IS:!; 8Hy!MKTwwKA$4D%XLng=fKH;Ow!qAL \oqM9TG*\_;3%3P #tێW;9Ŵؒ!]wfċpv'4m::$;m0Qὑ&{W7 Gj)8"d@tV$ Y !Sp25_t=3C%sm#B^62vqCoGe zKW6= ONhN$&Oh շS>9P*Bu$ؾj2G-0 cu(H-g;e5 o .s|w1x(1ՠ>]?77esYQ9-9JИ+8_&sr5uK,~u"XHϞxIfAP\#P =l {WsCÿ^ RJy7;t?zs~#*w`s^[ЖFR:Y럡rvMOE?nޣo "[citbsG2֑e)j|CXҥ͸g54N8B:"ګex /Gt,^Ѕ^vg|r5n[6ljmp|Z D$E'9jh:ʍ\+MKMғ|MhV@f Iu.ݶ.>gW`7Aaѧ4iwew <G#`_g>DLg"<3QҐa(OaD93dCc@DՏ[WAYc3GOl}HWy7ך "tԫ<#wEV=Vl DJd :_9nTZP7\Maە˭5эH#;Iѫ u#g);MO6Kë~lpn\櫩t3"4;{R}@o`@@0~ `Kc&BR5ʢ޲W%[irk$q))3}~Ҍg9(+kʉֲމjC*q@2|(A3G T5 نpjUVWtLlN:}VWr$nAi,j1toj[;Yˢؙ`yAC=x@ Q·p{ TTRӐ&fJ CoܬI. u/3Y"ڂ֝uŃqx6DR%S!fB34En?@0=J;̩tuJ @:U)Oe/܌+Sy\ b\hOL[|y2#D&]cP!5d~JABDT/㟢Kµ6΃e|syaK.KF 6Qc[6|x5H˕#] OH(ɉ D'v-+ #T~7H10`\ WƄyI!`EUшȞ |)v30NZ^6yѮ.Q0Ox ߂`.̠a")\ U`יJ3wJvi- ?E7@?^z^9-H2i`<8K9tB0WJ}6}qo$t Gw)' +b8-;6m"7"m{̗ZT18h0_<5ceVP\.I7 |ýKw*$់Bn^:=ӆQl|}⪃:;,}7]{]_ ^Ѻ!'W3/eUr!fFɇ]W1JA*ki;灶(`Z]0s|X'սlԤ$zm#[#ʙ[++z_OX El] &]1{pn=Iƈ1C$d5bߨcGal&w[J)Es6whS@U$d#?G0~bl`J I0eo*AڻړD(Sʡ@,YP15%sspK|EѭV0bi"|ttwp\pLdF*B76Έ阱/{xܳgpہ;`Yo-:svE-sFT?_7d߄Bl53fBC%Fq"TU$r[kwɚõ2gӧx}(uC}myfVGB(Lb,V+S]_d9B\vbBhy\*@PGe[tmPd 0m6*MNy0©F| 6UiGjkW'\gG*{ޮٱf] F|)f=#\c@ώŁ}ӷcDD UKq)n霦p&偄 %"=FvAd~󷷘B<+(:"ah#LXkq1CʬܯR0\l>G3%ȕabbz'O=_2DAL9Vdv:vO򔼾P»ݟլXmS)Y9gxGj(+ EX~% ha͊] xY~F ~ˋ6XJ),&cmUzVD%"BO_i@^O#~,|/KaD!ƗU'dxfu) ^aFDoϿ.l{xm:U~U- Z_ؼj#@K p5::K1NDq:3ݬ9/۳OjtJhE IKf:_eedKE> Ⱖ"hpqvtPs\[X%{Or kPA;GLL1!^?"c"Dd5gъT[R݂2 !̇5GgS5w9#o!21H|ҥ8sτ3Rj;tYEM YgA=&XXl1t16~__geiuZeu@g=Q90;A@XJAiXn^\  P (Xj;P?a 2˹+MI"Y6ZeI8o]m7!,Y"{~ѧ ):kG~3o+Z=8㢀Zr<^ӯuWA>!ǘ*4nQmi=q|#Ją)b^bi9UR?;vF<S}QEq֞G׵kZBj@UiH9H ޢaWѰ<&MX(~F\jU!n>jt"fIIM nZ2zT Uť~k⹎w/wa^*n2[Z]D>>v\>H^Kw^ϋan<S Њo95*M${i_jk _q«鐠37W&qطHĆ{{+zs Ǫ>!zp=s8N0Z%]<`qn:W/ ί縉l7@V*/;PF[!9E?KsF I8hv*_6Iq/ӹd\dciL߾8'":ewc"qG ?2e4ӦjқPUtzƤ :MIG1&]AE5Vw]U%_iAhwO zxX{^+'XEg=HM&_ >IjBԪy?+9GΣޮBbBȻ"Q3p }Lr4 | i2/vtf_G2vs5:%C7dCeN!ɖ^N0æb V4:'?I [w?/봹@p#()",dRn)i⇜BY Z^D }G=IJ6=!˻k[i EpsJy,r|tV#ޡX|zXVɫziX#zN@x|Ui ]&Jb"\̏`$iD>T9+% A$mp h:rRj ?DN-G4sˀ~[N 6/a"Z}h½ Z.dґa}j͊.o׭ܵl ԋCFxgjz b0T;L-c;g"BT߆QV`IcEFJ8(3#E'ƮIoD\lT(l@ts!;A [C6Y=cMYыwήGYj}PXNo`D 6CVSiNz 98v 1TRM@|u ̐`X;xrڌdOM6}RnR,mP_ᴑ.AU[Z-ܶshDl9B=[2 }c6j[ 26Sۡ>\_>`mT^jLos)^x)V\N%$U|n0%vmC1RjdyN'z6jpX!IG 쥃'X,z~R?Cx_Q6ҘhDC >g>T]>SFb=9I,a22L!QyaQ<_.@#yAy-'a3O]0F KR]/7qo|wH?+vCdZcQ]6fgi]7BCt|<}]!>>4e)-gaP\TUTe4.sbUIaEjf , FUw4Kr*#5\s.t#1Yz}t!qӗL,TǙIŚ]px8kU< Q,8Z93AUWD5SefXX*Z{|>(H,V!1T#s] ]QQMѷ@6'I,M,K hk-ns^^n}<Ή)[hu^iSxC<f?t}I3xKՓ@:m@`w(P(-iq W%L#7iVfkؠR-:%"P?B=[MtUwj5G,zJT 2~1Yn~DlX bD&)ި bKCfֹr[ٽ ~sIZ$upV'_`cv9eCp2[}OP1VC{EXu)2ՙ>Z=cJ-DBbZ+J&{}LMH%/%:Rc@A`e| p!Jxu7Ǯ!M൥ %Ɖre`q:Q\+Q5E^zЄϯgEW5EjN:xTcAٮabgsOe!Sa5r=0bAΞ?`Ro)Ea$u [f=g9|m<8h|=6R%80E`ry41h+'B0'_[ l Ӱ玲Bo]kr,n@r^iN@7-͸o?`/pd4 u5,)BKOIZA7DRmI@_w(Njʹ]&BylخOEUq"06\ӨzX=w /<7 Ѧ nU`t#aR0o#J-DSiפQῙ,Pefƌzz! 2;0cJnG` -:K:zsX }뀔!Il!_ }yzU|2Fq8Wv}Tz<g]mD̖8zy\^4ixTfqAU#0c'F+Q.k4*y zCywpVϧ!~ Ϗg]a+\}0%w4t3k^?'1>['yw (wm]9y? V> }TD xUjdo=y:.uOy'QSf7/nZvÓ(9MG\ĥUs6d7F >W [/QY^/i +Gpd󜨗8-/Y dA ZV,R5+/c7SR&r,5~Sزk5oH:nr(8R SaS.;#/M VsB *CpJw8_1P}!PѐtA$9`s\UȬGgOwl"4x}zHS})t唟M;xN}6~ié4fm1,ud<|ꦨ$_C8hmQK=މT8=ZP;e\ux"(GsM_~^0n1#V@ E=>>} (Z$ͷ^F4,hhY\1GPP|\`X.QV"R 0W:U=܇-r)q 2H[gh-1wݭDRoZV0(9ӕu@N£:"tck~ fYM6tېK0|xT-CP`C?1Efl/ML;Z"N|.]hKgU߫jkyc3E>e~Tvꤓn(mwQ|y~ءXH*&ǠB[CѢ;ЗBC6S -Ղ M#"*I4h@W*WٰiR}R8d];&+(<4ވkKR&gK+Rtr0fcd7ҤzîS)^zDbU99Ox$d$ӁՎ/P `{-n]))Y_v'Ow5@fFJ; 嚐\g ;O>:J9ڗ}*TΘ#Vԯpǿbd{( w}_0fZk:qcq}4 ,?%јh\p<$(UH#X~YߨNv_y5p1.S Ɍ}f&R"J'^kyz8u5]TNURf^R|/ﳵ8WRq!gED~4`Q,4rUNNe 6I됝#OgjSH{E(&"Y6{a?q 1&H&aMh\QS <#Q|f3-؞H