* @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; /** * The finder provides a simple way to locate files in the core and a set of extensions */ class finder { protected $extensions; protected $filesystem; protected $phpbb_root_path; protected $cache; protected $php_ext; /** * The cache variable name used to store $this->cached_queries in $this->cache. * * Allows the use of multiple differently configured finders with the same cache. * @var string */ protected $cache_name; /** * An associative array, containing all search parameters set in methods. * @var array */ protected $query; /** * A map from md5 hashes of serialized queries to their previously retrieved * results. * @var array */ protected $cached_queries; /** * Creates a new finder instance with its dependencies * * @param \phpbb\filesystem\filesystem_interface $filesystem Filesystem instance * @param string $phpbb_root_path Path to the phpbb root directory * @param \phpbb\cache\driver\driver_interface $cache A cache instance or null * @param string $php_ext php file extension * @param string $cache_name The name of the cache variable, defaults to * _ext_finder */ public function __construct(\phpbb\filesystem\filesystem_interface $filesystem, $phpbb_root_path = '', \phpbb\cache\driver\driver_interface $cache = null, $php_ext = 'php', $cache_name = '_ext_finder') { $this->filesystem = $filesystem; $this->phpbb_root_path = $phpbb_root_path; $this->cache = $cache; $this->php_ext = $php_ext; $this->cache_name = $cache_name; $this->query = array( 'core_path' => false, 'core_suffix' => false, 'core_prefix' => false, 'core_directory' => false, 'extension_suffix' => false, 'extension_prefix' => false, 'extension_directory' => false, ); $this->extensions = array(); $this->cached_queries = ($this->cache) ? $this->cache->get($this->cache_name) : false; } /** * Set the array of extensions * * @param array $extensions A list of extensions that should be searched aswell * @param bool $replace_list Should the list be emptied before adding the extensions * @return \phpbb\finder This object for chaining calls */ public function set_extensions(array $extensions, $replace_list = true) { if ($replace_list) { $this->extensions = array(); } foreach ($extensions as $ext_name) { $this->extensions[$ext_name] = $this->phpbb_root_path . 'ext/' . $ext_name . '/'; } return $this; } /** * Sets a core path to be searched in addition to extensions * * @param string $core_path The path relative to phpbb_root_path * @return \phpbb\finder This object for chaining calls */ public function core_path($core_path) { $this->query['core_path'] = $core_path; return $this; } /** * Sets the suffix all files found in extensions and core must match. * * There is no default file extension, so to find PHP files only, you will * have to specify .php as a suffix. However when using get_classes, the .php * file extension is automatically added to suffixes. * * @param string $suffix A filename suffix * @return \phpbb\finder This object for chaining calls */ public function suffix($suffix) { $this->core_suffix($suffix); $this->extension_suffix($suffix); return $this; } /** * Sets a suffix all files found in extensions must match * * There is no default file extension, so to find PHP files only, you will * have to specify .php as a suffix. However when using get_classes, the .php * file extension is automatically added to suffixes. * * @param string $extension_suffix A filename suffix * @return \phpbb\finder This object for chaining calls */ public function extension_suffix($extension_suffix) { $this->query['extension_suffix'] = $extension_suffix; return $this; } /** * Sets a suffix all files found in the core path must match * * There is no default file extension, so to find PHP files only, you will * have to specify .php as a suffix. However when using get_classes, the .php * file extension is automatically added to suffixes. * * @param string $core_suffix A filename suffix * @return \phpbb\finder This object for chaining calls */ public function core_suffix($core_suffix) { $this->query['core_suffix'] = $core_suffix; return $this; } /** * Sets the prefix all files found in extensions and core must match * * @param string $prefix A filename prefix * @return \phpbb\finder This object for chaining calls */ public function prefix($prefix) { $this->core_prefix($prefix); $this->extension_prefix($prefix); return $this; } /** * Sets a prefix all files found in extensions must match * * @param string $extension_prefix A filename prefix * @return \phpbb\finder This object for chaining calls */ public function extension_prefix($extension_prefix) { $this->query['extension_prefix'] = $extension_prefix; return $this; } /** * Sets a prefix all files found in the core path must match * * @param string $core_prefix A filename prefix * @return \phpbb\finder This object for chaining calls */ public function core_prefix($core_prefix) { $this->query['core_prefix'] = $core_prefix; return $this; } /** * Sets a directory all files found in extensions and core must be contained in * * Automatically sets the core_directory if its value does not differ from * the current directory. * * @param string $directory * @return \phpbb\finder This object for chaining calls */ public function directory($directory) { $this->core_directory($directory); $this->extension_directory($directory); return $this; } /** * Sets a directory all files found in extensions must be contained in * * @param string $extension_directory * @return \phpbb\finder This object for chaining calls */ public function extension_directory($extension_directory) { $this->query['extension_directory'] = $this->sanitise_directory($extension_directory); return $this; } /** * Sets a directory all files found in the core path must be contained in * * @param string $core_directory * @return \phpbb\finder This object for chaining calls */ public function core_directory($core_directory) { $this->query['core_directory'] = $this->sanitise_directory($core_directory); return $this; } /** * Removes occurances of /./ and makes sure path ends without trailing slash * * @param string $directory A directory pattern * @return string A cleaned up directory pattern */ protected function sanitise_directory($directory) { $directory = $this->filesystem->clean_path($directory); $dir_len = strlen($directory); if ($dir_len > 1 && $directory[$dir_len - 1] === '/') { $directory = substr($directory, 0, -1); } return $directory; } /** * Finds classes matching the configured options if they follow phpBB naming rules. * * The php file extension is automatically added to suffixes. * * Note: If a file is matched but contains a class name not following the * phpBB naming rules an incorrect class name will be returned. * * @param bool $cache Whether the result should be cached * @return array An array of found class names */ public function get_classes($cache = true) { $this->query['extension_suffix'] .= '.' . $this->php_ext; $this->query['core_suffix'] .= '.' . $this->php_ext; $files = $this->find($cache, false); return $this->get_classes_from_files($files); } /** * Get class names from a list of files * * @param array $files Array of files (from find()) * @return array Array of class names */ public function get_classes_from_files($files) { $classes = array(); foreach ($files as $file => $ext_name) { $class = substr($file, 0, -strlen('.' . $this->php_ext)); if ($ext_name === '/' && preg_match('#^includes/#', $file)) { $class = preg_replace('#^includes/#', '', $class); $classes[] = 'phpbb_' . str_replace('/', '_', $class); } else { $class = preg_replace('#^ext/#', '', $class); $classes[] = '\\' . str_replace('/', '\\', $class); } } return $classes; } /** * Finds all directories matching the configured options * * @param bool $cache Whether the result should be cached * @param bool $extension_keys Whether the result should have extension name as array key * @return array An array of paths to found directories */ public function get_directories($cache = true, $extension_keys = false) { return $this->find_with_root_path($cache, true, $extension_keys); } /** * Finds all files matching the configured options. * * @param bool $cache Whether the result should be cached * @return array An array of paths to found files */ public function get_files($cache = true) { return $this->find_with_root_path($cache, false); } /** * A wrapper around the general find which prepends a root path to results * * @param bool $cache Whether the result should be cached * @param bool $is_dir Directories will be returned when true, only files * otherwise * @param bool $extension_keys If true, result will be associative array * with extension name as key * @return array An array of paths to found items */ protected function find_with_root_path($cache = true, $is_dir = false, $extension_keys = false) { $items = $this->find($cache, $is_dir); $result = array(); foreach ($items as $item => $ext_name) { if ($extension_keys) { $result[$ext_name] = $this->phpbb_root_path . $item; } else { $result[] = $this->phpbb_root_path . $item; } } return $result; } /** * Finds all file system entries matching the configured options * * @param bool $cache Whether the result should be cached * @param bool $is_dir Directories will be returned when true, only files * otherwise * @return array An array of paths to found items */ public function find($cache = true, $is_dir = false) { $extensions = $this->extensions; if ($this->query['core_path']) { $extensions['/'] = $this->phpbb_root_path . $this->query['core_path']; } $files = array(); $file_list = $this->find_from_paths($extensions, $cache, $is_dir); foreach ($file_list as $file) { $files[$file['named_path']] = $file['ext_name']; } return $files; } /** * Finds all file system entries matching the configured options for one * specific extension * * @param string $extension_name Name of the extension * @param string $extension_path Relative path to the extension root directory * @param bool $cache Whether the result should be cached * @param bool $is_dir Directories will be returned when true, only files * otherwise * @return array An array of paths to found items */ public function find_from_extension($extension_name, $extension_path, $cache = true, $is_dir = false) { $extensions = array( $extension_name => $extension_path, ); $files = array(); $file_list = $this->find_from_paths($extensions, $cache, $is_dir); foreach ($file_list as $file) { $files[$file['named_path']] = $file['ext_name']; } return $files; } /** * Finds all file system entries matching the configured options from * an array of paths * * @param array $extensions Array of extensions (name => full relative path) * @param bool $cache Whether the result should be cached * @param bool $is_dir Directories will be returned when true, only files * otherwise * @return array An array of paths to found items */ public function find_from_paths($extensions, $cache = true, $is_dir = false) { $this->query['is_dir'] = $is_dir; $query = md5(serialize($this->query) . serialize($extensions)); if (!defined('DEBUG') && $cache && isset($this->cached_queries[$query])) { return $this->cached_queries[$query]; } $files = array(); foreach ($extensions as $name => $path) { $ext_name = $name; if (!file_exists($path)) { continue; } if ($name === '/') { $location = $this->query['core_path']; $name = ''; $suffix = $this->query['core_suffix']; $prefix = $this->query['core_prefix']; $directory = $this->query['core_directory']; } else { $location = 'ext/'; $name .= '/'; $suffix = $this->query['extension_suffix']; $prefix = $this->query['extension_prefix']; $directory = $this->query['extension_directory']; } // match only first directory if leading slash is given if ($directory === '/') { $directory_pattern = '^' . preg_quote(DIRECTORY_SEPARATOR, '#'); } else if ($directory && $directory[0] === '/') { if (!$is_dir) { $path .= substr($directory, 1); } $directory_pattern = '^' . preg_quote(str_replace('/', DIRECTORY_SEPARATOR, $directory) . DIRECTORY_SEPARATOR, '#'); } else { $directory_pattern = preg_quote(DIRECTORY_SEPARATOR . str_replace('/', DIRECTORY_SEPARATOR, $directory) . DIRECTORY_SEPARATOR, '#'); } if ($is_dir) { $directory_pattern .= '$'; } $directory_pattern = '#' . $directory_pattern . '#'; if (is_dir($path)) { $iterator = new \RecursiveIteratorIterator( new \phpbb\recursive_dot_prefix_filter_iterator( new \RecursiveDirectoryIterator( $path, \FilesystemIterator::SKIP_DOTS ) ), \RecursiveIteratorIterator::SELF_FIRST ); foreach ($iterator as $file_info) { $filename = $file_info->getFilename(); if ($file_info->isDir() == $is_dir) { if ($is_dir) { $relative_path = $iterator->getInnerIterator()->getSubPath() . DIRECTORY_SEPARATOR . basename($filename) . DIRECTORY_SEPARATOR; if ($relative_path[0] !== DIRECTORY_SEPARATOR) { $relative_path = DIRECTORY_SEPARATOR . $relative_path; } } else { $relative_path = $iterator->getInnerIterator()->getSubPathname(); if ($directory && $directory[0] === '/') { $relative_path = str_replace('/', DIRECTORY_SEPARATOR, $directory) . DIRECTORY_SEPARATOR . $relative_path; } else { $relative_path = DIRECTORY_SEPARATOR . $relative_path; } } if ((!$suffix || substr($relative_path, -strlen($suffix)) === $suffix) && (!$prefix || substr($filename, 0, strlen($prefix)) === $prefix) && (!$directory || preg_match($directory_pattern, $relative_path))) { $files[] = array( 'named_path' => str_replace(DIRECTORY_SEPARATOR, '/', $location . $name . substr($relative_path, 1)), 'ext_name' => $ext_name, 'path' => str_replace(array(DIRECTORY_SEPARATOR, $this->phpbb_root_path), array('/', ''), $file_info->getPath()) . '/', 'filename' => $filename, ); } } } } } if ($cache && $this->cache) { $this->cached_queries[$query] = $files; $this->cache->put($this->cache_name, $this->cached_queries); } return $files; } } \ځraG%9Io.G ;)Դue.,EO*RˉWC6O^Sޛ4(iy qaT710a+ -!,{5nlfebus0 sm=gk=Pv \БWo}O ,(%lQ[xʒEnTSu\w֙ya+ď9pE҂TjA ݞ–%ьC%`<jPFBWe) ""ҽCcyt9h6Q QڹУpj Tץ)"-6`E 7 К1I92|i#jCLB<96"'D*h-z WY3#(*/KJψb dc3b l+WG%9y;Sܐʛ`Pcȭ͙\xG 54`7yюF5J:921sɸkLX ٵ+,[F= 4P|"c_GmV!v)NMalA{ Npx-5|g|-L$&e8 wTO,"F*^ !Hkb|[Cr}rk%8IXPGMZQǾ 57/T-kf"23'hVWokohJl^Yh1vJc oHt6YDKRT;!oyOך &7P$n]T+}@2x;@'} J&´cǵ Mo*@B7coiGmv-oJ%A!aKm۸u녽(٧S_v#ȁDM^@i<\AW`0gJ!0#x>y"(O3[mK/vP@`O7[+/rs8AMy^h$NHƟ 1h홋Fpӌ5\B o%տ P=uO,ܪB& uJ-53TD"AqЇ`ηob sj &B8"JR??!b՞dß!kEvt_P<1lX{]Ht]6'U#gaF @)}{w&BɚMr$g"T0THb_eSIH:spJ-ƷxY=kJ-lGA<t&CAdp  15> BYAS#3#C,y?|V*6+l  9Tk<<![2g.hm?ݠ[O`Uz.aF;G'7|: 0йDц Z΅Ig&#WL`Af_Cdz %mr:y B*tBnNF 48<1!_}LLġmϱN`V񕒷-ʼT+*_65 >0ge·#p](R R>xwW)g/8'zK0Fő/IքTXYByT& ʒ߄–uz4 I 6 mn >VdFk.U߃fI&$i- A8Аk7|+<50t$2\&~.6IcNo)yCkASȿϠ6xj 0^֥WgJІ7zkWN ZxS@pTY2q q%'3 .S"wjuI4io"^ހ:Ex|L!^nR臤/E<|_9ܜ}]]c8]+iSᔑ9EF ϱ W. ݸS'8ľvUȠ;2NO%}#5U 1.eq>o5J9$˾ t ]"0uN|PEydr$!6-w @}B7@w)-$(}VvpC͝ |BN.%$Vkܑ#D#(eC9de$D,~ҟ3Lx-Y(=220 A LoljX͌Q,Ù?Ag1na,D'RY\=7VFUъn& X/n?+ ~DZ3 д\nb=SznzTer 8:#Pd_X&r-:J V#-SKAGv7h d;7_0M)]Zyb)r=3 YZ?ܠ(cѾQSݼNw@ +$*)2/zw=zx`E׼h;/YIbըk,avGfg~I<2`-xCBB-EN+Vacm$ ߤ+ϥvlY_O@u~ϫGMSѓ8+ o +qND W@cL$.\JMs$FWfmdH7Fy%_J2IZ>oа՟~9D`AdVn} ^gd],_͝/mgo#RpRT4T5եQRښ_UhCL= m["'*/K{+/t.D_ѥ&6ԏ2'`Fe$qVB߽T !9CbNO@:gxϜGt?B⦖&Y\A.FG!* w9^+.":NP߮Y )È & aVm0ĽcI>SWq`AT7$ζ]JGZ⾋EAehJz9?EaTصS1%][^Q#,(f HKFgmg3$xҘ?}.3 ՅQl$$tM$ĺ50vNo5($?"-ϱhymE,PدO}*"\n|;aց6 $$z\^Ö'1tfe ܾ jGosymFCN/wq2]eʹs+e'Lx@ cjSy-kמ:Įٿ͑/ӱrwxn&4aP \k$T)9d}MbhbK.t?@=@0Rpϣ8D;y #5E=c %7n>\|Ӷ|kΗXџtkAVԤ հj\^ ck:'{ DJ!? wh=\j][Kփ u8D#:w`*JDY3y9OsVՁO#ګXY+xBt*l)=QJm.|qKD$:ӷL?@ij[S0bz^ ̪U%IY*_Fu^-Wpc*7>O1vFOUӕvXa]lۏdԁ9Mfu 7{Ѯ}ikS:6j EPD8NN+0/,E af&?&^fsgbKTp:j*1 0^$VꨫGU[藘QvJ8lu0S)*dD7"OUj6nGO+ԏ 6G*ŚFW(1÷bg8c!ׯoC}:$xRl|u+ep-/*8di_ 5DѭI}6MY9^VUmOqǶ4G"hW@OfPi֩Zh"% l2!b/AL\NS#mZq;goAPe#]ŀAot&߅\{&1B<=7|Rqv˞>R՚:7cuьC?@͖XNFlX]Q>2kY v<'c;8FvT7'>j'tݵ7z.ҷxmȷ8p"?I<$m$r2EuR0B<3hYo-EjJ[nԭ X]=)|lWیa1-.*9JG&l<@3T1x3<B='5<\ƴNo`d`8J؃Y慽< W#{ ̍yIw_|l'D&i?S.9@=m;S -~P{az ~lұ&Í A]hm8+漮X6ȪK *qѧrG;o,[U)'@wLҏ!*P$=4B2D@H #N0wp̥x:"J3[71h# %$/ N)s,uKmϓL٠%N;MKZYQj1rR/֚;Ƿ({5d'5ZTE 6^z*ւ+>0 Pizx1Lfn^ZmeCXsC s :!iV1dC.V0~S֖AMeH30ZJ)YF4#Q#I6s#U~g ܲTޏQO<7# dX""gX_DJ.sO7iŀ#g%{3J!H!^eQ`@-Tت('U(kզ*}, GP'N ?7N"gC#L[͖I\L{Q?k)q V$q]}AM;* ^A.h5lzbaA-lx2˧5Sfo# trM(0$ff @_?m_uv(KƄCF'2Mt05CgV /Ërش2I{LHN=,e`p_Of~}Ң+ǦS2]:{*ѬWI~}\ &tx񍳩wwZc, Tz7NvK`( ?5=a6b)lXp=5,XG:B7Hõ'bܠaT'~Ms]Q[@20(QVhM*pizUCŌHc'ƸMa2uMsZ,>6ect tjfVLm ۸j@>X21ʳH)d0j!.b-ͼ-U_ :&EI6͟ҫ/,M -F#$+ocTl+rƱQ3X$-̌W\MWU(8L?d0Hhlg*%?ì*6epɝ޼xRe:XVO8#$XU*X݅aW d  R-ќÌ4ݍTg%Гp?7杭7Uluܿ[:B_rv ?5r0 ۉ Eer5 /˃u@RS@Yiy+kM{W'Dޡ=H_l6_#qV*Zw+AAnfo`@b&v7~3s6ohnzYH>ۯS&Y + f_hRu> h26I{v,NWV[!frWwJGS| MuWvh|bبozsRe:UI\J"Mw6m5M2) h]y)KMFt2f>BKs#id]=0%VV$F4\ Cbp^CIwqw/:v#|S$,Ih8%qi +eknhӎBx L3f<"@Oq%zc:LA*%|6GM]1SC*/a I/@ ~7O4lQΰ3*#5#sngr\^6C_`]~+%$ Hs$FP ++#BFven 0H)4V/Rfᇸf 7o@HqL@`Hȹ4&A%?X99N;F0@2:Oht/eZ)+dZa̮n8#?S։p%/aEKҸ#TyHj jJG Q>8Cai̗? jYSQiI,J{ʎ aP"DhxꟲB[j" 2wWǔ΁ ?խ&~nI5W/|R{)W>N[jrB`Awf^\(}6ZHMMF"dw޻4(2< -anS{ ٬|zH .7U|oQW,!q Qhpk9n,Lay2=Ib"Sn5^kzgP|Faؚ}b/ X~77({)Y+}p?Wr(\.t-~@w%1u@i@ɸ  'DnϛnͯkAt􍨈O'dl:+ZԯFVw Hb|PKf$7H­4*;mlЫ k!S?q20}k.yʪ%Q$57y>"O Yw߼57Giw BZɀ=RO=F#HZD_iL e\BvDr'Zz {g#ɉ>czpqx4$XDJsHٸpv/Q;˧g([ ($UuSҲ (p 6aѪgacu/@! zT*R|Q8|9a\zJiA tޔ1=kz Xx;B R Ŭ %|Lu[)@\d3ôA'V`K :UhlLqewTbL#몟{@=OL;Py"N.8l~Qe-rN k gS? Tks!ķLpuX;MCj&tkHzT#6 -p6I=pBFBiSwuIGNDɟw5E˲}6VkrG:WЛ>LUE Ĵޜ ڤۓS2a54bJ܋ouF_s70)b-.'2< t`b] L'Ͷ~Q<"fWB3tEQȝQp ؂Ӧ5 yҿ .18wNMgk$E9|,@Qi. F9ШIv64'?XIjL+Ƽ`GndzlGH2I`ÖقMc:&PaTN߇pB[Tv0 [U[Vp>m!?,>@h&݃ΨȦ4NA9sVs1]D0Bsg޲6?5PSs;mVx_l:ۄxUM;xAӨ(Fך%8v(L| S!G1F!ƗHAӒl2[#&3?siۥ6=Ul 35Ǻ.КLXv;vˆb`MZOy;Y{ wup8t^Rkeogdx@p˴_?K5?s.,B&Xa[%MQI v˧GPTCxk% < H"|j^(AJ妭+nzX/gQWv\yDNUeRoskǞ+n>ZO n^{2M1"wvg󧨩Xfdt_a:m`n h;/VlNF,-O+dѫ|bHh+D)lksfqzCG4ڭݠGe!xú2d%D^q|^jR6Q#!wg31W:Z[DQ Ph,˔#OyFR u`.Tk/XU8M7,o7MaQ}+'ƥ׿3-f)sD׮Rm.7GdU},xIg>tǪuvo;+Ǯ>JIKɺubȗA?hOYnZ[1dK?ٴ;zi^ZĞIPoBths(>O&dP " 7, u{ K5|mF;gnigk*WM _A`QcAn'G95]WfMcI/kʨDŽ/߾4^MOdܓ5#h2S5>j2~CtݯP*; MO-1ڮA^sniyo\aw@C7$ԋF J,A|D,`i슟y{?IWPj/tj(IFmRo +K݊"(֐%Z9yPH g`$$-O$$ph2I"*N\uP~ql560{/ %BUYd؆W&4>@ʄBBy-}RN hC(+t]\qq*x)PoAR -{OkpQWg3YG"W6H'87IMJ"m_aueOl7ٛ&xʾOV ]Gkň#/{@geIR m2Hkfl麻O 25{ho⧰>8g7TX8渱l0S);f.Y1?E=>=@f#eu.6E_ЪɘU;2WPa!E,DblsƛM]^)I`=V׹{niPP0M4V:"8#xtg_ncR0[H t= ) ܐ6/maz}-8XZ+/jC6PNwѡ&VD{& !(.z3k?bK16V?ں ˰wLdFLs͂C@=_O5g SP/hEz=scqm`-GG$Pʽ,JrzTp;p)ocG .H?bI%6G5bt$sIGzwb`iXdWpԺܸT eT\ʜ[eNG$̷ctn<I3%a37MQ? \s(A3mI+d< aGboj8f,{V_S̚;\ؚ? 4Pt. 3,ª!یmMIY3jVvf;[* 1!*n+M *fn-+Vs5{Y4-\T{GaeQW\c<ȊK1ӯנzH e6r ďbsK_g[y>Q*VU/E! p|c99^iV=Q  -"[ ?j'\ >ihy (>f1 ͋l; R\殣eGg^OEE Z0{sF\C\e`{"`0g#jJׂa[|_:XdRL&`Qj)72cz焜H#Cp.} iPĔ.'B^cl%!@lKzKnpdWM%ֶP!op9jP `\|c*-+&Er9(켬Z< aLj&?Ҿ>^<̳^CQ ʣރpe()< jT*ʿۦN9 w`Q6g/V YI\Q0!Wخ&_%Nc\k C%T@c_o!o,<CʑLL$%FPJ蔦5U(!3]x6Z,/4%4:z sGH:nN}` ?I}i,DcZ3ߠ؎/8qQ@w R@tg rW GRk#1/\WN}`xq1][WB_,__,ܷrY Q2~IT %FЃHdv$\bvɤŨV̩BQ?3U&lW7ƹ`0.M/W`/&3/QNPEVۛ=rEݤ̼jnci388a_R ]6p_p7y15E’i19(IH_s%SgTbQ==/>m4ERVMnlDwNov+1lfQuI.qR@B- lg z O$ 0~J; T&?w=xm8ћ96ï>0~4tpQ*ժ泑|s ױˀ&Kx=zkxz`\!bd&PĽC,L/|1X~4~Aq  q-sǃͅ^Ll-VhR>0K5]Hךˡ.FcA+v bX=棗uNVeNL~$5AYsC_Є u6  vOi+\5"4A`qbP~8<%PPR v0[zְ@D7¹0| 3SN,/Ϸ#9ZSk;=#.4" o6lF<LB]ϩO sb]1uՃ{^Mø@܀W XFLO7p_y7UzCTF[t=`֗d1?!`.UC+ Gm>.Q_`>/{zP<=4sJ6fiylIiŋǬ v X(`hLߞ^_?5hބ0zHKxW]nzu[uqD[ ^7O䡪Vѡ\\0xCLv9 D GwBɾǨvwQlH:B'EĮr%g56])$b"K_& BtCf-,x9\"-@ӧ7e M~#uJfL};C4t\ tK*HNc|)˥}ML*!P/ҮO$LC-My 8'T2/A$ Ćslt23\sOm4-h!b9l 鄮w'LEơAlyi/oTjVfIESZ{Q0zQ5FÚCZlwܛPP͘m\D9(tw*1aPsZ$dmh;cDN#W *!]x#AWfQ J ˄]ق  5td^20U34BGyMX.7^9M, !LXN<;+ts6)b /H! jϏ馎jz"qo;h"A$HKzCC_ rx, 5}roغQhQ--?C*MӃQn!}!ҷ.F;6E)^ZTX kUtp*ԧO#ZgL!֊|vv` vFmY^:Ϗ됴(1p@%QVsk` >*` $9Pky EXMM|NeRJG gH/2>.#R0Đ$\ xBqՂSMk`wAx!k=$CDqY (w (}rzGxDV0X^}JL;J-BgF嶆uv8$>]Der_Ж *BI`sӈ>s!/6?J-b}j&^KqqY=#Z`* 70MH}2zXCμ7z?HmP&1x8HUd֔.[?m]/OAd31 >P3lsZp7HH,Εp;GNGkεMH&E~ͼ۷S]h!?^hPem$RHB?7ӣ-,n~@ɑ[K#_~H'MnGr lBѹ2˂'p W{""yZPW]̺ $Edn{'g˂QOl0@Qao*63T/(RMFhq^Re/`N5%u/ F\/.kߦpj3 KB %lA]WKgmU@O|:.<0T6zփR,S"(8HjYP>z8ItOR'RszG?ڮl,b<ŵ%s4lؾ*;b`ʼn`)}[Ī~!)s%%O%-v(}mbo-yv[B=/n`Rt򣕽"E<S A %_HiPQHP`;ڗ }^-Fҝ>H'p="寎^1|9bZ|ϼ!5|.yu? cP X=Lh9'k? 0n?MEEcº YW4]9ć@/jM.Wk<ՙ*2Uk.d zL湾G=aLxsڞ ?/XARr~kYCSṃUYшm#0KW盎Vp cX+,a_S(;t w&)iGXG^8qڦL%hϟBMI!tf(~DuIzpiÎ^1,5ٍS 2m҇}h&^vY]B2e_۔%-yl^#g3^ 1@'T` 71yXHWRAQR*vS") Vbr(OMLq=DxRUӮN>q_ f^$.%Ε:Jp8}u)J$oHr-A>$>CiRtp j\Byfۊa`^LϔfG|\ F%ZdBJ "+N-*JfrjSk..{ײM{R毃NJZ6 2H'E$kFSRg{P{&c"|m9 3obA_O`&;SVlvŨluwqkusSH*ޞ:cI @TN,dmq^T"^3Aw*uT3CxLj͈[R+ҝ ={0pwjn0ZUSW4'l l8DnfN`cR (|gO M&?lE#`Ę[&&[Dg= #\ 2ߩ卟N ^)N9.iYOQaWHʕ1Vuer]$T8 lfd06^KZA@뉇DjxnA/tlt7zH A>6N R̈́e * ,; B=a<4 =M$TYzpFݪz%@0fƷ/:o0ύ_} 6c1 ؀6UY^ 7 U)4 Yn"~"hHuӺ\ĚĪ_AׅV!0b5MNٚ}chxGcLW7ES}NT.?`V? MNcENVl)x0dHJL)wZfܯ@vįVn*AX0"ċx`v7fgĬz@^'e}LmU-;>Tf HWdᆃdS5Fj%:C*̔CSϛ3|PyݮF]XkCX6A Ë]t,b[ . aE*],{Y?.cԴՈܨhPMw]5g:UG}/o Jѓ{@R.6R:gN3izMO}f4TS(`褤| xs˒G󙃆qf#ű0뜟5"FY0"N@a2šh lSTʃNjܟ瀬/ja druoI4~8)֌sZJVʳ;VM ]򷯹h+m}0I!LO fvh v@dW&Wz"kۆuѡ ?lND)")U/c嗵'޳9\i .M,ԕi"4`bg#'̎XbQƣR~2A dhpkM4r>KGM-uo9?4kK[E?u-]GnF7aO\ $9-d _܎iX{=P{%X,@>*Wjio)k@ʔd%c0lQ+='QjC.Ql:y}v <[ cҾJF\HNN_6`//2.LvclRN}1ǯ蟾0>- Ey3}qPpl;BLjy\npSYhH1 JB}N#_}}lwʺ Ka_UfaXcr nGpٷg`]W orP[ AJ.+?yT&I00A|K]+ 'IQF!T\axd g)hцr@(ruB%u0}SDu;_4B&l' 䚝#7"{egf p"wKȊIY` 'zf\]T?' Yx]xJH9_n02x㟙8H$wXHR-ԟ198Q%CwCSӁoaG 10*JATݎe;8V@_fiT}hO4~TkZ7KN#qJG%##,B":@k4'A`"8ǹH59C :L'c0/YĎhv^IX ~3KÉ?/#P3.ՠTl|mK>tĻ5BFѵ v_0a 4@lQ4m(LGqS0dM0.Y=J)x؅ua#S62B^`!5Ud DlO, M-_kL!#PGMny!ѴEaWDFrwUtHCܥ^OgA06t.'ӱ^%;F\#:K@-]CM*}!+D&Lwi|3RQEE$"ݓrlp ?+ ޡmxL& %sJ6ŕxDg peO~!08g8-ㄍ7tZ)D/Ǯp%63X/u BBnK6WYYob@=RP!Ez.&ƿ+2J`,x<{: +fI6D?\p+0 "ȷa<Wh|PaDuq$\T'&ۘ-&(ytP}ي{mMl9q+aK&{}.вe׾e1B)/v0 i_@ ݅pJ+5Ը_&?^7!@0섩2Go7ǹz`ӔI*%\Y4 )JV )yG/"|ݘÛS (WfI$6X*CJ9 m;!L:Bɲjд's7 r!@w0N2<0vS 1ԗZDzV_M7|};+u؆R 1 ]Z(޺dڻ{JYğЕ"l#[JƕPT)ћOB6Q=_| =˩Rٷ3LDF< ouxxt,H„(uVL5u^mAP)t,k!\4a=sWrN.@ 3SGͪ=]2veK"̆*E\ f 2#0L/"eSJw9z%WhWHWW ՍhQoomH:y~͗:vM-D>T8IwMc1PAMam/y.Lg 'Fͼ=tm \dErc/(pk@1/!yRUbkHhݱ ՍM=s9x1 UD$HqX8IRbQbZqZF \cΓ[s)5PGa@8Mq I*ܧw%7ljQCpa?&^ʉMnh;Kt1qQP̨:dGp0'wф/1>pjpZ5^ay_jpEA~cypXWU9yI?=LA "qS ei$/¯nÅK-)[>uq3{{|hޯF_I<"H b9[g5$\mCʗ2E),sۣ;0izP%mCעnѾcIl!n e! !MsJ7i)Qt0pM^T[Yּ ZZX(d4Ջxc#iSx@|!WLC$q4}1ĹFHp0,q!Z.5.)Ծ]66$]zew>vKZQ {/w~+M˾{>!r mT}+Qi3bBQS/Ɨƒ:'U*nwzZSrU6v`^Kl~Άvm& JC~JQ3"@ѵnVH㨼 c^ \՞9ͱV7 y}0#6JelSTڦa4ۣ4r(/G,V'-wwΩ?,_) -1=zTww_~JrQ /[+ACxlqre(ӵ:Z{ :pB(5_r({b_[፧cJpA'㚹ƋD!2NbPvZ'rbJ$;_5(Bcȼonc$G9mDHd(J˻Ub(ҙ"tL㦗iQiFIQ~Ū64|{뎛/ɆK4h+iN)|4?J|+{nW3D:ף [44Љ]]cfC cDra\mu[ۂ^{J2v6o띠VXa?`e:zowo`c5x *(}c( gPT2MšVf%7Y(  <]w Q} 珃{9Ÿ$-,ypn@0G&{*/t_[_Bd*Fc[ 97ڈZϲ[ZDq#WbvlX$V齖 = tiVqT^cZەn7uFǖM@4>y?N KS2 1dϫ̶/`>@j@@R%A> 4{ ,jm?XH/<:Ӗ,^Q)Elc|ȴhqBtD`N"M{-Dbg ɦBI פzolͦy1*M+4@I XES;8mUu[/OI{F7#(gL F^"0}\=T/|I9jI[wӄ {Btun~\bxcw' SĽѯ:K좈, Pr;/K/J4 HjUs_pS/8y:,Фj$,"v (3'$~v§6vavKg|*?t2>OnZN6\T7d{ۧFy?݊Z7zEAǔI+m߷pS9n_q|-d9;kaءskcUzqRq0JtO܁L1K1K ~{[k?bBYU̦۠.XMe}؊uA#0B :*هPDjk{}H[KS]o/b@S8E{Tpspϩ 3J-~'H*|\˴X8RdKHZ"d5^m (U_![K^&a (jZ}1!)1ujwѻ$ē|3*=B/hH[/ N|ӺC8W  ~H@i%쯓:H/[}2n3D|rCС}6:ix8JBaӞܰb$W(X{1 5:VGlz@\]$-VwmQ/ѧʿs"9-4_2]嶱vNO}.i86!o0 ɤe:lWU8eֽzFbY[IMmvW D;9ų (cks], ]W~P%\5GM$A&% &6c-|qYIe7]1ѽ_5HA[Z&.OtLd هopɢnq*#Y:ClDm5Xs愓Dy eA&6SЎ znA/۔*Č* )ΝBq:r x~@&c:-g=kX1dd›k"^y%bc':tp[InϏEʰ1d;uCkư|>!y\Bb=`*5L#7@DTj{E$*4'eĉFdLc^ #6:?n r} 8RhO+y rߥohPq 9AApi\# dPĞ1 8r'Pyr\<n\sfOM!v4zRS>k"Ĵ6˿H'Ve~5 @- _pՎΙ$[7(t-&k5TpAĘJO?gydO!o(p _TjIؓ(5_9ns6Ut(C?Ĩl[h^OJԻr dҺΏ9ҶXG_N,\wO_.:vY8t)Z!97eL f)-NaVA8`w ħX )ٮu幬xK+dG t+ß*Ia1+$ Fr]=DY|=/VR,wB\Ӳ_T*X?x;DJr3m}.5 biX?2Fy&(,MoȢ,ŞRdbzxhFfL_㖨_W|DΞ5ǐnMF=idj+qփ N{>SYl' MpRˌ4.#QAXjy*/?;꾢>ėLq:z,>&W{^euMTyJQ*SPR훁?g3gn4?_vC^qDAYĩuCݷ8t}a"3+oir}1`SB6dsECcŗNbu1fG'aPbOd08*& gҿ4=2L2>NI]S ~- $*y]MT fk?M Ӆ~rbL©ӫ D+Ui_vԲ^w:YQ5yjs1Oa sPt>:G&pKPHhS! t"zUI޸t="C?Ze:SpIWRX5nnŮaCf:T}.|NuY@EH%^ 0•a(d*sX/ p/L*];u1u9؅9jtVpAЈ *̄`Qs!ka:P?-Wmev¾ nOC7ͼ`NQф*(;aIZn(dZH'-UX1]F_'n#9qYkGƉ`Yf%S|uf#m!#Efn>UU +^M1 _1UIsǤ'RŦ3:'FaCD?zk8 BD'וRѮ L 5Q&k,.?qHƖ/˿R*2 ?M8E\1_z: Ӽmmӄx%?|0_Tn+O%R4?Lp{PeOrub \^$([UJϵ~&i1h-! ~ tj>Hۍ LǔKxos@10&Sr.mљVT97L>c7}zUd0q  P[TQdU`tE; d1Gt4ŌGHTe,cZ؍uv 9~ ꊑa6Ie<l;,n u y%>=.0/u"D1*'3:zs"TQ?w a:x׫8 |Oi OSKۨ#(/+&E>8Cg LHl%_6]%\p8CW5t2-9a'xJO6 =ܢE+118pqWPa:L&x_09OLRpP_)Tå*HHm/rJ }y, -z Nͬ@zڈhz]7}YO'X3{(B禫bukZ]1nfwRwQXåw&¦{㲺2M=vh3<C&ǔt3|pf%hiH{m,_{%q^:7 9nu!sŬQhX"lWoy{a6jy32L``zSqNSgWoW!x;ڢF #+\6&F:&>sxM>Y)mђA$LIeiݫK :~eC.;(K[Z LbV8 Q!9St@9M~R&_ GgnjbyUlQNVlH"bWNU:;mcw2Pr Z#Z;[Tťpzs#Ė &<,~"*}|rR{u[>:pQUpOͶD\-E*F4+p|\+τ(. Zx D`3An̙4cr-Wsa(WEz4> ՗ruR}qJ_d$KJx&bEoׅt>@mKB`e"ޢzbԤ`6 nWS.=Kc‰@unׅ76}+5-F&/SƉ|j*ungw17!Q @GF E\`dHClə.8!wo~<͋GV!9뚯)C#6 cB,ĞH4+E(y_@f@c#;,1 L nx:/,Aw t8|(4i/N BK#X)mY&ʟIO׊'ROϟ&Iz!\sfRBi ?$ ހ+xRju`&N-}t@#r aH6"zc/d"["h/ 4mDu6 !eЊvhYg1DS3 5C(֧@j\QD[g?ma ldw q`/& 9T^pﲇi>Z0L#&Q0V)8qXmqEg񅭃+\Ҙc[gAT '#J~ineqlK6 SPe"mbT=?exaA7g ݃`xu3Zkx/lz{Հ=usFA <=h^Щ/H05

ǹˬ.DT6 2W?zx<5+q2%b?[GՏJeynr A/[64T0Ű\xώgh@{y0!DFűVp kB!@wS1 [T" hKYuRcLvAwEr7 Uiq 'd};P}q_6,b*6iI݆c@I"TCqec}k :8{ :PLDtma~*39XmL5wurW)2 $4.G*I#E31Zl? I0fىro_jnI+22lH}>3(md&dkd©/"uG:0'Q+Uɳٴᓣ\ !8`; wر(#fjnP:I/[C\}aY\ T9A ~*TѿFw>THAu&%aR   #ؔ,|"O9wD ); n"p ['+s/SJ@}e L6߼b$3炴5{xtcyt(ShfoȍIJA` N7oA>gҞK:*vZI¶KUFvQchY繯/9ѻ{U|b>&C%h Or ߎy(=-}`h$3H@䜽YP|2=׀B>:$;["GySma1pzٱ~?P'έ)4q+ix`R8$JI.習d TUI% |4e_*7\ܜRh趁y҂&\g(洨BC{ +mFinzn0({П4 @'yVI*=.Wngغ)T3 ^&SW_'>x.0#'(ڽIR5Exmq &Rcs ?ei!e fdv^ÇWyt_m`F>(:C?Ӗz.Ӵ6ԛ~kA#ohfR @*].Mƞ'Ua"yHXU'>zNQv5bd^j<[sd6;F$Õ);@&C]@} &'Zx 5ڮ+paGr}^S ֱj: E7<>,> XɑS4G`2f3a0y*hم*ѫa,v墩L/qy#a]{v^*di8~h>RqW<KIQp%h\S?~4)Ř3O@"0@TS^FM"W=]. &j\ɊVHy6NwemKIW&S8em6. JW>whKXc|.e%jglϤ.ѺC( u>t`TJ?~g2gAlz)bC R8YKiȜj]&zciV|r)n/D0v)>nqJPo'$_djoJ`q`KbXT&vu9{:J\FEj_ft ,D&<+g][Q_<Ұ J =!itJzhYZXo $m!=b7G?Fַ͈#k,JE9u،C.ttMt^sHW;ƁNM}3Y.aЃr?~)!U Iעn§SI}3 AS&85劒پi6ɜ=!2&N2yG9ps3[ 84-mræſlԳZN)G0n4{x1;WfC8bz-*'*p:WP4t&=$pnZt5EjfźX08,sR}'cI1E82$ᆺq+4 bfE֟g7m*f.w!)z iB]h4W-ڝ{y|xTp[qq``3uf۸O_NJMW~oIV:m1\XY܋؟$]n.) dBvEϬ}4J>e@U3 ȃ+@0B'8ʮ9v_A ="7 cGEKYL+>3rRZ 穣@wӊAy6rBr1Dnnzsa^i E/.v+r饍[< d)Wp9ŤbIj>}Z}817~4UEFIy+tBtG2MSvS~E8aRi-$}qU}~/j;W cj9RiQUxKBCJ[6򌇢A`6֌?ěیun._5Cm 3r5;9#QAفvKԏu QZ|rfH-ؠ@boxr~7WRϖ'GmAM%T GPi@%_>-^-J5)io;Szl}ݠ3Z%MG#@a<#%G}q+2/q6 Zxtu-}Vh`a5s/'.re Þp| /M ( d)2֡/lmBu.wb\oB ^{b1&8`S|ԏl/tG\8žWzuF\2?)߫hQɦ,I;RϗiL%;/id^^Q<$_ !p|)Ue+ZREE| Aij|r vbw5] 3ԭ&DbM!¬\H(&@a3GtY֩+=Nw<1[[ڛ<9H6@!9%pe]woAܸ%.!Ccﷷo8Ksp__c)=0(߼!If[f 3ד p# O6<0ʻp*/_ mˀP ~ AfW.B&Ƙ(/|1n ;1c%