aboutsummaryrefslogtreecommitdiffstats
path: root/phpBB/includes
diff options
context:
space:
mode:
authorMarc Alexander <admin@m-a-styles.de>2013-02-07 00:07:59 +0100
committerMarc Alexander <admin@m-a-styles.de>2013-02-07 00:07:59 +0100
commitc56db535b4f0c943777ab159c14729d724f5d232 (patch)
tree56a63ae78f97a990d3ca1512c5333aebd467895a /phpBB/includes
parent11aff559d4fcde5c2935aa6ebdfcfbe162d91f64 (diff)
parenteffaef6bddf49b9016d66bd64706392fcdb452b3 (diff)
downloadforums-c56db535b4f0c943777ab159c14729d724f5d232.tar
forums-c56db535b4f0c943777ab159c14729d724f5d232.tar.gz
forums-c56db535b4f0c943777ab159c14729d724f5d232.tar.bz2
forums-c56db535b4f0c943777ab159c14729d724f5d232.tar.xz
forums-c56db535b4f0c943777ab159c14729d724f5d232.zip
Merge branch 'develop' of https://github.com/phpbb/phpbb3 into feature/avatars
Conflicts: phpBB/config/services.yml
Diffstat (limited to 'phpBB/includes')
-rw-r--r--phpBB/includes/acp/acp_groups.php2
-rw-r--r--phpBB/includes/captcha/captcha_non_gd.php2
-rw-r--r--phpBB/includes/constants.php1
-rw-r--r--phpBB/includes/db/db_tools.php29
-rw-r--r--phpBB/includes/db/migration/exception.php55
-rw-r--r--phpBB/includes/db/migration/migration.php190
-rw-r--r--phpBB/includes/db/migration/tool/config.php150
-rw-r--r--phpBB/includes/db/migration/tool/interface.php33
-rw-r--r--phpBB/includes/db/migration/tool/module.php513
-rw-r--r--phpBB/includes/db/migration/tool/permission.php622
-rw-r--r--phpBB/includes/db/migrator.php762
-rw-r--r--phpBB/includes/extension/interface.php4
-rw-r--r--phpBB/includes/functions_user.php91
-rw-r--r--phpBB/includes/mcp/mcp_pm_reports.php1
-rw-r--r--phpBB/includes/mcp/mcp_reports.php21
-rw-r--r--phpBB/includes/ucp/ucp_activate.php2
-rw-r--r--phpBB/includes/ucp/ucp_groups.php2
-rw-r--r--phpBB/includes/ucp/ucp_pm_compose.php2
18 files changed, 2431 insertions, 51 deletions
diff --git a/phpBB/includes/acp/acp_groups.php b/phpBB/includes/acp/acp_groups.php
index 25e199ab32..92fd466214 100644
--- a/phpBB/includes/acp/acp_groups.php
+++ b/phpBB/includes/acp/acp_groups.php
@@ -400,7 +400,7 @@ class acp_groups
foreach ($test_variables as $test => $type)
{
- if (isset($submit_ary[$test]) && ($action == 'add' || $group_row['group_' . $test] != $submit_ary[$test] || in_array($test, $set_attributes)))
+ if (isset($submit_ary[$test]) && ($action == 'add' || $group_row['group_' . $test] != $submit_ary[$test] || isset($group_attributes['group_avatar']) && strpos($test, 'avatar') === 0 || in_array($test, $set_attributes)))
{
settype($submit_ary[$test], $type);
$group_attributes['group_' . $test] = $group_row['group_' . $test] = $submit_ary[$test];
diff --git a/phpBB/includes/captcha/captcha_non_gd.php b/phpBB/includes/captcha/captcha_non_gd.php
index c2b97423e6..bb5067cafa 100644
--- a/phpBB/includes/captcha/captcha_non_gd.php
+++ b/phpBB/includes/captcha/captcha_non_gd.php
@@ -118,7 +118,7 @@ class captcha
$new_line = '';
$end = strlen($scanline) - ceil($width/2);
- for ($i = floor($width/2); $i < $end; $i++)
+ for ($i = (int) floor($width / 2); $i < $end; $i++)
{
$pixel = ord($scanline{$i});
diff --git a/phpBB/includes/constants.php b/phpBB/includes/constants.php
index 68af41ab20..68c96a2759 100644
--- a/phpBB/includes/constants.php
+++ b/phpBB/includes/constants.php
@@ -237,6 +237,7 @@ define('ICONS_TABLE', $table_prefix . 'icons');
define('LANG_TABLE', $table_prefix . 'lang');
define('LOG_TABLE', $table_prefix . 'log');
define('LOGIN_ATTEMPT_TABLE', $table_prefix . 'login_attempts');
+define('MIGRATIONS_TABLE', $table_prefix . 'migrations');
define('MODERATOR_CACHE_TABLE', $table_prefix . 'moderator_cache');
define('MODULES_TABLE', $table_prefix . 'modules');
define('POLL_OPTIONS_TABLE', $table_prefix . 'poll_options');
diff --git a/phpBB/includes/db/db_tools.php b/phpBB/includes/db/db_tools.php
index 2bb016cebd..e8c26fa502 100644
--- a/phpBB/includes/db/db_tools.php
+++ b/phpBB/includes/db/db_tools.php
@@ -346,6 +346,17 @@ class phpbb_db_tools
}
/**
+ * Setter for {@link $return_statements return_statements}.
+ *
+ * @param bool $return_statements True if SQL should not be executed but returned as strings
+ * @return null
+ */
+ public function set_return_statements($return_statements)
+ {
+ $this->return_statements = $return_statements;
+ }
+
+ /**
* Gets a list of tables in the database.
*
* @return array Array of table names (all lower case)
@@ -674,6 +685,8 @@ class phpbb_db_tools
* Handle passed database update array.
* Expected structure...
* Key being one of the following
+ * drop_tables: Drop tables
+ * add_tables: Add tables
* change_columns: Column changes (only type, not name)
* add_columns: Add columns to a table
* drop_keys: Dropping keys
@@ -1817,6 +1830,22 @@ class phpbb_db_tools
case 'mssql':
case 'mssqlnative':
+ // remove default cosntraints first
+ // http://msdn.microsoft.com/en-us/library/aa175912%28v=sql.80%29.aspx
+ $statements[] = "DECLARE @drop_default_name VARCHAR(100), @cmd VARCHAR(1000)
+ SET @drop_default_name =
+ (SELECT so.name FROM sysobjects so
+ JOIN sysconstraints sc ON so.id = sc.constid
+ WHERE object_name(so.parent_obj) = '{$table_name}'
+ AND so.xtype = 'D'
+ AND sc.colid = (SELECT colid FROM syscolumns
+ WHERE id = object_id('{$table_name}')
+ AND name = '{$column_name}'))
+ IF @drop_default_name <> ''
+ BEGIN
+ SET @cmd = 'ALTER TABLE [{$table_name}] DROP CONSTRAINT [' + @drop_default_name + ']'
+ EXEC(@cmd)
+ END";
$statements[] = 'ALTER TABLE [' . $table_name . '] DROP COLUMN [' . $column_name . ']';
break;
diff --git a/phpBB/includes/db/migration/exception.php b/phpBB/includes/db/migration/exception.php
new file mode 100644
index 0000000000..ffdcd97780
--- /dev/null
+++ b/phpBB/includes/db/migration/exception.php
@@ -0,0 +1,55 @@
+<?php
+/**
+*
+* @package db
+* @copyright (c) 2012 phpBB Group
+* @license http://opensource.org/licenses/gpl-license.php GNU Public License v2
+*
+*/
+
+/**
+* @ignore
+*/
+if (!defined('IN_PHPBB'))
+{
+ exit;
+}
+
+/**
+* The migrator is responsible for applying new migrations in the correct order.
+*
+* @package db
+*/
+class phpbb_db_migration_exception extends \Exception
+{
+ /**
+ * Extra parameters sent to exception to aid in debugging
+ * @var array
+ */
+ protected $parameters;
+
+ /**
+ * Throw an exception.
+ *
+ * First argument is the error message.
+ * Additional arguments will be output with the error message.
+ */
+ public function __construct()
+ {
+ $parameters = func_get_args();
+ $message = array_shift($parameters);
+ parent::__construct($message);
+
+ $this->parameters = $parameters;
+ }
+
+ /**
+ * Output the error as a string
+ *
+ * @return string
+ */
+ public function __toString()
+ {
+ return $this->message . ': ' . var_export($this->parameters, true);
+ }
+}
diff --git a/phpBB/includes/db/migration/migration.php b/phpBB/includes/db/migration/migration.php
new file mode 100644
index 0000000000..5f14a6953c
--- /dev/null
+++ b/phpBB/includes/db/migration/migration.php
@@ -0,0 +1,190 @@
+<?php
+/**
+*
+* @package db
+* @copyright (c) 2011 phpBB Group
+* @license http://opensource.org/licenses/gpl-license.php GNU Public License v2
+*
+*/
+
+/**
+* @ignore
+*/
+if (!defined('IN_PHPBB'))
+{
+ exit;
+}
+
+/**
+* Abstract base class for database migrations
+*
+* Each migration consists of a set of schema and data changes to be implemented
+* in a subclass. This class provides various utility methods to simplify editing
+* a phpBB.
+*
+* @package db
+*/
+abstract class phpbb_db_migration
+{
+ /** @var phpbb_config */
+ protected $config;
+
+ /** @var phpbb_db_driver */
+ protected $db;
+
+ /** @var phpbb_db_tools */
+ protected $db_tools;
+
+ /** @var string */
+ protected $table_prefix;
+
+ /** @var string */
+ protected $phpbb_root_path;
+
+ /** @var string */
+ protected $php_ext;
+
+ /** @var array Errors, if any occured */
+ protected $errors;
+
+ /** @var array List of queries executed through $this->sql_query() */
+ protected $queries = array();
+
+ /**
+ * Constructor
+ *
+ * @param phpbb_config $config
+ * @param phpbb_db_driver $db
+ * @param phpbb_db_tools $db_tools
+ * @param string $phpbb_root_path
+ * @param string $php_ext
+ * @param string $table_prefix
+ */
+ public function __construct(phpbb_config $config, phpbb_db_driver $db, phpbb_db_tools $db_tools, $phpbb_root_path, $php_ext, $table_prefix)
+ {
+ $this->config = $config;
+ $this->db = $db;
+ $this->db_tools = $db_tools;
+ $this->table_prefix = $table_prefix;
+
+ $this->phpbb_root_path = $phpbb_root_path;
+ $this->php_ext = $php_ext;
+
+ $this->errors = array();
+ }
+
+ /**
+ * Defines other migrations to be applied first
+ *
+ * @return array An array of migration class names
+ */
+ static public function depends_on()
+ {
+ return array();
+ }
+
+ /**
+ * Allows you to check if the migration is effectively installed (entirely optional)
+ *
+ * This is checked when a migration is installed. If true is returned, the migration will be set as
+ * installed without performing the database changes.
+ * This function is intended to help moving to migrations from a previous database updater, where some
+ * migrations may have been installed already even though they are not yet listed in the migrations table.
+ *
+ * @return bool True if this migration is installed, False if this migration is not installed (checked on install)
+ */
+ public function effectively_installed()
+ {
+ return false;
+ }
+
+ /**
+ * Updates the database schema by providing a set of change instructions
+ *
+ * @return array Array of schema changes (compatible with db_tools->perform_schema_changes())
+ */
+ public function update_schema()
+ {
+ return array();
+ }
+
+ /**
+ * Reverts the database schema by providing a set of change instructions
+ *
+ * @return array Array of schema changes (compatible with db_tools->perform_schema_changes())
+ */
+ public function revert_schema()
+ {
+ return array();
+ }
+
+ /**
+ * Updates data by returning a list of instructions to be executed
+ *
+ * @return array Array of data update instructions
+ */
+ public function update_data()
+ {
+ return array();
+ }
+
+ /**
+ * Reverts data by returning a list of instructions to be executed
+ *
+ * @return array Array of data instructions that will be performed on revert
+ * NOTE: calls to tools (such as config.add) are automatically reverted when
+ * possible, so you should not attempt to revert those, this is mostly for
+ * otherwise unrevertable calls (custom functions for example)
+ */
+ public function revert_data()
+ {
+ return array();
+ }
+
+ /**
+ * Wrapper for running queries to generate user feedback on updates
+ *
+ * @param string $sql SQL query to run on the database
+ * @return mixed Query result from db->sql_query()
+ */
+ protected function sql_query($sql)
+ {
+ $this->queries[] = $sql;
+
+ $this->db->sql_return_on_error(true);
+
+ if ($sql === 'begin')
+ {
+ $result = $this->db->sql_transaction('begin');
+ }
+ else if ($sql === 'commit')
+ {
+ $result = $this->db->sql_transaction('commit');
+ }
+ else
+ {
+ $result = $this->db->sql_query($sql);
+ if ($this->db->sql_error_triggered)
+ {
+ $this->errors[] = array(
+ 'sql' => $this->db->sql_error_sql,
+ 'code' => $this->db->sql_error_returned,
+ );
+ }
+ }
+
+ $this->db->sql_return_on_error(false);
+
+ return $result;
+ }
+
+ /**
+ * Get the list of queries run
+ *
+ * @return array
+ */
+ public function get_queries()
+ {
+ return $this->queries;
+ }
+}
diff --git a/phpBB/includes/db/migration/tool/config.php b/phpBB/includes/db/migration/tool/config.php
new file mode 100644
index 0000000000..d9cc20053e
--- /dev/null
+++ b/phpBB/includes/db/migration/tool/config.php
@@ -0,0 +1,150 @@
+<?php
+/**
+*
+* @package migration
+* @copyright (c) 2012 phpBB Group
+* @license http://opensource.org/licenses/gpl-license.php GNU Public License v2
+*
+*/
+
+/**
+* Migration config tool
+*
+* @package db
+*/
+class phpbb_db_migration_tool_config implements phpbb_db_migration_tool_interface
+{
+ /** @var phpbb_config */
+ protected $config;
+
+ /**
+ * Constructor
+ *
+ * @param phpbb_config $config
+ */
+ public function __construct(phpbb_config $config)
+ {
+ $this->config = $config;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function get_name()
+ {
+ return 'config';
+ }
+
+ /**
+ * Add a config setting.
+ *
+ * @param string $config_name The name of the config setting
+ * you would like to add
+ * @param mixed $config_value The value of the config setting
+ * @param bool $is_dynamic True if it is dynamic (changes very often)
+ * and should not be stored in the cache, false if not.
+ * @return null
+ */
+ public function add($config_name, $config_value, $is_dynamic = false)
+ {
+ if (isset($this->config[$config_name]))
+ {
+ throw new phpbb_db_migration_exception('CONFIG_ALREADY_EXISTS', $config_name);
+ }
+
+ $this->config->set($config_name, $config_value, !$is_dynamic);
+ }
+
+ /**
+ * Update an existing config setting.
+ *
+ * @param string $config_name The name of the config setting you would
+ * like to update
+ * @param mixed $config_value The value of the config setting
+ * @return null
+ */
+ public function update($config_name, $config_value)
+ {
+ if (!isset($this->config[$config_name]))
+ {
+ throw new phpbb_db_migration_exception('CONFIG_NOT_EXIST', $config_name);
+ }
+
+ $this->config->set($config_name, $config_value);
+ }
+
+ /**
+ * Update a config setting if the first argument equal to the
+ * current config value
+ *
+ * @param string $compare If equal to the current config value, will be
+ * updated to the new config value, otherwise not
+ * @param string $config_name The name of the config setting you would
+ * like to update
+ * @param mixed $config_value The value of the config setting
+ * @return null
+ */
+ public function update_if_equals($compare, $config_name, $config_value)
+ {
+ if (!isset($this->config[$config_name]))
+ {
+ throw new phpbb_db_migration_exception('CONFIG_NOT_EXIST', $config_name);
+ }
+
+ $this->config->set_atomic($config_name, $compare, $config_value);
+ }
+
+ /**
+ * Remove an existing config setting.
+ *
+ * @param string $config_name The name of the config setting you would
+ * like to remove
+ * @return null
+ */
+ public function remove($config_name)
+ {
+ if (!isset($this->config[$config_name]))
+ {
+ throw new phpbb_db_migration_exception('CONFIG_NOT_EXIST', $config_name);
+ }
+
+ $this->config->delete($config_name);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function reverse()
+ {
+ $arguments = func_get_args();
+ $original_call = array_shift($arguments);
+
+ $call = false;
+ switch ($original_call)
+ {
+ case 'add':
+ $call = 'remove';
+ break;
+
+ case 'remove':
+ $call = 'add';
+ break;
+
+ case 'update_if_equals':
+ $call = 'update_if_equals';
+
+ // Set to the original value if the current value is what we compared to originally
+ $arguments = array(
+ $arguments[2],
+ $arguments[1],
+ $arguments[0],
+ );
+ break;
+ }
+
+ if ($call)
+ {
+ return call_user_func_array(array(&$this, $call), $arguments);
+ }
+ }
+}
diff --git a/phpBB/includes/db/migration/tool/interface.php b/phpBB/includes/db/migration/tool/interface.php
new file mode 100644
index 0000000000..ced53b2023
--- /dev/null
+++ b/phpBB/includes/db/migration/tool/interface.php
@@ -0,0 +1,33 @@
+<?php
+/**
+*
+* @package migration
+* @copyright (c) 2012 phpBB Group
+* @license http://opensource.org/licenses/gpl-license.php GNU Public License v2
+*
+*/
+
+/**
+* Migration tool interface
+*
+* @package db
+*/
+interface phpbb_db_migration_tool_interface
+{
+ /**
+ * Retrieve a short name used for commands in migrations.
+ *
+ * @return string short name
+ */
+ public function get_name();
+
+ /**
+ * Reverse an original install action
+ *
+ * First argument is the original call to the class (e.g. add, remove)
+ * After the first argument, send the original arguments to the function in the original call
+ *
+ * @return null
+ */
+ public function reverse();
+}
diff --git a/phpBB/includes/db/migration/tool/module.php b/phpBB/includes/db/migration/tool/module.php
new file mode 100644
index 0000000000..afe1f21ec5
--- /dev/null
+++ b/phpBB/includes/db/migration/tool/module.php
@@ -0,0 +1,513 @@
+<?php
+/**
+*
+* @package migration
+* @copyright (c) 2012 phpBB Group
+* @license http://opensource.org/licenses/gpl-license.php GNU Public License v2
+*
+*/
+
+/**
+* Migration module management tool
+*
+* @package db
+*/
+class phpbb_db_migration_tool_module implements phpbb_db_migration_tool_interface
+{
+ /** @var phpbb_cache_service */
+ protected $cache;
+
+ /** @var dbal */
+ protected $db;
+
+ /** @var phpbb_user */
+ protected $user;
+
+ /** @var string */
+ protected $phpbb_root_path;
+
+ /** @var string */
+ protected $php_ext;
+
+ /** @var string */
+ protected $modules_table;
+
+ /**
+ * Constructor
+ *
+ * @param phpbb_db_driver $db
+ * @param mixed $cache
+ * @param phpbb_user $user
+ * @param string $phpbb_root_path
+ * @param string $php_ext
+ * @param string $modules_table
+ */
+ public function __construct(phpbb_db_driver $db, phpbb_cache_service $cache, phpbb_user $user, $phpbb_root_path, $php_ext, $modules_table)
+ {
+ $this->db = $db;
+ $this->cache = $cache;
+ $this->user = $user;
+ $this->phpbb_root_path = $phpbb_root_path;
+ $this->php_ext = $php_ext;
+ $this->modules_table = $modules_table;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function get_name()
+ {
+ return 'module';
+ }
+
+ /**
+ * Module Exists
+ *
+ * Check if a module exists
+ *
+ * @param string $class The module class(acp|mcp|ucp)
+ * @param int|string|bool $parent The parent module_id|module_langname (0 for no parent).
+ * Use false to ignore the parent check and check class wide.
+ * @param int|string $module The module_id|module_langname you would like to
+ * check for to see if it exists
+ * @return bool true/false if module exists
+ */
+ public function exists($class, $parent, $module)
+ {
+ // the main root directory should return true
+ if (!$module)
+ {
+ return true;
+ }
+
+ $parent_sql = '';
+ if ($parent !== false)
+ {
+ // Allows '' to be sent as 0
+ $parent = $parent ?: 0;
+
+ if (!is_numeric($parent))
+ {
+ $sql = 'SELECT module_id
+ FROM ' . $this->modules_table . "
+ WHERE module_langname = '" . $this->db->sql_escape($parent) . "'
+ AND module_class = '" . $this->db->sql_escape($class) . "'";
+ $result = $this->db->sql_query($sql);
+ $module_id = $this->db->sql_fetchfield('module_id');
+ $this->db->sql_freeresult($result);
+
+ if (!$module_id)
+ {
+ return false;
+ }
+
+ $parent_sql = 'AND parent_id = ' . (int) $module_id;
+ }
+ else
+ {
+ $parent_sql = 'AND parent_id = ' . (int) $parent;
+ }
+ }
+
+ $sql = 'SELECT module_id
+ FROM ' . $this->modules_table . "
+ WHERE module_class = '" . $this->db->sql_escape($class) . "'
+ $parent_sql
+ AND " . ((is_numeric($module)) ? 'module_id = ' . (int) $module : "module_langname = '" . $this->db->sql_escape($module) . "'");
+ $result = $this->db->sql_query($sql);
+ $module_id = $this->db->sql_fetchfield('module_id');
+ $this->db->sql_freeresult($result);
+
+ if ($module_id)
+ {
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Module Add
+ *
+ * Add a new module
+ *
+ * @param string $class The module class(acp|mcp|ucp)
+ * @param int|string $parent The parent module_id|module_langname (0 for no parent)
+ * @param array $data an array of the data on the new module.
+ * This can be setup in two different ways.
+ * 1. The "manual" way. For inserting a category or one at a time.
+ * It will be merged with the base array shown a bit below,
+ * but at the least requires 'module_langname' to be sent, and,
+ * if you want to create a module (instead of just a category) you must
+ * send module_basename and module_mode.
+ * array(
+ * 'module_enabled' => 1,
+ * 'module_display' => 1,
+ * 'module_basename' => '',
+ * 'module_class' => $class,
+ * 'parent_id' => (int) $parent,
+ * 'module_langname' => '',
+ * 'module_mode' => '',
+ * 'module_auth' => '',
+ * )
+ * 2. The "automatic" way. For inserting multiple at a time based on the
+ * specs in the info file for the module(s). For this to work the
+ * modules must be correctly setup in the info file.
+ * An example follows (this would insert the settings, log, and flag
+ * modes from the includes/acp/info/acp_asacp.php file):
+ * array(
+ * 'module_basename' => 'asacp',
+ * 'modes' => array('settings', 'log', 'flag'),
+ * )
+ * Optionally you may not send 'modes' and it will insert all of the
+ * modules in that info file.
+ * @param string|bool $include_path If you would like to use a custom include
+ * path, specify that here
+ * @return null
+ */
+ public function add($class, $parent = 0, $data = array(), $include_path = false)
+ {
+ // Allows '' to be sent as 0
+ $parent = $parent ?: 0;
+
+ // allow sending the name as a string in $data to create a category
+ if (!is_array($data))
+ {
+ $data = array('module_langname' => $data);
+ }
+
+ if (!isset($data['module_langname']))
+ {
+ // The "automatic" way
+ $basename = (isset($data['module_basename'])) ? $data['module_basename'] : '';
+ $basename = str_replace(array('/', '\\'), '', $basename);
+ $class = str_replace(array('/', '\\'), '', $class);
+
+ $include_path = ($include_path === false) ? $this->phpbb_root_path . 'includes/' : $include_path;
+ $info_file = "$class/info/$basename.{$this->php_ext}";
+
+ // The manual and automatic ways both failed...
+ if (!file_exists($include_path . $info_file))
+ {
+ throw new phpbb_db_migration_exception('MODULE_INFO_FILE_NOT_EXIST', $class, $info_file);
+ }
+
+ $classname = "{$basename}_info";
+
+ if (!class_exists($classname))
+ {
+ include($include_path . $info_file);
+ }
+
+ $info = new $classname;
+ $module = $info->module();
+ unset($info);
+
+ $result = '';
+ foreach ($module['modes'] as $mode => $module_info)
+ {
+ if (!isset($data['modes']) || in_array($mode, $data['modes']))
+ {
+ $new_module = array(
+ 'module_basename' => $basename,
+ 'module_langname' => $module_info['title'],
+ 'module_mode' => $mode,
+ 'module_auth' => $module_info['auth'],
+ 'module_display' => (isset($module_info['display'])) ? $module_info['display'] : true,
+ 'before' => (isset($module_info['before'])) ? $module_info['before'] : false,
+ 'after' => (isset($module_info['after'])) ? $module_info['after'] : false,
+ );
+
+ // Run the "manual" way with the data we've collected.
+ $this->add($class, $parent, $new_module);
+ }
+ }
+
+ return;
+ }
+
+ // The "manual" way
+ $module_log_name = ((isset($this->user->lang[$data['module_langname']])) ? $this->user->lang[$data['module_langname']] : $data['module_langname']);
+ add_log('admin', 'LOG_MODULE_ADD', $module_log_name);
+
+ if (!is_numeric($parent))
+ {
+ $sql = 'SELECT module_id
+ FROM ' . $this->modules_table . "
+ WHERE module_langname = '" . $this->db->sql_escape($parent) . "'
+ AND module_class = '" . $this->db->sql_escape($class) . "'";
+ $result = $this->db->sql_query($sql);
+ $module_id = $this->db->sql_fetchfield('module_id');
+ $this->db->sql_freeresult($result);
+
+ if (!$module_id)
+ {
+ throw new phpbb_db_migration_exception('MODULE_PARENT_NOT_EXIST', $parent);
+ }
+
+ $parent = $data['parent_id'] = $module_id;
+ }
+ else if (!$this->exists($class, false, $parent))
+ {
+ throw new phpbb_db_migration_exception('MODULE_PARENT_NOT_EXIST', $parent);
+ }
+
+ if ($this->exists($class, $parent, $data['module_langname']))
+ {
+ throw new phpbb_db_migration_exception('MODULE_ALREADY_EXIST', $data['module_langname']);
+ }
+
+ if (!class_exists('acp_modules'))
+ {
+ include($this->phpbb_root_path . 'includes/acp/acp_modules.' . $this->php_ext);
+ $this->user->add_lang('acp/modules');
+ }
+ $acp_modules = new acp_modules();
+
+ $module_data = array(
+ 'module_enabled' => (isset($data['module_enabled'])) ? $data['module_enabled'] : 1,
+ 'module_display' => (isset($data['module_display'])) ? $data['module_display'] : 1,
+ 'module_basename' => (isset($data['module_basename'])) ? $data['module_basename'] : '',
+ 'module_class' => $class,
+ 'parent_id' => (int) $parent,
+ 'module_langname' => (isset($data['module_langname'])) ? $data['module_langname'] : '',
+ 'module_mode' => (isset($data['module_mode'])) ? $data['module_mode'] : '',
+ 'module_auth' => (isset($data['module_auth'])) ? $data['module_auth'] : '',
+ );
+ $result = $acp_modules->update_module_data($module_data, true);
+
+ // update_module_data can either return a string or an empty array...
+ if (is_string($result))
+ {
+ // Error
+ throw new phpbb_db_migration_exception('MODULE_ERROR', $result);
+ }
+ else
+ {
+ // Success
+
+ // Move the module if requested above/below an existing one
+ if (isset($data['before']) && $data['before'])
+ {
+ $sql = 'SELECT left_id
+ FROM ' . $this->modules_table . "
+ WHERE module_class = '" . $this->db->sql_escape($class) . "'
+ AND parent_id = " . (int) $parent . "
+ AND module_langname = '" . $this->db->sql_escape($data['before']) . "'";
+ $this->db->sql_query($sql);
+ $to_left = (int) $this->db->sql_fetchfield('left_id');
+
+ $sql = 'UPDATE ' . $this->modules_table . "
+ SET left_id = left_id + 2, right_id = right_id + 2
+ WHERE module_class = '" . $this->db->sql_escape($class) . "'
+ AND left_id >= $to_left
+ AND left_id < {$module_data['left_id']}";
+ $this->db->sql_query($sql);
+
+ $sql = 'UPDATE ' . $this->modules_table . "
+ SET left_id = $to_left, right_id = " . ($to_left + 1) . "
+ WHERE module_class = '" . $this->db->sql_escape($class) . "'
+ AND module_id = {$module_data['module_id']}";
+ $this->db->sql_query($sql);
+ }
+ else if (isset($data['after']) && $data['after'])
+ {
+ $sql = 'SELECT right_id
+ FROM ' . $this->modules_table . "
+ WHERE module_class = '" . $this->db->sql_escape($class) . "'
+ AND parent_id = " . (int) $parent . "
+ AND module_langname = '" . $this->db->sql_escape($data['after']) . "'";
+ $this->db->sql_query($sql);
+ $to_right = (int) $this->db->sql_fetchfield('right_id');
+
+ $sql = 'UPDATE ' . $this->modules_table . "
+ SET left_id = left_id + 2, right_id = right_id + 2
+ WHERE module_class = '" . $this->db->sql_escape($class) . "'
+ AND left_id >= $to_right
+ AND left_id < {$module_data['left_id']}";
+ $this->db->sql_query($sql);
+
+ $sql = 'UPDATE ' . $this->modules_table . '
+ SET left_id = ' . ($to_right + 1) . ', right_id = ' . ($to_right + 2) . "
+ WHERE module_class = '" . $this->db->sql_escape($class) . "'
+ AND module_id = {$module_data['module_id']}";
+ $this->db->sql_query($sql);
+ }
+ }
+
+ // Clear the Modules Cache
+ $this->cache->destroy("_modules_$class");
+ }
+
+ /**
+ * Module Remove
+ *
+ * Remove a module
+ *
+ * @param string $class The module class(acp|mcp|ucp)
+ * @param int|string|bool $parent The parent module_id|module_langname(0 for no parent).
+ * Use false to ignore the parent check and check class wide.
+ * @param int|string $module The module id|module_langname
+ * @param string|bool $include_path If you would like to use a custom include path,
+ * specify that here
+ * @return null
+ */
+ public function remove($class, $parent = 0, $module = '', $include_path = false)
+ {
+ // Imitation of module_add's "automatic" and "manual" method so the uninstaller works from the same set of instructions for umil_auto
+ if (is_array($module))
+ {
+ if (isset($module['module_langname']))
+ {
+ // Manual Method
+ return $this->remove($class, $parent, $module['module_langname'], $include_path);
+ }
+
+ // Failed.
+ if (!isset($module['module_basename']))
+ {
+ throw new phpbb_db_migration_exception('MODULE_NOT_EXIST');
+ }
+
+ // Automatic method
+ $basename = str_replace(array('/', '\\'), '', $module['module_basename']);
+ $class = str_replace(array('/', '\\'), '', $class);
+
+ $include_path = ($include_path === false) ? $this->phpbb_root_path . 'includes/' : $include_path;
+ $info_file = "$class/info/$basename.{$this->php_ext}";
+
+ if (!file_exists($include_path . $info_file))
+ {
+ throw new phpbb_db_migration_exception('MODULE_NOT_EXIST', $info_file);
+ }
+
+ $classname = "{$basename}_info";
+
+ if (!class_exists($classname))
+ {
+ include($include_path . $info_file);
+ }
+
+ $info = new $classname;
+ $module_info = $info->module();
+ unset($info);
+
+ foreach ($module_info['modes'] as $mode => $info)
+ {
+ if (!isset($module['modes']) || in_array($mode, $module['modes']))
+ {
+ $this->remove($class, $parent, $info['title']) . '<br />';
+ }
+ }
+ }
+ else
+ {
+ if (!$this->exists($class, $parent, $module))
+ {
+ throw new phpbb_db_migration_exception('MODULE_NOT_EXIST', ((isset($this->user->lang[$module])) ? $this->user->lang[$module] : $module));
+ }
+
+ $parent_sql = '';
+ if ($parent !== false)
+ {
+ // Allows '' to be sent as 0
+ $parent = ($parent) ?: 0;
+
+ if (!is_numeric($parent))
+ {
+ $sql = 'SELECT module_id
+ FROM ' . $this->modules_table . "
+ WHERE module_langname = '" . $this->db->sql_escape($parent) . "'
+ AND module_class = '" . $this->db->sql_escape($class) . "'";
+ $result = $this->db->sql_query($sql);
+ $module_id = $this->db->sql_fetchfield('module_id');
+ $this->db->sql_freeresult($result);
+
+ // we know it exists from the module_exists check
+ $parent_sql = 'AND parent_id = ' . (int) $module_id;
+ }
+ else
+ {
+ $parent_sql = 'AND parent_id = ' . (int) $parent;
+ }
+ }
+
+ $module_ids = array();
+ if (!is_numeric($module))
+ {
+ $sql = 'SELECT module_id
+ FROM ' . $this->modules_table . "
+ WHERE module_langname = '" . $this->db->sql_escape($module) . "'
+ AND module_class = '" . $this->db->sql_escape($class) . "'
+ $parent_sql";
+ $result = $this->db->sql_query($sql);
+ while ($module_id = $this->db->sql_fetchfield('module_id'))
+ {
+ $module_ids[] = (int) $module_id;
+ }
+ $this->db->sql_freeresult($result);
+
+ $module_name = $module;
+ }
+ else
+ {
+ $module = (int) $module;
+ $sql = 'SELECT module_langname
+ FROM ' . $this->modules_table . "
+ WHERE module_id = $module
+ AND module_class = '" . $this->db->sql_escape($class) . "'
+ $parent_sql";
+ $result = $this->db->sql_query($sql);
+ $module_name = $this->db->sql_fetchfield('module_id');
+ $this->db->sql_freeresult($result);
+
+ $module_ids[] = $module;
+ }
+
+ if (!class_exists('acp_modules'))
+ {
+ include($this->phpbb_root_path . 'includes/acp/acp_modules.' . $this->php_ext);
+ $this->user->add_lang('acp/modules');
+ }
+ $acp_modules = new acp_modules();
+ $acp_modules->module_class = $class;
+
+ foreach ($module_ids as $module_id)
+ {
+ $result = $acp_modules->delete_module($module_id);
+ if (!empty($result))
+ {
+ throw new phpbb_db_migration_exception('CANNOT_REMOVE_MODULE', $module_id);
+ }
+ }
+
+ $this->cache->destroy("_modules_$class");
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function reverse()
+ {
+ $arguments = func_get_args();
+ $original_call = array_shift($arguments);
+
+ $call = false;
+ switch ($original_call)
+ {
+ case 'add':
+ $call = 'remove';
+ break;
+
+ case 'remove':
+ $call = 'add';
+ break;
+ }
+
+ if ($call)
+ {
+ return call_user_func_array(array(&$this, $call), $arguments);
+ }
+ }
+}
diff --git a/phpBB/includes/db/migration/tool/permission.php b/phpBB/includes/db/migration/tool/permission.php
new file mode 100644
index 0000000000..001d090f5a
--- /dev/null
+++ b/phpBB/includes/db/migration/tool/permission.php
@@ -0,0 +1,622 @@
+<?php
+/**
+*
+* @package migration
+* @copyright (c) 2012 phpBB Group
+* @license http://opensource.org/licenses/gpl-license.php GNU Public License v2
+*
+*/
+
+/**
+* Migration permission management tool
+*
+* @package db
+*/
+class phpbb_db_migration_tool_permission implements phpbb_db_migration_tool_interface
+{
+ /** @var phpbb_auth */
+ protected $auth;
+
+ /** @var phpbb_cache_service */
+ protected $cache;
+
+ /** @var dbal */
+ protected $db;
+
+ /** @var string */
+ protected $phpbb_root_path;
+
+ /** @var string */
+ protected $php_ext;
+
+ /**
+ * Constructor
+ *
+ * @param phpbb_db_driver $db
+ * @param mixed $cache
+ * @param phpbb_auth $auth
+ * @param string $phpbb_root_path
+ * @param string $php_ext
+ */
+ public function __construct(phpbb_db_driver $db, phpbb_cache_service $cache, phpbb_auth $auth, $phpbb_root_path, $php_ext)
+ {
+ $this->db = $db;
+ $this->cache = $cache;
+ $this->auth = $auth;
+ $this->phpbb_root_path = $phpbb_root_path;
+ $this->php_ext = $php_ext;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function get_name()
+ {
+ return 'permission';
+ }
+
+ /**
+ * Permission Exists
+ *
+ * Check if a permission (auth) setting exists
+ *
+ * @param string $auth_option The name of the permission (auth) option
+ * @param bool $global True for checking a global permission setting,
+ * False for a local permission setting
+ * @return bool true if it exists, false if not
+ */
+ public function exists($auth_option, $global = true)
+ {
+ if ($global)
+ {
+ $type_sql = ' AND is_global = 1';
+ }
+ else
+ {
+ $type_sql = ' AND is_local = 1';
+ }
+
+ $sql = 'SELECT auth_option_id
+ FROM ' . ACL_OPTIONS_TABLE . "
+ WHERE auth_option = '" . $this->db->sql_escape($auth_option) . "'"
+ . $type_sql;
+ $result = $this->db->sql_query($sql);
+
+ $row = $this->db->sql_fetchrow($result);
+ $this->db->sql_freeresult($result);
+
+ if ($row)
+ {
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Permission Add
+ *
+ * Add a permission (auth) option
+ *
+ * @param string $auth_option The name of the permission (auth) option
+ * @param bool $global True for checking a global permission setting,
+ * False for a local permission setting
+ * @return null
+ */
+ public function add($auth_option, $global = true, $copy_from = false)
+ {
+ if ($this->exists($auth_option, $global))
+ {
+ throw new phpbb_db_migration_exception('PERMISSION_ALREADY_EXISTS', $auth_option);
+ }
+
+ // We've added permissions, so set to true to notify the user.
+ $this->permissions_added = true;
+
+ if (!class_exists('auth_admin'))
+ {
+ include($this->phpbb_root_path . 'includes/acp/auth.' . $this->php_ext);
+ }
+ $auth_admin = new auth_admin();
+
+ // We have to add a check to see if the !$global (if global, local, and if local, global) permission already exists. If it does, acl_add_option currently has a bug which would break the ACL system, so we are having a work-around here.
+ if ($this->exists($auth_option, !$global))
+ {
+ $sql_ary = array(
+ 'is_global' => 1,
+ 'is_local' => 1,
+ );
+ $sql = 'UPDATE ' . ACL_OPTIONS_TABLE . '
+ SET ' . $this->db->sql_build_array('UPDATE', $sql_ary) . "
+ WHERE auth_option = '" . $this->db->sql_escape($auth_option) . "'";
+ $this->db->sql_query($sql);
+ }
+ else
+ {
+ if ($global)
+ {
+ $auth_admin->acl_add_option(array('global' => array($auth_option)));
+ }
+ else
+ {
+ $auth_admin->acl_add_option(array('local' => array($auth_option)));
+ }
+ }
+
+ // The permission has been added, now we can copy it if needed
+ if ($copy_from && isset($auth_admin->acl_options['id'][$copy_from]))
+ {
+ $old_id = $auth_admin->acl_options['id'][$copy_from];
+ $new_id = $auth_admin->acl_options['id'][$auth_option];
+
+ $tables = array(ACL_GROUPS_TABLE, ACL_ROLES_DATA_TABLE, ACL_USERS_TABLE);
+
+ foreach ($tables as $table)
+ {
+ $sql = 'SELECT *
+ FROM ' . $table . '
+ WHERE auth_option_id = ' . $old_id;
+ $result = $this->db->sql_query($sql);
+
+ $sql_ary = array();
+ while ($row = $this->db->sql_fetchrow($result))
+ {
+ $row['auth_option_id'] = $new_id;
+ $sql_ary[] = $row;
+ }
+ $this->db->sql_freeresult($result);
+
+ if (!empty($sql_ary))
+ {
+ $this->db->sql_multi_insert($table, $sql_ary);
+ }
+ }
+
+ $auth_admin->acl_clear_prefetch();
+ }
+ }
+
+ /**
+ * Permission Remove
+ *
+ * Remove a permission (auth) option
+ *
+ * @param string $auth_option The name of the permission (auth) option
+ * @param bool $global True for checking a global permission setting,
+ * False for a local permission setting
+ * @return null
+ */
+ public function remove($auth_option, $global = true)
+ {
+ if (!$this->exists($auth_option, $global))
+ {
+ throw new phpbb_db_migration_exception('PERMISSION_NOT_EXIST', $auth_option);
+ }
+
+ if ($global)
+ {
+ $type_sql = ' AND is_global = 1';
+ }
+ else
+ {
+ $type_sql = ' AND is_local = 1';
+ }
+ $sql = 'SELECT auth_option_id, is_global, is_local
+ FROM ' . ACL_OPTIONS_TABLE . "
+ WHERE auth_option = '" . $this->db->sql_escape($auth_option) . "'" .
+ $type_sql;
+ $result = $this->db->sql_query($sql);
+ $row = $this->db->sql_fetchrow($result);
+ $this->db->sql_freeresult($result);
+
+ $id = (int) $row['auth_option_id'];
+
+ // If it is a local and global permission, do not remove the row! :P
+ if ($row['is_global'] && $row['is_local'])
+ {
+ $sql = 'UPDATE ' . ACL_OPTIONS_TABLE . '
+ SET ' . (($global) ? 'is_global = 0' : 'is_local = 0') . '
+ WHERE auth_option_id = ' . $id;
+ $this->db->sql_query($sql);
+ }
+ else
+ {
+ // Delete time
+ $tables = array(ACL_GROUPS_TABLE, ACL_ROLES_DATA_TABLE, ACL_USERS_TABLE, ACL_OPTIONS_TABLE);
+ foreach ($tables as $table)
+ {
+ $this->db->sql_query('DELETE FROM ' . $table . '
+ WHERE auth_option_id = ' . $id);
+ }
+ }
+
+ // Purge the auth cache
+ $this->cache->destroy('_acl_options');
+ $this->auth->acl_clear_prefetch();
+ }
+
+ /**
+ * Add a new permission role
+ *
+ * @param string $role_name The new role name
+ * @param sting $role_type The type (u_, m_, a_)
+ * @return null
+ */
+ public function role_add($role_name, $role_type, $role_description = '')
+ {
+ $sql = 'SELECT role_id
+ FROM ' . ACL_ROLES_TABLE . "
+ WHERE role_name = '" . $this->db->sql_escape($role_name) . "'";
+ $this->db->sql_query($sql);
+ $role_id = (int) $this->db->sql_fetchfield('role_id');
+
+ if ($role_id)
+ {
+ throw new phpbb_db_migration_exception('ROLE_ALREADY_EXISTS', $old_role_name);
+ }
+
+ $sql = 'SELECT MAX(role_order) AS max_role_order
+ FROM ' . ACL_ROLES_TABLE . "
+ WHERE role_type = '" . $this->db->sql_escape($role_type) . "'";
+ $this->db->sql_query($sql);
+ $role_order = (int) $this->db->sql_fetchfield('max_role_order');
+ $role_order = (!$role_order) ? 1 : $role_order + 1;
+
+ $sql_ary = array(
+ 'role_name' => $role_name,
+ 'role_description' => $role_description,
+ 'role_type' => $role_type,
+ 'role_order' => $role_order,
+ );
+
+ $sql = 'INSERT INTO ' . ACL_ROLES_TABLE . ' ' . $this->db->sql_build_array('INSERT', $sql_ary);
+ $this->db->sql_query($sql);
+ }
+
+ /**
+ * Update the name on a permission role
+ *
+ * @param string $old_role_name The old role name
+ * @param string $new_role_name The new role name
+ * @return null
+ */
+ public function role_update($old_role_name, $new_role_name)
+ {
+ $sql = 'SELECT role_id
+ FROM ' . ACL_ROLES_TABLE . "
+ WHERE role_name = '" . $this->db->sql_escape($old_role_name) . "'";
+ $this->db->sql_query($sql);
+ $role_id = (int) $this->db->sql_fetchfield('role_id');
+
+ if (!$role_id)
+ {
+ throw new phpbb_db_migration_exception('ROLE_NOT_EXISTS', $old_role_name);
+ }
+
+ $sql = 'UPDATE ' . ACL_ROLES_TABLE . "
+ SET role_name = '" . $this->db->sql_escape($new_role_name) . "'
+ WHERE role_name = '" . $this->db->sql_escape($old_role_name) . "'";
+ $this->db->sql_query($sql);
+ }
+
+ /**
+ * Remove a permission role
+ *
+ * @param string $role_name The role name to remove
+ * @return null
+ */
+ public function role_remove($role_name)
+ {
+ $sql = 'SELECT role_id
+ FROM ' . ACL_ROLES_TABLE . "
+ WHERE role_name = '" . $this->db->sql_escape($role_name) . "'";
+ $this->db->sql_query($sql);
+ $role_id = (int) $this->db->sql_fetchfield('role_id');
+
+ if (!$role_id)
+ {
+ throw new phpbb_db_migration_exception('ROLE_NOT_EXIST', $role_name);
+ }
+
+ $sql = 'DELETE FROM ' . ACL_ROLES_DATA_TABLE . '
+ WHERE role_id = ' . $role_id;
+ $this->db->sql_query($sql);
+
+ $sql = 'DELETE FROM ' . ACL_ROLES_TABLE . '
+ WHERE role_id = ' . $role_id;
+ $this->db->sql_query($sql);
+
+ $this->auth->acl_clear_prefetch();
+ }
+
+ /**
+ * Permission Set
+ *
+ * Allows you to set permissions for a certain group/role
+ *
+ * @param string $name The name of the role/group
+ * @param string|array $auth_option The auth_option or array of
+ * auth_options you would like to set
+ * @param string $type The type (role|group)
+ * @param bool $has_permission True if you want to give them permission,
+ * false if you want to deny them permission
+ * @return null
+ */
+ public function permission_set($name, $auth_option, $type = 'role', $has_permission = true)
+ {
+ if (!is_array($auth_option))
+ {
+ $auth_option = array($auth_option);
+ }
+
+ $new_auth = array();
+ $sql = 'SELECT auth_option_id
+ FROM ' . ACL_OPTIONS_TABLE . '
+ WHERE ' . $this->db->sql_in_set('auth_option', $auth_option);
+ $result = $this->db->sql_query($sql);
+ while ($row = $this->db->sql_fetchrow($result))
+ {
+ $new_auth[] = (int) $row['auth_option_id'];
+ }
+ $this->db->sql_freeresult($result);
+
+ if (empty($new_auth))
+ {
+ return;
+ }
+
+ $current_auth = array();
+
+ $type = (string) $type; // Prevent PHP bug.
+
+ switch ($type)
+ {
+ case 'role':
+ $sql = 'SELECT role_id
+ FROM ' . ACL_ROLES_TABLE . "
+ WHERE role_name = '" . $this->db->sql_escape($name) . "'";
+ $this->db->sql_query($sql);
+ $role_id = (int) $this->db->sql_fetchfield('role_id');
+
+ if (!$role_id)
+ {
+ throw new phpbb_db_migration_exception('ROLE_NOT_EXIST', $name);
+ }
+
+ $sql = 'SELECT auth_option_id, auth_setting
+ FROM ' . ACL_ROLES_DATA_TABLE . '
+ WHERE role_id = ' . $role_id;
+ $result = $this->db->sql_query($sql);
+ while ($row = $this->db->sql_fetchrow($result))
+ {
+ $current_auth[$row['auth_option_id']] = $row['auth_setting'];
+ }
+ $this->db->sql_freeresult($result);
+ break;
+
+ case 'group':
+ $sql = 'SELECT group_id
+ FROM ' . GROUPS_TABLE . "
+ WHERE group_name = '" . $this->db->sql_escape($name) . "'";
+ $this->db->sql_query($sql);
+ $group_id = (int) $this->db->sql_fetchfield('group_id');
+
+ if (!$group_id)
+ {
+ throw new phpbb_db_migration_exception('GROUP_NOT_EXIST', $name);
+ }
+
+ // If the group has a role set for them we will add the requested permissions to that role.
+ $sql = 'SELECT auth_role_id
+ FROM ' . ACL_GROUPS_TABLE . '
+ WHERE group_id = ' . $group_id . '
+ AND auth_role_id <> 0
+ AND forum_id = 0';
+ $this->db->sql_query($sql);
+ $role_id = (int) $this->db->sql_fetchfield('auth_role_id');
+ if ($role_id)
+ {
+ $sql = 'SELECT role_name
+ FROM ' . ACL_ROLES_TABLE . '
+ WHERE role_id = ' . $role_id;
+ $this->db->sql_query($sql);
+ $role_name = $this->db->sql_fetchfield('role_name');
+
+ return $this->set($role_name, $auth_option, 'role', $has_permission);
+ }
+
+ $sql = 'SELECT auth_option_id, auth_setting
+ FROM ' . ACL_GROUPS_TABLE . '
+ WHERE group_id = ' . $group_id;
+ $result = $this->db->sql_query($sql);
+ while ($row = $this->db->sql_fetchrow($result))
+ {
+ $current_auth[$row['auth_option_id']] = $row['auth_setting'];
+ }
+ $this->db->sql_freeresult($result);
+ break;
+ }
+
+ $sql_ary = array();
+ switch ($type)
+ {
+ case 'role':
+ foreach ($new_auth as $auth_option_id)
+ {
+ if (!isset($current_auth[$auth_option_id]))
+ {
+ $sql_ary[] = array(
+ 'role_id' => $role_id,
+ 'auth_option_id' => $auth_option_id,
+ 'auth_setting' => $has_permission,
+ );
+ }
+ }
+
+ $this->db->sql_multi_insert(ACL_ROLES_DATA_TABLE, $sql_ary);
+ break;
+
+ case 'group':
+ foreach ($new_auth as $auth_option_id)
+ {
+ if (!isset($current_auth[$auth_option_id]))
+ {
+ $sql_ary[] = array(
+ 'group_id' => $group_id,
+ 'auth_option_id' => $auth_option_id,
+ 'auth_setting' => $has_permission,
+ );
+ }
+ }
+
+ $this->db->sql_multi_insert(ACL_GROUPS_TABLE, $sql_ary);
+ break;
+ }
+
+ $this->auth->acl_clear_prefetch();
+ }
+
+ /**
+ * Permission Unset
+ *
+ * Allows you to unset (remove) permissions for a certain group/role
+ *
+ * @param string $name The name of the role/group
+ * @param string|array $auth_option The auth_option or array of
+ * auth_options you would like to set
+ * @param string $type The type (role|group)
+ * @return null
+ */
+ public function permission_unset($name, $auth_option, $type = 'role')
+ {
+ if (!is_array($auth_option))
+ {
+ $auth_option = array($auth_option);
+ }
+
+ $to_remove = array();
+ $sql = 'SELECT auth_option_id
+ FROM ' . ACL_OPTIONS_TABLE . '
+ WHERE ' . $this->db->sql_in_set('auth_option', $auth_option);
+ $result = $this->db->sql_query($sql);
+ while ($row = $this->db->sql_fetchrow($result))
+ {
+ $to_remove[] = (int) $row['auth_option_id'];
+ }
+ $this->db->sql_freeresult($result);
+
+ if (empty($to_remove))
+ {
+ return;
+ }
+
+ $type = (string) $type; // Prevent PHP bug.
+
+ switch ($type)
+ {
+ case 'role':
+ $sql = 'SELECT role_id
+ FROM ' . ACL_ROLES_TABLE . "
+ WHERE role_name = '" . $this->db->sql_escape($name) . "'";
+ $this->db->sql_query($sql);
+ $role_id = (int) $this->db->sql_fetchfield('role_id');
+
+ if (!$role_id)
+ {
+ throw new phpbb_db_migration_exception('ROLE_NOT_EXIST', $name);
+ }
+
+ $sql = 'DELETE FROM ' . ACL_ROLES_DATA_TABLE . '
+ WHERE ' . $this->db->sql_in_set('auth_option_id', $to_remove);
+ $this->db->sql_query($sql);
+ break;
+
+ case 'group':
+ $sql = 'SELECT group_id
+ FROM ' . GROUPS_TABLE . "
+ WHERE group_name = '" . $this->db->sql_escape($name) . "'";
+ $this->db->sql_query($sql);
+ $group_id = (int) $this->db->sql_fetchfield('group_id');
+
+ if (!$group_id)
+ {
+ throw new phpbb_db_migration_exception('GROUP_NOT_EXIST', $name);
+ }
+
+ // If the group has a role set for them we will remove the requested permissions from that role.
+ $sql = 'SELECT auth_role_id
+ FROM ' . ACL_GROUPS_TABLE . '
+ WHERE group_id = ' . $group_id . '
+ AND auth_role_id <> 0';
+ $this->db->sql_query($sql);
+ $role_id = (int) $this->db->sql_fetchfield('auth_role_id');
+ if ($role_id)
+ {
+ $sql = 'SELECT role_name
+ FROM ' . ACL_ROLES_TABLE . '
+ WHERE role_id = ' . $role_id;
+ $this->db->sql_query($sql);
+ $role_name = $this->db->sql_fetchfield('role_name');
+
+ return $this->permission_unset($role_name, $auth_option, 'role');
+ }
+
+ $sql = 'DELETE FROM ' . ACL_GROUPS_TABLE . '
+ WHERE ' . $this->db->sql_in_set('auth_option_id', $to_remove);
+ $this->db->sql_query($sql);
+ break;
+ }
+
+ $this->auth->acl_clear_prefetch();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function reverse()
+ {
+ $arguments = func_get_args();
+ $original_call = array_shift($arguments);
+
+ $call = false;
+ switch ($original_call)
+ {
+ case 'add':
+ $call = 'remove';
+ break;
+
+ case 'remove':
+ $call = 'add';
+ break;
+
+ case 'permission_set':
+ $call = 'permission_unset';
+ break;
+
+ case 'permission_unset':
+ $call = 'permission_set';
+ break;
+
+ case 'role_add':
+ $call = 'role_remove';
+ break;
+
+ case 'role_remove':
+ $call = 'role_add';
+ break;
+
+ case 'role_update':
+ // Set to the original value if the current value is what we compared to originally
+ $arguments = array(
+ $arguments[1],
+ $arguments[0],
+ );
+ break;
+ }
+
+ if ($call)
+ {
+ return call_user_func_array(array(&$this, $call), $arguments);
+ }
+ }
+}
diff --git a/phpBB/includes/db/migrator.php b/phpBB/includes/db/migrator.php
new file mode 100644
index 0000000000..4456600b0a
--- /dev/null
+++ b/phpBB/includes/db/migrator.php
@@ -0,0 +1,762 @@
+<?php
+/**
+*
+* @package db
+* @copyright (c) 2011 phpBB Group
+* @license http://opensource.org/licenses/gpl-license.php GNU Public License
+*
+*/
+
+/**
+* @ignore
+*/
+if (!defined('IN_PHPBB'))
+{
+ exit;
+}
+
+/**
+* The migrator is responsible for applying new migrations in the correct order.
+*
+* @package db
+*/
+class phpbb_db_migrator
+{
+ /** @var phpbb_config */
+ protected $config;
+
+ /** @var phpbb_db_driver */
+ protected $db;
+
+ /** @var phpbb_db_tools */
+ protected $db_tools;
+
+ /** @var string */
+ protected $table_prefix;
+
+ /** @var string */
+ protected $phpbb_root_path;
+
+ /** @var string */
+ protected $php_ext;
+
+ /** @var string */
+ protected $migrations_table;
+
+ /**
+ * State of all migrations
+ *
+ * (SELECT * FROM migrations table)
+ *
+ * @var array
+ */
+ protected $migration_state = array();
+
+ /**
+ * Array of all migrations available to be run
+ *
+ * @var array
+ */
+ protected $migrations = array();
+
+ /**
+ * 'name' and 'class' of the last migration run
+ *
+ * @var array
+ */
+ public $last_run_migration = false;
+
+ /**
+ * Constructor of the database migrator
+ */
+ public function __construct(phpbb_config $config, phpbb_db_driver $db, phpbb_db_tools $db_tools, $migrations_table, $phpbb_root_path, $php_ext, $table_prefix, $tools)
+ {
+ $this->config = $config;
+ $this->db = $db;
+ $this->db_tools = $db_tools;
+
+ $this->migrations_table = $migrations_table;
+
+ $this->phpbb_root_path = $phpbb_root_path;
+ $this->php_ext = $php_ext;
+
+ $this->table_prefix = $table_prefix;
+
+ foreach ($tools as $tool)
+ {
+ $this->tools[$tool->get_name()] = $tool;
+ }
+
+ $this->load_migration_state();
+ }
+
+ /**
+ * Loads all migrations and their application state from the database.
+ *
+ * @return null
+ */
+ public function load_migration_state()
+ {
+ $this->migration_state = array();
+
+ $sql = "SELECT *
+ FROM " . $this->migrations_table;
+ $result = $this->db->sql_query($sql);
+
+ while ($migration = $this->db->sql_fetchrow($result))
+ {
+ $this->migration_state[$migration['migration_name']] = $migration;
+
+ $this->migration_state[$migration['migration_name']]['migration_depends_on'] = unserialize($migration['migration_depends_on']);
+ }
+
+ $this->db->sql_freeresult($result);
+ }
+
+ /**
+ * Sets the list of available migration class names to the given array.
+ *
+ * @param array $class_names An array of migration class names
+ * @return null
+ */
+ public function set_migrations($class_names)
+ {
+ $this->migrations = $class_names;
+ }
+
+ /**
+ * This function adds all migrations in a specified directory to the migrations table
+ *
+ * THIS SHOULD NOT GENERALLY BE USED! THIS IS FOR THE PHPBB INSTALLER.
+ * THIS WILL THROW ERRORS IF MIGRATIONS ALREADY EXIST IN THE TABLE, DO NOT CALL MORE THAN ONCE!
+ *
+ * @param string $path Path to migration data files
+ * @param bool $recursive Set to true to also load data files from subdirectories
+ * @return null
+ */
+ public function populate_migrations_from_directory($path, $recursive = true)
+ {
+ $existing_migrations = $this->migrations;
+
+ $this->migrations = array();
+ $this->load_migrations($path, true, $recursive);
+
+ foreach ($this->migrations as $name)
+ {
+ if ($this->migration_state($name) === false)
+ {
+ $state = array(
+ 'migration_depends_on' => $name::depends_on(),
+ 'migration_schema_done' => true,
+ 'migration_data_done' => true,
+ 'migration_data_state' => '',
+ 'migration_start_time' => time(),
+ 'migration_end_time' => time(),
+ );
+ $this->insert_migration($name, $state);
+ }
+ }
+
+ $this->migrations = $existing_migrations;
+ }
+
+ /**
+ * Load migration data files from a directory
+ *
+ * Migration data files loaded with this function MUST contain
+ * ONLY ONE class in them (or an exception will be thrown).
+ *
+ * @param string $path Path to migration data files
+ * @param bool $check_fulfillable If TRUE (default), we will check
+ * if all of the migrations are fulfillable after loading them.
+ * If FALSE, we will not check. You SHOULD check at least once
+ * to prevent errors (if including multiple directories, check
+ * with the last call to prevent throwing errors unnecessarily).
+ * @param bool $recursive Set to true to also load data files from subdirectories
+ * @return array Array of migration names
+ */
+ public function load_migrations($path, $check_fulfillable = true, $recursive = true)
+ {
+ if (!is_dir($path))
+ {
+ throw new phpbb_db_migration_exception('DIRECTORY INVALID', $path);
+ }
+
+ $handle = opendir($path);
+ while (($file = readdir($handle)) !== false)
+ {
+ if ($file == '.' || $file == '..')
+ {
+ continue;
+ }
+
+ // Recursion through subdirectories
+ if (is_dir($path . $file) && $recursive)
+ {
+ $this->load_migrations($path . $file . '/', $check_fulfillable, $recursive);
+ }
+
+ if (strpos($file, '_') !== 0 && strrpos($file, '.' . $this->php_ext) === (strlen($file) - strlen($this->php_ext) - 1))
+ {
+ // We try to find what class existed by comparing the classes declared before and after including the file.
+ $declared_classes = get_declared_classes();
+
+ include ($path . $file);
+
+ $added_classes = array_diff(get_declared_classes(), $declared_classes);
+
+ if (
+ // If two classes have been added and phpbb_db_migration is one of them, we've only added one real migration
+ !(sizeof($added_classes) == 2 && in_array('phpbb_db_migration', $added_classes)) &&
+ // Otherwise there should only be one class added
+ sizeof($added_classes) != 1
+ )
+ {
+ throw new phpbb_db_migration_exception('MIGRATION DATA FILE INVALID', $path . $file);
+ }
+
+ $name = array_pop($added_classes);
+
+ if (!in_array($name, $this->migrations))
+ {
+ $this->migrations[] = $name;
+ }
+ }
+ }
+
+ if ($check_fulfillable)
+ {
+ foreach ($this->migrations as $name)
+ {
+ if ($this->unfulfillable($name))
+ {
+ throw new phpbb_db_migration_exception('MIGRATION NOT FULFILLABLE', $name);
+ }
+ }
+ }
+
+ return $this->migrations;
+ }
+
+ /**
+ * Runs a single update step from the next migration to be applied.
+ *
+ * The update step can either be a schema or a (partial) data update. To
+ * check if update() needs to be called again use the finished() method.
+ *
+ * @return null
+ */
+ public function update()
+ {
+ foreach ($this->migrations as $name)
+ {
+ if (!isset($this->migration_state[$name]) ||
+ !$this->migration_state[$name]['migration_schema_done'] ||
+ !$this->migration_state[$name]['migration_data_done'])
+ {
+ if (!$this->try_apply($name))
+ {
+ continue;
+ }
+ else
+ {
+ return;
+ }
+ }
+ }
+ }
+
+ /**
+ * Attempts to apply a step of the given migration or one of its dependencies
+ *
+ * @param string The class name of the migration
+ * @return bool Whether any update step was successfully run
+ */
+ protected function try_apply($name)
+ {
+ if (!class_exists($name))
+ {
+ return false;
+ }
+
+ $migration = $this->get_migration($name);
+
+ $state = (isset($this->migration_state[$name])) ?
+ $this->migration_state[$name] :
+ array(
+ 'migration_depends_on' => $migration->depends_on(),
+ 'migration_schema_done' => false,
+ 'migration_data_done' => false,
+ 'migration_data_state' => '',
+ 'migration_start_time' => 0,
+ 'migration_end_time' => 0,
+ );
+
+ foreach ($state['migration_depends_on'] as $depend)
+ {
+ if (!isset($this->migration_state[$depend]) ||
+ !$this->migration_state[$depend]['migration_schema_done'] ||
+ !$this->migration_state[$depend]['migration_data_done'])
+ {
+ return $this->try_apply($depend);
+ }
+ }
+
+ $this->last_run_migration = array(
+ 'name' => $name,
+ 'class' => $migration,
+ );
+
+ if ($migration->effectively_installed())
+ {
+ $state = array(
+ 'migration_depends_on' => $migration->depends_on(),
+ 'migration_schema_done' => true,
+ 'migration_data_done' => true,
+ 'migration_data_state' => '',
+ 'migration_start_time' => 0,
+ 'migration_end_time' => 0,
+ );
+ }
+ else
+ {
+ if (!isset($this->migration_state[$name]))
+ {
+ $state['migration_start_time'] = time();
+ $this->insert_migration($name, $state);
+ }
+ }
+
+ if (!$state['migration_schema_done'])
+ {
+ $this->apply_schema_changes($migration->update_schema());
+ $state['migration_schema_done'] = true;
+ }
+ else if (!$state['migration_data_done'])
+ {
+ try
+ {
+ $result = $this->process_data_step($migration->update_data(), $state['migration_data_state']);
+
+ $state['migration_data_state'] = ($result === true) ? '' : $result;
+ $state['migration_data_done'] = ($result === true);
+ $state['migration_end_time'] = ($result === true) ? time() : 0;
+ }
+ catch (phpbb_db_migration_exception $e)
+ {
+ // Revert the schema changes
+ $this->revert($name);
+
+ // Rethrow exception
+ throw $e;
+ }
+ }
+
+ $insert = $state;
+ $insert['migration_depends_on'] = serialize($state['migration_depends_on']);
+ $sql = 'UPDATE ' . $this->migrations_table . '
+ SET ' . $this->db->sql_build_array('UPDATE', $insert) . "
+ WHERE migration_name = '" . $this->db->sql_escape($name) . "'";
+ $this->db->sql_query($sql);
+
+ $this->migration_state[$name] = $state;
+
+ return true;
+ }
+
+ /**
+ * Runs a single revert step from the last migration installed
+ *
+ * YOU MUST ADD/SET ALL MIGRATIONS THAT COULD BE DEPENDENT ON THE MIGRATION TO REVERT TO BEFORE CALLING THIS METHOD!
+ * The revert step can either be a schema or a (partial) data revert. To
+ * check if revert() needs to be called again use the migration_state() method.
+ *
+ * @param string $migration String migration name to revert (including any that depend on this migration)
+ * @return null
+ */
+ public function revert($migration)
+ {
+ if (!isset($this->migration_state[$migration]))
+ {
+ // Not installed
+ return;
+ }
+
+ foreach ($this->migration_state as $name => $state)
+ {
+ if (!empty($state['migration_depends_on']) && in_array($migration, $state['migration_depends_on']))
+ {
+ $this->revert($name);
+ }
+ }
+
+ $this->try_revert($migration);
+ }
+
+ /**
+ * Attempts to revert a step of the given migration or one of its dependencies
+ *
+ * @param string The class name of the migration
+ * @return bool Whether any update step was successfully run
+ */
+ protected function try_revert($name)
+ {
+ if (!class_exists($name))
+ {
+ return false;
+ }
+
+ $migration = $this->get_migration($name);
+
+ $state = $this->migration_state[$name];
+
+ $this->last_run_migration = array(
+ 'name' => $name,
+ 'class' => $migration,
+ );
+
+ if ($state['migration_data_done'])
+ {
+ if ($state['migration_data_state'] !== 'revert_data')
+ {
+ $result = $this->process_data_step($migration->update_data(), $state['migration_data_state'], true);
+
+ $state['migration_data_state'] = ($result === true) ? 'revert_data' : $result;
+ }
+ else
+ {
+ $result = $this->process_data_step($migration->revert_data(), $state['migration_data_state'], false);
+
+ $state['migration_data_state'] = ($result === true) ? '' : $result;
+ $state['migration_data_done'] = ($result === true) ? false : true;
+ }
+
+ $insert = $state;
+ $insert['migration_depends_on'] = serialize($state['migration_depends_on']);
+ $sql = 'UPDATE ' . $this->migrations_table . '
+ SET ' . $this->db->sql_build_array('UPDATE', $insert) . "
+ WHERE migration_name = '" . $this->db->sql_escape($name) . "'";
+ $this->db->sql_query($sql);
+
+ $this->migration_state[$name] = $state;
+ }
+ else
+ {
+ $this->apply_schema_changes($migration->revert_schema());
+
+ $sql = 'DELETE FROM ' . $this->migrations_table . "
+ WHERE migration_name = '" . $this->db->sql_escape($name) . "'";
+ $this->db->sql_query($sql);
+
+ unset($this->migration_state[$name]);
+ }
+
+ return true;
+ }
+
+ /**
+ * Apply schema changes from a migration
+ *
+ * Just calls db_tools->perform_schema_changes
+ *
+ * @param array $schema_changes from migration
+ */
+ protected function apply_schema_changes($schema_changes)
+ {
+ $this->db_tools->perform_schema_changes($schema_changes);
+ }
+
+ /**
+ * Process the data step of the migration
+ *
+ * @param array $steps The steps to run
+ * @param bool|string $state Current state of the migration
+ * @param bool $revert true to revert a data step
+ * @return bool|string migration state. True if completed, serialized array if not finished
+ */
+ protected function process_data_step($steps, $state, $revert = false)
+ {
+ $state = ($state) ? unserialize($state) : false;
+
+ foreach ($steps as $step_identifier => $step)
+ {
+ $last_result = false;
+ if ($state)
+ {
+ // Continue until we reach the step that matches the last step called
+ if ($state['step'] != $step_identifier)
+ {
+ continue;
+ }
+
+ // We send the result from last time to the callable function
+ $last_result = $state['result'];
+
+ // Set state to false since we reached the point we were at
+ $state = false;
+ }
+
+ try
+ {
+ // Result will be null or true if everything completed correctly
+ $result = $this->run_step($step, $last_result, $revert);
+ if ($result !== null && $result !== true)
+ {
+ return serialize(array(
+ 'result' => $result,
+ 'step' => $step_identifier,
+ ));
+ }
+ }
+ catch (phpbb_db_migration_exception $e)
+ {
+ // We should try rolling back here
+ foreach ($steps as $reverse_step_identifier => $reverse_step)
+ {
+ // If we've reached the current step we can break because we reversed everything that was run
+ if ($reverse_step_identifier == $step_identifier)
+ {
+ break;
+ }
+
+ // Reverse the step that was run
+ $result = $this->run_step($reverse_step, false, !$revert);
+ }
+
+ // rethrow the exception
+ throw $e;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Run a single step
+ *
+ * An exception should be thrown if an error occurs
+ *
+ * @param mixed $step Data step from migration
+ * @param mixed $last_result Result to pass to the callable (only for 'custom' method)
+ * @param bool $reverse False to install, True to attempt uninstallation by reversing the call
+ * @return null
+ */
+ protected function run_step($step, $last_result = false, $reverse = false)
+ {
+ $callable_and_parameters = $this->get_callable_from_step($step, $last_result, $reverse);
+
+ if ($callable_and_parameters === false)
+ {
+ return;
+ }
+
+ $callable = $callable_and_parameters[0];
+ $parameters = $callable_and_parameters[1];
+
+ return call_user_func_array($callable, $parameters);
+ }
+
+ /**
+ * Get a callable statement from a data step
+ *
+ * @param array $step Data step from migration
+ * @param mixed $last_result Result to pass to the callable (only for 'custom' method)
+ * @param bool $reverse False to install, True to attempt uninstallation by reversing the call
+ * @return array Array with parameters for call_user_func_array(), 0 is the callable, 1 is parameters
+ */
+ protected function get_callable_from_step(array $step, $last_result = false, $reverse = false)
+ {
+ $type = $step[0];
+ $parameters = $step[1];
+
+ $parts = explode('.', $type);
+
+ $class = $parts[0];
+ $method = false;
+
+ if (isset($parts[1]))
+ {
+ $method = $parts[1];
+ }
+
+ switch ($class)
+ {
+ case 'if':
+ if (!isset($parameters[0]))
+ {
+ throw new phpbb_db_migration_exception('MIGRATION_INVALID_DATA_MISSING_CONDITION', $step);
+ }
+
+ if (!isset($parameters[1]))
+ {
+ throw new phpbb_db_migration_exception('MIGRATION_INVALID_DATA_MISSING_STEP', $step);
+ }
+
+ $condition = $parameters[0];
+
+ if (!$condition)
+ {
+ return false;
+ }
+
+ $step = $parameters[1];
+
+ return $this->get_callable_from_step($step);
+ break;
+ case 'custom':
+ if (!is_callable($parameters[0]))
+ {
+ throw new phpbb_db_migration_exception('MIGRATION_INVALID_DATA_CUSTOM_NOT_CALLABLE', $step);
+ }
+
+ return array(
+ $parameters[0],
+ array($last_result),
+ );
+ break;
+
+ default:
+ if (!$method)
+ {
+ throw new phpbb_db_migration_exception('MIGRATION_INVALID_DATA_UNKNOWN_TYPE', $step);
+ }
+
+ if (!isset($this->tools[$class]))
+ {
+ throw new phpbb_db_migration_exception('MIGRATION_INVALID_DATA_UNDEFINED_TOOL', $step);
+ }
+
+ if (!method_exists(get_class($this->tools[$class]), $method))
+ {
+ throw new phpbb_db_migration_exception('MIGRATION_INVALID_DATA_UNDEFINED_METHOD', $step);
+ }
+
+ // Attempt to reverse operations
+ if ($reverse)
+ {
+ array_unshift($parameters, $method);
+
+ return array(
+ array($this->tools[$class], 'reverse'),
+ $parameters,
+ );
+ }
+
+ return array(
+ array($this->tools[$class], $method),
+ $parameters,
+ );
+ break;
+ }
+ }
+
+ /**
+ * Insert migration row into the database
+ *
+ * @param string $name Name of the migration
+ * @param array $state
+ * @return null
+ */
+ protected function insert_migration($name, $state)
+ {
+ $migration_row = $state;
+ $migration_row['migration_name'] = $name;
+ $migration_row['migration_depends_on'] = serialize($state['migration_depends_on']);
+
+ $sql = 'INSERT INTO ' . $this->migrations_table . '
+ ' . $this->db->sql_build_array('INSERT', $migration_row);
+ $this->db->sql_query($sql);
+
+ $this->migration_state[$name] = $state;
+ }
+
+ /**
+ * Checks if a migration's dependencies can even theoretically be satisfied.
+ *
+ * @param string $name The class name of the migration
+ * @return bool Whether the migration cannot be fulfilled
+ */
+ public function unfulfillable($name)
+ {
+ if (isset($this->migration_state[$name]))
+ {
+ return false;
+ }
+
+ if (!class_exists($name))
+ {
+ return true;
+ }
+
+ $migration = $this->get_migration($name);
+ $depends = $migration->depends_on();
+
+ foreach ($depends as $depend)
+ {
+ if ($this->unfulfillable($depend))
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Checks whether all available, fulfillable migrations have been applied.
+ *
+ * @return bool Whether the migrations have been applied
+ */
+ public function finished()
+ {
+ foreach ($this->migrations as $name)
+ {
+ if (!isset($this->migration_state[$name]))
+ {
+ // skip unfulfillable migrations, but fulfillables mean we
+ // are not finished yet
+ if ($this->unfulfillable($name))
+ {
+ continue;
+ }
+ return false;
+ }
+
+ $migration = $this->migration_state[$name];
+
+ if (!$migration['migration_schema_done'] || !$migration['migration_data_done'])
+ {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Gets a migration state (whether it is installed and to what extent)
+ *
+ * @param string $migration String migration name to check if it is installed
+ * @return bool|array False if the migration has not at all been installed, array
+ */
+ public function migration_state($migration)
+ {
+ if (!isset($this->migration_state[$migration]))
+ {
+ return false;
+ }
+
+ return $this->migration_state[$migration];
+ }
+
+ /**
+ * Helper to get a migration
+ *
+ * @param string $name Name of the migration
+ * @return phpbb_db_migration
+ */
+ protected function get_migration($name)
+ {
+ return new $name($this->config, $this->db, $this->db_tools, $this->phpbb_root_path, $this->php_ext, $this->table_prefix);
+ }
+}
diff --git a/phpBB/includes/extension/interface.php b/phpBB/includes/extension/interface.php
index 74ecb9b762..7b36a12bf6 100644
--- a/phpBB/includes/extension/interface.php
+++ b/phpBB/includes/extension/interface.php
@@ -45,7 +45,9 @@ interface phpbb_extension_interface
*
* @param mixed $old_state The return value of the previous call
* of this method, or false on the first call
- * @return null
+ * @return mixed Returns false after last step, otherwise
+ * temporary state which is passed as an
+ * argument to the next step
*/
public function disable_step($old_state);
diff --git a/phpBB/includes/functions_user.php b/phpBB/includes/functions_user.php
index bdf9f11066..e5b3932382 100644
--- a/phpBB/includes/functions_user.php
+++ b/phpBB/includes/functions_user.php
@@ -2233,12 +2233,12 @@ function group_create(&$group_id, $type, $name, $desc, $group_attributes, $allow
}
$db->sql_freeresult($result);
- if (isset($sql_ary['group_avatar']) && !$sql_ary['group_avatar'])
+ if (isset($sql_ary['group_avatar']))
{
remove_default_avatar($group_id, $user_ary);
}
- if (isset($sql_ary['group_rank']) && !$sql_ary['group_rank'])
+ if (isset($sql_ary['group_rank']))
{
remove_default_rank($group_id, $user_ary);
}
@@ -2743,8 +2743,8 @@ function remove_default_avatar($group_id, $user_ids)
user_avatar_width = 0,
user_avatar_height = 0
WHERE group_id = " . (int) $group_id . "
- AND user_avatar = '" . $db->sql_escape($row['group_avatar']) . "'
- AND " . $db->sql_in_set('user_id', $user_ids);
+ AND user_avatar = '" . $db->sql_escape($row['group_avatar']) . "'
+ AND " . $db->sql_in_set('user_id', $user_ids);
$db->sql_query($sql);
}
@@ -2781,9 +2781,9 @@ function remove_default_rank($group_id, $user_ids)
$sql = 'UPDATE ' . USERS_TABLE . '
SET user_rank = 0
WHERE group_id = ' . (int)$group_id . '
- AND user_rank <> 0
- AND user_rank = ' . (int)$row['group_rank'] . '
- AND ' . $db->sql_in_set('user_id', $user_ids);
+ AND user_rank <> 0
+ AND user_rank = ' . (int)$row['group_rank'] . '
+ AND ' . $db->sql_in_set('user_id', $user_ids);
$db->sql_query($sql);
}
@@ -2812,7 +2812,8 @@ function group_user_attributes($action, $group_id, $user_id_ary = false, $userna
case 'demote':
case 'promote':
- $sql = 'SELECT user_id FROM ' . USER_GROUP_TABLE . "
+ $sql = 'SELECT user_id
+ FROM ' . USER_GROUP_TABLE . "
WHERE group_id = $group_id
AND user_pending = 1
AND " . $db->sql_in_set('user_id', $user_id_ary);
@@ -2910,7 +2911,8 @@ function group_user_attributes($action, $group_id, $user_id_ary = false, $userna
return 'NO_USERS';
}
- $sql = 'SELECT user_id, group_id FROM ' . USERS_TABLE . '
+ $sql = 'SELECT user_id, group_id
+ FROM ' . USERS_TABLE . '
WHERE ' . $db->sql_in_set('user_id', $user_id_ary, false, true);
$result = $db->sql_query($sql);
@@ -3044,45 +3046,69 @@ function group_set_user_default($group_id, $user_id_ary, $group_attributes = fal
}
}
- // Before we update the user attributes, we will make a list of those having now the group avatar assigned
- if (isset($sql_ary['user_avatar']))
+ $updated_sql_ary = $sql_ary;
+
+ // Before we update the user attributes, we will update the rank for users that don't have a custom rank
+ if (isset($sql_ary['user_rank']))
{
- // Ok, get the original avatar data from users having an uploaded one (we need to remove these from the filesystem)
- $sql = 'SELECT user_id, group_id, user_avatar
- FROM ' . USERS_TABLE . '
- WHERE ' . $db->sql_in_set('user_id', $user_id_ary) . '
- AND user_avatar_type = ' . AVATAR_UPLOAD;
- $result = $db->sql_query($sql);
+ $sql = 'UPDATE ' . USERS_TABLE . '
+ SET ' . $db->sql_build_array('UPDATE', array('user_rank' => $sql_ary['user_rank'])) . '
+ WHERE user_rank = 0
+ AND ' . $db->sql_in_set('user_id', $user_id_ary);
+ $db->sql_query($sql);
+ unset($sql_ary['user_rank']);
+ }
- while ($row = $db->sql_fetchrow($result))
+ // Before we update the user attributes, we will update the avatar for users that don't have a custom avatar
+ $avatar_options = array('user_avatar', 'user_avatar_type', 'user_avatar_height', 'user_avatar_width');
+
+ if (isset($sql_ary['user_avatar']))
+ {
+ $avatar_sql_ary = array();
+ foreach ($avatar_options as $avatar_option)
{
- avatar_delete('user', $row);
- }
- $db->sql_freeresult($result);
+ if (isset($sql_ary[$avatar_option]))
+ {
+ $avatar_sql_ary[$avatar_option] = $sql_ary[$avatar_option];
+ }
+ }
+
+ $sql = 'UPDATE ' . USERS_TABLE . '
+ SET ' . $db->sql_build_array('UPDATE', $avatar_sql_ary) . "
+ WHERE user_avatar = ''
+ AND " . $db->sql_in_set('user_id', $user_id_ary);
+ $db->sql_query($sql);
}
- else
+
+ // Remove the avatar options, as we already updated them
+ foreach ($avatar_options as $avatar_option)
{
- unset($sql_ary['user_avatar_type']);
- unset($sql_ary['user_avatar_height']);
- unset($sql_ary['user_avatar_width']);
+ unset($sql_ary[$avatar_option]);
}
- $sql = 'UPDATE ' . USERS_TABLE . ' SET ' . $db->sql_build_array('UPDATE', $sql_ary) . '
- WHERE ' . $db->sql_in_set('user_id', $user_id_ary);
- $db->sql_query($sql);
+ if (!empty($sql_ary))
+ {
+ $sql = 'UPDATE ' . USERS_TABLE . '
+ SET ' . $db->sql_build_array('UPDATE', $sql_ary) . '
+ WHERE ' . $db->sql_in_set('user_id', $user_id_ary);
+ $db->sql_query($sql);
+ }
if (isset($sql_ary['user_colour']))
{
// Update any cached colour information for these users
- $sql = 'UPDATE ' . FORUMS_TABLE . " SET forum_last_poster_colour = '" . $db->sql_escape($sql_ary['user_colour']) . "'
+ $sql = 'UPDATE ' . FORUMS_TABLE . "
+ SET forum_last_poster_colour = '" . $db->sql_escape($sql_ary['user_colour']) . "'
WHERE " . $db->sql_in_set('forum_last_poster_id', $user_id_ary);
$db->sql_query($sql);
- $sql = 'UPDATE ' . TOPICS_TABLE . " SET topic_first_poster_colour = '" . $db->sql_escape($sql_ary['user_colour']) . "'
+ $sql = 'UPDATE ' . TOPICS_TABLE . "
+ SET topic_first_poster_colour = '" . $db->sql_escape($sql_ary['user_colour']) . "'
WHERE " . $db->sql_in_set('topic_poster', $user_id_ary);
$db->sql_query($sql);
- $sql = 'UPDATE ' . TOPICS_TABLE . " SET topic_last_poster_colour = '" . $db->sql_escape($sql_ary['user_colour']) . "'
+ $sql = 'UPDATE ' . TOPICS_TABLE . "
+ SET topic_last_poster_colour = '" . $db->sql_escape($sql_ary['user_colour']) . "'
WHERE " . $db->sql_in_set('topic_last_poster_id', $user_id_ary);
$db->sql_query($sql);
@@ -3094,6 +3120,9 @@ function group_set_user_default($group_id, $user_id_ary, $group_attributes = fal
}
}
+ // Make all values available for the event
+ $sql_ary = $updated_sql_ary;
+
/**
* Event when the default group is set for an array of users
*
diff --git a/phpBB/includes/mcp/mcp_pm_reports.php b/phpBB/includes/mcp/mcp_pm_reports.php
index be18dba944..86650947c7 100644
--- a/phpBB/includes/mcp/mcp_pm_reports.php
+++ b/phpBB/includes/mcp/mcp_pm_reports.php
@@ -122,6 +122,7 @@ class mcp_pm_reports
$message = bbcode_nl2br($message);
$message = smiley_text($message);
+ $report['report_text'] = make_clickable(bbcode_nl2br($report['report_text']));
if ($pm_info['message_attachment'] && $auth->acl_get('u_pm_download'))
{
diff --git a/phpBB/includes/mcp/mcp_reports.php b/phpBB/includes/mcp/mcp_reports.php
index 3426d62cdb..8da303f6e3 100644
--- a/phpBB/includes/mcp/mcp_reports.php
+++ b/phpBB/includes/mcp/mcp_reports.php
@@ -71,7 +71,7 @@ class mcp_reports
// closed reports are accessed by report id
$report_id = request_var('r', 0);
- $sql = 'SELECT r.post_id, r.user_id, r.report_id, r.report_closed, report_time, r.report_text, r.reported_post_text, r.reported_post_uid, r.reported_post_bitfield, rr.reason_title, rr.reason_description, u.username, u.username_clean, u.user_colour
+ $sql = 'SELECT r.post_id, r.user_id, r.report_id, r.report_closed, report_time, r.report_text, r.reported_post_text, r.reported_post_uid, r.reported_post_bitfield, r.reported_post_enable_magic_url, r.reported_post_enable_smilies, r.reported_post_enable_bbcode, rr.reason_title, rr.reason_description, u.username, u.username_clean, u.user_colour
FROM ' . REPORTS_TABLE . ' r, ' . REPORTS_REASONS_TABLE . ' rr, ' . USERS_TABLE . ' u
WHERE ' . (($report_id) ? 'r.report_id = ' . $report_id : "r.post_id = $post_id") . '
AND rr.reason_id = r.reason_id
@@ -94,6 +94,10 @@ class mcp_reports
$post_id = $report['post_id'];
$report_id = $report['report_id'];
+
+ $parse_post_flags = $report['reported_post_enable_bbcode'] ? OPTION_FLAG_BBCODE : 0;
+ $parse_post_flags += $report['reported_post_enable_smilies'] ? OPTION_FLAG_SMILIES : 0;
+ $parse_post_flags += $report['reported_post_enable_magic_url'] ? OPTION_FLAG_LINKS : 0;
$post_info = get_post_data(array($post_id), 'm_report', true);
@@ -136,18 +140,7 @@ class mcp_reports
$post_unread = (isset($topic_tracking_info[$post_info['topic_id']]) && $post_info['post_time'] > $topic_tracking_info[$post_info['topic_id']]) ? true : false;
- // Process message, leave it uncensored
- $message = $post_info['post_text'];
- if ($post_info['bbcode_bitfield'])
- {
- include_once($phpbb_root_path . 'includes/bbcode.' . $phpEx);
- $bbcode = new bbcode($post_info['bbcode_bitfield']);
- $bbcode->bbcode_second_pass($message, $post_info['bbcode_uid'], $post_info['bbcode_bitfield']);
- }
-
- $message = bbcode_nl2br($message);
- $message = smiley_text($message);
$report['report_text'] = make_clickable(bbcode_nl2br($report['report_text']));
if ($post_info['post_attachment'] && $auth->acl_get('u_download') && $auth->acl_get('f_download', $post_info['forum_id']))
@@ -168,7 +161,7 @@ class mcp_reports
if (sizeof($attachments))
{
$update_count = array();
- parse_attachments($post_info['forum_id'], $message, $attachments, $update_count);
+ parse_attachments($post_info['forum_id'], $report['reported_post_text'], $attachments, $update_count);
}
// Display not already displayed Attachments for this post, we already parsed them. ;)
@@ -227,7 +220,7 @@ class mcp_reports
'REPORTER_NAME' => get_username_string('username', $report['user_id'], $report['username'], $report['user_colour']),
'U_VIEW_REPORTER_PROFILE' => get_username_string('profile', $report['user_id'], $report['username'], $report['user_colour']),
- 'POST_PREVIEW' => generate_text_for_display($report['reported_post_text'], $report['reported_post_uid'], $report['reported_post_bitfield'], OPTION_FLAG_BBCODE | OPTION_FLAG_SMILIES, false),
+ 'POST_PREVIEW' => generate_text_for_display($report['reported_post_text'], $report['reported_post_uid'], $report['reported_post_bitfield'], $parse_post_flags, false),
'POST_SUBJECT' => ($post_info['post_subject']) ? $post_info['post_subject'] : $user->lang['NO_SUBJECT'],
'POST_DATE' => $user->format_date($post_info['post_time']),
'POST_IP' => $post_info['poster_ip'],
diff --git a/phpBB/includes/ucp/ucp_activate.php b/phpBB/includes/ucp/ucp_activate.php
index a0d0baf10f..577761dfde 100644
--- a/phpBB/includes/ucp/ucp_activate.php
+++ b/phpBB/includes/ucp/ucp_activate.php
@@ -50,7 +50,7 @@ class ucp_activate
trigger_error('ALREADY_ACTIVATED');
}
- if (($user_row['user_inactive_reason'] == INACTIVE_MANUAL) || $user_row['user_actkey'] != $key)
+ if ($user_row['user_inactive_reason'] == INACTIVE_MANUAL || $user_row['user_actkey'] !== $key)
{
trigger_error('WRONG_ACTIVATION');
}
diff --git a/phpBB/includes/ucp/ucp_groups.php b/phpBB/includes/ucp/ucp_groups.php
index 69cab610fc..9635fcf079 100644
--- a/phpBB/includes/ucp/ucp_groups.php
+++ b/phpBB/includes/ucp/ucp_groups.php
@@ -577,7 +577,7 @@ class ucp_groups
foreach ($test_variables as $test => $type)
{
- if (isset($submit_ary[$test]) && ($action == 'add' || $group_row['group_' . $test] != $submit_ary[$test]))
+ if (isset($submit_ary[$test]) && ($action == 'add' || $group_row['group_' . $test] != $submit_ary[$test] || isset($group_attributes['group_avatar']) && strpos($test, 'avatar') === 0))
{
settype($submit_ary[$test], $type);
$group_attributes['group_' . $test] = $group_row['group_' . $test] = $submit_ary[$test];
diff --git a/phpBB/includes/ucp/ucp_pm_compose.php b/phpBB/includes/ucp/ucp_pm_compose.php
index 5577e8dab3..c2d12d17c2 100644
--- a/phpBB/includes/ucp/ucp_pm_compose.php
+++ b/phpBB/includes/ucp/ucp_pm_compose.php
@@ -353,7 +353,7 @@ function compose_pm($id, $mode, $action, $user_folders = array())
$message_attachment = 0;
$message_text = $message_subject = '';
- if ($to_user_id && $action == 'post')
+ if ($to_user_id && $to_user_id != ANONYMOUS && $action == 'post')
{
$address_list['u'][$to_user_id] = 'to';
}
Zb#U\3p%=^,߈l{=Dܫ׶q7`f$Fe/o$щgo`d"09z}TVOch}@"2t+ff6+0RW\]1V{֖;BomeĤxϟ#'RuBbhT;~Xo7Wnkqcm3&R2)Usc(Dx]pZ m.%lұUh3_uп( Fa:Þ`i6<nI>sZmhϬ6m#p̻H07ˌ85[kz+Z#97[5#>zyH[fΌK(lDj2dDJ(gtuX߭nIn;gQ&tPֲ-q_O*R[\T07Nal.8ۈ5FX6in]4 'BӍ*FpUsb4ߏ<[32IBNq}ɬYCr{+`Ysz/*y#RƩHvZ3Y\B[q!hA*ty.zV\Cx ZoNJ8,rB6aK:CtL60 lRx;qwB}=3x1+$ Vm5Jce)Q0-P&F;lSI/t PoHEi!Gȉr~,?+ pbO#Ej8X>Q4w'!]U<[jN`l峑7k/*f kC_1p%v-x#SxRq2m*/K6Є16[j1Yi⁖\D`)&sTCd2Vl kJ.mMNSLsi)~s[z# s}ſ9-CY/_H"px8&3,3l|$(ߜGg-􏶮;$;;UpwqT[ W2f8PqM׸=4_=}Ipv4eme˂]@ܮѩStKOnΖR6 7E.@Wg"?w-d۩`fj1ߖly .a2v> 'I&EKw9'=wl(5-BWG>ޖ'_2Y4K[o7N>H(^#e16>!~k%~b*NvsؑgM]0:)"tV*?Tt K l%Thc23 ' و  NsmsGn@V8k_hF 3xxYV@0jM1T5^Ui?N{7~*g/X{%1U ;|\?o UʈǕJ]9f뻳PΎz*@DhnQS_xo|(.H. %.@vp 񒐂ّӲ+xT-Rqa.**l~pEm |{UCNi.wHf#片XJl{ߖ z(/;+Erc{ib|v2-qgd9s %mDGXq+&rLnKw)VCg/=J'k/ITOⶇs'74M Ȅ˧va ^<|êK-,Pisއ=~}B72_^AqX(F25Lh{92-$ߒ|R]MDf[2 aZ2BH3݅EUefܳ3)2X $ےŗ4*uq^:9<CfyGp-zfd]ɾ fiAR-v=lT͐ ˰p:~9%2,Ӎ9^SnCilJPmҁ0Pi+0XqTl +ՆXf0b:~ZO*<ʉ(JkLSeo=kr(;ŅVvwQ'@KڴjM3{ dJց2E 밳-.' xFVImO(N,QE Y{&v##V%CA>60%mflFPN,@[u3áfٵ%HiMy"`sXΫLy䕧y/Û&pZV7u-gQ;5G!AƙrdOhezLQgXO?p],i4]q|f=1԰oX[y/:/ohR {'K5JyfnX@IFqOnmUԲehPfh"ƟzT 8 *'d-:盫 h[xJցx^ٓ-b2(>Ry!|^ 6[!|6 |?| wP[X:AX=y>: CZӟ'w3Mݢw4[hQ@D'mvfkꭰ7̀9O_6%>xL*P5-Xs aa$~KAXK8XTdJݪ Y-jFdIh &nV'( '`ӳf|,ƽVQ0x ̔T+/̏ H}So6xAexgKHصۖ楻.n,ڒyp>SiT`J s.wn&)2!4$`n/N'*_ ɝ8fhY*Jϥt5F aN.HbBFM(oRJ/yjrǚq}j=ٱ>-^6ڍ2GX99XI%(fs"*n~5UXX寯Y0g1zG?y͸!Iy͹Ymi&p,[|@ >A~N@_bl28(en6] ieuׂI΅c%/ۄ iLjdvsC{DbQhLxMHN%qcPg]3N#U(.rs ,Bk m/N#@h囼wr2x HkiK 2,g,P?SnnanTFc*QN??HqT}yK dxyd:nƉe n(Œy& 爷tt"mr =sSɄ*MZpZp= mͩLb}~A,a])T0mR cOeClAӪ7sQJSK{>6cNFCTYB] W`-#c=^=^ q@6X5u6 @Wl(k-( axE#]􊑦>NJm XH>Nr4B%r+Q?8>p3kP7'Tܞ ,(L:|4.ukXr<)IȒDDO%^W}pybn.3qlons:{B F|q4n^Lrz!m}ncۂm*9Ûu^kK=܋ B ߜ|Guꋬ34z6xfyKPf,6_NƌUvP~*w)yJ f"PTH@Kfi`$W`M&w Oת͔:ZͿ2_8콷P͟!Y,%IEJf 4.V5FʽZgd`RvivzD'*1m9=W Y{S0'>/umB 6+t~~> [*Hq<ݼ ~ *G:܎Ef#bjۇ@ѥνU?4&!/#eIbvdLiG/C^qTݞ;uڤOE+^)+h}T W L?MȩHRgWHjvSD$(E OΏqGYۄ0^Ep]lwVBgQA~sHP_@kXץZũ0[=2*;AX~oa=bgla[yxw- R<8| 05ƍ絠 C*"aW$9_ڮz؋pzLSb-C6rke{ _ ⶚<URO'Z$8&]-9Nsa`fiE1C ="6D`9RCDVP~Wo֍QOJxonpxR@.B`2oTaԍ="͌鬹zOp ]Fn[@úA1rʇ h{c7p̹G~ =(ѣz%v]a Rمk6_JKiFmXI,qj R"s氎X&*;?Bf+`c w.x[u^z#bgKB++#[+%cz mԨm41C1Vv&ҫҕGA-~l]6FIIV xtW!r;\nTSc-kmθfN?uS,܀m%;\!~Sy<2_z"!ə|F(SxcfԄeJ^L" ) a!FRcq/rY!+9>Tc&lw@Ztq#S9Ix E1LB$DcHt QњX\-Qes4db2 S&_ >^Źbb怋gw>z9c^pP"xɊfvvDyez&OLjPbت۸wle? 7I{^~> A+w3/&%6bO{(k2|ܲbXuq [ujc#2(fU` usb+Kec26^_rSM07Jgt2]J0DsZBͿߢ \}z-&.y rBYF;!4IP !bnj@-f1%?ZazJh8)-&$FZ4?S`ijcz)"u\bfz*|) k#& ˊ=4Y# 3Bg\'{RbeߛKoGvߕH rW"*`4huK'F渢̧Ŝ ěmr!H i';ډr/wZHCnUWX}Ւ5*5z(o"/ ?$ز')Mbw5elPMHjRzC ٴS}``h‹1S<Y־5eQW`{4 Rؿg!ЉI^lLMkN;4S }Sk M2Y<$@hEDtS{;,x-s/ T]#tXҕR)u+juI`sGb~O'N;Q[@O_M'au˫O7b.򗜻9nLDnCw;+D`͒Ǯx,tht>«a \&:~|sc6p^k> hͶRt;hAFKӊh68ϯ͍(y3e5,0~:{(M6xHiutxfhp2;(4Jm ˉE[(Ojj)+!)uzM:ˆq ^K 3}튨j5O -sj@M͆ < 1bjGaUw'O[.>QqgO&_֣Y"6cw|h՛(r)]$8nCj2W'S.:8m$p ~`gA߯6 P<8[hdhB Fa5oalSm}%U*;/DzPE C \r: r^Un%5KB \N^>6"Z'ʰ(T<ڹ͐[E !l۝Y8ׁW}IFzy{/ Z!VU [C,蓃Ji}օS3}_o۟A܀@(P,3p/G&QVRt#) ܰjS]fIwS)g7EFU "8GB:RD^F3d]e8,o06oy%\I5,pT0HO i,ܺ:qeTh[~X py".oXkn2su/gɹF?2GOi x证XUd`C] quz&^tDN:u!:"U(Ă@Ry~Iy؀+6h~v,ɘNᙯpjNKVIG^ʻlj ~QZ, عA-*H/~L*@BPF tͰ}e(cs$r |BfT҂9. aZ] @]w:KagRt95o>W|3:$DtPTiE4B}^d>Vj.Dxc|Ur^qV\ܤ{S|tҧL3 RVs],|ry29)UGV}XIe(˹beUb];3lJ=RZ%>Rm )Z-l|۶10Z<).Nt֨78l4!T(-E4XpfgB?jA|EJIu{g 4Ʉoq2(,> 3`x^L6X!Kk5}/9RQ#mI1Aᡀ=n֢ۨZƛj-*pj箨;֮TDTXyGO4 Jt0$l\څC >P*S`⃌< ѦDݓ$6H,L|'Z[FƵ2`;ZHw )U\:5} ]C[oG"DI*-:rld1+LrO&Tt cC^TxL 1y_ ˃oNm7;F>n.+al;KLP1F 0SepF@ǐVu}XY `Ԣ60NHsH1q?aa+*ja $9]bbvY5R8|\˟FtOxIFf @pʃ$t0 79z 2R8y<Ь3 6`(';`{tw}NH!%[7DKJTa[ al7i W(@nN[מϴwcDWYtq8QiLny7-̪i[<'K%V;̺8 m󊆷;}3ԮrLWCU=b蝪L;_@`&Ҧ3%j1@2!"yZk߈ԉ8եwl!qzxfI9,`^@{dۇJwxIM!e3x`Oh@uۚ1)wMw+965ɬBffƀ8 ʕj QV[pLhǷ/YU NJ(Q\XyEz`+ɇţ<ؘxՅ1d`- "Rs8vHf]jv^ :O^:_ S!a$6rK{CJ_Ҹ{h:1Tw>@E MN#njnHYw7:t#.Ԡ[-N}i ߊWe IFhɧx:K5HYFyol?aJ20ڰS6Wx[.ٛhp]Ŷ w6 duy+RG5Uۍ,h .Oښ3;eOos x^=4Św}AsuQ$y 7C$k0!Feٯyb|7DK֖ lssq] aYhk)˽h}xgp}Ӡ㺑esDfPe MėshKb2  \ x d:~S7i5א"c ~@Lf.`UfXx(ne*QJf(n/ogͮh|m'@MN fEwZw^ F,NƑكŒ8# R4ڶYg i+'׿*"yŽ-HCDŽyu#O.yz exn=>]Jڦ ψ0؀NJ =2W%#4!U@i$E+bw4~7k!@b0;n7K9#;GL#מ}9̘FL/OB۰0=2`|l]~|$_% P' QF0,]bn9Sc߿jMJJ#ʫu-0LP;g=v`RȐ5MIwd?HС *C9jOpw%zJy ZLϛ^DjvEh*רj_ǡ OfB|2 Td (A jN$Q9XŮ_[ :2h fa[S,g"9ĸDh +,Iw{A B_Z oV:EY'3E!M\fIs!k2CsE:{8Ќ!/5FaJ_s誟v靨{Mh)%!.Bf9pٞ:.塒nG$}n. ]=rݕ}wf/H\D5AG4R q{gq* m,`{ ·&2_AvyǴSzzt< N '\tTB, +@կc@,rkr] Oq!dkȍtu@eȆHld \ ĞjfAGEe?fMr~0Bc;nln>(VW:+˒ǯK3$ٮEXٕ"3j>?ToЊ\6}{j,vhhhrWnHB5pik >MC8ecB'ص e!^T<[V.4AZt(JqqLOP>U_׽!Կ;2ntqΑ1MrB$B߾X ~BGwyP/U篢Tsܑ'IYf{}oRoRY ( 4UJ2e7Z ǴXKJt)wOJcW?įGyUe:.O naKix;"n͚0v.UHJҿI؆JUs5l]IB(pv3㙮mQ޼3HAgD; GsQpaO2Ds&H^jIvs6fQ-@=^Ok8xqT>4I)@1Ս崠AQR=)ziYX.tw@x|jdx@bTi,mA˛pL,qTBebm9VT*]1\z\7+X> 1j8JGMƁL8UXpku5T1ˇxt9kQ?ု]9ԒPete 0 .?fa#vQP +ŝZA} ^w୘yvqH2o cӉy*eX^t)/pf#)+ί&:p4Tv/k, 4K0GS.DGKA,j{h*β1Aq{syc,$Fd* j%|i8ocg\"=B)#%(@g/$ ߺ_4@>i׾C#U,7K<(}H3WIJ/[tTs2Q\B54tiZm=4'8y})VD6=|Η9!¹ԊhqVm\*|XR'ʎ~>gv$e6أĈsi'+T5KpuY@gK }O|[U X"eǞdH=r3'KFt% c; #/\s=/P} cx]yJ4T(KKA~$$Bd#>û;q2 l+|ˠ:f2/6P[o:_`e~v<ŵʛ C6 !')Ά҆ 6 W.ip~vԩ%*LJA6goE %D7op8E\D_L+<@e5X6 \'mG+ LR:l=^(m$'uBO.QxqϏ7xPMCNnıc_YRvk$2d7AaJxOU쭜S+'n=ʢ-]ZRBA;J`byQb]?E9LK ?\ (+GJKJ_*-;O4&:^SRp?i]Zat%&fTQ27k΍!KGo,'b޶@'p=}LR\U?2aŌ?ISF>}w@}'tSFA q/VnίZ6waTUs^Z>wWߥD2 dw-/Jb#V1{nľ%Vj6ǫmbRb0Jyy g aeKI۴Eᑣ"XQ]j<C-j)pA/te ȜCN@Q)bb`tZ2zI17Q :{Ռs26_j!jr]+?Gؠs.w{nB֡*8CΝyM[Ve5({B)4G5Dй\]ʂ@dS?TL?b3N:_fP2#֦ȗYD=; }7kRԯ=-f=-S$jxawyvsׯ}ntOpg2ravU]d4s'}z6Xj5Zw5>{sfENW޹墤v{Xr܄yu`*ESh-q>|`ft,l28H{c#_H1@ e9k@V睍sMvg0@D:ڈx roIIĴb5:0U,X7.PHH9b5d&edI0 3S]+&d[LB\M4ղu0>4?0'`oŌ.P2T V!Xq(cillFi9+EbDqNRD6mY0i /\B]m\a{ksX)\q#}jc!k#gL<$m+nXR@{)sK}jZ] zc@HX_U8:2$rp~o}x Zٌs$UH8pUz_ Ȇ,j>X4i`o Ǡ|>~LwRl7cQv‹?<ZޡYD(V793IT5:YBt*<'WV,jD7s LG.a7;6ѣPɝ໛X֊^\_?96qS,^IO'%%tcAwҲӺJ hy5pбx;Nq4,_g:|25p1ɮRN^HBאU(EQ'c0ZFB,ܫ4 _/a ^x:cN.3TAvEr}RA %C9w8UX:,LǩHi ('G軰1[9 i27[zttnsn6.2szV'ss;r'QӮȈ5M_)D1 !Bbp{T/g9HHX6YO*lBmLj@N<(u,#JZ3?Y}2&.]YJA{G<=qƪ,E6r0),?}ۚygKs-.[)ks!+Lxk&Ca}5z#-c"t%%"R~}7LZx!~j5:JM7~>/+vdGOTa5ژ),tj5 `R~7D?nA,xE!%@: vYN%U±E㸎_ mVdtE98As"qbZ^>U-v ?M6(i"HdsY͆c^uS17M,WO蠮%ߐsw`o#$^4<;<>RYK=J<=wgMD%,'Ջxq pi τC}.U`O^!`4+-Trkw_k P!V4%͛{E*sNjg+e-7t|U#L9m59i, H9 'L5Pы);C,鰎qE4/e'-$ ]5ؠZ?%~.ضh2;c8J?Ӆ ɁVK;#w}1wu3R,]0QC3@5KHF.z\b+KE]]{sD*3?05jX'9rНKW4xrI8SHr5tcw"ȟ#-nINVa-#:ևu3.ՈF#{]̳?^̯ Hcmh;GfϝDd4heTFl"ug mCXjtJ߯?.ꂝ- ~57k;1+>Pmp~qP)[Kyuk)#UNK֗wN*k{-¼Cdͳp1b6JhjiN"!p̽'nOc) +gǼD-GvmpA +{ͺT)ǝҿqYXt#L7ܿܙOkn'!P }Ihl>TX_:]\ Ngddds8vsAՕ+9e?gjGF9׌ T~JF~$]F=n Zk?b)ez(xKr z wa'3Ah $FD` =gN t{϶L7ȵUmWrR]u Әcs6*DRIc;yxK D#,&yh&͞cZ )DO̩s1rFa.:jFl,_ _ۚ<8VsD֋F։ymѣ#Ec]aW_|c/uJ^#Y27D'60z< ,v {z̈ L W5FCx_!4<65(mG~2wnc2Q]gZCN4kwV=AWS4esm~W36+^e|AHf񽍙@.hՀ`\+;>lZ)d|jr(2h2 gԖ񗗚? c-_xX-}fv%Ays1󉣯ۺ)AY0ukm9WmtNK RM뮉g!$} AIy $7h$G!jF|qH?9 3P5uvId;T.:OvBZ 䑫_>o1Y_yH@ 28FQE8+nK*Z}#Uٌ' |6HMߢy~nA!Ak5k—]DWC b-oq߭y.h.!!gl+t9׋kLsruSܬqR!h"!c;E:gr׿W`J13=K=Mھ Y 3z/Тpղ X7$V{PfDbI`0%lk辦ZU@XAQ_.Ŏ[RVQ4P!=VS-iC/Qodb4mN2!aҷlWn>}h$m1{@fڎw.û8~Š..dO j RdOCf2MNs:vrOx^P,6VyaOAbt1<&1ա ׻đ3U]ZS:<;NBfeaq;]֗;co_t[OcG+(С_ ~Qso@UOgqxke2nRw(t @Zhp׉-ZD]b5hu*9uM\r&^$0?gq.8ѥ'PdnynS߆xtb@t RLfFQ41E>"R0Yx]f1Q2 ?9k?|1ˆ8h'ր_p-h6eusb<]^kT8mZS@*?nF[򗪲%jV8%d 3G?ig_]?[`Z";Z ˙Do[ ڬQ^F%@MBDV@7,U1DLh a-Ub{&Ty4q#]2$r ƐRV%Qk,qh B'>^}̳M|~ }8X|-}r‹8bci);i)"Jm_ݛC"4/6V gIP}RN2w]p!SA{.3{韁cEO&ubv;W("p4WH̰ ؐĊ!gGΞG;Nb{?~H3p% O`n.y WF; \-̗GJd.7R05E+8eNc,dTS̝c<\e]-!ӅRE&`hpVk>VV`2 ~ +*Le6*u]ی@ hĆ}hC%ju;Fc_s5*t t>g@4~,2ᯒ6\eRG=r2zqL$[̬~|;+oK.M22fOy1 p|gNmc;x5e"\Ԃ5i@/ 7*6F(,c&Pӝ7DiQygn@P|F";.X+ 60 굁z=ޘ6l[o7/_D]ވNl_7' ) ˿J3Š7髣œKϾOP[C@|]LFO :KZ 8(U`5ipċӗe2I9O8 %tnjRU;VHaqZ _iCd<@JXރoed|5 N>j2+쵲ŮvoB෤zDd{ ,A3.:xiPN0ĥP#&ʁz92_7$bzNA₰ neҕzΘmj{2<5ɃFK!\,}FH;%3aYnbtb\~w6CX|AG%aJNBua=b44ŶPFLg]1LƿF8Cb}'YtUK`O,(vԂe@GK5r=Il9vIEvRCh~X7:< DQs hStnY[鍴]*ML";;x\Tݩftaք_o9Ţ\ڐ&m0Wqե-w`H!BR($xb:1q7_WYi9`LEeb4X?ֹWωVnr3gp.}2:IRTnj*SC&7qX1 -OZ! sE$!L(;XԌ? {6d]^k0~JIR@+iHhDBc? 6Jrg"S1T5NPѹtzCWv'w\og|_A e*k+kj Kꎵ9@%m1|!r %D6#== F?': K "%kJg*K"G 11r?R8E9QLIycma<16qQRL+CF3#ⱽق:e8CO|ÚR^g( upAvQ{orX B&FU;4Z:h3$ qHB*ALq_a@%/1-wjk8$B2|?K|>[ZTw[5zMnH~8$"Foan}Ɉ]osmo68̾01U%*K܄_IF@h@7) ;P Wx8$U.޿Y1L^1xanc%(@-E}v^zM~wߢ,wUf:W2gj 6 ~|Goƌ]2}t_ij?hmL[a,Մ} !R^z~dL"2:QZW}-m>[D)Z'RdzB p5UQ:>Be ͐r#k+YЅ(!(}\k?+͉D@9P]yaW<@1En9Xxjͧ,b-Z:c/gזkyiϫ Et?識970@dhl=z)Щ9Ie' L6#=O?ube?T6U6Ʒ H"Yh%Ef7s%8}RNXibo ն ɀMF4ufvۿo+H7`_u[#9azQ#aNFy zkob\%ssݔSq6RU3 Us-7T+ Pe5;*+cZUK C\8T?/lySN2 w546 k%Š(@(:)ZYI%Œut6BD*ϢUosJw =QЛcPo6E5 LG \3p: Ba-Hɣ_N)\XBXt-۵` f$?pLċl*/~THR%31j44\c , wN]qrيs7#Z3 VJP)`\ssKLaeŠj29ݲb.g[?;=oSouޭ yC.?1|7@=48(U %2"#[4%Q^'m&5s3Һt VURwGM[A>@uR?{}j4W* (ʪne {͐5ڤ3숛[B9o8X^Mk?B罕PQC,[KGMIXOGq4ko)qm\z,_9SJ7,z I hu:xJ1|v͝E7A˳O]n#ǍS{m@Yͮr !/\LV:D&E,EQ^bۼ-9]pfS빪k%PD ֤=)2ZJHSRaWS`gB3'*/ ӪQ0XS:e_?2fkbŏQR%Tq'q0 ~i_"M*Y;JhU)Ll<>,j֪_ӌH8e(YlTH?|GmZi/Z>ebES|\{RI[%fo%[jr7W-f'Wnﴛ BO4HJӭzf,PM5ɸL9!V!](̴ ܓYmy б+c5u{9qJJ^tShuʳTsG$ u*Kmd?+S)W"Hhl{yU[`OkLS孤=Y7+1 ){:͔A{^ntX6_ 8X O35sSe=}*3T~QtR+50cqh'-p9NWe/`\z`Gmmm0XH|c6d2.g(!>sXR%(ӟ^Q["(/wJ(b(xz=2wc˅RMti򆜄y[xK+x[ I%X5?89jDPC"<I\@'w}ȗVڮGs} w؇DM(]R7 ^[j:z̗Ux)R/tL+uΥ/3fqS}%hK%N@SR[O;]0' K:6ses REDEG R/Lp%t9K d*p0 *g^= FbրYjJig $-ZxA:%B F:C-poߘM𡼧ß[z{eɨA`Wc?mOJK%26 x~LZ%jJF|h[gPMOلGDuzL)R!B6-{yv!Czp]a'2]v*3b w"C(7Zy'ْ?l[d"Q NiOpp8u//X0Ph\#J4M?T G>]aAh#4 JP:m| ƋB2oXufγȸR%U@g{s9ju5E'a6ݟFvdq%Y!EjV$mń0@q0&d`%/j3 5׏}N1G 1mTWz :swnDBqtH *MU5AүxFq[2 IH}Af6:UX4ɑt2!%[qYȈվ wnĞ;r44f{) Q䍓ϠkܒFѤ 2}y:gD͚]㪵P+QTm_]=^Quᐦ'@C@;-A*BzL cO01-AɃ 蜦Y?@~8\vrqWNh6\ԅp1̧O{1{W*g/Sv+4odT tІ!}كo^'oX^KITs#.'{Qn=uNi2[ #Y e`[dW1( Zp"mS~! "B!crMㆲPծcG6{'\i0e(nu.9; i2VaD=S k3#V=Y,pf@? #D=t fVsex۬h G+tF&j|䥤AVWDcSh͛)E7ced?܄X\nbXzF,W -:5Q5D?ta4m^B0 y25L8_|H1#Uy=}>_/Y߲g4*b|._^.²UA =Ʀƒ,2׍*P k:m|{Z6Q/d[5i%ᄞ Lں2KEШԲ{1?a" m7vx n$q2 SpEėcTZ{>Qfl5U\x Dz# ,鋣Mxy#v*{b7L=$+ˆtQ 6rG$͖MMRoclR0>Y9!U."Dn(fsﰠU`v~FrjGd^-(|iEq%0_˒<&ƍu"onӟNr9[ۻ-Wp]uhjw搻4nIG=Xȧ61ȗ=]:o;}<0R ΂'룪BL즎oEE\Ό%WK&9N¿3= J㳐mf]'낉P 5N54 OoQIjnZ!pӸ:/RxŬ5q-|줥t{SS+)xYFEܠkef"V/c5 8juתP,[u? :]^d M2hxoP ym'hxUՌb{N]Ttu;;cf&Y!nY56JPBnyg1,+ x{D;o7K$?GV"8;nTX״{/UհH#X/mч|@jnV;A7X@ՃE.zBz[t(LӭzCf510gmcS6A4¬mf_">]Tov,q @JwC(}R U.ێ`u  *:' &'>'MҰA).' w%~\=97#}\72Zt;:7q ޫ2rY*l?a+ EǐO^3Xh3grSj]=Ls6VtiiWpw v-q>0"V}'ez2ů$F$BSx+}y- 4}aJȏ)&)PN0AȈ=5KtC@%dwsv/6QzRt]3g >LǯsEX8W~#%׳ߕ--- O37Ƙ3D=k^)zw웅4*CMr3<&R)h}CeՈKb+P!<.K49/-nQ NO$Z+Z'0c1>^YKVDpGoL: "[ߔw@nՉo$uz3Z}jRKE[)-VT KӜ}~Yl%{M͓X*5ӹk|S+NUV18)453D Sb-o\EhΗ;" ސk #k,? 2J馼uY=]'3Öx16]Q@¦s~;oOx/gw)H"MbʴQ 2P'˼m庽2:Q^1vC[IծçTNJ~aV"cMId퀲=֩cRKxF[[oenMqj>2YWJQe1`2yʶM4sLc#:9D(XSU+d*?jeUzC<<[bH0Is8VN~\H0+%9ϳ0UV&4ٷb88=^ֵ\?aB/ݡ PNeaQz~fdqmw0 ?}*"I[[F//Z vG4fǻGMvXx|* uI Ad,vz]?8c@b3D\|!{3uym("7V/,Y!AuxV k ,C_\:ηz|gf3+EĶ'ߩ/G{pwPW U7;zD.w_г%d%WJ ]dϛeq!$&,uy'ǯ;b] Kc('z*cY+V+ÕeΗWmFGfƢ`$tM#D˾qQA=|GտwQ6@g^SV0om dZx|/LU뿛6iͪxQm{|x vgՄ͐ߜޫgK 7>0T҇rD2}=U* %H5WW2H dJ2 ?W&'칭C+DYN1g\J$YۚgV(3lBګ`P@}mk_S61g = 3S}@E݅ΎJ|EC05,srpCq]fB- kNz;D@nv\é)-83b%x+L@nV-K’Mq٦\mɗi˴f_ppoJalȩ]u)edΗaI"+DY, h#mƘjȏA3xx (jUƄY\abzzB{c\F-oA |$G?5X5jQT=E,{|4zXSThEV. ՝1 ^uYsSҡv.ӻ맿sA:fkn`:C4*G՚7Snr鸖3m^ȮuQ kR$UԪ|gL+` v<7^l1i_°vmw-_צ;uhc ٯl,jl缧W+%C;.I f(lA̅UkxÐٴE =oBА˥2ǚԭr=bT0y6U[<@н0f6AaruH- KM R^# SXېGQooaܡ<_[R5Ot `D=Y).V⥪jV]'nR` ydanCৣc7.WSQkO-#6ҲQ;/X`p 9H!0iNWp 63 Qĝ8a9, ؒ=U8 Fo38KwUfEb[~.eFK㤪~^8u0Q y$=3k˜}4(A| EZj6EUWaBmZ`rjQyj1}(k^#Ps0C"3HYx1 C1K D{w6|LǗE6lu޺rup%H{~ RB7}fHt-ϋ:Bʿj[A/yA*r5sqRťI5_hF:-X#B( ?h9 iu,RzN"Nyʼn&MZ;\ML|?8P~Awy~;@^}T9ef bp)SQtnsb\:4=me-ݔ}/nF|<NrGPWs.fD'5K͇*e2uux { u.* EFu迪dJi!cn1 'm+!8|sһ=xZHW`ɣ9 ,SR佇}M? 2%B!52j?\ڒ`QƺlR<ebܷn" M(w,VڣfRin p .lVrUolüϥxZ+٥jREYk^K-»^ov skRM-} 8)F:!Z5CYE@xVP:ZϪ:ۺs![@F)˺+0#g={Ɇ%X[fC𹢯k3,1&Wg$ $S*: Hv, Lvzu^ ryFe˜z_ Gg4a l;aMVxnCZ\> z͖8&@,a 7K?DKrF0;+ʪ2,*d 7tSQ x?=tk*=ttPT/֑>ڕxߤ;GֱT |hBj'Sn !ZH 5•AAdxpZ5_ԮoKի-M#&L4 iHEzϊ aMbO8?Ÿn_|KþPMݛxB2 ؇zzq?V!ҞD8L]9A/Ÿ|9qJ `9p(g"F =&j#fNr,I"/A" g:~(pbwu<{8i;r=9! l J &(!QK)2O e4p+jb3Pvǟ]Op?JXÍO<Ǚ.IӆbVĭ|i}#2m3\(퍂6` [c_c؋Mi%X2!z׽' 1W_;@4w{$pȍ`л L)Հ)Qh ;p.+3Au*K'oPw>>bK\d ^U&G!N6\(F$ʚQ7e}_#eôqwu(2#ʽNibZ_m D?l;$*J5̌,sjµRsP^z"_E a$䴅jaQ6$۪t=g8}'x % snl{̆ -N_&)Ply 5}Jp޴쵦Ǻ'M c{V{IBhSD&xϸ-Fi\K#ыARxGNeJOIT8(8Z$Z{p VU!DMBD^R3ttpcN|*w|y}tFhƓ̳Ɛ^' !_bڃXA-JX&F4@&Lo`PgAiV%~ǚM>{ 1-_=A S6%Ot\6iSD퍆= jz9j)s A*qO0xW`a8]lf8іo{ˊcsFQh_$P"?JTE7j)Kh3%X֮hA*MG$s?+lN,.Efo$z<bg3\4( sV"^>a|3eNuC䥴|) yPɴC=V@fe#n҅޸/xˎA(IpǪJZC'R@|섷hND5Uү3gǗjĄ7Q3Ewl')[}f G$Kp#=wH8Ɵb‘Q n;  }<*MbƦvt3&L\+} R?30@GI5? JbV1sd"`ޒHצ XqU3Mf3F(K<$xX$ nhs$׳Q01`w n'/}{*P50Ogr {̛⚧!Y2b%U헟lc$eD|NBo_su"z9v/@Fֵ#AMamP>6Kb&2,YYu}?/?tւ^̟/ i>"kWpbQ\yx +@FrqrcxZ;0Wd`!")l)]ꤑMkJsq&[,f".|;|aI9hfYPN5톒ɗ|L5j\^0B78Wnqdy 5QlaI/y1EP<Új oE}(4NT+SGrt?F^y(O1S5r!| b[/&BP,G{D0uZ.OaMIM4&$*;H)'ԅm {Zkʳ=>i~B\[xusٙfN伎{' oa7X[}lyr YiwZ㤧)0{zuq(mQȯtH \@(X4lO홚2aѨfP.{fQ⢥M'(D21oʐ n .4zZaեs3e)0٤+2dD8>cc7TRƽsqVOA1Wtx 8΄Es Q: H_Vf{1h{Ҏ|[94=[|@P ,D X6dJ:# ؟ύ-5~ΘGp5=BV B0-m<k9s꩙ٌ` T &^ uUiRlw ޽aM''SIveT.[hHiLHژ>#I_ eZwDzd?OĉLJD;y\<>L*VU\ĵbݐԂnO`^ivv||~_$ء鲰Q,#DO¤aH {hDP*03kBx~$^RIM+J?(Ź0>ľ]$]Nɔ:;|{5{UmX 0bkwʛ7J 1=u4al#/W$ rcz\D%b0!]Ѱ ٷ,HR,^mwxǼHJ2pq֕a4}Fx; W-$PU+6Y)lGBNtxE:`!Y#viH;98%Q+4WT')0|A.{ei!cosL!ZJKK$&%dø ,T$^BlUw$0Ž> \x+2Џy'աZYJΥ]gē|ARH,n.`yƲ4- 6|["% heJ38`ttR&ͳsXjIsIv2r:"lWkaTr@xPJ^_`3\_< -1*7J:9v竨gq0=8=I$G%m4159صn ץXIq@j0* ~W 'Dǔy+èLu[uj/ֽ8Ĕ9g\5œ`f &M>5m jI+,(%J7xFg?TK+4VCK4Ɇp˨$',V|LJ㟲Zg68]՚7If8 "'=.mRFl]l%G;ߞDf1lc-J'7w8%ܝ\q.aǐ⼰8ZM6u{IZGl;e,T3pBa!1 MK]#3*!jӾ&^J.cF9CVETc&YĶ!=KX*x ND۹Hn *Ea!+Gn+wCs- jӟ˧3D=>b &^m]U@JUNdlx4H=nzɮE&7*l)xO` v.6in]J@j% 3Smmu,W[;L[v*SSzO3XVWCbX#gJZP\$Uf~k?Iq4vrL!S5SoXKwt Zҡ,!ָ6 1 ~%ؽʐ|b$ 5]S[b7HnMhq]\soü:Ol4[4|LX7Y5%j)ʨcŅ"bZs#LRE} "GxOJ;L.p] PH)ǒ(y~¤`--+F#5]l) +EI}@|\H;_W 5#L$' .<*eT0fzkeFlT: yH6{]08qi"siRUgn2'8HX؃7|$Tk >"`XO|֍̸27/-gX+<\sa^G]UEK HA|l6;ȼ5]Z6jek=,P}ڷeg&Bi}3qStOCYv=Wlĉ&U4WZRc/8{oq'gᙆS""iS{ ipo;xY{]Wmr$d}fӻ\qa,0z*Iʍ['Scu>=G^C,5:n y nqPbݕg*B3Y!i$/- 娮:_<\UW݋͕^HDXJ!\lynYLY$NSa2$lDF+n$6xs[!]9;Gs>Y?2sOػ̉ay!oA5f_X`5, H WojO#aY\ *71ADL:cݻƪp? PVj5{ptv6W'JKaKGlAՉ|EIkec) A@}],+S&R庂.}ޜ7-o᝖*NN4uY]<)+ s'NoB(wZ} tax7Z_;e !C9%\)xR\%./}+rAwn*7ۻY8B5>!zEk7ܭ>`+h[93sYzKb{6o@Az:V!3ڥ,?~gxPiT=V?1҃t/ 0t*jٿ?R:Fuh>gjK8adXC Mhݛ8a_҄ 0}{-Y3V5}?cEf9_(hϏΥCb7yGQcndLI$&t j,,w ї$2xxmn8Mc._kq7 ! c׼ 6xךAirQt{Asx9zF23BYE`^Ga-F "M  ^?ű;r}=3x]^m+ 83WPM)'\;k[+e |;Ae,HTY s}[pI#7=f=$e13Y8.|a c-/'^-';|~?!7IZ|Qvk4L2PMg%%S!YsS DH>U â2N;p#QPN>I eX"M$buѲ_-MvЙ!r,9OYNB>Y?m?7P'œ Gb~fԒ$86qpP $.ɥ+9|pǵ#D"jƣpSg !vy4sR0AcbNex4i7piInǁw.<01]YpH\>ʐ<5Mߑ1o9}`\lVfKDoz> sܼ`eAtfK]jd*3UK! V.2a#qg(>l74נ'aZcE'cL?h]GE hxPqZ")^K6[;b[yGTŰ1q%#X~Cr䢔sJ|µpִPi;<6q/<\r!?4,pO)xWKSXӆL蠃/HM:hMR LfmMx@hM*_n?O3}?VX^·1p3ԇ:l#uVN#4:qb̖Q"P mPQH2eD^`*l񗴀C옋H߶Skf 1V2k`?|zʤI9?:ˬM5f8!J ^T)|Vg@U#+`IKJXyr'I0o;x *M1AI!uցe2&#3P}u s|XUIИւ0nPbH4xs&HǽlS'7\9f+7o>cH:)< Ғ!U A }lO4G>k,nӽ\3o.hLcqUE] ;U )|Ɏ[2JI}H])cd*{-3 s:;tgAE}w4Lݑj i@.;"J P#M."F$ g=#|gχф6Mm2WUؽ*­xpZJpŷƆoa{=;jTIΚu]Pf>GᐇOAq#U}MmUn,pۤfOGX.+̒U|Sug,1NpwB/H{@b>[I4ՆHq η^_{go?t t~z-_QJ~l~~Af4'4ߔ$7#0Β?fn)qV^_O\ \÷z(V}s'BmG-ش:)$ :0juvG5vmZ?UNƸ0ԥM&aT<p'ޓj2ܗ8܍9.mOyb̯[f)0ݢ 4^{'CC9čuxM9ia.U5T2Ա~ղoCfo2Fxѯ('+hV \=T߭~t"=5z!Q}Qy0MZМo% Ăua{i-j6_pVM@@LOޘ2"[Ҏ Wx=Ȧ=Eݏ )l{A킷l=)z٣dx0Ԭ1U:aYחbĕ$y%Y:5{% St$~wUJv},McrXہWp-{Z: !;@{F|><7o6@/ǀ4MK_WݟBU!2?ɷ yY\ё ?kMs{6Y(& m}Bww};) I%2=q;Z`Y{y49'TF\|NpL 椇.dhqqwFRov(fjѾƒя= Oe8je)+1R+-/,q{͒1 ~.5`*Av@CmR Mƈ8wUym!SeALd<(5(f05cF-:Z<'>: ɼdۮ6k7)LF?'u0R tSLw+H}npR{OB 1W"Ή+$}@9n>aL!9WRnhաXI8UK-_ #4rW3$? 0TtOɬ"%M8.R ?oPՆS5KNOÊ]VTeb*xXrX0_"aTbfbG2GOFb:j$bs +F栾>S֓bݶ.¹@ّ(Ak^rYч婹~T4W=vWI2=F^mu+XV0͉MX $db'=l/(NG@!P8\bUu ɍ i^5]K6 Dwgn؊L?BزEZ t_'c2n݈`TWFtm$76+dxI)k|֏`Yf̡}<R*YxľGSj:$ZAÁ1idXɄYj0t"<,[5Zef* esDtŬ䝳U*#${}qg#]&5;tZ%cSg`%\臁[ù#dIǯI5($?` L:p{Bр=CD6KLWSMhiV,j/5HB7኉Q6_n:E`¯Fj>Bki0Ӎg"!Lk`ɻ3g纂l$(?wF1 FG50k.@"!gیaApru=qvBf" 𒒒o[ Ś1YU" ]sk%N TY&lrAk{ڼ8\ _TdհSZN[ި,i t.zhXptaFy{e[e[ ٔc,5`~E697MTm09ԕHK{v5Oת^NC-pZM6U"orp:v/0SoqŊ~ Z-b.!sNT;lqiRSy`X1 1Vb@y[~iqO2KHH#]dѱQb 8'P5[\O#hٛxY0ĺ$TA|o\Ñ'b|)Pڕt:rs1eY(QB%oRޯ:_?{.#MP:yʢM:%+>fcFfAQhcUHdqg @Kͺ՟rcV*n8$mqv۩gg@P~ p&mZתDE]۸##WػۓPl? `Sɟ12qVNmT9oI?9M%V{4j:WNS7Odm5g@&u[ 'Թ%jT[Q8!t.8TGc&FMR_N˗v0&XE3jXrL#i^.mRX{w ?p5e՛Νh#6ýTfJQ"W'S;ZVUs=lÊB`]]EV_aIg`PHŞ8CI|@5="LĮd~Mw:! W6r1Jp-xlKeA/ 5i0tmO.c,\b>;^7 BI>nT#7 l"79>pYs>YE3u`ןGAKN1h7jQCKw7|X:4SekV+6Q>_h9Kc97lqF+2\G,M/k9Ou|t>L?#FvxŘ{xm]Ve_npSQd_kq,HUZ;;_߶lq/ :Eɲwl۫Jll%.w3I!/{i7v_4˞nK5^1!\bsHN'F`"I cYԫ$5,\gLJ" 6PeKv*/]%o7a%Ť'uXꮚq t!l |V/ b;B&<+y`OMߓ%.5w\`_Mh/^FMr'$^ jkE-Rz/F]!6QMהa[d.Z/]0W{6"QLT4kq6˒G0:G^ͯ@"ACAxߔ׳rK8/}6l\ͺ(ژ&&yP -Y1w kIo=GhOXSÁhGte(HW蕡# |Z'#>ܠ`YvLZC yN94߈ӭ~G}:9BJblHA -LNS=C:/ <4T#P @. C`_3ṝ;դGEӉ'.B2ޑxS0>;L$Z(E{m0-t!S-&Qho`S5G*>}{U_&,@=. T9aFgQaسsd<*H=2nB=MM: ?*7]<>y;zί߫\-ri4SwbIjZ^"-$T ^r 1Ȳ> aCYpi7 9cV6`{X_ z̪ Z@i8*E9zrk8^(? _ 2G@=O:;Iv2¼w2:K<#Y-k,Qw2s`bK ھAGXF 2&i~XCJoDjۥOXNNSoL`D7 )cvy IVh($q/vKp4u5~`AsVmV# yp&A4ۍ;P.@Tѻ2{WJ4NN+* zg4qϡ6na#Q텆L*-cU+9Y 袻g)Pi*)@qj^7 T ^dq>Z*֞ZnͻB8Hۜ< N~8av,*VIc4WiF'JA+ja+*@w p@~;wz,lUy=:YI]JH^_t`+=E0HwO[:'ftԹÄ͘ݔS{LSe=-잙J,hOwO Qy,5ٿ]TPj`[7q:Q>7-'"y\Tefb[%0ItZ$`B<8妫))ynk}x `%34_j_.+BCPOU=)t8QIq$ aC+[Ggȳ@vwWHx67DeOj.4473^-P2UaQ6' YFG8pCWчuIب)6KNcx4lf-ݫU ")qUaCfDnJڕֶC;G;rgsc>—J2_\kBP#ct3K=UJI< v%+o~XT>`sn?E Cɿ/!5Jku]_MI1O՞ @S{;ݕRzkICXP%S۴s[.")` imjTX}OkbD_wrw<ǿ$| O߽qaX*$Ӵmz"I~~vxhփ&08pZ7vf`.qT䨺;ʄ u1mh%tY4Ry΀]Va+Ĵ}CsݿLdc#fP ϻ|Jb]€}OKŽ|ypdԾK hH#gT'k20 ;4=G&ܽWnTKi M4xjN ja尖'ou&AY2#<CSC0c25ce{#7rnVKI`H_;RǔX"4L'WӢtj>@e@` 4m7(2q&skH4tV p4#n^TSQ>=X $mo6>)8]d LK/|ƅW@2, (#LDP\ G R^ 4\7܃9T_1pE{`_r~[_c9>TS9HT"tCpTiϤMm͵uS5נ _n'[>v ʅpz8h\\(pi-ӻ^7I9*9L*J"Fz FxrK܉ 3uS3F>`vdobI*RG/Mf+!`JOUVJVqD<~w_uK r =]<ގ3 ~M9ivHI|[pᜁ_(1H{DQn/Pkق%lNy2c$fܨ ο0wpr]xZLRIqz@;! <4OɞJMũo$Ce#T`b`dS|&Bow\rP(Z.={F9#!Y e+V.3P;'hE%-g>h|ݚD1 ;ߑ  Pjןۿi+{O$eAퟌ@ 'N_꼖ka6WMP c,_GدK/n8IpO/%4!JE*1'[2.:P_k%[m UgX!Ze,s_lu?wQ~zr5(pρ6,eiW2{l |0']lweĐ8ߍ M_dōem!+E-ڧőz9}N֨3Jh]{;Aa/xeövt a"8l`QHNⴞ7bۓ|P[:31-jPxUbt[bPLY!>OT;qݔ›TIcC@&q Q1W@ϣՖ XkP@{mbvLu?҅%# 1ŇiF"pR#5v)Z4Xe# P!0kf9RɝA}UwWQKV {X==B_[P_LL):o6 i\Hwd}kK|g]?{GF;2ۨ 6rRUKcVk]!jºvӅlB t>G0«&]@L2 nb҇-^lۗjNr&G (Z7N}$A'^BʶHjai+ kk}Y6> i?Ƕv?};y%O]$/ڊF,vH)Bg64с)f: 1g.';eN;og=mYcх%~(T㿃_ms;7щ\ŷ5:6j"]E)]]ɅCSSI0"30% )##!I4r;|7Ԋ dP8h'g 4Lȏ7>23v"'wl!{9H1 RuYj_ F~F~=+RsxKiKFAWS71 1.b* ]1>a/80~}wzV^_fN1Qxi?o;Y*I06$P&eHX(W+e&׿/-ٴ=8 1T'!x[$kA{, V~a lj,LV9'Cs1{u@J2K1L1G6|!ްYgw@6#Mۇv?g UVuJ"ux"33W>wy0TCI_XTU'yTmpcLmo-VG 차iF{[TD<4嗧Q&J!:]K`E%6?/1B-!>)@ Ǖs/ FC4uߝi$hGvs+5D鉄/v:($ !ڔD"(3/@׊UNOzmW#4ڵ$d#%پD'HlIޭ '$3X\wi# oZҖ^a~ U[~Me ~i #y) {J~.00>񫅷@7զ|>t2O|x)g{uR`s68cԢi} SkpkS<,3@06kK >< }uؼih44m/FSD! Hȇ yDMb:5q(+p.cicai+N]WA/j@DG1{3ǘHx!׮6fW=bwCrJǖY nXP(^Wٴ?U8DF/;tylYl&碓e#hm8H=(m'ڛx8yHtǣ+nDfFV2/Yuj:{5>RQ?hWé&WڿO.h2zQG=/sU`cqĹͼ`PAurfZ],ϿJ[Xݡ;735tΥrdWl8+K+iƽJ6j7iRLE!Im&spx*vUȥưyɧ]#\2MB g^.^|Sb ?(jZQCۀ*~=P؏.A51}uּZ=ػt+u^w<`МAH G+,p2nHF_~g34|w6CY5 kt~ ?aD:)7fMGL[VZAXl&i ϔ6fl ʱZ}cNvRxӃ0y{ͬc2b0l慵˘W U6!F{oMRc Ԩm<%m}}L0 5ځȶKv~ޮ--ώ5j]\C-d UGx`5 QJ,?MV. Lx _:_q4[`ݵ`W ahEJ܎&O^=gbj[y"ƗnFs"gzS-͕@ooeh ޮDB%J.#ZXHI~ H/"#%h#0@*"r}fWjWp~d%/Lxw򨂘l0*68{_GIVLڦse$\[~7{n BV MzOFD`^6&JsrٍvoɈ.YwMJulT(/-CRofl .g]%\Bx^[eǂqѷ'i SRo8;JF'>=?`qeR'Q,հQ$PwĆQuuT:䄭?K~WT{;L1vHh > xeT SQ 8ؚ?}x`w=P54w0HՀ̐f⣆#g7i~}8^ o$h^)hLsLԠ GGԤ2N;KX_Jvڊ60;m~0oO.+\eeM>yFCvag%Z29JQPcPe5Xǰ0 ™uIL>6dT}F6?j-za*mO[)oYtµ0sG8ă1g I5&#ٟ:IQInxtFY&]0d?[j 8~.)W3}J,0hxWJPPn)rZth$YL V.fABQ6h|2 O=o@{~TwvsD2oL2A8𕜚RQ`ٿXy@C3ז_l1%ޠ)8JIlanizEPGrRX~[P'rF.Ji *ưcgq#$i\#Z<^{޿lp "(nMD!n)O@&yA.kt{yl=glI8,o]zU Lu#6y1„ȪU /ӰuvyVB*cS6+%p.tkmح$ ִ鱮D`Ek=؁ʳhZTfR3X[oqo A@(_ޱ"=WixeǓ':=U/-gtF_[C|X^_Jz-`KxZu.>>{z*}I1RУ? ӢqrlSwۧe:UQ?hC<4Hlc\,;u s…h 'A{7TOH&Z)BV d™/ˣVGNURdr|bi@۴6Nrj'E^ "P}6LU\ }Borf]ǿYA=(2J1J[p ? #@ù} w#Gf8&Bjm9 J&jR9[N1pH ^ 3 hHAڼ|—CaĶO%wxf]O\.:vc<Nk}/Y3fw9rW+xsJivB!9??)u|yBnE񃯫MYLQ])4YoYGc;LJN/e B nP/8¼igRN کsj_Kق;vEE<)HF 9#q/M|舮#<7t<26e'Ly*ǎ#!>N0<^0+ɟ $3DRmabW"]=V{Nz3"MAdlU|j 0ԸW|f\BTGbigE܃ G{y:G2Ac$Zw3rB -bT[P'ᆼ%' R[_âf{M:]Жt+ϸo%kΤE[t)Q"^'v 5vd)y̼0vMT^Pl9vAKvH+€,a$3tCQ S6NY*=_ ou*:ET0e 'l ۘW$s)ѿ|(l"t L &y;Ai>m^kPU(2ێF^nk#W5Z(M%be R!t!v&ԷY62jCK/[PgJ($6`T*5{%v2ׂFRmIk ɉk}c`䌢B|ޙLF!tY3^ڋPp_ȤT'/D2bfpV n ޏ*4&hjD:zV98bi2 dɀ{o1LLw<toB;_]Y׾-b@S*ޔig0-B$5lngx_2jl4.<'-{YDgP^c%jClwyO4#9eh.5G{zX7NL#G sC:MM/WRb`zf&ґÚ"؜5OJI̙f ͠ - -h %f)\RWKt5MD&hrG~d 4]2zEt% ,J-CJz:pA+2iՅA_3A99 wثVU=uǙGk[`Na}"ND 'W ?ҟ04k/Qim!4b^qC%N_ZM^ sE``m.CAP7)I׬FX4j+%3qiξuG6xy ]2<68v^ Z3ywV?w*ы/5=\3߱PæW;0Db-#q2pBe]A<]%[9ְПRAVhWN`*_ӌ,s^d/՛^;ˇ6]I'鈏 5d-?4P$RXܒuBXn/;\S'טT}Mb> W0uK:~ꅾjI= ' : l$2QشL&yJHV a(QӬ=Q|im3mqEWwNӚ_b.T=^Tj!bxHfÃ?%[y$ =O(2ԠMCV `4l[V x!`#8b?(" M*t΍ 4pVƌ ::ϤN!f5p7qHn-Y5il{<ف9 H/頻0jb5g=,.sEᆲ ^v_ 9< ?D'>mU>׷BrKzʕ^j8jLw,l7RR1rf%X-B" À*p5u8kz<Y+ ^"]=(FF+bs~7ҵ]*i}ش4[b Rb V2(T VGXV2.ωh{ RG( ȎI^ O)iI6tv$CP o<`X#;Hгp~ H 1I ;̐`zgGsqPv};Ba cw(QMU۬܆Le͟/EȾ~9\Ntq o[Ws ޓd%0DXiwWt]\]ju+2P|Sm\Y .4=u[8|;L:qwg|"Uy݊Ke5@Ny?=%p`}mw`T馟/O MuO,a[!m6ww+sM v r Xj_76'뒏BE|ܞ]E=̒yDTay05{υXy>y40r+H:# 1%Tofݻ.%퇠+KT. 0OlMQ|1ѯc56(|Cߋy.)@F=;bhML]hѯ +vZXY֍*mN]U}ryk}~T817 B1QeF&d6/>?w>; c:|R݅Orn{ֽ.z  0Y.Jo j2Fϯn\JF2bJ.)ޚ]~^Eʗ]% <8͜)KyW…Ƣ{CLo46<V>#)Wxnٟ7"t!9c~\~K۪8n]+2TMcA0)b5KǢr}J5cv*5CӤ/ Ldl@Wj[HUZa!L'B6J#npu8NhQ.I9(73u6dm.\f@KjmnGH>};r/WXLg,5 E5ˀڴN5ZgӱL!J:P (9 vƟFԩbNQh{F6V#lp?"rAͯ^6ed4^B9' ~57#;Fw߁]Yߗ4߱-( dޥHw nbuN 6DKXLr,qƚ`[='=NO+(6pGm@b\ %Hp7BVK#E%qq~o`91N>!s= @/vk{F hX `e c1%,f4A9Lz F=O6H0Y'cDwEo, _JT7~ ii12$=|Ŧ3\䟣r7{zvjQ]15sz%M:#]עױ6R/Zj$:T&,8]AU?BeY] 'DY}lw*byl>TGĚm x6`KMylT* :9]Ff H3'IIP*ŴՉOߍct*w,S?Xy,94Y4RTn25I1k>Hht_q)u99FV6qBs֖x` @ʲ<leoʺ(Y0"Rm6NA.߰Vv$+0`(PL%d"9¥OeWcC-kW%>m6wLY9 ҊRί% l/͡Ƶn]!:Wow䬊|sf5>x &0FXr2 \U3 ?FjQkab(? |9hPnSpgs"yX_?5x/@{btD;[ U!e5-eHm?jQA{M(Ohwtu~-DD/*}CiġZH\2ZqR@ +NF9W'̈́r[}NП΋@D=Ԏ5"b4ԘS6&zQ: 褀vQ,OBk1 , =I82s'P%q,2C~&6aDn,6NM1;zF`E|DU,W3x*:.}A}6}n6yx`3 ~!~beWwD(GOr *2D)*$wj#S~KFR0S"wSk_([UB "Ed2[@ ˪kFJS t*JX\_ӊMXR.?hxlO_nc# FRLPO[-0j9HXx ˸+AWp>222Nh(6DF-{LiRj/F.rK]e 32ZGϑ֘^tN baѶƤO2!:G􂌳ypuLkXgʺG ($\V0dfZ,QykTp_Է;Rk >۽l„vwn0nm(DߞJ-khZOlai :WsjQ(Qo ImƜlH~)@᳧IK "4a1B:IC')JhvA6bI[4ꤠp3T0SC4YAF'PQuqce_x9/'b  h>#@SP4i1HŸW'Q\+E[;k1m!Uz|- \WT G` d9Č8*( ~|zn=%aznIJ`A!߷k+2J7w?eR7jXrmf,=G8x %og1LwU|SYVf&ၕxxl6m5JՙV|b%&qH3:ɖ*w8j`pm-we:XltOIƺwtJ>8"ݡ(|] X;ߙPqc %s˰ G_qX!=)-X_H>8کy/62 -CTc9.95=ЁPh$Mk[=G?6TLYL)fRJ$R!@nF2ލQZ9? Wuu{cHo#_x $׉ T$38\8k?I&Mivq_{Bk6Ӵ.6+eYGF=F8oVnY@1Ea"łoN/=09fVm釗y* Yɳ7ZPoe)^qBLzN^)`^1ݝ Y;FjgBmtOsf)J?C#tm/E?6K7y+=SX\Z$KAͷmtnax}~ȕ:uo*H +o;`1? I\E$Nx>((Uv k.5 aUXfK7gý]r^zU?04h o:hAJvI BLԪ 1-,6]ڕ" S\;E+0Mha9z4KNŲ&,xqL%o,,Geo .z|^))xe+/>aPD]mRgTY6ϰ$Kbı3*K aYY(]=Ʈf&%Fiq3Օ~"XT.M!wc&K猟]INK%G 7=tʆ(EZ> ì 4C .h0{*",D^E,0i}DMd} :}:(}(TʷZALt|Y7^E­Ff[͏17E?lE*K`mP'JkV蛽}a5rDH7Y.d{VSIj+"*j8]O:5]L0# EslJ=4 {]7Ԛ@ͷb?ʾE)qh@u[T94,MX~L̬QhAQ=8Q̪Uw'WmqQ`Vu!w$D 3Y0Ĩ8Q I(-'2VoҦ@V3SHtpm F(pog^(Yp`n0!,˄҉;b&:ܮ=KRTи6cBW>Y56T*v{joL:q^J.C-i6jȂ6zGHX1JNXO0ɛ!V6+˒lFE[8h'JPx.f'7`NFB;!^C\f-h3O(! #ȥ]thﱨ섽sy*#jjlݜxߍ<)YGOc .9~4ͲF>Zc9!}v 4 Ņ#Vv5bƷe?e^3`ўlW%uv?7Azz<ͥ=ʏ5`tjҬ؏)c4$8&<=Ĵ]g|u8ܚG_keٝ %Mצ# b~8FZNHݺ:+ V,-?I 'Ǫۅ9m=VQ_0|Qwx2T q6D//uJk<;hzfP.< -OxIĈS/wkV _e)'9+z|_u̧Ns=^#4T|uf$t0['$÷LhͅZva9-D:204F=\UMnkLSZ隕*P)Nsk> ]n;6?EZIxnGt2ƥ%9$jǰl4)M:" eqJ{udSЎ,lĝKhIûyYuMr}줌@^A~kbqUdcN/rȍCx(^^tj 'cu}.$>4ocŸ 'T016  A9蠪W,kmebLSod43 *:s$VWC++j$n)Խ2|""\E$0)[e4XHc 2ՔẑUC^N e:D}~~*jheeaس1u.n'3 X߸3 FM(Q(d.zkǑpIuDZ,8tGY>(:!]v_4Yzeb 50)Ψﲏ۸|c>S'֧錽^fko׿OʭH_r)6s751 iu%ywU:#{g͑š<yo~u+ {e WrGIvgqz _uYUˮ~I#}ֱd"srt%U6͝Z+2'  S U$S3;^@y-bo08p,TN#[u84Lsۣ\j~i YZ' ,=gbtQ)ȃptЧCet 4I! [㘔6sbp@[ރG-ry-AzU5Evyɬ0Z1訊 ؠ76K- P| *XO SEȂ8ےJ(S'DqwM&o3=]&wLtF2 d{n.w辧5:x \$W 64b}}nh^&o#DZfT Rz vw .ł[9 _rBA@H Fرi ۇvp4"|em^UV#=|~D?Z`OMT `bb[m67MobSQ6EWF Ͱ?]N͙%ZTw6L?щ)cyv7{N{h*bQh9DHV ;v -Ҙ8D,+RD\ |SovpBh,<2V|[ :x+*y>@yov`c *5}_1$<;D|zmb+Kc#\ևz?^X[G>F&%%=R4.0|)@?2DBˆCL_I+ _Y1)/u<ߏ% RB4 +G؜p"ie5~ C{}5p$X@P~7lU"7J{,Y8@h18ހ b9/ҊBX_$+5]֥5)dQ~ߝŎDZ@bKDa{.y2-.d$ǂ"MhP{C橓B3o1}$Q6k<&$䣖ec@·}?\,C }P 6 < }7Jrn ޕU1b Op^>Ʒ(eI$7ݙ9U?uՒ6ԩN2~4nW%f11YBghFĬ[&xK5/ zF~@UOljJoeABMw±`OƼ!Ǜx=/AcTr(.jTqƳ -toj o?k-{yMٜ/m1E>~)pDSGE B~tao7zSqH<ش|-!€4,-;l{.3ګ—X;J=Ǚ+3r];ܶk`@+ (= gZυ.FpngsWahD]{UJjy>Pmi B<[vO]~R%̹QC Й7i<Q1] o?/AJy5gb,Ǫ$Nx(vw˼'kptNISE9c (CϠK} ԫV,͓-lJQҾu5xI` eQ7|c2K62`mU4ʡah }CjND|vGwNI @i @20iM\E,!3CkKy{|@] "WiTOU%&٢ةC;<ͤ)b8yFP*aA&mt> +(_RAfIV|-ëE鲴qxb q3L7*5+rw=11xlF/`H0hs 3[ g|v 3 @4kq\<K* n/@,²a 3<7fpfS9V]4<}⠪F) ;L%8[d1LՆ3`LnDHl{DR̾ }d_PKMpx濫97KA, ^S#-ٮz.xY!1.FA8k>Ҷ?ՇUi$f8ܗT*8(:ܯk'5<^r' KUQJb^,=d$2qRZ*ːct=4UW_8]b^Ŕ_f1>aYUconա%X}Bb2ǣp;m8ngrTԤgAZ-rglwgd.Mlya`l!u5qΥ~ .qʸn;K:U4= ~_ljR~[jA  eL;r#-da88c7;_(9 c\V4Y32''| `b<oOE:9&n!E- ~mib +K]c,U3@]bn &F%qJp~?d^eA9^̉24(/ǗDMY{yN+ŽhWy,BrO@u5H}z(}iနuDaSk"BR.>g}9`.hS9ʾo wZnnoS?KQfɗ0Rh8oի$4P3;>D CAć]^fIzF(lz|MQ0x%tG v: MJcjX1N^dľ2W=,jU>{E(2>͌vAZv@aVz4_&~ÓJXn @cW7d5S7J N2=_8SjzQ3t뀴l+Sqib{u[rܢ|VA q*ZKlf f0c̤ɋC+'gm,0#h[ އdf; KýS GB0#9]/oO-߅l@:~!Ft ~ePAm¶çK)m oԨM.вw3x]|K ߧ)2%z. ׍;uk67Nkp7ʣ,ql44E"c@pJG%'5'UhS$5"uHjf \{'otl v&POP(imd5g`oH : h(+)Y%\&e?.~mgV/vP_.6 @ ;O6؅Ttҳڛi7)dӛ-Km?o 9;& F/ & :]Q#zs[36Sy)XRX+mQE{;Z]q xDJyvވ҇l&$yh:g_&Wmqkq H,CRcKo i[)߮b?Z;+j\@i(Fj^Ґ4?ʊ'[<:ae :B Lor'͗G M ;S4FԭuLyY<$M۶+xg67ɺl+Af&".N㈓I7-qԝ-o1l^njjafJ!|:W]HS}?y[U+6"K+TOaC: ՜r V,(?ŗ4ÔY@aZs?(~M8iOL>r1032eP~;|QKli'n#C(P+U`{Jl_/ݫ|>;T-5CR܂q,ΐ0Xů E! yٻR3?_y6Zlg ܼO?dHl@=0wdbs Jy~+9l ̋:/t7}*[af.<47,,/BhX98Vk[JC7ުdbFC~'1t*'ͯB% ~'QfW{/6i[($7% r4u<C@L(4G>ЩX@ `Pl[ \yR&qrpc {wRu8Ŗk+rO4 m@l+KV!^KOvpy@LeGΒZh0[+IMXXw3Egg/љmbc6lrVA?KB$J%jeۺ\ʙi*OAgRδVGKNz)WO)\I,1(~IQi 9p8'>_ll4 e5i8:w(϶f[(bϙ!4B}LPN$'Ϗ]N4K>n=7p7炪=^Kx; ÑQ |#z3GҎN>y<lj:tR0N>&H/>I;T=4[P7gHqs&0j,҂ ¬2VIn9z_/{*Zp08y 3ݵ }Z2@̵H+vЎ*Uط7́r51>Z~Q0ۈȴ;-n/TEd.V&U$1Yr_j؜-q|p\Z,Qcv0,~̻㵯MKY^ྟt :%*EAv3ЯSr2KOl 1~%h#(˞PÀċH|d'jWE&BkDMfex%O qXrPGӛV!VkLY'^ ĽGЀKR{RQ$Jv,Z6J E:SnaӘԁVxlp' P&>Gku I:5EY=*ٍ&`s4+Ef&l+,G z3DXmBAPCٙj3ٍ"+3|/kiz%zX\=X5 NPE)@.Y;"leI/]O66I:{ݒ!lEu?X.`5p앦V;hNzߨ~枢Tĺn LoqըAJ12m>cP#!.X%`_ V2=6\$R@luJPKŦjuTYnsy94"vT'E9. $O"QҮs/9d"ؼfHPd5ڬ0@t=Q#!uǑ*UyMzv _A'+`9Ljd\j+?7֟o>JP /3:x=\.2zN-=#kJMuyłȬ_E9k 疊4P ï<9Q+6YFw,%{=6qRVB^< 0_/H] Fd1\yf8I* " D}ucܟ /)D5jn2}j 7A^Xn5P\MJCҒж8?|ݤJ T,yKcSL'7B_?wf{'%zJ1صx&@m{P &E Y|VQf 1tk ba{5\NUO+CJ0F|K_tQƎaӶ tˣz MWG~ ;ק1lV{/=~4|?>-TVO Ns֯r bBFw"28JA5叆<|u=Ony6=zGD,~:ӞR=cd<|1w]9NYhJ]i7̛sF>*ili).SP6ϨjZؖ=Y{fs9Tעmi uFX{܅7f۬t#5{Kݿ0T"I.-È&@oz4;sNyOP*OTK(dB66BEUYL7qbQ:2 㼞=kd~t3FW myvB{wH˕*7&UqKCy\]!p6XShñ̷6*5s'TA=KI? PZ+iE(a%9Y>szyʰG)y<ӄRCɓZHt)Oȁnn6SXzcXsL?;3eiuL2V5ohzf]yV2W_h61+m"`,P׃鷋,2s%2 S .p}&_ XKG O4t6NyMpy@5,9scÛ6,JR VÇ$3wd.zݵʨ;_kzB=U^)p|YNK[d{P'rQ4+O>X,@YEY!|Q%6Bri&$ڌ3/-m fz$IHq}kz?Oe.aOԒe| ,D@7Q`sU59//"Sq OI.TyRdqX` d<$l5z Ȇ9455M~duA|R:W떈,ޓ|/,M%}QkXNU/ֽBKI48u!4\Am (zaKEkɣrH@bJ!A OFZ[a k!c{|Z6i<'Ctp6蝤@O_،r~W+Onu8*sʿІŔAu]- != @lYJ<$6O^w 0G\,]5Lνw]ֿTq^Ҍx0ɒsڷe! 7G6_cMtc>ԕG1JbY.\QO×&@`e \!<[@@8YC%):o1cؾ{Mѽ7g:ikM5v +R{ΨsR@9A3_LE unͱ΍,FwWV7jRu2LO 릣iOܴ rD:\ f;{;% ?3_C/|YbuC-@ 琻W0%]=IE.zP>M'zRmNINc\2G< $N/vڃNx1OtA\:iʼnqW5 ўX Y3AF VFWnYtUUǿ##bj[`zv^?}5;" z8nFAl8g/3tT N}*sNAt5TgT?S:GM9q8͝,]9BPHKA N.N{W]dy˿p#ZC,WN8 +(L~#]bhaFUDc=Zp`mеsN֝=`X}ٿTHq:LC|צA'%_M&W Q870Tsu$NApC cS{YN;5g1oY^x('' J=ܿ  B;8OE=3{ {ܛuiʔϼZKU Aa$=oe)QLND*X#rOٍ""юA9CnڪbX!ڔ jc "G(@YVpIԍfN>KtSejA*M&^N|:\%=ugox ٖ"f9c^=w j= Tr}m6b!rU u-fzSQ<@{ KֈڏꚠiEA A҆ "1bG8Ahv@χk;ϑAlAė3נ0; R< 8PʢyB~c] pi%)!p-Xr  sy4IR($pY'` }Q:Hs*9pyDU'F|%'$txn9%tE-s/Dfi\#X|UqpC-otwWlNkz x'qC;lzi"8HQd  o[rE9tTtZ;-?( 5)NG#Z#{ҭIqI<)vؖ'Aľ?iג60ly)(Yӫe}9fIo[iM~v{9!q<5$ۃHjLi!IVϏ_s˫myUdA4~J͛< do.)5-3),8zw [˧%Ns"|(N5:/RH/|}Y:E![g3eӵ+#A4(*U i‘dC?P`%@TX+A'-qDBzL;v+z΅!&DګR,Z9L(| vXۺ%F8BPƶcul&a"U-/ ߎbzV{*49#F<숌F2唂@T@D5J!,8=4,,\ӐCweJ^«Gm湣<{Hޖq (`#k],J(VGXo:p/0/|yEeyM{Ufc"x9|ϒ)hlOf&] ] .)A hN`\ "Ă+k;ٕkpQƃqS>sk8LՊlYvg__4Rv9PY.> *̖mr-Ϭ >v9:,9I cلt}ګE7iVڥ>H tnzSCqgyhPGD=ϤS};ŐPl}, ,%W\n_N.[=Sĩ Pl sI|3'4ZӔΡװ|C焽e^?0;e'𖫩 S?"YLp^YO4d֣B*x6$ߛ~.&qNX[g;Y^ΚSf>{/$c\P"{6ǓYT4{:a"T@]vƦO戼- xc]Kd`o/ev* 9?vTI}8Zɘ+ea 8e``V"]PHλ{n"闌p[t,adPKDFI5,DNӽ7Y;a|S}ٯ|A R,`v#HI7ct=`8r3@ޛ{u0V&lZҾ#,ث Ljl*=}!VBo]huYxCA':XΪY /K1Bst >b0xM/Bѿ]X[s 0GʼnvҴPP_?!MV ˟缾ccdMp~cЗ*ewXK-ԝ4 9.կmkIԥ`@?Eí<`@}Q |H-~A E ?gQG, -@1!ynT/vd텪\TDzklPM5ڿ{mMELѶAkf@ lc<z(;t+#B\]rQuY>?AmQ_<&أb Ľd0?zŢTP=\wj:D ?cMзlON'9dYḆ]Xz9oNo>g#gq.hޟzq`7Eq8ߓ)M I>r'&gIaUQ]7)Rm͌Cl$@E/Mt0jkN*^؄lI~?ƫiL\P v}v@hf$k\0@J)#GEd^vsT.l]"M+{K"y+cז] Hc:3 be=[)A(޸`F Y:Ύ1>o4r0)7Y45+ eLUΉ~T,pEj:A+U=e:{QRW*;Wq[J>ï w/0o (8 pهmW%*&ң)c .V AN(A|:҆%\LKE?o ?z]N83*mr$EE]mnƖH_NV0m2$g5oz:E(K:u~DoS0kZk('ㇳt@w *zD垟8狒oSҙjszcAM.pC5=.%_ɤXK&6yvIGbxɅ}>7$7ਕ_)Gg~%FjeHk3,i >wSX)߻/:}e;6LkɃf鈗bTk[W wb}rdݠ+bO֯'"H^b 濟Ĝ)draącK/@Bn*IX"ӻN/'&~l;BQ}^2)-ಘu*0Ng+&( mnUxrEp |6crCWjoWsX/ 5a\Ǵ M7`/9Xs@ryDg5޻t?lj\LT$pE%穒Φ=*RoB] F5N?hWe0`/4ᇉ[OͰPCrʾ!'c;}mbc, UneB[RڍiCKLջ#"?I()H&X+Y4IqUn }z)LIWׅc𿓙OԴRf,Dp6P5A'TfQ6}ʑvPT>{ab[ \0JVٚ.~j8GF)6~DCj2e|]ȕ[}-oI;,n}A_9IXݦ<ɛM@( )1eԄՎU7ŪF*f@je`$nL 4@ОtN5!8r}bӂm5au[^y00矞ʃD ^ju:Gډ6S~Ł^Xn+r88C 51x(KtkQ5;bni{a3A  3$?XyTHB( ÆZ-c?qɱkCA+ĖӮ""k{֊kDk+x>Gu(.p$߄h}ʪ gn߅4)z3UݙCǫ!mZJ}CUB\ο^@d@L ?ՁRG܅p1A9I|6@2 lJ-AY0+SNqZ{Q`%/BhL_qja _L·=~R⵺=qŦ8xulʕDE|bdW(C-[|6&:[0ud.q__B;`ʊT/O*‰;b譟<~8Y˖s7P1\Dn] Eu +,nyLq۽'`ŅJXE5 m!" .NÑKi;ӕ7Um"_ S, n)'Ƞ0Co'R=x-`S %*ODѵ30`HWJ )VkX hU I"xGN'ٌCH_Ge&L~.-ќkclSqG WPZo8T̍ؒSVbKngC S新gv۞6i< I'0YAEmMaQ%e^X?S!8liWGuD5&%v@?7H+;T'a^sɬLJHװ>{1~x4(FA;jnJQnġ" tv6𠷠$"Bz#', w6r!`fDQH&k#PiRZһo/%dVACM/mWzQ[+WC^bw%Oq:kڸC~.}n~38xP/EwJ?gⶏ\b;AU>>ܱ%-[|QvL֓{!_fn3)=bNLg*SQ GT5P čEfdžUkj0jb]tIg8i\3׌Um^+4 $7rkx#7FD 32l"|- A)65uܼ9H@ tv@WZM=3b?jIFL#FƩ]q7H?'~ xEȕT 9PGluhfS,Kw'$Y?@!sSF0hjA,s-?hi$ I_w%~''XC +ZNeuz_a&ыk# ybCГ1'`@Nz*T.]^-xE@oκYsFvL}4tA9^o#z711TI㨄퉡giumpCfFvg`)4xBQKAV)H*oszJf>ȯ(ꃴ:_D7&CԚtH!5Cu6UGl?8!C>U:@[LsYK[`j#H f.XUU8>%ZDmcUyl"E(su/oɲR{jBRpW|?kxKd-j/QDdH^PKҹr\L%4캔#W ؆ƃGl#p[R3~2!_4 +xxnln7@rF}<:kF{G4[U`9Y֪9AXI~w;QǨ =J2 4LD,V)40Γ_kߦ INznվ-`ڟ=Ujl+M?и/ K4P$8AN.+ƽ|Fv p&UH+u|]G_'? XbFzß:a0$ԨVHmR9u~u1.sGsZI<%; fnլBDWM5 *kMH"4LH fpO"w&Y~f/Qyҁ{%ԡظkRQ9~^&a9jL'DhʴQth4tL+UN1CXj^*`2%MB0W5Wyo#DCZn$eFkP,HCx92kaG`R1u[]':= ,Ÿ&nDsJUn渤Y''RvFʼ1bmMM,>D%NY'E(\ذ}be6V+AEd%<}qt7+Faߴ6[- t4.p8ñK%"qJ@kB{lMU6:sԯQx1Ѫ!@:|[?8ܗVklLZW)Sp,)Arބ p 2mU&S oxAS$iRJ3#Ͷ NaG sإ>_pE`.x)S3CgVP1>*WAFZSXg2Ք[>d\_j? fOt7U@ocZ?qKш@J2i¹oθfRCGlkuu =ãekYv%`$h~}ZY& P{a'2O^;qmU FPA a&yJ'e~ 2lr:C BMibI""lB9HK!UGv?:/aR4Tg .pS:?y#)om|ugw]fͦ!"F$Zu9JPU\} {>Xa4&T ifΉYւ%e=a&^z[3|xQclwUpS˪:Zv,(w wdo*t1083~2#$JŰNN>/[R͹v[VXꥆƯҬSH5=%V-d"wwe`Iݧ5kZ*3s1 94򽥄yB7Y{طZǠaT&py{>@eЊ& Y ݪFYps#9w5ҴщFˈ!bشjӭCamd?ܘPFwgѐ@"!&&X]vTMr_-x2>^WYޯ%uAcd7{fd_?vR4IV۷S\ixz šIw7-0@֋šX!0nD$ 9hchmm y޺xa[&s;W]Ҥz{XϿGMKWJ#dq|~* p`pmDv˓vkʍAoh3&brDjٳW3Pe&,Zf4ߜeqTe ?GqS$qa<,дG:I]`7( 9ݏM\_z?N*CɲL8HEO̒VEcEO4fhz~f©#Quy*dcO |[u0sDz5'J J:Wg )w &OvnPI.k\5'݃D*JLhM-C\){Kڬ])6] vm˛@ $a X+] lXdϲ)Mr-IRL*; h)UDY/o\@wZ0e-CT:8?̾I3lpܲqB|gyyO5^ mLC;琴܈΍'%cK[сf/HBRgxPđ_C>ImnC}ek}C1XRn*kh(ց}CLfk R$ TOi:{Fά&tV'1Ҕkaz v_ v-r9buNON-VdSѺ{9̡m̃4 hGqbf2/|v'NGS= =+2!9-/dȞM&(]3B\PȭS eUprФ{xQ+O嬚]FaFxP ❶ו0:k7} ?o։]`TU]rp]V u#\\Դ^MRjM"O8k [eѺq넧'7էs!ވ_:mvԹZ7hGEC+.+=ڧ DAG4BC^Eô\5f˝U0+Hel7U tͥWagI s-Hhud~x)Иփ FYLq67uYJ } TNZmQVa?ػ$b@)/ɔ>4{r"^Zȅ{7X~,YSJ>ЅkY;-s6&)/ΛWʣ{@yJؐaDm+\\j'3 ޞ=mI1>޶}^y( YXJ*GL>oh)8n|QҍJ0 :%?DcxXV ZKԶzLz hU|R%'G/qm8vsFs 9EPdkm {%_h)b\,Ayr 7Ь3kDɿfGg=A&/ʅ@!6THxOd #p&)hʟ#A.'M5fc5HDZI(T*%M&[Lu:a4LoQ1L2l2P#^7Б l/,W>~ފqWؚTy+Eq\d%RRi?xŋ}qjYeKO`x LxsZr"5a-kk,rw\)%VT]raȰP.br) EEuK|g!'#P?}D|-l'4gLX7_$b..gMV^J1kSl$w! S/{P:GnMILcQں(^5g]{~3֊<) D=e`7?xa)S33<2lЕtZ,o")_ZkS뫆iT>KjԊZ|O<+]uX +Ge>A-テ4㕘T9n"?gk@#/+Wϩpx܆ 29ɌF70py&|` ,PBk,akD=})~)F:ŶsB-M~JDSb 74g'ߠ"&-oIMǗQ"-~[UTYqyy@>X3w,FP_·.-xMdXfik\'=tXf(NfvKH;"!ٕci$`Ks+d\ql|XQB a uݐfXgt~8g[D;]~AB*F5=-e3 4Qb_vђzIBHnJ*J1YɬZv֓t7Y貘z۫ϣFm,Uw70F=0#R۸RP՛jEq{}`xkQ} q`3C7֦W}"hpFea3 #YyZ]pA6;o QKE|R}>,m 7īEWw5BP"H }!l 7U f bgFOsctIu.$n[m;'y5Y`FGA-EpKxAWSQ'DTz*֚<vM] uo qg};T/Jk_9w\+7k7m};tNJhz[+h04szH1XƌLM᫃1W \0H \9Z>:Q7eYn6sړPaY_ *+VT~ӕI [#5z!W(8 U%چӔIbxMA0sTwF@:!CG(\_ڹ6;,-K#zzzSx@ϨU0$@zCKp,9 $'B#x*jojv3XSmeXXNT_!{1;zs`t}BP΃fgeA=|gf@3`jR ~ISܷXRzиhSFa+RM1'n࿕d@DJa-T |4M=кѸEgЛ/6h|Ք&aBi Np~ʬ*-:1Pq|l=RLISb3r@~0W)Q1r {Sh4Z!ʆZ@ag*pvitȅ EO5OJGڦp+BAZ+e{5īKxlLL$.*AME@W//q%kAktO3G$ln+pq=@5W _xN .G|+Ѩ}Mt.67l$BN4;]qn?$>3Bcd9<#]?2J͊e~gv8QK%VIcS!ʪ^H9e-usD}ch^wT[9]nk60x+O-")1k\<t_C;~>%e&k%\~T:GLl~1MQ~A & iCgO-cq7e Cb}B[6i= SK7^*F6YW].sT?}K+~*^y52!o_'G,w;M4ݨFOQddHv sI#, _9|>񈧭=w;^E 7@Sb UR/#:ei@h(=.2Qm{]ӫ",P )h_N3jܿz VXHMѩ^w%#KC(yv `ZvUNiO -۴RI 2Ɩ,rVc[* i:>f]>ӚQGtIsG[WnI2sv{ Vm&XdC;*琌qrzؗUMj3\~FU1 2<JԿ^qSv:ց?음WK>xKt fwČt}yL̬5>:ly4%ˣչ됓 W-9~-~(sfd= UAuI(' c]K?\1{ ~FZ,2=ƪ-NXqNs5*ynIq/Dp>4df+f uOb~kp-h|<|) : H oùOR *'(+R}>7E3'i,>qoh- ߔ+0rm h!>q6J}B~(0jZ&]mqyѮh'yA kX4k~m B|{WƅErbP6@ͼI6 jޡd.YQ `N;~{IKݳǢZUK}=/*rifښA:>4d#q[+($!PmnP W5I=αVCÀs QB],IfՊ|π>H-,~D (cqDbl4lj>&)&ȧlo!+dU"ge ^IClvw{ҒE!К=ɸD<L" ɽ}M-zj~ĤTHJ7%:7I&Ub^-,wRd,n :i97ΙWfwWw}=1}䰺D(9x唸Kx n"AU/sRH2%~%"`ً⮶PA_'>pR¾ $4dC0T#jNh'<:&g2PBBˆ\%:1xti]2W\B/&b͈! X"tgeK&j+yכEI䖘m Q j蟫3B|gKx`,R~n2]l', '%6$ƝxjK@L2CJ]Di1# wnrwo$} 0h[DT ^_G.n8 |LǎWOqC@C#vBV .E2դ #I`_M)|:/षN[ "A:Ӌ|--6H`."dÿ k<wjVZ92;Ast*>%i: n1 N횅.,3xz, u3% ||}~B1?"{&aY-K[zƳ:,#u>uv*~>(W9d= dd Qc4H–C3?dX-L8DDO:TZ@:-beBX2ס {20[]m]IjkE h'˸5P,$iiN|+ :sGO Sp:=Jg\%q[])ސ%5{teA ͗l¼=2Q0'#$aIb¸"m8²i+TrIλ𞸒긲(o`9ЇCs2cU6y'XQaYc_F|L,{ٰݞWLSFhE#{q]A[٤(VG뭤`b'w,dۃFpIh7bݠla$֩ s~27O4,B؈`՛B pd!Q=<;4'xb NO9b|ȼS8@_nx qn}r&;#8#yX_K@U,դ{.F*>߄+6<5b^3&xsewx8!R4R\Amk - WpEuW,9jm-=M-KfԀ0LcWіpXDh][S稘v,䆝kدdߌ*Da]<B"̤DiaHӾ{4 CQZ+^GL y Nx/R|IIe-{P$đ{? /%\!vqm>U0:^vPN JW]wi倠~@Z3,V fF9cDʤXa8b r[^G<ѱ4A8dC"*pYҷRAU,޿e#pi<;B]ןU!.d` 7+D9IURRM*E8 OKB+A/y :5^!)jX֔pGKI(!<)w%RJbGzaShBKݍlŹ#[G*"=c[Cs'*{%04˶ _1JT 脛B5K)Pf ^:](Aj7rMz^}̬)34a4l!H6pJN2s5z)צ=_M;J3q9 $z\E#~!G|g! /_Ԁtf!+1C=~ c +AGẌ́=i:S1@$`[掗QՎ%r"VrJ2 `[&^RE5|hvBa;:d@z3vhh[9@xbI뺑[l8con; qr Mtj%e gWЛnb ]UZ=QcOw[++ejti^x9z%2[Bg>$AesTL8rWnkas#t/1Oc$mv[S[V(wբH:H4-D a,'`{"}]kUЇVY%B6H:'ĩ4EXG8ySbT3k׃T6͎Fw+bBZs#[I@&uxxwdM jc?NTD^:jӄ~<ՊyAk ﷴX7S #b} A(3x!xxPlLk& 9E@hS{ꩱщ yWEnFX(su6-Z8m|?\!t ((d{,w}MNb |SUqܫ'];ֿ՚Ҟ/1޼\ q:X|s/ePCV8[o`p &7M!GW?9ѿJ9n' l8fȯ*,۱_϶Dt荘]5(;N(ήnMJI߆Kb Sha␱e-g#pFB怒ZjN@]mH8&ewBn(p/UՉCSxMƸiI%…_ _K au|oJ)JWa#- ^cPx^e=BF[2CLFyՏ{l[Vw>[rn3cfB̹hn &A Xa`"vg3JmDکיV!j) !+ZF/͝TH^z ~#&+޵*^:W>S.(F:zJ([9)uZszZz?}6Ax Hcެ8|j2\9h"Iʰ$T+C$u-$Y(7r_Sw$Jaw#WGŇEc8ծw쁳e.$5[7N*BD-4u~Ԇ zo?-1G%~R aC#5z·d&}B,){Gq>HIUWD zC9Kqt!#BjD-KRuCM=8_@YoJiy"z\=5:na%gѲYKڬ!1g 3uATj~!&f7$f·ky;p&@^a]i-*0uFL'*SZfMBWj?e{z!L9T[^ial}(2a]AX&`KE ROMY7LqCضB =wܒ >&݊nzG 1R|+eW,ϿouBc uH#tZu,i@|Omf'. N<]~T <=.B; oC f) û?:u0ḚR`a+8v ƍÆl,97U@Uv>"k|?TCO=_.\ OVtTEOAkY`2X?5x])]w;5~;P>@f ̬'|_\qg5KB[C 'w>pC_ҺXJ $sݘ MZ_ L1Z"9DIG`<{e7E& Itޡ; wrU8BsK+e2 ?JڨXbVMF8@E `dJ Ԣ9̒[?ڽr2NlXi1=aȆsӐf|` R`*pNj3FY4ؖ(Mt?[8.](XSjeԖf S0YS6*IGgHpF*Jڤgn'/Lv@Q{y6<)!Q@̨Oi̲0̈́`ccafLfq6.A&qNp)w6RwhͰCÊ?2q*9kԢu]Q_N{A*6^U*$}! /v%3eGr﫬(A:2-ޙVvU=Rg Dt>BDr'D퉸PSKBiX%ܚ(kː|9(ċ̤BIEebw?Jd );, K'& aB .ڬ?欵t+*/gOx@]'G^C۠+r`6RHE@=yCylx zL!j4>$~Pɬ05.c$5UU\ԯ!Y.E&Y:ylMg⻝u[/UiF0վ$(4#\*$Qܭ/P  SPߙ2H3z7hR@PXY'!U(ӶPvֶfNL`C $K[ jPi=>(#!8aceƥ[JŃ/UkyNB*]3%$.Z?Bq6׎WۀnxiIx~nxqQD0'zpU R&7ϋp%KlW/frYG!\m:bQܧ;_ʬQ"J9\chBl=+F}juaGخc4 N*,vv29o6ttD` .TCRW ў [C)1]V 5Ӣ뱢_"q|+N r 'J_ڽMV'F~SFtHw_ l@aI[9 j)[jg&l8׾f5]Io4mb5dW@d4-"~-IpSm:;?h鞙/_Ֆ^26(MH NOJ`PdEHaEAۛ\!VA/i [qM?Qy<]P$~ws zOY!u퇑/p޴?8& < iNZL]J%~ m$GV ƫv}wz U(уZKMMrz~"mG8._MQz'sE9ò%'CؤUP;'C \ ЗMJ쭇;/&n(gy2`^|އJv6tjCX镇3 0Nb_q`/ HaJ|DB06Yjח9U%iލn[ccŁvl/_=aXW 3c!\jgStLU'Qў9h8)`t9:7CL gg]5zOQ8R9m-vʝ "O$Gr{RSpl [ RѾFXQJ+Ĵqi9B=D\UKcSR8mOkx Y*@>܉ԎgT2;ϼAE DVf$Vuh {U];W. wGr,T.O'!bkg_oMRo3 НDO52&1ςHF,;$?wgf7JwZyDCtr8ZNmYlE;w:x1s5X^jŦ Pt$BKՄ3-I>u {~jh`v$nu`!c@&XiSjt%! AzN0vF.-ʆ,Ɉ!+ Q&NgBrxֶ)"8OIUyV=~fu~Vb/}hP^ZoVȇO9@6^u)r X _k/j`UCB@FA)uk)`Egiaf!fψ-YϦ=n>0\oھe{oAGtOȰS+TdBCm[>[8,bZyb, T%g #u1Vnѕ6iUbz}d ŏi~[50A8Y.vcP)irvnS'{?# \]RV]oХUt' |xcɶ$0[u7"L6#tͩ} X7A#NEi+21~eaDVHI;v 6^[UA93qYJ9.韦 ۸5]9i-z*mzrd2!;C[f5 ABײ6a}vXԥlkFny{懍\MuT=:?؄-<Іl7.R\`eSPyw:;QiB6߇1ZqO.0hl (\$ r``"uӐk=V]=a P; l+M)N߅afK)_h|@`iRQR 0S5tzz*B\s=q(6BOUGaٙr륄/l>X Iv .NX}ħQU- 'Jt5m:O$*M#dZ2d`<9[0)FRÕQL~ABtYdd+ )5l{5Ɍ#TA-MzFu LLwl (8JC3O!+bDy6e3isdp.͛8K`}+#opċ.)DMԣ/O*ȑ3GMz|bn*=٥iԅ`rD Pk>3+Q9|Q hڋ| xgCc,1,$ْZ{&=_AyH87Կ֛ LO$V_rofZ鎑YHyEj IQ ˻*AUj&jt|\e:O] -T L!BN 3;; ϭ(k`UdzQG}D&ްcS } xaEo& }`eb Xiy/hZ]XK琸 F?/EY[VQ1>*ȼ7!Ek۠O92cD0\M$YJf}ax|! em8@& :IϰL9?pL ZX0+2t )2 wP{,mb7Z(_K-A+y8tVղCnf~G6{j_LDLpWMAA3+$}<*RހFF,>*Oi"II`I@8D1/?$kr6h24iA%Z^_&Sӻ_SIpN\-+Թk͛bs?!}נ/1+]4Uݧ+C_o.5$rv|mԕwUSo"hj:KyL̞I;:jC(:_{ɷ $FŋSUcCNK@m anU0ܨh Yyb O!B}=#5 &҈)pTۂYv렜Wj].u]]jgP V$}$ى=OYLg /KIp"Sݫqo  s"S ^B4O 8 z,#<~r"1] bM NrҊ,?tntd \5Tra26"= (y T9Yr`O1O\̶-_P^A~eoqnp[:OcƇQ?)Lkћ9S%̈׵Iᥐ/߶A5*aQxB>oqm P<=v"=p9$>L)Z=ZٛdgЮlhJM#!Ȋio=]ћtSA[┭K$ё`V5g8@]aBL a)kJ{ϧUtQ&t4Z6!&4|;>Fvl7IK9j!C?Nq,-9> 2[ﴸJ bVjY+u85cMOgjzp?D`ZbUԊ^Z9DFN=Axmkhk-~})q $ dP6~ڙ7K[j*!)S@lW〉g2rXX5m:҆%?.m0جXVGӂ*Tmd!8"`Me|-@ohpJr+QeR&Dse8{TћR4>񅐮N6eLRV>s-s}]@jr}qPlc I|]h̪+daөë_̄_Ǘa,2L/ < 2|jzѐ(q \3 `D;wyBvy()ՀݡgHIו<4FzMU'G_@/;inHQo"[AOz+5K-hL5|m y&-E5-rpe S^OY;hd39^V?!~d L$!uM<pыwjpZm ѯʝ& +](iVv7xYdg\p#D+/˔"+ At]-Z򳧝\Tc8_E@CӜ:,Y[i["sFD1/dYYQ>y!o\^&9d'zHCͷv›BN3]C^Y!/P[HqRLLFb< Kɴ. *X3X'a3AH{W_Kb w>V%c:(]A/ޙːTޟ,x#69Fl*Ⱥ#tnF/-k8!ӞĆ&U#MZY: D_ߏ V 2z8d a O|cNwPiE(%$pI&#cQ6J0ScJZXJ j2ҷY"+ONCe]#AsV3GϓfF]&o\4EVԧ!<~4yrd-bFV+ +],0s:[J%L>rP;7GuWbiB6[M9vq$(̑ Mnƍrc&8>{$eV_BI߈{Vz fX@喊x$cEh^Ă1:2ζ?o Qx驧6yT1l;x{+9{4Y>Ed!ķkr1OԷiY+A"h}JUL 8R+hٌ/Yae&ֳhB%i^\G bk<ޭد;MT3l[&PfXs%dhP?K݈k-k-QG&ڪfHd㛹Pb#J‡#[L26RNVS Fo-پ84>t$Y 1nZtO:}W*U)`&5_DvtT -c9w>hAzG|VÈ2 @?.% > r`ju,v"j;yO3ߊSމ2R?<<1=u2D]ƶb>rVDHbu~y(Z5j_ᄿ3x.YTz*4D}q#3pLFuO!B/"ҿc<'Hppj)6$B2bTzZ@m[XXQw^T}~CC9Z('9Rb mQM_Q5lZskZ@Ul QER{g8 'Q]Tq ).}+Fo%l|0U==ןtYd@xܱg&FaxX*M%iռx:5۾qo9n買Baf*41bjv"фєgCZ@֥FŲ@\Cf;H$6:c;`j:X+NoK/&~r\献&\~q9(/'c%%2I(K㚚j6`;|xbio(J~o*[m}ed** }r [~='i5n*kfZȆ1'֔lYGĚconJIXGGfVʁ/DO]+:vЇɁKo sjā6S]lVPSɊ5I{M LRԭDp+o ~$@+%|`עsi=3;deOŅ uB)`Gal](n L"6^CsR8kz&`з"Y~O bsM#w(cKIu6iARCqWi(n}B$]} b=\Aʭ"U3tGs-b| (F:LBă,I@KC k W KJ]\`5' Z58a pkx(7Dkmڳ T݄奕Dx[p[*זfɷ&2/-j,<0Dըsxcd$´;hgEkb_C`24Ze*.RU޹f"W 8= B =wܲj#;I F<7Lх\\W6\nd'QO㭲PD<5c%Q^x)+i^bNffi]GiIJt.[I\8mljKq8n@c*; 2y%-4 UA0Ք?+#0R0ؾ(2mvs +@3ݐtH!(`U$Ѓt*BD]7m5uwrӎz[X|}gsd pS{@}N;i'RܱAfPmhon_>?>p/4|5 'ZRQ@ IŃ_Mq((CƏ<&sLZx$@4{MڗH>AGuHu鿴LEB6 ,Y"'R/ iP^a>6ԉ.;F.2rG_ #Z-Ȍ:Q+X&5vYo3!5>f. /5mE?7 A ȾUۇ;3IPףKշ4S V_@ *OW<̫gFz^܅ .??vxTxމ/P!T7H(A AH2N'](mp.N>P xtG Y^jH<38Py^|gATU4E=Ld'o`Q`>xZQTJKL]G2Ԝ@iNG^N߂l_M;3\Yĝ$rssN d=v1n:Z#V4%o14\lNA@ bfA8eb^җ$#$hm.nzCfA涾]4ƲYfaƓM,:}pԨ"?|a'1u9W?uA8: yHVށ_V1 !F4&;XjM{K pVU`$eU2=%#߾RĂ ;Ğp18ֲC]OQu%5HMg*֗jnoDFq = 'fyqHs@iN({ enMv0bK> l<?_ JMlk-/&{f C^aTC=cwi\Lkn!,&x%2M6c"1cF3uf<. }QRlXOkr*\\VFghw'|11BhD+rE,Gآ ^Hz׉ܥdNe$%0S8 n?5 F} $ᲭD*?ګvDJeȄPy "K/ )~@EE$ͭCU|BQ@K!{ge[L^Qx $a7 9\*AyiU{0gb]^6N<{ JgTK'Uy?ӹDYNM"GDmq&je> 6 (eG'V3D@b[o+ƫ&ᅦa3"s.iZ֝f07V$DQq s`9A{m|(j=zT {ؘGD_f%}^1#?rD`a'b{h RYX8XS.A4iEK.o12z]eE;%z,ʚԵHG)aOR/|Dn3D%~_ZPW'h$T)򜪧P#nȀC4UtG}2.e>RZbQoWex=lKn\W;apA${m| w2`v0q2DOqO`WB}beHzoP-QrjTѐHq!4{v,5pHegm\8=ezy޽ i-ȣ#1Χc0Qޏ[FwQrfDp-s!ޗ0̼Akk:r"L̈ɣvnXkz#[)S7V6fm=*w"ڶb1oNˑ}ȪѺf.!D/-V9[I@yϟ#x҇E[dL~NM2S@X7M}8^Xv2;îh/Ў{]ð (OWTvk_{qCl$CR1 >{/Nt8P<@W ldk.Eh[wSwme9 FU3m;3,>]ʍr,usʴd6LmfVtT Ԙ(т CP{FQρɉ%~1p 8DbB;…pPi!+/\K˺0ҿΨYV[5R{PvG6AP|đ098%s }fhI̢`1v['wf' IidYxPBKIw,c0# S`xLjIm檼vV )lf&_I$+ٱA*)ė*s1yӏ*ӰTu,RIb*EtÍziW\4Aia-l0'ؠdsE-He2#AɁ*|"uJN./ulZdCZK+̩V)t`)J'N>x5a܇W9PZ2|+>F;_(a;vY3 =/9є\K'y}CZ%iNQe07QbrÑ dv<4p}#U]z2P꽶)ݡrXԮ$ǥ&s9;pwX0"l3k}ʍ(!j>6BƵvy"4bړ]x+p -LFv\!!u9h|"hڂz|Ğ#UUDes4`_tEfT0ҼdAVKx7P\)(SrDXss\Q'|t9\^[(GШP qLnkMUQ RKh #EB<&Cx)6wi6쵁͇I Yh ?$?Lۼz`?'Li5wP.QSGN0gty 7Iٷ嚋Z=, %h՜cr0G1_\ivl\d-]uuw\[L\o xcyO?닢Xr9tt-J O(xB 'Ay k쀝@B8r" c@zBU3x.  "@SK[+gRWl_?8H|\p.AK]7%لڔ ٬C^lIdSݐ¤Skntl:>:jPW!#ǫ瞒[c41#aYC#=@#EȅʍD4TΌ1ע:$wޔkְaFUYx A{|CȁmKxH/ZpZ}˝G' of,A1HȀc,9]Y¹D k[DnO! VuTV"٦ՄveքtB #+m^խ!7%Ƚ9xTѲҁoCn!Xag̽w_VY M2F}^+G^`$N.]ص*RVu{K׀ It daXdQa*hI]ϙ8N0&`׍ Vk0SN]ygz!\dam@}Fr/ HN giR Ns.(]Lq}dI_:3f#II~sK3!eLL*]6UwDӸyfJLC,)\kleS 3&tI tJv- UPG׈섨Cu#J #6DW5Djc\4b-qY|pG Tt]pk|-2꺙KTCjqi#۬L"bib :;}ꐻo`5lkj7y;i'87FU&w6h]XC@shʲ$NFYՊF]uOs6Kv2mu7x3|b̗.-cd*CgHҳ:Hiŀ="+\N {_AOad_4{S-6AEwk".blzn*Gy`PhrrnaHpk8ʽI *#ՕKV; ӠNF9o減Kb$OE K-7Kp&} &Vye4FFxPHWEdkKwkGt39>7dmI# JorEw׺Fp*G blgmf~1=?_Z <`1R,Fi2!WIZ0}WPxcw(Z/= 5Ao$Pxnݮu8xjRìO|ogI:i\2YC^X*Q7'$Ѝ9e Jc2 mWbluCQ4_7FYDQ&xWk\<=QN$qEH{ ߻H0NA W!'_TbSVoKc# k!\if2$w}JS,7bk*w w>1kk?);a*C 6+W^'O-)sU)/yB{'g]oB X2`GrG[any 9kX>"}[;Q+ǵs鄑Pr(jukҫDeB065L{NE (6 !7YVh}$xyzJ2zT` 6֜b*:f[\P46}ywW",şt_%9'թZ㺾8TV'h&:=76啙>a:)ğ ֘RN:fwx 7ٞg8KDYTk'!}cf6/k#qov:Bbhϻ59_oX0f>㭭86낎7>: _eR u[͍r8?V=7Þv9CE4Y2ܩN~₀R^SgCOT0V SޫVU8DU^[R 9+_2o2KA-DiAlN8/k햬l`PQ%$;QSEs  r:p)C-Mq4C}[sӯ#DOzZ+M\jm1 q^`/Gã&L+0ɝLti]m<~FjHҩe\ +I$uX2?*.mCMHQ\eg$#v&[e~& u:--`,r^}("'FKM}H~x"6!X#iagUH.?d$mNQ޴ }ϯp88{= 8Y=8e:z穿A(Nm}@~by(p5e[օ]?ٻoݓ˺|V\gS@ȕIµyۋI6EzRF1T }hG6}`rtxA-L9Rߗ֯Ԥb9p|1x#)sDǍm pCPjގxU[ĔY;䗯W뛈/56=`jaPxL:`(8b:}eѧY>50"޴9T^3:[BV)% ^HǕ-vcMPq{*3Ԍ!/#o+'1m jli͕K2ەzC0&qpP g<])DfH91 NC'o=hqV #I&]/<<%Lx0ԀgV'.#$CaQIŎy^L\< cH^"xEwQvEFUMOϝ0{[OϮiAȷ$wFi㩊sOMEb"e=qO%7c0co7xɀ&D(w V9izNڤl:7 ;g7]Ad#4p;nHCJ͘s/ Fi?śslG2 1;%rrjF-Ay146N}+&g881S8Vۏ ut1V_WLk05s˂YCJR&2 LcK֍!IBAW|g7ӗNI])jJ!KwhJɪ;x2PCzBmcz^׵! ڥƗx]ou"/*{ux ce& ,c,פmv/{8i~K"S8\mܱkF3bTr(8b<>L'O)L-UVw&qq4-w,F KoxɄ]L?9)|ՈWz:-4ن8ț#Mr-8?DSR) (qONXź\t= VP8b T:Ӯ h CP?m=Z 9 S5Sʓ/_Zz3kյDlB>^ jI 6>G,/FNΥZ&=7gfaR 'p%Ц>^J0ߎw=T)ECk#fa3XfHtQ΅ {kV?Yn@ RJXEITT.U5'z; $ĚwjPukW2If!}fZ=vMLKԑD(zz ѿL؇DSmb-_N`)r``7&'Ja*?ҳKM%8+(ǐ9cG9.xHqT`t] ՅeP_?r N&Iek' TrLeX|~?^ ֪ $C[Gkb%h IYa^;iAmsa4֦\ (UKb#I"&Wu4as]9҉ROo$tg~iND3lgOVWXshNXwЈN+"|lLA!󕕁bg.P}3mG6;h4`寳Lӕ&sF>*׀3x? Ӝ}ki߃%6K~vE8;Fs\L.QQ F,S_z]JZ Ḳ!t/c|_@A%apYp_4,!̈́ǷZ"xyҒi8UOWf5.fOU-mEVgb吉aPqoU"?[PUKR$gI="RV,2:V,-f07z2D\ep€#pfwGE`K.ö? MJS/HHa A\%0yWl {S9vvp_-K ݡfP" ܁ A Mr)|sgUq@2|d*:fyQ:?**Ȥ#hc"7K6Izw&iǺ jzݓ܈k2`M~%sC+!0Qs81am((֕yyVeڂT(/"krf.!_s\6,SeJ0p̠^,۝QqE sudk!!Da+Oyzn67y[^WPIJVjl{n69^?UTqhFHc>}U:2@8r{پXތ7bu("A᪔WG9`pn/[}Cボ$$h[kaշtx\ںo3|ݞ ״9jR^P*봨lڡ2Bgd$%'~/:GȨWzY@dɅ2vwSvh @# (1.!CF iȽiLN}nMyp 4*'F2ZYۥcs 1VuOhS.z4tQ_AURyUIxP'{=LjAtڹUf#v A-%n Jd6A1$P*BL_+FFp@/ҙT78`ya_"s-p7 5jX .+7\"MnhB/.2A6W"ATf|nk.|jA)#|Ŝ$Uc0up z$(rdesN[.l$2zuLX%KP87/O<+=FT;1A xIM䦫sBPq܄"emoя(ޘuetz^"l%߫+(.tsOoS$TTfͻ $_]. ĠGѴpKZ'D X!<9d1=6^(iM}q=J=n3/h1 d+c8^WKu3ɍBx"Q[Zxj2ދWC!Eeda=3+g-q(1dhxRgXqpx;lQdhߦ?$8Tr1b?^_(ݭ{4QP xĩ֭=_SGWTAKEf*>$XB FPgY0>0+OY_8 \+ [@oz]ד{lg5[@ 1^51wIQ7A $-St2ѳ$&ȇ VіGCh'7n' q*8bu4OAs~_ \x WZ8AC]pkKj ϗəzFv0]_MYRsHN6? 㙅 N V_n~VeO??I4pWPlZMmҠ}\Xy4íKUS&r!! ˀ_({k{ܯ\bw"^r*ϔǠ&Q...MGsӂ\V_⋃X!{ܬg,@4{{g-%G bƘ+~DtNp#1t:3\HL'ڱ~) VՊ2bS ֎}vb7):66;e3D"aC BF9x8@%eO>K樊S+c{@>q~OL; \ A1g A1У%߀"׹ KW;daiN bn biap SGu kGUxf줍PrX_a0|e,'εhgHzDt{TR䦇4>u}xa5nn@C$Ya{AgTvU(diO۹Btmhp)w)ܹ*MsFBjpHHQ܀W3,RĀ)} ʺ잕){%LI˄ EooW8= y,GS toh= Y3T)˟7M%/ݳK\g2F4w%ɁZX 鬌I^ZR }旾ssNMw>w5Ck%VTSmQ$Y-`1x?lS]V_mxh+*F}1iB>зnnv8?Oi9" e !jA*Bɒ;,<:Icf eaHmKBJ~ucC[!43{ ,u0gOC%Yrc[J}W2cBL~35W.̉={y]On\O;׶/!ܦKOUx_ĤM41TH䝇a9^zQcK蒥[f+d=.@^ШIҠrɼWd 5U|¡.ɉhn@QQ{I<,V@dǪTN|G>bo;BfR 4$Lv]˅_"IUvVCk=c_rfN|ͨ,T!B@E ͋0=V8_|~qzaUo(Ob0W_nM퇳"ƅ&mljиLC i=Z!>(Y`eu ~!m G&Y˷2RX@M{DmEЕm?܄t[:@*2{ZJך{7LǑ'.^sMNpo遼HV"ԈzS gΪ*¢F;'ZeEUi=*(@@Wa V*߁AaB'W}K7,t "AnfYݥw,7#&܂7kP$)e {hIa ʊ& hG9J>G0lъƣQFٜzv>@l~U7N!5UyjwG-oѦ#a+ %{Q΂~ iɇM\ :P@#ĭ%Ҥ™Σ̀D&B+ b]^|}{=*r;a?u_} 텺wbyPp|A~b%Һ(MB;vwCr&z4W-3yXrPG(Zp+ǁфrY+7+<3_~lv=\,'n*n/LF{3MBIH/kao5RkLI gLVMsiD>x r~x$Y"<ǜ#FϡLd榶XgH?FLYPL5ϼg;4BD!a.nʥH;;I7p 6QǽXh(/^rYxfm^Pza T q68o, ܠDŽ-FߢdGA=7 /H60_x @#|^hZQK-A~r{5t,)4 [3'r߹p0w]-4cƑу:^qObSX5tV*A,$⢍.'69-0ˏC:Dv0;໗,ㇷ;X B8&&-0oDj慿Fy0Zi.ycR)nn; >cOX@5Ѷ^ku )'u0 #G,S:旱S#`gZ4Gl">]>'g_Ud{`r12R™ LRpʦ(q&? )TiDqMB fXPjAc{WjQ|MTuT}MOz$ Fa<1|iА7TȕMo<%O ~!BNS|yEeqQ'(s|1U Ppkl}VuqX?W~HMp}J#aF~,o8m(A2_#2$"I0pLYZzRqT4(ch[k:,;m9Y;цv&4Y@W`1̌eح#0 \GgDpyf"`z s_|fX]7F)\>V !9_9Y[ q)%FNdIUH*veCpķ:XJ_ Շ3!o7]3g@@"i%ܩ3F?Dttm(Nl|IzS$~:H-8 >^Y ,EipZ1l-^ؔٝVjEez QevxG^H(.k.?_OV`]=Ruc>P岟);]KOB65O0!>OOGFR0iRt(<pwU~x4nOWA"iH4  B"ÇD+pʞ< 0id!}0G>/)(ra,#rH%"\WЌC ]rLbf&b]y%[8{u=Amc7̃e=k(O fʟ,+G68EBֹH'HWÄCB->{RstO(H(dߞw=ܭh۵ʯ8UG`ÚGZ9;5 9ض~A6#̀/pG$ӿjrom !w2ea7/.X@E+WswJqɯe 2CA v?j^IQ٢&\Hfޅ+F]?$G\F"67lxOH1W@"+ CSP)+ۇC4UXP,nB?emO <8`8Y3 @?p/[+nn&@jf9QM'j=lyEuGU--o\@/#tTQqWGۏP'–؛ݛ.pɜH ~G~ {"gjaGw^hbiAid*츌 +Dto2?}lJ\ x{&P'<]kYsbK=ޗ\y Kބx7#M>4GS-0eVB?8]W˟a@aX.=w6-QZ^& J=3^됙g:Y[kޛ#?֨w0˗.E2e(g$BL9d CO#~cN7tZO˱)!:`8'I7~%^nL[#j2zĄ\gIlNw I,G:̓gCk{ޖЩXCZWl\>S+[, hBAa}aH>b 0lRPWR"{$h@J.¸{p~#)2o  "/o FY6L6ˤXHFuWtԅZ.eOjFGrҐƭ:Em| < oO#^' GvI& 1mf/tb.0 =UiѐUt2\ֱEʱmoNkxEg4[$G G%lVHOS'x$H=&?NܝkvecHԆX% / _@w,-˾oSβT+UI ޾čYj& }VW+n4w3* G(IMp-se+Gc2p~~&#deن Sa@hS1@i$/XYW!QܰN!d&sN Df+ ^t*=j:'¢.i93FŒ7M c+m>_?t@Yn#v+$HˑPb#_SԇpR;\Ğ;K{OwJtk3mȮѰG 3 7^׍ScGXN"9:zcL5e:DlvoAX_pXa)4XYܴS OĂ,Zbp.l ܏dM a%OWUwNIA"t"fa<@ 8HV ?wp umrpa*A6#B\qiKr}ȽB{FhK&$ w8˘̋`Q՞'&:&yp#n%{ Ud|(cu$C)~^cBޛs<"[QP$ީ+s4Dot. }i~ %唎#IXa*"ùז8u潀."B}h-yxa@օ_Im {xpF}]ʒ/2A7PQ {hZ=+:@q`Me<_6Rc@/h LTX9mj[;O:u2YeI9ל+7^иbޙ3 {, ƻh$.)u}-nf2C߂h|/ϒ6wДe$#;hZn{&׆P!KUqSʆ@O3kh~#UO # wܹ I1xOI8%lQRUE3C&]"YV#rKAy1a `!L3?Wi:ഒGSwBTKO}?(iakŃNC'b| ؽP^ZL#EյyE.Avcctӌh;:.Oݖ]فJ&"cgEϊD9=@'Y8U9Ȇ1+xh 7xv[>`튳BnrRk<_ Am$u Y/FHBRYE1/3 ]@5ѕui֪}!m txR2InUNjvbnPϬcf 2,w;Qnw}_oN;H"e;\r.z[@B R1dgYhzQ #lQF-̢sG(1C/it^wơEQq貝Fh0 1ZۢӘJQ涶x/Vn9\û'0q-&a](՗A+VϖCw_/1 ǼQu/h(D㱐b lP =eFԅ"|y0-p- t&HPaB$]:a=q!.W։ b='-Ȓ`H292l:i´A(GK=Յh{IGma 'HַD{x[Ynӱ202=l Lj۹G=֨2{u7W_6O!PUE)d]1+ KT׀d ezȎ=q`mmZzT OҏliX=*BfأǤ><_\ ,,;оggN~վnvL~Ñ!x[2r !\oe]M S qTi Ra:V9D$v5GXo%\@I^9^#e`:Qj즗DKEǪt +W Ȭ%uj*J41D%x<}l "yܨw)|9ZUO߯f mvz H:Im 3vN?%8'4D+EOI_q;v0`߽b]!^ֿaS2ldl .WW|DF(Ѻ4zog5dm`-M?<)^bJbJ*ɓ;if7FH0*-[ kJuq`qkGsVsSIQSvg,ZE,WkϠ$P/P) !qM}?;$9|mauWgrntJ~`Io,Ȧ ,. .kY=82h% =E};?7;«od~pO^mhv5(!yg&ѹ^sz4V 0f |x:м]Ok'o*3h)Q Wp z~LK#IXƎRh\&롆RmFNä9؊I*4_[u$#qdž"zDz:H4D|Ve[Kk×,QCt:/npliZehyQs.ь;g76unLBbڡmuz h˕:S92߫eEf nN C ~y͡l[sQ yOWC!I5=𢰣.MqY fbiX4]DR_='([!c6o q4}F.kQ_7%E dnrU k{0pͼ!wl}vXr=?]xTӡoҍ 0""%Ok5Wܢ0LnIDyK-v׉P1Rd‘O(/`n.,4v?^1 !)1Lkç T̉y0pR183s)H =&nķ>'#z[7!bh]st$0AXQgySh]h:]ofZuh )5N_6s]0rξ U@luF4YKO͏IMK\WLAP r ?,俾ոs~}<% +ƀ|9pb2d3ǡƒkFEOP>{)]bsv8ܷjZfjOg= W_gOئksIykEK0|It&E"Cߔ1ڧ; Dze9M뭮[3~~L6~TW 1f8 [ed'`~ JDCQ [fX|2}J& j|C0rnӚIg >n{5:pT% <еƭG<\xP<<ۜ W;n)1_)9pĴ=uJI3pl(7!}ɲ%%Y35?w+9r5KH**qAO "?Sȋ;V.Z3M..&{[\>g"=#N6!7Ih~5qIqNpyiTԈ%;:arCfPI6|fh!O )8T 7dMXkv\g)mb:v-uN%ԎafD]X ܩ"pxuK"^W:Mˊ̀[ՇEmLfx5OPIT^bd-94G+Gnw/aB+MO 36Q!J%Gr_>rEp>F&6-%] F_Ny 7^ɝДgk/%2MPNe3MW=id?|>i_>pNϦ([DepYO%:?-%)}k|ܵN&MǏ2\V_~C{ZUrIs":sA:OSق "%˺tYZjڂק>/BWj,O!&D@P1l+XX)}{{u7†Ac.e\Uy-~yE VrUhQF?>3.hvm_ td|g83G,vح7"aUU9dÜ=iy2Fgj#w6]!}Fv׶\ tk^7^sVן 0X0U\΍xXZ͟j4__ewC2UxXW%XRz^i!@S~ĊnŌ;ټ=LjB \@W y_ՙx& 'A-@܆^hf7N㣒pO[Q3L.4Rj:|d! 678 3\y %.7*7`@Bk)cho먟p0Eꪾ!t>d|!zQm2^{uY?g->$tSۏ7".[Hqh%|G֕Sٵ[`K,96۴K-^zO]cHõ:5$Bsz$+zںD#mhz `l~8} 0dOSJ[7.Q(Kx!JnY$4UngO% ˳yKk=ᶱS juc\Xat 0-,՗|3It\!,dP[*mxS|?{O' ̓.c9ހY\\bzgbyNDBn$ׇ\Rgf::5J7S`B g﫫2dKEv撀?e* :wtF/3[XXĵ "'HjA ʤmwC0=7DuI]y"~du*(OeנL8=4m{!-,F߽71ck^?񐾐Ijs(ͽ+2Zٟwy:eufeOJ*.9#U$SaW^JT8b^eWjCSEzcA3: ;?[,um-tG.OሬաFHl"6x<=3 (7t)y sJq!DqڧtFˇ:ƢoiG㛍^USwXHKDnjD' ̐*f=H\Db[nS3QT\aԨ=(Ե.~4c^3%t{͹N Kh8IJǥ %{&>?'Cfd%%:'E,`#C7J-%UĄjNv@N$L』/g^?FmG=Iᤛ_|<~Wd!{)!.4C496R퀰؉ :qyLh$'($^s9(dYGgkk*2^ю ,lR >{_;O+翾X.8ΧĜT1Bc ds5/t,]WGĤ @K"0X%J5ՙPOL~|Feʹ8؛"Vm M xW2c%B,IIB=JhV\2:p9qBŶr \10ۄx2,|YN /ȍEyt4c)\hHM%'T}\g 6I^Lp| B{uYEU-%1q>k5[vMoH;ƿWvN ,>_@eTBrzM*kDJD%;< bo1 +n/kŁi5gxn3]τfT*& *gcOb+۳z>{7>d#oiyRүZ& CRC[JCˉϛgX(Ej¦;$zqp+8Hm {|3'&W܀$G] #)Q6C#=p*j5^A`DeAზE >H`XP\oNj0\'bDj3{ܟa0&C3U+C JNݗ/>.Yp6<\H"+a]˖1h^ >VM~"zz6WOwճ:JM~:9dM3OE `X@aZXi^H gC6ډآ3QK9o l`*R#F͖u\3}5-YY낋o4$G' 4ɚLQE?;c  D)%1ָPI3eGL|9?x*'0.R!56b;8e7? 3Zә9`̿7̵OTNQw\~!bGg#Xxf~ʝ߲?H7ko}%i.x,LfpQߣi۳7o9x<<0%4oeW[kٸ|cM~^icˌQX *r>'\]&K~z,Ԑδؽ5R~T7zc suf8fv-NjwdQ;i'և V/=r ](J]|τ.TNH''D)3pԘ)9Vt)e#k'BDM6&tAM IM!W렶N_aOz.Y!~SNހ9kR .= !<[RrF*`p#$,e4OrsdiOTy"6=h_ݣdVKgD-&$jPiD>Sٵv& FVzw/tkՄm-L X \EQ'B*N-tK*V$xf< O2l$"*M1R\[0`$TmI>; w*hX]OQ53/&|gl'cmHEQk8uͩ[r""wF kq)&"8:J!?ݖ)\[T.TgN~&-KI fVsE2_y!:<㠎 k9*N*h x˒"g FS,N|#f`m)>d$9yGٯd늀`,'L?*)t[Wi[%˒}/b}BY4A#~}FV"9H~J)JckvlVwFUa,8A5lR&*3 &nL%ã)tyJ6 3,&>W|Yv3~a?/d4_ x  zG7q\M"\(׽B{CQ>BMOمkCliaPmWlGkՕX:L$i/5Hj_%mdCwCI Up?~%t,%+n,b^VЇP"H)+PGΪlͷr]Zҗt>2 SV5‡oPbI03>vPfa\rMgw-Oe$@Lj34e2edĿ9'eЀP6>/"n'&V"PS5^+9{2 .n=LUgqDD@я!0 D@TI 6VyR+h~WV;WxD>v'tQ&FZIqnIv !:01#'ʟ>")4Uz&&X'ì0o0U@kw!K) @D8t38X0Wlws`q~@e)S^rmwDգGjPf4Ӗ^{@/%qO r& rDe3l@b5)!I_Kϥ8r쓌N5j#%UV}m~dr 0xg"{rMY@v4<و4*Rw5 Q6XL~YN6,9/ 3saDpC9t4*!a.M.w%i+%27CNUV F;N{6k"5b6*T^5OMZsFP~Ӹ߭Ka* Eȧ4첄NCxR r *{wf!/{"(%sx#"pCxDT2ǒA(r<ƀVC}ƗAOԘ! }UkX/yJhO 7*vՈSkB!hI=ƆJ Rv8]G9#^~oi::hZڗh&^s#1ОQ?HKHmvT=uBcv/;j7{1{:Lݹak?24ZZ{!?e_*=.JБ*b!Dz,Gފf%qڏ}aC2o?Q=$!XU cR02,i$$wAJC-I0/#ΊXA!mkմy-P,E$<//Hx1/f"^V3}}aE_1{Dr-EmdkVsx*4OЏ;P "T|_j>(K,sbw yR=Ro y*gw,nָلb4I'iğk.xlc\8z[D[a-7`GcYy/!'6>/u" 6CZ"p*` -K$H#6n)aX ٧E?Te=9%՘FfPZ_]?RsaM%@0k!j&!э uqEJEio#IJ \e%DD[߹1ra%HوZ:*O{*H2>\5b819ԹN309?.εT+se ++ fMMrQY:IT(/Z?6qV<v42f}RW-2Z<^~pvvӚgiv*~C4da.ɼdD=>{S'2Kzr Ɗ.p,[hfެ$P;9*0h$&gzMO9!?1GB G\cujrߒEygG@9ZQ>J\tV3^{sVD] "ayҟF)!+ XFr6(A a)k7#muC:a acNu h4E|sSjm!!67SozJx/cH:$J`X oq3La&)$p. }ɀZE>\ЂU|qOP"נix \ M_6p:8rA2]g̤ dxVVwb_)7VbSs;]KEXQ)E\ܙ]**ݲwTN~H~d˩h]lib\|2_M6b8pCѳi%SUۏ)NI:<7J\gVꌘ&XC7HS6ԽU4(&QMBC4ȳ^1ܱ8n-ɘl0}kIDVcaje11 ̯,rqӫaykK (dmd]O핇*9g;zϷyaVhAFH ;|CFЕhc*G|g(HtfS#v)2X}O7IBE󳤌)M_gNٻ.h#> q'H-#oJ̇!sQ[{`mδ>aӴXB1AHlGW.iʢL* ;y oV SͣzW=_-b8RȻNJVNAް?wY8D$ێ"rJGpPw 3m0 Q[{Fa"Mwd|(r?$XŠDp,K.о݋HY%0)x?!$yϘЫE, 6 0 x[ đ1fG3B(ޙyw-v1kgHj6;̡eUҜ;$KqXD7[Me)ٶ{`󽫟'Se]Ol]<,ݨ|~BuΛhTBB2!FkK|QآKL#Rϵfٲs +KpTE4iCnCk5Wj%<@/EMF(tB2$/D/>yS@!%.IےiX_^N.9ЋgWyօ[X=[m/"6g&]Ɩ~`n co|_ _-p$8t R`Lb8ےr'L׻k/M4,Zlz{G ip&roVAwZ1#N o$+ٻWDW~ NJILKm &#R[y\w9U! RR3ꁝ tHU}"QEƙ|G0zhT.S榻mȲc^-y^}Q?0re4 ڂbY]luQ)uvMf]aI)qe*".F 'ҍ&3 tOE:4Yw=VkHᇼjE fDz"ng1[sr<vv2;:]Z] SzdcpiJ"]J L3?7@, |wFi'WŚ@I~^Gf)dr.ޗyZQg `s~+Rqm}8<~6[F,p;>b6f|JٗnfG`;Us !ݒ"aM¢A5C}zsSvug7MNQJz6: OQ\_g 3^s%$|@ ֟&n\YTBSLRi mbwZOz+DcD> >~/j"D%mA.G%}M}Xc.{NY mU`-H⤱̳5̳L#2̓)*`N` ZͯT"nN58ӊ,hOZnf&aԋ)ܘO!Pwkwz@L03lpK iZJ]kڔtgƊ*Kq]RZFl83yPee~Z`߄̩Wr|jT)gi-{'Ѭٹ8U)86VP}E׳ x5X4s*[u42ꆘ y'1"H AۘvZ|Z4d8,Wu;g m$}G c=`0ۖZ-@/5N VK86 yEF,齐=yuNE+Bp|tj#M"g š3Dž݋#%'1rk%:019`wYx}m!CXr= KcZ%hW:ai쭩Ȭ;,Z/m u쮅|y>sn*ubJL0tH%N@hb {&TAg@d* cV mFTms7i_@1A^Vu"(:Xǐ"#8y+PMJu'TΉFfQgȓMܱM UGԴo=c8Iʶ=glpk 2O⏑*{'O5՗a) ͹>2OB3Ipv qS]Ea:~#ś_@̸2R=3K~Cda>Rz)Xt43+=/y$E'XԢhVBZy2o8\iTRAJo0)Rr5EbC*JR%ӲXrh] گa`4YۮxeZ_QND` )z*r{8dX8Rj=3oޏ!op\wkS_QΏ,;鑆\%g],!JxdGTԕ[_xDF@߰>>:qzˆУ\9󔱇 D mJMiշ0|석$z#`Ytt moథdC}k0r Ӄ.K|k0:h DW>ofnA0$BVxěxDg@rPچ5'1f(ҙuj,Bޡe,Xؾf^,̙1gv{:)1$VlMh; +ɢcYVR!H |;{Э8S";,5[\zjSvf'Lq( _@$ȾU!J Xqo>/OiY/F78p@vC>y)n ϳA$fפ#jB/ycCQG iը<-[74 r{ !,N|+\}##Uy\r9^2W;_ R=8}&wBnB%[*"])FM*'vt{Ia!&9S28 \<(\}q}?Nӹ1l>z¦wzoTnNpɕṒٗALꣵ,Iv)yq5Ls`{҄md̅j#]Ba5qM_iAT[=>.2s *bt8O*7t^A]-:D. +'?0(mKc*,k{vf"(Q, ф߮L}ɱzL}L5w h{V?"MEvOn7̆l¡7_p6DU MxIaYU}nvۈTPTx(KV -ANBCKYDSfG7n QoUl: (΍UQ4}y!.3ge53"k}L2ׯ?-ibVv?hJ:^9aFܨwyLJoSLgOaj {}j#U/DR lG{ىɅΚEiA =ݝ eo^JgP#A>6+8ڨV#mU)lL"EGy&$B,#Õ`-uJ/ཞU\-'̙TpZ0֏m$&{2k*ǭb|"v!V*6*Ht4 fSCwLIHjy $&|/MGrò=_20.EwtfL'7RWNhX%xtoWB8+_~!AƉD?^4x)TٰoZB#ءOʽ T51߹ZOIa{/ 󌚁,a Nj )L5mٴ KխG_[0a mɎ( mBH_E2_ Rs`i%+hOI$K-FxQқ$"nxnci^> =zuXD[>c#]MPh9*Ђ)tɺK@ύ2Ť:\qFaO3a}eѬ) 04w| Y?aAa'ża; B;ޝgk$ADp2s~K{Z|0ԸdzIYBvd8{,IU@X"0'%1Tq+u۰2`jrWZekEo?inI MfsD:Ga  V Odp4C{G#k7H_FuchE$c8۾~jIZxeKZQI @փڨ(P@4+_dh_"PChOGXѧKo]CZ@bUe2Fذ8쓑3~;%u.PYo*|$1iy,ݨ q)TqtftDSb-Ң3?n&SYҒw廑, e`@o.N 6/U˙8o\0wm_1aܽ=!eJUVĿ kɹqAKE\0 N(,#E/,>=L2x G qPm97]KU=Hp|SU:Y )xxY:֎O(d؊Ya 5gRp{ίb :^xQ;_\P#Qq2B߀9[l58 ̝sJ}##ΥX:fljCzL#%žėi*Z} tުF=d )OXe8~,Ƨ Y\NR ^ːN@Y>0ƿDL4HATޘLu%c-M*vT_Pk6ފm2vr-J6?p8 z emLHG$xSFgo6zo?Z37Egb, bەg*N.6/v2TZ9r3Xsx*- [?ҤM>R=f6COϹGن?ve;qWįU?ȢXP_*c/HurQ<~%I`b[ınY4beAt[Gآ?zDqd.p@txZƷum@w)*50 y HЃ*xt#Ǣ<)&LC )6mu5M2[_=3g^uQ45QU󒮚xPzC9lK0^,5p(d6Le_NJ8:xOُ| p pJ;c)g~zL**HEa].™4럔5C5K sd|{R _6]xm^,*W1IǫȄI;dHb[JKr^Vg ~񟞜Ksh#{VXW`%HO]^k~E} ٳk/Fr++ƶ+Au4gy& oU- lJG ػb??AV;=;C(Q*9z7J~/%,l6=F(* Ƥe7Ncp' ڋfJLa!b.A5d{crfu̴J{hUm3"vB%喪؞rH]T!DH[++py[].k"T[/ ڠ/>ɰdЖF]X,w$|&Tɉ,. /!;KaO*"E%X O#bRnnV% J*S&v8M@ %XA/ʡZ&~qrw:U݉tBrO+VS"_y n* \ ;KJJ O wu{6,,7}$ux4 v7AHg5M"n%X: %C:ij =g6{؁M3. ۷kDN/ Aӣ/”eqor2*\ѷgky'ZU߮oMAMxyQ(weFNz+"Md=~0lr5FYEjZiOJ#QY\.|)- &R˝7ɟ =t}fipM غE z/v׬i۝/D ~~TZRăBa0b0ILBn}[Xq ;j+ v)[Q 0 :zj{)W;vrR8o,FN_^ҙú 5$'9D7 ]d D ~y ^.EF7*x&dtdEV& 0qJst!&ty%Mdz oO/ }JEAHGgMEg@7'Ř@A!OyŇB*k|g{hX-]4JsЌn!cȦF 56-cNcc/[!kQpVuN`pYmсѲt4SA(`(<1%@z𯁮7 H+J3X{5?hc띋%[C:)у}C3 T ~(tYh=uiRʷ*(i ~lRo{̶m^Z@zyLVR&My`큗(;$'O@6a'*MΨM"vޯJQ\Q&zH I7ls?)&濇f1 H|J. G zH37]L,U0SqwoDPZeuO'\v2t)M/ _!әt,+R5ۣ`zTmF!-sKHN'AYAb@01aj,"Q\[K˭NʳI<}c)/D7PI+0%"~~+׹ؠ!!gڲ+PNb:UV}2 _\b1 f5K"}"@y^n)E xu!L]ծa ;m !^cN`1w"͵+T֨E< ݂^ƭQFVA[m*mFTeo cK$-H[^[4M]SYka4ނösaj ϟtUYmmw*G]|كeU-]Λ0<5oz~@JjFȴ$3bШ۶xC{[0ԈB- F4 UNzc];bZn-%-}[wHUA> ܊V0,q1e{Op2gxKǘ^H̦)bT~Lh8/>@XJʧI XlA!@ Ni+E! /al*pw5:#U`TrUᏆ!1=8 xb_C9pWFv NW4gޣPsm> M4Y4U7!SaG<a[hnw]\8>,5pk |;5hi 8;{2BK&WV" WY43r̷9 Q(Q4NIo}eMJEjvuJcs`mVI|I_[d1-%2z:y 7r,n7N`|@E<7Ŋ5ЋNG`qĵ]>u5|%xg:sv 3J8Hk]~z[4#S[5juZs?My ,N &Mv}&y Tj›p'O.8[!UJyjhA y;5$w:'JJϛk~Vr.j$Ql'Iʌ|\a)/m;~TuD {1"~ۺtoVڈLw8w nx錤yS0Sȶ6``X4`+/wIĠ:?*|xb < ,W{G-t nKh?L VTpMQM$^||rbۊ&&gv Y4Q{@ͪN3ʙ)DE5Sh " T"SGjJiQIZy5mRԲ]$s#k%{XjesE},16Wl2@ -?+Nb,zڦSmN~OfaybUp;%PX5']HTLv@R@ s6S;P\^_ (G6@+5B &:LSm^.b(@eh6/l37R yF*tf\M!6w=\ b>+z+?'f}G< ¥ƵPj#+53Ƞeav|ϩtr8v !3rj1nsծ' Ĵ[@-{ f;*9RËQ2-u0jʒ`Uvi0ʴ% p}M^`D]秐Tjf(p5@m2TGT%rYnE;-w3z/2Y(sFp"|ixY19HȆP^ezX'قB[ܱ^p|8`IT?mRSOqfkm7bC]B8da:R$嫔ЪӭRkc2S Gb 4͇M+@%IĂ +u M!.|s%VYH'm ?xqTޡhJUY,C:RoKxjF iHoo2(@ѹnS%w#m w>ۘkbGi XىNyo!VE6X/m+5Q!8 so_w0emn$@4K-Ly083?*r)3!q ttTs#]`7@gv e"frWt&ߓ$Nfyp4!֗ꈠ;nQ?amWFDƐ 4.)rÅtZN3cfU*Aȑ&V!V -!Uqo^R..+vVHH7?vT18RF2[!E1Ap8%bAy1SЅ3[,pj]ϗnT58sX[ڲeIb(%ͼۃ4_ 9jNDr)q\tÎ5BVvߦgKs*+kC`m5*5}IW*+ͭunXGL=BI#qw`hO:K1 eJ;R'cUڛ (-2J~Q%,#֘h3HVaRXο|S F0=+dm7&4kϛ_-|pCFExfMl4b27`Eqxhyk=2 w;=0[9rAdL8&־Z-ΤUcz)KeCs5& 7mVsyrS=̜ X̗JM3ҹ3`,M$TR16xpie g6UG#&a[Zgخ^Ns%`~ %8.P2s&Ó[f.P;N<듴|7 .R[DrSχ¼+im'PYǚ8 c&ǻubߵ" <ϴ1 4YmKMnXwto-W;H.qXӲUbtK n=7cg|m*| E]Xpe]t jB"bHqr<R,(;CG|Y}h{*%ri*X2ss)yb[IT;}!?=+a۠Ħ >=ݵ2I ݵȭ~wڬ Njq!nT \ hSn)ؔ&Ⳮe3w5";!.wOcER8Zҭڲu[(o`+Q&[ 12%g. /Lz1 @8x Ct9ςfޚ 0ke5Eg/QZ}e^P@Ѕ@C^۶5(gVɏ>2$fN&<Ɯ{WoQ\0'Q>)!Wv #;f˄W_z>,P܊y5~n&Cȃ=wy{ @ˢIB LWy[2k6!AK:e"ig1\t%"zcp!{ x1 PEo6<ӷ<7+bE/4Aq ӧ6ݳr y\Yx7z 1Uk^K,:r/nALP@"r`җH U f#dfO(>; J șPPw4,vJэpܙTEgP*݃]LeՐ@*Wu|\!͸Nj# D)R;zMK7it.j(D'Ѻ' ng*'w@wJٖ `a;:(-H8Tޞm GSAACB$c/S8c:E? Ɠ~ ,Ӻ6N i;O]q΀1!,N,Ĩ\`uop3 HUz~fT3tF;o '=jw>Gt{:/f\ǫFZ 4QcwwicSwg!PNuFM?FC5wP1 F}ƻzNBn4D*#C'!a.nGPo,<Rl[Ua2 줗V&('%Ө1Q^(3ATOCŊ^ |0+'JEf&LIN%7۶a`&#Q|̹;ÁQv]nV@AKƶ)Ut "L+P,tpҾ7Cd%x3O?v|b]wLe%ND|Չ ʅԕXu4]t}O?p; \+T'xp#Uw.2jh4i>CGdO| .Bi+K) `ڟZW~ c}Q 4ZG!Zد#ݜ'w_Coz\2Lήsnƣ*"ʡF0,9$(1- QAWo\މ10wDl8kpaU]aO֯rcG7=fSPgȁ~SdmOiJ8z؞̶f$vcb +*j՝v<>tmyQɴatUG• `?Xeme 0{E/Em8|TNje0H~b}EIa Xjx-tWJ{E @YRj~pqkA;5L!Y6^\,OT28=Uu:A\ ʄ]ju$j8ze2Q6ZDP]4Ci(f Kܚj `'45#Q"L`t9J0ȟ-V`cW7_-M>8 kS1DmpDDVZ+/{Qenkn2%O]Y9ѷ!2t{&11_*~$uSP,V,AKf%wʼF+'iptҹؠtU]]Kv-C|7dBf\1.8ddj,сn.&H>m~ ^Dh}e%-Wɤ~G":w<GT. \$!qYv:'pjX7a j2xG-CHGC=(A3j~?T%p48ۭeX' m޹Bٴ`e6F˰{`5$8Qr24LO0ˀKY 壨?莝f*Geh m2!}[ȇasEc QIq/s`&mqۿOlSNpVvp(1X")CH](*OZ5}~W]8f AA "{-MJܵ)mH1!~iQt̚a8Lp2UlhiU"2qWU)}~hެG|/ܟϳƔ%f,_ 3xV<`3H题ſ$~$Ov?1t vG@24aO=?Wyv4߼Q;y?W¤TfHoEU  KP0MBR 5yx߉ 11Xp, vaJzp e[Eok:.AݟȌ{.9B~Cu u^N /IA7ͩo۲@Q' iD~㎭MRB1^+MVzf)KMҨ?7%ljWwj?+Ev,0n noD.agNT[3B!m,;WC>G# "G8wGxJss+/6ǯ ZPM^ʇ4,?[c qsmع 5;eWv,R7"/u A+6POr dCAgܳ ,s9jyezWa_KY1KEUS~##jYe^{!sg&31]X'mhiNi"9O7]-_OvK#S4@@EpaɹfndCW56RY^! 5K>ݦ3IaC;%2EWibc(HA]`ݐ2^N+U,c V%=h9ĭ7.G){ȩ聙זOS.4 7NBEP3휬Iuz6I'VWr֋ϣ71R7^e5C/~vneʇ4 0b0KwTsߓ D ><#aٓ eMq͸0yyZ6A ɖ\ttlctH[Ea݂V^u}Tf)=M @ϵ/Au#}%O굲/*~^#z3#tY9G &?8mZZK\%z mĀ/q!O¬YГ1^-\5=mV6Ke~{rF >o sЁTo׽>KX) s5Â_K}/!,8Q: ؆1'zAsǛͩA .}79 Ё% 1 X9#K"Qt9ZDwccnnYG; HZ!fi%G!0 \U\>V1uveH"j8lyAcFrg8+e6 wL nv{ɖTCdL4w VEJ>WX}YW^|xm`CK3Sm)Ζ֫hdb!wg⡆b7?+ HF|+w| S 2^8%BA[Ed5+EpPgt pCt%_Vl~} J]GbĽG4@Tˬf39)'e6AS" [+}mxލiPOh?57m$)|JJieMII_T2(K1/^q>i3Eeklf>Rtլ \Pi_tƔ}p~ _ֆʣڨm}-[n7}}&2(mueAc^wMlV$C9L=TSoG03ؔB$tV0ǦrfCw! {N_gä%m^s2S#/k~a6[({&f=.ZUɀůPlFˁ:8j7QLa1hf}R$a #]`fR]o>!B1'GHbf2~ْMI^5G>OGqv]oKLf5"MV`%U,cFOmpqur |yƕg3;~Hm=tJ/H74]7$XLM~KfR?]*&]t/E쯔sΚEu{ ַF /|4~6 g:Mţ|u;W)tTF%JLPG9Σ˞Q ֙"*o*c9"M?gl2vrrM2WDؑdY\hN[2ߒf ų֙v @j~a}3_ub-L).#-2`asߵ55*vɢ\g' KZA1d ܈8lJjj  _k10U s[<7cXqV [ОHu$sT=skxoC0ӥgGЪIx~?]k-I}H7-$M(=E̷R&(cWAgR?u~.!o O{h0B@TB& r Ǫff*] "V[lӍ0DLqE ؇h>].F,Ԓ;Gb-IJivtkSҽx]AӗVf:k\vu(k0)x_~.&믋/et}" ;Br 2v $rQ>Ϣj~H1" Sd'_fFUf9&ƁTP3B $Z͌ALP3K+%fWoGi-v*/fR(a rf&v^Qi)һz,eGsE q `x\L ṾCF;MFQSrFDpIVhEr]HM :]I)$WRb2KzS¥u"FÉm% Uu8k^$z>FLm}OiwmX{W% YJz|SxEW}\kgus6O/L7įcc iܨ`]x%* 6bcJ;NÖ7Lq~wn֔(upLȎ򁍺Q;XUaeRF|ZMckD_Ή\[HCjrP)o-KvuxZBoȇV[%Lh],OCThhYD-:cP۵$Faq|R6ՑљJuBsikN "[J t!bVkLmM:e+ы (nZ =EF&RdeMk(SV)-/Pf;k:֔r*̢Рy/V6PM\i4a`3pΈ3(KHַ`6["x{SzS<yڞ{3Oy/v< ܥq8mZdsD(i7E+,k-.=\aLߛŖx|^\Vň:NE N`ŠCŊhv,u6ce;3/Lc1cIAMV{1kى1\hu"q<381TkT-KB+L[4g?lP%*4gLFBt;2jsQ;{#҆2u1zg.xX%H>/-Ɠ2WTRQC^2G%6K2LB-?Ƕ:YcPjcMN%c,؂c]+eZkx/?R Z[&ffH׻\V>:/&l{}{:jrd$# J~4@ZނS0kHtq,C Oc%H-X]{Mҍo"۩ߜp7XӸe1j3Ǩ7(x?@P)ۑVb#h(W3)ܟ x]go:,~Wobcsm`*$$;j:VuY(x> Sދ _"+*ɱ"I |Zd\"_.xF%@tuyl;2~ t4Q)ƩwSk^*2bk؜j䱏FOaA_6_-֒טD}GnW/9Ӓ GIl}[vgW/4όDzdӂ@GFk4<lQ? &bdAOwB91Rg(%@?%K:3udvNއ}Tj׬TAG%B۽MP: МM3g:Yo>eOa_C p/ut庂JT>-`zd J fnC-aǝvbA.>̘5};.k, 6mRÉzư~G?}c:O\ qgBaI!|ϺA5\^1rQ(h ^V EgE YWaTm M~}I%;YT9R-.vk+E12lJK~yIz7Lޚ|bjJ;~%'${Yjku4ڗU^ ꜈r dBC~KҦNq3ƜhM8ɸjngh=}`$'yTkk1O0b/V`.Ʀ،Rj)ᅊ4l!o<^щE UyyIo.@w8s vbZDdEAzW$ѥ5":k#<g kǛைMDȞ$ήaNDSQ:Э-hal~ 7 +9ROAR|Q5K/eI z5$CG_ # 9^k Z7}j}xD`BEC%ݻ[h.VDc o=R}vJ,DWRYL '93Mj˿s\~qd |F^#%0=bRkDE(nִB$!g4lLM7~ƍf`Z?$˝2l=FYg獜e6S Uod+)\5]1^GpX77ҳAކ^Ӛb 'gϑZS^MVJ .lTxe7)=@b/f _PP- GԜ7DJ : .:rVBaJ?o->z0]B=Jl"Ed2Ҏ0x`q;5nUC,]w5sp$-u/HɥA1iggGpp`AJy62s[]M%ܛ@j ۨ=/-e5ͼ[)+]E}&'K${]X1z4䑠fS+KTMK&/C8ƍ:ll #^0*l/OOwכ(2E3Iz}yZ!Erh;L)SV9q* ;P;=0cr:ϑ@mYLJ13WTq"p^X~"Esk'UiNw!snmq+/pov~fuMH%8v|J% w!{bHOƾݜ|rn7N*i^5$ Rfq\e6T c$ӆjJfȡ8\ n?1UY\Y81ٕSQ?™9VJ?ku)TTwMԁ ^lEh1t%1!gc~u$j)Y[dn-TA GvLfjPP `, {YJrEPM gjTmS Ȳ%eY gaf;[oƿ[,$1͖H6/`{T$Ф^r~0XӳuʐoOeJ_eHlV-8;u;M RZqMi MzhcΆԲ(՝u,Gb~ ))ܧs`cd| }{&1sHn%e>7kْm̈́9+iX5Ook-ͼ)F9:&j{ eU̳?3-D$5\$[~x-hz,aTG=dՓwCpJ,^[v%\(J6LhuBԽ% >sP}ɑt< qa(7vيG1Z:dYx@c /5.y`}>R"X*P L(o:w!ȕIȋ 7XAqŽRX)%ӟĕ2/ːLeRG*xvgV"ǴɁԽn_5%dLXXa{wӛ=FO9q1FImG A?+E* 63E궇ʇ9QœJVB [D)gu*8/{HqYڅ6*3tؑ]"cES+QAm*sZO $FV8A,3bT8(_Y⽫ʹW^qc|,cy{Ptr0 'BEfMcsߕb;ē T/X> ,JlUnvyk<~A/ pDS"tk8y6#*D䛥]imK jq>ڜnu؎~;Bqq(#@83Q\J;Otc6SBf7<* ju {O{C'w/\B@r> UݭjbYK$IȳY/%c2*`y%TN I+i~hVe=\cpH4L?)Txf @8v ~kީسp tWZwzDeHhWD a8sjF1D.u[ҥ'%Wx]Wc8wx>Zp[&n L+o}WOIIm"pNZMlP/!lG ;/V:PKA OVc4gr1) Dtp Ϩ/ v-`^E=qtnh!`t$U'*Km/~jh@zĩ@K{劣 &QQX'Ӡ~JRRBazxA&5 gnZCcq2nO0jSyc oo aQsdLaMҶr p}G(aU25BBw|8AD@qH9؉/mo:"ɸ=׀/HΩ,ߔN/:d/ח,jORoRDpfVm,+a yU[b~R]Gh[t8SM"ː;kmsV η:Rc2 uKUGƳ%g:dġamrxݹL@#6>i9:m39@.UYB&a:YV7:^U4V5B 6PDE'ڛY[b9Z8`)U7[~Q^( 7c1I5 3Q>x3m+M<d<`'g˴<>ƴp'WIP7U=BWŅsksC"^[CVއ% '`/0~g1|Y]=r]2.ϘC(y/#b`iBD\iAǟf>S4Wq l)aӐ%߉T ]&`3 DĢL ![.rQ|"gV{+) I)Ms7`gj\۔7Iaj1dH~t;C8NN-G%u>~ݶVG`J*KgaeVɻ|? qu[ Xl!U6{EB yG Vpƴ: Y#V{ HB4߁8&>1+"@<_Ro@.J40hJ r-7+j$џ|vjVxb]`MFK,cs_w#|}eң`tHx쐻LLa٤~mjQ̇]j"s1'xp_BruL*[#줲S얹DICF>'T6j ?˖wzdD4_*۸* 4u8ܲ{mr,KM6 (oSN O,u^ 1-ּspHD37^Ge ]Qmƍ,3Ȱ4Li_acDtڑt:T2`>Mq42kPcLN%qxw^(,*gTM&nQ]bCC~.H-I\&x:[TGvbI<6s~v4dx @gN-Ft$멊%b1 RK5Ɉ!Qq BPG,L^kMury.4KbM$†&HA tt~}LEFPlς pQрzӸ*RI}r3t1i+v[T6v6)\?3=ݚ)3piKv8fx;~aÇ[lmO(7Ţ0땰u|BrnR" T;=vnBHs=bTϫCD\+Q 7ciôhW30!rOM66`$Յ췐׎c/) #uoz-0aqu71O+hIv6T~^_]f{9RMhnPvɊ EY$ra6xO^ ģ6ZI$_hfԫyh1k<7Xis9|'2X ݪYOf,H}7\EهrDq*V|,y4;2cigc|V}`aw,H noڎ)#rB͢ jt[R}2(zuf\N^? G9"[[HL4Hd'eПӒ2 \ 5noz s3A:phq]!1?0cީ[86cfZk`(w~" ABEysL{I&AMVIMæH+Y<;>|MESםٓ0qǷ\_W~-nð]nTZ+([\BBcaGTDH4s {pYe{>r^΃z2di҆pT%9qg٨ö^^T% xc7%YºJi7;SK7>QԊ;nC'tBȱ'aK㿂XL+d篠rVơ` >WxS֑[ڊ\'GaĘtG+;+4 7@O1G̀=S.SR .D4Hks~lz:^cV-S;&whu(RBB<#7ȍQCǜE#S}Ïd9v-|5H6O8[c %X٬1wY1yg/?WIC(.t~/%"-WxhbIo2._T"1(*j?nl9Đ05 ; цdjoӌIJ/ڳY*WT;Qׅ O|ao>o)lO ÆjZWȳG-s@ʣ"q$MPu!kC+xݰS43J5u[@W bEmℚCpv4ņɂB_R*jBg&IvHfۆ4ѹ`vt [ \ǐm9S&(=fIz ^:HFr/ *sĥ9@ ^- Bo<1] `rQV04a4T#b`D0M~^RaBwd|'漳j-]ڕ)#hu/PAmpd'lcDݦ K*!K"䪟Q2gjȼ1;lS'ȩdQo1H 0Tޛp\hR5JbV[P8Ғ gIX΄8܈8`eXqα״ ?Vuq!OOOkHV y"-qD \= 3CCybb8160ԲlqoӡMT 7u`#RN"KH]vlRYHڒd"d>kaJ;k&|qpbr)XM}Pn`.H&t3-"z2FYg3+vZ|;-)?\jU9sixJ]N΁#'}{D]&\?сnRpq(MڌuqW7g H#wg )nJb,V5ԛGaJ|p%s)D9N-$4tq XPa3R~ UU_y|M$y4JN>3ʂȶZQB X rrcR|YuY^Z=5Ic^YŀvYHG[wm $/v0-w( has] }Wx˛8s1?zaT [s%C8Y[t@HFΪ<^,u9t_ ^sIbU$)~[F'`#y2XV|fp^L)fz~[=OXO 3"cwK9ܨHcЫ:kC]QhY3Z̄ I'YmX\ I,cC5Z&YU*SsyZJ:_qnS0֬l2Bw lq_41XemXsoPZ"v56lα njz+25%sYi? )KNF8bscѰM'lR۬0~FFh2ŕV6{JNJ[ ¤’lYG~^<.L;͔sȧYe;SuFu)4I}iH҄Q aܻ՘FnbjofW=^^AVL!/zS!y,ʣݜ5l,G#[v>~j؃N;[(dMtu7|߳ O>\+bfu %ԋژb]_7ЪC&| #;00q8ʁ}j^X@.kԐdxM[BeCR:~_&f|U"ї稤//\bad}j'n7h"o?-suP΍~ @o?ϙ,L ]{k$Rꮷ;!aq(.K yJ@j%vo_'^˓ >y7&ފr5?DT^̌uTsBLj09Tx:_aV ABUk\4$ uəЫ K)ˢIx0Ct hjH :"ftG[,C\\bՖtw݃pCBTdVҭY.i;Br}oW^J?&j$\vk=ER=k{JP`֥peNSp&qt¦ Hs>"C;=;A#ߌgҝ͏1p=I#CЎBaZu]K`΃c}y}L%q l1Jږ~qUoB,jWȻ2M,aB"=%#$ohÓ ̧U#L}QbxK*U{[!j8@oCC9d0^E EiXɢi9z̶Bcgࠇ{r1B;]% =ˍK'u-,-&xw=$)B&c)7@4Dp'IW̃}pb9)7L"}W^HV@f9:JDDHb1Wpu 9  ic78t޴%2h *=w>0ze|95$,c'Dd '?"d>:Ӑ3jig&̺cY1{LEUD)(TMdNWB^K SԜы>G'|}X9"ӪEItyqcȣ$NF"xN(cd8$J;*iQi~I.Dr@NrCaުja&I*DUC2KLqtJ*Y skTKqps'qia&f  L@)f0-5{( ]< a\Y=i@9yo(t`u4s(^D:hc7}tˮyViUGb(x:8Os[[=54ѤRւs7"OW#E8)NM_WውuF@bzEWc`fi ){u.͜'grsP30.;$,7Y4(  kdEEn-Z\70/Vٔ*B9ˌߧ5ՠ|MG SH904]tca8gHhJ!6KszZ^aETvǶN4 Z?;rs)CQNGo80z|OGt">G*PV#~@2W3ȡf`cD] x )ʬZ|.3h}/DFu$4Nx"n↢]h㻓o荑ʹҞ5?Zڍ0@e͝]wld!VNXo bi1.zh#ϙ;gjqH;5 R.1{a4ǞKS`d‚*Z7, ~=~fudM! jaV>d]fAd00poG=<^+-MG @:?1h"yP0t8*$gZ驖Ύi%`ϑjشªkk8 6t#އ !>ol*L Cl6dFx`wbq=0$rMapG@ *k$egm1ذ (ʨ3(*| S00X?iԛMA.ݯV#Cs5.߄"?E tg Y,7$OEUZtDi,?ln2?&^E~bOrcd57?p2Tn03f Ϋ1?f ^3lRKFLR1U$CdNv ŶFi#!"6{BKV Ūb.cSj#:ٝۋ4@SEBPٱks󽦈 y:DDewܷd"xYbۋ<#]w>@vsv, yTe"P丄a6Wi,U),ek$d kE:RhHKwr嗠ǓfcV ߩHJ!xh'@xcV3i!M86KYe獐ՓL Q4'c1dd QLEZij|ʴ^bz e(a?NU otD̷=AhO8!Q;uIyFMP,dqxu5yV,. ݟ SzKh`f˯ fdF#g2 &U<{Q{,Ek %X_6HY9ӓ_PTR/駝zʦ_1ME +Dz5َw. z>>n>+bWч8|R:j%)KmВXQZ-ROt6eDK cߍO:=t lU2X6 Q(-~ c}+"PC5QdRcοsE `Dm,>^J(0Yَ75;>ɶnmqzd RjI5G 'd,X5F=HsN_B [j0kyt.d٘nX,'Po{» cYb iR+Va& t"w^HѤC`vM=JzBZ#5|nrHiN,9<ۈ +8]LprdS*#ak|ݶ7ZR' %'@Mc\L꿾sinuDCܮ.]]Xܒ Kk#HocLb+B q%@ϕZ̽`_&+eiyknFho̮$ wzN*imP*ԙ'+2 qE '#w$q:vxXt@cUX|X4Ș@E*Y?Iږ#d=m 8~4$ykk.* Ӟ]LS 7&xfT1](H-d=Da2%)W h\YV)3\t? DS^Fkt_4Ke!Әk%`yɺ1G5ylyo3VXvWbQ|>(onX̲tMo4ֿ%–նWc$f EHy/6CT^y(!وсr |DPo)j{'\_{6ae.P weaRw 2C(bw11wt\봷Yymh fmt2A#Ύ Uzr#RG)~\p#NIO]Bu-)aܤW 0.=9j -M٢^s"83=ܸb@<<\5ǜ.]x ^%[oF%!MNbǮy -`/CS"2{LHH3JNi#br8jo`1 HSXށ͠Zz!wa$\Zg- D7[O5(vvF+yCwK"Ց.iw lSxuc<0lo/6Hb?zrNݛB?2 'TسĴ?d[-V#PF}np&XN.l#EؚI=td{L88t!5xbDyA S-ТNluiTi{Q|,5WF%S+yOlff;!B';Ʉe#iIߓ!P(i&{%f۞Xtiaj,]0>;[ZE|yߘRKsvwZ8Eu4#vg%Gy^~@2'[pʵĉrԣ>;Lr19 pɫ< ͆55N?ZQ. 0%*_`Agܛ%n!::Z1\X̘&bcu/nmr k"-*a%Q )aR MAD)w}L9~Rnoh印@wH^x56˄ t`M"\oN;hѶ6F~fuQ?ôe^v7M!'F$ 2 jh!ն.rqx~Qfvq G.<ݖWuv0K16[1Vok<ɏUr꼮kfpWUR}]Ys sHG*S- ^@Ln8|UDbN[ _@OJagivq-#K ׄ*_vƠ _*1k@Е9K$ogX?=|q-%* -u=8I06o;?vX?yԉq;iS cɨ)0@wwX`Po5 4gLE.[# H񖺝 4jM;)O'=[XAPOuclG+ksi `ӛ{繜0o2J&>C[Ȧ+yW%{]lJ颚Ƹq~tS`Y PoAC@i/#y31LhYp l;NKv!'/pUU M~V^j Wh/R漛͏{# tg~Mú@ Ϫr+9Y$VC?#P+¾!τ.3DܠihX@Jx}LL+(*6|T3,RKWöCezx԰Ơ.O,JE.Q~Qoxƣ6t>t4rDg++n)k EjAlTQZAKȱrPD=F?Ȥ#J'%ƞ, 2S3[_ٍ\2.lȕ " Q锘RVt%ǝۂ\ f2Hrֈ_2BY_B9օ6\"PZNַ 4A v!OQ*VC>bJ kݩ{+SΜu_I-&w4F+ѽd߮x˝qM +:TU z㸿Lᴢ`Č5^*bۭ6l<Ή% 6A}鶹ZS5Z_(6?6#D*ʵhmJux%K*9VK;Sƶa~R)j'^SATT_jƭ񱬀O@dC~Adֶg#_* uqG\zk#ԂevnI6V9HwV $#‡dge)nAyMMl}>}@Tra7c˧C=B1齏ÑwA*'a:1g ?uUP> NC4!')/8%dZ$yڭSI<#^҃%2c=֟u.k\]ڲ=_)VMlPDX69PŃ7e< v+I)dʻ\c:_ҮK\dY_If~vF@61.ulp [-dLqM&͟";Q{82hG/bΗVq-pd8.w󍸋:5Q̍C\, i$c18F߮h$dn k;\:Ad \Cs-