From 6a951b4bd18c442f2512e68c02d0812e2393e4c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Schneider?= Date: Tue, 24 Apr 2012 21:09:44 +0200 Subject: [PATCH] Working on web admin Former-commit-id: 81b7ca06147f0384c6a5b92f9ad9dc422d289fdf --- .../Frameworks/Baikal/Core/Bootstrap.php | 67 ++++++----- .../Frameworks/Baikal/Core/Tools.php | 53 +++++++++ .../Frameworks/Baikal/Model/Config.php | 2 +- .../Baikal/Model/Config/Standard.php | 30 ++--- .../Frameworks/Baikal/Model/Config/System.php | 18 +++ .../BaikalAdmin/Controller/Install.php | 108 ------------------ .../Frameworks/BaikalAdmin/Core/Auth.php | 11 +- .../BaikalAdmin/WWWRoot/install/index.php | 17 ++- .../Baikal_0.1/Frameworks/Formal/Form.php | 10 +- Specific/config.php | 55 --------- Specific/db/baikal.sqlite | Bin 19456 -> 14336 bytes 11 files changed, 152 insertions(+), 219 deletions(-) delete mode 100644 CoreVersions/Baikal_0.1/Frameworks/BaikalAdmin/Controller/Install.php delete mode 100755 Specific/config.php diff --git a/CoreVersions/Baikal_0.1/Frameworks/Baikal/Core/Bootstrap.php b/CoreVersions/Baikal_0.1/Frameworks/Baikal/Core/Bootstrap.php index 76408a9..b48da1f 100644 --- a/CoreVersions/Baikal_0.1/Frameworks/Baikal/Core/Bootstrap.php +++ b/CoreVersions/Baikal_0.1/Frameworks/Baikal/Core/Bootstrap.php @@ -61,7 +61,7 @@ function installTool() { if(defined("BAIKAL_CONTEXT_INSTALL") && BAIKAL_CONTEXT_INSTALL === TRUE) { return; } else { - $sInstallToolUrl = prependSlash($sBaseUrl . "admin/install/"); + $sInstallToolUrl = BAIKAL_URI . "admin/install/"; header("Location: " . $sInstallToolUrl); exit(0); } @@ -101,7 +101,6 @@ define("BAIKAL_PATH_WWWROOT", BAIKAL_PATH_CORE . "WWWRoot/"); require_once(BAIKAL_PATH_CORE . "Distrib.php"); # Determine BAIKAL_URI -#print_r($_SERVER); $sScript = substr($_SERVER["SCRIPT_FILENAME"], strlen($_SERVER["DOCUMENT_ROOT"])); $sDirName = appendSlash(dirname($sScript)); $sBaseUrl = appendSlash(substr($sDirName, 0, -1 * strlen(BAIKAL_CONTEXT_BASEURI))); @@ -120,36 +119,50 @@ if( require_once(BAIKAL_PATH_SPECIFIC . "config.php"); require_once(BAIKAL_PATH_SPECIFIC . "config.system.php"); date_default_timezone_set(BAIKAL_TIMEZONE); - - - if(version_compare(BAIKAL_VERSION, BAIKAL_CONFIGURED_VERSION) > 0) { + + # Check that Baïkal is already configured + if(!defined("BAIKAL_CONFIGURED_VERSION")) { installTool(); + } else { - # Check if DB exists - if(!file_exists(BAIKAL_SQLITE_FILE)) { - die("DB file does not exist.
To create it, please copy 'Core/Resources/baikal.empty.sqlite' to 'Specific/db/baikal.sqlite'.
Please note the change in the file name while doing so (from 'baikal.empty.sqlite' to 'baikal.sqlite')."); - } + + # Check that running version matches configured version + if(version_compare(BAIKAL_VERSION, BAIKAL_CONFIGURED_VERSION) > 0) { + installTool(); + + } else { - # Database - $pdo = new PDO('sqlite:' . BAIKAL_SQLITE_FILE); - $pdo->setAttribute(PDO::ATTR_ERRMODE,PDO::ERRMODE_EXCEPTION); - - $bShouldCheckEnv = ((!defined("BAIKAL_CONTEXT_CLI") || BAIKAL_CONTEXT_CLI === FALSE) && (!defined("BAIKAL_CONTEXT_ADMIN") || BAIKAL_CONTEXT_ADMIN === FALSE)); - - if($bShouldCheckEnv === TRUE) { - # Mapping PHP errors to exceptions - function exception_error_handler($errno, $errstr, $errfile, $errline) { - throw new ErrorException($errstr, 0, $errno, $errfile, $errline); + # Check that admin password is set + if(!defined("BAIKAL_ADMIN_PASSWORDHASH")) { + installTool(); } - set_error_handler("exception_error_handler"); - } else { - error_reporting(E_ALL ^ E_NOTICE); + # Check that DB exists + if(!file_exists(BAIKAL_SQLITE_FILE)) { + die("DB file does not exist.
To create it, please copy 'Core/Resources/baikal.empty.sqlite' to 'Specific/db/baikal.sqlite'.
Please note the change in the file name while doing so (from 'baikal.empty.sqlite' to 'baikal.sqlite')."); + } + + # Database + $pdo = new PDO('sqlite:' . BAIKAL_SQLITE_FILE); + $pdo->setAttribute(PDO::ATTR_ERRMODE,PDO::ERRMODE_EXCEPTION); + + $bShouldCheckEnv = ((!defined("BAIKAL_CONTEXT_CLI") || BAIKAL_CONTEXT_CLI === FALSE) && (!defined("BAIKAL_CONTEXT_ADMIN") || BAIKAL_CONTEXT_ADMIN === FALSE)); + + if($bShouldCheckEnv === TRUE) { + # Mapping PHP errors to exceptions + function exception_error_handler($errno, $errstr, $errfile, $errline) { + throw new ErrorException($errstr, 0, $errno, $errfile, $errline); + } + + set_error_handler("exception_error_handler"); + } else { + error_reporting(E_ALL ^ E_NOTICE); + } + + unset($bShouldCheckEnv); + + // Autoloader + require_once(BAIKAL_PATH_SABREDAV . 'autoload.php'); } - - unset($bShouldCheckEnv); - - // Autoloader - require_once(BAIKAL_PATH_SABREDAV . 'autoload.php'); } } \ No newline at end of file diff --git a/CoreVersions/Baikal_0.1/Frameworks/Baikal/Core/Tools.php b/CoreVersions/Baikal_0.1/Frameworks/Baikal/Core/Tools.php index 9fa1f56..616d194 100644 --- a/CoreVersions/Baikal_0.1/Frameworks/Baikal/Core/Tools.php +++ b/CoreVersions/Baikal_0.1/Frameworks/Baikal/Core/Tools.php @@ -71,6 +71,59 @@ class Tools { return $password; } + public static function getCopyrightNotice($sLinePrefixChar = "#", $sLineSuffixChar = "", $sOpening = FALSE, $sClosing = FALSE) { + + if($sOpening === FALSE) { + $sOpening = str_repeat("#", 78); + } + + if($sClosing === FALSE) { + $sClosing = str_repeat("#", 78); + } + + $iYear = date("Y"); + + $sCode =<< +All rights reserved + +http://baikal.codr.fr + +This script is part of the Baïkal Server project. The Baïkal +Server project 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 2 of the License, or (at your option) any later version. + +The GNU General Public License can be found at +http://www.gnu.org/copyleft/gpl.html. + +This script 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. + +This copyright notice MUST APPEAR in all copies of the script! +CODE; + $sCode = "\n" . trim($sCode) . "\n"; + $aCode = explode("\n", $sCode); + foreach(array_keys($aCode) as $iLineNum) { + $aCode[$iLineNum] = trim($sLinePrefixChar . "\t" . $aCode[$iLineNum]); + } + + if(trim($sOpening) !== "") { + array_unshift($aCode, $sOpening); + } + + if(trim($sClosing) !== "") { + $aCode[] = $sClosing; + } + + return implode("\n", $aCode); + } + public static function timezones() { $aZones = array( "Africa/Abidjan", diff --git a/CoreVersions/Baikal_0.1/Frameworks/Baikal/Model/Config.php b/CoreVersions/Baikal_0.1/Frameworks/Baikal/Model/Config.php index 3d6629c..5df289a 100644 --- a/CoreVersions/Baikal_0.1/Frameworks/Baikal/Model/Config.php +++ b/CoreVersions/Baikal_0.1/Frameworks/Baikal/Model/Config.php @@ -101,7 +101,7 @@ abstract class Config extends \Flake\Core\Model\NoDb { $aRes[$sConstant] = $sValue; } elseif($iNbRes === 0) { - throw new \Exception("Baikal\Model\Config->parseConfig(): Unable to find constant '" . $prop . "' in config file"); + throw new \Exception("Baikal\Model\Config->parseConfig(): Unable to find constant '" . $sConstant . "' in config file"); } } diff --git a/CoreVersions/Baikal_0.1/Frameworks/Baikal/Model/Config/Standard.php b/CoreVersions/Baikal_0.1/Frameworks/Baikal/Model/Config/Standard.php index 0e6b405..8b91cf5 100644 --- a/CoreVersions/Baikal_0.1/Frameworks/Baikal/Model/Config/Standard.php +++ b/CoreVersions/Baikal_0.1/Frameworks/Baikal/Model/Config/Standard.php @@ -41,12 +41,6 @@ class Standard extends \Baikal\Model\Config { "BAIKAL_ADMIN_ENABLED" => array( "type" => "boolean", ), - "BAIKAL_STANDALONE_ALLOWED" => array( - "type" => "boolean", - ), - "BAIKAL_STANDALONE_PORT" => array( - "type" => "integer", - ), "BAIKAL_ADMIN_PASSWORDHASH" => array( "type" => "string", ) @@ -60,8 +54,6 @@ class Standard extends \Baikal\Model\Config { "BAIKAL_CARD_ENABLED" => "", "BAIKAL_CAL_ENABLED" => "", "BAIKAL_ADMIN_ENABLED" => "", - "BAIKAL_STANDALONE_ALLOWED" => "", - "BAIKAL_STANDALONE_PORT" => "", "BAIKAL_ADMIN_PASSWORDHASH" => "" ); @@ -95,30 +87,26 @@ class Standard extends \Baikal\Model\Config { ), ))); - $oMorpho->add(new \Formal\Element\Checkbox(array( - "prop" => "BAIKAL_STANDALONE_ALLOWED", - "label" => "Allow Standalone Baïkal execution" - ))); - - $oMorpho->add(new \Formal\Element\Text(array( - "prop" => "BAIKAL_STANDALONE_PORT", - "label" => "Standalone Baïkal port" - ))); - - $sNotice = "-- Leave empty to keep current password --"; $oMorpho->add(new \Formal\Element\Password(array( "prop" => "BAIKAL_ADMIN_PASSWORDHASH", "label" => "Web admin password", - "placeholder" => $sNotice, ))); $oMorpho->add(new \Formal\Element\Password(array( "prop" => "BAIKAL_ADMIN_PASSWORDHASH_CONFIRM", "label" => "Web admin password confirmation", - "placeholder" => $sNotice, "validation" => "sameas:BAIKAL_ADMIN_PASSWORDHASH", ))); + if(!defined("BAIKAL_ADMIN_PASSWORDHASH") || trim(BAIKAL_ADMIN_PASSWORDHASH) === "") { + + # No password set (Form is used in install tool), so password is required as it has to be defined + $oMorpho->element("BAIKAL_ADMIN_PASSWORDHASH")->setOption("validation", "required"); + } else { + $sNotice = "-- Leave empty to keep current password --"; + $oMorpho->element("BAIKAL_ADMIN_PASSWORDHASH")->setOption("placeholder", $sNotice); + $oMorpho->element("BAIKAL_ADMIN_PASSWORDHASH_CONFIRM")->setOption("placeholder", $sNotice); + } return $oMorpho; } diff --git a/CoreVersions/Baikal_0.1/Frameworks/Baikal/Model/Config/System.php b/CoreVersions/Baikal_0.1/Frameworks/Baikal/Model/Config/System.php index ce3a5b8..2d0c1e5 100644 --- a/CoreVersions/Baikal_0.1/Frameworks/Baikal/Model/Config/System.php +++ b/CoreVersions/Baikal_0.1/Frameworks/Baikal/Model/Config/System.php @@ -44,6 +44,12 @@ class System extends \Baikal\Model\Config { "BAIKAL_SQLITE_FILE" => array( "type" => "litteral", ), + "BAIKAL_STANDALONE_ALLOWED" => array( + "type" => "boolean", + ), + "BAIKAL_STANDALONE_PORT" => array( + "type" => "integer", + ), ); protected $aData = array( @@ -52,6 +58,8 @@ class System extends \Baikal\Model\Config { "BAIKAL_CARD_BASEURI" => "", "BAIKAL_CAL_BASEURI" => "", "BAIKAL_SQLITE_FILE" => "", + "BAIKAL_STANDALONE_ALLOWED" => "", + "BAIKAL_STANDALONE_PORT" => "", ); public function formMorphologyForThisModelInstance() { @@ -111,6 +119,16 @@ class System extends \Baikal\Model\Config { ) ))); + $oMorpho->add(new \Formal\Element\Checkbox(array( + "prop" => "BAIKAL_STANDALONE_ALLOWED", + "label" => "Allow Standalone Baïkal execution" + ))); + + $oMorpho->add(new \Formal\Element\Text(array( + "prop" => "BAIKAL_STANDALONE_PORT", + "label" => "Standalone Baïkal port" + ))); + return $oMorpho; } diff --git a/CoreVersions/Baikal_0.1/Frameworks/BaikalAdmin/Controller/Install.php b/CoreVersions/Baikal_0.1/Frameworks/BaikalAdmin/Controller/Install.php deleted file mode 100644 index 550a671..0000000 --- a/CoreVersions/Baikal_0.1/Frameworks/BaikalAdmin/Controller/Install.php +++ /dev/null @@ -1,108 +0,0 @@ - -* All rights reserved -* -* http://baikal.codr.fr -* -* This script is part of the Baïkal Server project. The Baïkal -* Server project 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 2 of the License, or (at your option) any later version. -* -* The GNU General Public License can be found at -* http://www.gnu.org/copyleft/gpl.html. -* -* This script 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. -* -* This copyright notice MUST APPEAR in all copies of the script! -***************************************************************/ - -namespace BaikalAdmin\Controller; - -class Install extends \Flake\Core\Controller { - - protected $aMessages = array(); - protected $oModel; # \BaikalAdmin\Model\Install - protected $oForm; # \Formal\Form - - public function __construct() { - parent::__construct(); - - $this->oModel = new \BaikalAdmin\Model\Install(); - - $this->oForm = $this->oModel->formForThisModelInstance(array( - "close" => FALSE - )); - } - - public function execute() { - if($this->oForm->submitted()) { - $this->oForm->execute(); - } - } - - public function render() { - - # Determine in which case we are - # A- Baïkal is not configured yet; new installation - # B- Baïkal is configured, but maintenance tasks have to be achieved; upgrade - - if(defined("BAIKAL_CONFIGURED_VERSION")) { - # we have to upgrade Baïkal (existing installation) - $sHtml = $this->render_upgradeInstall(); - } else { - # we have to initialize Baïkal (new installation) - $sHtml = $this->render_newInstall(); - } - - return $sHtml; - } - - protected function render_newInstall() { - $sBigIcon = \BaikalAdmin\Model\Install::bigicon(); - - $sBaikalVersion = BAIKAL_VERSION; - - $sHtml = << -

Baïkal initialization (version {$sBaikalVersion})

-

Configure your new Baïkal installation.

- -HTML; - - $sHtml .= $this->oForm->render(); - - return $sHtml; - } - - protected function render_upgradeInstall() { - $sBigIcon = \BaikalAdmin\Model\Install::bigicon(); - $sBaikalVersion = BAIKAL_VERSION; - $sBaikalConfiguredVersion = BAIKAL_CONFIGURED_VERSION; - - $sHtml = << -

Baïkal upgrade wizard

-

Upgrading Baïkal from version {$sBaikalConfiguredVersion} to version {$sBaikalVersion}.

- -HTML; - -/* $sHtml .= <<What is this ? -

- This is the Baïkal Install Tool.
- It's displayed because you just installed or upgraded your Baïkal installation.
- Baïkal requires some maintenance in order to ensure everything works as expected. -

-HTML; -*/ - return $sHtml; - } -} \ No newline at end of file diff --git a/CoreVersions/Baikal_0.1/Frameworks/BaikalAdmin/Core/Auth.php b/CoreVersions/Baikal_0.1/Frameworks/BaikalAdmin/Core/Auth.php index 5fa1221..31b8e4d 100644 --- a/CoreVersions/Baikal_0.1/Frameworks/BaikalAdmin/Core/Auth.php +++ b/CoreVersions/Baikal_0.1/Frameworks/BaikalAdmin/Core/Auth.php @@ -36,6 +36,13 @@ class Auth { } static function assertUnlocked() { + + if(defined("BAIKAL_CONTEXT_INSTALL") || BAIKAL_CONTEXT_INSTALL !== TRUE) { + $sToolName = "Baïkal Install Tool"; + } else { + $sToolName = "Baïkal Admin"; + } + $bLocked = TRUE; $sEnableFile = BAIKAL_PATH_SPECIFIC . "ENABLE_ADMIN"; if(file_exists($sEnableFile)) { @@ -52,13 +59,13 @@ class Auth { // file has been created more than an hour ago // delete and declare locked if(!@unlink($sEnableFile)) { - die("

Baïkal Admin is locked.

To unlock it, delete and re-create an empty file named ENABLE_ADMIN in Specific/config.php"); + die("

" . $sToolName . " is locked.

To unlock it, delete and re-create an empty file named ENABLE_ADMIN in Specific/config.php"); } } } if($bLocked) { - die("

Baïkal Admin is locked.

To unlock it, create an empty file named ENABLE_ADMIN in Specific/"); + die("

" . $sToolName . " is locked.

To unlock it, create an empty file named ENABLE_ADMIN in Specific/"); } } diff --git a/CoreVersions/Baikal_0.1/Frameworks/BaikalAdmin/WWWRoot/install/index.php b/CoreVersions/Baikal_0.1/Frameworks/BaikalAdmin/WWWRoot/install/index.php index b12c99c..21c5f60 100644 --- a/CoreVersions/Baikal_0.1/Frameworks/BaikalAdmin/WWWRoot/install/index.php +++ b/CoreVersions/Baikal_0.1/Frameworks/BaikalAdmin/WWWRoot/install/index.php @@ -40,11 +40,24 @@ require_once(dirname(dirname(dirname(__FILE__))) . "/Core/Bootstrap.php"); # ../ # Create and setup a page object $oPage = new \Flake\Controller\Page(BAIKALADMIN_PATH_TEMPLATES . "Page/index.html"); $oPage->injectHTTPHeaders(); -$oPage->setTitle("Baïkal Install Tool"); +$oPage->setTitle("Baïkal Maintainance"); $oPage->setBaseUrl(BAIKAL_URI); $oPage->zone("navbar")->addBlock(new \BaikalAdmin\Controller\Navigation\Topbar\Install()); -$oPage->zone("Payload")->addBlock(new \BaikalAdmin\Controller\Install()); + +if(!defined("BAIKAL_CONFIGURED_VERSION")) { + # we have to upgrade Baïkal (existing installation) + $oPage->zone("Payload")->addBlock(new \BaikalAdmin\Controller\Install\Initialize()); + +} elseif(!defined("BAIKAL_ADMIN_PASSWORDHASH")) { + # we have to set an admin password + $oPage->zone("Payload")->addBlock(new \BaikalAdmin\Controller\Install\AdminPassword()); + +} else { + # we have to initialize Baïkal (new installation) + $oPage->zone("Payload")->addBlock(new \BaikalAdmin\Controller\Install\VersionUpgrade()); +} + # Route the request //$GLOBALS["ROUTER"]::route($oPage); diff --git a/CoreVersions/Baikal_0.1/Frameworks/Formal/Form.php b/CoreVersions/Baikal_0.1/Frameworks/Formal/Form.php index bdd94c2..5ca496d 100644 --- a/CoreVersions/Baikal_0.1/Frameworks/Formal/Form.php +++ b/CoreVersions/Baikal_0.1/Frameworks/Formal/Form.php @@ -205,11 +205,15 @@ class Form { } public function persisted() { - if(is_null($this->bPersisted)) { - throw new \Exception("\Formal\Form->persisted(): information is not available yet. This method may only be called after execute()"); + if($this->submitted()) { + if(is_null($this->bPersisted)) { + throw new \Exception("\Formal\Form->persisted(): information is not available yet. This method may only be called after execute()"); + } + + return $this->bPersisted; } - return $this->bPersisted; + return FALSE; } public function validateRequired($sValue, \Formal\Form\Morphology $oMorpho, \Formal\Element $oElement) { diff --git a/Specific/config.php b/Specific/config.php deleted file mode 100755 index d0cbcdc..0000000 --- a/Specific/config.php +++ /dev/null @@ -1,55 +0,0 @@ - -* All rights reserved -* -* http://baikal.codr.fr -* -* This script is part of the Baïkal Server project. The Baïkal -* Server project 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 2 of the License, or (at your option) any later version. -* -* The GNU General Public License can be found at -* http://www.gnu.org/copyleft/gpl.html. -* -* This script 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. -* -* This copyright notice MUST APPEAR in all copies of the script! -***************************************************************/ - -############################################################################## -# In this section: Required configuration: you *have* to customize -# these settings for Baïkal to run properly -# - -# Timezone of your users, if unsure, check http://en.wikipedia.org/wiki/List_of_tz_database_time_zones -define("BAIKAL_TIMEZONE", "Europe/Paris"); - -############################################################################## -# In this section: Optional configuration: you *may* customize these settings -# - -# CardDAV ON/OFF switch; default TRUE -define("BAIKAL_CARD_ENABLED", TRUE); - -# CalDAV ON/OFF switch; default TRUE -define("BAIKAL_CAL_ENABLED", TRUE); - -# Baïkal Web Admin interface ON/OFF; default FALSE -define("BAIKAL_ADMIN_ENABLED", TRUE); - -# Standalone Server, allowed or not; default FALSE -define("BAIKAL_STANDALONE_ALLOWED", TRUE); - -# Standalone Server, port number; default 8888 -define("BAIKAL_STANDALONE_PORT", 8888); - -# Baïkal Web interface admin password hash; Set by Core/Scripts/adminpassword.php -define("BAIKAL_ADMIN_PASSWORDHASH", "5746d6eb0ff2968c494e5d904b8ef4b6"); diff --git a/Specific/db/baikal.sqlite b/Specific/db/baikal.sqlite index 6613bdb1b1ee6ee18ac2715965441f771ccba978..4d4494e8137f336e8ff528dc3a94f99b078be778 100755 GIT binary patch delta 67 zcmZpe!PrnRL0T|~fq{V!h+%+tqK+|R(8h!%%!^q>SkN%fVipY!5Z=t9@rNA%uYe7K delta 640 zcmZ`%&ubGw6n<~tM3>anu5GFi(1ju?Y^t(t(i|GF(vnltLhI6F*WC$}CQV2p*n`l) zf1pzk^xl(Ry6w%Yr`}61MNo_2p}|wBcyh+1Rf@jhdo#@Y^}RQ<(b#y6n`yfQ00TWF z)Si^tF)YQ)+jIqK2WV%{W^ZV9S(&sfX;sFn;O5dLtI=Fp3s)LJwe_IQTkUCH37%Ek z&QugNd8_3-uGd$i?%lskPNm*-beyG=G61@ZMJ&c5g$54EsoRXw@KcH(2JTC7ocOA| zK1=a&%QTts670CKZW#C?7VEfWvSgPo`E)B5uMp>@C>ut9EI8s@Dg5~|_JKQ^#!<_% z?)`^(7*zRM6f}FR@tpSgy+7m6lozx9;@tGy?jXCSW^@E}q-OMiih13}g$!c?+~L%v zLbY1$k;473Uelc?l{?Z2(1}X%g$4)6#Y+*m>oD^=(jm~HS__n+j2aO!I)Ei9Mi{+= ztQ0N^t)57cnH)g|sac)h%ReoQc|6Qbc%Bzc6e_tWUw9aB?uFx=7lLm6GoZ8It3w^d z<2{YjDGc>Gp$_z2GzEA59QChJVp0lqDO8c)Y0qJw{!()Dxvd@+ZYtOc{R%gf<2U*R DE=s0d