From 809e3ce4d544ded2cd59fc1b5ba744f72304a970 Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Fri, 20 Jan 2017 18:41:18 +0100 Subject: [PATCH] Debug browser notification --- htdocs/admin/agenda_other.php | 43 ++++--- htdocs/core/ajax/check_events.php | 70 ----------- htdocs/core/ajax/check_notifications.php | 80 ++++++++++++ ...ication.js.php => lib_notification.js.php} | 115 +++++++++--------- htdocs/core/modules/modAgenda.class.php | 2 +- htdocs/langs/en_US/admin.lang | 4 +- htdocs/main.inc.php | 11 +- .../common}/sound/notification.mp3 | Bin .../common/sound/notification_agenda.wav | Bin 0 -> 6564 bytes 9 files changed, 176 insertions(+), 149 deletions(-) delete mode 100644 htdocs/core/ajax/check_events.php create mode 100644 htdocs/core/ajax/check_notifications.php rename htdocs/core/js/{agenda_notification.js.php => lib_notification.js.php} (56%) rename htdocs/{comm/action => theme/common}/sound/notification.mp3 (100%) create mode 100644 htdocs/theme/common/sound/notification_agenda.wav diff --git a/htdocs/admin/agenda_other.php b/htdocs/admin/agenda_other.php index 2835d17e2fe..9bf3a4ddc16 100644 --- a/htdocs/admin/agenda_other.php +++ b/htdocs/admin/agenda_other.php @@ -408,31 +408,34 @@ print $form->selectarray('AGENDA_DEFAULT_VIEW', $tmplist, $conf->global->AGENDA_ print ''."\n"; // AGENDA NOTIFICATION -$var=!$var; -print ''."\n"; -print ''.$langs->trans('AGENDA_NOTIFICATION').''."\n"; -print ' '."\n"; -print ''."\n"; - -if (empty($conf->global->AGENDA_NOTIFICATION)) { - print ''.img_picto($langs->trans('Disabled'),'switch_off').''; - print ''."\n"; -} else { - print ''.img_picto($langs->trans('Enabled'),'switch_on').''; - print ''."\n"; - $var=!$var; +if ($conf->global->MAIN_FEATURES_LEVEL > 0) +{ + $var=!$var; print ''."\n"; - print ''.$langs->trans('AGENDA_NOTIFICATION_SOUND').''."\n"; + print ''.$langs->trans('AGENDA_NOTIFICATION').''."\n"; print ' '."\n"; print ''."\n"; - - if (empty($conf->global->AGENDA_NOTIFICATION_SOUND)) { - print ''.img_picto($langs->trans('Disabled'),'switch_off').''; + + if (empty($conf->global->AGENDA_NOTIFICATION)) { + print ''.img_picto($langs->trans('Disabled'),'switch_off').''; + print ''."\n"; } else { - print ''.img_picto($langs->trans('Enabled'),'switch_on').''; + print ''.img_picto($langs->trans('Enabled'),'switch_on').''; + print ''."\n"; + $var=!$var; + print ''."\n"; + print ''.$langs->trans('AGENDA_NOTIFICATION_SOUND').''."\n"; + print ' '."\n"; + print ''."\n"; + + if (empty($conf->global->AGENDA_NOTIFICATION_SOUND)) { + print ''.img_picto($langs->trans('Disabled'),'switch_off').''; + } else { + print ''.img_picto($langs->trans('Enabled'),'switch_on').''; + } + + print ''."\n"; } - - print ''."\n"; } print ''; diff --git a/htdocs/core/ajax/check_events.php b/htdocs/core/ajax/check_events.php deleted file mode 100644 index 03787c92f52..00000000000 --- a/htdocs/core/ajax/check_events.php +++ /dev/null @@ -1,70 +0,0 @@ - - * Copyright (C) 2017 Juanjo Menent - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - */ - -require '../../main.inc.php'; -require_once DOL_DOCUMENT_ROOT.'/comm/action/class/actioncomm.class.php'; - -global $user, $db, $langs, $conf; - -$time = GETPOST('time'); - -session_start(); - -//TODO Configure how long the upgrade will take -$time_update = 60; - -if (! empty($conf->global->AGENDA_NOTIFICATION)) { - if ($_SESSION['auto_check_events'] <= (int) $time) { - $_SESSION['auto_check_events'] = $time + $time_update; - - $eventos = array(); - - $sql = 'SELECT id'; - $sql .= ' FROM ' . MAIN_DB_PREFIX . 'actioncomm a, ' . MAIN_DB_PREFIX . 'actioncomm_resources ar'; - $sql .= ' WHERE datep BETWEEN ' . $db->idate($time + 1) . ' AND ' . $db->idate($time + $time_update); - $sql .= ' AND a.id = ar.fk_actioncomm'; - $sql .= ' AND a.code <> "AC_OTH_AUTO"'; - $sql .= ' AND ar.element_type = "user"'; - $sql .= ' AND ar.fk_element = ' . $user->id; - - $resql = $db->query($sql); - - if ($resql) { - - $actionmod = new ActionComm($db); - - while ($obj = $db->fetch_object($resql)) { - - $event = array(); - - $actionmod->fetch($obj->id); - - $event['id'] = $actionmod->id; - $event['tipo'] = $langs->transnoentities('Action' . $actionmod->code); - $event['titulo'] = $actionmod->label; - $event['location'] = $actionmod->location; - $eventos[] = $event; - $actionmod->initAsSpecimen(); - - } - } - - print json_encode($eventos); - } -} \ No newline at end of file diff --git a/htdocs/core/ajax/check_notifications.php b/htdocs/core/ajax/check_notifications.php new file mode 100644 index 00000000000..af56cb23700 --- /dev/null +++ b/htdocs/core/ajax/check_notifications.php @@ -0,0 +1,80 @@ + + * Copyright (C) 2017 Juanjo Menent + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +if (! defined('NOTOKENRENEWAL')) define('NOTOKENRENEWAL','1'); // Disables token renewal +if (! defined('NOREQUIREMENU')) define('NOREQUIREMENU','1'); +if (! defined('NOREQUIREHTML')) define('NOREQUIREHTML','1'); +if (! defined('NOREQUIREAJAX')) define('NOREQUIREAJAX','1'); +if (! defined('NOREQUIRESOC')) define('NOREQUIRESOC','1'); +if (! defined('NOREQUIRETRAN')) define('NOREQUIRETRAN','1'); + +require '../../main.inc.php'; +require_once DOL_DOCUMENT_ROOT.'/comm/action/class/actioncomm.class.php'; + +global $user, $db, $langs, $conf; + +$time = GETPOST('time'); +//$time=dol_now(); + +session_start(); + +$time_update = (empty($conf->global->MAIN_BROWSER_NOTIFICATION_FREQUENCY)?'3':(int) $conf->global->MAIN_BROWSER_NOTIFICATION_FREQUENCY); + +$eventos = array(); +//$eventos[]=array('type'=>'agenda', 'id'=>1, 'tipo'=>'eee', 'location'=>'aaa'); + +// TODO Remove test on session. Timer should be managed by a javascript timer +if ($_SESSION['auto_check_events'] <= (int) $time) +{ + $_SESSION['auto_check_events'] = $time + $time_update; + + $sql = 'SELECT id'; + $sql .= ' FROM ' . MAIN_DB_PREFIX . 'actioncomm a, ' . MAIN_DB_PREFIX . 'actioncomm_resources ar'; + $sql .= ' WHERE a.id = ar.fk_actioncomm'; + // TODO Try to make a solution with only a javascript timer that is easier. Difficulty is to avoid notification twice when. + // This need to extend period to be sure to not miss and save what we notified to avoid duplicate (save is not done yet). + $sql .= " AND datep BETWEEN '" . $db->idate($time + 1) . "' AND '" . $db->idate($time + $time_update) . "'"; + $sql .= ' AND a.code <> "AC_OTH_AUTO"'; + $sql .= ' AND ar.element_type = "user"'; + $sql .= ' AND ar.fk_element = ' . $user->id; + $sql .= ' LIMIT 10'; // Avoid too many notification at once + + $resql = $db->query($sql); + if ($resql) { + + $actionmod = new ActionComm($db); + + while ($obj = $db->fetch_object($resql)) { + + $actionmod->fetch($obj->id); + + $event = array(); + $event['type'] = 'agenda'; + $event['id'] = $actionmod->id; + $event['tipo'] = $langs->transnoentities('Action' . $actionmod->code); + $event['titulo'] = $actionmod->label; + $event['location'] = $actionmod->location; + + $eventos[] = $event; + } + } + +} + +print json_encode($eventos); + diff --git a/htdocs/core/js/agenda_notification.js.php b/htdocs/core/js/lib_notification.js.php similarity index 56% rename from htdocs/core/js/agenda_notification.js.php rename to htdocs/core/js/lib_notification.js.php index ad4b8cf1bdf..a870066c775 100644 --- a/htdocs/core/js/agenda_notification.js.php +++ b/htdocs/core/js/lib_notification.js.php @@ -14,8 +14,10 @@ * * You should have received a copy of the GNU General Public License * along with this program. If not, see . - * -*/ + * + * Library javascript to enable Browser notifications + */ + if (!defined('NOREQUIREUSER')) define('NOREQUIREUSER', '1'); if (!defined('NOREQUIRESOC')) define('NOREQUIRESOC', '1'); if (!defined('NOCSRFCHECK')) define('NOCSRFCHECK', 1); @@ -24,110 +26,113 @@ if (!defined('NOLOGIN')) define('NOLOGIN', 1); if (!defined('NOREQUIREMENU')) define('NOREQUIREMENU', 1); if (!defined('NOREQUIREHTML')) define('NOREQUIREHTML', 1); -session_cache_limiter(FALSE); - require_once '../../main.inc.php'; -if(!($_SERVER['HTTP_REFERER'] === $dolibarr_main_url_root . '/' || $_SERVER['HTTP_REFERER'] === $dolibarr_main_url_root . '/index.php')){ - +if (!($_SERVER['HTTP_REFERER'] === $dolibarr_main_url_root . '/' || $_SERVER['HTTP_REFERER'] === $dolibarr_main_url_root . '/index.php')) +{ global $langs, $conf; - $langs->load('agenda'); - // Define javascript type header('Content-type: text/javascript; charset=UTF-8'); + + // TODO Try to make a solution with only a javascript timer that is easier. Difficulty is to avoid notification twice when. + session_cache_limiter(FALSE); header('Cache-Control: no-cache'); - - // Check notification permissions API HTML5 - print 'if (Notification.permission !== "granted") { - Notification.requestPermission() - }' . PHP_EOL; - session_start(); if (!isset($_SESSION['auto_check_events'])) { - // Round to eliminate the second part $_SESSION['auto_check_events'] = floor(time() / 60) * 60; - print 'var time_session = ' . $_SESSION['auto_check_events'] . ';' . PHP_EOL; - print 'var now = ' . $_SESSION['auto_check_events'] . ';' . PHP_EOL; + print 'var time_session = ' . $_SESSION['auto_check_events'] . ';'."\n"; + print 'var now = ' . $_SESSION['auto_check_events'] . ';' . "\n"; } else { - - print 'var time_session = ' . $_SESSION['auto_check_events'] . ';' . PHP_EOL; - print 'var now = ' . time() . ';' . PHP_EOL; + print 'var time_session = ' . $_SESSION['auto_check_events'] . ';' . "\n"; + print 'var now = ' . time() . ';' . "\n"; } - - //TODO provisionally set to be checked every 60 seconds, the 1000 is because it needs to be in milliseconds - print 'var time_auto_update = 60;' . "\n"; + print 'var time_auto_update = '.(empty($conf->global->MAIN_BROWSER_NOTIFICATION_FREQUENCY)?'3':(int) $conf->global->MAIN_BROWSER_NOTIFICATION_FREQUENCY).';' . "\n"; ?> - + + /* Check if permission ok */ + if (Notification.permission !== "granted") { + Notification.requestPermission() + } if (now > (time_session + time_auto_update) || now == time_session) { first_execution(); //firts run auto check } else { - var time_first_execution = (time_auto_update - (now - time_session)) * 1000; + var time_first_execution = (time_auto_update - (now - time_session)) * 1000; //need milliseconds - setTimeout(first_execution, time_first_execution); //firts run auto check + setTimeout(first_execution, time_first_execution); //first run auto check } function first_execution() { + console.log("Call first_execution"); check_events(); setInterval(check_events, time_auto_update * 1000); //program time for run check events } function check_events() { - - $.ajax("", { - type: "post", // Usually post o get - async: true, - data: {time: time_session}, - success: function (result) { - - var arr = JSON.parse(result); - - if (arr.length > 0) { - if (Notification.permission === "granted") { - + if (Notification.permission === "granted") + { + console.log("Call check_events"); + $.ajax("", { + type: "post", // Usually post o get + async: true, + data: {time: time_session}, + success: function (result) { + var arr = JSON.parse(result); + if (arr.length > 0) { global->AGENDA_NOTIFICATION_SOUND){ - print 'var audio = new Audio(\''.dol_buildpath('/comm/action/sound/notification.mp3', 1).'\');'; + if (! empty($conf->global->AGENDA_NOTIFICATION_SOUND)) { + print 'var audio = new Audio(\''.DOL_URL_ROOT.'/theme/common/sound/notification_agenda.wav'.'\');'; } ?> - + $.each(arr, function (index, value) { + var url="notdefined"; + var title="Not defined"; var body = value['tipo'] + ': ' + value['titulo']; - if (value['location'] != null) { + if (value['type'] == 'agenda' && value['location'] != null && value['location'] != '') { body += '\n transnoentities('Location')?>: ' + value['location']; } - - - var title = "trans('Agenda') ?>"; + + if (value['type'] == 'agenda') + { + url = '' + value['id']; + title = 'trans('Agenda') ?>'; + } var extra = { - icon: "", + icon: '', body: body, tag: value['id'] }; - + // We release the notify var noti = new Notification(title, extra); - global->AGENDA_NOTIFICATION_SOUND){ - print 'if(index==0)audio.play();'."\n"; + if (index==0 && audio) + { + audio.play(); } - ?> noti.onclick = function (event) { + console.log("An event to notify on browser was received"); event.preventDefault(); // prevent the browser from focusing the Notification's tab window.focus(); - window.open("" + value['id'], '_blank'); + window.open(url, '_blank'); noti.close(); }; }); } } - } - }); + }); + } + else + { + console.log("Cancel check_events. Useless because Notification.permission is "+Notification.permission); + } + time_session += time_auto_update; } -langfiles = array("companies"); // Module parts - $this->module_parts = array('js' => array('/core/js/agenda_notification.js.php')); + $this->module_parts = array(); // Constants //----------- diff --git a/htdocs/langs/en_US/admin.lang b/htdocs/langs/en_US/admin.lang index fcb4df82669..f88736e93d1 100644 --- a/htdocs/langs/en_US/admin.lang +++ b/htdocs/langs/en_US/admin.lang @@ -1475,8 +1475,8 @@ AGENDA_USE_EVENT_TYPE_DEFAULT=Set automatically this default value for type of e AGENDA_DEFAULT_FILTER_TYPE=Set automatically this type of event into search filter of agenda view AGENDA_DEFAULT_FILTER_STATUS=Set automatically this status for events into search filter of agenda view AGENDA_DEFAULT_VIEW=Which tab do you want to open by default when selecting menu Agenda -AGENDA_NOTIFICATION=Enable events notification -AGENDA_NOTIFICATION_SOUND=Enable sound on notifications +AGENDA_NOTIFICATION=Enable event notification on user browsers when event date is reached (each user is able to refuse this from the browser confirmation question) +AGENDA_NOTIFICATION_SOUND=Enable sound notification ##### ClickToDial ##### ClickToDialDesc=This module allows to make phone numbers clickable. A click on this icon will call make your phone to call the phone number. This can be used to call a call center system from Dolibarr that can call the phone number on a SIP system for example. ClickToDialUseTelLink=Use just a link "tel:" on phone numbers diff --git a/htdocs/main.inc.php b/htdocs/main.inc.php index 8aba2e428f2..80c6b1b3dca 100644 --- a/htdocs/main.inc.php +++ b/htdocs/main.inc.php @@ -1303,7 +1303,16 @@ function top_htmlhead($head, $title='', $disablejs=0, $disablehead=0, $arrayofjs print ''."\n"; print ''."\n"; } - + + // Browser notifications + $enablebrowsernotif=false; + if (! empty($conf->agenda->enabled) && ! empty($conf->global->AGENDA_NOTIFICATION) && ! empty($conf->global->AGENDA_NOTIFICATION_SOUND)) $enablebrowsernotif=true; + if ($enablebrowsernotif) + { + print ''."\n"; + print ''."\n"; + } + // Global js function print ''."\n"; print ''."\n"; diff --git a/htdocs/comm/action/sound/notification.mp3 b/htdocs/theme/common/sound/notification.mp3 similarity index 100% rename from htdocs/comm/action/sound/notification.mp3 rename to htdocs/theme/common/sound/notification.mp3 diff --git a/htdocs/theme/common/sound/notification_agenda.wav b/htdocs/theme/common/sound/notification_agenda.wav new file mode 100644 index 0000000000000000000000000000000000000000..09e203c30f13c24f9dfab1533a1bd66766c3d4f9 GIT binary patch literal 6564 zcmd^DOLwD6lCIv(a(X$t+4ryLMfW+goyE*HE#pE(Aqy2q2=Pi@Ldb~>7RJKj>gG?K zFA}?|duGw6*CQW862#++$jBrz{_{Wn`q%N_zI^%bKmXTXzAl`9|JN^HzWf1yfBe%I z_5X)2|MEqIJEPSo^ux-=c$z6!oH-Spg5fw>6EzT?i^N}K-KUl>l+b;6mO zemk_;a=waRuQk^ny!|lj-I>l?dGU2-*0)iZO!wYp&l$5zDNCC%-pO02GvirGF=z8| zluf{xE98xvYimRC=oS*;v$o!J%f%$DMMJShd6=R_FjOoxD;wGisF_u6RjO0?n^|_R{En|2f zzP(~tNcjd8cxWblPy`#oU<{BF#&)BWGX)76pmhW{ED<*lfh~|Sy<-pWa`YI3i@@bJ z@@6mt_`})E8Dza=24pf7_4ADVz#OdD*GQpqdU;;4c{R$E&7wy3I&%QgVc(-c@~@hE zmM4oX#qh?No^?0qk6PL3Y*USc3oFAd)o0s&6b@U|1LjyS`%9r|ydA`R7KpK1FsB?Z zHe03r5bH*8elHffdwLTa&92UEJX==nna_;se9kp3KffL4Gu~d1HGX|*?b-V2+BtWR z`K1r%^WgHHEHt+tON;&JZ?O8<-5QsDt4APY)p#x8XMd}8=C`-A!JMPKSj>b^E_*;s z_>6g@Ea%L=NU>zg=GN`t|1fZ-m2YoAo}Dk1uJM;a=y&`)KF?X$jIA;As-bfiYw4F2c~F=l-NHrJDO8odRMdax89#Gc;CmkLeawH=Pt7l z%RaN_yK|pAbFb}6S9hL!;c2U;7dzb&-80t~bkz~#ntyvNn`dsAWqV2t^Uw-znq?!> zfaa%ag;&$(srrpcBDKec;Y>vnIM(eW%SBNXRqWrkIIbPWG^u6`K7)A{Ip z+a0kReA`w%_5>H#^?Y;aYiI84FU?waic90p?R+?I^;I@>JH+mK2{a=aFPK$+9r#=i z#&>S&-Y}+^;o-N`{Ho@8=JdD2>h4E3iuK*H9h)69n%miD^Y-^PV0tq)T>DlHuNkuE z(xsZd@4HOblkV-HYe_d$`iys%%1}`@wV-*gzt6(vU6}X5^Kg39JaKIw@3+VCWvRK< zwOb+XWAEHs`|;x3S;g3IA0CtI;A>`zt8PqqBz_wVsF=S1*Bu;%$Db?*fA5h z0%o4x)$I2#{Oi2v`usu5`pf?7EbaShj(u>%Vvm5)U;%R8(l%mjerQg0eR1?v4$CT1 z%I14y=)o{@Q}Zs@%+l8VxL?g|TQx59GfIo$Z?2%Oi6Rw>L*y@bNuD z&RoW9sX5(v+`#X#4Ym0m9_tVMJRHpjAZ%>ihL@hPh%Xm>&b;$bZ06Rn7qMYx&AC__ z;nS;dOgHbEz_OwjeC3v&Xv=VE=g%TpS-ffvuH~go9oqO>HmPU1CBCiJ3ZK-y^XM0? z@GOTnEhM+%T>6Vu(L5*1MO?ic9dnbMU&7_mZ(eiPSXCXZJ1)9vWw?3Y`io`QzNU_0 zA72Sm(!3Ow=@nh$FRifcQg`7T+7kz~s;%7Rnzs#i9$hTN$XsSkQ>@J09^BA_{a|g_ zRd*r_b^qiTPTHR%YvG-`Ltt82(-y0Rzi;7*9RPf|a+0Td@2)&v*W9(evPR8d$8c|F zQ{|BDEGMt)%&`J^No5A4g5{62J{4_NGGR~=<%Q7CLz)Q0v3e*kN9VAnFNjy{q zCa4D04|hB#S;8j~x^XCsqnIbDdig1nPLG@_NvcFi2XR4`RDi5B(>)|KR7~;&CFJBl z7Wxrt&2%a;)ji@=X;MA;!Ic#mz($E;3u3BBqW}ddxF?-}Kwh8>C8dxi-x9<@+5SX` z2&iZHK?oNs*#(QgmS&2xCGV;|{kG zI~Bvv*+5YpQg!|DBFUzBseq|Ca_A-$6HI~&*y;@p{#VgYykPn1!33AC1X;cNi~?a( zVVDfS0!b-LKgE>=vQ2YMbFPd}JSsRJ6q?z|0$EWb1?;CH3e3kr30< zr+%2s$fP;~O?jneOJx|1DD`0K=Lq`L!6*zsQzMx87=acm6G%!DKo(=JKe7N?Kuc9F zhRSDvLWpR^OrgPRN`?e;+pa3+Eq}crw zDm7(3Kqm^-$NN34OzfGH{_q!S7zLDq{QG4e!%wduq~0re+GSyiyrzALq7q=pc~QbZ zxZ7cC#Spu*=LOi&4|y74h`s4h?KzIZpcYkybM)55u3lCZc06zEVP~)Kz3^Oo71Bd> z00V|$Rvy8-bKPykt0!piiJF#A$6O6#et0?(bwj-3iFh||lvDz|fxFoTX^E3IsE!K_ z;;H0;<4n{NV$LSu2O$nT*f_4w_d=9ups0JONaF2=zEVd~<&n2>cTs*2u>8)Y(N{#`3Wq4YAd0FN1&n;0CU;j+JG3oi;B8+9DGxc;+TLJ-GV~Y6~31k z#-*r_=|s(m8W0s9@%l+o@tsvvi0u?L2^|{@`?{_&AcNim7?ht1Z%(r7}O_x&B+Tz4LwjJqT>7ilNx!ztEefW zx^{U6y~RY$D;#Dx9CG6nVwg|NQJ5pZpsv@7dMto_?STqw(Cci`OQAS?dVEa6HF;dZ z<8+5%z$*+f-}c(is^`-l7VLGHh-Xp4uI&cJv8gj4cU+#=O+~YU=pAYlNWt3K?9%GF zF5oY6Clx1ws)l^nVfkfoIU8@z|9j53pE47nY;Yn9tatR$o> z&!3(Uwk@q5T2m2LPk_>m*2dQLqpYmTU#l;rHS6tc~U6Ux>PF($P2Z4$aBo?W($myw^Ovz$lGEIDFh`g#%idWg~i6zpqRp{iZ#0b$mq0il~^GIAT$$D-?)#Z{oID(3| zGsit|hsOE73dC!ZFXuMzhbM3L5Z5ElZg=ydA79+L?ljlFG}tmjGjGlAA~ef_(0TvxFQ>wAAEivPX10;YTe;G^ASjsxfzvXY|-q&k}%yvW=b=*wG;!-(npeRuO@f`hUDyxDh$GS(2itjK-Jr=oX z75W@U4~X(8O>ibbNrXC+Y71}$ah#@pkfO64gs4!%C`yYRb^{DODs~}_AqsL7^7upj zG?ndBO^XtYnAA^I=86m;zkV?HkAIT{t|$;;8fBfT&HrAIUZc#K4r}Vn0+&(EpbY