aboutsummaryrefslogtreecommitdiffstats
path: root/tests
diff options
context:
space:
mode:
authorDhruv <dhruv.goel92@gmail.com>2013-06-13 22:00:12 +0530
committerDhruv <dhruv.goel92@gmail.com>2013-06-13 22:00:12 +0530
commite8b535bf9beda58ea8f75071ed31934534849cb1 (patch)
tree36de3a5c8f46dda54d593593f71cb221cd9085ec /tests
parentabd299b0a98be01499b31eb6901aca73d8c2b192 (diff)
parent6298cce464ea2035c107549dfed9bfcb2f985f9c (diff)
downloadforums-e8b535bf9beda58ea8f75071ed31934534849cb1.tar
forums-e8b535bf9beda58ea8f75071ed31934534849cb1.tar.gz
forums-e8b535bf9beda58ea8f75071ed31934534849cb1.tar.bz2
forums-e8b535bf9beda58ea8f75071ed31934534849cb1.tar.xz
forums-e8b535bf9beda58ea8f75071ed31934534849cb1.zip
Merge branch 'develop' into ticket/10820-develop
# By Joas Schilling (146) and others # Via Andreas Fischer (50) and others * develop: (356 commits) [ticket/11599] Copy the forums into a static array for later reuse [ticket/11602] Do not call localize_errors() if avatars are disabled [ticket/11601] Add protected method for database sync and call it [ticket/11601] Split post_setup_synchronisation logic from xml parsing [ticket/11550] Specify a valid path so it's clearer that it must be a path [ticket/11550] Move comments to correct function [ticket/11550] We use a different fixture set for extension_acp_test.php [ticket/11550] Fixtures should only be directories not files [ticket/11550] Use new functionality from the test case helpers [ticket/11550] Move functionality for copying/restoring to test case helpers [ticket/11550] Fix copying the fixtures in extension_permission_lang_test.php [ticket/11543] Add more users so #hidden <> #normal [ticket/11590] Close database connections when tearDown() is called [ticket/develop/11543] Use plurals in develop [ticket/11543] Use correct IP addresses and inject time for correct values [ticket/11543] Add unit tests for obtain_users_online_string() [ticket/11543] Add unit tests for obtain_users_online() with empty forum [ticket/11543] Add unit tests for obtain_users_online() [ticket/11543] Add unit tests for obtain_guest_count() [ticket/11481] Move prepended slash from calls into function ...
Diffstat (limited to 'tests')
-rw-r--r--tests/RUNNING_TESTS.md (renamed from tests/RUNNING_TESTS.txt)23
-rw-r--r--tests/dbal/migrator_test.php7
-rw-r--r--tests/extension/ext/barfoo/acp/a_info.php16
-rw-r--r--tests/extension/ext/barfoo/acp/a_module.php5
-rw-r--r--tests/extension/ext/barfoo/ext.php5
-rw-r--r--tests/extension/ext/foo/acp/a_info.php16
-rw-r--r--tests/extension/ext/foo/acp/a_module.php5
-rw-r--r--tests/extension/ext/foo/acp/fail_info.php19
-rw-r--r--tests/extension/ext/foo/acp/fail_module.php8
-rw-r--r--tests/extension/ext/foo/mcp/a_info.php16
-rw-r--r--tests/extension/ext/foo/mcp/a_module.php5
-rw-r--r--tests/extension/finder_test.php17
-rw-r--r--tests/extension/includes/acp/acp_foobar.php28
-rw-r--r--tests/extension/includes/acp/info/acp_foobar.php26
-rw-r--r--tests/extension/manager_test.php8
-rw-r--r--tests/extension/metadata_manager_test.php33
-rw-r--r--tests/extension/modules_test.php192
-rw-r--r--tests/functional/acp_groups_test.php120
-rw-r--r--tests/functional/acp_permissions_test.php19
-rw-r--r--tests/functional/auth_test.php15
-rw-r--r--tests/functional/browse_test.php9
-rw-r--r--tests/functional/common_groups_test.php63
-rw-r--r--tests/functional/extension_acp_test.php76
-rw-r--r--tests/functional/extension_controller_test.php76
-rw-r--r--tests/functional/extension_module_test.php99
-rw-r--r--tests/functional/extension_permission_lang_test.php80
-rw-r--r--tests/functional/fileupload_form_test.php5
-rw-r--r--tests/functional/fileupload_remote_test.php5
-rw-r--r--tests/functional/fixtures/ext/foo/bar/acp/main_info.php32
-rw-r--r--tests/functional/fixtures/ext/foo/bar/acp/main_module.php28
-rw-r--r--tests/functional/fixtures/ext/foo/bar/adm/style/foobar.html3
-rw-r--r--tests/functional/fixtures/ext/foo/bar/composer.json23
-rw-r--r--tests/functional/fixtures/ext/foo/bar/language/en/permissions_foo.php6
-rw-r--r--tests/functional/forgot_password_test.php44
-rw-r--r--tests/functional/memberlist_test.php28
-rw-r--r--tests/functional/metadata_manager_test.php85
-rw-r--r--tests/functional/notification_test.php5
-rw-r--r--tests/functional/posting_test.php17
-rw-r--r--tests/functional/ucp_groups_test.php53
-rw-r--r--tests/functions/fixtures/language_select.xml21
-rw-r--r--tests/functions/fixtures/obtain_online.xml121
-rw-r--r--tests/functions/fixtures/style_select.xml27
-rw-r--r--tests/functions/is_absolute_test.php56
-rw-r--r--tests/functions/language_select_test.php39
-rw-r--r--tests/functions/obtain_online_test.php227
-rw-r--r--tests/functions/style_select_test.php41
-rw-r--r--tests/functions/validate_hex_colour_test.php121
-rw-r--r--tests/functions_acp/build_cfg_template_test.php53
-rw-r--r--tests/lock/db_test.php14
-rw-r--r--tests/lock/flock_test.php11
-rw-r--r--tests/mock/lang.php5
-rw-r--r--tests/mock/metadata_manager.php21
-rw-r--r--tests/notification/fixtures/submit_post_bookmark.xml8
-rw-r--r--tests/notification/fixtures/submit_post_post.xml10
-rw-r--r--tests/notification/fixtures/submit_post_post_in_queue.xml8
-rw-r--r--tests/notification/fixtures/submit_post_quote.xml8
-rw-r--r--tests/notification/manager_helper.php (renamed from tests/mock/notifications_notification_manager.php)10
-rw-r--r--tests/notification/notification_test.php82
-rw-r--r--tests/notification/submit_post_base.php48
-rw-r--r--tests/search/common_test_case.php98
-rw-r--r--tests/template/datasets/ext_trivial/ext/trivial/styles/all/template/event_variable_spacing.html6
-rw-r--r--tests/template/datasets/ext_trivial/styles/silver/template/variable_spacing.html1
-rw-r--r--tests/template/template_events_test.php3
-rw-r--r--tests/template/template_spacing_test.php87
-rw-r--r--tests/template/template_test.php14
-rw-r--r--tests/template/templates/define.html2
-rw-r--r--tests/template/templates/define_advanced.html12
-rw-r--r--tests/template/templates/define_include2.html11
-rw-r--r--tests/template/templates/define_unclosed.html2
-rw-r--r--tests/template/templates/variable_spacing.html6
-rw-r--r--tests/test_framework/phpbb_database_test_case.php35
-rw-r--r--tests/test_framework/phpbb_database_test_connection_manager.php49
-rw-r--r--tests/test_framework/phpbb_functional_test_case.php242
-rw-r--r--tests/test_framework/phpbb_test_case_helpers.php52
-rw-r--r--tests/tree/fixtures/phpbb_forums.xml13
-rw-r--r--tests/tree/nestedset_forum_base.php115
-rw-r--r--tests/tree/nestedset_forum_get_data_test.php119
-rw-r--r--tests/tree/nestedset_forum_insert_delete_test.php120
-rw-r--r--tests/tree/nestedset_forum_move_test.php569
-rw-r--r--tests/tree/nestedset_forum_regenerate_test.php72
-rw-r--r--tests/tree/nestedset_forum_test.php116
-rw-r--r--tests/user/fixtures/user_loader.xml16
82 files changed, 3667 insertions, 344 deletions
diff --git a/tests/RUNNING_TESTS.txt b/tests/RUNNING_TESTS.md
index cede81d59d..f89c1fefeb 100644
--- a/tests/RUNNING_TESTS.txt
+++ b/tests/RUNNING_TESTS.md
@@ -7,9 +7,14 @@ Prerequisites
PHPUnit
-------
-phpBB unit tests use PHPUnit framework. Version 3.5 or better is required
-to run the tests. PHPUnit prefers to be installed via PEAR; refer to
-http://www.phpunit.de/ for more information.
+phpBB unit tests use the PHPUnit framework (see http://www.phpunit.de for more
+information). Version 3.5 or higher is required to run the tests. PHPUnit can
+be installed via Composer together with other development dependencies as
+follows.
+
+ $ cd phpBB
+ $ php ../composer.phar install --dev
+ $ cd ..
PHP extensions
--------------
@@ -77,14 +82,16 @@ In order to run tests on some of the databases that we support, it will be
necessary to provide a custom DSN string in test_config.php. This is only
needed for MSSQL 2000+ (PHP module), MSSQL via ODBC, and Firebird when
PDO_Firebird does not work on your system
-(https://bugs.php.net/bug.php?id=61183). The variable must be named $custom_dsn.
+(https://bugs.php.net/bug.php?id=61183). The variable must be named `$custom_dsn`.
Examples:
Firebird using http://www.firebirdsql.org/en/odbc-driver/
-$custom_dsn = "Driver={Firebird/InterBase(r) driver};dbname=$dbhost:$dbname";
+
+ $custom_dsn = "Driver={Firebird/InterBase(r) driver};dbname=$dbhost:$dbname";
MSSQL
-$custom_dsn = "Driver={SQL Server Native Client 10.0};Server=$dbhost;Database=$dbname";
+
+ $custom_dsn = "Driver={SQL Server Native Client 10.0};Server=$dbhost;Database=$dbname";
The other fields in test_config.php should be filled out as you would normally
to connect to that database in phpBB.
@@ -113,7 +120,7 @@ Running
Once the prerequisites are installed, run the tests from the project root
directory (above phpBB):
- $ phpunit
+ $ phpBB/vendor/bin/phpunit
Slow tests
--------------
@@ -123,7 +130,7 @@ Thus these tests are in the `slow` group, which is excluded by default. You can
enable slow tests by copying the phpunit.xml.all file to phpunit.xml. If you
only want the slow tests, run:
- $ phpunit --group slow
+ $ phpBB/vendor/bin/phpunit --group slow
More Information
================
diff --git a/tests/dbal/migrator_test.php b/tests/dbal/migrator_test.php
index 6390d6a715..1e40c9c6d6 100644
--- a/tests/dbal/migrator_test.php
+++ b/tests/dbal/migrator_test.php
@@ -55,11 +55,14 @@ class phpbb_dbal_migrator_test extends phpbb_database_test_case
'phpbb_',
$tools
);
+
+ $container = new phpbb_mock_container_builder();
+ $container->set('migrator', $migrator);
+
$this->extension_manager = new phpbb_extension_manager(
- new phpbb_mock_container_builder(),
+ $container,
$this->db,
$this->config,
- $this->migrator,
new phpbb_filesystem(),
'phpbb_ext',
dirname(__FILE__) . '/../../phpBB/',
diff --git a/tests/extension/ext/barfoo/acp/a_info.php b/tests/extension/ext/barfoo/acp/a_info.php
new file mode 100644
index 0000000000..cd7e4e574b
--- /dev/null
+++ b/tests/extension/ext/barfoo/acp/a_info.php
@@ -0,0 +1,16 @@
+<?php
+
+class phpbb_ext_barfoo_acp_a_info
+{
+ public function module()
+ {
+ return array(
+ 'filename' => 'phpbb_ext_barfoo_acp_a_module',
+ 'title' => 'Barfoo',
+ 'version' => '3.1.0-dev',
+ 'modes' => array(
+ 'config' => array('title' => 'Config', 'auth' => '', 'cat' => array('ACP_MODS')),
+ ),
+ );
+ }
+}
diff --git a/tests/extension/ext/barfoo/acp/a_module.php b/tests/extension/ext/barfoo/acp/a_module.php
new file mode 100644
index 0000000000..5bedb49645
--- /dev/null
+++ b/tests/extension/ext/barfoo/acp/a_module.php
@@ -0,0 +1,5 @@
+<?php
+
+class phpbb_ext_barfoo_acp_a_module
+{
+}
diff --git a/tests/extension/ext/barfoo/ext.php b/tests/extension/ext/barfoo/ext.php
new file mode 100644
index 0000000000..2e11ece8d1
--- /dev/null
+++ b/tests/extension/ext/barfoo/ext.php
@@ -0,0 +1,5 @@
+<?php
+
+class phpbb_ext_barfoo_ext extends phpbb_extension_base
+{
+}
diff --git a/tests/extension/ext/foo/acp/a_info.php b/tests/extension/ext/foo/acp/a_info.php
new file mode 100644
index 0000000000..3e9bbffaca
--- /dev/null
+++ b/tests/extension/ext/foo/acp/a_info.php
@@ -0,0 +1,16 @@
+<?php
+
+class phpbb_ext_foo_acp_a_info
+{
+ public function module()
+ {
+ return array(
+ 'filename' => 'phpbb_ext_foo_acp_a_module',
+ 'title' => 'Foobar',
+ 'version' => '3.1.0-dev',
+ 'modes' => array(
+ 'config' => array('title' => 'Config', 'auth' => '', 'cat' => array('ACP_MODS')),
+ ),
+ );
+ }
+}
diff --git a/tests/extension/ext/foo/acp/a_module.php b/tests/extension/ext/foo/acp/a_module.php
new file mode 100644
index 0000000000..093b4b1ad7
--- /dev/null
+++ b/tests/extension/ext/foo/acp/a_module.php
@@ -0,0 +1,5 @@
+<?php
+
+class phpbb_ext_foo_acp_a_module
+{
+}
diff --git a/tests/extension/ext/foo/acp/fail_info.php b/tests/extension/ext/foo/acp/fail_info.php
new file mode 100644
index 0000000000..99aa09551e
--- /dev/null
+++ b/tests/extension/ext/foo/acp/fail_info.php
@@ -0,0 +1,19 @@
+<?php
+/*
+* Due to the mismatch between the class name and the file name, this module
+* file shouldn't be found by the extension finder
+*/
+class phpbb_ext_foo_acp_foo_info
+{
+ public function module()
+ {
+ return array(
+ 'filename' => 'phpbb_ext_foo_acp_fail_module',
+ 'title' => 'Foobar',
+ 'version' => '3.1.0-dev',
+ 'modes' => array(
+ 'config' => array('title' => 'Config', 'auth' => '', 'cat' => array('ACP_MODS')),
+ ),
+ );
+ }
+}
diff --git a/tests/extension/ext/foo/acp/fail_module.php b/tests/extension/ext/foo/acp/fail_module.php
new file mode 100644
index 0000000000..a200d92d2f
--- /dev/null
+++ b/tests/extension/ext/foo/acp/fail_module.php
@@ -0,0 +1,8 @@
+<?php
+/*
+* Due to the mismatch between the class name and the file name of the module
+* info file, this module's info file shouldn't be found
+*/
+class phpbb_ext_foo_acp_fail_module
+{
+}
diff --git a/tests/extension/ext/foo/mcp/a_info.php b/tests/extension/ext/foo/mcp/a_info.php
new file mode 100644
index 0000000000..84a36b9134
--- /dev/null
+++ b/tests/extension/ext/foo/mcp/a_info.php
@@ -0,0 +1,16 @@
+<?php
+
+class phpbb_ext_foo_mcp_a_info
+{
+ public function module()
+ {
+ return array(
+ 'filename' => 'phpbb_ext_foo_mcp_a_module',
+ 'title' => 'Foobar',
+ 'version' => '3.1.0-dev',
+ 'modes' => array(
+ 'config' => array('title' => 'Config', 'auth' => '', 'cat' => array('MCP_MAIN')),
+ ),
+ );
+ }
+}
diff --git a/tests/extension/ext/foo/mcp/a_module.php b/tests/extension/ext/foo/mcp/a_module.php
new file mode 100644
index 0000000000..59d9f8cc6f
--- /dev/null
+++ b/tests/extension/ext/foo/mcp/a_module.php
@@ -0,0 +1,5 @@
+<?php
+
+class phpbb_ext_foo_mcp_a_module
+{
+}
diff --git a/tests/extension/finder_test.php b/tests/extension/finder_test.php
index dc3e26be02..6f3cebbd7c 100644
--- a/tests/extension/finder_test.php
+++ b/tests/extension/finder_test.php
@@ -158,6 +158,23 @@ class phpbb_extension_finder_test extends phpbb_test_case
);
}
+ public function test_find_from_extension()
+ {
+ $files = $this->finder
+ ->extension_directory('/type')
+ ->find_from_extension('foo', dirname(__FILE__) . '/ext/foo/');
+ $classes = $this->finder->get_classes_from_files($files);
+
+ sort($classes);
+ $this->assertEquals(
+ array(
+ 'phpbb_ext_foo_type_alternative',
+ 'phpbb_ext_foo_type_dummy_empty',
+ ),
+ $classes
+ );
+ }
+
/**
* These do not work because of changes with PHPBB3-11386
* They do not seem neccessary to me, so I am commenting them out for now
diff --git a/tests/extension/includes/acp/acp_foobar.php b/tests/extension/includes/acp/acp_foobar.php
new file mode 100644
index 0000000000..c256a432e2
--- /dev/null
+++ b/tests/extension/includes/acp/acp_foobar.php
@@ -0,0 +1,28 @@
+<?php
+/**
+*
+* @package testing
+* @copyright (c) 2013 phpBB Group
+* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2
+*
+*/
+
+/**
+* @ignore
+*/
+if (!defined('IN_PHPBB'))
+{
+ exit;
+}
+
+/**
+* @package acp
+*/
+class acp_foobar
+{
+ var $u_action;
+
+ function main($id, $mode)
+ {
+ }
+}
diff --git a/tests/extension/includes/acp/info/acp_foobar.php b/tests/extension/includes/acp/info/acp_foobar.php
new file mode 100644
index 0000000000..b89cfb9574
--- /dev/null
+++ b/tests/extension/includes/acp/info/acp_foobar.php
@@ -0,0 +1,26 @@
+<?php
+/**
+*
+* @package testing
+* @copyright (c) 2013 phpBB Group
+* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2
+*
+*/
+
+/**
+* @package module_install
+*/
+class acp_foobar_info
+{
+ function module()
+ {
+ return array(
+ 'filename' => 'acp_foobar',
+ 'title' => 'ACP Foobar',
+ 'version' => '3.1.0-dev',
+ 'modes' => array(
+ 'test' => array('title' => 'Test', 'auth' => '', 'cat' => array('ACP_GENERAL')),
+ ),
+ );
+ }
+}
diff --git a/tests/extension/manager_test.php b/tests/extension/manager_test.php
index 106f078691..a23e5a18d9 100644
--- a/tests/extension/manager_test.php
+++ b/tests/extension/manager_test.php
@@ -30,7 +30,7 @@ class phpbb_extension_manager_test extends phpbb_database_test_case
public function test_available()
{
- $this->assertEquals(array('bar', 'foo', 'vendor/moo'), array_keys($this->extension_manager->all_available()));
+ $this->assertEquals(array('bar', 'barfoo', 'foo', 'vendor/moo'), array_keys($this->extension_manager->all_available()));
}
public function test_enabled()
@@ -107,11 +107,13 @@ class phpbb_extension_manager_test extends phpbb_database_test_case
$table_prefix,
array()
);
+ $container = new phpbb_mock_container_builder();
+ $container->set('migrator', $migrator);
+
return new phpbb_extension_manager(
- new phpbb_mock_container_builder(),
+ $container,
$db,
$config,
- $migrator,
new phpbb_filesystem(),
'phpbb_ext',
dirname(__FILE__) . '/',
diff --git a/tests/extension/metadata_manager_test.php b/tests/extension/metadata_manager_test.php
index 05d1cbccc3..d410333f09 100644
--- a/tests/extension/metadata_manager_test.php
+++ b/tests/extension/metadata_manager_test.php
@@ -7,7 +7,9 @@
*
*/
-class metadata_manager_test extends phpbb_database_test_case
+require_once dirname(__FILE__) . '/../../phpBB/includes/db/db_tools.php';
+
+class phpbb_extension_metadata_manager_test extends phpbb_database_test_case
{
protected $class_loader;
protected $extension_manager;
@@ -59,11 +61,13 @@ class metadata_manager_test extends phpbb_database_test_case
$this->table_prefix,
array()
);
+ $container = new phpbb_mock_container_builder();
+ $container->set('migrator', $migrator);
+
$this->extension_manager = new phpbb_extension_manager(
- new phpbb_mock_container_builder(),
+ $container,
$this->db,
$this->config,
- $this->migrator,
new phpbb_filesystem(),
'phpbb_ext',
$this->phpbb_root_path,
@@ -416,31 +420,16 @@ class metadata_manager_test extends phpbb_database_test_case
* Get an instance of the metadata manager
*
* @param string $ext_name
- * @return phpbb_extension_metadata_manager_test
+ * @return phpbb_mock_metadata_manager
*/
private function get_metadata_manager($ext_name)
{
- return new phpbb_extension_metadata_manager_test(
+ return new phpbb_mock_metadata_manager(
$ext_name,
- $this->db,
+ $this->config,
$this->extension_manager,
- $this->phpbb_root_path,
- $this->phpEx,
$this->template,
- $this->config
+ $this->phpbb_root_path
);
}
}
-
-class phpbb_extension_metadata_manager_test extends phpbb_extension_metadata_manager
-{
- public function set_metadata($metadata)
- {
- $this->metadata = $metadata;
- }
-
- public function merge_metadata($metadata)
- {
- $this->metadata = array_merge($this->metadata, $metadata);
- }
-} \ No newline at end of file
diff --git a/tests/extension/modules_test.php b/tests/extension/modules_test.php
new file mode 100644
index 0000000000..fe71747c5d
--- /dev/null
+++ b/tests/extension/modules_test.php
@@ -0,0 +1,192 @@
+<?php
+/**
+*
+* @package testing
+* @copyright (c) 2013 phpBB Group
+* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2
+*
+*/
+
+require_once dirname(__FILE__) . '/ext/foo/acp/a_info.php';
+require_once dirname(__FILE__) . '/ext/foo/mcp/a_info.php';
+require_once dirname(__FILE__) . '/ext/foo/acp/fail_info.php';
+require_once dirname(__FILE__) . '/ext/barfoo/acp/a_info.php';
+require_once dirname(__FILE__) . '/../../phpBB/includes/acp/acp_modules.php';
+
+class phpbb_extension_modules_test extends phpbb_test_case
+{
+ protected $extension_manager;
+ protected $finder;
+
+ public function setUp()
+ {
+ global $phpbb_extension_manager;
+
+ $this->extension_manager = new phpbb_mock_extension_manager(
+ dirname(__FILE__) . '/',
+ array(
+ 'foo' => array(
+ 'ext_name' => 'foo',
+ 'ext_active' => '1',
+ 'ext_path' => 'ext/foo/',
+ ),
+ 'bar' => array(
+ 'ext_name' => 'bar',
+ 'ext_active' => '1',
+ 'ext_path' => 'ext/bar/',
+ ),
+ ));
+ $phpbb_extension_manager = $this->extension_manager;
+
+ $this->acp_modules = new acp_modules();
+ }
+
+ public function test_get_module_infos()
+ {
+ global $phpbb_root_path;
+
+ // Correctly set the root path for this test to this directory, so the classes can be found
+ $phpbb_root_path = dirname(__FILE__) . '/';
+
+ // Find acp module info files
+ $this->acp_modules->module_class = 'acp';
+ $acp_modules = $this->acp_modules->get_module_infos();
+ $this->assertEquals(array(
+ 'phpbb_ext_foo_acp_a_module' => array(
+ 'filename' => 'phpbb_ext_foo_acp_a_module',
+ 'title' => 'Foobar',
+ 'version' => '3.1.0-dev',
+ 'modes' => array(
+ 'config' => array('title' => 'Config', 'auth' => '', 'cat' => array('ACP_MODS')),
+ ),
+ ),
+ 'acp_foobar' => array(
+ 'filename' => 'acp_foobar',
+ 'title' => 'ACP Foobar',
+ 'version' => '3.1.0-dev',
+ 'modes' => array(
+ 'test' => array('title' => 'Test', 'auth' => '', 'cat' => array('ACP_GENERAL')),
+ ),
+ ),
+ ), $acp_modules);
+
+ // Find mcp module info files
+ $this->acp_modules->module_class = 'mcp';
+ $acp_modules = $this->acp_modules->get_module_infos();
+ $this->assertEquals(array(
+ 'phpbb_ext_foo_mcp_a_module' => array(
+ 'filename' => 'phpbb_ext_foo_mcp_a_module',
+ 'title' => 'Foobar',
+ 'version' => '3.1.0-dev',
+ 'modes' => array(
+ 'config' => array('title' => 'Config', 'auth' => '', 'cat' => array('MCP_MAIN')),
+ ),
+ ),
+ ), $acp_modules);
+
+ // Find a specific module info file (mcp_a_module)
+ $this->acp_modules->module_class = 'mcp';
+ $acp_modules = $this->acp_modules->get_module_infos('mcp_a_module');
+ $this->assertEquals(array(
+ 'phpbb_ext_foo_mcp_a_module' => array(
+ 'filename' => 'phpbb_ext_foo_mcp_a_module',
+ 'title' => 'Foobar',
+ 'version' => '3.1.0-dev',
+ 'modes' => array(
+ 'config' => array('title' => 'Config', 'auth' => '', 'cat' => array('MCP_MAIN')),
+ ),
+ ),
+ ), $acp_modules);
+
+ // Find a specific module info file (mcp_a_module) with passing the module_class
+ $this->acp_modules->module_class = '';
+ $acp_modules = $this->acp_modules->get_module_infos('mcp_a_module', 'mcp');
+ $this->assertEquals(array(
+ 'phpbb_ext_foo_mcp_a_module' => array(
+ 'filename' => 'phpbb_ext_foo_mcp_a_module',
+ 'title' => 'Foobar',
+ 'version' => '3.1.0-dev',
+ 'modes' => array(
+ 'config' => array('title' => 'Config', 'auth' => '', 'cat' => array('MCP_MAIN')),
+ ),
+ ),
+ ), $acp_modules);
+
+ // The mcp module info file we're looking for shouldn't exist
+ $this->acp_modules->module_class = 'mcp';
+ $acp_modules = $this->acp_modules->get_module_infos('mcp_a_fail');
+ $this->assertEquals(array(), $acp_modules);
+
+ // As there are no ucp modules we shouldn't find any
+ $this->acp_modules->module_class = 'ucp';
+ $acp_modules = $this->acp_modules->get_module_infos();
+ $this->assertEquals(array(), $acp_modules);
+
+ // Get module info of specified extension module
+ $this->acp_modules->module_class = 'acp';
+ $acp_modules = $this->acp_modules->get_module_infos('phpbb_ext_foo_acp_a_module');
+ $this->assertEquals(array(
+ 'phpbb_ext_foo_acp_a_module' => array (
+ 'filename' => 'phpbb_ext_foo_acp_a_module',
+ 'title' => 'Foobar',
+ 'version' => '3.1.0-dev',
+ 'modes' => array (
+ 'config' => array ('title' => 'Config', 'auth' => '', 'cat' => array ('ACP_MODS')),
+ ),
+ ),
+ ), $acp_modules);
+
+ // No specific module and module class set to an incorrect name
+ $acp_modules = $this->acp_modules->get_module_infos('', 'wcp', true);
+ $this->assertEquals(array(), $acp_modules);
+
+ // No specific module, no module_class set in the function parameter, and an incorrect module class
+ $this->acp_modules->module_class = 'wcp';
+ $acp_modules = $this->acp_modules->get_module_infos();
+ $this->assertEquals(array(), $acp_modules);
+
+ // No specific module, module class set to false (will default to the above acp)
+ // Setting $use_all_available will cause get_module_infos() to also load not enabled extensions (barfoo)
+ $this->acp_modules->module_class = 'acp';
+ $acp_modules = $this->acp_modules->get_module_infos('', false, true);
+ $this->assertEquals(array(
+ 'phpbb_ext_foo_acp_a_module' => array(
+ 'filename' => 'phpbb_ext_foo_acp_a_module',
+ 'title' => 'Foobar',
+ 'version' => '3.1.0-dev',
+ 'modes' => array(
+ 'config' => array('title' => 'Config', 'auth' => '', 'cat' => array('ACP_MODS')),
+ ),
+ ),
+ 'acp_foobar' => array(
+ 'filename' => 'acp_foobar',
+ 'title' => 'ACP Foobar',
+ 'version' => '3.1.0-dev',
+ 'modes' => array(
+ 'test' => array('title' => 'Test', 'auth' => '', 'cat' => array('ACP_GENERAL')),
+ ),
+ ),
+ 'phpbb_ext_barfoo_acp_a_module' => array(
+ 'filename' => 'phpbb_ext_barfoo_acp_a_module',
+ 'title' => 'Barfoo',
+ 'version' => '3.1.0-dev',
+ 'modes' => array(
+ 'config' => array('title' => 'Config', 'auth' => '', 'cat' => array('ACP_MODS')),
+ ),
+ )
+ ), $acp_modules);
+
+ // Specific module set to disabled extension
+ $acp_modules = $this->acp_modules->get_module_infos('phpbb_ext_barfoo_acp_a_module', 'acp', true);
+ $this->assertEquals(array(
+ 'phpbb_ext_barfoo_acp_a_module' => array(
+ 'filename' => 'phpbb_ext_barfoo_acp_a_module',
+ 'title' => 'Barfoo',
+ 'version' => '3.1.0-dev',
+ 'modes' => array(
+ 'config' => array('title' => 'Config', 'auth' => '', 'cat' => array('ACP_MODS')),
+ ),
+ )
+ ), $acp_modules);
+ }
+}
diff --git a/tests/functional/acp_groups_test.php b/tests/functional/acp_groups_test.php
new file mode 100644
index 0000000000..cdf8bf5117
--- /dev/null
+++ b/tests/functional/acp_groups_test.php
@@ -0,0 +1,120 @@
+<?php
+/**
+*
+* @package testing
+* @copyright (c) 2013 phpBB Group
+* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2
+*
+*/
+
+require_once dirname(__FILE__) . '/common_groups_test.php';
+
+/**
+* @group functional
+*/
+class phpbb_functional_acp_groups_test extends phpbb_functional_common_groups_test
+{
+ protected $form_data;
+
+ protected function get_url()
+ {
+ return 'adm/index.php?i=groups&mode=manage&action=edit';
+ }
+
+ public function acp_group_test_data()
+ {
+ return array(
+ 'both_yes' => array(
+ 5,
+ true,
+ true,
+ ),
+ 'legend_no_teampage' => array(
+ 5,
+ true,
+ false,
+ ),
+ 'no_legend_teampage' => array(
+ 5,
+ false,
+ true,
+ ),
+ 'both_no' => array(
+ 5,
+ false,
+ false,
+ ),
+ 'no_change' => array(
+ 5,
+ NULL,
+ NULL,
+ ),
+ 'back_to_default' => array(
+ 5,
+ true,
+ true,
+ ),
+ // Remove and add moderators back in order to reset
+ // group order to default one
+ 'mods_both_no' => array(
+ 4,
+ false,
+ false,
+ ),
+ 'mods_back_to_default' => array(
+ 4,
+ true,
+ true,
+ ),
+ );
+ }
+
+ /**
+ * @dataProvider acp_group_test_data
+ */
+ public function test_acp_groups_teampage($group_id, $tick_legend, $tick_teampage)
+ {
+ $this->group_manage_login();
+
+ // Manage Administrators group
+ $form = $this->get_group_manage_form($group_id);
+ $this->form_data[0] = $form->getValues();
+
+ if (isset($tick_legend) && isset($tick_teampage))
+ {
+ if ($tick_legend)
+ {
+ $form['group_legend']->tick();
+ }
+ else
+ {
+ $form['group_legend']->untick();
+ }
+
+ if ($tick_teampage)
+ {
+ $form['group_teampage']->tick();
+ }
+ else
+ {
+ $form['group_teampage']->untick();
+ }
+ }
+ $crawler = self::submit($form);
+ $this->assertContains($this->lang('GROUP_UPDATED'), $crawler->text());
+
+ $form = $this->get_group_manage_form($group_id);
+ if (!isset($tick_legend) && !isset($tick_teampage))
+ {
+ $this->form_data[1] = $form->getValues();
+ unset($this->form_data[0]['creation_time'], $this->form_data[0]['form_token'], $this->form_data[1]['creation_time'], $this->form_data[1]['form_token']);
+ $this->assertEquals($this->form_data[0], $this->form_data[1]);
+ }
+ else
+ {
+ $this->form_data = $form->getValues();
+ $this->assertEquals($tick_legend, $this->form_data['group_legend']);
+ $this->assertEquals($tick_teampage, $this->form_data['group_teampage']);
+ }
+ }
+}
diff --git a/tests/functional/acp_permissions_test.php b/tests/functional/acp_permissions_test.php
index f7fd44fc89..a3d272906f 100644
--- a/tests/functional/acp_permissions_test.php
+++ b/tests/functional/acp_permissions_test.php
@@ -25,25 +25,22 @@ class phpbb_functional_acp_permissions_test extends phpbb_functional_test_case
{
// Permissions tab
// XXX hardcoded id
- $crawler = $this->request('GET', 'adm/index.php?i=16&sid=' . $this->sid);
- $this->assert_response_success();
+ $crawler = self::request('GET', 'adm/index.php?i=16&sid=' . $this->sid);
// these language strings are html
- $this->assertContains($this->lang('ACP_PERMISSIONS_EXPLAIN'), $this->client->getResponse()->getContent());
+ $this->assertContains($this->lang('ACP_PERMISSIONS_EXPLAIN'), $this->get_content());
}
public function test_select_user()
{
// User permissions
- $crawler = $this->request('GET', 'adm/index.php?i=acp_permissions&icat=16&mode=setting_user_global&sid=' . $this->sid);
- $this->assert_response_success();
- $this->assertContains($this->lang('ACP_USERS_PERMISSIONS_EXPLAIN'), $this->client->getResponse()->getContent());
+ $crawler = self::request('GET', 'adm/index.php?i=acp_permissions&icat=16&mode=setting_user_global&sid=' . $this->sid);
+ $this->assertContains($this->lang('ACP_USERS_PERMISSIONS_EXPLAIN'), $this->get_content());
// Select admin
$form = $crawler->selectButton($this->lang('SUBMIT'))->form();
$data = array('username[0]' => 'admin');
$form->setValues($data);
- $crawler = $this->client->submit($form);
- $this->assert_response_success();
+ $crawler = self::submit($form);
$this->assertContains($this->lang('ACL_SET'), $crawler->filter('h1')->eq(1)->text());
}
@@ -91,8 +88,7 @@ class phpbb_functional_acp_permissions_test extends phpbb_functional_test_case
public function test_change_permission($description, $permission_type, $permission, $mode, $object_name, $object_id)
{
// Get the form
- $crawler = $this->request('GET', "adm/index.php?i=acp_permissions&icat=16&mode=$mode&${object_name}[0]=$object_id&type=$permission_type&sid=" . $this->sid);
- $this->assert_response_success();
+ $crawler = self::request('GET', "adm/index.php?i=acp_permissions&icat=16&mode=$mode&${object_name}[0]=$object_id&type=$permission_type&sid=" . $this->sid);
$this->assertContains($this->lang('ACL_SET'), $crawler->filter('h1')->eq(1)->text());
// XXX globals for phpbb_auth, refactor it later
@@ -114,8 +110,7 @@ class phpbb_functional_acp_permissions_test extends phpbb_functional_test_case
// set to never
$data = array("setting[$object_id][0][$permission]" => '0');
$form->setValues($data);
- $crawler = $this->client->submit($form);
- $this->assert_response_success();
+ $crawler = self::submit($form);
$this->assertContains($this->lang('AUTH_UPDATED'), $crawler->text());
// check acl again
diff --git a/tests/functional/auth_test.php b/tests/functional/auth_test.php
index f92a4a2210..ff4d3ced5c 100644
--- a/tests/functional/auth_test.php
+++ b/tests/functional/auth_test.php
@@ -17,8 +17,7 @@ class phpbb_functional_auth_test extends phpbb_functional_test_case
$this->login();
// check for logout link
- $crawler = $this->request('GET', 'index.php');
- $this->assert_response_success();
+ $crawler = self::request('GET', 'index.php');
$this->assertContains($this->lang('LOGOUT_USER', 'admin'), $crawler->filter('.navbar')->text());
}
@@ -26,8 +25,7 @@ class phpbb_functional_auth_test extends phpbb_functional_test_case
{
$this->create_user('anothertestuser');
$this->login('anothertestuser');
- $crawler = $this->request('GET', 'index.php');
- $this->assert_response_success();
+ $crawler = self::request('GET', 'index.php');
$this->assertContains('anothertestuser', $crawler->filter('.icon-logout')->text());
}
@@ -40,13 +38,11 @@ class phpbb_functional_auth_test extends phpbb_functional_test_case
$this->add_lang('ucp');
// logout
- $crawler = $this->request('GET', 'ucp.php?sid=' . $this->sid . '&mode=logout');
- $this->assert_response_success();
+ $crawler = self::request('GET', 'ucp.php?sid=' . $this->sid . '&mode=logout');
$this->assertContains($this->lang('LOGOUT_REDIRECT'), $crawler->filter('#message')->text());
// look for a register link, which should be visible only when logged out
- $crawler = $this->request('GET', 'index.php');
- $this->assert_response_success();
+ $crawler = self::request('GET', 'index.php');
$this->assertContains($this->lang('REGISTER'), $crawler->filter('.navbar')->text());
}
@@ -56,8 +52,7 @@ class phpbb_functional_auth_test extends phpbb_functional_test_case
$this->admin_login();
// check that we are logged in
- $crawler = $this->request('GET', 'adm/index.php?sid=' . $this->sid);
- $this->assert_response_success();
+ $crawler = self::request('GET', 'adm/index.php?sid=' . $this->sid);
$this->assertContains($this->lang('ADMIN_PANEL'), $crawler->filter('h1')->text());
}
}
diff --git a/tests/functional/browse_test.php b/tests/functional/browse_test.php
index b5748059c6..18a2ad9464 100644
--- a/tests/functional/browse_test.php
+++ b/tests/functional/browse_test.php
@@ -14,22 +14,19 @@ class phpbb_functional_browse_test extends phpbb_functional_test_case
{
public function test_index()
{
- $crawler = $this->request('GET', 'index.php');
- $this->assert_response_success();
+ $crawler = self::request('GET', 'index.php');
$this->assertGreaterThan(0, $crawler->filter('.topiclist')->count());
}
public function test_viewforum()
{
- $crawler = $this->request('GET', 'viewforum.php?f=2');
- $this->assert_response_success();
+ $crawler = self::request('GET', 'viewforum.php?f=2');
$this->assertGreaterThan(0, $crawler->filter('.topiclist')->count());
}
public function test_viewtopic()
{
- $crawler = $this->request('GET', 'viewtopic.php?t=1');
- $this->assert_response_success();
+ $crawler = self::request('GET', 'viewtopic.php?t=1');
$this->assertGreaterThan(0, $crawler->filter('.postbody')->count());
}
}
diff --git a/tests/functional/common_groups_test.php b/tests/functional/common_groups_test.php
new file mode 100644
index 0000000000..8c014aebed
--- /dev/null
+++ b/tests/functional/common_groups_test.php
@@ -0,0 +1,63 @@
+<?php
+/**
+*
+* @package testing
+* @copyright (c) 2013 phpBB Group
+* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2
+*
+*/
+
+/**
+* @group functional
+*/
+abstract class phpbb_functional_common_groups_test extends phpbb_functional_test_case
+{
+ abstract protected function get_url();
+
+ /**
+ * Get group_manage form
+ * @param int $group_id ID of the group that should be managed
+ */
+ protected function get_group_manage_form($group_id = 5)
+ {
+ // Manage Administrators group
+ $crawler = self::request('GET', $this->get_url() . "&g=$group_id&sid=" . $this->sid);
+ $form = $crawler->selectButton($this->lang('SUBMIT'))->form();
+ return $form;
+ }
+
+ /**
+ * Execute login calls and add_lang() calls for tests
+ */
+ protected function group_manage_login()
+ {
+ $this->login();
+ $this->admin_login();
+ $this->add_lang(array('ucp', 'acp/groups'));
+ }
+
+ public function groups_manage_test_data()
+ {
+ return array(
+ array('', 'GROUP_UPDATED'),
+ array('aa0000', 'GROUP_UPDATED'),
+
+ array('AAG000','WRONG_DATA_COLOUR'),
+ array('#AA0000', 'WRONG_DATA_COLOUR'),
+ );
+ }
+
+ /**
+ * @dataProvider groups_manage_test_data
+ */
+ public function test_groups_manage($input, $expected)
+ {
+ $this->group_manage_login();
+
+ // Manage Administrators group
+ $form = $this->get_group_manage_form();
+ $form['group_colour']->setValue($input);
+ $crawler = self::submit($form);
+ $this->assertContains($this->lang($expected), $crawler->text());
+ }
+}
diff --git a/tests/functional/extension_acp_test.php b/tests/functional/extension_acp_test.php
index 1879cbd62c..8614c0c963 100644
--- a/tests/functional/extension_acp_test.php
+++ b/tests/functional/extension_acp_test.php
@@ -12,34 +12,25 @@
*/
class phpbb_functional_extension_acp_test extends phpbb_functional_test_case
{
- static private $copied_files = array();
static private $helper;
- /**
- * This should only be called once before the tests are run.
- * This is used to copy the extensions to the phpBB install
- */
+ static protected $fixtures = array(
+ './',
+ );
+
static public function setUpBeforeClass()
{
- global $phpbb_root_path;
-
parent::setUpBeforeClass();
self::$helper = new phpbb_test_case_helpers(self);
+ self::$helper->copy_ext_fixtures(dirname(__FILE__) . '/../extension/ext/', self::$fixtures);
+ }
- self::$copied_files = array();
-
- if (file_exists($phpbb_root_path . 'ext/'))
- {
- // First, move any extensions setup on the board to a temp directory
- self::$copied_files = self::$helper->copy_dir($phpbb_root_path . 'ext/', $phpbb_root_path . 'store/temp_ext/');
-
- // Then empty the ext/ directory on the board (for accurate test cases)
- self::$helper->empty_dir($phpbb_root_path . 'ext/');
- }
+ static public function tearDownAfterClass()
+ {
+ parent::tearDownAfterClass();
- // Copy our ext/ files from the test case to the board
- self::$copied_files = array_merge(self::$copied_files, self::$helper->copy_dir(dirname(__FILE__) . '/../extension/ext/', $phpbb_root_path . 'ext/'));
+ self::$helper->restore_original_ext_dir();
}
public function setUp()
@@ -84,35 +75,12 @@ class phpbb_functional_extension_acp_test extends phpbb_functional_test_case
$this->add_lang('acp/extensions');
}
- /**
- * This should only be called once after the tests are run.
- * This is used to remove the files copied to the phpBB install
- */
- static public function tearDownAfterClass()
- {
- global $phpbb_root_path;
-
- if (file_exists($phpbb_root_path . 'store/temp_ext/'))
- {
- // Copy back the board installed extensions from the temp directory
- self::$helper->copy_dir($phpbb_root_path . 'store/temp_ext/', $phpbb_root_path . 'ext/');
- }
-
- // Remove all of the files we copied around (from board ext -> temp_ext, from test ext -> board ext)
- self::$helper->remove_files(self::$copied_files);
-
- if (file_exists($phpbb_root_path . 'store/temp_ext/'))
- {
- self::$helper->empty_dir($phpbb_root_path . 'store/temp_ext/');
- }
- }
-
public function test_list()
{
- $crawler = $this->request('GET', 'adm/index.php?i=acp_extensions&mode=main&sid=' . $this->sid);
+ $crawler = self::request('GET', 'adm/index.php?i=acp_extensions&mode=main&sid=' . $this->sid);
$this->assertCount(1, $crawler->filter('.ext_enabled'));
- $this->assertCount(4, $crawler->filter('.ext_disabled'));
+ $this->assertCount(5, $crawler->filter('.ext_disabled'));
$this->assertContains('phpBB Foo Extension', $crawler->filter('.ext_enabled')->eq(0)->text());
$this->assertContainsLang('PURGE', $crawler->filter('.ext_enabled')->eq(0)->text());
@@ -131,7 +99,7 @@ class phpbb_functional_extension_acp_test extends phpbb_functional_test_case
public function test_details()
{
- $crawler = $this->request('GET', 'adm/index.php?i=acp_extensions&mode=main&action=details&ext_name=foo&sid=' . $this->sid);
+ $crawler = self::request('GET', 'adm/index.php?i=acp_extensions&mode=main&action=details&ext_name=foo&sid=' . $this->sid);
$validation = array(
'DISPLAY_NAME' => 'phpBB Foo Extension',
@@ -174,46 +142,46 @@ class phpbb_functional_extension_acp_test extends phpbb_functional_test_case
public function test_enable_pre()
{
// Foo is already enabled (redirect to list)
- $crawler = $this->request('GET', 'adm/index.php?i=acp_extensions&mode=main&action=enable_pre&ext_name=foo&sid=' . $this->sid);
+ $crawler = self::request('GET', 'adm/index.php?i=acp_extensions&mode=main&action=enable_pre&ext_name=foo&sid=' . $this->sid);
$this->assertContainsLang('EXTENSION_NAME', $crawler->filter('html')->text());
$this->assertContainsLang('EXTENSION_OPTIONS', $crawler->filter('html')->text());
$this->assertContainsLang('EXTENSION_ACTIONS', $crawler->filter('html')->text());
- $crawler = $this->request('GET', 'adm/index.php?i=acp_extensions&mode=main&action=enable_pre&ext_name=vendor%2Fmoo&sid=' . $this->sid);
+ $crawler = self::request('GET', 'adm/index.php?i=acp_extensions&mode=main&action=enable_pre&ext_name=vendor%2Fmoo&sid=' . $this->sid);
$this->assertContainsLang('ENABLE_CONFIRM', $crawler->filter('html')->text());
}
public function test_disable_pre()
{
// Moo is not enabled (redirect to list)
- $crawler = $this->request('GET', 'adm/index.php?i=acp_extensions&mode=main&action=disable_pre&ext_name=vendor%2Fmoo&sid=' . $this->sid);
+ $crawler = self::request('GET', 'adm/index.php?i=acp_extensions&mode=main&action=disable_pre&ext_name=vendor%2Fmoo&sid=' . $this->sid);
$this->assertContainsLang('EXTENSION_NAME', $crawler->filter('html')->text());
$this->assertContainsLang('EXTENSION_OPTIONS', $crawler->filter('html')->text());
$this->assertContainsLang('EXTENSION_ACTIONS', $crawler->filter('html')->text());
- $crawler = $this->request('GET', 'adm/index.php?i=acp_extensions&mode=main&action=disable_pre&ext_name=foo&sid=' . $this->sid);
+ $crawler = self::request('GET', 'adm/index.php?i=acp_extensions&mode=main&action=disable_pre&ext_name=foo&sid=' . $this->sid);
$this->assertContainsLang('DISABLE_CONFIRM', $crawler->filter('html')->text());
}
public function test_purge_pre()
{
// test2 is not available (error)
- $crawler = $this->request('GET', 'adm/index.php?i=acp_extensions&mode=main&action=purge_pre&ext_name=test2&sid=' . $this->sid);
+ $crawler = self::request('GET', 'adm/index.php?i=acp_extensions&mode=main&action=purge_pre&ext_name=test2&sid=' . $this->sid);
$this->assertContains('The required file does not exist', $crawler->filter('html')->text());
- $crawler = $this->request('GET', 'adm/index.php?i=acp_extensions&mode=main&action=purge_pre&ext_name=foo&sid=' . $this->sid);
+ $crawler = self::request('GET', 'adm/index.php?i=acp_extensions&mode=main&action=purge_pre&ext_name=foo&sid=' . $this->sid);
$this->assertContainsLang('PURGE_CONFIRM', $crawler->filter('html')->text());
}
public function test_actions()
{
- $crawler = $this->request('GET', 'adm/index.php?i=acp_extensions&mode=main&action=enable&ext_name=vendor%2Fmoo&sid=' . $this->sid);
+ $crawler = self::request('GET', 'adm/index.php?i=acp_extensions&mode=main&action=enable&ext_name=vendor%2Fmoo&sid=' . $this->sid);
$this->assertContainsLang('ENABLE_SUCCESS', $crawler->filter('html')->text());
- $crawler = $this->request('GET', 'adm/index.php?i=acp_extensions&mode=main&action=disable&ext_name=vendor%2Fmoo&sid=' . $this->sid);
+ $crawler = self::request('GET', 'adm/index.php?i=acp_extensions&mode=main&action=disable&ext_name=vendor%2Fmoo&sid=' . $this->sid);
$this->assertContainsLang('DISABLE_SUCCESS', $crawler->filter('html')->text());
- $crawler = $this->request('GET', 'adm/index.php?i=acp_extensions&mode=main&action=purge&ext_name=vendor%2Fmoo&sid=' . $this->sid);
+ $crawler = self::request('GET', 'adm/index.php?i=acp_extensions&mode=main&action=purge&ext_name=vendor%2Fmoo&sid=' . $this->sid);
$this->assertContainsLang('PURGE_SUCCESS', $crawler->filter('html')->text());
}
} \ No newline at end of file
diff --git a/tests/functional/extension_controller_test.php b/tests/functional/extension_controller_test.php
index f28b321942..9ddf1e3e5c 100644
--- a/tests/functional/extension_controller_test.php
+++ b/tests/functional/extension_controller_test.php
@@ -6,6 +6,7 @@
* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2
*
*/
+require_once dirname(__FILE__) . '/../../phpBB/includes/db/db_tools.php';
/**
* @group functional
@@ -14,65 +15,27 @@ class phpbb_functional_extension_controller_test extends phpbb_functional_test_c
{
protected $phpbb_extension_manager;
+ static private $helper;
+
static protected $fixtures = array(
- 'foo/bar/config/routing.yml',
- 'foo/bar/config/services.yml',
- 'foo/bar/controller/controller.php',
- 'foo/bar/styles/prosilver/template/foo_bar_body.html',
+ 'foo/bar/config/',
+ 'foo/bar/controller/',
+ 'foo/bar/styles/prosilver/template/',
);
- /**
- * This should only be called once before the tests are run.
- * This is used to copy the fixtures to the phpBB install
- */
static public function setUpBeforeClass()
{
- global $phpbb_root_path;
parent::setUpBeforeClass();
- $directories = array(
- $phpbb_root_path . 'ext/foo/bar/',
- $phpbb_root_path . 'ext/foo/bar/config/',
- $phpbb_root_path . 'ext/foo/bar/controller/',
- $phpbb_root_path . 'ext/foo/bar/styles/prosilver/template',
- );
-
- foreach ($directories as $dir)
- {
- if (!is_dir($dir))
- {
- mkdir($dir, 0777, true);
- }
- }
-
- foreach (self::$fixtures as $fixture)
- {
- copy(
- "tests/functional/fixtures/ext/$fixture",
- "{$phpbb_root_path}ext/$fixture");
- }
+ self::$helper = new phpbb_test_case_helpers(self);
+ self::$helper->copy_ext_fixtures(dirname(__FILE__) . '/fixtures/ext/', self::$fixtures);
}
- /**
- * This should only be called once after the tests are run.
- * This is used to remove the fixtures from the phpBB install
- */
static public function tearDownAfterClass()
{
- global $phpbb_root_path;
-
- foreach (self::$fixtures as $fixture)
- {
- unlink("{$phpbb_root_path}ext/$fixture");
- }
+ parent::tearDownAfterClass();
- rmdir("{$phpbb_root_path}ext/foo/bar/config");
- rmdir("{$phpbb_root_path}ext/foo/bar/controller");
- rmdir("{$phpbb_root_path}ext/foo/bar/styles/prosilver/template");
- rmdir("{$phpbb_root_path}ext/foo/bar/styles/prosilver");
- rmdir("{$phpbb_root_path}ext/foo/bar/styles");
- rmdir("{$phpbb_root_path}ext/foo/bar");
- rmdir("{$phpbb_root_path}ext/foo");
+ self::$helper->restore_original_ext_dir();
}
public function setUp()
@@ -90,8 +53,8 @@ class phpbb_functional_extension_controller_test extends phpbb_functional_test_c
public function test_foo_bar()
{
$this->phpbb_extension_manager->enable('foo/bar');
- $crawler = $this->request('GET', 'app.php?controller=foo/bar');
- $this->assert_response_success();
+ $crawler = self::request('GET', 'app.php?controller=foo/bar', array(), false);
+ self::assert_response_status_code();
$this->assertContains("foo/bar controller handle() method", $crawler->filter('body')->text());
$this->phpbb_extension_manager->purge('foo/bar');
}
@@ -102,8 +65,7 @@ class phpbb_functional_extension_controller_test extends phpbb_functional_test_c
public function test_controller_with_template()
{
$this->phpbb_extension_manager->enable('foo/bar');
- $crawler = $this->request('GET', 'app.php?controller=foo/template');
- $this->assert_response_success();
+ $crawler = self::request('GET', 'app.php?controller=foo/template');
$this->assertContains("I am a variable", $crawler->filter('#content')->text());
$this->phpbb_extension_manager->purge('foo/bar');
}
@@ -115,8 +77,8 @@ class phpbb_functional_extension_controller_test extends phpbb_functional_test_c
public function test_missing_argument()
{
$this->phpbb_extension_manager->enable('foo/bar');
- $crawler = $this->request('GET', 'app.php?controller=foo/baz');
- $this->assertEquals(500, $this->client->getResponse()->getStatus());
+ $crawler = self::request('GET', 'app.php?controller=foo/baz', array(), false);
+ $this->assert_response_html(500);
$this->assertContains('Missing value for argument #1: test in class phpbb_ext_foo_bar_controller:baz', $crawler->filter('body')->text());
$this->phpbb_extension_manager->purge('foo/bar');
}
@@ -127,8 +89,8 @@ class phpbb_functional_extension_controller_test extends phpbb_functional_test_c
public function test_exception_should_result_in_500_status_code()
{
$this->phpbb_extension_manager->enable('foo/bar');
- $crawler = $this->request('GET', 'app.php?controller=foo/exception');
- $this->assertEquals(500, $this->client->getResponse()->getStatus());
+ $crawler = self::request('GET', 'app.php?controller=foo/exception', array(), false);
+ $this->assert_response_html(500);
$this->assertContains('Exception thrown from foo/exception route', $crawler->filter('body')->text());
$this->phpbb_extension_manager->purge('foo/bar');
}
@@ -144,8 +106,8 @@ class phpbb_functional_extension_controller_test extends phpbb_functional_test_c
*/
public function test_error_ext_disabled_or_404()
{
- $crawler = $this->request('GET', 'app.php?controller=does/not/exist');
- $this->assertEquals(404, $this->client->getResponse()->getStatus());
+ $crawler = self::request('GET', 'app.php?controller=does/not/exist', array(), false);
+ $this->assert_response_html(404);
$this->assertContains('No route found for "GET /does/not/exist"', $crawler->filter('body')->text());
}
}
diff --git a/tests/functional/extension_module_test.php b/tests/functional/extension_module_test.php
new file mode 100644
index 0000000000..c573ea5410
--- /dev/null
+++ b/tests/functional/extension_module_test.php
@@ -0,0 +1,99 @@
+<?php
+/**
+*
+* @package testing
+* @copyright (c) 2013 phpBB Group
+* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2
+*
+*/
+
+require_once dirname(__FILE__) . '/../../phpBB/includes/db/db_tools.php';
+require_once dirname(__FILE__) . '/../../phpBB/includes/acp/acp_modules.php';
+
+/**
+* @group functional
+*/
+class phpbb_functional_extension_module_test extends phpbb_functional_test_case
+{
+ protected $phpbb_extension_manager;
+
+ static private $helper;
+
+ static protected $fixtures = array(
+ './',
+ );
+
+ static public function setUpBeforeClass()
+ {
+ parent::setUpBeforeClass();
+
+ self::$helper = new phpbb_test_case_helpers(self);
+ self::$helper->copy_ext_fixtures(dirname(__FILE__) . '/fixtures/ext/', self::$fixtures);
+ }
+
+ static public function tearDownAfterClass()
+ {
+ parent::tearDownAfterClass();
+
+ self::$helper->restore_original_ext_dir();
+ }
+
+ public function setUp()
+ {
+ global $db;
+
+ parent::setUp();
+
+ $this->phpbb_extension_manager = $this->get_extension_manager();
+ $this->phpbb_extension_manager->enable('foo/bar');
+
+ $modules = new acp_modules();
+ $db = $this->get_db();
+
+ $sql = 'SELECT module_id
+ FROM ' . MODULES_TABLE . "
+ WHERE module_langname = 'acp'
+ AND module_class = 'ACP_CAT_DOT_MODS'";
+ $result = $db->sql_query($sql);
+ $module_id = (int) $db->sql_fetchfield('module_id');
+ $db->sql_freeresult($result);
+
+ $parent_data = array(
+ 'module_basename' => '',
+ 'module_enabled' => 1,
+ 'module_display' => 1,
+ 'parent_id' => $module_id,
+ 'module_class' => 'acp',
+ 'module_langname' => 'ACP_FOOBAR_TITLE',
+ 'module_mode' => '',
+ 'module_auth' => '',
+ );
+ $modules->update_module_data($parent_data, true);
+
+ $module_data = array(
+ 'module_basename' => 'phpbb_ext_foo_bar_acp_main_module',
+ 'module_enabled' => 1,
+ 'module_display' => 1,
+ 'parent_id' => $parent_data['module_id'],
+ 'module_class' => 'acp',
+ 'module_langname' => 'ACP_FOOBAR_TITLE',
+ 'module_mode' => 'mode',
+ 'module_auth' => '',
+ );
+ $modules->update_module_data($module_data, true);
+
+ $this->purge_cache();
+ }
+
+ /**
+ * Check a controller for extension foo/bar.
+ */
+ public function test_foo_bar()
+ {
+ $this->login();
+ $this->admin_login();
+ $crawler = self::request('GET', 'adm/index.php?i=phpbb_ext_foo_bar_acp_main_module&mode=mode&sid=' . $this->sid);
+ $this->assertContains("Bertie rulez!", $crawler->filter('#main')->text());
+ $this->phpbb_extension_manager->purge('foo/bar');
+ }
+}
diff --git a/tests/functional/extension_permission_lang_test.php b/tests/functional/extension_permission_lang_test.php
new file mode 100644
index 0000000000..6c1720735c
--- /dev/null
+++ b/tests/functional/extension_permission_lang_test.php
@@ -0,0 +1,80 @@
+<?php
+/**
+*
+* @package testing
+* @copyright (c) 2012 phpBB Group
+* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2
+*
+*/
+
+/**
+* @group functional
+*/
+class phpbb_functional_extension_permission_lang_test extends phpbb_functional_test_case
+{
+ protected $phpbb_extension_manager;
+
+ static private $helper;
+
+ static protected $fixtures = array(
+ 'foo/bar/language/en/',
+ );
+
+ static public function setUpBeforeClass()
+ {
+ parent::setUpBeforeClass();
+
+ self::$helper = new phpbb_test_case_helpers(self);
+ self::$helper->copy_ext_fixtures(dirname(__FILE__) . '/fixtures/ext/', self::$fixtures);
+ }
+
+ static public function tearDownAfterClass()
+ {
+ parent::tearDownAfterClass();
+
+ self::$helper->restore_original_ext_dir();
+ }
+
+ public function setUp()
+ {
+ parent::setUp();
+
+ $this->get_db();
+
+ $acl_ary = array(
+ 'auth_option' => 'u_foo',
+ 'is_global' => 1,
+ );
+
+ $sql = 'INSERT INTO phpbb_acl_options ' . $this->db->sql_build_array('INSERT', $acl_ary);
+ $this->db->sql_query($sql);
+
+ $this->phpbb_extension_manager = $this->get_extension_manager();
+
+ $this->purge_cache();
+
+ $this->login();
+ $this->admin_login();
+ $this->add_lang('acp/permissions');
+ }
+
+ public function test_auto_include_permission_lang_from_extensions()
+ {
+ $this->phpbb_extension_manager->enable('foo/bar');
+
+ // User permissions
+ $crawler = self::request('GET', 'adm/index.php?i=acp_permissions&icat=16&mode=setting_user_global&sid=' . $this->sid);
+
+ // Select admin
+ $form = $crawler->selectButton($this->lang('SUBMIT'))->form();
+ $data = array('username[0]' => 'admin');
+ $form->setValues($data);
+ $crawler = self::submit($form);
+
+ // language from language/en/acp/permissions_phpbb.php
+ $this->assertContains('Can attach files', $crawler->filter('body')->text());
+
+ // language from ext/foo/bar/language/en/permissions_foo.php
+ $this->assertContains('Can view foo', $crawler->filter('body')->text());
+ }
+}
diff --git a/tests/functional/fileupload_form_test.php b/tests/functional/fileupload_form_test.php
index b663c89e95..998c402fa3 100644
--- a/tests/functional/fileupload_form_test.php
+++ b/tests/functional/fileupload_form_test.php
@@ -32,7 +32,7 @@ class phpbb_functional_fileupload_form_test extends phpbb_functional_test_case
'error' => UPLOAD_ERR_OK,
);
- $crawler = $this->client->request(
+ $crawler = self::$client->request(
'POST',
'posting.php?mode=reply&f=2&t=1&sid=' . $this->sid,
array('add_file' => $this->lang('ADD_FILE')),
@@ -66,9 +66,8 @@ class phpbb_functional_fileupload_form_test extends phpbb_functional_test_case
{
$this->markTestIncomplete('Test fails intermittently.');
$crawler = $this->upload_file('valid.jpg', 'image/jpeg');
- $this->assert_response_success();
// ensure there was no error message rendered
- $this->assertNotContains('<h2>' . $this->lang('INFORMATION') . '</h2>', $this->client->getResponse()->getContent());
+ $this->assertNotContains('<h2>' . $this->lang('INFORMATION') . '</h2>', $this->get_content());
$this->assertContains($this->lang('POSTED_ATTACHMENTS'), $crawler->filter('#postform h3')->eq(1)->text());
}
}
diff --git a/tests/functional/fileupload_remote_test.php b/tests/functional/fileupload_remote_test.php
index 0deb79acf6..8e361ab77b 100644
--- a/tests/functional/fileupload_remote_test.php
+++ b/tests/functional/fileupload_remote_test.php
@@ -58,7 +58,7 @@ class phpbb_functional_fileupload_remote_test extends phpbb_functional_test_case
public function test_successful_upload()
{
$upload = new fileupload('', array('gif'), 1000);
- $file = $upload->remote_upload($this->root_url . 'styles/prosilver/theme/images/forum_read.gif');
+ $file = $upload->remote_upload(self::$root_url . 'styles/prosilver/theme/images/forum_read.gif');
$this->assertEquals(0, sizeof($file->error));
$this->assertTrue(file_exists($file->filename));
}
@@ -66,7 +66,8 @@ class phpbb_functional_fileupload_remote_test extends phpbb_functional_test_case
public function test_too_large()
{
$upload = new fileupload('', array('gif'), 100);
- $file = $upload->remote_upload($this->root_url . 'styles/prosilver/theme/images/forum_read.gif');
+ $file = $upload->remote_upload(self::$root_url . 'styles/prosilver/theme/images/forum_read.gif');
+ $this->assertEquals(1, sizeof($file->error));
$this->assertEquals('WRONG_FILESIZE', $file->error[0]);
}
}
diff --git a/tests/functional/fixtures/ext/foo/bar/acp/main_info.php b/tests/functional/fixtures/ext/foo/bar/acp/main_info.php
new file mode 100644
index 0000000000..21e38b09b5
--- /dev/null
+++ b/tests/functional/fixtures/ext/foo/bar/acp/main_info.php
@@ -0,0 +1,32 @@
+<?php
+
+/**
+*
+* @package testing
+* @copyright (c) 2013 phpBB Group
+* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2
+*
+*/
+
+/**
+* @ignore
+*/
+if (!defined('IN_PHPBB'))
+{
+ exit;
+}
+
+class phpbb_ext_foo_bar_acp_main_info
+{
+ function module()
+ {
+ return array(
+ 'filename' => 'phpbb_ext_foo_bar_acp_main_module',
+ 'title' => 'ACP_FOOBAR_TITLE',
+ 'version' => '1.0.0',
+ 'modes' => array(
+ 'mode' => array('title' => 'ACP_FOOBAR_MODE', 'auth' => '', 'cat' => array('ACP_FOOBAR_TITLE')),
+ ),
+ );
+ }
+}
diff --git a/tests/functional/fixtures/ext/foo/bar/acp/main_module.php b/tests/functional/fixtures/ext/foo/bar/acp/main_module.php
new file mode 100644
index 0000000000..c4ab69fb38
--- /dev/null
+++ b/tests/functional/fixtures/ext/foo/bar/acp/main_module.php
@@ -0,0 +1,28 @@
+<?php
+
+/**
+*
+* @package testing
+* @copyright (c) 2013 phpBB Group
+* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2
+*
+*/
+
+/**
+* @ignore
+*/
+if (!defined('IN_PHPBB'))
+{
+ exit;
+}
+
+class phpbb_ext_foo_bar_acp_main_module
+{
+ var $u_action;
+
+ function main($id, $mode)
+ {
+ $this->tpl_name = 'foobar';
+ $this->page_title = 'Bertie';
+ }
+}
diff --git a/tests/functional/fixtures/ext/foo/bar/adm/style/foobar.html b/tests/functional/fixtures/ext/foo/bar/adm/style/foobar.html
new file mode 100644
index 0000000000..3cb45c269c
--- /dev/null
+++ b/tests/functional/fixtures/ext/foo/bar/adm/style/foobar.html
@@ -0,0 +1,3 @@
+<!-- INCLUDE overall_header.html -->
+Bertie rulez!
+<!-- INCLUDE overall_footer.html -->
diff --git a/tests/functional/fixtures/ext/foo/bar/composer.json b/tests/functional/fixtures/ext/foo/bar/composer.json
new file mode 100644
index 0000000000..067a9d38eb
--- /dev/null
+++ b/tests/functional/fixtures/ext/foo/bar/composer.json
@@ -0,0 +1,23 @@
+{
+ "name": "foo/bar",
+ "type": "phpbb3-extension",
+ "description": "Testing extensions",
+ "homepage": "",
+ "version": "1.0.0",
+ "time": "2013-03-21 01:01:01",
+ "licence": "GPL-2.0",
+ "authors": [{
+ "name": "Joas Schilling",
+ "username": "nickvergessen",
+ "email": "nickvergessen@phpbb.com",
+ "homepage": "http://www.phpbb.com",
+ "role": "Developer"
+ }],
+ "require": {
+ "php": ">=5.3",
+ "phpbb": ">=3.1.0-dev"
+ },
+ "extra": {
+ "display-name": "phpBB 3.1 Extension Testing"
+ }
+}
diff --git a/tests/functional/fixtures/ext/foo/bar/language/en/permissions_foo.php b/tests/functional/fixtures/ext/foo/bar/language/en/permissions_foo.php
new file mode 100644
index 0000000000..cd4b9a32d1
--- /dev/null
+++ b/tests/functional/fixtures/ext/foo/bar/language/en/permissions_foo.php
@@ -0,0 +1,6 @@
+<?php
+
+// Admin Permissions
+$lang = array_merge($lang, array(
+ 'acl_u_foo' => array('lang' => 'Can view foo', 'cat' => 'misc'),
+));
diff --git a/tests/functional/forgot_password_test.php b/tests/functional/forgot_password_test.php
new file mode 100644
index 0000000000..906224efbb
--- /dev/null
+++ b/tests/functional/forgot_password_test.php
@@ -0,0 +1,44 @@
+<?php
+/**
+*
+* @package testing
+* @copyright (c) 2012 phpBB Group
+* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2
+*
+*/
+
+/**
+* @group functional
+*/
+class phpbb_functional_forgot_password_test extends phpbb_functional_test_case
+{
+ public function test_forgot_password_enabled()
+ {
+ global $config;
+ $this->add_lang('ucp');
+ $crawler = self::request('GET', 'ucp.php?mode=sendpassword');
+ $this->assertEquals($this->lang('SEND_PASSWORD'), $crawler->filter('h2')->text());
+ }
+
+ public function test_forgot_password_disabled()
+ {
+ $this->login();
+ $this->admin_login();
+ $this->add_lang('ucp');
+ $crawler = self::request('GET', 'adm/index.php?sid=' . $this->sid . '&i=acp_board&mode=security');
+
+ $form = $crawler->selectButton('Submit')->form();
+ $values = $form->getValues();
+
+ $values["config[allow_password_reset]"] = 0;
+ $form->setValues($values);
+ $crawler = self::submit($form);
+
+ $this->logout();
+
+ $crawler = self::request('GET', 'ucp.php?mode=sendpassword');
+ $this->assertContains($this->lang('UCP_PASSWORD_RESET_DISABLED', '', ''), $crawler->text());
+
+ }
+
+}
diff --git a/tests/functional/memberlist_test.php b/tests/functional/memberlist_test.php
index 92ede8bd04..738ec4f9dd 100644
--- a/tests/functional/memberlist_test.php
+++ b/tests/functional/memberlist_test.php
@@ -17,18 +17,15 @@ class phpbb_functional_memberlist_test extends phpbb_functional_test_case
$this->create_user('memberlist-test-user');
// logs in as admin
$this->login();
- $crawler = $this->request('GET', 'memberlist.php?sid=' . $this->sid);
- $this->assert_response_success();
+ $crawler = self::request('GET', 'memberlist.php?sid=' . $this->sid);
$this->assertContains('memberlist-test-user', $crawler->text());
// restrict by first character
- $crawler = $this->request('GET', 'memberlist.php?first_char=m&sid=' . $this->sid);
- $this->assert_response_success();
+ $crawler = self::request('GET', 'memberlist.php?first_char=m&sid=' . $this->sid);
$this->assertContains('memberlist-test-user', $crawler->text());
// make sure results for wrong character are not returned
- $crawler = $this->request('GET', 'memberlist.php?first_char=a&sid=' . $this->sid);
- $this->assert_response_success();
+ $crawler = self::request('GET', 'memberlist.php?first_char=a&sid=' . $this->sid);
$this->assertNotContains('memberlist-test-user', $crawler->text());
}
@@ -36,16 +33,13 @@ class phpbb_functional_memberlist_test extends phpbb_functional_test_case
{
$this->login();
// XXX hardcoded user id
- $crawler = $this->request('GET', 'memberlist.php?mode=viewprofile&u=2&sid=' . $this->sid);
- $this->assert_response_success();
+ $crawler = self::request('GET', 'memberlist.php?mode=viewprofile&u=2&sid=' . $this->sid);
$this->assertContains('admin', $crawler->filter('h2')->text());
}
protected function get_memberlist_leaders_table_crawler()
{
- $crawler = $this->request('GET', 'memberlist.php?mode=leaders&sid=' . $this->sid);
- $this->assert_response_success();
-
+ $crawler = self::request('GET', 'memberlist.php?mode=leaders&sid=' . $this->sid);
return $crawler->filter('.forumbg-table');
}
@@ -95,5 +89,17 @@ class phpbb_functional_memberlist_test extends phpbb_functional_test_case
$crawler = $this->get_memberlist_leaders_table_crawler();
$this->assertNotContains('memberlist-test-moderator', $crawler->eq(0)->text());
$this->assertContains('memberlist-test-moderator', $crawler->eq(1)->text());
+
+ // Add admin to moderators, should be visible as moderator
+ $this->add_user_group('GLOBAL_MODERATORS', array('admin'), true);
+ $crawler = $this->get_memberlist_leaders_table_crawler();
+ $this->assertNotContains('admin', $crawler->eq(0)->text());
+ $this->assertContains('admin', $crawler->eq(1)->text());
+
+ // Add admin to admins as leader, should be visible as admin, not moderator
+ $this->add_user_group('ADMINISTRATORS', array('admin'), true, true);
+ $crawler = $this->get_memberlist_leaders_table_crawler();
+ $this->assertContains('admin', $crawler->eq(0)->text());
+ $this->assertNotContains('admin', $crawler->eq(1)->text());
}
}
diff --git a/tests/functional/metadata_manager_test.php b/tests/functional/metadata_manager_test.php
new file mode 100644
index 0000000000..c55e7373ea
--- /dev/null
+++ b/tests/functional/metadata_manager_test.php
@@ -0,0 +1,85 @@
+<?php
+/**
+*
+* @package testing
+* @copyright (c) 2013 phpBB Group
+* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2
+*
+*/
+
+require_once dirname(__FILE__) . '/../../phpBB/includes/db/db_tools.php';
+
+/**
+* @group functional
+*/
+class phpbb_functional_metadata_manager_test extends phpbb_functional_test_case
+{
+ protected $phpbb_extension_manager;
+
+ static private $helper;
+
+ static protected $fixtures = array(
+ 'foo/bar/',
+ );
+
+ static public function setUpBeforeClass()
+ {
+ parent::setUpBeforeClass();
+
+ self::$helper = new phpbb_test_case_helpers(self);
+ self::$helper->copy_ext_fixtures(dirname(__FILE__) . '/fixtures/ext/', self::$fixtures);
+ }
+
+ static public function tearDownAfterClass()
+ {
+ parent::tearDownAfterClass();
+
+ self::$helper->restore_original_ext_dir();
+ }
+
+ public function setUp()
+ {
+ parent::setUp();
+
+ $this->phpbb_extension_manager = $this->get_extension_manager();
+
+ $this->purge_cache();
+ $this->phpbb_extension_manager->enable('foo/bar');
+
+ $this->login();
+ $this->admin_login();
+ $this->add_lang('acp/extensions');
+ }
+
+ public function test_extensions_list()
+ {
+ $crawler = self::request('GET', 'adm/index.php?i=acp_extensions&mode=main&sid=' . $this->sid);
+ $this->assertContains($this->lang('EXTENSIONS_EXPLAIN'), $crawler->filter('#main')->text());
+ $this->assertContains('phpBB 3.1 Extension Testing', $crawler->filter('#main')->text());
+ $this->assertContains('Details', $crawler->filter('#main')->text());
+ }
+
+ public function test_extensions_details()
+ {
+ $crawler = self::request('GET', 'adm/index.php?i=acp_extensions&mode=main&action=details&ext_name=foo%2Fbar&sid=' . $this->sid);
+
+ // Test whether the details are displayed
+ $this->assertContains($this->lang('CLEAN_NAME'), $crawler->filter('#main')->text());
+ $this->assertContains('foo/bar', $crawler->filter('#meta_name')->text());
+
+ $this->assertContains($this->lang('PHP_VERSION'), $crawler->filter('#main')->text());
+ $this->assertContains('>=5.3', $crawler->filter('#require_php')->text());
+ // Details should be html escaped
+ // However, text() only returns the displayed text, so HTML Special Chars are decoded.
+ // So we test this directly on the content of the response.
+ $this->assertContains('<p id="require_php">&gt;=5.3</p>', $this->get_content());
+ }
+
+ public function test_extensions_details_notexists()
+ {
+ $crawler = self::request('GET', 'adm/index.php?i=acp_extensions&mode=main&action=details&ext_name=not%2Fexists&sid=' . $this->sid);
+
+ // Error message because the files do not exist
+ $this->assertContains('The requi#κ~Tܼ:Ww{G ݾ le{ԥ)~uqmOkߙYf(wmJc꪿QRZ_}y߇oB???~ xw)6?~䪱6~/YRk zGz0&<b{#LMbx?a]ucmdk";?#~?$p{j}oW-sjm<UJUS"X*=[4wOjd@PS6@;./ 4 {PE@ ( @=j@k@S V{EZ| 2REI)P$Jw%* 5@sMM`{zqg}]uF-2rn:i'op`Rj:u| KQBQݚh^QU+,p}RI)Sn)sx)_!p΁=<̓eTk>KCx/fu <؛4yǾo }{>ۨٻ}sz)iiXr1@ W RhX@ @T:=:@@7u2vx'{hXH'.e}'y{[P;`rFט5eZ4{{lfAܫ5ok*xnם ZLҳ]@T ͚lgۮ^@ X[y'AYgoxo vu粒*1f3_G=(=u>ާ9tpI@קyw{:!Ϲw:]+ۇu@0 鐡c_e c E=׽Qs-QfQsD*t:Tf@48ݥ=Gt}emNųݦIby ºUep3 }.{@=]{¦lGۧ).V(iE(m:L^8>@pA6Qx>C@9V3`늢xW/8rwav۾x2s4:Y Egvxq@ PDG@UZb#냓 L.;_MݷEwN[Ga̖I1[|wHp]awcvێ,kmA|}xpܠHQTs v >fn5|RͰα 5҄P% ty')P/zP*Y!>0 P @v]t9Pwh5 @0 ii @A&L@! @iAAOș1F6Gz4dI'j)md3S ИM$ɐ%4 @@ i@)iI4dhhLzB~3TފxGMG4=#G@hIA2i@!6D)FO@Ii馚O&5O4Se<қSj='?JiQL ?T =MzD@ Fh @֟oz'ĐXt:[T :Ԋ~DTx7l^ey:~H뮤Tɱ6EMh''NDs2Kx|G\ /#֐`\ej_\8j?MTft~И#(BB?u7~ ڙ+/ݰIm ZU?ՂnD=ȯ(/E4ŸO*N%h%A_0XMrص̐O^}zlr3>(_?zx rBtCٶX ~[ ԖZ/[@lKpll[^^Rq),kaD~A=/?s7r7ynCG1+]PI'7vs0t@)X)XYI {~͡??/]).J,esy]z~&(4bBVZ-Wi?hMJƷ:4b!`|y/Kw u*aT/^؏c_40? /F.6gô|.J0~׌>_, ,WT$[|7 GT7 L: 9)@hPDYθ?MRVJ*C4JRLؐ  4DmTϣO:R/ BV: riQ4kT%BE|9"$pɚF0$PaBrr%9bA9 w-Bjxt )[\5>_98d9.qV@|b)MF$(B0?vlv%r=hunMVҦcCLm6m1SzZ)@U6ԥł{z``1yi?3ه0<ekBs  Aݒ<4v:~꫃B*Plk+!50 Nϫr:dViI1j zo:=]|2.</G]cJAaJ\@ P@ANKr NC;3s.%ghi^=<u9^`v{Lp|+JLeW"yҽEyh;D \_M /_?㢤0f8KəO>(T)#vOk/JLܛ$3 fl@"tiZTot|J<\e(Of-z `Kk⠟3*~~vc7UkyH;Qwa~{E``@osc1fj!.!:lElP&eI\ʤ#LWG:7וĴj03J=Mdz9"Ɛ(*vI(sY}6J[B[0e"!jUWIPYwS޶ӽϭ羆g8izGabx=ݿ|J /IVWؘ품 ,]̺)>&t:(Ϭ]E aSG;m7er5L(s~npqKJDZP좫E^ iөu$~*5ycB>2ϗ:ʬ'f̹Su"Rd-'(8>hͺAA~^Kqpy+ט@ŌKS Vt@SջmzH}9ٞrjcOXE<ˁ*[Y۬LMdGy_/9^ή2N!}R{#%jWdߝSok#KKߤBI\^Z|琋%%BjKSwdB JB>);}R J@Aq|C0KI?+(&mԺV ,1A#`(#  ȀXE 0T`"#dA *"R R,@b ""QHĊ21F 0E!"DdDGk__EX(" R1A+UX# P/ՙ$Z> <ڭI 4&`XqHm`,_?kt`"A`TdH+ F,"  `# @P")"AHAPAb1#$``"F 1TdFAP`"0b,`"QdR   $`(#FE!2#⧙.m42\lsMOVb1ѵbRFȨ""$A$X ԩ(Y*AF A@FDȬE<Ċ}FDHPP B!JAt}܏!{_+no[VmƶwsC&L4JR @:xBmK2a"_?%ԡQಊ}{u7-!N&S`}6p5LaV3BT`Ա5b0Pk3 )hw:c~^~ \I!Q!POؓ-pv?M^2WȔY2n)[ Sp6xk6֬蒵jPɈc< _H"0' ]ZH, L 𣒗55oMoyKկs}'dV"BCiϹ\En mu9waày`) *$Q`àb#AE@$EjaAb6" "bC!NwǗp3ANn9X<+Kn9j)bCQ3P]E{ s0XA1 @!F BHS_`3[ aSiGcVGavȡE+ܛ!14$KEzĆ[ִ$$ƚMc&4E#1#qDI$a= FĶҊ4I"2]80>@ogik/uš"`AHANEo&Av"DDU L=5&85=oZyHDcH) 1E),TYMx-B؎?[ޞzwcQ{n;o`K;9ܖOt}u?͍E[ݹjߌTK).HSf"cq2DmIZ5=T3)pJLSF k̵,7V"DdPVZ-Kaf[ŪJWQEXȀ%EaJ5V$m-QYpbXVRrbK ":LʶBb(AEZ"42)k+ aJeA``"DnQRb\U" 5Ur6q4Z fY jXeZ:D;֝t]*A#gP` m&8P0.$nÒwq[lejXD`lXX"+[q4±P]Sk)jQE.&,\((LQ"5em*V) V,-8Ѡٔ- mӂncx#_5j+QFP peVK YTYU+"U%UE )bTYF֭-дqXPKl6-.Nr.+8UեGJ[D++ZÌ)im*E R2h Ȩ[`؅-VJ,M5&S.7bT S2/q51jDAePeRRJѴ(բB َ.%bQ"1%aYFW-DAjEXX XZV08ԔiRR]8ȫmtk!,4-(pLc)rj(Q[Nl%q-2(-Z±RV*=rsr>mNADb2EbQ" V b(PbH)(ȠTAD( A X˾#E:a:zhi'-XX"w3S],> ]_5ǹyNQraXw{n{1u05u$7\#7E'[(/ ->36%0^bb[qFㆧŵDJeujv 5J*pjMT0d]7z!t;dHA"FR$Q*X-{R*C%$+˭LiحXgOҞbY.[rd3.$KŇ'sh Q0N+.`)Aܔ*dL@P20L|_:g!! TTT2~ }.\~&]-B)(,{[xrrEU܂erH"褻&-QgT=>NYd.msV:us8.#$p~&\8(YP2 ,oAKB e3k",X Sbǖ$W-'':=R+6$o2fHDI\ ASdrݕyV7/>uSE̶DCoZ *ɾq6:h=s(N)<2M{T_%i}Tx' M_, _'9iU-y8)?ms//A~^7Q%A:H)$+}>s6oi*B?=gF YHMvK}94c* |VM?GmcU헑,m{+]#6X %X*"`%E!֫ `ig$c׸;,Y" 8قy@jn5h_oD8(KUA0d'C{w!GUd*wF!B9NF=tp)Kl}TM4X!0*ϿXQ \}`44mWܽzdy+Pf;l&nBDr6`X/<;t3=7%(23@Ze_+i2p _.|à@#:U~h RFxH$4p/9L8cq`&DC{{A;L_\).6xPn»0 zGv򬧙[ڱ^?sܪoZm7˺@fE.GJdv fthƷv7Ch>V:F ަ߰{_v9㏘w04ji z6|X@Hu8muRb1!/óҷCk$rM пuAT(ckFTd6"JFwg Q23Q\5H,7-[F ڵLb>COY0dњŕDi> ^M#Z͇udU`0R1Xpל]=@ :c)>7R&-dZ5DlFpvw&LtCx ߒKyo*f/!(؀ R9]DIZaC#gWxbw| m1@}X 4EM$DbɴDɻݹ2~j[{Ya(]#BRhOZ_l(AjlYb# j .O$5A47َ>"+3>AST?o1]M7F8:Նz-io ÃI';Wz]6ˬ@ɓLq,9 s)_*IYHȴhMU2QnHU-Gm$܌R" \X:S]FC8 %ٕоG@D,0Glebg{F/i˳ksLlb@ϴo@_+vZFT ]5^"Sv)Zpg"h\0* zj8K~{ǫu ZX>׀sLJ 4@O(G-=|=X#s2c2ڈKYP+ŶQMkӳ|wv\MJDʵ"(B@ƙy~Mof)LVQ5]Y\*4o$Rnܰ<Kk$f_r}5U-\͝k7H1 (v07]|q}8J,lpg/.G-1V&ԏ|wi̍(&RsT,k)KZURrL0ɟBɉXQAkZ b9e\&[URYiJQ%eT*JjI %5{F\DX=/?=ݦ] z/P9a 12 ,ݪwdO"nA_f$mo9pt/w)Dm#qEpqaAmmFVĔDt5jVLQZUڋpQQwK%TZD-RlbZ.ۋ(`*QFeX7 \ V\Q,D C"UP\lJ pb Jef!\+\-SKQىRJc2 EaAR.jcRpKs0#2J4X2֬mF 1̢ 2hٖYqp1e12%,ˆ)Zѫj`cR5UKe"`]JLUV[qmg}ZzM}q 8//ų&O-喆7?ۿS,xNg=7jM,unmzur4j>3#R#JBi/ +j~4uB{?4o۟ =Z_zX!h&2+"4*+q?eTT Ww*xqxǯe'0&ҁym i~/o#NDAkEYJJFbj*QJ#h0!QHUmJ ( JJV"4FJ4 ؒ.use;%ٝ1rZφmMV7rKj\8\n86`TbAU"!,bDH)IE"1bcT"T` D`QB#" F " VFXE#KdZ2T E" h)h "[IF*-Q((ȩTF(ED+" DFX"(dAbUTE*` "1 68gry+ǃ>\س~N] $]2}[kڪP!2$c66rgLy~z{zTO *1J(*tAb؈IR ({0`NC9HE1w{/W7./Y\NӚ+2 ؀*D+V1b **F*" iF Gk__ݿ.Mﲘ[ե"ݮtZ7. c+hRFt}gY?I "}G6/m}G$R\L;Z!'Z>-&ۧ޷ nOg9ȁtE0{/pȷS* VF, j vaI("lր$$ f ~wGFyj;ThsGYΐA"`~'EPAA` I`,PV(( (@"dRE `((I,Y e$!Fa ,Q`",IXDEHbDjH*@P#$X,H($D$P",:$(XD@U*EPEPD$1 , HPFAI$E E$YEBY$X*AdR 2$D%B (dVZذ HJ(kHT "ʅaER*,`F(TAAbE(*# *FE"#""EbX'†0 X@PR X( ,"$QK%E(AS(V*,`ň DPXPQF0,dPR(ʄ`V0*5Gb91)[}~q50\>mm )Jc<.$0,/ @و&|nַR髄; ]>@p)4 ww9|Eg]u:42}tFS7a#쬥,@ Te}2ĥeV-//|7d* WyyGsxg)3Op(n'T`x`P^)LtgWpcm1b2K{='W*miv]FLH__4 N R?ut4t$dgޯuR|N?/h.+F:_?W}:KnFC}΂V'9wp]KPpUIaP߫Zu ?^kjJEnl}qUw~.9WC{Y)"A $P8cz 2T 5c39_|[^!W O7n%]3!0|/1@-;$ehEY'IzEg桨 @2^m so0޽^{1K\- n~O9:ݖ'ݩaR@@ќP'`eWtP6̨۪(?X%ߥ}Wiv$WGﴸN3Tr[͈F0>zߡ{vE(.vb@W2\RoÏu:3{nc3^Y:Gd<F̹^m򽌞?a/Fah`$nۿiÀ&# #z2x]/h ?8T(4>.0f|Wfgw?Z-˷'9vr>o|Kq Wk1>| e ;[eH<;CU{$xz#T%<=66CL|o` `fd{qRoTu1912}Zلb젷SOdvlvKûUz'w̼wBrZX*Ki0 7[ Vd˫ӥCNމPs;*4ArEz쭢Iekxߔ%^_*ɜO0emr+,.GqcΛ\wnLOY9;ːu45oMWkm^Yłs(mb v[{^V;v 竔ho|ymvx%RŖŴB}ksrv*/CӲ7>S[5X+#Ȧ,DC4N|5hw8_ViB*d)SGkbO\c (rs -S3Z'~?iip&sҕUhg?g*ޏhT]}-b\Xt~gEg}t]C7P-,W;_{5S]mm<(gw#}~Pl}?CKLR7{*VSkwl; !d;_MV7p-gZ[[XBWՐSsK B~݅ #!+IPl}0LGʥJ S-r%&EO?jG|#9?\ Op@tDS*7?U.?tS&iH \D X7PN|6~7<G_w:#?KߙL?g/c ]-*25&3xF2,2Qws ^.#~۵ ޜn<V79f&Ri0?w* l.;{>˞Ҁq5_Y xΈ0=`{~9gM񻯧oYtT%f. ދo'"OC! sqMDdgx*/gsܙw7eaueP,Cibs4ZϬ8ė ؀u!ykF:$XV نjr,beKЍӫp}0! (geN?k=5$͜9ˆsqsg>uXϥyR5>ev3g3uVvLԦ濊MPۺUy%yrn^Q %3tȱUl@rkDf;L! sK"e7!SOKfbu5MmFLRKnB\;#ǰFh23;R)Jr4Ru@gR[9Z\īL'ĻIY/KӢ& )cmdɫhݞ=y;\+< f2;oDtwRr܍x0Gu#M-84ٺwвJJp):ARU!3qcz**ܫ"sR8mג9j#S4q5@ť6xeR0S~&̲ԕ2R[K} |Έ\uhBz*˽MT\ pVؔSvљf pR`s.Ȕud(gK$0aEδ%q8ztTyK тt;lyGaBhн.`omLCt t2j4sFp, F";&N6# q1d˨3 p٢wp95恵Bh-jcʴ7D 0Mt=a:@uR:fCȥ.~('|mZOϷ[vaP!؂,{KaQB Z,G2d"aQmPzsКwjREV=:C7cڒvdFWɯ94,2_BnRPPdC̀tt0urW '-MCLїP&'J 0a!ެi_)aDƲiAY$BrX2K P$6Eȭ(ٛTյkl]9xlC6%&posFqno]pa֊o/){ڱ`Wv.)eZXx ^I'SRqMܼ8Ǔ.ObeuAbíV֎ =\A<2i.q]ft7)eַf8؊ٚᖇ}ѥ˂TEjU:oKUD!!zij䷵0p7H':j. ,kxD1$R,A '>qbY$*2JdM 6i@]9J[dJ(Rel-%:,11|)$DcPb hZE:T'T:A-*6@J `4b$MS&0(&5: ދQȖ31H JV "Hٴ ˻1&ЩPƗ!J55>wC@zw~kPEMa #$0:Anse[yAmOw~Su%\KI)!B<u&0HKf\#MgAE^)wQÇod2TjO&doyLbw.&\ %JR㒣*ExNϭϫYS}{ԅ{>\|98В E$@XBQ(!}sɵ3䜇!u'9 τIAIOb, -QlYܰf2*$Zx'g{C ¡9LLAL:K ɳS2)\8tݚ%Tqt";Kv2F ;S rrx_ICRɞЕ[/jjRZryP&-+@€NkF$;Mg3ʽu21/8zv)[! Cr\7Ӷ8 7 m'71 x=X;uPs?P_53|񜈡m)lBeTM Y1Kg\HeJ,*7RevMN0P|&Nn{-#\BA }tqZ rb/Fd301R\_i%H|ߚ͡4FWUfsHМd\[S 3Y4ZTpM`nan}ÔdSXֵ|\P(Nrreej+K()#1(i*CTy{8|*!If!~y^,iDssu8tg'hk 1h-mCz?4-)MTkHʈ 0ۢ'ݮF62إԍ+[W3] (Xl-%fx%VM˫nٯZ+>(|[o}Hɍ&Y}MϐATS"q UQK]_wu|ZmOznŊϙs Ty'KWE+8YQ=ѕVaӍw((3B!QAnkYQ/Θn:1v,rah}un=3뱠Uz7 wA7nN^PưLdsj;#u )xJӘˊllaI%m8g=)XtzL >\ 9G/ǻs[*[24se<]ϔ>9C[(]Fy*PPH H-74Q/Hitj;ʨ3~6ۥpevavqHpoTA(zIP&-jUIpiL53EHmLAC-x/A:I0d%d+bZ oi!U7\ݼIh1 f[+C ;j Ŕ@(C _A)'[:['O3ZC>2zQ!ܹ"0o]8i6)˙{^WGg0k,$Ob]undplKpvF\JF3= cjTnIӲfb톨_VLKi67bm1,0kvt#E4cr "7\mUPQX9Ui, ռE}&R@ºC8i$-;Ca;/-Xcnծ,ݍw\ld\bPSe\H+=f) %L*Rm -|RfUɫ;Bϙ^ nplUe 'p߳MrX2Ɣg$g{\ Hg Uc2&Y3DEH!6%heDN:j٥syǻ~f$V4wvJP<}8 7I&sfRuKa[챱__vK߯MI,An}]{*b) tL'CqC/ni߿.?zէ5YjElk[؍r-4E|$'@n)h¤k.!A0* #PE՟5l>ЁjH@r`9%qְKim-F[UC;;`;Ǐxq{5Ew> s]>[Y7j'e89;W gJNɋo D5 R?:qź2c$e΂`ޘ ג{rø9٫RYDB Vw5;Jp5ۋV614psCpLx߿eQv' s"&Jq cl 9|};9Do'9wx}VN|1LS>i 4Qq0GB0o׷?+l~pO'5Ի0Oș:oߗe__YӪ~??|3/ϟAj_=&܉qI$! *rdp8HNoLKlȶЫU$;G{L1J2%qV_w?y )tjBfڌH ^`~zJ|2zW^NX5 8[IFjgS132ۻu溎7}MTj1L'۔t{" F۶^ hկO~pڭ(۞*g%ìFj*E m7t7M,}\BNOe6VY|Fϗ}BQK`wjbA=-R}Rœ2 B9сƔAEBFcI{I_ooE|c׏7(hOSݐ$;•{w&ssRJ7c J8rѣ~&&Z) ^῀!fͳxɞmu$Wc>TiVB~߇NxO"W.tNUy;v׻;Qn=͔-,8[]ıҁkG%SM6sJd 9}y. 9g dT fHfb7#6$"j [/x #}4m`ɮss;O rBI<7?_K?4OVZ %Ggfϕ^a:|$Zp2HF|%)'I~?79%Lar+/G7Kƪg࿇j$5ߞҒG>t0Ҥnt7됮0_ 6-t ,LnA7m1cc` ߾}G׻ o $s(Ӎ&#engEx:#thf5bBJ3C?i7Kol*a;AA.>a3wJs&j`c*Qm7 'a!:`SYwL}$1{kW~uxu3gpv#zFyyΛ5]sb7mD[S InS8|2OH؋QHuYB!,p.s]5zmaH1@4vb@7YW?nz \RK;tIGV|ޮ鎞bQ0 kn@ Z `j%pͣ__ǧEdLov$g~iTFh < -Pѿ<lfwQޢq3x6yyh,vp]PnUK(.bAu HQA(1skO mq6ڑɿ-#΃G쒇6&@GOwc`8D~P\5^ɷmi":*oW!z9tC]/b՘R`HIV:1јS$iaD@?fHZN $i4@B5I-#wyѶE ,mHRLSsB37N&RuAB5}тMyZ28:^jfSlpY fbBKQU]tal*8&iI#xaI)~A ilP[}6ͱIq"v9;tχÇ't#mgIg~:ۋnhZ^*iL q LITz3HL`:wE]QۦP5G689|pTᏖ* >T8r2>w*NjpmzWNI$vـ3~=r Oei?I7~7BНZ.ynvgXIX(,ɀ\\4n)qyZUs3GyVבݽńp J/]X"$(,mM0O1, i, h *T|IuGd4v~<>匏F??1mDKz†)ےB&n=J!Z.q3W jo&;0t뙜.Z{#Pc]CSLxn|v\<^H!:=8«zGb\=+4 3{*FbяW;շ:.!'^j۱FHϧon!}::8(F}k YL"D 3$x\y|Z{='Tyy%B=㋴kXÃB yby0 0%mX Z°iV6 Z*)InX>j0{sp-=v<  ٚ뫝z\a}eY}Vpn6TwKİ,37>`&#)?7o-2c7OÏUŤ{{J&j]ќw9Mr~IY&0ݼz?gF=j nqYn WFBfwhI`4xL$H@b) `[X(PaPŒRe1(AbIY@VЩZ¥YK" HVJb,%HJ*AdZڐYRaXY+X6ڊI-KeI e--b"`" VE$!""V @A-ʪֲam(6,A58>$ϗӮdR<̬ofHhM3C ': S3T%;V5crvJP8tVC88xm H;DcJ UZNBZV @`8B71ͅT'<*V:1;rTeT{1.~P}=۴Q!"?̜Z@ahqX! CܩU^*UěCئ54a{Ly_K(hՎy{dKsbǗ}c#%1,`w!A|YYib(ş3Tx*byw5o: '~&=m,exz,'I `MyJ*!@ v2y~qSI뎋ߖۍ%2뛝?n D)^\w0yg}`tpl j냏OWAJUxSZQKm}w̞Lpy )9~yd{^,ɛgeBL c⍎SQ1beIà ;6m0s@M ?Jvft1/3K+S'QR"+ Ertv%<:3]' orPZV$%=4.IhkV~36(`ٍLB ZJi5 æ&bH7 9(Ak(HǡtLlBuTZ& %WfcVq4\L ]ۙWd8 &Z .S\nTiqf´>u HL-cp!.1~C;kԳp1* >2wz#;5-+R=3Mb@*UlڞwkռTWG xsI6υ-=\ڔVZ7$bIވJX׉r-Y)Rqo/n0Qf1 -϶L뿯q7׎d<KJ;b~fx]4+]j588Uyѓ(/F >v"s~J0n,!H:L8 I»YX{8>'6_K-[ۋL҃ö)3/Ep>+L)~^5 "J1>f9L-R[ /@^fn Yov;[m mΪbґ% A$I$dao{;AivrɓbҘD 0$l t"wú VgbdMC=y4wuZᘡ!wE ZBI2vIxӂJyuH.Q)-ƒ,:z􋴶I4OQ"RβYDczM h*dw ߽f{5,3] ;0E [4C0F#"22(*YTK3{x? ƄtY'f`B``,`o5&3jy[zBM-n~ փGn$:q34&g}O~Mt7C3Ũ U@TO<~<9h6.k 3QCW/"fba HT9UwW-^ , aG(ƹq] Y=?VgcY}6nQkS^vbR5j=GgCP0)P8eFڎG"',aU?BX yNy!K'yD2Q=>OoM[J/BVra%H=LΜ}̧9!Jw (4 (ڋ?fN.s40Rjenx}dZIߦ1 cǗt QnRE&# #~ߍ B[H49dQD`1XAb#A `">`Xj?~n b~-p>-Ïe#2; 1~@ljzM;ha )J1DEd,c"0m lBhi_j9 _w)~vnn 𐌍I=IPPH&C9@9041:̦'չ_9H=3~UW(j?q ,PͰWQ7__wݫ$:!JMܘbSw}0ar ˓}L^b Q;p+s k.Ko_`; ۟s|{3Y`{˙ު= Zq?ϊVY]7WLuR1f8<_dd&w:d d)w]7ۧ弃ɰ7J7Aɸ7Tfܮ.W7:hݜ@#0 Ϩ: v{d, ?Sأnlo8G79eqx[r.of]`Ej&֤pΗĥjX:Ԯ(gTLmAjU! Uyُa5z&jYm8cR^yewpHÓ/% hna{E"|bdv]V=-!|!IƲ9@=x1,""D0eWG&M?o4wVJ .{]iQsPdL;|m§fž)kS&}#1Šc|g~-ތ[3fN)i͊G҈vk  FXJY9dZWˆ4(ܢ4/#ҔٯmKc^䨄#ҁ H&kvpV=ͳWᾑSDPxH(4"v)QpvpW*C_ZOk?N4YI*g}^0]$8 W嵐=wo;<{ w$,Ah!V $o5jez?+@>!'  QKU:\5au:6~X= :-jW0:n!YCdhޛUXxc}|8#DR ^u>U "U']qq{Ub}٣7\+'j{0e#7gn2 ۯcL f2m,æ y 8>y_k9?T_T`N~#p5ڥ]#R)S'bw T)|$ͳ]Y ވr mcٞZ|s[NFEjŠ T(5)Ƞ* R(**KW2e (sfa-FYjTVA(Lh[!jT/Ƶœ&B#c\-E 5;jݨL LrѩF-U0V14Z6曢X,ŋ*]! sf`NY1Tc0݁(?0h$X" PAUDd`O a.\0{o g;_b@7.0SIK9Ia2T @$ΠLRbMC2JLh't/#eF0FHV '^ )4: ޵Q m{ 3gXØ`ho!k_-I 7vi!-no53B DNS#zt&lC32̐y]9 {Q I$T :L,K yh:d-9L(5~R BiJP@@R'0hⷛǡ|/ކ?vew8c;GWA,)z_?[zy(q2rUTC*k%Q"Js ( ueAZ-t` L@" %f0D`O+Qkw +a8]~:[D( G $}laǂmIe(Y?^Rԉ!oʖwX5PeωFT i3qwi_?-\1 u"$aHyf ;"<)RaaP4\*nF5-(p `W/~k+P'w1 L/?/YqzWmQC3Z,(0ƍ*4)u6fJL w0h6[j92M5 KFPE#VX0t50M,EEjkLQGbA [IDX!RVK+c`,Y,d Pi%l@3!KBQ"4A-"ce$,jR^ɂG/viĶXѪdlXAPQT`c:2"0HlF*ʢ#lH4ёJQJ_P 1R"NHB&H:ikYe8j ,0Z`Y#'4 &0+(ePY;X  1Jwl \)PAdTTQdDDQ%!HU`[VDZo!s+ 2Т9LPJZSؔĆm(Y,'# B0EY b,m$gVa$VAjfkKIHXLjJ 45lTZ ,0BQ$H ցbư&CYִ1%pj1B ,`,5QDPg Ѳ-*#]Q&2E,AQȱ$b0 l"i IbVXR T؈Ƒ",XGD J6FHa٥Jv!jCZ%ee B NqAjV# k+"Ad*F5+O>LyBbcX @ "ERTƶiD=,M: hU$`U@ے+$1G);&+:$uXi@Lb@6H,jqp(J+51g`"Yq01ZɕPo9 1M)Q"A![˃21X1a̅_WCC&K@-6ZdpDH4Z F=B7 -lk)e%#i*ݬJ0`%-i*11Hu hA ڠm̽a %F@fn|aAM$*2*( $UD(*őbYV ):3-P+əqQJ!V{̶sH_X&Pu0ibDNa7*т,P)b)*6BH6x4]A-#͚htY,JT ^@sщhT0 bDH2"Ah0M 2V,!FDZӃ5FX.郉Vƈr [gLQ1ZTEbJ`׸h Dm6$!iAXD`$Ed ̪5eaUڠ%c=E0c2aeZȭjQZ%JCMb(#"5U2QHGWk0D1NԸUZRq9)(QnXhdPA2KHB)PZdŠ,,m-&qVTl[K&Q1,E`[SM\lb6k5*L=_ݻfhQPa[70T%(%Fekm)`(ȍa*XTA9a:UU ,VLBb )feAJ8,8 K+mFԨZebTvԔd6Xa`R"Ԉ +,J83$p@A-6QJVS0Z.\29ZX+4"K`ư Q-+--B6h2#00,R"D(5X\.+h(1JkeE0A*X띄L4 5Q0ˌ043p RZhcS)TmZ娂!p^Bzqj-[$RE 5%mT\e$DD0iiriƭʱlK*M*dmFҫhDV6b)VDhUđ홧G}аҨKTA")Ե%hZQR$ h1yɐқ- 1K,Qֆ3F,`$6d횊1,j6lC" P+lA0XP0Eգ\^B[2e58HpVԬT@FJY sWO&(ṘjfL k]]4dG؇^! Dbg,,` 4i X"HB;1Ρp /4$dťa|D+*ѵDY)*(0AaCF q(EaJQ(\s2jSFF%c,+BF%(5}ogԧŘ5 @8i|qyGwah3u rǴ4wXj h&W89 !Q!]R'"SaEtNR )#0!Ie8$GL*"`i/ӳ|g>7o~MR/p#RPOn5 ,)YI#IT A%5٭IQ0=FXf$u3a"ݷ{u<'/q ƳKHň h5:2y.>??4)yuxu7sRlk!@FVX $dHz #e'kz 8mla4ZX.iI)Z~^HײBbjaW'S}?(Z#&Soxfؼm-k:v xMXE@-^}Md7f`w)ٚ}y{1b|jS31&,)PIxc0PR+ ,HQQbVI*P.X/a|f5pj~:??)N} sqva54=-Bm$ 6D}IгfC54ܿJxb%\E Jn%@,Za)V=TRxy `qDO:JcJKQ (ibp䆁A![JQG`M 7&6`Ͳ2өb 0F$M10+.YD7fRŌ=ʧDDIvq$g07JJtIlj# att`_6Lr}nY#ӷLu?S=y;mK Ϲu9,a9%$1<כS?y~q ;{_G/8:~*G^=z{\{Q㷽&\X1z)6x(X3nV{L+ :)*f:ӎt>iL.1!T(i iϺv8*^c;k6ُ $Va-:p0ڇ3#c,57qw};.}t&h<ωI?nMC``mCfj`Ȍ c"B$u7:GʼnRL3wW*S7|; ,ϧ &$2URcZ*k ڟ[:43z]Ē d̄XiDPA(JH1!h%$AG Ko5$}w/~rgQ(y.KwY?$j Z(ه$ DQ!հ0DrMwnlsvu# t\I?#7953ʊ/Dnaũ廢Սө{1$fZkse\֢Y)r\9dD Z{q6`Qp,Y>cE/-q`d%P'Ԭ2Pn| .2[RԔ n+A J+.X(cHLFFE`_qO]|h@Ff.2ڋ-Pp#-(aBCcJK/RC AmQJDXA H,@D-+Xw*b")A`:$M 8vBLeW,zRZ'6d()_MզS'U VW%%EFGnJ P[sNȌ=rXtk8ų('ϕiXYdSA [rKg/.%_RW5 b[23& ؼ 2{~Ku)AH喳aXj&I :W ."66XLɁi|;&hX\*P.FZ¢嵚 LˉJDĬ2Mj s0!\B[e%a,%B4[d˕,aZ70Z[ZS,̎JZaLzM&2l\ƶ!De0qX7-&ʂF}TVlJ HZ,( )c2b,bEl1L3 A#LS#9K)t{٪*blR.Y 1u 32`<6(&2*GK2Œʆ,XRpAL"d9Q 8?ȧgkz(?zA=f fYD}h1???uT1?tIfRGOYI!aasq.0OpkRz[)"@Oqp˥7^n_h&t?_;CBFu4 (0X\E8ޡyt4N'_њh Ck&BX+M`zZSIE\#3%'z$*jþJjd_ɕA+ 񦄶eL6JMgjVs|25t̴=Č9ձvM8{uKjR4 (hR<-YJf@ D [P}m\M̴ / c-["9;ñ n2!]rF'rn@ru]lR]ֻ01lפɵ6ZYx9_&L2f̦#vuLg4{0DM2UTM{l7Jۢ\N`N jlmv-\P4bJ4՚6[d'; +zޮѐD5nM+%p6KRU ]p@#@5)P6^O Cfk~x9y\<ۛg6눲C("r4aEWlB Y%w GUu;[@CCAl6쬠26 ftʤ Np#͋ɴ-SDf'OVUf۴ ؤՒ30QC3%b13Aޥ%(Iփ%buMʻa|řhnW28 ءĚM LM-m ]YCsE1 1HZR 2W z.V.^k4M䆋 axC{3éN$oe, 04'<02\)Z7sunqZ*Q&J@.'+4JHT} J"d]RHY7i\(y|HHHj+U I,dH2 $8U5h+JtKcHĝECNqaqVp7FG[bW!ʧ C^jRlŁA1$%fN)J& 6'yU9敦c^I^Gk{&Md<$Rs۳x+l1r^`)#EDCNB2TMiٳʘ> G.Р4%,/f㋆ G) &ӞiNL %&>t;dK|r3zr(2[_M@Xx)$ELZD8Ԋjhk\rHIfkx8Z F6QٜQn}:H $PEIBD”IIi I&0CصsɱrJ(L<`]ӟ'Mɾ<62 KiZXd2;؇tp4D0wW Qh(Br2Xb^!V :a ,ZCF-s"im!s4ʍ/amgLmLW/US*^ך+a Ʀ9e, 2)s-.Dd$Zխȡ3N1 ٨}BR,QT ``,da,epRoB7TݨI tQeUJcL_I$Y` b0F/M!2tBn042킬l@8&7Q5MHUK Sl8M{%TM40N7Y|5} ,`o78Snm2dlѹ*B :t LHJG~8qqm)qRtk %\C{&:Iؚk/AB+HEEX)_*'Rn 8`Þ/2Ff;8`$b>1\z+i ˼bW UbVW֥HK"g=׮< X;ӱO.Kߺïݫ0.NWбK'|ӮbqWΆox.f (/^DCW9D&;&1amoy,:ȁ1PQⴵx̤iEetol{o^ME{ej+4Oj?S$-aذҖIM.LL $o\8zW*DH&Ռ[v383^<ޤ"-hQ[^Z{[5+14:QU18 1Li(2LvD/ T3>uJ%)jΨ|jIn $5ӁYT<ݚ jxev4Y"i*`&3'(R=ގhJ_"VAڢDɲW7H)xQKTD#bӦC(L|Vl}֩)&Яa'yG c]~*F(wt/0v́ܞ?tޘ'7A!]#.U*_(_:og)TӶէN8|i@RBS~KSՖ<Ԫ%$m\^r Œ99fZjѢt؇4_O?7ӷi Ktg*;ԇNd6/Z(v?ѻ wYBLĚOWY3~+23BYjevIA5qT;|6<"]Ԥ=>{rlt7A&'ɉ!B_pk5D# ÎQ)%OJocpiCb8уѮ\V(zL3qN}s؁p;'6].pKz#B+ЙR]lb9?>Wh"傥@E7}bZE"Ld XK3Bl3bV فDct>0*A2%*@6 F=Q$#M4 #"Sb3Dݵ}n *֕jÜMpT L(g5(7qr _# N5eBlLC=@^Y'?~U\k$b8]? M/|Ic8- yU&VPAe93c}ٳWJOzR1?f^:A 0>ҽ9XƟ5ݖ޸иToG 1;F)_Wn52tEWkFR < qDBc[Jtٞ,L<-*ggY424jRO5(:ߖv!"Jى8|J6)-)9CKSӬEè6 TFnSO) '~N$kN̷?`<錻<2Ze kڹƐJ"%K%RnhhC4TWEm,)I/0)'[+lz '3;^LӸ`Uh2zN}WvX- YxRΫ^ h1t"kڴJuG+e~Y,&t#Wc %$4ι=f4KJ۱"(ma  P 1N\P9H6f&&+x2ZB!ʤj'`Wo|?VZHm╻ S(D1IDKb7v'f} #ZB|*Sg(f@PZx^tp{TgDLVG.\!ʦpp/mTpzU]*B[Ks#VWǛOptӷ]]( 6{{*q(ؼvd=;ٿ#.f^N =ibϥI<{j:@C˧6*c5t߭G>CV 8pz.tg݈iڔg%F*IaΜAG9*]@v¢,NpéPgb+M9&䂂sw_7O⨕ 3A-'Ni4 ?g-7јzia+Ta+LXh>\{Tglqζܬ ƙV+/;|z堏B]2n' ]-(qBi .eZLjYUQjOql~2cO~52O׺nI؟os~}wݜ+`Pk7aڕ$/Zր*[w,9tŧGw߉7} 0P[v.r[=:c<s̏ =IF^5t6ks((9A UlO>^9P1ebؚ@A)LDövJ+:Eܼ՝C9;a871 rذD8twV/Nϣ![r3EՖU-R%榓.ŃhqAv> ˎݴvfE }s7y٪Au?3ʠ?G=QJsT&FکSM ғr%#>rS822]ϲ2Cry%%IF>Mɐql1q!Kc%Tç:!:b(떧ND['/~mĊh|z\/W=Qڤnn(5 fRc+ E-ZqǞdUcZQ:P*ϸsEь?b]U|NLτtnx;+&ͯs[lk)C3DjG-2fdm*`vJiL[sT/OEL||~+f*ښXYEzicrKM ?#POVw_:\͊SkM1-ߎ糱g13,f #{+/vi&[9/ٱJjR ZC2Y'JQvLRVoZ+iTVic7rљHʘ:^{ œńP ( CGPSwxg@/휎_%u=<(6a:kW',n6{~佉f-ݤ{럮vcٴs 8;})TRwq#iYPvaؒ{ nK~>/kQCFspÔiEo[EC2Zﶤu.jҟHz} lLZYRȦx*+YNY=Ton(3UTF;Tmz_%boGFhQCV)nVJӼkNtcǝ7C/qWY}^YRiBqk>I"Vz!O"&a36) u@t8Eu |"Qz9RY=} vRJ"u#V0AC_*-ڢ%6>'αEhb8S+>kW˜fdžuoKNؠaX4 d]3_ kaF'ֵZL_ -5kkp:`(q8񸛪!i*bYE!OfwGӡ`V"6$S-u,^|W<Άg7|- iFNq]4{B 啕6_q!Mܧ}x2vv~wcp`wL*>{BZ.vB,y;i;TcsjnsRW0q!>Qҟ7lD$V.GRiC}by/=_95+Vˆۥe|EE"85GhKSJӯ/*þ·hɲ9pǏ+͍RΩ2z߾Ö[Uu}ǯ'&ck.u.[jtmGIo#mJV-4^v67eӤx֕X_vؤPFb, HHB;HzmpOK/ ӲLbrOG:m,n?~OT!}ͪ~pH^ėaqy(\9U֯mh]? Wms7Se-1 }YŒp‡g=v7FMUۊ޷P3⥜JV`v`gG!'EJSjRJ8)4'Cʴ4洬l6u Rl hQ_1yLpݭ &8X8nZJQT<; 7l' EmdXtQߥmAQ&+pd 7ngq|Tent6' \שΑ m;zKuѴa@J:}%Lu\--[7b~%ڻ@Un 6z=U Uݩߕt=T՗V t7޲ 7X f^mc1p1i uB"_H[ )7xةe9'"=8"aZ Wl:ޑ #1D [AwIQeghbִVQ|d_靳+<7KMs휘ÎֹSrOk>9ΗTΙ랔X}eE4ܪgEXDկX7zB#<*q]ս!N:ۖ%Vk_Z麗qws&ux龯({$:5;B;U)1%y~\sK^ǣSzkfcŸ9m lʕ(vص<o z&й'O6 u¯yA̓*{DG/n==۠Mϻ;[=o:YK9iwvV1 ϻ.6zC蝗= Ggvm!p4Px{qܭ#g?Ya n%T;3vqHzED$1ez?fsuW}BH. af@ K`}/_V~J}yJHF@ar 5n/9P}&`LD!p<&TZ}<#W;_x&򔀅) Y# ` oof}wX aJi(X6RF 1rC{󾏪:"57m ^ ۊ"lNOTBpr`J9>,VV3Ib8NVs3n'uhW_@,>Y?|uPD ozuuGCíhfU}k9]9EZ@s4?g@ƚp4Gx91;10Tteĩނ'\.JaJBy#Pobiu6jlװ Yh6KhEXHh/?YCΦ 뙰h~OW+$83n2e6*ZVua^ree\=)i&[Zˆd:'nFt'g5\L bdv2dWӚAU'M(j!BiF7͋o4ܿ>B nb,QWL^W?krdEcHIñly /;man06g!.zh1-jղ #Enyhsz(A!|}Dc3iFMp:|_{%eӓ_.[wwRPm'QA9zT+NTta5 X/lTY$f6>J F\e@1ug>nS'VhiBm=̟0:-­G]Q*ҫF,s>*/_3xKgvvc-7ƕdG.`$)>[:HV+\glS$nRٜaU^ܑOo-RyuL9l>~OEm8Bo{ f^Ӕ~K!r鄆FҸsʴiKydת9 fG"F }OڛTY Ii9/)%Ͽ /۩Ӷy;$/2*s;uZGv9(܅S+,UzbeQQfUզ\9/W~,oM,1g q;O׿rª8Sz5E]'=2Dڽ"8,AEt^Ɔfʓ9Z3ŕPMFv0kPlՖȖ]Qܶ!jz,r=HR/Xe3k $Pݡ\$ hV*~Q"dEo4N5~a=r%#c_uԬ+80ώ>S7ɕlQ]U7w2U\J~ >s|9RXY@x}5$[keD _ZXs?q p<߰r%)"" ,R/ϙ1ؠPT8{|;s4歞le8Úi,[+F00Sĸ+y]8|{:ڹ #Ղ8;5tT7fa QKuZ/K"k|g#*IlXpFa`Hㅢjk1S3T}`H.ݐUUgMzO6g%$%q[V ".?FL* 5y5"70~knkWq |"(d9GD vɜ#M;(I\ ÿ+v; ֪\vDX9ȼRE5Cq;u'6b3xF!Y[SS^]ZMcP:hZv7F*}8qw)m T57!U7ҵ PrNWQF-85dKoCsrnffTx+Pʵh&f(\ޞ1X`=S5aoAacZ'l!)~ZŤ q,Tr\ ֯q:5~h1v ,V#TDŝ =3<*;4<cDpE!EW)aAYͻQmq.|74ɬnG>e[;3ڙVFt1gK>9HGn~:6\;LYdHIhdZUS.ּn簛sZ#;,pfu?#.hau %Jpɴ!zAvKwILPG .YSܠ>RHS{}9;%:'/e !L~p)9di|gOF)O}3e$;W"%ʜ8/fQJm3. MtWC2 @HDS?ZkS;?L>_اk3Q?; A?t7~-E @f~MdA?|ACȂ Ψ2 hPew` g6&ǫl|~&ts?Jl2??χAS9?z C^%Vmy#{CޛJ7霊dv*syw;%2CŖ u׻ɆLh(ƅN@ȻdY+|/}y(s)r")착’|@exL2bpBC ioqa &|!XKKŽ &PK1:r;; 4r Dt2JU !E,0`i#H@R _c,:!*|4 l$M0zMR փ;&ؐ4}3t:ċ:55 'ptC59 i#|sk4ADUUDE/bweֳE5S8F  qf "M9y#L'nx' "[QsNo&v>LUHQT(THn|)v q>aͅMH~bΰk=7kaXۈkDd;Ȓmj8ݺ*噴1oPy4؈4'+$aژ3 8ȹ@ubxeX<]Ͽ1۪u_ cǀ¾s>K?|TU)O"}Xs0ւJO6o4`@ PR_MxقM>SԮǔ#:%`&oȮ&=ͥpunocGq6:'TJ37>_xfZBgG<$)[{[=HHZ}tפMSv;8gwvȳ??͞4zt;179m/Ce)F5׮,I2wDBW$7c, >λFoڛ{Gd,so&J1K|3O:&)ꂑ b, :M C諅M`"2D!%Ɣ7&>~ɸv4Gv>+V~Ln<̅go|qB0jǫ<7 +&Od3 k%c33333333331V&FEKeU-*" US-E[E4Ez! S4ɣ"cl RXb)V?\ȌUCULba)$$k_u,hT%xPΗ6B! 2_ 0a* ~ B\A=}X!hD,>ZԧL4iɼ{EI<"9w#`tmin`g[ ӤvvG6;&ʭz\[Jף!{,wEfDC9i=ڐr%VϽLHp Ma4=-_V6n3 ΍y-u1 İ TI¡ ^jg"z|Z6nSyz2b~^'?&𮷡D>WK>0l畆ft۾ƻemso2w7]guHP BfZO[$PMUX-+# {R|+JS6B d$',%P~oO>_ `A&޹ ۖj9@i~{~O?'-YL$'r':*5M%Ih7O)U^bf_,$Nx„(i~?ҵhqJ@PQ)``y $ve3ݼ,+,Ine*_3b޹wJca\)UE21[ W1z9V&Qq=nbsQZmw/d kW²GwA5tC3jmX&6&阻sүEY۫'j`13fOiJ:Q>FWݳA4>^$~p~cPM&CL?0 ~'>[|]9bJU@Sv o)zzˈ}]Mf`u֢"jMj`3Ȧyo͛v/:'FǍa1~v(KJNrvKi̦;}DoS&z<nHCKw5L˩^gni.*ԣp=~Dϧ 6NӴ{dF:-6o ham:#N~ WB1*{eo 7jG-"]`SUk;bwȱJmuVYvk y#dBU֦ܨC(uvR9,}[vO2$dS9tx X5yu.{,?l.\-eBrq9D-OGӘh0S|.=D ш_J3o(Jj! c  b$BXgk騩f*Cʐ;S"K 11E7q}nOضu[kL&]>9>Y0՝o1xrbp|0Os'cgZpZsWh럑J䛘kzyRu[I(יӉ y3Tl8dD}ڷn^;_ф?'3-s$cO 8[yS_JOڄ@E0#zY뇌|holإrȕ/:~ Ƨx~U3{Iϟ\Ma%X ҰߣC?I毨z/{|^ľwzB.h~7|c, WrB{nao@p5(A>ann(/,_X&fw%{94vwovS5 _j!~a:FwQlocx&~o=t `;}pN)qft%M^t\kTIF-s) =^ V.~"(3P$_jB׸~㗯g^̻b]wd9߱'Ίn;1u͚Zk4pl}CnZ#agV5}:1^HYnOBM|.>oh0 I ,M]Vh(3ӄvZe W[? AJy{ gƬg*_ >]lB,5xW N-#U3)JdBVSi/OCARPZ3JH@iNx@B3v!.q1v.?7ʇZi2rba/QTS,gOn6K &o'6Bɰ(0(@ @~/I ABA[X?֐(0Fغb|o'9©r7dHӐ"bFFY8O?t~ 7kr(iφ|> v@l=#7x@)P<PΖ\*2rhř_G&|.C^x`U$ E^v+ul#WݐoHuF si?\_Pyu. DY-]2JDGեъDTj6R` +vF:'ɹ0Faפ[T+-y0I qtG'zX g˪7` oLrG<@ht'Wb7\rnGwX4ͤo%ͩ 8 y |WU'S8O8Q9U`N{<߱Q'@qlsF+MwhqS{G5`ޟw_ `u7'(1XE4`"%! 5v\>^n̊씖]2:!b&qXD ?U~[tW{p$n4Щ&,FUvFQYo^FzЎrEݍa@}7$/$Ge<Nt$%$;4;Ɠ7bm»k$1r?{ f_7i; ßpl,FA5%7[4Dq&D ]tH) ,w }E^=B=kļ!MLX)` $Aπ1z62Бm`)J<::O̷rtnߎC;,9M1iIdf*IMt4657{7HH"Y$BA+=4rC,m kXPk}l,gGZޖp܏B? Q1䑡w713 d#53};zk,st3|)P/-sWf.;Uz%P(On>|$M'p; Y<'pTXebCTFD`5 (!S<xb9II;K`&lLXB:YVt6IT(j(vk#Tӫ+%)r Y*5Ew,BU7]g:Ǵ% { @FbmHw[*ŁMP֮ CmXQ '(CBs !UiWj{Eo1Zں*,U5b="l=d@4jWЌ띔5w;>{,ś쟺z 6zH ]SZ6.$Zc?v=gNA޺ԞZHiR(pگć&gN:pY7U=V(Pz*Rʈ;F2ݽ=lR0Xѫ5ӴA?@ I=ozlϲwo_ոɪPgZƣF@+I1CXnl"nh0}<4%sٗag6-MQP-lp7 DD߫6Xn^=ǜoSýEKBBm"_\p߽! Ʃ8@ҳӻ|ju>9 jtlB! \׌wl -骚dt ߿]6ӓ}ya'YcO/My%| 2*SQa"m 1ϧa!o|pQAj܇öK|VvrEim+2N%iڂʰɾInUK{N@~sDz,2>隚a=;}^}ҎRx]pc6PZdKg=}>΋D>6^Yۋ%[*%!DI\Vh ,ӏ(ԃ .ѩQw/K'|!*{d :A^9Z~>qMnwjxu?g;{bX .^QWdjX% _ ud|-Gs%kgJp{9]n$|OWN=1e !#2i4z(ͽ6EM^0Z,v pPXח#jюZ--T@\j^.X6u~߇)Hl0@(L0 JO%?Tj0,J,zQC:nA g K;m@}9𤴑Vj=[٣NDڊa^QYO@m+QSϗ~2Zp?:u%VPP[+ʼ<~mCƦETR4PP]-E|sjB*EQ"XH}o i_f,*#%Xf-iL))J -WZvaCo :"PF(ҽ^ZV YG>\px>̚im"69>]=Q@P~\Y~Yp Od& BD#̑E,BI?{ V@  QM6-O|~' ]Rt dm:$an&@tFCTMzXpޫ" *?nݸ3팻(u8$7ZH3ɿu>W$'|Z7GO$8rTf A3D\ :1jlϖB_* uRybd| 0CesPQ %(K'׫xvz(Ag#KRup³I<\XewmsŜ~8~k6eNT`Px7W^n9:IX횓b'| a]$*6< x4|^#6H[ ̢1xl!_>!}XNw>Gg18EVxf#Zu=xo40cd",R!`p ^7 $~| -=k"l1 R sE%~I{+wf0u&([?eR#Q>K=r??_؃7$3Gꮦ?|7&qY]it]s9;s6ƭ#/ڪ`_뛴fS`yCS,]زA@3 tؓp?Ի^׫~znܒw< Ɯ:s;)3Tq`q&_ZRO%5]xW0ssoEn-%}'ܶQ-}8H+gn k{ckn=9_1840LC2L|@ A|unn}Vm>l/N s>چܜg6zwٷckVkc_w"[3k 5#l "HMSkպ35_%RR/өnN5l!C}߾w:M?5:sW-F>@ĔUJ JXeݭ8$ooRݹot޼X\&x$}|iZdA2yg6`n Ϙ$=k{ L@J#]k0#}xDA>M,=z҅CY#,kcs~&T2H(hѮ?m^X*!$ jo-߅!)~_d+\tǧk.wSA:{WF$K^eoc{kt}XHB217rdߟhh޸ HL'YJy;^m>͢ ǓQm۾yb7[&>& <5N`DrSvwj Q"rP=S ֜lA!;ݨIÌ5݂zу̒ P3JN}a!^OzyטF:؏$* 5◉|0*ô8D{@;oTTdwuAwc|2Ϝ4f@g>=$vVZcC V YVp&Ai5bc|X#E֗oOZh#3 q RF=G,k* 8}󻁑 # үKs1kKl-N㻰"6?Sn9?,u}ywM7II!:fY[,r+#LHy9B p"Z$I>ݟ#3nsUY|t'NsxW@YǾtB ;Ư̯ȭ9PUD@~3wND.MѸ=?lmo@ OE<[}S= [;5Gk:6Xͷ ϟٳ{)bI`J{_ՇS]mR}W:?E@1!{۰9kkL+wG?sG^uE:V%tfwLǒ{Vy"}vSITSC qQ|L7uЛGS\ʿrow1nʃ"ՙ_ֻs"uu_!#p 1~;4Sn.*T^~dڼ\ȗlۃʒ[[ʍwZ5䰛^TmJǚ/ٿuW[u=cKȩL'?O Ks2WpU:m2FuUy{'ΧE>7`!'9+́u-KULF6h19l 3pۢ}p-)\#t6Iq$ ~d]33"FZ;x&Ly9ͫ8|ThTTZ%hF,.坞OsPΉ%$" ,4:.0P#<CUQFI{\>?~}tܤ3cn2σMUΒ* GK޽Ԯ8yIZxr| =I3\ #xXq⤈?|6!wpQ+y'=ω5ޫ{z ʲ)է?@ BzYx-{[gB{$4M6e86w-f:kGad5!R ,Z< 8v"ha̼[HV$ػ*Uqg(LD5d tm:l08sG6{h%.F:We@7q&À#d(7sao^, {`3:ͻVy[fo1mۇ -W娮>:o ߤ6zL|E]~\';5rlf[W>I, {žE!zQvK_eн~>g ˵F:rwƢpVFX0G޴ +UX>v8wubOྎ>,@F|;&:4%|]+y:8FG9z8 0 cR8f.|g5"66@"P`o^:ǯaF_؜5?*8 o(ջ{U,GN1iSgD4dfي5zpwir,5zwPCrh !~5`*\ +YLi/X)0 4*SDj_{#(Ig~#雜P=a7w14n|)suɠE~6Y-`3펦Zmd\3<~D5v=<-]FKSapR֔Ybd/!A% >!j^+)Oݘ_.n(4Q1C[#`AmAlSa+di1ߵjrӈU/ ^[t)oz

@BB 0pZ ermTz_Ԑ`e*J;u2NB6NI :vISt\sSece0dGY W15R2x%mK$~n"=Rdeg_*ʉ88T ,RqsBԬLdl8FJY* `hlT@LH-&5N .p 0YR9(?HMe7 /=4-flL)gav#Gퟲ/?0HOe~ D88>K3Q0~|3 7fHWgu b,[t`1BF-=)SjE\&Wp~9$8%zb@/Dq(X8ʘr@PS/N!0GP .x=ӐĈW +VI͉H&2Lp*̓AVݪ}gk{뭄`7gBEgsoʔAאk8'fg8<<ی٘tG?7,,I>bX;`` .ؙYyA:U8}(z'bEL+HZB2"xOV"gO_~oٶ (nCU2s<;Őq߇F?6ַL7p]`1 ssI%|+5 @fBMtunHAHankp@{I1,Ӏλ !6w mfy{4wWؔ (d{XvD#ꞘĖ]z938Jڊt6_DG4K6^$W|gY|F՞cK'>.׳M`&4 LE)>>dZViPL PkD6\lbe)2C~k:y:b+"_ދfJa1zw}~h6Qsm <]@sɷ);5=9y1$xT8&rW5( [!N0 <_N{L, K.supY1:xa AqԵv˜9)lѦONpXf;F'#V,EY+{2fJqvj[¨#RM8KGGi=)o-a/۞̺ /j9"8"[ĿpߑwG7Z ֔*#ȡB!E obz@HSm8e+Ҩ@ķ[ǰgGeje+?}_Wa.)eMmmUU M\?9_~Qh'j}\iCTwwL(o.\֒d˥yԺi<(4dh u >DN(!璛!0|!P+8~z Le9X2mI ـ 2IM$kNS%9#%daOoJ<Ջ9%Zlw"Xo}I_ 4'NPxxbZ!PZI=Pi=tcװ8yA3kgI$s?QD^FFݣdn?n}NE8q>WYZsձ(8nÃgt8j_ /|X 1Vѫ6ˎU>Vf |M`(71=|K~S?8W)5p=,TcيzI Vo}.@pF?fĀd">H ;5a !% ohT,R8!iJdxNMOZ5ũwA*q =TI*:gX9&8ugoMK|s=Ô.8Jqtgh,\ll\b*wB1>UoZ'ƬdzWGkE-`VN!PF7~+|lMd8vwRګ,ɹ`Dd#++1)L sQD/~8!yGmeܓvgP sFwz&eXSz3v}A^ch(8<s⪯ғR8b痄W3rW1tvz9xhu8\Nٶ¥yQa}]!9 BL:M'!@Z^=&EGyh_q'_Yi&OXjJ -iʟL;zPIm^:=ӋivM$t"~CչRٗ$zͩCq 6XL"> llY2̭;lenf;^8$(g&Jz8!g|j*V@f؛ hRCJCݞ̗V'm#!Ihgyxfu+n' G&9ݝO,셖=0D6Y<8$M3{dZ ?ErŔJRv&Yv }5כjB#e'ooB9EDzIfUun8ygJCn l~[u+Qqi74;Mڼ_׉Wp1]O:X(~Bua k*,C$ -Vz.>6ύ {}cSlOX3;tʃRDj‘NrS\rqXNJi&?[xזj0іf8+nr'x9jA 640߮OˬHdVd!W^u46tdžnӐ[~|Qxf@ nȆT3=wqq<&W`n֍XL׋۵SrCW\?DoTK̴3 MWuGa;fzQWmucS?t'nF{8+H@VD  d Hsp=hٓ_gO^ Kţc)8NBI| o"29U #v(fD܏CDn^e-[e`mC9{v2=Եfc11@ez?eH#¨x3Wᶺ4m& %j}gE&[C-&_S! 0s%5Zje('ԟu>+".AAm1Kc-4kU}!H{S?~&!d0vcסrUZ R8)>>W'z+l ڣКeʛ6rN-[y<hՔag YFe>&6GJb Ğ.x"s , @B,h K$EK&SIK75eVj2SÌ[!>C.yw@9'42VKS/9Z~vV |8 Cn qD8mhe?`TK'|Fx{NFNϴ08>RTBVֹ"@nyB?k$iT^f2r`%L8vtK&ιn^wﯢ$!x Hݣ袗TrEky3Tst0+^Z1딆s aDxǘ{O1p-;|?|z>Tjد:z|I] g_|*{m뇤&TT7גͳgٿ1 {'k[gJAA=7Nm(h5C8pQ׹zD7@{!_eso'9zsw?Lf51Av=0E_iaIL9)n&'hK(peb;3Y^qp%dkuDžΑ7وs=to3U).}6!iۅo'´k=[k9Z[?ӧ?ë!KїΤ,ܓ~+.g@vAD ]z7))ܾSںsVH wHcCc:E;4>\#ۇ‘HG8s&EB{[ћ45EKc_]N0*s/{QWnp dK ngQ{ 3fMeHJ8+ͷ~Q\8IsMDV,  槍͗<+ Vfbg/ !uA3b%'h뙺oM5+0_[}QMM^RZL[Xb]è+_tqC%bFi&;YjM}oGхU8˝̲ÎإL(5V+j4b֢JFU(E5)gmg)]z5he=rI2K<{𤦾Co@]Вh=VMJN&d]9iw[mTKnև\FS(QeO5s )wS"mg۳?SGLDtYM/8|opc;wROdy@[LP@q 6f_6)1}͘eە]J]ڪ#Œ4)R̘|%XҡSn_[3gF}6vD=Pu:t<;^XĶbp+۳]Փ$]Pp>aQd(fBqI/ErŠ ߽VY=]ʿMyO\"uضĘ@GY~g~\s|>RSڮd EI*:_ ײOۜbÒBf¨ToD6;-.}fs_&Kʒ/.c[!GwDdMP}fπ8E1?W͒f^;!Ŵ cJLʄxRh bi$#j H(5P./Iߛ-rݝZ-e<Yn ǎ9ӼXvijMm֛Oou$G2dluZlIFk76{[ńnxIYܫ\ rjpgaGc :pQQWiOBv_j,wf]6X&w>Aiٶ{5zG0{wU_w:b!4K_Q_ecxxYbӫNn=}Owuf[b 64Kklmi9o=ֱiӫF6]qV|tFȭ3ǥ= `-!-y̟Zn1nPOUN6_`34 ( JYjʘ6 aܪqBySJ,&5JW펟1gP$߱g%sSv@k}%MyNٺ#}(%"I:5vө?ȧ⸐aMW~6.u nl@ y ]wId;Ɔ6: @ nޝq6* `l#rINTe#vmx*N9~K6_]ZAϷf@:!2ɕ.9uvO}# ?}2KHxgɄ`NA6[ln5McڡPuOkߓE|'R͐[&敏RuJ$OÐiZRZ*W(M4HqJ˯M.2=3>@cvovRJ퍂96N u;t=Me f^9鳤8$5bSdhMżk_4޼޴Q;zRhҁ5 xnO5l<UoFۺލjoRjp?IAr[tuld^m9y Up6óȦ"~4r$.\q{:mXMƍūtubl>>g۔z=:(=w vς&Fw&}~Rmvob[ ~F~VzkwLS3-y淡 ڎX]EsDn!77~$_)WLQEo?Uoxvb.zuZU~Nd|rKB+e1R=s"t{p`cu!zXtmwNvsHE j~3ta8IyўxI~Ƹ7Nzsnxah!_eMxGZȹ +)NYשˢ-t9 R9GnMu(PJ>a_E8< -!Ӎ`u:|;I;^tIAjW{wE}~~* yRmu կVH\ٮ̆z,Rkr7(L{zsn3 #`s;5V\jqZ<`Dut$}US؂N66_W6)hJ̴=蠼2?Kcw\7 $vyHȪ5n;O?<#atSo# G) ,YoKǺʊ2F{i1]]֞~3[f O ֵK ʔV Ȏ{xI;*k>vڻEB0[ടO_e2ׁ"NYee23עCZ:t.XZ=!!8 v睏uQpzسy%xmS4xA O[Fypw3jVK9d;zTG3Sa=^ٱCn|ί r\?G=L4sW36೩;8AYAX w" |Tϲ H$UeOͯDvIJkA4n_yB6r-"]/QrSf-N] ^m+9+֠{3yWͦ(3 mf֒9wtfjn3f⪋iٵUuP6cERMGk+lp]@XЁ>nغl-3޵m7y ٓgv /^z5_&DG_'ҕJrteN nMt7mVD[pعz]Y`f)SrpFƚE^<^]>w7*ÎMq۸i%|ne2 1C$//ja(Kpas탇'f3s8kr59"l\"\uff'$;ﭚT\ؒt 1aC<=`v3QC]|ޛ=D)Z@ALZOpq]b:ڶX-ӯKWd,V :9Y}l3VL8CeΐgL%ɕHO-&RF[i2kUIU_G';7uS+R8X6dѲ )e\LHtP^MSγqgo/{#{S@ߦa|&OBŸ2qr6ʺ͈K#U+RM45L GtS?wW6N774|{[;dI.&pq{F%hX pʰn}+{۱pdx hN^\K8Ad=ޝdѿw0 xV#]U(?0뱇Qn2#1ceJ}{0 Jݞ̋kL?Q6Z`Gs!P|9=K* >;\|Ç}حAl>kvo^Ěe{]c<::QxWǼ,)_kLRےwCYs{2~H'A'ؒStzKnkwk7n.,-*1u3n2 D,ǐ$Y:f}Ϝx7] 33 V)y_3Um:PT9ВP3|n;:F;zU4T|ylhӝ i~t6;$OG-:;˗/:UVS6rNS&Jlwd^FD]O70dx+%nA:R벮9&8'n"==`Nھ5Mͻ$֣uhW^Û8ﳍ 4uj GE˷(Z91? c7ᦵe,e#wIԺ!;2m姮"䧌^L+öi_ yYu4,.['q9])kmQfӰkUhImnݣ?UoYۜZIFe6sI^)u+?ےW+׶<Y%;GbWX!Sxē2($- r1x1KfzP=B""K& lӗ]kR%@棦IS"EQֺAe.ѥ)302d\.6a5gM<0]N#&ab@"9U"Y0"E]q4l?CĒ&(^*~aN͸=IU&˥mk@{߽<}_΅-+qZƙu}8U$22vڞ_vˆ:˫ݒV|\: 1gɳS+Imnmn~?ߝQ:|^D/ '(as'߹ >5K fɁ|u⼰>R$udeFBn O YK(k,BJ MC.bgJ2j‰x)YDt%\|(]$w-=OCO@jP=- p|iCd|e uIEicgL.{̓71mn^׵O[$?gz,[: U7^NnڬOnop4}Y 3uy>/Fо̻g RGI:q;(ƶjYG{םlX堄(RfL2 *f a9U>l/m@9KyGEX_؍s>zBm So9_+Xv +R%/m.2GzY)83Inm2'ΩpD|ӱX9 B7(ʏq7JrUd.-断N#Bl3ȮQT}?ju 坃rG?^̈́֙ܝ#2TurP=;`prݷՀ8i#_w 3S͗Cev!"ru=:]OoaՓkڅZݳ3%^SM/#tiv_&vͧ 簮ߪW}"D7_oC \'䭿fk&{* EC{+@rx6E\%o`X}L"32[`U֌禂|63Y3ub^vD4BoqP $1"۷qYָrW=I`H.H;=:]:wa;4upK,S9_5u?j3B^h y{`jG1ZR_:[ɳFnj\ʬ[;ȾF{?MJKɬւgL٧cE.=qƻCs/=o;A7tsh$j풬P3q<}$e,}Lkψ{R&^*]|3VZ} 1b'@\낐F*#0Z+X! z,1%QN>u!bN}om.g:9,9" g )px^rKN[+T@Dc; #")F)bmvW>_XLu^N 07F e!s4bM@4d = .W}/ &\zTgSKܠD[UQ 9#3g. SDڒb&$YDL14Y2ۯWϧ*RԻlk Vj7-|Ǡj՘,qnɞ<dY" 2䪚E cW Vq#wYKf+{ eq_Z?CNi (Lb!':̽ ]ؔQ5hNyկGE.Х&ߵIGҨ|:(0.KƆxql m'tsR2Z,ZC)D+ҳv'VWX|.buG=BٜMDYook&%6i\4GA!U75}th6Xr4d٪)۵]5 ؆S~0rb̘FJ'18 D gbQޗA7Ta_XZԀC|M4]FH7a }3( jɦVb+'cYdۑn*;P$F&D-){Ћ'!=|%jKڑIkm|O#+ޓfQU*9|>"޺Z%5Ri[tRzm8=& tJ%;;"丑mߝ+ϑ'jnk/P!; 7#5l=R; oGMGb7KGO}H1.nAގ =ٝH@^m²o*^)&aJPL-TʗƔn`H P7`qbkSrNL:gBR36WfEe/sl-71cFl#>Tʶ,R@Ftlk?k|°*l&.m™ӎ yF0{ܰ&{y*ۿOY^g+Q`FgOx6mxߗռىV\v^c]q)NvP(R`!#_>2Nikbo?ŌÓR $~ZdPV,0Q%0,xNhJ֓fUs9˃E֍-̤E(q$ap-pAw{3V@dn H-^߁O=~:fGZZTALup\)0"0R^ QV .D[fC[sg%A$%Z b,04x MRyXQdy4@rW0LcK(@|_土N[ٱi?ڟ?'a6LlAf)-ŧnsصTR #=|EArdwT(YQ"`ꁪPT" 2w iO,%I"bzYj( X|[2*Ay/Ϫ1Y,EL\ TA,hi&!$MJUKm$ '@N:CF9 0FHH 91z=cS1²Lc J[*~/o/?>r=5o;>ӳAI0 N &L`/_gp@ٽ8rʭVڶ[mmmkii[mU-եx#3h3P[J3)\JZ\XXS2Y6&%:}O,}_曨G0?{}*bѥ0\˗D"EH0(,F "ʕXdAER‰**(%EȵQDAH0PDPB,DHUA DXĭ4hdJRFQZV1 (*6Q0a+`b[E##D$c$ F!*v?D(ߓ%g((!4.JlqO=ط"/ n#~cHX,5^@ܜS:V+jϪk='|4 ;;}BHSvH3"u?̊-V̂P]w! Iey %Ԩ "ki959"KHUPYDѠ]Gt>Mν9vsxr [c/|2dLg&1RKHiHfIUBKbAO3G>TeٻZe5 oQ"=6cҝLdS2t uBI`gg Y(s1niM-$7fzzٮH%{xcR"`,K0gօBR &tuՎMsͫŚ?ʯǾVimcO['WKY9^&|[8v2v,\fQaySk e۩s6Gh2ՍE%Zжa7Qydw ۞طCx:' RDFG5daUD $FI=2xz =Uvn(q*`$FED Eq 8>ی;D-0!!#I(3 &3Oᡃ^Μ`*PbYTU@Wp0(O23Sw 684"fD5Ѕ/JM/t@D̖٣tЖ΃kXf-8iE[Ԗ,ib 0EbLa4r`SPD๤8p AajTLRpa\EA $R@$1 puL@ZD!6|$:cHy׏WHӊI {GDXcfR9ɹ?boQ+%*`JDўZ2W/כcgJiLƵ;Ӗj*7o9wZ^!С/'kDUuh*/nS{Y8^:t:nYma͡o8`l Fw;ވm 𴆣8f0 lTH֎8\~~wHOo?]-st23yԂ=ea_c&XS` !=*IQbR1bA?vR?g G,?$2`?O$6)KJebb\" C O.'%FA$N$v/~ՕN7_G.L Uya|0y/{.sf;Bz5 *|\>$?[G eayֺs%LCڗZ2jMqMإ=3RA%k7>o߾Kw_hF*zS{0w[2@vcI )W" ArPyLROM%ڂj@ObO+#o/lh&$Ѷ 9dZPe=|Y9S|V?4 @Aa:̋?OMY""t~K2 HޘdYTם۾!{@v¡4 g{k[xjN笕<~p_ӘBoX5Ba ͹V7sZ}( DҘ@vBu("A=.7"#2(] M@YV[x@BsaUdjXl1c qd7Ҥ}lc4pR{ѴWTSXd 8FZ RtN, 5vnUqiޗd1f-vܝ&\P8l5 "c`XHBAEXAaH$B, RAd 6!6 6 ^Mvw LX+r>1vhw…'|f*`czKޙx3=115Є4،P= @ t%s_j?C7asg&󿓇OqƨCbHB$njFhgvxqAq;kSIg[x1w?>z|GuH8U.GRZN=sXӑc]t#VZ>[ yRKS4\ nH44ݸ5UyvÀIȞfyZ7 Gq M!u`RKe*i|xŃndbEl^4!\H+ai;-/vo^!'g`A'TC?0zE/n"[r`DHD7ϧFBIl 6ܣS @Rуj`~tPO~Zq>΄,4Z{Rxjtɛ{l߹2unbRQ#U)B~h)b ﲪJc<}߉=otS%(]6м||)纓:a-@j$xtޡi`:8(G8~d>˵:.??!t(-xCU<{~RՄ״ɴ5DAٲ#S8"HX@&-aj(}, 9u%SWb'{hR shhbcan/v>L蹇R~o>zmqYOw*qn_>CQ!. ӑ< l ,!.:2Qѝ.:i՜Pn 6E*K-©|"S8dH{#>ʘH.0u]r?!J3@|Eu¹A!Ee[t_u귖;̦wI*Q&hP (0 Pτ8/+s-6r M5smWO\<΂ ]J!5)C 4kY]p-Q2d0aI4Ò($.@FCj6(3]{v+$YI [A't ɐ{.xpS{-!2A"[n.@8Rpdk~`z؜BSUEÇ.Lf㧽B(C RpE\Tv5WP}M(\![67k\rƶBH>iѽ2ma& TU>c>cMң-J>U5d&WfᄂLS"DXi{LZ4WU \(<[액2fǃT˼4|,Kݟ"Y8LCF6|9 n֖J:jHo+pXh'kkFUl( |l s:fj{g$hm̍kԨDqt.Wo8eK[Mza6ׂ11FT.TqeF)s* b"UqK +rA̮%[JXG`g۟MziPi sַzF_dtkUUԒTDw=ǾZM:xZP ! bMzhՠe5kS%=pWl%%BkD?$uU'Ļ# 3à") ^ӤZ+bQIAVDQUU4ϓEBT-@(O<>6J/IJ~XwUǫK #D Ő4B,<ߋG WE4K{$~],LYbMHb z/.LMHu-a?ZMm"!T{Γ [ ԥ&x$uu1O*E2Qؑ%`֓Harfhf4m|vR@7M2 )=e่@I?1E 4<0=$줠,l[9mZnQ%& R繤@gݚ"ۨ5\rVפF(\<ďI+'G<0۶ <΋vr۳Wp;iNiOQn1qHIKxLOHԽ L.wY,GA\̙ `R̫A65L>9^wJSR8aDB["A#-9H޻Ɠ5i-y0f5'',*MܬI4Š@!dg\~w+ﷅ? ZY4&B5cKxrMLʀB+=3sX~ZnՁmR)2H'6 na2&h4=us_ON/_cޏtjƥ2b. lL[8ytҴ;mA?z@` ptj|GS mFY6AO 6 &8ٔ=ۓg 9j$H4xmxb,]8gm9<A7?-&_FQC&.n='F\ h$x dPq8l<6+?/v 4nkef+st04҅}-`!`cA8 Tt"D@'S^Gu9BvI0q7?(Fu,$`B$d۽* ġCkQ6GPFHJq~bQ-W$zҬV rAR,$? Wq@ y&E$B_m!mqsi`rXU _y 04-ŗF;aI-l{9{c ! NcN@Nn.,}־䥲Q?d<쿉@s"/ffD hFW>8٥n@TjؒU0':r֦kI5 `.8]1u4=,U~ Q-O؝ ,'%o`k$ߊuiԻc$u p4A~Ddӱ0zcLCۿ$'iUهWz^];bZJj BM|$x"]U2KkⅆņH,*]v4{3Sjg}3{bmh^G%>Q(%JOwNZKƇlx\2d"GMo|quq T?yx9o. lcUhGzHH1F dcm$X !#OP$B+~[2O'קpl߀XwP+us\; ˀ[^|;l fsGNNѾ~W7NϨ۾& 8>́K_[8V 'K;Ak%I%K}Ԇ\lBmxN_kX%2b9 f`V%&VboL1 &Yw@hh W][$'\|>mI Jy '/Jz~wAjCL+w z)`TKxM6 P.08ى #!QY$_w˜ aQ Q3Ͽͺs87Sq1%c@?' f̲0"oUŇ?+N'Ǫ@H`n$2BЏ ] tFv΃n{ؿ] ;~F7Cތsޝ}D]9YbNs o-Ҧim}w| {+Ղ[\۶n7x Ajl cC%M=2Gmx#8B;d^sߒ~ )2`g8J=Κ]+o[`G0.4ecEg3d$%rIqUϮSFd YiLhEie@Fe䩥OcR3}Ji&xC0dڌ4BaE&^D ?/}X[4̿]]Y}?ay~dDMSTZվn3ZOh /?Cb;з 2T_6~`A?7 D;8H2t:2- #vta,))cqt^ zjYx$IS~BÙ $\}d9YO{ݝ;nEJZxb44tƙ^ Ӌ'[h{6Xfk$pn7J@awf-I#e0eD2@ ^35̖6-VՖ.҉x+'a"/.aS׏j:ei[Ԇnug(@x8k-nΒ|oʨ0Dc=h2E!ف "Z^r |T*+-Ť,B2U \Qr'{lnTF-җ6y h2,EY$s<7"v@ k*!x|M0sw_IP20I |(:GZspٮh{sce0&=7MhJ,ၽ1R! H[wzYsT"j M v*;V. >4ؖ_rn ]NAiUPR;P oE@Uc5 a| yuV$:~{o "eZOr]ٝf#+r(;-$&9abW#ɡg !`-A!O˓@J_[\~"ܚjkɵĒtzuH)e~F;/`P[* xWgIk+pa\t?K"S :?;cqLP2qX [?p{_΂XH` _@@c^SrrPp,|>zE: .B UC3xT.qz|]lif'5LM\ ֨8glCْrK<}<7,Ȉx54k@+*ˢDA<1)C[?FI PmY t1n u1L.D!@̿Ş%63mPTZ"Y\{~\z*ļi#U2Nt6>cS  cVP0~plwgn{Ƿmض@fxM]Mq|Z&ĴԅF<^vxiͳv ow9zt}0|gݷW6e937n|%"M -f1l4 C>Y=y8TRe3pCi&-;Z7or/fBzI=RTxY'f5Q4jm A\EYLFiζZXTUy5_]њb`m cW`wk_HJi66K2ϝT7sG˟wy!h@9tjAq1 @6ʡ)j*% LXhM6K^]+crmjdwRHx$cb2F1jy ,'#"QAB"cƘb8q‚]&Xw[ zL?YYUdB(rܹ@Uآ7; !MbE:0xPLo/n+zέEw;`{#^9#n O@ =ftؙjO8gGӢ]Ɔpڄ̱"a)ٟ eӘr?n 4EͲz.5w/y,Q)xpIrLўD -1W⭳T1huаt55~}69D+_B'uu>m8ܰp Oۋ׶a6XjyӐS?}\͜ňWDq3Ȧ{wR-IZ1ۻO$鋐(c[1 g5sXw/Ny{yO |tq1 8+!2-;1O)M]kTyZ' ebw/Fk-CD)wr~&uSD0xa_ U`J4iK'.>STَ̳әsX`^g%cˤE!"t5nek,hh=39% '+n7SO\>r]ݵ|ZE6֒MM^R99 PXzU`PUIN_xeH 5<8WA,!{`m81&>V1@vX#L_۹Zƒ{u= 4Ӓ+BB@Qצ{8jE[cwr #LPޅ9cZ!;,4u$6czb!S䙒c3pcYbSy(MznHS+2b},Lʝ:Qnp.l(޼S䬧ioS] WҐҙ"Vy尧A|c#8%hAhf8Ƚ (nZ؀ jH " 2gE*ˏ/3,O=cZĘa4OKi^bykIU#btf wb۶YNwb~nHzIoEVִHUj 07%*jbVr Wbed]PM&k~8Be u3uBwNVnޑ] !R@@D NxsܺwA v LNpvf=^QTl)N'rBS H <)(A{L0KJ lDmI_ysA?eK<0;d`$QLƙj/ {;ops{ooW2 Ht 4;w[+M?jnV|5kP%{:JZV.J wpHoa껽Sql\]~ݪݍ!4<#Ȫ0<9x .DXBT&!uۘw"~g{:P#' 6͛kekP3slEݾ᤬%'TC@? `9FΫ׮䏩ɚkK)bh9fz&C0eXO x IvmOѧV^"6o m84&m(Wypl,z` $.~m1t_skWV!$(m lͧU]xsvM\ >m,Wd"IjG큑)s z5jy2|nHAxjN_l-=GQt VςR\].͋16Pז<" ީn:4tYzp010AkO$Mմߣ^x O_$ú բB5T~x}'Q_;[ۏ:ܛFrXقa[uj_j/H ϛa~O{E)E-`35W9?/"8|i {c~Fi`ß;[fzMSzR2[qc`߯cU'AGψbSq('U|Y̟zWC"gkW  řJEN0mƵ[y$kcgh#yYDO$[[W]7ۖR`d;Ke5C>׃P`1 `׻폋_LٽXۏ-)P⿏N_Z9̰MϛisuѤH*H u<xm_D(1b1TQF**UG_A.92}l R) LLla؝19ߎ$Y8D6rΨvjBvf ;68syJOjA[sDhuԇ0 =  y$9V+?Cj{nڌڃʎǃmduV<_p5}qp1}mQIQ~+"z|يK5I7g?MTQ7M\Gy ywme3Dm&v8 #m(@@ =] N*Gޒ.%@OHDkwa挓K%LszMeq:V:LB['6)'M )B؈ypOcmu:Q a܊~*(9aY g.g,L?!b ^ 0b331;.Q-.'qe1E|ƌbѢW00܈u"mRF%Ԏ 5E+H@c0Mk2G>}~s[MGyMk{lm 2#CΎ =~QhԒBrZ-ZzMy`oD! 0-9I_pZAY~4]7e)\Ba"cF"' /[/'k_Nj7phۺns[YҢ8D4ٹMSE ʥڼ){Q^߇%n5} 73I2oE#!ޢ8uhVǾQ1 9ȒB`HE:E6(vO@oEc dVhaC݅}Y]~DŢxA ~[4λOͿke-Q!_k%&lmks۳o&txzj^ v|a8'nfD=! z/+b=Mͳ;vpoR Bʥ ^;a1]S>OT"i(a @uT:jJ}݃€3MoIkkW\w=ߎI(Vވ<`K%v$JTD`@c}h8hT3AH+W_P;}{s,aH 4"H8`u7rbR1$ 2āGQ 5|Am@:E-aEmVӊO 9ZwOڮnHq*{Zۧmœ_ & iN|gp=`if͜HU37^MTJqIA,U m{/6]BEad$RG͆ovN> f| Bb:aI$vV;P\4kUcwNm|!WŚEmnB'ټn2aW1c(W팽nNQbo-88yd*;KSDLjSTPqw総m*}yBt;) @ kat<> פIE&3sz &bunPa7ПBj&ܷ)vo.Musws NL(k,a5Q*s_QG.݌ҡmyOgmߟW/CK^'<r%ͼɫr}+e#y̚t$;/`Ȇ  nVBٽ\@1-EbJ( ' 5g4u!B NM-?XLAL mZҚr `6\|O^,+Ϧ}ҥ]f'U:쓖 ] -Nm{.%#6h5Aؒ4mw@%~wyWE C*XbgN2޾~aNQsSddv!}p0J4@9VU-mVM=+ɿVktC۫$v;pqqKy/C䎐܇Vmn[x"nzv8yJ]d!bNݔwlkqvb1 9Ђ,jE&nE_cWϹtEzBnP݊r aIotJ,hb$ F-KAUNb- 2GGc䣵FwBwYCQF?aJ(Kf?1iǦ XB r8 +k)D$_ݦ>s ˗֮aRXAC7;S.b~ +71fr-C51Z4`a$+XJW|ElӬǔg3&BD :JŴh+44Yœ}mlx]Ԣ&ok~o!a ɻ ϙ@G'g]$D},-A5 JnybLY1Sy#,W 6J3+HrLRfRqmx25YUAa+||רL#o3̿bg^ 4DG7<oE1F1%U?{Pm(\"(>&~0)W>=ؽf Js@'rU9cRHJ\H$*}"޴ItԹ:L?xW l 9ۣ[yߛq,yiI?Cݹ uw8X*]OL|Y–Hu-уNV(Xi_Đr!zٕkF!j- Y1AB.E"!#&۳CUHQ;BI}v)mK9O븸dcM-TH7zxJI9~s xR\=J65s~Nqײ#dYq'˔K2wV֮)}u &Y@eՇ,ee9Xaqۯ9)38cK| esbDz8X"yGZE=za!?EI8`xc o)S!Ѥ'kCsijYRRdRQ̅مR*!$*k umٓwp Ks Ju`7 F˳:Cxmtt1 ,=SN:>t*~|AJ4KH y"i@ m~jSI5XX*h҉50$"]U!D@EFBo~܆wypqI|i on֦ - OVI- }_2֊>pF;!O'4c/c^1./}s+ MP@)㲪:?ion-_:e-gֳ'G,2 nDMn{tVG]:HK,0 ?NZ+Z'DwyD#4ddpos6A-t@k?Fl*vP(gI@*_'|. $k\c)BUuTu3o@#+3*e}`iamHEI"Fx^q ; "b WQ}yvΦצ{MYmDkU&O^'#3gΔq7v]F ok)R 3g:m)|o MAH#$8z#YmpJo'YfT */5W%.ԧ\>\^sI ?ٍ,3I%aډ" Ѩ ?)V/vaqtƖ}rN]kQخ{r:>,ET  CXdEl^7`rL&=7+V?߻E;"ltMb+O|0@A'lZM!2LF Qc9fA7n.Q5l *ݗ^SvL/o^Lxkn,`s[Yql_Gdp~mW>}P@]C-y"4C7eʥkoC\FgL!cM2q7^a:{<-\. T[Im^AXl1i3\,i2WybJAو$̮½V=L=4\MDq6S(2ۘɆ˷>I&̝,&1seְlk0UӜ@ )6}cT#ƈ8m}wM7P﨏!fg{V@*fyD[O/bNK6KCD`@a1i7a=zm4ȏ2X%tA7$/puRCGJ÷ᗣ­DJ7fߟ+ļ΁Vbm6&Zl/2 F#dD߭Fee3ɛ@e9ɺ'{)b md㓏LSGd3579CuJo5@mpg4_k~t4I8eH[/'o-zYhhGgx/{q bnCISB%{ȢZK4O]؍skDN9ˉĎOsv̗G ;8%sF{aPMCV3#UiCDhIGQWԃ᷊hX}ͺt5e8$vۍ@՝3{*òhD/LT­AJ')8?&o{x6y'sgJaL <5ȏt$@Ev:&d'Cа~Noaΐ:z[oca;bI)7>ܝ+ 8$H$`0$H@0b C @Tb%H8 >q_֨})g\_XB6!z]GV+nL5rw3ďd0PK!eC$z &a5Zu ʥLlP )\lZ۲^I AT_)E$h1E}ߞ<msQߺF=p5!g@x&0#{؇?KpIcԈKgeCR%*Ew72EvK{Fڌ6οigߤg5{g[hG p)kb%Xbx55IiRq9+7?ʣ4EZKWoL;FsQ4Sh8eJ3nߥ.|+. lm` QLO(xQdj_FC6@&rgϙIkWBKF\bƉkpma #fѻ Ӽ _Bjfg0y5> ȥ6q5ؘ.ӈϭMY4V(ro.~ P\wW j ^)ϲt/_"ȥBfm"HcݗW=ݹ'M_?٠iZ lXs̵ܓzH5huB}?-F^#*ަ}K=ԀN^`A=9(I`'ޜۻ2x9sIei1;i0v@K~VOVlDk'<o\7u@ P`X+i0P2Z$↯%#MĈ!1waU}~s=f_ɠuDLnI6,auXLrǛ7WŻтL{N@kf4Ԃ!CoteV7]8>˷`EےimiۭBMYG $ƀ TS/liM:-b]zh` p6,_jdyЗߖtMIn`BChZ?!84Bjc)Yjq&χ vkyeI3wm_5ITvҘ=6OӼѿ(Rݾ5aٿ-̜YuhlIH[^wH[дek՛;jN$<86]cNm[3 Lg~YU 6ݹ>z( 0SdKClM(HxC ~O6j3͞?+'0}ۈ p'>7}:ua{Ү4,GO> IBR\P/w3~ͨA.^d0.쮨&k ? 1 &X ];ېӰ+XYLB<ېD1E(Wpmclc~ǑHށO~۪O)aC8p4"ydu2`M\FË ѿb;65ò~!O5G!W~u[cCpm[|ϏW#my|V0I Mhvʕ(n&f[& !F0otPnNS|B C٩A;^0{NSպooDΔām.ƖY>06}IyI| \xM e[Y]{:=AB)}prdfK s%}~<]ԲhQc 2/VsAg%D"ScxV_xϚlhqcuGw2R_4~Nrp E}>m>~;ʂN .W!V:$Ee MP(EM%|ַgtVĉkoΜ&L>L|9|MQYj k7Df5 v hi덜;l1Q&T#gOBUCAjP=6@UwR]@ !.ـysl2ҫqg~"؊T5An1#@$j\[7Jަ-m6WG/h{6͸Qr Dݻ,Noq; I:Qaʄ_gkrzԫĢn.aG?j0@8lSShU8᥹~ش)Q=J4tc~-vm+_8; 3\hڗße5BYdȆ>_"{ ^Ȍܷ3]2ls'Pb?^q4GȜ⾵o԰J}&e7nL@v-iY[V!#{XޔW]ٷL9ҫg!DH (Ax<1 )/ PXP*ƌW!;|'"JKBe"E=b u1iYڣ3ͩZ- ljCEVtBLb9WY]"Mdելuj A cQexJRqZ aL6ڡ2XtjHmZ"("MTCөn$ ^@=Қe"?vwṄfr~ΉNSЊgsSFJhp  YR$ϓJD\e=֦Md|'~|?)s"oA??Fxd B3fMxݮnIQzW!D(bXx4& uZ$w (5)Y2LH+ˆJ^N$.*ܖJ o sƍJ%='25~~yQM)}gu F6]Yh1O^f+П{GNC#&Oʐ$lC[ftpԄ^$_= M5S.bM Yz!:o: \!n)SbwN&V>o* i??!šjf4)7U71Ҵ:SDbq.:[\nxo*QL߯k5j_X8p"y9y793.Ho5=/+&nk>.xEg.i aDznhXˉb'Pv2I#w]wK6uy/?/_B4Wȑg{$_muz\8-sT^|s)pop+ߤ燾+]Y)9%#ֹ׽Ďc_$/f*% ,=iqu3n_مDѠ>`vW] PAB~k(އ%u ;S3h0FZ@09z]풷Ndmet6h9Цzz8bQ}=6iDy %ǤU i4Z{saXxz y& L(|)'3DbFb^2P ozLLTxb@A;)۫M2wPa.b>Nf[@.:}O@Vڂ4sq:zGNxYQFA˳2vE oVeBB<[jzv \wە3n6M!Yr//A9A _bAZ (D EH4fK~< ]#aLm7( p%}i , >@Iǃ<=F|$g~CSd[<0_g-z]ί)5c;&{r4t_x Li\b1h7y8)bT#}^Пz4jrtC )vG37`U |"7-a3oݻϯ.4(@hXؤr vm| .@"h6rp{n=Q[>l-TFųAB `]Q'NQ5Mr~(A[UB j"G'}ݔ.asvOi3k{$74PBŰI^ rYp#Y7 tz`%^'mT7t^+S%B@wW&~Y_!mcvs=g-E!Ð Ƀ-T@8,$vWIi) j^PqmQԌjmȭj 47&bf_K108} _^WkY <^`Gz1lg&`^vXJ^-1(L $$A WM~AwVzL >ko6sh8! 3&"q{!o̓+WG소Jy6BdT|m{T]*8QF#AgR(fm^;ٜoc,_Ѽy|ZIi۶uVԆVJX$S=[aPjj]Eu:U~9xԠaҔ*YM.YyViͧ 2BV5f(/UNt.:P'f_Y&[vLF DHB T#C9Y[̩fUstt+a,U9ܛ x%{%RR>"Zx8j'foھd9zX00JacW-"HK )fS\n)Xk f=8h`њ3F*O_;7 "4˦,0*b9~uݹ^QfP\}|OI4ra& ߞ,/£x2&YvZ=#I.;qj~.u:`dS$Bwr_=@v꣬uTHZ}z 9SFo)6|4^ӱ&/Z$Jq.4@rD[< IHb&9\a5+,gN UAդkE9UIV$~op G>fHۗC_=XI~]~T ҄Dc`1<@ {:Me^sW9_ +E7UU7H%һp:m@ʵ2Z~qc=|pM4uz\jA{o$_mD?9FwvV17T@: =n3gEi秱 @ +WPw,&e8_YTB)RsbTk."bsT L!%}z(@ ]l/ қ˻ x<$ s{慹JJ#A{^o tPoo!oU{b{O*fl@A  H.C[sQ[Wn._5R}1}H#eO[7h8Dҗ(k/]YAMm2ms*qwj͌qy,wB@ 0 nJ>@^UW0/,,@FaflGƶ Mr )H˙ T 9+Xi`R~>`O_{_ܚV|g|4>9 &\A$䟺މ!c O0|* I3mݺGnqӱaZ;kzuvO%se Poa?u-؄cu t@ O-㽐I N,̼1b>"Xc"<7T6?D Fނ)jQ4wzÊ_.+IRb Aog>iws_芘VU$R>ߖ;k~Uz36Ѓx U$}xn$u&wl:MQea0N1c >[5PխR.Ėǫ>LJ.c:<6RYoo| <b,u}9kJYcdn{ʾ5.Wy8z5oƧz[aNGoDSRPЃvh?(}RYmIFJM6B,T  ]LsC}g#FKbĚn.@hq{!deȊΒsk١jzQ6nblOwOW>3U:<>*a ,W"-w7VtVC`heK@dЮ#L|nJhfv #1_)XxNO 䐈+9uZ9F,Zr˗:I?r@`m,R֔ZO((qJhۧ!C AE\}\Y1Y0^vN+q\NZYPlZV)-1h$MVЃ6I^O&ӿV $q{dqL<5-?$BĿӨOP }׉ OYw>vI7O$:\g&<` Ŵ8QzϗJ317qDB-ւ mzi[v5al|rzS+#ݿ$HJq1@aR^Klt;I{(s[R*m_5KQd/IJ5SZAF$mHfK6lφÿ_wP:w& T .kW¤!OӹvtJ@OS">^=t4{9|dL o_=l UF(Hzz\ED!7qJP3 FCך'Lޫ9￱-\4QAS`/d _;UU@dG`NtVDrę6 {x/Ei!m&2N 3/%tç:Ykf/@!P:.3D˦t94wq*%wSN]66}1UUp?`SbUTQ`s}}@\0'N2a&; Ce*cdF|K^Vuf* q@.E[>&}d9+5CBX5DHOx`=v> /> |_)c;]=t}dS'݇t=(AeFRkɛ]\r}>mhCϷKպs<ݽi=H1Rl;a8 G_y3 fIX2^f`o9Fp=k\CkJoFA2>{^|I{R6ۖihNyD$JRǽrTAlInYU_ x2܆ʵ^@YspZ2P;ߺySw$_bc{[ N.5(ާvE \[%do&cDč 𦀄37֋63;eOg^$M9~u& Z@\16X``5# yZJ,۾\pE A@ĥzP^g}r`w@PznAOj!!/x\A L-KqrD'r%I@\Zl'VJB 7oh.š!> N/v(=%)ˮp&t5zX#~rsv@B$p HNCs[F$f⋜w*L%n5Q-&#AT\ƊF"ߜH\s"ݕaiR }t%JlX(v/oyua,ig*D@I.BKV=%Qw"@UD`2DQV` R2)X?\sك2~{1p h" 1컞W Iܽdг; 3^<`~)M+pOjK_:FzYϹupߑ$)ϗ~+ռA}vO ӽVFyPH4X[C-Sđ~djS?4K%@1zy{=.-~9vMu0s>խԁ_:pd7@h̑JΠ]/.~TO)Tقjo5*[=HD@? KZinXU0U=+y_m︯MdnG1=L(؇5'@Sxo9po6uH~"߄Pf*5YN_9!?;846Kʎ9Yn^Y5Pxܝ`K%9ջ;rӳm|SΗvuVIХ/:A"nXL[S.I̹OK66] Ca|KV@ކm/O%%g1gRM'L.ƽ@ۯդh! ˇv([;h``csm[{'E/ Wh}4"3H) y[ףN>^~I=<ۜW{p! ,@Om..J&x/bW.k⃍JZɑ A"h{Bk\ qͷ y1==!cC`Pm uKun,hZ?/{͛6KAB,dEmKOTy`3T"]a4VZOY?WC`>ǪAxȬ_v݇|YEM:"E4R3_^:7ip&_,>vd@@۰a09=BZ f5BO{~:|S3 Yѳ`k ?rX9%ԃ)*ȣF@ԂIO&Àn1W0TJ0\R2a@`fM :%EWcw:4,D<,b (X"  Bz;ogf6|nEanRije'هZ&o8iK!V.ss%^O~ ]+YuwkMG!wiۑ{c۷k;#B1 7AX- pےU!36ufћ 9Y}-21._l>> 6}MBb.k RhhR>TR `1lt%p=˗Kc_m{FjgLѠߓKL}xޖڃsw=OO owkŒocS$PTsQ E@*Q̹٘g>G9PM˦2J6FBobvܑLJ -ĀibF9&0 "% λ0Ԁ{ c~/wTgbfzj]uL miI* :QBq=4-l[q|=ۧq~ޮ toP}a+ftsMH<ڸV\[JF-0䄌Q6ju! ɹJIZC#R#DNC[ױ[ "Y(wkĊ[\0dX#'w e520ήWDk9#}Wƅ#s! sm n-gUF5Ǯ-@lz61Պ5R淐792Huwb}٨CvBƺ= &re2#7~PQ5ueŖ KKkڇYm%7?C=VoNMYp+k }LhO>XR纯ȡfȝl #ɑd؎(Qc sfiEK {C ?Mwϼ?h=\:o0)8wƶ9͙$Ih{s. T0ZWrjL2>!/n$m*bQt %7WjjſiLNۍFyZa'Zcz6ydj8V_%91ȊF^&jB?o%ߐcuu{uPM~vlW[!*.C׿YhN$Éݢ濵Fi+[c'张AHM g?TwPRZ56Yt2'f>;վœS;i^@i rxo(B~PIR>t;}|}D|} D? N(!ukW {+~7+ׁ^u zᅾWDO'?n?Ȋ eΈ8 =۷l}5g̭2H8,ʢ#oMd߀K%Z 80g}Aec2B -uP+j~ uKxFt;>dž>v qw1nSM4;Y9 f>=Ra]^~gmn"/Ø;E3bp ,KmW.WXrSV `> Yq9G4#d &xkږެMNrT`{Lf9 Ma9[Mo%)e°dz3|W^ؗ+zcv l6$e~Vze N$;pL#VneU/ ; ׺% ◬{rDVGEfθ;K ׯ[$/M 2yx($ٍP34-D=?@EZ[¹BGǵjm!^^/u'.0J,IX*@@^B<۴ߙ^v#4޺/;-T鍮3͈ XncF$1 5ͳ ItlgNy*J[cT9?q8] i{ɮktغ@"e qM80ƖN &0mn !P[#rͥ^_2/%ЈQȁYJMTE/#]hҞk77 Cm66 Oԛܫ'iC]C>|]/%Nd)b"4"ɤlɭ8m`\o֋{n`H ƐCD$R: u$7%לt?,)@UU/3zlX41a"QU%bq@9iqX2ی͇YEXx }x֫d]2=N{iݖD)7#[^t'>u*qҜaUnVr4O>7]oҪ]tӘM@I(&(А25jׯenUA&cadbn?iN|r7"nOsDhtĞQp,>polmm#=ԐR/Nx`nmU [1)Y0x&]SMUa T+tRǖj"3Ne.KsJ0eau5;"(Tkɘ+>\d+Us&Iu)-mͶv?\ߖ|(N8rFͿ9/HPlzz6&|Y$WdNd_RWFM񟔛g.>mWۮ[{Lj_H=U}\3(YhvCwըjfc8.3_>bU\:0>O;}2 rʾ#Z @Bg uǶv$˯.AŻ り(p"& A`+J߭Dut`.@ X hM!x0oaH=_5ٹKǐ9on[ M vJ[E䐴Ќ]ñ? =Lu4^~v=:!%6^y&"^"I9(`̤.mХ eߧ5ʕBڳO1)Zr@m[@Ҩ5ooeض!]=ܩާ)?fo!~)طz`߶w(MeQJwyJj!)/f4zVm{5ke;~0G5- }=](Q2\y goW2dBہorUike9 A\f'ἵkwW)+`pF^n:/ѽXŐEU`v\'F\d <=]a,$,LOz ]k1_h5gv]KMbřě}P]V`|YQwqAQ*EN\:dJ K9޿nII(.$QX<ysiZ7SU 9 8kdKGS?] a\5Qϒ4ifoB NtaKrʱ*ݭi)z'ߡR.9OOy ۆʳgPy&$3x JW}otoۇ_ f^;z{M<3[W.=Xe4.yݾ;d >hJ3C'o6EEn7N;yAIM[O;,}rGx7z%<0vT\Hhvo.(\_$HŲlv%g; CBzxNH|~A4 oCKmy2XeٰgSގbM 6,F\;{Y9CphhN%/o.;Sے.۷v;ta oÉ6\akJy;ǿ"6䮖 gV)f߷Jp7뫍glβً( \:9SζUvnQ&5[\' 9c7B+yI 澦Ҙ|`VN3p;n]w*UM}ЧK )D!HV8`EcFml Kar [ Ox5UQO67owonq॔g]2i&\f,"؀ fl,rݼH+E"U󉠘€V(r?1GZw}C}k)QI^,}*5 TUs^۪?Cc#ٽSBX^+1%jY#1 VjwWJHH#~%*0*'u)"GYSw,.tNC//+`k[axSMp-9*&&?f<]^gM7235HiBHEŨA)?%{/3O^~yg6=A{,>K,5\gb+e"j5ߒʴdg:*Iw"ȹ9B#od5[l7N3B QXYW=Ĺt<ҋK>?cx;mxcC9x#e@f\+~ 4LNkFh׮N\/ ao#YNBW;t]&Gj=-"wfoܯ<2Q͕WrY8LGRoW{:v>zٵƫv3nspd j t씘+*+]qsgB<ŕpj:VifzGݶ({6ې1kDgй2{rv˜|/5`lWD3{vvTt]H) 41*Rf<^}eO1o)Yd7Umz|:ԟ!?Ou~ 4Y_Nz&0IiLDrw=tMZI5.nFMJ!: ;ztAL0 >B_f@2( < (I:2<-D'bg::ÒM<}um`YFH& !kdb 1*üpnњx=\VJx!6s@ipqnz%p[@&\pa$ozުq_fĖ&=,nӡtr$wmCqZH픣OJjyѐL= 2 ,NuZ1e8q3S?.o7&ۢyyo !$AaԷFcGAoZLH:;?ӠLLroy_g@-d&w7{Ú du Q_'ןŗ=~ uq%.)jt "H)۹oG *>hw ӏ "DYQwrH" 3[.NeYrO4A.w`c8_sf_D# Qk 4U|} kGFj8Әކ"% 3b'sƸT"f'W2yN "E9B F +C[3Nxf4vmI{) Dމ;?Q˿7{;;!1DnGɳpz" i )Ab_=ˉ|Td\iyc{k#{|}nwR_~F)ŠZ+{IJ= .ܥ^we`*>?炌ó|:O{\֭Bo.%8WT^z|x_w7Zy=~Q1OӌsqNM憇sgy,i ^iB팸#/[_o%+ک.]v~3rܧn(W:v}f=.p9^G1k6DN ()yjN3o%UHv9|Nj%OH~]ƃ9S!m%N%IB!!2])c]+/]{;jSe-'(k[PK`henѦ֩&Z|Ɩ)T^U%O{L\hm%\XKżőàUq;z p`WX 8(qf6-(iH^/d#:9&5oQUWݦ>75ܸ-h9BjnNv;E2^jζ5u=&4_ _*H_vFrmay% àf}G& HO)#:g5l-϶pw|ՙ›@2 G4 Ƌ!"9P ;{ Ѩ2ު`~m=~ }6!f U+݌3Hdi&ÝNԇf,kmmnȱSPn/0VfZ>s>ݢwvP=a tsG!pD1_+x8jJ xT>۩WJlbO?,zx? HbO91EGqu$q-W^>zmak-y2:Fؙ05҆K,J߇&^_7eH1]sz <{y`FajB2`4Y.g-WLX| n q aɳE߱ ϗ.}>Cnʶ\{RIG~[鱂YY%,݃gtViږ2Hę3}hh =(>!RG+lLJ>5kq6?(S@Ye3"m~mAFG$g14Ԃ7OSJ*FO1V- \A=iIOGG*AZ]R$vItϋw!K |'}/:EuLvF l 2xH wc*=#^d ïl| }BM}^jIWT<6F=P5Y&ACMe8&Jiq5|h޾CY&7PŨi$An@HdEe5cS]'b>=%ߜ֦Tq/rwS(54 ܖX7I~̃ϟ˱JmB mC6Wvm:BJce,\upLRCέ /Sc*~zgNJPkۼb1 snym-e[t!!7e`MͪCVFQ78 ɢě|J\:kxˈ &ؽS1>5')&'A(TKj -@;aQS%!1d֟WwOgbŵmcf%))fR?А*qX TV%佔G3oN^v#pu` ~N9b/P_3s]4sEظV}n)8DJ6:u2^GqĭNDI2h"?IDΒ`xxOw|u}#bZjӲ'K;@{Xs81~ކ.N%D樖pݽd5ͿCOV~0e|׏{4P>ӌ>K\210~ /98%@"͠DrZpFlA( @1JoR!w?\/.tLgqԘޝzg//u~βb.7(yL&|y!H/DmþW ^*RzYTws4ݏPD.J>; yEFiC9vBPX%|L"A q4T7/ _y^@)ijrX~n=XErHӢ獗ljK$?Ja3`,v K7׌t>:wc;JnX%g1M֣E43 2suI6cOKjX`7R'`b9 fi'X|m_.t_nJ:1l$) SGsnz@ oOuƺ+_u6q= 1l*Ɵi>e(ͫϸ#kk'3ޫp~fiv7{]t)}Kÿ_W1lׅM[4bs7bEXθi-~Q<~2ʂۻhA!:.z\vs*sHN{x*=5BYr7l&?V~^u;(:{X0ĔZLϒ%u*TeTBdz;YľBd*ro/oANમ}hz(M0$PL=xY~1m7pxcgh}=7[>}1J Jf5F4ˬ2jØȏ _0! aW~P*/M)l5 ƾj~'Ӊ@A走QM@B蘷Vկkd"jBgpΤ^A~a #ߒ֌?oBs`30cυu5)ZjgZ;ֿ.̧zAQ51 ` W Bnlr &z~!X<,?OVjfijo:;'*}#kXǂ 4X`.&sN))Gzތ?{րW ˻Ґ杋5(%Q>U3?{8N\ ͟ >uݶew:. 9ts&(l~\ ]W#dz<^I:YgKE2< 7D@9CA{6cbs=xYn'Pr{'+uϡ5WV8NXI$T"$Lπj')G闸̪|,Mmhp> Vx>IAc#* *0Ŭ** a4ixe;y@LiAHFCcW"|Lؙ,S0ww"|n87u4g0.9 ]S:izjb K!e,z|a傟ʇ[+PA %DA>R W&݊ рݞ R B˜_ `4kpoSX7y/ K 5 er`k\r[?u&b`@f8_ү zlY&1թ@/@Zih&Rȓ*OӗM8c֤r?WIpM!ёBtT.@6@$lf>١s| -, %ՕiMO5qG-L'`Eq+rP"G5q|w{aȽ<%O-4g-Gm/nlRܟ+9F7/.$Hc˫#{X+U/Fɮ?qͤ3m[koB~<8vͯsv֙ բ7r`@|E\~:Wwŷ*N-: q=.;|TY盇D_F2t]{AroLz/@9G-0>2j_L FdMkǞn?K 5y.>Ce1#ѕ*M$Hˆ3cBDԶn\>7mZW,MqҼw1JL\Zw/_@4&ؖ/#}]h\"F0rHBbB#;rpyЛ~eH4Wl-}'PE`/֞,t.)uD)ԱZ٣oƬ18h@@VF"%)`$v7B0و:-تW匓wRWm̫/:c(O,HOkV.BgŢ̗z8/zcEQ9.>k]@42@ RwBR81[߯w .B)H^x=XҒXʩyе醚<$vIqrS P4%o(1V&É3yYu|&kk{q[C? hTY D$vyMBuYL`8xb= <^4ѵIAQ_]*8}D}N ñ?Fh{$Wͽ̟vNS>X_5e)_O|@U \T\󞈱6{Tݷ]BL|IY4FĘ*PSo\0Mf^<>'˒MPNd$pqqFsqlHv0.g1c5mӪ=.ɤߌP@nwHf2RknCNβɴ/$0WoVf"s汬$+f)2,4=VwG{77P>]wobqݼ\bb$s{u/?8vu@[xB =ںw>~m>6uNnܷF-ٟ,gM v^Xee<%PoJ|tt\@&ԏگ>x@h^T]v·65-M}E[;a7ǁۆ|ref=D %+YH1P HFz=\XvG/A,1_т^Nﶇ$MP/ tv_{O; 6A{e͚,q?^/3|t@Pvf,Gɸ@A+;{_a{c_Vfs]yɭ07.kOls0b3ICqΊbILhcik$ ԥguۙ:սyԅ4R"'n+ ;PAI؇@kկ~>m&GF|*|nJ3zvV\iǒu'PX`x|P) eľS *nD'266Xe2ßdA gpd~ﶼy1f$bdd 0(cSfEgfxsܵI|lj} f&؛3nn azcԚ>B\Ho \pKGO~ӨŌ2 UJզ*o"Lz-i&F! nh<x ͎rg.X}ί cjt+BFH)`!dA9LE)5u;-7&2κ|rr$1Pbd5Ā$F7RQQo)"p?;u+_m5{^w=ub;v9&8=Y#9 f=*Z1SD7Bit3/꣘+(L៛sǩ]5EՒ6Dt03QV_Y8GLCz~@5Rd1_.mYf9,efwiferN[wA 02%RnxeA7GP풀x=h B}iN[ϽAKQMe,_6p Whs{8;7O*#HWw$wfymlu E j էHnċVE5m`S 7$:O7> G8"޹sKr-wddl^"HpA-EDܞ8yϹ@d߳|CK"ӎR E;t7(G7 z۴떎'|C>6H/q$ si`\րK[tj;aU`Mwe*dASPx@"h"mZ (5}fWGs + )tQVUT^`]w%YmWa>~ )^ {2iy0:b?IjT;xSd)|\H' \koW5+{1MR 8 Z.| 0G+Pa{\w=9}I4g_ g~ !kKcs/仞zs*A`BCPm z})QŹcVW{KA @qAKN7sY!R/֩ qPg!zZhllllm|hO sw?{p ii$[nAHB)aٖ$a6ԛ 3 s13vYx%`u=eC7FQ'T=eEWE4@LcMyUo/9ݟFxoʷ/gi ?JYeO&/%\s 0eR3=_ܻg'̷ÓoY$b!9YSܥ>&4p.Vh7^v$ &p֟&^:ĂʗAN"(`2JwiSY/F aקik`EԁmIﻔ0Q5$i B;際Df:?~[J-[iz04ͳDJ,Q%Y!=Y|Q0fF@$LߨG*fz%$K^[Tص㚅%V~K2tX"/Q'lSbs7,z브7~SԑhpA`18!)iXn Ɔ@P ' ?\ڱ_=ܳBa5|y=y|^heÅipB ܻq]Ov2]@n&Fsr%f9=d,7ϭQE`^:*j|xQ;^ `XS2As& ki4 |-sD3?0QVh>uTc0|]tZ{÷7gv+=mjKTC$:;5E"lpT5":ǞÍkW`"PCHQF)"F-XȨ[ILe/3%=ߏ4ׄr$ wT/͉b1oz? *~f *Fe.8Kj/j;{cR@:0r0tUFzq  )2.O#y~dmB|N]y뿧.% OSl2ɌSn;dI)4o[FZ{ʊHGU HF,˫jlNzشX%)@209x@ CCKvRf޹L!HDM!! ҋQ#}2bR<֫A^PINR`V(RWYT$r(4t;Ҧɪ(;;&Ͱ'ыCѦj 6BZ~Uxfɓ+4>=~1Hv;dvmEeG~Kڵ*LD3lu2+wL(QIha:5\Eu xi#(*{߬kWAH/)[Qyo*4Xſy?im3 U;2J'ẠZ23|GgŢo'0dLHUŋYĔh&d4V#U~V{b&UN5I #t?h~*Ec/ß$!*_%m?1bѯ/ Fkk, ML:͞\~<D||S?֏EAA7lCXDr Gߖ[gݿk_NIDZmZJ̈́fQC.W)#G-P DS wݕVէ*i _ݱŊ<>GTQN;qw_ӻbrlpM Y尮+3L+Fmqdv0#2"2X( d WWPx i[{_(!} |5\am۞32HKl-_ 8 R9e]wxL"1e|[w |z`~AG쉚UBx| {k8|m7\ DF/D4yt8hwd[huqvN o1E@ 8bU @ULn7}Ёc;=bDzr%~Q5)"+3ˇ'~֖Œ`}K $VpB 'hH3ou?a]g{4JǏzޤr#c[z7Ln x_?b6=!s^{} SNֆBIbYFo>(_j0`4H2=%$Y#U왃͈h""B818ij,Ѷ+/q5lc* ȽJuf(3!5Z`^b/>l{_/Fbz19awJNA~v^_ùnF޵"EXƟ9yWh?fl cq}~xώ7;Zf 4bB@;?.m,"j1y'}_6A=jvK>3Tʕ֢Ͽs?3&s>c¶ /vM:!!=PF_ᑞyݠh#k4\A2mK 1In%,_8oi3GMH[7d m̤ ,}{>;5rVF_t[=y<".f 0r׳CD׍G& <$N_-GdLvs_/ZDQM?{+'U߇_e&[eOSUxбU6jVGqa4nJ[[T2G 8{>s71YSAfMM}F8`H @?Me޼;|jcUg,+o~vR!%mOS=~x* 볫.*k.*8WdV7>ZSG3 Fkqx񚌵ɾ$&S3dZs OQF;V1tjM8']/c +߃(r6y=?:PMa'9Yd?Ž5 >lV%:T.=G4W5iؕHhCKRV L #&"4.t/:sR[ϝ%,zu1dN ]zi1e 9 Rk7q =ؾ]j/ZC6yÜ;%@a [AV(28AP^H0GQ~ xt9Am®y׀%ܬAWʓ/T0vG{:^ۤf2HJ#:ݔB دI 'dsϘ3_ Ӌ;eQcKky-t>˧-U-!g夘 vR$$SO^l6>>YasE8tmq9\e& }.КH5ᖌJ 6Z;N퐱c{5 :eK8^)(k_c%sHSҲLCP#n2Cm-˜z wvٰ )/*s@VjfEƼ@$ŷ瀧{I!TD vTwZpPɟkl|i_  [XE$Dg3)J# {9 &w''F"V0>ty$4>lMcîC%PPY1ߗw `H\pwh|﹩t/ 9B:롏Q (IqȐו6CnG dͲߙ?;[c4b?7;^K:C^[uuO^_VD+ckg": 2|4xc@)!V`ag v~᭥E]P:$a hgDwRɈ!-_{U/3hM5omA1FN2VPnbBxl(zVo6u1bM`eZiu TLAde oj7mRZ^J[73%S>Rp#ꋇN{2T!\}Cۏ @V(9MQ'ԛ+IE"N*=^ը :LY)֚ɳY2FJ#oedif^ue/dH'^FQ%DK_ͼ);=-R~%Q!LOD r HPSH]I78lИXCGTOb8.Q gLQK%hDYw$HQ_EAbr\x & yRH9x[%r`wxؓ;Yo";ɰ^NoP?:W 󯔕Mv0GsyMx c٣f;ϚSo!RtU56 dPz:zj^3UyƎRцx;`RErR?5 $6{6{T+zqNx\ Ff)]F+#] 1f#qG$9?3ޯm(TXk6Z umO%Tm]\5P~vJx*"/YaN4}GOBS:B' 93œ^.'OΗh5@(𒎜r:3CX3]ujakPYSCYhL&{BcJs0fCC$oPH;25vH=6@3;xZ 1^Imp/b ;ɷ[N*^+Fq2|87%Y_h{ű$wexl72_έW/gUO^}mȷ&r]zX;Pg39ѝű 1qb@쇣5h o,=g}*ㄛu>./@UVVܜx|~`"I0dJv@ ?Wa[;~sdɽ~7=tT@{g Z4|"N-C @IIwގ{ԭA\7׍IF#<ۂ`#ZyϗHsyc޻ {+3N|lurʔVF@::<|^|܄܉˯*/_{hg(GɃ4c(:/: d|9:pp`8tveiI3]ĴB2n'f~QΝYD{d?ᘩH @` LQ)Ts.SOu6 Ui>K|Y"m)h@A# qd"JB^b43a[Ks6(-?z}b6`Z%_- )HPQpxՠE̤ʫƖ5eLkFfeujՒZ *o\A$6pcWC^m6*L%B$ 8p ,< gW I*{A*ƮQM 8TiF珮jlх|ɀuqpE0ب BaR̈́۳ʠB Rr!2Xy!f1z n2cB6xs9,wE0:c~9̾XKEwD@ã+P [&Z|! HjN:SwV6exactl[+7t ,a,:;؁R}^טnE;ў)NgsY;9NDt\W Oo0DdmjBDמmՓUV|f[dHfs\a'W# M#LuK֝PV][vK Mr =W^=Nΰ=dהgKŒ|0rE"@'b9Hhj&Ís0:tx?V} 0 ^~w^yV@G5~>Ɇ|pM&/& ˿gG-r R2s30xq|BUF8#&zy( )Xo2ѤZkJ$at@6=kj\6۰ (R bc~+OYAb&~E7c |1~ @z=%bi)(n}{ }?#"|RKZBjݧOK|jB;&@cꐄ&Eɹyq^SvlodĴTlCXw<^I9/&aYAE\z2!2:{n5M(EV5epNt\G?c W|T7wjfNy? /9}9߃FERj("L$R/Ky($i OI#z ׎ņs5oEq"Ӻ1mR/T]rL6;PF11:=c[ڪz >v'z_+ɅCŲU@ /ʏ6jf,%*l,d/gTB+WFe^7o+}]d0Z2vt" g`B{>R nˈ} -!_CJU[14b@0yջHFo O̬^%~Dw W<7=!n\b:^Ǜ.(ez+w o"A_U) ΥM7 J!1"h/ N@m;#;J210*PKg& Z @HQ^Z}[3aݣt[wY˽Ç=D$heFdp5!%:^5"0y;٬Ï22`i=0EjfyNy4H҄CV9yRй;mq#N\0,GiHRIAơ ߩKΟ;y*{sϢXt3roMrM +)9 EVy`1cޭtRLzUAM6Bo`˔M% 񌽺W~rn;\>Ol(!Vu.}.^A%AqיLJ_.-<-kq#P|h{2'?!h+lrnz3*$\rbAV1d Fi2 lܴ$VoCY;2)|?[!zUTDWǰ}_IŬo"BRCzBn }ڰ{Ν'/;酞ܿ2L,W鏘wrG]J|=ڦ2#KOSm lXz F!@[ _ R o~[g>2܅dzz_\ifCݞ{BmY2b]ˉ ?3 z9GvNc/>ӭ 2b(/im!La%m n%T $v=&B_Wu^h82^-h q m1E~#.bPv1=q{4R*z]_;2j;lV\?{N6Ջjn0.kr6KƉ l/-x G>>ވƁ iQc !B A0&{~|ebTϛ}T8>g|˳lo,ffXy+Xz,@a^RG/0ˆWJTMLׯchjH Hs))FqXf2D6A=(x%#Msq2YE`;DyXGp^k9i(r@+?lE b$ħG :T]are&G'gi_%޲s=Mٺ(pNաy6GMڳwUd϶Nhhp܈Zj ut3-Nzw [i bXudw,N38FJ_Pű1@fEPmB`7s_o&p2vnYI{=$1eB &o?}hτ` {G-er|dߞ 7W۷{(lfG9st[Fm_.E SMchEJ"6[x+~_ /MْFuG@Llax > <}=yj#Pl+9{z*)gd}Ʈ׊^X8 {kFjeXg!85)d3xMM̱H#?î?0o^bVŤXs2VEڵ2vs#HKiY?XdpT=UXv^S7upp_QSI(H!É]eͪrpBl ',%1t&w!܈w:KA <.8Bm - w sO@=`Nk$)?Xa)#iPӼML |lXB,nzSYp_OÊ!:,|qJCj^br#Ҽ긼L7oe }إ?(Be:L%Ti?iM v;>U[D+ J+{Q>re '75L_ nXgT_jl :^.|*BBv['@n+O4W mT AWsS}HAQɺwƆ*- BHW>mhz yZ$DۯsQsƷ ml)aB -<ҽg- .IGN*OHE5h:zL V\ƣ_UWfY4"+()H@G%+ Q}{ ' LrA9v:ܺQN)˴Z<"fTy%(- Ta/6ŮN|.~D f$VEK0Z8V-4AOART_(—3ɿI Yoq3rw|59C"Kh5d&ss&gyYrMSssj ^PƝzRS]&o.1b#&L?{8$SaFÅH6nCT'n] ڭ%?%KM` Q"8 )ZE.ݪ 3Xo{"fr3xϛ~%<}EʞH ?F#>y4k|&r!71 LO< ʝ^jBBŌI(bB7%# 1t$РxUϦXA]Y;UI{VX\>uA9ޟU# ɺx9Wã{U,eK0rMS(v 2Fc& w=nveKϳB1gRB;Fbbj/KQa8r;骍 U$IcK]^d_E}hfTu_ґlޝo>QuJ}=,rZݨ!NܟDg.t!7{ۘ'=4!3Rb9"{Ȯu~Z ؚQ8<*/7'\pvL8#rsFvq/(|}\T> Ћ`tQNqV :Z9擦gqy1|lm6؄8RQ$':rrkŏ|n[K~ø(aWoVud7CdH2t8w.;ᚴz:kg,w_|Kt́6"^$UYLPaZ&69IN/sO3?4\aQv8B9JJ6"wS+RPdp"IէcY>ιp&nA[ߍo/.r(K=="YQP~MdƶNl@5CoZJ+=HxMI#FzC J9*J:RRR0 T5BdXoy.K M鵘.i"H`\@}zH.}#@4jx_ij^aY^.)S0!z{H\H4raQ'V4I[]vx דWOɀКwҲa^X%(y"J~ "K`jxnX<MFfyN vqVTv]SG)9," ]>j#Mls1r(ܫ%F0(*CV[,3AZ\p`tVjDVfI?Iyi&OV'̠AD`0 [D"I ΁@,+( 8@L4 sf]ctqb߅fA $|}"Y7Y\ 'ښ }\u|2 $IQ]`* |TsDWC<|&V$l-[37KA+~Y*S\n,o;܄ @i aWUFN) %Nl4*pVDUED#&\>&]+L'%;D{Jދ@98+05|ˆ˥- ۡ?d)kl}e|;\MT6(n|t-r齏ҪD:'n/2L )2Kj }wЦ !tP|Я)U@* dP 0DPRZbWETJU 8za`x-8D QJ$תynH =]n]z㲀OjPc[B! *uGZ*~O,F>7t3rDN2Bez_U|+U3^#G͡~@wQ:05Of&]Z7&6}/?nOCCz%[Ql@25(' ]1DpG gvM20<ec׹y_|w+qIiquǠBܽ ώ0ݽ^2I{M2|jYT;c;X澭~9\˸kG0hi} d-; o~ք]Y C%N߄\X쵥g+p1:|?j^@L)lz"XBw19+hW}=Y2G}?Eouůcٟ;TIEɇHlHJiWpܖoū;،FX0Zx x1݆Jc:a#{LZ=7x i>/3P<ljӟ €:^^ehbҗ F@uE3Pnk ׋;s~n9 Rn{_ΜUcof*ߒ`@AtDѿ^b~c`llȖ^}^97Z'T{\͑Z2B ƕ>:bp81 oVϿ6ƊTOCg9$%!}n&d ⠇slL,K%}0c}/pI*#K4@(a/f~Gsw_V)ܐ0y!6:J /yccdBF]A9H$6 A]QrFl%ǿ(~u$iꙒ*y~~TY>(:i^4h38t>wZoׯ JG:9!'Ҵ7I4Ioۻ)s0 -\AO[aoOOJZ(&dI!r "n$7:mv˽yt<] 6O-ʳIľNɻ$1wO9j~ 6PGOٯχ~O!C'$1/okӪ>X/X3m:xl`[k.~^M1 m-D,PFzI>g.{=AVT4j߷QL Ë~}SdzUyyڎH)ѢnDB'sn0Ԓ(0/1e1ylWxhص[ŏ٦u  tWVGs?/ 9&˧٤.RIx,H` & kQШn{32-GtW XtO_.0u9Yz `z <~,cAOM,ig N.L݄qݼCasWoTkHl?PЯiQM5bLtXW=xI $UHL:8@% {/ą+~K^OƾuŴƬqgNU^kި~>_´ niOZ}8gAB9~VdOFGó1Gg=\gEGe""ϿgBj0 k2MZt's>4{}< /-GFo 1ucvZmA2H>Z,{Ȩ` wv{mNSy=~/&Yo$wϳF߭r1`1B)D,џ;{4`z6ݾ~︫6SI[r^o6Vd}bFMD(`h)EAY lv2IJI#4ԗ"#kĘQLW+3KSYCS`IغYh'6Kc\ ,vr Q ? 'JZOpB &AkWŰ<..ݮxH9@폺d;%`14bEiD"G"D::OF!B9*o8zɛ)|HA@6||yg~>bBPn)u9]6C8roY'c0ُl\c}%sok3x1vfbј3 uiy ^ܷRqrѢߡIQ(VC>j!^hxD˄!=ȳIB8Cq+aVr7jyO%BO>#E|7px6J2N➛xV\;'6n{%QmtmI ֧P5IUeDH=FϏΪk, ֲƇT cx[6mOe E4k.D?'ЏWK  QVm(:l"cDG2cˆl (nAU.j;oⷪ2~}A@5ߤwpWu; W#9)I$iڹˏOӕ0 F]D3iAz}X_Ē  xH<5z-2@?)ӹ\ӷ Bk6Q `"‰L〷,9''v7wQ+9 tqmx=H7ft]mOD \Bf~ֶEE q(;bSy<@  k^M ;R3ウ鰿Z:2Uڔ|xMX)ٻGNJOz՟]|*Ndbu@_px%˕fGBjЉlS>/VBM@P$Y!0 1xKuK_v%.`N%tmLQևa!$@1DhuH15?=RMUkGIskG˜rqiCm 2]:Pi}+ E0OM$yքkrB $f|% *!XAq4pۻxz|Jfx ᩰ?]Ī<&j(!,m`|@޷sS&Ё9=܌n3\CsxF=I7n,Iuaz_ctg!*"W<Hp3#!CZON0֒N [v )2yQ/_طZvcy-d3p;Ez&!m/)lo `7xؤs? 4Sv;rzT~3i^$C!.8?=j?_2!Aeǁgw/XvU4aMl06᥄NB(R)IeLW|Z{>xc)wOyQ3\Jf";eDɋE ؘg.1T<5 Ȇ28d w6. :( 0.^m6ooO͝5^Po//?L+aڅNCKpEwHmvJjhFˤ ?nCJ[`/XwMA hpfCc{Ö& y5vg.|tkHH37BgQAg[^׸q&3DLi8FW>@) P>*k KuYo־ԅ {b}qyHV&/1T4Boݎ_&6A=.B( 7fkw=Cu#dʋjY4XwB *;CI{ifcOjRq󿖞$6ak QҬ*ߊn};ݱn AzFkq^z:@TXم\ĝ*&"> rR>.͊?>aS@h @!5,+ FROM phpbb_forums';

+ $result = $this->db->sql_query($sql);
+ $forums = $this->db->sql_fetchrowset($result);
+ $this->db->sql_freeresult($result);
+ }
+ else
+ {
+ $buffer = new phpbb_db_sql_insert_buffer($this->db, 'phpbb_forums');
+ $buffer->insert_all($forums);
+ $buffer->flush();
+
+ $this->database_synchronisation(array(
+ 'phpbb_forums' => array('forum_id'),
+ ));
+ }
+ }
+
+ protected function create_forum($name, $parent_id = 0)
+ {
+ $forum = $this->set->insert(array('forum_name' => $name, 'forum_desc' => '', 'forum_rules' => ''));
+ $this->set->change_parent($forum['forum_id'], $parent_id);
+ }
+}
diff --git a/tests/tree/nestedset_forum_get_data_test.php b/tests/tree/nestedset_forum_get_data_test.php
new file mode 100644
index 0000000000..ca1863e55e
--- /dev/null
+++ b/tests/tree/nestedset_forum_get_data_test.php
@@ -0,0 +1,119 @@
+<?php
+/**
+*
+* @package tree
+* @copyright (c) 2013 phpBB Group
+* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2
+*
+*/
+
+require_once dirname(__FILE__) . '/nestedset_forum_base.php';
+
+class phpbb_tests_tree_nestedset_forum_get_data_test extends phpbb_tests_tree_nestedset_forum_base
+{
+ public function get_path_and_subtree_data_data()
+ {
+ return array(
+ array(1, true, true, array(1, 2, 3)),
+ array(1, true, false, array(2, 3)),
+ array(1, false, true, array(3, 2, 1)),
+ array(1, false, false, array(3, 2)),
+
+ array(2, true, true, array(1, 2)),
+ array(2, true, false, array(1)),
+ array(2, false, true, array(2, 1)),
+ array(2, false, false, array(1)),
+
+ array(5, true, true, array(4, 5, 6)),
+ array(5, true, false, array(4, 6)),
+ array(5, false, true, array(6, 5, 4)),
+ array(5, false, false, array(6, 4)),
+ );
+ }
+
+ /**
+ * @dataProvider get_path_and_subtree_data_data
+ */
+ public function test_get_path_and_subtree_data($forum_id, $order_asc, $include_item, $expected)
+ {
+ $this->assertEquals($expected, array_keys($this->set->get_path_and_subtree_data($forum_id, $order_asc, $include_item)));
+ }
+
+ public function get_path_data_data()
+ {
+ return array(
+ array(1, true, true, array(1)),
+ array(1, true, false, array()),
+ array(1, false, true, array(1)),
+ array(1, false, false, array()),
+
+ array(2, true, true, array(1, 2)),
+ array(2, true, false, array(1)),
+ array(2, false, true, array(2, 1)),
+ array(2, false, false, array(1)),
+
+ array(5, true, true, array(4, 5)),
+ array(5, true, false, array(4)),
+ array(5, false, true, array(5, 4)),
+ array(5, false, false, array(4)),
+ );
+ }
+
+ /**
+ * @dataProvider get_path_data_data
+ */
+ public function test_get_path_data($forum_id, $order_asc, $include_item, $expected)
+ {
+ $this->assertEquals($expected, array_keys($this->set->get_path_data($forum_id, $order_asc, $include_item)));
+ }
+
+ public function get_subtree_data_data()
+ {
+ return array(
+ array(1, true, true, array(1, 2, 3)),
+ array(1, true, false, array(2, 3)),
+ array(1, false, true, array(3, 2, 1)),
+ array(1, false, false, array(3, 2)),
+
+ array(2, true, true, array(2)),
+ array(2, true, false, array()),
+ array(2, false, true, array(2)),
+ array(2, false, false, array()),
+
+ array(5, true, true, array(5, 6)),
+ array(5, true, false, array(6)),
+ array(5, false, true, array(6, 5)),
+ array(5, false, false, array(6)),
+ );
+ }
+
+ /**
+ * @dataProvider get_subtree_data_data
+ */
+ public function test_get_subtree_data($forum_id, $order_asc, $include_item, $expected)
+ {
+ $this->assertEquals($expected, array_keys($this->set->get_subtree_data($forum_id, $order_asc, $include_item)));
+ }
+
+ public function get_path_basic_data_data()
+ {
+ return array(
+ array(1, '', array()),
+ array(1, serialize(array()), array()),
+ array(2, '', array(1)),
+ array(2, serialize(array(1 => array())), array(1)),
+ array(10, '', array(7, 9)),
+ array(10, serialize(array(7 => array(), 9 => array())), array(7, 9)),
+ );
+ }
+
+ /**
+ * @dataProvider get_path_basic_data_data
+ */
+ public function test_get_path_basic_data($forum_id, $forum_parents, $expected)
+ {
+ $forum_data = $this->forum_data[$forum_id];
+ $forum_data['forum_parents'] = $forum_parents;
+ $this->assertEquals($expected, array_keys($this->set->get_path_basic_data($forum_data)));
+ }
+}
diff --git a/tests/tree/nestedset_forum_insert_delete_test.php b/tests/tree/nestedset_forum_insert_delete_test.php
new file mode 100644
index 0000000000..d0e9e02c2e
--- /dev/null
+++ b/tests/tree/nestedset_forum_insert_delete_test.php
@@ -0,0 +1,120 @@
+<?php
+/**
+*
+* @package tree
+* @copyright (c) 2013 phpBB Group
+* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2
+*
+*/
+
+require_once dirname(__FILE__) . '/nestedset_forum_base.php';
+
+class phpbb_tests_tree_nestedset_forum_add_remove_test extends phpbb_tests_tree_nestedset_forum_base
+{
+ public function delete_data()
+ {
+ return array(
+ array(1, array(1, 2, 3), array(
+ array('forum_id' => 4, 'parent_id' => 0, 'left_id' => 1, 'right_id' => 6),
+ array('forum_id' => 5, 'parent_id' => 4, 'left_id' => 2, 'right_id' => 5),
+ array('forum_id' => 6, 'parent_id' => 5, 'left_id' => 3, 'right_id' => 4),
+ array('forum_id' => 7, 'parent_id' => 0, 'left_id' => 7, 'right_id' => 16),
+ array('forum_id' => 8, 'parent_id' => 7, 'left_id' => 8, 'right_id' => 9),
+ array('forum_id' => 9, 'parent_id' => 7, 'left_id' => 10, 'right_id' => 13),
+ array('forum_id' => 10, 'parent_id' => 9, 'left_id' => 11, 'right_id' => 12),
+ array('forum_id' => 11, 'parent_id' => 7, 'left_id' => 14, 'right_id' => 15),
+ )),
+ array(2, array(2), array(
+ array('forum_id' => 1, 'parent_id' => 0, 'left_id' => 1, 'right_id' => 4),
+ array('forum_id' => 3, 'parent_id' => 1, 'left_id' => 2, 'right_id' => 3),
+ array('forum_id' => 4, 'parent_id' => 0, 'left_id' => 5, 'right_id' => 10),
+ array('forum_id' => 5, 'parent_id' => 4, 'left_id' => 6, 'right_id' => 9),
+ array('forum_id' => 6, 'parent_id' => 5, 'left_id' => 7, 'right_id' => 8),
+ array('forum_id' => 7, 'parent_id' => 0, 'left_id' => 11, 'right_id' => 20),
+ array('forum_id' => 8, 'parent_id' => 7, 'left_id' => 12, 'right_id' => 13),
+ array('forum_id' => 9, 'parent_id' => 7, 'left_id' => 14, 'right_id' => 17),
+ array('forum_id' => 10, 'parent_id' => 9, 'left_id' => 15, 'right_id' => 16),
+ array('forum_id' => 11, 'parent_id' => 7, 'left_id' => 18, 'right_id' => 19),
+ )),
+ );
+ }
+
+ /**
+ * @dataProvider delete_data
+ */
+ public function test_delete($forum_id, $expected_deleted, $expected)
+ {
+ $this->assertEquals($expected_deleted, $this->set->delete($forum_id));
+
+ $result = $this->db->sql_query("SELECT forum_id, parent_id, left_id, right_id
+ FROM phpbb_forums
+ ORDER BY left_id, forum_id ASC");
+ $this->assertEquals($expected, $this->db->sql_fetchrowset($result));
+ }
+
+ public function delete_throws_data()
+ {
+ return array(
+ array('Not an item', 0),
+ array('Item does not exist', 200),
+ );
+ }
+
+ /**
+ * @dataProvider delete_throws_data
+ *
+ * @expectedException OutOfBoundsException
+ * @expectedExceptionMessage FORUM_NESTEDSET_INVALID_ITEM
+ */
+ public function test_delete_throws($explain, $forum_id)
+ {
+ $this->set->delete($forum_id);
+ }
+
+ public function insert_data()
+ {
+ return array(
+ array(array(
+ 'forum_desc' => '',
+ 'forum_rules' => '',
+ 'forum_id' => 12,
+ 'parent_id' => 0,
+ 'left_id' => 23,
+ 'right_id' => 24,
+ 'forum_parents' => '',
+ ), array(
+ array('forum_id' => 1, 'parent_id' => 0, 'left_id' => 1, 'right_id' => 6),
+ array('forum_id' => 2, 'parent_id' => 1, 'left_id' => 2, 'right_id' => 3),
+ array('forum_id' => 3, 'parent_id' => 1, 'left_id' => 4, 'right_id' => 5),
+
+ array('forum_id' => 4, 'parent_id' => 0, 'left_id' => 7, 'right_id' => 12),
+ array('forum_id' => 5, 'parent_id' => 4, 'left_id' => 8, 'right_id' => 11),
+ array('forum_id' => 6, 'parent_id' => 5, 'left_id' => 9, 'right_id' => 10),
+
+ array('forum_id' => 7, 'parent_id' => 0, 'left_id' => 13, 'right_id' => 22),
+ array('forum_id' => 8, 'parent_id' => 7, 'left_id' => 14, 'right_id' => 15),
+ array('forum_id' => 9, 'parent_id' => 7, 'left_id' => 16, 'right_id' => 19),
+ array('forum_id' => 10, 'parent_id' => 9, 'left_id' => 17, 'right_id' => 18),
+ array('forum_id' => 11, 'parent_id' => 7, 'left_id' => 20, 'right_id' => 21),
+
+ array('forum_id' => 12, 'parent_id' => 0, 'left_id' => 23, 'right_id' => 24),
+ )),
+ );
+ }
+
+ /**
+ * @dataProvider insert_data
+ */
+ public function test_insert($expected_data, $expected)
+ {
+ $this->assertEquals($expected_data, $this->set->insert(array(
+ 'forum_desc' => '',
+ 'forum_rules' => '',
+ )));
+
+ $result = $this->db->sql_query('SELECT forum_id, parent_id, left_id, right_id
+ FROM phpbb_forums
+ ORDER BY left_id, forum_id ASC');
+ $this->assertEquals($expected, $this->db->sql_fetchrowset($result));
+ }
+}
diff --git a/tests/tree/nestedset_forum_move_test.php b/tests/tree/nestedset_forum_move_test.php
new file mode 100644
index 0000000000..fe506c8278
--- /dev/null
+++ b/tests/tree/nestedset_forum_move_test.php
@@ -0,0 +1,569 @@
+<?php
+/**
+*
+* @package tree
+* @copyright (c) 2013 phpBB Group
+* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2
+*
+*/
+
+require_once dirname(__FILE__) . '/nestedset_forum_base.php';
+
+class phpbb_tests_tree_nestedset_forum_move_test extends phpbb_tests_tree_nestedset_forum_base
+{
+ public function move_data()
+ {
+ return array(
+ array('Move first item up',
+ 1, 1, false, array(
+ array('forum_id' => 1, 'parent_id' => 0, 'left_id' => 1, 'right_id' => 6),
+ array('forum_id' => 2, 'parent_id' => 1, 'left_id' => 2, 'right_id' => 3),
+ array('forum_id' => 3, 'parent_id' => 1, 'left_id' => 4, 'right_id' => 5),
+ array('forum_id' => 4, 'parent_id' => 0, 'left_id' => 7, 'right_id' => 12),
+ array('forum_id' => 5, 'parent_id' => 4, 'left_id' => 8, 'right_id' => 11),
+ array('forum_id' => 6, 'parent_id' => 5, 'left_id' => 9, 'right_id' => 10),
+ array('forum_id' => 7, 'parent_id' => 0, 'left_id' => 13, 'right_id' => 22),
+ array('forum_id' => 8, 'parent_id' => 7, 'left_id' => 14, 'right_id' => 15),
+ array('forum_id' => 9, 'parent_id' => 7, 'left_id' => 16, 'right_id' => 19),
+ array('forum_id' => 10, 'parent_id' => 9, 'left_id' => 17, 'right_id' => 18),
+ array('forum_id' => 11, 'parent_id' => 7, 'left_id' => 20, 'right_id' => 21),
+ )),
+ array('Move last item down',
+ 7, -1, false, array(
+ array('forum_id' => 1, 'parent_id' => 0, 'left_id' => 1, 'right_id' => 6),
+ array('forum_id' => 2, 'parent_id' => 1, 'left_id' => 2, 'right_id' => 3),
+ array('forum_id' => 3, 'parent_id' => 1, 'left_id' => 4, 'right_id' => 5),
+ array('forum_id' => 4, 'parent_id' => 0, 'left_id' => 7, 'right_id' => 12),
+ array('forum_id' => 5, 'parent_id' => 4, 'left_id' => 8, 'right_id' => 11),
+ array('forum_id' => 6, 'parent_id' => 5, 'left_id' => 9, 'right_id' => 10),
+ array('forum_id' => 7, 'parent_id' => 0, 'left_id' => 13, 'right_id' => 22),
+ array('forum_id' => 8, 'parent_id' => 7, 'left_id' => 14, 'right_id' => 15),
+ array('forum_id' => 9, 'parent_id' => 7, 'left_id' => 16, 'right_id' => 19),
+ array('forum_id' => 10, 'parent_id' => 9, 'left_id' => 17, 'right_id' => 18),
+ array('forum_id' => 11, 'parent_id' => 7, 'left_id' => 20, 'right_id' => 21),
+ )),
+ array('Move first item down',
+ 1, -1, true, array(
+ array('forum_id' => 4, 'parent_id' => 0, 'left_id' => 1, 'right_id' => 6),
+ array('forum_id' => 5, 'parent_id' => 4, 'left_id' => 2, 'right_id' => 5),
+ array('forum_id' => 6, 'parent_id' => 5, 'left_id' => 3, 'right_id' => 4),
+ array('forum_id' => 1, 'parent_id' => 0, 'left_id' => 7, 'right_id' => 12),
+ array('forum_id' => 2, 'parent_id' => 1, 'left_id' => 8, 'right_id' => 9),
+ array('forum_id' => 3, 'parent_id' => 1, 'left_id' => 10, 'right_id' => 11),
+ array('forum_id' => 7, 'parent_id' => 0, 'left_id' => 13, 'right_id' => 22),
+ array('forum_id' => 8, 'parent_id' => 7, 'left_id' => 14, 'right_id' => 15),
+ array('forum_id' => 9, 'parent_id' => 7, 'left_id' => 16, 'right_id' => 19),
+ array('forum_id' => 10, 'parent_id' => 9, 'left_id' => 17, 'right_id' => 18),
+ array('forum_id' => 11, 'parent_id' => 7, 'left_id' => 20, 'right_id' => 21),
+ )),
+ array('Move second item up',
+ 4, 1, true, array(
+ array('forum_id' => 4, 'parent_id' => 0, 'left_id' => 1, 'right_id' => 6),
+ array('forum_id' => 5, 'parent_id' => 4, 'left_id' => 2, 'right_id' => 5),
+ array('forum_id' => 6, 'parent_id' => 5, 'left_id' => 3, 'right_id' => 4),
+ array('forum_id' => 1, 'parent_id' => 0, 'left_id' => 7, 'right_id' => 12),
+ array('forum_id' => 2, 'parent_id' => 1, 'left_id' => 8, 'right_id' => 9),
+ array('forum_id' => 3, 'parent_id' => 1, 'left_id' => 10, 'right_id' => 11),
+ array('forum_id' => 7, 'parent_id' => 0, 'left_id' => 13, 'right_id' => 22),
+ array('forum_id' => 8, 'parent_id' => 7, 'left_id' => 14, 'right_id' => 15),
+ array('forum_id' => 9, 'parent_id' => 7, 'left_id' => 16, 'right_id' => 19),
+ array('forum_id' => 10, 'parent_id' => 9, 'left_id' => 17, 'right_id' => 18),
+ array('forum_id' => 11, 'parent_id' => 7, 'left_id' => 20, 'right_id' => 21),
+ )),
+ array('Move last item up',
+ 7, 1, true, array(
+ array('forum_id' => 1, 'parent_id' => 0, 'left_id' => 1, 'right_id' => 6),
+ array('forum_id' => 2, 'parent_id' => 1, 'left_id' => 2, 'right_id' => 3),
+ array('forum_id' => 3, 'parent_id' => 1, 'left_id' => 4, 'right_id' => 5),
+ array('forum_id' => 7, 'parent_id' => 0, 'left_id' => 7, 'right_id' => 16),
+ array('forum_id' => 8, 'parent_id' => 7, 'left_id' => 8, 'right_id' => 9),
+ array('forum_id' => 9, 'parent_id' => 7, 'left_id' => 10, 'right_id' => 13),
+ array('forum_id' => 10, 'parent_id' => 9, 'left_id' => 11, 'right_id' => 12),
+ array('forum_id' => 11, 'parent_id' => 7, 'left_id' => 14, 'right_id' => 15),
+ array('forum_id' => 4, 'parent_id' => 0, 'left_id' => 17, 'right_id' => 22),
+ array('forum_id' => 5, 'parent_id' => 4, 'left_id' => 18, 'right_id' => 21),
+ array('forum_id' => 6, 'parent_id' => 5, 'left_id' => 19, 'right_id' => 20),
+ )),
+ array('Move last item up by 2',
+ 7, 2, true, array(
+ array('forum_id' => 7, 'parent_id' => 0, 'left_id' => 1, 'right_id' => 10),
+ array('forum_id' => 8, 'parent_id' => 7, 'left_id' => 2, 'right_id' => 3),
+ array('forum_id' => 9, 'parent_id' => 7, 'left_id' => 4, 'right_id' => 7),
+ array('forum_id' => 10, 'parent_id' => 9, 'left_id' => 5, 'right_id' => 6),
+ array('forum_id' => 11, 'parent_id' => 7, 'left_id' => 8, 'right_id' => 9),
+ array('forum_id' => 1, 'parent_id' => 0, 'left_id' => 11, 'right_id' => 16),
+ array('forum_id' => 2, 'parent_id' => 1, 'left_id' => 12, 'right_id' => 13),
+ array('forum_id' => 3, 'parent_id' => 1, 'left_id' => 14, 'right_id' => 15),
+ array('forum_id' => 4, 'parent_id' => 0, 'left_id' => 17, 'right_id' => 22),
+ array('forum_id' => 5, 'parent_id' => 4, 'left_id' => 18, 'right_id' => 21),
+ array('forum_id' => 6, 'parent_id' => 5, 'left_id' => 19, 'right_id' => 20),
+ )),
+ array('Move last item up by 100',
+ 7, 100, true, array(
+ array('forum_id' => 7, 'parent_id' => 0, 'left_id' => 1, 'right_id' => 10),
+ array('forum_id' => 8, 'parent_id' => 7, 'left_id' => 2, 'right_id' => 3),
+ array('forum_id' => 9, 'parent_id' => 7, 'left_id' => 4, 'right_id' => 7),
+ array('forum_id' => 10, 'parent_id' => 9, 'left_id' => 5, 'right_id' => 6),
+ array('forum_id' => 11, 'parent_id' => 7, 'left_id' => 8, 'right_id' => 9),
+ array('forum_id' => 1, 'parent_id' => 0, 'left_id' => 11, 'right_id' => 16),
+ array('forum_id' => 2, 'parent_id' => 1, 'left_id' => 12, 'right_id' => 13),
+ array('forum_id' => 3, 'parent_id' => 1, 'left_id' => 14, 'right_id' => 15),
+ array('forum_id' => 4, 'parent_id' => 0, 'left_id' => 17, 'right_id' => 22),
+ array('forum_id' => 5, 'parent_id' => 4, 'left_id' => 18, 'right_id' => 21),
+ array('forum_id' => 6, 'parent_id' => 5, 'left_id' => 19, 'right_id' => 20),
+ )),
+ );
+ }
+
+ /**
+ * @dataProvider move_data
+ */
+ public function test_move($explain, $forum_id, $delta, $expected_moved, $expected)
+ {
+ $this->assertEquals($expected_moved, $this->set->move($forum_id, $delta));
+
+ $result = $this->db->sql_query("SELECT forum_id, parent_id, left_id, right_id
+ FROM phpbb_forums
+ ORDER BY left_id, forum_id ASC");
+ $this->assertEquals($expected, $this->db->sql_fetchrowset($result));
+ }
+
+ public function move_down_data()
+ {
+ return array(
+ array('Move last item down',
+ 7, false, array(
+ array('forum_id' => 1, 'parent_id' => 0, 'left_id' => 1, 'right_id' => 6),
+ array('forum_id' => 2, 'parent_id' => 1, 'left_id' => 2, 'right_id' => 3),
+ array('forum_id' => 3, 'parent_id' => 1, 'left_id' => 4, 'right_id' => 5),
+ array('forum_id' => 4, 'parent_id' => 0, 'left_id' => 7, 'right_id' => 12),
+ array('forum_id' => 5, 'parent_id' => 4, 'left_id' => 8, 'right_id' => 11),
+ array('forum_id' => 6, 'parent_id' => 5, 'left_id' => 9, 'right_id' => 10),
+ array('forum_id' => 7, 'parent_id' => 0, 'left_id' => 13, 'right_id' => 22),
+ array('forum_id' => 8, 'parent_id' => 7, 'left_id' => 14, 'right_id' => 15),
+ array('forum_id' => 9, 'parent_id' => 7, 'left_id' => 16, 'right_id' => 19),
+ array('forum_id' => 10, 'parent_id' => 9, 'left_id' => 17, 'right_id' => 18),
+ array('forum_id' => 11, 'parent_id' => 7, 'left_id' => 20, 'right_id' => 21),
+ )),
+ array('Move first item down',
+ 1, true, array(
+ array('forum_id' => 4, 'parent_id' => 0, 'left_id' => 1, 'right_id' => 6),
+ array('forum_id' => 5, 'parent_id' => 4, 'left_id' => 2, 'right_id' => 5),
+ array('forum_id' => 6, 'parent_id' => 5, 'left_id' => 3, 'right_id' => 4),
+ array('forum_id' => 1, 'parent_id' => 0, 'left_id' => 7, 'right_id' => 12),
+ array('forum_id' => 2, 'parent_id' => 1, 'left_id' => 8, 'right_id' => 9),
+ array('forum_id' => 3, 'parent_id' => 1, 'left_id' => 10, 'right_id' => 11),
+ array('forum_id' => 7, 'parent_id' => 0, 'left_id' => 13, 'right_id' => 22),
+ array('forum_id' => 8, 'parent_id' => 7, 'left_id' => 14, 'right_id' => 15),
+ array('forum_id' => 9, 'parent_id' => 7, 'left_id' => 16, 'right_id' => 19),
+ array('forum_id' => 10, 'parent_id' => 9, 'left_id' => 17, 'right_id' => 18),
+ array('forum_id' => 11, 'parent_id' => 7, 'left_id' => 20, 'right_id' => 21),
+ )),
+ );
+ }
+
+ /**
+ * @dataProvider move_down_data
+ */
+ public function test_move_down($explain, $forum_id, $expected_moved, $expected)
+ {
+ $this->assertEquals($expected_moved, $this->set->move_down($forum_id));
+
+ $result = $this->db->sql_query("SELECT forum_id, parent_id, left_id, right_id
+ FROM phpbb_forums
+ ORDER BY left_id, forum_id ASC");
+ $this->assertEquals($expected, $this->db->sql_fetchrowset($result));
+ }
+
+ public function move_up_data()
+ {
+ return array(
+ array('Move first item up',
+ 1, false, array(
+ array('forum_id' => 1, 'parent_id' => 0, 'left_id' => 1, 'right_id' => 6),
+ array('forum_id' => 2, 'parent_id' => 1, 'left_id' => 2, 'right_id' => 3),
+ array('forum_id' => 3, 'parent_id' => 1, 'left_id' => 4, 'right_id' => 5),
+ array('forum_id' => 4, 'parent_id' => 0, 'left_id' => 7, 'right_id' => 12),
+ array('forum_id' => 5, 'parent_id' => 4, 'left_id' => 8, 'right_id' => 11),
+ array('forum_id' => 6, 'parent_id' => 5, 'left_id' => 9, 'right_id' => 10),
+ array('forum_id' => 7, 'parent_id' => 0, 'left_id' => 13, 'right_id' => 22),
+ array('forum_id' => 8, 'parent_id' => 7, 'left_id' => 14, 'right_id' => 15),
+ array('forum_id' => 9, 'parent_id' => 7, 'left_id' => 16, 'right_id' => 19),
+ array('forum_id' => 10, 'parent_id' => 9, 'left_id' => 17, 'right_id' => 18),
+ array('forum_id' => 11, 'parent_id' => 7, 'left_id' => 20, 'right_id' => 21),
+ )),
+ array('Move second item up',
+ 4, true, array(
+ array('forum_id' => 4, 'parent_id' => 0, 'left_id' => 1, 'right_id' => 6),
+ array('forum_id' => 5, 'parent_id' => 4, 'left_id' => 2, 'right_id' => 5),
+ array('forum_id' => 6, 'parent_id' => 5, 'left_id' => 3, 'right_id' => 4),
+ array('forum_id' => 1, 'parent_id' => 0, 'left_id' => 7, 'right_id' => 12),
+ array('forum_id' => 2, 'parent_id' => 1, 'left_id' => 8, 'right_id' => 9),
+ array('forum_id' => 3, 'parent_id' => 1, 'left_id' => 10, 'right_id' => 11),
+ array('forum_id' => 7, 'parent_id' => 0, 'left_id' => 13, 'right_id' => 22),
+ array('forum_id' => 8, 'parent_id' => 7, 'left_id' => 14, 'right_id' => 15),
+ array('forum_id' => 9, 'parent_id' => 7, 'left_id' => 16, 'right_id' => 19),
+ array('forum_id' => 10, 'parent_id' => 9, 'left_id' => 17, 'right_id' => 18),
+ array('forum_id' => 11, 'parent_id' => 7, 'left_id' => 20, 'right_id' => 21),
+ )),
+ );
+ }
+
+ /**
+ * @dataProvider move_up_data
+ */
+ public function test_move_up($explain, $forum_id, $expected_moved, $expected)
+ {
+ $this->assertEquals($expected_moved, $this->set->move_up($forum_id));
+
+ $result = $this->db->sql_query("SELECT forum_id, parent_id, left_id, right_id
+ FROM phpbb_forums
+ ORDER BY left_id, forum_id ASC");
+ $this->assertEquals($expected, $this->db->sql_fetchrowset($result));
+ }
+
+ public function move_children_data()
+ {
+ return array(
+ array('Item has no children',
+ 2, 1, false, array(
+ array('forum_id' => 1, 'parent_id' => 0, 'left_id' => 1, 'right_id' => 6, 'forum_parents' => 'a:0:{}'),
+ array('forum_id' => 2, 'parent_id' => 1, 'left_id' => 2, 'right_id' => 3, 'forum_parents' => 'a:0:{}'),
+ array('forum_id' => 3, 'parent_id' => 1, 'left_id' => 4, 'right_id' => 5, 'forum_parents' => 'a:0:{}'),
+
+ array('forum_id' => 4, 'parent_id' => 0, 'left_id' => 7, 'right_id' => 12, 'forum_parents' => 'a:0:{}'),
+ array('forum_id' => 5, 'parent_id' => 4, 'left_id' => 8, 'right_id' => 11, 'forum_parents' => 'a:0:{}'),
+ array('forum_id' => 6, 'parent_id' => 5, 'left_id' => 9, 'right_id' => 10, 'forum_parents' => 'a:0:{}'),
+
+ array('forum_id' => 7, 'parent_id' => 0, 'left_id' => 13, 'right_id' => 22, 'forum_parents' => 'a:0:{}'),
+ array('forum_id' => 8, 'parent_id' => 7, 'left_id' => 14, 'right_id' => 15, 'forum_parents' => 'a:0:{}'),
+ array('forum_id' => 9, 'parent_id' => 7, 'left_id' => 16, 'right_id' => 19, 'forum_parents' => 'a:0:{}'),
+ array('forum_id' => 10, 'parent_id' => 9, 'left_id' => 17, 'right_id' => 18, 'forum_parents' => 'a:0:{}'),
+ array('forum_id' => 11, 'parent_id' => 7, 'left_id' => 20, 'right_id' => 21, 'forum_parents' => 'a:0:{}'),
+ )),
+ array('Move to same parent',
+ 4, 4, false, array(
+ array('forum_id' => 1, 'parent_id' => 0, 'left_id' => 1, 'right_id' => 6, 'forum_parents' => 'a:0:{}'),
+ array('forum_id' => 2, 'parent_id' => 1, 'left_id' => 2, 'right_id' => 3, 'forum_parents' => 'a:0:{}'),
+ array('forum_id' => 3, 'parent_id' => 1, 'left_id' => 4, 'right_id' => 5, 'forum_parents' => 'a:0:{}'),
+
+ array('forum_id' => 4, 'parent_id' => 0, 'left_id' => 7, 'right_id' => 12, 'forum_parents' => 'a:0:{}'),
+ array('forum_id' => 5, 'parent_id' => 4, 'left_id' => 8, 'right_id' => 11, 'forum_parents' => 'a:0:{}'),
+ array('forum_id' => 6, 'parent_id' => 5, 'left_id' => 9, 'right_id' => 10, 'forum_parents' => 'a:0:{}'),
+
+ array('forum_id' => 7, 'parent_id' => 0, 'left_id' => 13, 'right_id' => 22, 'forum_parents' => 'a:0:{}'),
+ array('forum_id' => 8, 'parent_id' => 7, 'left_id' => 14, 'right_id' => 15, 'forum_parents' => 'a:0:{}'),
+ array('forum_id' => 9, 'parent_id' => 7, 'left_id' => 16, 'right_id' => 19, 'forum_parents' => 'a:0:{}'),
+ array('forum_id' => 10, 'parent_id' => 9, 'left_id' => 17, 'right_id' => 18, 'forum_parents' => 'a:0:{}'),
+ array('forum_id' => 11, 'parent_id' => 7, 'left_id' => 20, 'right_id' => 21, 'forum_parents' => 'a:0:{}'),
+ )),
+ array('Move single child up',
+ 5, 1, true, array(
+ array('forum_id' => 1, 'parent_id' => 0, 'left_id' => 1, 'right_id' => 8, 'forum_parents' => 'a:0:{}'),
+ array('forum_id' => 2, 'parent_id' => 1, 'left_id' => 2, 'right_id' => 3, 'forum_parents' => 'a:0:{}'),
+ array('forum_id' => 3, 'parent_id' => 1, 'left_id' => 4, 'right_id' => 5, 'forum_parents' => 'a:0:{}'),
+ array('forum_id' => 6, 'parent_id' => 1, 'left_id' => 6, 'right_id' => 7, 'forum_parents' => ''),
+
+ array('forum_id' => 4, 'parent_id' => 0, 'left_id' => 9, 'right_id' => 12, 'forum_parents' => 'a:0:{}'),
+ array('forum_id' => 5, 'parent_id' => 4, 'left_id' => 10, 'right_id' => 11, 'forum_parents' => 'a:0:{}'),
+
+ array('forum_id' => 7, 'parent_id' => 0, 'left_id' => 13, 'right_id' => 22, 'forum_parents' => 'a:0:{}'),
+ array('forum_id' => 8, 'parent_id' => 7, 'left_id' => 14, 'right_id' => 15, 'forum_parents' => 'a:0:{}'),
+ array('forum_id' => 9, 'parent_id' => 7, 'left_id' => 16, 'right_id' => 19, 'forum_parents' => 'a:0:{}'),
+ array('forum_id' => 10, 'parent_id' => 9, 'left_id' => 17, 'right_id' => 18, 'forum_parents' => 'a:0:{}'),
+ array('forum_id' => 11, 'parent_id' => 7, 'left_id' => 20, 'right_id' => 21, 'forum_parents' => 'a:0:{}'),
+ )),
+ array('Move nested children up',
+ 4, 1, true, array(
+ array('forum_id' => 1, 'parent_id' => 0, 'left_id' => 1, 'right_id' => 10, 'forum_parents' => 'a:0:{}'),
+ array('forum_id' => 2, 'parent_id' => 1, 'left_id' => 2, 'right_id' => 3, 'forum_parents' => 'a:0:{}'),
+ array('forum_id' => 3, 'parent_id' => 1, 'left_id' => 4, 'right_id' => 5, 'forum_parents' => 'a:0:{}'),
+ array('forum_id' => 5, 'parent_id' => 1, 'left_id' => 6, 'right_id' => 9, 'forum_parents' => ''),
+ array('forum_id' => 6, 'parent_id' => 5, 'left_id' => 7, 'right_id' => 8, 'forum_parents' => ''),
+
+ array('forum_id' => 4, 'parent_id' => 0, 'left_id' => 11, 'right_id' => 12, 'forum_parents' => 'a:0:{}'),
+
+ array('forum_id' => 7, 'parent_id' => 0, 'left_id' => 13, 'right_id' => 22, 'forum_parents' => 'a:0:{}'),
+ array('forum_id' => 8, 'parent_id' => 7, 'left_id' => 14, 'right_id' => 15, 'forum_parents' => 'a:0:{}'),
+ array('forum_id' => 9, 'parent_id' => 7, 'left_id' => 16, 'right_id' => 19, 'forum_parents' => 'a:0:{}'),
+ array('forum_id' => 10, 'parent_id' => 9, 'left_id' => 17, 'right_id' => 18, 'forum_parents' => 'a:0:{}'),
+ array('forum_id' => 11, 'parent_id' => 7, 'left_id' => 20, 'right_id' => 21, 'forum_parents' => 'a:0:{}'),
+ )),
+ array('Move single child down',
+ 5, 7, true, array(
+ array('forum_id' => 1, 'parent_id' => 0, 'left_id' => 1, 'right_id' => 6, 'forum_parents' => 'a:0:{}'),
+ array('forum_id' => 2, 'parent_id' => 1, 'left_id' => 2, 'right_id' => 3, 'forum_parents' => 'a:0:{}'),
+ array('forum_id' => 3, 'parent_id' => 1, 'left_id' => 4, 'right_id' => 5, 'forum_parents' => 'a:0:{}'),
+
+ array('forum_id' => 4, 'parent_id' => 0, 'left_id' => 7, 'right_id' => 10, 'forum_parents' => 'a:0:{}'),
+ array('forum_id' => 5, 'parent_id' => 4, 'left_id' => 8, 'right_id' => 9, 'forum_parents' => 'a:0:{}'),
+
+ array('forum_id' => 7, 'parent_id' => 0, 'left_id' => 11, 'right_id' => 22, 'forum_parents' => 'a:0:{}'),
+ array('forum_id' => 8, 'parent_id' => 7, 'left_id' => 12, 'right_id' => 13, 'forum_parents' => 'a:0:{}'),
+ array('forum_id' => 9, 'parent_id' => 7, 'left_id' => 14, 'right_id' => 17, 'forum_parents' => 'a:0:{}'),
+ array('forum_id' => 10, 'parent_id' => 9, 'left_id' => 15, 'right_id' => 16, 'forum_parents' => 'a:0:{}'),
+ array('forum_id' => 11, 'parent_id' => 7, 'left_id' => 18, 'right_id' => 19, 'forum_parents' => 'a:0:{}'),
+ array('forum_id' => 6, 'parent_id' => 7, 'left_id' => 20, 'right_id' => 21, 'forum_parents' => ''),
+
+ )),
+ array('Move nested children down',
+ 4, 7, true, array(
+ array('forum_id' => 1, 'parent_id' => 0, 'left_id' => 1, 'right_id' => 6, 'forum_parents' => 'a:0:{}'),
+ array('forum_id' => 2, 'parent_id' => 1, 'left_id' => 2, 'right_id' => 3, 'forum_parents' => 'a:0:{}'),
+ array('forum_id' => 3, 'parent_id' => 1, 'left_id' => 4, 'right_id' => 5, 'forum_parents' => 'a:0:{}'),
+
+ array('forum_id' => 4, 'parent_id' => 0, 'left_id' => 7, 'right_id' => 8, 'forum_parents' => 'a:0:{}'),
+
+ array('forum_id' => 7, 'parent_id' => 0, 'left_id' => 9, 'right_id' => 22, 'forum_parents' => 'a:0:{}'),
+ array('forum_id' => 8, 'parent_id' => 7, 'left_id' => 10, 'right_id' => 11, 'forum_parents' => 'a:0:{}'),
+ array('forum_id' => 9, 'parent_id' => 7, 'left_id' => 12, 'right_id' => 15, 'forum_parents' => 'a:0:{}'),
+ array('forum_id' => 10, 'parent_id' => 9, 'left_id' => 13, 'right_id' => 14, 'forum_parents' => 'a:0:{}'),
+ array('forum_id' => 11, 'parent_id' => 7, 'left_id' => 16, 'right_id' => 17, 'forum_parents' => 'a:0:{}'),
+ array('forum_id' => 5, 'parent_id' => 7, 'left_id' => 18, 'right_id' => 21, 'forum_parents' => ''),
+ array('forum_id' => 6, 'parent_id' => 5, 'left_id' => 19, 'right_id' => 20, 'forum_parents' => ''),
+ )),
+ array('Move single child to parent 0',
+ 5, 0, true, array(
+ array('forum_id' => 1, 'parent_id' => 0, 'left_id' => 1, 'right_id' => 6, 'forum_parents' => 'a:0:{}'),
+ array('forum_id' => 2, 'parent_id' => 1, 'left_id' => 2, 'right_id' => 3, 'forum_parents' => 'a:0:{}'),
+ array('forum_id' => 3, 'parent_id' => 1, 'left_id' => 4, 'right_id' => 5, 'forum_parents' => 'a:0:{}'),
+
+ array('forum_id' => 4, 'parent_id' => 0, 'left_id' => 7, 'right_id' => 10, 'forum_parents' => 'a:0:{}'),
+ array('forum_id' => 5, 'parent_id' => 4, 'left_id' => 8, 'right_id' => 9, 'forum_parents' => 'a:0:{}'),
+
+ array('forum_id' => 7, 'parent_id' => 0, 'left_id' => 11, 'right_id' => 20, 'forum_parents' => 'a:0:{}'),
+ array('forum_id' => 8, 'parent_id' => 7, 'left_id' => 12, 'right_id' => 13, 'forum_parents' => 'a:0:{}'),
+ array('forum_id' => 9, 'parent_id' => 7, 'left_id' => 14, 'right_id' => 17, 'forum_parents' => 'a:0:{}'),
+ array('forum_id' => 10, 'parent_id' => 9, 'left_id' => 15, 'right_id' => 16, 'forum_parents' => 'a:0:{}'),
+ array('forum_id' => 11, 'parent_id' => 7, 'left_id' => 18, 'right_id' => 19, 'forum_parents' => 'a:0:{}'),
+
+ array('forum_id' => 6, 'parent_id' => 0, 'left_id' => 21, 'right_id' => 22, 'forum_parents' => ''),
+ )),
+ array('Move nested children to parent 0',
+ 4, 0, true, array(
+ array('forum_id' => 1, 'parent_id' => 0, 'left_id' => 1, 'right_id' => 6, 'forum_parents' => 'a:0:{}'),
+ array('forum_id' => 2, 'parent_id' => 1, 'left_id' => 2, 'right_id' => 3, 'forum_parents' => 'a:0:{}'),
+ array('forum_id' => 3, 'parent_id' => 1, 'left_id' => 4, 'right_id' => 5, 'forum_parents' => 'a:0:{}'),
+
+ array('forum_id' => 4, 'parent_id' => 0, 'left_id' => 7, 'right_id' => 8, 'forum_parents' => 'a:0:{}'),
+
+ array('forum_id' => 7, 'parent_id' => 0, 'left_id' => 9, 'right_id' => 18, 'forum_parents' => 'a:0:{}'),
+ array('forum_id' => 8, 'parent_id' => 7, 'left_id' => 10, 'right_id' => 11, 'forum_parents' => 'a:0:{}'),
+ array('forum_id' => 9, 'parent_id' => 7, 'left_id' => 12, 'right_id' => 15, 'forum_parents' => 'a:0:{}'),
+ array('forum_id' => 10, 'parent_id' => 9, 'left_id' => 13, 'right_id' => 14, 'forum_parents' => 'a:0:{}'),
+ array('forum_id' => 11, 'parent_id' => 7, 'left_id' => 16, 'right_id' => 17, 'forum_parents' => 'a:0:{}'),
+
+ array('forum_id' => 5, 'parent_id' => 0, 'left_id' => 19, 'right_id' => 22, 'forum_parents' => ''),
+ array('forum_id' => 6, 'parent_id' => 5, 'left_id' => 20, 'right_id' => 21, 'forum_parents' => ''),
+ )),
+ );
+ }
+
+ /**
+ * @dataProvider move_children_data
+ */
+ public function test_move_children($explain, $forum_id, $target_id, $expected_moved, $expected)
+ {
+ $this->assertEquals($expected_moved, $this->set->move_children($forum_id, $target_id));
+
+ $result = $this->db->sql_query("SELECT forum_id, parent_id, left_id, right_id, forum_parents
+ FROM phpbb_forums
+ ORDER BY left_id, forum_id ASC");
+ $this->assertEquals($expected, $this->db->sql_fetchrowset($result));
+ }
+
+ public function move_children_throws_item_data()
+ {
+ return array(
+ array('Item 0 does not exist', 0, 5),
+ array('Item does not exist', 200, 5),
+ );
+ }
+
+ /**
+ * @dataProvider move_children_throws_item_data
+ *
+ * @expectedException OutOfBoundsException
+ * @expectedExceptionMessage FORUM_NESTEDSET_INVALID_ITEM
+ */
+ public function test_move_children_throws_item($explain, $forum_id, $target_id)
+ {
+ $this->set->move_children($forum_id, $target_id);
+ }
+
+ public function move_children_throws_parent_data()
+ {
+ return array(
+ array('New parent is child', 4, 5),
+ array('New parent is child 2', 7, 9),
+ array('New parent does not exist', 1, 200),
+ );
+ }
+
+ /**
+ * @dataProvider move_children_throws_parent_data
+ *
+ * @expectedException OutOfBoundsException
+ * @expectedExceptionMessage FORUM_NESTEDSET_INVALID_PARENT
+ */
+ public function test_move_children_throws_parent($explain, $forum_id, $target_id)
+ {
+ $this->set->move_children($forum_id, $target_id);
+ }
+
+ public function change_parent_data()
+ {
+ return array(
+ array('Move single child up',
+ 6, 1, true, array(
+ array('forum_id' => 1, 'parent_id' => 0, 'left_id' => 1, 'right_id' => 8, 'forum_parents' => 'a:0:{}'),
+ array('forum_id' => 2, 'parent_id' => 1, 'left_id' => 2, 'right_id' => 3, 'forum_parents' => 'a:0:{}'),
+ array('forum_id' => 3, 'parent_id' => 1, 'left_id' => 4, 'right_id' => 5, 'forum_parents' => 'a:0:{}'),
+ array('forum_id' => 6, 'parent_id' => 1, 'left_id' => 6, 'right_id' => 7, 'forum_parents' => ''),
+
+ array('forum_id' => 4, 'parent_id' => 0, 'left_id' => 9, 'right_id' => 12, 'forum_parents' => 'a:0:{}'),
+ array('forum_id' => 5, 'parent_id' => 4, 'left_id' => 10, 'right_id' => 11, 'forum_parents' => 'a:0:{}'),
+
+ array('forum_id' => 7, 'parent_id' => 0, 'left_id' => 13, 'right_id' => 22, 'forum_parents' => 'a:0:{}'),
+ array('forum_id' => 8, 'parent_id' => 7, 'left_id' => 14, 'right_id' => 15, 'forum_parents' => 'a:0:{}'),
+ array('forum_id' => 9, 'parent_id' => 7, 'left_id' => 16, 'right_id' => 19, 'forum_parents' => 'a:0:{}'),
+ array('forum_id' => 10, 'parent_id' => 9, 'left_id' => 17, 'right_id' => 18, 'forum_parents' => 'a:0:{}'),
+ array('forum_id' => 11, 'parent_id' => 7, 'left_id' => 20, 'right_id' => 21, 'forum_parents' => 'a:0:{}'),
+ )),
+ array('Move nested children up',
+ 5, 1, true, array(
+ array('forum_id' => 1, 'parent_id' => 0, 'left_id' => 1, 'right_id' => 10, 'forum_parents' => 'a:0:{}'),
+ array('forum_id' => 2, 'parent_id' => 1, 'left_id' => 2, 'right_id' => 3, 'forum_parents' => 'a:0:{}'),
+ array('forum_id' => 3, 'parent_id' => 1, 'left_id' => 4, 'right_id' => 5, 'forum_parents' => 'a:0:{}'),
+ array('forum_id' => 5, 'parent_id' => 1, 'left_id' => 6, 'right_id' => 9, 'forum_parents' => ''),
+ array('forum_id' => 6, 'parent_id' => 5, 'left_id' => 7, 'right_id' => 8, 'forum_parents' => ''),
+
+ array('forum_id' => 4, 'parent_id' => 0, 'left_id' => 11, 'right_id' => 12, 'forum_parents' => 'a:0:{}'),
+
+ array('forum_id' => 7, 'parent_id' => 0, 'left_id' => 13, 'right_id' => 22, 'forum_parents' => 'a:0:{}'),
+ array('forum_id' => 8, 'parent_id' => 7, 'left_id' => 14, 'right_id' => 15, 'forum_parents' => 'a:0:{}'),
+ array('forum_id' => 9, 'parent_id' => 7, 'left_id' => 16, 'right_id' => 19, 'forum_parents' => 'a:0:{}'),
+ array('forum_id' => 10, 'parent_id' => 9, 'left_id' => 17, 'right_id' => 18, 'forum_parents' => 'a:0:{}'),
+ array('forum_id' => 11, 'parent_id' => 7, 'left_id' => 20, 'right_id' => 21, 'forum_parents' => 'a:0:{}'),
+ )),
+ array('Move single child down',
+ 6, 7, true, array(
+ array('forum_id' => 1, 'parent_id' => 0, 'left_id' => 1, 'right_id' => 6, 'forum_parents' => 'a:0:{}'),
+ array('forum_id' => 2, 'parent_id' => 1, 'left_id' => 2, 'right_id' => 3, 'forum_parents' => 'a:0:{}'),
+ array('forum_id' => 3, 'parent_id' => 1, 'left_id' => 4, 'right_id' => 5, 'forum_parents' => 'a:0:{}'),
+
+ array('forum_id' => 4, 'parent_id' => 0, 'left_id' => 7, 'right_id' => 10, 'forum_parents' => 'a:0:{}'),
+ array('forum_id' => 5, 'parent_id' => 4, 'left_id' => 8, 'right_id' => 9, 'forum_parents' => 'a:0:{}'),
+
+ array('forum_id' => 7, 'parent_id' => 0, 'left_id' => 11, 'right_id' => 22, 'forum_parents' => 'a:0:{}'),
+ array('forum_id' => 8, 'parent_id' => 7, 'left_id' => 12, 'right_id' => 13, 'forum_parents' => 'a:0:{}'),
+ array('forum_id' => 9, 'parent_id' => 7, 'left_id' => 14, 'right_id' => 17, 'forum_parents' => 'a:0:{}'),
+ array('forum_id' => 10, 'parent_id' => 9, 'left_id' => 15, 'right_id' => 16, 'forum_parents' => 'a:0:{}'),
+ array('forum_id' => 11, 'parent_id' => 7, 'left_id' => 18, 'right_id' => 19, 'forum_parents' => 'a:0:{}'),
+ array('forum_id' => 6, 'parent_id' => 7, 'left_id' => 20, 'right_id' => 21, 'forum_parents' => ''),
+ )),
+ array('Move nested children down',
+ 5, 7, true, array(
+ array('forum_id' => 1, 'parent_id' => 0, 'left_id' => 1, 'right_id' => 6, 'forum_parents' => 'a:0:{}'),
+ array('forum_id' => 2, 'parent_id' => 1, 'left_id' => 2, 'right_id' => 3, 'forum_parents' => 'a:0:{}'),
+ array('forum_id' => 3, 'parent_id' => 1, 'left_id' => 4, 'right_id' => 5, 'forum_parents' => 'a:0:{}'),
+
+ array('forum_id' => 4, 'parent_id' => 0, 'left_id' => 7, 'right_id' => 8, 'forum_parents' => 'a:0:{}'),
+
+ array('forum_id' => 7, 'parent_id' => 0, 'left_id' => 9, 'right_id' => 22, 'forum_parents' => 'a:0:{}'),
+ array('forum_id' => 8, 'parent_id' => 7, 'left_id' => 10, 'right_id' => 11, 'forum_parents' => 'a:0:{}'),
+ array('forum_id' => 9, 'parent_id' => 7, 'left_id' => 12, 'right_id' => 15, 'forum_parents' => 'a:0:{}'),
+ array('forum_id' => 10, 'parent_id' => 9, 'left_id' => 13, 'right_id' => 14, 'forum_parents' => 'a:0:{}'),
+ array('forum_id' => 11, 'parent_id' => 7, 'left_id' => 16, 'right_id' => 17, 'forum_parents' => 'a:0:{}'),
+ array('forum_id' => 5, 'parent_id' => 7, 'left_id' => 18, 'right_id' => 21, 'forum_parents' => ''),
+ array('forum_id' => 6, 'parent_id' => 5, 'left_id' => 19, 'right_id' => 20, 'forum_parents' => ''),
+ )),
+ array('Move single child to parent 0',
+ 6, 0, true, array(
+ array('forum_id' => 1, 'parent_id' => 0, 'left_id' => 1, 'right_id' => 6, 'forum_parents' => 'a:0:{}'),
+ array('forum_id' => 2, 'parent_id' => 1, 'left_id' => 2, 'right_id' => 3, 'forum_parents' => 'a:0:{}'),
+ array('forum_id' => 3, 'parent_id' => 1, 'left_id' => 4, 'right_id' => 5, 'forum_parents' => 'a:0:{}'),
+
+ array('forum_id' => 4, 'parent_id' => 0, 'left_id' => 7, 'right_id' => 10, 'forum_parents' => 'a:0:{}'),
+ array('forum_id' => 5, 'parent_id' => 4, 'left_id' => 8, 'right_id' => 9, 'forum_parents' => 'a:0:{}'),
+
+ array('forum_id' => 7, 'parent_id' => 0, 'left_id' => 11, 'right_id' => 20, 'forum_parents' => 'a:0:{}'),
+ array('forum_id' => 8, 'parent_id' => 7, 'left_id' => 12, 'right_id' => 13, 'forum_parents' => 'a:0:{}'),
+ array('forum_id' => 9, 'parent_id' => 7, 'left_id' => 14, 'right_id' => 17, 'forum_parents' => 'a:0:{}'),
+ array('forum_id' => 10, 'parent_id' => 9, 'left_id' => 15, 'right_id' => 16, 'forum_parents' => 'a:0:{}'),
+ array('forum_id' => 11, 'parent_id' => 7, 'left_id' => 18, 'right_id' => 19, 'forum_parents' => 'a:0:{}'),
+
+ array('forum_id' => 6, 'parent_id' => 0, 'left_id' => 21, 'right_id' => 22, 'forum_parents' => ''),
+ )),
+ array('Move nested children to parent 0',
+ 5, 0, true, array(
+ array('forum_id' => 1, 'parent_id' => 0, 'left_id' => 1, 'right_id' => 6, 'forum_parents' => 'a:0:{}'),
+ array('forum_id' => 2, 'parent_id' => 1, 'left_id' => 2, 'right_id' => 3, 'forum_parents' => 'a:0:{}'),
+ array('forum_id' => 3, 'parent_id' => 1, 'left_id' => 4, 'right_id' => 5, 'forum_parents' => 'a:0:{}'),
+
+ array('forum_id' => 4, 'parent_id' => 0, 'left_id' => 7, 'right_id' => 8, 'forum_parents' => 'a:0:{}'),
+
+ array('forum_id' => 7, 'parent_id' => 0, 'left_id' => 9, 'right_id' => 18, 'forum_parents' => 'a:0:{}'),
+ array('forum_id' => 8, 'parent_id' => 7, 'left_id' => 10, 'right_id' => 11, 'forum_parents' => 'a:0:{}'),
+ array('forum_id' => 9, 'parent_id' => 7, 'left_id' => 12, 'right_id' => 15, 'forum_parents' => 'a:0:{}'),
+ array('forum_id' => 10, 'parent_id' => 9, 'left_id' => 13, 'right_id' => 14, 'forum_parents' => 'a:0:{}'),
+ array('forum_id' => 11, 'parent_id' => 7, 'left_id' => 16, 'right_id' => 17, 'forum_parents' => 'a:0:{}'),
+
+ array('forum_id' => 5, 'parent_id' => 0, 'left_id' => 19, 'right_id' => 22, 'forum_parents' => ''),
+ array('forum_id' => 6, 'parent_id' => 5, 'left_id' => 20, 'right_id' => 21, 'forum_parents' => ''),
+ )),
+ );
+ }
+
+ /**
+ * @dataProvider change_parent_data
+ */
+ public function test_change_parent($explain, $forum_id, $target_id, $expected_moved, $expected)
+ {
+ $this->assertEquals($expected_moved, $this->set->change_parent($forum_id, $target_id));
+
+ $result = $this->db->sql_query("SELECT forum_id, parent_id, left_id, right_id, forum_parents
+ FROM phpbb_forums
+ ORDER BY left_id, forum_id ASC");
+ $this->assertEquals($expected, $this->db->sql_fetchrowset($result));
+ }
+
+ public function change_parent_throws_item_data()
+ {
+ return array(
+ array('Item 0 does not exist', 0, 5),
+ array('Item does not exist', 200, 5),
+ );
+ }
+
+ /**
+ * @dataProvider change_parent_throws_item_data
+ *
+ * @expectedException OutOfBoundsException
+ * @expectedExceptionMessage FORUM_NESTEDSET_INVALID_ITEM
+ */
+ public function test_change_parent_throws_item($explain, $forum_id, $target_id)
+ {
+ $this->set->change_parent($forum_id, $target_id);
+ }
+
+ public function change_parent_throws_parent_data()
+ {
+ return array(
+ array('New parent is child', 4, 5),
+ array('New parent is child 2', 7, 9),
+ array('New parent does not exist', 1, 200),
+ );
+ }
+
+ /**
+ * @dataProvider change_parent_throws_parent_data
+ *
+ * @expectedException OutOfBoundsException
+ * @expectedExceptionMessage FORUM_NESTEDSET_INVALID_PARENT
+ */
+ public function test_change_parent_throws_parent($explain, $forum_id, $target_id)
+ {
+ $this->set->change_parent($forum_id, $target_id);
+ }
+}
diff --git a/tests/tree/nestedset_forum_regenerate_test.php b/tests/tree/nestedset_forum_regenerate_test.php
new file mode 100644
index 0000000000..38338dbc4d
--- /dev/null
+++ b/tests/tree/nestedset_forum_regenerate_test.php
@@ -0,0 +1,72 @@
+<?php
+/**
+*
+* @package tree
+* @copyright (c) 2013 phpBB Group
+* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2
+*
+*/
+
+require_once dirname(__FILE__) . '/nestedset_forum_base.php';
+
+class phpbb_tests_tree_nestedset_forum_regenerate_test extends phpbb_tests_tree_nestedset_forum_base
+{
+ protected $fixed_set = array(
+ array('forum_id' => 1, 'parent_id' => 0, 'left_id' => 1, 'right_id' => 6, 'forum_parents' => ''),
+ array('forum_id' => 2, 'parent_id' => 1, 'left_id' => 2, 'right_id' => 3, 'forum_parents' => ''),
+ array('forum_id' => 3, 'parent_id' => 1, 'left_id' => 4, 'right_id' => 5, 'forum_parents' => ''),
+
+ array('forum_id' => 4, 'parent_id' => 0, 'left_id' => 7, 'right_id' => 12, 'forum_parents' => ''),
+ array('forum_id' => 5, 'parent_id' => 4, 'left_id' => 8, 'right_id' => 11, 'forum_parents' => ''),
+ array('forum_id' => 6, 'parent_id' => 5, 'left_id' => 9, 'right_id' => 10, 'forum_parents' => ''),
+
+ array('forum_id' => 7, 'parent_id' => 0, 'left_id' => 13, 'right_id' => 22, 'forum_parents' => ''),
+ array('forum_id' => 8, 'parent_id' => 7, 'left_id' => 14, 'right_id' => 15, 'forum_parents' => ''),
+ array('forum_id' => 9, 'parent_id' => 7, 'left_id' => 16, 'right_id' => 19, 'forum_parents' => ''),
+ array('forum_id' => 10, 'parent_id' => 9, 'left_id' => 17, 'right_id' => 18, 'forum_parents' => ''),
+ array('forum_id' => 11, 'parent_id' => 7, 'left_id' => 20, 'right_id' => 21, 'forum_parents' => ''),
+ );
+
+ public function regenerate_left_right_ids_data()
+ {
+ return array(
+ array('UPDATE phpbb_forums
+ SET left_id = 0,
+ right_id = 0', false),
+ array('UPDATE phpbb_forums
+ SET left_id = 28,
+ right_id = 28
+ WHERE left_id > 12', false),
+ array('UPDATE phpbb_forums
+ SET left_id = left_id * 2,
+ right_id = right_id * 2', false),
+ array('UPDATE phpbb_forums
+ SET left_id = left_id * 2,
+ right_id = right_id * 2
+ WHERE left_id > 12', false),
+ array('UPDATE phpbb_forums
+ SET left_id = left_id - 4,
+ right_id = right_id * 4
+ WHERE left_id > 4', false),
+ array('UPDATE phpbb_forums
+ SET left_id = 0,
+ right_id = 0
+ WHERE left_id > 12', true),
+ );
+ }
+
+ /**
+ * @dataProvider regenerate_left_right_ids_data
+ */
+ public function test_regenerate_left_right_ids($breaking_query, $reset_ids)
+ {
+ $result = $this->db->sql_query($breaking_query);
+
+ $this->assertEquals(23, $this->set->regenerate_left_right_ids(1, 0, $reset_ids));
+
+ $result = $this->db->sql_query('SELECT forum_id, parent_id, left_id, right_id, forum_parents
+ FROM phpbb_forums
+ ORDER BY left_id, forum_id ASC');
+ $this->assertEquals($this->fixed_set, $this->db->sql_fetchrowset($result));
+ }
+}
diff --git a/tests/tree/nestedset_forum_test.php b/tests/tree/nestedset_forum_test.php
new file mode 100644
index 0000000000..516c794ffc
--- /dev/null
+++ b/tests/tree/nestedset_forum_test.php
@@ -0,0 +1,116 @@
+<?php
+/**
+*
+* @package tree
+* @copyright (c) 2013 phpBB Group
+* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2
+*
+*/
+
+require_once dirname(__FILE__) . '/nestedset_forum_base.php';
+
+class pphpbb_tests_tree_nestedset_forum_test extends phpbb_tests_tree_nestedset_forum_base
+{
+ public function forum_constructor_data()
+ {
+ return array(
+ array(array(
+ array('forum_id' => 1, 'parent_id' => 0, 'left_id' => 1, 'right_id' => 6),
+ array('forum_id' => 2, 'parent_id' => 1, 'left_id' => 2, 'right_id' => 3),
+ array('forum_id' => 3, 'parent_id' => 1, 'left_id' => 4, 'right_id' => 5),
+
+ array('forum_id' => 4, 'parent_id' => 0, 'left_id' => 7, 'right_id' => 12),
+ array('forum_id' => 5, 'parent_id' => 4, 'left_id' => 8, 'right_id' => 11),
+ array('forum_id' => 6, 'parent_id' => 5, 'left_id' => 9, 'right_id' => 10),
+
+ array('forum_id' => 7, 'parent_id' => 0, 'left_id' => 13, 'right_id' => 22),
+ array('forum_id' => 8, 'parent_id' => 7, 'left_id' => 14, 'right_id' => 15),
+ array('forum_id' => 9, 'parent_id' => 7, 'left_id' => 16, 'right_id' => 19),
+ array('forum_id' => 10, 'parent_id' => 9, 'left_id' => 17, 'right_id' => 18),
+ array('forum_id' => 11, 'parent_id' => 7, 'left_id' => 20, 'right_id' => 21),
+ )),
+ );
+ }
+
+ /**
+ * @dataProvider forum_constructor_data
+ */
+ public function test_forum_constructor($expected)
+ {
+ $result = $this->db->sql_query('SELECT forum_id, parent_id, left_id, right_id
+ FROM phpbb_forums
+ ORDER BY left_id, forum_id ASC');
+ $this->assertEquals($expected, $this->db->sql_fetchrowset($result));
+ }
+
+ public function get_sql_where_data()
+ {
+ return array(
+ array('SELECT forum_id
+ FROM phpbb_forums
+ %s
+ ORDER BY forum_id ASC',
+ 'WHERE', '', array(
+ array('forum_id' => 1),
+ array('forum_id' => 2),
+ array('forum_id' => 3),
+
+ array('forum_id' => 4),
+ array('forum_id' => 5),
+ array('forum_id' => 6),
+
+ array('forum_id' => 7),
+ array('forum_id' => 8),
+ array('forum_id' => 9),
+ array('forum_id' => 10),
+ array('forum_id' => 11),
+ )),
+ array('SELECT f.forum_id
+ FROM phpbb_forums f
+ %s
+ ORDER BY f.forum_id ASC',
+ 'WHERE', 'f.', array(
+ array('forum_id' => 1),
+ array('forum_id' => 2),
+ array('forum_id' => 3),
+
+ array('forum_id' => 4),
+ array('forum_id' => 5),
+ array('forum_id' => 6),
+
+ array('forum_id' => 7),
+ array('forum_id' => 8),
+ array('forum_id' => 9),
+ array('forum_id' => 10),
+ array('forum_id' => 11),
+ )),
+ array('SELECT forum_id
+ FROM phpbb_forums
+ WHERE forum_id < 4 %s
+ ORDER BY forum_id ASC',
+ 'AND', '', array(
+ array('forum_id' => 1),
+ array('forum_id' => 2),
+ array('forum_id' => 3),
+ )),
+ array('SELECT f.forum_id
+ FROM phpbb_forums f
+ WHERE f.forum_id < 4 %s
+ ORDER BY f.forum_id ASC',
+ 'AND', 'f.', array(
+ array('forum_id' => 1),
+ array('forum_id' => 2),
+ array('forum_id' => 3),
+ )),
+ );
+ }
+
+ /**
+ * @dataProvider get_sql_where_data
+ */
+ public function test_get_sql_where($sql_query, $operator, $column_prefix, $expected)
+ {
+ $result = $this->db->sql_query(sprintf($sql_query, $this->set->get_sql_where($operator, $column_prefix)));
+ $this->assertEquals($expected, $this->db->sql_fetchrowset($result));
+ }
+}
diff --git a/tests/user/fixtures/user_loader.xml b/tests/user/fixtures/user_loader.xml
index 737376f326..1fed8b5838 100644
--- a/tests/user/fixtures/user_loader.xml
+++ b/tests/user/fixtures/user_loader.xml
@@ -2,22 +2,38 @@
<dataset>
<table name="phpbb_users">
<column>user_id</column>
+ <column>user_permissions</column>
<column>username</column>
<column>username_clean</column>
+ <column>user_sig</column>
+ <column>user_occ</column>
+ <column>user_interests</column>
<row>
<value>1</value>
+ <value></value>
<value>Guest</value>
<value>guest</value>
+ <value></value>
+ <value></value>
+ <value></value>
</row>
<row>
<value>2</value>
+ <value></value>
<value>Admin</value>
<value>admin</value>
+ <value></value>
+ <value></value>
+ <value></value>
</row>
<row>
<value>3</value>
+ <value></value>
<value>Test</value>
<value>test</value>
+ <value></value>
+ <value></value>
+ <value></value>
</row>
</table>
</dataset>
0)vo q+p@  lµ  $R$Uc HEF kTlEU_R#20D$DdD'rVH(CPF%,ڔV]C|Cgc Li l>08!7; _ؿFybTӘLRH@d,KvrVL$p4&1ĈAޕy|7SFwƹ0v݁hc^?ӟi?T*APXƝ%#@O=`/|t:Cf:E?ܵ X!vZL$7ɵQad H 0 FX2-q<\n"1H֫}8I/vs3PzĂ`fz81&4 Uϴ=+Z6r ddNt`#Dg6 9;* G!G€'رb~T8nC-\yuݠR@F0h I E# T1l(U)J-JIJT !cHؕ4urC$E eh9{%NbϴW}WtSGkڲ`AK!P <^=*_1IH4 &t6EY#APD @A F 0*"1FIHH0bb xYeE֖XC,0`1c *#& F X#ASI8_75ɬ>M e3aE`+h dӫHQ7hDԐpIY4aa6 Op#CI!EXH @"A;]>W'M@xۄD$ ٪u}U*Mr|ϯK5g󨵙&$4 cppHQrpmL 26p; riC{k~-aq$AADito^PG2`h$RvdXQH(wOTJv"Т | qtW^8v S);}ηs(b3hZ1ѥ05b"&]@7JM#!0S_$䮻MeC `[ߐtvbX i !eg\wOv8V\I TOF uޖHaб,%,<sa$ȪYB{gGA+ĉ@FE7 P6YOYVU 3R~_’%VCH7Y 4 s ;A C ŏ) P%FF0Q*2,DDA`,H(XFc`4D7usZޭ3F]4>޻)Sܹ.L([%h@c:[Dp0#j-92Ou4ig{.cЄSp9hHHJ*CqaZNsHHV|]ޔ4G_0:F$">t򔝻%=ӣzQ0} QNl -ެ@'D$pQD ƸQ4c7s#t', !H0[lnKb 8spufan]Tp̳sȯjFq#.xa?'r;y]Ak+F6]5M,L\xRK:vR+ MҜK`dWkz+k^kh98j-( !B8#N$%SVg kR 066xW&R 4J2zKA'~A!#:pޤS4ưJ*yVk^GY~; kyilV1SxjR.oyk`U;~b)/ ?j/ )5((< ;*DDb[2E3^wLn,;^"rgKؘ?|w%רoԲ"pLB4W ( Ȫ7f!^[5GЪ,sYFc!&Dlr ]wƫOWī `qR,ӓWC,zd/ҫZ5Q7%.T_ufT?)ѪWo3cƽa Diٲ qؒv7Ig:Qݽ(io#@Fn )=lj/;Ȫ{3:jˮ~m޲\_$72YТ>Oehjs;'+ڇ|?=Քƌ/|[xr)h!b1O_}qYk|T2ᆎHuL/Z*Liz3IBI<4;;x! ~ 8Uo8{ Vؖ ڢ=>gvߣ!c*oYQxw cuG q21џFoÏ-~巫+|Y}ӵB+&$l[":URss:~_k

8c':4@\qPWON(ѱ gDwǡá61+n r0s\PvFnm._ " q gW| t^uCP |Lfx1AN|,WSQmC ͫJtOd@ p3G`I 6M`SSIPڼ@X*ypvNz+\{+}1ԞZ$Jxn \^4V1,ɈS|ek{9'$UD/oKH@yHh/mv)CiWNJm v#'Q;*=Xޖ}`OXv>Ԯ=g=k56:nUppnI+yK ԀcB Z]KvkAL޶ C l`~j:cN+HT  p@ t`5"#/,3nvY^C,zdq$,X%. G17'|{ (`b@%J!1D#a%2Chď997<UWR'-.<@~|R?蜩t16L!;q:Q*?EgO.?y;Xw"JdBX$aſxE *atqqF mT#UW*DPIX >RIH?D$ndu œ8!uևX(/S =g4:THiE l%wh4f@DPTDHSF}o'73\#m&5SDz&y|;$ Y}8hp V55Z'$Ԯmĩu#ۛKFdkos6Đ)*y!#$a!1  2# 2D+gv+8 7sķ.]S.vLIQHNE^0L$T~ivWs 8ȊEbB`{AA* ,"#'[ȡAT* EFAPIy V9OH e! XdO T!hG hz}7ṁV}gH5lLo4k%I}L;@%#3ukd>WPoRoYd !.@ `oȿĜ`2\!n_9=\6L4lvg:u}~3fL-^wU$e`D*4"]n9=|i9`nZI[o 1` us)у: 2Nۼs5:>+YV*VuMiPj' SWJ1$K V˳v詷zSUvfULiIǰ_ x~ F*eK2ށV),X|jah3 w֒yv*jJ}T]Q?GgF| ttFl܁|: 6CYh >X  A]Y&ZL| Tk ~htւQ(nF,YI!$."LUB7, Ę9 *a"|3zŽיv'žWho]}#BEb\`H$P B0Hb%BYZ#- ww{ӛ/jG:HWg2Q`A$Q6e@L2fbP>JD%Q)HQ)!M?/إefJ8 U2 ),` gm !K(LC>&!pM'  '0S's Mu{yn'I0rTF L@ȂK(E 2Ixxd!Z#`i!XqDb[Tc%LFچ_?|Eq.3zpjs3,.Գ꣗O= 6fˍFkt4F#(utPSM]wkNδQ ~Q_/E&H i철fqpbNl֎>>jڵ΍إo[:!,[-Mh۞e %έxYRFH9;#g}O61Ѯ?~mUY!<9Bw|cٌ2̭lQ%Z-JbQHCw.Q~h';enw"h\!iߚ0"Bd,Lc $a,!,b4Ȓf K @Y )iJHAdň5$0Q \H#a`@@B$h9&"PRiam*)-H )BV!YBB d0Da")(s5\|،'~0A0šk~y9wNಗL狻EVrrDVK֥l*򲇇tLICARdYQNdf^l[H8g{g&G̉Zi"56h |ŅI_PEB(;͕T1w*~8#;kfxJN/[r@}fOP"2!," Q@XHF"UH V,b PdF1QQE, ``1@bA$ #"A"ATU"bbDEAa ( EA@@EdXR \PRccŏϵ^zr /-Uvp,Esif4cTe;6UpXS=i}WoT}jaee;ZRMu|ܡJftO#nzkLh&&+(n(/iKj\9P.a1kmFd.BqJp&8$JҦ˃V<=KDqNԂ=6*%X'_BÔrf; T|b$X* ap6'+n V4qddvY:xSkV e[|zBܐE yT:yѧ> Fkjc؛]/3nC:E¹G ~eqf2aaHz#Ů hp.e5 2e\gJ%yw;著|YoV&_5'?e^/Җj6kvl6mo*&8ƭ~{Y^m]\FL1fY:;4vg[ZeibՊSZf\=M #*bKw۹rK5V Mݪ=2O%eI'͒Fzr~5_QGVLt@ջeŕԨBg E-L:f }B"*gFvҺZ Ryt˥'ߧsu]xL6y* zw<9+kMãi'D-9SݯYm<,v?\bM=̉q\ī kjL߳}αSDs<[/* aځW5Td߱8st+'z߶䇹lA9yz?$/MV|HBx2sթ{]-$=*5Ln'wW_VmCF 2ir|+(NPnTo0xFFVoyldxz(˻ĜoSh7,nȴMCK> t זhϛM-bf5%xevگ]13]0o|:2˲^>k䭶xTzg{mKn<C=TOt_Bў7͂elC6^-6'=7¢Tu<)%ݻ*!Z-=y՟6:_|˿od zxCudC -^ڗ-QI;9G@"scNDx:=ךLM\8bT{9C[0&nZONSqNn"NHmӶt5N]-Kɔ{6ǰdٌ*'-[өu#ɈB <+jsb9r1l;MM"oyh cC)z;J/ߓp2>JQ=OOqMGk;DB\6u8r˻ZNYݞvv%y~kNycb!;g*-;qp^C~cxZ<0W2T؁ Zߴ۶34͒=WY\\a &&qQv/Gu 60҄Y˔w&r)cR~ ءNO֗dHrA;!s%FW3=;7w? ѫZ|zG/{IDO9ƢO3,"oy>x@ b߮*G).ҨX&;T ?mN+V?%lg+jz9$N,.ſ>dT'Nda !!@|kxHfy2R1dkx`"N(ÙTg`85WO[EJP负 t3i4cWQg N6jW?|mFO[p_f0pɕV58!!@  V֖6 *'hk͞d}%C Un) @P RJF*?Gy{vE{!AJ'E$HfB -@I M9K(%IJaѨ&pjaJk8΄0/%H!,8ލ4ɼΌށMAMoy=MRVA DqK ¼ )+F R/35[PUmF<+RBYR,UHTCO(EQ0Kh媰U Z%y8>_56Jf[:~\Fy VF.aՋiZ3+D, %zde3 ɹh3+B`u0 n0 m!ٓ ,h%!R$E61~mnf9'4QA0`B(QRBS03*PP[2DaK0-@b)2, ]#*@F&LdU\1O@R .\A3{46]Z5SCz\bF0pErL),RekuD^ mg-uM I7S)JZq'T}W(_[J-Q!r#dl[Uu~ǹHžqg2pPP#!@D 2\>1f(thbau im( !&PdX0[fe1 Oۡ Y9m$GGOZ߿wO o;d0$@8R~w{nΈE7-PԠrHB:'_Vb(O 5|@J;o] ]Zo #b^}gtx~7T~:~ MSNݜI;(˜ɐ]g;_rͣ $Zi ۼ6_&rsDh>88g{tdEKª21v#/ lr5vZ;@6bYYuDQ:f!jXm/_:vԦжOFcfvأoŹ5[,HjR!H@H/q`"0W1E8&Idc*Q*(FJ֊UE1E ehfc\JM.X %Jj%U{]|y?.JK36WHo$_@hmW6ns@ݎ') j}јX[hH6[^6l\KKklFH=ߥ|]WL;J ̈́ A&n_8i.n-L >:wJmXgz8PѺ QXwTt5eͷ1 @_n'1aGHN'AH!%E @EGNOONLJ]ۡM !E4җV?ʽzV[?~n/].ɷ]&S[CZ3)ˇ\N(\ZmU>P.*ySF5ї4%t c~Kb X聰PNi)@^=M.t/lݜ-+sLDB+xPvs6 ygl` y9n RB-sxrP55^IG95G&nlP PJ#Pf)/6qF|7gEq>m>< `dqg6hgq Aߨj>Vĥy`@$0A k۹3`ApwٷqKsQ|TxbZz7? m0~orkR!J=lmgwNO ~8 Um46Hφn,QQv K!;OǢ;^WhzNo% r.Y+5ϳ7w%mր w~/*F]DcXۙ3r:JGA JCe4Sۦ٬caj -j̱AԺCF"#WL6%L&CfCLXSJ T%8m! eS1;;V4a0;)vCc!'Uյxx<egX\檻 rΫpշu߬j*;6s>a"գ8)H!! X ",FPI$Aƛ^iz ,d R\I#I"3<],E9ǎzY]UT9Mɨj[G^}$gFF!R0 `0 a}q4G3NxņMbf)+07,X,7:"hURF,"&y+wTnl2wQҊQӎR0ݹ8>GhExB[W6\ z|ܺ+HIQg 0ExczpN(B`H!lUୋtOasƞz-V)|Zݫ #~ ݝ+ԻhLGMZ5+A)Fn㥪;We˿3oc:)U-h@@61HHX(1 $TF 4‰H1H0A>;]TF!EH,`HH@c=xFx4"_ƏT`X@bDP$ AY#׋|HALK˯hT@iJLa2%Ed[/%\R@;ȅ@?Rݰׇ}Szqxwv7}7lBlZil@"˥L+̙}TJ5OyE@a$b T fh_XDEBm$dPXD"T@a1A@}H:k  EjK/H1*()*(*0AEE$2I;s!:?<'/[s1NN w2dYp,*gRĵ,Y&xZI,P1$$YmrTP|0{4H jٳdQ*Ka$ F Xc V:_gŐىL2%%%%9 xCju_)Rs6lȊ ($I{f.@RzoH 5oTQ(ŶZHEGos}t(o$ TI}zs>Opπ։/!Cs͆~V!4,5ivnɏ{5wSE h$@jR60_ޑeT+j)ܞBLJU6ߨX(cHMưG[A1W^l2 a&h%"IE{s0H2Eqb20&1JıDSO?Nǻ ߪ )f -W&wdsg'Ο&>~}٦lQe*$U$VC~y<Z(VIB*ZYH7e楟griۼ?1>{siFܭʋL}dө s7]TS\48Fq9M~i[).,׻FN}RZJ+[VM :ZN<S,wk+)? ]A (fS u"OoDݤ=(Lljص%RSy2?O9(K:r`VCp ~ X́3!&gmj&m2' rsM6h`vFv{.~frΐՈy`wULe3NPt3P90K ÜN9e8*S횶Zԛ})d$4ݫNq£Dw).=b܃В[پɮ9"[3.]>H)Dy8R Դ6S$3)ZQ2 uB.rxD{WYC`AprZ°_0[*o4ݑYߵ'b탤xݨBL! bhyO:4DpQ#uiIA+?:a2YVӆ ԇq]ɑ͆ Uj7cX_>4th"dY3'E3abΘXn!Yl`y ܚa2ҙmmJ s?_͞Khu{e03iFa5L P<|DŸlh zPC˻q6U8F&UEalUDG 4IC];*ݮ*PPg E5gBR Yr78R|BWDG}u6sX-a'Jw TRX)JKTY$Qo^&ɯh`8M0 u=Fuغ?U9Yk&\Bg.K MN+[:sxL)kΰka/qM((gX[^wƍaM36}>o\|)HgA\BC"rPiѨzjJVzhChTOJ;]EIȬpVZTj>͒&@sݩ<{WncNQJLWwC`9S0,J":E|&ڠmil_߰BE(&R`Rp^&$z^朕#]2F.w/*꧎nM0@z7L`wrZC6B&YOK;HtrLga q! }@Ղ"BzMyŴ _'RJd-h5Ha؝lͱ"1@5ȨbJ$TVX `Bp"F D *H("Vf]QYuާҭ$379ۃzE)gzK0`2fd:Z<2H{H,K|fn|q`g3O̜׍,T'gnJuxbhSFYJA~Få F>?W?}-{ G9"V%*nm?3GsJ6U艰/qr}=6Ϭ9 L7UjЛlxl6iz<ړv^; EK ˏ;)"!a~VYj+i7kC(k0 Hj RFI6}.E+x>==Yyմa9zCFIq#- Qvy(]~j56 nYjHtaﴀ%`)@ͫ?AfJsS圣?#lrm $ ~CpYşup>;ޢ4 =ϯPvAQp z9tJ|Lٔ_Eo ^"44̂mT6_$[$ rss[e J$YχZƮ@b #]ݰN̻9~inoW^L%7mj}ԷA!`fiո{Z6. 2` ,mQ請vm}X DGYb@J͚1scXܱkpZ7 J?p݂J[52UҾ=9 nѪ{:K`b\e%z1usW{+Fh#>q8;~%NJ+3Y&ݣX9돭d[w- &_mٟ[k#coՙ+W\,?n66U"+1I$-;+88P:cF.7@`@y$RA` WyȆNvu[~go{U[9aG-h}ndo;wݛGL-5㾆r }sl?F%CU! ]Du^lԼ?)jL.`aL$"lBv[`A1Ub0R(%"Б]G:8k?Msw:rA$6aApDc -;17#b@"w(bVI պ!Mbw `58`!ݰRbbHe 95`1 **"@NNCSIWM-vIK%,6աGNs@B y pE 2|8HMW^^ffmiI!h<`eǠ㗆Q.nˋIF_'m{'ì:S ٸ|H{rA{w ϢK%4jXaU 1J0dBdDE e,Xd$X""TV@d$ EU>mNNz0"#2/4(ç)A9yo.ܜӵ=ɋ7W\G F m&֖#j9z1O"saZ.džfDz\bRo.SgpRpL.ugZLSݢ0~ᠩף]Gv%{86ܝ@]E$vpF\_n6ߴȼ_Cyt Q }}َ{9]@WGt\_:@Ddbl7  Z>PB,l4pg$;cvX5y+5cS>}.R' _Wo-ze%T7:t> LW]?0AބD <9IQ9&YɇKgޫ 橱msxQvgNCH2I$UR(eIc@Ƙēi6!~:: xݼwǮ& u$+LgxbH!,0G~ƅ` :vΪ$g@r(LVkALAB KVHa"0Qc"0 VE_ҵ,Qb#QDP0cB`8 k~W`@M!!BD@!Ҋ/6#^M|[#A.P ČEIePN &PH1 ) TRhۭS(($)h׷t/^e*hFra?c_]2g#POPz|9`kNO !~6jCr.Gp /3 G|O(]k?sJQ8lȄ0C0m=21G*Z# ( ZXGeɭ:Es" x*0Y0gMk"H!mbUCЭPFI1Ə ÁE%dv21#;BcP3ELϓނc!PIQh(VJ _pPzɒos ,$6n^#P_B{_6 C!C)!@QHgӇ/?ށܼ+`cimR Ht!8}HL sHtP(QRDx,⤘3)$vtnKBKs( ľ~'Y'@I3.=2`WՓ8vFjRLFY"3;52B2LPլ @j.8p^JJRM ) g3;/nN2E*3if!5XG;U@CJ*$h Q rvϊc<6lthҽ\fνָ&2b\wlX^f-[6h.Ȁ|-{[o˦xen&b"aٟK\Tҳ~:0Nf!T3lM_aoz-&J$>1[sʣMf p p0wqWH]/'轷2v .ʚ;ʬ@Nnk]EDəu^#:/L5Aڱΰf 6@,#Pk`Ŵ* h-b]>SAf (K3q C+HeJ9UVL%6iF*Z\,Q3U*եIFbc(4 3 ek6^?@s,07@߿h]Gl/Wf]P*YHJîFd0G=z3 /cZ>)Kdq 7DzEwnF[&"7W]յ d0$3)ݲ_ WCw=0YIE&;v2aizNKe8[\ &vML#8a3VE1 QYɯ ,0[z,R)6T;Rw 0FGaquxZSfC8[XØAd[8wl 7 , 5U!Lޯ Kv FƎ6ǭ6rF2ZXY!GrY^η4srsM䥧G*&RV "(!ӛw$e%8 s;;Cph4YGL!D ;bpNsW[ i՘ 7Gpގ{N/I1 1]3e&pRDF"7 f,5{zKo5 g%G[Όη]g`K`++D9y͔eY9˜!f$/-l:J)4Q=js(S:e_4` !”dPήR&&.6X%Ye"J-/tEԉaJUS$kVhPvkcFNoyDBƥW+$yX"K *Z2T#eRrJ-KJ(ǽk!kS[=*Riv)i;kAOed sNN8QMo1yyȭfp)O4#¬%95s8ْ%rM1Z_OO L( ض[U/WDh8S@D=A/,d>VY; X,(s!nq! t(D9JF&Hz$Q:a3 r^EeZsD0x %rt=&N܁XBAXX7ƳjF 0EU0$I/g19ƜoJlFRDFvfUZ2h2rGَ=_خ+#Enxo &F+!-%V)X*3Q2 %sv|ɳY\.[;~{~mlS]oacGߗ 3'=`ܫv`}>UP&OD/'TMR hڬڛtV_:aӶA\< sZ'%Ҷ " 1֦J,up6,t8ɘJ.(TQ8%_)yc9g",d=X `Db Y4T^ H6w?3?=>i؁,` Zb Zvf {*Ȣx2b1!ʑ>9RCݜO׶&Mό7}Z(>ݔ6PXg|pbcI2ӜyuWZBN =͟eNc%j={go Ą@r}#iJRh8.M-c@N+b8i\׋ߣ:k,wvwAMu"đ&xj:dEX* 2xhϐm6 !<ڬR䘸whiv|v'low@=1rDN& >Nrq`-ո Xb)b#~ߓw%^/ &l<1R|HH$sx ˗7I "PtRb)S8|rycx 8/sЊ`vW˳+f`-!y"ߊ%8ιq0 8)f~jR̄H0kClH@"@@ϿTcVJ3m. dM uw7g_?~ *Q͹Fn7y6rʬĄrmaԐd#^.[9!7s9}%]۹ٿ$Yά)3h7c익g-'i6EJJ (Y  a-AmjN}hكWp{t]8 E8'0< 'MiSh Z_{wۇe[F,ԂyjS l[P>>!QOVdSwEk5wY#IPD4%b L*(o5rG6+ae7o [Ha?U88| #ID Z$1#R,p:Jܣp/W-{{A\ bo <$b|VCH$ QD[^L!5 m8<)@g[71_ǻZYk?n֭؛t7`y|꽚W-<>/p:?5y i4ԒaTEkAu* ``p_,V|eA5:Y%Ik n һbP@IjENğdWε5?ˇTqTJK=</ nō1NL%=\RKo\V1:Evy.¡ɜ۫CKU$)mԤ5ؖRSd,H]Z; vS|N&I(HP8gq27kHm MV%TlJ6i;?7dXU !HT? o?oWFƧ83Pu Y23~#o|;E"E$T F4F{Aqw2; 3; t!"HgU&SvN('fY$9 @`B D;7nP1,*ma$ CY0@|srG_Dӿ?pג^C\ uU~ob`UA,˚eRt>_~ʷ'dQPdQhH @d$eLJ!:[ EqmH:3Mns$ޡ͢5Zf$%Fj:-S[Tb[趀jaOD/֙^iYgBkZ0 aĦK6@!٣A#H[0pD.e0FT\E0 C 4R(cSh!Hm1ؿO{ +gûr@43X<\ay`kC3ڡJRM&_77o18QR[(ȭ FI2cz'AIL[(L:gwqn+$#+72}>ꅂ/&g1iCf6A U'KZ RfOPH iN$PI M"xw pf5I|XKwoeI5qLKss[)uˮg[kb`*<ꠁ;h{ 0}:p?b@Fٶ1T*,=L( ᶎ[|-j?‰*QIҊ $>{QrwXcLx=qznxL%fhf77ϑD:FBK + 55;0Th``o7X̐pj`Ke<,$pdb: $Iѫa, $%,Z0Y4) EY"v00$@!)`WBՔ6HZN@hLuqp1mI1&aj@`fmrL]fC dE\`7V4k&H%tXe)jJe% &K7L6ܚšf[fuLql3 `uf1vd)hF-R`\U, N ,A ZR").@ d& &LG h]a("1TQ Wˆ[kVZ#`,!\q2,Bh 2L$6nM؃ JR,#&"HF2B<(afʌ'6 c Jd u!"m:2H`dD@%-$`*|L XSW8#$2`֤cE( OD};rJ'eKc;|"{ҦF%eMuD3j$X)is~T߭g`sC%ww1;Ub` `0۵܃Qwr5/;țM{,CYp 5x7[إ39 w~/iTl wXo~N,Xp- \#_y.}K`vF׶ `OBAO ;?Z LccA>(Ma=e*K UG; 3)C/-XW PD )aQ (IX@HLX`-!I! & ; "ҩlb0X0A p4vJPFS D0f$H{V@.MA.+#Cb h_ Ta"gw) >_ (ٷzE.B}ĸ?$f>O}isE ]$Sl"CW(ZȨ tZ!;[HxgXOmTtdQF,b 6Fxtj3|v`2DUDD DD <li`E)nhTr !GEYy҇+DG%~Tb(, A( b zabڔR芤@H!;v,HbDD"m&Ē_+ֹ^OԉX1d>_o~%&6h:pP)i1,&0c4LX9VRTwXȃĠH2,^JDɞru,N uФ!7[M;2ig:!,哳Y1#09cn߷cB4`5'iI ;Aaa0gAH%dcE5V)D:Cvrn|.~-ЙS߆/:<'mcE D[`lIv}~?] 7L{ _Ϙ;Zω`aS)&`(j`To?oxb fMY/qAa9!H@$GFZ/pѹgmL2qmNn%/9#u&mXOdZQfn|XzKT= t}d6)U5ߗ&KߚLRXE2WF‚Y[q(1p2FP()H4 AHaK!D(Y5o,&&JF%3G[JdPJT{]OIp@~ώU0I$޹93yLLJtkvd`=cc@ lp `2 $URsNo_/\x,,$@@@Jrћ5ﻭgͷԟ] 쁕abJM25IoY jhҒcU}Oi}JS., ZF2o\ [x6*ʲg_]Dd䡳j>}\=$us?;b7G:iMC烚O go3Ji\=[8meuw(;&o~eڣ@?FӿG ;#Md}H/1f?yַH1CW\Nvx24gX2Hy7T&Vm[vq}539.vȐ`9rU-N<%<ã*YMcK׎Xb ^WRs\3+*_ ZośP>zwPwpocBMO#va`?=%`I:8#FG-E v'5#,z5pT369իʒi߆bic1wM=3{hp u# h<[:h=Ѝbs}A9%g;ťٯTraD@hq8ϢB9|`\8tPo;:m8 h'R[tތK]EwfK ϕn^PwݖCp 6A*<7a|Jm|}Zvg2s7ת; D fyuiJAE/xO(w_1mՊW?䷲=DuM9ajzsgA2\ZcFFȆЦ*n(:^QS"גDŽnxu 4A^]N|qF< 9wNS?yi -ŮU]~TU(8([uhӦ~ŗMh񰙸 iy>kdZ',<=ȅ- N힇l.A3h[eVᚵA|JM_: dLO=r=PQ-]f!"ZG{Q%9 ㆞1;+&+鿅嘮T/;nzb~oe+CJٟJ=XnIJ<}M?3.In8N>Y;>z;GٕvD2["1mgۗ*%, 5̏瞞;wB͖Y6pלp׹+:]<-;oFkC@-HlkF~e RM^ .oe2"ޗ^}=kш8'LU/!r̼hK`K/L?m$t L% (C"H1*ZJtȐZ?ah$UU׸2y{ovyl $Y81 ]ޥCfn{W5a@}7 8V0pb](xXo@I)U_Ѵb< %La$R @ud 60j2,@HX((%h",*AI$YR0U"(!tvŌ` E!DDDZVY-) eFA`"UTb]ez4uT=יW=.,ueNJ))$b@R=JoR$"0dckVǧͳ%:B9#_GG> "Cݼ?Dz?ju!q򲟵ʤu9kKnȽ?XA4U|fq6 ! *AgSO48~uZH* Hy`%-/ib tw6s|$c21"$QT#*F)#(3ć2UTd? !EnI,B-d^c~T|ce%%uY̜c'͈-s`ܿ#Vm0h])c8@DBMX}7@;0oK{.̲оQ\Gۜۇ|<#ۯ&ϞvO7SJ0%i1YѼq`CTs!dCHdB$NLD6|/ K-@@%  A>n@!$d F!@ {*r滓Jp@ga9̞>va U]8N.^;6˜!X l().6@II~FHdlA&a'k3dD,@gh;PWZޏ4@Z]1d*)pFK%NHxKշL%2T"DQ"UTX)q K1VJ`%J JX̤RQ(R`#-ZKE"2,PVUeB AiAB* TT$$<~~N*P jRO4/}7-M%j>PSf=y~SRb/(DiFyzk0!8 @`X$P DwD."Q=uvwx7Ѡ2$HD᱕l$$DbdR RE$IF @PE XUHTc.c"ryӴuBƢb#EhzVp׶4%LI1c[8{0Қصe^#Sҍߧkb(x9 {i(௏mǧ* W^fΑĺaV1ҵ۪f4>*ϸ\tQ<߼;`;8*.=]F16o*IhSͪQ3;]M:Fp_[\cgKߧm II7SIDj<2ƒ>Bg'عTIjJE$,@11kfnoKĴ ;)h(49gG~kL5ŵEsQ6ꮦ1oTG!.jS dB%ȶbu Őq~p`]Jm)iU1<24{) tWMMIvWY;?]f? 'CQ3LosÌfAYܪ2#kv e+mUtnStU+i aAӳ3֬Z>Vc9ߗ/um!#kU>ajl@gvLx/[n[l&͜!ZUP5DhDH$C 3:ps]zNGeuVdUf3 #} [4×G"ݰwӵXaT^n3}ِe!m(&꒏kL'T >/nOվw<j@Wbׂ?eF+q"h HS#>Z҂g;Y1߾XnUph c1~EdNJ~ZyƯ8;guv;ܗ0E}KYL/qVi`_Am-'SK[u5-fD >tV=I;ymFKGnPw{lXhS:  ԝ_v{3)Yӑʔdj/>~d(b L5nnV032 xELjy=OXyAޫs2F8cKCi4ev1hj1e; ,[B7nPM܃6ư믁xgA Yb _=uWLoz {$wNO4VL23X gw#}wo-I#|-g蠭|늏;r2ya41y!֓tTW _n-#g3? n#u<Ս1HMZ mH}yW]3?ʩ1/C</:V]y *c-J)) '9sLFeu*X9_~ծEKj%XXJIS_`Ȫȴ"Ўt6QJPʕ5TA(IE=\\ی;vIp!hkcfX0?NA BQ)1I{*lsoҿN/)1`c20ؠ(( d]lj,)XGcr9iYHƈPgKcI+3.F<ЃFk>7\DR^u*7?y޼_HOx=ƝD|5h,m>Zr½Źiڿ ԲC+rD\f62o[=Q"*ʨq}]#M oL1qxLITNN( `zٿk-iO>.lM@%Ⱥ`b뇎`DQ12ʖ|5GzKQ3I_\_̔fkc~:Ҕҍ2oFa<j] %^vX=%rǯ͘cf{Z)Td3I˷o Ex܉ &jB͍ fuwүx}Ժ(D+G~[|]ܼNvt 1YH!Kmמ CvbUA$ ;p%;5bҍ):v]4R娑KK.P@fce}=X]Oj,RgާŀDpD°eTB-MF~{s[|6 V0D zmlFNm9+T%ۍnnהY[|9"Ojjflt`|A'=ў!,!M]E)'kz42lϩbI@^0+<à"."/D@SB\9g Dx?NBKA :ses3IJc8&%"&s/8i~L̢\jkߧ4r6忝,@&,UBtzۿRxkAq !|JX㏼,{=ß9bd542 f} JdDk;+X ;gt\u^a8+ڭ~+ +FWƖl곍YI DJ.ɋ{=覯YԄֲTDQTٸ1KR 67siCKAFou( X"G.y/i!+QY)-t` H ȲEXG77=bT uҪ\ ݮN@,jD]Mc7{{8o>(6 B~ԓ|]SΔ? ;7:c ?65B ]Yt"vuEkԔwELC~kvm w.CАVV\qm(LIyzz}IJɭɢ .^=ϱgm t`8\h~5zҤ_wՄ>ne|mTKퟞ8% 4:@ z`، AA4"0Dl@4:~D}ܗ^_mJT&6Vj!@A-lDNӠBH #Dj1c0\;8ߦ& "*L~5Q4m(ܲǃ*UÇLJ• EL,I%RyY :f&_k׷"/琐Bdeۏh ύ֦xM8ҕC[KYU g84ӄH Q PH<) F^j*ɓu2omt3w%$ƏaiDJ^[};ھ]Wx;S}7BJ"=LxX hWM}E?Y$2+7gHM 85LA A?vyћVZٸnyA?/Ҿ? ўH=6޲DWr]g븶d *c&x ȡfߋn!0=X} <1+] +H,L"\\ʜk,RYY6(?V$vy㏓V9ǒBsaߖLF J)9:bٻRNa'j ac눘''m_1.(&~ܟ$뻠o׼'*pD O@!850X mpbaa pI),ٹRgL=RIq_y3H _DȮ~/Fy O0E~56sy>.^&% kn{R\ss7g+w5dnst'G2n9U]PnIw:XBPVd70&_^}cLc\A >M饉l/ʐ멶.*t"ɯnfxߘN"I=h/(˖IldXҒmׁ7 '!N(C~m2Cb!QXS N4*(^SM 1 ;1)Y5-kBEJ$\eKߔ+ !MCJPBzS!?y>Nj Z-h3Cwd`&D%.O0R82f Ke,%B'8ҔPPaCN*Bת$kbGU^k- ~)?a:-wp6mgB @l&ɑQ$t <to=?h8ыKYxG Uj'!Ұ+7ʣ)4i*I/e:?TP_vl] ہ5:EMe)C| >[@_-R;c'#_~Fb1GK~]EN$u7qC霩Z{a\0AJDzM|tr| 9{Q?ዯ~Nv_/.Gf-C6WZ:!yQ=lݪɑGFZq 64\Gh>ғG۱K0.q5'ibpIC1R,M`r ^3C@B֮,G0'CNvCvQ֮ҋ8 C"A9̬BbBhÙ&gx^5{_zGcVŀ)ky6H>d?vM|-/;C|P#2(HVP/ fpQpis)dϠ dGs}> %/fP'8 Qa&䲄hH'&W<vNd)I_t]Ed"2sJQ>4̿Fw5dR )R,|`~^{`DA#3@l:尠 XM@ FPj*r4ń$PV@l~5]We֦7>6ED5^;R B@t%y}~LB+$`\r=;Yv?DL, 0(Z.f˃E7oH߼:c o_  N:*04Lf šN$õВBm`$vD'x06-)@i ͌XqM:d|ߧdj#&-b=8&|~탻NrR/N: OL:U+ gPHsxo(Mc/8mC<8SyO7ōښhv鿷YP %D $}ޜh[~sv{aQ!mEh:>Xsyha7k8JNLWEk {-Tzp/HZ7Ծ0^Ë6bwKKȞ´s@S?c΀79JU` /V`{Gxo>/BgMtttV(- !0li0c9IL6cuN已o6OzURriV1F %T"Wy"EKSUǤs&rzY-xG[ A`'J!<}׳B4"N֔Hm$B $(+ @H*(B dd$XAQLLI8{tדW]tucGY(a_DRѫʲp({#ӖU>KU!@cLlBcb_lB sW}U/&jΝ,fx!Ņ!Ԩ\s.YZiA 9\x`3jٟm.E HDC`1 K@C턏e8ft|a/v vT~Ť%&E$I AANsL]apDs2ϧ&3d(}zKZ0<>zhX=8l ܷ?G$C"\5M6`o !N<9*cv05%-^]}fKjc*ب!'jDC!)@>H=QWVm(*Ǵ:6|9q\Gnϫ1ʞ?uWtK_jHu֤==wRضґ*N51b0@^YjJdX$ rhk`^WAI6_O|7vgÕg:(En=ph/p46y1xloǃMu94'eࢭݳkL\4_@D+ d0 \e*RTgo_>2Z@O@j$ I͒f?)`2N4ENPn+ּ)^ш2g+~\lqӐՑz\P,6M_yw}]ͻ0o4gJǗQ)ß\Zv!$r2HGv Uv}糣/'_\~:KRL 㻖?򬿛nKZn;5HIEih#/Cc`$8\0İ|{3<_ĝܒˁ%> ҽED$ JZO& "rfi|PNӲ-CA 9!FMۊ%u.gH8&F}ja;TC=m؈ $EiӲbztԫFفSVr֯묋Eqz6 /4W% $X+a(C"zRAGLTX 4ɣڲ1,Ȣ8aa[5ˬ86h`XғIxCqp]/F|5+0c]yN&w]CpA.#r«20Fb O k&瘦DEP"0CSY*1o0TUA* l+)df Cz& )$1( H 1‹q8kmK00n M)vn HP"!YȬP: ^s9S}idcC&)Ǟ^Z 23Qv/VT5:@\ƈJ4gWz4]|JO;w߉ԢY ѻ10&atGz-_\!?eN5`ߵ)AO| Gm(^#]rjImtDBMC- E`Z7L`[p͵[#:02#h$9S&e']rTFoVu u1"vホ'VwMP MMH+#yJ[ہ# T:f\h3)F6߮䩧)n!ߧ-IΪ-sgd8UWѭ 9FgCNMGsȧT*l8Gp1PC~ 粆F4kL, U>DT#G^fs5a+Q46kG+ v[$DHkKT~72]k2sd.@OqvP̣oP-L7zo_)M%F&4f8:Lfv(ӟK,m_4nb/RVu6wyH8 thBaE RL+{*.aOIFiؗ`3ײ\-r&N A8MN<7x FȤa0+ lBoN| yLo131JxooV s(&#ja,dLldfYLm}PoYUCcԹ)yvpһvmx{|AithF*er2[FmjTv`\xɿ[}?Zt/I,)UY0chحs0P?H @S|̮)K;gYƥ:v՜m(WRL3<\+DϑZڱL/+|EEߏ"Y]rPq@$gx[J%?-4^Xvv;#KLiy8OpqӿKDP j˨:%>W}o|X̍*Ѣ-^q' ̀_£w8jՎlt<&ZbUH^j|h$5z0G%!l9a>*Z%TV裋ijVABVX0iӠJH^<SK(T`Q.?Z8A(IylT@*<64S&n}DK ~(Oֽ hԆRpMa'd4߀m 3rnS 8 ҖmxǏw$cF8&|'=}jtڞ?@=q]wZf}0Qgwr 소Di7Zx^ZZ6^\;r]T,CKf#&0o8ӸoEjӲH/F}PK3$@78e*A <SN|i|کh 2AZ!c0yޙ" $,dA0w>?U;ߩ"{:@P6!b4)jnրG-{<:sͻ::>QSh훣Ty#hcchi`CPX") # )6 I1,U2Ժ:K ӱ]^:Q YT?tkٔ{^ĽQfXc_t#8-[eVh6\}mWHl3wll l˹;=igcC/79M#;U);ۓ H9aekHzK2iv&sšl-鮭*aWi/s|Ҥ¾ g%ULLiK>8[Ն5 ;.1O~[V^P>'1ͧop_,5_g^{__lm466FoM X6 ]n=xy<0eIh;@)ݖnH,ȯԫa2UE%m Ay妏 rhSӝz԰k} ;.>ob2~I=D!cq1ySZzܻFmϦpa-UopTۢ?#"MzQ&7NԷ(ެes.`֎|L.YjZ~d]<>T$Wdr둻ˌfޕ&r` :F9}&076Ei =o߫3MTWDӃSvJcR;i^}|Uv޷WE>21%)OkvTٯP{[CvafAo)B'wx,>7Ui[[ࠆȃI F\/ *lvavp/v#L-oKQ Uzc|e Jص=:I<Hh\F48VqP yv <5z`Ozo̯1n(OI=9el8M~HKb6Q{VDBy76C3]y@3c)fMS|0tu:*[pp3#oqqռ·AsGmw}?N ꣑yo a?@D& wRp%s?1"LHBb>.C&jϛǬ_4ZyVQ^9;m7Ѕ[g&Xt0oq]MpD'f.6Z҆^fՊM@.rRr_jP(A r`%mx73 @̢QvsE|[CntǿEOAϏ-_ Y+įxzY^/f6 Z+ H,؋7O~D ֠.|,`r*z'd_)7&)"z揎Qabo <=yh1ʏ2oy HtP%8҈E8xsT{ /]-1 ə5\뻚KrzV6N;ԵGս9O,ʊL/x{E `F\]%OV%ӣ,-lt裏gz_ 5ox+cdoo297#܏uc-Ķf:x@&sjtv~ e-[h9j+?o.]Gڃߥ >Xds3T3^lPa=9\K|p,û4[d{vn ' ;vHab;G @~Aqeqx0@N[8,&o3ZK\.[)EhOU=4s}xm#_#v:Iz`^@pbK4u}=M4SM=Iލxg *bm 45,HmP`TyS7oZ,uv%݄|cvK?SDG~4BBmmY@PUcJ/$dUH (d`* aődQȲ(ȑA# `)F b"@ $,"ȰT !AQ VDTQF1I) 2E#$Db0QA$Q$bTU1X 1AF"10ȌHb"d@bQ@H *  ,(1dE +"*"F0H! EDRE"  $D*"ł",c"PDbDdQX(*0"0EcX)EH,QAQcF R+E`+`b(1X",0Q",Edd #"* AB @Ta2!@(F(+PX1b @HA`(1@QH+ AF"(Ed(E F" {*EC鮬,SJ/O~[P-wDS~QnMf!Jg#&UT!RF0ԁ>d{vc1AT( Q6&5`()>3.Bϱ?GZi/a&BFή-hB@1Тlr/NJ.%3=NPaJ BRfO҄v^瑷Uﭾq,9/^[v'OxɻG2(!)@( cjX (bU8m=?LV@ ? BVk)GGcZ 3kOV𽛛ea0JvZ0<{ݝ(MnPv8V( /X@!/ K]1#Olr/V iok˾'}r/G`y ѐ]Ha~EI@ AIM?6T$H-x"I3 J@D2B)hHE$q-eIӘgUl[( Z JD2ϗ'2S4z?顶c(9fо}L 4`azSA{qʽgc `+Ȣ`ZV K]]\GzK`tV% E0;ǞR 1I[4xS$^Ԥ#ROe*\ŀLy$sC{n:tD;p q'Wظ n؇VB(E%H2JE,rA&R3jR$4vf3N0. a */˙! e>ajKjCYn"aPTCr>dj"i|НQ=sY'vR\ۛ!{{z__q5^r%<蹩bH@.婀}9b{xQ^D&LI9&#RbRsHiBBȕ1Q<5*Iհ8ì4)uR'x~?h}t++AGYҌ"P= M LR,`px<Y0 ?/]' ’wfMNP1wh~q9Ika&{J77}ws?_ &i=e_›k'VrZ|$?|<6EYg_][u C( ;-C~vSЮѾߌ,G?>ǑLk23x\|d+ܮʚhv?.Q?2|eoWZp8>]ǛMu!~;Z/{uy.Jv+"c;}- %&Xz>OѳKn\Ni[la]6&OKV};YkrH_TwƟ'_>gmsѨw(-o/d+U;q% k<[<]7FnO}aN -56odw6 w$9 s0b+{&m5Рi{Ucz]]'=GZ0yr RD5=HH[0Sb(um5?NOs+q jz"*\ʲFdr=<9B:u#-8oK,TRv~vkxZ*) '>y|B~S-G6^^uKrŌ|*໹;Roo,ʫidPwPmuq'[y7UE{/3ӻ%3N 1xyEgהR=-M&[fg's˲_fhAB6\ɁZ^f\.xO_ ۟MTVg] |}-=Y"IJhh9Xgq3?c 9>e{o/1":MpP:1o}-M~+UB<{_g]FyZۉ_Z[mG^FC=Ce,-??4I[l/Lf4xH _y_<Vv9XQг.zkcl!?%7.'»hvGcUrf_UG~/_ةMhT\Ue4GaS~2jRr)p|&fj0}xBi+ '`yƋrțB`I,"4Nj)hl,In_4?s]P=t|,mD  whvֽh?h=ao7$E1|WKS@ڀǙFpy{AHR4x~O/-wh<سPZʧ?g(x9~h+,˝h_r}߶|8o&*Y}GT^C+_ϧHKSb-5)4>8m6K-h= nӉ}FlކBC۝_@})`Gi$7>d£~[fvErβ;$ҞgE<4hQ7ޖo1zˮ.RN}uO%W8gcx=ֹi77 #q%]Mly{X?~zq4)v棷~s5_=%'Ǻx{ۋS#{Ԁ]'1Y;7ٯY[yK_|bn~?7޿:I^oodQ!M0֨sSOaW5[]k\;W ?oCw4x1dJ&jK B%W|cD-d=/2iCOmzRus9D5MG޶LFZbz?-M/_+VzogrC92YsJup/!}蚼I[^$l||} eCKh<O$ԫC@)_M._yr'KiEԪE3#N=ZmVP}f)|?C3;Eqy*]++8BI!_Ɵt:I>.x8Msy5O Nkܥl_qAk_>vєN-ױr!k5ݞ7Ywl; RgDkR?RJZs΋_xXEX?R`@~!Mϩ3"FΆq*aH@0a Q@Q!oT1g!?EǏʊܠdԎC`yK9QzH)%!g&Ҩ8LA\ć=UcJRJ=Ue~ }Oױw~5FQ6; w=ak_5^xj9e9 ߾:}-|Z=N -45Q _Lw jCIfNa1T]_ɤ\5 DiڮӯD2g[%5{x2R~P`%^p})~nS~. ,[%Wο04[5%כ3~DՀ']%.בp}KdvAs~C./i/qH?%ge'}S+y>{;u;#cUy|_dJ,i/G> مtº-3?u'G`wB[ZY|/o'iۜjj[|TSOZU\ ~HUiК(Z I+]nQm=f Ϳs2c\LKȺ]*ʸHLԳ8{r+U)B{d}LB*5&g'i ):2_4\Y/_\BРʿr 煬>-O41!`G+=P@WefoSadb-[bm*J߯[gz)T>QdA{:bI,-bCg y_8:]*tVbvU꯽>q:5i ,~=q6nkF+ɲdn*|~yW>#}Q?~cWĹ OSU{s%'!WaΫ vjg3>%]iZ9JcT^Z4^V?w#(\x_[񾭞e7Li6=D8+k*;˓֥]ثS~$?,~ÐE_+=N5M+n)纓m59IzQh Wпuk{]e!t~ t+|5u.*]W3Z%+Ί֊nCԱxnͤєճ'_#{[Bf;w rhcW=@Ȧ{??df"hs?x 7+c |o@-ාS{yU4dZ^|7Ҹn_Is*>krXwL|0\Ƨ!@z^73/*xq.r__ӑ,2!z5c}SOfVnz>㨎H;cphy}d/Qh|6sǮ6λQ ]2+ey1}[wۉ?aViD;g.s03b3G`'{sS>BQ?28^Lhl'L_4~k$v>VϨ_HL<7~79,{^'!Bٹ6oF|u 2#}njvyM[:y%>巻f$~ǼW!vڮ׫:i trWU~D^ö7zkF:=iǒ^oMKJkϾzU͋NV9Zzޗr;߇u#t?mǝWKsU|7̞5R#Ur>-R?=RH>f/mRzw|S]Ve\QJy{[wb]'rk7['䆹3$dcb>}^oz)_?Jg}.>We:̺-M{u\iKOOd5qי(Ew Β{2et?cw~!HiSl"6\oN߇HeC[L9i|#=5^yS`z_ f|{E^YRSPXUǯ^㥚czz=^K d8כvYfc,(w:mÅ\;7jy{|Zeg;vdvNgh/Lʬ;#'d)_GIQxղ_fTߗ0ǔjXD|rl j5}/ۂZi2N?)Z3 ~r Hq|7e#GyWBgd}4;ku;]Hxtչ/EJ'h/GFYg+}˞\ btb&d[cP拲h#4OCXDЍ/ÌG+[EsŸ 6Wwj7%Zt}fSUlEY!PoHfR ~ܻly5Ng=?ғ8ڮWAf{.pxC}~ 9T+fvgÎ[a;Z)K|99|7'a}/],h)yF~r .PJݧ n4I懣K4Q|Y<M|:;W0Кy~V?,}W7^o/%ɤtCWقmA_sTލ?&SݟJŲߑMt}ƷcO3*/s]i2K{):{׭-=76Gn |Ўgs}d5!Ǣk40WNǟSRhlcQcg2uG-[Kb|;YBZNsO:jWw:ok _Eg{;^w<-k>*<leYTBJ _n\V+]Se)5X?N8S }<.zY)sy,~qfxqOw]oUڸ/3EIup 7>՟ϥ=ǘ8ԲSt9^£WЏVCogQځs8d#C>ɳ49]ՁB2M(rߣSj;cP}cB@] o 9`nFSUtT̿VƼe w; SHM^gT!>O˓C^']` .?gM'`qd,;G>㱢 d,oV\_0bjEN㻫#!8h~T_$? W3慨BA)%(UE D<#h~E8J%+oߋR0:)#˩o1g}aJGӸCH');G}] I ׇ_Rp33Jwxw\ r QtR0V32!vs`D4]- "lߣޱMAf7U~m=>n/OEZ377Y/5cwLԉٚCs٤D 4#V^Gw*>kSKW诬h^nraSU[Y+]SF V:*Jm`H'X6SѠ۩9n!CHPI:MPjw}54͗~BʬRGVK`h3K3ï:oPhO▯Ie;`wo\׊Vs2h((VyՙN)UJ>ψ.˛}[ͭBVjھqF2Ii1-72  A8.q}$ru Q17FoSJRŽQ!:>l3;'Ddf̕Y±9O.:L{y͹\ϴs5xQ]pW>k]xpŷ뎳 NC=Jju N75>0&Uzb<s֣I[ij7O?;8zaqbXj7ʗӭ2*N~,MjReFT u<hMΣ_. !tx{glƤV`ؤhsfHolkR%uxp#}ݜ~u~ד}?"Zskӽg^8Tq~l,h)R ᥧM*e49:5 "&#DdXKEԪ~e]|7:ӜN9C>E,s\Cf:_TÚND_~Q(K15Bz  *R^N1-8D6-

p~_sFuH6O:;=|[[%պu{z>:|MP8&%/՛LeR$`ZD“SRצ 5Kxh3J~fC)z waUќ1}OC;5:"|oGwVQ4 B[(|~2U#=%iz}Y`4 [CB$.mNDv7,~sY'*s~DhBwɓ(˝Kj%ֽuԱΑF?Rm6׏%][jvKK_w>b:}ح.nqcz (ԨfE}VB53]b#*  6OAMW#ٜ$K:a,FpuZѮx~тΞWUūǰW1PtrW~ /ir`FSÞjџ[y&eTs}}dեퟓV-4D~>Bi>*cb#^oTʞL)KJg `i&&?c>(CC!9԰4 ȗe&9)-Vv]5gQ]ebMeXXX7KW)*tuˆy& VpVͭJ*Yivˎ*(40UEdRijRn $OH= 3o}O636~6D,lUՠIKeRӞ>xol~u7(uNt6K2۾p'*U+b#L<CcXsKLNu.hmީ=s~|35rϿcw ,ʸ~жU9fi\~RRz(:Zq~մKH)ꧩ0+ZbƚQ5FѪ M'U>uI`s0Kz@nޖoyC#?>CBx3u&KDeLIS)K fAi :zǭUjjWV%D󲧣P~J~t)O׃sܠ[