* @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\notification\method; /** * In Board notification method class * This class handles in board notifications. This method is enabled by default. * * @package notifications */ class board extends \phpbb\notification\method\base { /** @var \phpbb\user_loader */ protected $user_loader; /** @var \phpbb\db\driver\driver_interface */ protected $db; /** @var \phpbb\cache\driver\driver_interface */ protected $cache; /** @var \phpbb\user */ protected $user; /** @var \phpbb\config\config */ protected $config; /** @var string */ protected $notification_types_table; /** @var string */ protected $notifications_table; /** * Notification Method Board Constructor * * @param \phpbb\user_loader $user_loader * @param \phpbb\db\driver\driver_interface $db * @param \phpbb\cache\driver\driver_interface $cache * @param \phpbb\user $user * @param \phpbb\config\config $config * @param string $notification_types_table * @param string $notifications_table */ public function __construct(\phpbb\user_loader $user_loader, \phpbb\db\driver\driver_interface $db, \phpbb\cache\driver\driver_interface $cache, \phpbb\user $user, \phpbb\config\config $config, $notification_types_table, $notifications_table) { $this->user_loader = $user_loader; $this->db = $db; $this->cache = $cache; $this->user = $user; $this->config = $config; $this->notification_types_table = $notification_types_table; $this->notifications_table = $notifications_table; } /** * {@inheritdoc} */ public function add_to_queue(\phpbb\notification\type\type_interface $notification) { $this->queue[] = $notification; } /** * {@inheritdoc} */ public function get_type() { return 'notification.method.board'; } /** * {@inheritdoc} */ public function is_available() { return $this->config['allow_board_notifications']; } /** * {@inheritdoc} */ public function is_enabled_by_default() { return true; } /** * {@inheritdoc} */ public function get_notified_users($notification_type_id, array $options) { $notified_users = array(); $sql = 'SELECT n.* FROM ' . $this->notifications_table . ' n, ' . $this->notification_types_table . ' nt WHERE n.notification_type_id = ' . (int) $notification_type_id . (isset($options['item_id']) ? ' AND n.item_id = ' . (int) $options['item_id'] : '') . (isset($options['item_parent_id']) ? ' AND n.item_parent_id = ' . (int) $options['item_parent_id'] : '') . (isset($options['user_id']) ? ' AND n.user_id = ' . (int) $options['user_id'] : '') . (isset($options['read']) ? ' AND n.notification_read = ' . (int) $options['read'] : '') .' AND nt.notification_type_id = n.notification_type_id AND nt.notification_type_enabled = 1'; $result = $this->db->sql_query($sql); while ($row = $this->db->sql_fetchrow($result)) { $notified_users[$row['user_id']] = $row; } $this->db->sql_freeresult($result); return $notified_users; } /** * {@inheritdoc} */ public function load_notifications(array $options = array()) { // Merge default options $options = array_merge(array( 'notification_id' => false, 'user_id' => $this->user->data['user_id'], 'order_by' => 'notification_time', 'order_dir' => 'DESC', 'limit' => 0, 'start' => 0, 'all_unread' => false, 'count_unread' => false, 'count_total' => false, ), $options); // If all_unread, count_unread must be true $options['count_unread'] = ($options['all_unread']) ? true : $options['count_unread']; // Anonymous users and bots never receive notifications if ($options['user_id'] == $this->user->data['user_id'] && ($this->user->data['user_id'] == ANONYMOUS || $this->user->data['user_type'] == USER_IGNORE)) { return array( 'notifications' => array(), 'unread_count' => 0, 'total_count' => 0, ); } $notifications = $user_ids = array(); $load_special = array(); $total_count = $unread_count = 0; if ($options['count_unread']) { // Get the total number of unread notifications $sql = 'SELECT COUNT(n.notification_id) AS unread_count FROM ' . $this->notifications_table . ' n, ' . $this->notification_types_table . ' nt WHERE n.user_id = ' . (int) $options['user_id'] . ' AND n.notification_read = 0 AND nt.notification_type_id = n.notification_type_id AND nt.notification_type_enabled = 1'; $result = $this->db->sql_query($sql); $unread_count = (int) $this->db->sql_fetchfield('unread_count'); $this->db->sql_freeresult($result); } if ($options['count_total']) { // Get the total number of notifications $sql = 'SELECT COUNT(n.notification_id) AS total_count FROM ' . $this->notifications_table . ' n, ' . $this->notification_types_table . ' nt WHERE n.user_id = ' . (int) $options['user_id'] . ' AND nt.notification_type_id = n.notification_type_id AND nt.notification_type_enabled = 1'; $result = $this->db->sql_query($sql); $total_count = (int) $this->db->sql_fetchfield('total_count'); $this->db->sql_freeresult($result); } if (!$options['count_total'] || $total_count) { $rowset = array(); // Get the main notifications $sql = 'SELECT n.*, nt.notification_type_name FROM ' . $this->notifications_table . ' n, ' . $this->notification_types_table . ' nt WHERE n.user_id = ' . (int) $options['user_id'] . (($options['notification_id']) ? ((is_array($options['notification_id'])) ? ' AND ' . $this->db->sql_in_set('n.notification_id', $options['notification_id']) : ' AND n.notification_id = ' . (int) $options['notification_id']) : '') . ' AND nt.notification_type_id = n.notification_type_id AND nt.notification_type_enabled = 1 ORDER BY n.' . $this->db->sql_escape($options['order_by']) . ' ' . $this->db->sql_escape($options['order_dir']); $result = $this->db->sql_query_limit($sql, $options['limit'], $options['start']); while ($row = $this->db->sql_fetchrow($result)) { $rowset[$row['notification_id']] = $row; } $this->db->sql_freeresult($result); // Get all unread notifications if ($unread_count && $options['all_unread'] && !empty($rowset)) { $sql = 'SELECT n.*, nt.notification_type_name FROM ' . $this->notifications_table . ' n, ' . $this->notification_types_table . ' nt WHERE n.user_id = ' . (int) $options['user_id'] . ' AND n.notification_read = 0 AND ' . $this->db->sql_in_set('n.notification_id', array_keys($rowset), true) . ' AND nt.notification_type_id = n.notification_type_id AND nt.notification_type_enabled = 1 ORDER BY n.' . $this->db->sql_escape($options['order_by']) . ' ' . $this->db->sql_escape($options['order_dir']); $result = $this->db->sql_query_limit($sql, $options['limit'], $options['start']); while ($row = $this->db->sql_fetchrow($result)) { $rowset[$row['notification_id']] = $row; } $this->db->sql_freeresult($result); } foreach ($rowset as $row) { $notification = $this->notification_manager->get_item_type_class($row['notification_type_name'], $row); // Array of user_ids to query all at once $user_ids = array_merge($user_ids, $notification->users_to_query()); // Some notification types also require querying additional tables themselves if (!isset($load_special[$row['notification_type_name']])) { $load_special[$row['notification_type_name']] = array(); } $load_special[$row['notification_type_name']] = array_merge($load_special[$row['notification_type_name']], $notification->get_load_special()); $notifications[$row['notification_id']] = $notification; } $this->user_loader->load_users($user_ids); // Allow each type to load its own special items foreach ($load_special as $item_type => $data) { $item_class = $this->notification_manager->get_item_type_class($item_type); $item_class->load_special($data, $notifications); } } return array( 'notifications' => $notifications, 'unread_count' => $unread_count, 'total_count' => $total_count, ); } /** * {@inheritdoc} */ public function notify() { $insert_buffer = new \phpbb\db\sql_insert_buffer($this->db, $this->notifications_table); /** @var \phpbb\notification\type\type_interface $notification */ foreach ($this->queue as $notification) { $data = $notification->get_insert_array(); $insert_buffer->insert($data); } $insert_buffer->flush(); // We're done, empty the queue $this->empty_queue(); } /** * {@inheritdoc} */ public function update_notification($notification, array $data, array $options) { // Allow the notifications class to over-ride the update_notifications functionality if (method_exists($notification, 'update_notifications')) { // Return False to over-ride the rest of the update if ($notification->update_notifications($data) === false) { return; } } $notification_type_id = $this->notification_manager->get_notification_type_id($notification->get_type()); $update_array = $notification->create_update_array($data); $sql = 'UPDATE ' . $this->notifications_table . ' SET ' . $this->db->sql_build_array('UPDATE', $update_array) . ' WHERE notification_type_id = ' . (int) $notification_type_id . (isset($options['item_id']) ? ' AND item_id = ' . (int) $options['item_id'] : '') . (isset($options['item_parent_id']) ? ' AND item_parent_id = ' . (int) $options['item_parent_id'] : '') . (isset($options['user_id']) ? ' AND user_id = ' . (int) $options['user_id'] : '') . (isset($options['read']) ? ' AND notification_read = ' . (int) $options['read'] : ''); $this->db->sql_query($sql); } /** * {@inheritdoc} */ public function mark_notifications($notification_type_id, $item_id, $user_id, $time = false, $mark_read = true) { $time = ($time !== false) ? $time : time(); $sql = 'UPDATE ' . $this->notifications_table . ' SET notification_read = ' . ($mark_read ? 1 : 0) . ' WHERE notification_time <= ' . (int) $time . (($notification_type_id !== false) ? ' AND ' . (is_array($notification_type_id) ? $this->db->sql_in_set('notification_type_id', $notification_type_id) : 'notification_type_id = ' . $notification_type_id) : '') . (($user_id !== false) ? ' AND ' . (is_array($user_id) ? $this->db->sql_in_set('user_id', $user_id) : 'user_id = ' . (int) $user_id) : '') . (($item_id !== false) ? ' AND ' . (is_array($item_id) ? $this->db->sql_in_set('item_id', $item_id) : 'item_id = ' . (int) $item_id) : ''); $this->db->sql_query($sql); } /** * {@inheritdoc} */ public function mark_notifications_by_parent($notification_type_id, $item_parent_id, $user_id, $time = false, $mark_read = true) { $time = ($time !== false) ? $time : time(); $sql = 'UPDATE ' . $this->notifications_table . ' SET notification_read = ' . ($mark_read ? 1 : 0) . ' WHERE notification_time <= ' . (int) $time . (($notification_type_id !== false) ? ' AND ' . (is_array($notification_type_id) ? $this->db->sql_in_set('notification_type_id', $notification_type_id) : 'notification_type_id = ' . $notification_type_id) : '') . (($item_parent_id !== false) ? ' AND ' . (is_array($item_parent_id) ? $this->db->sql_in_set('item_parent_id', $item_parent_id, false, true) : 'item_parent_id = ' . (int) $item_parent_id) : '') . (($user_id !== false) ? ' AND ' . (is_array($user_id) ? $this->db->sql_in_set('user_id', $user_id) : 'user_id = ' . (int) $user_id) : ''); $this->db->sql_query($sql); } /** * {@inheritdoc} */ public function mark_notifications_by_id($notification_id, $time = false, $mark_read = true) { $time = ($time !== false) ? $time : time(); $sql = 'UPDATE ' . $this->notifications_table . ' SET notification_read = ' . ($mark_read ? 1 : 0) . ' WHERE notification_time <= ' . (int) $time . ' AND ' . ((is_array($notification_id)) ? $this->db->sql_in_set('notification_id', $notification_id) : 'notification_id = ' . (int) $notification_id); $this->db->sql_query($sql); } /** * {@inheritdoc} */ public function delete_notifications($notification_type_id, $item_id, $parent_id = false, $user_id = false) { $sql = 'DELETE FROM ' . $this->notifications_table . ' WHERE notification_type_id = ' . (int) $notification_type_id . ' AND ' . (is_array($item_id) ? $this->db->sql_in_set('item_id', $item_id) : 'item_id = ' . (int) $item_id) . (($parent_id !== false) ? ' AND ' . ((is_array($parent_id) ? $this->db->sql_in_set('item_parent_id', $parent_id) : 'item_parent_id = ' . (int) $parent_id)) : '') . (($user_id !== false) ? ' AND ' . ((is_array($user_id) ? $this->db->sql_in_set('user_id', $user_id) : 'user_id = ' . (int) $user_id)) : ''); $this->db->sql_query($sql); } /** * {@inheritdoc} */ public function prune_notifications($timestamp, $only_read = true) { $sql = 'DELETE FROM ' . $this->notifications_table . ' WHERE notification_time < ' . (int) $timestamp . (($only_read) ? ' AND notification_read = 1' : ''); $this->db->sql_query($sql); $this->config->set('read_notification_last_gc', time(), false); } /** * {@inheritdoc} */ public function purge_notifications($notification_type_id) { $sql = 'DELETE FROM ' . $this->notifications_table . ' WHERE notification_type_id = ' . (int) $notification_type_id; $this->db->sql_query($sql); $sql = 'DELETE FROM ' . $this->notification_types_table . ' WHERE notification_type_id = ' . (int) $notification_type_id; $this->db->sql_query($sql); $this->cache->destroy('notification_type_ids'); } } ~xsؕGH"}VZF_;]r=+K5RUE: Rc)p*U$'Y??H$XZ*,Q3iRyT+1^EJ/hXsF$C1KvOr 8}/TwS$mQcezŀ6:(!@|. ["10C}3"31ʶYr'#0rZJ䉓:T[ d,2Bjlv>= 1yfJ6[r;NgZXjF "HS앜j@Pm./bN] ~4<r >s丕#V#S@מfja% Th:.մ'qkƜ9KccmKsv04Cs|eǾw5pW*TY?Tz)PΓ,#8asva~XVyH{Rnn~x!yp%PH9q~AQe`c'p} _oIAcC=nK epwy@,\%ҿ Ri*ޣ}ɮCwM Ϭ}!e+Ps#a_ 4,,}pM } TU_ʩF@v; [~Ȟ #o# G06",;EPV[ G!548%LlGTqY`7p8>6]d=!O߱V^7aϊ~KrKq!6ib /r"4IkX>Q4SA=WYGvް: wzU?MX/HbOؒtN\.?l $< 㢸/eaH!C Kꬽ=eL"((su˳iD:f~$焱`6^7ͬZ{[&CuнS:4,ԫU7uՈNQZXx"gr#dB a/z&x{s|h*h 4օxLwbse8 ~% ona`& \ޤ-ȗ08lHv{# o8Xb]$/<ӡj'T+O8^O@-?΋x1#%cۼ(W=e!ߋzȏ?W4[v& ԖLaG=a4c$znNVPP+ Qw.s;jop<fQkpI<dm&O,c}%`E -\rЏ8D岐OeXv8Q\| 4䍻c:dK@ɂ-[σM`%ݶoe@ J^q;paΘ S~ ARf!R!(.~|>8-*\Bp DAGmɂ}O#@E%0t[z7%nruo/̄?Ow=w)Ca)jߥ8H[+y!+r2 k =ݺFN}Y~ΛṚJfޱ1K5ǨLyu|al&en%\Ae񹤴-܌,8x;+pv}Ӭ"8JgfBƧ⌘a zWHgo*GQgǼ[Ҕ$E1Y"!iBQ`@R2##hgFd҅R{ -j}UbcebAyPd,]\2$1RS O 9BG(Su~~{) M- Z9^bA,EAQf{!g5G+1Q= k5OdD W=rK)MRB`dKGi#py,;)6o2|^7!/2wV#9S P3 &(4Ę2nXYb;t8c>= xg4*&,lZ1w_fF{hF𶲄tE3E&^F3mץI I%=iɸt Os0TJ1ޘz+j]?ab:mj}#An}K+0HWN~#Q<[$C@X֤X\F P8$Nb' /cXHFT؍DRoYyh=Fޮ'݂4GoQAvX& eğn?ԙ%^gf+=Z$9k$9 )Y4 (huWPM @9R};Zhq`~^g/hy(HbkPFnCQ< y3h>Na86-}Kㅰ%{#7`Bbp] [#f.실- QOh$ * 7a[vs^l].#\=vC):isMFo臗9C [McSOT3#ޚR;f>ːo/PMd(d~lċ!%10y4AIJg:y8t趾WĽ[TKo737'̑Oq!ۇT*ij 9<,Ӡew 776]8bՌP.Q8OkbC>|fL*"y%7~_qbai? :t_@i.&QԾ)ݡaj%8'[&fֵR=z &5FNM.x$ܶQۄiJNp>RpڅW|VqΜ` *&a[%Y~v"Ȏje.,P,:g~"Y{WOz~;jG @`]zbl@@?Y  1$Q!Bj'`["فʂgAr* \ 3P+- IG=Y@.΄ΠBֱvh|FՄTjyQt-1W z``\*olr럩W]AD1;ȃS* iv25' ]~V ڌы_:g3#< 92w_ҩ[{ZsP[fjGE^sRG2OlEkӒ5q,cw3Z̀̏W$^.Qֻ6vϝ9kP\j9,p@Ϟ9guoN&l8 QIHtB^j2 Rϒ9sό?zӶ˛`yb%2EP $v\hO] ~rK`~ :iv4o@y~6g6tP:lI`>ukcD z4OO>A̻UqXx*=./r8LN}gɇ_ÎL7|^$4ۢZ}}3)9WuQC aB<μ2BːX~ͻ̤˪saɇZ{gS<{,OqxVi6%[12`Z⏗TEEoP株ҷ2<Ë4B*Jڊ3:1" q.G fr"! 8:f]Ά-@՘Rxu ;O g ͏8v<≳8Ai D"Z۪|(uU @LUICY yo!:A!ΤUTR 7tP UL6G$ODDcpNk}/Fz󗪑,0=w%c SzV_[R'?4U)=7!oIۥExӑD}Q0I@Y uw% wZPev}„SWk#.ʳ\SJJ%C5"9b#6RD%N1M|'X۽'F^%Z@E+/\Er~⬾Ѧ %2HKzE*vxz/9,aגy񒨁«5F, vi=7}7V!,\7fmF{\m罉yuL٬y+ε*-댸n}"5e$@oL/xX+iw gEesv?MWW4c0_h|d0XT|0|6+/0W~&9JذKvQQn? 惚tHRZ`(g1-[Sdlp(opf ӄPK)ؿs=M'Xċ|(71F"5E+:!+VϯQ1DDni }$.2-LEUJ %?wËɊIOFWD]Qy6#?r* F'}8&b"xRB$$QlĿHI~q?P 6SV~]x=-POh4!U֯(A̜gBq܎Iu#ղڴ۝=[a6\ìdl$Ayu,LmE`1=YB.~0=8>xLT [Dy{M(\wS0]md}‚k$MeCo? F ZcG2"GQeӞk\78 Ou ]+* YCyyw+;S>VƜuƫv.J k 1d -K<,1#/`(CC}n Xq,;zJjD|Jf1ϥ /ƽGq<~ *e]pivY%4MUyjq}L&<b_E o!5b/q&=%^'2k4äߢ*Ѣ\0m6U irj PJ}LEK\Щ6M+ K7w{ޯ=7#d R`rxwX‚!Ǘxn]oָ';q68om)hi@n sIFg@:PzB$}B@ _]V$n~K8&:3 ;!\w eB6gёUi`ųRcϵEO3FMߡ/24]cWHT-®-ojm-b8ā \Z@'WԱwt7/1>^M.@2q٦kJo ގz[Ur;J 6l Ԏb"E)093?EޝWASg#=SS+\M6JB 8ZY A?:LڜO@$=:WCʩRᏩv+f}n Zϩ8 `Gq!TBӑkڂ/񶒱 h;vxX?y3 F‹~,K`}l4xD!֦!$xZdc=E EqrCI<Rm x/ PZ;;)mzE&񓨛L0k.})3.*?=Xt+c0]^@fdŎ5wǎ!t[_2wtRΏc͛#;B=.3 hBFs0MmB8ճ-$ \-EΗ}EK3I Xb::ƅt@:7!+Y0-֧D^[pu}= / 00H/ˑ7349OL==&&Zk'!?.-.v=2y@tN=:]0I>lZ≨ 1۔Ј!ݞ"k`A˶FS-C`;aKcu6\7WW>sD,Tf mtzƹEd0U[-_4 -OӕJTb{4𐇄""{8!{uPI/Q\ P~FD fo|l~*nfLxpm\oA@^Yk/7`i L)4D!˸ݣ#@ƸR1 fɼۚuPe0/ĢFuWD%۔`~4&Q:j]=_gm9h/6d,0}J,%" ;&Ǯq 2Cpv 6Z }Ou'FjeD#4V 4!~ԥh>Lr7@ ~ۯb(f3L@쫾xC埳У6 R/;xmXi+|/}xUzijOSo-j&1ŵh-N` %>(]QY-gs(?EgAHv#nd^%1x Ӫl8kiy* 4PDaNZ,Ȼd 8-'u nj]ITL}_M>Zj/R&qbchw5" 5&rEXeDyl hCh ޜi\- vKc PkN*xi`ljrb@錕`%0fS'Â.3H8xYj ֦#sֿvZf+Oc څget%* B/v0o)&r[#Ɛgӗ'n|*cE){c#~ՃY׉CҮ( hlwtgA¦vi_+ ,4e._[Z EgB×ٺ<:hyS9n78 r+h6G- q)8!%9/pe7~)%ЏvbU3c% [ntZJ.Qc׋Y$0&OoKG'lԊc6K:#lF&EQklkӸנF=J ҖqQ`ށ_z SLD z~u ?Bycl P2{gw0_awgٛc2 º6OфLǺM[p<(_ =ln(eʫzUt i58_X潄9 0l b&ҀBzUsi.2]ҳA2҂IF'=펽h+CT8fޮ"҂^Rt&+.Vǔ&IH6/I2Zf uQJp#~|Sq̉RX^.PRmWqXEku]?K}Z3J1'!Ѯ $^arFV~7kH:iD@Bq5r6Ƥws%Ŋi+_]V" A;Ta!lZK X.j:1ktV¡.I4m@Ie˜a~!u|Hw8Ąߡ_B@:v#Q~Y@NVqJb;!Ĺ ` I9&|ȪnY*s[#ZYYH1VY9*>-`{Ӑ&ը:ˮߖp,a(02/3O{==w@ppJ:;Wo7xp$[u.6(Q~w g-l;_Q0/7T%,r-AGSTy\ߵ،['$sٗazڌ`"a>᝖}SC`9$vl6c/HHy@U$|{4t5b6@S_|b'TH[FHhMKEMt".EaqLWO)&X:b$<;d/!"U :Ò-EkB$s݂kgzrqc<~<?s5 Q CAS/{&oТVCI6ÔoSC{@ %$T~ `YB ʭh0}_$"+1!=G5U5'ȚB}Bk7x%oݫ̋҈NNGH`k4#Zک.'X.jFkQ&햤 f냺"UoY( w7k;q ^x=oT>1/L,kU e]F L/}z9y_ˆ^AٷkX5ӨCUzdMNۘY Dss&7_ǼXIulb*Duc 2  5ҷ ~8s>s쉵vls{J<NBJ{Jj?fk#Ǎ`4T8ZM>QM}BkWh)>_f }O'wbl!N8N3€quGKuأCNKwqOY|f2qjv 4Ȉ8"RļyѪ"і$gޯ~Kmqs"DM誱p Oܓh ,0;mOv)]0.AO5l/8Vպ+Cƶ񓓹X議FlE[Wv֧ή4!S>ΥyUCsܱvIh3<:,-?012O"KdRuG_q עo/ *IZ׃*̺`^=ɻc_%(`PQ$oje;2Jlak4 t㘅D5nj"f{SDQ/`s?NCXvL:{Z_=Jmda]U ?Ά^쐰Ep: ZUc! ([,bs @7=,w[s(1EgQm sOܕX0LTX4r]- Fߺo9ctoETkq҇B&lK {RCBN)k};jv9eh S"SWgIڧ DvuA_ b/alŽJ:P *5KAaNa]b"rr]Y 65ia94Z- ,fS.B Pi-m"7e,A*1{͎$ PKy~qVcBaۻA8aPaWt|d*];fV|jʴqxi3JVWl}i&JZ3`ͷ¾cgcv$iфR'2j=/m ^^=߽PrRN2Y:MbK!:'n )A/e}hցˠ2Pjdnzh"䠭C[}}-jʘ~ ;3m1|˂Z|)$t0XȏAtBBSP{V4] % >A/7ɘ^>""'`uB>#Q`0J 2gR|7"yMZ DjO8O8݊do]*?[qA75ZXb,7+] .qtAIPLr! V* d+"tp-f>a&V`%.*~ iN!}2m^O˯i޾V?;,RIe|!wE1 3xP{va;jvpUcgRt ќtN)'d-fc 5iֽN\ jҟ+9 \kbI) *7;_]\^J&P(6<2m+Iu& ]K٣If:Z%X=LchK*U'= %!^@<}In׮Z$XHe)FIs<(GQT}ݵ ]0 1,ӭN+v[ GS"w˺MåZeF,Q93V;ZZ+cPfD$AlF"zKRe~u]͛z7"f=Y횅2moc7X34biS@X<9(A=i4߱A#I-O!Ҏw&ЋEUrMSy3ޢX=m矆W]r o=Bw)my8F5Df`jQV_+?`A&8%J v0; A)b43(zVdi"76H=h+d5M㱥y (2IgjLuayd1Lڿ.eҞ|1qljWw׉Yb:.Ki4f>$'on$-/{ A Veͤ$N{Yof%gvkV楗+qTq.n0bwUVn1|$)F>~ehx"ȴIQIƞ.;LJv\1t%E +S#ǃWWH /%`'5J<^#ZB7ALcy>ovvcaV <‡ zןy"qն7=3]IbUgII`lYrGzX`6lB*٤ҝ| 4{[j!matEA%,{C7)dV<}X"n*MB [ƻ5v oIFTz3:hb[#GB* F%ɅEѶ-}3BâUms73Y)}g*s800pjm\ /ntn6%:!5aB4w` aW1;l'GҜ$8kmѺ[!p^YJukإm bjdb}-+*dр ;8+Pr,}$Oioh!ǫ ~B~CA=-$c Olwzpa|zr[|I߁l3ȸ"f>{#mzCM}b#eqj'gB08v#ջ|uO߅ogSY=+gR ܳM(b7_Jz_x0N@ݫfa}qNfUSB zyEl"Wn /Z1(W7W%̠ nH J"5nL՞9& &("׷v>ѓq.KNpK7 "m6g'#|_qĄ2٠eL3-ˢ_T1=B>Dy+RS cx*{u"[w'c#..4z.* | чcv`L=[F&vՕ54;I#f;uT[P7&LJ頖U|bɂq"ELh%|G]&^$F%S f<."Dَ S Fє"梄qٓ{ȳvOU3%p}`_dn #)1ÐyjN%U]>e7:&Bt>$`7z#b: OLbO1ἸkD#ݨчXɹۋT2,/9qY. )ś=U]l4P\gXgʇ7D3E`] 2S>}ڍ~Ŕf5 d*~ե .bW|rOsvPz ocۅд<8[UJrs4-]w+E,|JZ6?]߷rm@g ivQɴdjvڗΒ~RDezޢ^٪voCΧ0]/6a/`o}oݲY(z:$90+mVa:-E˯ӻwe{/~B&BвJ 3@~Xix7DhhqV͞ΞCeN$zf&|/n `EQіbZ2/L &)PI|;5)74z|e׸$+[jXKPҡ)en+F֧LE%D} =`vu#mU#͝]g;否:h2MDoZϮwpn KQ~,C㊷w|Dnq`b&4[l]R%[8E7,+, rXFNjUzs{ U:C9< n "ӷr%bOsTΙWH@UBslbhٵ< S- 3=;"j(m9ωZR_i+}\PVBɭ)%~?V`!Řu{g7#.0r/"m~NL6Rc:Xn VQL8s0Gsl:XyK2GtlCv&u (jQ;W.W⍛NcrmQ፡/O|B'TGkoKMT? ^4ç6Hkgdk~ _ZV0|'HYUYB~OQYJT;sW䠳>42gkXrKg]ؒLRCԿ%b#" M+ʹ__!ߦm~n4[N IveK7˼; Qk {D9 û,o~f}$BsV֙0TsN2'%-u7_o]EN!R<3~ ֨(`;MpzCljAn:hs@uR@24xނm=G(%ˁJ?xgA Q}o'sVPӗ ER;k1@P9DyW!);rMlg F3c82'V@$5Ge,) LA4zm,lԗ64z*FM40*GJ,#MM"ؽ@ O6@`U~[ڼe7whrr~٨:պt];)ֆi^_6;4cT:d Y[hmZ{`[;]6d1+9c#kX:B@'Pqrݹ/J6xi/ZXbIJB 4)NҀTZH߫B*ΩVcSיҞP[]-Nߕr "L $Uv~HߧÝDHe8G@T'5n#t%0-7 ߒ@" ]߶!WņSlE>9EyD{h)}Wevp>Ź[gH&d2QTBRMkcc$ '%4W6`-{i ܼ޿Jj)O9$a7gSS ʡ!y9p7(D⍀^xW :Z~ew?4@ 7qP; },8T$QLɈ3d9O]@`k,&mÏ՗běn-BU V(TT~Sֽ76 R c7K2DcddF[*X0/p``ZKti 7ZL5c@ҊE-+4}oWqx?vgֽ2;,5phC29T]^Iu!Tr45xWbRFW@gF9sZ!8U|f, pfq7DaަLAB˧o3;Q9z%hL5`Qrhk WU^n0kidzA=!li),5AukM iu7jO)m:n$1 4QF0|j_\-oN*H~8|Ө-CUsPcbÞ Ǥ/XbxVig>zFs;i`DYAw}2F ~alk9'Z>.xY=/'~mf,m` >>bG)5 <"+.; %Z4#|SGh[Dmn*aژ![X>ڐΨOڣ'Bu.*7o[2PyO3YG.߁KTT垿U`3֠> {D:@!9 0fsivp%Ud"FNuLFV6F bז2v{J޹ΫW0lRWaV/~C/y2 yOBq(K$8[jI"}ZUEq"D:)b1ßJ|Y_Lg*3#-kFw-zJpNu~3#~lŏfwKcHR( S+b9N0>'JQf3H qHmr9mTԠʼn:xa/e7Q:V8RnMG̊Ҿ_.R DsP Vz\7Z'OMA$)w=T$%yY d6VT]f/d#& w,ȡv-dm2%1 ܆)DM4l_s21 YP_[en]4&s0XJ@Z+R{OpLA~GFwȑ05 {{:-aL +@nf Дs3ĸvqIٽ"PUAqO8pR|Z"IY2uM`JT5>wU&=Q+*" Buo!OxN(v `CY6t'M'D{zeVFC$YUQ="2,#qK{)\C>&25(n ( |onu acTŠ)RzEۥ씛p86ΐ K %Vڐ?fpą\'/xZ9/Е&.eXM[ױ{`l4]rB&SpɥCwc}ޖvUfBJ[7XW [?Ѥ_gxWw"NTGyo`E쬔3F+F|%*1*Zj-EWDV.xfTZq w4K(2~5/D.|!ގpjIO-Y:oC}7nWBnEӱSf^M0y* fܕYXX{ʑhQEeAHP7ՠӷ6y9a3G\)R'*pCY(-!>O:iIb3߽ruouٛSEA'ft;8QZR /Ϣ3C:.kYǙW@3 BXB; 5+bư$G^R,%ʩWc W˷9(s>%Y"BB8hdRFKŤ9sNoeClʒkn_ӏu^e#s(;4 _d]sY.o4 F2q0ی^ƼBemOh)ۧ"'(6\!yInT M\K,7H_ rh5R&*I.Ҁ973v] R&p1'n#xReԐ{/NZqS8eV2`z~?2+ Jh^+*03K2/-eJ> 4IT%ā`Pv yD;H0,|TcpobdKtðSy7b9d@ЅT"R;[ w#ZD5甮u8>6vp9z $f"J̈-UBQx+Ze3"]+Ӥ2ODRiZ>hMsr+L$x>6Ɔ2.7t,$w ^z9Iy/^xƇ_jFKD6JFm/`NԱ'{&;6EUxeCݧK F7Czl氠$YhO)̓cNTd]q?z b\{]jPO > MX^JZ}lH7\i=G%4I{S2eá Ӎң0.?Ƙ.†#C)m?@u^&_(L)s{C:h<`~b?VUwykAd(i:0̦ȑfHLX̚o }4>9\8:cuٲ?oOjy49["ue.rWF]41Pش˕JО;*Aչ2+$" .>flވk8lNya:B{CNGB$X9UQjx'UmO!43ج\։Ue]bsi{l[iǸj:I PBrtt; w[7. l\Q 72ﶂ9mcG"΃pYoqՖo||SDT||XN;c0AfzJ4XiR-L,?I1h' l˻UU_[϶vw%5i:EQ $;9tRlg gYT/.n=1mr)nD/5X DNt<mlM޸iJ=jK엖z$z,FشjPރ_B׽ @^7oXiz ̀9h6wV݄:w:K=Oyhj]g9-5goI>eG羆tw`=S6ʙ Gqς(YlA Gw-GwDΔʂ\=-<\?(g@~sغ_/}@>P8s^ E$@&PHj,Y<ߒWc'|Vơwp6;oWsDE!JWح&vjSkH[CHH-7;v?Z ȯ`7Isü=WM4ְȈ9c!RҌVyMsah4#ft-M-bj%_b&֎FM15j80T{[nt=S_nb|:ϐw0Rh sU \̛+GX_ֽ\[TmW48)-`F!\6ˌ‰sa1Zu SxAe|ܪruDiC.@n"@Rd6Mx}P:DAr0<`L:=ٽdێEX ig:m/YI.4*đ$_F!O?)(\PkJy2+Le@ư *YAM,0f^(gC}hıc-2_< E&Dn|[0:eYH2{Y:KӮɽ! -0*q\)~M[2+\PJ%B){X 1-Ԋ4z=V1orM<^/{};@c?s64q 2Nlݘv53^*$zOI_MWѴ$UAh->iD<[D٩nޑ.kBMoo#'_F'_%( ë8ҝ{:OA}qq1hOkmOuvrVי;F5 !%t.s=]WXv  W{T QH5{"o K-*VOif%^?nNQ1}{+V,!7*%F[ƷCM1S-n;F?U)Sͣ*edlE78R֯=lPܫd+'n}V$er9Ł{؊Τ(SW(@kݪ$^!r!9uwGigmdV*HYl"ݶp\?BAd-E&B)Oѿ%l"+N{sR#eAbGK+)#k[6WJ7"~.{%~aety dBؽ&* mzѦf /*dn 2NgȂ$XO0f. F S+:D̪"To52ؚ'|z+*Vmɫ'$E ӻyFSPFNU0pZdҡ(ڍW jĦIs mMB2(NpJoJ 2%Z?,8Wt(NpJ*#|UlV"A _Y!%`0#L]y:4NtqLfH?ႊdl$Hbc~%y9 +G SBmȍPIKW^!dFYQ1=D]q酬>LH:) yCznR(A0:>C{Vj?("oT? bP0}[ʤi4=dʢB;|΋RR-vfBIJ^~2RȩF ݉w !z_ZiE('m#_oÒuAq{ sO 4ёNz'B$k= tӧB;6826Op:]rH$&5^[(N$ʩצ-*;s&ݔ+YC z|Us9;@y:cA~0A^taLl;d6WoO@KL.ʳHdr?Xp )Vѻ3'[׶kޠVybWNSpwwhǼ2ǣoɅ-,(D OKPQfgr{_ƎqB7ҼkI||<=wNnf(JR#LK &<_/>*e,#>KA1Œ*`֞ $_X|uy\&L -\dwe|1'\:^ϨqOhFcjglZӣ{3%5>4nXt Zqؕ4@>ͥO-xX]'xrI"& w08 }|]W&]uHIZ?GB+Qv.VG#otD$O FJy)L ׂs#ZŠ8˙`Vν,~&۩*\{$gUluCa9YU.9 à EZG̟NY`T@']fb9O_?ϡzKwF$-mqM;Nrw04ʼiVU*!yv,ZiYy~iﺽ ' c!d2ߨzg~El)cf s+Ei̲gL>ܱ,?AIs0D[R۵!ǭ6t甆f>9xْ4dvS#XMwdF@ֈH%" eﱒS+,Zٞ }zÀ@ƫ ɳS=W|h=-+<Chg! sur4%$]f 7e6C bp8#=+ EI 2<:8,V]ʈ6}QNphD)8/U,:?_F)lNrd9nud$&qgfԢzloLߩfiv*po<>R$]@HҠ (ג,},8h%96Ϯ7@K,xkzT\0NT91Q8 ܏sj|OKnQem ca7|ysNb4bQh=CQHuyS+߹:;$N{,x |wvk{=?T'q9xi&;E>*iu'#O; ^^]h~G{<5GW>B獛0؆zqb2 ]>#%j_&$TXNN +pz7zмyZ^"VK"u݌$CdӤ@ۑ-;]B DNts$JTC Kn8&qX4ܧ\ίsұ XĂOo"l2ϸUڿrxR=1?>`1wz>-2N~ PvrXi$5Gj8 v8-}3hӛ^!]qb2UdWJYړL_$c&,0 GzY]" z@{Bh,(G:jޛV; 7E{(/RSמ8XzN :Q-N=qm)lVBJQ/",5,h ZVb4s OMJ4:v t$}M [&"H)v96h1VOl VPtKqa{ 6Id'~"Sr2F X,(:@Р2"zάgTTSQiP6*g7! KΕ?3!E yy]7'䱭-ܘŧs iikr TҨ)u^_\î̫L0=iAז!9737n4NGikPch=^VS,w$AL2k!=" LZˑQUD *dᣣ㸋eg|Oa@RbJ('k-oN:ά*Rʜ}#V>pkS{w"!#zzf ~zm’FJ?_\TS(P"`PS I2* jWFk_*> *g7P/c!fXJ0 \Vd+]K`(!7ujR!ƅwx2z<+ބ-h$pZ2O3 _6:IBDcm0 ď~ޤ 0bC"aU^zYK5^j'&{4'^sr4!%cXNOi%cb^~GjJrlpN̄)0W<8ţP2qrxwxZ"s<Ԡ=0( (E7n7UQ& "5rq,B^{uMRmXlNΖO?.Q)"7)ImEJDL9K3#E@/iX"b<~u"nGqccyq3F4ZB֯{'G)Rm,WYeCQWn#x#[ݭj) ͗٣]q槞6#c#A;Ѳ֕#T]t)GcJWMT~hb?O~ٮL8g8\]RAXUXn|d\ydBs3pHgNk5A͆%{{WC2-JźL?vCM2>"Y|BFF /YQpm!`4NFVu[Ms1jSR5 V}IF zD$Sv@Q~},v:K_軫Qɛ>5,__QL8OK/ac6)\rOD v8xlVFH*WÈk0HJ!r{6>^\niz0Y*/ nĀ}C!#ᙕ.(#ScS/OM^(|fD߿-qGLɰJ4"Ȕe 1>zt AUiH!n(3PhͶF۱b,SW:Jܰkc]#Z~S{Cg9vssT/:α/L'2IԔa~`\Ep0EòdųAzV}/C]J(#.@tCu5_ $I-m0Y\kOE^q-%gga2!qts;v-2_ú?;6Ӈ.Q~ya57F]{,&͈G"2.|E4U{ٻ,NַȖAjNp[uүbn9 A nl$0T -;څK7hjfYygృGM Kx:|5ILR7Oy\!VېȺ?fo6a? /o;;FO} 9ǷXP>qe $ĩ2Lkxzm$ -FEoZ7B90?GKdKlJ~H~ Q|# @}ҏKߖ4BIa>̞ѷ2T %o?!>:! um_2㠶xj܄ͼ ^"UHe7(}a}9/R/C'PDxǞGWo/u*+ydhҊrg_"ߘyCHrG8Tx ܇Y6+KMA$[[=[(Abb{xgjX\*{&%e7M6k !\ wǍ a6D,*I'% 4W+Tܘ.ii݁`M1thPGpY/Yam&`PE+~ٖqC'{")Y\%Zg%dAvsoN1{H2 b:ΠUE9!Ri*vx'h[@h ZBn=9,!ȋ@` bNXri6+L^3q֊Pzep%UP+%:W.͌' xJ2f~Z$,XR!nfBMg*N$IL Ưg4)LtkbH)zM ʊ}?Nz4NT]5v@v&Ko䲇^G/L2d-o7"u{ L+5^p˗cYFg.:F6ؓJ0$!C%T:m%Ln0MQh^< 8DHYڒ}Dvvtо[bE39Lp<<\"DvFeMdnoqqD~i))')WńU:ff0: ۱#In/Z 1<(Z; Psw|^7/g%KSz)N(D$rDFJ%(+s2ZARAͨBN;*:>(bU"CzHVjY>iU?8 ɹkLqoZ)hY.&/BC4Lt ?Nngpo*d F |d<^#LRxFђpjDǴK\فO_1#i kNʋ7Ed ~)ڑp,f\Zp/z܊=МŴy0&kk]f6Y;1K PpfV v4g? e1RN)5p`$ǜQ%׆duoZ[co R&!JT*#J fB:SF9ӑ%O"n2"i@'$GɞaՔJB(n"~|zr>3HM8f:p3XJ05\dwr ].+D9f:Q5E{jmqM;-F é-wdaY-x\̬f֜3}8 !{꭮X-5ҰcI 5]^v' vEv9*+q(`URq˕>sg t"t,#edFAs%ڏ-AjFKcX:pG*3Z~=ٮ||ށ,}HewnVjv{csJt/o­(1%lG0Vհ*/Wd 4K9u TS@ 9'-N%j;2:QrfB9 9n@5UNδ>:oN>//z Rȫ23VNbۓtOL!K:$^'VD _?N4ߦZg.)=RA]})Op:Aß*FMmD`$-\7M#C9޺9a6e37|T5 h xɴ<8ic4fnjf7 +7}_i*nٯ u6} ^R#Є<_t^#,A&@4E12z(m^KзG{*ٴ ǜ2F]w["pi8 z;]`&AzNk\01~g fܔB!' Nnf'aW7?.[؆D{m"l2݄WCgrXβ3h9CUpB0~NCsHȣ6@ k`D SqMaOǟaԟ Z03z0~$O(4V^"m't4N^))a4 |i\*ﺔF ՙ&:]P=)WF dn)4[<U~XyK=LR>Z\$+}ȃYB3 KvM=ZQVY<@R1 "zѬTe*Z;:+{ Er2EfQ'\%峄X3{ JAu@.8eZl(mޭwUSw B>g{+5{FҠO -8#M <Ǟõ3nYtY >;9=zv!bF貖Ch m׀IO~ 9p%H)?a% QJfRx~MU-\a6xb=Q`K4g4o}lR :p $CmqJL)x-BErDŽxrldm9q"P;@=𚋀q{eE?˰M}-sHy/J8!@{qx˿<%|bapAIͱ{笾X,[r(4!x>߯eN":I]x S>= ZkY )y3tO >mWT`˜_ۃms0•a@\6qGLOM?xIcLu?_n8?9.]rʁ"!Dg3 ne.W n\ȡ]AHӵez)u5 cnGtറ+'5Eh-uXCs9)R8{tWݐ]z *݊!,sA?v \&ߜJ~gE,2;7 $ ^H=uN/`CLkTNKEec3@pcgpAwb.@*k5cbj w̘T#\dA7/GiUxף#v/iC hKjwbbg>@ގm>.N'-_f9yyMhzepc6yw־㊰³[ _GeC ~[h*1tեenҘF?g'q7I F7 \*|kd9H%L")mڟo&h4#ʤ2 n#d3_C z|&hr%9Pu@f /s(nzV:U+O.$KVMl+Z+1@ C#m\nOK\ezq|b.7]O3G"}C U<_g-V$6OG Afb`p|3$GSAHRv%M T h׆[Ÿ30@wxɞ7 uv/@ KJc4@tB j*h ,\?riorEN7q%g 臌" wr8;25%)um5:lOXHty04@a%g!˹1% Q?cX7FvEXYuѭ9p giZϷ ϓn!VPԗ3HhN\sˎL\&(*ݖ_AlC4,v$Q^Q/c&{JdpTQ ^|*:NR) Ϸq %_dga`㣌< Zk-6-Մ-m.xظ_(ܗcI}fg_ \eDz:Ϯ YtyL ?i '=l(:@c!!p91t9 Zy+=b$f^hJIۗ{C{~̹cv+Olۥ%u5ʿ a_p&>{`ka֦L`UnMO66f&.nn!} :XU Ѿ.y|@q/P}I `D7ڧ[0n´y}˖Glan:ή6D,G$ Tx:1z;MDnUk5Cw{ǙneOϼ(j)?PtAV hCi{F#~gfgFy쉫?E$"[W|ԽbDV2ߞ0mEqqh/ RaNGuE[!Qb8d\P=>|E 2#^L76L ۖ]yضGG6{1<xqMj0|˔6?4Jut+4^5Q={Flm'CF"`U> dR@Х)*;0 >~t~ pF# -7m#gj .ka0ԒSN RF{THMv x{lJ=tKյw2NKq^s\ r8As/g{vuL ɐuGc]:209w/_!::PCZw Gn e#6 pTq(# =1U_{H\CbP^֕oxpBM7G?s0ɪٌ:X>ckVrӀuy8eݫfM:[[zRz!-9 JuSiG:8:%o^q}~tKb]ZOy4Ka?#a7wQw8aNhjhx髶Help8*Mt ^S]7"/#DVCht\3Av*Zз҆ bT\/`[F¦`=6szXmE5v9n 5d6M:4&M,J GW9.Pzoܺ̚r _ M}.Ad'dGFWb+;C 70 ȼ,;ΊAʯ|6l=bj2k/J6&A2ΥjCvS$6̈5*"O~wMPS8+3bD՛)g]UD'TUX-Zr!kC;y-IN)=TUXᇍ8| CAyHc1 ܱ@Mkى9x#PUp}Mѿ[sK)!D95M6)DJ%IXKFW5"D-ה-u%Q'F8çS*ۮLa*0!S&H0+Yپ}jPC=^~MjX+#݅G@ 17` y[V8n~ z ]ͺO$LH RH}antSc$=lhy>|pany y}סJw> .Zӛ~t8Nu,仏Q c IɡhKa?ꩋȐC r!vIs;wjs}6f6Z۶Q1mU+EōTxJ x 6~`P9zLUg_tx`!co>0a1Pelv[ިbo3՛@\Aaـ"ePJ:pvxn7X{wKS]j%2k[#2K1bCDslcds/oha{L>^4Uܶ[c*!q9~Wo|dZ @M@U%!~QNx_b'2lҊg'U2z^'ћt{*&/I#ųbjp?*p䍪w`K J ّ &佯:hcVև_!? |#hW:얌Nz{Ǐ ]ՠ^4YM(Vm!Y ks5\8=, n ?UG<YiE3C(M/0@Ax>aAL <*ުJ:6+:;Ȭ۞硧6؆]wf/U*4n "|3WwW!GB`aj*!jQ%O**nT~y` /E uq.)4X[ء>ܘt~`4SL$ ;=e=AF5hMi΍VJu꼉Hji$W /w]3=*V XGi'zcBUb,~GVioyPcOqXbۘ)9_&$ ﲋl/`5:}Upj m